fork(1) download
  1. // -------------------------------------------------------------------------------------
  2. // A sample program in C performing some calculations between two Gregorian dates
  3. // difference in days and in (d,m,y) form, weekday & leap years indicators
  4. // jdn conversions, +/- arithmetic
  5. // -------------------------------------------------------------------------------------
  6. // migf1, Athens 2011 * use at your own risk * free for whatever use, plz give me credit
  7. // -------------------------------------------------------------------------------------
  8. // external alogorithms:
  9. // http://w...content-available-to-author-only...t.com/KB/datetime/DateDurationCalculation1.aspx
  10. // http://w...content-available-to-author-only...c.ch/cal_stud/jdn.htm#comp
  11.  
  12. #include <stdio.h>
  13. #include <time.h>
  14. #include <string.h>
  15. #include <errno.h>
  16. #include <ctype.h>
  17. #include <stdlib.h>
  18.  
  19. #define MAX_INBUF 255+1 // for our input buffer
  20. #define INVALID -1 // just a flag for invalid d,m,y
  21.  
  22. #define validday(d) ( (d) > 0 && (d) < 32 ) // 1st validity check for days
  23. #define validmonth(m) ( (m) > 0 && (m) < 13 ) // 1st validity check for months
  24. #define validyear(y) ( (y) > 1751 && (y) < 10000 ) // 1st validity check for years
  25.  
  26. #define isleap(y) ( !((y) % 4) && ( (y) % 100 || !((y) % 400) ) )
  27.  
  28. #define myabs(x) ( (x) < 0 ? -(x) : (x) ) // absolute value
  29.  
  30. typedef enum bool { FALSE=0, TRUE } bool; // our custom boolean type
  31. enum { JAN=1, FEB }; // just two month constants
  32. enum { ID1=1, ID2 }; // to identify 1st and 2nd dates
  33.  
  34. typedef struct Date { // our date structure
  35. long d; // 1-31
  36. long m; // 1-12
  37. long y; // 1752-9999
  38. } Date;
  39.  
  40. // ------------------------------------------------------------------------------------
  41. // Read s from stdin until either len chars have been typed or ENTER has been hit,
  42. // and null-terminate s (if ENTER was there, it is replaced).
  43. // Return the null-terminated s
  44. //
  45. char *s_get(char *s, size_t len)
  46. {
  47. char *cp;
  48. for (cp=s; (*cp=getc(stdin)) != '\n' && (cp-s) < len-1; cp++ )
  49. ; // for-loop with empty body
  50. *cp = '\0'; // null-terminate last character
  51.  
  52. return s;
  53. }
  54.  
  55. // ------------------------------------------------------------------------------------
  56. // Trim leading & trailing blanks from string s, pad it with '\0's and return it
  57. // (or NULL on error)
  58. //
  59. char *s_trim(char *s)
  60. {
  61. if ( !s || !*s ) // error, early exit
  62. return NULL;
  63.  
  64. char *cp1; // for parsing the whole s
  65. char *cp2; // for shifting & padding
  66.  
  67. // trim leading & shift left remaining
  68. for (cp1=s; isblank((int)*cp1); cp1++ ) // skip leading blanks, via cp1
  69. ;
  70. for (cp2=s; *cp1; cp1++, cp2++) // shift-left remaining chars, via cp2
  71. *cp2 = *cp1;
  72. *cp2-- = '\0'; // mark end of left trimmed s
  73.  
  74. // replace trailing blanks with '\0's
  75. while ( cp2 > s && isblank((int)*cp2) )
  76. *cp2-- = '\0'; // pad with '\0'
  77.  
  78. return s;
  79. }
  80.  
  81. // ------------------------------------------------------------------------------------
  82. // Convert s to lowercase and return it (or NULL on error)
  83. //
  84. char *s_tolower( char *s )
  85. {
  86. if ( !s ) // error, early exit
  87. return NULL;
  88.  
  89. char *ret;
  90. for ( ret=s; (*s=tolower(*s)); s++ )
  91. ;
  92. return ret;
  93. }
  94.  
  95. // ------------------------------------------------------------------------------------
  96. // break a string up to maxtokens tokens and store them in *tokens[]
  97. // (uses " " as the delimeter string)
  98. // returns the number of tokens, or 0 on failure
  99. // ------------------------------------------------------------------------------------
  100.  
  101. int s_tokenize(char *s, char *tokens[], int maxtokens, char *delimiters)
  102. {
  103. if ( !s || !*s || !tokens || !maxtokens || !delimiters || !*delimiters )
  104. return 0;
  105.  
  106. register int i=0;
  107.  
  108. tokens[0] = strtok(s, delimiters);
  109. if (tokens[0] == NULL)
  110. return 0;
  111. for (i=1; i < maxtokens && (tokens[i]=strtok(NULL, delimiters)) != NULL; i++);
  112.  
  113. return i;
  114. }
  115.  
  116. // ------------------------------------------------------------------------------------
  117. // Convert a Gregorian date to a Julian Day count
  118. // IMPORTANT: accurate ONLY for dates after Oct 15, 1582 (Gregorian Calendar)
  119. // Algorithm by Henry F. Fliegel & Thomas C. Van Flandern:
  120. // http://w...content-available-to-author-only...c.ch/cal_stud/jdn.htm#comp
  121. //
  122. long date_2jdn( Date date )
  123. {
  124. return
  125. ( 1461 * ( date.y + 4800 + ( date.m - 14 ) / 12 ) ) / 4 +
  126. ( 367 * ( date.m - 2 - 12 * ( ( date.m - 14 ) / 12 ) ) ) / 12 -
  127. ( 3 * ( ( date.y + 4900 + ( date.m - 14 ) / 12 ) / 100 ) ) / 4 +
  128. date.d - 32075;
  129.  
  130. }
  131.  
  132. // ----------------------------------------------------------------------------------
  133. // Convert a Julian Day count to a Gregorian date (d,m,y)
  134. // IMPORTANT: accurate ONLY for dates after Oct 15, 1582 (Gregorian Calendar)
  135. // Algorithm by Henry F. Fliegel & Thomas C. Van Flandern:
  136. // http://w...content-available-to-author-only...c.ch/cal_stud/jdn.htm#comp
  137. //
  138. Date *jdn_2date( Date *date, long jd )
  139. {
  140. long l = jd + 68569;
  141. long n = ( 4 * l ) / 146097;
  142. l = l - ( 146097 * n + 3 ) / 4;
  143. long i = ( 4000 * ( l + 1 ) ) / 1461001;
  144. l = l - ( 1461 * i ) / 4 + 31;
  145. long j = ( 80 * l ) / 2447;
  146. date->d = l - ( 2447 * j ) / 80;
  147. l = j / 11;
  148. date->m = j + 2 - ( 12 * l );
  149. date->y = 100 * ( n - 49 ) + i + l;
  150.  
  151. return date;
  152. }
  153.  
  154. // ------------------------------------------------------------------------------------
  155. // Convert Gregorian date to weekday (valid from Sep 14, 1752 to Dec 31, 9999)
  156. // Return 0 to 6 (Mon to Sun)
  157. //
  158. int date_2weekday( Date date )
  159. {
  160. return date_2jdn(date) % 7; // return jdn % 7
  161. }
  162.  
  163. // ------------------------------------------------------------------------------------
  164. // Calc the difference between date1 and date2 and RETURN it expressed as # of days.
  165. // IMPORTANT:,
  166. // the difference is also calc'ed as days, months, years and passed into datediff
  167. // Algorithm by Mohammed Ali Babu
  168. // http://w...content-available-to-author-only...t.com/KB/datetime/DateDurationCalculation1.aspx
  169. //
  170. long date_diff( Date *datediff, Date date1, Date date2, int mdays[] )
  171. {
  172. if ( !datediff )
  173. return -1;
  174.  
  175. long int jdn1 = date_2jdn( date1 ); // calc jd of date1
  176. long int jdn2 = date_2jdn( date2 ); // calc jd of date2
  177. Date *dp2 = (jdn2 > jdn1) ? &date2 : &date1; // dp2 points to latest date
  178. Date *dp1 = (dp2 == &date1) ? &date2 : &date1; // dp1 points to earliest date
  179.  
  180. /*
  181. * the following alogorithm is published by Mohammed Ali Babu at:
  182. * http://w...content-available-to-author-only...t.com/KB/datetime/DateDurationCalculation1.aspx
  183. */
  184.  
  185. // first calc the difference of the day part
  186. int increment = 0;
  187. if ( dp1->d > dp2->d )
  188. increment = mdays[ dp1->m - 1 ];
  189.  
  190. if (increment == -1)
  191. {
  192. if ( isleap( dp1->y ) )
  193. increment = 29;
  194. else
  195. increment = 28;
  196. }
  197.  
  198. if (increment != 0)
  199. {
  200. datediff->d = (dp2->d + increment) - dp1->d;
  201. increment = 1;
  202. }
  203. else
  204. datediff->d = dp2->d - dp1->d;
  205.  
  206. // then calc the difference of the month part
  207. if ( (dp1->m + increment) > dp2->m )
  208. {
  209. datediff->m = (dp2->m + 12) - (dp1->m + increment);
  210. increment = 1;
  211. }
  212. else {
  213. datediff->m = dp2->m - (dp1->m + increment);
  214. increment = 0;
  215. }
  216.  
  217. // and last calculate the difference of the year part
  218. datediff->y = dp2->y - (dp1->y + increment);
  219.  
  220.  
  221. return myabs( jdn2-jdn1 );
  222. }
  223.  
  224. // ------------------------------------------------------------------------------------
  225. // Add ndays to basedate and Return the result into date (ndays can be negative)
  226. // (calstart and calend are used for boundary checking)
  227. //
  228. Date *date_plusdays( Date *date, Date basedate, long ndays, Date calstart, Date calend )
  229. {
  230. long jstart = date_2jdn( calstart ); // julian day of our calendar start date
  231. long jend = date_2jdn( calend ); // julian day of our calendar end date
  232. long jd = date_2jdn( basedate ); // julian day of basedate
  233.  
  234. if ( jd+ndays > jend ) // fix overflow (calend)
  235. return jdn_2date( date, jend );
  236.  
  237. if ( jd+ndays < jstart ) // fix underflow (calstart)
  238. return jdn_2date( date, jstart );
  239.  
  240. return jdn_2date( date, jd+ndays );
  241. }
  242.  
  243. // ------------------------------------------------------------------------------------
  244. // Convert string inbuf into a valid signed long int, store it in date->d
  245. // Return FALSE on failure
  246. //
  247. bool date_getoperation( Date *date, char *inbuf )
  248. {
  249. if ( !inbuf || !*inbuf || (*inbuf != '+' && *inbuf != '-') )
  250. return FALSE;
  251.  
  252. char *tail; // in strtol() for err checking
  253.  
  254. errno = 0;
  255. date->m = date->y = INVALID;
  256. date->d = strtol(inbuf, &tail, 10); // convert inbuf to long
  257. if ( *tail != '\0' || errno == ERANGE )
  258. {
  259. puts("\t*** error: invalid operation");
  260. date->d = INVALID;
  261. return FALSE;
  262. }
  263.  
  264. return TRUE;
  265. }
  266.  
  267. // ------------------------------------------------------------------------------------
  268. // If string inbuf contains any of our recognized mnemonics, date is set accordingly
  269. // and the function returns TRUE (otherwise it returns FALSE).
  270. //
  271. bool date_getmnemonic( Date *date, char *inbuf, const Date calstart, const Date calend )
  272. {
  273. if ( !inbuf || !*inbuf ) // inbuf is either non-existant or empty
  274. return FALSE; // early exit
  275.  
  276. time_t today = time( NULL ); // get system time
  277. struct tm *tmtoday = localtime( &today );// convert it to a tm structure
  278. long jstart = date_2jdn( calstart ); // julian day of our calendar start date
  279. long jend = date_2jdn( calend ); // julian day of our calendar end date
  280. long jtoday;
  281. Date caltoday;
  282.  
  283. caltoday.d = (long) tmtoday->tm_mday;
  284. caltoday.m = (long) tmtoday->tm_mon + 1;
  285. caltoday.y = (long) tmtoday->tm_year + 1900;
  286. jtoday = date_2jdn( caltoday );
  287.  
  288. if ( !strcmp( inbuf, "start") ) {
  289. jdn_2date( date, jstart );
  290. return TRUE;
  291. }
  292. if ( !strcmp( inbuf, "yesterday") ) {
  293. // jdn_2date( date, jtoday-1 ); // faster but no boundary checking
  294. date_plusdays( date, caltoday, -1, calstart, calend);
  295. return TRUE;
  296. }
  297. if ( !strcmp( inbuf, "today") ) {
  298. jdn_2date( date, jtoday );
  299. return TRUE;
  300. }
  301. if ( !strcmp( inbuf, "tomorrow") ) {
  302. // jdn_2date( date, jtoday+1 ); // faster but no boundary checking
  303. date_plusdays( date, caltoday, +1, calstart, calend);
  304. return TRUE;
  305. }
  306. if ( !strcmp( inbuf, "end") ) {
  307. jdn_2date( date, jend );
  308. return TRUE;
  309. }
  310.  
  311. return FALSE;
  312. }
  313.  
  314. // ------------------------------------------------------------------------------------
  315. // Read a Gregorian date as a string form stdin, validate it & return it inside *date
  316. // IMPORTANT:
  317. //
  318. // The function returns TRUE if the input string is a valid operation (i.e "-200" )
  319. // instead of a valid date string (i.e. "1/1/2000" ). In that case, date->d
  320. // is assigned the numeric value of the operation ( -200 for the above example )
  321. // while date->m and date->y are assined the value -1 ( INVALID ).
  322. //
  323. // Only the second date is allowed to accept an operation string, so we use
  324. // the parameter 'id' in order to know whether we're dealing with date1 or
  325. // date2 in here.
  326. //
  327. // Since in case of a valid operation in the input, the returned date does NOT
  328. // hold a valid date, further processing needs to be done in the main() function,
  329. // to ensure that date2 is properly converted to a valid date, equalling to:
  330. // date1 + date2.d, before attempting to do anything else with it (e.g. printing
  331. // it)
  332. //
  333. //
  334. bool date_askuser( int id, char *prompt, Date *date,
  335. int max_inbuf, int mdays[], Date calstart, Date calend )
  336. {
  337. if ( !prompt || !date) // inbuf does not exist or it is empty
  338. return FALSE;
  339.  
  340. char inbuf[ max_inbuf ]; // for reading the user input
  341. char *stokens[3]; // to read d, m, y as strings
  342. char *tail; // in strtol() for err checking
  343. long jdn; // for converting date to jdn
  344. bool stop = TRUE; // for controlling the main loop
  345. bool operated = FALSE; // got a date or an operation?
  346.  
  347. do
  348. {
  349. stop = TRUE; // reset boolean flag to TRUE
  350. operated = FALSE; // reset boolean flag to FALSE
  351.  
  352. // prompt and get user input
  353. printf( prompt ); // ask for input
  354. fflush(stdin); // clear input buffer (stdin)
  355. s_get(inbuf, max_inbuf); // read input as string in inbuf
  356. s_trim( s_tolower(inbuf) ); // trim leading & trailng blanks
  357.  
  358. // check if user typed just an ENTER
  359. if ( !*inbuf )
  360. continue; // stop = TRUE
  361.  
  362. // check if user typed an operation (+/-) followed by a number
  363. if ( id == ID1 && (*inbuf =='+' || *inbuf == '-') ) {
  364. puts("\t*** error: operations only allowed in the 2nd date");
  365. stop = FALSE;
  366. continue;
  367. }
  368. if ( date_getoperation(date, inbuf) ) {
  369. operated = TRUE;
  370. continue; // stop = TRUE
  371. }
  372.  
  373. // check if user typed any of our mnemonic strings
  374. if ( date_getmnemonic( date, inbuf, calstart, calend ) ) {
  375. stop = TRUE;
  376. continue;
  377. }
  378.  
  379. // split inbuf in up to 3 strings (tokens) assuming d, m, y
  380. if ( s_tokenize( inbuf, stokens, 3, " /,.;:\t") != 3 ) {
  381. puts("\t*** error: invalid date");
  382. stop = FALSE;
  383. continue;
  384. }
  385.  
  386. // demand from user to type a day between 1-31
  387. errno = 0; // reset golbal var errno
  388. date->d = strtol(stokens[0], &tail, 10);// convert str day to long
  389. if ( !validday(date->d) || *tail != '\0' || errno == ERANGE )
  390. {
  391. puts("\t*** error: valid days are 1-31");
  392. date->d = INVALID;
  393. stop = FALSE;
  394. continue;
  395. }
  396.  
  397. // demand from user to type a month between 1-12
  398. errno = 0; // reset golbal var errno
  399. date->m = strtol(stokens[1], &tail, 10);// convert str month to long
  400. if ( *tail != '\0' || errno == ERANGE || !validmonth(date->m) )
  401. {
  402. puts("\t*** error: valid months are 1-12");
  403. date->m = INVALID;
  404. stop = FALSE;
  405. continue;
  406. }
  407.  
  408. // demand from user to type a year between 1752-9999
  409. errno = 0; // reset golbal errno
  410. date->y = strtol(stokens[2], &tail, 10);// convert str year to long
  411. if ( *tail != '\0' || errno == ERANGE || !validyear(date->y) )
  412. {
  413. puts("\t*** error: valid years are 1752-9999");
  414. date->y = INVALID;
  415. stop = FALSE;
  416. continue;
  417. }
  418.  
  419. /* now we have a complete date (d,m,y) but this
  420. * does not gurantee us that it is a valid one
  421. * (e.g. 29/2/2000 is valid, but 29/2/2001 is not)
  422. * (also, e.g. 31/4 is invalid, an so on)
  423. */
  424.  
  425. // ensure day lyes inside the month boundary of the typed date
  426. if ( date->d > mdays[ date->m - 1 ] )
  427. {
  428. if ( date->d == 29 && date->m == FEB && isleap( date->y ) )
  429. continue; // stop = TRUE
  430.  
  431. date->d = date->m = date->y = INVALID;
  432. puts("\t*** error: invalid day");
  433. stop = FALSE;
  434. continue;
  435. }
  436.  
  437. // ensure date is between 14/9/1752 and 31/12/9999
  438. jdn = date_2jdn( *date ); // convert date to day
  439. if ( date_2jdn(calstart) > jdn || jdn > date_2jdn(calend) )
  440. {
  441. date->d = date->m = date->y = INVALID;
  442. puts("\t*** error: valid dates are 14/9/1752 - 31/12/9999");
  443. stop = FALSE;
  444. continue;
  445. }
  446.  
  447. } while( !stop || !*inbuf );
  448.  
  449. return operated;
  450. }
  451.  
  452. // ------------------------------------------------------------------------------------
  453. void print_help( void )
  454. {
  455. printf("\n\n%70s\n", "<mig_f1@hotmail.com>\n");
  456. puts("A sample program performing various calculations between two dates in \nthe time interval: 14/9/1752 to 31/12/9999 (results never exceed it).\n\nEach date input is checked in real time against both syntax & logical\nerrors. It can be in one of the following formats...\n-\td/m/y\t\t: other separators: :,.; and tab\n-\ta signed number\t: e.g. -200 subtracts 200 days from 1st Date\n-\tstart\t\t: equals to 14/9/1752\n-\tyesterday\t: equals to yesterday's date\n-\ttoday\t\t: equals to today's date\n-\ttomorrow\t: equals to tomorrow's date\n-\tend\t\t: equals to 31/12/9999\n\nThe calculated results include...\n-\tdifference expressed in days\n-\tdifference expressed in days, months, years\n-\tweekday indication\n-\tJulian Day Number indication\n-\tleap year indication");
  457.  
  458. return;
  459. }
  460.  
  461. // ------------------------------------------------------------------------------------
  462. void print_results(long int ndays, Date date, char *mnames[], char *dnames[] )
  463. {
  464. printf("\tDiff:\t%ld day(s)\n", ndays);
  465. printf( "\t\t%ld years(s), %02ld months(s) and %02ld days(s)\n",
  466. date.y, date.m, date.d );
  467.  
  468. return;
  469. }
  470.  
  471. // ------------------------------------------------------------------------------------
  472. void print_date(Date date, int id, char *mnames[], char *dnames[] )
  473. {
  474. if ( !validday( date.d ) || !validmonth( date.m ) || !validyear( date.y ) )
  475. return;
  476.  
  477. printf( "\t%d. %s %02ld %04ld, %-09s (jdn: %ld) %s\n",
  478. id,
  479. mnames[ (int)(date.m)-1 ],
  480. date.d, date.y, dnames[ date_2weekday(date) ],
  481. date_2jdn( date ),
  482. isleap( date.y ) ? "\t(leap year)" : ""
  483. );
  484.  
  485. return;
  486. }
  487.  
  488. // ------------------------------------------------------------------------------------
  489. int main( void )
  490. {
  491. char *dnames[7] = { // day names
  492. "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
  493. };
  494. char *mnames[12] = { // month names
  495. "JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"
  496. };
  497. // month lengths in days
  498. int mdays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  499.  
  500. Date calstart = { .d=14, .m=9, .y=1752 }; // our calendar starting date
  501. Date calend = { .d=31, .m=12, .y=9999 }; // our calendar ending date
  502.  
  503. Date date1, date2; // dates to be read from user
  504. Date diffdate; // for holding diff as (d,m,y)
  505. long ndays; // difference in days
  506. bool operated = FALSE; // input was date or operation?
  507. char inbuf[MAX_INBUF] = ""; // our own input buffer
  508.  
  509. do
  510. {
  511. print_help();
  512.  
  513. // read date1 and date2
  514. date_askuser( ID1, "\nFirst date (d/m/y): ", &date1,
  515. MAX_INBUF, mdays, calstart, calend );
  516. operated = date_askuser(
  517. ID2, "Second date (d/m/y): ", &date2,
  518. MAX_INBUF, mdays, calstart, calend );
  519.  
  520. // if date2 was an operation, store the resulting date into date2
  521. if ( operated )
  522. date_plusdays( &date2, date1, date2.d, calstart, calend);
  523.  
  524. // calc difference between date1 and date2
  525. ndays = date_diff( &diffdate, date1, date2, mdays );
  526.  
  527. // print dates and results
  528. putchar('\n');
  529. puts("\t--------------------------------------------------");
  530. print_date( date1, ID1, mnames, dnames );
  531. print_date( date2, ID2, mnames, dnames );
  532. puts("\t--------------------------------------------------");
  533. print_results( ndays, diffdate, mnames, dnames );
  534. puts("\t--------------------------------------------------");
  535.  
  536. printf("\ntry again (/n): ");
  537. fflush(stdin);
  538. } while ( *s_tolower( s_get(inbuf, MAX_INBUF) ) != 'n' );
  539.  
  540. exit(0);
  541. }
  542.  
Not running #stdin #stdout 0s 0KB
stdin
Standard input is empty
stdout
Standard output is empty