Skip to content

Commit ab2068f

Browse files
committed
Fix timezone extraction to work in all locales
Followup to emscripten-core#21585
1 parent ecdca7b commit ab2068f

File tree

9 files changed

+112
-72
lines changed

9 files changed

+112
-72
lines changed

.circleci/config.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,9 @@ jobs:
918918
python: "$EMSDK_PYTHON"
919919
- run-tests:
920920
title: "crossplatform tests"
921-
test_targets: "--crossplatform-only"
921+
# Skipping test_strftime_zZ_gb_locale since this test forces the
922+
# locale to one that is not necessarily available on macOS.
923+
test_targets: "--crossplatform-only skip:other.test_strftime_zZ_gb_locale"
922924
- upload-test-results
923925

924926
test-mac-arm64:
@@ -940,7 +942,9 @@ jobs:
940942
# are currently missing arm64 macos binaries.
941943
- run-tests:
942944
title: "crossplatform tests"
943-
test_targets: "--crossplatform-only"
945+
# Skipping test_strftime_zZ_gb_locale since this test forces the
946+
# locale to one that is not necessarily available on macOS.
947+
test_targets: "--crossplatform-only skip:other.test_strftime_zZ_gb_locale"
944948
- upload-test-results
945949

946950
workflows:

src/generated_struct_info32.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@
402402
"TIOCGWINSZ": 21523,
403403
"TIOCSPGRP": 21520,
404404
"TIOCSWINSZ": 21524,
405-
"TZNAME_MAX": 6,
405+
"TZNAME_MAX": 16,
406406
"UUID_TYPE_DCE_RANDOM": 4,
407407
"UUID_VARIANT_DCE": 1,
408408
"W_OK": 2,

src/generated_struct_info64.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@
402402
"TIOCGWINSZ": 21523,
403403
"TIOCSPGRP": 21520,
404404
"TIOCSWINSZ": 21524,
405-
"TZNAME_MAX": 6,
405+
"TZNAME_MAX": 16,
406406
"UUID_TYPE_DCE_RANDOM": 4,
407407
"UUID_VARIANT_DCE": 1,
408408
"W_OK": 2,

src/library.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,10 +649,12 @@ addToLibrary({
649649

650650
{{{ makeSetValue('daylight', '0', 'Number(winterOffset != summerOffset)', 'i32') }}};
651651

652-
var extractZone = (date) => date.toLocaleTimeString(undefined, {timeZoneName:'short'}).split(' ')[2];
652+
var extractZone = (date) => date.toLocaleTimeString(undefined, {hour12:false, timeZoneName:'short'}).split(' ')[1];
653653
var winterName = extractZone(winter);
654654
var summerName = extractZone(summer);
655655
#if ASSERTIONS
656+
assert(winterName);
657+
assert(summerName);
656658
assert(lengthBytesUTF8(winterName) <= {{{ cDefs.TZNAME_MAX }}}, `timezone name truncated to fit in TZNAME_MAX (${winterName})`);
657659
assert(lengthBytesUTF8(summerName) <= {{{ cDefs.TZNAME_MAX }}}, `timezone name truncated to fit in TZNAME_MAX (${summerName})`);
658660
#endif

system/lib/libc/musl/include/limits.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,14 @@
5151
#define SYMLOOP_MAX 40
5252
#define WORD_BIT 32
5353
#define SSIZE_MAX LONG_MAX
54+
#ifdef __EMSCRIPTEN__
55+
// We depend on the JS API to reteive the local name for the current
56+
// timezone and this can sometimes exceed 6 chats. For example:
57+
// TZ='Asia/Kathmandu' yields 'GMT+5:45'.
58+
#define TZNAME_MAX 16
59+
#else
5460
#define TZNAME_MAX 6
61+
#endif
5562
#define TTY_NAME_MAX 32
5663
#define HOST_NAME_MAX 255
5764

test/common.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,29 @@ def modified(self, *args, **kwargs):
365365
return decorated
366366

367367

368+
# Decorator version of env_modify
369+
def also_with_env_modify(name_updates_mapping):
370+
371+
def decorated(f):
372+
@wraps(f)
373+
def metafunc(self, updates, *args, **kwargs):
374+
if updates:
375+
with env_modify(updates):
376+
return f(self, *args, **kwargs)
377+
else:
378+
return f(self, *args, **kwargs)
379+
380+
parameterize = {'': (None,)}
381+
for name, updates in name_updates_mapping.items():
382+
parameterize[name] = (updates,)
383+
384+
metafunc._parameterize = parameterize
385+
386+
return metafunc
387+
388+
return decorated
389+
390+
368391
def also_with_minimal_runtime(f):
369392
assert callable(f)
370393

test/other/test_strftime_zZ.c

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#include <errno.h>
2+
#include <stdio.h>
3+
#include <string.h>
4+
#include <time.h>
5+
6+
int main() {
7+
void tzset(void);
8+
9+
// Buffer to hold the current hour of the day. Format is HH + nul
10+
// character.
11+
char hour[3];
12+
13+
// Buffer to hold our ISO 8601 formatted UTC offset for the current
14+
// timezone. Format is [+-]hhmm + nul character.
15+
char utcOffset[6];
16+
17+
// Buffer to hold the timezone name or abbreviation. Just make it
18+
// sufficiently large to hold most timezone names.
19+
char timezone[128];
20+
21+
struct tm tm;
22+
23+
// Get the current timestamp.
24+
const time_t now = time(NULL);
25+
26+
// What time is that here?
27+
if (localtime_r(&now, &tm) == NULL) {
28+
const int error = errno;
29+
printf("Failed to get localtime for timestamp=%lld; errno=%d; %s", now, errno, strerror(error));
30+
return 1;
31+
}
32+
33+
size_t result = 0;
34+
35+
// Get the formatted hour of the day.
36+
if ((result = strftime(hour, 3, "%H", &tm)) != 2) {
37+
const int error = errno;
38+
printf("Failed to format hour for timestamp=%lld; result=%zu; errno=%d; %s\n",
39+
now, result, error, strerror(error));
40+
return 1;
41+
}
42+
printf("The current hour of the day is: %s\n", hour);
43+
44+
// Get the formatted UTC offset in ISO 8601 format.
45+
if ((result = strftime(utcOffset, 6, "%z", &tm)) != 5) {
46+
const int error = errno;
47+
printf("Failed to format UTC offset for timestamp=%lld; result=%zu; errno=%d; %s\n",
48+
now, result, error, strerror(error));
49+
return 1;
50+
}
51+
printf("The current timezone offset is: %s\n", utcOffset);
52+
53+
// Get the formatted timezone name or abbreviation. We don't know how long
54+
// this will be, so just expect some data to be written to the buffer.
55+
if ((result = strftime(timezone, 128, "%Z", &tm)) == 0) {
56+
const int error = errno;
57+
printf("Failed to format timezone for timestamp=%lld; result=%zu; errno=%d; %s\n",
58+
now, result, error, strerror(error));
59+
return 1;
60+
}
61+
printf("The current timezone is: %s\n", timezone);
62+
63+
printf("ok!\n");
64+
return 0;
65+
}

test/runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ def prepend_default(arg):
466466
all_tests = get_all_tests(modules)
467467
if options.crossplatform_only:
468468
tests = get_crossplatform_tests(modules)
469+
skip_requested_tests(options.tests, modules)
469470
else:
470471
tests = tests_with_expanded_wildcards(tests, all_tests)
471472
tests = skip_requested_tests(tests, modules)

test/test_other.py

Lines changed: 5 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
from common import env_modify, no_mac, no_windows, only_windows, requires_native_clang, with_env_modify
3535
from common import create_file, parameterized, NON_ZERO, node_pthreads, TEST_ROOT, test_file
3636
from common import compiler_for, EMBUILDER, requires_v8, requires_node, requires_wasm64, requires_node_canary
37-
from common import requires_wasm_eh, crossplatform, with_both_eh_sjlj, with_both_sjlj, also_with_standalone_wasm
37+
from common import requires_wasm_eh, crossplatform, with_both_eh_sjlj, with_both_sjlj
38+
from common import also_with_standalone_wasm, also_with_env_modify
3839
from common import also_with_minimal_runtime, also_with_wasm_bigint, also_with_wasm64, flaky
3940
from common import EMTEST_BUILD_VERBOSE, PYTHON, WEBIDL_BINDER
4041
from common import requires_network
@@ -5717,73 +5718,10 @@ def test_only_force_stdlibs_2(self):
57175718
self.run_process([EMXX, 'src.cpp', '-sDISABLE_EXCEPTION_CATCHING=0'])
57185719
self.assertContained('Caught exception: std::exception', self.run_js('a.out.js'))
57195720

5721+
@crossplatform
5722+
@also_with_env_modify({'gb_locale': {'LC_ALL': 'en_GB'}, 'long_tz': {'TZ': 'Asia/Kathmandu'}})
57205723
def test_strftime_zZ(self):
5721-
create_file('src.c', r'''
5722-
#include <errno.h>
5723-
#include <stdio.h>
5724-
#include <string.h>
5725-
#include <time.h>
5726-
5727-
int main() {
5728-
// Buffer to hold the current hour of the day. Format is HH + nul
5729-
// character.
5730-
char hour[3];
5731-
5732-
// Buffer to hold our ISO 8601 formatted UTC offset for the current
5733-
// timezone. Format is [+-]hhmm + nul character.
5734-
char utcOffset[6];
5735-
5736-
// Buffer to hold the timezone name or abbreviation. Just make it
5737-
// sufficiently large to hold most timezone names.
5738-
char timezone[128];
5739-
5740-
struct tm tm;
5741-
5742-
// Get the current timestamp.
5743-
const time_t now = time(NULL);
5744-
5745-
// What time is that here?
5746-
if (localtime_r(&now, &tm) == NULL) {
5747-
const int error = errno;
5748-
printf("Failed to get localtime for timestamp=%lld; errno=%d; %s", now, errno, strerror(error));
5749-
return 1;
5750-
}
5751-
5752-
size_t result = 0;
5753-
5754-
// Get the formatted hour of the day.
5755-
if ((result = strftime(hour, 3, "%H", &tm)) != 2) {
5756-
const int error = errno;
5757-
printf("Failed to format hour for timestamp=%lld; result=%zu; errno=%d; %s\n",
5758-
now, result, error, strerror(error));
5759-
return 1;
5760-
}
5761-
printf("The current hour of the day is: %s\n", hour);
5762-
5763-
// Get the formatted UTC offset in ISO 8601 format.
5764-
if ((result = strftime(utcOffset, 6, "%z", &tm)) != 5) {
5765-
const int error = errno;
5766-
printf("Failed to format UTC offset for timestamp=%lld; result=%zu; errno=%d; %s\n",
5767-
now, result, error, strerror(error));
5768-
return 1;
5769-
}
5770-
printf("The current timezone offset is: %s\n", utcOffset);
5771-
5772-
// Get the formatted timezone name or abbreviation. We don't know how long
5773-
// this will be, so just expect some data to be written to the buffer.
5774-
if ((result = strftime(timezone, 128, "%Z", &tm)) == 0) {
5775-
const int error = errno;
5776-
printf("Failed to format timezone for timestamp=%lld; result=%zu; errno=%d; %s\n",
5777-
now, result, error, strerror(error));
5778-
return 1;
5779-
}
5780-
printf("The current timezone is: %s\n", timezone);
5781-
5782-
printf("ok!\n");
5783-
return 0;
5784-
}
5785-
''')
5786-
self.do_runf('src.c', 'ok!')
5724+
self.do_runf('other/test_strftime_zZ.c', 'ok!')
57875725

57885726
def test_strptime_symmetry(self):
57895727
self.do_runf('strptime_symmetry.cpp', 'TEST PASSED')

0 commit comments

Comments
 (0)