DUK_EXTERNAL duk_double_t duk_components_to_time(duk_context *ctx, duk_time_components *comp) { duk_double_t d; duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS]; duk_uint_t flags; DUK_ASSERT(ctx != NULL); DUK_ASSERT(comp != NULL); /* XXX: or check? */ DUK_UNREF(ctx); /* Match Date constructor behavior (with UTC time). Month is given * as zero-based. Day-of-month is given as one-based so normalize * it to zero-based as the internal conversion helpers expects all * components to be zero-based. */ flags = 0; /* XXX: expensive conversion; use array format in API instead, or unify * time provider and time API to use same struct? */ dparts[DUK_DATE_IDX_YEAR] = comp->year; dparts[DUK_DATE_IDX_MONTH] = comp->month; dparts[DUK_DATE_IDX_DAY] = comp->day - 1.0; dparts[DUK_DATE_IDX_HOUR] = comp->hours; dparts[DUK_DATE_IDX_MINUTE] = comp->minutes; dparts[DUK_DATE_IDX_SECOND] = comp->seconds; dparts[DUK_DATE_IDX_MILLISECOND] = comp->milliseconds; dparts[DUK_DATE_IDX_WEEKDAY] = 0; /* ignored */ d = duk_bi_date_get_timeval_from_dparts(dparts, flags); return d; }
/* 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)); t1 = 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; }