Skip to content

Temporal does not compile with shared ICU or no ICU #62676

Description

@richardlau

When Node.js is configured with --with-intl=none (or --without-intl) or --with-intl=system-icu the build fails when Temporal is enabled.

e.g.

20:51:03   ccache clang++-19 -o /home/iojs/build/workspace/node-test-commit-linux-containered/out/Release/obj.target/v8_base_without_compiler/deps/v8/src/objects/js-temporal-zoneinfo64.o ../deps/v8/src/objects/js-temporal-zoneinfo64.cc '-D_GLIBCXX_USE_CXX11_ABI=1' '-D_FILE_OFFSET_BITS=64' '-DNODE_OPENSSL_CONF_NAME=nodejs_conf' '-DICU_NO_USER_DATA_OVERRIDE' '-DV8_GYP_BUILD' '-DV8_TYPED_ARRAY_MAX_SIZE_IN_HEAP=64' '-DBUILDING_V8_SHARED' '-DBUILDING_V8_PLATFORM_SHARED' '-D__STDC_FORMAT_MACROS' '-DOPENSSL_NO_PINSHARED' '-DOPENSSL_THREADS' '-DV8_TARGET_ARCH_X64' '-DV8_HAVE_TARGET_OS' '-DV8_TARGET_OS_LINUX' '-DV8_EMBEDDER_STRING="-node.17"' '-DENABLE_DISASSEMBLER' '-DV8_PROMISE_INTERNAL_FIELD_COUNT=1' '-DENABLE_GDB_JIT_INTERFACE' '-DV8_ENABLE_PRIVATE_MAPPING_FORK_OPTIMIZATION' '-DV8_SHORT_BUILTIN_CALLS' '-DOBJECT_PRINT' '-DV8_INTL_SUPPORT' '-DV8_TEMPORAL_SUPPORT' '-DV8_ATOMIC_OBJECT_FIELD_WRITES' '-DV8_ENABLE_LAZY_SOURCE_POSITIONS' '-DV8_USE_SIPHASH' '-DV8_ENABLE_SEEDED_ARRAY_INDEX_HASH' '-DNDEBUG' '-DV8_WIN64_UNWINDING_INFO' '-DV8_ENABLE_REGEXP_INTERPRETER_THREADED_DISPATCH' '-DV8_USE_ZLIB' '-DV8_ENABLE_LEAPTIERING' '-DV8_ENABLE_SPARKPLUG' '-DV8_ENABLE_MAGLEV' '-DV8_ENABLE_TURBOFAN' '-DV8_ENABLE_WEBASSEMBLY' '-DV8_ENABLE_JAVASCRIPT_PROMISE_HOOKS' '-DV8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA' '-DV8_ALLOCATION_FOLDING' '-DV8_ALLOCATION_SITE_TRACKING' '-DV8_ADVANCED_BIGINT_ALGORITHMS' '-DV8_ENABLE_WASM_SIMD256_REVEC' '-DICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_STATIC' -I../deps/v8 -I../deps/v8/include -I/opt/icu-73.2/include -I/home/iojs/build/workspace/node-test-commit-linux-containered/out/Release/obj/gen/inspector-generated-output-root -I../deps/v8/third_party/inspector_protocol -I/home/iojs/build/workspace/node-test-commit-linux-containered/out/Release/obj/gen -I/home/iojs/build/workspace/node-test-commit-linux-containered/out/Release/obj/gen/inspector-generated-output-root/include -I/home/iojs/build/workspace/node-test-commit-linux-containered/out/Release/obj/gen/generate-bytecode-output-root -I../deps/v8/third_party/zlib -I../deps/v8/third_party/zlib/google -I../deps/v8/third_party/fp16/src/include -I../deps/v8/third_party/highway/src -I../deps/v8/third_party/simdutf -I../deps/v8/third_party/abseil-cpp -I../deps/crates/vendor/temporal_capi/bindings/cpp  -fvisibility=hidden -fvisibility-inlines-hidden -pthread -Wno-unused-parameter -m64 -O3 -fno-omit-frame-pointer -fdata-sections -ffunction-sections -O3 -fno-rtti -fno-exceptions -fno-strict-aliasing -std=gnu++20 -Wno-invalid-offsetof -Wno-nullability-completeness -MMD -MF /home/iojs/build/workspace/node-test-commit-linux-containered/out/Release/.deps//home/iojs/build/workspace/node-test-commit-linux-containered/out/Release/obj.target/v8_base_without_compiler/deps/v8/src/objects/js-temporal-zoneinfo64.o.d.raw   -c
20:51:05 ../deps/v8/src/objects/js-temporal-zoneinfo64.cc:14:10: fatal error: 'udatamem.h' file not found
20:51:05    14 | #include "udatamem.h"
20:51:05       |          ^~~~~~~~~~~~
20:51:05 1 error generated.
20:51:05 make[2]: *** [tools/v8_gypfiles/v8_base_without_compiler.target.mk:1138: /home/iojs/build/workspace/node-test-commit-linux-containered/out/Release/obj.target/v8_base_without_compiler/deps/v8/src/objects/js-temporal-zoneinfo64.o] Error 1
23:19:59 /usr/bin/ld: /home/iojs/build/workspace/node-test-commit-linux-containered/out/Release/obj.target/v8_base_without_compiler/deps/v8/src/objects/js-temporal-zoneinfo64.o: in function `v8::internal::ZoneInfo64Provider::ZoneInfo64Provider()':
23:19:59 js-temporal-zoneinfo64.cc:(.text._ZN2v88internal18ZoneInfo64ProviderC2Ev+0x13): undefined reference to `zoneinfo64_static_data_len'
23:19:59 /usr/bin/ld: js-temporal-zoneinfo64.cc:(.text._ZN2v88internal18ZoneInfo64ProviderC2Ev+0x1d): undefined reference to `zoneinfo64_static_data'
23:20:00 clang++-19: error: linker command failed with exit code 1 (use -v to see invocation)
23:20:00 make[2]: *** [tools/v8_gypfiles/mksnapshot.target.mk:222: /home/iojs/build/workspace/node-test-commit-linux-containered/out/Release/mksnapshot] Error 1
23:20:00 rm a50936564b94c8a1c04b713ccd861b094c973ce83a2131b86111777ecadc0a82.intermediate 

The first issue (with --with-intl=system-icu) stems from V8 using a private header file from ICU. In #62508, this is being worked around by temporarily patching V8 with https://github.com/nodejs/node/pull/62508/files#diff-89a9a4423f9b83142d44bfb376f2c56ffff396ee1ef98c4930a61d5ed9732c7f. Unfortunately applying that in #61806 breaks parallel/test-temporal-with-zoneinfo in the default (no --with-intl=... specified) case.

e.g. https://github.com/nodejs/node/actions/runs/24165429332/job/70525857573?pr=61806#step:9:6040

=== release test-temporal-with-zoneinfo ===
Path: parallel/test-temporal-with-zoneinfo
Error: --- stderr ---
/home/runner/work/node/node/node/test/parallel/test-temporal-with-zoneinfo.js:19
assert.strictEqual(pdt.toString(), '1969-07-20T20:17:00Z');
                       ^

Error: Temporal error: Internal error: Failed to load timezone info.
    at Instant.toString (<anonymous>)
    at Object.<anonymous> (/home/runner/work/node/node/node/test/parallel/test-temporal-with-zoneinfo.js:19:24)
    at Module._compile (node:internal/modules/cjs/loader:1829:14)
    at Object..js (node:internal/modules/cjs/loader:1969:10)
    at Module.load (node:internal/modules/cjs/loader:1552:32)
    at Module._load (node:internal/modules/cjs/loader:1354:12)
    at wrapModuleLoad (node:internal/modules/cjs/loader:255:19)
    at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5)
    at node:internal/main/run_main_module:33:47

Node.js v26.0.0-pre
Command: out/Release/node --harmony-temporal /home/runner/work/node/node/node/test/parallel/test-temporal-with-zoneinfo.js

===
=== 1 tests failed
===

Failed tests:
out/Release/node --harmony-temporal /home/runner/work/node/node/node/test/parallel/test-temporal-with-zoneinfo.js

Also ideally we don't want to be carrying patches on top of V8 as much as possible, so would prefer something patched upstream in V8 if possible.

Refs: #61806 (comment)

Activity

  1. added
    v8 engineIssues and PRs related to the V8 dependency.
    on Apr 10, 2026
  2. jasnell commented on May 7, 2026

    @jasnell
    Member

    Has this issue been resolved? Does it need to remain open?

  3. aduh95 commented on May 7, 2026

    @aduh95
    Contributor

    It has not been resolved, no action have been taken

  4. Leask commented on May 9, 2026

    @Leask

    I opened a V8-side CL intended to fix this at the source instead of carrying a Node-specific V8 patch:

    https://chromium-review.googlesource.com/c/v8/v8/+/7831715

    Design:

    • stop using ICU's private udatamem.h / UDataMemory::length path when Temporal + i18n is enabled;
    • always generate and link the existing zoneinfo64_static_data blob when Temporal is enabled;
    • make the i18n and non-i18n Temporal timezone provider use the same baked zoneinfo64.res data path.

    Expected effect:

    • --with-intl=system-icu no longer depends on private ICU headers from the system ICU package;
    • --without-intl / no-ICU Temporal builds still get the generated zoneinfo64 data linked in;
    • embedders using system/shared ICU do not need ICU private headers or private ABI details just to load Temporal timezone data.

    This is upstream V8 work, so Node still needs to wait for V8 review/landing and then consume it via a V8 roll/backport, including any generated build file updates needed on the Node side. Until then, downstreams may still need temporary packaging workarounds. The intent is to replace the earlier __has_include style workaround with a cleaner design that removes the private ICU dependency entirely.

  5. Leask commented on May 10, 2026

    @Leask

    Update after V8 review feedback on the upstream CL:

    https://chromium-review.googlesource.com/c/v8/v8/+/7831715

    V8's position is that using ICU4C data for zoneinfo64.res is deliberate, because bundled ICU already ships that timezone data and V8 does not want to duplicate it in normal i18n builds.

    Given that, I think the downstream fix should not be to silently disable Temporal for packagers. If the current V8 Temporal implementation requires the bundled/full ICU source path, Node's build should make that requirement clear and downstreams should pin to the supported upstream ICU source/version at build time.

    Concretely, for distributions that want to ship Node 26 with Temporal enabled, the viable packaging route is:

    ./configure \
      --with-intl=full-icu \
      --with-icu-source=/path/to/node-pinned-icu-source

    That keeps the build aligned with the ICU version expected by Node/V8, avoids the unsupported system/shared ICU Temporal path, and produces a complete Node package instead of a build where a headline Node 26 feature is trimmed out.

    It would still be useful for Node's configure behavior to make this sharper: if --with-intl=system-icu or --without-intl cannot support Temporal with the current V8 dependency, the build should clearly direct packagers to the pinned full-ICU source route rather than just producing a Node binary with Temporal disabled by default.

  6. debohman commented on May 10, 2026

    @debohman

    Building V8 should not require outside private headers to build, period. This is a breaking of the practice of public vs. private APIs. You should be able to build node using existing system libraries through public APIs.

  7. Leask commented on May 10, 2026

    @Leask

    I agree with this. Requiring external/system ICU consumers to include udatamem.h crosses the public/private API boundary.

    After looking at ICU's data APIs, I think the deeper gap may be in ICU's public API surface:

    • public udata_open() can open the ICU data item;
    • public udata_getMemory() returns the actual data payload;
    • public udata_getInfo() returns metadata from the data header;
    • but there does not appear to be a public API that returns a stable (raw data item including the ICU data header, exact length) view.

    That is exactly the shape V8/temporal_rs currently wants for zoneinfo64.res: not interpreted ICU resource-bundle access, but the raw .res binary item including the header, with a usable length.

    ICU already has internal APIs close to this (udata_getRawMemory() and udata_getLength()), and the ICU source even notes that udata_getLength() could be considered for public exposure, but would need stronger semantics for exact lengths in all cases:

    https://github.com/unicode-org/icu/blob/main/icu4c/source/common/udatamem.cpp

    So I think the path that would satisfy all sides is probably:

    1. V8 should not duplicate timezone data when bundled/full ICU already ships zoneinfo64.res.
    2. Node/system-ICU builds should not be forced to use ICU private headers.
    3. ICU should expose a public, stable data-item export/view API that lets embedders get a raw ICU data item plus length when they intentionally need to pass that binary data to another engine such as temporal_rs.

    Until such an ICU API exists, downstream packagers still need a practical build path. For Node 26, that likely means using the supported full-ICU source route for Temporal-enabled packages rather than silently disabling Temporal. But long term, a standardized ICU data export API would be cleaner than either duplicating timezone data in V8 or requiring private ICU headers.

  8. debohman commented on May 10, 2026

    @debohman
  9. Leask commented on May 11, 2026

    @Leask

    Follow-up with the ICU upstream tracking link:

    My current read of the long-term direction is:

    1. V8/Chromium should be able to keep the current no-duplicate-data design when ICU already ships zoneinfo64.res.
    2. Node and other external/system-ICU embedders should not need ICU private headers such as udatamem.h to build a public Node release.
    3. ICU should expose a stable public data-item view/export API that can return the raw ICU data item plus exact length for cases like temporal_rs that intentionally need the raw .res bytes.

    Until that ICU API exists, the practical V8-side short-term fix seems to be an explicit V8 build option for the Temporal zoneinfo64 provider: default to the ICU-backed path for Chromium/bundled ICU builds, and let embedders that cannot use private ICU headers disable that path and use baked zoneinfo64 data instead. That is better than silently dropping Temporal from Node builds, and cleaner than relying on __has_include to guess packaging policy.

  10. debohman commented on May 11, 2026

    @debohman

    Is there any chance that you could fix this in a minor release of node 26? That is, assuming that you can get ICU and V8 to fix it.

    My concern is that node 26 is slated to become an LTS release.

  11. github-actions commented on Aug 9, 2026

    @github-actions
    Contributor

    This issue has been marked as stale due to 90 days of inactivity.
    It will be automatically closed in 30 days if no further activity occurs. If this is still relevant, please leave a comment or update it to keep it open.

  12. added
    staleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.
    on Aug 9, 2026
  13. 34 remaining items

  14. added a commit that references this issue on Oct 4, 2026
  15. added a commit that references this issue on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

i18n-apiIssues and PRs related to Node.js internationalization support.icuIssues and PRs related to the ICU dependency.never-staleIssues and PRs exempt from automated stale handling.v8 engineIssues and PRs related to the V8 dependency.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions