| /* |
| * Unix-like Date providers |
| * |
| * Generally useful Unix / POSIX / ANSI Date providers. |
| */ |
| |
| #include "duk_internal.h" |
| |
| /* The necessary #includes are in place in duk_config.h. */ |
| |
| /* Buffer sizes for some UNIX calls. Larger than strictly necessary |
| * to avoid Valgrind errors. |
| */ |
| #define DUK__STRPTIME_BUF_SIZE 64 |
| #define DUK__STRFTIME_BUF_SIZE 64 |
| |
| #if defined(DUK_USE_DATE_NOW_GETTIMEOFDAY) |
| /* Get current Ecmascript time (= UNIX/Posix time, but in milliseconds). */ |
| DUK_INTERNAL duk_double_t duk_bi_date_get_now_gettimeofday(duk_context *ctx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| struct timeval tv; |
| duk_double_t d; |
| |
| if (gettimeofday(&tv, NULL) != 0) { |
| DUK_ERROR_INTERNAL_DEFMSG(thr); |
| } |
| |
| d = ((duk_double_t) tv.tv_sec) * 1000.0 + |
| ((duk_double_t) (tv.tv_usec / 1000)); |
| DUK_ASSERT(DUK_FLOOR(d) == d); /* no fractions */ |
| |
| return d; |
| } |
| #endif /* DUK_USE_DATE_NOW_GETTIMEOFDAY */ |
| |
| #if defined(DUK_USE_DATE_NOW_TIME) |
| /* Not a very good provider: only full seconds are available. */ |
| DUK_INTERNAL duk_double_t duk_bi_date_get_now_time(duk_context *ctx) { |
| time_t t; |
| |
| DUK_UNREF(ctx); |
| t = time(NULL); |
| return ((duk_double_t) t) * 1000.0; |
| } |
| #endif /* DUK_USE_DATE_NOW_TIME */ |
| |
| #if defined(DUK_USE_DATE_TZO_GMTIME) || defined(DUK_USE_DATE_TZO_GMTIME_R) |
| /* Get local time offset (in seconds) for a certain (UTC) instant 'd'. */ |
| DUK_INTERNAL duk_int_t duk_bi_date_get_local_tzoffset_gmtime(duk_double_t d) { |
| time_t t, t1, t2; |
| duk_int_t parts[DUK_DATE_IDX_NUM_PARTS]; |
| duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS]; |
| struct tm tms[2]; |
| #ifdef DUK_USE_DATE_TZO_GMTIME |
| struct tm *tm_ptr; |
| #endif |
| |
| /* For NaN/inf, the return value doesn't matter. */ |
| if (!DUK_ISFINITE(d)) { |
| return 0; |
| } |
| |
| /* If not within Ecmascript range, some integer time calculations |
| * won't work correctly (and some asserts will fail), so bail out |
| * if so. This fixes test-bug-date-insane-setyear.js. There is |
| * a +/- 24h leeway in this range check to avoid a test262 corner |
| * case documented in test-bug-date-timeval-edges.js. |
| */ |
| if (!duk_bi_date_timeval_in_leeway_range(d)) { |
| DUK_DD(DUK_DDPRINT("timeval not within valid range, skip tzoffset computation to avoid integer overflows")); |
| return 0; |
| } |
| |
| /* |
| * This is a bit tricky to implement portably. The result depends |
| * on the timestamp (specifically, DST depends on the timestamp). |
| * If e.g. UNIX APIs are used, they'll have portability issues with |
| * very small and very large years. |
| * |
| * Current approach: |
| * |
| * - Stay within portable UNIX limits by using equivalent year mapping. |
| * Avoid year 1970 and 2038 as some conversions start to fail, at |
| * least on some platforms. Avoiding 1970 means that there are |
| * currently DST discrepancies for 1970. |
| * |
| * - Create a UTC and local time breakdowns from 't'. Then create |
| * a time_t using gmtime() and localtime() and compute the time |
| * difference between the two. |
| * |
| * Equivalent year mapping (E5 Section 15.9.1.8): |
| * |
| * If the host environment provides functionality for determining |
| * daylight saving time, the implementation of ECMAScript is free |
| * to map the year in question to an equivalent year (same |
| * leap-year-ness and same starting week day for the year) for which |
| * the host environment provides daylight saving time information. |
| * The only restriction is that all equivalent years should produce |
| * the same result. |
| * |
| * This approach is quite reasonable but not entirely correct, e.g. |
| * the specification also states (E5 Section 15.9.1.8): |
| * |
| * The implementation of ECMAScript should not try to determine |
| * whether the exact time was subject to daylight saving time, but |
| * just whether daylight saving time would have been in effect if |
| * the _current daylight saving time algorithm_ had been used at the |
| * time. This avoids complications such as taking into account the |
| * years that the locale observed daylight saving time year round. |
| * |
| * Since we rely on the platform APIs for conversions between local |
| * time and UTC, we can't guarantee the above. Rather, if the platform |
| * has historical DST rules they will be applied. This seems to be the |
| * general preferred direction in Ecmascript standardization (or at least |
| * implementations) anyway, and even the equivalent year mapping should |
| * be disabled if the platform is known to handle DST properly for the |
| * full Ecmascript range. |
| * |
| * The following has useful discussion and links: |
| * |
| * https://bugzilla.mozilla.org/show_bug.cgi?id=351066 |
| */ |
| |
| duk_bi_date_timeval_to_parts(d, parts, dparts, DUK_DATE_FLAG_EQUIVYEAR /*flags*/); |
| DUK_ASSERT(parts[DUK_DATE_IDX_YEAR] >= 1970 && parts[DUK_DATE_IDX_YEAR] <= 2038); |
| |
| d = duk_bi_date_get_timeval_from_dparts(dparts, 0 /*flags*/); |
| DUK_ASSERT(d >= 0 && d < 2147483648.0 * 1000.0); /* unsigned 31-bit range */ |
| t = (time_t) (d / 1000.0); |
| DUK_DDD(DUK_DDDPRINT("timeval: %lf -> time_t %ld", (double) d, (long) t)); |
| |
| DUK_MEMZERO((void *) tms, sizeof(struct tm) * 2); |
| |
| #if defined(DUK_USE_DATE_TZO_GMTIME_R) |
| (void) gmtime_r(&t, &tms[0]); |
| (void) localtime_r(&t, &tms[1]); |
| #elif defined(DUK_USE_DATE_TZO_GMTIME) |
| tm_ptr = gmtime(&t); |
| DUK_MEMCPY((void *) &tms[0], tm_ptr, sizeof(struct tm)); |
| tm_ptr = localtime(&t); |
| DUK_MEMCPY((void *) &tms[1], tm_ptr, sizeof(struct tm)); |
| #else |
| #error internal error |
| #endif |
| DUK_DDD(DUK_DDDPRINT("gmtime result: tm={sec:%ld,min:%ld,hour:%ld,mday:%ld,mon:%ld,year:%ld," |
| "wday:%ld,yday:%ld,isdst:%ld}", |
| (long) tms[0].tm_sec, (long) tms[0].tm_min, (long) tms[0].tm_hour, |
| (long) tms[0].tm_mday, (long) tms[0].tm_mon, (long) tms[0].tm_year, |
| (long) tms[0].tm_wday, (long) tms[0].tm_yday, (long) tms[0].tm_isdst)); |
| DUK_DDD(DUK_DDDPRINT("localtime result: tm={sec:%ld,min:%ld,hour:%ld,mday:%ld,mon:%ld,year:%ld," |
| "wday:%ld,yday:%ld,isdst:%ld}", |
| (long) tms[1].tm_sec, (long) tms[1].tm_min, (long) tms[1].tm_hour, |
| (long) tms[1].tm_mday, (long) tms[1].tm_mon, (long) tms[1].tm_year, |
| (long) tms[1].tm_wday, (long) tms[1].tm_yday, (long) tms[1].tm_isdst)); |
| |
| /* tm_isdst is both an input and an output to mktime(), use 0 to |
| * avoid DST handling in mktime(): |
| * - https://github.com/svaarala/duktape/issues/406 |
| * - http://stackoverflow.com/questions/8558919/mktime-and-tm-isdst |
| */ |
| tms[0].tm_isdst = 0; |
| tms[1].tm_isdst = 0; |
| t1 = mktime(&tms[0]); /* UTC */ |
| t2 = mktime(&tms[1]); /* local */ |
| if (t1 == (time_t) -1 || t2 == (time_t) -1) { |
| /* This check used to be for (t < 0) but on some platforms |
| * time_t is unsigned and apparently the proper way to detect |
| * an mktime() error return is the cast above. See e.g.: |
| * http://pubs.opengroup.org/onlinepubs/009695299/functions/mktime.html |
| */ |
| goto error; |
| } |
| DUK_DDD(DUK_DDDPRINT("t1=%ld (utc), t2=%ld (local)", (long) t1, (long) t2)); |
| |
| /* Compute final offset in seconds, positive if local time ahead of |
| * UTC (returned value is UTC-to-local offset). |
| * |
| * difftime() returns a double, so coercion to int generates quite |
| * a lot of code. Direct subtraction is not portable, however. |
| * XXX: allow direct subtraction on known platforms. |
| */ |
| #if 0 |
| return (duk_int_t) (t2 - t1); |
| #endif |
| return (duk_int_t) difftime(t2, t1); |
| |
| error: |
| /* XXX: return something more useful, so that caller can throw? */ |
| DUK_D(DUK_DPRINT("mktime() failed, d=%lf", (double) d)); |
| return 0; |
| } |
| #endif /* DUK_USE_DATE_TZO_GMTIME */ |
| |
| #if defined(DUK_USE_DATE_PRS_STRPTIME) |
| DUK_INTERNAL duk_bool_t duk_bi_date_parse_string_strptime(duk_context *ctx, const char *str) { |
| struct tm tm; |
| time_t t; |
| char buf[DUK__STRPTIME_BUF_SIZE]; |
| |
| /* copy to buffer with spare to avoid Valgrind gripes from strptime */ |
| DUK_ASSERT(str != NULL); |
| DUK_MEMZERO(buf, sizeof(buf)); /* valgrind whine without this */ |
| DUK_SNPRINTF(buf, sizeof(buf), "%s", (const char *) str); |
| buf[sizeof(buf) - 1] = (char) 0; |
| |
| DUK_DDD(DUK_DDDPRINT("parsing: '%s'", (const char *) buf)); |
| |
| DUK_MEMZERO(&tm, sizeof(tm)); |
| if (strptime((const char *) buf, "%c", &tm) != NULL) { |
| DUK_DDD(DUK_DDDPRINT("before mktime: tm={sec:%ld,min:%ld,hour:%ld,mday:%ld,mon:%ld,year:%ld," |
| "wday:%ld,yday:%ld,isdst:%ld}", |
| (long) tm.tm_sec, (long) tm.tm_min, (long) tm.tm_hour, |
| (long) tm.tm_mday, (long) tm.tm_mon, (long) tm.tm_year, |
| (long) tm.tm_wday, (long) tm.tm_yday, (long) tm.tm_isdst)); |
| tm.tm_isdst = -1; /* negative: dst info not available */ |
| |
| t = mktime(&tm); |
| DUK_DDD(DUK_DDDPRINT("mktime() -> %ld", (long) t)); |
| if (t >= 0) { |
| duk_push_number(ctx, ((duk_double_t) t) * 1000.0); |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| #endif /* DUK_USE_DATE_PRS_STRPTIME */ |
| |
| #if defined(DUK_USE_DATE_PRS_GETDATE) |
| DUK_INTERNAL duk_bool_t duk_bi_date_parse_string_getdate(duk_context *ctx, const char *str) { |
| struct tm tm; |
| duk_small_int_t rc; |
| time_t t; |
| |
| /* For this to work, DATEMSK must be set, so this is not very |
| * convenient for an embeddable interpreter. |
| */ |
| |
| DUK_MEMZERO(&tm, sizeof(struct tm)); |
| rc = (duk_small_int_t) getdate_r(str, &tm); |
| DUK_DDD(DUK_DDDPRINT("getdate_r() -> %ld", (long) rc)); |
| |
| if (rc == 0) { |
| t = mktime(&tm); |
| DUK_DDD(DUK_DDDPRINT("mktime() -> %ld", (long) t)); |
| if (t >= 0) { |
| duk_push_number(ctx, (duk_double_t) t); |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| #endif /* DUK_USE_DATE_PRS_GETDATE */ |
| |
| #if defined(DUK_USE_DATE_FMT_STRFTIME) |
| DUK_INTERNAL duk_bool_t duk_bi_date_format_parts_strftime(duk_context *ctx, duk_int_t *parts, duk_int_t tzoffset, duk_small_uint_t flags) { |
| char buf[DUK__STRFTIME_BUF_SIZE]; |
| struct tm tm; |
| const char *fmt; |
| |
| DUK_UNREF(tzoffset); |
| |
| /* If the platform doesn't support the entire Ecmascript range, we need |
| * to return 0 so that the caller can fall back to the default formatter. |
| * |
| * For now, assume that if time_t is 8 bytes or more, the whole Ecmascript |
| * range is supported. For smaller time_t values (4 bytes in practice), |
| * assumes that the signed 32-bit range is supported. |
| * |
| * XXX: detect this more correctly per platform. The size of time_t is |
| * probably not an accurate guarantee of strftime() supporting or not |
| * supporting a large time range (the full Ecmascript range). |
| */ |
| if (sizeof(time_t) < 8 && |
| (parts[DUK_DATE_IDX_YEAR] < 1970 || parts[DUK_DATE_IDX_YEAR] > 2037)) { |
| /* be paranoid for 32-bit time values (even avoiding negative ones) */ |
| return 0; |
| } |
| |
| DUK_MEMZERO(&tm, sizeof(tm)); |
| tm.tm_sec = parts[DUK_DATE_IDX_SECOND]; |
| tm.tm_min = parts[DUK_DATE_IDX_MINUTE]; |
| tm.tm_hour = parts[DUK_DATE_IDX_HOUR]; |
| tm.tm_mday = parts[DUK_DATE_IDX_DAY]; /* already one-based */ |
| tm.tm_mon = parts[DUK_DATE_IDX_MONTH] - 1; /* one-based -> zero-based */ |
| tm.tm_year = parts[DUK_DATE_IDX_YEAR] - 1900; |
| tm.tm_wday = parts[DUK_DATE_IDX_WEEKDAY]; |
| tm.tm_isdst = 0; |
| |
| DUK_MEMZERO(buf, sizeof(buf)); |
| if ((flags & DUK_DATE_FLAG_TOSTRING_DATE) && (flags & DUK_DATE_FLAG_TOSTRING_TIME)) { |
| fmt = "%c"; |
| } else if (flags & DUK_DATE_FLAG_TOSTRING_DATE) { |
| fmt = "%x"; |
| } else { |
| DUK_ASSERT(flags & DUK_DATE_FLAG_TOSTRING_TIME); |
| fmt = "%X"; |
| } |
| (void) strftime(buf, sizeof(buf) - 1, fmt, &tm); |
| DUK_ASSERT(buf[sizeof(buf) - 1] == 0); |
| |
| duk_push_string(ctx, buf); |
| return 1; |
| } |
| #endif /* DUK_USE_DATE_FMT_STRFTIME */ |
| |
| #undef DUK__STRPTIME_BUF_SIZE |
| #undef DUK__STRFTIME_BUF_SIZE |