]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | /* |
2 | * Date built-ins | |
3 | * | |
4 | * Unlike most built-ins, Date has some platform dependencies for getting | |
5 | * UTC time, converting between UTC and local time, and parsing and | |
6 | * formatting time values. These are all abstracted behind DUK_USE_xxx | |
7 | * config options. There are built-in platform specific providers for | |
8 | * POSIX and Windows, but external providers can also be used. | |
9 | * | |
10 | * See doc/datetime.rst. | |
11 | * | |
12 | */ | |
13 | ||
14 | #include "duk_internal.h" | |
15 | ||
16 | /* | |
17 | * Forward declarations | |
18 | */ | |
19 | ||
20 | DUK_LOCAL_DECL duk_double_t duk__push_this_get_timeval_tzoffset(duk_context *ctx, duk_small_uint_t flags, duk_int_t *out_tzoffset); | |
21 | DUK_LOCAL_DECL duk_double_t duk__push_this_get_timeval(duk_context *ctx, duk_small_uint_t flags); | |
22 | DUK_LOCAL_DECL void duk__twodigit_year_fixup(duk_context *ctx, duk_idx_t idx_val); | |
23 | DUK_LOCAL_DECL duk_ret_t duk__set_this_timeval_from_dparts(duk_context *ctx, duk_double_t *dparts, duk_small_uint_t flags); | |
24 | ||
25 | /* | |
26 | * Other file level defines | |
27 | */ | |
28 | ||
29 | /* Debug macro to print all parts and dparts (used manually because of debug level). */ | |
30 | #define DUK__DPRINT_PARTS_AND_DPARTS(parts,dparts) do { \ | |
31 | DUK_D(DUK_DPRINT("parts: %ld %ld %ld %ld %ld %ld %ld %ld, dparts: %lf %lf %lf %lf %lf %lf %lf %lf", \ | |
32 | (long) (parts)[0], (long) (parts)[1], \ | |
33 | (long) (parts)[2], (long) (parts)[3], \ | |
34 | (long) (parts)[4], (long) (parts)[5], \ | |
35 | (long) (parts)[6], (long) (parts)[7], \ | |
36 | (double) (dparts)[0], (double) (dparts)[1], \ | |
37 | (double) (dparts)[2], (double) (dparts)[3], \ | |
38 | (double) (dparts)[4], (double) (dparts)[5], \ | |
39 | (double) (dparts)[6], (double) (dparts)[7])); \ | |
40 | } while (0) | |
41 | #define DUK__DPRINT_PARTS(parts) do { \ | |
42 | DUK_D(DUK_DPRINT("parts: %ld %ld %ld %ld %ld %ld %ld %ld", \ | |
43 | (long) (parts)[0], (long) (parts)[1], \ | |
44 | (long) (parts)[2], (long) (parts)[3], \ | |
45 | (long) (parts)[4], (long) (parts)[5], \ | |
46 | (long) (parts)[6], (long) (parts)[7])); \ | |
47 | } while (0) | |
48 | #define DUK__DPRINT_DPARTS(dparts) do { \ | |
49 | DUK_D(DUK_DPRINT("dparts: %lf %lf %lf %lf %lf %lf %lf %lf", \ | |
50 | (double) (dparts)[0], (double) (dparts)[1], \ | |
51 | (double) (dparts)[2], (double) (dparts)[3], \ | |
52 | (double) (dparts)[4], (double) (dparts)[5], \ | |
53 | (double) (dparts)[6], (double) (dparts)[7])); \ | |
54 | } while (0) | |
55 | ||
56 | /* Equivalent year for DST calculations outside [1970,2038[ range, see | |
57 | * E5 Section 15.9.1.8. Equivalent year has the same leap-year-ness and | |
58 | * starts with the same weekday on Jan 1. | |
59 | * https://bugzilla.mozilla.org/show_bug.cgi?id=351066 | |
60 | */ | |
61 | #define DUK__YEAR(x) ((duk_uint8_t) ((x) - 1970)) | |
62 | DUK_LOCAL duk_uint8_t duk__date_equivyear[14] = { | |
63 | #if 1 | |
64 | /* This is based on V8 EquivalentYear() algorithm (see src/genequivyear.py): | |
65 | * http://code.google.com/p/v8/source/browse/trunk/src/date.h#146 | |
66 | */ | |
67 | ||
68 | /* non-leap year: sunday, monday, ... */ | |
69 | DUK__YEAR(2023), DUK__YEAR(2035), DUK__YEAR(2019), DUK__YEAR(2031), | |
70 | DUK__YEAR(2015), DUK__YEAR(2027), DUK__YEAR(2011), | |
71 | ||
72 | /* leap year: sunday, monday, ... */ | |
73 | DUK__YEAR(2012), DUK__YEAR(2024), DUK__YEAR(2008), DUK__YEAR(2020), | |
74 | DUK__YEAR(2032), DUK__YEAR(2016), DUK__YEAR(2028) | |
75 | #endif | |
76 | ||
77 | #if 0 | |
78 | /* This is based on Rhino EquivalentYear() algorithm: | |
79 | * https://github.com/mozilla/rhino/blob/f99cc11d616f0cdda2c42bde72b3484df6182947/src/org/mozilla/javascript/NativeDate.java | |
80 | */ | |
81 | ||
82 | /* non-leap year: sunday, monday, ... */ | |
83 | DUK__YEAR(1978), DUK__YEAR(1973), DUK__YEAR(1985), DUK__YEAR(1986), | |
84 | DUK__YEAR(1981), DUK__YEAR(1971), DUK__YEAR(1977), | |
85 | ||
86 | /* leap year: sunday, monday, ... */ | |
87 | DUK__YEAR(1984), DUK__YEAR(1996), DUK__YEAR(1980), DUK__YEAR(1992), | |
88 | DUK__YEAR(1976), DUK__YEAR(1988), DUK__YEAR(1972) | |
89 | #endif | |
90 | }; | |
91 | #undef DUK__YEAR | |
92 | ||
93 | /* | |
94 | * ISO 8601 subset parser. | |
95 | */ | |
96 | ||
97 | /* Parser part count. */ | |
98 | #define DUK__NUM_ISO8601_PARSER_PARTS 9 | |
99 | ||
100 | /* Parser part indices. */ | |
101 | #define DUK__PI_YEAR 0 | |
102 | #define DUK__PI_MONTH 1 | |
103 | #define DUK__PI_DAY 2 | |
104 | #define DUK__PI_HOUR 3 | |
105 | #define DUK__PI_MINUTE 4 | |
106 | #define DUK__PI_SECOND 5 | |
107 | #define DUK__PI_MILLISECOND 6 | |
108 | #define DUK__PI_TZHOUR 7 | |
109 | #define DUK__PI_TZMINUTE 8 | |
110 | ||
111 | /* Parser part masks. */ | |
112 | #define DUK__PM_YEAR (1 << DUK__PI_YEAR) | |
113 | #define DUK__PM_MONTH (1 << DUK__PI_MONTH) | |
114 | #define DUK__PM_DAY (1 << DUK__PI_DAY) | |
115 | #define DUK__PM_HOUR (1 << DUK__PI_HOUR) | |
116 | #define DUK__PM_MINUTE (1 << DUK__PI_MINUTE) | |
117 | #define DUK__PM_SECOND (1 << DUK__PI_SECOND) | |
118 | #define DUK__PM_MILLISECOND (1 << DUK__PI_MILLISECOND) | |
119 | #define DUK__PM_TZHOUR (1 << DUK__PI_TZHOUR) | |
120 | #define DUK__PM_TZMINUTE (1 << DUK__PI_TZMINUTE) | |
121 | ||
122 | /* Parser separator indices. */ | |
123 | #define DUK__SI_PLUS 0 | |
124 | #define DUK__SI_MINUS 1 | |
125 | #define DUK__SI_T 2 | |
126 | #define DUK__SI_SPACE 3 | |
127 | #define DUK__SI_COLON 4 | |
128 | #define DUK__SI_PERIOD 5 | |
129 | #define DUK__SI_Z 6 | |
130 | #define DUK__SI_NUL 7 | |
131 | ||
132 | /* Parser separator masks. */ | |
133 | #define DUK__SM_PLUS (1 << DUK__SI_PLUS) | |
134 | #define DUK__SM_MINUS (1 << DUK__SI_MINUS) | |
135 | #define DUK__SM_T (1 << DUK__SI_T) | |
136 | #define DUK__SM_SPACE (1 << DUK__SI_SPACE) | |
137 | #define DUK__SM_COLON (1 << DUK__SI_COLON) | |
138 | #define DUK__SM_PERIOD (1 << DUK__SI_PERIOD) | |
139 | #define DUK__SM_Z (1 << DUK__SI_Z) | |
140 | #define DUK__SM_NUL (1 << DUK__SI_NUL) | |
141 | ||
142 | /* Rule control flags. */ | |
143 | #define DUK__CF_NEG (1 << 0) /* continue matching, set neg_tzoffset flag */ | |
144 | #define DUK__CF_ACCEPT (1 << 1) /* accept string */ | |
145 | #define DUK__CF_ACCEPT_NUL (1 << 2) /* accept string if next char is NUL (otherwise reject) */ | |
146 | ||
147 | #define DUK__PACK_RULE(partmask,sepmask,nextpart,flags) \ | |
148 | ((duk_uint32_t) (partmask) + \ | |
149 | (((duk_uint32_t) (sepmask)) << 9) + \ | |
150 | (((duk_uint32_t) (nextpart)) << 17) + \ | |
151 | (((duk_uint32_t) (flags)) << 21)) | |
152 | ||
153 | #define DUK__UNPACK_RULE(rule,var_nextidx,var_flags) do { \ | |
154 | (var_nextidx) = (duk_small_uint_t) (((rule) >> 17) & 0x0f); \ | |
155 | (var_flags) = (duk_small_uint_t) ((rule) >> 21); \ | |
156 | } while (0) | |
157 | ||
158 | #define DUK__RULE_MASK_PART_SEP 0x1ffffUL | |
159 | ||
160 | /* Matching separator index is used in the control table */ | |
161 | DUK_LOCAL const duk_uint8_t duk__parse_iso8601_seps[] = { | |
162 | DUK_ASC_PLUS /*0*/, DUK_ASC_MINUS /*1*/, DUK_ASC_UC_T /*2*/, DUK_ASC_SPACE /*3*/, | |
163 | DUK_ASC_COLON /*4*/, DUK_ASC_PERIOD /*5*/, DUK_ASC_UC_Z /*6*/, DUK_ASC_NUL /*7*/ | |
164 | }; | |
165 | ||
166 | /* Rule table: first matching rule is used to determine what to do next. */ | |
167 | DUK_LOCAL const duk_uint32_t duk__parse_iso8601_control[] = { | |
168 | DUK__PACK_RULE(DUK__PM_YEAR, DUK__SM_MINUS, DUK__PI_MONTH, 0), | |
169 | DUK__PACK_RULE(DUK__PM_MONTH, DUK__SM_MINUS, DUK__PI_DAY, 0), | |
170 | DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY, DUK__SM_T | DUK__SM_SPACE, DUK__PI_HOUR, 0), | |
171 | DUK__PACK_RULE(DUK__PM_HOUR, DUK__SM_COLON, DUK__PI_MINUTE, 0), | |
172 | DUK__PACK_RULE(DUK__PM_MINUTE, DUK__SM_COLON, DUK__PI_SECOND, 0), | |
173 | DUK__PACK_RULE(DUK__PM_SECOND, DUK__SM_PERIOD, DUK__PI_MILLISECOND, 0), | |
174 | DUK__PACK_RULE(DUK__PM_TZHOUR, DUK__SM_COLON, DUK__PI_TZMINUTE, 0), | |
175 | DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY | DUK__PM_HOUR /*Note1*/ | DUK__PM_MINUTE | DUK__PM_SECOND | DUK__PM_MILLISECOND, DUK__SM_PLUS, DUK__PI_TZHOUR, 0), | |
176 | DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY | DUK__PM_HOUR /*Note1*/ | DUK__PM_MINUTE | DUK__PM_SECOND | DUK__PM_MILLISECOND, DUK__SM_MINUS, DUK__PI_TZHOUR, DUK__CF_NEG), | |
177 | DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY | DUK__PM_HOUR /*Note1*/ | DUK__PM_MINUTE | DUK__PM_SECOND | DUK__PM_MILLISECOND, DUK__SM_Z, 0, DUK__CF_ACCEPT_NUL), | |
178 | DUK__PACK_RULE(DUK__PM_YEAR | DUK__PM_MONTH | DUK__PM_DAY | DUK__PM_HOUR /*Note1*/ | DUK__PM_MINUTE | DUK__PM_SECOND | DUK__PM_MILLISECOND | DUK__PM_TZHOUR /*Note2*/ | DUK__PM_TZMINUTE, DUK__SM_NUL, 0, DUK__CF_ACCEPT) | |
179 | ||
180 | /* Note1: the specification doesn't require matching a time form with | |
181 | * just hours ("HH"), but we accept it here, e.g. "2012-01-02T12Z". | |
182 | * | |
183 | * Note2: the specification doesn't require matching a timezone offset | |
184 | * with just hours ("HH"), but accept it here, e.g. "2012-01-02T03:04:05+02" | |
185 | */ | |
186 | }; | |
187 | ||
188 | DUK_LOCAL duk_bool_t duk__parse_string_iso8601_subset(duk_context *ctx, const char *str) { | |
189 | duk_int_t parts[DUK__NUM_ISO8601_PARSER_PARTS]; | |
190 | duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS]; | |
191 | duk_double_t d; | |
192 | const duk_uint8_t *p; | |
193 | duk_small_uint_t part_idx = 0; | |
194 | duk_int_t accum = 0; | |
195 | duk_small_uint_t ndigits = 0; | |
196 | duk_bool_t neg_year = 0; | |
197 | duk_bool_t neg_tzoffset = 0; | |
198 | duk_uint_fast8_t ch; | |
199 | duk_small_uint_t i; | |
200 | ||
201 | /* During parsing, month and day are one-based; set defaults here. */ | |
202 | DUK_MEMZERO(parts, sizeof(parts)); | |
203 | DUK_ASSERT(parts[DUK_DATE_IDX_YEAR] == 0); /* don't care value, year is mandatory */ | |
204 | parts[DUK_DATE_IDX_MONTH] = 1; | |
205 | parts[DUK_DATE_IDX_DAY] = 1; | |
206 | ||
207 | /* Special handling for year sign. */ | |
208 | p = (const duk_uint8_t *) str; | |
209 | ch = p[0]; | |
210 | if (ch == DUK_ASC_PLUS) { | |
211 | p++; | |
212 | } else if (ch == DUK_ASC_MINUS) { | |
213 | neg_year = 1; | |
214 | p++; | |
215 | } | |
216 | ||
217 | for (;;) { | |
218 | ch = *p++; | |
219 | DUK_DDD(DUK_DDDPRINT("parsing, part_idx=%ld, char=%ld ('%c')", | |
220 | (long) part_idx, (long) ch, | |
221 | (int) ((ch >= 0x20 && ch <= 0x7e) ? ch : DUK_ASC_QUESTION))); | |
222 | ||
223 | if (ch >= DUK_ASC_0 && ch <= DUK_ASC_9) { | |
224 | if (ndigits >= 9) { | |
225 | DUK_DDD(DUK_DDDPRINT("too many digits -> reject")); | |
226 | goto reject; | |
227 | } | |
228 | if (part_idx == DUK__PI_MILLISECOND /*msec*/ && ndigits >= 3) { | |
229 | /* ignore millisecond fractions after 3 */ | |
230 | } else { | |
231 | accum = accum * 10 + ((duk_int_t) ch) - ((duk_int_t) DUK_ASC_0) + 0x00; | |
232 | ndigits++; | |
233 | } | |
234 | } else { | |
235 | duk_uint_fast32_t match_val; | |
236 | duk_small_int_t sep_idx; | |
237 | ||
238 | if (ndigits <= 0) { | |
239 | goto reject; | |
240 | } | |
241 | if (part_idx == DUK__PI_MILLISECOND) { | |
242 | /* complete the millisecond field */ | |
243 | while (ndigits < 3) { | |
244 | accum *= 10; | |
245 | ndigits++; | |
246 | } | |
247 | } | |
248 | parts[part_idx] = accum; | |
249 | DUK_DDD(DUK_DDDPRINT("wrote part %ld -> value %ld", (long) part_idx, (long) accum)); | |
250 | ||
251 | accum = 0; | |
252 | ndigits = 0; | |
253 | ||
254 | for (i = 0; i < (duk_small_uint_t) (sizeof(duk__parse_iso8601_seps) / sizeof(duk_uint8_t)); i++) { | |
255 | if (duk__parse_iso8601_seps[i] == ch) { | |
256 | break; | |
257 | } | |
258 | } | |
259 | if (i == (duk_small_uint_t) (sizeof(duk__parse_iso8601_seps) / sizeof(duk_uint8_t))) { | |
260 | DUK_DDD(DUK_DDDPRINT("separator character doesn't match -> reject")); | |
261 | goto reject; | |
262 | } | |
263 | ||
264 | sep_idx = i; | |
265 | match_val = (1UL << part_idx) + (1UL << (sep_idx + 9)); /* match against rule part/sep bits */ | |
266 | ||
267 | for (i = 0; i < (duk_small_uint_t) (sizeof(duk__parse_iso8601_control) / sizeof(duk_uint32_t)); i++) { | |
268 | duk_uint_fast32_t rule = duk__parse_iso8601_control[i]; | |
269 | duk_small_uint_t nextpart; | |
270 | duk_small_uint_t cflags; | |
271 | ||
272 | DUK_DDD(DUK_DDDPRINT("part_idx=%ld, sep_idx=%ld, match_val=0x%08lx, considering rule=0x%08lx", | |
273 | (long) part_idx, (long) sep_idx, | |
274 | (unsigned long) match_val, (unsigned long) rule)); | |
275 | ||
276 | if ((rule & match_val) != match_val) { | |
277 | continue; | |
278 | } | |
279 | ||
280 | DUK__UNPACK_RULE(rule, nextpart, cflags); | |
281 | ||
282 | DUK_DDD(DUK_DDDPRINT("rule match -> part_idx=%ld, sep_idx=%ld, match_val=0x%08lx, " | |
283 | "rule=0x%08lx -> nextpart=%ld, cflags=0x%02lx", | |
284 | (long) part_idx, (long) sep_idx, | |
285 | (unsigned long) match_val, (unsigned long) rule, | |
286 | (long) nextpart, (unsigned long) cflags)); | |
287 | ||
288 | if (cflags & DUK__CF_NEG) { | |
289 | neg_tzoffset = 1; | |
290 | } | |
291 | ||
292 | if (cflags & DUK__CF_ACCEPT) { | |
293 | goto accept; | |
294 | } | |
295 | ||
296 | if (cflags & DUK__CF_ACCEPT_NUL) { | |
297 | DUK_ASSERT(*(p - 1) != (char) 0); | |
298 | if (*p == DUK_ASC_NUL) { | |
299 | goto accept; | |
300 | } | |
301 | goto reject; | |
302 | } | |
303 | ||
304 | part_idx = nextpart; | |
305 | break; | |
306 | } /* rule match */ | |
307 | ||
308 | if (i == (duk_small_uint_t) (sizeof(duk__parse_iso8601_control) / sizeof(duk_uint32_t))) { | |
309 | DUK_DDD(DUK_DDDPRINT("no rule matches -> reject")); | |
310 | goto reject; | |
311 | } | |
312 | ||
313 | if (ch == 0) { | |
314 | /* This shouldn't be necessary, but check just in case | |
315 | * to avoid any chance of overruns. | |
316 | */ | |
317 | DUK_DDD(DUK_DDDPRINT("NUL after rule matching (should not happen) -> reject")); | |
318 | goto reject; | |
319 | } | |
320 | } /* if-digit-else-ctrl */ | |
321 | } /* char loop */ | |
322 | ||
11fdf7f2 TL |
323 | /* We should never exit the loop above. */ |
324 | DUK_UNREACHABLE(); | |
7c673cae FG |
325 | |
326 | reject: | |
327 | DUK_DDD(DUK_DDDPRINT("reject")); | |
328 | return 0; | |
329 | ||
330 | accept: | |
331 | DUK_DDD(DUK_DDDPRINT("accept")); | |
332 | ||
333 | /* Apply timezone offset to get the main parts in UTC */ | |
334 | if (neg_year) { | |
335 | parts[DUK__PI_YEAR] = -parts[DUK__PI_YEAR]; | |
336 | } | |
337 | if (neg_tzoffset) { | |
338 | parts[DUK__PI_HOUR] += parts[DUK__PI_TZHOUR]; | |
339 | parts[DUK__PI_MINUTE] += parts[DUK__PI_TZMINUTE]; | |
340 | } else { | |
341 | parts[DUK__PI_HOUR] -= parts[DUK__PI_TZHOUR]; | |
342 | parts[DUK__PI_MINUTE] -= parts[DUK__PI_TZMINUTE]; | |
343 | } | |
344 | parts[DUK__PI_MONTH] -= 1; /* zero-based month */ | |
345 | parts[DUK__PI_DAY] -= 1; /* zero-based day */ | |
346 | ||
347 | /* Use double parts, they tolerate unnormalized time. | |
348 | * | |
349 | * Note: DUK_DATE_IDX_WEEKDAY is initialized with a bogus value (DUK__PI_TZHOUR) | |
350 | * on purpose. It won't be actually used by duk_bi_date_get_timeval_from_dparts(), | |
351 | * but will make the value initialized just in case, and avoid any | |
352 | * potential for Valgrind issues. | |
353 | */ | |
354 | for (i = 0; i < DUK_DATE_IDX_NUM_PARTS; i++) { | |
355 | DUK_DDD(DUK_DDDPRINT("part[%ld] = %ld", (long) i, (long) parts[i])); | |
356 | dparts[i] = parts[i]; | |
357 | } | |
358 | ||
359 | d = duk_bi_date_get_timeval_from_dparts(dparts, 0 /*flags*/); | |
360 | duk_push_number(ctx, d); | |
361 | return 1; | |
362 | } | |
363 | ||
364 | /* | |
365 | * Date/time parsing helper. | |
366 | * | |
367 | * Parse a datetime string into a time value. We must first try to parse | |
368 | * the input according to the standard format in E5.1 Section 15.9.1.15. | |
369 | * If that fails, we can try to parse using custom parsing, which can | |
370 | * either be platform neutral (custom code) or platform specific (using | |
371 | * existing platform API calls). | |
372 | * | |
373 | * Note in particular that we must parse whatever toString(), toUTCString(), | |
374 | * and toISOString() can produce; see E5.1 Section 15.9.4.2. | |
375 | * | |
376 | * Returns 1 to allow tail calling. | |
377 | * | |
378 | * There is much room for improvement here with respect to supporting | |
379 | * alternative datetime formats. For instance, V8 parses '2012-01-01' as | |
380 | * UTC and '2012/01/01' as local time. | |
381 | */ | |
382 | ||
383 | DUK_LOCAL duk_ret_t duk__parse_string(duk_context *ctx, const char *str) { | |
384 | /* XXX: there is a small risk here: because the ISO 8601 parser is | |
385 | * very loose, it may end up parsing some datetime values which | |
386 | * would be better parsed with a platform specific parser. | |
387 | */ | |
388 | ||
389 | DUK_ASSERT(str != NULL); | |
390 | DUK_DDD(DUK_DDDPRINT("parse datetime from string '%s'", (const char *) str)); | |
391 | ||
392 | if (duk__parse_string_iso8601_subset(ctx, str) != 0) { | |
393 | return 1; | |
394 | } | |
395 | ||
396 | #if defined(DUK_USE_DATE_PARSE_STRING) | |
397 | /* Contract, either: | |
398 | * - Push value on stack and return 1 | |
399 | * - Don't push anything on stack and return 0 | |
400 | */ | |
401 | ||
402 | if (DUK_USE_DATE_PARSE_STRING(ctx, str) != 0) { | |
403 | return 1; | |
404 | } | |
405 | #else | |
406 | /* No platform-specific parsing, this is not an error. */ | |
407 | #endif | |
408 | ||
409 | duk_push_nan(ctx); | |
410 | return 1; | |
411 | } | |
412 | ||
413 | /* | |
414 | * Calendar helpers | |
415 | * | |
416 | * Some helpers are used for getters and can operate on normalized values | |
417 | * which can be represented with 32-bit signed integers. Other helpers are | |
418 | * needed by setters and operate on un-normalized double values, must watch | |
419 | * out for non-finite numbers etc. | |
420 | */ | |
421 | ||
422 | DUK_LOCAL duk_uint8_t duk__days_in_month[12] = { | |
423 | (duk_uint8_t) 31, (duk_uint8_t) 28, (duk_uint8_t) 31, (duk_uint8_t) 30, | |
424 | (duk_uint8_t) 31, (duk_uint8_t) 30, (duk_uint8_t) 31, (duk_uint8_t) 31, | |
425 | (duk_uint8_t) 30, (duk_uint8_t) 31, (duk_uint8_t) 30, (duk_uint8_t) 31 | |
426 | }; | |
427 | ||
428 | /* Maximum iteration count for computing UTC-to-local time offset when | |
429 | * creating an Ecmascript time value from local parts. | |
430 | */ | |
431 | #define DUK__LOCAL_TZOFFSET_MAXITER 4 | |
432 | ||
433 | /* Because 'day since epoch' can be negative and is used to compute weekday | |
434 | * using a modulo operation, add this multiple of 7 to avoid negative values | |
435 | * when year is below 1970 epoch. Ecmascript time values are restricted to | |
436 | * +/- 100 million days from epoch, so this adder fits nicely into 32 bits. | |
437 | * Round to a multiple of 7 (= floor(100000000 / 7) * 7) and add margin. | |
438 | */ | |
439 | #define DUK__WEEKDAY_MOD_ADDER (20000000 * 7) /* 0x08583b00 */ | |
440 | ||
441 | DUK_INTERNAL duk_bool_t duk_bi_date_is_leap_year(duk_int_t year) { | |
442 | if ((year % 4) != 0) { | |
443 | return 0; | |
444 | } | |
445 | if ((year % 100) != 0) { | |
446 | return 1; | |
447 | } | |
448 | if ((year % 400) != 0) { | |
449 | return 0; | |
450 | } | |
451 | return 1; | |
452 | } | |
453 | ||
454 | DUK_INTERNAL duk_bool_t duk_bi_date_timeval_in_valid_range(duk_double_t x) { | |
455 | return (x >= -DUK_DATE_MSEC_100M_DAYS && x <= DUK_DATE_MSEC_100M_DAYS); | |
456 | } | |
457 | ||
458 | DUK_INTERNAL duk_bool_t duk_bi_date_timeval_in_leeway_range(duk_double_t x) { | |
459 | return (x >= -DUK_DATE_MSEC_100M_DAYS_LEEWAY && x <= DUK_DATE_MSEC_100M_DAYS_LEEWAY); | |
460 | } | |
461 | ||
462 | DUK_INTERNAL duk_bool_t duk_bi_date_year_in_valid_range(duk_double_t x) { | |
463 | return (x >= DUK_DATE_MIN_ECMA_YEAR && x <= DUK_DATE_MAX_ECMA_YEAR); | |
464 | } | |
465 | ||
466 | DUK_LOCAL duk_double_t duk__timeclip(duk_double_t x) { | |
467 | if (!DUK_ISFINITE(x)) { | |
468 | return DUK_DOUBLE_NAN; | |
469 | } | |
470 | ||
471 | if (!duk_bi_date_timeval_in_valid_range(x)) { | |
472 | return DUK_DOUBLE_NAN; | |
473 | } | |
474 | ||
475 | x = duk_js_tointeger_number(x); | |
476 | ||
477 | /* Here we'd have the option to normalize -0 to +0. */ | |
478 | return x; | |
479 | } | |
480 | ||
481 | /* Integer division which floors also negative values correctly. */ | |
482 | DUK_LOCAL duk_int_t duk__div_floor(duk_int_t a, duk_int_t b) { | |
483 | DUK_ASSERT(b > 0); | |
484 | if (a >= 0) { | |
485 | return a / b; | |
486 | } else { | |
487 | /* e.g. a = -4, b = 5 --> -4 - 5 + 1 / 5 --> -8 / 5 --> -1 | |
488 | * a = -5, b = 5 --> -5 - 5 + 1 / 5 --> -9 / 5 --> -1 | |
489 | * a = -6, b = 5 --> -6 - 5 + 1 / 5 --> -10 / 5 --> -2 | |
490 | */ | |
491 | return (a - b + 1) / b; | |
492 | } | |
493 | } | |
494 | ||
495 | /* Compute day number of the first day of a given year. */ | |
496 | DUK_LOCAL duk_int_t duk__day_from_year(duk_int_t year) { | |
497 | /* Note: in integer arithmetic, (x / 4) is same as floor(x / 4) for non-negative | |
498 | * values, but is incorrect for negative ones. | |
499 | */ | |
500 | return 365 * (year - 1970) | |
501 | + duk__div_floor(year - 1969, 4) | |
502 | - duk__div_floor(year - 1901, 100) | |
503 | + duk__div_floor(year - 1601, 400); | |
504 | } | |
505 | ||
506 | /* Given a day number, determine year and day-within-year. */ | |
507 | DUK_LOCAL duk_int_t duk__year_from_day(duk_int_t day, duk_small_int_t *out_day_within_year) { | |
508 | duk_int_t year; | |
509 | duk_int_t diff_days; | |
510 | ||
511 | /* estimate year upwards (towards positive infinity), then back down; | |
512 | * two iterations should be enough | |
513 | */ | |
514 | ||
515 | if (day >= 0) { | |
516 | year = 1970 + day / 365; | |
517 | } else { | |
518 | year = 1970 + day / 366; | |
519 | } | |
520 | ||
521 | for (;;) { | |
522 | diff_days = duk__day_from_year(year) - day; | |
523 | DUK_DDD(DUK_DDDPRINT("year=%ld day=%ld, diff_days=%ld", (long) year, (long) day, (long) diff_days)); | |
524 | if (diff_days <= 0) { | |
525 | DUK_ASSERT(-diff_days < 366); /* fits into duk_small_int_t */ | |
526 | *out_day_within_year = -diff_days; | |
527 | DUK_DDD(DUK_DDDPRINT("--> year=%ld, day-within-year=%ld", | |
528 | (long) year, (long) *out_day_within_year)); | |
529 | DUK_ASSERT(*out_day_within_year >= 0); | |
530 | DUK_ASSERT(*out_day_within_year < (duk_bi_date_is_leap_year(year) ? 366 : 365)); | |
531 | return year; | |
532 | } | |
533 | ||
534 | /* Note: this is very tricky; we must never 'overshoot' the | |
535 | * correction downwards. | |
536 | */ | |
537 | year -= 1 + (diff_days - 1) / 366; /* conservative */ | |
538 | } | |
539 | } | |
540 | ||
541 | /* Given a (year, month, day-within-month) triple, compute day number. | |
542 | * The input triple is un-normalized and may contain non-finite values. | |
543 | */ | |
544 | DUK_LOCAL duk_double_t duk__make_day(duk_double_t year, duk_double_t month, duk_double_t day) { | |
545 | duk_int_t day_num; | |
546 | duk_bool_t is_leap; | |
547 | duk_small_int_t i, n; | |
548 | ||
549 | /* Assume that year, month, day are all coerced to whole numbers. | |
550 | * They may also be NaN or infinity, in which case this function | |
551 | * must return NaN or infinity to ensure time value becomes NaN. | |
552 | * If 'day' is NaN, the final return will end up returning a NaN, | |
553 | * so it doesn't need to be checked here. | |
554 | */ | |
555 | ||
556 | if (!DUK_ISFINITE(year) || !DUK_ISFINITE(month)) { | |
557 | return DUK_DOUBLE_NAN; | |
558 | } | |
559 | ||
560 | year += DUK_FLOOR(month / 12.0); | |
561 | ||
562 | month = DUK_FMOD(month, 12.0); | |
563 | if (month < 0.0) { | |
564 | /* handle negative values */ | |
565 | month += 12.0; | |
566 | } | |
567 | ||
568 | /* The algorithm in E5.1 Section 15.9.1.12 normalizes month, but | |
569 | * does not normalize the day-of-month (nor check whether or not | |
570 | * it is finite) because it's not necessary for finding the day | |
571 | * number which matches the (year,month) pair. | |
572 | * | |
573 | * We assume that duk__day_from_year() is exact here. | |
574 | * | |
575 | * Without an explicit infinity / NaN check in the beginning, | |
576 | * day_num would be a bogus integer here. | |
577 | * | |
578 | * It's possible for 'year' to be out of integer range here. | |
579 | * If so, we need to return NaN without integer overflow. | |
580 | * This fixes test-bug-setyear-overflow.js. | |
581 | */ | |
582 | ||
583 | if (!duk_bi_date_year_in_valid_range(year)) { | |
584 | DUK_DD(DUK_DDPRINT("year not in ecmascript valid range, avoid integer overflow: %lf", (double) year)); | |
585 | return DUK_DOUBLE_NAN; | |
586 | } | |
587 | day_num = duk__day_from_year((duk_int_t) year); | |
588 | is_leap = duk_bi_date_is_leap_year((duk_int_t) year); | |
589 | ||
590 | n = (duk_small_int_t) month; | |
591 | for (i = 0; i < n; i++) { | |
592 | day_num += duk__days_in_month[i]; | |
593 | if (i == 1 && is_leap) { | |
594 | day_num++; | |
595 | } | |
596 | } | |
597 | ||
598 | /* If 'day' is NaN, returns NaN. */ | |
599 | return (duk_double_t) day_num + day; | |
600 | } | |
601 | ||
602 | /* Split time value into parts. The time value is assumed to be an internal | |
603 | * one, i.e. finite, no fractions. Possible local time adjustment has already | |
604 | * been applied when reading the time value. | |
605 | */ | |
606 | DUK_INTERNAL void duk_bi_date_timeval_to_parts(duk_double_t d, duk_int_t *parts, duk_double_t *dparts, duk_small_uint_t flags) { | |
607 | duk_double_t d1, d2; | |
608 | duk_int_t t1, t2; | |
609 | duk_int_t day_since_epoch; | |
610 | duk_int_t year; /* does not fit into 16 bits */ | |
611 | duk_small_int_t day_in_year; | |
612 | duk_small_int_t month; | |
613 | duk_small_int_t day; | |
614 | duk_small_int_t dim; | |
615 | duk_int_t jan1_since_epoch; | |
616 | duk_small_int_t jan1_weekday; | |
617 | duk_int_t equiv_year; | |
618 | duk_small_uint_t i; | |
619 | duk_bool_t is_leap; | |
620 | duk_small_int_t arridx; | |
621 | ||
622 | DUK_ASSERT(DUK_ISFINITE(d)); /* caller checks */ | |
623 | DUK_ASSERT(DUK_FLOOR(d) == d); /* no fractions in internal time */ | |
624 | ||
625 | /* The timevalue must be in valid Ecmascript range, but since a local | |
626 | * time offset can be applied, we need to allow a +/- 24h leeway to | |
627 | * the value. In other words, although the UTC time is within the | |
628 | * Ecmascript range, the local part values can be just outside of it. | |
629 | */ | |
630 | DUK_UNREF(duk_bi_date_timeval_in_leeway_range); | |
631 | DUK_ASSERT(duk_bi_date_timeval_in_leeway_range(d)); | |
632 | ||
633 | /* these computations are guaranteed to be exact for the valid | |
634 | * E5 time value range, assuming milliseconds without fractions. | |
635 | */ | |
636 | d1 = (duk_double_t) DUK_FMOD(d, (double) DUK_DATE_MSEC_DAY); | |
637 | if (d1 < 0.0) { | |
638 | /* deal with negative values */ | |
639 | d1 += (duk_double_t) DUK_DATE_MSEC_DAY; | |
640 | } | |
641 | d2 = DUK_FLOOR((double) (d / (duk_double_t) DUK_DATE_MSEC_DAY)); | |
642 | DUK_ASSERT(d2 * ((duk_double_t) DUK_DATE_MSEC_DAY) + d1 == d); | |
643 | /* now expected to fit into a 32-bit integer */ | |
644 | t1 = (duk_int_t) d1; | |
645 | t2 = (duk_int_t) d2; | |
646 | day_since_epoch = t2; | |
647 | DUK_ASSERT((duk_double_t) t1 == d1); | |
648 | DUK_ASSERT((duk_double_t) t2 == d2); | |
649 | ||
650 | /* t1 = milliseconds within day (fits 32 bit) | |
651 | * t2 = day number from epoch (fits 32 bit, may be negative) | |
652 | */ | |
653 | ||
654 | parts[DUK_DATE_IDX_MILLISECOND] = t1 % 1000; t1 /= 1000; | |
655 | parts[DUK_DATE_IDX_SECOND] = t1 % 60; t1 /= 60; | |
656 | parts[DUK_DATE_IDX_MINUTE] = t1 % 60; t1 /= 60; | |
657 | parts[DUK_DATE_IDX_HOUR] = t1; | |
658 | DUK_ASSERT(parts[DUK_DATE_IDX_MILLISECOND] >= 0 && parts[DUK_DATE_IDX_MILLISECOND] <= 999); | |
659 | DUK_ASSERT(parts[DUK_DATE_IDX_SECOND] >= 0 && parts[DUK_DATE_IDX_SECOND] <= 59); | |
660 | DUK_ASSERT(parts[DUK_DATE_IDX_MINUTE] >= 0 && parts[DUK_DATE_IDX_MINUTE] <= 59); | |
661 | DUK_ASSERT(parts[DUK_DATE_IDX_HOUR] >= 0 && parts[DUK_DATE_IDX_HOUR] <= 23); | |
662 | ||
663 | DUK_DDD(DUK_DDDPRINT("d=%lf, d1=%lf, d2=%lf, t1=%ld, t2=%ld, parts: hour=%ld min=%ld sec=%ld msec=%ld", | |
664 | (double) d, (double) d1, (double) d2, (long) t1, (long) t2, | |
665 | (long) parts[DUK_DATE_IDX_HOUR], | |
666 | (long) parts[DUK_DATE_IDX_MINUTE], | |
667 | (long) parts[DUK_DATE_IDX_SECOND], | |
668 | (long) parts[DUK_DATE_IDX_MILLISECOND])); | |
669 | ||
670 | /* This assert depends on the input parts representing time inside | |
671 | * the Ecmascript range. | |
672 | */ | |
673 | DUK_ASSERT(t2 + DUK__WEEKDAY_MOD_ADDER >= 0); | |
674 | parts[DUK_DATE_IDX_WEEKDAY] = (t2 + 4 + DUK__WEEKDAY_MOD_ADDER) % 7; /* E5.1 Section 15.9.1.6 */ | |
675 | DUK_ASSERT(parts[DUK_DATE_IDX_WEEKDAY] >= 0 && parts[DUK_DATE_IDX_WEEKDAY] <= 6); | |
676 | ||
677 | year = duk__year_from_day(t2, &day_in_year); | |
678 | day = day_in_year; | |
679 | is_leap = duk_bi_date_is_leap_year(year); | |
680 | for (month = 0; month < 12; month++) { | |
681 | dim = duk__days_in_month[month]; | |
682 | if (month == 1 && is_leap) { | |
683 | dim++; | |
684 | } | |
685 | DUK_DDD(DUK_DDDPRINT("month=%ld, dim=%ld, day=%ld", | |
686 | (long) month, (long) dim, (long) day)); | |
687 | if (day < dim) { | |
688 | break; | |
689 | } | |
690 | day -= dim; | |
691 | } | |
692 | DUK_DDD(DUK_DDDPRINT("final month=%ld", (long) month)); | |
693 | DUK_ASSERT(month >= 0 && month <= 11); | |
694 | DUK_ASSERT(day >= 0 && day <= 31); | |
695 | ||
696 | /* Equivalent year mapping, used to avoid DST trouble when platform | |
697 | * may fail to provide reasonable DST answers for dates outside the | |
698 | * ordinary range (e.g. 1970-2038). An equivalent year has the same | |
699 | * leap-year-ness as the original year and begins on the same weekday | |
700 | * (Jan 1). | |
701 | * | |
702 | * The year 2038 is avoided because there seem to be problems with it | |
703 | * on some platforms. The year 1970 is also avoided as there were | |
704 | * practical problems with it; an equivalent year is used for it too, | |
705 | * which breaks some DST computations for 1970 right now, see e.g. | |
706 | * test-bi-date-tzoffset-brute-fi.js. | |
707 | */ | |
708 | if ((flags & DUK_DATE_FLAG_EQUIVYEAR) && (year < 1971 || year > 2037)) { | |
709 | DUK_ASSERT(is_leap == 0 || is_leap == 1); | |
710 | ||
711 | jan1_since_epoch = day_since_epoch - day_in_year; /* day number for Jan 1 since epoch */ | |
712 | DUK_ASSERT(jan1_since_epoch + DUK__WEEKDAY_MOD_ADDER >= 0); | |
713 | jan1_weekday = (jan1_since_epoch + 4 + DUK__WEEKDAY_MOD_ADDER) % 7; /* E5.1 Section 15.9.1.6 */ | |
714 | DUK_ASSERT(jan1_weekday >= 0 && jan1_weekday <= 6); | |
715 | arridx = jan1_weekday; | |
716 | if (is_leap) { | |
717 | arridx += 7; | |
718 | } | |
719 | DUK_ASSERT(arridx >= 0 && arridx < (duk_small_int_t) (sizeof(duk__date_equivyear) / sizeof(duk_uint8_t))); | |
720 | ||
721 | equiv_year = (duk_int_t) duk__date_equivyear[arridx] + 1970; | |
722 | year = equiv_year; | |
723 | DUK_DDD(DUK_DDDPRINT("equiv year mapping, year=%ld, day_in_year=%ld, day_since_epoch=%ld, " | |
724 | "jan1_since_epoch=%ld, jan1_weekday=%ld -> equiv year %ld", | |
725 | (long) year, (long) day_in_year, (long) day_since_epoch, | |
726 | (long) jan1_since_epoch, (long) jan1_weekday, (long) equiv_year)); | |
727 | } | |
728 | ||
729 | parts[DUK_DATE_IDX_YEAR] = year; | |
730 | parts[DUK_DATE_IDX_MONTH] = month; | |
731 | parts[DUK_DATE_IDX_DAY] = day; | |
732 | ||
733 | if (flags & DUK_DATE_FLAG_ONEBASED) { | |
734 | parts[DUK_DATE_IDX_MONTH]++; /* zero-based -> one-based */ | |
735 | parts[DUK_DATE_IDX_DAY]++; /* -""- */ | |
736 | } | |
737 | ||
738 | if (dparts != NULL) { | |
739 | for (i = 0; i < DUK_DATE_IDX_NUM_PARTS; i++) { | |
740 | dparts[i] = (duk_double_t) parts[i]; | |
741 | } | |
742 | } | |
743 | } | |
744 | ||
745 | /* Compute time value from (double) parts. The parts can be either UTC | |
746 | * or local time; if local, they need to be (conceptually) converted into | |
747 | * UTC time. The parts may represent valid or invalid time, and may be | |
748 | * wildly out of range (but may cancel each other and still come out in | |
749 | * the valid Date range). | |
750 | */ | |
751 | DUK_INTERNAL duk_double_t duk_bi_date_get_timeval_from_dparts(duk_double_t *dparts, duk_small_uint_t flags) { | |
752 | #if defined(DUK_USE_PARANOID_DATE_COMPUTATION) | |
753 | /* See comments below on MakeTime why these are volatile. */ | |
754 | volatile duk_double_t tmp_time; | |
755 | volatile duk_double_t tmp_day; | |
756 | volatile duk_double_t d; | |
757 | #else | |
758 | duk_double_t tmp_time; | |
759 | duk_double_t tmp_day; | |
760 | duk_double_t d; | |
761 | #endif | |
762 | duk_small_uint_t i; | |
763 | duk_int_t tzoff, tzoffprev1, tzoffprev2; | |
764 | ||
765 | /* Expects 'this' at top of stack on entry. */ | |
766 | ||
767 | /* Coerce all finite parts with ToInteger(). ToInteger() must not | |
768 | * be called for NaN/Infinity because it will convert e.g. NaN to | |
769 | * zero. If ToInteger() has already been called, this has no side | |
770 | * effects and is idempotent. | |
771 | * | |
772 | * Don't read dparts[DUK_DATE_IDX_WEEKDAY]; it will cause Valgrind | |
773 | * issues if the value is uninitialized. | |
774 | */ | |
775 | for (i = 0; i <= DUK_DATE_IDX_MILLISECOND; i++) { | |
776 | /* SCANBUILD: scan-build complains here about assigned value | |
777 | * being garbage or undefined. This is correct but operating | |
778 | * on undefined values has no ill effect and is ignored by the | |
779 | * caller in the case where this happens. | |
780 | */ | |
781 | d = dparts[i]; | |
782 | if (DUK_ISFINITE(d)) { | |
783 | dparts[i] = duk_js_tointeger_number(d); | |
784 | } | |
785 | } | |
786 | ||
787 | /* Use explicit steps in computation to try to ensure that | |
788 | * computation happens with intermediate results coerced to | |
789 | * double values (instead of using something more accurate). | |
790 | * E.g. E5.1 Section 15.9.1.11 requires use of IEEE 754 | |
791 | * rules (= Ecmascript '+' and '*' operators). | |
792 | * | |
793 | * Without 'volatile' even this approach fails on some platform | |
794 | * and compiler combinations. For instance, gcc 4.8.1 on Ubuntu | |
795 | * 64-bit, with -m32 and without -std=c99, test-bi-date-canceling.js | |
796 | * would fail because of some optimizations when computing tmp_time | |
797 | * (MakeTime below). Adding 'volatile' to tmp_time solved this | |
798 | * particular problem (annoyingly, also adding debug prints or | |
799 | * running the executable under valgrind hides it). | |
800 | */ | |
801 | ||
802 | /* MakeTime */ | |
803 | tmp_time = 0.0; | |
804 | tmp_time += dparts[DUK_DATE_IDX_HOUR] * ((duk_double_t) DUK_DATE_MSEC_HOUR); | |
805 | tmp_time += dparts[DUK_DATE_IDX_MINUTE] * ((duk_double_t) DUK_DATE_MSEC_MINUTE); | |
806 | tmp_time += dparts[DUK_DATE_IDX_SECOND] * ((duk_double_t) DUK_DATE_MSEC_SECOND); | |
807 | tmp_time += dparts[DUK_DATE_IDX_MILLISECOND]; | |
808 | ||
809 | /* MakeDay */ | |
810 | tmp_day = duk__make_day(dparts[DUK_DATE_IDX_YEAR], dparts[DUK_DATE_IDX_MONTH], dparts[DUK_DATE_IDX_DAY]); | |
811 | ||
812 | /* MakeDate */ | |
813 | d = tmp_day * ((duk_double_t) DUK_DATE_MSEC_DAY) + tmp_time; | |
814 | ||
815 | DUK_DDD(DUK_DDDPRINT("time=%lf day=%lf --> timeval=%lf", | |
816 | (double) tmp_time, (double) tmp_day, (double) d)); | |
817 | ||
818 | /* Optional UTC conversion. */ | |
819 | if (flags & DUK_DATE_FLAG_LOCALTIME) { | |
820 | /* DUK_USE_DATE_GET_LOCAL_TZOFFSET() needs to be called with a | |
821 | * time value computed from UTC parts. At this point we only | |
822 | * have 'd' which is a time value computed from local parts, so | |
823 | * it is off by the UTC-to-local time offset which we don't know | |
824 | * yet. The current solution for computing the UTC-to-local | |
825 | * time offset is to iterate a few times and detect a fixed | |
826 | * point or a two-cycle loop (or a sanity iteration limit), | |
827 | * see test-bi-date-local-parts.js and test-bi-date-tzoffset-basic-fi.js. | |
828 | * | |
829 | * E5.1 Section 15.9.1.9: | |
830 | * UTC(t) = t - LocalTZA - DaylightSavingTA(t - LocalTZA) | |
831 | * | |
832 | * For NaN/inf, DUK_USE_DATE_GET_LOCAL_TZOFFSET() returns 0. | |
833 | */ | |
834 | ||
835 | #if 0 | |
836 | /* Old solution: don't iterate, incorrect */ | |
837 | tzoff = DUK_USE_DATE_GET_LOCAL_TZOFFSET(d); | |
838 | DUK_DDD(DUK_DDDPRINT("tzoffset w/o iteration, tzoff=%ld", (long) tzoff)); | |
839 | d -= tzoff * 1000L; | |
840 | DUK_UNREF(tzoffprev1); | |
841 | DUK_UNREF(tzoffprev2); | |
842 | #endif | |
843 | ||
844 | /* Iteration solution */ | |
845 | tzoff = 0; | |
846 | tzoffprev1 = 999999999L; /* invalid value which never matches */ | |
847 | for (i = 0; i < DUK__LOCAL_TZOFFSET_MAXITER; i++) { | |
848 | tzoffprev2 = tzoffprev1; | |
849 | tzoffprev1 = tzoff; | |
850 | tzoff = DUK_USE_DATE_GET_LOCAL_TZOFFSET(d - tzoff * 1000L); | |
851 | DUK_DDD(DUK_DDDPRINT("tzoffset iteration, i=%d, tzoff=%ld, tzoffprev1=%ld tzoffprev2=%ld", | |
852 | (int) i, (long) tzoff, (long) tzoffprev1, (long) tzoffprev2)); | |
853 | if (tzoff == tzoffprev1) { | |
854 | DUK_DDD(DUK_DDDPRINT("tzoffset iteration finished, i=%d, tzoff=%ld, tzoffprev1=%ld, tzoffprev2=%ld", | |
855 | (int) i, (long) tzoff, (long) tzoffprev1, (long) tzoffprev2)); | |
856 | break; | |
857 | } else if (tzoff == tzoffprev2) { | |
858 | /* Two value cycle, see e.g. test-bi-date-tzoffset-basic-fi.js. | |
859 | * In these cases, favor a higher tzoffset to get a consistent | |
860 | * result which is independent of iteration count. Not sure if | |
861 | * this is a generically correct solution. | |
862 | */ | |
863 | DUK_DDD(DUK_DDDPRINT("tzoffset iteration two-value cycle, i=%d, tzoff=%ld, tzoffprev1=%ld, tzoffprev2=%ld", | |
864 | (int) i, (long) tzoff, (long) tzoffprev1, (long) tzoffprev2)); | |
865 | if (tzoffprev1 > tzoff) { | |
866 | tzoff = tzoffprev1; | |
867 | } | |
868 | break; | |
869 | } | |
870 | } | |
871 | DUK_DDD(DUK_DDDPRINT("tzoffset iteration, tzoff=%ld", (long) tzoff)); | |
872 | d -= tzoff * 1000L; | |
873 | } | |
874 | ||
875 | /* TimeClip(), which also handles Infinity -> NaN conversion */ | |
876 | d = duk__timeclip(d); | |
877 | ||
878 | return d; | |
879 | } | |
880 | ||
881 | /* | |
882 | * API oriented helpers | |
883 | */ | |
884 | ||
885 | /* Push 'this' binding, check that it is a Date object; then push the | |
886 | * internal time value. At the end, stack is: [ ... this timeval ]. | |
887 | * Returns the time value. Local time adjustment is done if requested. | |
888 | */ | |
889 | DUK_LOCAL duk_double_t duk__push_this_get_timeval_tzoffset(duk_context *ctx, duk_small_uint_t flags, duk_int_t *out_tzoffset) { | |
890 | duk_hthread *thr = (duk_hthread *) ctx; | |
891 | duk_hobject *h; | |
892 | duk_double_t d; | |
893 | duk_int_t tzoffset = 0; | |
894 | ||
895 | duk_push_this(ctx); | |
896 | h = duk_get_hobject(ctx, -1); /* XXX: getter with class check, useful in built-ins */ | |
897 | if (h == NULL || DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_DATE) { | |
11fdf7f2 | 898 | DUK_ERROR_TYPE(thr, "expected Date"); |
7c673cae FG |
899 | } |
900 | ||
901 | duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE); | |
902 | d = duk_to_number(ctx, -1); | |
903 | duk_pop(ctx); | |
904 | ||
905 | if (DUK_ISNAN(d)) { | |
906 | if (flags & DUK_DATE_FLAG_NAN_TO_ZERO) { | |
907 | d = 0.0; | |
908 | } | |
909 | if (flags & DUK_DATE_FLAG_NAN_TO_RANGE_ERROR) { | |
11fdf7f2 | 910 | DUK_ERROR_RANGE(thr, "Invalid Date"); |
7c673cae FG |
911 | } |
912 | } | |
913 | /* if no NaN handling flag, may still be NaN here, but not Inf */ | |
914 | DUK_ASSERT(!DUK_ISINF(d)); | |
915 | ||
916 | if (flags & DUK_DATE_FLAG_LOCALTIME) { | |
917 | /* Note: DST adjustment is determined using UTC time. | |
918 | * If 'd' is NaN, tzoffset will be 0. | |
919 | */ | |
920 | tzoffset = DUK_USE_DATE_GET_LOCAL_TZOFFSET(d); /* seconds */ | |
921 | d += tzoffset * 1000L; | |
922 | } | |
923 | if (out_tzoffset) { | |
924 | *out_tzoffset = tzoffset; | |
925 | } | |
926 | ||
927 | /* [ ... this ] */ | |
928 | return d; | |
929 | } | |
930 | ||
931 | DUK_LOCAL duk_double_t duk__push_this_get_timeval(duk_context *ctx, duk_small_uint_t flags) { | |
932 | return duk__push_this_get_timeval_tzoffset(ctx, flags, NULL); | |
933 | } | |
934 | ||
935 | /* Set timeval to 'this' from dparts, push the new time value onto the | |
936 | * value stack and return 1 (caller can then tail call us). Expects | |
937 | * the value stack to contain 'this' on the stack top. | |
938 | */ | |
939 | DUK_LOCAL duk_ret_t duk__set_this_timeval_from_dparts(duk_context *ctx, duk_double_t *dparts, duk_small_uint_t flags) { | |
940 | duk_double_t d; | |
941 | ||
942 | /* [ ... this ] */ | |
943 | ||
944 | d = duk_bi_date_get_timeval_from_dparts(dparts, flags); | |
945 | duk_push_number(ctx, d); /* -> [ ... this timeval_new ] */ | |
946 | duk_dup_top(ctx); /* -> [ ... this timeval_new timeval_new ] */ | |
947 | duk_put_prop_stridx(ctx, -3, DUK_STRIDX_INT_VALUE); | |
948 | ||
949 | /* stack top: new time value, return 1 to allow tail calls */ | |
950 | return 1; | |
951 | } | |
952 | ||
953 | /* 'out_buf' must be at least DUK_BI_DATE_ISO8601_BUFSIZE long. */ | |
954 | DUK_LOCAL void duk__format_parts_iso8601(duk_int_t *parts, duk_int_t tzoffset, duk_small_uint_t flags, duk_uint8_t *out_buf) { | |
955 | char yearstr[8]; /* "-123456\0" */ | |
956 | char tzstr[8]; /* "+11:22\0" */ | |
957 | char sep = (flags & DUK_DATE_FLAG_SEP_T) ? DUK_ASC_UC_T : DUK_ASC_SPACE; | |
958 | ||
959 | DUK_ASSERT(parts[DUK_DATE_IDX_MONTH] >= 1 && parts[DUK_DATE_IDX_MONTH] <= 12); | |
960 | DUK_ASSERT(parts[DUK_DATE_IDX_DAY] >= 1 && parts[DUK_DATE_IDX_DAY] <= 31); | |
961 | DUK_ASSERT(parts[DUK_DATE_IDX_YEAR] >= -999999 && parts[DUK_DATE_IDX_YEAR] <= 999999); | |
962 | ||
963 | /* Note: %06d for positive value, %07d for negative value to include | |
964 | * sign and 6 digits. | |
965 | */ | |
966 | DUK_SNPRINTF(yearstr, | |
967 | sizeof(yearstr), | |
968 | (parts[DUK_DATE_IDX_YEAR] >= 0 && parts[DUK_DATE_IDX_YEAR] <= 9999) ? "%04ld" : | |
969 | ((parts[DUK_DATE_IDX_YEAR] >= 0) ? "+%06ld" : "%07ld"), | |
970 | (long) parts[DUK_DATE_IDX_YEAR]); | |
971 | yearstr[sizeof(yearstr) - 1] = (char) 0; | |
972 | ||
973 | if (flags & DUK_DATE_FLAG_LOCALTIME) { | |
974 | /* tzoffset seconds are dropped; 16 bits suffice for | |
975 | * time offset in minutes | |
976 | */ | |
977 | if (tzoffset >= 0) { | |
978 | duk_small_int_t tmp = tzoffset / 60; | |
979 | DUK_SNPRINTF(tzstr, sizeof(tzstr), "+%02d:%02d", (int) (tmp / 60), (int) (tmp % 60)); | |
980 | } else { | |
981 | duk_small_int_t tmp = -tzoffset / 60; | |
982 | DUK_SNPRINTF(tzstr, sizeof(tzstr), "-%02d:%02d", (int) (tmp / 60), (int) (tmp % 60)); | |
983 | } | |
984 | tzstr[sizeof(tzstr) - 1] = (char) 0; | |
985 | } else { | |
986 | tzstr[0] = DUK_ASC_UC_Z; | |
987 | tzstr[1] = (char) 0; | |
988 | } | |
989 | ||
990 | /* Unlike year, the other parts fit into 16 bits so %d format | |
991 | * is portable. | |
992 | */ | |
993 | if ((flags & DUK_DATE_FLAG_TOSTRING_DATE) && (flags & DUK_DATE_FLAG_TOSTRING_TIME)) { | |
994 | DUK_SPRINTF((char *) out_buf, "%s-%02d-%02d%c%02d:%02d:%02d.%03d%s", | |
995 | (const char *) yearstr, (int) parts[DUK_DATE_IDX_MONTH], (int) parts[DUK_DATE_IDX_DAY], (int) sep, | |
996 | (int) parts[DUK_DATE_IDX_HOUR], (int) parts[DUK_DATE_IDX_MINUTE], | |
997 | (int) parts[DUK_DATE_IDX_SECOND], (int) parts[DUK_DATE_IDX_MILLISECOND], (const char *) tzstr); | |
998 | } else if (flags & DUK_DATE_FLAG_TOSTRING_DATE) { | |
999 | DUK_SPRINTF((char *) out_buf, "%s-%02d-%02d", | |
1000 | (const char *) yearstr, (int) parts[DUK_DATE_IDX_MONTH], (int) parts[DUK_DATE_IDX_DAY]); | |
1001 | } else { | |
1002 | DUK_ASSERT(flags & DUK_DATE_FLAG_TOSTRING_TIME); | |
1003 | DUK_SPRINTF((char *) out_buf, "%02d:%02d:%02d.%03d%s", | |
1004 | (int) parts[DUK_DATE_IDX_HOUR], (int) parts[DUK_DATE_IDX_MINUTE], | |
1005 | (int) parts[DUK_DATE_IDX_SECOND], (int) parts[DUK_DATE_IDX_MILLISECOND], | |
1006 | (const char *) tzstr); | |
1007 | } | |
1008 | } | |
1009 | ||
1010 | /* Helper for string conversion calls: check 'this' binding, get the | |
1011 | * internal time value, and format date and/or time in a few formats. | |
1012 | * Return value allows tail calls. | |
1013 | */ | |
1014 | DUK_LOCAL duk_ret_t duk__to_string_helper(duk_context *ctx, duk_small_uint_t flags) { | |
1015 | duk_double_t d; | |
1016 | duk_int_t parts[DUK_DATE_IDX_NUM_PARTS]; | |
1017 | duk_int_t tzoffset; /* seconds, doesn't fit into 16 bits */ | |
1018 | duk_bool_t rc; | |
1019 | duk_uint8_t buf[DUK_BI_DATE_ISO8601_BUFSIZE]; | |
1020 | ||
1021 | DUK_UNREF(rc); /* unreferenced with some options */ | |
1022 | ||
1023 | d = duk__push_this_get_timeval_tzoffset(ctx, flags, &tzoffset); | |
1024 | if (DUK_ISNAN(d)) { | |
1025 | duk_push_hstring_stridx(ctx, DUK_STRIDX_INVALID_DATE); | |
1026 | return 1; | |
1027 | } | |
1028 | DUK_ASSERT(DUK_ISFINITE(d)); | |
1029 | ||
1030 | /* formatters always get one-based month/day-of-month */ | |
1031 | duk_bi_date_timeval_to_parts(d, parts, NULL, DUK_DATE_FLAG_ONEBASED); | |
1032 | DUK_ASSERT(parts[DUK_DATE_IDX_MONTH] >= 1 && parts[DUK_DATE_IDX_MONTH] <= 12); | |
1033 | DUK_ASSERT(parts[DUK_DATE_IDX_DAY] >= 1 && parts[DUK_DATE_IDX_DAY] <= 31); | |
1034 | ||
1035 | if (flags & DUK_DATE_FLAG_TOSTRING_LOCALE) { | |
1036 | /* try locale specific formatter; if it refuses to format the | |
1037 | * string, fall back to an ISO 8601 formatted value in local | |
1038 | * time. | |
1039 | */ | |
1040 | #if defined(DUK_USE_DATE_FORMAT_STRING) | |
1041 | /* Contract, either: | |
1042 | * - Push string to value stack and return 1 | |
1043 | * - Don't push anything and return 0 | |
1044 | */ | |
1045 | ||
1046 | rc = DUK_USE_DATE_FORMAT_STRING(ctx, parts, tzoffset, flags); | |
1047 | if (rc != 0) { | |
1048 | return 1; | |
1049 | } | |
1050 | #else | |
1051 | /* No locale specific formatter; this is OK, we fall back | |
1052 | * to ISO 8601. | |
1053 | */ | |
1054 | #endif | |
1055 | } | |
1056 | ||
1057 | /* Different calling convention than above used because the helper | |
1058 | * is shared. | |
1059 | */ | |
1060 | duk__format_parts_iso8601(parts, tzoffset, flags, buf); | |
1061 | duk_push_string(ctx, (const char *) buf); | |
1062 | return 1; | |
1063 | } | |
1064 | ||
1065 | /* Helper for component getter calls: check 'this' binding, get the | |
1066 | * internal time value, split it into parts (either as UTC time or | |
1067 | * local time), push a specified component as a return value to the | |
1068 | * value stack and return 1 (caller can then tail call us). | |
1069 | */ | |
1070 | DUK_LOCAL duk_ret_t duk__get_part_helper(duk_context *ctx, duk_small_uint_t flags_and_idx) { | |
1071 | duk_double_t d; | |
1072 | duk_int_t parts[DUK_DATE_IDX_NUM_PARTS]; | |
1073 | duk_small_uint_t idx_part = (duk_small_uint_t) (flags_and_idx >> DUK_DATE_FLAG_VALUE_SHIFT); /* unpack args */ | |
1074 | ||
1075 | DUK_ASSERT_DISABLE(idx_part >= 0); /* unsigned */ | |
1076 | DUK_ASSERT(idx_part < DUK_DATE_IDX_NUM_PARTS); | |
1077 | ||
1078 | d = duk__push_this_get_timeval(ctx, flags_and_idx); | |
1079 | if (DUK_ISNAN(d)) { | |
1080 | duk_push_nan(ctx); | |
1081 | return 1; | |
1082 | } | |
1083 | DUK_ASSERT(DUK_ISFINITE(d)); | |
1084 | ||
1085 | duk_bi_date_timeval_to_parts(d, parts, NULL, flags_and_idx); /* no need to mask idx portion */ | |
1086 | ||
1087 | /* Setter APIs detect special year numbers (0...99) and apply a +1900 | |
1088 | * only in certain cases. The legacy getYear() getter applies -1900 | |
1089 | * unconditionally. | |
1090 | */ | |
1091 | duk_push_int(ctx, (flags_and_idx & DUK_DATE_FLAG_SUB1900) ? parts[idx_part] - 1900 : parts[idx_part]); | |
1092 | return 1; | |
1093 | } | |
1094 | ||
1095 | /* Helper for component setter calls: check 'this' binding, get the | |
1096 | * internal time value, split it into parts (either as UTC time or | |
1097 | * local time), modify one or more components as specified, recompute | |
1098 | * the time value, set it as the internal value. Finally, push the | |
1099 | * new time value as a return value to the value stack and return 1 | |
1100 | * (caller can then tail call us). | |
1101 | */ | |
1102 | DUK_LOCAL duk_ret_t duk__set_part_helper(duk_context *ctx, duk_small_uint_t flags_and_maxnargs) { | |
1103 | duk_double_t d; | |
1104 | duk_int_t parts[DUK_DATE_IDX_NUM_PARTS]; | |
1105 | duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS]; | |
1106 | duk_idx_t nargs; | |
1107 | duk_small_uint_t maxnargs = (duk_small_uint_t) (flags_and_maxnargs >> DUK_DATE_FLAG_VALUE_SHIFT); /* unpack args */ | |
1108 | duk_small_uint_t idx_first, idx; | |
1109 | duk_small_uint_t i; | |
1110 | ||
1111 | nargs = duk_get_top(ctx); | |
1112 | d = duk__push_this_get_timeval(ctx, flags_and_maxnargs); | |
1113 | DUK_ASSERT(DUK_ISFINITE(d) || DUK_ISNAN(d)); | |
1114 | ||
1115 | if (DUK_ISFINITE(d)) { | |
1116 | duk_bi_date_timeval_to_parts(d, parts, dparts, flags_and_maxnargs); | |
1117 | } else { | |
1118 | /* NaN timevalue: we need to coerce the arguments, but | |
1119 | * the resulting internal timestamp needs to remain NaN. | |
1120 | * This works but is not pretty: parts and dparts will | |
1121 | * be partially uninitialized, but we only write to them. | |
1122 | */ | |
1123 | } | |
1124 | ||
1125 | /* | |
1126 | * Determining which datetime components to overwrite based on | |
1127 | * stack arguments is a bit complicated, but important to factor | |
1128 | * out from setters themselves for compactness. | |
1129 | * | |
1130 | * If DUK_DATE_FLAG_TIMESETTER, maxnargs indicates setter type: | |
1131 | * | |
1132 | * 1 -> millisecond | |
1133 | * 2 -> second, [millisecond] | |
1134 | * 3 -> minute, [second], [millisecond] | |
1135 | * 4 -> hour, [minute], [second], [millisecond] | |
1136 | * | |
1137 | * Else: | |
1138 | * | |
1139 | * 1 -> date | |
1140 | * 2 -> month, [date] | |
1141 | * 3 -> year, [month], [date] | |
1142 | * | |
1143 | * By comparing nargs and maxnargs (and flags) we know which | |
1144 | * components to override. We rely on part index ordering. | |
1145 | */ | |
1146 | ||
1147 | if (flags_and_maxnargs & DUK_DATE_FLAG_TIMESETTER) { | |
1148 | DUK_ASSERT(maxnargs >= 1 && maxnargs <= 4); | |
1149 | idx_first = DUK_DATE_IDX_MILLISECOND - (maxnargs - 1); | |
1150 | } else { | |
1151 | DUK_ASSERT(maxnargs >= 1 && maxnargs <= 3); | |
1152 | idx_first = DUK_DATE_IDX_DAY - (maxnargs - 1); | |
1153 | } | |
1154 | DUK_ASSERT_DISABLE(idx_first >= 0); /* unsigned */ | |
1155 | DUK_ASSERT(idx_first < DUK_DATE_IDX_NUM_PARTS); | |
1156 | ||
1157 | for (i = 0; i < maxnargs; i++) { | |
1158 | if ((duk_idx_t) i >= nargs) { | |
1159 | /* no argument given -> leave components untouched */ | |
1160 | break; | |
1161 | } | |
1162 | idx = idx_first + i; | |
1163 | DUK_ASSERT_DISABLE(idx >= 0); /* unsigned */ | |
1164 | DUK_ASSERT(idx < DUK_DATE_IDX_NUM_PARTS); | |
1165 | ||
1166 | if (idx == DUK_DATE_IDX_YEAR && (flags_and_maxnargs & DUK_DATE_FLAG_YEAR_FIXUP)) { | |
1167 | duk__twodigit_year_fixup(ctx, (duk_idx_t) i); | |
1168 | } | |
1169 | ||
1170 | dparts[idx] = duk_to_number(ctx, i); | |
1171 | ||
1172 | if (idx == DUK_DATE_IDX_DAY) { | |
1173 | /* Day-of-month is one-based in the API, but zero-based | |
1174 | * internally, so fix here. Note that month is zero-based | |
1175 | * both in the API and internally. | |
1176 | */ | |
1177 | /* SCANBUILD: complains about use of uninitialized values. | |
1178 | * The complaint is correct, but operating in undefined | |
1179 | * values here is intentional in some cases and the caller | |
1180 | * ignores the results. | |
1181 | */ | |
1182 | dparts[idx] -= 1.0; | |
1183 | } | |
1184 | } | |
1185 | ||
1186 | /* Leaves new timevalue on stack top and returns 1, which is correct | |
1187 | * for part setters. | |
1188 | */ | |
1189 | if (DUK_ISFINITE(d)) { | |
1190 | return duk__set_this_timeval_from_dparts(ctx, dparts, flags_and_maxnargs); | |
1191 | } else { | |
1192 | /* Internal timevalue is already NaN, so don't touch it. */ | |
1193 | duk_push_nan(ctx); | |
1194 | return 1; | |
1195 | } | |
1196 | } | |
1197 | ||
1198 | /* Apply ToNumber() to specified index; if ToInteger(val) in [0,99], add | |
1199 | * 1900 and replace value at idx_val. | |
1200 | */ | |
1201 | DUK_LOCAL void duk__twodigit_year_fixup(duk_context *ctx, duk_idx_t idx_val) { | |
1202 | duk_double_t d; | |
1203 | ||
1204 | /* XXX: idx_val would fit into 16 bits, but using duk_small_uint_t | |
1205 | * might not generate better code due to casting. | |
1206 | */ | |
1207 | ||
1208 | /* E5 Sections 15.9.3.1, B.2.4, B.2.5 */ | |
1209 | duk_to_number(ctx, idx_val); | |
1210 | if (duk_is_nan(ctx, idx_val)) { | |
1211 | return; | |
1212 | } | |
1213 | duk_dup(ctx, idx_val); | |
1214 | duk_to_int(ctx, -1); | |
1215 | d = duk_get_number(ctx, -1); /* get as double to handle huge numbers correctly */ | |
1216 | if (d >= 0.0 && d <= 99.0) { | |
1217 | d += 1900.0; | |
1218 | duk_push_number(ctx, d); | |
1219 | duk_replace(ctx, idx_val); | |
1220 | } | |
1221 | duk_pop(ctx); | |
1222 | } | |
1223 | ||
1224 | /* Set datetime parts from stack arguments, defaulting any missing values. | |
1225 | * Day-of-week is not set; it is not required when setting the time value. | |
1226 | */ | |
1227 | DUK_LOCAL void duk__set_parts_from_args(duk_context *ctx, duk_double_t *dparts, duk_idx_t nargs) { | |
1228 | duk_double_t d; | |
1229 | duk_small_uint_t i; | |
1230 | duk_small_uint_t idx; | |
1231 | ||
1232 | /* Causes a ToNumber() coercion, but doesn't break coercion order since | |
1233 | * year is coerced first anyway. | |
1234 | */ | |
1235 | duk__twodigit_year_fixup(ctx, 0); | |
1236 | ||
1237 | /* There are at most 7 args, but we use 8 here so that also | |
1238 | * DUK_DATE_IDX_WEEKDAY gets initialized (to zero) to avoid the potential | |
1239 | * for any Valgrind gripes later. | |
1240 | */ | |
1241 | for (i = 0; i < 8; i++) { | |
1242 | /* Note: rely on index ordering */ | |
1243 | idx = DUK_DATE_IDX_YEAR + i; | |
1244 | if ((duk_idx_t) i < nargs) { | |
1245 | d = duk_to_number(ctx, (duk_idx_t) i); | |
1246 | if (idx == DUK_DATE_IDX_DAY) { | |
1247 | /* Convert day from one-based to zero-based (internal). This may | |
1248 | * cause the day part to be negative, which is OK. | |
1249 | */ | |
1250 | d -= 1.0; | |
1251 | } | |
1252 | } else { | |
1253 | /* All components default to 0 except day-of-month which defaults | |
1254 | * to 1. However, because our internal day-of-month is zero-based, | |
1255 | * it also defaults to zero here. | |
1256 | */ | |
1257 | d = 0.0; | |
1258 | } | |
1259 | dparts[idx] = d; | |
1260 | } | |
1261 | ||
1262 | DUK_DDD(DUK_DDDPRINT("parts from args -> %lf %lf %lf %lf %lf %lf %lf %lf", | |
1263 | (double) dparts[0], (double) dparts[1], | |
1264 | (double) dparts[2], (double) dparts[3], | |
1265 | (double) dparts[4], (double) dparts[5], | |
1266 | (double) dparts[6], (double) dparts[7])); | |
1267 | } | |
1268 | ||
1269 | /* | |
1270 | * Helper to format a time value into caller buffer, used by logging. | |
1271 | * 'out_buf' must be at least DUK_BI_DATE_ISO8601_BUFSIZE long. | |
1272 | */ | |
1273 | ||
1274 | DUK_INTERNAL void duk_bi_date_format_timeval(duk_double_t timeval, duk_uint8_t *out_buf) { | |
1275 | duk_int_t parts[DUK_DATE_IDX_NUM_PARTS]; | |
1276 | ||
1277 | duk_bi_date_timeval_to_parts(timeval, | |
1278 | parts, | |
1279 | NULL, | |
1280 | DUK_DATE_FLAG_ONEBASED); | |
1281 | ||
1282 | duk__format_parts_iso8601(parts, | |
1283 | 0 /*tzoffset*/, | |
1284 | DUK_DATE_FLAG_TOSTRING_DATE | | |
1285 | DUK_DATE_FLAG_TOSTRING_TIME | | |
1286 | DUK_DATE_FLAG_SEP_T /*flags*/, | |
1287 | out_buf); | |
1288 | } | |
1289 | ||
1290 | /* | |
1291 | * Indirect magic value lookup for Date methods. | |
1292 | * | |
1293 | * Date methods don't put their control flags into the function magic value | |
1294 | * because they wouldn't fit into a LIGHTFUNC's magic field. Instead, the | |
1295 | * magic value is set to an index pointing to the array of control flags | |
1296 | * below. | |
1297 | * | |
1298 | * This must be kept in strict sync with genbuiltins.py! | |
1299 | */ | |
1300 | ||
1301 | static duk_uint16_t duk__date_magics[] = { | |
1302 | /* 0: toString */ | |
1303 | DUK_DATE_FLAG_TOSTRING_DATE + DUK_DATE_FLAG_TOSTRING_TIME + DUK_DATE_FLAG_LOCALTIME, | |
1304 | ||
1305 | /* 1: toDateString */ | |
1306 | DUK_DATE_FLAG_TOSTRING_DATE + DUK_DATE_FLAG_LOCALTIME, | |
1307 | ||
1308 | /* 2: toTimeString */ | |
1309 | DUK_DATE_FLAG_TOSTRING_TIME + DUK_DATE_FLAG_LOCALTIME, | |
1310 | ||
1311 | /* 3: toLocaleString */ | |
1312 | DUK_DATE_FLAG_TOSTRING_DATE + DUK_DATE_FLAG_TOSTRING_TIME + DUK_DATE_FLAG_TOSTRING_LOCALE + DUK_DATE_FLAG_LOCALTIME, | |
1313 | ||
1314 | /* 4: toLocaleDateString */ | |
1315 | DUK_DATE_FLAG_TOSTRING_DATE + DUK_DATE_FLAG_TOSTRING_LOCALE + DUK_DATE_FLAG_LOCALTIME, | |
1316 | ||
1317 | /* 5: toLocaleTimeString */ | |
1318 | DUK_DATE_FLAG_TOSTRING_TIME + DUK_DATE_FLAG_TOSTRING_LOCALE + DUK_DATE_FLAG_LOCALTIME, | |
1319 | ||
1320 | /* 6: toUTCString */ | |
1321 | DUK_DATE_FLAG_TOSTRING_DATE + DUK_DATE_FLAG_TOSTRING_TIME, | |
1322 | ||
1323 | /* 7: toISOString */ | |
1324 | DUK_DATE_FLAG_TOSTRING_DATE + DUK_DATE_FLAG_TOSTRING_TIME + DUK_DATE_FLAG_NAN_TO_RANGE_ERROR + DUK_DATE_FLAG_SEP_T, | |
1325 | ||
1326 | /* 8: getFullYear */ | |
1327 | DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_YEAR << DUK_DATE_FLAG_VALUE_SHIFT), | |
1328 | ||
1329 | /* 9: getUTCFullYear */ | |
1330 | 0 + (DUK_DATE_IDX_YEAR << DUK_DATE_FLAG_VALUE_SHIFT), | |
1331 | ||
1332 | /* 10: getMonth */ | |
1333 | DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_MONTH << DUK_DATE_FLAG_VALUE_SHIFT), | |
1334 | ||
1335 | /* 11: getUTCMonth */ | |
1336 | 0 + (DUK_DATE_IDX_MONTH << DUK_DATE_FLAG_VALUE_SHIFT), | |
1337 | ||
1338 | /* 12: getDate */ | |
1339 | DUK_DATE_FLAG_ONEBASED + DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_DAY << DUK_DATE_FLAG_VALUE_SHIFT), | |
1340 | ||
1341 | /* 13: getUTCDate */ | |
1342 | DUK_DATE_FLAG_ONEBASED + (DUK_DATE_IDX_DAY << DUK_DATE_FLAG_VALUE_SHIFT), | |
1343 | ||
1344 | /* 14: getDay */ | |
1345 | DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_WEEKDAY << DUK_DATE_FLAG_VALUE_SHIFT), | |
1346 | ||
1347 | /* 15: getUTCDay */ | |
1348 | 0 + (DUK_DATE_IDX_WEEKDAY << DUK_DATE_FLAG_VALUE_SHIFT), | |
1349 | ||
1350 | /* 16: getHours */ | |
1351 | DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_HOUR << DUK_DATE_FLAG_VALUE_SHIFT), | |
1352 | ||
1353 | /* 17: getUTCHours */ | |
1354 | 0 + (DUK_DATE_IDX_HOUR << DUK_DATE_FLAG_VALUE_SHIFT), | |
1355 | ||
1356 | /* 18: getMinutes */ | |
1357 | DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_MINUTE << DUK_DATE_FLAG_VALUE_SHIFT), | |
1358 | ||
1359 | /* 19: getUTCMinutes */ | |
1360 | 0 + (DUK_DATE_IDX_MINUTE << DUK_DATE_FLAG_VALUE_SHIFT), | |
1361 | ||
1362 | /* 20: getSeconds */ | |
1363 | DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_SECOND << DUK_DATE_FLAG_VALUE_SHIFT), | |
1364 | ||
1365 | /* 21: getUTCSeconds */ | |
1366 | 0 + (DUK_DATE_IDX_SECOND << DUK_DATE_FLAG_VALUE_SHIFT), | |
1367 | ||
1368 | /* 22: getMilliseconds */ | |
1369 | DUK_DATE_FLAG_LOCALTIME + (DUK_DATE_IDX_MILLISECOND << DUK_DATE_FLAG_VALUE_SHIFT), | |
1370 | ||
1371 | /* 23: getUTCMilliseconds */ | |
1372 | 0 + (DUK_DATE_IDX_MILLISECOND << DUK_DATE_FLAG_VALUE_SHIFT), | |
1373 | ||
1374 | /* 24: setMilliseconds */ | |
1375 | DUK_DATE_FLAG_TIMESETTER + DUK_DATE_FLAG_LOCALTIME + (1 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1376 | ||
1377 | /* 25: setUTCMilliseconds */ | |
1378 | DUK_DATE_FLAG_TIMESETTER + (1 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1379 | ||
1380 | /* 26: setSeconds */ | |
1381 | DUK_DATE_FLAG_TIMESETTER + DUK_DATE_FLAG_LOCALTIME + (2 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1382 | ||
1383 | /* 27: setUTCSeconds */ | |
1384 | DUK_DATE_FLAG_TIMESETTER + (2 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1385 | ||
1386 | /* 28: setMinutes */ | |
1387 | DUK_DATE_FLAG_TIMESETTER + DUK_DATE_FLAG_LOCALTIME + (3 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1388 | ||
1389 | /* 29: setUTCMinutes */ | |
1390 | DUK_DATE_FLAG_TIMESETTER + (3 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1391 | ||
1392 | /* 30: setHours */ | |
1393 | DUK_DATE_FLAG_TIMESETTER + DUK_DATE_FLAG_LOCALTIME + (4 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1394 | ||
1395 | /* 31: setUTCHours */ | |
1396 | DUK_DATE_FLAG_TIMESETTER + (4 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1397 | ||
1398 | /* 32: setDate */ | |
1399 | DUK_DATE_FLAG_LOCALTIME + (1 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1400 | ||
1401 | /* 33: setUTCDate */ | |
1402 | 0 + (1 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1403 | ||
1404 | /* 34: setMonth */ | |
1405 | DUK_DATE_FLAG_LOCALTIME + (2 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1406 | ||
1407 | /* 35: setUTCMonth */ | |
1408 | 0 + (2 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1409 | ||
1410 | /* 36: setFullYear */ | |
1411 | DUK_DATE_FLAG_NAN_TO_ZERO + DUK_DATE_FLAG_LOCALTIME + (3 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1412 | ||
1413 | /* 37: setUTCFullYear */ | |
1414 | DUK_DATE_FLAG_NAN_TO_ZERO + (3 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1415 | ||
1416 | /* 38: getYear */ | |
1417 | DUK_DATE_FLAG_LOCALTIME + DUK_DATE_FLAG_SUB1900 + (DUK_DATE_IDX_YEAR << DUK_DATE_FLAG_VALUE_SHIFT), | |
1418 | ||
1419 | /* 39: setYear */ | |
1420 | DUK_DATE_FLAG_NAN_TO_ZERO + DUK_DATE_FLAG_YEAR_FIXUP + (3 << DUK_DATE_FLAG_VALUE_SHIFT), | |
1421 | }; | |
1422 | ||
1423 | DUK_LOCAL duk_small_uint_t duk__date_get_indirect_magic(duk_context *ctx) { | |
1424 | duk_small_int_t magicidx = (duk_small_uint_t) duk_get_current_magic(ctx); | |
1425 | DUK_ASSERT(magicidx >= 0 && magicidx < (duk_small_int_t) (sizeof(duk__date_magics) / sizeof(duk_uint16_t))); | |
1426 | return (duk_small_uint_t) duk__date_magics[magicidx]; | |
1427 | } | |
1428 | ||
1429 | /* | |
1430 | * Constructor calls | |
1431 | */ | |
1432 | ||
1433 | DUK_INTERNAL duk_ret_t duk_bi_date_constructor(duk_context *ctx) { | |
1434 | duk_idx_t nargs = duk_get_top(ctx); | |
1435 | duk_bool_t is_cons = duk_is_constructor_call(ctx); | |
1436 | duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS]; | |
1437 | duk_double_t d; | |
1438 | ||
1439 | DUK_DDD(DUK_DDDPRINT("Date constructor, nargs=%ld, is_cons=%ld", (long) nargs, (long) is_cons)); | |
1440 | ||
1441 | duk_push_object_helper(ctx, | |
1442 | DUK_HOBJECT_FLAG_EXTENSIBLE | | |
1443 | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DATE), | |
1444 | DUK_BIDX_DATE_PROTOTYPE); | |
1445 | ||
1446 | /* Unlike most built-ins, the internal [[PrimitiveValue]] of a Date | |
1447 | * is mutable. | |
1448 | */ | |
1449 | ||
1450 | if (nargs == 0 || !is_cons) { | |
1451 | d = duk__timeclip(DUK_USE_DATE_GET_NOW(ctx)); | |
1452 | duk_push_number(ctx, d); | |
1453 | duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_W); | |
1454 | if (!is_cons) { | |
1455 | /* called as a normal function: return new Date().toString() */ | |
1456 | duk_to_string(ctx, -1); | |
1457 | } | |
1458 | return 1; | |
1459 | } else if (nargs == 1) { | |
1460 | duk_to_primitive(ctx, 0, DUK_HINT_NONE); | |
1461 | if (duk_is_string(ctx, 0)) { | |
1462 | duk__parse_string(ctx, duk_to_string(ctx, 0)); | |
1463 | duk_replace(ctx, 0); /* may be NaN */ | |
1464 | } | |
1465 | d = duk__timeclip(duk_to_number(ctx, 0)); | |
1466 | duk_push_number(ctx, d); | |
1467 | duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_W); | |
1468 | return 1; | |
1469 | } | |
1470 | ||
1471 | duk__set_parts_from_args(ctx, dparts, nargs); | |
1472 | ||
1473 | /* Parts are in local time, convert when setting. */ | |
1474 | ||
1475 | (void) duk__set_this_timeval_from_dparts(ctx, dparts, DUK_DATE_FLAG_LOCALTIME /*flags*/); /* -> [ ... this timeval ] */ | |
1476 | duk_pop(ctx); /* -> [ ... this ] */ | |
1477 | return 1; | |
1478 | } | |
1479 | ||
1480 | DUK_INTERNAL duk_ret_t duk_bi_date_constructor_parse(duk_context *ctx) { | |
1481 | return duk__parse_string(ctx, duk_to_string(ctx, 0)); | |
1482 | } | |
1483 | ||
1484 | DUK_INTERNAL duk_ret_t duk_bi_date_constructor_utc(duk_context *ctx) { | |
1485 | duk_idx_t nargs = duk_get_top(ctx); | |
1486 | duk_double_t dparts[DUK_DATE_IDX_NUM_PARTS]; | |
1487 | duk_double_t d; | |
1488 | ||
1489 | /* Behavior for nargs < 2 is implementation dependent: currently we'll | |
1490 | * set a NaN time value (matching V8 behavior) in this case. | |
1491 | */ | |
1492 | ||
1493 | if (nargs < 2) { | |
1494 | duk_push_nan(ctx); | |
1495 | } else { | |
1496 | duk__set_parts_from_args(ctx, dparts, nargs); | |
1497 | d = duk_bi_date_get_timeval_from_dparts(dparts, 0 /*flags*/); | |
1498 | duk_push_number(ctx, d); | |
1499 | } | |
1500 | return 1; | |
1501 | } | |
1502 | ||
1503 | DUK_INTERNAL duk_ret_t duk_bi_date_constructor_now(duk_context *ctx) { | |
1504 | duk_double_t d; | |
1505 | ||
1506 | d = DUK_USE_DATE_GET_NOW(ctx); | |
1507 | DUK_ASSERT(duk__timeclip(d) == d); /* TimeClip() should never be necessary */ | |
1508 | duk_push_number(ctx, d); | |
1509 | return 1; | |
1510 | } | |
1511 | ||
1512 | /* | |
1513 | * String/JSON conversions | |
1514 | * | |
1515 | * Human readable conversions are now basically ISO 8601 with a space | |
1516 | * (instead of 'T') as the date/time separator. This is a good baseline | |
1517 | * and is platform independent. | |
1518 | * | |
1519 | * A shared native helper to provide many conversions. Magic value contains | |
1520 | * a set of flags. The helper provides: | |
1521 | * | |
1522 | * toString() | |
1523 | * toDateString() | |
1524 | * toTimeString() | |
1525 | * toLocaleString() | |
1526 | * toLocaleDateString() | |
1527 | * toLocaleTimeString() | |
1528 | * toUTCString() | |
1529 | * toISOString() | |
1530 | * | |
1531 | * Notes: | |
1532 | * | |
1533 | * - Date.prototype.toGMTString() and Date.prototype.toUTCString() are | |
1534 | * required to be the same Ecmascript function object (!), so it is | |
1535 | * omitted from here. | |
1536 | * | |
1537 | * - Date.prototype.toUTCString(): E5.1 specification does not require a | |
1538 | * specific format, but result should be human readable. The | |
1539 | * specification suggests using ISO 8601 format with a space (instead | |
1540 | * of 'T') separator if a more human readable format is not available. | |
1541 | * | |
1542 | * - Date.prototype.toISOString(): unlike other conversion functions, | |
1543 | * toISOString() requires a RangeError for invalid date values. | |
1544 | */ | |
1545 | ||
1546 | DUK_INTERNAL duk_ret_t duk_bi_date_prototype_tostring_shared(duk_context *ctx) { | |
1547 | duk_small_uint_t flags = duk__date_get_indirect_magic(ctx); | |
1548 | return duk__to_string_helper(ctx, flags); | |
1549 | } | |
1550 | ||
1551 | DUK_INTERNAL duk_ret_t duk_bi_date_prototype_value_of(duk_context *ctx) { | |
1552 | /* This native function is also used for Date.prototype.getTime() | |
1553 | * as their behavior is identical. | |
1554 | */ | |
1555 | ||
1556 | duk_double_t d = duk__push_this_get_timeval(ctx, 0 /*flags*/); /* -> [ this ] */ | |
1557 | DUK_ASSERT(DUK_ISFINITE(d) || DUK_ISNAN(d)); | |
1558 | duk_push_number(ctx, d); | |
1559 | return 1; | |
1560 | } | |
1561 | ||
1562 | DUK_INTERNAL duk_ret_t duk_bi_date_prototype_to_json(duk_context *ctx) { | |
1563 | /* Note: toJSON() is a generic function which works even if 'this' | |
1564 | * is not a Date. The sole argument is ignored. | |
1565 | */ | |
1566 | ||
1567 | duk_push_this(ctx); | |
1568 | duk_to_object(ctx, -1); | |
1569 | ||
1570 | duk_dup_top(ctx); | |
1571 | duk_to_primitive(ctx, -1, DUK_HINT_NUMBER); | |
1572 | if (duk_is_number(ctx, -1)) { | |
1573 | duk_double_t d = duk_get_number(ctx, -1); | |
1574 | if (!DUK_ISFINITE(d)) { | |
1575 | duk_push_null(ctx); | |
1576 | return 1; | |
1577 | } | |
1578 | } | |
1579 | duk_pop(ctx); | |
1580 | ||
1581 | duk_get_prop_stridx(ctx, -1, DUK_STRIDX_TO_ISO_STRING); | |
1582 | duk_dup(ctx, -2); /* -> [ O toIsoString O ] */ | |
1583 | duk_call_method(ctx, 0); | |
1584 | return 1; | |
1585 | } | |
1586 | ||
1587 | /* | |
1588 | * Getters. | |
1589 | * | |
1590 | * Implementing getters is quite easy. The internal time value is either | |
1591 | * NaN, or represents milliseconds (without fractions) from Jan 1, 1970. | |
1592 | * The internal time value can be converted to integer parts, and each | |
1593 | * part will be normalized and will fit into a 32-bit signed integer. | |
1594 | * | |
1595 | * A shared native helper to provide all getters. Magic value contains | |
1596 | * a set of flags and also packs the date component index argument. The | |
1597 | * helper provides: | |
1598 | * | |
1599 | * getFullYear() | |
1600 | * getUTCFullYear() | |
1601 | * getMonth() | |
1602 | * getUTCMonth() | |
1603 | * getDate() | |
1604 | * getUTCDate() | |
1605 | * getDay() | |
1606 | * getUTCDay() | |
1607 | * getHours() | |
1608 | * getUTCHours() | |
1609 | * getMinutes() | |
1610 | * getUTCMinutes() | |
1611 | * getSeconds() | |
1612 | * getUTCSeconds() | |
1613 | * getMilliseconds() | |
1614 | * getUTCMilliseconds() | |
1615 | * getYear() | |
1616 | * | |
1617 | * Notes: | |
1618 | * | |
1619 | * - Date.prototype.getDate(): 'date' means day-of-month, and is | |
1620 | * zero-based in internal calculations but public API expects it to | |
1621 | * be one-based. | |
1622 | * | |
1623 | * - Date.prototype.getTime() and Date.prototype.valueOf() have identical | |
1624 | * behavior. They have separate function objects, but share the same C | |
1625 | * function (duk_bi_date_prototype_value_of). | |
1626 | */ | |
1627 | ||
1628 | DUK_INTERNAL duk_ret_t duk_bi_date_prototype_get_shared(duk_context *ctx) { | |
1629 | duk_small_uint_t flags_and_idx = duk__date_get_indirect_magic(ctx); | |
1630 | return duk__get_part_helper(ctx, flags_and_idx); | |
1631 | } | |
1632 | ||
1633 | DUK_INTERNAL duk_ret_t duk_bi_date_prototype_get_timezone_offset(duk_context *ctx) { | |
1634 | /* | |
1635 | * Return (t - LocalTime(t)) in minutes: | |
1636 | * | |
1637 | * t - LocalTime(t) = t - (t + LocalTZA + DaylightSavingTA(t)) | |
1638 | * = -(LocalTZA + DaylightSavingTA(t)) | |
1639 | * | |
1640 | * where DaylightSavingTA() is checked for time 't'. | |
1641 | * | |
1642 | * Note that the sign of the result is opposite to common usage, | |
1643 | * e.g. for EE(S)T which normally is +2h or +3h from UTC, this | |
1644 | * function returns -120 or -180. | |
1645 | * | |
1646 | */ | |
1647 | ||
1648 | duk_double_t d; | |
1649 | duk_int_t tzoffset; | |
1650 | ||
1651 | /* Note: DST adjustment is determined using UTC time. */ | |
1652 | d = duk__push_this_get_timeval(ctx, 0 /*flags*/); | |
1653 | DUK_ASSERT(DUK_ISFINITE(d) || DUK_ISNAN(d)); | |
1654 | if (DUK_ISNAN(d)) { | |
1655 | duk_push_nan(ctx); | |
1656 | } else { | |
1657 | DUK_ASSERT(DUK_ISFINITE(d)); | |
1658 | tzoffset = DUK_USE_DATE_GET_LOCAL_TZOFFSET(d); | |
1659 | duk_push_int(ctx, -tzoffset / 60); | |
1660 | } | |
1661 | return 1; | |
1662 | } | |
1663 | ||
1664 | /* | |
1665 | * Setters. | |
1666 | * | |
1667 | * Setters are a bit more complicated than getters. Component setters | |
1668 | * break down the current time value into its (normalized) component | |
1669 | * parts, replace one or more components with -unnormalized- new values, | |
1670 | * and the components are then converted back into a time value. As an | |
1671 | * example of using unnormalized values: | |
1672 | * | |
1673 | * var d = new Date(1234567890); | |
1674 | * | |
1675 | * is equivalent to: | |
1676 | * | |
1677 | * var d = new Date(0); | |
1678 | * d.setUTCMilliseconds(1234567890); | |
1679 | * | |
1680 | * A shared native helper to provide almost all setters. Magic value | |
1681 | * contains a set of flags and also packs the "maxnargs" argument. The | |
1682 | * helper provides: | |
1683 | * | |
1684 | * setMilliseconds() | |
1685 | * setUTCMilliseconds() | |
1686 | * setSeconds() | |
1687 | * setUTCSeconds() | |
1688 | * setMinutes() | |
1689 | * setUTCMinutes() | |
1690 | * setHours() | |
1691 | * setUTCHours() | |
1692 | * setDate() | |
1693 | * setUTCDate() | |
1694 | * setMonth() | |
1695 | * setUTCMonth() | |
1696 | * setFullYear() | |
1697 | * setUTCFullYear() | |
1698 | * setYear() | |
1699 | * | |
1700 | * Notes: | |
1701 | * | |
1702 | * - Date.prototype.setYear() (Section B addition): special year check | |
1703 | * is omitted. NaN / Infinity will just flow through and ultimately | |
1704 | * result in a NaN internal time value. | |
1705 | * | |
1706 | * - Date.prototype.setYear() does not have optional arguments for | |
1707 | * setting month and day-in-month (like setFullYear()), but we indicate | |
1708 | * 'maxnargs' to be 3 to get the year written to the correct component | |
1709 | * index in duk__set_part_helper(). The function has nargs == 1, so only | |
1710 | * the year will be set regardless of actual argument count. | |
1711 | */ | |
1712 | ||
1713 | DUK_INTERNAL duk_ret_t duk_bi_date_prototype_set_shared(duk_context *ctx) { | |
1714 | duk_small_uint_t flags_and_maxnargs = duk__date_get_indirect_magic(ctx); | |
1715 | return duk__set_part_helper(ctx, flags_and_maxnargs); | |
1716 | } | |
1717 | ||
1718 | DUK_INTERNAL duk_ret_t duk_bi_date_prototype_set_time(duk_context *ctx) { | |
1719 | duk_double_t d; | |
1720 | ||
1721 | (void) duk__push_this_get_timeval(ctx, 0 /*flags*/); /* -> [ timeval this ] */ | |
1722 | d = duk__timeclip(duk_to_number(ctx, 0)); | |
1723 | duk_push_number(ctx, d); | |
1724 | duk_dup_top(ctx); | |
1725 | duk_put_prop_stridx(ctx, -3, DUK_STRIDX_INT_VALUE); /* -> [ timeval this timeval ] */ | |
1726 | ||
1727 | return 1; | |
1728 | } |