time.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. /**
  2. time.c - Date and Time handling
  3. Copyright (c) All Rights Reserved. See details at the end of the file.
  4. */
  5. /********************************* Includes ***********************************/
  6. #include "goahead.h"
  7. /********************************** Defines ***********************************/
  8. #define SEC_PER_MIN (60)
  9. #define SEC_PER_HOUR (60 * 60)
  10. #define SEC_PER_DAY (86400)
  11. #define SEC_PER_YEAR (INT64(31556952))
  12. /*
  13. Token types
  14. */
  15. #define TOKEN_DAY 0x01000000
  16. #define TOKEN_MONTH 0x02000000
  17. #define TOKEN_ZONE 0x04000000
  18. #define TOKEN_OFFSET 0x08000000
  19. typedef struct TimeToken {
  20. char *name;
  21. int value;
  22. int type;
  23. } TimeToken;
  24. static WebsHash timeTokens = -1;
  25. static TimeToken days[] = {
  26. { "sun", 0, TOKEN_DAY },
  27. { "mon", 1, TOKEN_DAY },
  28. { "tue", 2, TOKEN_DAY },
  29. { "wed", 3, TOKEN_DAY },
  30. { "thu", 4, TOKEN_DAY },
  31. { "fri", 5, TOKEN_DAY },
  32. { "sat", 6, TOKEN_DAY },
  33. { 0, 0 },
  34. };
  35. static TimeToken fullDays[] = {
  36. { "sunday", 0, TOKEN_DAY },
  37. { "monday", 1, TOKEN_DAY },
  38. { "tuesday", 2, TOKEN_DAY },
  39. { "wednesday", 3, TOKEN_DAY },
  40. { "thursday", 4, TOKEN_DAY },
  41. { "friday", 5, TOKEN_DAY },
  42. { "saturday", 6, TOKEN_DAY },
  43. { 0, 0 },
  44. };
  45. /*
  46. Make origin 1 to correspond to user date entries 10/28/2014
  47. */
  48. static TimeToken months[] = {
  49. { "jan", 1, TOKEN_MONTH },
  50. { "feb", 2, TOKEN_MONTH },
  51. { "mar", 3, TOKEN_MONTH },
  52. { "apr", 4, TOKEN_MONTH },
  53. { "may", 5, TOKEN_MONTH },
  54. { "jun", 6, TOKEN_MONTH },
  55. { "jul", 7, TOKEN_MONTH },
  56. { "aug", 8, TOKEN_MONTH },
  57. { "sep", 9, TOKEN_MONTH },
  58. { "oct", 10, TOKEN_MONTH },
  59. { "nov", 11, TOKEN_MONTH },
  60. { "dec", 12, TOKEN_MONTH },
  61. { 0, 0 },
  62. };
  63. static TimeToken fullMonths[] = {
  64. { "january", 1, TOKEN_MONTH },
  65. { "february", 2, TOKEN_MONTH },
  66. { "march", 3, TOKEN_MONTH },
  67. { "april", 4, TOKEN_MONTH },
  68. { "may", 5, TOKEN_MONTH },
  69. { "june", 6, TOKEN_MONTH },
  70. { "july", 7, TOKEN_MONTH },
  71. { "august", 8, TOKEN_MONTH },
  72. { "september", 9, TOKEN_MONTH },
  73. { "october", 10, TOKEN_MONTH },
  74. { "november", 11, TOKEN_MONTH },
  75. { "december", 12, TOKEN_MONTH },
  76. { 0, 0 }
  77. };
  78. static TimeToken ampm[] = {
  79. { "am", 0, TOKEN_OFFSET },
  80. { "pm", (12 * 3600), TOKEN_OFFSET },
  81. { 0, 0 },
  82. };
  83. static TimeToken zones[] = {
  84. { "ut", 0, TOKEN_ZONE },
  85. { "utc", 0, TOKEN_ZONE },
  86. { "gmt", 0, TOKEN_ZONE },
  87. { "edt", -240, TOKEN_ZONE },
  88. { "est", -300, TOKEN_ZONE },
  89. { "cdt", -300, TOKEN_ZONE },
  90. { "cst", -360, TOKEN_ZONE },
  91. { "mdt", -360, TOKEN_ZONE },
  92. { "mst", -420, TOKEN_ZONE },
  93. { "pdt", -420, TOKEN_ZONE },
  94. { "pst", -480, TOKEN_ZONE },
  95. { 0, 0 },
  96. };
  97. static TimeToken offsets[] = {
  98. { "tomorrow", 86400, TOKEN_OFFSET },
  99. { "yesterday", -86400, TOKEN_OFFSET },
  100. { "next week", (86400 * 7), TOKEN_OFFSET },
  101. { "last week", -(86400 * 7), TOKEN_OFFSET },
  102. { 0, 0 },
  103. };
  104. static int timeSep = ':';
  105. static int normalMonthStart[] = {
  106. 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 0,
  107. };
  108. static int leapMonthStart[] = {
  109. 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 0
  110. };
  111. static int leapYear(int year);
  112. static void validateTime(struct tm *tm, struct tm *defaults);
  113. /************************************ Code ************************************/
  114. PUBLIC int websTimeOpen()
  115. {
  116. TimeToken *tt;
  117. timeTokens = hashCreate(59);
  118. for (tt = days; tt->name; tt++) {
  119. hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
  120. }
  121. for (tt = fullDays; tt->name; tt++) {
  122. hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
  123. }
  124. for (tt = months; tt->name; tt++) {
  125. hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
  126. }
  127. for (tt = fullMonths; tt->name; tt++) {
  128. hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
  129. }
  130. for (tt = ampm; tt->name; tt++) {
  131. hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
  132. }
  133. for (tt = zones; tt->name; tt++) {
  134. hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
  135. }
  136. for (tt = offsets; tt->name; tt++) {
  137. hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
  138. }
  139. return 0;
  140. }
  141. PUBLIC void websTimeClose()
  142. {
  143. if (timeTokens >= 0) {
  144. hashFree(timeTokens);
  145. timeTokens = -1;
  146. }
  147. }
  148. static int leapYear(int year)
  149. {
  150. if (year % 4) {
  151. return 0;
  152. } else if (year % 400 == 0) {
  153. return 1;
  154. } else if (year % 100 == 0) {
  155. return 0;
  156. }
  157. return 1;
  158. }
  159. static int daysSinceEpoch(int year)
  160. {
  161. int days;
  162. days = 365 * (year - 1970);
  163. days += ((year-1) / 4) - (1970 / 4);
  164. days -= ((year-1) / 100) - (1970 / 100);
  165. days += ((year-1) / 400) - (1970 / 400);
  166. return days;
  167. }
  168. static WebsTime makeTime(struct tm *tp)
  169. {
  170. int days, year, month;
  171. year = tp->tm_year + 1900 + tp->tm_mon / 12;
  172. month = tp->tm_mon % 12;
  173. if (month < 0) {
  174. month += 12;
  175. --year;
  176. }
  177. days = daysSinceEpoch(year);
  178. days += leapYear(year) ? leapMonthStart[month] : normalMonthStart[month];
  179. days += tp->tm_mday - 1;
  180. return (days * SEC_PER_DAY) + ((((((tp->tm_hour * 60)) + tp->tm_min) * 60) + tp->tm_sec));
  181. }
  182. static int lookupSym(char *token, int kind)
  183. {
  184. TimeToken *tt;
  185. if ((tt = (TimeToken*) hashLookupSymbol(timeTokens, token)) == 0) {
  186. return -1;
  187. }
  188. if (kind != tt->type) {
  189. return -1;
  190. }
  191. return tt->value;
  192. }
  193. static int getNum(char **token, int sep)
  194. {
  195. int num;
  196. if (*token == 0) {
  197. return 0;
  198. }
  199. num = atoi(*token);
  200. *token = strchr(*token, sep);
  201. if (*token) {
  202. *token += 1;
  203. }
  204. return num;
  205. }
  206. static int getNumOrSym(char **token, int sep, int kind, int *isAlpah)
  207. {
  208. char *cp;
  209. int num;
  210. assert(token && *token);
  211. if (*token == 0) {
  212. return 0;
  213. }
  214. if (isalpha((uchar) **token)) {
  215. *isAlpah = 1;
  216. cp = strchr(*token, sep);
  217. if (cp) {
  218. *cp++ = '\0';
  219. }
  220. num = lookupSym(*token, kind);
  221. *token = cp;
  222. return num;
  223. }
  224. num = atoi(*token);
  225. *token = strchr(*token, sep);
  226. if (*token) {
  227. *token += 1;
  228. }
  229. *isAlpah = 0;
  230. return num;
  231. }
  232. static void swapDayMonth(struct tm *tp)
  233. {
  234. int tmp;
  235. tmp = tp->tm_mday;
  236. tp->tm_mday = tp->tm_mon;
  237. tp->tm_mon = tmp;
  238. }
  239. /*
  240. Parse the a date/time string and return the result in *time. Missing date items may be provided
  241. via the defaults argument. This is a tolerant parser. It is not validating and will do its best
  242. to parse any possible date string.
  243. */
  244. PUBLIC int websParseDateTime(WebsTime *time, char *dateString, struct tm *defaults)
  245. {
  246. TimeToken *tt;
  247. struct tm tm;
  248. char *str, *next, *token, *cp, *sep;
  249. int64 value;
  250. int kind, hour, min, negate, value1, value2, value3, alpha, alpha2, alpha3;
  251. int dateSep, offset, zoneOffset, fullYear;
  252. if (!dateString) {
  253. dateString = "";
  254. }
  255. offset = 0;
  256. zoneOffset = 0;
  257. sep = ", \t";
  258. cp = 0;
  259. next = 0;
  260. fullYear = 0;
  261. /*
  262. Set these mandatory values to -1 so we can tell if they are set to valid values
  263. WARNING: all the calculations use tm_year with origin 0, not 1900. It is fixed up below.
  264. */
  265. tm.tm_year = -MAXINT;
  266. tm.tm_mon = tm.tm_mday = tm.tm_hour = tm.tm_sec = tm.tm_min = tm.tm_wday = -1;
  267. tm.tm_min = tm.tm_sec = tm.tm_yday = -1;
  268. #if ME_UNIX_LIKE && !CYGWIN
  269. tm.tm_gmtoff = 0;
  270. tm.tm_zone = 0;
  271. #endif
  272. /*
  273. Set to -1 to try to determine if DST is in effect
  274. */
  275. tm.tm_isdst = -1;
  276. str = slower(dateString);
  277. /*
  278. Handle ISO dates: 2009-05-21t16:06:05.000z
  279. */
  280. if (strchr(str, ' ') == 0 && strchr(str, '-') && str[slen(str) - 1] == 'z') {
  281. for (cp = str; *cp; cp++) {
  282. if (*cp == '-') {
  283. *cp = '/';
  284. } else if (*cp == 't' && cp > str && isdigit((uchar) cp[-1]) && isdigit((uchar) cp[1]) ) {
  285. *cp = ' ';
  286. }
  287. }
  288. }
  289. token = stok(str, sep, &next);
  290. while (token && *token) {
  291. if (snumber(token)) {
  292. /*
  293. Parse either day of month or year. Priority to day of month. Format: <29> Jan <15> <2014>
  294. */
  295. value = atoi(token);
  296. if (value > 3000) {
  297. *time = value;
  298. return 0;
  299. } else if (value > 32 || (tm.tm_mday >= 0 && tm.tm_year == -MAXINT)) {
  300. if (value >= 1000) {
  301. fullYear = 1;
  302. }
  303. tm.tm_year = (int) value - 1900;
  304. } else if (tm.tm_mday < 0) {
  305. tm.tm_mday = (int) value;
  306. }
  307. } else if (strncmp(token, "gmt", 3) == 0 || strncmp(token, "utc", 3) == 0) {
  308. /*
  309. Timezone format: GMT|UTC[+-]NN[:]NN
  310. GMT-08:00, UTC-0800
  311. */
  312. token += 3;
  313. if (token[0] == '-' || token[0] == '+') {
  314. negate = *token == '-' ? -1 : 1;
  315. token++;
  316. } else {
  317. negate = 1;
  318. }
  319. if (strchr(token, ':')) {
  320. hour = getNum(&token, timeSep);
  321. min = getNum(&token, timeSep);
  322. } else {
  323. hour = atoi(token);
  324. min = hour % 100;
  325. hour /= 100;
  326. }
  327. zoneOffset = negate * (hour * 60 + min);
  328. } else if (isalpha((uchar) *token)) {
  329. if ((tt = (TimeToken*) hashLookupSymbol(timeTokens, token)) != 0) {
  330. kind = tt->type;
  331. value = tt->value;
  332. switch (kind) {
  333. case TOKEN_DAY:
  334. tm.tm_wday = (int) value;
  335. break;
  336. case TOKEN_MONTH:
  337. tm.tm_mon = (int) value;
  338. break;
  339. case TOKEN_OFFSET:
  340. /* Named timezones or symbolic names like: tomorrow, yesterday, next week ... */
  341. /* Units are seconds */
  342. offset += (int) value;
  343. break;
  344. case TOKEN_ZONE:
  345. zoneOffset = (int) value;
  346. break;
  347. default:
  348. /* Just ignore unknown values */
  349. break;
  350. }
  351. }
  352. } else if ((cp = strchr(token, timeSep)) != 0 && isdigit((uchar) token[0])) {
  353. /*
  354. Time: 10:52[:23]
  355. Must not parse GMT-07:30
  356. */
  357. tm.tm_hour = getNum(&token, timeSep);
  358. tm.tm_min = getNum(&token, timeSep);
  359. tm.tm_sec = getNum(&token, timeSep);
  360. } else {
  361. dateSep = '/';
  362. if (strchr(token, dateSep) == 0) {
  363. dateSep = '-';
  364. if (strchr(token, dateSep) == 0) {
  365. dateSep = '.';
  366. if (strchr(token, dateSep) == 0) {
  367. dateSep = 0;
  368. }
  369. }
  370. }
  371. if (dateSep) {
  372. /*
  373. Date: 07/28/2014, 07/28/08, Jan/28/2014, Jaunuary-28-2014, 28-jan-2014
  374. Support order: dd/mm/yy, mm/dd/yy and yyyy/mm/dd
  375. Support separators "/", ".", "-"
  376. */
  377. value1 = getNumOrSym(&token, dateSep, TOKEN_MONTH, &alpha);
  378. value2 = getNumOrSym(&token, dateSep, TOKEN_MONTH, &alpha2);
  379. value3 = getNumOrSym(&token, dateSep, TOKEN_MONTH, &alpha3);
  380. if (value1 > 31) {
  381. /* yy/mm/dd */
  382. tm.tm_year = value1;
  383. tm.tm_mon = value2;
  384. tm.tm_mday = value3;
  385. } else if (value1 > 12 || alpha2) {
  386. /*
  387. dd/mm/yy
  388. Cannot detect 01/02/03 This will be evaluated as Jan 2 2003 below.
  389. */
  390. tm.tm_mday = value1;
  391. tm.tm_mon = value2;
  392. tm.tm_year = value3;
  393. } else {
  394. /*
  395. The default to parse is mm/dd/yy unless the mm value is out of range
  396. */
  397. tm.tm_mon = value1;
  398. tm.tm_mday = value2;
  399. tm.tm_year = value3;
  400. }
  401. }
  402. }
  403. token = stok(NULL, sep, &next);
  404. }
  405. /*
  406. Y2K fix and rebias
  407. */
  408. if (0 <= tm.tm_year && tm.tm_year < 100 && !fullYear) {
  409. if (tm.tm_year < 50) {
  410. tm.tm_year += 2000;
  411. } else {
  412. tm.tm_year += 1900;
  413. }
  414. }
  415. if (tm.tm_year >= 1900) {
  416. tm.tm_year -= 1900;
  417. }
  418. /*
  419. Convert back to origin 0 for months
  420. */
  421. if (tm.tm_mon > 0) {
  422. tm.tm_mon--;
  423. }
  424. /*
  425. Validate and fill in missing items with defaults
  426. */
  427. validateTime(&tm, defaults);
  428. *time = makeTime(&tm);
  429. *time += -(zoneOffset * SEC_PER_MIN);
  430. *time += offset;
  431. return 0;
  432. }
  433. static void validateTime(struct tm *tp, struct tm *defaults)
  434. {
  435. struct tm empty;
  436. /*
  437. Fix apparent day-mon-year ordering issues. Cannot fix everything!
  438. */
  439. if ((12 <= tp->tm_mon && tp->tm_mon <= 31) && 0 <= tp->tm_mday && tp->tm_mday <= 11) {
  440. /*
  441. Looks like day month are swapped
  442. */
  443. swapDayMonth(tp);
  444. }
  445. if (tp->tm_year != -MAXINT && tp->tm_mon >= 0 && tp->tm_mday >= 0 && tp->tm_hour >= 0) {
  446. /* Everything defined */
  447. return;
  448. }
  449. /*
  450. Use empty time if missing
  451. */
  452. if (defaults == NULL) {
  453. memset(&empty, 0, sizeof(empty));
  454. defaults = &empty;
  455. empty.tm_mday = 1;
  456. empty.tm_year = 70;
  457. }
  458. if (tp->tm_hour < 0 && tp->tm_min < 0 && tp->tm_sec < 0) {
  459. tp->tm_hour = defaults->tm_hour;
  460. tp->tm_min = defaults->tm_min;
  461. tp->tm_sec = defaults->tm_sec;
  462. }
  463. /*
  464. Get weekday, if before today then make next week
  465. */
  466. if (tp->tm_wday >= 0 && tp->tm_year == -MAXINT && tp->tm_mon < 0 && tp->tm_mday < 0) {
  467. tp->tm_mday = defaults->tm_mday + (tp->tm_wday - defaults->tm_wday + 7) % 7;
  468. tp->tm_mon = defaults->tm_mon;
  469. tp->tm_year = defaults->tm_year;
  470. }
  471. /*
  472. Get month, if before this month then make next year
  473. */
  474. if (tp->tm_mon >= 0 && tp->tm_mon <= 11 && tp->tm_mday < 0) {
  475. if (tp->tm_year == -MAXINT) {
  476. tp->tm_year = defaults->tm_year + (((tp->tm_mon - defaults->tm_mon) < 0) ? 1 : 0);
  477. }
  478. tp->tm_mday = defaults->tm_mday;
  479. }
  480. /*
  481. Get date, if before current time then make tomorrow
  482. */
  483. if (tp->tm_hour >= 0 && tp->tm_year == -MAXINT && tp->tm_mon < 0 && tp->tm_mday < 0) {
  484. tp->tm_mday = defaults->tm_mday + ((tp->tm_hour - defaults->tm_hour) < 0 ? 1 : 0);
  485. tp->tm_mon = defaults->tm_mon;
  486. tp->tm_year = defaults->tm_year;
  487. }
  488. if (tp->tm_year == -MAXINT) {
  489. tp->tm_year = defaults->tm_year;
  490. }
  491. if (tp->tm_mon < 0) {
  492. tp->tm_mon = defaults->tm_mon;
  493. }
  494. if (tp->tm_mday < 0) {
  495. tp->tm_mday = defaults->tm_mday;
  496. }
  497. if (tp->tm_yday < 0) {
  498. tp->tm_yday = (leapYear(tp->tm_year + 1900) ?
  499. leapMonthStart[tp->tm_mon] : normalMonthStart[tp->tm_mon]) + tp->tm_mday - 1;
  500. }
  501. if (tp->tm_hour < 0) {
  502. tp->tm_hour = defaults->tm_hour;
  503. }
  504. if (tp->tm_min < 0) {
  505. tp->tm_min = defaults->tm_min;
  506. }
  507. if (tp->tm_sec < 0) {
  508. tp->tm_sec = defaults->tm_sec;
  509. }
  510. }
  511. /*
  512. Copyright (c) Embedthis Software. All Rights Reserved.
  513. This software is distributed under commercial and open source licenses.
  514. You may use the Embedthis GoAhead open source license or you may acquire
  515. a commercial license from Embedthis Software. You agree to be fully bound
  516. by the terms of either license. Consult the LICENSE.md distributed with
  517. this software for full details and other copyrights.
  518. */