2 * Unix-like Date providers
4 * Generally useful Unix / POSIX / ANSI Date providers.
7 #include "duk_internal.h"
9 /* The necessary #includes are in place in duk_config.h. */
11 /* Buffer sizes for some UNIX calls. Larger than strictly necessary
12 * to avoid Valgrind errors.
14 #define DUK__STRPTIME_BUF_SIZE 64
15 #define DUK__STRFTIME_BUF_SIZE 64
17 #if defined(DUK_USE_DATE_NOW_GETTIMEOFDAY)
18 /* Get current Ecmascript time (= UNIX/Posix time, but in milliseconds). */
19 DUK_INTERNAL duk_double_t
duk_bi_date_get_now_gettimeofday(duk_context
*ctx
) {
20 duk_hthread
*thr
= (duk_hthread
*) ctx
;
24 if (gettimeofday(&tv
, NULL
) != 0) {
25 DUK_ERROR_INTERNAL_DEFMSG(thr
);
28 d
= ((duk_double_t
) tv
.tv_sec
) * 1000.0 +
29 ((duk_double_t
) (tv
.tv_usec
/ 1000));
30 DUK_ASSERT(DUK_FLOOR(d
) == d
); /* no fractions */
34 #endif /* DUK_USE_DATE_NOW_GETTIMEOFDAY */
36 #if defined(DUK_USE_DATE_NOW_TIME)
37 /* Not a very good provider: only full seconds are available. */
38 DUK_INTERNAL duk_double_t
duk_bi_date_get_now_time(duk_context
*ctx
) {
43 return ((duk_double_t
) t
) * 1000.0;
45 #endif /* DUK_USE_DATE_NOW_TIME */
47 #if defined(DUK_USE_DATE_TZO_GMTIME) || defined(DUK_USE_DATE_TZO_GMTIME_R)
48 /* Get local time offset (in seconds) for a certain (UTC) instant 'd'. */
49 DUK_INTERNAL duk_int_t
duk_bi_date_get_local_tzoffset_gmtime(duk_double_t d
) {
51 duk_int_t parts
[DUK_DATE_IDX_NUM_PARTS
];
52 duk_double_t dparts
[DUK_DATE_IDX_NUM_PARTS
];
54 #ifdef DUK_USE_DATE_TZO_GMTIME
58 /* For NaN/inf, the return value doesn't matter. */
59 if (!DUK_ISFINITE(d
)) {
63 /* If not within Ecmascript range, some integer time calculations
64 * won't work correctly (and some asserts will fail), so bail out
65 * if so. This fixes test-bug-date-insane-setyear.js. There is
66 * a +/- 24h leeway in this range check to avoid a test262 corner
67 * case documented in test-bug-date-timeval-edges.js.
69 if (!duk_bi_date_timeval_in_leeway_range(d
)) {
70 DUK_DD(DUK_DDPRINT("timeval not within valid range, skip tzoffset computation to avoid integer overflows"));
75 * This is a bit tricky to implement portably. The result depends
76 * on the timestamp (specifically, DST depends on the timestamp).
77 * If e.g. UNIX APIs are used, they'll have portability issues with
78 * very small and very large years.
82 * - Stay within portable UNIX limits by using equivalent year mapping.
83 * Avoid year 1970 and 2038 as some conversions start to fail, at
84 * least on some platforms. Avoiding 1970 means that there are
85 * currently DST discrepancies for 1970.
87 * - Create a UTC and local time breakdowns from 't'. Then create
88 * a time_t using gmtime() and localtime() and compute the time
89 * difference between the two.
91 * Equivalent year mapping (E5 Section 15.9.1.8):
93 * If the host environment provides functionality for determining
94 * daylight saving time, the implementation of ECMAScript is free
95 * to map the year in question to an equivalent year (same
96 * leap-year-ness and same starting week day for the year) for which
97 * the host environment provides daylight saving time information.
98 * The only restriction is that all equivalent years should produce
101 * This approach is quite reasonable but not entirely correct, e.g.
102 * the specification also states (E5 Section 15.9.1.8):
104 * The implementation of ECMAScript should not try to determine
105 * whether the exact time was subject to daylight saving time, but
106 * just whether daylight saving time would have been in effect if
107 * the _current daylight saving time algorithm_ had been used at the
108 * time. This avoids complications such as taking into account the
109 * years that the locale observed daylight saving time year round.
111 * Since we rely on the platform APIs for conversions between local
112 * time and UTC, we can't guarantee the above. Rather, if the platform
113 * has historical DST rules they will be applied. This seems to be the
114 * general preferred direction in Ecmascript standardization (or at least
115 * implementations) anyway, and even the equivalent year mapping should
116 * be disabled if the platform is known to handle DST properly for the
117 * full Ecmascript range.
119 * The following has useful discussion and links:
121 * https://bugzilla.mozilla.org/show_bug.cgi?id=351066
124 duk_bi_date_timeval_to_parts(d
, parts
, dparts
, DUK_DATE_FLAG_EQUIVYEAR
/*flags*/);
125 DUK_ASSERT(parts
[DUK_DATE_IDX_YEAR
] >= 1970 && parts
[DUK_DATE_IDX_YEAR
] <= 2038);
127 d
= duk_bi_date_get_timeval_from_dparts(dparts
, 0 /*flags*/);
128 DUK_ASSERT(d
>= 0 && d
< 2147483648.0 * 1000.0); /* unsigned 31-bit range */
129 t
= (time_t) (d
/ 1000.0);
130 DUK_DDD(DUK_DDDPRINT("timeval: %lf -> time_t %ld", (double) d
, (long) t
));
132 DUK_MEMZERO((void *) tms
, sizeof(struct tm
) * 2);
134 #if defined(DUK_USE_DATE_TZO_GMTIME_R)
135 (void) gmtime_r(&t
, &tms
[0]);
136 (void) localtime_r(&t
, &tms
[1]);
137 #elif defined(DUK_USE_DATE_TZO_GMTIME)
139 DUK_MEMCPY((void *) &tms
[0], tm_ptr
, sizeof(struct tm
));
140 tm_ptr
= localtime(&t
);
141 DUK_MEMCPY((void *) &tms
[1], tm_ptr
, sizeof(struct tm
));
143 #error internal error
145 DUK_DDD(DUK_DDDPRINT("gmtime result: tm={sec:%ld,min:%ld,hour:%ld,mday:%ld,mon:%ld,year:%ld,"
146 "wday:%ld,yday:%ld,isdst:%ld}",
147 (long) tms
[0].tm_sec
, (long) tms
[0].tm_min
, (long) tms
[0].tm_hour
,
148 (long) tms
[0].tm_mday
, (long) tms
[0].tm_mon
, (long) tms
[0].tm_year
,
149 (long) tms
[0].tm_wday
, (long) tms
[0].tm_yday
, (long) tms
[0].tm_isdst
));
150 DUK_DDD(DUK_DDDPRINT("localtime result: tm={sec:%ld,min:%ld,hour:%ld,mday:%ld,mon:%ld,year:%ld,"
151 "wday:%ld,yday:%ld,isdst:%ld}",
152 (long) tms
[1].tm_sec
, (long) tms
[1].tm_min
, (long) tms
[1].tm_hour
,
153 (long) tms
[1].tm_mday
, (long) tms
[1].tm_mon
, (long) tms
[1].tm_year
,
154 (long) tms
[1].tm_wday
, (long) tms
[1].tm_yday
, (long) tms
[1].tm_isdst
));
156 /* tm_isdst is both an input and an output to mktime(), use 0 to
157 * avoid DST handling in mktime():
158 * - https://github.com/svaarala/duktape/issues/406
159 * - http://stackoverflow.com/questions/8558919/mktime-and-tm-isdst
163 t1
= mktime(&tms
[0]); /* UTC */
164 t2
= mktime(&tms
[1]); /* local */
165 if (t1
== (time_t) -1 || t2
== (time_t) -1) {
166 /* This check used to be for (t < 0) but on some platforms
167 * time_t is unsigned and apparently the proper way to detect
168 * an mktime() error return is the cast above. See e.g.:
169 * http://pubs.opengroup.org/onlinepubs/009695299/functions/mktime.html
173 DUK_DDD(DUK_DDDPRINT("t1=%ld (utc), t2=%ld (local)", (long) t1
, (long) t2
));
175 /* Compute final offset in seconds, positive if local time ahead of
176 * UTC (returned value is UTC-to-local offset).
178 * difftime() returns a double, so coercion to int generates quite
179 * a lot of code. Direct subtraction is not portable, however.
180 * XXX: allow direct subtraction on known platforms.
183 return (duk_int_t
) (t2
- t1
);
185 return (duk_int_t
) difftime(t2
, t1
);
188 /* XXX: return something more useful, so that caller can throw? */
189 DUK_D(DUK_DPRINT("mktime() failed, d=%lf", (double) d
));
192 #endif /* DUK_USE_DATE_TZO_GMTIME */
194 #if defined(DUK_USE_DATE_PRS_STRPTIME)
195 DUK_INTERNAL duk_bool_t
duk_bi_date_parse_string_strptime(duk_context
*ctx
, const char *str
) {
198 char buf
[DUK__STRPTIME_BUF_SIZE
];
200 /* copy to buffer with spare to avoid Valgrind gripes from strptime */
201 DUK_ASSERT(str
!= NULL
);
202 DUK_MEMZERO(buf
, sizeof(buf
)); /* valgrind whine without this */
203 DUK_SNPRINTF(buf
, sizeof(buf
), "%s", (const char *) str
);
204 buf
[sizeof(buf
) - 1] = (char) 0;
206 DUK_DDD(DUK_DDDPRINT("parsing: '%s'", (const char *) buf
));
208 DUK_MEMZERO(&tm
, sizeof(tm
));
209 if (strptime((const char *) buf
, "%c", &tm
) != NULL
) {
210 DUK_DDD(DUK_DDDPRINT("before mktime: tm={sec:%ld,min:%ld,hour:%ld,mday:%ld,mon:%ld,year:%ld,"
211 "wday:%ld,yday:%ld,isdst:%ld}",
212 (long) tm
.tm_sec
, (long) tm
.tm_min
, (long) tm
.tm_hour
,
213 (long) tm
.tm_mday
, (long) tm
.tm_mon
, (long) tm
.tm_year
,
214 (long) tm
.tm_wday
, (long) tm
.tm_yday
, (long) tm
.tm_isdst
));
215 tm
.tm_isdst
= -1; /* negative: dst info not available */
218 DUK_DDD(DUK_DDDPRINT("mktime() -> %ld", (long) t
));
220 duk_push_number(ctx
, ((duk_double_t
) t
) * 1000.0);
227 #endif /* DUK_USE_DATE_PRS_STRPTIME */
229 #if defined(DUK_USE_DATE_PRS_GETDATE)
230 DUK_INTERNAL duk_bool_t
duk_bi_date_parse_string_getdate(duk_context
*ctx
, const char *str
) {
235 /* For this to work, DATEMSK must be set, so this is not very
236 * convenient for an embeddable interpreter.
239 DUK_MEMZERO(&tm
, sizeof(struct tm
));
240 rc
= (duk_small_int_t
) getdate_r(str
, &tm
);
241 DUK_DDD(DUK_DDDPRINT("getdate_r() -> %ld", (long) rc
));
245 DUK_DDD(DUK_DDDPRINT("mktime() -> %ld", (long) t
));
247 duk_push_number(ctx
, (duk_double_t
) t
);
254 #endif /* DUK_USE_DATE_PRS_GETDATE */
256 #if defined(DUK_USE_DATE_FMT_STRFTIME)
257 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
) {
258 char buf
[DUK__STRFTIME_BUF_SIZE
];
264 /* If the platform doesn't support the entire Ecmascript range, we need
265 * to return 0 so that the caller can fall back to the default formatter.
267 * For now, assume that if time_t is 8 bytes or more, the whole Ecmascript
268 * range is supported. For smaller time_t values (4 bytes in practice),
269 * assumes that the signed 32-bit range is supported.
271 * XXX: detect this more correctly per platform. The size of time_t is
272 * probably not an accurate guarantee of strftime() supporting or not
273 * supporting a large time range (the full Ecmascript range).
275 if (sizeof(time_t) < 8 &&
276 (parts
[DUK_DATE_IDX_YEAR
] < 1970 || parts
[DUK_DATE_IDX_YEAR
] > 2037)) {
277 /* be paranoid for 32-bit time values (even avoiding negative ones) */
281 DUK_MEMZERO(&tm
, sizeof(tm
));
282 tm
.tm_sec
= parts
[DUK_DATE_IDX_SECOND
];
283 tm
.tm_min
= parts
[DUK_DATE_IDX_MINUTE
];
284 tm
.tm_hour
= parts
[DUK_DATE_IDX_HOUR
];
285 tm
.tm_mday
= parts
[DUK_DATE_IDX_DAY
]; /* already one-based */
286 tm
.tm_mon
= parts
[DUK_DATE_IDX_MONTH
] - 1; /* one-based -> zero-based */
287 tm
.tm_year
= parts
[DUK_DATE_IDX_YEAR
] - 1900;
288 tm
.tm_wday
= parts
[DUK_DATE_IDX_WEEKDAY
];
291 DUK_MEMZERO(buf
, sizeof(buf
));
292 if ((flags
& DUK_DATE_FLAG_TOSTRING_DATE
) && (flags
& DUK_DATE_FLAG_TOSTRING_TIME
)) {
294 } else if (flags
& DUK_DATE_FLAG_TOSTRING_DATE
) {
297 DUK_ASSERT(flags
& DUK_DATE_FLAG_TOSTRING_TIME
);
300 (void) strftime(buf
, sizeof(buf
) - 1, fmt
, &tm
);
301 DUK_ASSERT(buf
[sizeof(buf
) - 1] == 0);
303 duk_push_string(ctx
, buf
);
306 #endif /* DUK_USE_DATE_FMT_STRFTIME */
308 #undef DUK__STRPTIME_BUF_SIZE
309 #undef DUK__STRFTIME_BUF_SIZE