diff --git a/docs/content/limitations/builtin-apis/date.mdx b/docs/content/limitations/builtin-apis/date.mdx index db5278c53..81d37ebc3 100644 --- a/docs/content/limitations/builtin-apis/date.mdx +++ b/docs/content/limitations/builtin-apis/date.mdx @@ -1 +1 @@ -- **Date:** Date unions, year/month constructors, setters, statically typed TypeScript identity comparisons, thrown Date values, and locale/string formatters are unsupported. Zero-argument and single number/string constructors, storage and passing, `getTime`, `valueOf`, `toISOString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile. JavaScript Dates retain identity through checked storage and support Date-copy construction and JSON serialization. A TypeScript Date converted to `unknown` becomes a Date with the same time value; each conversion creates a new Date, so two `unknown` values converted from one TypeScript Date are not `===`. See [Dates and timezones](#dates-and-timezones) for parsing restrictions. +- **Date:** Date unions, year/month constructors, setters, statically typed TypeScript identity comparisons, thrown Date values, `toUTCString`, and locale formatters are unsupported. Zero-argument and single number/string constructors, storage and passing, `getTime`, `valueOf`, `toISOString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), `toString`, `toDateString`, `toTimeString`, `String(date)` and template literal conversion, calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile. JavaScript Dates retain identity through checked storage and support Date-copy construction and JSON serialization. A TypeScript Date converted to `unknown` becomes a Date with the same time value; each conversion creates a new Date, so two `unknown` values converted from one TypeScript Date are not `===`. See [Dates and timezones](#dates-and-timezones) for parsing restrictions. diff --git a/docs/content/limitations/dates-and-timezones.mdx b/docs/content/limitations/dates-and-timezones.mdx index ad830eb9a..e88cfb462 100644 --- a/docs/content/limitations/dates-and-timezones.mdx +++ b/docs/content/limitations/dates-and-timezones.mdx @@ -1,3 +1,5 @@ String constructors and `Date.parse` accept ECMAScript date-only forms, date-times with an explicit `Z` or numeric offset, and GMT certificate-validity strings from the supported X509 APIs. Other V8-specific forms and offset-less local date-times produce an Invalid Date or `NaN`; `SC6005` reports string literals in those forms. Local calendar getters use the operating system's timezone database. Historical results can differ from Node.js when timezone data differs. For valid extreme years outside a platform calendar API's range, scriptc uses a calendar-equivalent year in the 400-year Gregorian cycle to obtain the timezone rule. + +`toString`, `toTimeString`, `String(date)` and template literals print the zone's long name after the GMT offset, as Node does (`GMT-0500 (Central Daylight Time)`). scriptc identifies the zone the way Node does, from `TZ` when it names a zone, else from `/etc/localtime`. Outside Windows, a write to `process.env.TZ` changes the zone for later conversions and getters. The names are the en-US names for the IANA zones; Node prints them in the language of the locale that `LC_ALL`, `LC_MESSAGES` or `LANG` selects, so with a non-English locale the name differs from Node's. A `TZ` value that is a file path or a POSIX rule string (such as `EST5EDT,M3.2.0,M11.1.0`) gives offsets and names that differ from Node's, which ignores those forms. On Windows, the name is the `GMT+hh:mm` form unless `TZ` names a zone. On `wasm32-wasi`, local time is UTC and the name is `Coordinated Universal Time`. diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 67cb77047..87acd391d 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -325,6 +325,9 @@ export const LIB_FN_SYMS: Record = { "date.getMilliseconds": "scr_date_get_milliseconds", "date.getUTCMilliseconds": "scr_date_get_milliseconds", "date.getTimezoneOffset": "scr_date_get_timezone_offset", + "date.toString": "scr_date_to_string", + "date.toDateString": "scr_date_to_date_string", + "date.toTimeString": "scr_date_to_time_string", "fs.existsSync": "scr_fs_exists", // ── the throwing slice (MAY_THROW_LIB_FNS members): the generic path // emits the standard pending check after each — exprs.ts's finish. diff --git a/packages/compiler/src/frontend/lowering/builtins/dates.ts b/packages/compiler/src/frontend/lowering/builtins/dates.ts index fa880bdc9..896eeaf28 100644 --- a/packages/compiler/src/frontend/lowering/builtins/dates.ts +++ b/packages/compiler/src/frontend/lowering/builtins/dates.ts @@ -33,8 +33,14 @@ const DATE_GETTER_FNS: Readonly> = { getTimezoneOffset: "date.getTimezoneOffset", }; +const DATE_TEXT_FNS: ReadonlyMap = new Map([ + ["toString", "date.toString"], + ["toDateString", "date.toDateString"], + ["toTimeString", "date.toTimeString"], +]); + const DATE_METHOD_HINT = - "getTime(), valueOf(), toISOString(), toJSON(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale/string formatters have no lowering"; + "getTime(), valueOf(), toISOString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters, toUTCString() and locale formatters have no lowering"; /** The Date slice: statics plus read-only Date values backed by one * TimeClip'd millisecond scalar. Construction lives in lowerNew; @@ -117,6 +123,10 @@ export function lowerDateCall( if (fn !== undefined) { return { kind: "libCall", fn, args: [receiver], type: F64, loc }; } + const textFn = DATE_TEXT_FNS.get(name); + if (textFn !== undefined) { + return { kind: "libCall", fn: textFn, args: [receiver], type: STRING, loc }; + } lowerer.noLowering( `Date.prototype.${name}`, call, diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index d72faa936..a19c8e4ae 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -8863,6 +8863,10 @@ export function ensureString(lowerer: Lowerer, e: IrExpr, node: ts.Node): IrExpr if (e.type.kind === "string") return e; if (e.type.kind === "url") return { kind: "libCall", fn: "url.href", args: [e], type: STRING, loc: e.loc }; + // String(date) / `${date}` / "" + date: Date's ToPrimitive prefers + // toString(). + if (e.type.kind === "date") + return { kind: "libCall", fn: "date.toString", args: [e], type: STRING, loc: e.loc }; if (e.type.kind === "regex") { return { kind: "regexIntrinsic", diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 44ed37e3c..0dead1c35 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -2321,6 +2321,25 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [ name: "Date.prototype.getTimezoneOffset", fns: ["date.getTimezoneOffset"], }, + { + id: "stdlib.date.toString", + kind: "stdlib", + name: "Date.prototype.toString", + fns: ["date.toString"], + note: "local time with the host zone's long English name; String(date) and template literals use it", + }, + { + id: "stdlib.date.toDateString", + kind: "stdlib", + name: "Date.prototype.toDateString", + fns: ["date.toDateString"], + }, + { + id: "stdlib.date.toTimeString", + kind: "stdlib", + name: "Date.prototype.toTimeString", + fns: ["date.toTimeString"], + }, // ── perf_hooks (lowerPerfHooksCall): the monotonic clock. { id: "node-builtin.perf_hooks.performance.now", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e61bb2594..a87e40659 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -4208,6 +4208,12 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "date.getMilliseconds": { argTypes: [DATE_T], result: F64 }, "date.getUTCMilliseconds": { argTypes: [DATE_T], result: F64 }, "date.getTimezoneOffset": { argTypes: [DATE_T], result: F64 }, + /** Date.prototype.toString / toDateString / toTimeString and String(date): + * V8's local-time formats with the host zone's long name, "Invalid Date" + * for an invalid date. Owned (+1) string; never throws. */ + "date.toString": { argTypes: [DATE_T], result: STRING }, + "date.toDateString": { argTypes: [DATE_T], result: STRING }, + "date.toTimeString": { argTypes: [DATE_T], result: STRING }, /** The composed `new Date(dateString).getTime()` read: one borrowed * string, f64 milliseconds since epoch. The parsed grammar is BOUNDED * (documented divergence — V8's parser accepts far more): the ASN.1 diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index a167254c2..637ecc2a0 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -4209,6 +4209,12 @@ "status": "static", "note": "one date string, using the bounded parser shared with new Date(dateString).getTime()" }, + { + "id": "stdlib.date.toDateString", + "kind": "stdlib", + "name": "Date.prototype.toDateString", + "status": "static" + }, { "id": "stdlib.date.toISOString", "kind": "stdlib", @@ -4216,6 +4222,19 @@ "status": "static", "note": "UTC ISO formatting over constructed and stored Date values" }, + { + "id": "stdlib.date.toString", + "kind": "stdlib", + "name": "Date.prototype.toString", + "status": "static", + "note": "local time with the host zone's long English name; String(date) and template literals use it" + }, + { + "id": "stdlib.date.toTimeString", + "kind": "stdlib", + "name": "Date.prototype.toTimeString", + "status": "static" + }, { "id": "stdlib.date.valueOf", "kind": "stdlib", diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5963-date-to-string.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5963-date-to-string.ts.json new file mode 100644 index 000000000..b6743d677 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5963-date-to-string.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5963-date-to-string.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5964-date-dynamic-conversions.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5964-date-dynamic-conversions.js.json new file mode 100644 index 000000000..1609e844d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5964-date-dynamic-conversions.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5964-date-dynamic-conversions.js" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 401933904..f1814e914 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -6805,6 +6805,15 @@ static bool scr_dyn_error_encoding(const ScrDyn *d) { return d->kind == SCR_DYN_OBJ && scr_dyn_obj_get(d, "%error", 6) != NULL; } +static bool scr_dyn_is_date(const ScrDyn *d) { + return d->kind == SCR_DYN_HANDLE && d->v.handle.tag == SCR_DYNH_DATE; +} + +/* A Date's valueOf(): its time value as a number. +1. */ +static ScrDyn *scr_dyn_date_value_of(const ScrDyn *d) { + return scr_dyn_handle_ops_of(d)->invoke(d->v.handle.ptr, (ScrDyn *)d, "valueOf", NULL, 0, "valueOf"); +} + /* Receiver-kind-dispatched toString() on a checked-dynamic value (the * dyn method surface — a stream's 'data'/for-await chunk is the common * receiver): bytes decode per the encoding (Node's Buffer.toString, @@ -6843,7 +6852,7 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) { if (scr_dyn_error_encoding(d) && !scr_dyn_property_owner(d, "toString", 8)) return scr_dyn_error_string(d); return scr_str_new("[object Object]", 15); case SCR_DYN_HANDLE: - if (d->v.handle.tag == SCR_DYNH_REGEXP || d->v.handle.tag == SCR_DYNH_URL) { + if (d->v.handle.tag == SCR_DYNH_REGEXP || d->v.handle.tag == SCR_DYNH_URL || d->v.handle.tag == SCR_DYNH_DATE) { ScrDyn *text = scr_dyn_handle_ops_of(d)->invoke(d->v.handle.ptr, (ScrDyn *)d, "toString", NULL, 0, "RegExp.toString"); if (!text) return NULL; ScrStr *out = scr_str_retain(text->v.str); @@ -7267,6 +7276,14 @@ bool scr_dyn_number_coerce_js(const ScrDyn *d, double *out) { return false; } default: { + /* A Date's valueOf answers its time value. */ + if (scr_dyn_is_date(d)) { + ScrDyn *time = scr_dyn_date_value_of(d); + if (!time) return false; + *out = time->v.num; + scr_dyn_release(time); + return true; + } /* Arrays, byte views, functions, promises, and native handles inherit a * valueOf that returns the receiver, then use their existing JS-exact * string rendering for the numeric conversion. */ @@ -7287,8 +7304,14 @@ double scr_dyn_number_coerce(const ScrDyn *d) { static ScrDyn *scr_dyn_add_primitive(const ScrDyn *d); +/* ToPrimitive with the number hint, which differs from the default hint + * only for a Date: its time value. */ +static ScrDyn *scr_dyn_number_primitive(const ScrDyn *d) { + return scr_dyn_is_date(d) ? scr_dyn_date_value_of(d) : scr_dyn_add_primitive(d); +} + double scr_dyn_number_constructor(const ScrDyn *d) { - ScrDyn *primitive = scr_dyn_add_primitive(d); + ScrDyn *primitive = scr_dyn_number_primitive(d); if (!primitive) return NAN; double result = primitive->kind == SCR_DYN_BIGINT ? scr_bigint_to_f64(primitive->v.bigint) : scr_dyn_number_coerce(primitive); @@ -7297,7 +7320,7 @@ double scr_dyn_number_constructor(const ScrDyn *d) { } static ScrBigInt *scr_dyn_bigint_convert(const ScrDyn *d, bool allow_number) { - ScrDyn *primitive = scr_dyn_add_primitive(d); + ScrDyn *primitive = scr_dyn_number_primitive(d); if (!primitive) return NULL; ScrBigInt *result = NULL; switch (primitive->kind) { @@ -7430,7 +7453,7 @@ ScrDyn *scr_dyn_add(const ScrDyn *left, const ScrDyn *right) { } ScrDyn *scr_dyn_to_numeric(const ScrDyn *value) { - ScrDyn *primitive = scr_dyn_add_primitive(value); + ScrDyn *primitive = scr_dyn_number_primitive(value); if (!primitive) return NULL; if (primitive->kind == SCR_DYN_BIGINT) return primitive; double number; @@ -7454,9 +7477,9 @@ ScrDyn *scr_dyn_increment(const ScrDyn *value, bool increment) { bool scr_dyn_compare(const ScrDyn *left, const ScrDyn *right, const ScrStr *operation) { // ToPrimitive is observable and always visits the original left operand // first, including the > and <= spellings of relational comparison. - ScrDyn *l = scr_dyn_add_primitive(left); + ScrDyn *l = scr_dyn_number_primitive(left); if (!l) return false; - ScrDyn *r = scr_dyn_add_primitive(right); + ScrDyn *r = scr_dyn_number_primitive(right); if (!r) { scr_dyn_release(l); return false; } double order = 2; if (l->kind == SCR_DYN_STR && r->kind == SCR_DYN_STR) { diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index af57ab057..1d45de4d4 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -10682,6 +10682,9 @@ double scr_date_utc(double y, double mo, double d, typedef struct ScrDateParts { long long year; int month, date, day, hours, minutes, seconds, milliseconds; + /* Local fields: tm_isdst at the instant (or its surrogate). It fills the + * padding before timezone_offset, so the struct keeps its size. */ + bool isdst; double timezone_offset; } ScrDateParts; @@ -10718,6 +10721,7 @@ static void scr_date_utc_parts_unchecked(double t, ScrDateParts *out) { out->seconds = (int)(msday / 1000 % 60); out->milliseconds = (int)(msday % 1000); out->timezone_offset = 0; + out->isdst = false; } static bool scr_date_utc_parts(double ms, ScrDateParts *out) { @@ -10801,6 +10805,7 @@ static bool scr_date_local_parts_uncached(double clipped, ScrDateParts *out) { scr_date_utc_parts_unchecked(clipped + local_offset * 1000.0, out); double timezone_offset = trunc(-local_offset / 60.0); out->timezone_offset = timezone_offset == 0 ? 0 : timezone_offset; + out->isdst = tmv.tm_isdst > 0; return true; } @@ -10840,6 +10845,364 @@ double scr_date_get_timezone_offset(double ms) { return scr_date_local_parts(ms, &p) ? p.timezone_offset : NAN; } +/* ── Date time zone names ─────────────────────────────────────────────── + * Long time zone names for Date.prototype.toString and toTimeString. + * + * Node prints "GMT-0500 (Central Daylight Time)": the name comes from ICU + * for the host zone, in standard or daylight form. ICU identifies the host + * zone from TZ when it looks like a zone ID, else from /etc/localtime (the + * symlink's target, or the zoneinfo file with the same contents), else + * from the libc zone abbreviation. The runtime follows the same steps and + * looks the ID up in the names Node prints for each tzdata zone + * (scr_timezone_data.h, generated). An ID without a row prints Node's + * fallback for a zone without a name, "GMT+hh:mm" with the zone's standard + * offset. + * + * Whether an instant is in daylight time follows V8: instants before 1970 + * or after 2038-01-19 use the instant with the same month, day and time in + * an equivalent year between 2008 and 2037 (same leap-year status and + * weekday of January 1), and tm_isdst decides. A few zones carry Node's + * own daylight-time changes instead: those whose tzdata rules use a + * negative daylight saving (Europe/Dublin's winter time, Morocco's Ramadan + * time), where ICU's data calls the larger offset daylight time, and those + * Node shows in daylight time through 2038 (British Columbia's permanent + * UTC-7, which tzdata names standard time). */ +#include "scr_timezone_data.h" + +#define SCR_TZ_ROWS (sizeof scr_tz_names / sizeof scr_tz_names[0]) + +/* Text that no other code in this file uses lives in named arrays: string + * literals would join the merged read-only strings of every program that + * links this file. */ +static const char scr_tz_posix_ids[4][8] = { "PST8PDT", "MST7MDT", "CST6CDT", "EST5EDT" }; +static const char scr_tz_prefixes[2][7] = { "posix/", "right/" }; +static const char scr_tz_env_name[] = "TZ"; +static const char scr_tz_utc[] = "UTC"; +static const char scr_tz_localtime_path[] = "/etc/localtime"; +static const char scr_tz_zoneinfo_tail[] = "/zoneinfo/"; +static const char scr_tz_zoneinfo_dir[] = "/usr/share/zoneinfo/"; +static const char scr_tz_skipped[4][11] = { ".", "..", "posixrules", "localtime" }; +static const char scr_tz_fallback_format[] = "GMT%c%02lld:%02lld"; +static const char scr_date_invalid_text[] = "Invalid Date"; + +/* The thread's host zone, detected again after a write to process.env.TZ + * (scr_date_tz_generation). */ +typedef struct { + unsigned generation; + const ScrTzNames *row; /* NULL: no row, use the fallback */ + bool resolved; + bool fallback_ready; + char fallback[16]; /* "GMT+hh:mm" */ +} ScrTzState; + +static SCR_TL ScrTzState scr_tz_state; + +/* The local UTC offset (seconds) and tm_isdst at an epoch second. */ +static bool scr_tz_offset_at(double sec, double *offset, bool *isdst) { + struct tm tmv; + if (!scr_date_localtime(sec, &tmv)) return false; + double local = + scr_days_from_civil((long long)tmv.tm_year + 1900, tmv.tm_mon + 1, tmv.tm_mday) * 86400.0 + + tmv.tm_hour * 3600.0 + tmv.tm_min * 60.0 + tmv.tm_sec; + *offset = local - sec; + *isdst = tmv.tm_isdst > 0; + return true; +} + +/* ICU's isValidOlsonID: letters and punctuation up to an optional run of at + * most two digits ending the string ("GMT+11"), or one of the four POSIX + * zone IDs tzdata also names. */ +static bool scr_tz_valid_id(const char *id) { + size_t i = 0; + while (id[i] && !(id[i] >= '0' && id[i] <= '9') && id[i] != ',') i++; + size_t max = i + 2; + while (id[i] && id[i] >= '0' && id[i] <= '9' && i < max) i++; + if (id[i] == 0) return true; + for (size_t k = 0; k < 4; k++) { + if (!strcmp(id, scr_tz_posix_ids[k])) return true; + } + return false; +} + +static const char *scr_tz_skip_prefix(const char *id) { + return !strncmp(id, scr_tz_prefixes[0], 6) || !strncmp(id, scr_tz_prefixes[1], 6) ? id + 6 : id; +} + +static const ScrTzNames *scr_tz_find(const char *id) { + size_t lo = 0, hi = SCR_TZ_ROWS; + while (lo < hi) { + size_t mid = lo + (hi - lo) / 2; + int c = strcmp(id, scr_tz_ids + scr_tz_names[mid].id); + if (c == 0) return &scr_tz_names[mid]; + if (c < 0) hi = mid; + else lo = mid + 1; + } + return NULL; +} + +#if !defined(_WIN32) && !defined(__wasi__) + +/* The bytes of a file, or NULL. */ +static char *scr_tz_read_file(const char *path, size_t *len) { + FILE *f = fopen(path, "rb"); + if (!f) return NULL; + size_t cap = 4096, n = 0; + char *data = malloc(cap); + while (data) { + n += fread(data + n, 1, cap - n, f); + if (n < cap) break; + char *grown = realloc(data, cap * 2); + if (!grown) { + free(data); + data = NULL; + break; + } + data = grown; + cap *= 2; + } + fclose(f); + *len = n; + return data; +} + +static bool scr_tz_same_file(const char *path, const char *data, size_t len) { + struct stat st; + if (stat(path, &st) != 0 || !S_ISREG(st.st_mode) || (size_t)st.st_size != len) return false; + size_t other_len; + char *other = scr_tz_read_file(path, &other_len); + bool same = other && other_len == len && memcmp(other, data, len) == 0; + free(other); + return same; +} + +/* ICU's search for a zoneinfo file with /etc/localtime's contents, in + * directory order, skipping "posixrules" and "localtime". Writes the + * path below the zoneinfo directory into `out`. */ +static bool scr_tz_search(char *dir, size_t dir_len, const char *data, size_t len, char *out, size_t cap) { + DIR *d = opendir(dir); + if (!d) return false; + bool found = false; + struct dirent *entry; + while (!found && (entry = readdir(d)) != NULL) { + const char *name = entry->d_name; + bool skipped = false; + for (size_t k = 0; k < 4; k++) skipped = skipped || !strcmp(name, scr_tz_skipped[k]); + if (skipped) continue; + size_t name_len = strlen(name); + if (dir_len + name_len + 2 > PATH_MAX) continue; + memcpy(dir + dir_len, name, name_len + 1); + DIR *sub = opendir(dir); + if (sub) { + closedir(sub); + dir[dir_len + name_len] = '/'; + dir[dir_len + name_len + 1] = 0; + found = scr_tz_search(dir, dir_len + name_len + 1, data, len, out, cap); + } else if (scr_tz_same_file(dir, data, len)) { + const char *id = scr_tz_skip_prefix(dir + strlen(scr_tz_zoneinfo_dir)); + size_t id_len = strlen(id); + if (id_len < cap) { + memcpy(out, id, id_len + 1); + found = true; + } + } + dir[dir_len] = 0; + } + closedir(d); + return found; +} +#endif + +/* The host zone's row, or NULL when ICU would not find a zone name. */ +static const ScrTzNames *scr_tz_detect(void) { +#if defined(__wasi__) + /* WASI's libc keeps every local time in UTC. */ + return scr_tz_find(scr_tz_utc); +#else + const char *tz = getenv(scr_tz_env_name); + if (tz && scr_tz_valid_id(tz)) { + if (tz[0] == ':') tz++; + return scr_tz_find(scr_tz_skip_prefix(tz)); + } +#ifdef _WIN32 + return NULL; +#else + /* The zone ID is the path below a zoneinfo directory of the + * /etc/localtime symlink's target, or of a zoneinfo file with the same + * contents when /etc/localtime is a regular file. */ + char path[PATH_MAX]; + if (realpath(scr_tz_localtime_path, path) && strcmp(path, scr_tz_localtime_path) != 0) { + const char *tail = strstr(path, scr_tz_zoneinfo_tail); + if (tail) { + tail = scr_tz_skip_prefix(tail + strlen(scr_tz_zoneinfo_tail)); + if (scr_tz_valid_id(tail)) return scr_tz_find(tail); + } + } else { + size_t len; + DIR *zoneinfo = opendir(scr_tz_zoneinfo_dir); + char *data = zoneinfo ? scr_tz_read_file(scr_tz_localtime_path, &len) : NULL; + if (zoneinfo) closedir(zoneinfo); + if (data) { + char dir[PATH_MAX]; + memcpy(dir, scr_tz_zoneinfo_dir, sizeof scr_tz_zoneinfo_dir); + char id[PATH_MAX]; + bool found = scr_tz_search(dir, strlen(dir), data, len, id, sizeof id); + free(data); + if (found && scr_tz_valid_id(id)) return scr_tz_find(id); + } + } + /* Otherwise the standard-time abbreviation names the zone ("UTC" when + * there is no /etc/localtime). */ + tzset(); + return scr_tz_find(tzname[0]); +#endif +#endif +} + +static ScrTzState *scr_tz_current(void) { + ScrTzState *s = &scr_tz_state; + unsigned generation = scr_date_tz_generation; + if (s->generation != generation || !s->resolved) { + s->generation = generation; + s->row = scr_tz_detect(); + s->resolved = true; + s->fallback_ready = false; + } + return s; +} + +/* "GMT+hh:mm" for the zone's current standard offset: the name Node prints + * for a zone ID it does not know. */ +static const char *scr_tz_fallback(ScrTzState *s) { + if (!s->fallback_ready) { + double offset = 0; + bool isdst = false; + long long seconds = 0; + if (scr_tz_offset_at((double)time(NULL), &offset, &isdst)) seconds = (long long)offset - (isdst ? 3600 : 0); + long long minutes = (seconds < 0 ? -seconds : seconds) / 60; + snprintf(s->fallback, sizeof s->fallback, scr_tz_fallback_format, seconds < 0 ? '-' : '+', + minutes / 60, minutes % 60); + s->fallback_ready = true; + } + return s->fallback; +} + +/* V8's EquivalentTime: the same month, day and time of day in a year + * between 2008 and 2037 with the same leap-year status and weekday of + * January 1. */ +static double scr_tz_equivalent_time(double ms) { + ScrDateParts p; + scr_date_utc_parts_unchecked(ms, &p); + double within = ms - floor(ms / 86400000.0) * 86400000.0; + long long year = p.year; + bool leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; + long long week_day = ((long long)scr_days_from_civil(year, 1, 1) + 4) % 7; + if (week_day < 0) week_day += 7; + long long recent = (leap ? 1956 : 1967) + (week_day * 12) % 28; + long long equivalent = 2008 + (recent + 3 * 28 - 2008) % 28; + return (scr_days_from_civil(equivalent, p.month + 1, 1) + p.date - 1) * 86400000.0 + within; +} + +static bool scr_tz_is_daylight(double ms, int isdst_hint, const ScrTzNames *row) { + /* V8 names instants in [0, 2^31 - 1] seconds directly. */ + if (ms < 0 || (long long)ms > 2147483647000LL) { + ms = scr_tz_equivalent_time(ms); + isdst_hint = -1; + } + double sec = floor(ms / 1000.0); + if (row->change_count > 0) { + /* Daylight time after an odd number of changes. */ + const int32_t *changes = scr_tz_daylight_changes + row->first_change; + size_t lo = 0, hi = row->change_count; + while (lo < hi) { + size_t mid = lo + (hi - lo) / 2; + if (changes[mid] <= sec) lo = mid + 1; + else hi = mid; + } + return (lo & 1) != 0; + } + if (isdst_hint >= 0) return isdst_hint != 0; + double offset; + bool isdst; + return scr_tz_offset_at(sec, &offset, &isdst) && isdst; +} + +const char *scr_date_zone_name(double ms, int isdst) { + ScrTzState *s = scr_tz_current(); + if (!s->row) return scr_tz_fallback(s); + bool daylight = scr_tz_is_daylight(ms, isdst, s->row); + return scr_tz_name_text + (daylight ? s->row->daylight : s->row->standard); +} + +/* Date.prototype.toString, toDateString and toTimeString: V8's formats over + * the local calendar fields ("Sat Oct 18 2026 09:12:00 GMT-0500 (Central + * Daylight Time)"), with years before 1 as "-0001", and "Invalid Date" for + * an invalid date. */ +enum { SCR_DATE_TEXT_FULL, SCR_DATE_TEXT_DATE, SCR_DATE_TEXT_TIME }; + +/* `value` (>= 0) in decimal, zero-padded to `width` digits. */ +static char *scr_date_put_digits(char *p, long long value, int width) { + char digits[24]; + int n = 0; + do { + digits[n++] = (char)('0' + value % 10); + value /= 10; + } while (value > 0); + while (n < width) digits[n++] = '0'; + while (n > 0) *p++ = digits[--n]; + return p; +} + +static ScrStr *scr_date_text(double ms, int mode) { + static const char week_days[7][3] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + static const char months[12][3] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + ScrDateParts p; + if (!scr_date_local_parts(ms, &p)) return scr_str_new(scr_date_invalid_text, sizeof scr_date_invalid_text - 1); + char buf[160]; + char *o = buf; + if (mode != SCR_DATE_TEXT_TIME) { + memcpy(o, week_days[p.day], 3); + o[3] = ' '; + memcpy(o + 4, months[p.month], 3); + o[7] = ' '; + o = scr_date_put_digits(o + 8, p.date, 2); + *o++ = ' '; + /* "%04d", or "%05d" (a sign and four digits) before year 0. */ + if (p.year < 0) *o++ = '-'; + o = scr_date_put_digits(o, p.year < 0 ? -p.year : p.year, 4); + } + if (mode == SCR_DATE_TEXT_FULL) *o++ = ' '; + if (mode != SCR_DATE_TEXT_DATE) { + int offset = -(int)p.timezone_offset; + int magnitude = offset < 0 ? -offset : offset; + o = scr_date_put_digits(o, p.hours, 2); + *o++ = ':'; + o = scr_date_put_digits(o, p.minutes, 2); + *o++ = ':'; + o = scr_date_put_digits(o, p.seconds, 2); + *o++ = ' '; + *o++ = 'G'; + *o++ = 'M'; + *o++ = 'T'; + *o++ = offset < 0 ? '-' : '+'; + o = scr_date_put_digits(o, magnitude / 60, 2); + o = scr_date_put_digits(o, magnitude % 60, 2); + *o++ = ' '; + *o++ = '('; + /* Zone names are at most 64 bytes (scr_timezone_data.h). */ + const char *zone = scr_date_zone_name(ms, p.isdst); + size_t zone_len = strlen(zone); + memcpy(o, zone, zone_len); + o += zone_len; + *o++ = ')'; + } + return scr_str_new(buf, (size_t)(o - buf)); +} + +ScrStr *scr_date_to_string(double ms) { return scr_date_text(ms, SCR_DATE_TEXT_FULL); } +ScrStr *scr_date_to_date_string(double ms) { return scr_date_text(ms, SCR_DATE_TEXT_DATE); } +ScrStr *scr_date_to_time_string(double ms) { return scr_date_text(ms, SCR_DATE_TEXT_TIME); } + /* ── Number statics ──────────────────────────────────────────────────── * JS-exact: the ES2015 Number statics never coerce (unlike the global * isNaN/isFinite), and the compiler routes only number-typed arguments @@ -11176,11 +11539,25 @@ double scr_dyn_native_date_value(const ScrDyn *value) { return ((ScrNativeDate *)value->v.handle.ptr)->milliseconds; } +/* Method names without another use in this file, in named arrays (see + * scr_tz_posix_ids). */ +static const char scr_date_to_date_string_name[] = "toDateString"; +static const char scr_date_to_time_string_name[] = "toTimeString"; + static ScrDyn *scr_native_date_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what) { (void)self; (void)args; (void)argc; (void)what; double ms = ((ScrNativeDate *)ptr)->milliseconds; if (!strcmp(method, "getTime") || !strcmp(method, "valueOf")) return scr_dyn_new_num(ms); + if (!strcmp(method, "toString") || !strcmp(method, scr_date_to_date_string_name) || + !strcmp(method, scr_date_to_time_string_name)) { + ScrStr *text = !strcmp(method, "toString") ? scr_date_to_string(ms) + : !strcmp(method, scr_date_to_date_string_name) ? scr_date_to_date_string(ms) + : scr_date_to_time_string(ms); + ScrDyn *result = scr_dyn_new_str(text); + scr_str_release(text); + return result; + } if (!strcmp(method, "toISOString") || !strcmp(method, "toJSON")) { if (!strcmp(method, "toJSON") && !isfinite(ms)) return scr_dyn_new_null(); ScrStr *text = scr_date_to_iso(ms); @@ -11228,7 +11605,9 @@ ScrDyn *scr_dyn_native_date_new(const ScrDyn *arguments) { /* The Date.prototype methods scr_native_date_invoke answers. */ static const char *const scr_native_date_methods[] = { - "getTime", "valueOf", "toISOString", "toJSON", "getFullYear", "getUTCFullYear", "getMonth", + "getTime", "valueOf", "toISOString", "toJSON", "toString", scr_date_to_date_string_name, + scr_date_to_time_string_name, + "getFullYear", "getUTCFullYear", "getMonth", "getUTCMonth", "getDate", "getUTCDate", "getDay", "getUTCDay", "getHours", "getUTCHours", "getMinutes", "getUTCMinutes", "getSeconds", "getUTCSeconds", "getMilliseconds", "getUTCMilliseconds", "getTimezoneOffset", diff --git a/packages/runtime/src/scr_number.h b/packages/runtime/src/scr_number.h index f980daeb5..ac96db8e7 100644 --- a/packages/runtime/src/scr_number.h +++ b/packages/runtime/src/scr_number.h @@ -120,6 +120,15 @@ double scr_date_get_minutes(double ms, bool utc); double scr_date_get_seconds(double ms, bool utc); double scr_date_get_milliseconds(double ms); double scr_date_get_timezone_offset(double ms); +/* Date.prototype.toString, toDateString and toTimeString with V8's + * formats; "Invalid Date" for NaN. +1. */ +ScrStr *scr_date_to_string(double ms); +ScrStr *scr_date_to_date_string(double ms); +ScrStr *scr_date_to_time_string(double ms); +/* The long time zone name Node prints after the GMT offset for the host + * zone at time value `ms`; `isdst` is tm_isdst at `ms`, or -1 when not + * known. Borrowed; valid until the next call on the same thread. */ +const char *scr_date_zone_name(double ms, int isdst); /* One-argument ABI wrappers used by the LLVM lib-call table. */ double scr_date_get_full_year_local(double ms); double scr_date_get_full_year_utc(double ms); diff --git a/packages/runtime/src/scr_timezone_data.h b/packages/runtime/src/scr_timezone_data.h new file mode 100644 index 000000000..3503b84f9 --- /dev/null +++ b/packages/runtime/src/scr_timezone_data.h @@ -0,0 +1,1555 @@ +/* Generated by scripts/gen-timezone-names.mjs from Node v24.22.0 + * (ICU 78.3, CLDR 48.0) and tzdata 2026c. Do not edit. + * Each IANA zone or link ID maps to the long English names Node prints for + * its standard and daylight time. A row with change_count > 0 takes + * daylight time from scr_tz_daylight_changes[first_change ...]: the epoch + * seconds at which daylight time starts or ends between 1970 and + * 2038-01-19 (INT32_MIN first when 1970 starts in daylight time). Rows are + * sorted by ID. */ +#ifndef SCR_TIMEZONE_DATA_H +#define SCR_TIMEZONE_DATA_H +#include +typedef struct { uint16_t id, standard, daylight, first_change, change_count; } ScrTzNames; +static const char scr_tz_ids[] = + "Africa/Abidjan\0" + "Africa/Accra\0" + "Africa/Addis_Ababa\0" + "Africa/Algiers\0" + "Africa/Asmara\0" + "Africa/Asmera\0" + "Africa/Bamako\0" + "Africa/Bangui\0" + "Africa/Banjul\0" + "Africa/Bissau\0" + "Africa/Blantyre\0" + "Africa/Brazzaville\0" + "Africa/Bujumbura\0" + "Africa/Cairo\0" + "Africa/Casablanca\0" + "Africa/Ceuta\0" + "Africa/Conakry\0" + "Africa/Dakar\0" + "Africa/Dar_es_Salaam\0" + "Africa/Djibouti\0" + "Africa/Douala\0" + "Africa/El_Aaiun\0" + "Africa/Freetown\0" + "Africa/Gaborone\0" + "Africa/Harare\0" + "Africa/Johannesburg\0" + "Africa/Juba\0" + "Africa/Kampala\0" + "Africa/Khartoum\0" + "Africa/Kigali\0" + "Africa/Kinshasa\0" + "Africa/Lagos\0" + "Africa/Libreville\0" + "Africa/Lome\0" + "Africa/Luanda\0" + "Africa/Lubumbashi\0" + "Africa/Lusaka\0" + "Africa/Malabo\0" + "Africa/Maputo\0" + "Africa/Maseru\0" + "Africa/Mbabane\0" + "Africa/Mogadishu\0" + "Africa/Monrovia\0" + "Africa/Nairobi\0" + "Africa/Ndjamena\0" + "Africa/Niamey\0" + "Africa/Nouakchott\0" + "Africa/Ouagadougou\0" + "Africa/Porto-Novo\0" + "Africa/Sao_Tome\0" + "Africa/Timbuktu\0" + "Africa/Tripoli\0" + "Africa/Tunis\0" + "Africa/Windhoek\0" + "America/Adak\0" + "America/Anchorage\0" + "America/Anguilla\0" + "America/Antigua\0" + "America/Araguaina\0" + "America/Argentina/Buenos_Aires\0" + "America/Argentina/Catamarca\0" + "America/Argentina/ComodRivadavia\0" + "America/Argentina/Cordoba\0" + "America/Argentina/Jujuy\0" + "America/Argentina/La_Rioja\0" + "America/Argentina/Mendoza\0" + "America/Argentina/Rio_Gallegos\0" + "America/Argentina/Salta\0" + "America/Argentina/San_Juan\0" + "America/Argentina/San_Luis\0" + "America/Argentina/Tucuman\0" + "America/Argentina/Ushuaia\0" + "America/Aruba\0" + "America/Asuncion\0" + "America/Atikokan\0" + "America/Atka\0" + "America/Bahia\0" + "America/Bahia_Banderas\0" + "America/Barbados\0" + "America/Belem\0" + "America/Belize\0" + "America/Blanc-Sablon\0" + "America/Boa_Vista\0" + "America/Bogota\0" + "America/Boise\0" + "America/Buenos_Aires\0" + "America/Cambridge_Bay\0" + "America/Campo_Grande\0" + "America/Cancun\0" + "America/Caracas\0" + "America/Catamarca\0" + "America/Cayenne\0" + "America/Cayman\0" + "America/Chicago\0" + "America/Chihuahua\0" + "America/Ciudad_Juarez\0" + "America/Coral_Harbour\0" + "America/Cordoba\0" + "America/Costa_Rica\0" + "America/Coyhaique\0" + "America/Creston\0" + "America/Cuiaba\0" + "America/Curacao\0" + "America/Danmarkshavn\0" + "America/Dawson\0" + "America/Dawson_Creek\0" + "America/Denver\0" + "America/Detroit\0" + "America/Dominica\0" + "America/Edmonton\0" + "America/Eirunepe\0" + "America/El_Salvador\0" + "America/Ensenada\0" + "America/Fort_Nelson\0" + "America/Fort_Wayne\0" + "America/Fortaleza\0" + "America/Glace_Bay\0" + "America/Godthab\0" + "America/Goose_Bay\0" + "America/Grand_Turk\0" + "America/Grenada\0" + "America/Guadeloupe\0" + "America/Guatemala\0" + "America/Guayaquil\0" + "America/Guyana\0" + "America/Halifax\0" + "America/Havana\0" + "America/Hermosillo\0" + "America/Indiana/Indianapolis\0" + "America/Indiana/Knox\0" + "America/Indiana/Marengo\0" + "America/Indiana/Petersburg\0" + "America/Indiana/Tell_City\0" + "America/Indiana/Vevay\0" + "America/Indiana/Vincennes\0" + "America/Indiana/Winamac\0" + "America/Indianapolis\0" + "America/Inuvik\0" + "America/Iqaluit\0" + "America/Jamaica\0" + "America/Jujuy\0" + "America/Juneau\0" + "America/Kentucky/Louisville\0" + "America/Kentucky/Monticello\0" + "America/Knox_IN\0" + "America/Kralendijk\0" + "America/La_Paz\0" + "America/Lima\0" + "America/Los_Angeles\0" + "America/Louisville\0" + "America/Lower_Princes\0" + "America/Maceio\0" + "America/Managua\0" + "America/Manaus\0" + "America/Marigot\0" + "America/Martinique\0" + "America/Matamoros\0" + "America/Mazatlan\0" + "America/Mendoza\0" + "America/Menominee\0" + "America/Merida\0" + "America/Metlakatla\0" + "America/Mexico_City\0" + "America/Miquelon\0" + "America/Moncton\0" + "America/Monterrey\0" + "America/Montevideo\0" + "America/Montreal\0" + "America/Montserrat\0" + "America/Nassau\0" + "America/New_York\0" + "America/Nipigon\0" + "America/Nome\0" + "America/Noronha\0" + "America/North_Dakota/Beulah\0" + "America/North_Dakota/Center\0" + "America/North_Dakota/New_Salem\0" + "America/Nuuk\0" + "America/Ojinaga\0" + "America/Panama\0" + "America/Pangnirtung\0" + "America/Paramaribo\0" + "America/Phoenix\0" + "America/Port-au-Prince\0" + "America/Port_of_Spain\0" + "America/Porto_Acre\0" + "America/Porto_Velho\0" + "America/Puerto_Rico\0" + "America/Punta_Arenas\0" + "America/Rainy_River\0" + "America/Rankin_Inlet\0" + "America/Recife\0" + "America/Regina\0" + "America/Resolute\0" + "America/Rio_Branco\0" + "America/Rosario\0" + "America/Santa_Isabel\0" + "America/Santarem\0" + "America/Santiago\0" + "America/Santo_Domingo\0" + "America/Sao_Paulo\0" + "America/Scoresbysund\0" + "America/Shiprock\0" + "America/Sitka\0" + "America/St_Barthelemy\0" + "America/St_Johns\0" + "America/St_Kitts\0" + "America/St_Lucia\0" + "America/St_Thomas\0" + "America/St_Vincent\0" + "America/Swift_Current\0" + "America/Tegucigalpa\0" + "America/Thule\0" + "America/Thunder_Bay\0" + "America/Tijuana\0" + "America/Toronto\0" + "America/Tortola\0" + "America/Vancouver\0" + "America/Virgin\0" + "America/Whitehorse\0" + "America/Winnipeg\0" + "America/Yakutat\0" + "America/Yellowknife\0" + "Antarctica/Casey\0" + "Antarctica/Davis\0" + "Antarctica/DumontDUrville\0" + "Antarctica/Macquarie\0" + "Antarctica/Mawson\0" + "Antarctica/McMurdo\0" + "Antarctica/Palmer\0" + "Antarctica/Rothera\0" + "Antarctica/South_Pole\0" + "Antarctica/Syowa\0" + "Antarctica/Troll\0" + "Antarctica/Vostok\0" + "Arctic/Longyearbyen\0" + "Asia/Aden\0" + "Asia/Almaty\0" + "Asia/Amman\0" + "Asia/Anadyr\0" + "Asia/Aqtau\0" + "Asia/Aqtobe\0" + "Asia/Ashgabat\0" + "Asia/Ashkhabad\0" + "Asia/Atyrau\0" + "Asia/Baghdad\0" + "Asia/Bahrain\0" + "Asia/Baku\0" + "Asia/Bangkok\0" + "Asia/Barnaul\0" + "Asia/Beirut\0" + "Asia/Bishkek\0" + "Asia/Brunei\0" + "Asia/Calcutta\0" + "Asia/Chita\0" + "Asia/Choibalsan\0" + "Asia/Chongqing\0" + "Asia/Chungking\0" + "Asia/Colombo\0" + "Asia/Dacca\0" + "Asia/Damascus\0" + "Asia/Dhaka\0" + "Asia/Dili\0" + "Asia/Dubai\0" + "Asia/Dushanbe\0" + "Asia/Famagusta\0" + "Asia/Gaza\0" + "Asia/Harbin\0" + "Asia/Hebron\0" + "Asia/Ho_Chi_Minh\0" + "Asia/Hong_Kong\0" + "Asia/Hovd\0" + "Asia/Irkutsk\0" + "Asia/Istanbul\0" + "Asia/Jakarta\0" + "Asia/Jayapura\0" + "Asia/Jerusalem\0" + "Asia/Kabul\0" + "Asia/Kamchatka\0" + "Asia/Karachi\0" + "Asia/Kashgar\0" + "Asia/Kathmandu\0" + "Asia/Katmandu\0" + "Asia/Khandyga\0" + "Asia/Kolkata\0" + "Asia/Krasnoyarsk\0" + "Asia/Kuala_Lumpur\0" + "Asia/Kuching\0" + "Asia/Kuwait\0" + "Asia/Macao\0" + "Asia/Macau\0" + "Asia/Magadan\0" + "Asia/Makassar\0" + "Asia/Manila\0" + "Asia/Muscat\0" + "Asia/Nicosia\0" + "Asia/Novokuznetsk\0" + "Asia/Novosibirsk\0" + "Asia/Omsk\0" + "Asia/Oral\0" + "Asia/Phnom_Penh\0" + "Asia/Pontianak\0" + "Asia/Pyongyang\0" + "Asia/Qatar\0" + "Asia/Qostanay\0" + "Asia/Qyzylorda\0" + "Asia/Rangoon\0" + "Asia/Riyadh\0" + "Asia/Saigon\0" + "Asia/Sakhalin\0" + "Asia/Samarkand\0" + "Asia/Seoul\0" + "Asia/Shanghai\0" + "Asia/Singapore\0" + "Asia/Srednekolymsk\0" + "Asia/Taipei\0" + "Asia/Tashkent\0" + "Asia/Tbilisi\0" + "Asia/Tehran\0" + "Asia/Tel_Aviv\0" + "Asia/Thimbu\0" + "Asia/Thimphu\0" + "Asia/Tokyo\0" + "Asia/Tomsk\0" + "Asia/Ujung_Pandang\0" + "Asia/Ulaanbaatar\0" + "Asia/Ulan_Bator\0" + "Asia/Urumqi\0" + "Asia/Ust-Nera\0" + "Asia/Vientiane\0" + "Asia/Vladivostok\0" + "Asia/Yakutsk\0" + "Asia/Yangon\0" + "Asia/Yekaterinburg\0" + "Asia/Yerevan\0" + "Atlantic/Azores\0" + "Atlantic/Bermuda\0" + "Atlantic/Canary\0" + "Atlantic/Cape_Verde\0" + "Atlantic/Faeroe\0" + "Atlantic/Faroe\0" + "Atlantic/Jan_Mayen\0" + "Atlantic/Madeira\0" + "Atlantic/Reykjavik\0" + "Atlantic/South_Georgia\0" + "Atlantic/St_Helena\0" + "Atlantic/Stanley\0" + "Australia/ACT\0" + "Australia/Adelaide\0" + "Australia/Brisbane\0" + "Australia/Broken_Hill\0" + "Australia/Canberra\0" + "Australia/Currie\0" + "Australia/Darwin\0" + "Australia/Eucla\0" + "Australia/Hobart\0" + "Australia/LHI\0" + "Australia/Lindeman\0" + "Australia/Lord_Howe\0" + "Australia/Melbourne\0" + "Australia/NSW\0" + "Australia/North\0" + "Australia/Perth\0" + "Australia/Queensland\0" + "Australia/South\0" + "Australia/Sydney\0" + "Australia/Tasmania\0" + "Australia/Victoria\0" + "Australia/West\0" + "Australia/Yancowinna\0" + "Brazil/Acre\0" + "Brazil/DeNoronha\0" + "Brazil/East\0" + "Brazil/West\0" + "CET\0" + "CST6CDT\0" + "Canada/Atlantic\0" + "Canada/Central\0" + "Canada/Eastern\0" + "Canada/Mountain\0" + "Canada/Newfoundland\0" + "Canada/Pacific\0" + "Canada/Saskatchewan\0" + "Canada/Yukon\0" + "Chile/Continental\0" + "Chile/EasterIsland\0" + "Cuba\0" + "EET\0" + "EST\0" + "EST5EDT\0" + "Egypt\0" + "Eire\0" + "Etc/GMT\0" + "Etc/GMT+0\0" + "Etc/GMT+1\0" + "Etc/GMT+10\0" + "Etc/GMT+11\0" + "Etc/GMT+12\0" + "Etc/GMT+2\0" + "Etc/GMT+3\0" + "Etc/GMT+4\0" + "Etc/GMT+5\0" + "Etc/GMT+6\0" + "Etc/GMT+7\0" + "Etc/GMT+8\0" + "Etc/GMT+9\0" + "Etc/GMT-0\0" + "Etc/GMT-1\0" + "Etc/GMT-10\0" + "Etc/GMT-11\0" + "Etc/GMT-12\0" + "Etc/GMT-13\0" + "Etc/GMT-14\0" + "Etc/GMT-2\0" + "Etc/GMT-3\0" + "Etc/GMT-4\0" + "Etc/GMT-5\0" + "Etc/GMT-6\0" + "Etc/GMT-7\0" + "Etc/GMT-8\0" + "Etc/GMT-9\0" + "Etc/GMT0\0" + "Etc/Greenwich\0" + "Etc/UCT\0" + "Etc/UTC\0" + "Etc/Universal\0" + "Etc/Zulu\0" + "Europe/Amsterdam\0" + "Europe/Andorra\0" + "Europe/Astrakhan\0" + "Europe/Athens\0" + "Europe/Belfast\0" + "Europe/Belgrade\0" + "Europe/Berlin\0" + "Europe/Bratislava\0" + "Europe/Brussels\0" + "Europe/Bucharest\0" + "Europe/Budapest\0" + "Europe/Busingen\0" + "Europe/Chisinau\0" + "Europe/Copenhagen\0" + "Europe/Dublin\0" + "Europe/Gibraltar\0" + "Europe/Guernsey\0" + "Europe/Helsinki\0" + "Europe/Isle_of_Man\0" + "Europe/Istanbul\0" + "Europe/Jersey\0" + "Europe/Kaliningrad\0" + "Europe/Kiev\0" + "Europe/Kirov\0" + "Europe/Kyiv\0" + "Europe/Lisbon\0" + "Europe/Ljubljana\0" + "Europe/London\0" + "Europe/Luxembourg\0" + "Europe/Madrid\0" + "Europe/Malta\0" + "Europe/Mariehamn\0" + "Europe/Minsk\0" + "Europe/Monaco\0" + "Europe/Moscow\0" + "Europe/Nicosia\0" + "Europe/Oslo\0" + "Europe/Paris\0" + "Europe/Podgorica\0" + "Europe/Prague\0" + "Europe/Riga\0" + "Europe/Rome\0" + "Europe/Samara\0" + "Europe/San_Marino\0" + "Europe/Sarajevo\0" + "Europe/Saratov\0" + "Europe/Simferopol\0" + "Europe/Skopje\0" + "Europe/Sofia\0" + "Europe/Stockholm\0" + "Europe/Tallinn\0" + "Europe/Tirane\0" + "Europe/Tiraspol\0" + "Europe/Ulyanovsk\0" + "Europe/Uzhgorod\0" + "Europe/Vaduz\0" + "Europe/Vatican\0" + "Europe/Vienna\0" + "Europe/Vilnius\0" + "Europe/Volgograd\0" + "Europe/Warsaw\0" + "Europe/Zagreb\0" + "Europe/Zaporozhye\0" + "Europe/Zurich\0" + "GB\0" + "GB-Eire\0" + "GMT\0" + "GMT+0\0" + "GMT-0\0" + "GMT0\0" + "Greenwich\0" + "HST\0" + "Hongkong\0" + "Iceland\0" + "Indian/Antananarivo\0" + "Indian/Chagos\0" + "Indian/Christmas\0" + "Indian/Cocos\0" + "Indian/Comoro\0" + "Indian/Kerguelen\0" + "Indian/Mahe\0" + "Indian/Maldives\0" + "Indian/Mauritius\0" + "Indian/Mayotte\0" + "Indian/Reunion\0" + "Iran\0" + "Israel\0" + "Jamaica\0" + "Japan\0" + "Kwajalein\0" + "Libya\0" + "MET\0" + "MST\0" + "MST7MDT\0" + "Mexico/BajaNorte\0" + "Mexico/BajaSur\0" + "Mexico/General\0" + "NZ\0" + "NZ-CHAT\0" + "Navajo\0" + "PRC\0" + "PST8PDT\0" + "Pacific/Apia\0" + "Pacific/Auckland\0" + "Pacific/Bougainville\0" + "Pacific/Chatham\0" + "Pacific/Chuuk\0" + "Pacific/Easter\0" + "Pacific/Efate\0" + "Pacific/Enderbury\0" + "Pacific/Fakaofo\0" + "Pacific/Fiji\0" + "Pacific/Funafuti\0" + "Pacific/Galapagos\0" + "Pacific/Gambier\0" + "Pacific/Guadalcanal\0" + "Pacific/Guam\0" + "Pacific/Honolulu\0" + "Pacific/Johnston\0" + "Pacific/Kanton\0" + "Pacific/Kiritimati\0" + "Pacific/Kosrae\0" + "Pacific/Kwajalein\0" + "Pacific/Majuro\0" + "Pacific/Marquesas\0" + "Pacific/Midway\0" + "Pacific/Nauru\0" + "Pacific/Niue\0" + "Pacific/Norfolk\0" + "Pacific/Noumea\0" + "Pacific/Pago_Pago\0" + "Pacific/Palau\0" + "Pacific/Pitcairn\0" + "Pacific/Pohnpei\0" + "Pacific/Ponape\0" + "Pacific/Port_Moresby\0" + "Pacific/Rarotonga\0" + "Pacific/Saipan\0" + "Pacific/Samoa\0" + "Pacific/Tahiti\0" + "Pacific/Tarawa\0" + "Pacific/Tongatapu\0" + "Pacific/Truk\0" + "Pacific/Wake\0" + "Pacific/Wallis\0" + "Pacific/Yap\0" + "Poland\0" + "Portugal\0" + "ROC\0" + "ROK\0" + "Singapore\0" + "Turkey\0" + "UCT\0" + "US/Alaska\0" + "US/Aleutian\0" + "US/Arizona\0" + "US/Central\0" + "US/East-Indiana\0" + "US/Eastern\0" + "US/Hawaii\0" + "US/Indiana-Starke\0" + "US/Michigan\0" + "US/Mountain\0" + "US/Pacific\0" + "US/Samoa\0" + "UTC\0" + "Universal\0" + "W-SU\0" + "WET\0" + "Zulu\0"; +static const char scr_tz_name_text[] = + "Greenwich Mean Time\0" + "East Africa Time\0" + "Central European Standard Time\0" + "Central European Summer Time\0" + "West Africa Time\0" + "Central Africa Time\0" + "Eastern European Standard Time\0" + "Eastern European Summer Time\0" + "Western European Standard Time\0" + "Western European Summer Time\0" + "South Africa Standard Time\0" + "GMT+02:00\0" + "GMT+01:00\0" + "Hawaii-Aleutian Standard Time\0" + "Hawaii-Aleutian Daylight Time\0" + "Alaska Standard Time\0" + "Alaska Daylight Time\0" + "Atlantic Standard Time\0" + "Brasilia Standard Time\0" + "Brasilia Summer Time\0" + "Argentina Standard Time\0" + "Argentina Summer Time\0" + "Paraguay Standard Time\0" + "Paraguay Summer Time\0" + "Eastern Standard Time\0" + "Central Standard Time\0" + "Central Daylight Time\0" + "Atlantic Daylight Time\0" + "Amazon Standard Time\0" + "Amazon Summer Time\0" + "Colombia Standard Time\0" + "Colombia Summer Time\0" + "Mountain Standard Time\0" + "Mountain Daylight Time\0" + "Eastern Daylight Time\0" + "Venezuela Time\0" + "French Guiana Time\0" + "GMT-03:00\0" + "GMT+00:00\0" + "Yukon Time\0" + "GMT-07:00\0" + "Acre Standard Time\0" + "Acre Summer Time\0" + "Pacific Standard Time\0" + "Pacific Daylight Time\0" + "Greenland Standard Time\0" + "Greenland Summer Time\0" + "Ecuador Time\0" + "GMT-05:00\0" + "Guyana Time\0" + "Cuba Standard Time\0" + "Cuba Daylight Time\0" + "Mexican Pacific Standard Time\0" + "Mexican Pacific Daylight Time\0" + "Bolivia Time\0" + "Peru Standard Time\0" + "Peru Summer Time\0" + "St. Pierre & Miquelon Standard Time\0" + "St. Pierre & Miquelon Daylight Time\0" + "Uruguay Standard Time\0" + "Uruguay Summer Time\0" + "Fernando de Noronha Standard Time\0" + "Fernando de Noronha Summer Time\0" + "Suriname Time\0" + "Chile Standard Time\0" + "Chile Summer Time\0" + "Newfoundland Standard Time\0" + "Newfoundland Daylight Time\0" + "Australian Western Standard Time\0" + "Davis Time\0" + "Dumont d’Urville Time\0" + "Australian Eastern Standard Time\0" + "Australian Eastern Daylight Time\0" + "Mawson Time\0" + "New Zealand Standard Time\0" + "New Zealand Daylight Time\0" + "Rothera Time\0" + "Syowa Time\0" + "Vostok Time\0" + "Arabian Standard Time\0" + "Kazakhstan Time\0" + "GMT+05:00\0" + "GMT+03:00\0" + "Kamchatka Standard Time\0" + "Kamchatka Summer Time\0" + "Turkmenistan Standard Time\0" + "Turkmenistan Summer Time\0" + "Arabian Daylight Time\0" + "Azerbaijan Standard Time\0" + "Azerbaijan Summer Time\0" + "Indochina Time\0" + "Krasnoyarsk Standard Time\0" + "Krasnoyarsk Summer Time\0" + "Kyrgyzstan Time\0" + "GMT+06:00\0" + "Brunei Time\0" + "India Standard Time\0" + "Yakutsk Standard Time\0" + "Yakutsk Summer Time\0" + "Ulaanbaatar Standard Time\0" + "Ulaanbaatar Summer Time\0" + "China Standard Time\0" + "China Daylight Time\0" + "Bangladesh Standard Time\0" + "Bangladesh Summer Time\0" + "Timor-Leste Time\0" + "Gulf Standard Time\0" + "Tajikistan Time\0" + "Hong Kong Standard Time\0" + "Hong Kong Summer Time\0" + "Khovd Standard Time\0" + "Khovd Summer Time\0" + "Irkutsk Standard Time\0" + "Irkutsk Summer Time\0" + "Türkiye Standard Time\0" + "Türkiye Summer Time\0" + "Western Indonesia Time\0" + "Eastern Indonesia Time\0" + "Israel Standard Time\0" + "Israel Daylight Time\0" + "Afghanistan Time\0" + "Pakistan Standard Time\0" + "Pakistan Summer Time\0" + "Nepal Time\0" + "Malaysia Time\0" + "Magadan Standard Time\0" + "Magadan Summer Time\0" + "Central Indonesia Time\0" + "Philippine Standard Time\0" + "Philippine Summer Time\0" + "Omsk Standard Time\0" + "Omsk Summer Time\0" + "Korean Standard Time\0" + "Myanmar Time\0" + "Uzbekistan Standard Time\0" + "Uzbekistan Summer Time\0" + "Korean Daylight Time\0" + "Singapore Standard Time\0" + "Taiwan Standard Time\0" + "Taiwan Daylight Time\0" + "Georgia Standard Time\0" + "Georgia Summer Time\0" + "Iran Standard Time\0" + "Iran Daylight Time\0" + "Bhutan Time\0" + "Japan Standard Time\0" + "Vladivostok Standard Time\0" + "Vladivostok Summer Time\0" + "Yekaterinburg Standard Time\0" + "Yekaterinburg Summer Time\0" + "Armenia Standard Time\0" + "Armenia Summer Time\0" + "Azores Standard Time\0" + "Azores Summer Time\0" + "Cape Verde Standard Time\0" + "South Georgia Time\0" + "Falkland Islands Standard Time\0" + "Falkland Islands Summer Time\0" + "Australian Central Standard Time\0" + "Australian Central Daylight Time\0" + "Australian Central Western Standard Time\0" + "Australian Central Western Daylight Time\0" + "Lord Howe Standard Time\0" + "Lord Howe Daylight Time\0" + "Australian Western Daylight Time\0" + "Easter Island Standard Time\0" + "Easter Island Summer Time\0" + "GMT-01:00\0" + "GMT-10:00\0" + "GMT-11:00\0" + "GMT-12:00\0" + "GMT-02:00\0" + "GMT-04:00\0" + "GMT-06:00\0" + "GMT-08:00\0" + "GMT-09:00\0" + "GMT+10:00\0" + "GMT+11:00\0" + "GMT+12:00\0" + "GMT+13:00\0" + "GMT+14:00\0" + "GMT+04:00\0" + "GMT+07:00\0" + "GMT+08:00\0" + "GMT+09:00\0" + "Coordinated Universal Time\0" + "Samara Standard Time\0" + "Samara Summer Time\0" + "British Summer Time\0" + "Irish Standard Time\0" + "Moscow Standard Time\0" + "Moscow Summer Time\0" + "Indian Ocean Time\0" + "Christmas Island Time\0" + "Cocos Islands Time\0" + "French Southern & Antarctic Time\0" + "Seychelles Time\0" + "Maldives Time\0" + "Mauritius Standard Time\0" + "Mauritius Summer Time\0" + "Réunion Time\0" + "Marshall Islands Time\0" + "Chatham Standard Time\0" + "Chatham Daylight Time\0" + "Samoa Standard Time\0" + "Samoa Daylight Time\0" + "Chuuk Time\0" + "Vanuatu Standard Time\0" + "Vanuatu Summer Time\0" + "Phoenix Islands Time\0" + "Tokelau Time\0" + "Fiji Standard Time\0" + "Fiji Summer Time\0" + "Tuvalu Time\0" + "Galapagos Time\0" + "Gambier Time\0" + "Solomon Islands Time\0" + "Chamorro Standard Time\0" + "Line Islands Time\0" + "Kosrae Time\0" + "Marquesas Time\0" + "American Samoa Standard Time\0" + "Nauru Time\0" + "Niue Time\0" + "Norfolk Island Standard Time\0" + "Norfolk Island Daylight Time\0" + "New Caledonia Standard Time\0" + "New Caledonia Summer Time\0" + "Palau Time\0" + "Pitcairn Time\0" + "Pohnpei Time\0" + "Papua New Guinea Time\0" + "Cook Islands Standard Time\0" + "Cook Islands Summer Time\0" + "Tahiti Time\0" + "Gilbert Islands Time\0" + "Tonga Standard Time\0" + "Tonga Summer Time\0" + "Wake Island Time\0" + "Wallis & Futuna Time\0"; +static const int32_t scr_tz_daylight_changes[] = { + 141264000, 147222000, 199756800, 207702000, 231292800, 244249200, 265507200, 271033200, + 1212278400, 1220223600, 1243814400, 1250809200, 1272758400, 1281222000, 1301788800, 1312066800, + 1335664800, 1342749600, 1345428000, 1348970400, 1367114400, 1373162400, 1376100000, 1382839200, + 1396144800, 1403920800, 1406944800, 1414288800, 1427594400, 1434247200, 1437271200, 1445738400, + 1459044000, 1465092000, 1468116000, 1477792800, 1490493600, 1495332000, 1498960800, 1509242400, + 1521943200, 1526176800, 1529200800, 1557021600, 1560045600, 1587261600, 1590890400, 1618106400, + 1621130400, 1648346400, 1651975200, 1679191200, 1682215200, 1710036000, 1713060000, 1740276000, + 1743904800, 1771120800, 1774144800, 1789866000, 199756800, 207702000, 231292800, 244249200, + 265507200, 271033200, 1212278400, 1220223600, 1243814400, 1250809200, 1272758400, 1281222000, + 1301788800, 1312066800, 1335664800, 1342749600, 1345428000, 1348970400, 1367114400, 1373162400, + 1376100000, 1382839200, 1396144800, 1403920800, 1406944800, 1414288800, 1427594400, 1434247200, + 1437271200, 1445738400, 1459044000, 1465092000, 1468116000, 1477792800, 1490493600, 1495332000, + 1498960800, 1509242400, 1521943200, 1526176800, 1529200800, 1557021600, 1560045600, 1587261600, + 1590890400, 1618106400, 1621130400, 1648346400, 1651975200, 1679191200, 1682215200, 1710036000, + 1713060000, 1740276000, 1743904800, 1771120800, 1774144800, 1789866000, 778640400, 796780800, + 810090000, 828835200, 841539600, 860284800, 873594000, 891734400, 905043600, 923184000, 936493200, + 954633600, 967942800, 986083200, 999392400, 1018137600, 1030842000, 1049587200, 1062896400, + 1081036800, 1094346000, 1112486400, 1125795600, 1143936000, 1157245200, 1175385600, 1188694800, + 1207440000, 1220749200, 1238889600, 1252198800, 1270339200, 1283648400, 1301788800, 1315098000, + 1333238400, 1346547600, 1365292800, 1377997200, 1396742400, 1410051600, 1428192000, 1441501200, + 1459641600, 1472950800, 1491091200, 1504400400, 1508796000, 73472400, 89193600, 104922000, + 120643200, 136371600, 152092800, 167821200, 183542400, 199270800, 215596800, 230720400, 247046400, + 262774800, 278496000, 294224400, 309945600, 325674000, 341395200, 357123600, 372844800, 388573200, + 404899200, 420022800, 436348800, 452077200, 467798400, 483526800, 499248000, 514976400, 530697600, + 544611600, 562147200, 576061200, 594201600, 607510800, 625651200, 638960400, 657100800, 671014800, + 688550400, 702464400, 720000000, 733914000, 752054400, 765363600, 783504000, 796813200, 814953600, + 828867600, 846403200, 860317200, 877852800, 891766800, 909302400, 923216400, 941356800, 954666000, + 972806400, 986115600, 1004256000, 1018170000, 1035705600, 1049619600, 1067155200, 1081069200, + 1099209600, 1112518800, 1130659200, 1143968400, 1162108800, 1173603600, 1194163200, 1205053200, + 1225612800, 1236502800, 1257062400, 1268557200, 1289116800, 1300006800, 1320566400, 1331456400, + 1352016000, 1362906000, 1383465600, 1394355600, 1414915200, 1425805200, 1446364800, 1457859600, + 1478419200, 1489309200, 1509868800, 1520758800, 1541318400, 1552208400, 1572768000, 1583658000, + 1604217600, 1615712400, 1636272000, 1647162000, 1667721600, 1678611600, 1699171200, 1710061200, + 1730620800, 1741510800, 1762070400, 1772960400, 9972000, 25693200, 41421600, 57747600, 73476000, + 89197200, 104925600, 120646800, 136375200, 152096400, 167824800, 183546000, 199274400, 215600400, + 230724000, 247050000, 262778400, 278499600, 294228000, 309949200, 325677600, 341398800, 357127200, + 372848400, 388576800, 404902800, 420026400, 436352400, 452080800, 467802000, 483530400, 499251600, + 514980000, 530701200, 544615200, 562150800, 576064800, 594205200, 607514400, 625654800, 638964000, + 657104400, 671018400, 688554000, 702468000, 720003600, 733917600, 752058000, 765367200, 783507600, + 796816800, 814957200, 828871200, 846406800, 860320800, 877856400, 891770400, 909306000, 923220000, + 941360400, 954669600, 972810000, 986119200, 1004259600, 1018173600, 1035709200, 1049623200, + 1067158800, 1081072800, 1099213200, 1112522400, 1130662800, 1143972000, 1162112400, 1173607200, + 1194166800, 1205056800, 1225616400, 1236506400, 1257066000, 1268560800, 1289120400, 1300010400, + 1320570000, 1331460000, 1352019600, 1362909600, 1383469200, 1394359200, 1414918800, 1425808800, + 1446368400, 1457863200, 1478422800, 1489312800, 1509872400, 1520762400, 1541322000, 1552212000, + 1572771600, 1583661600, 1604221200, 1615716000, 1636275600, 1647165600, 1667725200, 1678615200, + 1699174800, 1710064800, 1730624400, 1741514400, 1762074000, 1772964000, 73472400, 89193600, + 104922000, 120643200, 136371600, 152092800, 167821200, 183542400, 199270800, 215596800, 230720400, + 247046400, 262774800, 278496000, 294224400, 309945600, 325674000, 341395200, 357123600, 372844800, + 388573200, 404899200, 420022800, 436348800, 452077200, 467798400, 483526800, 499248000, 514976400, + 530697600, 544611600, 562147200, 576061200, 594201600, 607510800, 625651200, 638960400, 657100800, + 671014800, 688550400, 702464400, 720000000, 733914000, 752054400, 765363600, 783504000, 796813200, + 814953600, 828867600, 846403200, 860317200, 877852800, 891766800, 909302400, 923216400, 941356800, + 954666000, 972806400, 986115600, 1004256000, 1018170000, 1035705600, 1049619600, 1067155200, + 1081069200, 1099209600, 1112518800, 1130659200, 1143968400, 1162108800, 1173603600, 1194163200, + 1205053200, 1225612800, 1236502800, 1257062400, 1268557200, 1289116800, 1300006800, 1320566400, + 1331456400, 1352016000, 1362906000, 1383465600, 1394355600, 1414915200, 1425805200, 1446364800, + 1457859600, 1478419200, 1489309200, 1509868800, 1520758800, 1541318400, 1552208400, 1572768000, + 1583658000, 1604217600, 1615712400, 1636272000, 1647162000, 1667721600, 1678611600, 1699171200, + 1710061200, 1730620800, 1741510800, 1762070400, 1772960400, 73472400, 89193600, 104922000, + 120643200, 136371600, 152092800, 167821200, 183542400, 199270800, 215596800, 230720400, 247046400, + 262774800, 278496000, 294224400, 309945600, 325674000, 341395200, 357123600, 372844800, 388573200, + 404899200, 420022800, 436348800, 452077200, 467798400, 483526800, 499248000, 514976400, 530697600, + 544611600, 562147200, 576061200, 594201600, 607510800, 625651200, 638960400, 657100800, 671014800, + 688550400, 702464400, 720000000, 733914000, 752054400, 765363600, 783504000, 796813200, 814953600, + 828867600, 846403200, 860317200, 877852800, 891766800, 909302400, 923216400, 941356800, 954666000, + 972806400, 986115600, 1004256000, 1018170000, 1035705600, 1049619600, 1067155200, 1081069200, + 1099209600, 1112518800, 1130659200, 1143968400, 1162108800, 1173603600, 1194163200, 1205053200, + 1225612800, 1236502800, 1257062400, 1268557200, 1289116800, 1300006800, 1320566400, 1331456400, + 1352016000, 1362906000, 1383465600, 1394355600, 1414915200, 1425805200, 1446364800, 1457859600, + 1478419200, 1489309200, 1509868800, 1520758800, 1541318400, 1552208400, 1572768000, 1583658000, + 1604217600, 1615712400, 1636272000, 1647162000, 1667721600, 1678611600, 1699171200, 1710061200, + 1730620800, 1741510800, 1762070400, 1772960400, 9972000, 25693200, 41421600, 57747600, 73476000, + 89197200, 104925600, 120646800, 136375200, 152096400, 167824800, 183546000, 199274400, 215600400, + 230724000, 247050000, 262778400, 278499600, 294228000, 309949200, 325677600, 341398800, 357127200, + 372848400, 388576800, 404902800, 420026400, 436352400, 452080800, 467802000, 483530400, 499251600, + 514980000, 530701200, 544615200, 562150800, 576064800, 594205200, 607514400, 625654800, 638964000, + 657104400, 671018400, 688554000, 702468000, 720003600, 733917600, 752058000, 765367200, 783507600, + 796816800, 814957200, 828871200, 846406800, 860320800, 877856400, 891770400, 909306000, 923220000, + 941360400, 954669600, 972810000, 986119200, 1004259600, 1018173600, 1035709200, 1049623200, + 1067158800, 1081072800, 1099213200, 1112522400, 1130662800, 1143972000, 1162112400, 1173607200, + 1194166800, 1205056800, 1225616400, 1236506400, 1257066000, 1268560800, 1289120400, 1300010400, + 1320570000, 1331460000, 1352019600, 1362909600, 1383469200, 1394359200, 1414918800, 1425808800, + 1446368400, 1457863200, 1478422800, 1489312800, 1509872400, 1520762400, 1541322000, 1552212000, + 1572771600, 1583661600, 1604221200, 1615716000, 1636275600, 1647165600, 1667725200, 1678615200, + 1699174800, 1710064800, 1730624400, 1741514400, 1762074000, 1772964000, 69818400, 89172000, + 101268000, 120621600, 132717600, 152071200, 164167200, 183520800, 196221600, 214970400, 227671200, + 246420000, 259120800, 278474400, 290570400, 309924000, 322020000, 341373600, 354675600, 372819600, + 386125200, 404269200, 417574800, 435718800, 449024400, 467773200, 481078800, 499222800, 512528400, + 530672400, 543978000, 562122000, 575427600, 593571600, 606877200, 625626000, 638326800, 657075600, + 670381200, 688525200, 701830800, 719974800, 733280400, 751424400, 764730000, 782874000, 796179600, + 814323600, 828234000, 846378000, 859683600, 877827600, 891133200, 909277200, 922582800, 941331600, + 954032400, 972781200, 985482000, 1004230800, 1017536400, 1035680400, 1048986000, 1067130000, + 1080435600, 1099184400, 1111885200, 1130634000, 1143334800, 1162083600, 1174784400, 1193533200, + 1206838800, 1224982800, 1238288400, 1256432400, 1269738000, 1288486800, 1301187600, 1319936400, + 1332637200, 1351386000, 1364691600, 1382835600, 1396141200, 1414285200, 1427590800, 1445734800, + 1459040400, 1477789200, 1490490000, 1509238800, 1521939600, 1540688400, 1553994000, 1572138000, + 1585443600, 1603587600, 1616893200, 1635642000, 1648342800, 1667091600, 1679792400, 1698541200, + 1711846800, 1729990800, 1743296400, 1761440400, 1774746000, 1792890000, 1806195600, 1824944400, + 1837645200, 1856394000, 1869094800, 1887843600, 1901149200, 1919293200, 1932598800, 1950742800, + 1964048400, 1982797200, 1995498000, 2014246800, 2026947600, 2045696400, 2058397200, 2077146000, + 2090451600, 2108595600, 2121901200, 2140045200, +}; +static const ScrTzNames scr_tz_names[] = { + { 0, 0, 0, 0, 0 }, + { 15, 0, 0, 0, 0 }, + { 28, 20, 20, 0, 0 }, + { 47, 37, 68, 0, 0 }, + { 62, 20, 20, 0, 0 }, + { 76, 20, 20, 0, 0 }, + { 90, 0, 0, 0, 0 }, + { 104, 97, 97, 0, 0 }, + { 118, 0, 0, 0, 0 }, + { 132, 0, 0, 0, 0 }, + { 146, 114, 114, 0, 0 }, + { 162, 97, 97, 0, 0 }, + { 181, 114, 114, 0, 0 }, + { 198, 134, 165, 0, 0 }, + { 211, 194, 225, 0, 60 }, + { 229, 37, 68, 0, 0 }, + { 242, 0, 0, 0, 0 }, + { 257, 0, 0, 0, 0 }, + { 270, 20, 20, 0, 0 }, + { 291, 20, 20, 0, 0 }, + { 307, 97, 97, 0, 0 }, + { 321, 194, 225, 60, 58 }, + { 337, 0, 0, 0, 0 }, + { 353, 114, 114, 0, 0 }, + { 369, 114, 114, 0, 0 }, + { 383, 254, 254, 0, 0 }, + { 403, 114, 281, 0, 0 }, + { 415, 20, 20, 0, 0 }, + { 430, 114, 281, 0, 0 }, + { 446, 114, 114, 0, 0 }, + { 460, 97, 97, 0, 0 }, + { 476, 97, 97, 0, 0 }, + { 489, 97, 97, 0, 0 }, + { 507, 0, 0, 0, 0 }, + { 519, 97, 97, 0, 0 }, + { 533, 114, 114, 0, 0 }, + { 551, 114, 114, 0, 0 }, + { 565, 97, 97, 0, 0 }, + { 579, 114, 114, 0, 0 }, + { 593, 254, 254, 0, 0 }, + { 607, 254, 254, 0, 0 }, + { 622, 20, 20, 0, 0 }, + { 639, 0, 0, 0, 0 }, + { 655, 20, 20, 0, 0 }, + { 670, 97, 291, 0, 0 }, + { 686, 97, 97, 0, 0 }, + { 700, 0, 0, 0, 0 }, + { 718, 0, 0, 0, 0 }, + { 737, 97, 97, 0, 0 }, + { 755, 0, 0, 0, 0 }, + { 771, 0, 0, 0, 0 }, + { 787, 134, 165, 0, 0 }, + { 802, 37, 68, 0, 0 }, + { 815, 114, 281, 118, 48 }, + { 831, 301, 331, 0, 0 }, + { 844, 361, 382, 0, 0 }, + { 862, 403, 403, 0, 0 }, + { 879, 403, 403, 0, 0 }, + { 895, 426, 449, 0, 0 }, + { 913, 470, 494, 0, 0 }, + { 944, 470, 494, 0, 0 }, + { 972, 470, 494, 0, 0 }, + { 1005, 470, 494, 0, 0 }, + { 1031, 470, 494, 0, 0 }, + { 1055, 470, 494, 0, 0 }, + { 1082, 470, 494, 0, 0 }, + { 1108, 470, 494, 0, 0 }, + { 1139, 470, 494, 0, 0 }, + { 1163, 470, 494, 0, 0 }, + { 1190, 470, 494, 0, 0 }, + { 1217, 470, 494, 0, 0 }, + { 1243, 470, 494, 0, 0 }, + { 1269, 403, 403, 0, 0 }, + { 1283, 516, 539, 0, 0 }, + { 1300, 560, 560, 0, 0 }, + { 1317, 301, 331, 0, 0 }, + { 1330, 426, 449, 0, 0 }, + { 1344, 582, 604, 0, 0 }, + { 1367, 403, 626, 0, 0 }, + { 1384, 426, 449, 0, 0 }, + { 1398, 582, 604, 0, 0 }, + { 1413, 403, 403, 0, 0 }, + { 1434, 649, 670, 0, 0 }, + { 1452, 689, 712, 0, 0 }, + { 1467, 733, 756, 0, 0 }, + { 1481, 470, 494, 0, 0 }, + { 1502, 733, 756, 0, 0 }, + { 1524, 649, 670, 0, 0 }, + { 1545, 560, 779, 0, 0 }, + { 1560, 801, 801, 0, 0 }, + { 1576, 470, 494, 0, 0 }, + { 1594, 816, 816, 0, 0 }, + { 1610, 560, 560, 0, 0 }, + { 1625, 582, 604, 0, 0 }, + { 1641, 582, 604, 0, 0 }, + { 1659, 733, 756, 0, 0 }, + { 1681, 560, 560, 0, 0 }, + { 1703, 470, 494, 0, 0 }, + { 1719, 582, 604, 0, 0 }, + { 1738, 835, 835, 0, 0 }, + { 1756, 733, 733, 0, 0 }, + { 1772, 649, 670, 0, 0 }, + { 1787, 403, 403, 0, 0 }, + { 1803, 0, 845, 0, 0 }, + { 1824, 855, 866, 0, 0 }, + { 1839, 733, 756, 0, 0 }, + { 1860, 733, 756, 0, 0 }, + { 1875, 560, 779, 0, 0 }, + { 1891, 403, 403, 0, 0 }, + { 1908, 733, 756, 166, 109 }, + { 1925, 876, 895, 0, 0 }, + { 1942, 582, 604, 0, 0 }, + { 1962, 912, 934, 0, 0 }, + { 1979, 733, 756, 0, 0 }, + { 1999, 560, 779, 0, 0 }, + { 2018, 426, 449, 0, 0 }, + { 2036, 403, 626, 0, 0 }, + { 2054, 956, 980, 0, 0 }, + { 2070, 403, 626, 0, 0 }, + { 2088, 560, 779, 0, 0 }, + { 2107, 403, 403, 0, 0 }, + { 2123, 403, 403, 0, 0 }, + { 2142, 582, 604, 0, 0 }, + { 2160, 1002, 1015, 0, 0 }, + { 2178, 1025, 1025, 0, 0 }, + { 2193, 403, 626, 0, 0 }, + { 2209, 1037, 1056, 0, 0 }, + { 2224, 1075, 1105, 0, 0 }, + { 2243, 560, 779, 0, 0 }, + { 2272, 582, 604, 0, 0 }, + { 2293, 560, 779, 0, 0 }, + { 2317, 560, 779, 0, 0 }, + { 2344, 582, 604, 0, 0 }, + { 2370, 560, 779, 0, 0 }, + { 2392, 560, 779, 0, 0 }, + { 2418, 560, 779, 0, 0 }, + { 2442, 560, 779, 0, 0 }, + { 2463, 733, 756, 0, 0 }, + { 2478, 560, 779, 0, 0 }, + { 2494, 560, 779, 0, 0 }, + { 2510, 470, 494, 0, 0 }, + { 2524, 361, 382, 0, 0 }, + { 2539, 560, 779, 0, 0 }, + { 2567, 560, 779, 0, 0 }, + { 2595, 582, 604, 0, 0 }, + { 2611, 403, 403, 0, 0 }, + { 2630, 1135, 1135, 0, 0 }, + { 2645, 1148, 1167, 0, 0 }, + { 2658, 912, 934, 0, 0 }, + { 2678, 560, 779, 0, 0 }, + { 2697, 403, 403, 0, 0 }, + { 2719, 426, 449, 0, 0 }, + { 2734, 582, 604, 0, 0 }, + { 2750, 649, 670, 0, 0 }, + { 2765, 403, 403, 0, 0 }, + { 2781, 403, 626, 0, 0 }, + { 2800, 582, 604, 0, 0 }, + { 2818, 1075, 1105, 0, 0 }, + { 2835, 470, 494, 0, 0 }, + { 2851, 582, 604, 0, 0 }, + { 2869, 582, 604, 0, 0 }, + { 2884, 361, 382, 0, 0 }, + { 2903, 582, 604, 0, 0 }, + { 2923, 1184, 1220, 0, 0 }, + { 2940, 403, 626, 0, 0 }, + { 2956, 582, 604, 0, 0 }, + { 2974, 1256, 1278, 0, 0 }, + { 2993, 560, 779, 0, 0 }, + { 3010, 403, 403, 0, 0 }, + { 3029, 560, 779, 0, 0 }, + { 3044, 560, 779, 0, 0 }, + { 3061, 560, 779, 0, 0 }, + { 3077, 361, 382, 0, 0 }, + { 3090, 1298, 1332, 0, 0 }, + { 3106, 582, 604, 0, 0 }, + { 3134, 582, 604, 0, 0 }, + { 3162, 582, 604, 0, 0 }, + { 3193, 956, 980, 0, 0 }, + { 3206, 582, 604, 0, 0 }, + { 3222, 560, 560, 0, 0 }, + { 3237, 560, 779, 0, 0 }, + { 3257, 1364, 1364, 0, 0 }, + { 3276, 733, 733, 0, 0 }, + { 3292, 560, 779, 0, 0 }, + { 3315, 403, 403, 0, 0 }, + { 3337, 876, 895, 0, 0 }, + { 3356, 649, 670, 0, 0 }, + { 3376, 403, 403, 0, 0 }, + { 3396, 835, 835, 0, 0 }, + { 3417, 582, 604, 0, 0 }, + { 3437, 582, 604, 0, 0 }, + { 3458, 426, 449, 0, 0 }, + { 3473, 582, 582, 0, 0 }, + { 3488, 582, 604, 0, 0 }, + { 3505, 876, 895, 0, 0 }, + { 3524, 470, 494, 0, 0 }, + { 3540, 912, 934, 0, 0 }, + { 3561, 426, 449, 0, 0 }, + { 3578, 1378, 1398, 0, 0 }, + { 3595, 403, 626, 0, 0 }, + { 3617, 426, 449, 0, 0 }, + { 3635, 956, 980, 0, 0 }, + { 3656, 733, 756, 0, 0 }, + { 3673, 361, 382, 0, 0 }, + { 3687, 403, 403, 0, 0 }, + { 3709, 1416, 1443, 0, 0 }, + { 3726, 403, 403, 0, 0 }, + { 3743, 403, 403, 0, 0 }, + { 3760, 403, 403, 0, 0 }, + { 3778, 403, 403, 0, 0 }, + { 3797, 582, 582, 0, 0 }, + { 3819, 582, 604, 0, 0 }, + { 3839, 403, 626, 0, 0 }, + { 3853, 560, 779, 0, 0 }, + { 3873, 912, 934, 0, 0 }, + { 3889, 560, 779, 0, 0 }, + { 3905, 403, 403, 0, 0 }, + { 3921, 912, 934, 275, 113 }, + { 3939, 403, 403, 0, 0 }, + { 3954, 855, 866, 0, 0 }, + { 3973, 582, 604, 0, 0 }, + { 3990, 361, 382, 0, 0 }, + { 4006, 733, 756, 388, 109 }, + { 4026, 1470, 1470, 0, 0 }, + { 4043, 1503, 1503, 0, 0 }, + { 4060, 1514, 1514, 0, 0 }, + { 4086, 1538, 1571, 0, 0 }, + { 4107, 1604, 1604, 0, 0 }, + { 4125, 1616, 1642, 0, 0 }, + { 4144, 835, 835, 0, 0 }, + { 4162, 1668, 1668, 0, 0 }, + { 4181, 1616, 1642, 0, 0 }, + { 4203, 1681, 1681, 0, 0 }, + { 4220, 0, 281, 0, 0 }, + { 4237, 1692, 1692, 0, 0 }, + { 4255, 37, 68, 0, 0 }, + { 4275, 1704, 1704, 0, 0 }, + { 4285, 1726, 1742, 0, 0 }, + { 4297, 1752, 1752, 0, 0 }, + { 4308, 1762, 1786, 0, 0 }, + { 4320, 1726, 1742, 0, 0 }, + { 4331, 1726, 1742, 0, 0 }, + { 4343, 1808, 1835, 0, 0 }, + { 4357, 1808, 1835, 0, 0 }, + { 4372, 1726, 1742, 0, 0 }, + { 4384, 1704, 1860, 0, 0 }, + { 4397, 1704, 1704, 0, 0 }, + { 4410, 1882, 1907, 0, 0 }, + { 4420, 1930, 1930, 0, 0 }, + { 4433, 1945, 1971, 0, 0 }, + { 4446, 134, 165, 0, 0 }, + { 4458, 1995, 2011, 0, 0 }, + { 4471, 2021, 2021, 0, 0 }, + { 4483, 2033, 2033, 0, 0 }, + { 4497, 2053, 2075, 0, 0 }, + { 4508, 2095, 2121, 0, 0 }, + { 4524, 2145, 2165, 0, 0 }, + { 4539, 2145, 2165, 0, 0 }, + { 4554, 2033, 2033, 0, 0 }, + { 4567, 2185, 2210, 0, 0 }, + { 4578, 1752, 1752, 0, 0 }, + { 4592, 2185, 2210, 0, 0 }, + { 4603, 2233, 2233, 0, 0 }, + { 4613, 2250, 2250, 0, 0 }, + { 4624, 2269, 1742, 0, 0 }, + { 4638, 134, 165, 0, 0 }, + { 4653, 134, 165, 0, 0 }, + { 4663, 2145, 2165, 0, 0 }, + { 4675, 134, 165, 0, 0 }, + { 4687, 1930, 1930, 0, 0 }, + { 4704, 2285, 2309, 0, 0 }, + { 4719, 2331, 2351, 0, 0 }, + { 4729, 2369, 2391, 0, 0 }, + { 4742, 2411, 2434, 0, 0 }, + { 4756, 2455, 2455, 0, 0 }, + { 4769, 2478, 2478, 0, 0 }, + { 4783, 2501, 2522, 0, 0 }, + { 4798, 2543, 2543, 0, 0 }, + { 4809, 1762, 1786, 0, 0 }, + { 4824, 2560, 2583, 0, 0 }, + { 4837, 2011, 2011, 0, 0 }, + { 4850, 2604, 2604, 0, 0 }, + { 4865, 2604, 2604, 0, 0 }, + { 4879, 2053, 2075, 0, 0 }, + { 4893, 2033, 2033, 0, 0 }, + { 4906, 1945, 1971, 0, 0 }, + { 4923, 2615, 2615, 0, 0 }, + { 4941, 2615, 2615, 0, 0 }, + { 4954, 1704, 1704, 0, 0 }, + { 4966, 2145, 2165, 0, 0 }, + { 4977, 2145, 2165, 0, 0 }, + { 4988, 2629, 2651, 0, 0 }, + { 5001, 2671, 2671, 0, 0 }, + { 5015, 2694, 2719, 0, 0 }, + { 5027, 2250, 2250, 0, 0 }, + { 5039, 134, 165, 0, 0 }, + { 5052, 1945, 1971, 0, 0 }, + { 5070, 1945, 1971, 0, 0 }, + { 5087, 2742, 2761, 0, 0 }, + { 5097, 1726, 1742, 0, 0 }, + { 5107, 1930, 1930, 0, 0 }, + { 5123, 2455, 2455, 0, 0 }, + { 5138, 2778, 2778, 0, 0 }, + { 5153, 1704, 1704, 0, 0 }, + { 5164, 1726, 1742, 0, 0 }, + { 5178, 1726, 1742, 0, 0 }, + { 5193, 2799, 2799, 0, 0 }, + { 5206, 1704, 1704, 0, 0 }, + { 5218, 1930, 1930, 0, 0 }, + { 5230, 2629, 2651, 0, 0 }, + { 5244, 2812, 2837, 0, 0 }, + { 5259, 2778, 2860, 0, 0 }, + { 5270, 2145, 2165, 0, 0 }, + { 5284, 2881, 2881, 0, 0 }, + { 5299, 2629, 2651, 0, 0 }, + { 5318, 2905, 2926, 0, 0 }, + { 5330, 2812, 2837, 0, 0 }, + { 5344, 2947, 2969, 0, 0 }, + { 5357, 2989, 3008, 0, 0 }, + { 5369, 2501, 2522, 0, 0 }, + { 5383, 3027, 3027, 0, 0 }, + { 5395, 3027, 3027, 0, 0 }, + { 5408, 3039, 3039, 0, 0 }, + { 5419, 1945, 1971, 0, 0 }, + { 5430, 2671, 2671, 0, 0 }, + { 5449, 2095, 2121, 0, 0 }, + { 5466, 2095, 2121, 0, 0 }, + { 5482, 2011, 2011, 0, 0 }, + { 5494, 3059, 3085, 0, 0 }, + { 5508, 1930, 1930, 0, 0 }, + { 5523, 3059, 3085, 0, 0 }, + { 5540, 2053, 2075, 0, 0 }, + { 5553, 2799, 2799, 0, 0 }, + { 5565, 3109, 3137, 0, 0 }, + { 5584, 3163, 3185, 0, 0 }, + { 5597, 3205, 3226, 0, 0 }, + { 5613, 403, 626, 0, 0 }, + { 5630, 194, 225, 0, 0 }, + { 5646, 3245, 3245, 0, 0 }, + { 5666, 194, 225, 0, 0 }, + { 5682, 194, 225, 0, 0 }, + { 5697, 37, 68, 0, 0 }, + { 5716, 194, 225, 0, 0 }, + { 5733, 0, 0, 0, 0 }, + { 5752, 3270, 3270, 0, 0 }, + { 5775, 0, 0, 0, 0 }, + { 5794, 3289, 3320, 0, 0 }, + { 5811, 1538, 1571, 0, 0 }, + { 5825, 3349, 3382, 0, 0 }, + { 5844, 1538, 1571, 0, 0 }, + { 5863, 3349, 3382, 0, 0 }, + { 5885, 1538, 1571, 0, 0 }, + { 5904, 1538, 1571, 0, 0 }, + { 5921, 3349, 3349, 0, 0 }, + { 5938, 3415, 3456, 0, 0 }, + { 5954, 1538, 1571, 0, 0 }, + { 5971, 3497, 3521, 0, 0 }, + { 5985, 1538, 1571, 0, 0 }, + { 6004, 3497, 3521, 0, 0 }, + { 6024, 1538, 1571, 0, 0 }, + { 6044, 1538, 1571, 0, 0 }, + { 6058, 3349, 3349, 0, 0 }, + { 6074, 1470, 3545, 0, 0 }, + { 6090, 1538, 1571, 0, 0 }, + { 6111, 3349, 3382, 0, 0 }, + { 6127, 1538, 1571, 0, 0 }, + { 6144, 1538, 1571, 0, 0 }, + { 6163, 1538, 1571, 0, 0 }, + { 6182, 1470, 3545, 0, 0 }, + { 6197, 3349, 3382, 0, 0 }, + { 6218, 876, 895, 0, 0 }, + { 6230, 1298, 1332, 0, 0 }, + { 6247, 426, 449, 0, 0 }, + { 6259, 649, 670, 0, 0 }, + { 6271, 37, 68, 0, 0 }, + { 6275, 582, 604, 0, 0 }, + { 6283, 403, 626, 0, 0 }, + { 6299, 582, 604, 0, 0 }, + { 6314, 560, 779, 0, 0 }, + { 6329, 733, 756, 497, 109 }, + { 6345, 1416, 1443, 0, 0 }, + { 6365, 912, 934, 606, 113 }, + { 6380, 582, 582, 0, 0 }, + { 6400, 855, 866, 0, 0 }, + { 6413, 1378, 1398, 0, 0 }, + { 6431, 3578, 3606, 0, 0 }, + { 6450, 1037, 1056, 0, 0 }, + { 6455, 134, 165, 0, 0 }, + { 6459, 560, 560, 0, 0 }, + { 6463, 560, 779, 0, 0 }, + { 6471, 134, 165, 0, 0 }, + { 6477, 0, 0, 0, 0 }, + { 6482, 0, 0, 0, 0 }, + { 6490, 0, 0, 0, 0 }, + { 6500, 3632, 3632, 0, 0 }, + { 6510, 3642, 3642, 0, 0 }, + { 6521, 3652, 3652, 0, 0 }, + { 6532, 3662, 3662, 0, 0 }, + { 6543, 3672, 3672, 0, 0 }, + { 6553, 835, 835, 0, 0 }, + { 6563, 3682, 3682, 0, 0 }, + { 6573, 1015, 1015, 0, 0 }, + { 6583, 3692, 3692, 0, 0 }, + { 6593, 866, 866, 0, 0 }, + { 6603, 3702, 3702, 0, 0 }, + { 6613, 3712, 3712, 0, 0 }, + { 6623, 0, 0, 0, 0 }, + { 6633, 291, 291, 0, 0 }, + { 6643, 3722, 3722, 0, 0 }, + { 6654, 3732, 3732, 0, 0 }, + { 6665, 3742, 3742, 0, 0 }, + { 6676, 3752, 3752, 0, 0 }, + { 6687, 3762, 3762, 0, 0 }, + { 6698, 281, 281, 0, 0 }, + { 6708, 1752, 1752, 0, 0 }, + { 6718, 3772, 3772, 0, 0 }, + { 6728, 1742, 1742, 0, 0 }, + { 6738, 2011, 2011, 0, 0 }, + { 6748, 3782, 3782, 0, 0 }, + { 6758, 3792, 3792, 0, 0 }, + { 6768, 3802, 3802, 0, 0 }, + { 6778, 0, 0, 0, 0 }, + { 6787, 0, 0, 0, 0 }, + { 6801, 3812, 3812, 0, 0 }, + { 6809, 3812, 3812, 0, 0 }, + { 6817, 3812, 3812, 0, 0 }, + { 6831, 3812, 3812, 0, 0 }, + { 6840, 37, 68, 0, 0 }, + { 6857, 37, 68, 0, 0 }, + { 6872, 3839, 3860, 0, 0 }, + { 6889, 134, 165, 0, 0 }, + { 6903, 0, 3879, 0, 0 }, + { 6918, 37, 68, 0, 0 }, + { 6934, 37, 68, 0, 0 }, + { 6948, 37, 68, 0, 0 }, + { 6966, 37, 68, 0, 0 }, + { 6982, 134, 165, 0, 0 }, + { 6999, 37, 68, 0, 0 }, + { 7015, 37, 68, 0, 0 }, + { 7031, 134, 165, 0, 0 }, + { 7047, 37, 68, 0, 0 }, + { 7065, 0, 3899, 719, 132 }, + { 7079, 37, 68, 0, 0 }, + { 7096, 0, 291, 0, 0 }, + { 7112, 134, 165, 0, 0 }, + { 7128, 0, 291, 0, 0 }, + { 7147, 2411, 2434, 0, 0 }, + { 7163, 0, 291, 0, 0 }, + { 7177, 134, 165, 0, 0 }, + { 7196, 134, 165, 0, 0 }, + { 7208, 3919, 3940, 0, 0 }, + { 7221, 134, 165, 0, 0 }, + { 7233, 194, 225, 0, 0 }, + { 7247, 37, 68, 0, 0 }, + { 7264, 0, 3879, 0, 0 }, + { 7278, 37, 68, 0, 0 }, + { 7296, 37, 68, 0, 0 }, + { 7310, 37, 68, 0, 0 }, + { 7323, 134, 165, 0, 0 }, + { 7340, 3919, 3940, 0, 0 }, + { 7353, 37, 68, 0, 0 }, + { 7367, 3919, 3940, 0, 0 }, + { 7381, 134, 165, 0, 0 }, + { 7396, 37, 68, 0, 0 }, + { 7408, 37, 68, 0, 0 }, + { 7421, 37, 68, 0, 0 }, + { 7438, 37, 68, 0, 0 }, + { 7452, 134, 165, 0, 0 }, + { 7464, 37, 68, 0, 0 }, + { 7476, 3839, 3860, 0, 0 }, + { 7490, 37, 68, 0, 0 }, + { 7508, 37, 68, 0, 0 }, + { 7524, 3839, 3860, 0, 0 }, + { 7539, 3919, 3940, 0, 0 }, + { 7557, 37, 68, 0, 0 }, + { 7571, 134, 165, 0, 0 }, + { 7584, 37, 68, 0, 0 }, + { 7601, 134, 165, 0, 0 }, + { 7616, 37, 68, 0, 0 }, + { 7630, 134, 165, 0, 0 }, + { 7646, 3839, 3860, 0, 0 }, + { 7663, 134, 165, 0, 0 }, + { 7679, 37, 68, 0, 0 }, + { 7692, 37, 68, 0, 0 }, + { 7707, 37, 68, 0, 0 }, + { 7721, 134, 165, 0, 0 }, + { 7736, 3919, 3940, 0, 0 }, + { 7753, 37, 68, 0, 0 }, + { 7767, 37, 68, 0, 0 }, + { 7781, 134, 165, 0, 0 }, + { 7799, 37, 68, 0, 0 }, + { 7813, 0, 3879, 0, 0 }, + { 7816, 0, 3879, 0, 0 }, + { 7824, 0, 0, 0, 0 }, + { 7828, 0, 0, 0, 0 }, + { 7834, 0, 0, 0, 0 }, + { 7840, 0, 0, 0, 0 }, + { 7845, 0, 0, 0, 0 }, + { 7855, 301, 301, 0, 0 }, + { 7859, 2285, 2309, 0, 0 }, + { 7868, 0, 0, 0, 0 }, + { 7876, 20, 20, 0, 0 }, + { 7896, 3959, 3959, 0, 0 }, + { 7910, 3977, 3977, 0, 0 }, + { 7927, 3999, 3999, 0, 0 }, + { 7940, 20, 20, 0, 0 }, + { 7954, 4018, 4018, 0, 0 }, + { 7971, 4051, 4051, 0, 0 }, + { 7983, 4067, 4067, 0, 0 }, + { 7999, 4081, 4105, 0, 0 }, + { 8016, 20, 20, 0, 0 }, + { 8031, 4127, 4127, 0, 0 }, + { 8046, 2989, 3008, 0, 0 }, + { 8051, 2501, 2522, 0, 0 }, + { 8058, 560, 779, 0, 0 }, + { 8066, 3039, 3039, 0, 0 }, + { 8072, 4141, 4141, 0, 0 }, + { 8082, 134, 165, 0, 0 }, + { 8088, 37, 68, 0, 0 }, + { 8092, 733, 733, 0, 0 }, + { 8096, 733, 756, 0, 0 }, + { 8104, 912, 934, 0, 0 }, + { 8121, 1075, 1105, 0, 0 }, + { 8136, 582, 604, 0, 0 }, + { 8151, 1616, 1642, 0, 0 }, + { 8154, 4163, 4185, 0, 0 }, + { 8162, 733, 756, 0, 0 }, + { 8169, 2145, 2165, 0, 0 }, + { 8173, 912, 934, 0, 0 }, + { 8181, 4207, 4227, 0, 0 }, + { 8194, 1616, 1642, 0, 0 }, + { 8211, 3732, 3732, 0, 0 }, + { 8232, 4163, 4185, 0, 0 }, + { 8248, 4247, 4247, 0, 0 }, + { 8262, 3578, 3606, 0, 0 }, + { 8277, 4258, 4280, 0, 0 }, + { 8291, 4300, 4300, 0, 0 }, + { 8309, 4321, 4321, 0, 0 }, + { 8325, 4334, 4353, 0, 0 }, + { 8338, 4370, 4370, 0, 0 }, + { 8355, 4382, 3692, 0, 0 }, + { 8373, 4397, 4397, 0, 0 }, + { 8389, 4410, 4410, 0, 0 }, + { 8409, 4431, 3722, 0, 0 }, + { 8422, 301, 301, 0, 0 }, + { 8439, 301, 301, 0, 0 }, + { 8456, 4300, 4300, 0, 0 }, + { 8471, 4454, 4454, 0, 0 }, + { 8490, 4472, 4472, 0, 0 }, + { 8505, 4141, 4141, 0, 0 }, + { 8523, 4141, 4141, 0, 0 }, + { 8538, 4484, 4484, 0, 0 }, + { 8556, 4499, 4499, 0, 0 }, + { 8571, 4528, 4528, 0, 0 }, + { 8585, 4539, 4539, 0, 0 }, + { 8598, 4549, 4578, 0, 0 }, + { 8614, 4607, 4635, 0, 0 }, + { 8629, 4499, 4499, 0, 0 }, + { 8647, 4661, 4661, 0, 0 }, + { 8661, 4672, 4672, 0, 0 }, + { 8678, 4686, 4686, 0, 0 }, + { 8694, 4686, 4686, 0, 0 }, + { 8709, 4699, 4699, 0, 0 }, + { 8730, 4721, 4748, 0, 0 }, + { 8748, 4431, 3722, 0, 0 }, + { 8763, 4499, 4499, 0, 0 }, + { 8777, 4773, 4773, 0, 0 }, + { 8792, 4785, 4785, 0, 0 }, + { 8807, 4806, 4826, 0, 0 }, + { 8825, 4247, 4247, 0, 0 }, + { 8838, 4844, 4844, 0, 0 }, + { 8851, 4861, 4861, 0, 0 }, + { 8866, 4247, 4247, 0, 0 }, + { 8878, 37, 68, 0, 0 }, + { 8885, 194, 225, 0, 0 }, + { 8894, 2905, 2926, 0, 0 }, + { 8898, 2778, 2860, 0, 0 }, + { 8902, 2881, 2881, 0, 0 }, + { 8912, 2411, 2434, 0, 0 }, + { 8919, 3812, 3812, 0, 0 }, + { 8923, 361, 382, 0, 0 }, + { 8933, 301, 331, 0, 0 }, + { 8945, 733, 733, 0, 0 }, + { 8956, 582, 604, 0, 0 }, + { 8967, 560, 779, 0, 0 }, + { 8983, 560, 779, 0, 0 }, + { 8994, 301, 301, 0, 0 }, + { 9004, 582, 604, 0, 0 }, + { 9022, 560, 779, 0, 0 }, + { 9034, 733, 756, 0, 0 }, + { 9046, 912, 934, 0, 0 }, + { 9057, 4499, 4499, 0, 0 }, + { 9066, 3812, 3812, 0, 0 }, + { 9070, 3812, 3812, 0, 0 }, + { 9080, 3919, 3940, 0, 0 }, + { 9085, 194, 225, 0, 0 }, + { 9089, 3812, 3812, 0, 0 }, +}; +#endif diff --git a/scripts/gen-timezone-names.mjs b/scripts/gen-timezone-names.mjs new file mode 100644 index 000000000..69aab583e --- /dev/null +++ b/scripts/gen-timezone-names.mjs @@ -0,0 +1,292 @@ +#!/usr/bin/env node +// Long time zone names for Date.prototype.toString, toTimeString and +// String(date), derived from the pinned Node release's ICU. +// +// node scripts/gen-timezone-names.mjs [--tzdata ] regenerate the table +// node scripts/gen-timezone-names.mjs [--tzdata ] --check verify only +// +// Node prints the zone's long English name for standard or daylight time +// after the GMT offset ("GMT-0500 (Central Daylight Time)"). The name +// depends only on the zone and on whether the instant is in daylight time +// (V8 maps instants outside 1970 to 2038-01-19 into that range), so each +// zone ID in the IANA tzdata (zones and links, read from tzdata.zi) is +// sampled once a day over that range with TZ set to it, and the two names +// Node prints are recorded. Where the name changes together with the UTC +// offset, the name at the larger offset is the daylight name. Zones +// without a CLDR name print Node's "GMT+hh:mm" fallback, which is recorded +// as is. +// +// The runtime takes daylight time from tm_isdst, except for two kinds of +// zones, which get the exact instants at which Node's name changes: +// zones whose tzdata rules use a negative daylight saving (Europe/Dublin's +// winter time, Morocco's Ramadan time), where ICU's data calls the larger +// offset daylight time, and zones that Node shows in daylight time through +// 2038 (British Columbia's permanent UTC-7, which tzdata names standard +// time). +import { spawn } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { writeFile } from "node:fs/promises"; +import { availableParallelism } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const self = fileURLToPath(import.meta.url); +const root = fileURLToPath(new URL("..", import.meta.url)); +const out = join(root, "packages/runtime/src/scr_timezone_data.h"); + +// V8 names the zone of instants in [0, 2^31 - 1] seconds directly. +const FIRST_MS = 0; +const LAST_MS = 2147483647000; +const DAY_MS = 86400000; + +const zoneName = (ms) => { + const text = new Date(ms).toString(); + return text.slice(text.indexOf("(") + 1, -1); +}; + +if (process.argv.includes("--sample")) { + // One zone, selected by TZ: the first name printed, then every change of + // name with the exact instant (ms) and the UTC offsets (minutes) before + // and after it. + const changes = []; + let previous = null; + const visit = (ms) => { + const name = zoneName(ms); + const offset = -new Date(ms).getTimezoneOffset(); + if (previous === null) { + changes.push({ at: ms, name, offset }); + } else if (previous.name !== name) { + // The first second with the new name. + let lo = previous.ms / 1000; + let hi = ms / 1000; + while (hi - lo > 1) { + const mid = Math.floor((lo + hi) / 2); + if (zoneName(mid * 1000) === name) hi = mid; + else lo = mid; + } + changes.push({ at: hi * 1000, from: previous, name, offset }); + } + previous = { ms, name, offset }; + }; + for (let ms = FIRST_MS; ms < LAST_MS; ms += DAY_MS) visit(ms); + visit(LAST_MS); + process.stdout.write(JSON.stringify(changes)); + process.exit(0); +} + +const tzdataIndex = process.argv.indexOf("--tzdata"); +const tzdata = tzdataIndex >= 0 ? process.argv[tzdataIndex + 1] : "/usr/share/zoneinfo/tzdata.zi"; +const checkOnly = process.argv.includes("--check"); + +/** Zone and link names in tzdata.zi, and the zones that save a negative + * amount at some time from 1970 on. Links name their target. */ +function readTzdata(path) { + const text = readFileSync(path, "utf8"); + const zones = new Map(); + const negativeRules = new Set(); + const negativeZones = new Set(); + const year = (field) => (field === undefined ? Infinity : Number.parseInt(field, 10)); + let zone = null; + for (const line of text.split("\n")) { + const fields = line.trim().split(/\s+/); + if (line.startsWith("R ")) { + // R NAME FROM TO - IN ON AT SAVE LETTER, TO "o" (only) or "ma" (max). + zone = null; + const to = + fields[3] === "o" ? year(fields[2]) : fields[3] === "ma" ? Infinity : year(fields[3]); + if (fields[8]?.startsWith("-") && to >= 1970) negativeRules.add(fields[1]); + continue; + } + if (line.startsWith("L ")) { + zone = null; + zones.set(fields[2], fields[1]); + continue; + } + // Z NAME STDOFF RULES FORMAT [UNTIL], continued by STDOFF RULES FORMAT [UNTIL]. + let rest; + if (line.startsWith("Z ")) { + zone = fields[1]; + zones.set(zone, zone); + rest = fields.slice(2); + } else if (zone && fields.length >= 3 && !line.startsWith("#")) { + rest = fields; + } else { + zone = null; + continue; + } + const [, rule, , until] = rest; + if (year(until) >= 1970 && (negativeRules.has(rule) || /^-\d/.test(rule))) + negativeZones.add(zone); + } + const version = /^# version (\S+)/.exec(text)?.[1] ?? "unknown"; + return { zones, negativeZones, version }; +} + +/** The zone's [standard, daylight] names as Node prints them: at a change + * of name that also changes the UTC offset, the name printed at the larger + * offset is the daylight name; the name chosen at most such changes wins. */ +function classify(id, changes) { + const names = new Set(changes.map((c) => c.name)); + if (names.size > 2) throw new Error(`${id}: more than two names`); + if (names.size === 1) { + const [name] = names; + return [name, name]; + } + // Zones that moved between offsets keep one daylight name, so the vote + // can include a transition that only changed the standard offset. + const votes = new Map(); + for (const c of changes) { + if (!c.from || c.from.offset === c.offset) continue; + const name = c.offset > c.from.offset ? c.name : c.from.name; + votes.set(name, (votes.get(name) ?? 0) + 1); + } + const ranked = [...votes].sort((a, b) => b[1] - a[1]); + if (ranked.length === 0 || (ranked.length === 2 && ranked[0][1] === ranked[1][1])) + throw new Error(`${id}: no daylight name`); + const daylight = ranked[0][0]; + const standard = [...names].find((n) => n !== daylight); + return [standard, daylight]; +} + +function sample(id) { + return new Promise((resolve, reject) => { + const env = { ...process.env, TZ: id, LANG: "en_US.UTF-8" }; + delete env.LC_ALL; + delete env.LC_MESSAGES; + const child = spawn(process.execPath, [self, "--sample"], { env }); + let text = ""; + child.stdout.on("data", (chunk) => (text += chunk)); + child.on("error", reject); + child.on("close", (code) => + code === 0 ? resolve(JSON.parse(text)) : reject(new Error(`${id}: sampler exited ${code}`)), + ); + }); +} + +const { zones, negativeZones, version } = readTzdata(tzdata); +const ids = [...zones.keys()] + .filter((id) => { + try { + new Intl.DateTimeFormat("en-US", { timeZone: id }); + return true; + } catch { + return false; + } + }) + .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); + +const samples = new Map(); +let next = 0; +await Promise.all( + Array.from({ length: availableParallelism() }, async () => { + while (next < ids.length) { + const id = ids[next++]; + samples.set(id, await sample(id)); + } + }), +); + +// Pools of NUL-terminated strings, addressed by byte offset. +function pool() { + const offsets = new Map(); + let text = ""; + return { + add(s) { + if (!offsets.has(s)) { + offsets.set(s, Buffer.byteLength(text)); + text += s + "\0"; + } + return offsets.get(s); + }, + get text() { + return text; + }, + }; +} +const idPool = pool(); +const namePool = pool(); +const changeSeconds = []; +const rows = ids.map((id) => { + const changes = samples.get(id); + const [standard, daylight] = classify(id, changes); + // Exact daylight-time changes, as epoch seconds at which daylight time + // starts or ends; INT32_MIN first when 1970 starts in daylight time. + let first = 0; + let count = 0; + // Daylight time with no change in the last year sampled. + const last = changes.at(-1); + const permanent = last.name === daylight && LAST_MS - last.at > 366 * DAY_MS; + if (standard !== daylight && (negativeZones.has(zones.get(id)) || permanent)) { + first = changeSeconds.length; + if (changes[0].name === daylight) changeSeconds.push("INT32_MIN"); + for (const c of changes.slice(1)) changeSeconds.push(String(c.at / 1000)); + count = changeSeconds.length - first; + } + return ` { ${idPool.add(id)}, ${namePool.add(standard)}, ${namePool.add(daylight)}, ${first}, ${count} },`; +}); +// The runtime formats into a fixed buffer. +for (const [standard, daylight] of ids.map((id) => classify(id, samples.get(id)))) { + if (Buffer.byteLength(standard) > 64 || Buffer.byteLength(daylight) > 64) + throw new Error(`zone name longer than 64 bytes: ${standard} / ${daylight}`); +} +for (const p of [idPool, namePool]) { + if (Buffer.byteLength(p.text) > 0xffff) throw new Error("string pool exceeds 16-bit offsets"); +} + +const cString = (text) => + text + .split("\0") + .slice(0, -1) + .map((s) => ` "${s.replace(/[\\"]/g, "\\$&")}\\0"`) + .join("\n"); + +const wrap = (items) => { + const lines = []; + let line = " "; + for (const item of items) { + if (line.length + item.length + 2 > 100) { + lines.push(line); + line = " "; + } + line += ` ${item},`; + } + if (line.trim()) lines.push(line); + return lines.join("\n"); +}; + +const header = `/* Generated by scripts/gen-timezone-names.mjs from Node ${process.version} + * (ICU ${process.versions.icu}, CLDR ${process.versions.cldr}) and tzdata ${version}. Do not edit. + * Each IANA zone or link ID maps to the long English names Node prints for + * its standard and daylight time. A row with change_count > 0 takes + * daylight time from scr_tz_daylight_changes[first_change ...]: the epoch + * seconds at which daylight time starts or ends between 1970 and + * 2038-01-19 (INT32_MIN first when 1970 starts in daylight time). Rows are + * sorted by ID. */ +#ifndef SCR_TIMEZONE_DATA_H +#define SCR_TIMEZONE_DATA_H +#include +typedef struct { uint16_t id, standard, daylight, first_change, change_count; } ScrTzNames; +static const char scr_tz_ids[] = +${cString(idPool.text)}; +static const char scr_tz_name_text[] = +${cString(namePool.text)}; +static const int32_t scr_tz_daylight_changes[] = { +${wrap(changeSeconds)} +}; +static const ScrTzNames scr_tz_names[] = { +${rows.join("\n")} +}; +#endif +`; + +if (checkOnly) { + const current = readFileSync(out, "utf8"); + if (current !== header) { + process.stderr.write(`${out} is out of date; run node scripts/gen-timezone-names.mjs\n`); + process.exit(1); + } + process.stdout.write(`${out} is up to date (${ids.length} zones)\n`); +} else { + await writeFile(out, header); + process.stdout.write(`wrote ${out} (${ids.length} zones)\n`); +} diff --git a/tests/corpus/5963-date-to-string.ts b/tests/corpus/5963-date-to-string.ts new file mode 100644 index 000000000..3b3024f3c --- /dev/null +++ b/tests/corpus/5963-date-to-string.ts @@ -0,0 +1,93 @@ +// Date.prototype.toString, toDateString and toTimeString, String(date), +// template literals and string concatenation print local time with the +// GMT offset and the zone's long name, as Node does. Each write to +// process.env.TZ changes the zone for later conversions and getters. +function at(ms: number): Date { + return new Date(ms); +} +const winter = at(Date.UTC(2026, 0, 15, 12, 30, 45, 678)); +const summer = at(Date.UTC(2026, 6, 15, 3, 4, 5)); +const epoch = at(0); +const beforeEpoch = at(Date.UTC(1950, 5, 1, 18)); +const farFuture = at(Date.UTC(2100, 6, 4, 9)); +const yearZero = at(-62167219200000); +const maxDate = at(8.64e15); +const minDate = at(-8.64e15); +const invalid = new Date(NaN); + +function show(zone: string): void { + process.env.TZ = zone; + console.log(`== ${zone}`); + console.log(winter.toString()); + console.log(String(summer)); + console.log(`${epoch} | ${beforeEpoch}`); + console.log("far: " + farFuture); + console.log(winter.toDateString(), "/", winter.toTimeString()); + console.log(summer.toDateString(), "/", summer.toTimeString()); + console.log(summer.getHours(), summer.getTimezoneOffset()); +} + +for (const zone of [ + "America/Chicago", + "America/New_York", + "America/St_Johns", + "Europe/Dublin", + "Europe/London", + "Europe/Berlin", + "Asia/Kolkata", + "Asia/Kathmandu", + "Asia/Tokyo", + "Australia/Lord_Howe", + "Australia/Sydney", + "Pacific/Chatham", + "America/Sao_Paulo", + "UTC", + "Etc/GMT+5", + "Etc/GMT-14", + "US/Pacific", + ":Asia/Seoul", +]) { + show(zone); +} + +// Daylight-time edges: the instants before and at a change. +process.env.TZ = "America/Chicago"; +const spring = Date.UTC(2026, 2, 8, 8); +const autumn = Date.UTC(2026, 10, 1, 7); +console.log(new Date(spring - 1000).toString(), "|", new Date(spring).toString()); +console.log(new Date(autumn - 1000).toTimeString(), "|", new Date(autumn).toTimeString()); +process.env.TZ = "Europe/Dublin"; +console.log(new Date(Date.UTC(2026, 2, 29, 0, 59, 59)).toString()); +console.log(new Date(Date.UTC(2026, 2, 29, 1)).toString()); +process.env.TZ = "Australia/Sydney"; +console.log(new Date(Date.UTC(2026, 3, 4, 15, 59, 59)).toString()); +console.log(new Date(Date.UTC(2026, 3, 4, 16)).toString()); + +// Extreme and invalid time values. +process.env.TZ = "UTC"; +console.log(maxDate.toString(), "|", minDate.toDateString()); +console.log(yearZero.toString(), "|", at(Date.UTC(-1, 11, 31, 23)).toDateString()); +console.log(at(Date.UTC(-12345, 0, 1)).toDateString(), "|", at(Date.UTC(12345, 0, 1)).toDateString()); +console.log(invalid.toString(), invalid.toDateString(), invalid.toTimeString(), `${invalid}`, String(invalid)); + +// A Date held as unknown converts the same way. +process.env.TZ = "Asia/Kolkata"; +const held: unknown = winter; +console.log(String(held), `${held}`); + +// Values that are not zone IDs and unset TZ. +for (const zone of ["Not/A_Zone", ""]) { + process.env.TZ = zone; + console.log(JSON.stringify(zone), winter.toString()); +} +delete process.env.TZ; +process.env.TZ = "UTC"; +console.log(winter.toString()); + +// Formatting functions and fields. +function stamp(label: string, d: Date): string { + return `${label}: ${d.toDateString()} at ${d.toTimeString().slice(0, 8)}`; +} +const event = { name: "launch", when: summer }; +process.env.TZ = "America/New_York"; +console.log(stamp(event.name, event.when), "|", event.when.toString().length); diff --git a/tests/corpus/5964-date-dynamic-conversions.js b/tests/corpus/5964-date-dynamic-conversions.js new file mode 100644 index 000000000..c2e5aff0d --- /dev/null +++ b/tests/corpus/5964-date-dynamic-conversions.js @@ -0,0 +1,29 @@ +// Dates held in untyped JavaScript values convert like Node's: string +// conversions use toString() (String, templates, +, ==, join), number +// conversions use the time value (Number, BigInt, unary +, -, *, <, >=), +// and toString, toDateString and toTimeString are callable as methods and +// as function values. +process.env.TZ = "America/Chicago"; +const date = new Date(Date.UTC(2026, 9, 18, 14, 12, 30)); +const held = [date, new Date(NaN), new Date(0)]; +const x = /** @type {any} */ (held[0]); +const bad = /** @type {any} */ (held[1]); +const zero = /** @type {any} */ (held[2]); + +console.log(String(x)); +console.log(`${x} / ${bad}`); +console.log(x.toString(), "|", x.toDateString(), "|", x.toTimeString()); +console.log(held.join(" ; ")); +console.log(x + 1, "|", 1 + x, "|", x + ""); +console.log(+x, x - 0, x * 1, Number(x), Number(bad), BigInt(zero)); +console.log(x > 5, x < zero, x >= x, x <= 1792332720000, bad < x, bad >= x); +console.log(x == x.toString(), x == 1792332750000, zero == "0"); + +const toTime = x.toTimeString; +const toDate = x.toDateString; +console.log(toTime.call(x), "|", toDate.call(zero)); + +process.env.TZ = "Asia/Kathmandu"; +console.log(String(x), "|", [x, zero].toString()); +process.env.TZ = "Europe/Dublin"; +console.log(`${zero}`, "|", zero.toTimeString()); diff --git a/tests/diagnostics/date-formatters.ts b/tests/diagnostics/date-formatters.ts index 6a2cb57a7..1ec1cacc6 100644 --- a/tests/diagnostics/date-formatters.ts +++ b/tests/diagnostics/date-formatters.ts @@ -1,5 +1,5 @@ // Unsupported formatters must reach the Date method diagnostic, including // names inherited from Object.prototype by the compiler's dispatch table. const date = new Date(0); -date.toString(); +date.toUTCString(); date.toLocaleString(); diff --git a/tests/harness/__snapshots__/date-formatters.ts.txt b/tests/harness/__snapshots__/date-formatters.ts.txt index 66d98d861..6fbe82dd6 100644 --- a/tests/harness/__snapshots__/date-formatters.ts.txt +++ b/tests/harness/__snapshots__/date-formatters.ts.txt @@ -1,17 +1,17 @@ -date-formatters.ts:4:1 - error SC2020: 'Date.prototype.toString' is part of the standard library types but has no scriptc lowering yet +date-formatters.ts:4:1 - error SC2020: 'Date.prototype.toUTCString' is part of the standard library types but has no scriptc lowering yet 3 | const date = new Date(0); - 4 | date.toString(); - | ^~~~~~~~~~~~~~~ + 4 | date.toUTCString(); + | ^~~~~~~~~~~~~~~~~~ 5 | date.toLocaleString(); - hint: getTime(), valueOf(), toISOString(), toJSON(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale/string formatters have no lowering + hint: getTime(), valueOf(), toISOString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters, toUTCString() and locale formatters have no lowering date-formatters.ts:5:1 - error SC2020: 'Date.prototype.toLocaleString' is part of the standard library types but has no scriptc lowering yet - 4 | date.toString(); + 4 | date.toUTCString(); 5 | date.toLocaleString(); | ^~~~~~~~~~~~~~~~~~~~~ 6 | - hint: getTime(), valueOf(), toISOString(), toJSON(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale/string formatters have no lowering \ No newline at end of file + hint: getTime(), valueOf(), toISOString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters, toUTCString() and locale formatters have no lowering \ No newline at end of file diff --git a/tests/harness/__snapshots__/stdlib-fence.ts.txt b/tests/harness/__snapshots__/stdlib-fence.ts.txt index 337354eec..34e6d5a3c 100644 --- a/tests/harness/__snapshots__/stdlib-fence.ts.txt +++ b/tests/harness/__snapshots__/stdlib-fence.ts.txt @@ -66,7 +66,7 @@ stdlib-fence.ts:25:1 - error SC2020: 'Date.prototype.setUTCFullYear with argumen | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 26 | const sameDate = mutableDate === mutableDate; // scalar storage must not fake object identity - hint: getTime(), valueOf(), toISOString(), toJSON(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale/string formatters have no lowering + hint: getTime(), valueOf(), toISOString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters, toUTCString() and locale formatters have no lowering stdlib-fence.ts:26:18 - error SC1043: comparing non-number, non-string values are not supported yet diff --git a/tests/harness/corpus-costs.json b/tests/harness/corpus-costs.json index 8131c1cd2..9fd80a3fd 100644 --- a/tests/harness/corpus-costs.json +++ b/tests/harness/corpus-costs.json @@ -2190,6 +2190,8 @@ "5960-generic-mapped-types.ts": 2540, "5961-frame-spread-updates.ts": 3260, "5962-frame-tuples.ts": 3208, + "5963-date-to-string.ts": 2355, + "5964-date-dynamic-conversions.js": 2307, "5970-net-connect-host-name.ts": 2400, "5971-buffer-chunks-to-string.ts": 2700, "5972-net-set-encoding-listeners.ts": 2550, @@ -5249,6 +5251,8 @@ "5960-generic-mapped-types.ts": 3300, "5961-frame-spread-updates.ts": 3732, "5962-frame-tuples.ts": 3372, + "5963-date-to-string.ts": 3199, + "5964-date-dynamic-conversions.js": 3084, "5970-net-connect-host-name.ts": 2800, "5971-buffer-chunks-to-string.ts": 3100, "5972-net-set-encoding-listeners.ts": 3000,