1 /*
2  *  Unix-like Date providers
3  *
4  *  Generally useful Unix / POSIX / ANSI Date providers.
5  */
6 
7 #include "duk_internal.h"
8 
9 /* The necessary #includes are in place in duk_config.h. */
10 
11 /* Buffer sizes for some UNIX calls.  Larger than strictly necessary
12  * to avoid Valgrind errors.
13  */
14 #define DUK__STRPTIME_BUF_SIZE  64
15 #define DUK__STRFTIME_BUF_SIZE  64
16 
17 #if defined(DUK_USE_DATE_NOW_GETTIMEOFDAY)
18 /* Get current Ecmascript time (= UNIX/Posix time, but in milliseconds). */
duk_bi_date_get_now_gettimeofday(duk_context * ctx)19 DUK_INTERNAL duk_double_t duk_bi_date_get_now_gettimeofday(duk_context *ctx) {
20 	duk_hthread *thr = (duk_hthread *) ctx;
21 	struct timeval tv;
22 	duk_double_t d;
23 
24 	if (gettimeofday(&tv, NULL) != 0) {
25 		DUK_ERROR_INTERNAL_DEFMSG(thr);
26 	}
27 
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 */
31 
32 	return d;
33 }
34 #endif  /* DUK_USE_DATE_NOW_GETTIMEOFDAY */
35 
36 #if defined(DUK_USE_DATE_NOW_TIME)
37 /* Not a very good provider: only full seconds are available. */
duk_bi_date_get_now_time(duk_context * ctx)38 DUK_INTERNAL duk_double_t duk_bi_date_get_now_time(duk_context *ctx) {
39 	time_t t;
40 
41 	DUK_UNREF(ctx);
42 	t = time(NULL);
43 	return ((duk_double_t) t) * 1000.0;
44 }
45 #endif  /* DUK_USE_DATE_NOW_TIME */
46 
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'. */
duk_bi_date_get_local_tzoffset_gmtime(duk_double_t d)49 DUK_INTERNAL duk_int_t duk_bi_date_get_local_tzoffset_gmtime(duk_double_t d) {
50 	time_t t, t1, t2;
51 	duk_int_t parts[DUK_DATE_IDX_NUM_PARTS];
52 	duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS];
53 	struct tm tms[2];
54 #ifdef DUK_USE_DATE_TZO_GMTIME
55 	struct tm *tm_ptr;
56 #endif
57 
58 	/* For NaN/inf, the return value doesn't matter. */
59 	if (!DUK_ISFINITE(d)) {
60 		return 0;
61 	}
62 
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.
68 	 */
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"));
71 		return 0;
72 	}
73 
74 	/*
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.
79 	 *
80 	 *  Current approach:
81 	 *
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.
86 	 *
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.
90 	 *
91 	 *  Equivalent year mapping (E5 Section 15.9.1.8):
92 	 *
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
99 	 *    the same result.
100 	 *
101 	 *  This approach is quite reasonable but not entirely correct, e.g.
102 	 *  the specification also states (E5 Section 15.9.1.8):
103 	 *
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.
110 	 *
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.
118 	 *
119 	 *  The following has useful discussion and links:
120 	 *
121 	 *    https://bugzilla.mozilla.org/show_bug.cgi?id=351066
122 	 */
123 
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);
126 
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));
131 
132 	DUK_MEMZERO((void *) tms, sizeof(struct tm) * 2);
133 
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)
138 	tm_ptr = gmtime(&t);
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));
142 #else
143 #error internal error
144 #endif
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));
155 
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
160 	 */
161 	tms[0].tm_isdst = 0;
162 	tms[1].tm_isdst = 0;
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
170 		 */
171 		goto error;
172 	}
173 	DUK_DDD(DUK_DDDPRINT("t1=%ld (utc), t2=%ld (local)", (long) t1, (long) t2));
174 
175 	/* Compute final offset in seconds, positive if local time ahead of
176 	 * UTC (returned value is UTC-to-local offset).
177 	 *
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.
181 	 */
182 #if 0
183 	return (duk_int_t) (t2 - t1);
184 #endif
185 	return (duk_int_t) difftime(t2, t1);
186 
187  error:
188 	/* XXX: return something more useful, so that caller can throw? */
189 	DUK_D(DUK_DPRINT("mktime() failed, d=%lf", (double) d));
190 	return 0;
191 }
192 #endif  /* DUK_USE_DATE_TZO_GMTIME */
193 
194 #if defined(DUK_USE_DATE_PRS_STRPTIME)
duk_bi_date_parse_string_strptime(duk_context * ctx,const char * str)195 DUK_INTERNAL duk_bool_t duk_bi_date_parse_string_strptime(duk_context *ctx, const char *str) {
196 	struct tm tm;
197 	time_t t;
198 	char buf[DUK__STRPTIME_BUF_SIZE];
199 
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;
205 
206 	DUK_DDD(DUK_DDDPRINT("parsing: '%s'", (const char *) buf));
207 
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 */
216 
217 		t = mktime(&tm);
218 		DUK_DDD(DUK_DDDPRINT("mktime() -> %ld", (long) t));
219 		if (t >= 0) {
220 			duk_push_number(ctx, ((duk_double_t) t) * 1000.0);
221 			return 1;
222 		}
223 	}
224 
225 	return 0;
226 }
227 #endif  /* DUK_USE_DATE_PRS_STRPTIME */
228 
229 #if defined(DUK_USE_DATE_PRS_GETDATE)
duk_bi_date_parse_string_getdate(duk_context * ctx,const char * str)230 DUK_INTERNAL duk_bool_t duk_bi_date_parse_string_getdate(duk_context *ctx, const char *str) {
231 	struct tm tm;
232 	duk_small_int_t rc;
233 	time_t t;
234 
235 	/* For this to work, DATEMSK must be set, so this is not very
236 	 * convenient for an embeddable interpreter.
237 	 */
238 
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));
242 
243 	if (rc == 0) {
244 		t = mktime(&tm);
245 		DUK_DDD(DUK_DDDPRINT("mktime() -> %ld", (long) t));
246 		if (t >= 0) {
247 			duk_push_number(ctx, (duk_double_t) t);
248 			return 1;
249 		}
250 	}
251 
252 	return 0;
253 }
254 #endif  /* DUK_USE_DATE_PRS_GETDATE */
255 
256 #if defined(DUK_USE_DATE_FMT_STRFTIME)
duk_bi_date_format_parts_strftime(duk_context * ctx,duk_int_t * parts,duk_int_t tzoffset,duk_small_uint_t flags)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];
259 	struct tm tm;
260 	const char *fmt;
261 
262 	DUK_UNREF(tzoffset);
263 
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.
266 	 *
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.
270 	 *
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).
274 	 */
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) */
278 		return 0;
279 	}
280 
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];
289 	tm.tm_isdst = 0;
290 
291 	DUK_MEMZERO(buf, sizeof(buf));
292 	if ((flags & DUK_DATE_FLAG_TOSTRING_DATE) && (flags & DUK_DATE_FLAG_TOSTRING_TIME)) {
293 		fmt = "%c";
294 	} else if (flags & DUK_DATE_FLAG_TOSTRING_DATE) {
295 		fmt = "%x";
296 	} else {
297 		DUK_ASSERT(flags & DUK_DATE_FLAG_TOSTRING_TIME);
298 		fmt = "%X";
299 	}
300 	(void) strftime(buf, sizeof(buf) - 1, fmt, &tm);
301 	DUK_ASSERT(buf[sizeof(buf) - 1] == 0);
302 
303 	duk_push_string(ctx, buf);
304 	return 1;
305 }
306 #endif  /* DUK_USE_DATE_FMT_STRFTIME */
307 
308 #undef DUK__STRPTIME_BUF_SIZE
309 #undef DUK__STRFTIME_BUF_SIZE
310