Giter Club home page Giter Club logo

temporal-polyfill's Introduction

Temporal Polyfill

This polyfill was kicked off by some of the champions of the Temporal proposal. The goal is to be ready for production use when the Temporal proposal reaches Stage 4, although like with all OSS work progress is dependent on contributors. We're eagerly welcoming to contributors who want to help build and maintain this polyfill. PRs are always welcome!

Note that this polyfill is not affiliated with TC39. Links to other polyfills can be found here.

This polyfill is compatible with Node.js 14 or later.

Roadmap

  • Fork non-production polyfill from tc39/proposal-temporal repo
  • Release initial pre-alpha to NPM at @js-temporal/polyfill
  • Sync the code in this repo with the handful of polyfill changes that have recently been made in the tc39/proposal-temporal repo
  • Release alpha version to NPM
  • Deprecate all other earlier Temporal polyfills
  • Optimize slow operations by reducing calls to Intl.DateTimeFormat constructor (see #7, #8, #10, #12)
  • Convert to TypeScript for better maintainability
  • Improve typing of sources for better maintainability
  • Migrate to JSBI for improved compile-time safety around BigInt operations.
  • Optimize performance of other slow operations
  • Release production version to NPM

Bug Reports and Feedback

If you think you've found a bug in the Temporal API itself (not the implementation in this polyfill), please file an issue in the tc39/proposal-temporal issue tracker issue tracker.

If you've found a bug in this polyfill—meaning that the implementation here doesn't match the Temporal spec—please file an issue in this repo's issue tracker.

Documentation

Reference documentation and examples for the Temporal API can be found here.

A cookbook to help you get started and learn the ins and outs of Temporal is available here

If you find a bug in the documentation, please file a bug over in the tc39/proposal-temporal issue tracker issue tracker.

Note that the Temporal documentation is in the process of being migrated to MDN. You can track the progress of the MDN migration here.

Usage

To install:

$ npm install @js-temporal/polyfill

CJS Usage:

const { Temporal, Intl, toTemporalInstant } = require('@js-temporal/polyfill');
Date.prototype.toTemporalInstant = toTemporalInstant;

Import the polyfill as an ES6 module:

import { Temporal, Intl, toTemporalInstant } from '@js-temporal/polyfill';
Date.prototype.toTemporalInstant = toTemporalInstant;

Note that this polyfill currently does not install a global Temporal object like a real implementation will. This behavior avoids hiding the global Temporal object in environments where a real Temporal implementation is present. See this issue for more background on this decision. Once JS engines start shipping with Temporal, we may decide to change this behavior to match built-in behavior more closely. See #2 to provide feedback or track this issue.

This polyfill ships ES2020 code for both CJS and ESM bundles - if your environment does not support ES2020, then please make sure to transpile the content of this package along with the rest of your code.

This polyfill uses JSBI to enable backwards-compatibility for environments that don't support native bigints. If your project only ever runs in environments that do support native bigints (see caniuse data), we highly recommend configuring the JSBI Babel plugin that translates JSBI calls to their native bigint equivalent, improving code-size and performance. We are exploring ways to provide separate builds for these use-cases in #155.

Contributing / Help Wanted

We're eagerly welcoming to contributors who want to help build and maintain this polyfill. PRs are always welcome!

temporal-polyfill's People

Contributors

12wrigja avatar aditi-1400 avatar akshgpt7 avatar bananer avatar cjtenny avatar fer22f avatar gibson042 avatar gilmoreorless avatar guijemont avatar jessealama avatar jsoref avatar justingrant avatar katemihalikova avatar liviamedeiros avatar maggiepint avatar midnightdesign avatar ms2ger avatar notpeter avatar notwoods avatar pipobscure avatar ptomato avatar qmma70 avatar redneb avatar romulocintra avatar rraziel avatar ryzokuken avatar sffc avatar shrujalshah28 avatar sleonia avatar tars0x9752 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

temporal-polyfill's Issues

Error trying to import ESM Temporal polyfill in a CJS environment

This polyfill is marked as ESM in package..json via type: 'module', but this is causing an error at runtime in my code which runs in an AWS Lambda function. According to this Stack Overflow answer, AWS Lambda's Node 14 runtime does not support ESM at the moment (or at least didn't as of a few months ago).

It's possible (I haven't tested) that the workaround is what's described in dherault/serverless-offline#1014 (comment) might help:

It's possible to distribute both using exports. Old versions of Node will ignore it and fall back to main while newer versions will load the ES module if the caller supports it.

{
  "type": "module",
  "main": "dist/index.cjs",
  "exports": {
    "import": "./src/index.js",
    "require": "./dist/index.cjs"
  },
  "module": "index.js"
}

Below is the error I'm seeing at runtime when my code is run locally using the serverless-offline package, which is a harness for running AWS Lambda functions locally on my dev machine. This harness doesn't apparently support ESM either, per dherault/serverless-offline#1014.

If any experts in Node's ESM support, Webpack 5, and/or AWS Lambda have any suggestions, I'm all ears!

/usr/local/bin/node --lazy ./../../../../../../usr/local/lib/node_modules/serverless/bin/serverless offline start --noTimeout --dontPrintOutput --httpsProtocol .serverlessoffline

Error: Must use import to load ES Module: /Users/justingrant/Documents/hdev/h3/api/node_modules/@js-temporal/polyfill/dist/index.js
require() of ES modules is not supported.
require() of /Users/justingrant/Documents/hdev/h3/api/node_modules/@js-temporal/polyfill/dist/index.js from /Users/justingrant/Documents/hdev/h3/api/.webpack/service/src/getCurrentUser.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /Users/justingrant/Documents/hdev/h3/api/node_modules/@js-temporal/polyfill/package.json.

    at new NodeError (node:internal/errors:370:5)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1112:13)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:816:12)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:93:18)
    at Object.@js-temporal/polyfill (/Users/justingrant/Documents/hdev/h3/api/.webpack/service/src/webpack:/h3-api/external "@js-temporal/polyfill":1:1)
    at __webpack_require__ (/Users/justingrant/Documents/hdev/h3/api/.webpack/service/src/webpack:/h3-api/webpack/bootstrap:19:1)
    at Object.../web/src/Serializer.ts (/Users/justingrant/Documents/hdev/h3/api/.webpack/service/src/getCurrentUser.js:1226:79)
    at __webpack_require__ (/Users/justingrant/Documents/hdev/h3/api/.webpack/service/src/webpack:/h3-api/webpack/bootstrap:19:1)
    at Object../src/libs/lambdaHandler.ts (/Users/justingrant/Documents/hdev/h3/api/.webpack/service/src/getCurrentUser.js:55:77)
    at __webpack_require__ (/Users/justingrant/Documents/hdev/h3/api/.webpack/service/src/webpack:/h3-api/webpack/bootstrap:19:1)
    at /Users/justingrant/Documents/hdev/h3/api/.webpack/service/src/getCurrentUser.js:2204:77
    at /Users/justingrant/Documents/hdev/h3/api/.webpack/service/src/getCurrentUser.js:2233:3
    at Object.<anonymous> (/Users/justingrant/Documents/hdev/h3/api/.webpack/service/src/getCurrentUser.js:2238:12)
    at Module._compile (node:internal/modules/cjs/loader:1095:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1124:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:816:12)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:93:18)
    at /Users/justingrant/Documents/hdev/h3/api/node_modules/serverless-offline/dist/lambda/handler-runner/in-process-runner/InProcessRunner.js:157:133
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at InProcessRunner.run (/Users/justingrant/Documents/hdev/h3/api/node_modules/serverless-offline/dist/lambda/handler-runner/in-process-runner/InProcessRunner.js:157:9)

UMD bundle is not correctly transpiled to ES5.

Similar to GoogleChromeLabs/jsbi#79 as the rollup config is similar.

Turns out that the Babel plugin isn't transpiling anything down to ES5 code when the input is TypeScript code. This should be fixed before our next release, as this will be a regression in build output since the last (0.2.0 release).

Chaining `subtract` on `PlainYearMonth` leads to a lock

I am not sure what the cause of this issue is but look at the following:

const month = Temporal.PlainYearMonth.from(Temporal.Now.plainDate('gregory', 'Europe/Stockholm')) // Temporal.PlainYearMonth <2021-09-01[u-ca=gregory]>
month.subtract({months: 5}) // Temporal.PlainYearMonth <2021-04-01[u-ca=gregory]>
month.subtract({months: 1}).subtract({months: 1}).subtract({months: 1}) // Temporal.PlainYearMonth <2021-07-01[u-ca=gregory]>, but expected 2021-06-01 instead
month.subtract({months: 1}).subtract({months: 1}).subtract({months: 5}) // still 2021-07-01, at this point it is "locked" and subtracting will always keep the month as 07

It seems like for some reason the chain leads to some state where it is not possible to subtract months anymore,.

Reducing memory footprint by reusing Intl.DateTimeFormat instances

Hello. I was going to post this on the original proposal-temporal repo, but since the goal there wasn't about making a polyfill for production use, I decided not to. However, since this polyfill has that goal, it seems like a relevant issue.

When using the polyfill, I ran into a deno memory issue (which I reported in denoland/deno#10721). The root problem is that in the polyfill, there are a lot of new Intl.DateTimeFormat calls, objects which V8 apparently doesn't clean up as well as it could. After some investigation, I found out node is affected too, albeit some orders of magnitude less.

The issue can be demonstrated in Temporal as follows:

import { Temporal } from '@js-temporal/polyfill';
const { ZonedDateTime } = Temporal;

setInterval(() => {
  var a;
  for (let i = 0; i < 100; i++) {
    a = ZonedDateTime.from("2021-01-01T00:00:00-03:00[America/Sao_Paulo]");
    a = Temporal.now.zonedDateTimeISO().toString();
  }
}, 1);

Which ranks at ~567M resident memory in node, and ~1033M in (newer) deno.

My solution to this problem originally, was to fork the repo for my personal use and implement a cache for these objects. A Map caches IntlDateTimeFormats at every site they are required, as such:

// ecmascript.mjs
const IntlDateTimeFormatEnUsCache = new Map();

// ...
  GetCanonicalTimeZoneIdentifier: (timeZoneIdentifier) => {
    // ...
    if (!IntlDateTimeFormatEnUsCache.has(String(timeZoneIdentifier))) {
      IntlDateTimeFormatEnUsCache.set(String(timeZoneIdentifier), new IntlDateTimeFormat('en-us', {
        timeZone: String(timeZoneIdentifier), hour12: false, era: 'short',
        year: 'numeric', month: 'numeric', day: 'numeric',
        hour: 'numeric', minute: 'numeric', second: 'numeric'
      }));
    }
    const formatter = IntlDateTimeFormatEnUsCache.get(String(timeZoneIdentifier));

Node's memory footprint in that same code above went to ~100K after this change. Deno scored at ~239M (which is about the floor memory usage of deno currently).

This is an issue that V8 could fix, but it can also be done at the library level.

Time Ambiguity issue?

So from my understanding the way Temporal will handle ambiguous times due to DST is if for example

{ timeZone: 'Europe/London', year: 2022, month: 3, day: 27, hour: 1, minute: 30 };

Was used this would under the current give either:

2022-03-27T02:30:00+01:00[Europe/London] OR 2022-03-27T00:30:00+00:00[Europe/London] dependent on disambiguation however. My requirement is to return is 2022-03-27T00:59:99.999+00:00 (the last possible time before 01:30) as the requirement is that a job is done BEFORE 01:30 clock time and naturally 02:30 is naturally after 01:30 and 00:30 is 30 minutes before the jump, therefore, moving the latest time forward by 30 minutes. is this possible under the current framework?

Unable to run test262 test suite locally

This could be some weird issue with my local setup, or some issue with Git submodules, but I can no longer run the test262 tests locally.

Steps followed:

cd ~/Documents && git clone [email protected]:js-temporal/temporal-polyfill.git temporal-polyfill-tmp
cd temporal-polyfill-tmp
npm install
npm run test262

yields

jameswr:~/Documents/temporal-polyfill-tmp$ npm run test262

> @js-temporal/[email protected] test262
> TEST262=1 npm run build && ./test/test262.sh


> @js-temporal/[email protected] build
> rm -rf dist/* && rollup -c rollup.config.js


lib/init.ts → dist/script.js...
(!) Circular dependency
lib/ecmascript.ts -> lib/calendar.ts -> lib/ecmascript.ts
created dist/script.js in 4.2s
./test/test262.sh: line 14: cd: ./test/../test262/test/: No such file or directory
jameswr:~/Documents/temporal-polyfill-tmp$ 

@ptomato any ideas why this might happen? The tests seem to work fine on CI, and as far as I can tell that runs basically all the same commands as I did.

Release to NPM before TypeScript migration

Before #21 lands, should we cut a release to NPM? There's been a bunch of PRs recently and I suspect after the TS migration there will be a few follow-ups and tweaks needed, so might be good to have the new stuff available for testing before pausing releases to stabilize on TS.

Release to npm

We've landed a lot of PRs since the last npm release, so it's a good time to release again soon.

I think we should probably release before the JSBI changes are landed.

We should also sync with the latest polyfill-temporal changes before releasing.

Error on importing from browser console

Version: 0.4.1 from jsDelivr

I just tried

(await import('https://cdn.jsdelivr.net/npm/@js-temporal/[email protected]/dist/index.esm.js')).Temporal

to import polyfill in browser console, but I got:

Uncaught TypeError: Failed to resolve module specifier "jsbi". Relative references must start with either "/", "./", or "../".

Consider caching and/or lazy-init of DateTimeFormat instances in intl.mjs

#7 and #8 showed that new DateTimeFormat() is astoundingly expensive in time and memory. Big perf wins come from caching these instances.

The constructor for DateTimeFormat in this polyfill (which is called in every toLocaleString call) calls the builtin Intl.DateTimeFormat constructor 8 times. This will be slooooooooooow.

All those instances are probably unnecessary. At a minimum, we should lazy-init these instances so they're only constructed when needed. Given that users typically re-use the same options over and over, we could also consider caching DateTimeFormat instances too.

Temporal.Now.plainDateISO() & plainTimeISO throw undefined timezone

Reproduction Steps:

Try to call Temporal.Now.plainDateISO().

Expected Results:

According to https://tc39.es/proposal-temporal/docs/now.html#plainDateISO , it should give a PlainDate.

Actual Results:

Uncaught RangeError: Invalid time zone: undefined
    at ParseTemporalTimeZoneString (file:///home/rektide/src/week-of-year/node_modules/@js-temporal/polyfill/dist/index.esm.js:2794:11)
    at ParseTemporalTimeZone (file:///home/rektide/src/week-of-year/node_modules/@js-temporal/polyfill/dist/index.esm.js:2582:35)
    at SystemTimeZone (file:///home/rektide/src/week-of-year/node_modules/@js-temporal/polyfill/dist/index.esm.js:5965:33)
    at timeZone (file:///home/rektide/src/week-of-year/node_modules/@js-temporal/polyfill/dist/index.esm.js:7975:12)
    at Temporal.Now.plainDateISO (file:///home/rektide/src/week-of-year/node_modules/@js-temporal/polyfill/dist/index.esm.js:7968:46)

Tested on Node v16.14.0.

Workaround

Explicitly specify a timezone name: Temporal.Now.plainDateISO('GMT'). This will return a PlainDate.

Potential Roadmap Updates

Personally, I think the project is reaching a point of stability and it might be a good time to cut another release that includes the migration to JSBI, among other larger changes, and see what kind of feedback we get from the community.

Some thoughts on the roadmap:

  • My own goal is to continue to ensure compatibility with Google's toolchain and optimization strategy using Closure Compiler, in particular investigating how property renaming might work (currently, many fields are accessed through their string names, which forces them to be left as-is). There's some related tsc flags that can be enabled that would help here.
  • investigate all platform dependencies for the polyfill (e.g. Intl APIs, BigInt) and consider how those can be made to work for much older browsers (as IE11 is still a support requirement for Google)
  • investigate code-size savings and runtime performance improvements through the use of the JSBI Babel plugin, and how to ship that via NPM. I suspect a good chunk of overall codesize for js-temporal/temporal-polyfill comes from using JSBI (and possibly all the calendar implementations?), and many users don't need this code. I solved this internal to Google (as we can rely on a single form of static code optimization), but I'd like to consider what this looks like in the OSS world. Community input/contributions would be fantastic here - I greatly lack expertise in OSS toolchains.
  • investigate code-splitting of the polyfill code. This probably works differently for each optimizer in use (Closure Compiler, Terser, others?), and while my primary code is to make sure a "pay for what you use" mindset is accurate for this polyfill I'd be interested in collaborating with other optimizers to improve dead code removal.

Unable to handle valid ISO 8601 week format

Version "@js-temporal/polyfill": "0.4.0"

Just tried: Temporal.PlainDate.from('2022-W12'), where 2022-W12 is ISO week-numbering format.
Receiving: Uncaught RangeError: invalid ISO 8601 string: 2022-W12

ISO 8601 source: https://en.wikipedia.org/wiki/ISO_8601#Week_dates

Don't know how important this issue is, or performance hit would result implementing it, but error could have message like unsupported ISO 8601 string?

Failed to resolve module specifier

Hello, excuse me for representing the naive Web programmers, but I did not succeed importing the polyfill as an ES6 module.
I know, I am a poor guy using a Windows 10 machine and trying to use ES6 module locally, with the "http-server" facility.

The main problem comes from not being able to resolve file locations of "from" statements. All begins with the main import command, after running npm install @js-temporal/polyfill.
A locator needs to be added to the from parameter of the import command: './node_modules/', so it looks:
import { Temporal, Intl, toTemporalInstant } from './node_modules/@js-temporal/polyfill/lib/index.mjs';

But then, at some point the import process stops with the following message:

Uncaught TypeError: Failed to resolve module specifier "big-integer". Relative references must start with either "/", "./", or "../".

From Firefox, the problem was identified in instant.mjs. I tried to change this reference in instant.mjs. So I tried updating the reference, but the same message occurs with other imported files. So I feel there is something missing in the Usage section of the Readme file.

Sorry if I miss something very simple for the seasoned programmers you are. Can you help me ?

Tracking of new Normative changes / drift between tc39/proposal-temporal and this repo

Now that there's the test262 test suite running against this repo, we probably need a way to track changes that have landed in the proposal repo and port them over. This is likely to only get more complex as we continue to productionize this repo's content.

The current test262 implementation always pulls the test directly from the main branch of the proposal repo, so knowing that something needs to change is likely not difficult (as CI will start to fail), but knowing what has and has not already been ported might is where I think it could get difficult.

My suggestion is that we have a script (running on CI?) that is able to look at the Git history for the polyfill folder within the proposal repo and tries to map the commits there 1-1 to commits in this repo's history. We could indicate which commit maps to which by including specific text within the commit messages:

My commit message

PROPOSAL_TEMPORAL_COMMITS=<hash1>,<hash2>

I think this is sort of similar to what Google's Copybara system does (see the PiperOrigin-RevId and ChangeId text in the commit message), so I'll also see if they have any ideas/suggestions.

Thoughts from other maintainers? As much as possible, it would be great to find a solution that would scale to allow other polyfill repos to adopt it without any intervention from the proposal repo (unlike say using repository dispatch events).

Memory usage of polyfill

From tc39/proposal-temporal#683:

I maintain a javascript recurring dates library, rSchedule, which happens to be date library agnostic (i.e. it supports moment, luxon, js-joda, and standard Date). For fun, I decided to try implementing a date adapter for the temporal polyfill and running it against my tests (can be found in the temporal-date-adapter-2 branch).

The good news is that integrating the current (experimental) version of the temporal polyfill into my library was easy and it successfully passed all of my 29,794 tests (my library has a custom date object that does all the heavy lifting, so these tests don't actually test proposal-temporal much).

The less great news is that the temporal polyfill required over 12gb of memory to complete all of these tests, while luxon uses about 1 gb of memory to pass the same test suite (not to mention the speed difference). Because the proposal-temporal polyfill is just intended for experimentation, this performance might be acceptible, but I wanted to pass along the info regardless.

This is a tracking issue for finding and optimizing memory problems such as the above one.

Removed month number on PlainMonthDay

Hello,

In a recent update, monthCode (a string) was added next to month (a number), cf. #1203.
In the associated changes, for PlainMonthDay the month number was removed while other types kept it, cf. here.

Was this done on purpose? I know it is still possible to get it via getISOFields().isoMonth, but I was wondering if it was meant to be this way or if it was a slight mistake in the commit.

Note: great work on this much needed "improvement" (if you can still call it that given the scope) to good old Date by the way

Oddness in year arithmetic with lunisolar calendars

I noticed something odd about adding and subtracting years in a lunisolar calendar:

const date = Temporal.PlainDate.from({ year: 5782, monthCode: 'M08', day: 2, calendar: 'hebrew' });
console.log(date.toLocaleString('en-u-ca-hebrew'));  // => 2 Iyar 5782
console.log(date.add({years: 1}).toLocaleString('en-u-ca-hebrew'));  // => 2 Sivan 5783

It seems like the ordinal month is being preserved, but it seems more likely that the expectation would be for the month code to be preserved. (5782 is a leap year in the Hebrew calendar, so Iyar is month 9. Next year, Sivan is month 9 and Iyar is month 8.) But then again, it seems like this could be dependent on the custom of whoever uses each calendar.

Z designator (designating UTC) results in errors

When giving PlainDateTime a datetime string with time zone in brackets, the time zone is simply ignored:

Temporal.PlainDateTime.from('2022-02-07T10:30:00[Europe/Brussels]')

Temporal.PlainDateTime {repr: 'Temporal.PlainDateTime <2022-02-07T10:30:00>'}

When giving PlainDateTime a datetime string with Z appended (UTC), an error occurs:

Temporal.PlainDateTime.from('2022-02-07T10:30:00Z')

ecmascript.mjs:1120 Uncaught RangeError: Z designator not supported for PlainDateTime
at Object.ToTemporalDateTime (ecmascript.mjs:1120:20)

When giving ZonedDateTime a datetime string with time zone in brackets:

Temporal.ZonedDateTime.from('2022-02-07T10:30:00[Europe/Brussels]')

Temporal.ZonedDateTime {repr: 'Temporal.ZonedDateTime <2022-02-07T10:30:00+01:00[Europe/Brussels]>'}

When giving ZonedDateTime a datetime string with Z appended (UTC), another error occurs:

Temporal.ZonedDateTime.from('2022-02-07T10:30:00Z')

ecmascript.mjs:322 Uncaught RangeError: Temporal.ZonedDateTime requires a time zone ID in brackets
at Object.ParseTemporalZonedDateTimeString (ecmascript.mjs:322:33)

It is very common for programming languages to append Z on a datetime in UTC. My Elixir backend returns a string like '2022-02-07T10:30:00Z' and Temporal doesn't seem to give me an easy way to parse it.
I don't see why time zones in brackets are allowed but not the Z designator.

Avoid exposing BigInt compatibility library implementation as part of public API when native BigInt is not available

get epochSeconds() {
if (!ES.IsTemporalInstant(this)) throw new TypeError('invalid receiver');
const value = GetSlot(this, EPOCHNANOSECONDS);
return +value.divide(1e9);
}
get epochMilliseconds() {
if (!ES.IsTemporalInstant(this)) throw new TypeError('invalid receiver');
const value = bigInt(GetSlot(this, EPOCHNANOSECONDS));
return +value.divide(1e6);
}
get epochMicroseconds() {
if (!ES.IsTemporalInstant(this)) throw new TypeError('invalid receiver');
const value = GetSlot(this, EPOCHNANOSECONDS);
return bigIntIfAvailable(value.divide(1e3));
}
get epochNanoseconds() {
if (!ES.IsTemporalInstant(this)) throw new TypeError('invalid receiver');
return bigIntIfAvailable(GetSlot(this, EPOCHNANOSECONDS));
}

In some places, Temporal specifies it returns bigint, the native BigInt type. This polyfill does aim to work on browsers that don't have native BigInt support (primarily, IE11), but returning the objects used by a BigInt compatibility library can cause maintenance headaches for both us as maintainers and our consumers (who would likely have to update their code if we ever changed or removed the BigInt compatibility layer). This issue tracks how we handle this problem.

Quoting @ptomato:

other options would be to return numbers, or strings, or omit entry points that accept or return bigints

It would be interesting to explore omitting these properties when we know that BigInt is not available, maybe something like (untested) static fromEpochMicroseconds(epochMicroseconds: BigInt ? bigint : never): Temporal.Instant;

Run Test262 on ES5/Optimized build artifacts

As discussed on #89, we are only running the 262 tests on an intermediate build artifact, not the final package content that is pushed to NPM. The code we push to NPM has been minified (using Terser) and (potentially) transpiled to ES5, and this (currently) causes ~900 tests failures related to the more "esoteric" parts of the test262 test suite: functions don't retain their "names" (e.g. Temporal.prototype.Calendar.from.name !== "name"), some functions' lengths are incorrect (due to having to transpile default parameters to ES5), etc. At the time of writing, no "practical" tests are failing for these artifacts - actual computations are succeeding and returning correct dates/times/etc.

This issue should track

  • setting up a second Test262 suite that runs in parallel to the original, checking that the packaged version of our build is mostly spec-compliant.
  • potentially adjusting the various build tool settings to increase spec-compatibility, where feasible.

Work around WebKit bug with DateTimeFormat.resolvedOptions

I just reported a bug in WebKit (https://bugs.webkit.org/show_bug.cgi?id=231041) which breaks some functionality in this polyfill:

> new Temporal.ZonedDateTime(957270896_987_650_000n, "UTC").toLocaleString('en', {dateStyle:'short'})
"5/2/00"

I don't have access to a recent enough version of Safari to test on that supports dateStyle/timeStyle, but I suspect that in Safari 14.1 onward (released April 2021), the above code will instead throw:

TypeError: dateStyle and timeStyle may not be used with other DateTimeFormat options

Therefore it might be good to work around this bug, depending on how soon a fix is released and how quickly users are expected to update.

How to run Test262 on this polyfill?

There should be a way to run Test262 tests against this polyfill, as a complement (and verification) to the existing non-Test262 tests.

Ideally, we wouldn't have to duplicate the Test262 test code from proposal-temporal. Could a script check out the latest Test262 code from proposal-temporal and run it locally against this repo?

GetFormatterParts throws exception in Firefox/Chromium 96 browsers...

In js-temporal.js, around line 8470, this library uses an interesting series of comma assignments that I think break in the latest versions of certain browsers out there.

I used the following code to fix it:

    var _datetime$split = datetime.split(/,\s+/);
    var _datetime$split2 = _slicedToArray(_datetime$split, 3);
    var date = _datetime$split2[0],
        fullYear = _datetime$split2[1],
        time = _datetime$split2[2];

    var _date$split = date.split(' ');
    var _date$split2 = _slicedToArray(_date$split, 2);
    var month = _date$split2[0],

        day = _date$split2[1];

    var _fullYear$split = fullYear.split(' ');
    var _fullYear$split2 = _slicedToArray(_fullYear$split, 2);
    var year = _fullYear$split2[0],
        era = _fullYear$split2[1];

    var _time$split = time.split(':');
    var _time$split2 = _slicedToArray(_time$split, 3);
    var hour = _time$split2[0],
        minute = _time$split2[1],
        second = _time$split2[2];

I've provided the full file here:

js-temporal.js.zip

Er, I can see about providing a proper submission.

Change type-only imports to use `import type`

There are a few places where type-only imports use import:

  • These two imports in intl.ts
import { Temporal, Intl } from '..';
import { DateTimeFormatParams as Params, DateTimeFormatReturn as Return } from './internaltypes';
  • import { Temporal } from '..'; in almost every code file

When imports are only types, it's probably best to use import type instead of import. See https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export for context.

It's optional, but can't hurt and may help in some cases with bundlers, babel, etc.

Remove Node 12 test logic and data

Now that Node 12 is nearing end-of-life, we're only going to support Node 14+ in this polyfill. There are some tests that include conditional logic and perhaps test data for Node 12 that we should remove at some point.

Expose *Like utility types

For a TDD kata I was writing, I wanted to be able to say:

const isMorning = (t: PlainTimeLike) => t.hour < 12 but the PlainTimeLike type isn't exposed. Given that it makes up part of the interface of methods like PlainDate.toPlainDateTime() is there a chance you will expose these types?

Implement release automation

While investigating #150 , I noticed that our latest release wasn't minified at all. I'm guessing this is because I forgot to set the NODE_ENV environment variable before building/publishing.

Generally, having to rely on a human to get the release steps right is unfortunate - it would be much nicer if this was entirely automated.

Update type declarations for "Timezone-like" and "Calendar-like" parameters to support ZDT property bags

The current public types for TimeZone.from look like this:

static from(timeZone: TimeZoneProtocol | string): Temporal.TimeZone | TimeZoneProtocol;

But the spec and the polyfill also accept a ZDT-like property bag too, per tc39/proposal-temporal#925. Specifically, they accept any object with a timeZone property and will use that property's value as the time zone, with one exception: per tc39/proposal-temporal#925 (comment), recursive bags like {timeZone: {timeZone: 'Asia/Tokyo'}} are disallowed.

Therefore, the declaration should also allow a { timeZone: TimeZoneProtocol | string } parameter type.

This issue tracks the work to:

  1. Figure out how to encode the "no recursive bags" in TS types. One possible way (not sure this will work) could be something like this:
type NoRecursiveTimeZones = { timeZone: { timeZone: never } };
. . .
  static from(timeZone: TimeZoneProtocol | string | { timeZone: TimeZoneProtocol } | NoRecursiveTimeZones): Temporal.TimeZone | TimeZoneProtocol; 
  1. Use that declaration throughout index.d.ts wherever TimeZone-like parameters are accepted
  2. Port the changes to proposal-temporal, and verify that the docs are correct for methods that accept a time zone parameter.
  3. Remove any now-unnecessary type casts, like the one @ptomato found in in https://github.com/js-temporal/temporal-polyfill/pull/109/files#r779978091.

Release 0.4.0

I think now's a good time to create another release.

I'd like to target a cutoff point within the next week or so, and hopefully #105 can land first? It would be great to have that included.

TimeZone GetNextTransition / GetPreviousTransition for far-future and far-past dates

The current implementation of TimeZone's GetNextTransition / GetPreviousTransition methods behave poorly for very large or very small inputs, including possible denial-of-service issues caused by O(1) loops of expensive code.

Examples:

// Repro 1: far-future date, get previous transition
Temporal.TimeZone.from('Etc/GMT+10').getPreviousTransition(new Temporal.Instant(0n).add({hours: 24 * 1e8}));
  // expected: ???
  // actual: runs for a loooooooong time, probably over an hour (denial of service attack?) then will return null

// Repro 2: far-past date, get next transition
Temporal.TimeZone.from('Etc/GMT+10').getNextTransition(new Temporal.Instant(0n).add({hours: -24 * (1e8-1)}));
  // expected: ???
  // actual: runs for a loooooooong time, probably over an hour (denial of service attack?) then will return null

// Repro 3: future date in year 3021, get next transition
Temporal.TimeZone.from('Etc/GMT+10').getNextTransition(new Temporal.Instant(0n).add({hours: 24 * 365 * 1000}));
  // expected: ???
  // actual: null

There are a few issues here:

  1. Can we prevent a denial-of-service attack caused by this expensive, O(1) loop?
  2. DST looking forward more than a few decades in the future seems inherently unreliable, and certainly not worth exposing the user's computer to a DoS opportunity.

Should we simply limit future dates to N years in the future (N = 100? N = 50? N = 20?), and throw a RangeError for anything past that?

BTW, we already limit the problem for too-small dates (There was no DST before 1847 C.E.) when calling GetPreviousTransition. We should use the same smarts in GetNextTransition to avoid Repro #2 above.

PlainYearMonth objects with a Gregorian calendar do not work correctly when subtracting months in some cases

The title is a mouthful, so just to break it down - subtraction of months does not work when:

  • It is done on a PlainYearMonth
  • The object has a calendar
  • The calendar is "gregory"
  • The month of the object 31 days. For example, December (12).

Here is a code example:

const date = new Temporal.PlainYearMonth(2021, 12, "gregory");
const result = date.subtract({ months: 4 });
console.log("date", date.toString(), result.toString());

The log result is

date: 2021-12-01[u-ca=gregory] 2021-12-01[u-ca=gregory] 

The month stays the same if it's December. I debugged it and it seems to be a problem of having a calendar attached - when subtraction happens, it sets the day to 31 (the last day of the month) and then it tries to subtract 30 days four times (for the four months being subtracted) but each iteration in the loop, it sets the date to subtract from as 2021-12-31, so it ends in 2021-12-01 not shifting the month, then next iteration it subtracts from the last day of the month again: 2021-12-31

Weirdly, not all 31 day months work the same. And not all 30 day months work the same, either. Here is my test for trying to subtract 4 months from various times in 2021:

Date Start date End date result
new Temporal.PlainYearMonth(2021, 1, "gregory") 2021-01-01[u-ca=gregory] 2020-12-01[u-ca=gregory] ❌1 month shift.
new Temporal.PlainYearMonth(2021, 2, "gregory") 2021-02-01[u-ca=gregory] 2020-10-01[u-ca=gregory] ✔ correct 4 month shift.
new Temporal.PlainYearMonth(2021, 3, "gregory") 2021-03-01[u-ca=gregory] 2021-03-01[u-ca=gregory] ❌ no shift.
new Temporal.PlainYearMonth(2021, 4, "gregory") 2021-04-01[u-ca=gregory] 2021-03-01[u-ca=gregory] ❌ 1 month shift.
new Temporal.PlainYearMonth(2021, 5, "gregory") 2021-05-01[u-ca=gregory] 2021-05-01[u-ca=gregory] ❌ no shift.
new Temporal.PlainYearMonth(2021, 6, "gregory") 2021-06-01[u-ca=gregory] 2021-03-01[u-ca=gregory] ❌ 3 months shift.
new Temporal.PlainYearMonth(2021, 7, "gregory") 2021-07-01[u-ca=gregory] 2021-07-01[u-ca=gregory] ❌ no shift.
new Temporal.PlainYearMonth(2021, 8, "gregory") 2021-08-01[u-ca=gregory] 2021-07-01[u-ca=gregory] ❌ 1 month shift.
new Temporal.PlainYearMonth(2021, 9, "gregory") 2021-09-01[u-ca=gregory] 2021-05-01[u-ca=gregory] ✔ correct 4 month shift.
new Temporal.PlainYearMonth(2021, 10, "gregory") 2021-10-01[u-ca=gregory] 2021-10-01[u-ca=gregory] ❌ no shift.
new Temporal.PlainYearMonth(2021, 11, "gregory") 2021-11-01[u-ca=gregory] 2021-07-01[u-ca=gregory] ✔ correct 4 month shift.
new Temporal.PlainYearMonth(2021, 12, "gregory") 2021-12-01[u-ca=gregory] 2021-12-01[u-ca=gregory] ❌ no shift.

It appears that the majority of the months do not work when subtracting and most likely because they fail on the previous month. January rolls over to December then fails to subtract three times.

Seems that having a day because of the Gregorian calendar is an issue. Using "iso8601" as a calendar does not cause this behaviour. I have not tried other calendars.

The version of the @js-temporal/polyfill package is 0.2.0

You can find a runnable example in this CodeSandbox

Should we un-export unused exports?

With #57 merged, it's easy now to check which exports are unused. npx ts-prune is great! Output is below.

I opened #62 for the easy case: functions that are not used anywhere, and that should be removed.

But there's also a lot of exports in ecmascript.ts that are never imported anywhere else-- they're just used internally in that file. Should we un-export them? I'm not sure about why we'd want to leave them exported, but others may know a reason.

I assume that some of these may be used in tests, so I assume we should wait until test code is converted to TS before evaluating which exports are actually unused.

Not sure why toTemporalInstant shows up as unused. Also not sure what's going on with the globals (Temporal, Intl) but assume it has to do with use as globals so they're not imported?

cc @12wrigja

justingrant@jgmac temporal-polyfill % npx ts-prune                                  
index.d.ts:1730 - toTemporalInstant
index.d.ts:1728 - Intl (used in module)
lib/ecmascript.ts:65 - IsInteger (used in module)
lib/ecmascript.ts:97 - ToInteger (used in module)
lib/ecmascript.ts:134 - ToIntegerNoFraction (used in module)
lib/ecmascript.ts:261 - TemporalTimeZoneFromString (used in module)
lib/ecmascript.ts:277 - FormatCalendarAnnotation (used in module)
lib/ecmascript.ts:283 - ParseISODateTime (used in module)
lib/ecmascript.ts:347 - ParseTemporalInstantString (used in module)
lib/ecmascript.ts:351 - ParseTemporalZonedDateTimeString (used in module)
lib/ecmascript.ts:355 - ParseTemporalDateTimeString (used in module)
lib/ecmascript.ts:359 - ParseTemporalDateString (used in module)
lib/ecmascript.ts:363 - ParseTemporalTimeString (used in module)
lib/ecmascript.ts:384 - ParseTemporalYearMonthString (used in module)
lib/ecmascript.ts:399 - ParseTemporalMonthDayString (used in module)
lib/ecmascript.ts:411 - ParseTemporalTimeZoneString (used in module)
lib/ecmascript.ts:434 - ParseTemporalDurationString (used in module)
lib/ecmascript.ts:470 - ParseTemporalInstant (used in module)
lib/ecmascript.ts:525 - DurationHandleFractions (used in module)
lib/ecmascript.ts:564 - ToTemporalDurationRecord (used in module)
lib/ecmascript.ts:1729 - GetTemporalCalendarWithISODefault (used in module)
lib/ecmascript.ts:1860 - DisambiguatePossibleInstants (used in module)
lib/ecmascript.ts:1985 - GetPossibleInstantsFor (used in module)
lib/ecmascript.ts:2269 - FormatTimeZoneOffsetString (used in module)
lib/ecmascript.ts:2306 - GetISOPartsFromEpoch (used in module)
lib/ecmascript.ts:2329 - GetIANATimeZoneDateTimeParts (used in module)
lib/ecmascript.ts:2383 - GetFormatterParts (used in module)
lib/ecmascript.ts:2516 - BalanceISOYearMonth (used in module)
lib/ecmascript.ts:2526 - BalanceISODate (used in module)
lib/ecmascript.ts:2555 - BalanceISODateTime (used in module)
lib/ecmascript.ts:2569 - BalanceTime (used in module)
lib/ecmascript.ts:2621 - NanosecondsToDays (used in module)
lib/ecmascript.ts:3007 - ConstrainISODate (used in module)
lib/ecmascript.ts:3013 - ConstrainTime (used in module)
lib/ecmascript.ts:3023 - ConstrainISODateTime
lib/ecmascript.ts:3040 - RejectISODate (used in module)
lib/ecmascript.ts:3045 - RejectDateRange (used in module)
lib/ecmascript.ts:3059 - RejectDateTime (used in module)
lib/ecmascript.ts:3064 - RejectDateTimeRange (used in module)
lib/ecmascript.ts:3085 - RejectYearMonthRange (used in module)
lib/ecmascript.ts:3094 - RejectDuration (used in module)
lib/ecmascript.ts:3709 - RoundNumberToIncrement (used in module)
lib/ecmascript.ts:3829 - DaysUntil (used in module)
lib/ecmascript.ts:3841 - MoveRelativeDate (used in module)
lib/ecmascript.ts:4243 - NonNegativeModulo (used in module)
lib/ecmascript.ts:4318 - GetOption (used in module)
lib/ecmascript.ts:4330 - GetNumberOption (used in module)
lib/index.ts:11 - Intl (used in module)
lib/index.ts:11 - toTemporalInstant (used in module)
lib/init.ts:42 - Temporal (used in module)
lib/init.ts:42 - Intl (used in module)
lib/init.ts:42 - toTemporalInstant (used in module)
lib/regex.ts:3 - timeZoneID (used in module)
lib/regex.ts:8 - calendarID (used in module)
lib/regex.ts:13 - datesplit (used in module)

Easier way to type parameters and return types of class implementations using types in index.d.ts?

I learned something sad today: TS doesn't automatically infer the types of methods that implement interface methods. Example:

// index.d.ts
namespace Temporal {
  class Foo {
    bar(s: string): number;
  }
}

// foo.ts
class Foo implements Temporal.Foo {
  bar(s) {
    //^ Parameter 's' implicitly has an 'any' type.(7006) :-(
    console.log(s.toUpperCase());
    return 42;
  }
}

Details:

This means that even though we have detailed types in index.d.ts, if we want to remove noImplicitAny then we'll still need to figure out how to get type annotations to the parameters of every one of hundreds of Temporal class methods.

I think this will have to be done manually by copy/pasting from index.d.ts into each class implementation, but I'm opening this issue in case there's an easier way to do this.

cc @12wrigja

Tree shaking

Forwarded from tc39/proposal-temporal#709:

I suspect that the current structure of the polyfill (classes + using a big ecmascript.js object + es-abstract) is difficult or impossible for bundlers to tree-shake. The result will be a big bundle size increase for apps using the polyfill.

I don't know much about implementing tree shaking in a library, but I assume there are probably two problems to solve:

  1. helping rollup exclude unused code in the polyfill's dependencies (notably es-abstract). This reduces Temporal's own bundle size.
  2. ensuring that the resulting polyfill is tree-shakeable with app bundlers. This reduces the bundle size of apps that use the polyfill. Webpack is the most popular but I'm not sure what other bundlers (e.g. Parcel?) would need to also be tested too.

It's possible that the same set of techniques work to address both problems, but I'm not sure.

Here's a few links:

I did some work previously to tree-shake all the unused parts of es-abstract, by importing es-abstract's files individually, which did improve things a lot, but I stopped there. This is a tracking issue for investigating tree-shaking properly.

Should the `impl` objects of non-ISO calendars be ES6 classes too?

Context: built-in calendar implementations are contained in plain objects, not ES6 class instances. The ISO calendar uses a singleton object, while non-ISO calendars have a singleton object that's cloned for each of the 18 supported non-ISO calendars and amended with a single helper property that differs for each calendar and encapsulates per-calendar data and logic.

The reason for splitting things in this way was originally to keep the ISO and non-ISO implementations as similar as possible, and to make it really clear when reading code that when you see helper. it means that code is doing something where behavior may vary between non-ISO calendars.

#113 converted all the helper objects into ES6 class instances. They're still singletons at runtime, but making them classes made the code easier to understand and (hopefully!) maintain.

@12wrigja made a suggestion in #113 (comment): should the top-level implementation objects also be ES6 classes?

There are three options:

  1. Leave as-is
  2. Convert both the ISO and non-ISO XxxImpl instances to classes (either anonymous class expressions or named classes which are only instantiated once) but otherwise leave the impl vs. helper split as-is.
  3. Convert ISO to a class like (2), but for non-ISO we'd both convert to a class and also remove the "impl" vs. "helper" split. If we did this, then we'd merge the nonIsoImpl object with the HelperBase class to get a single abstract base class for all non-ISO calendars, which would then be specialized with derived classes for each of the 18 non-ISO calendars, per the inheritance structure described in #113.

I don't have a strong opinion between these options.

An interesting related question is whether #113 and/or the changes proposed above should be ported back to proposal-temporal. Regardless of the answer, (2) or (3) above would introduce more deviation from proposal-temporal and therefore probably more pressure to port changes over. But, honestly, the changes in #113 are more than both (2) and (3) so we should probably answer that question independently of this issue.

@ptomato @12wrigja FYI

GMT offsets for ZonedDateTimes have their sign flipped

Temporal.Now.zonedDateTimeISO("Etc/GMT+2");
Saves as:
"Temporal.ZonedDateTime <2021-09-10T17:33:37.680174947+02:00[Etc/GMT-2]>"
So if we use zonedTimeISO.toLocaleString("en-gb") we get:
10/09/2021, 17:33:37 GMT+2

rather than:
10/09/2021, 13:33:37 GMT-2

This only seems to be an issue with "Etc/GMT±" so functions as expected for timezones such as "London/Europe"

What could cause two different instances of the slots WeakMap to be created?

I'm working my may through a large project and converting it over to use Temporal, and I'm stumped by a problem I'm seeing where a PlainDate instance is created by internal Temporal code, and then that instance is rejected (again by Temporal's own internal code) as invalid. When I stepped through in the debugger, the problem seems to be that the slots WeakMap is somehow different between one line in ecmascript.ts and the next line! Here's the code where it fails:

  DateFromFields: (calendar, fields, options) => {
    const dateFromFields = ES.GetMethod(calendar, 'dateFromFields');
    const result = ES.Call(dateFromFields, calendar, [fields, options]);
       //  `result` is a valid Temporal instance, e.g. its methods return valid values, etc.
       //  When the line above runs, the `slots` WeakMap contains ~100 objects 
    if (!ES.IsTemporalDate(result)) throw new TypeError('invalid result');
       // but this line above throws, because when this code runs, the 
       // `slots` WeakMap only contains one object
    return result;
  },

I tried setting a breakpoint on the line in slots.ts where the slots object is actually created, and that breakpoint is only being hit once.

FWIW, this Temporal package is not from npm but instead is custom-built to remove type:"module" per #29, because otherwise the polyfill won't run at all. I'm npm install-ing it from the local .tgz file created by npm pack.

At this point I'm stumped. Any idea what could be causing this?

Error in Firefox using gregorian calendar

Code sample:

Temporal.Now.plainDateTime('gregory', 'Europe/Stockholm').add({ days: 2 })

Error that happens RangeError: Era a (ISO year 2022) was not matched by any era

Tested in Firefox 96.0 on Ubuntu 20.04, with version 0.3.0

I have seen it referenced here as well: novacbn/kahi-ui#114 (comment)

This seems to be a follow up of #92

I don't think this is a duplicate of the previous issues but my apologies if it is.

DateTimeFormatOptions typing override missing calendar

The TC39 docs have this example:

monthDay.toLocaleString(locales, { calendar: monthDay.calendar });

But it doesn't compile for me with the polyfill library unless I do this:

monthDay.toLocaleString(locales, { calendar: monthDay.calendar.id });

The options parameter takes calendar: string | undefined while monthDay.calendar is a Temporal.CalendarProtocol. I thought the example code might have been incorrect, but it turns out that if you pass in a Temporal.Calendar there it is interpreted correctly.

The polyfill library doctors the Intl.DateTimeFormatOptions. It looks like it redeclares timeZone, but I think also should be redeclaring the calendar property to also accept Temporal.CalendarProtocol.

  export interface DateTimeFormatOptions extends Omit<globalThis.Intl.DateTimeFormatOptions, 'timeZone'> {
    timeZone?: string | Temporal.TimeZoneProtocol;
    // TODO: remove the props below after TS lib declarations are updated
    dayPeriod?: 'narrow' | 'short' | 'long';
    dateStyle?: 'full' | 'long' | 'medium' | 'short';
    timeStyle?: 'full' | 'long' | 'medium' | 'short';
  }

How to handle typing of mutated `let` variables or function parameters?

A common pattern in our current polyfill code is to reassign variables and parameters. How should we work around TS's limitation that mutating a variable doesn't change its type from TS's perspective?

For example: (TS playground)

function toString(a: unknown) {
  a = String(a);
  return a;
  // expected return type: String
  // actual return type: unknown
}

toString(42).includes('4')
// ^^ Object is of type 'unknown'.(2571)

// explicitly declaring a return type doesn't work either
function toString2(a: unknown): string {
  a = String(a);
  return a;
  // ^^ Type 'unknown' is not assignable to type 'string'.(2322)
}

There is apparently a future TS proposal microsoft/TypeScript#45870 which may fix this. I commented there to ask if it will help us.

cc @12wrigja

GetFormatterParts failing on latest Firefox Nightly

From https://bugzilla.mozilla.org/show_bug.cgi?id=1742572:

Standalone test case:

function GetFormatterParts(timeZone, epochMilliseconds){
  const formatter = new Intl.DateTimeFormat("en-us", {
    timeZone,
    hour12: false,
    era: "short",
    year: "numeric",
    month: "numeric",
    day: "numeric",
    hour: "numeric",
    minute: "numeric",
    second: "numeric"
  });
  const datetime = formatter.format(new Date(epochMilliseconds));
  const [date, fullYear, time] = datetime.split(/,\s+/);
  const [month, day] = date.split(" ");
  const [year, era] = fullYear.split(" ");
  const [hour, minute, second] = time.split(":");
  return {
    year: era === "BC" ? -year + 1 : +year,
    month: +month,
    day: +day,
    hour: hour === "24" ? 0 : +hour,
    minute: +minute,
    second: +second
  };
}

GetFormatterParts("America/Los_Angeles", Date.now());

GetFormatterParts expects that the formatted date-time string can be split into three parts through the regular expression /,\s+/. This worked before the update, because the formatted string looked like "11 23, 2021 AD, 05:00:00". But after the update to ICU 70, which includes an update to CLDR 40, the string now looks like "11/23/2021 A, 05:00:00", i.e. can no longer be separated into three parts when searching for a comma.

Broken test script in the last commit

Hello,

I believe the last commit broke the test script by forgetting a time in the command.

I'll make in quick PR in a minute to revert it, I just didn't want to include it in my other PR in case it might have been done on purpose and for the usual reasons doesn't work on Windows or something 😅.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.