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