123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598 |
- /**
- time.c - Date and Time handling
- Copyright (c) All Rights Reserved. See details at the end of the file.
- */
- /********************************* Includes ***********************************/
- #include "goahead.h"
- /********************************** Defines ***********************************/
- #define SEC_PER_MIN (60)
- #define SEC_PER_HOUR (60 * 60)
- #define SEC_PER_DAY (86400)
- #define SEC_PER_YEAR (INT64(31556952))
- /*
- Token types
- */
- #define TOKEN_DAY 0x01000000
- #define TOKEN_MONTH 0x02000000
- #define TOKEN_ZONE 0x04000000
- #define TOKEN_OFFSET 0x08000000
- typedef struct TimeToken {
- char *name;
- int value;
- int type;
- } TimeToken;
- static WebsHash timeTokens = -1;
- static TimeToken days[] = {
- { "sun", 0, TOKEN_DAY },
- { "mon", 1, TOKEN_DAY },
- { "tue", 2, TOKEN_DAY },
- { "wed", 3, TOKEN_DAY },
- { "thu", 4, TOKEN_DAY },
- { "fri", 5, TOKEN_DAY },
- { "sat", 6, TOKEN_DAY },
- { 0, 0 },
- };
- static TimeToken fullDays[] = {
- { "sunday", 0, TOKEN_DAY },
- { "monday", 1, TOKEN_DAY },
- { "tuesday", 2, TOKEN_DAY },
- { "wednesday", 3, TOKEN_DAY },
- { "thursday", 4, TOKEN_DAY },
- { "friday", 5, TOKEN_DAY },
- { "saturday", 6, TOKEN_DAY },
- { 0, 0 },
- };
- /*
- Make origin 1 to correspond to user date entries 10/28/2014
- */
- static TimeToken months[] = {
- { "jan", 1, TOKEN_MONTH },
- { "feb", 2, TOKEN_MONTH },
- { "mar", 3, TOKEN_MONTH },
- { "apr", 4, TOKEN_MONTH },
- { "may", 5, TOKEN_MONTH },
- { "jun", 6, TOKEN_MONTH },
- { "jul", 7, TOKEN_MONTH },
- { "aug", 8, TOKEN_MONTH },
- { "sep", 9, TOKEN_MONTH },
- { "oct", 10, TOKEN_MONTH },
- { "nov", 11, TOKEN_MONTH },
- { "dec", 12, TOKEN_MONTH },
- { 0, 0 },
- };
- static TimeToken fullMonths[] = {
- { "january", 1, TOKEN_MONTH },
- { "february", 2, TOKEN_MONTH },
- { "march", 3, TOKEN_MONTH },
- { "april", 4, TOKEN_MONTH },
- { "may", 5, TOKEN_MONTH },
- { "june", 6, TOKEN_MONTH },
- { "july", 7, TOKEN_MONTH },
- { "august", 8, TOKEN_MONTH },
- { "september", 9, TOKEN_MONTH },
- { "october", 10, TOKEN_MONTH },
- { "november", 11, TOKEN_MONTH },
- { "december", 12, TOKEN_MONTH },
- { 0, 0 }
- };
- static TimeToken ampm[] = {
- { "am", 0, TOKEN_OFFSET },
- { "pm", (12 * 3600), TOKEN_OFFSET },
- { 0, 0 },
- };
- static TimeToken zones[] = {
- { "ut", 0, TOKEN_ZONE },
- { "utc", 0, TOKEN_ZONE },
- { "gmt", 0, TOKEN_ZONE },
- { "edt", -240, TOKEN_ZONE },
- { "est", -300, TOKEN_ZONE },
- { "cdt", -300, TOKEN_ZONE },
- { "cst", -360, TOKEN_ZONE },
- { "mdt", -360, TOKEN_ZONE },
- { "mst", -420, TOKEN_ZONE },
- { "pdt", -420, TOKEN_ZONE },
- { "pst", -480, TOKEN_ZONE },
- { 0, 0 },
- };
- static TimeToken offsets[] = {
- { "tomorrow", 86400, TOKEN_OFFSET },
- { "yesterday", -86400, TOKEN_OFFSET },
- { "next week", (86400 * 7), TOKEN_OFFSET },
- { "last week", -(86400 * 7), TOKEN_OFFSET },
- { 0, 0 },
- };
- static int timeSep = ':';
- static int normalMonthStart[] = {
- 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 0,
- };
- static int leapMonthStart[] = {
- 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 0
- };
- static int leapYear(int year);
- static void validateTime(struct tm *tm, struct tm *defaults);
- /************************************ Code ************************************/
- PUBLIC int websTimeOpen()
- {
- TimeToken *tt;
- timeTokens = hashCreate(59);
- for (tt = days; tt->name; tt++) {
- hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
- }
- for (tt = fullDays; tt->name; tt++) {
- hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
- }
- for (tt = months; tt->name; tt++) {
- hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
- }
- for (tt = fullMonths; tt->name; tt++) {
- hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
- }
- for (tt = ampm; tt->name; tt++) {
- hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
- }
- for (tt = zones; tt->name; tt++) {
- hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
- }
- for (tt = offsets; tt->name; tt++) {
- hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
- }
- return 0;
- }
- PUBLIC void websTimeClose()
- {
- if (timeTokens >= 0) {
- hashFree(timeTokens);
- timeTokens = -1;
- }
- }
- static int leapYear(int year)
- {
- if (year % 4) {
- return 0;
- } else if (year % 400 == 0) {
- return 1;
- } else if (year % 100 == 0) {
- return 0;
- }
- return 1;
- }
- static int daysSinceEpoch(int year)
- {
- int days;
- days = 365 * (year - 1970);
- days += ((year-1) / 4) - (1970 / 4);
- days -= ((year-1) / 100) - (1970 / 100);
- days += ((year-1) / 400) - (1970 / 400);
- return days;
- }
- static WebsTime makeTime(struct tm *tp)
- {
- int days, year, month;
- year = tp->tm_year + 1900 + tp->tm_mon / 12;
- month = tp->tm_mon % 12;
- if (month < 0) {
- month += 12;
- --year;
- }
- days = daysSinceEpoch(year);
- days += leapYear(year) ? leapMonthStart[month] : normalMonthStart[month];
- days += tp->tm_mday - 1;
- return (days * SEC_PER_DAY) + ((((((tp->tm_hour * 60)) + tp->tm_min) * 60) + tp->tm_sec));
- }
- static int lookupSym(char *token, int kind)
- {
- TimeToken *tt;
- if ((tt = (TimeToken*) hashLookupSymbol(timeTokens, token)) == 0) {
- return -1;
- }
- if (kind != tt->type) {
- return -1;
- }
- return tt->value;
- }
- static int getNum(char **token, int sep)
- {
- int num;
- if (*token == 0) {
- return 0;
- }
- num = atoi(*token);
- *token = strchr(*token, sep);
- if (*token) {
- *token += 1;
- }
- return num;
- }
- static int getNumOrSym(char **token, int sep, int kind, int *isAlpah)
- {
- char *cp;
- int num;
- assert(token && *token);
- if (*token == 0) {
- return 0;
- }
- if (isalpha((uchar) **token)) {
- *isAlpah = 1;
- cp = strchr(*token, sep);
- if (cp) {
- *cp++ = '\0';
- }
- num = lookupSym(*token, kind);
- *token = cp;
- return num;
- }
- num = atoi(*token);
- *token = strchr(*token, sep);
- if (*token) {
- *token += 1;
- }
- *isAlpah = 0;
- return num;
- }
- static void swapDayMonth(struct tm *tp)
- {
- int tmp;
- tmp = tp->tm_mday;
- tp->tm_mday = tp->tm_mon;
- tp->tm_mon = tmp;
- }
- /*
- Parse the a date/time string and return the result in *time. Missing date items may be provided
- via the defaults argument. This is a tolerant parser. It is not validating and will do its best
- to parse any possible date string.
- */
- PUBLIC int websParseDateTime(WebsTime *time, char *dateString, struct tm *defaults)
- {
- TimeToken *tt;
- struct tm tm;
- char *str, *next, *token, *cp, *sep;
- int64 value;
- int kind, hour, min, negate, value1, value2, value3, alpha, alpha2, alpha3;
- int dateSep, offset, zoneOffset, fullYear;
- if (!dateString) {
- dateString = "";
- }
- offset = 0;
- zoneOffset = 0;
- sep = ", \t";
- cp = 0;
- next = 0;
- fullYear = 0;
- /*
- Set these mandatory values to -1 so we can tell if they are set to valid values
- WARNING: all the calculations use tm_year with origin 0, not 1900. It is fixed up below.
- */
- tm.tm_year = -MAXINT;
- tm.tm_mon = tm.tm_mday = tm.tm_hour = tm.tm_sec = tm.tm_min = tm.tm_wday = -1;
- tm.tm_min = tm.tm_sec = tm.tm_yday = -1;
- #if ME_UNIX_LIKE && !CYGWIN
- tm.tm_gmtoff = 0;
- tm.tm_zone = 0;
- #endif
- /*
- Set to -1 to try to determine if DST is in effect
- */
- tm.tm_isdst = -1;
- str = slower(dateString);
- /*
- Handle ISO dates: 2009-05-21t16:06:05.000z
- */
- if (strchr(str, ' ') == 0 && strchr(str, '-') && str[slen(str) - 1] == 'z') {
- for (cp = str; *cp; cp++) {
- if (*cp == '-') {
- *cp = '/';
- } else if (*cp == 't' && cp > str && isdigit((uchar) cp[-1]) && isdigit((uchar) cp[1]) ) {
- *cp = ' ';
- }
- }
- }
- token = stok(str, sep, &next);
- while (token && *token) {
- if (snumber(token)) {
- /*
- Parse either day of month or year. Priority to day of month. Format: <29> Jan <15> <2014>
- */
- value = atoi(token);
- if (value > 3000) {
- *time = value;
- return 0;
- } else if (value > 32 || (tm.tm_mday >= 0 && tm.tm_year == -MAXINT)) {
- if (value >= 1000) {
- fullYear = 1;
- }
- tm.tm_year = (int) value - 1900;
- } else if (tm.tm_mday < 0) {
- tm.tm_mday = (int) value;
- }
- } else if (strncmp(token, "gmt", 3) == 0 || strncmp(token, "utc", 3) == 0) {
- /*
- Timezone format: GMT|UTC[+-]NN[:]NN
- GMT-08:00, UTC-0800
- */
- token += 3;
- if (token[0] == '-' || token[0] == '+') {
- negate = *token == '-' ? -1 : 1;
- token++;
- } else {
- negate = 1;
- }
- if (strchr(token, ':')) {
- hour = getNum(&token, timeSep);
- min = getNum(&token, timeSep);
- } else {
- hour = atoi(token);
- min = hour % 100;
- hour /= 100;
- }
- zoneOffset = negate * (hour * 60 + min);
- } else if (isalpha((uchar) *token)) {
- if ((tt = (TimeToken*) hashLookupSymbol(timeTokens, token)) != 0) {
- kind = tt->type;
- value = tt->value;
- switch (kind) {
- case TOKEN_DAY:
- tm.tm_wday = (int) value;
- break;
- case TOKEN_MONTH:
- tm.tm_mon = (int) value;
- break;
- case TOKEN_OFFSET:
- /* Named timezones or symbolic names like: tomorrow, yesterday, next week ... */
- /* Units are seconds */
- offset += (int) value;
- break;
- case TOKEN_ZONE:
- zoneOffset = (int) value;
- break;
- default:
- /* Just ignore unknown values */
- break;
- }
- }
- } else if ((cp = strchr(token, timeSep)) != 0 && isdigit((uchar) token[0])) {
- /*
- Time: 10:52[:23]
- Must not parse GMT-07:30
- */
- tm.tm_hour = getNum(&token, timeSep);
- tm.tm_min = getNum(&token, timeSep);
- tm.tm_sec = getNum(&token, timeSep);
- } else {
- dateSep = '/';
- if (strchr(token, dateSep) == 0) {
- dateSep = '-';
- if (strchr(token, dateSep) == 0) {
- dateSep = '.';
- if (strchr(token, dateSep) == 0) {
- dateSep = 0;
- }
- }
- }
- if (dateSep) {
- /*
- Date: 07/28/2014, 07/28/08, Jan/28/2014, Jaunuary-28-2014, 28-jan-2014
- Support order: dd/mm/yy, mm/dd/yy and yyyy/mm/dd
- Support separators "/", ".", "-"
- */
- value1 = getNumOrSym(&token, dateSep, TOKEN_MONTH, &alpha);
- value2 = getNumOrSym(&token, dateSep, TOKEN_MONTH, &alpha2);
- value3 = getNumOrSym(&token, dateSep, TOKEN_MONTH, &alpha3);
- if (value1 > 31) {
- /* yy/mm/dd */
- tm.tm_year = value1;
- tm.tm_mon = value2;
- tm.tm_mday = value3;
- } else if (value1 > 12 || alpha2) {
- /*
- dd/mm/yy
- Cannot detect 01/02/03 This will be evaluated as Jan 2 2003 below.
- */
- tm.tm_mday = value1;
- tm.tm_mon = value2;
- tm.tm_year = value3;
- } else {
- /*
- The default to parse is mm/dd/yy unless the mm value is out of range
- */
- tm.tm_mon = value1;
- tm.tm_mday = value2;
- tm.tm_year = value3;
- }
- }
- }
- token = stok(NULL, sep, &next);
- }
- /*
- Y2K fix and rebias
- */
- if (0 <= tm.tm_year && tm.tm_year < 100 && !fullYear) {
- if (tm.tm_year < 50) {
- tm.tm_year += 2000;
- } else {
- tm.tm_year += 1900;
- }
- }
- if (tm.tm_year >= 1900) {
- tm.tm_year -= 1900;
- }
- /*
- Convert back to origin 0 for months
- */
- if (tm.tm_mon > 0) {
- tm.tm_mon--;
- }
- /*
- Validate and fill in missing items with defaults
- */
- validateTime(&tm, defaults);
- *time = makeTime(&tm);
- *time += -(zoneOffset * SEC_PER_MIN);
- *time += offset;
- return 0;
- }
- static void validateTime(struct tm *tp, struct tm *defaults)
- {
- struct tm empty;
- /*
- Fix apparent day-mon-year ordering issues. Cannot fix everything!
- */
- if ((12 <= tp->tm_mon && tp->tm_mon <= 31) && 0 <= tp->tm_mday && tp->tm_mday <= 11) {
- /*
- Looks like day month are swapped
- */
- swapDayMonth(tp);
- }
- if (tp->tm_year != -MAXINT && tp->tm_mon >= 0 && tp->tm_mday >= 0 && tp->tm_hour >= 0) {
- /* Everything defined */
- return;
- }
- /*
- Use empty time if missing
- */
- if (defaults == NULL) {
- memset(&empty, 0, sizeof(empty));
- defaults = ∅
- empty.tm_mday = 1;
- empty.tm_year = 70;
- }
- if (tp->tm_hour < 0 && tp->tm_min < 0 && tp->tm_sec < 0) {
- tp->tm_hour = defaults->tm_hour;
- tp->tm_min = defaults->tm_min;
- tp->tm_sec = defaults->tm_sec;
- }
- /*
- Get weekday, if before today then make next week
- */
- if (tp->tm_wday >= 0 && tp->tm_year == -MAXINT && tp->tm_mon < 0 && tp->tm_mday < 0) {
- tp->tm_mday = defaults->tm_mday + (tp->tm_wday - defaults->tm_wday + 7) % 7;
- tp->tm_mon = defaults->tm_mon;
- tp->tm_year = defaults->tm_year;
- }
- /*
- Get month, if before this month then make next year
- */
- if (tp->tm_mon >= 0 && tp->tm_mon <= 11 && tp->tm_mday < 0) {
- if (tp->tm_year == -MAXINT) {
- tp->tm_year = defaults->tm_year + (((tp->tm_mon - defaults->tm_mon) < 0) ? 1 : 0);
- }
- tp->tm_mday = defaults->tm_mday;
- }
- /*
- Get date, if before current time then make tomorrow
- */
- if (tp->tm_hour >= 0 && tp->tm_year == -MAXINT && tp->tm_mon < 0 && tp->tm_mday < 0) {
- tp->tm_mday = defaults->tm_mday + ((tp->tm_hour - defaults->tm_hour) < 0 ? 1 : 0);
- tp->tm_mon = defaults->tm_mon;
- tp->tm_year = defaults->tm_year;
- }
- if (tp->tm_year == -MAXINT) {
- tp->tm_year = defaults->tm_year;
- }
- if (tp->tm_mon < 0) {
- tp->tm_mon = defaults->tm_mon;
- }
- if (tp->tm_mday < 0) {
- tp->tm_mday = defaults->tm_mday;
- }
- if (tp->tm_yday < 0) {
- tp->tm_yday = (leapYear(tp->tm_year + 1900) ?
- leapMonthStart[tp->tm_mon] : normalMonthStart[tp->tm_mon]) + tp->tm_mday - 1;
- }
- if (tp->tm_hour < 0) {
- tp->tm_hour = defaults->tm_hour;
- }
- if (tp->tm_min < 0) {
- tp->tm_min = defaults->tm_min;
- }
- if (tp->tm_sec < 0) {
- tp->tm_sec = defaults->tm_sec;
- }
- }
- /*
- Copyright (c) Embedthis Software. All Rights Reserved.
- This software is distributed under commercial and open source licenses.
- You may use the Embedthis GoAhead open source license or you may acquire
- a commercial license from Embedthis Software. You agree to be fully bound
- by the terms of either license. Consult the LICENSE.md distributed with
- this software for full details and other copyrights.
- */
|