diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d27b714..574e1cb8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,10 @@ name: CI -on: [push] +on: + push: + branches: + - main env: TZ: "America/New_York" @@ -11,29 +14,35 @@ jobs: name: CI runs-on: ubuntu-latest + strategy: + matrix: + node: [14.21.3, 16.20.0, 18.19.0, 20.10.0] + steps: - name: Checkout uses: actions/checkout@v3 + with: + submodules: recursive - name: Setup PNPM uses: pnpm/action-setup@v2.2.4 with: - version: 7.14.0 + version: 8.6.3 - name: Setup Node uses: actions/setup-node@v3 with: - node-version: 16.13.1 + node-version: 18.19.0 cache: 'pnpm' - name: Install dependencies run: pnpm install - - name: Lint - run: pnpm run lint + # - name: Lint + # run: pnpm run lint - name: Build run: pnpm run build - name: Test - run: pnpm run test + run: cd packages/temporal-polyfill && NODE_VERSION=${{ matrix.node }} pnpm run test diff --git a/.gitignore b/.gitignore index 3dfe42cc..181493d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,4 @@ - node_modules -/coverage -tsconfig.tsbuildinfo -dist - -# see pkgExportsFix -/packages/*/*.d.ts -# hack -!/packages/temporal-spec/*.d.ts +/packages/*/dist/* +/!packages/*/dist/.npmignore diff --git a/.gitmodules b/.gitmodules index 331f0a63..c58ddab9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,12 @@ [submodule "scripts/data/moment"] - path = scripts/data/moment + path = packages/locale-data/data/moment url = https://github.com/moment/moment.git [submodule "scripts/data/fullcalendar"] - path = scripts/data/fullcalendar + path = packages/locale-data/data/fullcalendar url = https://github.com/fullcalendar/fullcalendar.git [submodule "packages/temporal-polyfill/test262"] - path = packages/temporal-polyfill/test262 + path = test262 url = https://github.com/tc39/test262 +[submodule "packages/temporal-test262-runner"] + path = packages/temporal-test262-runner + url = git@github.com:fullcalendar/temporal-test262-runner.git diff --git a/.npmrc b/.npmrc index bffee7e0..2b777241 100644 --- a/.npmrc +++ b/.npmrc @@ -1,7 +1,3 @@ engine-strict = true - -# for some reason 16.18.0 cause sub-second tz offsets to fail -# NOTE: keep in sync with github workflow -use-node-version = 16.13.1 - +use-node-version = ${NODE_VERSION:-18.19.0} prefer-workspace-packages = true diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 7f3f4ffc..b308e589 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,5 @@ { "recommendations": [ - "dbaeumer.vscode-eslint", - "firsttris.vscode-jest-runner" + "dbaeumer.vscode-eslint" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 5e0065c7..74582950 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,56 +1,11 @@ { "configurations": [ - { - "name": "Jest: Debug All", - "type": "node", - "request": "launch", - "runtimeArgs": [ - "--inspect-brk", - "${workspaceRoot}/node_modules/jest/bin/jest.js", - "--runInBand", - // NOTE: keep following args in sync with settings.json - "--no-cache", - "--watchAll=false" - ], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "sourceMaps": true - }, - { - "name": "Jest: Debug File", - "type": "node", - "request": "launch", - // NOTE: keep args in sync with settings.json - "runtimeArgs": [ - "--inspect-brk", - "${workspaceRoot}/node_modules/jest/bin/jest.js", - "--runInBand", - // NOTE: keep following args in sync with settings.json - "--no-cache", - "--watchAll=false", - // specific file - "${file}" - ], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "sourceMaps": true - }, - { - "name": "Test262: Run All", - "type": "node", - "request": "launch", - "runtimeExecutable": "pnpm", - "runtimeArgs": ["run", "test262"], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "autoAttachChildProcesses": true - }, { "name": "Test262: Debug File", "type": "node", "request": "launch", "runtimeExecutable": "pnpm", - "runtimeArgs": ["run", "test262", "--no-timeout", "${file}"], + "runtimeArgs": ["run", "test", "--no-timeout", "${file}"], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", "autoAttachChildProcesses": true diff --git a/.vscode/settings.json b/.vscode/settings.json index a1bca648..b8210243 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "editor.codeActionsOnSave": { - "source.fixAll": true + "source.fixAll": "explicit" }, "typescript.enablePromptUseWorkspaceTsdk": true, "jestrunner.changeDirectoryToWorkspaceRoot": false, // allows entering sub-workspace, is faster diff --git a/packages/temporal-polyfill/CHANGELOG.md b/CHANGELOG.md similarity index 58% rename from packages/temporal-polyfill/CHANGELOG.md rename to CHANGELOG.md index 706cf8b9..144fd987 100644 --- a/packages/temporal-polyfill/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,36 @@ +v0.2.0 (2024-01-07) +------------------- + +- Updated with latest [test262](https://github.com/tc39/test262) conformance tests (Nov 2023) (#3). +All tests passing barring intentional deviations from spec, documented in [README](README.md). +- Breaking changes include all those [mentioned here](https://github.com/js-temporal/temporal-polyfill/blob/main/CHANGELOG.md#044) +and [normative changes](https://github.com/tc39/proposal-temporal/issues/2628) introduced between May 2023 - Nov 2023, +most notably changes to "user-visible operations". +- Size of minified+gzipped bundle increased from 17.3 kB -> 20.0 kB due to stricter compliance with latest spec. +- In NPM directory, all files are now top-level as opposed to within `dist/`. Thus, the [jsDelivr URL](https://cdn.jsdelivr.net/npm/temporal-polyfill@0.2.0/global.min.js) has changed. +- Fixed bugs: #9, #12, #13, #21 +- Improved README content, including comparison with @js-temporal (#22) +- Renamed github repo to fullcalendar/temporal-polyfill + + v0.1.1 (2023-02-15) ------------------- + - fix: upgrade temporal-spec, which is now compatible with moduleResolution:node16 (#17 cont'd) - fix: don't fallback to native Temporal implementation for ponyfill (#19 cont'd) v0.1.0 (2023-02-09) ------------------- + - fix: Support TypeScript 4.7 moduleResolution:node16 (#17) - fix: Avoiding fallback to native Temporal implementation (#19) v0.0.8 (2022-08-24) ------------------- + - Support environments without BigInt. See browser version matrix in README. - Fixed TypeScript syntax error in `temporal-spec/index.d.ts` (#10) - Fixed missing .d.ts files for environments that don't support export maps. diff --git a/README.md b/README.md index f90d10a6..b5f972d6 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,274 @@ -# Temporal Monorepo +# temporal-polyfill -A lightweight [Temporal] polyfill and other futuristic JavaScript date utilities. +A lightweight polyfill for [Temporal](https://tc39.es/proposal-temporal/docs/), successor to the JavaScript `Date` object. +Only 20.0 kB, with near-perfect [spec compliance](#spec-compliance). -## Project Updates -- **2023-04-22:** The codebase is undergoing a refactor to respond to the lastest changes in the [Temporal] spec. This refactor will also improve minification size, performance, and conformance to [TC39 tests](https://github.com/tc39/test262). Please subscribe to [this ticket](https://github.com/fullcalendar/temporal/issues/3) for updates. -- **2023-09-12:** The refactor [is going well](https://github.com/fullcalendar/temporal/issues/3#issuecomment-1716106547). A number of bugfixes and bundle-size optimizations are outstanding. +## Table of Contents -## temporal-polyfill +- [Installation](#installation) +- [Comparison with `@js-temporal/polyfill`](#comparison-with-js-temporalpolyfill) +- [Spec Compliance](#spec-compliance) +- [Browser Support](#browser-support) +- [BigInt Considerations](#bigint-considerations) +- [Tree-shakeable API](#tree-shakeable-api) (coming soon) -A spec-compliant polyfill in 16kb. + +## Installation ``` npm install temporal-polyfill ``` -**A) Import globally:** +Import as an ES module without side effects: ```js -import 'temporal-polyfill/global' +import { Temporal } from 'temporal-polyfill' console.log(Temporal.Now.zonedDateTimeISO().toString()) ``` -**B) Import as an ES module** without side effects: +Or, import globally: ```js -import { Temporal } from 'temporal-polyfill' +import 'temporal-polyfill/global' console.log(Temporal.Now.zonedDateTimeISO().toString()) ``` -[Read more about temporal-polyfill](packages/temporal-polyfill/README.md) +Use a ` + +``` -## Repo Dev Commands -``` -pnpm build -pnpm watch -pnpm test --watch -pnpm test --coverage -pnpm lint -pnpm size -``` +## Comparison with `@js-temporal/polyfill` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Package + temporal-polyfill + + @js-temporal/polyfill +
Repo + + fullcalendar/temporal-polyfill + + + + js-temporal/temporal-polyfill + +
CreatorsFullCalendar lead dev arshawChampions of the Temporal proposal
Minified+gzip size ✝20.0 kB43.2 kB (+116%)
Minified-only size ✝58.7 kB206.0 kB (+251%)
Spec compliance + Strict compliance for common API.
+ Relaxed compliance for subclassing
built-in types. +
+ Strict compliance for entire API. +
Spec date + Nov 2023 (latest) + + May 2023 +
BigInt ApproachInternally avoids BigInt operations altogetherInternally relies on JSBI
Global usage in ESM + import 'temporal-polyfill/global' + Not currently possible
+ +✝ Compares [global.min.js](https://cdn.jsdelivr.net/npm/temporal-polyfill@0.2.0/global.min.js) with [index.esm.js](https://cdn.jsdelivr.net/npm/@js-temporal/polyfill@0.4.4/dist/index.esm.js), which are similarly transpiled. + + +## Spec Compliance + +All calendar systems (ex: `chinese`, `persian`) and time zones are supported. + +Compliance with the latest version of the Temporal spec (Nov 2023) is near-perfect with the following intentional deviations: + +- `Duration::toString` does not display units greater than `Number.MAX_SAFE_INTEGER` according to spec. Precision is chosen differently. +- *Custom implementations* of Calendars and TimeZones are queried differently. Only affects those subclassing built-in classes, which is extremely rare. See the CALLING entries in the [test-ignore file](https://github.com/fullcalendar/temporal/blob/main/packages/temporal-polyfill/scripts/test-config/expected-failures.txt). +- There are believed to be 3 bugs in the Temporal spec itself, one of which [has been filed](https://github.com/tc39/proposal-temporal/issues/2742). See SPEC-BUG entries in the [test-ignore file](https://github.com/fullcalendar/temporal/blob/main/packages/temporal-polyfill/scripts/test-config/expected-failures.txt). +- Canonicalization of time zone IDs is simplified, leveraging the built-in `Intl` API. +- `Intl.DateTimeFormat` has not been polyfilled to accept number-offset values for the `timeZone` option. +- Method descriptors and `Function::length` are not strictly compliant due to ES-related space-saving techniques. + +The [Official ECMAScript Conformance Test Suite](https://github.com/tc39/test262) has: + +- 6811 *total* Temporal-related test files +- 6138 *passed* by `temporal-polyfill` +- 495 *ignored* due to superficial method descriptor compliance +- 178 *ignored* due to other aforementioned intentional deviations + + +## Browser Support + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Minimum required browsers: +
Chrome 60
(Jul 2017)
Firefox 55
(Aug 2017)
Safari 11.1
(Mar 2018)
Safari iOS 11.3
(Mar 2018)
Edge 79
(Jan 2020)
Node.js 14
(Apr 2020)
+
+ If you transpile, you can support older browsers down to: +
Chrome 57
(Mar 2017)
Firefox 52
(Mar 2017)
Safari 10
(Sep 2016)
Safari iOS 10
(Sep 2016)
Edge 15
(Apr 2017)
Node.js 14
(Apr 2020)
-[Temporal]: https://github.com/tc39/proposal-temporal + + + +## BigInt Considerations + +This polyfill works fine in environments that do not support [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt). + +However, to use methods that accept/emit them, your browser [must support BigInt](https://caniuse.com/bigint). + +Here's how to sidestep this browser compatibility issue: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
❌ Avoid microseconds/nanoseconds✅ Use milliseconds instead
instant.epochMicrosecondsinstant.epochMilliseconds
instant.epochNanosecondsinstant.epochMilliseconds
Temporal.Instant.fromEpochMicroseconds(micro)Temporal.Instant.fromEpochMilliseconds(milli)
Temporal.Instant.fromEpochNanoseconds(nano)Temporal.Instant.fromEpochMilliseconds(milli)
new Temporal.Instant(nano)Temporal.Instant.fromEpochMilliseconds(milli)
zonedDateTime.epochMicrosecondszonedDateTime.epochMilliseconds
zonedDateTime.epochNanosecondszonedDateTime.epochMilliseconds
+ new Temporal.ZonedDateTime(nano, tz, cal) + + Temporal.Instant.fromEpochMilliseconds(milli)
+   .toZonedDateTimeISO() // or toZonedDateTime +
+ + +## Tree-shakeable API + +🚧 Coming Soon + +For library authors and other devs who are hyper-concerned about bundle size, `temporal-polyfill` will be providing an alternate API designed for tree-shaking. + +```js +import * as ZonedDateTime from 'temporal-polyfill/fns/zoneddatetime' + +const zdt = ZonedDateTime.from({ year: 2024, month: 1, day: 1 }) +const s = ZonedDateTime.toString(zdt) // not how you normally call a method! +``` diff --git a/babel.config.cjs b/babel.config.cjs deleted file mode 100644 index c6279a40..00000000 --- a/babel.config.cjs +++ /dev/null @@ -1,9 +0,0 @@ -// For Jest. Individual packages extend this config -// TODO: when running test in "built-mode", don't do any babel transforming - -module.exports = { - presets: [ - ['@babel/preset-env', { targets: { node: 'current' } }], - '@babel/preset-typescript', - ], -} diff --git a/scripts/config/eslint.cjs b/eslint.base.cjs similarity index 100% rename from scripts/config/eslint.cjs rename to eslint.base.cjs diff --git a/jest.config.base.cjs b/jest.config.base.cjs deleted file mode 100644 index 11d8343d..00000000 --- a/jest.config.base.cjs +++ /dev/null @@ -1,8 +0,0 @@ -// Only contains simple config that does not reference third-party packages - -process.env.TZ = 'America/New_York' - -module.exports = { - // timers: 'modern', - // verbose: true, -} diff --git a/jest.config.cjs b/jest.config.cjs deleted file mode 100644 index 46fca186..00000000 --- a/jest.config.cjs +++ /dev/null @@ -1,12 +0,0 @@ -const base = require('./jest.config.base.cjs') - -module.exports = { - ...base, - projects: [ - '/packages/temporal-polyfill', - // '/packages/locale-weekinfo', - // '/packages/locale-textinfo', - // '/packages/datetimeformat-tokens', - // '/packages/durationformat-polyfill', - ], -} diff --git a/package.json b/package.json index c0e0afbc..0510525c 100644 --- a/package.json +++ b/package.json @@ -21,46 +21,26 @@ "scripts": { "preinstall": "npx only-allow pnpm", "ci": "pnpm run clean && pnpm run lint && pnpm run build && pnpm run test", - "build": "cd ./packages/temporal-polyfill && pnpm run build", - "build:dev": "cd ./packages/temporal-polyfill && pnpm run build:dev", - "watch": "cd ./packages/temporal-polyfill && pnpm run watch", - "test": "cd ./packages/temporal-polyfill && pnpm run test", - "test262": "cd ./packages/temporal-polyfill && pnpm run test262", - "lint": "cd ./packages/temporal-polyfill && pnpm run lint", - "size": "cd ./packages/temporal-polyfill && pnpm run size", - "clean": "cd ./packages/temporal-polyfill && pnpm run clean", - "locales-scrape": "node ./scripts/localesScrape.cjs", - "locales-compile": "echo 'TODO: recursively execute locales-compile'" + "build": "pnpm --filter temporal-polyfill run build", + "dev": "pnpm --filter temporal-polyfill run dev", + "test": "pnpm --filter temporal-polyfill run test", + "lint": "pnpm --filter temporal-polyfill run lint", + "clean": "pnpm --filter temporal-polyfill run clean" }, "devDependencies": { - "@babel/core": "^7.14.6", - "@babel/preset-env": "^7.14.5", - "@babel/preset-typescript": "^7.15.0", - "@rollup/plugin-node-resolve": "^13.3.0", - "@types/jest": "^26.0.23", "@typescript-eslint/eslint-plugin": "^4.22.1", "@typescript-eslint/parser": "^4.22.1", - "colors": "^1.4.0", - "deepmerge": "^4.2.2", - "esbuild": "^0.14.38", "eslint": "^7.25.0", "eslint-config-standard": "^16.0.3", "eslint-import-resolver-node": "^0.3.6", "eslint-plugin-import": "^2.24.2", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.0", - "jest": "^27.0.4", - "minimatch": "^5.0.1", - "rollup": "^2.55.1", - "rollup-plugin-dts": "^3.0.2", - "rollup-plugin-esbuild": "^4.9.1", - "rollup-plugin-terser": "^7.0.2", - "typescript": "^4.3.5", - "yargs": "^17.0.1" + "typescript": "^5.1.6" }, "pnpm": { "patchedDependencies": { - "@js-temporal/temporal-test262-runner@0.9.0": "patches/@js-temporal__temporal-test262-runner@0.9.0.patch" + "export-size@0.7.0": "patches/export-size@0.7.0.patch" } } } diff --git a/packages/datetimeformat-tokens/babel.config.cjs b/packages/datetimeformat-tokens/babel.config.cjs deleted file mode 100644 index 085ec4d0..00000000 --- a/packages/datetimeformat-tokens/babel.config.cjs +++ /dev/null @@ -1,4 +0,0 @@ - -module.exports = { - extends: '../../babel.config.cjs', -} diff --git a/packages/datetimeformat-tokens/jest.config.cjs b/packages/datetimeformat-tokens/jest.config.cjs deleted file mode 100644 index 6f34bce6..00000000 --- a/packages/datetimeformat-tokens/jest.config.cjs +++ /dev/null @@ -1,8 +0,0 @@ -const base = require('../../jest.config.base.cjs') - -module.exports = { - ...base, - roots: [ - '/src', - ], -} diff --git a/packages/datetimeformat-tokens/package.json b/packages/datetimeformat-tokens/package.json index 6fbab7ff..4e7ea0b0 100644 --- a/packages/datetimeformat-tokens/package.json +++ b/packages/datetimeformat-tokens/package.json @@ -35,8 +35,7 @@ "temporal-spec": "workspace:*" }, "devDependencies": { - "@types/jest": "^26.0.23", - "jest": "^27.0.4", + "locale-data": "workspace:*", "temporal-polyfill": "workspace:*" } } diff --git a/packages/durationformat-polyfill/babel.config.cjs b/packages/durationformat-polyfill/babel.config.cjs deleted file mode 100644 index 085ec4d0..00000000 --- a/packages/durationformat-polyfill/babel.config.cjs +++ /dev/null @@ -1,4 +0,0 @@ - -module.exports = { - extends: '../../babel.config.cjs', -} diff --git a/packages/durationformat-polyfill/jest.config.cjs b/packages/durationformat-polyfill/jest.config.cjs deleted file mode 100644 index 6f34bce6..00000000 --- a/packages/durationformat-polyfill/jest.config.cjs +++ /dev/null @@ -1,8 +0,0 @@ -const base = require('../../jest.config.base.cjs') - -module.exports = { - ...base, - roots: [ - '/src', - ], -} diff --git a/packages/durationformat-polyfill/package.json b/packages/durationformat-polyfill/package.json index 19175135..d0ed64b9 100644 --- a/packages/durationformat-polyfill/package.json +++ b/packages/durationformat-polyfill/package.json @@ -31,8 +31,6 @@ "temporal-spec": "workspace:*" }, "devDependencies": { - "@types/jest": "^26.0.23", - "jest": "^27.0.4", "temporal-polyfill": "workspace:*" } } diff --git a/scripts/data/fullcalendar b/packages/locale-data/data/fullcalendar similarity index 100% rename from scripts/data/fullcalendar rename to packages/locale-data/data/fullcalendar diff --git a/scripts/data/moment b/packages/locale-data/data/moment similarity index 100% rename from scripts/data/moment rename to packages/locale-data/data/moment diff --git a/locales/af.json b/packages/locale-data/locales/af.json similarity index 100% rename from locales/af.json rename to packages/locale-data/locales/af.json diff --git a/locales/ar-DZ.json b/packages/locale-data/locales/ar-DZ.json similarity index 100% rename from locales/ar-DZ.json rename to packages/locale-data/locales/ar-DZ.json diff --git a/locales/ar-KW.json b/packages/locale-data/locales/ar-KW.json similarity index 100% rename from locales/ar-KW.json rename to packages/locale-data/locales/ar-KW.json diff --git a/locales/ar-LY.json b/packages/locale-data/locales/ar-LY.json similarity index 100% rename from locales/ar-LY.json rename to packages/locale-data/locales/ar-LY.json diff --git a/locales/ar-MA.json b/packages/locale-data/locales/ar-MA.json similarity index 100% rename from locales/ar-MA.json rename to packages/locale-data/locales/ar-MA.json diff --git a/locales/ar-SA.json b/packages/locale-data/locales/ar-SA.json similarity index 100% rename from locales/ar-SA.json rename to packages/locale-data/locales/ar-SA.json diff --git a/locales/ar-TN.json b/packages/locale-data/locales/ar-TN.json similarity index 100% rename from locales/ar-TN.json rename to packages/locale-data/locales/ar-TN.json diff --git a/locales/ar.json b/packages/locale-data/locales/ar.json similarity index 100% rename from locales/ar.json rename to packages/locale-data/locales/ar.json diff --git a/locales/az.json b/packages/locale-data/locales/az.json similarity index 100% rename from locales/az.json rename to packages/locale-data/locales/az.json diff --git a/locales/be.json b/packages/locale-data/locales/be.json similarity index 100% rename from locales/be.json rename to packages/locale-data/locales/be.json diff --git a/locales/bg.json b/packages/locale-data/locales/bg.json similarity index 100% rename from locales/bg.json rename to packages/locale-data/locales/bg.json diff --git a/locales/bm.json b/packages/locale-data/locales/bm.json similarity index 100% rename from locales/bm.json rename to packages/locale-data/locales/bm.json diff --git a/locales/bn-BD.json b/packages/locale-data/locales/bn-BD.json similarity index 100% rename from locales/bn-BD.json rename to packages/locale-data/locales/bn-BD.json diff --git a/locales/bn.json b/packages/locale-data/locales/bn.json similarity index 100% rename from locales/bn.json rename to packages/locale-data/locales/bn.json diff --git a/locales/bo.json b/packages/locale-data/locales/bo.json similarity index 100% rename from locales/bo.json rename to packages/locale-data/locales/bo.json diff --git a/locales/br.json b/packages/locale-data/locales/br.json similarity index 100% rename from locales/br.json rename to packages/locale-data/locales/br.json diff --git a/locales/bs.json b/packages/locale-data/locales/bs.json similarity index 100% rename from locales/bs.json rename to packages/locale-data/locales/bs.json diff --git a/locales/ca.json b/packages/locale-data/locales/ca.json similarity index 100% rename from locales/ca.json rename to packages/locale-data/locales/ca.json diff --git a/locales/cs.json b/packages/locale-data/locales/cs.json similarity index 100% rename from locales/cs.json rename to packages/locale-data/locales/cs.json diff --git a/locales/cv.json b/packages/locale-data/locales/cv.json similarity index 100% rename from locales/cv.json rename to packages/locale-data/locales/cv.json diff --git a/locales/cy.json b/packages/locale-data/locales/cy.json similarity index 100% rename from locales/cy.json rename to packages/locale-data/locales/cy.json diff --git a/locales/da.json b/packages/locale-data/locales/da.json similarity index 100% rename from locales/da.json rename to packages/locale-data/locales/da.json diff --git a/locales/de-AT.json b/packages/locale-data/locales/de-AT.json similarity index 100% rename from locales/de-AT.json rename to packages/locale-data/locales/de-AT.json diff --git a/locales/de-CH.json b/packages/locale-data/locales/de-CH.json similarity index 100% rename from locales/de-CH.json rename to packages/locale-data/locales/de-CH.json diff --git a/locales/de.json b/packages/locale-data/locales/de.json similarity index 100% rename from locales/de.json rename to packages/locale-data/locales/de.json diff --git a/locales/dv.json b/packages/locale-data/locales/dv.json similarity index 100% rename from locales/dv.json rename to packages/locale-data/locales/dv.json diff --git a/locales/el.json b/packages/locale-data/locales/el.json similarity index 100% rename from locales/el.json rename to packages/locale-data/locales/el.json diff --git a/locales/en-AU.json b/packages/locale-data/locales/en-AU.json similarity index 100% rename from locales/en-AU.json rename to packages/locale-data/locales/en-AU.json diff --git a/locales/en-CA.json b/packages/locale-data/locales/en-CA.json similarity index 100% rename from locales/en-CA.json rename to packages/locale-data/locales/en-CA.json diff --git a/locales/en-GB.json b/packages/locale-data/locales/en-GB.json similarity index 100% rename from locales/en-GB.json rename to packages/locale-data/locales/en-GB.json diff --git a/locales/en-IE.json b/packages/locale-data/locales/en-IE.json similarity index 100% rename from locales/en-IE.json rename to packages/locale-data/locales/en-IE.json diff --git a/locales/en-IL.json b/packages/locale-data/locales/en-IL.json similarity index 100% rename from locales/en-IL.json rename to packages/locale-data/locales/en-IL.json diff --git a/locales/en-IN.json b/packages/locale-data/locales/en-IN.json similarity index 100% rename from locales/en-IN.json rename to packages/locale-data/locales/en-IN.json diff --git a/locales/en-NZ.json b/packages/locale-data/locales/en-NZ.json similarity index 100% rename from locales/en-NZ.json rename to packages/locale-data/locales/en-NZ.json diff --git a/locales/en-SG.json b/packages/locale-data/locales/en-SG.json similarity index 100% rename from locales/en-SG.json rename to packages/locale-data/locales/en-SG.json diff --git a/locales/en.json b/packages/locale-data/locales/en.json similarity index 100% rename from locales/en.json rename to packages/locale-data/locales/en.json diff --git a/locales/eo.json b/packages/locale-data/locales/eo.json similarity index 100% rename from locales/eo.json rename to packages/locale-data/locales/eo.json diff --git a/locales/es-DO.json b/packages/locale-data/locales/es-DO.json similarity index 100% rename from locales/es-DO.json rename to packages/locale-data/locales/es-DO.json diff --git a/locales/es-MX.json b/packages/locale-data/locales/es-MX.json similarity index 100% rename from locales/es-MX.json rename to packages/locale-data/locales/es-MX.json diff --git a/locales/es-US.json b/packages/locale-data/locales/es-US.json similarity index 100% rename from locales/es-US.json rename to packages/locale-data/locales/es-US.json diff --git a/locales/es.json b/packages/locale-data/locales/es.json similarity index 100% rename from locales/es.json rename to packages/locale-data/locales/es.json diff --git a/locales/et.json b/packages/locale-data/locales/et.json similarity index 100% rename from locales/et.json rename to packages/locale-data/locales/et.json diff --git a/locales/eu.json b/packages/locale-data/locales/eu.json similarity index 100% rename from locales/eu.json rename to packages/locale-data/locales/eu.json diff --git a/locales/fa.json b/packages/locale-data/locales/fa.json similarity index 100% rename from locales/fa.json rename to packages/locale-data/locales/fa.json diff --git a/locales/fi.json b/packages/locale-data/locales/fi.json similarity index 100% rename from locales/fi.json rename to packages/locale-data/locales/fi.json diff --git a/locales/fil.json b/packages/locale-data/locales/fil.json similarity index 100% rename from locales/fil.json rename to packages/locale-data/locales/fil.json diff --git a/locales/fo.json b/packages/locale-data/locales/fo.json similarity index 100% rename from locales/fo.json rename to packages/locale-data/locales/fo.json diff --git a/locales/fr-CA.json b/packages/locale-data/locales/fr-CA.json similarity index 100% rename from locales/fr-CA.json rename to packages/locale-data/locales/fr-CA.json diff --git a/locales/fr-CH.json b/packages/locale-data/locales/fr-CH.json similarity index 100% rename from locales/fr-CH.json rename to packages/locale-data/locales/fr-CH.json diff --git a/locales/fr.json b/packages/locale-data/locales/fr.json similarity index 100% rename from locales/fr.json rename to packages/locale-data/locales/fr.json diff --git a/locales/fy.json b/packages/locale-data/locales/fy.json similarity index 100% rename from locales/fy.json rename to packages/locale-data/locales/fy.json diff --git a/locales/ga.json b/packages/locale-data/locales/ga.json similarity index 100% rename from locales/ga.json rename to packages/locale-data/locales/ga.json diff --git a/locales/gd.json b/packages/locale-data/locales/gd.json similarity index 100% rename from locales/gd.json rename to packages/locale-data/locales/gd.json diff --git a/locales/gl.json b/packages/locale-data/locales/gl.json similarity index 100% rename from locales/gl.json rename to packages/locale-data/locales/gl.json diff --git a/locales/gom-DEVA.json b/packages/locale-data/locales/gom-DEVA.json similarity index 100% rename from locales/gom-DEVA.json rename to packages/locale-data/locales/gom-DEVA.json diff --git a/locales/gom-LATN.json b/packages/locale-data/locales/gom-LATN.json similarity index 100% rename from locales/gom-LATN.json rename to packages/locale-data/locales/gom-LATN.json diff --git a/locales/gu.json b/packages/locale-data/locales/gu.json similarity index 100% rename from locales/gu.json rename to packages/locale-data/locales/gu.json diff --git a/locales/he.json b/packages/locale-data/locales/he.json similarity index 100% rename from locales/he.json rename to packages/locale-data/locales/he.json diff --git a/locales/hi.json b/packages/locale-data/locales/hi.json similarity index 100% rename from locales/hi.json rename to packages/locale-data/locales/hi.json diff --git a/locales/hr.json b/packages/locale-data/locales/hr.json similarity index 100% rename from locales/hr.json rename to packages/locale-data/locales/hr.json diff --git a/locales/hu.json b/packages/locale-data/locales/hu.json similarity index 100% rename from locales/hu.json rename to packages/locale-data/locales/hu.json diff --git a/locales/hy-AM.json b/packages/locale-data/locales/hy-AM.json similarity index 100% rename from locales/hy-AM.json rename to packages/locale-data/locales/hy-AM.json diff --git a/locales/id.json b/packages/locale-data/locales/id.json similarity index 100% rename from locales/id.json rename to packages/locale-data/locales/id.json diff --git a/locales/is.json b/packages/locale-data/locales/is.json similarity index 100% rename from locales/is.json rename to packages/locale-data/locales/is.json diff --git a/locales/it-CH.json b/packages/locale-data/locales/it-CH.json similarity index 100% rename from locales/it-CH.json rename to packages/locale-data/locales/it-CH.json diff --git a/locales/it.json b/packages/locale-data/locales/it.json similarity index 100% rename from locales/it.json rename to packages/locale-data/locales/it.json diff --git a/locales/ja.json b/packages/locale-data/locales/ja.json similarity index 100% rename from locales/ja.json rename to packages/locale-data/locales/ja.json diff --git a/locales/jv.json b/packages/locale-data/locales/jv.json similarity index 100% rename from locales/jv.json rename to packages/locale-data/locales/jv.json diff --git a/locales/ka.json b/packages/locale-data/locales/ka.json similarity index 100% rename from locales/ka.json rename to packages/locale-data/locales/ka.json diff --git a/locales/kk.json b/packages/locale-data/locales/kk.json similarity index 100% rename from locales/kk.json rename to packages/locale-data/locales/kk.json diff --git a/locales/km.json b/packages/locale-data/locales/km.json similarity index 100% rename from locales/km.json rename to packages/locale-data/locales/km.json diff --git a/locales/kn.json b/packages/locale-data/locales/kn.json similarity index 100% rename from locales/kn.json rename to packages/locale-data/locales/kn.json diff --git a/locales/ko.json b/packages/locale-data/locales/ko.json similarity index 100% rename from locales/ko.json rename to packages/locale-data/locales/ko.json diff --git a/locales/ku.json b/packages/locale-data/locales/ku.json similarity index 100% rename from locales/ku.json rename to packages/locale-data/locales/ku.json diff --git a/locales/ky.json b/packages/locale-data/locales/ky.json similarity index 100% rename from locales/ky.json rename to packages/locale-data/locales/ky.json diff --git a/locales/lb.json b/packages/locale-data/locales/lb.json similarity index 100% rename from locales/lb.json rename to packages/locale-data/locales/lb.json diff --git a/locales/lo.json b/packages/locale-data/locales/lo.json similarity index 100% rename from locales/lo.json rename to packages/locale-data/locales/lo.json diff --git a/locales/lt.json b/packages/locale-data/locales/lt.json similarity index 100% rename from locales/lt.json rename to packages/locale-data/locales/lt.json diff --git a/locales/lv.json b/packages/locale-data/locales/lv.json similarity index 100% rename from locales/lv.json rename to packages/locale-data/locales/lv.json diff --git a/locales/me.json b/packages/locale-data/locales/me.json similarity index 100% rename from locales/me.json rename to packages/locale-data/locales/me.json diff --git a/locales/mi.json b/packages/locale-data/locales/mi.json similarity index 100% rename from locales/mi.json rename to packages/locale-data/locales/mi.json diff --git a/locales/mk.json b/packages/locale-data/locales/mk.json similarity index 100% rename from locales/mk.json rename to packages/locale-data/locales/mk.json diff --git a/locales/ml.json b/packages/locale-data/locales/ml.json similarity index 100% rename from locales/ml.json rename to packages/locale-data/locales/ml.json diff --git a/locales/mn.json b/packages/locale-data/locales/mn.json similarity index 100% rename from locales/mn.json rename to packages/locale-data/locales/mn.json diff --git a/locales/mr.json b/packages/locale-data/locales/mr.json similarity index 100% rename from locales/mr.json rename to packages/locale-data/locales/mr.json diff --git a/locales/ms-MY.json b/packages/locale-data/locales/ms-MY.json similarity index 100% rename from locales/ms-MY.json rename to packages/locale-data/locales/ms-MY.json diff --git a/locales/ms.json b/packages/locale-data/locales/ms.json similarity index 100% rename from locales/ms.json rename to packages/locale-data/locales/ms.json diff --git a/locales/mt.json b/packages/locale-data/locales/mt.json similarity index 100% rename from locales/mt.json rename to packages/locale-data/locales/mt.json diff --git a/locales/my.json b/packages/locale-data/locales/my.json similarity index 100% rename from locales/my.json rename to packages/locale-data/locales/my.json diff --git a/locales/nb.json b/packages/locale-data/locales/nb.json similarity index 100% rename from locales/nb.json rename to packages/locale-data/locales/nb.json diff --git a/locales/ne.json b/packages/locale-data/locales/ne.json similarity index 100% rename from locales/ne.json rename to packages/locale-data/locales/ne.json diff --git a/locales/nl-BE.json b/packages/locale-data/locales/nl-BE.json similarity index 100% rename from locales/nl-BE.json rename to packages/locale-data/locales/nl-BE.json diff --git a/locales/nl.json b/packages/locale-data/locales/nl.json similarity index 100% rename from locales/nl.json rename to packages/locale-data/locales/nl.json diff --git a/locales/nn.json b/packages/locale-data/locales/nn.json similarity index 100% rename from locales/nn.json rename to packages/locale-data/locales/nn.json diff --git a/locales/oc-LNC.json b/packages/locale-data/locales/oc-LNC.json similarity index 100% rename from locales/oc-LNC.json rename to packages/locale-data/locales/oc-LNC.json diff --git a/locales/pa-IN.json b/packages/locale-data/locales/pa-IN.json similarity index 100% rename from locales/pa-IN.json rename to packages/locale-data/locales/pa-IN.json diff --git a/locales/pl.json b/packages/locale-data/locales/pl.json similarity index 100% rename from locales/pl.json rename to packages/locale-data/locales/pl.json diff --git a/locales/pt-BR.json b/packages/locale-data/locales/pt-BR.json similarity index 100% rename from locales/pt-BR.json rename to packages/locale-data/locales/pt-BR.json diff --git a/locales/pt.json b/packages/locale-data/locales/pt.json similarity index 100% rename from locales/pt.json rename to packages/locale-data/locales/pt.json diff --git a/locales/ro.json b/packages/locale-data/locales/ro.json similarity index 100% rename from locales/ro.json rename to packages/locale-data/locales/ro.json diff --git a/locales/ru.json b/packages/locale-data/locales/ru.json similarity index 100% rename from locales/ru.json rename to packages/locale-data/locales/ru.json diff --git a/locales/sd.json b/packages/locale-data/locales/sd.json similarity index 100% rename from locales/sd.json rename to packages/locale-data/locales/sd.json diff --git a/locales/se.json b/packages/locale-data/locales/se.json similarity index 100% rename from locales/se.json rename to packages/locale-data/locales/se.json diff --git a/locales/si.json b/packages/locale-data/locales/si.json similarity index 100% rename from locales/si.json rename to packages/locale-data/locales/si.json diff --git a/locales/sk.json b/packages/locale-data/locales/sk.json similarity index 100% rename from locales/sk.json rename to packages/locale-data/locales/sk.json diff --git a/locales/sl.json b/packages/locale-data/locales/sl.json similarity index 100% rename from locales/sl.json rename to packages/locale-data/locales/sl.json diff --git a/locales/sq.json b/packages/locale-data/locales/sq.json similarity index 100% rename from locales/sq.json rename to packages/locale-data/locales/sq.json diff --git a/locales/sr-CYRL.json b/packages/locale-data/locales/sr-CYRL.json similarity index 100% rename from locales/sr-CYRL.json rename to packages/locale-data/locales/sr-CYRL.json diff --git a/locales/sr.json b/packages/locale-data/locales/sr.json similarity index 100% rename from locales/sr.json rename to packages/locale-data/locales/sr.json diff --git a/locales/ss.json b/packages/locale-data/locales/ss.json similarity index 100% rename from locales/ss.json rename to packages/locale-data/locales/ss.json diff --git a/locales/sv.json b/packages/locale-data/locales/sv.json similarity index 100% rename from locales/sv.json rename to packages/locale-data/locales/sv.json diff --git a/locales/sw.json b/packages/locale-data/locales/sw.json similarity index 100% rename from locales/sw.json rename to packages/locale-data/locales/sw.json diff --git a/locales/ta.json b/packages/locale-data/locales/ta.json similarity index 100% rename from locales/ta.json rename to packages/locale-data/locales/ta.json diff --git a/locales/te.json b/packages/locale-data/locales/te.json similarity index 100% rename from locales/te.json rename to packages/locale-data/locales/te.json diff --git a/locales/tet.json b/packages/locale-data/locales/tet.json similarity index 100% rename from locales/tet.json rename to packages/locale-data/locales/tet.json diff --git a/locales/tg.json b/packages/locale-data/locales/tg.json similarity index 100% rename from locales/tg.json rename to packages/locale-data/locales/tg.json diff --git a/locales/th.json b/packages/locale-data/locales/th.json similarity index 100% rename from locales/th.json rename to packages/locale-data/locales/th.json diff --git a/locales/tk.json b/packages/locale-data/locales/tk.json similarity index 100% rename from locales/tk.json rename to packages/locale-data/locales/tk.json diff --git a/locales/tl-PH.json b/packages/locale-data/locales/tl-PH.json similarity index 100% rename from locales/tl-PH.json rename to packages/locale-data/locales/tl-PH.json diff --git a/locales/tlh.json b/packages/locale-data/locales/tlh.json similarity index 100% rename from locales/tlh.json rename to packages/locale-data/locales/tlh.json diff --git a/locales/tr.json b/packages/locale-data/locales/tr.json similarity index 100% rename from locales/tr.json rename to packages/locale-data/locales/tr.json diff --git a/locales/tzl.json b/packages/locale-data/locales/tzl.json similarity index 100% rename from locales/tzl.json rename to packages/locale-data/locales/tzl.json diff --git a/locales/tzm-LATN.json b/packages/locale-data/locales/tzm-LATN.json similarity index 100% rename from locales/tzm-LATN.json rename to packages/locale-data/locales/tzm-LATN.json diff --git a/locales/tzm.json b/packages/locale-data/locales/tzm.json similarity index 100% rename from locales/tzm.json rename to packages/locale-data/locales/tzm.json diff --git a/locales/ug-CN.json b/packages/locale-data/locales/ug-CN.json similarity index 100% rename from locales/ug-CN.json rename to packages/locale-data/locales/ug-CN.json diff --git a/locales/uk.json b/packages/locale-data/locales/uk.json similarity index 100% rename from locales/uk.json rename to packages/locale-data/locales/uk.json diff --git a/locales/ur.json b/packages/locale-data/locales/ur.json similarity index 100% rename from locales/ur.json rename to packages/locale-data/locales/ur.json diff --git a/locales/uz-LATN.json b/packages/locale-data/locales/uz-LATN.json similarity index 100% rename from locales/uz-LATN.json rename to packages/locale-data/locales/uz-LATN.json diff --git a/locales/uz.json b/packages/locale-data/locales/uz.json similarity index 100% rename from locales/uz.json rename to packages/locale-data/locales/uz.json diff --git a/locales/vi.json b/packages/locale-data/locales/vi.json similarity index 100% rename from locales/vi.json rename to packages/locale-data/locales/vi.json diff --git a/locales/x-PSEUDO.json b/packages/locale-data/locales/x-PSEUDO.json similarity index 100% rename from locales/x-PSEUDO.json rename to packages/locale-data/locales/x-PSEUDO.json diff --git a/locales/yo.json b/packages/locale-data/locales/yo.json similarity index 100% rename from locales/yo.json rename to packages/locale-data/locales/yo.json diff --git a/locales/zh-CN.json b/packages/locale-data/locales/zh-CN.json similarity index 100% rename from locales/zh-CN.json rename to packages/locale-data/locales/zh-CN.json diff --git a/locales/zh-HK.json b/packages/locale-data/locales/zh-HK.json similarity index 100% rename from locales/zh-HK.json rename to packages/locale-data/locales/zh-HK.json diff --git a/locales/zh-MO.json b/packages/locale-data/locales/zh-MO.json similarity index 100% rename from locales/zh-MO.json rename to packages/locale-data/locales/zh-MO.json diff --git a/locales/zh-TW.json b/packages/locale-data/locales/zh-TW.json similarity index 100% rename from locales/zh-TW.json rename to packages/locale-data/locales/zh-TW.json diff --git a/packages/locale-data/package.json b/packages/locale-data/package.json new file mode 100644 index 00000000..7229d4a5 --- /dev/null +++ b/packages/locale-data/package.json @@ -0,0 +1,9 @@ +{ + "name": "locale-data", + "version": "0.0.0", + "dependencies": { + "colors": "^1.4.0", + "deepmerge": "^4.2.2", + "yargs": "^17.0.1" + } +} diff --git a/scripts/lib/localesList.cjs b/packages/locale-data/scripts/lib/localesList.cjs similarity index 100% rename from scripts/lib/localesList.cjs rename to packages/locale-data/scripts/lib/localesList.cjs diff --git a/scripts/localesScrape.cjs b/packages/locale-data/scripts/localesScrape.cjs old mode 100644 new mode 100755 similarity index 94% rename from scripts/localesScrape.cjs rename to packages/locale-data/scripts/localesScrape.cjs index b8b23f8d..95818bfc --- a/scripts/localesScrape.cjs +++ b/packages/locale-data/scripts/localesScrape.cjs @@ -1,3 +1,5 @@ +#!/usr/bin/env node + const { existsSync, readdirSync } = require('fs') const { readFile, writeFile } = require('fs/promises') const { resolve } = require('path') @@ -8,12 +10,8 @@ const { hideBin } = require('yargs/helpers') require('colors') const args = yargs(hideBin(process.argv)).boolean('v').argv -const momentLocaleRoot = resolve(args.$0, '..', 'data/moment/locale') -const fullcalendarLocaleRoot = resolve( - args.$0, - '..', - 'data/fullcalendar/packages/core/src/locales', -) +const momentLocaleRoot = resolve(args.$0, '../data/moment/locale') +const fullcalendarLocaleRoot = resolve(args.$0, '../data/fullcalendar/packages/core/src/locales') async function writeLocale(localeStr) { // Used for specific parsing nuances, intlStr represents the locale with the suffix capitalized @@ -89,7 +87,7 @@ async function writeLocale(localeStr) { const workspaceLocalePath = resolve( args.$0, - '../../locales', + '../locales', `${intlStr}.json`, ) @@ -118,7 +116,7 @@ async function writeLocale(localeStr) { // Write to file await writeFile( - resolve(args.$0, '../../locales', `${intlStr}.json`), + resolve(args.$0, '../locales', `${intlStr}.json`), JSON.stringify(localeData, null, 2), { encoding: 'utf8', flag: 'w' }, ) diff --git a/packages/locale-textinfo/babel.config.cjs b/packages/locale-textinfo/babel.config.cjs deleted file mode 100644 index 085ec4d0..00000000 --- a/packages/locale-textinfo/babel.config.cjs +++ /dev/null @@ -1,4 +0,0 @@ - -module.exports = { - extends: '../../babel.config.cjs', -} diff --git a/packages/locale-textinfo/jest.config.cjs b/packages/locale-textinfo/jest.config.cjs deleted file mode 100644 index 6f34bce6..00000000 --- a/packages/locale-textinfo/jest.config.cjs +++ /dev/null @@ -1,8 +0,0 @@ -const base = require('../../jest.config.base.cjs') - -module.exports = { - ...base, - roots: [ - '/src', - ], -} diff --git a/packages/locale-textinfo/package.json b/packages/locale-textinfo/package.json index f271410a..5fa265ea 100644 --- a/packages/locale-textinfo/package.json +++ b/packages/locale-textinfo/package.json @@ -32,7 +32,6 @@ "locales-compile": "pnpm run compile-directions" }, "devDependencies": { - "@types/jest": "^26.0.23", - "jest": "^27.0.4" + "locale-data": "workspace:*" } } diff --git a/packages/locale-weekinfo/babel.config.cjs b/packages/locale-weekinfo/babel.config.cjs deleted file mode 100644 index 085ec4d0..00000000 --- a/packages/locale-weekinfo/babel.config.cjs +++ /dev/null @@ -1,4 +0,0 @@ - -module.exports = { - extends: '../../babel.config.cjs', -} diff --git a/packages/locale-weekinfo/jest.config.cjs b/packages/locale-weekinfo/jest.config.cjs deleted file mode 100644 index 6f34bce6..00000000 --- a/packages/locale-weekinfo/jest.config.cjs +++ /dev/null @@ -1,8 +0,0 @@ -const base = require('../../jest.config.base.cjs') - -module.exports = { - ...base, - roots: [ - '/src', - ], -} diff --git a/packages/locale-weekinfo/package.json b/packages/locale-weekinfo/package.json index 0c8d77e1..51a4c7a0 100644 --- a/packages/locale-weekinfo/package.json +++ b/packages/locale-weekinfo/package.json @@ -33,7 +33,6 @@ "locales-compile": "pnpm compile-firstDays && pnpm compile-minimalDays" }, "devDependencies": { - "@types/jest": "^26.0.23", - "jest": "^27.0.4" + "locale-data": "workspace:*" } } diff --git a/packages/temporal-polyfill/.eslintrc.cjs b/packages/temporal-polyfill/.eslintrc.cjs deleted file mode 100644 index 2a75cc97..00000000 --- a/packages/temporal-polyfill/.eslintrc.cjs +++ /dev/null @@ -1,2 +0,0 @@ - -module.exports = require('../../scripts/config/eslint.cjs') diff --git a/packages/temporal-polyfill/.eslintrc.cjs.disabled b/packages/temporal-polyfill/.eslintrc.cjs.disabled new file mode 100644 index 00000000..9d9447f7 --- /dev/null +++ b/packages/temporal-polyfill/.eslintrc.cjs.disabled @@ -0,0 +1,2 @@ + +module.exports = require('../../eslint.base.cjs') diff --git a/packages/temporal-polyfill/README.md b/packages/temporal-polyfill/README.md deleted file mode 100644 index 82b17a28..00000000 --- a/packages/temporal-polyfill/README.md +++ /dev/null @@ -1,74 +0,0 @@ - -# temporal-polyfill - -A spec-compliant [Temporal] JavaScript polyfill in 16kb. - -[Codepen](https://codepen.io/arshaw/pen/VwrMQPJ?editors=1111) - -[CDN link](https://cdn.jsdelivr.net/npm/temporal-polyfill@0.0.8/dist/global.js) - - - -## Browser Support - -| Minimum required version if... | Chrome | Edge | Safari | Safari iOS | Firefox | Node | -| --- | --- | --- | --- | --- | --- | --- | -| ...transpiling to target environment | 36 (Jul 2014) | 14 (Aug 2016) | 10 (Sep 2016) | 10 (Sep 2016) | 52 (Mar 2017) | 12 (Apr 2019) | -| ...leaving untranspiled | 60 (Jul 2017) | 79 (Jan 2020) | 11.1 (Mar 2018) | 11.3 (Mar 2018) | 55 (Aug 2017) | 12 (Apr 2019) | -| ...using BigInt features | 67 (May 2018) | 79 (Jan 2020) | 14 (Sep 2020) | 14 (Sep 2020) | 68 (July 2019) | 12 (Apr 2019) | - - -## BigInt Considerations - -This polyfill works fine in environments without [BigInt]. However, without BigInt, the following will throw errors: - -| ❌ Avoid microseconds/nanoseconds | ✅ Use milliseconds instead | -| -------------------------------- | --------------------------- | -| `instant.epochMicroseconds` | `instant.epochMilliseconds` | -| `instant.epochNanoseconds` | `instant.epochMilliseconds` | -| `Temporal.Instant.fromEpochMicroseconds(micro)` | `Temporal.Instant.fromEpochMilliseconds(milli)` | -| `Temporal.Instant.fromEpochNanoseconds(nano)` | `Temporal.Instant.fromEpochMilliseconds(milli)` | -| `new Temporal.Instant(nano)` | `Temporal.Instant.fromEpochMilliseconds(milli)` | -| `zonedDateTime.epochMicroseconds` | `zonedDateTime.epochMilliseconds` | -| `zonedDateTime.epochNanoseconds` | `zonedDateTime.epochMilliseconds` | -| `new Temporal.ZonedDateTime(nano, tz, cal)` | `Temporal.Instant.fromEpochMilliseconds(milli).toZonedDateTimeISO()` | -| | `Temporal.Instant.fromEpochMilliseconds(milli).toZonedDateTime(cal)` | - -FYI, it is not possible to properly polyfill BigInt. [More Information][JSBI-why] - - -## Installation - -``` -npm install temporal-polyfill -``` - -**A) Import globally:** - -```js -import 'temporal-polyfill/global' - -console.log(Temporal.Now.zonedDateTimeISO().toString()) -``` - -**B) Import as an ES module** without side effects: - -```js -import { Temporal } from 'temporal-polyfill' - -console.log(Temporal.Now.zonedDateTimeISO().toString()) -``` - -**C)** The above techniques try using the built-in `Temporal` object and fall back to the polyfill. -To guarantee using the polyfill, do this: - -```js -import { Temporal } from 'temporal-polyfill/impl' - -console.log(Temporal.Now.zonedDateTimeISO().toString()) -``` - - -[Temporal]: https://github.com/tc39/proposal-temporal -[BigInt]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt -[JSBI-why]: https://github.com/GoogleChromeLabs/jsbi#why diff --git a/packages/temporal-polyfill/babel.config.cjs b/packages/temporal-polyfill/babel.config.cjs deleted file mode 100644 index 085ec4d0..00000000 --- a/packages/temporal-polyfill/babel.config.cjs +++ /dev/null @@ -1,4 +0,0 @@ - -module.exports = { - extends: '../../babel.config.cjs', -} diff --git a/packages/temporal-polyfill/dist/.npmignore b/packages/temporal-polyfill/dist/.npmignore new file mode 100644 index 00000000..e8e373a5 --- /dev/null +++ b/packages/temporal-polyfill/dist/.npmignore @@ -0,0 +1,2 @@ +.tsc +tsconfig.tsbuildinfo diff --git a/packages/temporal-polyfill/jest-setup.js b/packages/temporal-polyfill/jest-setup.js deleted file mode 100644 index 876f6ea6..00000000 --- a/packages/temporal-polyfill/jest-setup.js +++ /dev/null @@ -1,21 +0,0 @@ - -/* -Shims to make tests from @js-temporal/temporal-polyfill conform to Jest -*/ - -/* -Ignore the returned value of test. The tests often do arrow functions like this: - it('test name', () => doSomething()) -*/ -const origTest = global.test -global.test = global.it = function(name, fn, timeout) { - origTest(name, function() { - fn() // don't forward return value - }, timeout) -} - -/* -Simple aliases -*/ -global.before = global.beforeEach -global.after = global.afterEach diff --git a/packages/temporal-polyfill/jest.config.cjs b/packages/temporal-polyfill/jest.config.cjs deleted file mode 100644 index 8baae5b1..00000000 --- a/packages/temporal-polyfill/jest.config.cjs +++ /dev/null @@ -1,16 +0,0 @@ -const base = require('../../jest.config.base.cjs') - -module.exports = { - ...base, - setupFilesAfterEnv: [ - '/jest-setup.js', - ], - moduleNameMapper: { - // TODO: supply the built file when in CI mode - // TODO: way to test built files? - 'temporal-polyfill/impl': '/src/impl.build.ts', - }, - testMatch: [ - '/tests/**/*.js', - ], -} diff --git a/packages/temporal-polyfill/misc/LICENSE.txt b/packages/temporal-polyfill/misc/LICENSE.txt deleted file mode 100644 index 75c4ded4..00000000 --- a/packages/temporal-polyfill/misc/LICENSE.txt +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2017, 2018, 2019, 2020 ECMA International - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. diff --git a/packages/temporal-polyfill/misc/expected-failures-before-node16.txt b/packages/temporal-polyfill/misc/expected-failures-before-node16.txt deleted file mode 100644 index 624162ab..00000000 --- a/packages/temporal-polyfill/misc/expected-failures-before-node16.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Before Node 16, dateStyle/timeStyle options didn't conflict with other options -intl402/Temporal/Instant/prototype/toLocaleString/options-conflict.js -intl402/Temporal/PlainDate/prototype/toLocaleString/options-conflict.js -intl402/Temporal/PlainDateTime/prototype/toLocaleString/options-conflict.js -intl402/Temporal/PlainTime/prototype/toLocaleString/options-conflict.js -intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-conflict.js - -# Before Node 16, calling an uncallable value seems to throw a RangeError, not a TypeError -intl402/Temporal/PlainYearMonth/prototype/toLocaleString/timezone-getoffsetnanosecondsfor-not-callable.js -intl402/Temporal/PlainMonthDay/prototype/toLocaleString/timezone-getoffsetnanosecondsfor-not-callable.js -intl402/DateTimeFormat/prototype/format/temporal-objects-timezone-getoffsetnanosecondsfor-not-callable.js -intl402/DateTimeFormat/prototype/formatRange/temporal-objects-timezone-getoffsetnanosecondsfor-not-callable.js -intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-objects-timezone-getoffsetnanosecondsfor-not-callable.js -intl402/DateTimeFormat/prototype/formatToParts/temporal-objects-timezone-getoffsetnanosecondsfor-not-callable.js - -# For some reason, these tests (which are identical to the ones above) do pass. -# TODO(issue 213): check if this could be because of a bug. -# intl402/Temporal/PlainDate/prototype/toLocaleString/timezone-getoffsetnanosecondsfor-not-callable.js -# intl402/Temporal/PlainDateTime/prototype/toLocaleString/timezone-getoffsetnanosecondsfor-not-callable.js -# intl402/Temporal/PlainTime/prototype/toLocaleString/timezone-getoffsetnanosecondsfor-not-callable.js - diff --git a/packages/temporal-polyfill/misc/expected-failures-before-node18.txt b/packages/temporal-polyfill/misc/expected-failures-before-node18.txt deleted file mode 100644 index 69367f62..00000000 --- a/packages/temporal-polyfill/misc/expected-failures-before-node18.txt +++ /dev/null @@ -1,4 +0,0 @@ -# Intl.supportedValuesOf("timeZone") is only available starting in Node 18 -intl402/Temporal/TimeZone/supported-values-of.js -intl402/Temporal/TimeZone/prototype/getNextTransition/transition-at-instant-boundaries.js -intl402/Temporal/TimeZone/prototype/getPreviousTransition/transition-at-instant-boundaries.js diff --git a/packages/temporal-polyfill/misc/expected-failures-opt.txt b/packages/temporal-polyfill/misc/expected-failures-opt.txt deleted file mode 100644 index e3f1ca16..00000000 --- a/packages/temporal-polyfill/misc/expected-failures-opt.txt +++ /dev/null @@ -1,66 +0,0 @@ -# Failures in this file only apply to the optimized, but not transpiled, -# polyfill sources. - -# This is an issue with Terser optimizing away default parameters -# See https://github.com/terser/terser/issues/1115 -built-ins/Temporal/Duration/compare/length.js -built-ins/Temporal/PlainDate/from/length.js -built-ins/Temporal/PlainDateTime/from/length.js -built-ins/Temporal/PlainMonthDay/from/length.js -built-ins/Temporal/PlainTime/from/length.js -built-ins/Temporal/PlainYearMonth/from/length.js -built-ins/Temporal/ZonedDateTime/from/length.js -built-ins/Temporal/Calendar/prototype/dateAdd/length.js -built-ins/Temporal/Calendar/prototype/dateFromFields/length.js -built-ins/Temporal/Calendar/prototype/dateUntil/length.js -built-ins/Temporal/Calendar/prototype/monthDayFromFields/length.js -built-ins/Temporal/Calendar/prototype/yearMonthFromFields/length.js -built-ins/Temporal/Duration/prototype/add/length.js -built-ins/Temporal/Duration/prototype/subtract/length.js -built-ins/Temporal/Duration/prototype/toLocaleString/length.js -built-ins/Temporal/Duration/prototype/toString/length.js -built-ins/Temporal/Instant/prototype/since/length.js -built-ins/Temporal/Instant/prototype/toLocaleString/length.js -built-ins/Temporal/Instant/prototype/toString/length.js -built-ins/Temporal/Instant/prototype/until/length.js -built-ins/Temporal/PlainDate/prototype/add/length.js -built-ins/Temporal/PlainDate/prototype/since/length.js -built-ins/Temporal/PlainDate/prototype/subtract/length.js -built-ins/Temporal/PlainDate/prototype/toLocaleString/length.js -built-ins/Temporal/PlainDate/prototype/toPlainDateTime/length.js -built-ins/Temporal/PlainDate/prototype/toString/length.js -built-ins/Temporal/PlainDate/prototype/until/length.js -built-ins/Temporal/PlainDate/prototype/with/length.js -built-ins/Temporal/PlainDateTime/prototype/add/length.js -built-ins/Temporal/PlainDateTime/prototype/since/length.js -built-ins/Temporal/PlainDateTime/prototype/subtract/length.js -built-ins/Temporal/PlainDateTime/prototype/toLocaleString/length.js -built-ins/Temporal/PlainDateTime/prototype/toString/length.js -built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/length.js -built-ins/Temporal/PlainDateTime/prototype/until/length.js -built-ins/Temporal/PlainDateTime/prototype/with/length.js -built-ins/Temporal/PlainDateTime/prototype/withPlainTime/length.js -built-ins/Temporal/PlainMonthDay/prototype/toLocaleString/length.js -built-ins/Temporal/PlainMonthDay/prototype/toString/length.js -built-ins/Temporal/PlainMonthDay/prototype/with/length.js -built-ins/Temporal/PlainTime/prototype/since/length.js -built-ins/Temporal/PlainTime/prototype/toLocaleString/length.js -built-ins/Temporal/PlainTime/prototype/toString/length.js -built-ins/Temporal/PlainTime/prototype/until/length.js -built-ins/Temporal/PlainTime/prototype/with/length.js -built-ins/Temporal/PlainYearMonth/prototype/add/length.js -built-ins/Temporal/PlainYearMonth/prototype/since/length.js -built-ins/Temporal/PlainYearMonth/prototype/subtract/length.js -built-ins/Temporal/PlainYearMonth/prototype/toLocaleString/length.js -built-ins/Temporal/PlainYearMonth/prototype/toString/length.js -built-ins/Temporal/PlainYearMonth/prototype/until/length.js -built-ins/Temporal/PlainYearMonth/prototype/with/length.js -built-ins/Temporal/TimeZone/prototype/getInstantFor/length.js -built-ins/Temporal/ZonedDateTime/prototype/add/length.js -built-ins/Temporal/ZonedDateTime/prototype/since/length.js -built-ins/Temporal/ZonedDateTime/prototype/subtract/length.js -built-ins/Temporal/ZonedDateTime/prototype/toLocaleString/length.js -built-ins/Temporal/ZonedDateTime/prototype/toString/length.js -built-ins/Temporal/ZonedDateTime/prototype/until/length.js -built-ins/Temporal/ZonedDateTime/prototype/with/length.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/length.js diff --git a/packages/temporal-polyfill/misc/expected-failures-todo-migrated-code.txt b/packages/temporal-polyfill/misc/expected-failures-todo-migrated-code.txt deleted file mode 100644 index 064cec81..00000000 --- a/packages/temporal-polyfill/misc/expected-failures-todo-migrated-code.txt +++ /dev/null @@ -1,692 +0,0 @@ -# Failures in this file will be fixed after migrating the latest polyfill changes -# from proposal-temporal. - -built-ins/Temporal/Calendar/from/calendar-case-insensitive.js -built-ins/Temporal/Calendar/from/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/from/calendar-wrong-type.js -built-ins/Temporal/Calendar/prototype/dateAdd/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/Calendar/prototype/dateAdd/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/dateAdd/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/dateAdd/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/dateAdd/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/dateAdd/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/dateAdd/order-of-operations.js -built-ins/Temporal/Calendar/prototype/dateUntil/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/dateUntil/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/dateUntil/argument-propertybag-calendar-wrong-type.js -built-ins/Temporal/Calendar/prototype/dateUntil/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/dateUntil/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/dateUntil/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/dateUntil/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/dateUntil/order-of-operations.js -built-ins/Temporal/Calendar/prototype/day/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/day/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/Calendar/prototype/day/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/day/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/day/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/day/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/day/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/dayOfYear/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/dayOfYear/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/Calendar/prototype/dayOfYear/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/dayOfYear/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/dayOfYear/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/dayOfYear/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/dayOfYear/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/daysInMonth/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/daysInMonth/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/Calendar/prototype/daysInMonth/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/daysInMonth/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/daysInMonth/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/daysInMonth/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/daysInMonth/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/daysInWeek/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/daysInWeek/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/Calendar/prototype/daysInWeek/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/daysInWeek/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/daysInWeek/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/daysInWeek/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/daysInWeek/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/daysInYear/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/daysInYear/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/Calendar/prototype/daysInYear/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/daysInYear/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/daysInYear/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/daysInYear/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/daysInYear/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/id/custom-calendar.js -built-ins/Temporal/Calendar/prototype/inLeapYear/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/inLeapYear/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/Calendar/prototype/inLeapYear/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/inLeapYear/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/inLeapYear/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/inLeapYear/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/inLeapYear/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/mergeFields/non-string-properties.js -built-ins/Temporal/Calendar/prototype/mergeFields/order-of-operations.js -built-ins/Temporal/Calendar/prototype/month/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/month/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/Calendar/prototype/month/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/month/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/month/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/month/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/month/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/monthCode/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/monthCode/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/Calendar/prototype/monthCode/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/monthCode/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/monthCode/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/monthCode/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/monthCode/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/monthsInYear/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/monthsInYear/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/Calendar/prototype/monthsInYear/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/monthsInYear/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/monthsInYear/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/monthsInYear/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/monthsInYear/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/weekOfYear/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/weekOfYear/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/Calendar/prototype/weekOfYear/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/weekOfYear/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/weekOfYear/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/weekOfYear/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/weekOfYear/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/year/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/year/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/Calendar/prototype/year/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/year/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/year/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/year/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/year/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-calendar-datefromfields-called-with-null-prototype-fields.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-calendar-fields-undefined.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-leap-second.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-number.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-leap-second.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-number.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-string.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-wrong-type.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-propertybag-calendar-year-zero.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string-calendar-annotation.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string-critical-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string-date-with-utc-offset.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string-invalid.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string-multiple-time-zone.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string-time-separators.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string-time-zone-annotation.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string-unknown-annotation.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string-with-utc-designator.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-string.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-wrong-type.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-zoneddatetime-convert.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-zoneddatetime-slots.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-non-integer.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-zoneddatetime-timezone-getoffsetnanosecondsfor-out-of-range.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/basic.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/branding.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/builtin.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/calendar-datefromfields-called-with-options-undefined.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/calendar-fields-iterable.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/calendar-temporal-object.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/cross-year.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/infinity-throws-rangeerror.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/length.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/name.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/prop-desc.js -built-ins/Temporal/Calendar/prototype/yearOfWeek/year-zero.js -built-ins/Temporal/Duration/compare/order-of-operations.js -built-ins/Temporal/Duration/compare/relativeto-propertybag-timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Duration/compare/relativeto-propertybag-timezone-wrong-type.js -built-ins/Temporal/Duration/prototype/add/order-of-operations.js -built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-calendar-fields-undefined.js -built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-calendar-wrong-type.js -built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-timezone-wrong-type.js -built-ins/Temporal/Duration/prototype/add/relativeto-string-datetime.js -built-ins/Temporal/Duration/prototype/round/nanoseconds-to-days-loop-indefinitely-2.js -built-ins/Temporal/Duration/prototype/round/nanoseconds-to-days-precision-exact-mathematical-values-1.js -built-ins/Temporal/Duration/prototype/round/nanoseconds-to-days-precision-exact-mathematical-values-2.js -built-ins/Temporal/Duration/prototype/round/order-of-operations.js -built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-calendar-fields-undefined.js -built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-calendar-wrong-type.js -built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-timezone-wrong-type.js -built-ins/Temporal/Duration/prototype/round/relativeto-string-datetime.js -built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js -built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-calendar-fields-undefined.js -built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-calendar-wrong-type.js -built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-timezone-wrong-type.js -built-ins/Temporal/Duration/prototype/subtract/relativeto-string-datetime.js -built-ins/Temporal/Duration/prototype/toString/fractionalseconddigits-non-integer.js -built-ins/Temporal/Duration/prototype/toString/order-of-operations.js -built-ins/Temporal/Duration/prototype/total/order-of-operations.js -built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-1.js -built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-2.js -built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-calendar-fields-undefined.js -built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-calendar-wrong-type.js -built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-timezone-wrong-type.js -built-ins/Temporal/Duration/prototype/total/relativeto-string-datetime.js -built-ins/Temporal/Instant/compare/argument-string-calendar-annotation.js -built-ins/Temporal/Instant/compare/argument-string-date-with-utc-offset.js -built-ins/Temporal/Instant/compare/argument-string-time-zone-annotation.js -built-ins/Temporal/Instant/compare/argument-string-unknown-annotation.js -built-ins/Temporal/Instant/from/argument-string-calendar-annotation.js -built-ins/Temporal/Instant/from/argument-string-date-with-utc-offset.js -built-ins/Temporal/Instant/from/argument-string-time-zone-annotation.js -built-ins/Temporal/Instant/from/argument-string-unknown-annotation.js -built-ins/Temporal/Instant/prototype/equals/argument-string-calendar-annotation.js -built-ins/Temporal/Instant/prototype/equals/argument-string-date-with-utc-offset.js -built-ins/Temporal/Instant/prototype/equals/argument-string-time-zone-annotation.js -built-ins/Temporal/Instant/prototype/equals/argument-string-unknown-annotation.js -built-ins/Temporal/Instant/prototype/since/argument-string-calendar-annotation.js -built-ins/Temporal/Instant/prototype/since/argument-string-date-with-utc-offset.js -built-ins/Temporal/Instant/prototype/since/argument-string-time-zone-annotation.js -built-ins/Temporal/Instant/prototype/since/argument-string-unknown-annotation.js -built-ins/Temporal/Instant/prototype/since/order-of-operations.js -built-ins/Temporal/Instant/prototype/toString/fractionalseconddigits-non-integer.js -built-ins/Temporal/Instant/prototype/toString/order-of-operations.js -built-ins/Temporal/Instant/prototype/toString/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Instant/prototype/toString/timezone-wrong-type.js -built-ins/Temporal/Instant/prototype/toZonedDateTime/calendar-case-insensitive.js -built-ins/Temporal/Instant/prototype/toZonedDateTime/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Instant/prototype/toZonedDateTime/calendar-wrong-type.js -built-ins/Temporal/Instant/prototype/toZonedDateTime/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Instant/prototype/toZonedDateTime/timezone-wrong-type.js -built-ins/Temporal/Instant/prototype/toZonedDateTimeISO/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Instant/prototype/toZonedDateTimeISO/timezone-wrong-type.js -built-ins/Temporal/Instant/prototype/until/argument-string-calendar-annotation.js -built-ins/Temporal/Instant/prototype/until/argument-string-date-with-utc-offset.js -built-ins/Temporal/Instant/prototype/until/argument-string-time-zone-annotation.js -built-ins/Temporal/Instant/prototype/until/argument-string-unknown-annotation.js -built-ins/Temporal/Instant/prototype/until/order-of-operations.js -built-ins/Temporal/Now/plainDate/calendar-case-insensitive.js -built-ins/Temporal/Now/plainDate/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Now/plainDate/calendar-wrong-type.js -built-ins/Temporal/Now/plainDate/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Now/plainDate/timezone-wrong-type.js -built-ins/Temporal/Now/plainDateISO/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Now/plainDateISO/timezone-wrong-type.js -built-ins/Temporal/Now/plainDateTime/calendar-case-insensitive.js -built-ins/Temporal/Now/plainDateTime/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Now/plainDateTime/calendar-wrong-type.js -built-ins/Temporal/Now/plainDateTime/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Now/plainDateTime/timezone-wrong-type.js -built-ins/Temporal/Now/plainDateTimeISO/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Now/plainDateTimeISO/timezone-wrong-type.js -built-ins/Temporal/Now/plainTimeISO/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Now/plainTimeISO/timezone-wrong-type.js -built-ins/Temporal/Now/zonedDateTime/calendar-case-insensitive.js -built-ins/Temporal/Now/zonedDateTime/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/Now/zonedDateTime/calendar-wrong-type.js -built-ins/Temporal/Now/zonedDateTime/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Now/zonedDateTime/timezone-wrong-type.js -built-ins/Temporal/Now/zonedDateTimeISO/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/Now/zonedDateTimeISO/timezone-wrong-type.js -built-ins/Temporal/PlainDate/calendar-case-insensitive.js -built-ins/Temporal/PlainDate/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDate/compare/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainDate/compare/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDate/compare/argument-propertybag-calendar-wrong-type.js -built-ins/Temporal/PlainDate/compare/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDate/compare/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDate/compare/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDate/compare/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDate/from/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainDate/from/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainDate/from/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDate/from/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDate/from/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDate/from/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDate/from/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDate/from/calendar-fields-custom.js -built-ins/Temporal/PlainDate/from/order-of-operations.js -built-ins/Temporal/PlainDate/prototype/dayOfWeek/validate-calendar-value.js -built-ins/Temporal/PlainDate/prototype/dayOfYear/validate-calendar-value.js -built-ins/Temporal/PlainDate/prototype/daysInMonth/validate-calendar-value.js -built-ins/Temporal/PlainDate/prototype/daysInWeek/validate-calendar-value.js -built-ins/Temporal/PlainDate/prototype/daysInYear/validate-calendar-value.js -built-ins/Temporal/PlainDate/prototype/equals/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainDate/prototype/equals/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainDate/prototype/equals/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDate/prototype/equals/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDate/prototype/equals/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDate/prototype/equals/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDate/prototype/equals/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDate/prototype/inLeapYear/validate-calendar-value.js -built-ins/Temporal/PlainDate/prototype/monthsInYear/validate-calendar-value.js -built-ins/Temporal/PlainDate/prototype/since/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainDate/prototype/since/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainDate/prototype/since/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDate/prototype/since/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDate/prototype/since/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDate/prototype/since/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDate/prototype/since/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDate/prototype/since/order-of-operations.js -built-ins/Temporal/PlainDate/prototype/toPlainDateTime/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDate/prototype/toPlainDateTime/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDate/prototype/toPlainDateTime/argument-string-time-designator-required-for-disambiguation.js -built-ins/Temporal/PlainDate/prototype/toPlainDateTime/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDate/prototype/toPlainDateTime/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDate/prototype/toPlainMonthDay/calendar-fields-undefined.js -built-ins/Temporal/PlainDate/prototype/toPlainYearMonth/calendar-fields-undefined.js -built-ins/Temporal/PlainDate/prototype/toString/calendar-tostring.js -built-ins/Temporal/PlainDate/prototype/toString/calendarname-critical.js -built-ins/Temporal/PlainDate/prototype/toZonedDateTime/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDate/prototype/toZonedDateTime/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDate/prototype/toZonedDateTime/argument-string-time-designator-required-for-disambiguation.js -built-ins/Temporal/PlainDate/prototype/toZonedDateTime/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDate/prototype/toZonedDateTime/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDate/prototype/toZonedDateTime/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/PlainDate/prototype/toZonedDateTime/timezone-wrong-type.js -built-ins/Temporal/PlainDate/prototype/until/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainDate/prototype/until/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDate/prototype/until/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDate/prototype/until/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDate/prototype/until/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDate/prototype/until/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDate/prototype/until/order-of-operations.js -built-ins/Temporal/PlainDate/prototype/weekOfYear/validate-calendar-value.js -built-ins/Temporal/PlainDate/prototype/with/calendar-fields-undefined.js -built-ins/Temporal/PlainDate/prototype/with/order-of-operations.js -built-ins/Temporal/PlainDate/prototype/withCalendar/calendar-case-insensitive.js -built-ins/Temporal/PlainDate/prototype/withCalendar/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDate/prototype/withCalendar/calendar-wrong-type.js -built-ins/Temporal/PlainDate/prototype/year/validate-calendar-value.js -built-ins/Temporal/PlainDate/prototype/yearOfWeek/basic.js -built-ins/Temporal/PlainDate/prototype/yearOfWeek/branding.js -built-ins/Temporal/PlainDate/prototype/yearOfWeek/custom.js -built-ins/Temporal/PlainDate/prototype/yearOfWeek/prop-desc.js -built-ins/Temporal/PlainDate/prototype/yearOfWeek/validate-calendar-value.js -built-ins/Temporal/PlainDateTime/calendar-case-insensitive.js -built-ins/Temporal/PlainDateTime/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDateTime/compare/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainDateTime/compare/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDateTime/compare/argument-propertybag-calendar-wrong-type.js -built-ins/Temporal/PlainDateTime/compare/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDateTime/compare/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDateTime/compare/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDateTime/compare/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDateTime/from/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainDateTime/from/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainDateTime/from/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDateTime/from/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDateTime/from/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDateTime/from/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDateTime/from/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDateTime/from/order-of-operations.js -built-ins/Temporal/PlainDateTime/prototype/dayOfWeek/validate-calendar-value.js -built-ins/Temporal/PlainDateTime/prototype/dayOfYear/validate-calendar-value.js -built-ins/Temporal/PlainDateTime/prototype/daysInMonth/validate-calendar-value.js -built-ins/Temporal/PlainDateTime/prototype/daysInWeek/validate-calendar-value.js -built-ins/Temporal/PlainDateTime/prototype/daysInYear/validate-calendar-value.js -built-ins/Temporal/PlainDateTime/prototype/equals/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainDateTime/prototype/equals/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainDateTime/prototype/equals/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDateTime/prototype/equals/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDateTime/prototype/equals/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDateTime/prototype/equals/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDateTime/prototype/equals/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDateTime/prototype/inLeapYear/validate-calendar-value.js -built-ins/Temporal/PlainDateTime/prototype/monthsInYear/validate-calendar-value.js -built-ins/Temporal/PlainDateTime/prototype/since/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainDateTime/prototype/since/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDateTime/prototype/since/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDateTime/prototype/since/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDateTime/prototype/since/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDateTime/prototype/since/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDateTime/prototype/since/order-of-operations.js -built-ins/Temporal/PlainDateTime/prototype/toPlainMonthDay/calendar-fields-undefined.js -built-ins/Temporal/PlainDateTime/prototype/toPlainYearMonth/calendar-fields-undefined.js -built-ins/Temporal/PlainDateTime/prototype/toString/calendar-tostring.js -built-ins/Temporal/PlainDateTime/prototype/toString/calendarname-critical.js -built-ins/Temporal/PlainDateTime/prototype/toString/fractionalseconddigits-non-integer.js -built-ins/Temporal/PlainDateTime/prototype/toString/order-of-operations.js -built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/timezone-wrong-type.js -built-ins/Temporal/PlainDateTime/prototype/until/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainDateTime/prototype/until/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDateTime/prototype/until/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDateTime/prototype/until/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDateTime/prototype/until/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDateTime/prototype/until/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDateTime/prototype/until/order-of-operations.js -built-ins/Temporal/PlainDateTime/prototype/weekOfYear/validate-calendar-value.js -built-ins/Temporal/PlainDateTime/prototype/with/calendar-fields-undefined.js -built-ins/Temporal/PlainDateTime/prototype/with/order-of-operations.js -built-ins/Temporal/PlainDateTime/prototype/withCalendar/calendar-case-insensitive.js -built-ins/Temporal/PlainDateTime/prototype/withCalendar/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDateTime/prototype/withCalendar/calendar-wrong-type.js -built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDateTime/prototype/withPlainTime/argument-string-calendar-annotation.js -built-ins/Temporal/PlainDateTime/prototype/withPlainTime/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainDateTime/prototype/withPlainTime/argument-string-time-designator-required-for-disambiguation.js -built-ins/Temporal/PlainDateTime/prototype/withPlainTime/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainDateTime/prototype/withPlainTime/argument-string-unknown-annotation.js -built-ins/Temporal/PlainDateTime/prototype/year/validate-calendar-value.js -built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/basic.js -built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/branding.js -built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/custom.js -built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/prop-desc.js -built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/validate-calendar-value.js -built-ins/Temporal/PlainMonthDay/calendar-case-insensitive.js -built-ins/Temporal/PlainMonthDay/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainMonthDay/from/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainMonthDay/from/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainMonthDay/from/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainMonthDay/from/argument-string-calendar-annotation.js -built-ins/Temporal/PlainMonthDay/from/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainMonthDay/from/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainMonthDay/from/argument-string-unknown-annotation.js -built-ins/Temporal/PlainMonthDay/from/order-of-operations.js -built-ins/Temporal/PlainMonthDay/prototype/equals/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainMonthDay/prototype/equals/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainMonthDay/prototype/equals/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainMonthDay/prototype/equals/argument-string-calendar-annotation.js -built-ins/Temporal/PlainMonthDay/prototype/equals/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainMonthDay/prototype/equals/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainMonthDay/prototype/equals/argument-string-unknown-annotation.js -built-ins/Temporal/PlainMonthDay/prototype/toPlainDate/calendar-fields-undefined.js -built-ins/Temporal/PlainMonthDay/prototype/toString/calendar-tostring.js -built-ins/Temporal/PlainMonthDay/prototype/toString/calendarname-critical.js -built-ins/Temporal/PlainMonthDay/prototype/with/calendar-fields-undefined.js -built-ins/Temporal/PlainMonthDay/prototype/with/order-of-operations.js -built-ins/Temporal/PlainTime/compare/argument-string-calendar-annotation.js -built-ins/Temporal/PlainTime/compare/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainTime/compare/argument-string-time-designator-required-for-disambiguation.js -built-ins/Temporal/PlainTime/compare/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainTime/compare/argument-string-unknown-annotation.js -built-ins/Temporal/PlainTime/from/argument-string-calendar-annotation.js -built-ins/Temporal/PlainTime/from/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainTime/from/argument-string-time-designator-required-for-disambiguation.js -built-ins/Temporal/PlainTime/from/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainTime/from/argument-string-unknown-annotation.js -built-ins/Temporal/PlainTime/prototype/add/precision-exact-mathematical-values-1.js -built-ins/Temporal/PlainTime/prototype/add/precision-exact-mathematical-values-2.js -built-ins/Temporal/PlainTime/prototype/add/precision-exact-mathematical-values-3.js -built-ins/Temporal/PlainTime/prototype/equals/argument-string-calendar-annotation.js -built-ins/Temporal/PlainTime/prototype/equals/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainTime/prototype/equals/argument-string-time-designator-required-for-disambiguation.js -built-ins/Temporal/PlainTime/prototype/equals/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainTime/prototype/equals/argument-string-unknown-annotation.js -built-ins/Temporal/PlainTime/prototype/since/argument-string-calendar-annotation.js -built-ins/Temporal/PlainTime/prototype/since/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainTime/prototype/since/argument-string-time-designator-required-for-disambiguation.js -built-ins/Temporal/PlainTime/prototype/since/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainTime/prototype/since/argument-string-unknown-annotation.js -built-ins/Temporal/PlainTime/prototype/since/order-of-operations.js -built-ins/Temporal/PlainTime/prototype/subtract/precision-exact-mathematical-values-1.js -built-ins/Temporal/PlainTime/prototype/subtract/precision-exact-mathematical-values-2.js -built-ins/Temporal/PlainTime/prototype/subtract/precision-exact-mathematical-values-3.js -built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-string-calendar-annotation.js -built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-string-unknown-annotation.js -built-ins/Temporal/PlainTime/prototype/toString/fractionalseconddigits-non-integer.js -built-ins/Temporal/PlainTime/prototype/toString/order-of-operations.js -built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-string-calendar-annotation.js -built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-string-unknown-annotation.js -built-ins/Temporal/PlainTime/prototype/toZonedDateTime/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/PlainTime/prototype/toZonedDateTime/timezone-wrong-type.js -built-ins/Temporal/PlainTime/prototype/until/argument-string-calendar-annotation.js -built-ins/Temporal/PlainTime/prototype/until/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainTime/prototype/until/argument-string-time-designator-required-for-disambiguation.js -built-ins/Temporal/PlainTime/prototype/until/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainTime/prototype/until/argument-string-unknown-annotation.js -built-ins/Temporal/PlainTime/prototype/until/order-of-operations.js -built-ins/Temporal/PlainYearMonth/calendar-case-insensitive.js -built-ins/Temporal/PlainYearMonth/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainYearMonth/compare/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainYearMonth/compare/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainYearMonth/compare/argument-propertybag-calendar-wrong-type.js -built-ins/Temporal/PlainYearMonth/compare/argument-string-calendar-annotation.js -built-ins/Temporal/PlainYearMonth/compare/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainYearMonth/compare/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainYearMonth/compare/argument-string-unknown-annotation.js -built-ins/Temporal/PlainYearMonth/from/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainYearMonth/from/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainYearMonth/from/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainYearMonth/from/argument-string-calendar-annotation.js -built-ins/Temporal/PlainYearMonth/from/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainYearMonth/from/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainYearMonth/from/argument-string-unknown-annotation.js -built-ins/Temporal/PlainYearMonth/from/order-of-operations.js -built-ins/Temporal/PlainYearMonth/prototype/add/calendar-fields-undefined.js -built-ins/Temporal/PlainYearMonth/prototype/add/order-of-operations.js -built-ins/Temporal/PlainYearMonth/prototype/daysInMonth/validate-calendar-value.js -built-ins/Temporal/PlainYearMonth/prototype/daysInYear/validate-calendar-value.js -built-ins/Temporal/PlainYearMonth/prototype/equals/argument-calendar-fields-undefined.js -built-ins/Temporal/PlainYearMonth/prototype/equals/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainYearMonth/prototype/equals/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainYearMonth/prototype/equals/argument-string-calendar-annotation.js -built-ins/Temporal/PlainYearMonth/prototype/equals/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainYearMonth/prototype/equals/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainYearMonth/prototype/equals/argument-string-unknown-annotation.js -built-ins/Temporal/PlainYearMonth/prototype/inLeapYear/validate-calendar-value.js -built-ins/Temporal/PlainYearMonth/prototype/monthsInYear/validate-calendar-value.js -built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainYearMonth/prototype/since/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainYearMonth/prototype/since/argument-string-calendar-annotation.js -built-ins/Temporal/PlainYearMonth/prototype/since/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainYearMonth/prototype/since/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainYearMonth/prototype/since/argument-string-unknown-annotation.js -built-ins/Temporal/PlainYearMonth/prototype/since/calendar-fields-undefined.js -built-ins/Temporal/PlainYearMonth/prototype/since/order-of-operations.js -built-ins/Temporal/PlainYearMonth/prototype/subtract/calendar-fields-undefined.js -built-ins/Temporal/PlainYearMonth/prototype/subtract/order-of-operations.js -built-ins/Temporal/PlainYearMonth/prototype/toPlainDate/calendar-fields-undefined.js -built-ins/Temporal/PlainYearMonth/prototype/toPlainDate/copies-merge-fields-object.js -built-ins/Temporal/PlainYearMonth/prototype/toString/calendar-tostring.js -built-ins/Temporal/PlainYearMonth/prototype/toString/calendarname-critical.js -built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/PlainYearMonth/prototype/until/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/PlainYearMonth/prototype/until/argument-string-calendar-annotation.js -built-ins/Temporal/PlainYearMonth/prototype/until/argument-string-date-with-utc-offset.js -built-ins/Temporal/PlainYearMonth/prototype/until/argument-string-time-zone-annotation.js -built-ins/Temporal/PlainYearMonth/prototype/until/argument-string-unknown-annotation.js -built-ins/Temporal/PlainYearMonth/prototype/until/calendar-fields-undefined.js -built-ins/Temporal/PlainYearMonth/prototype/until/order-of-operations.js -built-ins/Temporal/PlainYearMonth/prototype/with/calendar-fields-undefined.js -built-ins/Temporal/PlainYearMonth/prototype/with/order-of-operations.js -built-ins/Temporal/PlainYearMonth/prototype/year/validate-calendar-value.js -built-ins/Temporal/TimeZone/from/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/TimeZone/from/timezone-wrong-type.js -built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-calendar-fields-undefined.js -built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-string-calendar-annotation.js -built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-string-date-with-utc-offset.js -built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-string-time-zone-annotation.js -built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-string-unknown-annotation.js -built-ins/Temporal/TimeZone/prototype/getInstantFor/order-of-operations.js -built-ins/Temporal/TimeZone/prototype/getNextTransition/argument-string-calendar-annotation.js -built-ins/Temporal/TimeZone/prototype/getNextTransition/argument-string-date-with-utc-offset.js -built-ins/Temporal/TimeZone/prototype/getNextTransition/argument-string-time-zone-annotation.js -built-ins/Temporal/TimeZone/prototype/getNextTransition/argument-string-unknown-annotation.js -built-ins/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/argument-string-calendar-annotation.js -built-ins/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/argument-string-date-with-utc-offset.js -built-ins/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/argument-string-time-zone-annotation.js -built-ins/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/argument-string-unknown-annotation.js -built-ins/Temporal/TimeZone/prototype/getOffsetStringFor/argument-string-calendar-annotation.js -built-ins/Temporal/TimeZone/prototype/getOffsetStringFor/argument-string-date-with-utc-offset.js -built-ins/Temporal/TimeZone/prototype/getOffsetStringFor/argument-string-time-zone-annotation.js -built-ins/Temporal/TimeZone/prototype/getOffsetStringFor/argument-string-unknown-annotation.js -built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/argument-string-calendar-annotation.js -built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/argument-string-date-with-utc-offset.js -built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/argument-string-time-zone-annotation.js -built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/argument-string-unknown-annotation.js -built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/calendar-case-insensitive.js -built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/calendar-wrong-type.js -built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-calendar-fields-undefined.js -built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-string-calendar-annotation.js -built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-string-date-with-utc-offset.js -built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-string-time-zone-annotation.js -built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-string-unknown-annotation.js -built-ins/Temporal/TimeZone/prototype/getPreviousTransition/argument-string-calendar-annotation.js -built-ins/Temporal/TimeZone/prototype/getPreviousTransition/argument-string-date-with-utc-offset.js -built-ins/Temporal/TimeZone/prototype/getPreviousTransition/argument-string-time-zone-annotation.js -built-ins/Temporal/TimeZone/prototype/getPreviousTransition/argument-string-unknown-annotation.js -built-ins/Temporal/TimeZone/prototype/id/custom-timezone.js -built-ins/Temporal/ZonedDateTime/calendar-case-insensitive.js -built-ins/Temporal/ZonedDateTime/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/ZonedDateTime/compare/argument-calendar-fields-undefined.js -built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-calendar-wrong-type.js -built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/ZonedDateTime/compare/argument-propertybag-timezone-wrong-type.js -built-ins/Temporal/ZonedDateTime/compare/argument-string-calendar-annotation.js -built-ins/Temporal/ZonedDateTime/compare/argument-string-date-with-utc-offset.js -built-ins/Temporal/ZonedDateTime/compare/argument-string-time-zone-annotation.js -built-ins/Temporal/ZonedDateTime/compare/argument-string-unknown-annotation.js -built-ins/Temporal/ZonedDateTime/compare/order-of-operations.js -built-ins/Temporal/ZonedDateTime/from/argument-calendar-fields-undefined.js -built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/ZonedDateTime/from/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/ZonedDateTime/from/argument-propertybag-timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/ZonedDateTime/from/argument-propertybag-timezone-wrong-type.js -built-ins/Temporal/ZonedDateTime/from/argument-string-calendar-annotation.js -built-ins/Temporal/ZonedDateTime/from/argument-string-date-with-utc-offset.js -built-ins/Temporal/ZonedDateTime/from/argument-string-time-zone-annotation.js -built-ins/Temporal/ZonedDateTime/from/argument-string-unknown-annotation.js -built-ins/Temporal/ZonedDateTime/from/offset-overrides-critical-flag.js -built-ins/Temporal/ZonedDateTime/from/order-of-operations.js -built-ins/Temporal/ZonedDateTime/prototype/dayOfWeek/validate-calendar-value.js -built-ins/Temporal/ZonedDateTime/prototype/dayOfYear/validate-calendar-value.js -built-ins/Temporal/ZonedDateTime/prototype/daysInMonth/validate-calendar-value.js -built-ins/Temporal/ZonedDateTime/prototype/daysInWeek/validate-calendar-value.js -built-ins/Temporal/ZonedDateTime/prototype/daysInYear/validate-calendar-value.js -built-ins/Temporal/ZonedDateTime/prototype/equals/argument-calendar-fields-undefined.js -built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/ZonedDateTime/prototype/equals/argument-propertybag-timezone-wrong-type.js -built-ins/Temporal/ZonedDateTime/prototype/equals/argument-string-calendar-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/equals/argument-string-date-with-utc-offset.js -built-ins/Temporal/ZonedDateTime/prototype/equals/argument-string-time-zone-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/equals/argument-string-unknown-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/equals/order-of-operations.js -built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/precision-exact-mathematical-values.js -built-ins/Temporal/ZonedDateTime/prototype/inLeapYear/validate-calendar-value.js -built-ins/Temporal/ZonedDateTime/prototype/monthsInYear/validate-calendar-value.js -built-ins/Temporal/ZonedDateTime/prototype/since/argument-calendar-fields-undefined.js -built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/ZonedDateTime/prototype/since/argument-propertybag-timezone-wrong-type.js -built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-calendar-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-date-with-utc-offset.js -built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-time-zone-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/since/argument-string-unknown-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js -built-ins/Temporal/ZonedDateTime/prototype/toPlainMonthDay/calendar-fields-undefined.js -built-ins/Temporal/ZonedDateTime/prototype/toPlainYearMonth/calendar-fields-undefined.js -built-ins/Temporal/ZonedDateTime/prototype/toString/calendar-tostring.js -built-ins/Temporal/ZonedDateTime/prototype/toString/calendarname-critical.js -built-ins/Temporal/ZonedDateTime/prototype/toString/fractionalseconddigits-non-integer.js -built-ins/Temporal/ZonedDateTime/prototype/toString/order-of-operations.js -built-ins/Temporal/ZonedDateTime/prototype/toString/timezonename-critical.js -built-ins/Temporal/ZonedDateTime/prototype/until/argument-calendar-fields-undefined.js -built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/ZonedDateTime/prototype/until/argument-propertybag-timezone-wrong-type.js -built-ins/Temporal/ZonedDateTime/prototype/until/argument-string-calendar-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/until/argument-string-date-with-utc-offset.js -built-ins/Temporal/ZonedDateTime/prototype/until/argument-string-time-zone-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/until/argument-string-unknown-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js -built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/validate-calendar-value.js -built-ins/Temporal/ZonedDateTime/prototype/with/calendar-fields-undefined.js -built-ins/Temporal/ZonedDateTime/prototype/with/copies-merge-fields-object.js -built-ins/Temporal/ZonedDateTime/prototype/with/order-of-operations.js -built-ins/Temporal/ZonedDateTime/prototype/withCalendar/calendar-case-insensitive.js -built-ins/Temporal/ZonedDateTime/prototype/withCalendar/calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/ZonedDateTime/prototype/withCalendar/calendar-wrong-type.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-calendar-fields-undefined.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-case-insensitive.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-string-calendar-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-string-date-with-utc-offset.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-string-time-zone-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-string-unknown-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/argument-string-calendar-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/argument-string-date-with-utc-offset.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/argument-string-time-designator-required-for-disambiguation.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/argument-string-time-zone-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/argument-string-unknown-annotation.js -built-ins/Temporal/ZonedDateTime/prototype/withTimeZone/timezone-instance-does-not-get-timeZone-property.js -built-ins/Temporal/ZonedDateTime/prototype/withTimeZone/timezone-wrong-type.js -built-ins/Temporal/ZonedDateTime/prototype/year/validate-calendar-value.js -built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/branding.js -built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/custom.js -built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/prop-desc.js -built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/timezone-getoffsetnanosecondsfor-non-integer.js -built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/timezone-getoffsetnanosecondsfor-not-callable.js -built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/timezone-getoffsetnanosecondsfor-out-of-range.js -built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/timezone-getoffsetnanosecondsfor-wrong-type.js -built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/validate-calendar-value.js -built-ins/Temporal/ZonedDateTime/timezone-instance-does-not-get-timeZone-property.js -intl402/Temporal/Calendar/calendar-case-insensitive.js -intl402/Temporal/Calendar/from/calendar-case-insensitive.js -intl402/Temporal/Calendar/prototype/era/argument-calendar-fields-undefined.js -intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-case-insensitive.js -intl402/Temporal/Calendar/prototype/era/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -intl402/Temporal/Calendar/prototype/era/argument-string-calendar-annotation.js -intl402/Temporal/Calendar/prototype/era/argument-string-date-with-utc-offset.js -intl402/Temporal/Calendar/prototype/era/argument-string-time-zone-annotation.js -intl402/Temporal/Calendar/prototype/era/argument-string-unknown-annotation.js -intl402/Temporal/Calendar/prototype/eraYear/argument-calendar-fields-undefined.js -intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-case-insensitive.js -intl402/Temporal/Calendar/prototype/eraYear/argument-propertybag-calendar-instance-does-not-get-calendar-property.js -intl402/Temporal/Calendar/prototype/eraYear/argument-string-calendar-annotation.js -intl402/Temporal/Calendar/prototype/eraYear/argument-string-date-with-utc-offset.js -intl402/Temporal/Calendar/prototype/eraYear/argument-string-time-zone-annotation.js -intl402/Temporal/Calendar/prototype/eraYear/argument-string-unknown-annotation.js -intl402/Temporal/Calendar/prototype/yearOfWeek/infinity-throws-rangeerror.js -intl402/Temporal/TimeZone/prototype/getNextTransition/transition-at-instant-boundaries.js -intl402/Temporal/TimeZone/prototype/getPreviousTransition/transition-at-instant-boundaries.js -intl402/Temporal/ZonedDateTime/prototype/withCalendar/calendar-case-insensitive.js -built-ins/Temporal/Duration/prototype/add/nanoseconds-to-days-loop-indefinitely-2.js -built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-nanoseconds-to-days-range-errors.js -built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-nanoseconds-to-days-range-errors.js -built-ins/Temporal/Duration/prototype/subtract/nanoseconds-to-days-loop-indefinitely-2.js -built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-nanoseconds-to-days-range-errors.js -built-ins/Temporal/Duration/prototype/total/nanoseconds-to-days-loop-indefinitely-2.js -built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-nanoseconds-to-days-range-errors.js -built-ins/Temporal/PlainDate/argument-convert.js -built-ins/Temporal/ZonedDateTime/prototype/since/nanoseconds-to-days-loop-indefinitely-2.js -built-ins/Temporal/ZonedDateTime/prototype/since/nanoseconds-to-days-range-errors.js -built-ins/Temporal/ZonedDateTime/prototype/until/nanoseconds-to-days-loop-indefinitely-2.js -built-ins/Temporal/ZonedDateTime/prototype/until/nanoseconds-to-days-range-errors.js diff --git a/packages/temporal-polyfill/misc/expected-failures.txt b/packages/temporal-polyfill/misc/expected-failures.txt deleted file mode 100644 index d9e39ed8..00000000 --- a/packages/temporal-polyfill/misc/expected-failures.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Failures in this file are expected to fail for all Test262 tests. To record -# expected test failures for the transpiled or optimized builds of the polyfill, -# see expected-failures-es5.txt and expected-failures-opt.txt respectively. - -# https://github.com/tc39/test262/pull/3548 - -# -# fullcalendar/temporal notes -# -# Start with `pn test262 'Calendar/**'` -# Figure out `[input.high, input.low]` error -# diff --git a/packages/temporal-polyfill/package.json b/packages/temporal-polyfill/package.json index f47d2d79..22b10942 100644 --- a/packages/temporal-polyfill/package.json +++ b/packages/temporal-polyfill/package.json @@ -1,8 +1,8 @@ { "name": "temporal-polyfill", - "version": "0.1.1", + "version": "0.2.0", "title": "Temporal Polyfill", - "description": "A spec-compliant Temporal JavaScript polyfill in 16kb", + "description": "A lightweight polyfill for Temporal, successor to the JavaScript Date object", "author": { "name": "Adam Shaw", "email": "arshaw@users.noreply.github.com", @@ -18,75 +18,55 @@ "copyright": "2022 Adam Shaw", "repository": { "type": "git", - "url": "https://github.com/fullcalendar/temporal.git", + "url": "https://github.com/fullcalendar/temporal-polyfill.git", "directory": "packages/temporal-polyfill" }, "scripts": { - "build": "pnpm run types && pnpm run bundle", - "build:dev": "pnpm run types && NO_MIN=1 pnpm run bundle", - "watch": "concurrently npm:types:watch npm:bundle:watch", - "types": "tsc --build --preserveWatchOutput", - "types:watch": "tsc --build --preserveWatchOutput --watch", - "bundle": "rollup -c ../../scripts/config/pkgBundle.cjs", - "bundle:watch": "rollup -c ../../scripts/config/pkgBundle.cjs --watch", - "test": "jest .", - "test262": "node ./runtest262.js", + "build": "pnpm run bundle:clean && pnpm run pkg-json && pnpm run tsc && pnpm run type-overrides && pnpm run bundle", + "dev": "pnpm run bundle:clean && pnpm run pkg-json && pnpm run tsc && pnpm run type-overrides && concurrently --prefix-colors green npm:tsc:dev npm:bundle:dev", + "tsc": "tsc --build", + "tsc:dev": "tsc --build --watch --pretty", + "tsc:clean": "rm -rf dist/.tsc dist/tsconfig.tsbuildinfo", + "type-overrides": "cp src/typeOverrides/*.d.ts dist", + "bundle": "./scripts/bundle.js", + "bundle:dev": "./scripts/bundle.js --dev", + "bundle:clean": "rm -rf dist/*.js dist/*.cjs dist/*.map dist/*.d.ts", + "pkg-json": "./scripts/pkg-json.js", + "pkg-json:clean": "rm -rf dist/package.json", + "test": "./scripts/test.js", "lint": "eslint .", - "size": "node ../../scripts/pkgSize.cjs", - "clean": "node ../../scripts/pkgClean.cjs" + "clean": "pnpm run tsc:clean && pnpm run bundle:clean && pnpm run pkg-json:clean", + "size": "gzip-size --include-original dist/global.min.js && pnpm export-size ./dist" }, "type": "module", - "types": "./dist/index.d.ts", - "main": "./dist/index.cjs", - "module": "./dist/index.mjs", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "require": "./dist/index.cjs", - "import": "./dist/index.mjs" - }, - "./impl": { - "types": "./dist/impl.d.ts", - "require": "./dist/impl.cjs", - "import": "./dist/impl.mjs" - }, - "./shim": { - "types": "./dist/shim.d.ts", - "require": "./dist/shim.cjs", - "import": "./dist/shim.mjs" - }, - "./global": { - "types": "./dist/global.d.ts", - "require": "./dist/global.cjs", - "import": "./dist/global.mjs" + "buildConfig": { + "exports": { + ".": {}, + "./impl": {}, + "./global": { + "iife": true + } } }, - "sideEffects": [ - "./dist/global.*" - ], - "files": [ - "/src", - "/dist", - "/*.d.ts" - ], + "publishConfig": { + "directory": "./dist", + "linkDirectory": true + }, "dependencies": { - "temporal-spec": "~0.1.0" + "temporal-spec": "~0.2.0" }, "devDependencies": { - "@types/chai": "^4.2.22", - "@types/jest": "^26.0.24", + "@js-temporal/temporal-test262-runner": "workspace:*", + "@rollup/plugin-terser": "^0.4.4", "@types/node": "^16.9.1", - "@js-temporal/temporal-test262-runner": "^0.9.0", - "ansi-colors": "^4.1.3", - "chai": "^4.3.4", - "concurrently": "^7.6.0", + "concurrently": "^8.2.0", "eslint": "^7.25.0", - "jest": "^27.0.6", - "jest-date-mock": "^1.0.8", - "js-yaml": "^4.1.0", - "progress": "^2.0.3", - "rollup": "^2.55.1", - "tiny-glob": "^0.2.9", - "typescript": "^4.3.5" + "export-size": "^0.7.0", + "gzip-size": "^7.0.0", + "rollup": "^3.27.2", + "rollup-plugin-sourcemaps": "^0.6.3", + "terser": "^5.19.2", + "typescript": "^5.1.6", + "yargs": "^17.7.2" } } diff --git a/packages/temporal-polyfill/runtest262.js b/packages/temporal-polyfill/runtest262.js deleted file mode 100644 index a78facdc..00000000 --- a/packages/temporal-polyfill/runtest262.js +++ /dev/null @@ -1,27 +0,0 @@ -import runTest262 from '@js-temporal/temporal-test262-runner' - -const testGlobs = process.argv.slice(2) -const noTimeoutIndex = testGlobs.indexOf('--no-timeout') -const noTimeout = noTimeoutIndex !== -1 -if (noTimeout) { - testGlobs.splice(noTimeoutIndex, 1) -} - -const result = runTest262({ - test262Dir: 'test262', - polyfillCodeFile: 'dist/global.js', - expectedFailureFiles: [ - // from https://github.com/js-temporal/temporal-polyfill/blob/main/runtest262.mjs - 'misc/expected-failures.txt', - // 'misc/expected-failures-es5.txt', - 'misc/expected-failures-opt.txt', - 'misc/expected-failures-before-node18.txt', - // 'misc/expected-failures-before-node16.txt', - // 'misc/expected-failures-todo-migrated-code.txt', - ], - testGlobs, - timeoutMsecs: (noTimeout ? (24 * 60 * 60) : 2) * 1000, -}) - -// if result is `true`, all tests succeeded -process.exit(result ? 0 : 1) diff --git a/packages/temporal-polyfill/scripts/bundle.js b/packages/temporal-polyfill/scripts/bundle.js new file mode 100755 index 00000000..879f60ae --- /dev/null +++ b/packages/temporal-polyfill/scripts/bundle.js @@ -0,0 +1,219 @@ +#!/usr/bin/env node + +import { join as joinPaths, basename } from 'path' +import { readFile } from 'fs/promises' +import { rollup as rollupBuild, watch as rollupWatch } from 'rollup' +import sourcemaps from 'rollup-plugin-sourcemaps' +import terser from '@rollup/plugin-terser' + +// TODO: make DRY with pkg-json.js +const extensions = { + esm: '.esm.js', + cjs: '.cjs', + iife: '.js', +} + +writeBundles( + joinPaths(process.argv[1], '../..'), + process.argv.slice(2).includes('--dev'), +) + +async function writeBundles(pkgDir, isDev) { + const configs = await buildConfigs(pkgDir, isDev) + + await (isDev ? watchWithConfigs : buildWithConfigs)(configs) +} + +async function buildConfigs(pkgDir, isDev) { + const temporalReservedWords = await readTemporalReservedWords(pkgDir) + + const pkgJsonPath = joinPaths(pkgDir, 'package.json') + const pkgJson = JSON.parse(await readFile(pkgJsonPath)) + const exportMap = pkgJson.buildConfig.exports + const moduleInputs = {} + const iifeConfigs = [] + + for (const exportPath in exportMap) { + const exportConfig = exportMap[exportPath] + const shortName = exportPath === '.' ? 'index' : exportPath.replace(/^\.\//, '') + const inputPath = joinPaths(pkgDir, 'dist/.tsc', shortName + '.js') + + moduleInputs[shortName] = inputPath + + if (exportConfig.iife) { + iifeConfigs.push({ + input: inputPath, + onwarn, + plugins: [ + // for reading sourcemaps from tsc + isDev && sourcemaps(), + ], + output: [ + { + format: 'iife', + file: joinPaths('dist', shortName + extensions.iife), + sourcemap: isDev, + sourcemapExcludeSources: true, + plugins: [ + !isDev && buildTerserPlugin({ + humanReadable: true, + optimize: true, + }) + ], + }, + !isDev && { + format: 'iife', + file: joinPaths('dist', shortName + '.min' + extensions.iife), + plugins: [ + buildTerserPlugin({ + optimize: true, + mangleProps: true, + manglePropsExcept: temporalReservedWords, + }), + ] + } + ], + }) + } + } + + return [ + { + input: moduleInputs, + onwarn, + output: [ + { + format: 'cjs', + dir: 'dist', + entryFileNames: '[name]' + extensions.cjs, + chunkFileNames: `chunk-[${isDev ? 'name' : 'hash'}]` + extensions.cjs, + plugins: [ + !isDev && buildTerserPlugin({ + humanReadable: true, + optimize: true, + // don't mangleProps. CJS export names are affected + }) + ] + }, + { + format: 'es', + dir: 'dist', + entryFileNames: '[name]' + extensions.esm, + chunkFileNames: `chunk-[${isDev ? 'name' : 'hash'}]` + extensions.esm, + plugins: [ + !isDev && buildTerserPlugin({ + humanReadable: true, + optimize: true, + mangleProps: true, + manglePropsExcept: temporalReservedWords, + }) + ], + } + ], + }, + ...iifeConfigs, + ] +} + +function buildWithConfigs(configs) { + return Promise.all( + configs.map(async (config) => { + const bundle = await rollupBuild(config) + + return Promise.all( + arrayify(config.output).map((outputConfig) => { + return bundle.write(outputConfig) + }) + ) + }) + ) +} + +async function watchWithConfigs(configs) { + const rollupWatcher = rollupWatch(configs) + + return new Promise((resolve) => { + rollupWatcher.on('event', (ev) => { + switch (ev.code) { + case 'ERROR': + console.error(ev.error) + break + case 'BUNDLE_END': + console.log(formatWriteMessage(ev.input)) + break + case 'END': + resolve() + break + } + }) + }) +} + +function formatWriteMessage(input) { + const inputPaths = typeof input === 'object' ? Object.values(input) : [input] + const inputNames = inputPaths.map((inputPath) => basename(inputPath)) + + return `Bundled ${inputNames.join(', ')}` +} + +function onwarn(warning) { + if (warning.code !== 'CIRCULAR_DEPENDENCY') { + console.error(warning.toString()) + } +} + +function arrayify(input) { + return Array.isArray(input) ? input : (input == null ? [] : [input]) +} + +// Terser +// ------------------------------------------------------------------------------------------------- + +const terserNameCache = {} // for keeping prop mangling consistent across files + +function buildTerserPlugin({ + humanReadable = false, + optimize = false, + mangleProps = false, + manglePropsExcept, +}) { + return terser({ + compress: optimize && { + ecma: 2018, + passes: 3, // enough to remove dead object assignment, get lower size + keep_fargs: false, // remove unused function args + unsafe_arrows: true, + unsafe_methods: true, + booleans_as_integers: true, + hoist_funs: true, + }, + // Unfortunately can't just mangle props and nothing else + mangle: mangleProps && { + keep_fnames: humanReadable, + keep_classnames: humanReadable, + properties: { + reserved: manglePropsExcept, + keep_quoted: true, + }, + }, + format: { + beautify: humanReadable, + braces: humanReadable, + indent_level: 2, + }, + nameCache: terserNameCache, + }) +} + +const startsWithLetterRegExp = /^[a-zA-Z]/ + +async function readTemporalReservedWords(pkgDir) { + const code = await readFile(joinPaths(pkgDir, '../temporal-spec/global.d.ts'), 'utf-8') + return code.split(/\W+/) + .filter((symbol) => symbol && startsWithLetterRegExp.test(symbol)) + .concat([ + 'resolvedOptions', + 'useGrouping', + 'relatedYear', + ]) +} diff --git a/packages/temporal-polyfill/scripts/pkg-json.js b/packages/temporal-polyfill/scripts/pkg-json.js new file mode 100755 index 00000000..08dc69fd --- /dev/null +++ b/packages/temporal-polyfill/scripts/pkg-json.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node + +import { join as joinPaths } from 'path' +import { readFile, writeFile } from 'fs/promises' + +writePkgJson( + joinPaths(process.argv[1], '../..') +) + +async function writePkgJson(pkgDir) { + const srcPkgJsonPath = joinPaths(pkgDir, 'package.json') + const distPkgJsonPath = joinPaths(pkgDir, 'dist/package.json') + + const srcPkgJson = JSON.parse(await readFile(srcPkgJsonPath)) + const distPkgJson = { ...srcPkgJson } + + const srcExportMap = srcPkgJson.buildConfig.exports + const distExportMap = {} + + for (const exportPath in srcExportMap) { + const shortName = exportPath === '.' ? './index' : exportPath + + // TODO: make DRY with bundle.js + distExportMap[exportPath] = { + types: shortName + '.d.ts', + require: shortName + '.cjs', + import: shortName + '.esm.js', + } + } + + distPkgJson.types = distExportMap['.'].types + distPkgJson.main = distExportMap['.'].require + distPkgJson.module = distExportMap['.'].import + distPkgJson.unpkg = distPkgJson.jsdelivr = './global.min.js' + distPkgJson.exports = distExportMap + + delete distPkgJson.scripts + delete distPkgJson.buildConfig + delete distPkgJson.publishConfig + delete distPkgJson.devDependencies + + await writeFile(distPkgJsonPath, JSON.stringify(distPkgJson, undefined, 2)) +} diff --git a/packages/temporal-polyfill/scripts/test-config/expected-failures-before-node16.txt b/packages/temporal-polyfill/scripts/test-config/expected-failures-before-node16.txt new file mode 100644 index 00000000..4a04ff09 --- /dev/null +++ b/packages/temporal-polyfill/scripts/test-config/expected-failures-before-node16.txt @@ -0,0 +1,10 @@ +# Before Node 16, dateStyle/timeStyle options didn't conflict with other options +intl402/Temporal/Instant/prototype/toLocaleString/options-conflict.js +intl402/Temporal/PlainDate/prototype/toLocaleString/options-conflict.js +intl402/Temporal/PlainDateTime/prototype/toLocaleString/options-conflict.js +intl402/Temporal/PlainTime/prototype/toLocaleString/options-conflict.js +intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-conflict.js + +# Not sure why these tests fail in Node 14, but it's EOL so not worth investigating. +built-ins/Temporal/PlainYearMonth/prototype/toLocaleString/builtin-calendar-no-observable-calls.js +built-ins/Temporal/PlainMonthDay/prototype/toLocaleString/builtin-calendar-no-observable-calls.js diff --git a/packages/temporal-polyfill/scripts/test-config/expected-failures-before-node18.txt b/packages/temporal-polyfill/scripts/test-config/expected-failures-before-node18.txt new file mode 100644 index 00000000..23978368 --- /dev/null +++ b/packages/temporal-polyfill/scripts/test-config/expected-failures-before-node18.txt @@ -0,0 +1,15 @@ +# timeZoneName: 'shortOffset' is only available starting in Node 18 +intl402/DateTimeFormat/constructor-options-timeZoneName-valid.js + +# Intl.supportedValuesOf("timeZone") is only available starting in Node 18 +intl402/Temporal/TimeZone/supported-values-of.js +intl402/Temporal/TimeZone/prototype/equals/canonical-not-equal.js +intl402/Temporal/TimeZone/prototype/equals/timezone-case-insensitive.js + +intl402/Temporal/TimeZone/prototype/getNextTransition/transition-at-instant-boundaries.js +intl402/Temporal/TimeZone/prototype/getPreviousTransition/transition-at-instant-boundaries.js +intl402/Temporal/PlainDate/prototype/toLocaleString/calendar-mismatch.js +intl402/Temporal/PlainDateTime/prototype/toLocaleString/calendar-mismatch.js +intl402/Temporal/PlainMonthDay/prototype/toLocaleString/calendar-mismatch.js +intl402/Temporal/PlainYearMonth/prototype/toLocaleString/calendar-mismatch.js +intl402/Temporal/ZonedDateTime/prototype/toLocaleString/calendar-mismatch.js diff --git a/packages/temporal-polyfill/scripts/test-config/expected-failures-intl-format-norm.txt b/packages/temporal-polyfill/scripts/test-config/expected-failures-intl-format-norm.txt new file mode 100644 index 00000000..5f9d0c58 --- /dev/null +++ b/packages/temporal-polyfill/scripts/test-config/expected-failures-intl-format-norm.txt @@ -0,0 +1,8 @@ + +# Really picky about whitespace between characters +# Nothing we can do about this since it relies on Intl.DateTimeFormat +staging/Intl402/Temporal/old/date-time-format.js +staging/Intl402/Temporal/old/datetime-toLocaleString.js +staging/Intl402/Temporal/old/instant-toLocaleString.js +staging/Intl402/Temporal/old/time-toLocaleString.js +intl402/DateTimeFormat/prototype/format/temporal-objects-resolved-time-zone.js diff --git a/packages/temporal-polyfill/misc/expected-failures-es5.txt b/packages/temporal-polyfill/scripts/test-config/expected-failures-surface.txt similarity index 57% rename from packages/temporal-polyfill/misc/expected-failures-es5.txt rename to packages/temporal-polyfill/scripts/test-config/expected-failures-surface.txt index d0e61f29..58558e93 100644 --- a/packages/temporal-polyfill/misc/expected-failures-es5.txt +++ b/packages/temporal-polyfill/scripts/test-config/expected-failures-surface.txt @@ -1,355 +1,518 @@ -# Failures in this file only apply to the optimized and transpiled polyfill -# sources. -built-ins/Temporal/Calendar/from/builtin.js -built-ins/Temporal/Calendar/from/not-a-constructor.js -built-ins/Temporal/Duration/compare/builtin.js -built-ins/Temporal/Duration/compare/not-a-constructor.js -built-ins/Temporal/Duration/from/builtin.js -built-ins/Temporal/Duration/from/not-a-constructor.js -built-ins/Temporal/Instant/compare/builtin.js -built-ins/Temporal/Instant/compare/not-a-constructor.js -built-ins/Temporal/Instant/from/builtin.js -built-ins/Temporal/Instant/from/not-a-constructor.js -built-ins/Temporal/Instant/fromEpochMicroseconds/builtin.js -built-ins/Temporal/Instant/fromEpochMicroseconds/not-a-constructor.js -built-ins/Temporal/Instant/fromEpochMilliseconds/builtin.js -built-ins/Temporal/Instant/fromEpochMilliseconds/not-a-constructor.js -built-ins/Temporal/Instant/fromEpochNanoseconds/builtin.js -built-ins/Temporal/Instant/fromEpochNanoseconds/not-a-constructor.js -built-ins/Temporal/Instant/fromEpochSeconds/builtin.js -built-ins/Temporal/Instant/fromEpochSeconds/not-a-constructor.js -built-ins/Temporal/Now/instant/not-a-constructor.js -built-ins/Temporal/Now/plainDateTime/not-a-constructor.js -built-ins/Temporal/Now/plainDateTimeISO/not-a-constructor.js -built-ins/Temporal/Now/timeZone/not-a-constructor.js -built-ins/Temporal/Now/zonedDateTime/not-a-constructor.js -built-ins/Temporal/Now/zonedDateTimeISO/not-a-constructor.js -built-ins/Temporal/PlainDate/compare/builtin.js -built-ins/Temporal/PlainDate/compare/not-a-constructor.js -built-ins/Temporal/PlainDate/from/builtin.js -built-ins/Temporal/PlainDate/from/not-a-constructor.js -built-ins/Temporal/PlainDateTime/compare/builtin.js -built-ins/Temporal/PlainDateTime/compare/not-a-constructor.js -built-ins/Temporal/PlainDateTime/from/builtin.js -built-ins/Temporal/PlainDateTime/from/not-a-constructor.js -built-ins/Temporal/PlainMonthDay/from/builtin.js -built-ins/Temporal/PlainMonthDay/from/not-a-constructor.js -built-ins/Temporal/PlainTime/compare/builtin.js -built-ins/Temporal/PlainTime/compare/not-a-constructor.js -built-ins/Temporal/PlainTime/from/builtin.js -built-ins/Temporal/PlainTime/from/not-a-constructor.js -built-ins/Temporal/PlainYearMonth/compare/builtin.js -built-ins/Temporal/PlainYearMonth/compare/not-a-constructor.js -built-ins/Temporal/PlainYearMonth/from/builtin.js -built-ins/Temporal/PlainYearMonth/from/not-a-constructor.js -built-ins/Temporal/TimeZone/from/builtin.js -built-ins/Temporal/TimeZone/from/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/compare/builtin.js -built-ins/Temporal/ZonedDateTime/compare/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/from/builtin.js -built-ins/Temporal/ZonedDateTime/from/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/dateAdd/builtin.js -built-ins/Temporal/Calendar/prototype/dateAdd/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/dateFromFields/builtin.js -built-ins/Temporal/Calendar/prototype/dateFromFields/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/dateUntil/builtin.js -built-ins/Temporal/Calendar/prototype/dateUntil/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/day/builtin.js -built-ins/Temporal/Calendar/prototype/day/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/dayOfWeek/builtin.js -built-ins/Temporal/Calendar/prototype/dayOfWeek/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/dayOfYear/builtin.js +#################################################################################################### +# Surface-level JS Lang-related incompatibilities, like how class methods are defined +#################################################################################################### +# cd test262/test + +# +# find built-ins/Temporal intl402/Temporal -name 'length.js' +# +built-ins/Temporal/PlainMonthDay/length.js +built-ins/Temporal/PlainMonthDay/from/length.js +built-ins/Temporal/PlainMonthDay/prototype/with/length.js +built-ins/Temporal/PlainMonthDay/prototype/toPlainDate/length.js +built-ins/Temporal/PlainMonthDay/prototype/equals/length.js +built-ins/Temporal/PlainDateTime/length.js +built-ins/Temporal/PlainDateTime/from/length.js +built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/length.js +built-ins/Temporal/PlainDateTime/prototype/with/length.js +built-ins/Temporal/PlainDateTime/prototype/subtract/length.js +built-ins/Temporal/PlainDateTime/prototype/since/length.js +built-ins/Temporal/PlainDateTime/prototype/withCalendar/length.js +built-ins/Temporal/PlainDateTime/prototype/withPlainDate/length.js +built-ins/Temporal/PlainDateTime/prototype/until/length.js +built-ins/Temporal/PlainDateTime/prototype/equals/length.js +built-ins/Temporal/PlainDateTime/prototype/add/length.js +built-ins/Temporal/PlainDateTime/prototype/round/length.js +built-ins/Temporal/PlainYearMonth/length.js +built-ins/Temporal/PlainYearMonth/from/length.js +built-ins/Temporal/PlainYearMonth/prototype/with/length.js +built-ins/Temporal/PlainYearMonth/prototype/toPlainDate/length.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/length.js +built-ins/Temporal/PlainYearMonth/prototype/since/length.js +built-ins/Temporal/PlainYearMonth/prototype/until/length.js +built-ins/Temporal/PlainYearMonth/prototype/equals/length.js +built-ins/Temporal/PlainYearMonth/prototype/add/length.js +built-ins/Temporal/Calendar/length.js +built-ins/Temporal/Calendar/prototype/dayOfYear/length.js +built-ins/Temporal/Calendar/prototype/month/length.js +built-ins/Temporal/Calendar/prototype/dateFromFields/length.js +built-ins/Temporal/Calendar/prototype/dateAdd/length.js +built-ins/Temporal/Calendar/prototype/mergeFields/length.js +built-ins/Temporal/Calendar/prototype/monthCode/length.js +built-ins/Temporal/Calendar/prototype/monthDayFromFields/length.js +built-ins/Temporal/Calendar/prototype/yearMonthFromFields/length.js +built-ins/Temporal/Calendar/prototype/daysInWeek/length.js +built-ins/Temporal/Calendar/prototype/inLeapYear/length.js +built-ins/Temporal/Calendar/prototype/daysInMonth/length.js +built-ins/Temporal/Calendar/prototype/year/length.js +built-ins/Temporal/Calendar/prototype/dateUntil/length.js +built-ins/Temporal/Calendar/prototype/dayOfWeek/length.js +built-ins/Temporal/Calendar/prototype/fields/length.js +built-ins/Temporal/Calendar/prototype/weekOfYear/length.js +built-ins/Temporal/Calendar/prototype/yearOfWeek/length.js +built-ins/Temporal/Calendar/prototype/day/length.js +built-ins/Temporal/Calendar/prototype/daysInYear/length.js +built-ins/Temporal/Calendar/prototype/monthsInYear/length.js +built-ins/Temporal/TimeZone/length.js +built-ins/Temporal/TimeZone/prototype/getInstantFor/length.js +built-ins/Temporal/TimeZone/prototype/getPreviousTransition/length.js +built-ins/Temporal/TimeZone/prototype/getNextTransition/length.js +built-ins/Temporal/TimeZone/prototype/getOffsetStringFor/length.js +built-ins/Temporal/TimeZone/prototype/equals/length.js +built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/length.js +built-ins/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/length.js +built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/length.js +built-ins/Temporal/ZonedDateTime/length.js +built-ins/Temporal/ZonedDateTime/from/length.js +built-ins/Temporal/ZonedDateTime/prototype/with/length.js +built-ins/Temporal/ZonedDateTime/prototype/withTimeZone/length.js +built-ins/Temporal/ZonedDateTime/prototype/subtract/length.js +built-ins/Temporal/ZonedDateTime/prototype/since/length.js +built-ins/Temporal/ZonedDateTime/prototype/withCalendar/length.js +built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/length.js +built-ins/Temporal/ZonedDateTime/prototype/until/length.js +built-ins/Temporal/ZonedDateTime/prototype/equals/length.js +built-ins/Temporal/ZonedDateTime/prototype/add/length.js +built-ins/Temporal/ZonedDateTime/prototype/round/length.js +built-ins/Temporal/Instant/length.js +built-ins/Temporal/Instant/prototype/toZonedDateTime/length.js +built-ins/Temporal/Instant/prototype/subtract/length.js +built-ins/Temporal/Instant/prototype/since/length.js +built-ins/Temporal/Instant/prototype/until/length.js +built-ins/Temporal/Instant/prototype/toZonedDateTimeISO/length.js +built-ins/Temporal/Instant/prototype/equals/length.js +built-ins/Temporal/Instant/prototype/add/length.js +built-ins/Temporal/Instant/prototype/round/length.js +built-ins/Temporal/PlainTime/from/length.js +built-ins/Temporal/PlainTime/prototype/toZonedDateTime/length.js +built-ins/Temporal/PlainTime/prototype/with/length.js +built-ins/Temporal/PlainTime/prototype/toPlainDateTime/length.js +built-ins/Temporal/PlainTime/prototype/subtract/length.js +built-ins/Temporal/PlainTime/prototype/since/length.js +built-ins/Temporal/PlainTime/prototype/until/length.js +built-ins/Temporal/PlainTime/prototype/equals/length.js +built-ins/Temporal/PlainTime/prototype/add/length.js +built-ins/Temporal/PlainTime/prototype/round/length.js +built-ins/Temporal/PlainDate/length.js +built-ins/Temporal/PlainDate/from/length.js +built-ins/Temporal/PlainDate/prototype/toZonedDateTime/length.js +built-ins/Temporal/PlainDate/prototype/with/length.js +built-ins/Temporal/PlainDate/prototype/subtract/length.js +built-ins/Temporal/PlainDate/prototype/since/length.js +built-ins/Temporal/PlainDate/prototype/withCalendar/length.js +built-ins/Temporal/PlainDate/prototype/until/length.js +built-ins/Temporal/PlainDate/prototype/equals/length.js +built-ins/Temporal/PlainDate/prototype/add/length.js +built-ins/Temporal/Duration/prototype/with/length.js +built-ins/Temporal/Duration/prototype/subtract/length.js +built-ins/Temporal/Duration/prototype/total/length.js +built-ins/Temporal/Duration/prototype/add/length.js +built-ins/Temporal/Duration/prototype/round/length.js +built-ins/Temporal/Duration/compare/length.js +intl402/Temporal/Calendar/prototype/eraYear/length.js +intl402/Temporal/Calendar/prototype/era/length.js + +# +# find built-ins/Temporal intl402/Temporal -name 'not-a-constructor.js' +# +built-ins/Temporal/PlainMonthDay/prototype/toJSON/not-a-constructor.js +built-ins/Temporal/PlainMonthDay/prototype/with/not-a-constructor.js +built-ins/Temporal/PlainMonthDay/prototype/toPlainDate/not-a-constructor.js +built-ins/Temporal/PlainMonthDay/prototype/getCalendar/not-a-constructor.js +built-ins/Temporal/PlainMonthDay/prototype/valueOf/not-a-constructor.js +built-ins/Temporal/PlainMonthDay/prototype/equals/not-a-constructor.js +built-ins/Temporal/PlainMonthDay/prototype/toLocaleString/not-a-constructor.js +built-ins/Temporal/PlainMonthDay/prototype/toString/not-a-constructor.js +built-ins/Temporal/PlainMonthDay/prototype/getISOFields/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/toJSON/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/with/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/toPlainMonthDay/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/toPlainDate/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/subtract/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/since/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/withCalendar/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/withPlainDate/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/until/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/getCalendar/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/valueOf/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/equals/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/withPlainTime/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/add/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/toPlainYearMonth/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/toLocaleString/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/toString/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/toPlainTime/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/round/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/getISOFields/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/toJSON/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/with/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/toPlainDate/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/since/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/until/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/getCalendar/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/valueOf/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/equals/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/add/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/toLocaleString/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/toString/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/getISOFields/not-a-constructor.js +built-ins/Temporal/Calendar/prototype/toJSON/not-a-constructor.js built-ins/Temporal/Calendar/prototype/dayOfYear/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/daysInMonth/builtin.js -built-ins/Temporal/Calendar/prototype/daysInMonth/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/daysInWeek/builtin.js -built-ins/Temporal/Calendar/prototype/daysInWeek/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/daysInYear/builtin.js -built-ins/Temporal/Calendar/prototype/daysInYear/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/fields/builtin.js -EXPECTED built-ins/Temporal/Calendar/prototype/fields/long-input.js -built-ins/Temporal/Calendar/prototype/fields/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/inLeapYear/builtin.js -built-ins/Temporal/Calendar/prototype/inLeapYear/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/mergeFields/builtin.js -built-ins/Temporal/Calendar/prototype/mergeFields/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/month/builtin.js built-ins/Temporal/Calendar/prototype/month/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/monthCode/builtin.js +built-ins/Temporal/Calendar/prototype/dateFromFields/not-a-constructor.js +built-ins/Temporal/Calendar/prototype/dateAdd/not-a-constructor.js +built-ins/Temporal/Calendar/prototype/mergeFields/not-a-constructor.js built-ins/Temporal/Calendar/prototype/monthCode/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/monthDayFromFields/builtin.js built-ins/Temporal/Calendar/prototype/monthDayFromFields/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/monthsInYear/builtin.js -built-ins/Temporal/Calendar/prototype/monthsInYear/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/toJSON/builtin.js -built-ins/Temporal/Calendar/prototype/toJSON/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/toString/builtin.js +built-ins/Temporal/Calendar/prototype/yearMonthFromFields/not-a-constructor.js +built-ins/Temporal/Calendar/prototype/daysInWeek/not-a-constructor.js +built-ins/Temporal/Calendar/prototype/inLeapYear/not-a-constructor.js +built-ins/Temporal/Calendar/prototype/daysInMonth/not-a-constructor.js +built-ins/Temporal/Calendar/prototype/year/not-a-constructor.js +built-ins/Temporal/Calendar/prototype/dateUntil/not-a-constructor.js +built-ins/Temporal/Calendar/prototype/dayOfWeek/not-a-constructor.js +built-ins/Temporal/Calendar/prototype/fields/not-a-constructor.js built-ins/Temporal/Calendar/prototype/toString/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/weekOfYear/builtin.js built-ins/Temporal/Calendar/prototype/weekOfYear/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/year/builtin.js -built-ins/Temporal/Calendar/prototype/year/not-a-constructor.js -built-ins/Temporal/Calendar/prototype/yearMonthFromFields/builtin.js -built-ins/Temporal/Calendar/prototype/yearMonthFromFields/not-a-constructor.js -built-ins/Temporal/Duration/prototype/abs/builtin.js -built-ins/Temporal/Duration/prototype/abs/not-a-constructor.js -built-ins/Temporal/Duration/prototype/add/builtin.js -built-ins/Temporal/Duration/prototype/add/not-a-constructor.js -built-ins/Temporal/Duration/prototype/negated/builtin.js -built-ins/Temporal/Duration/prototype/negated/not-a-constructor.js -built-ins/Temporal/Duration/prototype/round/builtin.js -built-ins/Temporal/Duration/prototype/round/not-a-constructor.js -built-ins/Temporal/Duration/prototype/subtract/builtin.js -built-ins/Temporal/Duration/prototype/subtract/not-a-constructor.js -built-ins/Temporal/Duration/prototype/toJSON/builtin.js -built-ins/Temporal/Duration/prototype/toJSON/not-a-constructor.js -built-ins/Temporal/Duration/prototype/toLocaleString/builtin.js -built-ins/Temporal/Duration/prototype/toLocaleString/not-a-constructor.js -built-ins/Temporal/Duration/prototype/toString/builtin.js -built-ins/Temporal/Duration/prototype/toString/not-a-constructor.js -built-ins/Temporal/Duration/prototype/total/builtin.js -built-ins/Temporal/Duration/prototype/total/not-a-constructor.js -built-ins/Temporal/Duration/prototype/valueOf/builtin.js -built-ins/Temporal/Duration/prototype/valueOf/not-a-constructor.js -built-ins/Temporal/Duration/prototype/with/builtin.js -built-ins/Temporal/Duration/prototype/with/name.js -built-ins/Temporal/Duration/prototype/with/not-a-constructor.js -built-ins/Temporal/Instant/prototype/add/builtin.js -built-ins/Temporal/Instant/prototype/add/not-a-constructor.js -built-ins/Temporal/Instant/prototype/equals/builtin.js -built-ins/Temporal/Instant/prototype/equals/not-a-constructor.js -built-ins/Temporal/Instant/prototype/round/builtin.js -built-ins/Temporal/Instant/prototype/round/not-a-constructor.js -built-ins/Temporal/Instant/prototype/since/builtin.js -built-ins/Temporal/Instant/prototype/since/not-a-constructor.js -built-ins/Temporal/Instant/prototype/subtract/builtin.js -built-ins/Temporal/Instant/prototype/subtract/not-a-constructor.js -built-ins/Temporal/Instant/prototype/toJSON/builtin.js +built-ins/Temporal/Calendar/prototype/yearOfWeek/not-a-constructor.js +built-ins/Temporal/Calendar/prototype/day/not-a-constructor.js +built-ins/Temporal/Calendar/prototype/daysInYear/not-a-constructor.js +built-ins/Temporal/Calendar/prototype/monthsInYear/not-a-constructor.js +built-ins/Temporal/TimeZone/prototype/toJSON/not-a-constructor.js +built-ins/Temporal/TimeZone/prototype/getInstantFor/not-a-constructor.js +built-ins/Temporal/TimeZone/prototype/getPreviousTransition/not-a-constructor.js +built-ins/Temporal/TimeZone/prototype/getNextTransition/not-a-constructor.js +built-ins/Temporal/TimeZone/prototype/getOffsetStringFor/not-a-constructor.js +built-ins/Temporal/TimeZone/prototype/equals/not-a-constructor.js +built-ins/Temporal/TimeZone/prototype/toString/not-a-constructor.js +built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/not-a-constructor.js +built-ins/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/not-a-constructor.js +built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/toJSON/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/startOfDay/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/with/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/toPlainMonthDay/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/toPlainDateTime/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/withTimeZone/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/toPlainDate/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/subtract/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/since/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/withCalendar/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/getTimeZone/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/until/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/getCalendar/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/toInstant/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/valueOf/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/equals/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/add/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/toPlainYearMonth/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/toLocaleString/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/toString/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/toPlainTime/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/round/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/getISOFields/not-a-constructor.js built-ins/Temporal/Instant/prototype/toJSON/not-a-constructor.js -built-ins/Temporal/Instant/prototype/toLocaleString/builtin.js -built-ins/Temporal/Instant/prototype/toLocaleString/not-a-constructor.js -built-ins/Temporal/Instant/prototype/toString/builtin.js -built-ins/Temporal/Instant/prototype/toString/not-a-constructor.js -built-ins/Temporal/Instant/prototype/toZonedDateTime/builtin.js built-ins/Temporal/Instant/prototype/toZonedDateTime/not-a-constructor.js -built-ins/Temporal/Instant/prototype/toZonedDateTimeISO/builtin.js -built-ins/Temporal/Instant/prototype/toZonedDateTimeISO/not-a-constructor.js -built-ins/Temporal/Instant/prototype/until/builtin.js +built-ins/Temporal/Instant/prototype/subtract/not-a-constructor.js +built-ins/Temporal/Instant/prototype/since/not-a-constructor.js built-ins/Temporal/Instant/prototype/until/not-a-constructor.js -built-ins/Temporal/Instant/prototype/valueOf/builtin.js +built-ins/Temporal/Instant/prototype/toZonedDateTimeISO/not-a-constructor.js built-ins/Temporal/Instant/prototype/valueOf/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/add/builtin.js -built-ins/Temporal/PlainDate/prototype/add/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/equals/builtin.js -built-ins/Temporal/PlainDate/prototype/equals/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/getISOFields/builtin.js -built-ins/Temporal/PlainDate/prototype/getISOFields/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/since/builtin.js -built-ins/Temporal/PlainDate/prototype/since/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/subtract/builtin.js -built-ins/Temporal/PlainDate/prototype/subtract/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/toJSON/builtin.js +built-ins/Temporal/Instant/prototype/equals/not-a-constructor.js +built-ins/Temporal/Instant/prototype/add/not-a-constructor.js +built-ins/Temporal/Instant/prototype/toLocaleString/not-a-constructor.js +built-ins/Temporal/Instant/prototype/toString/not-a-constructor.js +built-ins/Temporal/Instant/prototype/round/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/toJSON/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/toZonedDateTime/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/with/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/toPlainDateTime/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/subtract/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/since/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/until/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/valueOf/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/equals/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/add/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/toLocaleString/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/toString/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/round/not-a-constructor.js +built-ins/Temporal/PlainTime/prototype/getISOFields/not-a-constructor.js built-ins/Temporal/PlainDate/prototype/toJSON/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/toLocaleString/builtin.js -built-ins/Temporal/PlainDate/prototype/toLocaleString/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/toPlainDateTime/builtin.js -built-ins/Temporal/PlainDate/prototype/toPlainDateTime/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/toPlainMonthDay/builtin.js -built-ins/Temporal/PlainDate/prototype/toPlainMonthDay/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/toPlainYearMonth/builtin.js -built-ins/Temporal/PlainDate/prototype/toPlainYearMonth/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/toString/builtin.js -built-ins/Temporal/PlainDate/prototype/toString/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/toZonedDateTime/builtin.js built-ins/Temporal/PlainDate/prototype/toZonedDateTime/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/until/builtin.js -built-ins/Temporal/PlainDate/prototype/until/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/valueOf/builtin.js -built-ins/Temporal/PlainDate/prototype/valueOf/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/with/builtin.js -built-ins/Temporal/PlainDate/prototype/with/name.js built-ins/Temporal/PlainDate/prototype/with/not-a-constructor.js -built-ins/Temporal/PlainDate/prototype/withCalendar/builtin.js +built-ins/Temporal/PlainDate/prototype/toPlainMonthDay/not-a-constructor.js +built-ins/Temporal/PlainDate/prototype/toPlainDateTime/not-a-constructor.js +built-ins/Temporal/PlainDate/prototype/subtract/not-a-constructor.js +built-ins/Temporal/PlainDate/prototype/since/not-a-constructor.js built-ins/Temporal/PlainDate/prototype/withCalendar/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/add/builtin.js -built-ins/Temporal/PlainDateTime/prototype/add/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/equals/builtin.js -built-ins/Temporal/PlainDateTime/prototype/equals/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/getISOFields/builtin.js -built-ins/Temporal/PlainDateTime/prototype/getISOFields/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/round/builtin.js -built-ins/Temporal/PlainDateTime/prototype/round/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/since/builtin.js -built-ins/Temporal/PlainDateTime/prototype/since/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/subtract/builtin.js -built-ins/Temporal/PlainDateTime/prototype/subtract/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/toJSON/builtin.js -built-ins/Temporal/PlainDateTime/prototype/toJSON/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/toLocaleString/builtin.js -built-ins/Temporal/PlainDateTime/prototype/toLocaleString/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/toPlainDate/builtin.js -built-ins/Temporal/PlainDateTime/prototype/toPlainDate/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/toPlainMonthDay/builtin.js -built-ins/Temporal/PlainDateTime/prototype/toPlainMonthDay/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/toPlainTime/builtin.js -built-ins/Temporal/PlainDateTime/prototype/toPlainTime/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/toPlainYearMonth/builtin.js -built-ins/Temporal/PlainDateTime/prototype/toPlainYearMonth/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/toString/builtin.js -built-ins/Temporal/PlainDateTime/prototype/toString/not-a-constructor.js +built-ins/Temporal/PlainDate/prototype/until/not-a-constructor.js +built-ins/Temporal/PlainDate/prototype/getCalendar/not-a-constructor.js +built-ins/Temporal/PlainDate/prototype/valueOf/not-a-constructor.js +built-ins/Temporal/PlainDate/prototype/equals/not-a-constructor.js +built-ins/Temporal/PlainDate/prototype/add/not-a-constructor.js +built-ins/Temporal/PlainDate/prototype/toPlainYearMonth/not-a-constructor.js +built-ins/Temporal/PlainDate/prototype/toLocaleString/not-a-constructor.js +built-ins/Temporal/PlainDate/prototype/toString/not-a-constructor.js +built-ins/Temporal/PlainDate/prototype/getISOFields/not-a-constructor.js +built-ins/Temporal/Duration/prototype/toJSON/not-a-constructor.js +built-ins/Temporal/Duration/prototype/with/not-a-constructor.js +built-ins/Temporal/Duration/prototype/subtract/not-a-constructor.js +built-ins/Temporal/Duration/prototype/valueOf/not-a-constructor.js +built-ins/Temporal/Duration/prototype/total/not-a-constructor.js +built-ins/Temporal/Duration/prototype/add/not-a-constructor.js +built-ins/Temporal/Duration/prototype/abs/not-a-constructor.js +built-ins/Temporal/Duration/prototype/toLocaleString/not-a-constructor.js +built-ins/Temporal/Duration/prototype/toString/not-a-constructor.js +built-ins/Temporal/Duration/prototype/round/not-a-constructor.js +built-ins/Temporal/Duration/prototype/negated/not-a-constructor.js +intl402/Temporal/Calendar/prototype/eraYear/not-a-constructor.js +intl402/Temporal/Calendar/prototype/era/not-a-constructor.js + +# +# find built-ins/Temporal intl402/Temporal -name 'builtin.js' +# +built-ins/Temporal/PlainMonthDay/prototype/toJSON/builtin.js +built-ins/Temporal/PlainMonthDay/prototype/with/builtin.js +built-ins/Temporal/PlainMonthDay/prototype/toPlainDate/builtin.js +built-ins/Temporal/PlainMonthDay/prototype/getCalendar/builtin.js +built-ins/Temporal/PlainMonthDay/prototype/valueOf/builtin.js +built-ins/Temporal/PlainMonthDay/prototype/equals/builtin.js +built-ins/Temporal/PlainMonthDay/prototype/toLocaleString/builtin.js +built-ins/Temporal/PlainMonthDay/prototype/toString/builtin.js +built-ins/Temporal/PlainMonthDay/prototype/getISOFields/builtin.js +built-ins/Temporal/PlainDateTime/prototype/toJSON/builtin.js built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/builtin.js -built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/until/builtin.js -built-ins/Temporal/PlainDateTime/prototype/until/not-a-constructor.js -built-ins/Temporal/PlainDateTime/prototype/valueOf/builtin.js -built-ins/Temporal/PlainDateTime/prototype/valueOf/not-a-constructor.js built-ins/Temporal/PlainDateTime/prototype/with/builtin.js -built-ins/Temporal/PlainDateTime/prototype/with/name.js -built-ins/Temporal/PlainDateTime/prototype/with/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/toPlainMonthDay/builtin.js +built-ins/Temporal/PlainDateTime/prototype/toPlainDate/builtin.js +built-ins/Temporal/PlainDateTime/prototype/subtract/builtin.js +built-ins/Temporal/PlainDateTime/prototype/since/builtin.js built-ins/Temporal/PlainDateTime/prototype/withCalendar/builtin.js -built-ins/Temporal/PlainDateTime/prototype/withCalendar/not-a-constructor.js built-ins/Temporal/PlainDateTime/prototype/withPlainDate/builtin.js -built-ins/Temporal/PlainDateTime/prototype/withPlainDate/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/until/builtin.js +built-ins/Temporal/PlainDateTime/prototype/getCalendar/builtin.js +built-ins/Temporal/PlainDateTime/prototype/valueOf/builtin.js +built-ins/Temporal/PlainDateTime/prototype/equals/builtin.js built-ins/Temporal/PlainDateTime/prototype/withPlainTime/builtin.js -built-ins/Temporal/PlainDateTime/prototype/withPlainTime/not-a-constructor.js -built-ins/Temporal/PlainMonthDay/prototype/equals/builtin.js -built-ins/Temporal/PlainMonthDay/prototype/equals/not-a-constructor.js -built-ins/Temporal/PlainMonthDay/prototype/getISOFields/builtin.js -built-ins/Temporal/PlainMonthDay/prototype/getISOFields/not-a-constructor.js -built-ins/Temporal/PlainMonthDay/prototype/toJSON/builtin.js -built-ins/Temporal/PlainMonthDay/prototype/toJSON/not-a-constructor.js -built-ins/Temporal/PlainMonthDay/prototype/toLocaleString/builtin.js -built-ins/Temporal/PlainMonthDay/prototype/toLocaleString/not-a-constructor.js -built-ins/Temporal/PlainMonthDay/prototype/toPlainDate/builtin.js -built-ins/Temporal/PlainMonthDay/prototype/toPlainDate/not-a-constructor.js -built-ins/Temporal/PlainMonthDay/prototype/toString/builtin.js -built-ins/Temporal/PlainMonthDay/prototype/toString/not-a-constructor.js -built-ins/Temporal/PlainMonthDay/prototype/valueOf/builtin.js -built-ins/Temporal/PlainMonthDay/prototype/valueOf/not-a-constructor.js -built-ins/Temporal/PlainMonthDay/prototype/with/builtin.js -built-ins/Temporal/PlainMonthDay/prototype/with/name.js -built-ins/Temporal/PlainMonthDay/prototype/with/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/add/builtin.js -built-ins/Temporal/PlainTime/prototype/add/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/equals/builtin.js -built-ins/Temporal/PlainTime/prototype/equals/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/getISOFields/builtin.js -built-ins/Temporal/PlainTime/prototype/getISOFields/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/round/builtin.js -built-ins/Temporal/PlainTime/prototype/round/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/since/builtin.js -built-ins/Temporal/PlainTime/prototype/since/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/subtract/builtin.js -built-ins/Temporal/PlainTime/prototype/subtract/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/toJSON/builtin.js -built-ins/Temporal/PlainTime/prototype/toJSON/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/toLocaleString/builtin.js -built-ins/Temporal/PlainTime/prototype/toLocaleString/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/toPlainDateTime/builtin.js -built-ins/Temporal/PlainTime/prototype/toPlainDateTime/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/toString/builtin.js -built-ins/Temporal/PlainTime/prototype/toString/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/toZonedDateTime/builtin.js -built-ins/Temporal/PlainTime/prototype/toZonedDateTime/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/until/builtin.js -built-ins/Temporal/PlainTime/prototype/until/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/valueOf/builtin.js -built-ins/Temporal/PlainTime/prototype/valueOf/not-a-constructor.js -built-ins/Temporal/PlainTime/prototype/with/builtin.js -built-ins/Temporal/PlainTime/prototype/with/name.js -built-ins/Temporal/PlainTime/prototype/with/not-a-constructor.js -built-ins/Temporal/PlainYearMonth/prototype/add/builtin.js -built-ins/Temporal/PlainYearMonth/prototype/add/not-a-constructor.js -built-ins/Temporal/PlainYearMonth/prototype/equals/builtin.js -built-ins/Temporal/PlainYearMonth/prototype/equals/not-a-constructor.js -built-ins/Temporal/PlainYearMonth/prototype/getISOFields/builtin.js -built-ins/Temporal/PlainYearMonth/prototype/getISOFields/not-a-constructor.js -built-ins/Temporal/PlainYearMonth/prototype/since/builtin.js -built-ins/Temporal/PlainYearMonth/prototype/since/not-a-constructor.js -built-ins/Temporal/PlainYearMonth/prototype/subtract/builtin.js -built-ins/Temporal/PlainYearMonth/prototype/subtract/not-a-constructor.js +built-ins/Temporal/PlainDateTime/prototype/add/builtin.js +built-ins/Temporal/PlainDateTime/prototype/toPlainYearMonth/builtin.js +built-ins/Temporal/PlainDateTime/prototype/toLocaleString/builtin.js +built-ins/Temporal/PlainDateTime/prototype/toString/builtin.js +built-ins/Temporal/PlainDateTime/prototype/toPlainTime/builtin.js +built-ins/Temporal/PlainDateTime/prototype/round/builtin.js +built-ins/Temporal/PlainDateTime/prototype/getISOFields/builtin.js built-ins/Temporal/PlainYearMonth/prototype/toJSON/builtin.js -built-ins/Temporal/PlainYearMonth/prototype/toJSON/not-a-constructor.js -built-ins/Temporal/PlainYearMonth/prototype/toLocaleString/builtin.js -built-ins/Temporal/PlainYearMonth/prototype/toLocaleString/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/with/builtin.js built-ins/Temporal/PlainYearMonth/prototype/toPlainDate/builtin.js -built-ins/Temporal/PlainYearMonth/prototype/toPlainDate/not-a-constructor.js -built-ins/Temporal/PlainYearMonth/prototype/toString/builtin.js -built-ins/Temporal/PlainYearMonth/prototype/toString/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/builtin.js +built-ins/Temporal/PlainYearMonth/prototype/since/builtin.js built-ins/Temporal/PlainYearMonth/prototype/until/builtin.js -built-ins/Temporal/PlainYearMonth/prototype/until/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/getCalendar/builtin.js built-ins/Temporal/PlainYearMonth/prototype/valueOf/builtin.js -built-ins/Temporal/PlainYearMonth/prototype/valueOf/not-a-constructor.js -built-ins/Temporal/PlainYearMonth/prototype/with/builtin.js -built-ins/Temporal/PlainYearMonth/prototype/with/name.js -built-ins/Temporal/PlainYearMonth/prototype/with/not-a-constructor.js +built-ins/Temporal/PlainYearMonth/prototype/equals/builtin.js +built-ins/Temporal/PlainYearMonth/prototype/add/builtin.js +built-ins/Temporal/PlainYearMonth/prototype/toLocaleString/builtin.js +built-ins/Temporal/PlainYearMonth/prototype/toString/builtin.js +built-ins/Temporal/PlainYearMonth/prototype/getISOFields/builtin.js +built-ins/Temporal/Calendar/prototype/toJSON/builtin.js +built-ins/Temporal/Calendar/prototype/dayOfYear/builtin.js +built-ins/Temporal/Calendar/prototype/month/builtin.js +built-ins/Temporal/Calendar/prototype/dateFromFields/builtin.js +built-ins/Temporal/Calendar/prototype/dateAdd/builtin.js +built-ins/Temporal/Calendar/prototype/mergeFields/builtin.js +built-ins/Temporal/Calendar/prototype/monthCode/builtin.js +built-ins/Temporal/Calendar/prototype/monthDayFromFields/builtin.js +built-ins/Temporal/Calendar/prototype/yearMonthFromFields/builtin.js +built-ins/Temporal/Calendar/prototype/daysInWeek/builtin.js +built-ins/Temporal/Calendar/prototype/inLeapYear/builtin.js +built-ins/Temporal/Calendar/prototype/daysInMonth/builtin.js +built-ins/Temporal/Calendar/prototype/year/builtin.js +built-ins/Temporal/Calendar/prototype/dateUntil/builtin.js +built-ins/Temporal/Calendar/prototype/dayOfWeek/builtin.js +built-ins/Temporal/Calendar/prototype/fields/builtin.js +built-ins/Temporal/Calendar/prototype/toString/builtin.js +built-ins/Temporal/Calendar/prototype/weekOfYear/builtin.js +built-ins/Temporal/Calendar/prototype/yearOfWeek/builtin.js +built-ins/Temporal/Calendar/prototype/day/builtin.js +built-ins/Temporal/Calendar/prototype/daysInYear/builtin.js +built-ins/Temporal/Calendar/prototype/monthsInYear/builtin.js +built-ins/Temporal/TimeZone/prototype/toJSON/builtin.js built-ins/Temporal/TimeZone/prototype/getInstantFor/builtin.js -built-ins/Temporal/TimeZone/prototype/getInstantFor/not-a-constructor.js +built-ins/Temporal/TimeZone/prototype/getPreviousTransition/builtin.js built-ins/Temporal/TimeZone/prototype/getNextTransition/builtin.js -built-ins/Temporal/TimeZone/prototype/getNextTransition/not-a-constructor.js -built-ins/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/builtin.js -built-ins/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/not-a-constructor.js built-ins/Temporal/TimeZone/prototype/getOffsetStringFor/builtin.js -built-ins/Temporal/TimeZone/prototype/getOffsetStringFor/not-a-constructor.js -built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/builtin.js -built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/not-a-constructor.js -built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/builtin.js -built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/not-a-constructor.js -built-ins/Temporal/TimeZone/prototype/getPreviousTransition/builtin.js -built-ins/Temporal/TimeZone/prototype/getPreviousTransition/not-a-constructor.js -built-ins/Temporal/TimeZone/prototype/toJSON/builtin.js -built-ins/Temporal/TimeZone/prototype/toJSON/not-a-constructor.js +built-ins/Temporal/TimeZone/prototype/equals/builtin.js built-ins/Temporal/TimeZone/prototype/toString/builtin.js -built-ins/Temporal/TimeZone/prototype/toString/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/add/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/add/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/equals/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/equals/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/getISOFields/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/getISOFields/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/round/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/round/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/since/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/since/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/startOfDay/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/startOfDay/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/subtract/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/subtract/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/toInstant/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/toInstant/not-a-constructor.js +built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/builtin.js +built-ins/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/builtin.js +built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/builtin.js built-ins/Temporal/ZonedDateTime/prototype/toJSON/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/toJSON/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/toLocaleString/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/toLocaleString/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/toPlainDate/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/toPlainDate/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/toPlainDateTime/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/toPlainDateTime/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/toPlainMonthDay/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/toPlainMonthDay/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/toPlainTime/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/toPlainTime/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/toPlainYearMonth/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/toPlainYearMonth/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/toString/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/toString/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/until/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/until/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/valueOf/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/valueOf/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/startOfDay/builtin.js built-ins/Temporal/ZonedDateTime/prototype/with/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/with/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/with/name.js +built-ins/Temporal/ZonedDateTime/prototype/toPlainMonthDay/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/toPlainDateTime/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/withTimeZone/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/toPlainDate/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/subtract/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/since/builtin.js built-ins/Temporal/ZonedDateTime/prototype/withCalendar/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/withCalendar/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/getTimeZone/builtin.js built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/until/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/getCalendar/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/toInstant/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/valueOf/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/equals/builtin.js built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/not-a-constructor.js -built-ins/Temporal/ZonedDateTime/prototype/withTimeZone/builtin.js -built-ins/Temporal/ZonedDateTime/prototype/withTimeZone/not-a-constructor.js -intl402/Temporal/Calendar/prototype/era/builtin.js -intl402/Temporal/Calendar/prototype/era/not-a-constructor.js +built-ins/Temporal/ZonedDateTime/prototype/add/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/toPlainYearMonth/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/toLocaleString/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/toString/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/toPlainTime/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/round/builtin.js +built-ins/Temporal/ZonedDateTime/prototype/getISOFields/builtin.js +built-ins/Temporal/Instant/prototype/toJSON/builtin.js +built-ins/Temporal/Instant/prototype/toZonedDateTime/builtin.js +built-ins/Temporal/Instant/prototype/subtract/builtin.js +built-ins/Temporal/Instant/prototype/since/builtin.js +built-ins/Temporal/Instant/prototype/until/builtin.js +built-ins/Temporal/Instant/prototype/toZonedDateTimeISO/builtin.js +built-ins/Temporal/Instant/prototype/valueOf/builtin.js +built-ins/Temporal/Instant/prototype/equals/builtin.js +built-ins/Temporal/Instant/prototype/add/builtin.js +built-ins/Temporal/Instant/prototype/toLocaleString/builtin.js +built-ins/Temporal/Instant/prototype/toString/builtin.js +built-ins/Temporal/Instant/prototype/round/builtin.js +built-ins/Temporal/PlainTime/prototype/toJSON/builtin.js +built-ins/Temporal/PlainTime/prototype/toZonedDateTime/builtin.js +built-ins/Temporal/PlainTime/prototype/with/builtin.js +built-ins/Temporal/PlainTime/prototype/toPlainDateTime/builtin.js +built-ins/Temporal/PlainTime/prototype/subtract/builtin.js +built-ins/Temporal/PlainTime/prototype/since/builtin.js +built-ins/Temporal/PlainTime/prototype/until/builtin.js +built-ins/Temporal/PlainTime/prototype/valueOf/builtin.js +built-ins/Temporal/PlainTime/prototype/equals/builtin.js +built-ins/Temporal/PlainTime/prototype/add/builtin.js +built-ins/Temporal/PlainTime/prototype/toLocaleString/builtin.js +built-ins/Temporal/PlainTime/prototype/toString/builtin.js +built-ins/Temporal/PlainTime/prototype/round/builtin.js +built-ins/Temporal/PlainTime/prototype/getISOFields/builtin.js +built-ins/Temporal/PlainDate/prototype/toJSON/builtin.js +built-ins/Temporal/PlainDate/prototype/toZonedDateTime/builtin.js +built-ins/Temporal/PlainDate/prototype/with/builtin.js +built-ins/Temporal/PlainDate/prototype/toPlainMonthDay/builtin.js +built-ins/Temporal/PlainDate/prototype/toPlainDateTime/builtin.js +built-ins/Temporal/PlainDate/prototype/subtract/builtin.js +built-ins/Temporal/PlainDate/prototype/since/builtin.js +built-ins/Temporal/PlainDate/prototype/withCalendar/builtin.js +built-ins/Temporal/PlainDate/prototype/until/builtin.js +built-ins/Temporal/PlainDate/prototype/getCalendar/builtin.js +built-ins/Temporal/PlainDate/prototype/valueOf/builtin.js +built-ins/Temporal/PlainDate/prototype/equals/builtin.js +built-ins/Temporal/PlainDate/prototype/add/builtin.js +built-ins/Temporal/PlainDate/prototype/toPlainYearMonth/builtin.js +built-ins/Temporal/PlainDate/prototype/toLocaleString/builtin.js +built-ins/Temporal/PlainDate/prototype/toString/builtin.js +built-ins/Temporal/PlainDate/prototype/getISOFields/builtin.js +built-ins/Temporal/Duration/prototype/toJSON/builtin.js +built-ins/Temporal/Duration/prototype/with/builtin.js +built-ins/Temporal/Duration/prototype/subtract/builtin.js +built-ins/Temporal/Duration/prototype/valueOf/builtin.js +built-ins/Temporal/Duration/prototype/total/builtin.js +built-ins/Temporal/Duration/prototype/add/builtin.js +built-ins/Temporal/Duration/prototype/abs/builtin.js +built-ins/Temporal/Duration/prototype/toLocaleString/builtin.js +built-ins/Temporal/Duration/prototype/toString/builtin.js +built-ins/Temporal/Duration/prototype/round/builtin.js +built-ins/Temporal/Duration/prototype/negated/builtin.js intl402/Temporal/Calendar/prototype/eraYear/builtin.js -intl402/Temporal/Calendar/prototype/eraYear/not-a-constructor.js +intl402/Temporal/Calendar/prototype/era/builtin.js + +# prop-desc +built-ins/Temporal/PlainDate/prototype/prop-desc.js +built-ins/Temporal/PlainDateTime/prototype/prop-desc.js +built-ins/Temporal/PlainYearMonth/prototype/prop-desc.js +built-ins/Temporal/PlainMonthDay/prototype/prop-desc.js +built-ins/Temporal/PlainTime/prototype/prop-desc.js +built-ins/Temporal/Instant/prototype/prop-desc.js +built-ins/Temporal/Duration/prototype/prop-desc.js +built-ins/Temporal/Calendar/prototype/prop-desc.js +built-ins/Temporal/ZonedDateTime/prototype/prop-desc.js +built-ins/Temporal/TimeZone/prototype/prop-desc.js + +# not allowed to call built-in utilities like Number.isFinite or Math.sign +built-ins/Temporal/Duration/call-builtin.js + +# we use array spreading internally +built-ins/Temporal/PlainMonthDay/from/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainMonthDay/prototype/with/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainMonthDay/prototype/toPlainDate/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainMonthDay/prototype/equals/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDateTime/from/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDateTime/prototype/with/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDateTime/prototype/toPlainMonthDay/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDateTime/prototype/since/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDateTime/prototype/withPlainDate/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDateTime/prototype/until/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDateTime/prototype/equals/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDateTime/prototype/toPlainYearMonth/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDateTime/compare/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainYearMonth/from/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainYearMonth/prototype/with/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainYearMonth/prototype/toPlainDate/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainYearMonth/prototype/since/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainYearMonth/prototype/since/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainYearMonth/prototype/until/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainYearMonth/prototype/until/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainYearMonth/prototype/equals/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainYearMonth/prototype/add/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainYearMonth/compare/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/dayOfYear/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/month/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/dateAdd/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/monthCode/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/daysInWeek/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/inLeapYear/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/daysInMonth/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/year/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/dateUntil/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/dayOfWeek/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/weekOfYear/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/yearOfWeek/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/day/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/daysInYear/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Calendar/prototype/monthsInYear/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/TimeZone/prototype/getInstantFor/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/TimeZone/prototype/getPossibleInstantsFor/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/ZonedDateTime/from/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/ZonedDateTime/prototype/with/builtin-calendar-no-array-iteration.js +built-ins/Temporal/ZonedDateTime/prototype/toPlainMonthDay/builtin-calendar-no-array-iteration.js +built-ins/Temporal/ZonedDateTime/prototype/since/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/ZonedDateTime/prototype/until/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/ZonedDateTime/prototype/equals/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/ZonedDateTime/prototype/toPlainYearMonth/builtin-calendar-no-array-iteration.js +built-ins/Temporal/ZonedDateTime/compare/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainTime/prototype/toZonedDateTime/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainTime/prototype/toPlainDateTime/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDate/from/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDate/prototype/with/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDate/prototype/toPlainMonthDay/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDate/prototype/since/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDate/prototype/until/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDate/prototype/equals/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDate/prototype/toPlainYearMonth/builtin-calendar-no-array-iteration.js +built-ins/Temporal/PlainDate/compare/argument-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Duration/prototype/subtract/relativeto-propertybag-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Duration/prototype/total/relativeto-propertybag-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Duration/prototype/add/relativeto-propertybag-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Duration/prototype/round/relativeto-propertybag-builtin-calendar-no-array-iteration.js +built-ins/Temporal/Duration/compare/relativeto-propertybag-builtin-calendar-no-array-iteration.js +intl402/Temporal/Calendar/prototype/era/argument-builtin-calendar-no-array-iteration.js +intl402/Temporal/Calendar/prototype/eraYear/argument-builtin-calendar-no-array-iteration.js diff --git a/packages/temporal-polyfill/scripts/test-config/expected-failures.txt b/packages/temporal-polyfill/scripts/test-config/expected-failures.txt new file mode 100644 index 00000000..a42d5458 --- /dev/null +++ b/packages/temporal-polyfill/scripts/test-config/expected-failures.txt @@ -0,0 +1,280 @@ + +#################################################################################################### +# Duration +#################################################################################################### + +# SPEC-BUG +# Duration::round wrong with zdt relativeTo +# https://github.com/tc39/proposal-temporal/issues/2742 +intl402/Temporal/Duration/prototype/round/relativeto-string-datetime.js +built-ins/Temporal/Duration/prototype/round/timezone-getpossibleinstantsfor-iterable.js + +# PRECISION +# Duration::toString... we display super large duration units as Number would, not BigInt +built-ins/Temporal/Duration/prototype/toString/precision-exact-mathematical-values.js +built-ins/Temporal/Duration/prototype/toString/precision-formatted-as-decimal-number.js + +#################################################################################################### +# ZonedDateTime +#################################################################################################### + +# CALLING +# for converting from ZonedDateTime -> PlainYearMonth, Calendar::year/monthCode is supposed to be queried. +# for converting from ZonedDateTime -> PlainMonthDay, Calendar::monthCode/day is supposed to be queried. +# Instead, we treat the incoming ZonedDateTime as a bag and access these parts as normal properties. +# Better for code compactness. +# (Same shortcoming for PlainDateTime but those tests are not written) +built-ins/Temporal/ZonedDateTime/prototype/toPlainMonthDay/order-of-operations.js +built-ins/Temporal/ZonedDateTime/prototype/toPlainYearMonth/order-of-operations.js + +#################################################################################################### +# PlainDateTime +#################################################################################################### + +# CALLING +# PlainDateTime::with is supposed to access time parts via internal slots. +# We treat the current PlainDateTime as a bag and access the time parts as normal properties, +# Better for code compactness. +built-ins/Temporal/PlainDateTime/prototype/with/order-of-operations.js + +#################################################################################################### +# Calendar +#################################################################################################### + +# SPEC-BUG +# CalendarRecord's dateAdd method is plucked but never used because all units are <=day +# just don't pluck it. file bug? +built-ins/Temporal/Duration/compare/order-of-operations.js +built-ins/Temporal/Duration/prototype/round/order-of-operations.js +built-ins/Temporal/Duration/prototype/total/order-of-operations.js + +# CALLING +# Our onion-shell balancing algorithm results in fewer calls to dateAdd/dateUntil. +# We do not do UnbalanceDateDurationRelative+BalanceDateDurationRelative +built-ins/Temporal/PlainDateTime/prototype/since/order-of-operations.js +built-ins/Temporal/PlainDateTime/prototype/until/order-of-operations.js +built-ins/Temporal/Duration/prototype/add/calendar-dateadd-called-with-options-undefined.js +built-ins/Temporal/Duration/prototype/round/calendar-dateadd-called-with-options-undefined.js +built-ins/Temporal/Duration/prototype/subtract/calendar-dateadd-called-with-options-undefined.js +built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateuntil-called-with-singular-largestunit.js +built-ins/Temporal/ZonedDateTime/prototype/until/calendar-dateuntil-called-with-singular-largestunit.js +built-ins/Temporal/Duration/prototype/round/calendar-dateuntil-called-with-singular-largestunit.js +built-ins/Temporal/ZonedDateTime/prototype/since/calendar-dateadd-called-with-options-undefined.js +built-ins/Temporal/ZonedDateTime/prototype/until/calendar-dateadd-called-with-options-undefined.js +built-ins/Temporal/Duration/prototype/round/dateuntil-field.js +built-ins/Temporal/PlainDate/prototype/until/order-of-operations.js +built-ins/Temporal/PlainDate/prototype/since/order-of-operations.js + +# CALLING +# Combination of above: "CalendarRecord's dateAdd method is plucked" and "Our onion-shell balancing algorithm" +built-ins/Temporal/Duration/prototype/add/order-of-operations.js +built-ins/Temporal/Duration/prototype/subtract/order-of-operations.js + +# CALLING +# In the spec, `AddDuration` adds dur0 y/m/w/d, then dur1 y/m/w/d, then combines and adds time parts. +# Our version uses two `moveDateTime` calls for better code reuse, which results in: +# +dur0.timeparts +dur0.ymwd +dur1.timeparts +dur1.ymwd +# Same ultimate results but different calls to Calendar::dateAdd w/ different intermediate durations. +built-ins/Temporal/Duration/prototype/subtract/calendar-dateadd.js + +# CALLING +# PlainYearMonth operations break spec because we don't convert to raw fields and then back +# to PlainYearMonth. We leverage Calendar::day instead +built-ins/Temporal/PlainYearMonth/prototype/since/calendar-datefromfields-called-with-options-undefined.js +built-ins/Temporal/PlainYearMonth/prototype/since/calendar-fields-iterable.js +built-ins/Temporal/PlainYearMonth/prototype/since/calendar-fromfields-called-with-null-prototype-fields.js +built-ins/Temporal/PlainYearMonth/prototype/since/order-of-operations.js +built-ins/Temporal/PlainYearMonth/prototype/until/calendar-datefromfields-called-with-options-undefined.js +built-ins/Temporal/PlainYearMonth/prototype/until/calendar-fields-iterable.js +built-ins/Temporal/PlainYearMonth/prototype/until/calendar-fromfields-called-with-null-prototype-fields.js +built-ins/Temporal/PlainYearMonth/prototype/until/order-of-operations.js +built-ins/Temporal/PlainYearMonth/prototype/add/order-of-operations.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/order-of-operations.js +built-ins/Temporal/PlainYearMonth/prototype/add/calendar-datefromfields-called.js +built-ins/Temporal/PlainYearMonth/prototype/add/calendar-fromfields-called-with-null-prototype-fields.js +built-ins/Temporal/PlainYearMonth/prototype/add/calendar-yearmonthfromfields-called-with-null-prototype-options.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/calendar-datefromfields-called.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/calendar-fromfields-called-with-null-prototype-fields.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/calendar-yearmonthfromfields-called-with-null-prototype-options.js +built-ins/Temporal/PlainYearMonth/prototype/add/end-of-month-out-of-range.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/end-of-month-out-of-range.js +built-ins/Temporal/PlainYearMonth/prototype/add/calendar-fields-iterable.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/calendar-fields-iterable.js +built-ins/Temporal/PlainYearMonth/prototype/add/constructor-in-calendar-fields.js +built-ins/Temporal/PlainYearMonth/prototype/add/duplicate-calendar-fields.js +built-ins/Temporal/PlainYearMonth/prototype/add/proto-in-calendar-fields.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/constructor-in-calendar-fields.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/duplicate-calendar-fields.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/proto-in-calendar-fields.js +built-ins/Temporal/PlainYearMonth/prototype/add/calendar-arguments.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/calendar-arguments.js +built-ins/Temporal/PlainYearMonth/prototype/add/calendar-arguments-extra-options.js +built-ins/Temporal/PlainYearMonth/prototype/add/overflow-wrong-type.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/calendar-arguments-extra-options.js +built-ins/Temporal/PlainYearMonth/prototype/subtract/overflow-wrong-type.js + +# CALLING +# when ZonedDateTime needs to query Calendar, should give PlainDateTime instead of PlainDate +built-ins/Temporal/ZonedDateTime/prototype/day/custom.js +built-ins/Temporal/ZonedDateTime/prototype/dayOfWeek/custom.js +built-ins/Temporal/ZonedDateTime/prototype/dayOfYear/custom.js +built-ins/Temporal/ZonedDateTime/prototype/daysInMonth/custom.js +built-ins/Temporal/ZonedDateTime/prototype/daysInWeek/custom.js +built-ins/Temporal/ZonedDateTime/prototype/daysInYear/custom.js +built-ins/Temporal/ZonedDateTime/prototype/inLeapYear/custom.js +built-ins/Temporal/ZonedDateTime/prototype/month/custom.js +built-ins/Temporal/ZonedDateTime/prototype/monthCode/custom.js +built-ins/Temporal/ZonedDateTime/prototype/monthsInYear/custom.js +built-ins/Temporal/ZonedDateTime/prototype/year/custom.js +built-ins/Temporal/ZonedDateTime/prototype/yearOfWeek/custom.js + +# CALLING +# problem with our adapter needing specific instance of Temporal object +built-ins/Temporal/Duration/compare/calendar-dateadd-called-with-plaindate-instance.js +built-ins/Temporal/PlainDate/prototype/add/custom.js +built-ins/Temporal/PlainDate/prototype/day/custom.js +built-ins/Temporal/PlainDate/prototype/dayOfWeek/custom.js +built-ins/Temporal/PlainDate/prototype/dayOfYear/custom.js +built-ins/Temporal/PlainDate/prototype/daysInMonth/custom.js +built-ins/Temporal/PlainDate/prototype/daysInWeek/custom.js +built-ins/Temporal/PlainDate/prototype/daysInYear/custom.js +built-ins/Temporal/PlainDate/prototype/inLeapYear/custom.js +built-ins/Temporal/PlainDate/prototype/month/custom.js +built-ins/Temporal/PlainDate/prototype/monthCode/custom.js +built-ins/Temporal/PlainDate/prototype/monthsInYear/custom.js +built-ins/Temporal/PlainDate/prototype/since/calendar-dateadd-called-with-plaindate-instance.js +built-ins/Temporal/PlainDate/prototype/subtract/custom.js +built-ins/Temporal/PlainDate/prototype/since/custom.js +built-ins/Temporal/PlainDate/prototype/until/calendar-dateadd-called-with-plaindate-instance.js +built-ins/Temporal/PlainDate/prototype/until/custom.js +built-ins/Temporal/PlainDate/prototype/weekOfYear/custom.js +built-ins/Temporal/PlainDate/prototype/with/custom.js +built-ins/Temporal/PlainDate/prototype/year/custom.js +built-ins/Temporal/PlainDate/prototype/yearOfWeek/custom.js +built-ins/Temporal/PlainDateTime/prototype/day/custom.js +built-ins/Temporal/PlainDateTime/prototype/dayOfWeek/custom.js +built-ins/Temporal/PlainDateTime/prototype/dayOfYear/custom.js +built-ins/Temporal/PlainDateTime/prototype/daysInMonth/custom.js +built-ins/Temporal/PlainDateTime/prototype/daysInWeek/custom.js +built-ins/Temporal/PlainDateTime/prototype/daysInYear/custom.js +built-ins/Temporal/PlainDateTime/prototype/inLeapYear/custom.js +built-ins/Temporal/PlainDateTime/prototype/month/custom.js +built-ins/Temporal/PlainDateTime/prototype/monthCode/custom.js +built-ins/Temporal/PlainDateTime/prototype/monthsInYear/custom.js +built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/plain-custom-timezone.js +built-ins/Temporal/PlainDateTime/prototype/weekOfYear/custom.js +built-ins/Temporal/PlainDateTime/prototype/year/custom.js +built-ins/Temporal/PlainDateTime/prototype/yearOfWeek/custom.js +built-ins/Temporal/ZonedDateTime/prototype/weekOfYear/custom.js +built-ins/Temporal/PlainYearMonth/prototype/daysInMonth/custom.js +built-ins/Temporal/PlainYearMonth/prototype/daysInYear/custom.js +built-ins/Temporal/PlainYearMonth/prototype/inLeapYear/custom.js +built-ins/Temporal/PlainYearMonth/prototype/month/custom.js +built-ins/Temporal/PlainYearMonth/prototype/monthCode/custom.js +built-ins/Temporal/PlainYearMonth/prototype/monthsInYear/custom.js +built-ins/Temporal/PlainYearMonth/prototype/year/custom.js +built-ins/Temporal/PlainMonthDay/prototype/day/custom.js +built-ins/Temporal/PlainMonthDay/prototype/monthCode/custom.js +built-ins/Temporal/TimeZone/prototype/getPlainDateTimeFor/custom-timezone.js +built-ins/Temporal/Duration/prototype/add/calendar-dateadd-called-with-plaindate-instance.js +built-ins/Temporal/Duration/prototype/subtract/calendar-dateadd-called-with-plaindate-instance.js +built-ins/Temporal/Duration/prototype/total/calendar-dateadd-called-with-plaindate-instance.js +built-ins/Temporal/Duration/prototype/round/calendar-dateadd-called-with-plaindate-instance.js + +#################################################################################################### +# TimeZone +#################################################################################################### + +# SPEC-BUG +# It's not necessary to compute hours-in-day when rounding time parts. File bug? +# (hours-in-day needs 2 extra getPossibleInstantsFor calls) +built-ins/Temporal/ZonedDateTime/prototype/round/timezone-getpossibleinstantsfor-iterable.js +built-ins/Temporal/ZonedDateTime/prototype/round/getpossibleinstantsfor-called-with-iso8601-calendar.js +built-ins/Temporal/ZonedDateTime/prototype/round/div-zero.js + +# PRECISION (TimeZone subclass/protocol only) +# we don't support hours-in-day greater than 10000000xxx (line 119 in the test), +# which can happen if 1-day-apart TimeZone::getPossibleInstantsFor calls give results wildly apart. +# results in slightly-less-precise-than-desirable (already ridiculous) .hoursInDay values +# this happens because we don't leverage bigint for such normally-simple operations +built-ins/Temporal/ZonedDateTime/prototype/hoursInDay/precision-exact-mathematical-values.js + +# CALLING +# Moving/Diffing/Rounding with ZonedDateTimes +# For balancing-up time parts to higher units, the spec uses NanosecondsToDays, +# which liberally calls TimeZoneProtocol::getOffsetNanosecondsFor and ::getPossibleInstants, +# and has copious sanity-checking around results to guard against malicious TimeZoneProtocols. +# We don't have a separate internal algorithm for balancing. Instead, we add+diff instead, which +# uses a simpler back-off strategy for detecting malicious TimeZoneProtocol results. +built-ins/Temporal/Duration/prototype/round/throws-in-balance-duration-when-sign-mismatched-with-zoned-date-time.js +built-ins/Temporal/Duration/prototype/round/zero-day-length.js +built-ins/Temporal/Duration/prototype/total/zero-day-length.js +built-ins/Temporal/ZonedDateTime/prototype/since/nanoseconds-to-days-range-errors.js +built-ins/Temporal/ZonedDateTime/prototype/until/nanoseconds-to-days-range-errors.js +built-ins/Temporal/Duration/prototype/add/relativeto-zoneddatetime-nanoseconds-to-days-range-errors.js +built-ins/Temporal/Duration/prototype/subtract/relativeto-zoneddatetime-nanoseconds-to-days-range-errors.js +built-ins/Temporal/Duration/prototype/round/relativeto-zoneddatetime-nanoseconds-to-days-range-errors.js +built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-nanoseconds-to-days-range-errors.js +built-ins/Temporal/Duration/prototype/add/nanoseconds-to-days-loop-indefinitely.js +built-ins/Temporal/Duration/prototype/round/nanoseconds-to-days-loop-indefinitely.js +built-ins/Temporal/Duration/prototype/subtract/nanoseconds-to-days-loop-indefinitely.js +built-ins/Temporal/Duration/prototype/total/nanoseconds-to-days-loop-indefinitely.js +built-ins/Temporal/ZonedDateTime/prototype/since/nanoseconds-to-days-loop-indefinitely.js +built-ins/Temporal/ZonedDateTime/prototype/until/nanoseconds-to-days-loop-indefinitely.js +built-ins/Temporal/ZonedDateTime/prototype/since/order-of-operations.js +built-ins/Temporal/ZonedDateTime/prototype/until/order-of-operations.js + +# CALLING +# From/With/Round/Compare/Equals uses InterpretISODateTimeOffset which calls +# TimeZoneProtocol::getPossibleInstants many more times than our algorithm, not sure why. +# The below tests might also be experiencing reduced TimeZoneProtocol calls due to the +# NanosecondsToDays issue mentioned above. +built-ins/Temporal/ZonedDateTime/prototype/round/order-of-operations.js +built-ins/Temporal/ZonedDateTime/prototype/with/order-of-operations.js +built-ins/Temporal/ZonedDateTime/from/order-of-operations.js +built-ins/Temporal/ZonedDateTime/compare/order-of-operations.js +built-ins/Temporal/ZonedDateTime/from/order-of-operations.js +built-ins/Temporal/ZonedDateTime/prototype/equals/order-of-operations.js +built-ins/Temporal/ZonedDateTime/from/argument-propertybag-ambiguous-wall-clock-time.js + +# CALLING +# our zonedInternalsToIso cache (which converts zdt to iso-fields) avoids a second call to getOffsetNanosecondsFor +built-ins/Temporal/ZonedDateTime/prototype/withPlainDate/order-of-operations.js + +# CALLING +# getPossibleInstantsFor wants plainDateTimes that sometimes have calendar. instead, always give iso +built-ins/Temporal/PlainDate/prototype/toZonedDateTime/timezone-getpossibleinstantsfor.js + +#################################################################################################### +# Intl +#################################################################################################### + +# NOT-IMPLEMENTED +# Spec says we should NOT canonicalize time zone IDs, but that's too hard while also normalizing them. +intl402/Temporal/ZonedDateTime/from/do-not-canonicalize-iana-identifiers.js +intl402/Temporal/TimeZone/prototype/equals/argument-object.js +intl402/DateTimeFormat/timezone-not-canonicalized.js +intl402/Temporal/TimeZone/basic.js +intl402/Temporal/TimeZone/from/argument-valid.js +intl402/Temporal/TimeZone/etc-timezone.js +intl402/Temporal/TimeZone/from/etc-timezone.js +intl402/Temporal/TimeZone/from/iana-legacy-names.js +intl402/Temporal/TimeZone/iana-legacy-names.js +intl402/Temporal/TimeZone/non-canonical-utc.js +intl402/Temporal/TimeZone/links-etcetera.js +intl402/Temporal/TimeZone/links-northamerica.js +intl402/Temporal/TimeZone/links-europe.js +intl402/Temporal/TimeZone/links-backzone.js +intl402/Temporal/TimeZone/links-backward.js +intl402/Temporal/TimeZone/links-asia.js +intl402/Temporal/TimeZone/links-africa.js +intl402/Temporal/TimeZone/from/timezone-case-insensitive.js +intl402/Temporal/ZonedDateTime/from/timezone-case-insensitive.js +intl402/DateTimeFormat/timezone-case-insensitive.js + +# NOT-IMPLEMENTED +# Intl.DateTimeFormat does not accept numeric-offset `timeZones`s and we are okay with that +intl402/DateTimeFormat/prototype/format/offset-timezone-gmt-same.js +intl402/DateTimeFormat/prototype/formatToParts/offset-timezone-correct.js +intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-basic.js +intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-change.js diff --git a/packages/temporal-polyfill/scripts/test.js b/packages/temporal-polyfill/scripts/test.js new file mode 100755 index 00000000..905d9652 --- /dev/null +++ b/packages/temporal-polyfill/scripts/test.js @@ -0,0 +1,80 @@ +#!/usr/bin/env node +/* +Based on https://github.com/js-temporal/temporal-polyfill/blob/main/runtest262.mjs +*/ + +import runTest262 from '@js-temporal/temporal-test262-runner'; +import { join as joinPaths } from 'path'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; + +const scriptsDir = joinPaths(process.argv[1], '..'); +const pkgDir = joinPaths(scriptsDir, '..'); +const monorepoDir = joinPaths(pkgDir, '../..'); + +yargs(hideBin(process.argv)) + .command( + '*', + 'Run test262 tests', + (builder) => { + builder.option('update', { + requiresArg: false, + default: false, + type: 'boolean', + description: 'Whether to update the existing expected-failure files on-disk and remove tests that now pass.' + }); + builder.option('timeout', { + requiresArg: false, + default: 30000, + type: 'number', + description: 'Millisecond allowance for a single test file to run' + }) + builder.option('max', { + requiresArg: false, + default: 10, + type: 'number', + description: 'Maxiumum allowed number of failures before aborting' + }) + builder.option('min', { // for "minify" + requiresArg: false, + default: false, + type: 'boolean', + description: 'Whether to test the minified bundle' + }) + }, + (parsedArgv) => { + const expectedFailureFiles = [ + 'expected-failures.txt', + 'expected-failures-surface.txt', + ]; + + const nodeVersion = process.versions.node + const nodeMajorVersion = parseInt(nodeVersion.split('.')[0]); + if (nodeMajorVersion >= 18) expectedFailureFiles.push('expected-failures-intl-format-norm.txt'); + if (nodeMajorVersion < 18) expectedFailureFiles.push('expected-failures-before-node18.txt'); + if (nodeMajorVersion < 16) expectedFailureFiles.push('expected-failures-before-node16.txt'); + + console.log(`Testing with Node v${nodeVersion}...`) + + const result = runTest262({ + test262Dir: joinPaths(monorepoDir, 'test262'), + polyfillCodeFile: joinPaths( + pkgDir, + parsedArgv.min + ? 'dist/global.min.js' + : 'dist/global.js' + ), + expectedFailureFiles: expectedFailureFiles.map((filename) => ( + joinPaths(scriptsDir, 'test-config', filename) + )), + testGlobs: parsedArgv._, + timeoutMsecs: parsedArgv.timeout || 86400000, + updateExpectedFailureFiles: parsedArgv.update, + maxFailures: parsedArgv.max, + fullPath: true + }); + + process.exit(result ? 0 : 1); + } + ) + .help().argv; diff --git a/packages/temporal-polyfill/src/argParse/calendar.ts b/packages/temporal-polyfill/src/argParse/calendar.ts deleted file mode 100644 index 713de345..00000000 --- a/packages/temporal-polyfill/src/argParse/calendar.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { isoCalendarID } from '../calendarImpl/isoCalendarImpl' -import { ensureObj } from '../dateUtils/abstract' -import { Calendar, createDefaultCalendar } from '../public/calendar' -import { isObjectLike } from './refine' - -// TODO: move to argParse like timeZoneFromObj? -export function calendarFromObj(obj: any): Temporal.CalendarProtocol { - const innerCalendar = obj.calendar - if (innerCalendar === undefined) { - return obj - } - if (isObjectLike(innerCalendar) && innerCalendar.calendar === undefined) { - return innerCalendar as any - } - return new Calendar(innerCalendar) -} - -export function extractCalendar(input: any): Temporal.CalendarProtocol { - if (input.calendar === undefined) { - return createDefaultCalendar() - } - return ensureObj(Calendar, input.calendar) -} - -export function getCommonCalendar( - obj0: { calendar: Temporal.CalendarProtocol }, - obj1: { calendar: Temporal.CalendarProtocol }, -): Temporal.CalendarProtocol { - const { calendar } = obj0 - ensureCalendarsEqual(calendar, obj1.calendar) - return calendar -} - -export function getStrangerCalendar( - obj0: { calendar: Temporal.CalendarProtocol }, - obj1: { calendar: Temporal.CalendarProtocol }, -): Temporal.CalendarProtocol { - const calendar0 = obj0.calendar - const calendar1 = obj1.calendar - - if (calendar0.id === isoCalendarID) { - return calendar1 - } - if (calendar1.id === isoCalendarID) { - return calendar0 - } - if (calendar0.id !== calendar1.id) { - throw new RangeError('Non-ISO calendars incompatible') - } - - return calendar0 -} - -export function ensureCalendarsEqual( - calendar0: Temporal.CalendarProtocol, - calendar1: Temporal.CalendarProtocol, -): void { - if (calendar0.toString() !== calendar1.toString()) { - throw new RangeError('Calendars must match') - } -} diff --git a/packages/temporal-polyfill/src/argParse/calendarDisplay.ts b/packages/temporal-polyfill/src/argParse/calendarDisplay.ts deleted file mode 100644 index 50bc1cb7..00000000 --- a/packages/temporal-polyfill/src/argParse/calendarDisplay.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createOptionParser } from './refine' - -export const CALENDAR_DISPLAY_AUTO = 0 -export const CALENDAR_DISPLAY_NEVER = 1 -export const CALENDAR_DISPLAY_ALWAYS = 2 -export type CalendarDisplayInt = 0 | 1 | 2 - -export interface CalendarDisplayMap { - auto: 0 - never: 1 - always: 2 -} -export const calendarDisplayMap: CalendarDisplayMap = { - auto: 0, - never: 1, - always: 2, -} - -export const parseCalendarDisplayOption = createOptionParser( - 'calendarName', - calendarDisplayMap, - CALENDAR_DISPLAY_AUTO, -) diff --git a/packages/temporal-polyfill/src/argParse/diffOptions.ts b/packages/temporal-polyfill/src/argParse/diffOptions.ts deleted file mode 100644 index 0fdb7de6..00000000 --- a/packages/temporal-polyfill/src/argParse/diffOptions.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { DAY, UnitInt, nanoIn } from '../dateUtils/units' -import { RoundingFunc } from '../utils/math' -import { ensureOptionsObj } from './refine' -import { parseRoundingModeOption } from './roundingMode' -import { parseUnit } from './unitStr' - -export interface DiffConfig { - smallestUnit: UnitType - largestUnit: UnitType - roundingFunc: RoundingFunc - roundingIncrement: number -} - -export function parseDiffOptions< - UnitArg extends Temporal.DateTimeUnit, - UnitIntType extends UnitInt ->( - options: Temporal.DifferenceOptions | undefined, - largestUnitDefault: UnitIntType, - smallestUnitDefault: UnitIntType, - minUnit: UnitIntType, - maxUnit: UnitIntType, - forDurationRounding?: boolean, // TODO: change to 'defaultRoundingFunc' -): DiffConfig { - const ensuredOptions = ensureOptionsObj(options) - const roundingIncrement = ensuredOptions.roundingIncrement ?? 1 - const smallestUnit = parseUnit(ensuredOptions.smallestUnit, smallestUnitDefault, minUnit, maxUnit) - const roundingFunc = parseRoundingModeOption( - ensuredOptions, - forDurationRounding ? Math.round : Math.trunc, - ) - - let largestUnitArg = ensuredOptions.largestUnit - if (largestUnitArg === 'auto') { - largestUnitArg = undefined - } - - largestUnitDefault = Math.max(largestUnitDefault, smallestUnit) as UnitIntType - const largestUnit = parseUnit(largestUnitArg, largestUnitDefault, minUnit, maxUnit) - - if (smallestUnit > largestUnit) { - throw new RangeError('Bad smallestUnit/largestUnit') - } - - if (smallestUnit < DAY) { - const largerNano = nanoIn[smallestUnit + 1] - const incNano = nanoIn[smallestUnit] * roundingIncrement - - if (largerNano === incNano) { - throw new RangeError('Must not equal larger unit') - } - - if (largerNano % incNano) { - throw new RangeError('Must divide into larger unit') - } - } - - return { - smallestUnit, - largestUnit, - roundingFunc, - roundingIncrement, - } -} diff --git a/packages/temporal-polyfill/src/argParse/disambig.ts b/packages/temporal-polyfill/src/argParse/disambig.ts deleted file mode 100644 index 3a42aeed..00000000 --- a/packages/temporal-polyfill/src/argParse/disambig.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { createOptionParser } from './refine' - -export const DISAMBIG_COMPATIBLE = 0 -export const DISAMBIG_EARLIER = 1 -export const DISAMBIG_LATER = 2 -export const DISAMBIG_REJECT = 3 -export type DisambigInt = 0 | 1 | 2 | 3 - -export interface DisambigMap { - compatible: 0 - earlier: 1 - later: 2 - reject: 3 -} -export const disambigMap: DisambigMap = { - compatible: 0, - earlier: 1, - later: 2, - reject: 3, -} - -export const parseDisambigOption = createOptionParser( - 'disambiguation', - disambigMap, - DISAMBIG_COMPATIBLE, -) diff --git a/packages/temporal-polyfill/src/argParse/fieldStr.ts b/packages/temporal-polyfill/src/argParse/fieldStr.ts deleted file mode 100644 index 46953a22..00000000 --- a/packages/temporal-polyfill/src/argParse/fieldStr.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { strArrayToHash } from '../utils/obj' -import { durationUnitNames } from './unitStr' - -export const yearMonthFieldMap = { - era: String, - eraYear: refineNumber, - year: refineNumber, - month: refineNumber, - monthCode: String, -} - -export const dateFieldMap = { - ...yearMonthFieldMap, - day: refineNumber, -} - -export const timeFieldMap = { - hour: refineNumber, - minute: refineNumber, - second: refineNumber, - millisecond: refineNumber, - microsecond: refineNumber, - nanosecond: refineNumber, -} - -export const monthDayFieldMap = { - era: String, - eraYear: refineNumber, - year: refineNumber, - month: refineNumber, - monthCode: String, - day: refineNumber, -} - -// TODO: more DRY with constrainInt -function refineNumber(input: any): number { - const num = Number(input) - - if (!Number.isFinite(num)) { - throw new RangeError('Number must be finite') - } - - return num -} - -export const durationFieldMap = strArrayToHash(durationUnitNames, () => Number) diff --git a/packages/temporal-polyfill/src/argParse/isoFormatOptions.ts b/packages/temporal-polyfill/src/argParse/isoFormatOptions.ts deleted file mode 100644 index 44908010..00000000 --- a/packages/temporal-polyfill/src/argParse/isoFormatOptions.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { - MICROSECOND, - MILLISECOND, - MINUTE, - NANOSECOND, - SECOND, - nanoIn, - unitDigitMap, -} from '../dateUtils/units' -import { OVERFLOW_REJECT } from './overflowHandling' -import { constrainInt, ensureOptionsObj } from './refine' -import { parseRoundingModeOption } from './roundingMode' -import { RoundingConfig } from './roundingOptions' -import { parseUnit } from './unitStr' - -export type DurationToStringUnitInt = // TODO: move this??? weird name/location for this - typeof SECOND | - typeof MILLISECOND | - typeof MICROSECOND | - typeof NANOSECOND - -export type TimeToStringUnitInt = typeof MINUTE | DurationToStringUnitInt - -export interface TimeToStringConfig< - UnitType extends TimeToStringUnitInt = TimeToStringUnitInt -> extends RoundingConfig { - fractionalSecondDigits: number | undefined -} - -export type DurationToStringConfig = TimeToStringConfig - -export function parseTimeToStringOptions( - options: Temporal.ToStringPrecisionOptions | undefined, - largestUnit: UnitType = MINUTE as UnitType, -): TimeToStringConfig { - const ensuredOptions = ensureOptionsObj(options) - const smallestUnitArg = ensuredOptions.smallestUnit - const digitsArg = ensuredOptions.fractionalSecondDigits - let smallestUnit = NANOSECOND as UnitType - let incNano = 1 - let digits: number | undefined - - if (smallestUnitArg !== undefined) { - smallestUnit = parseUnit( - smallestUnitArg, - undefined, // no default. a required field - NANOSECOND as UnitType, // minUnit - largestUnit, // maxUnit - ) - incNano = nanoIn[smallestUnit] - digits = unitDigitMap[smallestUnit] || 0 - } else if (digitsArg !== undefined && digitsArg !== 'auto') { - digits = constrainInt(digitsArg, 0, 9, OVERFLOW_REJECT) - incNano = Math.pow(10, 9 - digits) - } - - return { - smallestUnit, - fractionalSecondDigits: digits, - roundingFunc: parseRoundingModeOption(options, Math.trunc), - incNano, - } -} diff --git a/packages/temporal-polyfill/src/argParse/offsetDisplay.ts b/packages/temporal-polyfill/src/argParse/offsetDisplay.ts deleted file mode 100644 index fd6883d6..00000000 --- a/packages/temporal-polyfill/src/argParse/offsetDisplay.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createOptionParser } from './refine' - -export const OFFSET_DISPLAY_AUTO = 0 -export const OFFSET_DISPLAY_NEVER = 1 -export type OffsetDisplayInt = 0 | 1 - -export interface OffsetDisplayMap { - auto: 0 - never: 1 -} -export const offsetDisplayMap: OffsetDisplayMap = { - auto: 0, - never: 1, -} - -export const parseOffsetDisplayOption = createOptionParser( - 'offset', - offsetDisplayMap, - OFFSET_DISPLAY_AUTO, -) diff --git a/packages/temporal-polyfill/src/argParse/offsetHandling.ts b/packages/temporal-polyfill/src/argParse/offsetHandling.ts deleted file mode 100644 index d11ac2b6..00000000 --- a/packages/temporal-polyfill/src/argParse/offsetHandling.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createOptionParser } from './refine' - -export const OFFSET_PREFER = 0 -export const OFFSET_USE = 1 -export const OFFSET_IGNORE = 2 -export const OFFSET_REJECT = 3 -export type OffsetHandlingInt = 0 | 1 | 2 | 3 - -export interface OffsetHandlingMap { - prefer: 0 - use: 1 - ignore: 2 - reject: 3 -} -export const offsetHandlingMap: OffsetHandlingMap = { - prefer: 0, - use: 1, - ignore: 2, - reject: 3, -} - -export const parseOffsetHandlingOption = createOptionParser( - 'offset', - offsetHandlingMap, -) diff --git a/packages/temporal-polyfill/src/argParse/overflowHandling.ts b/packages/temporal-polyfill/src/argParse/overflowHandling.ts deleted file mode 100644 index 2d797474..00000000 --- a/packages/temporal-polyfill/src/argParse/overflowHandling.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createOptionParser } from './refine' - -export const OVERFLOW_CONSTRAIN = 0 -export const OVERFLOW_REJECT = 1 -export type OverflowHandlingInt = 0 | 1 - -export interface OverflowHandlingMap { - constrain: 0 - reject: 1 -} -export const overflowHandlingMap: OverflowHandlingMap = { - constrain: 0, - reject: 1, -} - -export const parseOverflowOption = createOptionParser( - 'overflow', - overflowHandlingMap, - OVERFLOW_CONSTRAIN, -) diff --git a/packages/temporal-polyfill/src/argParse/refine.ts b/packages/temporal-polyfill/src/argParse/refine.ts deleted file mode 100644 index 3b167784..00000000 --- a/packages/temporal-polyfill/src/argParse/refine.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { ValueOf } from '../utils/obj' -import { OVERFLOW_REJECT, OverflowHandlingInt } from './overflowHandling' - -export function createOptionParser(propName: string, map: Map, defaultVal?: ValueOf): ( - options: Record | undefined, // TODO: better type - runtimeDefaultVal?: ValueOf, -) => ValueOf { - const valueParser = createParser(propName, map, defaultVal) - return ( - options: Record | undefined, - runtimeDefaultVal?: ValueOf, - ) => { - const ensured = ensureOptionsObj(options) - return valueParser(ensured[propName] as any, runtimeDefaultVal) - } -} - -export function createParser(nameForError: string, map: Map, defaultVal?: ValueOf): ( - arg: keyof Map | undefined, - runtimeDefaultVal?: ValueOf -) => ValueOf { - return ( - input: keyof Map | undefined, - runtimeDefaultVal?: ValueOf, - ): ValueOf => { - if (input === undefined) { - const d = runtimeDefaultVal ?? defaultVal - if (d === undefined) { - throw new RangeError(`Must specify a ${nameForError}`) - } - return d - } - if (map[input] === undefined) { - throw new RangeError(`Invalid ${nameForError}: ${String(input)}`) - } - return map[input] - } -} - -// TODO: better error messages for invalid properties -export function constrainInt( - val: number | undefined, - min: number, // inclusive. serves as default - max: number, // inclusive - overflowHandling: OverflowHandlingInt, -): number { - if (val === undefined) { - return min - } - - if (!Number.isFinite(val)) { - throw new RangeError('Number must be finite') - } - - // convert floating-point to integer - val = Math.trunc(val) - - const newVal = Math.min(Math.max(val, min), max) - if (newVal !== val && overflowHandling === OVERFLOW_REJECT) { - throw new RangeError('Invalid overflowed value ' + val) - } - - return newVal -} - -export function refineFields any }>( - input: { [FieldName in keyof Map]?: unknown }, - refinerMap: Map, -): { [FieldName in keyof Map]?: ReturnType } { - const res: { [FieldName in keyof Map]?: ReturnType } = {} - - for (const fieldName in refinerMap) { - if (input[fieldName] !== undefined) { - res[fieldName] = refinerMap[fieldName](input[fieldName]) - } - } - - return res -} - -export function ensureOptionsObj( - options: Partial | undefined, - strict?: boolean, -): Partial { - if (options === undefined && !strict) { - return {} - } - if (!isObjectLike(options)) { - throw TypeError('options must be an object or undefined') - } - return options -} - -const objectLikeTypeRE = /object|function/ - -export function isObjectLike(v: any): v is Record { - return v !== null && objectLikeTypeRE.test(typeof v) -} diff --git a/packages/temporal-polyfill/src/argParse/roundingMode.ts b/packages/temporal-polyfill/src/argParse/roundingMode.ts deleted file mode 100644 index c55f1261..00000000 --- a/packages/temporal-polyfill/src/argParse/roundingMode.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { RoundingFunc } from '../utils/math' -import { createOptionParser } from './refine' - -export interface RoundingModeMap { - halfExpand: RoundingFunc - ceil: RoundingFunc - trunc: RoundingFunc - floor: RoundingFunc -} - -const roundingModeMap: RoundingModeMap = { - halfExpand: Math.round, - ceil: Math.ceil, - trunc: Math.trunc, - floor: Math.floor, -} - -// TODO: start using ENUM-like types. It's bad to have caller code referencing Math.* functions - -export const parseRoundingModeOption = createOptionParser( - 'roundingMode', - roundingModeMap, - // TODO: always default to Math.trunc? -) diff --git a/packages/temporal-polyfill/src/argParse/roundingOptions.ts b/packages/temporal-polyfill/src/argParse/roundingOptions.ts deleted file mode 100644 index 25453204..00000000 --- a/packages/temporal-polyfill/src/argParse/roundingOptions.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { DayTimeUnit } from '../dateUtils/dayAndTime' -import { DAY, DayTimeUnitInt, nanoIn, nanoInDay } from '../dateUtils/units' -import { RoundingFunc } from '../utils/math' -import { ensureOptionsObj } from './refine' -import { parseRoundingModeOption } from './roundingMode' -import { parseUnit } from './unitStr' - -export interface RoundingConfig { - smallestUnit: UnitType - roundingFunc: RoundingFunc - incNano: number -} - -export function parseRoundingOptions< - UnitArgType extends DayTimeUnit, - UnitType extends DayTimeUnitInt ->( - options: Temporal.RoundTo | UnitArgType, - minUnit: UnitType, - maxUnit: UnitType, - relaxedDivisibility?: boolean, -): RoundingConfig { - const optionsObj: Temporal.RoundTo | undefined = - typeof options === 'string' - ? { smallestUnit: options } - : options - - const ensuredOptions = ensureOptionsObj(optionsObj, true) // strict=true - const roundingIncrement = ensuredOptions.roundingIncrement ?? 1 - const smallestUnit = parseUnit(ensuredOptions.smallestUnit, undefined, minUnit, maxUnit) - const roundingFunc = parseRoundingModeOption(ensuredOptions, Math.round) - const incNano = nanoIn[smallestUnit] * roundingIncrement - - if (smallestUnit === DAY) { - if (roundingIncrement !== 1) { - throw new RangeError('When smallestUnit is days, roundingIncrement must be 1') - } - } else { - const largerNano = relaxedDivisibility ? nanoInDay : nanoIn[smallestUnit + 1] - - if (!relaxedDivisibility && largerNano === incNano) { - throw new RangeError('Must not equal larger unit') - } - - if (largerNano % incNano) { - throw new RangeError('Must divide into larger unit') - } - } - - return { - smallestUnit, - roundingFunc, - incNano, - } -} diff --git a/packages/temporal-polyfill/src/argParse/thisContext.ts b/packages/temporal-polyfill/src/argParse/thisContext.ts deleted file mode 100644 index 311b447c..00000000 --- a/packages/temporal-polyfill/src/argParse/thisContext.ts +++ /dev/null @@ -1,50 +0,0 @@ - -// TODO: apply to all other types of objects -export function ensureThisContext( - ObjClass: { prototype: Obj }, -): void { - const proto = ObjClass.prototype as any - - Object.getOwnPropertyNames(ObjClass.prototype).forEach((methodName: string) => { - if (methodName !== 'constructor') { - // not a getter - if (!Reflect.getOwnPropertyDescriptor(proto, methodName)?.get) { - const origMethod = proto[methodName] - - if (typeof origMethod === 'function') { // necessary? - // eslint-disable-next-line func-style - const newMethod = function(this: Obj) { - // https://stackoverflow.com/questions/367768/how-to-detect-if-a-function-is-called-as-constructor - if (new.target) { - throw new TypeError('Cannot call as constructor') - } - - if (!(this instanceof (ObjClass as any))) { - throw new TypeError(`this-context must be a ${proto[Symbol.toStringTag]}`) - } - // eslint-disable-next-line prefer-rest-params - return origMethod.apply(this, arguments as any) - } - - Object.defineProperty(newMethod, 'name', { - value: methodName, - // necessary options for a read-only property - writable: false, - enumerable: false, - configurable: true, - }) - - Object.defineProperty(newMethod, 'length', { - value: origMethod.length, - // necessary options for a read-only property - writable: false, - enumerable: false, - configurable: true, - }) - - proto[methodName] = newMethod - } - } - } - }) -} diff --git a/packages/temporal-polyfill/src/argParse/timeZone.ts b/packages/temporal-polyfill/src/argParse/timeZone.ts deleted file mode 100644 index f22c52c1..00000000 --- a/packages/temporal-polyfill/src/argParse/timeZone.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { ensureObj } from '../dateUtils/abstract' -import { TimeZone } from '../public/timeZone' -import { isObjectLike } from './refine' - -export function timeZoneFromObj(obj: any): Temporal.TimeZoneProtocol { - const innerTimeZone = obj.timeZone - if (innerTimeZone === undefined) { - return obj - } - if (isObjectLike(innerTimeZone) && innerTimeZone.timeZone === undefined) { - return innerTimeZone as any - } - return new TimeZone(innerTimeZone) -} - -export function extractTimeZone(input: any): Temporal.TimeZoneProtocol { - if (input.timeZone === undefined) { - throw new TypeError('Must specify timeZone') - } - return ensureObj(TimeZone, input.timeZone) -} diff --git a/packages/temporal-polyfill/src/argParse/timeZoneDisplay.ts b/packages/temporal-polyfill/src/argParse/timeZoneDisplay.ts deleted file mode 100644 index ef20e6c1..00000000 --- a/packages/temporal-polyfill/src/argParse/timeZoneDisplay.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createOptionParser } from './refine' - -export const TIME_ZONE_DISPLAY_AUTO = 0 -export const TIME_ZONE_DISPLAY_NEVER = 1 -export type TimeZoneDisplayInt = 0 | 1 - -export interface TimeZoneDisplayMap { - auto: 0 - never: 1 -} -export const timeZoneDisplayMap: TimeZoneDisplayMap = { - auto: 0, - never: 1, -} - -export const parseTimeZoneDisplayOption = createOptionParser( - 'timeZoneName', - timeZoneDisplayMap, - TIME_ZONE_DISPLAY_AUTO, -) diff --git a/packages/temporal-polyfill/src/argParse/totalOptions.ts b/packages/temporal-polyfill/src/argParse/totalOptions.ts deleted file mode 100644 index 0110dabb..00000000 --- a/packages/temporal-polyfill/src/argParse/totalOptions.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { NANOSECOND, UnitInt, YEAR } from '../dateUtils/units' -import { PlainDateTimeArg } from '../public/plainDateTime' -import { ensureOptionsObj } from './refine' -import { parseUnit } from './unitStr' - -// only for duration - -export interface DurationTotalConfig { - unit: UnitInt, - relativeTo?: PlainDateTimeArg -} - -export function parseTotalConfig(optionsArg: Temporal.DurationTotalOf): DurationTotalConfig { - let relativeTo: PlainDateTimeArg | undefined - let unitName: Temporal.TotalUnit | undefined - - if (typeof optionsArg === 'string') { - unitName = optionsArg - } else { - unitName = ensureOptionsObj(optionsArg).unit - relativeTo = optionsArg.relativeTo - } - - return { - unit: parseUnit(unitName, undefined, NANOSECOND, YEAR), - relativeTo, - } -} diff --git a/packages/temporal-polyfill/src/argParse/unitStr.ts b/packages/temporal-polyfill/src/argParse/unitStr.ts deleted file mode 100644 index 00adb9ac..00000000 --- a/packages/temporal-polyfill/src/argParse/unitStr.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { UnsignedDurationFields } from '../dateUtils/durationFields' -import { UnitInt } from '../dateUtils/units' -import { strArrayToHash } from '../utils/obj' - -// These names must match the indexes of the Unit integers - -export const timeUnitNames: Temporal.TimeUnit[] = [ - 'nanosecond', - 'microsecond', - 'millisecond', - 'second', - 'minute', - 'hour', -] -export const dateUnitNames: Temporal.DateUnit[] = [ - 'day', - 'week', - 'month', - 'year', -] -export const unitNames: Temporal.DateTimeUnit[] = [ - ...timeUnitNames, - ...dateUnitNames, -] - -// Duration / Plurals -// TODO: use Temporal.PluralUnit type somehow? - -export const durationUnitNames: (keyof UnsignedDurationFields)[] = unitNames.map( - (unit) => (unit + 's') as keyof UnsignedDurationFields, -) - -// Parsing - -const unitMap = strArrayToHash(unitNames, (_str, i) => i) -const pluralUnitMap = strArrayToHash(durationUnitNames, (_str, i) => i) - -export function parseUnit( - input: Temporal.DateTimeUnit | Temporal.PluralUnit | undefined, - defaultUnit: UnitType | undefined, - minUnit: UnitType, - maxUnit: UnitType, -): UnitType { - let num: UnitType - if (input === undefined) { - if (defaultUnit === undefined) { - throw new RangeError('Unit is required') // TOOD: better error message with setting name - } - num = defaultUnit - } else { - num = (unitMap[input] ?? pluralUnitMap[input]) as UnitType - - if (num === undefined || num < minUnit || num > maxUnit) { - throw new RangeError('Invalid unit ' + input) // TOOD: better error message with setting name - } - } - - return num -} diff --git a/packages/temporal-polyfill/src/calendarImpl/bugs.ts b/packages/temporal-polyfill/src/calendarImpl/bugs.ts deleted file mode 100644 index 6e8e1c08..00000000 --- a/packages/temporal-polyfill/src/calendarImpl/bugs.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { isoToEpochMilli } from '../dateUtils/epoch' -import { milliInDay, nanoInMilli } from '../dateUtils/units' -import { LargeInt } from '../utils/largeInt' -import { queryCalendarImpl } from './calendarImplQuery' -import { IntlCalendarImpl } from './intlCalendarImpl' - -// https://bugs.chromium.org/p/chromium/issues/detail?id=1173158 -const good1582 = isoToEpochMilli(1582, 10, 15) - -// https://bugs.chromium.org/p/v8/issues/detail?id=10527 -const goodIslamic = isoToEpochMilli(622, 7, 18) - -const goodEpochMillis: { [cal: string]: number } = { - buddhist: good1582, - japanese: good1582, - roc: good1582, - islamic: goodIslamic, - 'islamic-rgsa': goodIslamic, - indian: 0, // https://bugs.chromium.org/p/v8/issues/detail?id=10529 -} - -const hasBugByID: { [cal: string]: boolean } = {} - -export function checkEpochNanoBuggy(epochNano: LargeInt, calendarID: string): void { - return checkEpochMilliBuggy(epochNano.div(nanoInMilli).toNumber(), calendarID) -} - -export function checkEpochMilliBuggy(epochMilli: number, calendarID: string): void { - if (isEpochMilliBuggy(epochMilli, calendarID)) { - throw new RangeError('Invalid timestamp for calendar') - } -} - -function isEpochMilliBuggy(epochMilli: number, calendarID: string): boolean { - return hasEpochMilliBug(calendarID) && epochMilli < goodEpochMillis[calendarID] -} - -function hasEpochMilliBug(calendarID: string) { - let hasBug = hasBugByID[calendarID] - if (hasBug === undefined) { - const goodEpochMilli = goodEpochMillis[calendarID] - if (goodEpochMilli === undefined) { - hasBug = false - } else { - let impl = queryCalendarImpl(calendarID) - - // HACK - // Even if Japanese, must leverage Intl.DateTimeFormat, so force IntlCalendarImpl - if (!(impl instanceof IntlCalendarImpl)) { - impl = new IntlCalendarImpl(calendarID) - } - - const badEpochMilli = goodEpochMilli - milliInDay - const fields = impl.computeFields(badEpochMilli) - hasBug = badEpochMilli !== impl.epochMilliseconds(fields.year, fields.month, fields.day) - } - hasBugByID[calendarID] = hasBug - } - return hasBug -} diff --git a/packages/temporal-polyfill/src/calendarImpl/calendarImpl.ts b/packages/temporal-polyfill/src/calendarImpl/calendarImpl.ts deleted file mode 100644 index bb0817cd..00000000 --- a/packages/temporal-polyfill/src/calendarImpl/calendarImpl.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { numSign } from '../utils/math' -import { padZeros } from '../utils/string' -import { eraOrigins } from './eraOrigins' - -export interface CalendarImplFields { // like DateFields, but without monthCode - era: string | undefined - eraYear: number | undefined - year: number - month: number - day: number -} - -export interface CalendarImplFieldsDumb { // like CalendarImplFields, but with month string - era: string | undefined - eraYear: number | undefined - year: number, - month: string, - day: number -} - -export abstract class CalendarImpl { - constructor( - public id: string, - ) {} - - // ISO -> Calendar-dependent - - abstract computeFields(epochMilli: number): CalendarImplFields - - // Calendar-dependent computation - // caller is responsible for constraining given values - - abstract epochMilliseconds(year: number, month: number, day: number): number - abstract daysInMonth(year: number, month: number): number - abstract monthsInYear(year: number): number - abstract inLeapYear(year: number): boolean - abstract guessYearForMonthDay(monthCode: string, day: number): number - abstract normalizeISOYearForMonthDay(isoYear: number): number - - // month -> monthCode - monthCode(month: number, _year: number): string { - return 'M' + padZeros(month, 2) - } - - // monthCode -> month - // not responsible for constraining - // TODO: throw error when not starting with M? - convertMonthCode(monthCode: string, _year: number): [ - number, // month - boolean, // unusedLeap (a valid 'L', but not used in this year) - ] { - // TODO: more DRY - const monthCodeIsLeap = /L$/.test(monthCode) - const monthCodeInt = parseInt(monthCode.substr(1)) // chop off 'M' - - if (monthCodeIsLeap) { - throw new RangeError('Calendar system doesnt support leap months') // TODO: more DRY - } - - return [monthCodeInt, false] - } -} - -// eraYear -> year -export function convertEraYear( - calendarID: string, - eraYear: number, - era: string, - fromDateTimeFormat?: boolean, -): number { - let origin = eraOrigins[getCalendarIDBase(calendarID)]?.[era] - - if (origin === undefined) { - if (fromDateTimeFormat) { - origin = 0 - } else { - throw new Error('Unkown era ' + era) - } - } - - // see the origin format in the config file - return (origin + eraYear) * (numSign(origin) || 1) -} - -// TODO: somehow combine with convertEraYear -export function hasEras(calendarID: string): boolean { - return eraOrigins[getCalendarIDBase(calendarID)] !== undefined -} - -export function getCalendarIDBase(calendarID: string): string { - return calendarID.split('-')[0] -} diff --git a/packages/temporal-polyfill/src/calendarImpl/calendarImplQuery.ts b/packages/temporal-polyfill/src/calendarImpl/calendarImplQuery.ts deleted file mode 100644 index 8558e0cc..00000000 --- a/packages/temporal-polyfill/src/calendarImpl/calendarImplQuery.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { CalendarImpl, getCalendarIDBase } from './calendarImpl' -import { GregoryCalendarImpl } from './gregoryCalendarImpl' -import { IntlCalendarImpl } from './intlCalendarImpl' -import { IslamicCalendarImpl } from './islamicCalendarImpl' -import { isoCalendarID, isoCalendarImpl } from './isoCalendarImpl' -import { JapaneseCalendarImpl } from './japaneseCalendarImpl' - -const implClasses: { [calendarID: string]: { new(id: string): CalendarImpl } } = { - gregory: GregoryCalendarImpl, - japanese: JapaneseCalendarImpl, - islamic: IslamicCalendarImpl, -} - -const implCache: { [calendarID: string]: CalendarImpl } = { - [isoCalendarID]: isoCalendarImpl, -} - -export function queryCalendarImpl(id: string): CalendarImpl { - id = String(id) - const key = id.toLocaleLowerCase() // lowercase matches isoCalendarID - - return implCache[key] || - (implCache[key] = new ( - implClasses[getCalendarIDBase(key)] || - IntlCalendarImpl - )(id)) -} diff --git a/packages/temporal-polyfill/src/calendarImpl/eraOrigins.ts b/packages/temporal-polyfill/src/calendarImpl/eraOrigins.ts deleted file mode 100644 index 1c10229a..00000000 --- a/packages/temporal-polyfill/src/calendarImpl/eraOrigins.ts +++ /dev/null @@ -1,51 +0,0 @@ - -// for converting from [era,eraYear] -> year -// if origin is >=0, -// year = origin + eraYear -// if origin is <0, consider the era to be 'reverse' direction -// year = -origin - eraYear -// year = -(origin + eraYear) -export const eraOrigins: { - [calendarID: string]: { [era: string]: number } -} = { - gregory: { - bce: -1, - ce: 0, - }, - ethioaa: { - era0: 0, - }, - ethiopic: { - era0: 0, - era1: 5500, - }, - coptic: { - era0: -1, - era1: 0, - }, - roc: { - beforeroc: -1, - minguo: 0, - }, - buddhist: { - be: 0, - }, - islamic: { - ah: 0, - }, - indian: { - saka: 0, - }, - persian: { - ap: 0, - }, - japanese: { - bce: -1, - ce: 0, - meiji: 1867, - taisho: 1911, - showa: 1925, - heisei: 1988, - reiwa: 2018, - }, -} diff --git a/packages/temporal-polyfill/src/calendarImpl/gregoryCalendarImpl.ts b/packages/temporal-polyfill/src/calendarImpl/gregoryCalendarImpl.ts deleted file mode 100644 index 07ad57ea..00000000 --- a/packages/temporal-polyfill/src/calendarImpl/gregoryCalendarImpl.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { CalendarImplFields } from './calendarImpl' -import { ISOCalendarImpl } from './isoCalendarImpl' - -// for converting year -> [era,eraYear] -// (can't use eraOrigins, it's for the other direction) -export class GregoryCalendarImpl extends ISOCalendarImpl { - computeFields(epochMilli: number): CalendarImplFields { - const fields = super.computeFields(epochMilli) - const { year } = fields - return { - ...fields, - era: year < 1 ? 'bce' : 'ce', - eraYear: year < 1 ? -(year - 1) : year, - } - } -} diff --git a/packages/temporal-polyfill/src/calendarImpl/intlCalendarImpl.ts b/packages/temporal-polyfill/src/calendarImpl/intlCalendarImpl.ts deleted file mode 100644 index 781d8970..00000000 --- a/packages/temporal-polyfill/src/calendarImpl/intlCalendarImpl.ts +++ /dev/null @@ -1,292 +0,0 @@ -import { computeDaysInYear } from '../dateUtils/calendar' -import { - addDaysMilli, - diffDaysMilli, - isoEpochOriginYear, - isoToEpochMilli, -} from '../dateUtils/epoch' -import { hashIntlFormatParts, normalizeShortEra } from '../dateUtils/intlFormat' -import { OrigDateTimeFormat } from '../native/intlUtils' -import { - CalendarImpl, - CalendarImplFields, - CalendarImplFieldsDumb, - convertEraYear, - getCalendarIDBase, - hasEras, -} from './calendarImpl' - -type MonthCache = [ - number[], // epochMillis - string[], // monthStrs - { [monthStr: string]: number }, // monthStrToNum (value is 1-based) -] - -const calLeapMonths: { [cal: string]: number } = { - hebrew: 6, // consistent month - chinese: 0, // zero implies variable month - dangi: 0, // " -} - -export class IntlCalendarImpl extends CalendarImpl { - private format: Intl.DateTimeFormat - - // difference between iso year numbers and the calendar's at 1980 - private yearCorrection: number - - // epochMilli starting points for each month - private monthCacheByYear: { [year: string]: MonthCache } - - constructor(id: string) { - const format = buildFormat(id) - - if (!isRelatedCalendar(id, format.resolvedOptions().calendar)) { - throw new RangeError('Invalid calendar: ' + id) - } - - super(id) - this.format = format - this.yearCorrection = this.computeFieldsDumb(0).year - isoEpochOriginYear - this.monthCacheByYear = {} - } - - epochMilliseconds(year: number, month: number, day: number): number { - const epochMillis = this.queryMonthCache(year)[0] - const marker = epochMillis[month - 1] - - // move to correct day-of-month - return addDaysMilli(marker, day - 1) - } - - daysInMonth(year: number, month: number): number { - const epochMillis = this.queryMonthCache(year)[0] - const startMarker = epochMillis[month - 1] - - // The `month` variable, which is 1-based, should now be considered an index for `monthCache` - // It is +1 from the previous index, used to compute the `endMarker` - if (month >= epochMillis.length) { - year++ - month = 0 - } - - const endMarker = this.queryMonthCache(year)[0][month] - return diffDaysMilli(startMarker, endMarker) - } - - monthsInYear(year: number): number { - const epochMillis = this.queryMonthCache(year)[0] - return epochMillis.length - } - - // month -> monthCode - monthCode(month: number, year: number): string { - const leapMonth = this.queryLeapMonthByYear(year) - - if (!leapMonth || month < leapMonth) { - return super.monthCode(month, year) - } - - return super.monthCode(month - 1, year) + - (month === leapMonth ? 'L' : '') - } - - // monthCode -> month - convertMonthCode(monthCode: string, year: number): [number, boolean] { - const leapMonth = this.queryLeapMonthByYear(year) // 0 if none - - // TODO: more DRY - let monthCodeIsLeap = /L$/.test(monthCode) - let monthCodeInt = parseInt(monthCode.substr(1)) // chop off 'M' - let unusedLeap = false - - // validate the leap-month - if (monthCodeIsLeap) { - const presetLeapMonth = calLeapMonths[this.id] // TODO: use base ID? - - if (presetLeapMonth === undefined) { - throw new RangeError('Calendar system doesnt support leap months') - } - - if (presetLeapMonth) { - if (monthCodeInt !== presetLeapMonth - 1) { - throw new RangeError('Invalid leap-month month code') - } - } else { // variable leap months (HACK: hardcoded for chinese/dangi) - if (monthCodeInt <= 1 || monthCodeInt >= 12) { - throw new RangeError('Invalid leap-month month code') - } - } - } - - if (monthCodeIsLeap && !(leapMonth && monthCodeInt === leapMonth - 1)) { - unusedLeap = true - monthCodeIsLeap = false // yuck - } - - if (monthCodeIsLeap || (leapMonth && monthCodeInt >= leapMonth)) { - monthCodeInt++ - } - - return [monthCodeInt, unusedLeap] - } - - // TODO: look at number of months too? - inLeapYear(year: number): boolean { - const days = computeDaysInYear(this, year) - return days > computeDaysInYear(this, year - 1) && - days > computeDaysInYear(this, year + 1) - } - - guessYearForMonthDay(monthCode: string, day: number): number { - let year = isoEpochOriginYear + this.yearCorrection - const maxYear = year + 100 - - for (; year < maxYear; year++) { - const [month, unusedLeap] = this.convertMonthCode(monthCode, year) - - if ( - !unusedLeap && - month <= this.monthsInYear(year) && - day <= this.daysInMonth(year, month) - ) { - return year - } - } - - throw new Error('Could not guess year') // TODO: better error - } - - normalizeISOYearForMonthDay(isoYear: number): number { - return isoYear - } - - computeFields(epochMilli: number): CalendarImplFields { - const dumbFields = this.computeFieldsDumb(epochMilli) - const monthStrToNum = this.queryMonthCache(dumbFields.year)[2] - - return { - ...dumbFields, - month: monthStrToNum[dumbFields.month], - } - } - - // returns a *string* month, not numeric - private computeFieldsDumb(epochMilli: number): CalendarImplFieldsDumb { - const partHash = hashIntlFormatParts(this.format, epochMilli) - let era: string | undefined - let eraYear: number | undefined - let year = parseInt(partHash.relatedYear || partHash.year) - - if (partHash.era && hasEras(this.id)) { - era = normalizeShortEra(partHash.era) - eraYear = year - year = convertEraYear(this.id, eraYear, era, true) // fromDateTimeFormat=true - } - - return { - era, - eraYear, - year, - month: partHash.month, - day: parseInt(partHash.day), - } - } - - // the month number (1-based) that the leap-month falls on - // for example, the '3bis' leap month would fall on `4` - // TODO: cache somehow? - private queryLeapMonthByYear(year: number): number | undefined { - const currentCache = this.queryMonthCache(year) - const prevCache = this.queryMonthCache(year - 1) - const nextCache = this.queryMonthCache(year + 1) - - // in a leap year? - // TODO: consolidate with inLeapYear? - if ( - currentCache[0].length > prevCache[0].length && - currentCache[0].length > nextCache[0].length - ) { - const currentMonthStrs = currentCache[1] - const prevMonthStrs = prevCache[1] - - for (let i = 0; i < prevMonthStrs.length; i++) { - if (prevMonthStrs[i] !== currentMonthStrs[i]) { - return i + 1 // convert to 1-based - } - } - } - - return undefined - } - - private queryMonthCache(year: number): MonthCache { - const { monthCacheByYear } = this - - return monthCacheByYear[year] || - (monthCacheByYear[year] = this.buildMonthCache(year)) - } - - private buildMonthCache(year: number): MonthCache { - const epochMillis: number[] = [] - const monthStrs: string[] = [] - const monthStrToNum: { [monthStr: string]: number } = {} - - // either part-way through the desired year or very slightly before - let epochMilli = isoToEpochMilli(this.guessISOYear(year), 1, 1) - - // ensure marker is in year+1 - epochMilli = addDaysMilli(epochMilli, 400) - - // move epochMilli into past through each month - while (true) { - const fields = this.computeFieldsDumb(epochMilli) - - // stop if month went too far into past - if (fields.year < year) { - break - } - - // move to start-of-month - epochMilli = addDaysMilli(epochMilli, 1 - fields.day) - - // only record the epochMilli if NOT in future year - if (fields.year === year) { - epochMillis.unshift(epochMilli) - monthStrs.unshift(fields.month) - } - - // move to last day of previous month - epochMilli = addDaysMilli(epochMilli, -1) - } - - for (let i = 0; i < monthStrs.length; i++) { - monthStrToNum[monthStrs[i]] = i + 1 - } - - return [epochMillis, monthStrs, monthStrToNum] - } - - // subclasses that override should round UP - protected guessISOYear(year: number): number { - return year - this.yearCorrection - } -} - -// Exposed Internals, for subclasses - -export function buildFormat(calendarID: string): Intl.DateTimeFormat { - return new OrigDateTimeFormat('en-US', { - calendar: calendarID, - era: 'short', // 'narrow' is too terse for japanese months - year: 'numeric', - month: 'short', // easier to identify monthCodes - day: 'numeric', - timeZone: 'UTC', - }) -} - -// utils - -function isRelatedCalendar(specificCalendarID: string, relatedCalendarID: string): boolean { - return getCalendarIDBase(specificCalendarID) === getCalendarIDBase(relatedCalendarID) -} diff --git a/packages/temporal-polyfill/src/calendarImpl/islamicCalendarImpl.ts b/packages/temporal-polyfill/src/calendarImpl/islamicCalendarImpl.ts deleted file mode 100644 index 45bf5d1a..00000000 --- a/packages/temporal-polyfill/src/calendarImpl/islamicCalendarImpl.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IntlCalendarImpl } from './intlCalendarImpl' - -export class IslamicCalendarImpl extends IntlCalendarImpl { - protected guessISOYear(year: number): number { - // https://en.wikipedia.org/wiki/Hijri_year#Formula - // round UP because superclass said so - return Math.ceil(year * 32 / 33 + 622) - } -} diff --git a/packages/temporal-polyfill/src/calendarImpl/isoCalendarImpl.ts b/packages/temporal-polyfill/src/calendarImpl/isoCalendarImpl.ts deleted file mode 100644 index 3ef3767d..00000000 --- a/packages/temporal-polyfill/src/calendarImpl/isoCalendarImpl.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { epochMilliToISOFields, isoEpochLeapYear, isoToEpochMilli } from '../dateUtils/epoch' -import { CalendarImpl, CalendarImplFields } from './calendarImpl' - -export class ISOCalendarImpl extends CalendarImpl { - computeFields(epochMilli: number): CalendarImplFields { - const fields = epochMilliToISOFields(epochMilli) - return { - era: undefined, - eraYear: undefined, - year: fields.isoYear, - month: fields.isoMonth, - day: fields.isoDay, - } - } - - epochMilliseconds(year: number, month: number, day: number): number { - return isoToEpochMilli(year, month, day) - } - - // will work for any year, not just years within valid Date range - daysInMonth(year: number, month: number): number { - if (month === 2) { - return this.inLeapYear(year) ? 29 : 28 - } - if (month === 4 || month === 6 || month === 9 || month === 11) { - return 30 - } - return 31 - } - - monthsInYear(): number { - return 12 - } - - inLeapYear(year: number): boolean { - return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) - } - - guessYearForMonthDay(): number { - return isoEpochLeapYear - } - - normalizeISOYearForMonthDay(): number { - return isoEpochLeapYear - } -} - -export const isoCalendarID = 'iso8601' -export const isoCalendarImpl = new ISOCalendarImpl(isoCalendarID) diff --git a/packages/temporal-polyfill/src/calendarImpl/japaneseCalendarImpl.ts b/packages/temporal-polyfill/src/calendarImpl/japaneseCalendarImpl.ts deleted file mode 100644 index 140f2f48..00000000 --- a/packages/temporal-polyfill/src/calendarImpl/japaneseCalendarImpl.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { isoToEpochMilli } from '../dateUtils/epoch' -import { hashIntlFormatParts, normalizeShortEra } from '../dateUtils/intlFormat' -import { CalendarImplFields } from './calendarImpl' -import { GregoryCalendarImpl } from './gregoryCalendarImpl' -import { buildFormat } from './intlCalendarImpl' - -const primaryEraMilli = isoToEpochMilli(1868, 9, 8) - -/* -The Japanese calendar has same months like Gregorian, same eraYears, -but has era names that are Japanese after a certain point. -*/ -export class JapaneseCalendarImpl extends GregoryCalendarImpl { - private format = buildFormat('japanese') - - computeFields(epochMilli: number): CalendarImplFields { - const fields = super.computeFields(epochMilli) - - if (epochMilli >= primaryEraMilli) { - const partHash = hashIntlFormatParts(this.format, epochMilli) - fields.era = normalizeShortEra(partHash.era) - fields.eraYear = parseInt(partHash.relatedYear || partHash.year) // TODO: more DRY w/ intl - } - - return fields - } -} diff --git a/packages/temporal-polyfill/src/classApi/calendar.ts b/packages/temporal-polyfill/src/classApi/calendar.ts new file mode 100644 index 00000000..7a961aba --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/calendar.ts @@ -0,0 +1,193 @@ +import { DateBagStrict, MonthDayBagStrict, YearMonthBagStrict, dateFieldNamesAlpha } from '../internal/calendarFields' +import { requireNonNullish, requireString } from '../internal/cast' +import { LargestUnitOptions, OverflowOptions, refineCalendarDiffOptions } from '../internal/optionsRefine' +import { excludeUndefinedProps } from '../internal/utils' +import { getRequiredDateFields, getRequiredMonthDayFields, getRequiredYearMonthFields } from '../internal/calendarConfig' +import { createSlotClass } from './slotsForClasses' +import { refineCalendarSlot } from './slotsForClasses' +import { PlainDateTime } from './plainDateTime' +import { ZonedDateTime } from './zonedDateTime' +import { PlainDate, PlainDateArg, createPlainDate, toPlainDateSlots } from './plainDate' +import { PlainMonthDay, createPlainMonthDay } from './plainMonthDay' +import { PlainYearMonth, createPlainYearMonth } from './plainYearMonth' +import { Duration, DurationArg, createDuration, toDurationSlots } from './duration' +import { NativeStandardOps } from '../internal/calendarNative' +import { calendarFieldMethods } from './mixins' +import { createNativeStandardOps, normalizeCalendarId } from '../internal/calendarNativeQuery' +import { refinePlainDateBag, refinePlainMonthDayBag, refinePlainYearMonthBag } from '../internal/bag' +import { BrandingSlots, createDurationSlots, createPlainDateSlots } from '../internal/slots' +import * as errorMessages from '../internal/errorMessages' +import { createProtocolChecker } from './utils' + +export type Calendar = any +export type CalendarArg = CalendarProtocol | string | PlainDate | PlainDateTime | ZonedDateTime | PlainMonthDay | PlainYearMonth +export type CalendarClassSlots = BrandingSlots & { + id: string, + native: NativeStandardOps +} + +const calendarMethods = { + toString(slots: CalendarClassSlots) { + return slots.id + }, + toJSON(slots: CalendarClassSlots) { + return slots.id + }, + ...calendarFieldMethods, + dateAdd( + { id, native }: CalendarClassSlots, + plainDateArg: PlainDateArg, + durationArg: DurationArg, + options?: OverflowOptions, + ): PlainDate { + return createPlainDate( + createPlainDateSlots( + native.dateAdd( + toPlainDateSlots(plainDateArg), + toDurationSlots(durationArg), + options, + ), + id, + ) + ) + }, + dateUntil( + { native }: CalendarClassSlots, + plainDateArg0: PlainDateArg, + plainDateArg1: PlainDateArg, + options?: LargestUnitOptions, + ): Duration { + return createDuration( + createDurationSlots( + native.dateUntil( + toPlainDateSlots(plainDateArg0), + toPlainDateSlots(plainDateArg1), + refineCalendarDiffOptions(options), + ) + ) + ) + }, + dateFromFields( + { id, native }: CalendarClassSlots, + fields: DateBagStrict, + options?: OverflowOptions, + ): PlainDate { + return createPlainDate( + refinePlainDateBag(native, fields, options, getRequiredDateFields(id)), + ) + }, + yearMonthFromFields( + { id, native }: CalendarClassSlots, + fields: YearMonthBagStrict, + options?: OverflowOptions, + ): PlainYearMonth { + return createPlainYearMonth( + refinePlainYearMonthBag(native, fields, options, getRequiredYearMonthFields(id)), + ) + }, + monthDayFromFields( + { id, native }: CalendarClassSlots, + fields: MonthDayBagStrict, + options?: OverflowOptions, + ): PlainMonthDay { + return createPlainMonthDay( + refinePlainMonthDayBag(native, false, fields, options, getRequiredMonthDayFields(id)), + ) + }, + fields({ native }: CalendarClassSlots, fieldNames: Iterable): Iterable { + /* + Bespoke logic for converting Iterable to string[], while doing some validation + */ + const allowed = new Set(dateFieldNamesAlpha) + const fieldNamesArray: string[] = [] + + for (const fieldName of fieldNames) { + requireString(fieldName) + + if (!allowed.has(fieldName)) { + throw new RangeError(errorMessages.forbiddenField(fieldName)) + } + + allowed.delete(fieldName) // prevents duplicates! can this be done somewhere else? + fieldNamesArray.push(fieldName) + } + + return native.fields(fieldNamesArray) + }, + mergeFields( + { native }: CalendarClassSlots, + fields0: Record, + fields1: Record + ): Record { + return native.mergeFields( + excludeUndefinedProps(requireNonNullish(fields0)), + excludeUndefinedProps(requireNonNullish(fields1)), + ) + }, +} + +export const [Calendar] = createSlotClass( + 'Calendar', + (id: string): CalendarClassSlots => { + id = normalizeCalendarId(requireString(id)) + const calendarNative = createNativeStandardOps(id) + return { + branding: 'Calendar', + id, + native: calendarNative, + } + }, + { + id(slots: CalendarClassSlots) { + return slots.id + } + }, + calendarMethods, + { + from(arg: CalendarArg): CalendarProtocol { + const calendarSlot = refineCalendarSlot(arg) // either string or CalendarProtocol + return typeof calendarSlot === 'string' + ? new Calendar(calendarSlot) + : calendarSlot + } + } +) + +// CalendarProtocol +// ------------------------------------------------------------------------------------------------- + +/* +TODO: eventually use temporal-spec +We need to fill-out ambient declarations on classes like PlainDate to they match for PlainDateLike, etc +*/ +export interface CalendarProtocol { + id: string + year(dateArg: PlainYearMonth | PlainDateArg): number + month(dateArg: PlainYearMonth | PlainDateArg): number + monthCode(dateArg: PlainYearMonth | PlainMonthDay | PlainDateArg): string + day(dateArg: PlainMonthDay | PlainDateArg): number + era(dateArg: PlainYearMonth | PlainDateArg): string | undefined + eraYear(dateArg: PlainYearMonth | PlainDateArg): number | undefined + dayOfWeek(dateArg: PlainDateArg): number + dayOfYear(dateArg: PlainDateArg): number + weekOfYear(dateArg: PlainDateArg): number + yearOfWeek(dateArg: PlainDateArg): number + daysInWeek(dateArg: PlainDateArg): number + daysInMonth(dateArg: PlainYearMonth | PlainDateArg): number + daysInYear(dateArg: PlainYearMonth | PlainDateArg): number + monthsInYear(dateArg: PlainYearMonth | PlainDateArg): number + inLeapYear(dateArg: PlainYearMonth | PlainDateArg): boolean + dateFromFields(fields: DateBagStrict, options?: OverflowOptions): PlainDate + yearMonthFromFields(fields: YearMonthBagStrict, options?: OverflowOptions): PlainYearMonth + monthDayFromFields(fields: MonthDayBagStrict, options?: OverflowOptions): PlainMonthDay + dateAdd(dateArg: PlainDateArg, duration: DurationArg, options?: OverflowOptions): PlainDate + dateUntil(dateArg0: PlainDateArg, dateArg1: PlainDateArg, options?: LargestUnitOptions): Duration + fields(fieldNames: Iterable): Iterable + mergeFields(fields0: Record, fields1: Record): Record + toString?(): string + toJSON?(): string +} + +export const checkCalendarProtocol = createProtocolChecker( + Object.keys(calendarMethods).slice(4), // remove toString/toJSON/era/eraYear +) diff --git a/packages/temporal-polyfill/src/classApi/calendarAdapter.ts b/packages/temporal-polyfill/src/classApi/calendarAdapter.ts new file mode 100644 index 00000000..7e7cff54 --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/calendarAdapter.ts @@ -0,0 +1,205 @@ +import { DiffOptions, OverflowOptions } from '../internal/optionsRefine' +import { DateBag, DateBagStrict, MonthDayBag, MonthDayBagStrict, YearMonthBag, YearMonthBagStrict } from '../internal/calendarFields' +import { requireObjectlike, requirePositiveInteger } from '../internal/cast' +import { DurationFields } from '../internal/durationFields' +import { IsoDateFields } from '../internal/calendarIsoFields' +import { Unit, unitNamesAsc } from '../internal/units' +import { Callable, bindArgs } from '../internal/utils' +import { createDuration, getDurationSlots } from './duration' +import { createPlainDate, getPlainDateSlots } from './plainDate' +import { getPlainMonthDaySlots } from './plainMonthDay' +import { getPlainYearMonthSlots } from './plainYearMonth' +import { CalendarSlot } from './slotsForClasses' +import { DurationSlots, PlainDateSlots, PlainMonthDaySlots, PlainYearMonthSlots, createDurationSlots, createPlainDateSlots } from '../internal/slots' +import { CalendarProtocol } from './calendar' + +// Compound Adapter Functions +// ------------------------------------------------------------------------------------------------- + +function fieldsAdapter( + calendarProtocol: CalendarProtocol, + fieldsMethod: CalendarProtocol['fields'], + fieldNames: Iterable, +): string[] { + return [...fieldsMethod.call(calendarProtocol, fieldNames)] +} + +function mergeFieldsAdapter( + calendarProtocol: CalendarProtocol, + mergeFields: CalendarProtocol['mergeFields'], + fields: any, + additionalFields: any, +) { + return requireObjectlike( + mergeFields.call( + calendarProtocol, + Object.assign(Object.create(null), fields), + Object.assign(Object.create(null), additionalFields), + ), + ) +} + +function dateFromFieldsAdapter( + calendarProtocol: CalendarProtocol, + dateFromFields: CalendarProtocol['dateFromFields'], + fields: DateBag, + options?: OverflowOptions, +): PlainDateSlots { + return getPlainDateSlots( + dateFromFields.call( + calendarProtocol, + Object.assign(Object.create(null), fields) as DateBagStrict, + options, + ) + ) +} + +function yearMonthFromFieldsAdapter( + calendarProtocol: CalendarProtocol, + yearMonthFromFields: CalendarProtocol['yearMonthFromFields'], + fields: YearMonthBag, + options?: OverflowOptions, +): PlainYearMonthSlots { + return getPlainYearMonthSlots( + yearMonthFromFields.call( + calendarProtocol, + Object.assign(Object.create(null), fields) as YearMonthBagStrict, + options, + ) + ) +} + +function monthDayFromFieldsAdapter( + calendarProtocol: CalendarProtocol, + monthDayFromFields: CalendarProtocol['monthDayFromFields'], + fields: MonthDayBag, + options?: OverflowOptions, +): PlainMonthDaySlots { + return getPlainMonthDaySlots( + monthDayFromFields.call( + calendarProtocol, + Object.assign(Object.create(null), fields) as MonthDayBagStrict, + options, + ) + ) +} + +function dateAddAdapter( + calendarProtocol: CalendarProtocol, + dateAdd: CalendarProtocol['dateAdd'], + isoFields: IsoDateFields, + durationFields: DurationFields, + options?: OverflowOptions, +): PlainDateSlots { + return getPlainDateSlots( + dateAdd.call( + calendarProtocol, + createPlainDate( + createPlainDateSlots( + isoFields, + calendarProtocol, + ), + ), + createDuration( + createDurationSlots(durationFields), + ), + options, + ) + ) +} + +function dateUntilAdapter( + calendarProtocol: CalendarProtocol, + dateUntil: CalendarProtocol['dateUntil'], + isoFields0: IsoDateFields, + isoFields1: IsoDateFields, + largestUnit: Unit, + origOptions?: DiffOptions +): DurationSlots { + return getDurationSlots( + dateUntil.call( + calendarProtocol, + createPlainDate( + createPlainDateSlots( + isoFields0, + calendarProtocol, + ), + ), + createPlainDate( + createPlainDateSlots( + isoFields1, + calendarProtocol, + ), + ), + Object.assign( + Object.create(null), + origOptions, + { largestUnit: unitNamesAsc[largestUnit] }, + ) + ), + ) +} + +function dayAdapter( + calendarProtocol: CalendarProtocol, + dayMethod: CalendarProtocol['day'], + isoFields: IsoDateFields, +): number { + return requirePositiveInteger( + dayMethod.call( + calendarProtocol, + createPlainDate( + createPlainDateSlots( + isoFields, + calendarProtocol, + ) + ) + ) + ) +} + +// Compound Adapter Sets +// ------------------------------------------------------------------------------------------------- + +const refineAdapters = { fields: fieldsAdapter } +export const dateRefineAdapters = { dateFromFields: dateFromFieldsAdapter, ...refineAdapters } +export const yearMonthRefineAdapters = { yearMonthFromFields: yearMonthFromFieldsAdapter, ...refineAdapters } +export const monthDayRefineAdapters = { monthDayFromFields: monthDayFromFieldsAdapter, ...refineAdapters } + +const modAdapters = { mergeFields: mergeFieldsAdapter } +export const dateModAdapters = { ...dateRefineAdapters, ...modAdapters } +export const yearMonthModAdapters = { ...yearMonthRefineAdapters, ...modAdapters } +export const monthDayModAdapters = { ...monthDayRefineAdapters, ...modAdapters } + +export const moveAdapters = { dateAdd: dateAddAdapter } +export const diffAdapters = { ...moveAdapters, dateUntil: dateUntilAdapter } +export const yearMonthMoveAdapters = { ...moveAdapters, day: dayAdapter } +export const yearMonthDiffAdapters = { ...diffAdapters, day: dayAdapter } + +// Compound Adapter Instantiation +// ------------------------------------------------------------------------------------------------- + +export type AdapterCompoundOps = { + [K in keyof KV]: + KV[K] extends (c: CalendarProtocol, m: Callable, ...args: infer Args) => infer Return + ? (...args: Args) => Return + : never +} + +export function createAdapterCompoundOps( + calendarProtocol: CalendarProtocol, + adapterFuncs: KV, +): AdapterCompoundOps { + const keys = Object.keys(adapterFuncs).sort() + const boundFuncs = {} as any + + for (const key of keys) { + boundFuncs[key] = bindArgs( + (adapterFuncs as any)[key], + calendarProtocol, + (calendarProtocol as any)[key], + ) + } + + return boundFuncs +} diff --git a/packages/temporal-polyfill/src/classApi/calendarOpsQuery.ts b/packages/temporal-polyfill/src/classApi/calendarOpsQuery.ts new file mode 100644 index 00000000..24096783 --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/calendarOpsQuery.ts @@ -0,0 +1,34 @@ +import { createNativeStandardOps } from '../internal/calendarNativeQuery' +import { AdapterCompoundOps, createAdapterCompoundOps, dateModAdapters, dateRefineAdapters, diffAdapters, monthDayModAdapters, monthDayRefineAdapters, moveAdapters, yearMonthDiffAdapters, yearMonthModAdapters, yearMonthMoveAdapters, yearMonthRefineAdapters } from './calendarAdapter' +import { CalendarSlot } from './slotsForClasses' + +// Refine +export const createYearMonthRefineOps = createCompoundOpsCreator(yearMonthRefineAdapters) +export const createDateRefineOps = createCompoundOpsCreator(dateRefineAdapters) +export const createMonthDayRefineOps = createCompoundOpsCreator(monthDayRefineAdapters) + +// Mod +export const createYearMonthModOps = createCompoundOpsCreator(yearMonthModAdapters) +export const createDateModOps = createCompoundOpsCreator(dateModAdapters) +export const createMonthDayModOps = createCompoundOpsCreator(monthDayModAdapters) + +// Math +export const createMoveOps = createCompoundOpsCreator(moveAdapters) +export const createDiffOps = createCompoundOpsCreator(diffAdapters) +export const createYearMonthMoveOps = createCompoundOpsCreator(yearMonthMoveAdapters) +export const createYearMonthDiffOps = createCompoundOpsCreator(yearMonthDiffAdapters) + +// ------------------------------------------------------------------------------------------------- + +function createCompoundOpsCreator( + adapterFuncs: KV, +): ( + (calendarSlot: CalendarSlot) => AdapterCompoundOps +) { + return (calendarSlot) => { + if (typeof calendarSlot === 'string') { + return createNativeStandardOps(calendarSlot) as any // has everything + } + return createAdapterCompoundOps(calendarSlot, adapterFuncs) + } +} diff --git a/packages/temporal-polyfill/src/classApi/calendarRefiners.ts b/packages/temporal-polyfill/src/classApi/calendarRefiners.ts new file mode 100644 index 00000000..99fa7a7e --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/calendarRefiners.ts @@ -0,0 +1,36 @@ +import { requireBoolean, requireInteger, requireIntegerOrUndefined, requirePositiveInteger, requireString, requireStringOrUndefined } from '../internal/cast' + +export const yearMonthOnlyRefiners = { + era: requireStringOrUndefined, + eraYear: requireIntegerOrUndefined, + year: requireInteger, + month: requirePositiveInteger, + + daysInMonth: requirePositiveInteger, + daysInYear: requirePositiveInteger, + inLeapYear: requireBoolean, + monthsInYear: requirePositiveInteger, +} + +export const monthOnlyRefiners = { + monthCode: requireString, +} + +export const dayOnlyRefiners = { + day: requirePositiveInteger, +} + +export const dateOnlyRefiners = { + dayOfWeek: requirePositiveInteger, + dayOfYear: requirePositiveInteger, + weekOfYear: requirePositiveInteger, + yearOfWeek: requireInteger, + daysInWeek: requirePositiveInteger, +} + +export const dateRefiners = { + ...yearMonthOnlyRefiners, + ...monthOnlyRefiners, + ...dayOnlyRefiners, + ...dateOnlyRefiners, +} diff --git a/packages/temporal-polyfill/src/classApi/dateTimeFormat.ts b/packages/temporal-polyfill/src/classApi/dateTimeFormat.ts new file mode 100644 index 00000000..69a8ba16 --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/dateTimeFormat.ts @@ -0,0 +1,164 @@ +import { Classlike, createLazyGenerator, createPropDescriptors, pluckProps } from '../internal/utils' +import { OrigDateTimeFormat, LocalesArg, OptionNames, ClassFormatConfig, plainYearMonthConfig, plainMonthDayConfig, plainDateConfig, plainDateTimeConfig, plainTimeConfig, instantConfig, createFormatPrepper, zonedDateTimeConfig, toEpochMillis } from '../internal/formatIntl' +import { ZonedDateTime } from './zonedDateTime' +import { PlainDate } from './plainDate' +import { PlainTime } from './plainTime' +import { PlainDateTime } from './plainDateTime' +import { PlainMonthDay } from './plainMonthDay' +import { PlainYearMonth } from './plainYearMonth' +import { Instant } from './instant' +import { getSlots } from './slotsForClasses' +import { BrandingSlots } from '../internal/slots' +import * as errorMessages from '../internal/errorMessages' + +type OrigFormattable = number | Date +type TemporalFormattable = Instant | + PlainDate | + PlainDateTime | + ZonedDateTime | + PlainYearMonth | + PlainMonthDay | + PlainTime + +export type Formattable = TemporalFormattable | OrigFormattable + +const prepSubformatMap = new WeakMap() + +// Intl.DateTimeFormat +// ------------------------------------------------------------------------------------------------- + +export class DateTimeFormat extends OrigDateTimeFormat { + constructor(locales: LocalesArg, options: Intl.DateTimeFormatOptions = {}) { + super(locales, options) + + // Copy options so accessing doesn't cause side-effects + // Must store recursively flattened options because given `options` could mutate in future + // Algorithm: whitelist against resolved options + const resolvedOptions = this.resolvedOptions() + const origOptions = pluckProps( + Object.keys(options) as OptionNames, + resolvedOptions as Intl.DateTimeFormatOptions + ) + + prepSubformatMap.set(this, createBoundFormatPrepFunc(origOptions, resolvedOptions)) + } + + format(arg?: Formattable): string { + const prepSubformat = prepSubformatMap.get(this)! + const [format, epochMilli] = prepSubformat(arg) + + // can't use the origMethod.call() trick because .format() is always bound + // https://tc39.es/ecma402/#sec-intl.datetimeformat.prototype.format + return format + ? format.format(epochMilli) + : super.format(arg as OrigFormattable) + } + + formatToParts(arg?: Formattable): Intl.DateTimeFormatPart[] { + const prepSubformat = prepSubformatMap.get(this)! + const [format, epochMilli] = prepSubformat(arg) + + return format + ? format.formatToParts(epochMilli) + : super.formatToParts(arg as OrigFormattable) + } +} + +export interface DateTimeFormat { + formatRange(arg0: Formattable, arg1: Formattable): string + formatRangeToParts(arg0: Formattable, arg1: Formattable): Intl.DateTimeFormatPart[] +} + +;['formatRange', 'formatRangeToParts'].forEach((methodName) => { + const origMethod = (OrigDateTimeFormat as Classlike).prototype[methodName] + + if (origMethod) { + Object.defineProperties(DateTimeFormat.prototype, createPropDescriptors({ + [methodName]: function (this: DateTimeFormat, arg0: Formattable, arg1: Formattable) { + const prepSubformat = prepSubformatMap.get(this)! + const [format, epochMilli0, epochMilli1] = prepSubformat(arg0, arg1) + + return format + ? origMethod.call(format, epochMilli0, epochMilli1) + : origMethod.call(this, arg0, arg1) + } + })) + } +}) + +// Format Prepping for Intl.DateTimeFormat ("Bound") +// ------------------------------------------------------------------------------------------------- + +type BoundFormatPrepFunc = ( // already bound to locale/options + arg0?: Formattable, + arg1?: Formattable +) => BoundFormatPrepFuncRes + +type BoundFormatPrepFuncRes = [ + Intl.DateTimeFormat | undefined, + number | undefined, + number | undefined, +] + +const classFormatConfigs: Record> = { + PlainYearMonth: plainYearMonthConfig, + PlainMonthDay: plainMonthDayConfig, + PlainDate: plainDateConfig, + PlainDateTime: plainDateTimeConfig, + PlainTime: plainTimeConfig, + Instant: instantConfig, + // ZonedDateTime not allowed to be formatted by Intl.DateTimeFormat +} + +function createBoundFormatPrepFunc( + origOptions: Intl.DateTimeFormatOptions, + resolvedOptions: Intl.ResolvedDateTimeFormatOptions, +): BoundFormatPrepFunc { + const resolvedLocale = resolvedOptions.locale + + const queryFormat = createLazyGenerator((branding: string) => { + const [transformOptions] = classFormatConfigs[branding] + const transformedOptions = transformOptions(origOptions) + return new OrigDateTimeFormat(resolvedLocale, transformedOptions) + }) + + return (arg0, arg1) => { + const slots0 = getSlots(arg0) + const { branding } = slots0 || {} + let slots1: BrandingSlots | undefined + + if (arg1 !== undefined) { + slots1 = getSlots(arg1) + + if (branding !== (slots1 || {}).branding) { + throw new TypeError(errorMessages.mismatchingFormatTypes) + } + } + + if (branding) { + const config = classFormatConfigs[branding] + if (!config) { + throw new TypeError(errorMessages.invalidFormatType(branding)) + } + + return [ + queryFormat(branding)!, + ...toEpochMillis(config, resolvedOptions, slots0, slots1), + ] as BoundFormatPrepFuncRes + } + + return [] as unknown as BoundFormatPrepFuncRes + } +} + +// Format Prepping for each class' toLocaleString +// (best place for this?) +// ------------------------------------------------------------------------------------------------- + +export const prepPlainYearMonthFormat = createFormatPrepper(plainYearMonthConfig) +export const prepPlainMonthDayFormat = createFormatPrepper(plainMonthDayConfig) +export const prepPlainDateFormat = createFormatPrepper(plainDateConfig) +export const prepPlainDateTimeFormat = createFormatPrepper(plainDateTimeConfig) +export const prepPlainTimeFormat = createFormatPrepper(plainTimeConfig) +export const prepInstantFormat = createFormatPrepper(instantConfig) +export const prepZonedDateTimeFormat = createFormatPrepper(zonedDateTimeConfig) diff --git a/packages/temporal-polyfill/src/classApi/duration.ts b/packages/temporal-polyfill/src/classApi/duration.ts new file mode 100644 index 00000000..a60b55fe --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/duration.ts @@ -0,0 +1,150 @@ +import { + DurationRoundOptions, + RelativeToOptions, + TimeDisplayOptions, + TotalUnitOptionsWithRel, +} from '../internal/optionsRefine' +import { NumSign, isObjectLike } from '../internal/utils' +import { UnitName } from '../internal/units' +import { DurationBag } from '../internal/calendarFields' +import { BrandingSlots, DurationBranding, DurationSlots, PlainDateBranding, PlainDateSlots, PlainDateTimeBranding, PlainDateTimeSlots, ZonedDateTimeBranding, ZonedDateTimeSlots, createPlainDateSlots } from '../internal/slots' +import { createSlotClass, getSlots } from './slotsForClasses' +import { durationGetters, neverValueOf } from './mixins' +import { PlainDateArg } from './plainDate' +import { ZonedDateTimeArg } from './zonedDateTime' +import { createDateRefineOps, createDiffOps } from './calendarOpsQuery' +import { createTimeZoneOps } from './timeZoneOpsQuery' +import { LocalesArg } from '../internal/formatIntl' +import { TimeZoneSlot, refineTimeZoneSlot } from './slotsForClasses' +import { CalendarSlot, getCalendarSlotFromBag } from './slotsForClasses' +import { MarkerSlots, absDuration, addDurations, negateDuration, queryDurationBlank, queryDurationSign, roundDuration } from '../internal/durationMath' +import { CalendarArg } from './calendar' +import { TimeZoneArg } from './timeZone' +import { parseDuration, parseZonedOrPlainDateTime } from '../internal/parseIso' +import { ZonedDateTimeBag, durationWithFields, refineDurationBag, refineMaybeZonedDateTimeBag } from '../internal/bag' +import { constructDurationSlots } from '../internal/construct' +import { totalDuration } from '../internal/total' +import { formatDurationIso } from '../internal/formatIso' +import { compareDurations } from '../internal/compare' +import { DurationFields } from '../internal/durationFields' + +export type Duration = any & DurationFields +export type DurationArg = Duration | DurationBag | string + +export const [Duration, createDuration, getDurationSlots] = createSlotClass( + DurationBranding, + constructDurationSlots, + { + ...durationGetters, + blank(slots: DurationSlots): boolean { + return queryDurationBlank(slots) + }, + sign(slots: DurationSlots): NumSign { + return queryDurationSign(slots) + }, + }, + { + with(slots: DurationSlots, mod: DurationBag): Duration { + return createDuration(durationWithFields(slots, mod)) + }, + add(slots: DurationSlots, otherArg: DurationArg, options?: RelativeToOptions) { + return createDuration( + addDurations(refinePublicRelativeTo, createDiffOps, createTimeZoneOps, slots, toDurationSlots(otherArg), options) + ) + }, + subtract(slots: DurationSlots, otherArg: DurationArg, options?: RelativeToOptions) { + return createDuration( + addDurations(refinePublicRelativeTo, createDiffOps, createTimeZoneOps, slots, toDurationSlots(otherArg), options, true) + ) + }, + negated(slots: DurationSlots): Duration { + return createDuration(negateDuration(slots)) + }, + abs(slots: DurationSlots): Duration { + return createDuration(absDuration(slots)) + }, + round(slots: DurationSlots, options: DurationRoundOptions): Duration { + return createDuration( + roundDuration(refinePublicRelativeTo, createDiffOps, createTimeZoneOps, slots, options) + ) + }, + total(slots: DurationSlots, options: TotalUnitOptionsWithRel | UnitName): number { + return totalDuration(refinePublicRelativeTo, createDiffOps, createTimeZoneOps, slots, options) + }, + toString: formatDurationIso, + toLocaleString(slots: DurationSlots, locales?: LocalesArg, options?: any): string { + return new (Intl as any).DurationFormat(locales, options).format(this) + }, + toJSON(slots: DurationSlots): string { + return formatDurationIso(slots) + }, + valueOf: neverValueOf, + }, + { + from(arg: DurationArg): Duration { + return createDuration(toDurationSlots(arg)) + }, + compare( + durationArg0: DurationArg, + durationArg1: DurationArg, + options?: RelativeToOptions, + ): NumSign { + return compareDurations( + refinePublicRelativeTo, + createDiffOps, + createTimeZoneOps, + toDurationSlots(durationArg0), + toDurationSlots(durationArg1), + options, + ) + } + }, +) + +// Utils +// ------------------------------------------------------------------------------------------------- + +export function toDurationSlots(arg: DurationArg): DurationSlots { + if (isObjectLike(arg)) { + const slots = getSlots(arg) + + if (slots && slots.branding === DurationBranding) { + return slots as DurationSlots + } + + return refineDurationBag(arg as DurationBag) + } + + return parseDuration(arg) +} + +function refinePublicRelativeTo( + relativeTo: ZonedDateTimeArg | PlainDateArg | undefined, +): MarkerSlots | undefined { + if (relativeTo !== undefined) { + if (isObjectLike(relativeTo)) { + const slots = (getSlots(relativeTo) || {}) as Partial + + switch (slots.branding) { + case ZonedDateTimeBranding: + case PlainDateBranding: + return slots as (ZonedDateTimeSlots | PlainDateSlots) + + case PlainDateTimeBranding: + return createPlainDateSlots(slots as PlainDateTimeSlots) + } + + const calendar = getCalendarSlotFromBag(relativeTo as any) // !!! + const res = refineMaybeZonedDateTimeBag( + refineTimeZoneSlot, + createTimeZoneOps, + createDateRefineOps(calendar), + relativeTo as unknown as ZonedDateTimeBag, // !!! + ) + + return { ...res, calendar } + } + + return parseZonedOrPlainDateTime(relativeTo) + } +} diff --git a/packages/temporal-polyfill/src/classApi/instant.ts b/packages/temporal-polyfill/src/classApi/instant.ts new file mode 100644 index 00000000..7019b4d0 --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/instant.ts @@ -0,0 +1,145 @@ +import { LocalesArg } from '../internal/formatIntl' +import { DiffOptions, InstantDisplayOptions, RoundingOptions } from '../internal/optionsRefine' +import { requireObjectlike } from '../internal/cast' +import { NumSign, isObjectLike } from '../internal/utils' +import { UnitName, nanoInMilli } from '../internal/units' +import { numberToDayTimeNano } from '../internal/dayTimeNano' +import { InstantBranding, InstantSlots, ZonedDateTimeBranding, ZonedDateTimeSlots, createInstantSlots } from '../internal/slots' +import { createSlotClass, getSlots } from './slotsForClasses' +import { CalendarSlot, refineCalendarSlot } from './slotsForClasses' +import { TimeZoneSlot, refineTimeZoneSlot } from './slotsForClasses' +import { Duration, DurationArg, createDuration, toDurationSlots } from './duration' +import { TimeZoneArg } from './timeZone' +import { CalendarArg } from './calendar' +import { ZonedDateTime, createZonedDateTime } from './zonedDateTime' +import { epochGetters, neverValueOf } from './mixins' +import { createSimpleTimeZoneOps } from './timeZoneOpsQuery' +import { constructInstantSlots } from '../internal/construct' +import { moveInstant } from '../internal/move' +import { diffInstants } from '../internal/diff' +import { roundInstant } from '../internal/round' +import { compareInstants, instantsEqual } from '../internal/compare' +import { formatInstantIso } from '../internal/formatIso' +import { epochMicroToInstant, epochMilliToInstant, epochNanoToInstant, epochSecToInstant, instantToZonedDateTime } from '../internal/convert' +import { parseInstant } from '../internal/parseIso' +import { prepInstantFormat } from './dateTimeFormat' + +export type Instant = any +export type InstantArg = Instant | string + +export const [Instant, createInstant, getInstantSlots] = createSlotClass( + InstantBranding, + constructInstantSlots, + epochGetters, + { + add(slots: InstantSlots, durationArg: DurationArg): Instant { + return createInstant( + moveInstant(slots, toDurationSlots(durationArg)), + ) + }, + subtract(slots: InstantSlots, durationArg: DurationArg): Instant { + return createInstant( + moveInstant(slots, toDurationSlots(durationArg), true), + ) + }, + until(slots: InstantSlots, otherArg: InstantArg, options?: DiffOptions): Duration { + return createDuration( + diffInstants( + slots, + toInstantSlots(otherArg), + options, + ), + ) + }, + since(slots: InstantSlots, otherArg: InstantArg, options?: DiffOptions): Duration { + return createDuration( + diffInstants(slots, toInstantSlots(otherArg), options, true), + ) + }, + round(slots: InstantSlots, options: RoundingOptions | UnitName): Instant { + return createInstant( + roundInstant(slots, options), + ) + }, + equals(slots: InstantSlots, otherArg: InstantArg): boolean { + return instantsEqual(slots, toInstantSlots(otherArg)) + }, + toString(slots: InstantSlots, options?: InstantDisplayOptions): string { + return formatInstantIso(refineTimeZoneSlot, createSimpleTimeZoneOps, slots, options) + }, + toJSON(slots: InstantSlots): string { + return formatInstantIso(refineTimeZoneSlot, createSimpleTimeZoneOps, slots) + }, + toLocaleString(slots: InstantSlots, locales?: LocalesArg, options?: Intl.DateTimeFormatOptions): string { + const [format, epochMilli] = prepInstantFormat(locales, options, slots) + return format.format(epochMilli) + }, + toZonedDateTimeISO(slots: InstantSlots, timeZoneArg: TimeZoneArg): ZonedDateTime { + return createZonedDateTime( + instantToZonedDateTime(slots, refineTimeZoneSlot(timeZoneArg)), + ) + }, + toZonedDateTime(slots: InstantSlots, options: { timeZone: TimeZoneArg, calendar: CalendarArg }): ZonedDateTime { + const refinedObj = requireObjectlike(options) + + return createZonedDateTime( + instantToZonedDateTime( + slots, + refineTimeZoneSlot(refinedObj.timeZone), + refineCalendarSlot(refinedObj.calendar), + ) + ) + }, + valueOf: neverValueOf, + }, + { + from(arg: InstantArg) { + return createInstant(toInstantSlots(arg)) + }, + fromEpochSeconds(epochSec: number): Instant { + return createInstant(epochSecToInstant(epochSec)) + }, + fromEpochMilliseconds(epochMilli: number): Instant { + return createInstant(epochMilliToInstant(epochMilli)) + }, + fromEpochMicroseconds(epochMicro: bigint): Instant { + return createInstant(epochMicroToInstant(epochMicro)) + }, + fromEpochNanoseconds(epochNano: bigint): Instant { + return createInstant(epochNanoToInstant(epochNano)) + }, + compare(a: InstantArg, b: InstantArg): NumSign { + return compareInstants(toInstantSlots(a), toInstantSlots(b)) + } + } +) + +// Utils +// ------------------------------------------------------------------------------------------------- + +export function toInstantSlots(arg: InstantArg): InstantSlots { + if (isObjectLike(arg)) { + const slots = getSlots(arg) + if (slots) { + switch (slots.branding) { + case InstantBranding: + return slots as InstantSlots + + case ZonedDateTimeBranding: + return createInstantSlots((slots as ZonedDateTimeSlots).epochNanoseconds) + } + } + } + return parseInstant(arg as any) +} + +// Legacy Date +// ------------------------------------------------------------------------------------------------- + +export function toTemporalInstant(this: Date): Instant { + return createInstant( + createInstantSlots( + numberToDayTimeNano(this.valueOf(), nanoInMilli), + ) + ) +} diff --git a/packages/temporal-polyfill/src/classApi/mixins.ts b/packages/temporal-polyfill/src/classApi/mixins.ts new file mode 100644 index 00000000..508fc50d --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/mixins.ts @@ -0,0 +1,173 @@ +import { EpochSlots, PlainDateBranding, PlainMonthDayBranding, PlainYearMonthBranding, createPlainDateSlots, getId } from '../internal/slots' +import { dayTimeNanoToBigInt } from '../internal/dayTimeNano' +import { DurationFields, durationFieldNamesAsc } from '../internal/durationFields' +import { IsoDateFields, isoTimeFieldNamesAsc } from '../internal/calendarIsoFields' +import { epochNanoToMicro, epochNanoToMilli, epochNanoToSec } from '../internal/epochAndTime' +import { Callable, mapPropNames, mapProps } from '../internal/utils' +import { createPlainDate, toPlainDateSlots } from './plainDate' +import { getSlots } from './slotsForClasses' +import { timeFieldNamesAsc } from '../internal/calendarFields' +import { yearMonthOnlyRefiners, dateOnlyRefiners, monthOnlyRefiners, dayOnlyRefiners, dateRefiners } from './calendarRefiners' +import { CalendarSlot } from './slotsForClasses' +import { createNativeStandardOps } from '../internal/calendarNativeQuery' +import * as errorMessages from '../internal/errorMessages' +import { Calendar, CalendarProtocol } from './calendar' + +// For Calendar +// ------------------------------------------------------------------------------------------------- +// Always assumes underlying Native calendar `ops` + +function createCalendarFieldMethods(methodNameMap: M, alsoAccept: string[]): { + [K in keyof M]: (dateArg: any) => any +} { + const methods = {} as any + + for (const methodName in methodNameMap) { + methods[methodName] = function(this: any, { native }: any, dateArg: any) { + const argSlots = (getSlots(dateArg) || {}) as any + const { branding } = argSlots + const refinedSlots = branding === PlainDateBranding || alsoAccept.includes(branding) + ? argSlots + : toPlainDateSlots(dateArg) + + return (native as any)[methodName](refinedSlots) + } + } + + return methods +} + +export const calendarFieldMethods = { + ...createCalendarFieldMethods(yearMonthOnlyRefiners, [PlainYearMonthBranding]), + ...createCalendarFieldMethods(dateOnlyRefiners, []), + ...createCalendarFieldMethods(monthOnlyRefiners, [PlainYearMonthBranding, PlainMonthDayBranding]), + ...createCalendarFieldMethods(dayOnlyRefiners, [PlainMonthDayBranding]), +} + +// For PlainDate/etc +// ------------------------------------------------------------------------------------------------- +// Assumes general calendar (native/adapter) + +function createCalendarGetters(methodNameMap: M): { + [K in keyof M]: () => any +} { + const methods = {} as any + + for (const methodName in methodNameMap) { + methods[methodName] = function(this: any, slots: any) { + const { calendar } = slots + const simpleOps = createSimpleOps(calendar) as any + return simpleOps[methodName](slots) + } + } + + return methods +} + +export const dateGetters = createCalendarGetters(dateRefiners) +export const yearMonthGetters = createCalendarGetters({ + ...yearMonthOnlyRefiners, + ...monthOnlyRefiners, +}) +export const monthDayGetters = createCalendarGetters({ + ...monthOnlyRefiners, + ...dayOnlyRefiners, +}) +export const calendarIdGetters = { + calendarId(slots: any): string { + return getId(slots.calendar) + }, +} + +// Calendar "Simple" Ops +// ------------------------------------------------------------------------------------------------- + +interface AdapterSimpleState { + calendarProtocol: CalendarProtocol +} + +type AdapterSimpleOps = { + [K in keyof typeof dateRefiners]: (isoFields: IsoDateFields) => any +} + +const adapterSimpleOps = mapProps( + (refiner, methodName) => { + return function (this: AdapterSimpleState, isoFields: IsoDateFields) { + const { calendarProtocol } = this + return refiner( + (calendarProtocol as any)[methodName]( + createPlainDate( + createPlainDateSlots(isoFields, calendarProtocol), + ) + ) + ) + } + }, + dateRefiners as Record +) as AdapterSimpleOps + +function createAdapterSimpleOps( + calendarProtocol: CalendarProtocol +): AdapterSimpleOps { + return Object.assign( + Object.create(adapterSimpleOps), + { calendarProtocol } as AdapterSimpleState + ) +} + +function createSimpleOps(calendarSlot: CalendarSlot): AdapterSimpleOps { + if (typeof calendarSlot === 'string') { + return createNativeStandardOps(calendarSlot) // has everything + } + return createAdapterSimpleOps(calendarSlot) +} + + +// Duration +// ------------------------------------------------------------------------------------------------- + +export const durationGetters = mapPropNames((propName: keyof DurationFields) => { + return function (this: any, slots: any) { + return slots[propName] + } +}, durationFieldNamesAsc) + +// Time +// ------------------------------------------------------------------------------------------------- + +export const timeGetters = mapPropNames((name, i) => { + return function (this: any, slots: any) { + return (slots)[isoTimeFieldNamesAsc[i]] + } +}, timeFieldNamesAsc) + +// Epoch +// ------------------------------------------------------------------------------------------------- + +export const epochGetters = { + epochSeconds(slots: EpochSlots) { + return epochNanoToSec(slots.epochNanoseconds) + }, + epochMilliseconds(slots: EpochSlots) { + return epochNanoToMilli(slots.epochNanoseconds) + }, + epochMicroseconds(slots: EpochSlots) { + return epochNanoToMicro(slots.epochNanoseconds) + }, + epochNanoseconds(slots: EpochSlots) { + return dayTimeNanoToBigInt(slots.epochNanoseconds) + }, +} + +// Misc +// ------------------------------------------------------------------------------------------------- + +export function neverValueOf() { + throw new TypeError(errorMessages.forbiddenValueOf) +} + +export function getCalendarFromSlots({ calendar }: { calendar: CalendarSlot }): Calendar { + return typeof calendar === 'string' + ? new Calendar(calendar) + : calendar +} diff --git a/packages/temporal-polyfill/src/classApi/now.ts b/packages/temporal-polyfill/src/classApi/now.ts new file mode 100644 index 00000000..f5a6ec0d --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/now.ts @@ -0,0 +1,111 @@ +import { isoCalendarId } from '../internal/calendarConfig' +import { createPropDescriptors, createStringTagDescriptors } from '../internal/utils' +import { CalendarSlot, refineCalendarSlot } from './slotsForClasses' +import { TimeZoneSlot, refineTimeZoneSlot } from './slotsForClasses' +import { Instant, createInstant } from './instant' +import { PlainDate, createPlainDate } from './plainDate' +import { PlainTime, createPlainTime } from './plainTime' +import { PlainDateTime, createPlainDateTime } from './plainDateTime' +import { ZonedDateTime, createZonedDateTime } from './zonedDateTime' +import { createSimpleTimeZoneOps } from './timeZoneOpsQuery' +import { getCurrentEpochNanoseconds, getCurrentIsoDateTime, getCurrentTimeZoneId } from '../internal/current' +import { createInstantSlots, createPlainDateTimeSlots, createPlainDateSlots, createPlainTimeSlots, createZonedDateTimeSlots } from '../internal/slots' + +export const Now = Object.defineProperties({}, { + ...createStringTagDescriptors('Temporal.Now'), + ...createPropDescriptors({ + + timeZoneId() { + return getCurrentTimeZoneId() // we call separately to return function.name + }, + + instant(): Instant { + return createInstant( + createInstantSlots( + getCurrentEpochNanoseconds(), + ), + ) + }, + + zonedDateTime( + calendar: CalendarSlot, + timeZone: TimeZoneSlot = getCurrentTimeZoneId(), + ): ZonedDateTime { + return createZonedDateTime( + createZonedDateTimeSlots( + getCurrentEpochNanoseconds(), + refineTimeZoneSlot(timeZone), + refineCalendarSlot(calendar), + ) + ) + }, + + zonedDateTimeISO( + timeZone: TimeZoneSlot = getCurrentTimeZoneId(), + ): ZonedDateTime { + return createZonedDateTime( + createZonedDateTimeSlots( + getCurrentEpochNanoseconds(), + refineTimeZoneSlot(timeZone), + isoCalendarId, + ) + ) + }, + + plainDateTime( + calendar: CalendarSlot, + timeZone: TimeZoneSlot = getCurrentTimeZoneId(), + ): PlainDateTime { + return createPlainDateTime( + createPlainDateTimeSlots( + getCurrentIsoDateTime(createSimpleTimeZoneOps(refineTimeZoneSlot(timeZone))), + refineCalendarSlot(calendar), + ) + ) + }, + + plainDateTimeISO( + timeZone: TimeZoneSlot = getCurrentTimeZoneId(), + ): PlainDateTime { + return createPlainDateTime( + createPlainDateTimeSlots( + getCurrentIsoDateTime(createSimpleTimeZoneOps(refineTimeZoneSlot(timeZone))), + isoCalendarId, + ) + ) + }, + + plainDate( + calendar: CalendarSlot, + timeZone: TimeZoneSlot = getCurrentTimeZoneId(), + ): PlainDate { + return createPlainDate( + createPlainDateSlots( + getCurrentIsoDateTime(createSimpleTimeZoneOps(refineTimeZoneSlot(timeZone))), + refineCalendarSlot(calendar), + ) + ) + }, + + plainDateISO( + timeZone: TimeZoneSlot = getCurrentTimeZoneId(), + ): PlainDate { + return createPlainDate( + createPlainDateSlots( + getCurrentIsoDateTime(createSimpleTimeZoneOps(refineTimeZoneSlot(timeZone))), + isoCalendarId, + ) + ) + }, + + plainTimeISO( + timeZone: TimeZoneSlot = getCurrentTimeZoneId(), + ): PlainTime { + return createPlainTime( + createPlainTimeSlots( + getCurrentIsoDateTime(createSimpleTimeZoneOps(refineTimeZoneSlot(timeZone))) + ) + ) + }, + }), +}) diff --git a/packages/temporal-polyfill/src/classApi/plainDate.ts b/packages/temporal-polyfill/src/classApi/plainDate.ts new file mode 100644 index 00000000..4c759c80 --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/plainDate.ts @@ -0,0 +1,169 @@ +import { DateBag, DateFields } from '../internal/calendarFields' +import { LocalesArg } from '../internal/formatIntl' +import { DateTimeDisplayOptions, DiffOptions, OverflowOptions, copyOptions, refineOverflowOptions } from '../internal/optionsRefine' +import { NumSign, bindArgs, isObjectLike } from '../internal/utils' +import { BrandingSlots, PlainDateBranding, PlainDateSlots, PlainDateTimeBranding, PlainDateTimeSlots, ZonedDateTimeBranding, ZonedDateTimeSlots, createPlainDateSlots, getId, removeBranding } from '../internal/slots' +import { CalendarSlot, createSlotClass, getCalendarSlotFromBag, refineCalendarSlot } from './slotsForClasses' +import { PlainDateTime, createPlainDateTime } from './plainDateTime' +import { PlainMonthDay, createPlainMonthDay } from './plainMonthDay' +import { PlainTimeArg, toPlainTimeSlots } from './plainTime' +import { PlainYearMonth, createPlainYearMonth } from './plainYearMonth' +import { CalendarArg } from './calendar' +import { calendarIdGetters, dateGetters, getCalendarFromSlots, neverValueOf } from './mixins' +import { optionalToPlainTimeFields } from './utils' +import { TimeZone, TimeZoneArg } from './timeZone' +import { ZonedDateTime, createZonedDateTime } from './zonedDateTime' +import { Duration, DurationArg, createDuration, toDurationSlots } from './duration' +import { TimeZoneSlot, refineTimeZoneSlot } from './slotsForClasses' +import { getSlots, rejectInvalidBag } from './slotsForClasses' +import { createDateModOps, createDateRefineOps, createDiffOps, createMonthDayRefineOps, createMoveOps, createYearMonthRefineOps } from './calendarOpsQuery' +import { createSimpleTimeZoneOps, createTimeZoneOps } from './timeZoneOpsQuery' +import { PlainDateBag, plainDateWithFields, refinePlainDateBag } from '../internal/bag' +import { constructPlainDateSlots } from '../internal/construct' +import { slotsWithCalendar } from '../internal/mod' +import { movePlainDate } from '../internal/move' +import { diffPlainDates } from '../internal/diff' +import { plainDatesEqual, compareIsoDateFields } from '../internal/compare' +import { formatPlainDateIso } from '../internal/formatIso' +import { plainDateToPlainDateTime, plainDateToPlainMonthDay, plainDateToPlainYearMonth, plainDateToZonedDateTime, zonedDateTimeToPlainDate } from '../internal/convert' +import { parsePlainDate } from '../internal/parseIso' +import { prepPlainDateFormat } from './dateTimeFormat' + +export type PlainDate = any & DateFields +export type PlainDateArg = PlainDate | PlainDateBag | string + +// TODO: give `this` a type + +export const [PlainDate, createPlainDate, getPlainDateSlots] = createSlotClass( + PlainDateBranding, + bindArgs(constructPlainDateSlots, refineCalendarSlot), + { + ...calendarIdGetters, + ...dateGetters, + }, + { + with(slots: PlainDateSlots, mod: DateBag, options?: OverflowOptions) { + return createPlainDate( + plainDateWithFields(createDateModOps, slots, this, rejectInvalidBag(mod), options) + ) + }, + withCalendar(slots: PlainDateSlots, calendarArg: CalendarArg): PlainDate { + return createPlainDate( + slotsWithCalendar(slots, refineCalendarSlot(calendarArg)) + ) + }, + add(slots: PlainDateSlots, durationArg: DurationArg, options?: OverflowOptions): PlainDate { + return createPlainDate( + movePlainDate(createMoveOps, slots, toDurationSlots(durationArg), options) + ) + }, + subtract(slots: PlainDateSlots, durationArg: DurationArg, options?: OverflowOptions): PlainDate { + return createPlainDate( + movePlainDate(createMoveOps, slots, toDurationSlots(durationArg), options, true) + ) + }, + until(slots: PlainDateSlots, otherArg: PlainDateArg, options?: DiffOptions): Duration { + return createDuration( + diffPlainDates(createDiffOps, slots, toPlainDateSlots(otherArg), options) + ) + }, + since(slots: PlainDateSlots, otherArg: PlainDateArg, options?: DiffOptions): Duration { + return createDuration( + diffPlainDates(createDiffOps, slots, toPlainDateSlots(otherArg), options, true) + ) + }, + equals(slots: PlainDateSlots, otherArg: PlainDateArg): boolean { + return plainDatesEqual(slots, toPlainDateSlots(otherArg)) + }, + toString: formatPlainDateIso, + toJSON(slots: PlainDateSlots): string { + return formatPlainDateIso(slots) + }, + toLocaleString(slots: PlainDateSlots, locales?: LocalesArg, options?: Intl.DateTimeFormatOptions) { + const [format, epochMilli] = prepPlainDateFormat(locales, options, slots) + return format.format(epochMilli) + }, + toZonedDateTime( + slots: PlainDateSlots, + options: TimeZoneArg | { timeZone: TimeZoneArg, plainTime?: PlainTimeArg }, + ): ZonedDateTime { + const optionsObj = + (!isObjectLike(options) || options instanceof TimeZone) + ? { timeZone: options } + : options as { timeZone: TimeZoneArg, plainTime?: PlainTimeArg } + + return createZonedDateTime( + plainDateToZonedDateTime(refineTimeZoneSlot, toPlainTimeSlots, createTimeZoneOps, slots, optionsObj) + ) + }, + toPlainDateTime(slots: PlainDateSlots, plainTimeArg?: PlainTimeArg): PlainDateTime { + return createPlainDateTime( + plainDateToPlainDateTime(slots, optionalToPlainTimeFields(plainTimeArg)) + ) + }, + toPlainYearMonth(slots: PlainDateSlots): PlainYearMonth { + return createPlainYearMonth( + plainDateToPlainYearMonth(createYearMonthRefineOps, slots, this) + ) + }, + toPlainMonthDay(slots: PlainDateSlots): PlainMonthDay { + return createPlainMonthDay( + plainDateToPlainMonthDay(createMonthDayRefineOps, slots, this) + ) + }, + getISOFields: removeBranding, + getCalendar: getCalendarFromSlots, + valueOf: neverValueOf, + }, + { + from(arg: any, options?: OverflowOptions): PlainDate { + return createPlainDate( + toPlainDateSlots(arg, options) + ) + }, + compare(arg0: PlainDateArg, arg1: PlainDateArg): NumSign { + return compareIsoDateFields( + toPlainDateSlots(arg0), + toPlainDateSlots(arg1), + ) + } + }, +) + +// Utils +// ------------------------------------------------------------------------------------------------- + +export function toPlainDateSlots(arg: PlainDateArg, options?: OverflowOptions): PlainDateSlots { + options = copyOptions(options) + + if (isObjectLike(arg)) { + const slots = (getSlots(arg) || {}) as Partial + + switch (slots.branding) { + case PlainDateBranding: + refineOverflowOptions(options) // parse unused options + return slots as PlainDateSlots + + case PlainDateTimeBranding: + refineOverflowOptions(options) // parse unused options + return createPlainDateSlots(slots as PlainDateTimeSlots) + + case ZonedDateTimeBranding: + refineOverflowOptions(options) // parse unused options + return zonedDateTimeToPlainDate( + createSimpleTimeZoneOps, + slots as ZonedDateTimeSlots + ) + } + + return refinePlainDateBag( + createDateRefineOps(getCalendarSlotFromBag(arg as PlainDateBag)), + arg as PlainDateBag, + options, + ) + } + + const res = parsePlainDate(arg) + refineOverflowOptions(options) // parse unused options + return res +} diff --git a/packages/temporal-polyfill/src/classApi/plainDateTime.ts b/packages/temporal-polyfill/src/classApi/plainDateTime.ts new file mode 100644 index 00000000..b0c9faec --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/plainDateTime.ts @@ -0,0 +1,188 @@ +import { DateFields, DateTimeBag, TimeFields } from '../internal/calendarFields' +import { isoTimeFieldDefaults } from '../internal/calendarIsoFields' +import { DateTimeDisplayOptions, DiffOptions, EpochDisambigOptions, OverflowOptions, RoundingOptions, copyOptions, refineOverflowOptions } from '../internal/optionsRefine' +import { UnitName } from '../internal/units' +import { NumSign, bindArgs, isObjectLike } from '../internal/utils' +import { PlainDateBranding, PlainDateSlots, PlainDateTimeBranding, PlainDateTimeSlots, ZonedDateTimeBranding, ZonedDateTimeSlots, createPlainDateTimeSlots, createPlainDateSlots, createPlainTimeSlots, getId, removeBranding, BrandingSlots } from '../internal/slots' +import { getSlots, rejectInvalidBag, createSlotClass } from './slotsForClasses' +import { CalendarSlot, getCalendarSlotFromBag, refineCalendarSlot } from './slotsForClasses' +import { TimeZoneSlot, refineTimeZoneSlot } from './slotsForClasses' +import { CalendarArg } from './calendar' +import { Duration, DurationArg, createDuration, toDurationSlots } from './duration' +import { PlainDate, PlainDateArg, createPlainDate, toPlainDateSlots } from './plainDate' +import { PlainMonthDay, createPlainMonthDay } from './plainMonthDay' +import { PlainTime, PlainTimeArg, createPlainTime } from './plainTime' +import { PlainYearMonth, createPlainYearMonth } from './plainYearMonth' +import { TimeZoneArg } from './timeZone' +import { ZonedDateTime, createZonedDateTime } from './zonedDateTime' +import { calendarIdGetters, dateGetters, getCalendarFromSlots, neverValueOf, timeGetters } from './mixins' +import { optionalToPlainTimeFields } from './utils' +import { createDateModOps, createDateRefineOps, createDiffOps, createMonthDayRefineOps, createMoveOps, createYearMonthRefineOps } from './calendarOpsQuery' +import { createSimpleTimeZoneOps, createTimeZoneOps } from './timeZoneOpsQuery' +import { PlainDateBag, PlainDateTimeBag, plainDateTimeWithFields, refinePlainDateTimeBag } from '../internal/bag' +import { constructPlainDateTimeSlots } from '../internal/construct' +import { plainDateTimeWithPlainDate, plainDateTimeWithPlainTime, slotsWithCalendar } from '../internal/mod' +import { movePlainDateTime } from '../internal/move' +import { diffPlainDateTimes } from '../internal/diff' +import { roundPlainDateTime } from '../internal/round' +import { plainDateTimesEqual, compareIsoDateTimeFields } from '../internal/compare' +import { formatPlainDateTimeIso } from '../internal/formatIso' +import { plainDateTimeToPlainMonthDay, plainDateTimeToPlainYearMonth, plainDateTimeToZonedDateTime, zonedDateTimeToPlainDateTime } from '../internal/convert' +import { parsePlainDateTime } from '../internal/parseIso' +import { prepPlainDateTimeFormat } from './dateTimeFormat' +import { LocalesArg } from '../internal/formatIntl' + +export type PlainDateTime = any & DateFields & TimeFields +export type PlainDateTimeArg = PlainDateTime | PlainDateTimeBag | string + +export const [PlainDateTime, createPlainDateTime] = createSlotClass( + PlainDateTimeBranding, + bindArgs(constructPlainDateTimeSlots, refineCalendarSlot), + { + ...calendarIdGetters, + ...dateGetters, + ...timeGetters, + }, + { + with(slots: PlainDateTimeSlots, mod: DateTimeBag, options?: OverflowOptions): PlainDateTime { + return createPlainDateTime( + plainDateTimeWithFields(createDateModOps, slots, this, rejectInvalidBag(mod), options) + ) + }, + withPlainTime(slots: PlainDateTimeSlots, plainTimeArg?: PlainTimeArg): PlainDateTime { + return createPlainDateTime( + plainDateTimeWithPlainTime(slots, optionalToPlainTimeFields(plainTimeArg)) + ) + }, + withPlainDate(slots: PlainDateTimeSlots, plainDateArg: PlainDateArg): PlainDateTime { + return createPlainDateTime( + plainDateTimeWithPlainDate(slots, toPlainDateSlots(plainDateArg)) + ) + }, + withCalendar(slots: PlainDateTimeSlots, calendarArg: CalendarArg): PlainDateTime { + return createPlainDateTime( + slotsWithCalendar(slots, refineCalendarSlot(calendarArg)) + ) + }, + add(slots: PlainDateTimeSlots, durationArg: DurationArg, options?: OverflowOptions): PlainDateTime { + return createPlainDateTime( + movePlainDateTime(createMoveOps, slots, toDurationSlots(durationArg), options) + ) + }, + subtract(slots: PlainDateTimeSlots, durationArg: DurationArg, options?: OverflowOptions): PlainDateTime { + return createPlainDateTime( + movePlainDateTime(createMoveOps, slots, toDurationSlots(durationArg), options, true) + ) + }, + until(slots: PlainDateTimeSlots, otherArg: PlainDateTimeArg, options?: DiffOptions): Duration { + return createDuration( + diffPlainDateTimes(createDiffOps, slots, toPlainDateTimeSlots(otherArg), options) + ) + }, + since(slots: PlainDateTimeSlots, otherArg: PlainDateTimeArg, options?: DiffOptions): Duration { + return createDuration( + diffPlainDateTimes(createDiffOps, slots, toPlainDateTimeSlots(otherArg), options, true) + ) + }, + round(slots: PlainDateTimeSlots, options: RoundingOptions | UnitName): PlainDateTime { + return createPlainDateTime( + roundPlainDateTime(slots, options) + ) + }, + equals(slots: PlainDateTimeSlots, otherArg: PlainDateTimeArg): boolean { + return plainDateTimesEqual(slots, toPlainDateTimeSlots(otherArg)) + }, + toString(slots: PlainDateTimeSlots, options?: DateTimeDisplayOptions): string { + return formatPlainDateTimeIso(slots, options) + }, + toJSON(slots: PlainDateTimeSlots): string { + return formatPlainDateTimeIso(slots) + }, + toLocaleString(slots: PlainDateTimeSlots, locales?: LocalesArg, options?: Intl.DateTimeFormatOptions) { + const [format, epochMilli] = prepPlainDateTimeFormat(locales, options, slots) + return format.format(epochMilli) + }, + toZonedDateTime(slots: PlainDateTimeSlots, timeZoneArg: TimeZoneArg, options?: EpochDisambigOptions): ZonedDateTime { + return createZonedDateTime( + plainDateTimeToZonedDateTime(createTimeZoneOps, slots, refineTimeZoneSlot(timeZoneArg), options) + ) + }, + toPlainDate(slots: PlainDateTimeSlots): PlainDate { + return createPlainDate( + createPlainDateSlots(slots) + ) + }, + toPlainYearMonth(slots: PlainDateTimeSlots): PlainYearMonth { + return createPlainYearMonth( + plainDateTimeToPlainYearMonth(createYearMonthRefineOps, slots, this) + ) + }, + toPlainMonthDay(slots: PlainDateTimeSlots): PlainMonthDay { + return createPlainMonthDay( + plainDateTimeToPlainMonthDay(createMonthDayRefineOps, slots, this) + ) + }, + toPlainTime(slots: PlainDateTimeSlots): PlainTime { + return createPlainTime( + createPlainTimeSlots(slots), + ) + }, + getISOFields: removeBranding, + getCalendar: getCalendarFromSlots, + valueOf: neverValueOf, + }, + { + from(arg: PlainDateTimeArg, options: OverflowOptions): PlainDateTime { + return createPlainDateTime( + toPlainDateTimeSlots(arg, options) + ) + }, + compare(arg0: PlainDateTimeArg, arg1: PlainDateTimeArg): NumSign { + return compareIsoDateTimeFields( + toPlainDateTimeSlots(arg0), + toPlainDateTimeSlots(arg1), + ) + } + } +) + +// Utils +// ------------------------------------------------------------------------------------------------- + +export function toPlainDateTimeSlots(arg: PlainDateTimeArg, options?: OverflowOptions): PlainDateTimeSlots { + options = copyOptions(options) + + if (isObjectLike(arg)) { + const slots = (getSlots(arg) || {}) as Partial + + switch (slots.branding) { + case PlainDateTimeBranding: + refineOverflowOptions(options) // parse unused options + return slots as PlainDateTimeSlots + + case PlainDateBranding: + refineOverflowOptions(options) // parse unused options + return createPlainDateTimeSlots({ + ...(slots as PlainDateSlots), + ...isoTimeFieldDefaults, + }) + + case ZonedDateTimeBranding: + refineOverflowOptions(options) // parse unused options + return zonedDateTimeToPlainDateTime( + createSimpleTimeZoneOps, + slots as ZonedDateTimeSlots, + ) + } + + return refinePlainDateTimeBag( + createDateRefineOps(getCalendarSlotFromBag(arg as PlainDateBag)), + arg as PlainDateBag, + options, + ) + } + + const res = parsePlainDateTime(arg) + refineOverflowOptions(options) // parse unused options + return res +} diff --git a/packages/temporal-polyfill/src/classApi/plainMonthDay.ts b/packages/temporal-polyfill/src/classApi/plainMonthDay.ts new file mode 100644 index 00000000..816f7364 --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/plainMonthDay.ts @@ -0,0 +1,95 @@ +import { isoCalendarId } from '../internal/calendarConfig' +import { MonthDayBag, MonthDayFieldsIntl, YearFields } from '../internal/calendarFields' +import { LocalesArg } from '../internal/formatIntl' +import { DateTimeDisplayOptions, OverflowOptions, copyOptions, refineOverflowOptions } from '../internal/optionsRefine' +import { bindArgs, isObjectLike } from '../internal/utils' +import { PlainMonthDayBranding, PlainMonthDaySlots, getId, removeBranding } from '../internal/slots' +import { createSlotClass, getSlots, rejectInvalidBag } from './slotsForClasses' +import { PlainDate, createPlainDate } from './plainDate' +import { CalendarSlot, extractCalendarSlotFromBag, refineCalendarSlot } from './slotsForClasses' +import { CalendarArg } from './calendar' +import { calendarIdGetters, getCalendarFromSlots, monthDayGetters, neverValueOf } from './mixins' +import { createDateModOps, createMonthDayModOps, createMonthDayRefineOps } from './calendarOpsQuery' +import { createNativeStandardOps } from '../internal/calendarNativeQuery' +import { PlainMonthDayBag, plainMonthDayWithFields, refinePlainMonthDayBag } from '../internal/bag' +import { constructPlainMonthDaySlots } from '../internal/construct' +import { plainMonthDaysEqual } from '../internal/compare' +import { formatPlainMonthDayIso } from '../internal/formatIso' +import { plainMonthDayToPlainDate } from '../internal/convert' +import { parsePlainMonthDay } from '../internal/parseIso' +import { prepPlainMonthDayFormat } from './dateTimeFormat' + +export type PlainMonthDay = any & MonthDayFieldsIntl +export type PlainMonthDayArg = PlainMonthDay | PlainMonthDayBag | string + +export const [PlainMonthDay, createPlainMonthDay, getPlainMonthDaySlots] = createSlotClass( + PlainMonthDayBranding, + bindArgs(constructPlainMonthDaySlots, refineCalendarSlot), + { + ...calendarIdGetters, + ...monthDayGetters, + }, + { + with(slots: PlainMonthDaySlots, mod: MonthDayBag, options?: OverflowOptions): PlainMonthDay { + return createPlainMonthDay( + plainMonthDayWithFields(createMonthDayModOps, slots, this, rejectInvalidBag(mod), options) + ) + }, + equals(slots: PlainMonthDaySlots, otherArg: PlainMonthDayArg): boolean { + return plainMonthDaysEqual(slots, toPlainMonthDaySlots(otherArg)) + }, + toString: formatPlainMonthDayIso, + toJSON(slots: PlainMonthDaySlots): string { + return formatPlainMonthDayIso(slots) + }, + toLocaleString(slots: PlainMonthDaySlots, locales?: LocalesArg, options?: Intl.DateTimeFormatOptions): string { + const [format, epochMilli] = prepPlainMonthDayFormat(locales, options, slots) + return format.format(epochMilli) + }, + toPlainDate(slots: PlainMonthDaySlots, bag: YearFields): PlainDate { + return createPlainDate( + plainMonthDayToPlainDate(createDateModOps, slots, this, bag) + ) + }, + getISOFields: removeBranding, + getCalendar: getCalendarFromSlots, + valueOf: neverValueOf, + }, + { + from(arg: PlainMonthDayArg, options?: OverflowOptions): PlainMonthDay { + return createPlainMonthDay( + toPlainMonthDaySlots(arg, options), + ) + } + }, +) + +// Utils +// ------------------------------------------------------------------------------------------------- + +export function toPlainMonthDaySlots(arg: PlainMonthDayArg, options?: OverflowOptions): PlainMonthDaySlots { + options = copyOptions(options) + + if (isObjectLike(arg)) { + const slots = getSlots(arg) + + if (slots && slots.branding === PlainMonthDayBranding) { + refineOverflowOptions(options) // parse unused options + return slots as PlainMonthDaySlots + } + + const calendarMaybe = extractCalendarSlotFromBag(arg as PlainMonthDaySlots) + const calendar = calendarMaybe || isoCalendarId + + return refinePlainMonthDayBag( + createMonthDayRefineOps(calendar), + !calendarMaybe, + arg as MonthDayBag, + options, + ) + } + + const res = parsePlainMonthDay(createNativeStandardOps, arg) + refineOverflowOptions(options) // parse unused options + return res +} diff --git a/packages/temporal-polyfill/src/classApi/plainTime.ts b/packages/temporal-polyfill/src/classApi/plainTime.ts new file mode 100644 index 00000000..8b407c6e --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/plainTime.ts @@ -0,0 +1,141 @@ +import { TimeBag, TimeFields } from '../internal/calendarFields' +import { IsoTimeFields } from '../internal/calendarIsoFields' +import { LocalesArg } from '../internal/formatIntl' +import { + DiffOptions, + OverflowOptions, + RoundingOptions, + TimeDisplayOptions, + refineOverflowOptions, +} from '../internal/optionsRefine' +import { UnitName } from '../internal/units' +import { NumSign, isObjectLike } from '../internal/utils' +import { BrandingSlots, PlainDateTimeBranding, PlainDateTimeSlots, PlainTimeBranding, PlainTimeSlots, ZonedDateTimeBranding, ZonedDateTimeSlots, createPlainTimeSlots, removeBranding } from '../internal/slots' +import { createSlotClass, getSlots, rejectInvalidBag } from './slotsForClasses' +import { PlainDateArg, toPlainDateSlots } from './plainDate' +import { PlainDateTime, createPlainDateTime } from './plainDateTime' +import { TimeZoneArg } from './timeZone' +import { ZonedDateTime, createZonedDateTime } from './zonedDateTime' +import { Duration, DurationArg, createDuration, toDurationSlots } from './duration' +import { neverValueOf, timeGetters } from './mixins' +import { TimeZoneSlot, refineTimeZoneSlot } from './slotsForClasses' +import { CalendarSlot } from './slotsForClasses' +import { createSimpleTimeZoneOps, createTimeZoneOps } from './timeZoneOpsQuery' +import { PlainTimeBag, plainTimeWithFields, refinePlainTimeBag } from '../internal/bag' +import { constructPlainTimeSlots } from '../internal/construct' +import { movePlainTime } from '../internal/move' +import { diffPlainTimes } from '../internal/diff' +import { roundPlainTime } from '../internal/round' +import { plainTimesEqual, compareIsoTimeFields } from '../internal/compare' +import { formatPlainTimeIso } from '../internal/formatIso' +import { plainTimeToPlainDateTime, plainTimeToZonedDateTime, zonedDateTimeToPlainTime } from '../internal/convert' +import { parsePlainTime } from '../internal/parseIso' +import { prepPlainTimeFormat } from './dateTimeFormat' + +export type PlainTime = any & TimeFields +export type PlainTimeArg = PlainTime | PlainTimeBag | string + +export const [PlainTime, createPlainTime] = createSlotClass( + PlainTimeBranding, + constructPlainTimeSlots, + timeGetters, + { + with(slots: PlainTimeSlots, mod: TimeBag, options?: OverflowOptions): PlainTime { + return createPlainTime( + plainTimeWithFields(this, rejectInvalidBag(mod), options) + ) + }, + add(slots: PlainTimeSlots, durationArg: DurationArg): PlainTime { + return createPlainTime( + movePlainTime(slots, toDurationSlots(durationArg)) + ) + }, + subtract(slots: PlainTimeSlots, durationArg: DurationArg): PlainTime { + return createPlainTime( + movePlainTime(slots, toDurationSlots(durationArg), true) + ) + }, + until(slots: PlainTimeSlots, otherArg: PlainTimeArg, options?: DiffOptions): Duration { + return createDuration( + diffPlainTimes(slots, toPlainTimeSlots(otherArg), options) + ) + }, + since(slots: PlainTimeSlots, otherArg: PlainTimeArg, options?: DiffOptions): Duration { + return createDuration( + diffPlainTimes(slots, toPlainTimeSlots(otherArg), options, true) + ) + }, + round(slots: PlainTimeSlots, options: RoundingOptions | UnitName): PlainTime { + return createPlainTime( + roundPlainTime(slots, options) + ) + }, + equals(slots: PlainTimeSlots, other: PlainTimeArg): boolean { + return plainTimesEqual(slots, toPlainTimeSlots(other)) + }, + toString: formatPlainTimeIso, + toJSON(slots: PlainTimeSlots): string { + return formatPlainTimeIso(slots) + }, + toLocaleString(slots: PlainTimeSlots, locales?: LocalesArg, options?: Intl.DateTimeFormatOptions): string { + const [format, epochMilli] = prepPlainTimeFormat(locales, options, slots) + return format.format(epochMilli) + }, + toZonedDateTime(slots: PlainTimeSlots, options: { timeZone: TimeZoneArg, plainDate: PlainDateArg }): ZonedDateTime { + return createZonedDateTime( + plainTimeToZonedDateTime(refineTimeZoneSlot, toPlainDateSlots, createTimeZoneOps, slots, options) + ) + }, + toPlainDateTime(slots: PlainTimeSlots, plainDateArg: PlainDateArg): PlainDateTime { + return createPlainDateTime( + plainTimeToPlainDateTime(slots, toPlainDateSlots(plainDateArg)) + ) + }, + getISOFields: removeBranding, + valueOf: neverValueOf, + }, + { + from(arg: PlainTimeArg, options?: OverflowOptions): PlainTime { + return createPlainTime( + toPlainTimeSlots(arg, options) + ) + }, + compare(arg0: PlainTimeArg, arg1: PlainTimeArg): NumSign { + return compareIsoTimeFields( + toPlainTimeSlots(arg0), + toPlainTimeSlots(arg1), + ) + } + } +) + +// Utils +// ------------------------------------------------------------------------------------------------- + +export function toPlainTimeSlots(arg: PlainTimeArg, options?: OverflowOptions): PlainTimeSlots { + if (isObjectLike(arg)) { + const slots = (getSlots(arg) || {}) as Partial + + switch (slots.branding) { + case PlainTimeBranding: + refineOverflowOptions(options) // parse unused options + return slots as PlainTimeSlots + + case PlainDateTimeBranding: + refineOverflowOptions(options) // parse unused options + return createPlainTimeSlots(slots as PlainDateTimeSlots) + + case ZonedDateTimeBranding: + refineOverflowOptions(options) // parse unused options + return zonedDateTimeToPlainTime( + createSimpleTimeZoneOps, + slots as ZonedDateTimeSlots, + ) + } + + return refinePlainTimeBag(arg as PlainTimeBag, options) + } + + refineOverflowOptions(options) // parse unused options + return parsePlainTime(arg) +} diff --git a/packages/temporal-polyfill/src/classApi/plainYearMonth.ts b/packages/temporal-polyfill/src/classApi/plainYearMonth.ts new file mode 100644 index 00000000..7288d618 --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/plainYearMonth.ts @@ -0,0 +1,119 @@ +import { YearMonthBag, YearMonthFieldsIntl } from '../internal/calendarFields' +import { Duration, DurationArg, createDuration, toDurationSlots } from './duration' +import { LocalesArg } from '../internal/formatIntl' +import { DateTimeDisplayOptions, DiffOptions, OverflowOptions, copyOptions, refineOverflowOptions } from '../internal/optionsRefine' +import { NumSign, bindArgs, isObjectLike } from '../internal/utils' +import { PlainYearMonthBranding, PlainYearMonthSlots, getId, removeBranding } from '../internal/slots' +import { getSlots, rejectInvalidBag, createSlotClass } from './slotsForClasses' +import { CalendarSlot, getCalendarSlotFromBag, refineCalendarSlot } from './slotsForClasses' +import { CalendarArg } from './calendar' +import { PlainDate, createPlainDate } from './plainDate' +import { calendarIdGetters, getCalendarFromSlots, neverValueOf, yearMonthGetters } from './mixins' +import { createDateModOps, createYearMonthDiffOps, createYearMonthModOps, createYearMonthMoveOps, createYearMonthRefineOps } from './calendarOpsQuery' +import { createNativeStandardOps } from '../internal/calendarNativeQuery' +import { PlainYearMonthBag, plainYearMonthWithFields, refinePlainYearMonthBag } from '../internal/bag' +import { constructPlainYearMonthSlots } from '../internal/construct' +import { movePlainYearMonth } from '../internal/move' +import { diffPlainYearMonth } from '../internal/diff' +import { plainYearMonthsEqual, compareIsoDateFields } from '../internal/compare' +import { formatPlainYearMonthIso } from '../internal/formatIso' +import { plainYearMonthToPlainDate } from '../internal/convert' +import { parsePlainYearMonth } from '../internal/parseIso' +import { prepPlainYearMonthFormat } from './dateTimeFormat' + +export type PlainYearMonth = any & YearMonthFieldsIntl +export type PlainYearMonthArg = PlainYearMonth | PlainYearMonthBag | string + +export const [PlainYearMonth, createPlainYearMonth, getPlainYearMonthSlots] = createSlotClass( + PlainYearMonthBranding, + bindArgs(constructPlainYearMonthSlots, refineCalendarSlot), + { + ...calendarIdGetters, + ...yearMonthGetters, + }, + { + with(slots: PlainYearMonthSlots, mod: YearMonthBag, options?: OverflowOptions): PlainYearMonth { + return createPlainYearMonth( + plainYearMonthWithFields(createYearMonthModOps, slots, this, rejectInvalidBag(mod), options) + ) + }, + add(slots: PlainYearMonthSlots, durationArg: DurationArg, options?: OverflowOptions): PlainYearMonth { + return createPlainYearMonth( + movePlainYearMonth(createYearMonthMoveOps, slots, toDurationSlots(durationArg), options) + ) + }, + subtract(slots: PlainYearMonthSlots, durationArg: DurationArg, options?: OverflowOptions): PlainYearMonth { + return createPlainYearMonth( + movePlainYearMonth(createYearMonthMoveOps, slots, toDurationSlots(durationArg), options, true) + ) + }, + until(slots: PlainYearMonthSlots, otherArg: PlainYearMonthArg, options?: DiffOptions): Duration { + return createDuration( + diffPlainYearMonth(createYearMonthDiffOps, slots, toPlainYearMonthSlots(otherArg), options) + ) + }, + since(slots: PlainYearMonthSlots, otherArg: PlainYearMonthArg, options?: DiffOptions): Duration { + return createDuration( + diffPlainYearMonth(createYearMonthDiffOps, slots, toPlainYearMonthSlots(otherArg), options, true) + ) + }, + equals(slots: PlainYearMonthSlots, otherArg: PlainYearMonthArg): boolean { + return plainYearMonthsEqual(slots, toPlainYearMonthSlots(otherArg)) + }, + toString: formatPlainYearMonthIso, + toJSON(slots: PlainYearMonthSlots) { + return formatPlainYearMonthIso(slots) + }, + toLocaleString(slots: PlainYearMonthSlots, locales?: LocalesArg, options?: Intl.DateTimeFormatOptions): string { + const [format, epochMilli] = prepPlainYearMonthFormat(locales, options, slots) + return format.format(epochMilli) + }, + toPlainDate(slots: PlainYearMonthSlots, bag: { day: number }): PlainDate { + return createPlainDate( + plainYearMonthToPlainDate(createDateModOps, slots, this, bag) + ) + }, + getISOFields: removeBranding, + getCalendar: getCalendarFromSlots, + valueOf: neverValueOf, + }, + { + from(arg: PlainYearMonthArg, options?: OverflowOptions): PlainYearMonth { + return createPlainYearMonth( + toPlainYearMonthSlots(arg, options) + ) + }, + compare(arg0: PlainYearMonthArg, arg1: PlainYearMonthArg): NumSign { + return compareIsoDateFields( + toPlainYearMonthSlots(arg0), + toPlainYearMonthSlots(arg1), + ) + }, + } +) + +// Utils +// ------------------------------------------------------------------------------------------------- + +export function toPlainYearMonthSlots(arg: PlainYearMonthArg, options?: OverflowOptions) { + options = copyOptions(options) + + if (isObjectLike(arg)) { + const slots = getSlots(arg) + + if (slots && slots.branding === PlainYearMonthBranding) { + refineOverflowOptions(options) // parse unused options + return slots as PlainYearMonthSlots + } + + return refinePlainYearMonthBag( + createYearMonthRefineOps(getCalendarSlotFromBag(arg as any)), // !!! + arg as any, // !!! + options, + ) + } + + const res = parsePlainYearMonth(createNativeStandardOps, arg) + refineOverflowOptions(options) // parse unused options + return res +} diff --git a/packages/temporal-polyfill/src/classApi/slotsForClasses.ts b/packages/temporal-polyfill/src/classApi/slotsForClasses.ts new file mode 100644 index 00000000..84024f60 --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/slotsForClasses.ts @@ -0,0 +1,153 @@ +import { isoCalendarId } from '../internal/calendarConfig' +import { IsoDateFields, IsoTimeFields } from '../internal/calendarIsoFields' +import { BrandingSlots, refineCalendarSlotString, refineTimeZoneSlotString } from '../internal/slots' +import { createGetterDescriptors, createNameDescriptors, createPropDescriptors, createStringTagDescriptors, isObjectLike, mapProps } from '../internal/utils' +import { CalendarArg, CalendarProtocol, checkCalendarProtocol } from './calendar' +import { TimeZoneArg } from './timeZone' +import { TimeZoneProtocol, checkTimeZoneProtocol } from './timeZoneProtocol' +import * as errorMessages from '../internal/errorMessages' + +// Lookup +// ------------------------------------------------------------------------------------------------- + +const slotsMap = new WeakMap() + +// TODO: allow type-input, so caller doesn't need to cast so much +export const getSlots = slotsMap.get.bind(slotsMap) +const setSlots = slotsMap.set.bind(slotsMap) + +// Class +// ------------------------------------------------------------------------------------------------- + +export function createSlotClass( + branding: string, + construct: any, + getters: any, + methods: any, + staticMethods: any, +): any { + function Class(this: any, ...args: any[]) { + if (this instanceof Class) { + setSlots(this, construct(...args)) + } else { + throw new TypeError(errorMessages.invalidCallingContext) + } + } + + Object.defineProperties(Class.prototype, { + ...createGetterDescriptors(mapProps(curryMethod as any, getters) as any), // !!! + ...createPropDescriptors(mapProps(curryMethod as any, methods)), + ...createStringTagDescriptors('Temporal.' + branding), + }) + + Object.defineProperties(Class, { + ...createPropDescriptors(staticMethods), + ...createNameDescriptors(branding), + }) + + function curryMethod(method: any, methodName: string) { + return Object.defineProperties( + function(this: any, ...args: any[]) { + const slots = getSlots(this) + if (!slots || slots.branding !== branding) { + throw new TypeError(errorMessages.invalidCallingContext) + } + return method.call(this, slots, ...args) + }, + createNameDescriptors(methodName), + ) + } + + return [ + Class, + + // createViaSlots + (slots: BrandingSlots) => { + const instance = Object.create(Class.prototype) + setSlots(instance, slots) + return instance + }, + + // getSpecificSlots + (obj: any) => { + const slots = getSlots(obj) + if (!slots || slots.branding !== branding) { + throw new TypeError(errorMessages.invalidCallingContext) + } + return slots + }, + ] +} + +// Reject +// ------------------------------------------------------------------------------------------------- + +export function rejectInvalidBag(bag: B): B { + if ( + getSlots(bag) || + (bag as any).calendar !== undefined || + (bag as any).timeZone !== undefined + ) { + throw new TypeError(errorMessages.invalidBag) + } + return bag +} + +// getISOFields +// ------------------------------------------------------------------------------------------------- + +export type PublicDateSlots = IsoDateFields & { calendar: CalendarSlot } +export type PublicDateTimeSlots = PublicDateSlots & IsoTimeFields + +// Calendar +// ------------------------------------------------------------------------------------------------- + +export type CalendarSlot = CalendarProtocol | string + +export function refineCalendarSlot(calendarArg: CalendarArg): CalendarSlot { + if (isObjectLike(calendarArg)) { + // look at other date-like objects + const { calendar } = (getSlots(calendarArg) || {}) as { calendar?: CalendarSlot } + if (calendar) { + return calendar + } + + checkCalendarProtocol(calendarArg as CalendarProtocol) + return calendarArg as CalendarProtocol + } + + return refineCalendarSlotString(calendarArg) +} + +// bag +// --- + +export function getCalendarSlotFromBag(bag: { calendar?: CalendarArg }): CalendarSlot { + return extractCalendarSlotFromBag(bag) || isoCalendarId +} + +export function extractCalendarSlotFromBag(bag: { calendar?: CalendarArg }): CalendarSlot | undefined { + const { calendar } = bag + if (calendar !== undefined) { + return refineCalendarSlot(calendar) + } +} + +// TimeZone +// ------------------------------------------------------------------------------------------------- + +export type TimeZoneSlot = TimeZoneProtocol | string + +export function refineTimeZoneSlot(arg: TimeZoneArg): TimeZoneSlot { + if (isObjectLike(arg)) { + const { timeZone } = (getSlots(arg) || {}) as { timeZone?: TimeZoneSlot } + + if (timeZone) { + return timeZone // TimeZoneOps + } + + checkTimeZoneProtocol(arg as TimeZoneProtocol) + return arg as TimeZoneProtocol + } + return refineTimeZoneSlotString(arg) +} diff --git a/packages/temporal-polyfill/src/public/temporal.ts b/packages/temporal-polyfill/src/classApi/temporal.ts similarity index 52% rename from packages/temporal-polyfill/src/public/temporal.ts rename to packages/temporal-polyfill/src/classApi/temporal.ts index 94f9d6c6..c17a09f6 100644 --- a/packages/temporal-polyfill/src/public/temporal.ts +++ b/packages/temporal-polyfill/src/classApi/temporal.ts @@ -1,4 +1,6 @@ +import { createPropDescriptors, createStringTagDescriptors } from '../internal/utils' +// public import { Calendar } from './calendar' import { Duration } from './duration' import { Instant } from './instant' @@ -11,17 +13,19 @@ import { PlainYearMonth } from './plainYearMonth' import { TimeZone } from './timeZone' import { ZonedDateTime } from './zonedDateTime' -export const Temporal = { - PlainYearMonth, - PlainMonthDay, - PlainDate, - PlainTime, - PlainDateTime, - ZonedDateTime, - Instant, - Calendar, - TimeZone, - Duration, - Now, - [Symbol.toStringTag]: 'Temporal', // TODO: make readonly, dry with attachStringTag? -} +export const Temporal = Object.defineProperties({}, { + ...createStringTagDescriptors('Temporal'), + ...createPropDescriptors({ + PlainYearMonth, + PlainMonthDay, + PlainDate, + PlainTime, + PlainDateTime, + ZonedDateTime, + Instant, + Calendar, + TimeZone, + Duration, + Now, + }) +}) diff --git a/packages/temporal-polyfill/src/classApi/timeZone.ts b/packages/temporal-polyfill/src/classApi/timeZone.ts new file mode 100644 index 00000000..2bcf5206 --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/timeZone.ts @@ -0,0 +1,125 @@ +import { NativeTimeZone } from '../internal/timeZoneNative' +import { queryNativeTimeZone } from '../internal/timeZoneNative' +import { formatOffsetNano } from '../internal/formatIso' +import { EpochDisambigOptions, refineEpochDisambigOptions } from '../internal/optionsRefine' +import { isoCalendarId } from '../internal/calendarConfig' +import { DayTimeNano } from '../internal/dayTimeNano' +import { getSingleInstantFor } from '../internal/timeZoneOps' +import { epochNanoToIso } from '../internal/epochAndTime' +import { createSlotClass, refineCalendarSlot } from './slotsForClasses' +import { refineTimeZoneSlot } from './slotsForClasses' +import { ZonedDateTime } from './zonedDateTime' +import { CalendarArg } from './calendar' +import { Instant, InstantArg, createInstant, toInstantSlots } from './instant' +import { PlainDateTime, PlainDateTimeArg, createPlainDateTime, toPlainDateTimeSlots } from './plainDateTime' +import { TimeZoneProtocol } from './timeZoneProtocol' +import { createAdapterOps, simpleTimeZoneAdapters } from './timeZoneAdapter' +import { requireString } from '../internal/cast' +import { BrandingSlots, createInstantSlots, createPlainDateTimeSlots, isTimeZoneSlotsEqual } from '../internal/slots' + +export type TimeZone = any +export type TimeZoneArg = TimeZoneProtocol | string | ZonedDateTime +export type TimeZoneClassSlots = BrandingSlots & { + id: string + native: NativeTimeZone +} + +export const [TimeZone, createTimeZone] = createSlotClass( + 'TimeZone', + (timeZoneId: string): TimeZoneClassSlots => { + const timeZoneNative = queryNativeTimeZone(requireString(timeZoneId)) + return { + branding: 'TimeZone', + id: timeZoneNative.id, + native: timeZoneNative, + } + }, + { + id(slots: TimeZoneClassSlots) { + return slots.id + } + }, + { + toString(slots: TimeZoneClassSlots) { + return slots.id + }, + toJSON(slots: TimeZoneClassSlots) { + return slots.id + }, + getPossibleInstantsFor({ native }: TimeZoneClassSlots, plainDateTimeArg: PlainDateTimeArg): Instant[] { + return native.getPossibleInstantsFor(toPlainDateTimeSlots(plainDateTimeArg)) + .map((epochNano: DayTimeNano) => { + return createInstant( + createInstantSlots(epochNano) + ) + }) + }, + getOffsetNanosecondsFor({ native }: TimeZoneClassSlots, instantArg: InstantArg): number { + return native.getOffsetNanosecondsFor(toInstantSlots(instantArg).epochNanoseconds) + }, + getOffsetStringFor(slots: TimeZoneClassSlots, instantArg: InstantArg): string { + const epochNano = toInstantSlots(instantArg).epochNanoseconds + const calendarOps = createAdapterOps(this, simpleTimeZoneAdapters) // for accessing own methods + const offsetNano = calendarOps.getOffsetNanosecondsFor(epochNano) + + return formatOffsetNano(offsetNano) + }, + getPlainDateTimeFor( + slots: TimeZoneClassSlots, + instantArg: InstantArg, + calendarArg: CalendarArg = isoCalendarId + ): PlainDateTime { + const epochNano = toInstantSlots(instantArg).epochNanoseconds + const calendarOps = createAdapterOps(this, simpleTimeZoneAdapters) // for accessing own methods + const offsetNano = calendarOps.getOffsetNanosecondsFor(epochNano) + + return createPlainDateTime( + createPlainDateTimeSlots( + epochNanoToIso(epochNano, offsetNano), + refineCalendarSlot(calendarArg), + ) + ) + }, + getInstantFor( + slots: TimeZoneClassSlots, + plainDateTimeArg: PlainDateTimeArg, + options?: EpochDisambigOptions, + ): Instant { + const isoFields = toPlainDateTimeSlots(plainDateTimeArg) + const epochDisambig = refineEpochDisambigOptions(options) + const calendarOps = createAdapterOps(this) // for accessing own methods + + return createInstant( + createInstantSlots(getSingleInstantFor(calendarOps, isoFields, epochDisambig)) + ) + }, + getNextTransition({ native }: TimeZoneClassSlots, instantArg: InstantArg): Instant | null { + return getImplTransition(1, native, instantArg) + }, + getPreviousTransition({ native }: TimeZoneClassSlots, instantArg: InstantArg): Instant | null { + return getImplTransition(-1, native, instantArg) + }, + equals(slots: TimeZoneClassSlots, otherArg: TimeZoneArg): boolean { + // weird: pass-in `this` as a CalendarProtocol in case subclasses override `id` getter + return isTimeZoneSlotsEqual(this, refineTimeZoneSlot(otherArg)) + }, + }, + { + from(arg: TimeZoneArg): TimeZoneProtocol { + const timeZoneSlot = refineTimeZoneSlot(arg) + return typeof timeZoneSlot === 'string' + ? new TimeZone(timeZoneSlot) + : timeZoneSlot + } + } +) + +// Utils +// ------------------------------------------------------------------------------------------------- + +function getImplTransition(direction: -1 | 1, impl: NativeTimeZone, instantArg: InstantArg): Instant | null { + const epochNano = impl.getTransition(toInstantSlots(instantArg).epochNanoseconds, direction) + return epochNano ? + createInstant(createInstantSlots(epochNano)) : + null +} diff --git a/packages/temporal-polyfill/src/classApi/timeZoneAdapter.ts b/packages/temporal-polyfill/src/classApi/timeZoneAdapter.ts new file mode 100644 index 00000000..95ec7b19 --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/timeZoneAdapter.ts @@ -0,0 +1,89 @@ +import { isoCalendarId } from '../internal/calendarConfig' +import { DayTimeNano } from '../internal/dayTimeNano' +import { IsoDateTimeFields } from '../internal/calendarIsoFields' +import { Callable, bindArgs } from '../internal/utils' +import { Instant, createInstant, getInstantSlots } from './instant' +import { createPlainDateTime } from './plainDateTime' +import { TimeZoneProtocol } from './timeZoneProtocol' +import { requireFunction, requireInteger } from '../internal/cast' +import { nanoInUtcDay } from '../internal/units' +import { createInstantSlots, createPlainDateTimeSlots } from '../internal/slots' +import { validateTimeZoneOffset } from '../internal/timeZoneOps' + +// Individual Adapters +// ------------------------------------------------------------------------------------------------- + +function adapterGetOffsetNanosecondsFor( + timeZoneProtocol: TimeZoneProtocol, + getOffsetNanosecondsFor: TimeZoneProtocol['getOffsetNanosecondsFor'], + epochNano: DayTimeNano, +): number { + return validateTimeZoneOffsetRes( + getOffsetNanosecondsFor.call( + timeZoneProtocol, + createInstant(createInstantSlots(epochNano)) + ) + ) +} + +function adapterGetPossibleInstantsFor( + timeZoneProtocol: TimeZoneProtocol, + getPossibleInstantsFor: TimeZoneProtocol['getPossibleInstantsFor'], + isoFields: IsoDateTimeFields, +): DayTimeNano[] { + return [ + ...getPossibleInstantsFor.call( + timeZoneProtocol, + createPlainDateTime( + createPlainDateTimeSlots(isoFields, isoCalendarId) + ) + ) + ].map((instant: Instant) => { + return getInstantSlots(instant).epochNanoseconds + }) +} + +function validateTimeZoneOffsetRes(offsetNano: number): number { + return validateTimeZoneOffset(requireInteger(offsetNano)) +} + +// Adapter Sets +// ------------------------------------------------------------------------------------------------- + +export const timeZoneAdapters = { + getOffsetNanosecondsFor: adapterGetOffsetNanosecondsFor, + getPossibleInstantsFor: adapterGetPossibleInstantsFor, +} + +// TODO: rename to be about 'offset' +export const simpleTimeZoneAdapters = { + getOffsetNanosecondsFor: adapterGetOffsetNanosecondsFor, +} + +// Adapter Instantiation +// ------------------------------------------------------------------------------------------------- + +export type AdapterOps = { + [K in keyof KV]: + KV[K] extends (tz: TimeZoneProtocol, m: Callable, ...args: infer Args) => infer Return + ? (...args: Args) => Return + : never +} + +export function createAdapterOps( + timeZoneProtocol: TimeZoneProtocol, + adapterFuncs: KV = timeZoneAdapters as any, +): AdapterOps { + const keys = Object.keys(adapterFuncs).sort() + const boundFuncs = {} as any + + for (const key of keys) { + boundFuncs[key] = bindArgs( + (adapterFuncs as any)[key], + timeZoneProtocol, + requireFunction((timeZoneProtocol as any)[key]), + ) + } + + return boundFuncs +} diff --git a/packages/temporal-polyfill/src/classApi/timeZoneOpsQuery.ts b/packages/temporal-polyfill/src/classApi/timeZoneOpsQuery.ts new file mode 100644 index 00000000..7d1174be --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/timeZoneOpsQuery.ts @@ -0,0 +1,18 @@ +import { queryNativeTimeZone } from '../internal/timeZoneNative' +import { SimpleTimeZoneOps, TimeZoneOps } from '../internal/timeZoneOps' +import { createAdapterOps, simpleTimeZoneAdapters } from './timeZoneAdapter' +import { TimeZoneSlot } from './slotsForClasses' + +export function createTimeZoneOps( + timeZoneSlot: TimeZoneSlot, + adapterFuncs?: any +): TimeZoneOps { + if (typeof timeZoneSlot === 'string') { + return queryNativeTimeZone(timeZoneSlot) + } + return createAdapterOps(timeZoneSlot, adapterFuncs) as any +} + +export function createSimpleTimeZoneOps(timeZoneSlot: TimeZoneSlot): SimpleTimeZoneOps { + return createTimeZoneOps(timeZoneSlot, simpleTimeZoneAdapters as any) +} diff --git a/packages/temporal-polyfill/src/classApi/timeZoneProtocol.ts b/packages/temporal-polyfill/src/classApi/timeZoneProtocol.ts new file mode 100644 index 00000000..febd7b80 --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/timeZoneProtocol.ts @@ -0,0 +1,27 @@ +import { EpochDisambigOptions } from '../internal/optionsRefine' +import { CalendarArg } from './calendar' +import { Instant, InstantArg } from './instant' +import { PlainDateTime, PlainDateTimeArg } from './plainDateTime' +import { createProtocolChecker } from './utils' +import { TimeZoneArg } from './timeZone' +import { timeZoneAdapters } from './timeZoneAdapter' + +/* +TODO: eventually use temporal-spec +See problems mentioned in calendarProtocol +*/ +export interface TimeZoneProtocol { + id: string + getOffsetNanosecondsFor(instant: InstantArg): number + getOffsetStringFor?(instant: InstantArg): string + getPlainDateTimeFor?(instant: InstantArg, calendarArg?: CalendarArg): PlainDateTime + getInstantFor?(dateTime: PlainDateTimeArg, options?: EpochDisambigOptions): Instant + getNextTransition?(startingPoint: InstantArg): Instant | null + getPreviousTransition?(startingPoint: InstantArg): Instant | null + getPossibleInstantsFor(dateTime: PlainDateTimeArg): Instant[] + toString?(): string + toJSON?(): string + equals?(otherArg: TimeZoneArg): boolean +} + +export const checkTimeZoneProtocol = createProtocolChecker(Object.keys(timeZoneAdapters)) diff --git a/packages/temporal-polyfill/src/classApi/utils.ts b/packages/temporal-polyfill/src/classApi/utils.ts new file mode 100644 index 00000000..b5b6dfa9 --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/utils.ts @@ -0,0 +1,20 @@ +import { IsoTimeFields } from '../internal/calendarIsoFields' +import { hasAllPropsByName } from '../internal/utils' +import { PlainTimeArg, toPlainTimeSlots } from './plainTime' +import * as errorMessages from '../internal/errorMessages' + +export function createProtocolChecker(propNames: string[]) { + propNames = propNames + .concat('id') + .sort() + + return (obj: any) => { + if (!hasAllPropsByName(obj, propNames)) { + throw new TypeError(errorMessages.invalidProtocol) + } + } +} + +export function optionalToPlainTimeFields(timeArg: PlainTimeArg | undefined): IsoTimeFields | undefined { + return timeArg === undefined ? undefined : toPlainTimeSlots(timeArg) +} diff --git a/packages/temporal-polyfill/src/classApi/zonedDateTime.ts b/packages/temporal-polyfill/src/classApi/zonedDateTime.ts new file mode 100644 index 00000000..5be5a994 --- /dev/null +++ b/packages/temporal-polyfill/src/classApi/zonedDateTime.ts @@ -0,0 +1,271 @@ +import { DateTimeBag } from '../internal/calendarFields' +import { LocalesArg } from '../internal/formatIntl' +import { formatOffsetNano, formatZonedDateTimeIso } from '../internal/formatIso' +import { + DiffOptions, + OverflowOptions, + RoundingOptions, + ZonedDateTimeDisplayOptions, + ZonedFieldOptions, + copyOptions, + refineZonedFieldOptions, +} from '../internal/optionsRefine' +import { UnitName } from '../internal/units' +import { NumSign, bindArgs, isObjectLike, mapProps } from '../internal/utils' +import { IsoDateTimeFields } from '../internal/calendarIsoFields' +import { ZonedIsoDateTimeSlots, computeHoursInDay, computeStartOfDay, getZonedIsoDateTimeSlots, zonedInternalsToIso } from '../internal/timeZoneOps' +import { ZonedDateTimeBranding, ZonedDateTimeSlots, createDurationSlots, getId } from '../internal/slots' +import { createSlotClass, getSlots, rejectInvalidBag } from './slotsForClasses' +import { CalendarSlot, getCalendarSlotFromBag, refineCalendarSlot } from './slotsForClasses' +import { TimeZoneSlot, refineTimeZoneSlot } from './slotsForClasses' +import { CalendarArg } from './calendar' +import { Duration, DurationArg, createDuration, toDurationSlots } from './duration' +import { Instant, createInstant } from './instant' +import { PlainDate, PlainDateArg, createPlainDate, toPlainDateSlots } from './plainDate' +import { PlainDateTime, createPlainDateTime } from './plainDateTime' +import { PlainMonthDay, createPlainMonthDay } from './plainMonthDay' +import { PlainTime, PlainTimeArg, createPlainTime } from './plainTime' +import { PlainYearMonth, createPlainYearMonth } from './plainYearMonth' +import { TimeZone, TimeZoneArg } from './timeZone' +import { TimeZoneProtocol } from './timeZoneProtocol' +import { neverValueOf, dateGetters, timeGetters, epochGetters, getCalendarFromSlots, calendarIdGetters } from './mixins' +import { optionalToPlainTimeFields } from './utils' +import { createDateModOps, createDateRefineOps, createDiffOps, createMonthDayRefineOps, createMoveOps, createYearMonthRefineOps } from './calendarOpsQuery' +import { createSimpleTimeZoneOps, createTimeZoneOps } from './timeZoneOpsQuery' +import { ZonedDateTimeBag, refineZonedDateTimeBag, zonedDateTimeWithFields } from '../internal/bag' +import { constructZonedDateTimeSlots } from '../internal/construct' +import { slotsWithCalendar, slotsWithTimeZone, zonedDateTimeWithPlainDate, zonedDateTimeWithPlainTime } from '../internal/mod' +import { moveZonedDateTime } from '../internal/move' +import { diffZonedDateTimes } from '../internal/diff' +import { roundZonedDateTime } from '../internal/round' +import { compareZonedDateTimes, zonedDateTimesEqual } from '../internal/compare' +import { zonedDateTimeToInstant, zonedDateTimeToPlainDate, zonedDateTimeToPlainDateTime, zonedDateTimeToPlainMonthDay, zonedDateTimeToPlainTime, zonedDateTimeToPlainYearMonth } from '../internal/convert' +import { parseZonedDateTime } from '../internal/parseIso' +import { prepZonedDateTimeFormat } from './dateTimeFormat' + +export type ZonedDateTime = any +export type ZonedDateTimeArg = ZonedDateTime | ZonedDateTimeBag | string + +export const [ZonedDateTime, createZonedDateTime] = createSlotClass( + ZonedDateTimeBranding, + bindArgs(constructZonedDateTimeSlots, refineCalendarSlot, refineTimeZoneSlot), + { + ...epochGetters, + ...calendarIdGetters, + ...adaptToIsoFields(dateGetters), + ...adaptToIsoFields(timeGetters), + hoursInDay(slots: ZonedDateTimeSlots): number { + return computeHoursInDay(createTimeZoneOps, slots) + }, + offsetNanoseconds(slots: ZonedDateTimeSlots) { + return slotsToIsoFields(slots).offsetNanoseconds + }, + offset(slots: ZonedDateTimeSlots): string { + return formatOffsetNano(slotsToIsoFields(slots).offsetNanoseconds) + }, + timeZoneId(slots: ZonedDateTimeSlots): string { + return getId(slots.timeZone) + }, + }, + { + with(slots: ZonedDateTimeSlots, mod: DateTimeBag, options?: ZonedFieldOptions): ZonedDateTime { + return createZonedDateTime( + zonedDateTimeWithFields(createDateModOps, createTimeZoneOps, slots, this, rejectInvalidBag(mod), options), + ) + }, + withPlainTime(slots: ZonedDateTimeSlots, plainTimeArg?: PlainTimeArg): ZonedDateTime { + return createZonedDateTime( + zonedDateTimeWithPlainTime(createTimeZoneOps, slots, optionalToPlainTimeFields(plainTimeArg)) + ) + }, + withPlainDate(slots: ZonedDateTimeSlots, plainDateArg: PlainDateArg): ZonedDateTime { + return createZonedDateTime( + zonedDateTimeWithPlainDate(createTimeZoneOps, slots, toPlainDateSlots(plainDateArg)) + ) + }, + withTimeZone(slots: ZonedDateTimeSlots, timeZoneArg: TimeZoneArg): ZonedDateTime { + return createZonedDateTime( + slotsWithTimeZone(slots, refineTimeZoneSlot(timeZoneArg)) + ) + }, + withCalendar(slots: ZonedDateTimeSlots, calendarArg: CalendarArg): ZonedDateTime { + return createZonedDateTime( + slotsWithCalendar(slots, refineCalendarSlot(calendarArg)) + ) + }, + add(slots: ZonedDateTimeSlots, durationArg: DurationArg, options?: OverflowOptions): ZonedDateTime { + return createZonedDateTime( + moveZonedDateTime( + createMoveOps, + createTimeZoneOps, + slots, + toDurationSlots(durationArg), + options, + ) + ) + }, + subtract(slots: ZonedDateTimeSlots, durationArg: DurationArg, options?: OverflowOptions): ZonedDateTime { + return createZonedDateTime( + moveZonedDateTime( + createMoveOps, + createTimeZoneOps, + slots, + toDurationSlots(durationArg), + options, + true, + ) + ) + }, + until(slots: ZonedDateTimeSlots, otherArg: ZonedDateTimeArg, options?: DiffOptions): Duration { + return createDuration( + createDurationSlots( + diffZonedDateTimes( + createDiffOps, + createTimeZoneOps, + slots, + toZonedDateTimeSlots(otherArg), + options, + ), + ) + ) + }, + since(slots: ZonedDateTimeSlots, otherArg: ZonedDateTimeArg, options?: DiffOptions): Duration { + return createDuration( + createDurationSlots( + diffZonedDateTimes( + createDiffOps, + createTimeZoneOps, + slots, + toZonedDateTimeSlots(otherArg), + options, + true, + ), + ) + ) + }, + round(slots: ZonedDateTimeSlots, options: RoundingOptions | UnitName): ZonedDateTime { + return createZonedDateTime( + roundZonedDateTime(createTimeZoneOps, slots, options) + ) + }, + startOfDay(slots: ZonedDateTimeSlots): ZonedDateTime { + return createZonedDateTime( + computeStartOfDay(createTimeZoneOps, slots) + ) + }, + equals(slots: ZonedDateTimeSlots, otherArg: ZonedDateTimeArg): boolean { + return zonedDateTimesEqual(slots, toZonedDateTimeSlots(otherArg)) + }, + toString(slots: ZonedDateTimeSlots, options?: ZonedDateTimeDisplayOptions): string { + return formatZonedDateTimeIso(createSimpleTimeZoneOps, slots, options) + }, + toJSON(slots: ZonedDateTimeSlots): string { + return formatZonedDateTimeIso(createSimpleTimeZoneOps, slots) + }, + toLocaleString(slots: ZonedDateTimeSlots, locales: LocalesArg, options: Intl.DateTimeFormatOptions = {}): string { + const [format, epochMilli] = prepZonedDateTimeFormat(locales, options, slots) + return format.format(epochMilli) + }, + toInstant(slots: ZonedDateTimeSlots): Instant { + return createInstant( + zonedDateTimeToInstant(slots) + ) + }, + toPlainDate(slots: ZonedDateTimeSlots): PlainDate { + return createPlainDate( + zonedDateTimeToPlainDate(createSimpleTimeZoneOps, slots) + ) + }, + toPlainTime(slots: ZonedDateTimeSlots): PlainTime { + return createPlainTime( + zonedDateTimeToPlainTime(createSimpleTimeZoneOps, slots) + ) + }, + toPlainDateTime(slots: ZonedDateTimeSlots): PlainDateTime { + return createPlainDateTime( + zonedDateTimeToPlainDateTime(createSimpleTimeZoneOps, slots) + ) + }, + toPlainYearMonth(slots: ZonedDateTimeSlots): PlainYearMonth { + return createPlainYearMonth( + zonedDateTimeToPlainYearMonth(createYearMonthRefineOps, slots, this) + ) + }, + toPlainMonthDay(slots: ZonedDateTimeSlots): PlainMonthDay { + return createPlainMonthDay( + zonedDateTimeToPlainMonthDay(createMonthDayRefineOps, slots, this) + ) + }, + getISOFields(slots: ZonedDateTimeSlots): ZonedIsoDateTimeSlots { + return getZonedIsoDateTimeSlots(createSimpleTimeZoneOps, slots) + }, + getCalendar: getCalendarFromSlots, + getTimeZone({ timeZone }: ZonedDateTimeSlots): TimeZoneProtocol { + return typeof timeZone === 'string' + ? new TimeZone(timeZone) + : timeZone + }, + valueOf: neverValueOf, + }, + { + from(arg: any, options?: ZonedFieldOptions) { + return createZonedDateTime(toZonedDateTimeSlots(arg, options)) + }, + compare(arg0: ZonedDateTimeArg, arg1: ZonedDateTimeArg): NumSign { + return compareZonedDateTimes( + toZonedDateTimeSlots(arg0), + toZonedDateTimeSlots(arg1), + ) + } + } +) + +function adaptToIsoFields(methods: any) { + return mapProps( + (method: any) => { + return (slots: ZonedDateTimeSlots) => { + return method(slotsToIsoFields(slots)) + } + }, + methods, + ) +} + +function slotsToIsoFields( + slots: ZonedDateTimeSlots +): IsoDateTimeFields & { offsetNanoseconds: number, calendar: CalendarSlot } { + const timeZoneNative = createSimpleTimeZoneOps(slots.timeZone) + return { + ...zonedInternalsToIso(slots, timeZoneNative), + calendar: slots.calendar, // TODO: have zonedInternalsToIso do this? + } +} + +// Utils +// ------------------------------------------------------------------------------------------------- + +export function toZonedDateTimeSlots(arg: ZonedDateTimeArg, options?: ZonedFieldOptions): ZonedDateTimeSlots { + options = copyOptions(options) + + if (isObjectLike(arg)) { + const slots = getSlots(arg) + + if (slots && slots.branding === ZonedDateTimeBranding) { + refineZonedFieldOptions(options) // parse unused options + return slots as ZonedDateTimeSlots + } + + const calendarSlot = getCalendarSlotFromBag(arg as any) + + return refineZonedDateTimeBag( + refineTimeZoneSlot, + createTimeZoneOps, + createDateRefineOps(calendarSlot), + calendarSlot, + arg as any, // !!! + options, + ) + } + + return parseZonedDateTime(arg, options) +} diff --git a/packages/temporal-polyfill/src/dateUtils/abstract.ts b/packages/temporal-polyfill/src/dateUtils/abstract.ts deleted file mode 100644 index c209ec0c..00000000 --- a/packages/temporal-polyfill/src/dateUtils/abstract.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { createWeakMap } from '../utils/obj' - -// weird to have this here -// to prevent circular reference -export const formatFactoryFactorySymbol = Symbol() - -// Functions - -export function ensureObj( - ObjClass: { - new(...constructorArgs: any[]): Obj - from(fromArg: ObjArg, ...otherArgs: OtherArgs): Obj - }, - arg: ObjArg, - ...otherArgs: OtherArgs -): Obj { - if (arg instanceof ObjClass) { - return arg - } - return ObjClass.from(arg, ...otherArgs) -} - -// Mixins -// use this technique instead of class inheritance because spec demands no intermediate prototypes - -export interface JsonMethods { - toJSON(): string -} - -export function mixinJsonMethods( - ObjClass: { prototype: Obj }, -): void { - ObjClass.prototype.toJSON = function(this: Obj) { - return this.toString() - } -} - -export interface NoValueMethods extends JsonMethods { - valueOf(): never -} - -export function mixinNoValueMethods( - ObjClass: { prototype: Obj }, -): void { - mixinJsonMethods(ObjClass) - - ObjClass.prototype.valueOf = function(this: Obj) { - throw new Error('Cannot convert object using valueOf') - } -} - -export interface IsoMasterMethods extends NoValueMethods { - getISOFields(): ISOFields -} - -const [getISOFields, setISOFields] = createWeakMap, any>() - -export function mixinIsoMasterMethods>( - ObjClass: { prototype: Obj }, -): void { - mixinNoValueMethods(ObjClass) - - ObjClass.prototype.getISOFields = function(this: Obj) { - return getISOFields(this) - } -} - -// must be called from constructor -export function initIsoMaster( - obj: IsoMasterMethods, - isoFields: ISOFields, -): void { - setISOFields(obj, Object.freeze(isoFields)) -} diff --git a/packages/temporal-polyfill/src/dateUtils/calendar.ts b/packages/temporal-polyfill/src/dateUtils/calendar.ts deleted file mode 100644 index 87551914..00000000 --- a/packages/temporal-polyfill/src/dateUtils/calendar.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { ensureCalendarsEqual } from '../argParse/calendar' -import { OVERFLOW_REJECT, parseOverflowOption } from '../argParse/overflowHandling' -import { CalendarImpl, convertEraYear } from '../calendarImpl/calendarImpl' -import { PlainDate, PlainDateArg, createDate } from '../public/plainDate' -import { PlainDateTime } from '../public/plainDateTime' -import { PlainMonthDay } from '../public/plainMonthDay' -import { PlainYearMonth } from '../public/plainYearMonth' -import { ZonedDateTime } from '../public/zonedDateTime' -import { constrainDateFields } from './constrain' -import { diffDaysMilli, epochMilliToISOFields } from './epoch' -import { ISODateFields } from './isoFields' -import { LocalDateFields } from './localFields' - -// Date-type Testing (TODO: move this?) - -export type DateLikeInstance = // has year/month/day - PlainDate | - PlainDateTime | - ZonedDateTime - -export type DateISOInstance = // has isoYear/isoMonth/isoDay - DateLikeInstance | - PlainYearMonth | - PlainMonthDay - -function isDateISOInstance(arg: unknown): arg is DateISOInstance { - return ( - arg instanceof PlainDate || - arg instanceof PlainDateTime || - arg instanceof ZonedDateTime || - arg instanceof PlainYearMonth || - arg instanceof PlainMonthDay - ) -} - -// Calendar-dependent Field Querying - -export function queryDateFields( - arg: DateISOInstance | PlainDateArg, - calendar: Temporal.CalendarProtocol, - disallowMonthDay?: boolean, -): LocalDateFields { - let date: PlainDate - - if (arg instanceof PlainDate) { - date = arg - } else if (isDateISOInstance(arg)) { - if (disallowMonthDay && arg instanceof PlainMonthDay) { - throw new TypeError('PlainMonthDay not allowed') - } - date = createDate(arg.getISOFields()) - } else { - date = PlainDate.from(arg) - } - - ensureCalendarsEqual(date.calendar, calendar) - return date -} - -// ISO Field Querying - -export function queryDateISOFields( - dateLike: DateISOInstance | Temporal.PlainDateLike, - calendarImpl: CalendarImpl, - options: Temporal.AssignmentOptions | undefined, -): ISODateFields { - if (isDateISOInstance(dateLike)) { - return dateLike.getISOFields() // hard work has already been done - } - - let { era, eraYear, year, month, monthCode, day } = dateLike - const yearFromEra = (eraYear !== undefined && era !== undefined) - ? convertEraYear(calendarImpl.id, eraYear, era) - : undefined - - if (year === undefined) { - if (yearFromEra !== undefined) { - year = yearFromEra - } else { - throw new TypeError('Must specify either a year or an era & eraYear') - } - } else { - if (yearFromEra !== undefined) { - if (yearFromEra !== year) { - throw new RangeError('year and era/eraYear must match') - } - } - } - - if (day === undefined) { - throw new TypeError('Must specify day') - } - - const overflow = parseOverflowOption(options) - - if (monthCode !== undefined) { - const [tryMonth, unusedLeap] = calendarImpl.convertMonthCode(monthCode, year) - - if (month !== undefined && month !== tryMonth) { - throw new RangeError('Month doesnt match with monthCode') - } - - month = tryMonth - - if (unusedLeap) { - if (overflow === OVERFLOW_REJECT) { - throw new RangeError('Month code out of range') - } - // constrain to last day of month - day = calendarImpl.daysInMonth(year, month) - } - } else if (month === undefined) { - throw new TypeError('Must specify either a month or monthCode') - } - - [year, month, day] = constrainDateFields( - year, month, day, - calendarImpl, - overflow, - ) - - return epochMilliToISOFields(calendarImpl.epochMilliseconds(year, month, day)) -} - -export function getExistingDateISOFields( - dateArg: DateISOInstance | PlainDateArg, - disallowMonthDay?: boolean, -): ISODateFields { - if (isDateISOInstance(dateArg)) { - if (disallowMonthDay && dateArg instanceof PlainMonthDay) { - throw new TypeError('PlainMonthDay not allowed') - } - return dateArg.getISOFields() - } else { - return PlainDate.from(dateArg).getISOFields() - } -} - -// Calendar-dependent Math - -export function computeDaysInYear(calendarImpl: CalendarImpl, year: number): number { - return diffDaysMilli( - calendarImpl.epochMilliseconds(year, 1, 1), - calendarImpl.epochMilliseconds(year + 1, 1, 1), - ) -} - -export function computeDayOfYear( - calendarImpl: CalendarImpl, - year: number, - month: number, - day: number, -): number { - return diffDaysMilli( - calendarImpl.epochMilliseconds(year, 1, 1), - calendarImpl.epochMilliseconds(year, month, day), - ) + 1 // 1-based -} diff --git a/packages/temporal-polyfill/src/dateUtils/compare.ts b/packages/temporal-polyfill/src/dateUtils/compare.ts deleted file mode 100644 index e25b34df..00000000 --- a/packages/temporal-polyfill/src/dateUtils/compare.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { LargeInt, compareLargeInts } from '../utils/largeInt' -import { compareValues } from '../utils/math' -import { durationDayTimeToNano, isoTimeToNano } from './dayAndTime' -import { DiffableObj } from './diff' -import { DurationFields, computeLargestDurationUnit } from './durationFields' -import { EpochableFields, epochNanoSymbol, isoFieldsToEpochNano } from './epoch' -import { ISOTimeFields } from './isoFields' -import { LocalDateFields } from './localFields' -import { DAY } from './units' - -export interface ComparableDateTime { - getISOFields(): EpochableFields -} - -export interface ComparableTime { - getISOFields(): ISOTimeFields -} - -export interface ComparableEpochObj { - [epochNanoSymbol]: LargeInt -} - -// Equality (considers Calendar & timeZone) -// ------------------------------------------------------------------------------------------------- - -type EqualityTestObj = ComparableDateTime & { calendar: Temporal.CalendarProtocol } -type ZonedEqualityTestObj = EqualityTestObj & { timeZone: Temporal.TimeZoneProtocol } - -export function zonedDateTimesEqual(a: ZonedEqualityTestObj, b: ZonedEqualityTestObj): boolean { - return dateTimesEqual(a, b) && - a.timeZone.toString() === b.timeZone.toString() -} - -export function dateTimesEqual(a: EqualityTestObj, b: EqualityTestObj): boolean { - return !compareDateTimes(a, b) && - a.calendar.toString() === b.calendar.toString() -} - -// Comparison -// ------------------------------------------------------------------------------------------------- - -export function compareDateTimes( - a: ComparableDateTime, - b: ComparableDateTime, -): Temporal.ComparisonResult { - return compareLargeInts( - isoFieldsToEpochNano(a.getISOFields()), - isoFieldsToEpochNano(b.getISOFields()), - ) -} - -export function compareTimes(t0: ComparableTime, t1: ComparableTime): Temporal.ComparisonResult { - return compareValues( - isoTimeToNano(t0.getISOFields()), - isoTimeToNano(t1.getISOFields()), - ) -} - -export function compareLocalDateFields( - d0: LocalDateFields, - d1: LocalDateFields, -): Temporal.ComparisonResult { - return compareValues(d0.year, d1.year) || - compareValues(d0.month, d1.month) || - compareValues(d0.day, d1.day) -} - -export function compareEpochObjs( - a: ComparableEpochObj, - b: ComparableEpochObj, -): Temporal.ComparisonResult { - return compareLargeInts( - a[epochNanoSymbol], - b[epochNanoSymbol], - ) -} - -export function compareDurations( - fields0: DurationFields, - fields1: DurationFields, - relativeTo: DiffableObj | undefined, -): Temporal.ComparisonResult { - if ( - relativeTo === undefined && - computeLargestDurationUnit(fields0) <= DAY && - computeLargestDurationUnit(fields1) <= DAY - ) { - return compareLargeInts( - durationDayTimeToNano(fields0), - durationDayTimeToNano(fields1), - ) - } - - if (!relativeTo) { - throw new RangeError('Need relativeTo') - } - - const date0 = relativeTo.add(fields0) - const date1 = relativeTo.add(fields1) - - if (relativeTo[epochNanoSymbol] !== undefined) { - return compareEpochObjs(date0 as ComparableEpochObj, date1 as ComparableEpochObj) - } - - return compareDateTimes(date0 as ComparableDateTime, date1 as ComparableDateTime) -} diff --git a/packages/temporal-polyfill/src/dateUtils/constrain.ts b/packages/temporal-polyfill/src/dateUtils/constrain.ts deleted file mode 100644 index 06a31ea6..00000000 --- a/packages/temporal-polyfill/src/dateUtils/constrain.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { OVERFLOW_REJECT, OverflowHandlingInt } from '../argParse/overflowHandling' -import { constrainInt } from '../argParse/refine' -import { CalendarImpl } from '../calendarImpl/calendarImpl' -import { isoCalendarImpl } from '../calendarImpl/isoCalendarImpl' -import { ISODateFields, ISODateTimeFields, ISOTimeFields } from './isoFields' - -export function constrainDateFields( - year: number, - month: number, - day: number, - calendarImpl: CalendarImpl, - overflow: OverflowHandlingInt, -): [number, number, number] { - year = Number(year) // not using constrainValue, which converts to a number - month = constrainInt(month, 1, calendarImpl.monthsInYear(year), overflow) - day = constrainInt(day, 1, calendarImpl.daysInMonth(year, month), overflow) - - return [year, month, day] -} - -export function constrainDateISO( // also ensures numbers - isoFields: ISODateFields, - overflow: OverflowHandlingInt, -): ISODateFields { - const [isoYear, isoMonth, isoDay] = constrainDateFields( - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - isoCalendarImpl, - overflow, - ) - return { isoYear, isoMonth, isoDay } -} - -export function isValidDateISO(isoFields: ISODateFields): boolean { - // HACK - try { - constrainDateISO(isoFields, OVERFLOW_REJECT) - return true - } catch (ex) { - return false - } -} - -export function constrainDateTimeISO( // also ensures numbers - isoFields: ISODateTimeFields, - overflow: OverflowHandlingInt, -): ISODateTimeFields { - return { - ...constrainDateISO(isoFields, overflow), - ...constrainTimeISO(isoFields, overflow), - } -} - -export function constrainTimeISO( // also converts to number - { - isoHour, isoMinute, isoSecond, - isoMillisecond, isoMicrosecond, isoNanosecond, - }: ISOTimeFields, - overflow: OverflowHandlingInt, -): ISOTimeFields { - isoHour = constrainInt(isoHour, 0, 23, overflow) - isoMinute = constrainInt(isoMinute, 0, 59, overflow) - isoSecond = constrainInt(isoSecond, 0, 59, overflow) - isoMillisecond = constrainInt(isoMillisecond, 0, 999, overflow) - isoMicrosecond = constrainInt(isoMicrosecond, 0, 999, overflow) - isoNanosecond = constrainInt(isoNanosecond, 0, 999, overflow) - return { isoHour, isoMinute, isoSecond, isoMillisecond, isoMicrosecond, isoNanosecond } -} diff --git a/packages/temporal-polyfill/src/dateUtils/dayAndTime.ts b/packages/temporal-polyfill/src/dateUtils/dayAndTime.ts deleted file mode 100644 index d1f02cad..00000000 --- a/packages/temporal-polyfill/src/dateUtils/dayAndTime.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { LargeInt, createLargeInt } from '../utils/largeInt' -import { DurationFields, DurationTimeFields, signDuration } from './durationFields' -import { ISOTimeFields } from './isoFields' -import { LocalTimeFields } from './localFields' -import { - DAY, - DayTimeUnitInt, - HOUR, - MICROSECOND, - MILLISECOND, - MINUTE, - SECOND, - nanoInDay, - nanoInHour, - nanoInMicro, - nanoInMilli, - nanoInMinute, - nanoInSecond, -} from './units' - -// weird place for this -export type DayTimeUnit = 'day' | Temporal.TimeUnit - -export const zeroISOTimeFields: ISOTimeFields = { - isoHour: 0, - isoMinute: 0, - isoSecond: 0, - isoMillisecond: 0, - isoMicrosecond: 0, - isoNanosecond: 0, -} - -export const zeroDurationTimeFields: DurationTimeFields = { - hours: 0, - minutes: 0, - seconds: 0, - milliseconds: 0, - microseconds: 0, - nanoseconds: 0, -} - -// fields -> fields -// ------------------------------------------------------------------------------------------------- - -export function partialLocalTimeToISO(fields: Partial): ISOTimeFields { - return { - isoHour: fields.hour || 0, - isoMinute: fields.minute || 0, - isoSecond: fields.second || 0, - isoMillisecond: fields.millisecond || 0, - isoMicrosecond: fields.microsecond || 0, - isoNanosecond: fields.nanosecond || 0, - } -} - -// fields -> nano -// ------------------------------------------------------------------------------------------------- - -export function durationDayTimeToNano(fields: DurationFields): LargeInt { - return createLargeInt(nanoInDay) - .mult(fields.days) - .add(durationTimeToNano(fields)) -} - -// must return bigint because there could be huge hour value, -// which would cause the nanosecond's precision to max out. -export function durationTimeToNano(fields: DurationTimeFields): LargeInt { - return createLargeInt(fields.nanoseconds) - .add(createLargeInt(fields.microseconds).mult(nanoInMicro)) - .add(createLargeInt(fields.milliseconds).mult(nanoInMilli)) - .add(createLargeInt(fields.seconds).mult(nanoInSecond)) - .add(createLargeInt(fields.minutes).mult(nanoInMinute)) - .add(createLargeInt(fields.hours).mult(nanoInHour)) -} - -export function isoTimeToNano(fields: ISOTimeFields): number { - return fields.isoHour * nanoInHour + - fields.isoMinute * nanoInMinute + - fields.isoSecond * nanoInSecond + - fields.isoMillisecond * nanoInMilli + - fields.isoMicrosecond * nanoInMicro + - fields.isoNanosecond -} - -// nano -> fields -// ------------------------------------------------------------------------------------------------- - -export function nanoToDuration(nano: LargeInt, largestUnit: DayTimeUnitInt): DurationFields { - let days = 0 - let hours = 0 - let minutes = 0 - let seconds = 0 - let milliseconds = 0 - let microseconds = 0 - let temp: LargeInt - - switch (largestUnit) { - case DAY: - temp = nano.div(nanoInDay) - days = temp.toNumber() - nano = nano.sub(temp.mult(nanoInDay)) - // fall through - case HOUR: - temp = nano.div(nanoInHour) - hours = temp.toNumber() - nano = nano.sub(temp.mult(nanoInHour)) - // fall through - case MINUTE: - temp = nano.div(nanoInMinute) - minutes = temp.toNumber() - nano = nano.sub(temp.mult(nanoInMinute)) - // fall through - case SECOND: - temp = nano.div(nanoInSecond) - seconds = temp.toNumber() - nano = nano.sub(temp.mult(nanoInSecond)) - // fall through - case MILLISECOND: - temp = nano.div(nanoInMilli) - milliseconds = temp.toNumber() - nano = nano.sub(temp.mult(nanoInMilli)) - // fall through - case MICROSECOND: - temp = nano.div(nanoInMicro) - microseconds = temp.toNumber() - nano = nano.sub(temp.mult(nanoInMicro)) - } - - return signDuration({ - years: 0, - months: 0, - weeks: 0, - days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds: nano.toNumber(), - }) -} - -/* -Ensures the resulting ISOTimeFields are positive and within 24 hours. -Will adjust dayDelta if necessary. -*/ -export function nanoToISOTime(timeNano: number): [ISOTimeFields, number] { - const dayDelta = Math.floor(timeNano / nanoInDay) - timeNano -= dayDelta * nanoInDay // ensures timeNano becomes positive - - const isoHour = Math.floor(timeNano / nanoInHour) - timeNano -= isoHour * nanoInHour - - const isoMinute = Math.floor(timeNano / nanoInMinute) - timeNano -= isoMinute * nanoInMinute - - const isoSecond = Math.floor(timeNano / nanoInSecond) - timeNano -= isoSecond * nanoInSecond - - const isoMillisecond = Math.floor(timeNano / nanoInMilli) - timeNano -= isoMillisecond * nanoInMilli - - const isoMicrosecond = Math.floor(timeNano / nanoInMicro) - timeNano -= isoMicrosecond * nanoInMicro - - const isoTimeFields: ISOTimeFields = { - isoHour, - isoMinute, - isoSecond, - isoMillisecond, - isoMicrosecond, - isoNanosecond: timeNano, - } - - return [isoTimeFields, dayDelta] -} diff --git a/packages/temporal-polyfill/src/dateUtils/diff.ts b/packages/temporal-polyfill/src/dateUtils/diff.ts deleted file mode 100644 index d070c682..00000000 --- a/packages/temporal-polyfill/src/dateUtils/diff.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { DiffConfig } from '../argParse/diffOptions' -import { OVERFLOW_CONSTRAIN } from '../argParse/overflowHandling' -import { unitNames } from '../argParse/unitStr' -import { CalendarImpl } from '../calendarImpl/calendarImpl' -import { createDate } from '../public/plainDate' -import { LargeInt, createLargeInt } from '../utils/largeInt' -import { compareValues, roundToIncrement, roundToIncrementBI } from '../utils/math' -import { compareLocalDateFields } from './compare' -import { constrainDateFields } from './constrain' -import { isoTimeToNano, nanoToDuration } from './dayAndTime' -import { DurationFields, mergeDurations, signDuration } from './durationFields' -import { EpochableObj, diffDaysMilli, toEpochNano } from './epoch' -import { ISOTimeFields } from './isoFields' -import { LocalDateFields } from './localFields' -import { roundDurationSpan } from './roundingDuration' -import { addMonths, addYears } from './translate' -import { - HOUR, - MONTH, - TimeUnitInt, - UnitInt, - WEEK, - YEAR, - isDateUnit, - nanoIn, -} from './units' - -export type DiffableObj = LocalDateFields & EpochableObj & { - add(durationFields: Partial): DiffableObj -} - -// used for zoned date times as well -export function diffDateTimes( - dt0: DiffableObj, - dt1: DiffableObj, - calendar: Temporal.CalendarProtocol, - flip: boolean, - diffConfig: DiffConfig, -): DurationFields { - return roundDurationSpan( - diffAccurate(dt0, dt1, calendar, diffConfig.largestUnit), - dt0, - dt1, - calendar, - flip, - diffConfig, - ) -} - -export function diffDates( - d0: DiffableObj, - d1: DiffableObj, - calendar: Temporal.CalendarProtocol, - flip: boolean, - diffConfig: DiffConfig, -): DurationFields { - const balancedDuration = calendar.dateUntil(d0, d1, { - largestUnit: unitNames[diffConfig.largestUnit] as Temporal.DateUnit, - }) - return roundDurationSpan(balancedDuration, d0, d1, calendar, flip, diffConfig) -} - -export function diffTimes( - t0: ISOTimeFields, - t1: ISOTimeFields, - diffConfig: DiffConfig, -): DurationFields { - const roundedDiff = roundToIncrement( - isoTimeToNano(t1) - isoTimeToNano(t0), - nanoIn[diffConfig.smallestUnit] * diffConfig.roundingIncrement, - diffConfig.roundingFunc, - ) - return nanoToDuration(createLargeInt(roundedDiff), diffConfig.largestUnit) -} - -export function diffEpochNanos( - epochNano0: LargeInt, - epochNano1: LargeInt, - diffConfig: DiffConfig, -): DurationFields { - const roundedDiff = roundToIncrementBI( - epochNano1.sub(epochNano0), - nanoIn[diffConfig.smallestUnit] * diffConfig.roundingIncrement, - diffConfig.roundingFunc, - ) - return nanoToDuration(roundedDiff, diffConfig.largestUnit) -} - -// Utils -// ------------------------------------------------------------------------------------------------- - -export function diffDateFields( - d0: LocalDateFields, - d1: LocalDateFields, - calendarImpl: CalendarImpl, - largestUnit: UnitInt, -): DurationFields { - let years = 0; let months = 0; let weeks = 0; let days = 0 - - switch (largestUnit) { - case YEAR: - years = wholeYearsUntil(d0, d1, calendarImpl) - d0 = addYears(d0, years, calendarImpl, OVERFLOW_CONSTRAIN) - // fallthrough - case MONTH: - months = wholeMonthsUntil(d0, d1, calendarImpl) - d0 = addMonths(d0, months, calendarImpl, OVERFLOW_CONSTRAIN) - } - - days = diffDaysMilli( - calendarImpl.epochMilliseconds(d0.year, d0.month, d0.day), - calendarImpl.epochMilliseconds(d1.year, d1.month, d1.day), - ) - - if (largestUnit === WEEK) { - weeks = Math.trunc(days / 7) - days %= 7 - } - - return signDuration({ - years, - months, - weeks, - days, - hours: 0, - minutes: 0, - seconds: 0, - milliseconds: 0, - microseconds: 0, - nanoseconds: 0, - }) -} - -function wholeYearsUntil( - d0: LocalDateFields, - d1: LocalDateFields, - calendarImpl: CalendarImpl, -): number { - // simulate destination year - const [, newMonth, newDay] = constrainDateFields( - d1.year, - d0.month, - d0.day, - calendarImpl, - OVERFLOW_CONSTRAIN, - ) - - const generalSign = compareLocalDateFields(d1, d0) - const monthSign = compareValues(d1.month, newMonth) || compareValues(d1.day, newDay) - - return d1.year - d0.year - ( - (monthSign && generalSign && monthSign !== generalSign) - ? generalSign - : 0 - ) -} - -function wholeMonthsUntil( - d0: LocalDateFields, - d1: LocalDateFields, - calendarImpl: CalendarImpl, -): number { - let monthsToAdd = 0 - const generalSign = compareLocalDateFields(d1, d0) - - if (generalSign) { - // move ahead by whole years - let { year } = d0 - while (year !== d1.year) { - monthsToAdd += calendarImpl.monthsInYear(year) * generalSign - year += generalSign - } - - // simulate destination year (same as wholeYearsUntil... optimization opportunity?) - const [, newMonth, newDay] = constrainDateFields( - d1.year, - d0.month, - d0.day, - calendarImpl, - OVERFLOW_CONSTRAIN, - ) - - // add remaining months (or subtract overshot months) - monthsToAdd += d1.month - newMonth - - // correct when we overshoot the day-of-month - const daySign = compareValues(d1.day, newDay) - if (daySign && generalSign && daySign !== generalSign) { - monthsToAdd -= generalSign - } - } - - return monthsToAdd -} - -export function diffAccurate( - dt0: DiffableObj, - dt1: DiffableObj, - calendar: Temporal.CalendarProtocol, - largestUnit: UnitInt, -): DurationFields { - // a time unit - if (!isDateUnit(largestUnit)) { - return diffTimeScale(dt0, dt1, largestUnit) - } - - const dateStart = createDate({ ...dt0.getISOFields(), calendar }) - let dateMiddle = createDate({ ...dt1.getISOFields(), calendar }) - let dateTimeMiddle: DiffableObj - let bigDuration: DurationFields - let timeDuration: DurationFields - let bigSign: Temporal.ComparisonResult - let timeSign: Temporal.ComparisonResult - - do { - bigDuration = calendar.dateUntil( - dateStart, - dateMiddle, - { largestUnit: unitNames[largestUnit] as Temporal.DateUnit }, - ) - dateTimeMiddle = dt0.add(bigDuration) - timeDuration = diffTimeScale(dateTimeMiddle, dt1, HOUR) - bigSign = bigDuration.sign - timeSign = timeDuration.sign - } while ( - // did we overshoot? keep backing up a day - bigSign && timeSign && - bigSign !== timeSign && - (dateMiddle = dateMiddle.add({ days: timeSign })) // move dateMiddle closer to dt0 - ) - - return mergeDurations(bigDuration, timeDuration) -} - -function diffTimeScale( - dt0: EpochableObj, - dt1: EpochableObj, - largestUnit: TimeUnitInt, -): DurationFields { - return nanoToDuration(toEpochNano(dt1).sub(toEpochNano(dt0)), largestUnit) -} diff --git a/packages/temporal-polyfill/src/dateUtils/durationFields.ts b/packages/temporal-polyfill/src/dateUtils/durationFields.ts deleted file mode 100644 index 4a675bab..00000000 --- a/packages/temporal-polyfill/src/dateUtils/durationFields.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { durationUnitNames } from '../argParse/unitStr' -import { numSign } from '../utils/math' -import { mapHashByKeys } from '../utils/obj' -import { NANOSECOND, UnitInt, YEAR } from './units' - -// duration -// special note about not doing spreads -// NO.... just use the internal props instead! - -export interface DurationTimeFields { - hours: number - minutes: number - seconds: number - milliseconds: number - microseconds: number - nanoseconds: number -} - -export interface UnsignedDurationFields extends DurationTimeFields { - years: number - months: number - weeks: number - days: number -} - -// prefer this over unsigned -export interface DurationFields extends UnsignedDurationFields { - sign: Temporal.ComparisonResult -} - -// special note about not doing spreads - -const durationFieldNames: (keyof DurationFields)[] = - (durationUnitNames as (keyof DurationFields)[]).concat('sign') - -type NumberFields = { [prop: string]: number } - -export function negateDuration(fields: DurationFields): DurationFields { - return mapHashByKeys( - fields as unknown as NumberFields, - durationFieldNames, - (n: number) => -n || 0, // prevent -0 // TODO: make general util - ) as unknown as DurationFields -} - -export function absDuration(fields: DurationFields): DurationFields { - return mapHashByKeys( - fields as unknown as NumberFields, - durationFieldNames, - (n: number) => Math.abs(n), // TODO: make general util? Just pass-in Math.abs? - ) as unknown as DurationFields -} - -// TODO: rename. confusing with 'override' -export function mergeDurations(d0: DurationFields, d1: DurationFields): DurationFields { - return { - sign: d0.sign || d1.sign, - years: d0.years + d1.years, - months: d0.months + d1.months, - weeks: d0.weeks + d1.weeks, - days: d0.days + d1.days, - hours: d0.hours + d1.hours, - minutes: d0.minutes + d1.minutes, - seconds: d0.seconds + d1.seconds, - milliseconds: d0.milliseconds + d1.milliseconds, - microseconds: d0.microseconds + d1.microseconds, - nanoseconds: d0.nanoseconds + d1.nanoseconds, - } -} - -// workaround for not being able to do spreads -// TODO: use refineDuration somehow? -export function overrideDuration(d0: DurationFields, d1: Partial): DurationFields { - return signDuration({ - years: d1.years ?? d0.years, - months: d1.months ?? d0.months, - weeks: d1.weeks ?? d0.weeks, - days: d1.days ?? d0.days, - hours: d1.hours ?? d0.hours, - minutes: d1.minutes ?? d0.minutes, - seconds: d1.seconds ?? d0.seconds, - milliseconds: d1.milliseconds ?? d0.milliseconds, - microseconds: d1.microseconds ?? d0.microseconds, - nanoseconds: d1.nanoseconds ?? d0.nanoseconds, - }) -} - -export function refineDurationNumbers(unsignedFields: UnsignedDurationFields): DurationFields { - const fields = signDuration(unsignedFields) - const { sign } = fields - - for (const fieldName of durationUnitNames) { - const fieldNum = fields[fieldName as keyof UnsignedDurationFields] - const fieldSign = numSign(fields[fieldName as keyof UnsignedDurationFields]!) - - if (fieldSign && fieldSign !== sign) { - throw new RangeError('All fields must be same sign') - } - - if (!Number.isInteger(fieldNum)) { - throw new RangeError('Duration fields must be integers') - } - } - - return fields -} - -export function signDuration(fields: UnsignedDurationFields): DurationFields { - return { ...fields, sign: computeDurationSign(fields) } -} - -function computeDurationSign(fields: UnsignedDurationFields): Temporal.ComparisonResult { - let sign: Temporal.ComparisonResult = 0 - - for (const fieldName of durationUnitNames) { - const fieldNum = fields[fieldName as keyof UnsignedDurationFields] - - if (fieldNum) { - sign = numSign(fields[fieldName]) - break - } - } - - return sign -} - -export function computeLargestDurationUnit(dur: DurationFields): UnitInt { - let unit: UnitInt = YEAR - - while ( - unit > NANOSECOND && - !dur[durationUnitNames[unit]] - ) { - unit-- - } - - return (unit as UnitInt) // wtf -} diff --git a/packages/temporal-polyfill/src/dateUtils/durationSpan.ts b/packages/temporal-polyfill/src/dateUtils/durationSpan.ts deleted file mode 100644 index abff9cae..00000000 --- a/packages/temporal-polyfill/src/dateUtils/durationSpan.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { unitNames } from '../argParse/unitStr' -import { PlainDate } from '../public/plainDate' -import { DiffableObj, diffAccurate } from './diff' -import { DurationFields, overrideDuration } from './durationFields' -import { DAY, DateUnitInt, UnitInt, WEEK } from './units' - -export function spanDurationFrom( - duration: DurationFields, - largestUnit: UnitInt, - relativeTo: DiffableObj, - calendar: Temporal.CalendarProtocol, -): DurationFields { - return ( - relativeTo instanceof PlainDate - ? spanDurationFromDate( - duration, - Math.max(DAY, largestUnit) as DateUnitInt, - relativeTo, - calendar, - ) - : spanDurationFromDateTime(duration, largestUnit, relativeTo, calendar) - )[0] -} - -// does not need to worry about time fields at all! or dst! -// only worries about date fields. can rely completely on calendar -export function spanDurationFromDate( - duration: DurationFields, - largestUnit: DateUnitInt, - relativeTo: DiffableObj, - calendar: Temporal.CalendarProtocol, -): [DurationFields, DiffableObj] { - const translated = relativeTo.add(duration) - const newDuration = calendar.dateUntil(relativeTo, translated, { - largestUnit: unitNames[largestUnit] as Temporal.DateUnit, - }) - - return [newDuration, translated] -} - -export function spanDurationFromDateTime( - fields: DurationFields, - largestUnit: UnitInt, - relativeTo: DiffableObj, - calendar: Temporal.CalendarProtocol, - dissolveWeeks?: boolean, -): [DurationFields, DiffableObj] { - // balancing does not care about weeks - // TODO: make this more readable. responsibility of caller? - const forcedWeeks = dissolveWeeks !== true && largestUnit > WEEK && fields.weeks - if (forcedWeeks) { - fields = overrideDuration(fields, { weeks: 0 }) - } - - let translated = relativeTo.add(fields) - - // ***uses calendar.dateUntil under the hood - let balancedDuration = diffAccurate(relativeTo, translated, calendar, largestUnit) - - // add weeks back in - if (forcedWeeks) { - balancedDuration = overrideDuration(balancedDuration, { weeks: forcedWeeks }) - translated = translated.add({ weeks: forcedWeeks }) - } - - return [balancedDuration, translated] -} diff --git a/packages/temporal-polyfill/src/dateUtils/epoch.ts b/packages/temporal-polyfill/src/dateUtils/epoch.ts deleted file mode 100644 index 1323cf03..00000000 --- a/packages/temporal-polyfill/src/dateUtils/epoch.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { isoCalendarImpl } from '../calendarImpl/isoCalendarImpl' -import { LargeInt, createLargeInt } from '../utils/largeInt' -import { numSign, positiveModulo } from '../utils/math' -import { isoTimeToNano } from './dayAndTime' -import { - ISODateFields, - ISODateTimeFields, - ISODateTimeFieldsMilli, - ISOTimeFields, -} from './isoFields' -import { - milliInDay, - milliInSecond, nanoInMicro, - nanoInMilli, -} from './units' - -export const isoEpochOriginYear = 1970 -export const isoEpochLeapYear = 1972 // first ISO leap year after origin - -export type EpochableFields = ISODateFields & Partial - -export const epochNanoSymbol = Symbol() - -export interface EpochableObj { - [epochNanoSymbol]?: LargeInt - getISOFields(): EpochableFields -} - -/* -GENERAL ROUNDING TIPS: -- use trunc on timeZoneOffsets and durations (directionally outward from 0 origin) - - for this, trunc and % go well together -- use floor on epoch-times, time-of-days, week numbers (directionally forward only) - - for this, floor and positiveModulo go well together -*/ -// ISO Field <-> Epoch Math - -export function isoFieldsToEpochNano(isoFields: EpochableFields): LargeInt { - return isoToEpochNano( - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - isoFields.isoHour, - isoFields.isoMinute, - isoFields.isoSecond, - isoFields.isoMillisecond, - isoFields.isoMicrosecond, - isoFields.isoNanosecond, - ) -} - -export function isoFieldsToEpochMilli(isoFields: EpochableFields): number { - return isoToEpochMilli( - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - isoFields.isoHour, - isoFields.isoMinute, - isoFields.isoSecond, - isoFields.isoMillisecond, - ) -} - -export function isoToEpochNano( - isoYear: number, - isoMonth: number, - isoDay: number, - isoHour?: number, - isoMinute?: number, - isoSecond?: number, - isoMillisecond?: number, - isoMicrosecond?: number, - isoNanosecond?: number, -): LargeInt { - return createLargeInt( - isoToEpochMilli( - isoYear, - isoMonth, - isoDay, - isoHour, - isoMinute, - isoSecond, - isoMillisecond, - ), - ).mult(nanoInMilli).add( - (isoMicrosecond ?? 0) * nanoInMicro + - (isoNanosecond ?? 0), - ) -} - -export function isoToEpochMilli( - isoYear: number, - isoMonth: number, - isoDay: number, - isoHour?: number, - isoMinute?: number, - isoSecond?: number, - isoMillisecond?: number, -): number { - const sign = numSign(isoYear) - let dayShiftAbs = 0 - let isoDayTry: number - let milli: number | undefined - - // https://stackoverflow.com/a/5870822/96342 - const twoDigitYearBug = isoYear >= 0 && isoYear < 1000 - const isoYearTemp = twoDigitYearBug ? isoYear + 1200 : isoYear - - // Temporal must represent year-month-days and year-months that don't have their start-of-unit - // in bounds. Keep moving the date towards the origin one day at a time until in-bounds. - // We won't need to shift more than a month. - for (; dayShiftAbs < 31; dayShiftAbs++) { - isoDayTry = isoDay - (sign * dayShiftAbs) - - const milliTry = Date.UTC( - isoYearTemp, - isoMonth - 1, - isoDayTry, - isoHour ?? 0, - isoMinute ?? 0, - isoSecond ?? 0, - isoMillisecond ?? 0, - ) - // is valid? (TODO: rename isInvalid -> isValid) - if (!isInvalid(milliTry)) { - milli = milliTry + (sign * dayShiftAbs * milliInDay) - break - } - } - - if ( - milli === undefined || - // ensure day didn't underflow/overflow to get to an in-bounds date - isoDayTry! < 1 || - isoDayTry! > isoCalendarImpl.daysInMonth(isoYear, isoMonth) - ) { - throwOutOfRange() - } - - if (twoDigitYearBug) { - milli = new Date(milli!).setUTCFullYear(isoYear) - } - - return milli! -} - -export function epochNanoToISOFields(epochNano: LargeInt): ISODateTimeFields { - let epochMilli = epochNano.div(nanoInMilli) - let leftoverNano = epochNano.sub(epochMilli.mult(nanoInMilli)).toNumber() - - // ensure leftoverNano. bump to millisecond below if necessary - if (leftoverNano < 0) { - leftoverNano += nanoInMilli - epochMilli = epochMilli.sub(1) - } - - const isoMicrosecond = Math.floor(leftoverNano / nanoInMicro) - leftoverNano -= isoMicrosecond * nanoInMicro - - return { - ...epochMilliToISOFields(epochMilli.toNumber()), - isoMicrosecond, - isoNanosecond: leftoverNano, - } -} - -export function epochMilliToISOFields(epochMilli: number): ISODateTimeFieldsMilli { - const [legacy, dayUnshift] = nudgeToLegacyDate(epochMilli) - return { - isoYear: legacy.getUTCFullYear(), - isoMonth: legacy.getUTCMonth() + 1, - isoDay: legacy.getUTCDate() + dayUnshift, - isoHour: legacy.getUTCHours(), - isoMinute: legacy.getUTCMinutes(), - isoSecond: legacy.getUTCSeconds(), - isoMillisecond: legacy.getUTCMilliseconds(), - } -} - -// High-level conversions - -export function toEpochNano(dt: EpochableObj): LargeInt { - return dt[epochNanoSymbol] ?? isoFieldsToEpochNano(dt.getISOFields()) -} - -// Misc conversions - -export function isoYearToEpochSeconds(isoYear: number): number { - return Math.floor(isoToEpochMilli(isoYear, 1, 1) / milliInSecond) -} - -export function epochNanoToISOYear(epochNano: LargeInt): number { - return nudgeToLegacyDate( - epochNano.div(nanoInMilli).toNumber(), - )[0].getUTCFullYear() -} - -// Day-of-Week (move?) - -export function computeISODayOfWeek(isoYear: number, isoMonth: number, isoDay: number): number { - const [legacy, dayUnshift] = nudgeToLegacyDate(isoToEpochMilli(isoYear, isoMonth, isoDay)) - return positiveModulo( - legacy.getUTCDay() + dayUnshift, - 7, - ) || 7 // convert Sun...Mon to Mon...Sun -} - -// Utils - -function nudgeToLegacyDate(epochMilli: number): [Date, number] { - const sign = numSign(epochMilli) - let dayShiftAbs = 0 - let date: Date | undefined - - // undo the dayShift done in isoToEpochMilli - // won't need to move more than a month (max month days is 31, so 30) - for (; dayShiftAbs < 31; dayShiftAbs++) { - const dateTry = new Date(epochMilli - (sign * dayShiftAbs * milliInDay)) - - if (!isInvalid(dateTry)) { - date = dateTry - break - } - } - - if (date === undefined) { - throwOutOfRange() - } - - return [date!, sign * dayShiftAbs] -} - -function isInvalid(n: { valueOf(): number; }): boolean { - return isNaN(n.valueOf()) -} - -export function throwOutOfRange(): void { - throw new RangeError('Date outside of supported range') -} - -// Epoch-Millisecond Math -// (move to diff file?) - -export function diffDaysMilli(epochMilli0: number, epochMilli1: number): number { - return Math.round((epochMilli1 - epochMilli0) / milliInDay) -} - -export function addDaysMilli(epochMilli: number, days: number): number { - return epochMilli + days * milliInDay -} - -export function splitEpochNano(epochNano: LargeInt): [LargeInt, number] { - const isoFields = epochNanoToISOFields(epochNano) - const dayEpochNano = isoToEpochNano(isoFields.isoYear, isoFields.isoMonth, isoFields.isoDay) - const timeNano = isoTimeToNano(isoFields) - return [dayEpochNano, timeNano] -} diff --git a/packages/temporal-polyfill/src/dateUtils/fromAndWith.ts b/packages/temporal-polyfill/src/dateUtils/fromAndWith.ts deleted file mode 100644 index d1b61515..00000000 --- a/packages/temporal-polyfill/src/dateUtils/fromAndWith.ts +++ /dev/null @@ -1,331 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { extractCalendar } from '../argParse/calendar' -import { - dateFieldMap, - durationFieldMap, - monthDayFieldMap, - timeFieldMap, - yearMonthFieldMap, -} from '../argParse/fieldStr' -import { OverflowHandlingInt } from '../argParse/overflowHandling' -import { isObjectLike, refineFields } from '../argParse/refine' -import { extractTimeZone } from '../argParse/timeZone' -import { Calendar, mergeCalFields } from '../public/calendar' -import { PlainDate } from '../public/plainDate' -import { PlainMonthDay } from '../public/plainMonthDay' -import { PlainYearMonth } from '../public/plainYearMonth' -import { ZonedDateTime } from '../public/zonedDateTime' -import { mapHash } from '../utils/obj' -import { constrainTimeISO } from './constrain' -import { partialLocalTimeToISO, zeroISOTimeFields } from './dayAndTime' -import { DurationFields } from './durationFields' -import { isoEpochLeapYear } from './epoch' -import { ISOTimeFields } from './isoFields' -import { LocalTimeFields } from './localFields' -import { OffsetComputableFields } from './offset' -import { parseOffsetNano } from './parse' - -export const processZonedDateTimeFromFields = buildSafeFunc(tryZonedDateTimeFromFields) -export const processDateTimeFromFields = buildSafeFunc(tryDateTimeFromFields) -export const processDateFromFields = buildSafeFunc(tryDateFromFields) -export const processYearMonthFromFields = buildSafeFunc(tryYearMonthFromFields) -export const processMonthDayFromFields = buildSafeFunc(tryMonthDayFromFields) -export const processTimeFromFields = buildSafeFunc(tryTimeFromFields) - -export const processZonedDateTimeWithFields = buildSafeFunc(tryZonedDateTimeWithFields, true) -export const processDateTimeWithFields = buildSafeFunc(tryDateTimeWithFields, true) -export const processDateWithFields = buildSafeFunc(tryDateWithFields, true) -export const processYearMonthWithFields = buildSafeFunc(tryYearMonthWithFields, true) -export const processMonthDayWithFields = buildSafeFunc(tryMonthDayWithFields, true) -export const processTimeWithFields = buildSafeFunc(tryTimeWithFields, true) - -export const processDurationFields = buildSafeFunc(tryDurationFields) - -// ::from (UNSAFE verions) - -function tryZonedDateTimeFromFields( - rawFields: any, - overflowHandling: OverflowHandlingInt, - options?: Temporal.AssignmentOptions, -): OffsetComputableFields | undefined { - const res = tryDateTimeFromFields(rawFields, overflowHandling, options) - - if (res) { - return { - ...res, - timeZone: extractTimeZone(rawFields), - offsetNanoseconds: rawFields.offset !== undefined - ? parseOffsetNano(String(rawFields.offset)) - : undefined, - } - } -} - -function tryDateTimeFromFields( - rawFields: Temporal.PlainDateLike, - overflowHandling: OverflowHandlingInt, - options?: Temporal.AssignmentOptions, -): Temporal.PlainDateTimeISOFields | undefined { - const dateRes = tryDateFromFields(rawFields, options) - const timeRes = tryTimeFromFields(rawFields, overflowHandling) - - if (dateRes) { - return { - ...dateRes.getISOFields(), - ...(timeRes || zeroISOTimeFields), - } - } -} - -function tryDateFromFields( - rawFields: Temporal.PlainDateLike, - options?: Temporal.AssignmentOptions, -): PlainDate | undefined { - const calendar = extractCalendar(rawFields) - const filteredFields = filterFieldsViaCalendar(rawFields, dateFieldMap, calendar) - - if (hasAnyProps(filteredFields)) { - return calendar.dateFromFields(filteredFields, options) - } -} - -function tryYearMonthFromFields( - rawFields: Temporal.PlainYearMonthLike, - options?: Temporal.AssignmentOptions, -): PlainYearMonth | undefined { - const calendar = extractCalendar(rawFields) - const filteredFields = filterFieldsViaCalendar(rawFields, yearMonthFieldMap, calendar) - - if (hasAnyProps(filteredFields)) { - return calendar.yearMonthFromFields(filteredFields, options) - } -} - -function tryMonthDayFromFields( - rawFields: any, - options?: Temporal.AssignmentOptions, -): PlainMonthDay | undefined { - const calendar = extractCalendar(rawFields) - const filteredFields = filterFieldsViaCalendar(rawFields, monthDayFieldMap, calendar) - - if (hasAnyProps(filteredFields)) { - if (rawFields.year === undefined && rawFields.calendar === undefined) { - filteredFields.year = isoEpochLeapYear - } - - return calendar.monthDayFromFields(filteredFields, options) - } -} - -function tryTimeFromFields( - rawFields: any, - overflowHandling: OverflowHandlingInt, -): ISOTimeFields | undefined { - const refinedFields = refineFields(rawFields, timeFieldMap) - - if (hasAnyProps(refinedFields)) { - return constrainTimeISO(partialLocalTimeToISO(refinedFields), overflowHandling) - } -} - -// ::with (UNSAFE versions) - -function tryZonedDateTimeWithFields( - zonedDateTime: ZonedDateTime, - rawFields: any, - overflowHandling: OverflowHandlingInt, - options?: Temporal.AssignmentOptions, -): OffsetComputableFields | undefined { - const res = tryDateTimeWithFields(zonedDateTime, rawFields, overflowHandling, options) - const hasNewOffset = rawFields.offset !== undefined - - if (res || hasNewOffset) { - return { - ...(res || zonedDateTime.getISOFields()), - timeZone: zonedDateTime.timeZone, - offsetNanoseconds: hasNewOffset - ? parseOffsetNano(String(rawFields.offset)) - : zonedDateTime.offsetNanoseconds, - } - } -} - -function tryDateTimeWithFields( - plainDateTime: any, - rawFields: any, - overflowHandling: OverflowHandlingInt, - options?: Temporal.AssignmentOptions, -): Temporal.PlainDateTimeISOFields | undefined { - const dateRes = tryDateWithFields(plainDateTime, rawFields, options) - const timeRes = tryTimeWithFields(plainDateTime, rawFields, overflowHandling) - - if (dateRes || timeRes) { - return { - ...plainDateTime.getISOFields(), - ...(dateRes ? dateRes.getISOFields() : {}), - ...timeRes, - } - } -} - -function tryDateWithFields( - plainDate: any, - rawFields: any, - options?: Temporal.AssignmentOptions, -): PlainDate | undefined { - const calendar: Calendar = plainDate.calendar - const filteredFields = filterFieldsViaCalendar(rawFields, dateFieldMap, calendar) - - if (hasAnyProps(filteredFields)) { - const mergedFields = mergeFieldsViaCalendar(plainDate, filteredFields, dateFieldMap, calendar) - return calendar.dateFromFields(mergedFields, options) - } -} - -function tryYearMonthWithFields( - plainYearMonth: any, - rawFields: any, - options?: Temporal.AssignmentOptions, -): PlainYearMonth | undefined { - const calendar: Calendar = plainYearMonth.calendar - const filteredFields = filterFieldsViaCalendar(rawFields, yearMonthFieldMap, calendar) - - if (hasAnyProps(filteredFields)) { - const mergedFields = mergeFieldsViaCalendar( - plainYearMonth, - rawFields, - yearMonthFieldMap, - calendar, - ) - return calendar.yearMonthFromFields(mergedFields, options) - } -} - -function tryMonthDayWithFields( - plainMonthDay: any, - rawFields: any, - options?: Temporal.AssignmentOptions, -): PlainMonthDay | undefined { - const calendar: Calendar = plainMonthDay.calendar - const filteredFields = filterFieldsViaCalendar(rawFields, monthDayFieldMap, calendar) - - if (hasAnyProps(filteredFields)) { - const mergedFields = mergeFieldsViaCalendar( - plainMonthDay, - rawFields, - monthDayFieldMap, - calendar, - ) - return calendar.monthDayFromFields(mergedFields, options) - } -} - -function tryTimeWithFields( - plainTime: any, - rawFields: any, - overflowHandling: OverflowHandlingInt, -): ISOTimeFields | undefined { - const refinedFields = refineFields(rawFields, timeFieldMap) - - if (hasAnyProps(refinedFields)) { - const mergedFields = mergeLocalTimeFields(plainTime, refinedFields) - return constrainTimeISO(partialLocalTimeToISO(mergedFields), overflowHandling) - } -} - -// duration (used for ::from and ::with) - -function tryDurationFields(rawFields: any): DurationFields | undefined { - const refinedFields = refineFields(rawFields, durationFieldMap) as any // !!! - - if (hasAnyProps(refinedFields)) { - return refinedFields - } -} - -// utils - -function filterFieldsViaCalendar( - objOrFields: any, - fieldMap: any, - calendar: Temporal.CalendarProtocol, -): any { - let fieldNames = Object.keys(fieldMap) - - if (calendar.fields) { - // convert Iterable to string[]... better way? - fieldNames = Array.prototype.slice.call(calendar.fields(fieldNames)) - } else { - // a Calendar 'protocol' - // filter by method names - fieldNames = Object.keys(filterFieldsViaWhitelist(calendar, fieldNames)) - } - - return filterFieldsViaWhitelist(objOrFields, fieldNames) -} - -function filterFieldsViaWhitelist(objOrFields: any, whitelist: string[]): any { - const filtered = {} as any - - for (const propName of whitelist) { - if (objOrFields[propName] !== undefined) { - filtered[propName] = objOrFields[propName] - } - } - - return filtered -} - -function mergeFieldsViaCalendar( - existingObj: any, - fields: any, - fieldMap: any, - calendar: Temporal.CalendarProtocol, -): any { - const existingFields = filterFieldsViaCalendar(existingObj, fieldMap, calendar) - - if (calendar.mergeFields) { - return calendar.mergeFields(existingFields, fields) - } - - return mergeCalFields(existingFields, fields) -} - -function mergeLocalTimeFields( - base: LocalTimeFields, - fields: Partial, -): LocalTimeFields { - return mapHash(timeFieldMap, (_refineFunc, fieldName) => ( - fields[fieldName as keyof LocalTimeFields] ?? base[fieldName as keyof LocalTimeFields] - )) -} - -// TODO: use chaining instead of flag -function buildSafeFunc( - func: (...args: Args) => Res | undefined, - isWith?: boolean, -): (...args: Args) => Res { - return (...args: Args) => { - if (isWith) { - const rawFields = args[1] - if (!isObjectLike(rawFields)) { - throw new TypeError('must be object-like') - } - if (rawFields.calendar !== undefined) { - throw new TypeError('calendar not allowed') - } - if (rawFields.timeZone !== undefined) { - throw new TypeError('timeZone not allowed') - } - } - const res = func(...args) - if (!res) { - throw new TypeError('No valid fields') - } - return res - } -} - -function hasAnyProps(fields: any): boolean { - return Object.keys(fields).length > 0 -} diff --git a/packages/temporal-polyfill/src/dateUtils/intlFormat.ts b/packages/temporal-polyfill/src/dateUtils/intlFormat.ts deleted file mode 100644 index dd1514cc..00000000 --- a/packages/temporal-polyfill/src/dateUtils/intlFormat.ts +++ /dev/null @@ -1,31 +0,0 @@ - -export type IntlFormatPartsMap = { [partType: string]: string } - -export function hashIntlFormatParts( - format: Intl.DateTimeFormat, - epochMillisecond: number, -): IntlFormatPartsMap { - const hash: IntlFormatPartsMap = {} - const parts = format.formatToParts(epochMillisecond) // TODO: use original methods - - for (const part of parts) { - hash[part.type] = part.value - } - - return hash -} - -const eraRemap: { [eraIn: string]: string } = { - bc: 'bce', - ad: 'ce', -} - -export function normalizeShortEra(formattedEra: string): string { - // Example 'Before R.O.C.' -> 'beforeroc' - formattedEra = formattedEra - .toLowerCase() - .normalize('NFD') // break apart accents, for 'Shōwa' -> 'Showa' - .replace(/[^a-z0-9]/g, '') - - return eraRemap[formattedEra] || formattedEra -} diff --git a/packages/temporal-polyfill/src/dateUtils/isoFieldValidation.ts b/packages/temporal-polyfill/src/dateUtils/isoFieldValidation.ts deleted file mode 100644 index 2783f285..00000000 --- a/packages/temporal-polyfill/src/dateUtils/isoFieldValidation.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { checkEpochNanoBuggy } from '../calendarImpl/bugs' -import { LargeInt, compareLargeInts, createLargeInt } from '../utils/largeInt' -import { isoFieldsToEpochNano, throwOutOfRange } from './epoch' -import { ISODateFields, ISODateTimeFields } from './isoFields' -import { nanoInDay } from './units' - -/* -Extreme valid inputs - Legacy Date - Date.UTC(-271821, 4 - 1, 20, 0, 0, 0, 0) - Date.UTC(275760, 9 - 1, 13, 0, 0, 0, 0) - Instant - Temporal.Instant.fromEpochMilliseconds(Date.UTC(-271821, 4 - 1, 20, 0, 0, 0, 0)) - Temporal.Instant.fromEpochMilliseconds(Date.UTC(275760, 9 - 1, 13, 0, 0, 0, 0)) - PlainDateTime - new Temporal.PlainDateTime(-271821, 4, 19, 0, 0, 0, 0, 0, 1).toString() - new Temporal.PlainDateTime(275760, 9, 13, 23, 59, 59, 999, 999, 999).toString() - PlainDate - new Temporal.PlainDate(-271821, 4, 19).toString() - new Temporal.PlainDate(275760, 9, 13).toString() - PlainYearMonth - new Temporal.PlainYearMonth(-271821, 4).toString() - new Temporal.PlainYearMonth(275760, 9).toString() -*/ - -const almostDay = nanoInDay - 1 // one nanosecond shy of day -const maxInstantBI = createLargeInt(nanoInDay).mult(100000000) // 100,000,000 days -const minInstantBI = maxInstantBI.mult(-1) -const maxPlainBI = maxInstantBI.add(almostDay) -const minPlainBI = minInstantBI.sub(almostDay) - -export function validateYearMonth(isoFields: ISODateFields, calendarID: string): void { - // might throw an error - // moves between days in month - const epochNano = isoFieldsToEpochNano(isoFields) - - checkEpochNanoBuggy(epochNano, calendarID) -} - -export function validateDate(isoFields: ISODateFields, calendarID: string): void { - const epochNano = isoFieldsToEpochNano(isoFields) - - validatePlain( - // if potentially very negative, measure last nanosecond of day - // to increase chances it's in-bounds - epochNano.add(epochNano.sign() < 0 ? almostDay : 0), - ) - checkEpochNanoBuggy(epochNano, calendarID) -} - -export function validateDateTime(isoFields: ISODateTimeFields, calendarID: string): void { - const epochNano = isoFieldsToEpochNano(isoFields) - - validatePlain(epochNano) - checkEpochNanoBuggy(epochNano, calendarID) -} - -export function validateInstant(epochNano: LargeInt): void { - if ( - compareLargeInts(epochNano, minInstantBI) === -1 || - compareLargeInts(epochNano, maxInstantBI) === 1 - ) { - throwOutOfRange() - } -} - -export function validatePlain(epochNano: LargeInt): void { - // like validateInstant's bounds, but expanded 24:59:59.999999999 - if ( - compareLargeInts(epochNano, minPlainBI) === -1 || - compareLargeInts(epochNano, maxPlainBI) === 1 - ) { - throwOutOfRange() - } -} diff --git a/packages/temporal-polyfill/src/dateUtils/isoFields.ts b/packages/temporal-polyfill/src/dateUtils/isoFields.ts deleted file mode 100644 index b91d6bed..00000000 --- a/packages/temporal-polyfill/src/dateUtils/isoFields.ts +++ /dev/null @@ -1,27 +0,0 @@ - -export interface ISOYearFields { - isoYear: number; -} - -export interface ISOYearMonthFields extends ISOYearFields { - isoMonth: number; -} - -export interface ISODateFields extends ISOYearMonthFields { - isoDay: number; -} - -export interface ISOTimeFieldsMilli { - isoHour: number; - isoMinute: number; - isoSecond: number; - isoMillisecond: number; -} - -export interface ISOTimeFields extends ISOTimeFieldsMilli { - isoMicrosecond: number; - isoNanosecond: number; -} - -export type ISODateTimeFields = ISODateFields & ISOTimeFields -export type ISODateTimeFieldsMilli = ISODateFields & ISOTimeFieldsMilli diff --git a/packages/temporal-polyfill/src/dateUtils/isoFormat.ts b/packages/temporal-polyfill/src/dateUtils/isoFormat.ts deleted file mode 100644 index 17c8cf5a..00000000 --- a/packages/temporal-polyfill/src/dateUtils/isoFormat.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { - CALENDAR_DISPLAY_ALWAYS, - CALENDAR_DISPLAY_NEVER, - CalendarDisplayInt, -} from '../argParse/calendarDisplay' -import { DurationToStringConfig, TimeToStringConfig } from '../argParse/isoFormatOptions' -import { TIME_ZONE_DISPLAY_NEVER, TimeZoneDisplayInt } from '../argParse/timeZoneDisplay' -import { isoCalendarID } from '../calendarImpl/isoCalendarImpl' -import { createLargeInt } from '../utils/largeInt' -import { RoundingFunc, roundToIncrementBI } from '../utils/math' -import { getSignStr, padZeros } from '../utils/string' -import { nanoToISOTime } from './dayAndTime' -import { DurationFields } from './durationFields' -import { ISODateFields, ISODateTimeFields, ISOTimeFields } from './isoFields' -import { - MINUTE, - SECOND, - TimeUnitInt, - nanoIn, - nanoInMicro, - nanoInMilli, - nanoInSecond, -} from './units' - -// given ISO fields should already be rounded -export function formatDateTimeISO( - fields: ISODateTimeFields, - formatConfig: TimeToStringConfig, -): string { - return formatDateISO(fields) + 'T' + formatTimeISO(fields, formatConfig) -} - -export function formatDateISO(fields: ISODateFields): string { - return formatYearMonthISO(fields) + '-' + padZeros(fields.isoDay, 2) -} - -export function formatYearMonthISO(fields: ISODateFields): string { - const { isoYear } = fields - return ( - (isoYear < 1000 || isoYear > 9999) - ? getSignStr(isoYear) + padZeros(Math.abs(isoYear), 6) - : padZeros(isoYear, 4) - ) + '-' + padZeros(fields.isoMonth, 2) -} - -export function formatMonthDayISO(fields: ISODateFields): string { - return padZeros(fields.isoMonth, 2) + '-' + padZeros(fields.isoDay, 2) -} - -// given ISO fields should already be rounded -// formatConfig is NOT for rounding. only for smallestUnit/fractionalSecondDigits -export function formatTimeISO( - fields: ISOTimeFields, - formatConfig: TimeToStringConfig, // tighten type? remove roundingMode? -): string { - const parts: string[] = [padZeros(fields.isoHour, 2)] - - if (formatConfig.smallestUnit <= MINUTE) { - parts.push(padZeros(fields.isoMinute, 2)) - - if (formatConfig.smallestUnit <= SECOND) { - parts.push( - padZeros(fields.isoSecond, 2) + - formatPartialSeconds( - fields.isoMillisecond, - fields.isoMicrosecond, - fields.isoNanosecond, - formatConfig.fractionalSecondDigits, - )[0], - ) - } - } - - return parts.join(':') -} - -// TODO: combine with formatTimeISO -export function formatOffsetISO(offsetNano: number): string { - const [fields, dayDelta] = nanoToISOTime(Math.abs(offsetNano)) - const partialSecondsStr = formatPartialSeconds( - fields.isoMillisecond, - fields.isoMicrosecond, - fields.isoNanosecond, - undefined, - )[0] - - return getSignStr(offsetNano) + - // format beyond 24:00 (TODO: somehow convince nanoToISOTime to have topheavy hours?) - padZeros(fields.isoHour + dayDelta * 24, 2) + ':' + - padZeros(fields.isoMinute, 2) + - ((fields.isoSecond || partialSecondsStr) - ? ':' + padZeros(fields.isoSecond, 2) + partialSecondsStr - : '') -} - -// you MUST pass in Calendar::toString() -// this is WEIRD. proper solution: have a proper CalendarProtocol object -export function formatCalendarID( - calendarID: string | undefined, - display: CalendarDisplayInt, -): string { - if ( - calendarID && ( // might be blank if custom calendar implementation - display === CALENDAR_DISPLAY_ALWAYS || - (display !== CALENDAR_DISPLAY_NEVER && calendarID !== isoCalendarID) - ) - ) { - return `[u-ca=${calendarID}]` - } - return '' -} - -export function formatTimeZoneID(timeZoneID: string, display: TimeZoneDisplayInt): string { - if (display !== TIME_ZONE_DISPLAY_NEVER) { - return `[${timeZoneID}]` - } - return '' -} - -export function formatDurationISO( - fields: DurationFields, - formatConfig: DurationToStringConfig, -): string { - const { smallestUnit, fractionalSecondDigits, roundingFunc } = formatConfig - const { sign } = fields - const hours = fields.hours - const minutes = fields.minutes - let seconds = fields.seconds - let partialSecondsStr = '' - - if (smallestUnit <= SECOND) { // should be just less-than!!? - const res = formatPartialSeconds( - fields.milliseconds, - fields.microseconds, - fields.nanoseconds, - fractionalSecondDigits, - roundingFunc, - smallestUnit, - ) - partialSecondsStr = res[0] - seconds += res[1] - } - - // guarantee display of seconds if... - const forceSeconds = - fractionalSecondDigits !== undefined || // fractionalSecondDigits explicitly specified - partialSecondsStr || // partial seconds, either via fractionalSecondDigits or default - !sign // duration is completely empty, display 'PT0S' - - return (sign < 0 ? '-' : '') + 'P' + - collapseDurationTuples([ - [fields.years, 'Y'], - [fields.months, 'M'], - [fields.weeks, 'W'], - [fields.days, 'D'], - ]) + - (hours || minutes || seconds || forceSeconds - ? 'T' + - collapseDurationTuples([ - [hours, 'H'], - [minutes, 'M'], - [ - smallestUnit <= SECOND ? seconds : 0, - partialSecondsStr + 'S', - forceSeconds, - ], - ]) - : '') -} - -// use BigInts, because less likely to overflow and formatting never does scientific notation -function collapseDurationTuples(tuples: [number, string, unknown?][]): string { - return tuples.map(([num, postfix, forceShow]) => { - if (forceShow || num) { - // avoid outputting scientific notation - // https://stackoverflow.com/a/50978675/96342 - const numStr = Math.abs(num).toLocaleString('fullwide', { useGrouping: false }) - return numStr + postfix - } - return '' - }).join('') -} - -function formatPartialSeconds( - milliseconds: number, - microseconds: number, - nanoseconds: number, - fractionalSecondDigits: number | undefined, - roundingFunc?: RoundingFunc, // HACK for forcing this func to do rounding - smallestUnit?: TimeUnitInt, // HACK for forcing this func to do rounding -): [string, number] { // [afterDecimalStr, secondsOverflow] - let totalNano = createLargeInt(milliseconds).mult(nanoInMilli) - .add(createLargeInt(microseconds).mult(nanoInMicro)) - .add(nanoseconds) - - // HACK. sometimes input is pre-rounded, other times not - // not DRY. search for Math.pow - if (roundingFunc) { - totalNano = roundToIncrementBI( - totalNano, - fractionalSecondDigits === undefined - ? nanoIn[smallestUnit!] // not needed anymore I don't think - : Math.pow(10, 9 - fractionalSecondDigits), - roundingFunc, - ) - } - - const totalNanoAbs = totalNano.abs() - const seconds = totalNanoAbs.div(nanoInSecond) - const leftoverNano = totalNanoAbs.sub(seconds.mult(nanoInSecond)) - - let afterDecimal = padZeros(leftoverNano.toNumber(), 9) - afterDecimal = fractionalSecondDigits === undefined - ? afterDecimal.replace(/0+$/, '') // strip trailing zeros - : afterDecimal.substr(0, fractionalSecondDigits) - - return [ - afterDecimal ? '.' + afterDecimal : '', - seconds.toNumber() * (totalNano.sign() || 1), // restore sign - ] -} diff --git a/packages/temporal-polyfill/src/dateUtils/localFields.ts b/packages/temporal-polyfill/src/dateUtils/localFields.ts deleted file mode 100644 index 611e350a..00000000 --- a/packages/temporal-polyfill/src/dateUtils/localFields.ts +++ /dev/null @@ -1,30 +0,0 @@ -// local essentials -// special note about not doing spreads - -export interface LocalYearFields { - year: number; -} - -export interface LocalYearMonthFields extends LocalYearFields { - month: number; -} - -export interface LocalDateFields extends LocalYearMonthFields { - day: number; -} - -export interface LocalMonthDayFields { - monthCode: string; - day: number; -} - -export interface LocalTimeFields { - hour: number; - minute: number; - second: number; - millisecond: number; - microsecond: number; - nanosecond: number; -} - -export type LocalDateTimeFields = LocalDateFields & LocalTimeFields diff --git a/packages/temporal-polyfill/src/dateUtils/mixins.ts b/packages/temporal-polyfill/src/dateUtils/mixins.ts deleted file mode 100644 index 531afabe..00000000 --- a/packages/temporal-polyfill/src/dateUtils/mixins.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { unitNames } from '../argParse/unitStr' -import { LargeInt } from '../utils/largeInt' -import { attachGetters, strArrayToHash } from '../utils/obj' -import { capitalizeFirstLetter } from '../utils/string' -import { DateISOInstance } from './calendar' -import { epochNanoSymbol } from './epoch' -import { nanoInMicro, nanoInMilli, nanoInSecond } from './units' - -// Epoch Fields - -export interface ComputedEpochFields { - epochNanoseconds: bigint - epochMicroseconds: bigint - epochMilliseconds: number - epochSeconds: number -} - -export function mixinEpochFields( - ObjClass: { prototype: Obj }, -): void { - attachGetters(ObjClass, { - epochNanoseconds(): bigint { - return this[epochNanoSymbol].toBigInt() - }, - epochMicroseconds(): bigint { - return this[epochNanoSymbol].div(nanoInMicro).toBigInt() - }, - epochMilliseconds(): number { - return this[epochNanoSymbol].div(nanoInMilli).toNumber() - }, - epochSeconds(): number { - return this[epochNanoSymbol].div(nanoInSecond).toNumber() - }, - }) -} - -// ISO Fields - -const isoFieldMap: { [Key: string]: string } = { - calendar: 'calendar', -} -for (const unitName of unitNames) { - isoFieldMap[unitName] = 'iso' + capitalizeFirstLetter(unitName) -} - -// always mixes in `calendar` -export function mixinISOFields( - ObjClass: { prototype: Obj }, - unitNames: Temporal.DateTimeUnit[] = [], -): void { - attachGetters( - ObjClass, - strArrayToHash( - (unitNames as string[]).concat('calendar'), - (propName) => function(this: Obj) { - return this.getISOFields()[isoFieldMap[propName]] - }, - ), - ) -} - -// Calendar Fields - -export interface YearMonthCalendarFields { - era: string | undefined - eraYear: number | undefined - year: number - month: number - monthCode: string - daysInMonth: number - daysInYear: number - monthsInYear: number - inLeapYear: boolean -} - -export interface MonthDayCalendarFields { - monthCode: string - day: number -} - -export interface DateCalendarFields extends YearMonthCalendarFields { - day: number - daysInWeek: number - dayOfWeek: number - dayOfYear: number - weekOfYear: number -} - -export const yearMonthCalendarFields: (keyof YearMonthCalendarFields)[] = [ - 'era', - 'eraYear', - 'year', - 'month', - 'monthCode', - 'daysInMonth', - 'daysInYear', - 'monthsInYear', - 'inLeapYear', -] - -export const monthDayCalendarFields: (keyof MonthDayCalendarFields)[] = [ - 'monthCode', - 'day', -] - -export const dateCalendarFields: (keyof DateCalendarFields)[] = [ - ...yearMonthCalendarFields, - 'day', - 'dayOfWeek', - 'dayOfYear', - 'weekOfYear', - 'daysInWeek', -] - -export function mixinCalendarFields( - ObjClass: { prototype: Obj }, - propNames: (keyof DateCalendarFields)[], -): void { - attachGetters( - ObjClass, - strArrayToHash(propNames, (propName) => function(this: Obj) { - const value = this.calendar[propName as keyof DateCalendarFields]( - this as Temporal.PlainDateLike, - ) - Object.defineProperty(this, propName, { // cache the value on the object - value, - configurable: true, // what classes do. TODO: ensure everywhere - }) - return value - }), - ) -} - -// affects how objects are displayed in console - -// TODO: make readonly somehow? -export function attachStringTag(objOrClass: any, name: string): void { - (objOrClass.prototype || objOrClass)[Symbol.toStringTag] = 'Temporal.' + name -} diff --git a/packages/temporal-polyfill/src/dateUtils/offset.ts b/packages/temporal-polyfill/src/dateUtils/offset.ts deleted file mode 100644 index 21bd7f9b..00000000 --- a/packages/temporal-polyfill/src/dateUtils/offset.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { parseDisambigOption } from '../argParse/disambig' -import { - OFFSET_IGNORE, - OFFSET_REJECT, - OFFSET_USE, - OffsetHandlingInt, -} from '../argParse/offsetHandling' -import { Calendar } from '../public/calendar' -import { Instant } from '../public/instant' -import { createDateTime } from '../public/plainDateTime' -import { LargeInt } from '../utils/largeInt' -import { roundToMinute } from '../utils/math' -import { zeroISOTimeFields } from './dayAndTime' -import { epochNanoSymbol, isoFieldsToEpochNano } from './epoch' -import { ISODateFields, ISODateTimeFields } from './isoFields' -import { getInstantFor } from './timeZone' -import { addDays } from './translate' - -export interface OffsetComputableFields extends ISODateTimeFields { - calendar: Temporal.CalendarProtocol - timeZone: Temporal.TimeZoneProtocol - offsetNanoseconds?: number // TODO: change this back to `offset`? better for ZonedDateTime? - Z?: boolean -} - -export function checkInvalidOffset(isoFields: OffsetComputableFields): void { - const { offsetNanoseconds: offsetNano, timeZone, Z } = isoFields - - // a non-Z offset defined? (for ALWAYS use Z as zero offset) - if (offsetNano !== undefined && !Z) { - const matchingEpochNano = findMatchingEpochNano(isoFields, offsetNano, timeZone, true) - - if (matchingEpochNano === undefined) { - throw new RangeError('Mismatching offset/timezone') // TODO: more DRY - } - } -} - -export function computeZonedDateTimeEpochNano( - isoFields: OffsetComputableFields, - fuzzyMatching?: boolean, - offsetHandling: OffsetHandlingInt = OFFSET_REJECT, - disambigOptions?: Temporal.AssignmentOptions, -): LargeInt { - const { offsetNanoseconds: offsetNano, timeZone, Z } = isoFields - - if (offsetNano !== undefined && offsetHandling !== OFFSET_IGNORE) { - // we ALWAYS use Z as zero offset - if (offsetHandling === OFFSET_USE || Z) { - return isoFieldsToEpochNano(isoFields).sub(offsetNano) - } else { - const matchingEpochNano = findMatchingEpochNano( - isoFields, - offsetNano, - timeZone, - fuzzyMatching, - ) - if (matchingEpochNano !== undefined) { - return matchingEpochNano - } - if (offsetHandling === OFFSET_REJECT) { - throw new RangeError('Mismatching offset/timezone') - } - // else, OFFSET_PREFER... - } - } - - // compute fresh from TimeZone - const instant = getInstantFor( - timeZone, - createDateTime(isoFields), - parseDisambigOption(disambigOptions), - ) - - // TODO: better typing solution - return (instant as Instant)[epochNanoSymbol] -} - -function findMatchingEpochNano( - isoFields: ISODateTimeFields & { calendar: Temporal.CalendarProtocol }, - offsetNano: number, - timeZone: Temporal.TimeZoneProtocol, - fuzzyMatching?: boolean, -): LargeInt | undefined { - const possibleInstants = timeZone.getPossibleInstantsFor(createDateTime(isoFields)) - const utcEpochNano = isoFieldsToEpochNano(isoFields) - const roundedOffsetNano = fuzzyMatching ? roundToMinute(offsetNano) : offsetNano - - for (const instant of possibleInstants) { - const possibleEpochNano = (instant as Instant)[epochNanoSymbol] // TODO: better typing - const possibleOffsetNano = utcEpochNano.sub(possibleEpochNano).toNumber() - const possibleOffsetRefined = fuzzyMatching - ? roundToMinute(possibleOffsetNano) - : possibleOffsetNano - - if (possibleOffsetRefined === roundedOffsetNano) { - return possibleEpochNano - } - } -} - -// best file for this? -export function computeNanoInDay( - fields: ISODateFields & { timeZone: Temporal.TimeZoneProtocol }, -): number { - const { timeZone } = fields - - // TODO: awkard with iso8601 calendar - const day0 = { ...fields, ...zeroISOTimeFields, calendar: new Calendar('iso8601') } - const day1 = { ...addDays(day0, 1), ...zeroISOTimeFields, calendar: new Calendar('iso8601') } - const epochNano0 = (getInstantFor(timeZone, createDateTime(day0)) as Instant)[epochNanoSymbol] - const epochNano1 = (getInstantFor(timeZone, createDateTime(day1)) as Instant)[epochNanoSymbol] - - return epochNano1.sub(epochNano0).toNumber() -} diff --git a/packages/temporal-polyfill/src/dateUtils/parse.ts b/packages/temporal-polyfill/src/dateUtils/parse.ts deleted file mode 100644 index 94bce594..00000000 --- a/packages/temporal-polyfill/src/dateUtils/parse.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { padEnd } from '../utils/string' -import { isValidDateISO } from './constrain' -import { nanoToISOTime } from './dayAndTime' -import { isoEpochLeapYear } from './epoch' -import { ISODateFields, ISODateTimeFields, ISOTimeFields } from './isoFields' -import { - dateTimeRegExp, - monthDayRegExp, - normalizeDashes, - offsetRegExp, - timeRegExp, - yearMonthRegExp, -} from './parseRegExp' -import { - nanoInHour, - nanoInMinute, - nanoInSecond, -} from './units' - -export interface DateParseResults extends ISODateFields { - calendar: string | undefined -} - -export interface DateTimeParseResult extends ISODateTimeFields { - calendar: string | undefined -} - -export interface ZonedDateTimeParseResult extends DateTimeParseResult { - timeZone: string | undefined - offsetNanoseconds: number | undefined - Z: boolean | undefined // whether ISO8601 specified with 'Z' as offset indicator -} - -// hard functions (throw error on failure) - -export function parseZonedDateTime(str: string): ZonedDateTimeParseResult { - const res = tryParseZonedDateTime(str) - if (!res) { - throw createParseError('dateTime', str) - } - return res -} - -export function parseDateTime(str: string): DateTimeParseResult { - const res = tryParseDateTime(str) - if (!res) { - throw createParseError('dateTime', str) - } - return res -} - -export function parseYearMonth(str: string): DateParseResults { - const res = tryParseYearMonth(str) || tryParseDateTime(str) - if (!res) { - throw createParseError('yearMonth', str) - } - return res -} - -export function parseMonthDay(str: string): DateParseResults { - const res = tryParseMonthDay(str) || tryParseDateTime(str) - if (!res) { - throw createParseError('monthDay', str) - } - return res -} - -export function parseOffsetNano(str: string): number { - const res = tryParseOffsetNano(str) - if (res === undefined) { - throw createParseError('timeZone', str) // timeZone? - } - return res -} - -export function parseTime(str: string): ISOTimeFields { - let res = tryParseTime(str) - - if (res !== undefined) { - // detect ambiguity in format - if (str.charAt(0) !== 'T') { - const tryOther = tryParseYearMonth(str) || tryParseMonthDay(str) - if (tryOther && isValidDateISO(tryOther)) { - res = undefined // invalid - } - } - } else { - res = tryParseDateTime(str, true) - } - - if (res === undefined) { - throw createParseError('time', str) - } - return res -} - -// soft functions (return undefined on failure) - -const zRE = /^Z$/i - -export function tryParseZonedDateTime(str: string): ZonedDateTimeParseResult | undefined { - const m = dateTimeRegExp.exec(normalizeDashes(str)) - if (m) { - return parseZonedDateTimeParts(m.slice(1)) - } -} - -export function tryParseDateTime( - str: string, - requireTime?: boolean, - allowZ?: boolean, -): DateTimeParseResult | undefined { - const m = dateTimeRegExp.exec(normalizeDashes(str)) - if ( - m && - (allowZ || !zRE.test(m[12])) && // don't allow Z (12 means index 11 when unsliced) - (!requireTime || m[4]) // timeEverything (4 means index 3 when unsliced) - ) { - return parseDateTimeParts(m.slice(1)) - } -} - -function tryParseYearMonth(str: string): DateParseResults | undefined { - const m = yearMonthRegExp.exec(normalizeDashes(str)) - if (m) { - return parseYearMonthParts(m.slice(1)) - } -} - -function tryParseMonthDay(str: string): DateParseResults | undefined { - const m = monthDayRegExp.exec(normalizeDashes(str)) - if (m) { - return parseMonthDayParts(m.slice(1)) - } -} - -function tryParseTime(str: string) { - const m = timeRegExp.exec(normalizeDashes(str)) - if (m) { - return parseTimeParts(m.slice(1)) - } -} - -export function tryParseOffsetNano(str: string): number | undefined { - const m = offsetRegExp.exec(normalizeDashes(str)) - if (m) { - return parseOffsetParts(m.slice(1)) - } -} - -// parsing of string parts -// TODO: don't need toInt1/toInt0 as much because parts are more guaranteed now -// TODO: combine the zoned/unzoned cases. will simplify caller functions? - -function parseZonedDateTimeParts(parts: string[]): ZonedDateTimeParseResult { - const zOrOffset = parts[11] - let offsetNanoseconds: number | undefined - let Z = false - - if (zOrOffset) { - Z = zRE.test(zOrOffset) - offsetNanoseconds = Z ? 0 : parseOffsetParts(parts.slice(12)) - } - - return { - ...parseDateTimeParts(parts), - timeZone: parts[21], - offsetNanoseconds, - Z, - } -} - -function parseDateTimeParts(parts: string[]): DateTimeParseResult { - return { - calendar: parts[23], - isoYear: toInt1(parts[0]), - isoMonth: toInt1(parts[1]), - isoDay: toInt1(parts[2]), - ...parseTimeParts(parts.slice(4)), - } -} - -function parseYearMonthParts(parts: string[]): DateParseResults { - return { - calendar: parts[14], - isoYear: toInt1(parts[0]), - isoMonth: toInt1(parts[1]), - isoDay: 1, - } -} - -function parseMonthDayParts(parts: string[]): DateParseResults { - return { - calendar: parts[15], - isoYear: isoEpochLeapYear, - isoMonth: toInt1(parts[1]), - isoDay: toInt1(parts[2]), - } -} - -function parseTimeParts(parts: string[]): ISOTimeFields { - const isoSecond = toInt0(parts[4]) - - return { - ...nanoToISOTime(parseNanoAfterDecimal(parts[6] || ''))[0], - isoHour: toInt0(parts[0]), - isoMinute: toInt0(parts[2]), - isoSecond: isoSecond === 60 ? 59 : isoSecond, // massage lead-second - } -} - -function parseOffsetParts(parts: string[]): number { - return (parts[0] === '+' ? 1 : -1) * timePartsToNano(parts.slice(1)) -} - -// time parsing as nanoseconds - -function timePartsToNano(parts: string[]): number { - return toInt0(parts[0]) * nanoInHour + - toInt0(parts[2]) * nanoInMinute + - toInt0(parts[4]) * nanoInSecond + - parseNanoAfterDecimal(parts[6] || '') -} - -export function parseNanoAfterDecimal(str: string): number { - return parseInt(padEnd(str, 9, '0')) -} - -// general utils - -function toInt0(input: string | undefined): number { // 0-based - return parseInt(input || '0') -} - -function toInt1(input: string | undefined): number { // 1-based - return parseInt(input || '1') -} - -export function toIntMaybe(input: string | undefined): number | undefined { - return input === undefined ? undefined : parseInt(input) -} - -export function createParseError(type: string, str: string): any { - throw new RangeError(`Cannot parse ${type} '${str}'`) -} diff --git a/packages/temporal-polyfill/src/dateUtils/parseDuration.ts b/packages/temporal-polyfill/src/dateUtils/parseDuration.ts deleted file mode 100644 index 25858244..00000000 --- a/packages/temporal-polyfill/src/dateUtils/parseDuration.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { createLargeInt } from '../utils/largeInt' -import { excludeUndefined } from '../utils/obj' -import { nanoToDuration } from './dayAndTime' -import { - DurationFields, - UnsignedDurationFields, - negateDuration, - signDuration, -} from './durationFields' -import { createParseError, parseNanoAfterDecimal, toIntMaybe } from './parse' -import { durationRegExp, normalizeDashes } from './parseRegExp' -import { - HOUR, - MILLISECOND, - MINUTE, - SECOND, - TimeUnitInt, - nanoIn, - nanoInSecond, -} from './units' - -export function parseDuration(str: string): DurationFields { - const res = tryParseDuration(str) - if (res === undefined) { - throw createParseError('duration', str) - } - return res -} - -function tryParseDuration(str: string): DurationFields | undefined { - const match = durationRegExp.exec(normalizeDashes(str)) - if (match) { // TODO: break out into parseDurationParts - let hours: number | undefined - let minutes: number | undefined - let seconds: number | undefined - let leftoverNano: number | undefined - - ([hours, leftoverNano] = parseDurationTimeUnit(match[8], match[10], HOUR, undefined)); - ([minutes, leftoverNano] = parseDurationTimeUnit(match[12], match[14], MINUTE, leftoverNano)); - ([seconds, leftoverNano] = parseDurationTimeUnit(match[16], match[18], SECOND, leftoverNano)) - - const fields: Partial = excludeUndefined({ - years: toIntMaybe(match[2]), - months: toIntMaybe(match[3]), - weeks: toIntMaybe(match[4]), - days: toIntMaybe(match[5]), - hours, - minutes, - seconds, - }) - - if (!Object.keys(fields).length) { - throw new RangeError('Duration string must have at least one field') - } - - const small = nanoToDuration(createLargeInt(leftoverNano || 0), MILLISECOND) - // TODO: use mergeDurations somehow? - fields.milliseconds = small.milliseconds - fields.microseconds = small.microseconds - fields.nanoseconds = small.nanoseconds - - let signedDuration = signDuration(fields as UnsignedDurationFields) - - if (match[1] === '-') { - signedDuration = negateDuration(signedDuration) - } - - return signedDuration - } -} - -function parseDurationTimeUnit( - beforeDecimal: string | undefined, - afterDecimal: string | undefined, - unit: TimeUnitInt, - leftoverNano: number | undefined, -): [number | undefined, number | undefined] { // [wholeUnits, leftoverNano] - if (beforeDecimal !== undefined) { - if (leftoverNano !== undefined) { - throw new RangeError('Partial units must be last unit') - } - return [ - parseInt(beforeDecimal), - afterDecimal !== undefined - ? parseNanoAfterDecimal(afterDecimal) * (nanoIn[unit] / nanoInSecond) // mult by # of secs - : undefined, - ] - } else if (leftoverNano !== undefined) { - const wholeUnits = Math.trunc(leftoverNano / nanoIn[unit]) - return [wholeUnits, leftoverNano - (wholeUnits * nanoIn[unit])] - } else { - return [undefined, undefined] - } -} diff --git a/packages/temporal-polyfill/src/dateUtils/parseRefine.ts b/packages/temporal-polyfill/src/dateUtils/parseRefine.ts deleted file mode 100644 index 21d840b1..00000000 --- a/packages/temporal-polyfill/src/dateUtils/parseRefine.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Calendar, createDefaultCalendar } from '../public/calendar' -import { TimeZone } from '../public/timeZone' - -interface BaseParseResult { - calendar: string | undefined -} - -interface ZonedParseResult extends BaseParseResult { - timeZone: string | undefined -} - -export function refineBaseObj( - parsed: T, -): T & { calendar: Calendar } { // TODO: weird TS anding-behavior - return { - ...parsed, - calendar: parsed.calendar === undefined - ? createDefaultCalendar() - : new Calendar(parsed.calendar), - } -} - -export function refineZonedObj( - parsed: T, -): T & { calendar: Calendar, timeZone: TimeZone } { // TODO: weird TS anding-behavior - return { - ...refineBaseObj(parsed), - timeZone: new TimeZone(parsed.timeZone!), // will throw error if empty timeZone - } -} diff --git a/packages/temporal-polyfill/src/dateUtils/parseRegExp.ts b/packages/temporal-polyfill/src/dateUtils/parseRegExp.ts deleted file mode 100644 index 471d8030..00000000 --- a/packages/temporal-polyfill/src/dateUtils/parseRegExp.ts +++ /dev/null @@ -1,60 +0,0 @@ - -const yearMonthRegExpStr = - '([+-]\\d{6}|\\d{4})' + // 0: year - '-?(\\d{2})' // 1: month - // ending... 12: timeZone, 14: calendar - -const dateRegExpStr = - yearMonthRegExpStr + // 0-1: yearMonth - '-?(\\d{2})' // 2: day - // ending... 13: timeZone, 15: calendar - -const monthDayRegExpStr = - '(--)?(\\d{2})' + // 1: month - '-?(\\d{2})' // 2: day - // ending... 13: timeZone, 15: calendar - -const timeRegExpStr = - '(\\d{2})' + // 0: hour - '(:?(\\d{2})' + // 2: minute - '(:?(\\d{2})' + // 4: second - '([.,](\\d{1,9}))?' + // 6: afterDecimal - ')?)?' - -const dateTimeRegExpStr = - dateRegExpStr + // 0-2: date - '([T ]' + // 3: timeEverything - timeRegExpStr + // 4-10: time - ')?' - // ending... 11: zOrOffset, 12-19: offset, 21: timeZone, 23: calendar - -const offsetRegExpStr = - '([+-])' + // 0: plusOrMinus - timeRegExpStr // 1-7: time - -const endingRegExpStr = - '(Z|' + // 0: zOrOffset - offsetRegExpStr + // 1-8: offset - ')?' + - '(\\[([^=\\]]+)\\])?' + // 10: timeZone - '(\\[u-ca=([^\\]]+)\\])?' // 12: calendar - -export const yearMonthRegExp = createRegExp(yearMonthRegExpStr + endingRegExpStr) -export const monthDayRegExp = createRegExp(monthDayRegExpStr + endingRegExpStr) -export const dateTimeRegExp = createRegExp(dateTimeRegExpStr + endingRegExpStr) -export const timeRegExp = createRegExp('T?' + timeRegExpStr + endingRegExpStr) -export const offsetRegExp = createRegExp(offsetRegExpStr) - -// TODO: use same DRY technique as above -export const durationRegExp = /^([-+])?P(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T((\d+)([.,](\d{1,9}))?H)?((\d+)([.,](\d{1,9}))?M)?((\d+)([.,](\d{1,9}))?S)?)?$/i - -// TODO: inject this into regexes above? -const unicodeDashRegExp = /\u2212/g - -function createRegExp(meat: string): RegExp { - return new RegExp(`^${meat}$`, 'i') -} - -export function normalizeDashes(str: string): string { - return str.replace(unicodeDashRegExp, '-') -} diff --git a/packages/temporal-polyfill/src/dateUtils/relativeTo.ts b/packages/temporal-polyfill/src/dateUtils/relativeTo.ts deleted file mode 100644 index 8716ae04..00000000 --- a/packages/temporal-polyfill/src/dateUtils/relativeTo.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { isObjectLike } from '../argParse/refine' -import { PlainDateTime, PlainDateTimeArg, createDateTime } from '../public/plainDateTime' -import { - ZonedDateTime, - ZonedDateTimeArg, - createZonedDateTimeFromFields, -} from '../public/zonedDateTime' -import { ensureObj } from './abstract' -import { tryParseZonedDateTime } from './parse' -import { refineBaseObj, refineZonedObj } from './parseRefine' - -export function extractRelativeTo( - arg: ZonedDateTimeArg | PlainDateTimeArg | undefined, -): ZonedDateTime | PlainDateTime | undefined { - if (arg === undefined) { - return undefined - } - - if (isObjectLike(arg)) { - if (arg instanceof ZonedDateTime || arg instanceof PlainDateTime) { - return arg - } - return ensureObj( - (arg as Temporal.ZonedDateTimeLike).timeZone !== undefined - ? ZonedDateTime - : PlainDateTime, - arg as Temporal.ZonedDateTimeLike, - ) - } - - // assume a string... - // TODO: general toString util for ALL parsing that prevents parsing symbols - // https://github.com/ljharb/es-abstract/blob/main/2020/ToString.js - if (typeof arg === 'symbol') { - throw new TypeError('Incorrect relativeTo type') - } - - const parsed = tryParseZonedDateTime(String(arg)) - if (parsed) { - if (parsed.timeZone !== undefined) { - return createZonedDateTimeFromFields(refineZonedObj(parsed), true) - } else { - return createDateTime(refineBaseObj(parsed)) - } - } - - throw new RangeError('Invalid value of relativeTo') -} diff --git a/packages/temporal-polyfill/src/dateUtils/rounding.ts b/packages/temporal-polyfill/src/dateUtils/rounding.ts deleted file mode 100644 index a203f5ee..00000000 --- a/packages/temporal-polyfill/src/dateUtils/rounding.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { OFFSET_PREFER } from '../argParse/offsetHandling' -import { RoundingConfig } from '../argParse/roundingOptions' -import { LargeInt } from '../utils/largeInt' -import { roundToIncrement, roundToIncrementBI } from '../utils/math' -import { isoTimeToNano, nanoToISOTime, zeroISOTimeFields } from './dayAndTime' -import { splitEpochNano } from './epoch' -import { ISODateTimeFields, ISOTimeFields } from './isoFields' -import { computeNanoInDay, computeZonedDateTimeEpochNano } from './offset' -import { addDays } from './translate' -import { DAY, DayTimeUnitInt, TimeUnitInt } from './units' - -export function roundDateTime( - fields: ISODateTimeFields, - roundingConfig: RoundingConfig, -): ISODateTimeFields { - const timeNano = isoTimeToNano(fields) - const roundedTimeNano = roundNano(timeNano, roundingConfig) - const [isoTimeFields, dayDelta] = nanoToISOTime(roundedTimeNano) - - const dayStartTranslated = addDays(fields, dayDelta) - return { ...dayStartTranslated, ...isoTimeFields } -} - -export function roundTime( - fields: ISOTimeFields, - roundingConfig: RoundingConfig, -): ISOTimeFields { - const timeNano = isoTimeToNano(fields) - const roundedTimeNano = roundNano(timeNano, roundingConfig) - const [isoTimeFields] = nanoToISOTime(roundedTimeNano) - return isoTimeFields -} - -export function roundEpochNano( - epochNano: LargeInt, - roundingConfig: RoundingConfig, -): LargeInt { - const [dayEpochNano, timeNano] = splitEpochNano(epochNano) - const roundedTimeNano = roundNano(timeNano, roundingConfig) - return dayEpochNano.add(roundedTimeNano) -} - -// returns epochNano! -export function roundZonedDateTimeFields( - fields: ISODateTimeFields & { - calendar: Temporal.CalendarProtocol, - timeZone: Temporal.TimeZoneProtocol, - }, - offsetNanoseconds: number, - roundingConfig: RoundingConfig, -): LargeInt { - const { calendar, timeZone } = fields - let timeNano = isoTimeToNano(fields) - let isoTimeFields: ISOTimeFields - let dayDelta: number - - if (roundingConfig.smallestUnit === DAY) { - isoTimeFields = zeroISOTimeFields - dayDelta = roundingConfig.roundingFunc(timeNano / computeNanoInDay(fields)) - } else { - timeNano = roundNano(timeNano, roundingConfig) - ;([isoTimeFields, dayDelta] = nanoToISOTime(timeNano)) - } - - const dayStartTranslated = addDays(fields, dayDelta) - return computeZonedDateTimeEpochNano( - { - ...dayStartTranslated, - ...isoTimeFields, - offsetNanoseconds, - calendar, // !!! - timeZone, // !!! - }, - false, - OFFSET_PREFER, // for offsetNanoseconds conflicts - ) -} - -// low-level utils (just for day-and-time) - -export function roundNano(nano: number, roundingConfig: RoundingConfig): number { - return roundToIncrement( - nano, - roundingConfig.incNano, - roundingConfig.roundingFunc, - ) -} - -export function roundNanoBI( - nano: LargeInt, - roundingConfig: RoundingConfig, -): LargeInt { - return roundToIncrementBI( - nano, - roundingConfig.incNano, - roundingConfig.roundingFunc, - ) -} diff --git a/packages/temporal-polyfill/src/dateUtils/roundingDuration.ts b/packages/temporal-polyfill/src/dateUtils/roundingDuration.ts deleted file mode 100644 index e2730767..00000000 --- a/packages/temporal-polyfill/src/dateUtils/roundingDuration.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { DiffConfig } from '../argParse/diffOptions' -import { durationUnitNames } from '../argParse/unitStr' -import { roundToIncrement, roundToIncrementBI } from '../utils/math' -import { durationDayTimeToNano, nanoToDuration } from './dayAndTime' -import { DiffableObj } from './diff' -import { - DurationFields, - UnsignedDurationFields, - computeLargestDurationUnit, - negateDuration, -} from './durationFields' -import { spanDurationFrom, spanDurationFromDateTime } from './durationSpan' -import { toEpochNano } from './epoch' -import { computeExactDuration } from './totalUnits' -import { - DAY, - NANOSECOND, - UnitInt, - isDateUnit, - isDayTimeUnit, - nanoIn, -} from './units' - -// duration rounding is very different from datetime rounding -// more similar to diffing -// TODO: make a new type `DurationRoundingConfig` that includes relativeTo - -export function roundDuration( - duration: DurationFields, - diffConfig: DiffConfig, - relativeTo: DiffableObj | undefined, // TODO: start using `DurationRoundingConfig` - calendar: Temporal.CalendarProtocol | undefined, -): DurationFields { - const { largestUnit, smallestUnit, roundingIncrement, roundingFunc } = diffConfig - - if ( - relativeTo === undefined && // skip this block if relativeTo defined - computeLargestDurationUnit(duration) <= DAY && - isDayTimeUnit(largestUnit) && - isDayTimeUnit(smallestUnit) - ) { - const nano = roundToIncrementBI( - durationDayTimeToNano(duration), - nanoIn[smallestUnit] * roundingIncrement, - roundingFunc, - ) - return nanoToDuration(nano, largestUnit) - } - - if (!relativeTo) { - throw new RangeError('Need relativeTo') - } - - const [spannedDuration, relativeToTranslated] = spanDurationFromDateTime( - duration, - largestUnit, - relativeTo, - calendar!, - ) - - return roundDurationSpan( - spannedDuration, - relativeTo, - relativeToTranslated, - calendar!, - false, - diffConfig, - ) -} - -export function roundDurationSpan( - spannedDuration: DurationFields, - d0: DiffableObj, - d1: DiffableObj, - calendar: Temporal.CalendarProtocol, - flip: boolean, - diffConfig: DiffConfig, -): DurationFields { - const { largestUnit, smallestUnit, roundingIncrement, roundingFunc } = diffConfig - - // optimize for time units - if (!isDateUnit(largestUnit)) { - const diffNano = toEpochNano(d1).sub(toEpochNano(d0)).mult(flip ? -1 : 1) - const diffNanoRounded = roundToIncrementBI( - diffNano, - nanoIn[smallestUnit] * roundingIncrement, - roundingFunc, - ) - return nanoToDuration(diffNanoRounded, largestUnit) - } - - let durationFields = computeExactDuration(spannedDuration, smallestUnit, d0, d1) - const unitName = durationUnitNames[smallestUnit] as keyof UnsignedDurationFields - - function doRound() { - const orig = durationFields[unitName] // computeExactDuration guarantees value - durationFields[unitName] = roundToIncrement(orig, roundingIncrement, roundingFunc) - } - - if (roundingFunc === Math.round) { - // 'halfExpand' cares about point-to-point translation - doRound() - } - if (flip) { - durationFields = negateDuration(durationFields) - } - if (roundingFunc !== Math.round) { - // other rounding techniques operate on final number - doRound() - } - - // TODO: instead of this mess, have a halfExpandDirection arg - // rebalance - if (smallestUnit > NANOSECOND) { - if (flip) { - // yuck - durationFields = negateDuration( - spanDurationFrom(negateDuration(durationFields), largestUnit, d0, calendar), - ) - } else { - durationFields = spanDurationFrom(durationFields, largestUnit, d0, calendar) - } - } - - return durationFields -} diff --git a/packages/temporal-polyfill/src/dateUtils/timeZone.ts b/packages/temporal-polyfill/src/dateUtils/timeZone.ts deleted file mode 100644 index 0fb73977..00000000 --- a/packages/temporal-polyfill/src/dateUtils/timeZone.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { - DISAMBIG_COMPATIBLE, - DISAMBIG_EARLIER, - DISAMBIG_LATER, - DISAMBIG_REJECT, - DisambigInt, -} from '../argParse/disambig' -import { Instant } from '../public/instant' -import { PlainDateTime } from '../public/plainDateTime' -import { toEpochNano } from './epoch' -import { nanoInDay } from './units' - -// Utils for working with TimeZoneProtocol - -export function getInstantFor( - timeZoneProtocol: Temporal.TimeZoneProtocol, - dateTime: PlainDateTime, - disambigInt: DisambigInt = DISAMBIG_COMPATIBLE, -): Temporal.Instant { - const possibleInstants = timeZoneProtocol.getPossibleInstantsFor(dateTime) - - if (possibleInstants.length === 1) { - return possibleInstants[0] - } else { - if (disambigInt === DISAMBIG_REJECT) { - throw new RangeError('Ambiguous offset') - } - - // within a transition that jumps back - // (compat means earlier) - if (possibleInstants.length) { - return possibleInstants[ - disambigInt === DISAMBIG_LATER - ? 1 - : 0 // DISAMBIG_EARLIER and DISAMBIG_COMPATIBLE - ] - - // within a transition that jumps forward - // (compat means later) - } else { - const gapNano = computeGapNear(timeZoneProtocol, dateTime) - const moreInstants = timeZoneProtocol.getPossibleInstantsFor( - dateTime.add({ - nanoseconds: - gapNano * - (disambigInt === DISAMBIG_EARLIER - ? -1 - : 1), // DISAMBIG_LATER and DISAMBIG_COMPATIBLE - }), - ) - - return moreInstants[// either 1 or 2 choices - disambigInt === DISAMBIG_EARLIER - ? 0 - : moreInstants.length - 1 // DISAMBIG_LATER and DISAMBIG_COMPATIBLE - ] - } - } -} - -function computeGapNear( - timeZoneProtocol: Temporal.TimeZoneProtocol, - plainDateTime: PlainDateTime, -): number { - const utcEpochNano = toEpochNano(plainDateTime) - const offsetDayBefore = timeZoneProtocol.getOffsetNanosecondsFor( - new Instant(utcEpochNano.sub(nanoInDay)), - ) - const offsetDayAfter = timeZoneProtocol.getOffsetNanosecondsFor( - new Instant(utcEpochNano.add(nanoInDay)), - ) - return offsetDayAfter - offsetDayBefore -} diff --git a/packages/temporal-polyfill/src/dateUtils/totalUnits.ts b/packages/temporal-polyfill/src/dateUtils/totalUnits.ts deleted file mode 100644 index 07f73293..00000000 --- a/packages/temporal-polyfill/src/dateUtils/totalUnits.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { durationUnitNames } from '../argParse/unitStr' -import { durationDayTimeToNano } from './dayAndTime' -import { DiffableObj } from './diff' -import { - DurationFields, - UnsignedDurationFields, - computeLargestDurationUnit, -} from './durationFields' -import { spanDurationFromDateTime } from './durationSpan' -import { toEpochNano } from './epoch' -import { DAY, UnitInt, YEAR, isDayTimeUnit, nanoIn } from './units' - -export function computeTotalUnits( - duration: DurationFields, - unit: UnitInt, - relativeTo: DiffableObj | undefined, - calendar: Temporal.CalendarProtocol | undefined, -): number { - if ( - relativeTo === undefined && - computeLargestDurationUnit(duration) <= DAY && - isDayTimeUnit(unit) - ) { - // TODO: accidentaly loss of precision? - return durationDayTimeToNano(duration).toNumber() / nanoIn[unit] - } - - if (!relativeTo) { - throw new RangeError('Need relativeTo') - } - - const [balancedDuration, relativeToTranslated] = spanDurationFromDateTime( - duration, - unit, - relativeTo, - calendar!, - true, // dissolveWeeks - ) - - const durationLike = computeExactDuration( - balancedDuration, - unit, - relativeTo, - relativeToTranslated, - ) - - const unitName = durationUnitNames[unit] as keyof UnsignedDurationFields - return durationLike[unitName] -} - -// TODO: rename to computeFracDuration -// PRECONDITION: dates have same calendar -// RETURNS: raw duration fields that might have floating-point values -// Those floating-point values will need to rounded before creating a proper Duration -export function computeExactDuration( - balancedDuration: DurationFields, - smallestUnit: UnitInt, - dt0: DiffableObj, - dt1: DiffableObj, -): DurationFields { - const smallestUnitName = durationUnitNames[smallestUnit] as keyof UnsignedDurationFields - const { sign } = balancedDuration - - if (!sign) { // prevents division by zero - return balancedDuration - } - - // make a new duration object that excludes units smaller than smallestUnit - const dur: Partial = {} - for (let unit = YEAR; unit >= smallestUnit; unit--) { - const durationUnit = durationUnitNames[unit] as keyof UnsignedDurationFields - dur[durationUnit] = balancedDuration[durationUnit] - } - - // a single additional unit of `unit` - const incDur: Partial = { [smallestUnitName]: sign } - const startDateTime = dt0.add(dur) - const endDateTime = startDateTime.add(incDur) - - const startNano = toEpochNano(startDateTime) - const endNano = toEpochNano(endDateTime) - const middleNano = toEpochNano(dt1) - const unitFrac = - middleNano.sub(startNano).toNumber() / - endNano.sub(startNano).toNumber() * sign - - dur[smallestUnitName]! += unitFrac // above loop populated this - return dur as DurationFields -} diff --git a/packages/temporal-polyfill/src/dateUtils/translate.ts b/packages/temporal-polyfill/src/dateUtils/translate.ts deleted file mode 100644 index e023b328..00000000 --- a/packages/temporal-polyfill/src/dateUtils/translate.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { OverflowHandlingInt } from '../argParse/overflowHandling' -import { constrainInt } from '../argParse/refine' -import { CalendarImpl } from '../calendarImpl/calendarImpl' -import { Instant } from '../public/instant' -import { createDate } from '../public/plainDate' -import { createDateTime } from '../public/plainDateTime' -import { LargeInt } from '../utils/largeInt' -import { - durationDayTimeToNano, - durationTimeToNano, - isoTimeToNano, - nanoToDuration, - nanoToISOTime, - zeroDurationTimeFields, -} from './dayAndTime' -import { DiffableObj, diffAccurate } from './diff' -import { DurationFields, computeLargestDurationUnit, overrideDuration } from './durationFields' -import { - addDaysMilli, - epochMilliToISOFields, - epochNanoSymbol, - epochNanoToISOFields, - isoFieldsToEpochNano, - isoToEpochMilli, -} from './epoch' -import { - ISODateFields, - ISODateTimeFields, - ISOTimeFields, -} from './isoFields' -import { LocalDateFields } from './localFields' -import { getInstantFor } from './timeZone' -import { DAY, DayTimeUnitInt, nanoInDay } from './units' - -type TranslatableObj = ISODateTimeFields & { calendar: Temporal.CalendarProtocol } -type ZonedTranslatableObj = TranslatableObj & { timeZone: Temporal.TimeZoneProtocol } - -export function translateZonedDateTimeFields( - fields: ZonedTranslatableObj, - duration: DurationFields, - options: Temporal.AssignmentOptions | undefined, // Calendar needs these options to be raw -): LargeInt { - const { calendar, timeZone } = fields - - // add date fields first - const translatedDate = calendar.dateAdd( - createDate(fields), - // don't let calendar round time fields to day - overrideDuration(duration, zeroDurationTimeFields), - options, - ) - - // add time-of-day back in - const translatedDateTime = createDateTime({ - ...fields, - ...translatedDate.getISOFields(), - }) - - // add time fields of duration - const translatedInstant = getInstantFor(timeZone, translatedDateTime) as Instant // TODO: better - return translatedInstant[epochNanoSymbol].add(durationTimeToNano(duration)) -} - -export function translateDateTime( - fields: TranslatableObj, - duration: DurationFields, - options: Temporal.AssignmentOptions | undefined, // Calendar needs raw options -): ISODateTimeFields { - const { calendar } = fields - - // add large fields first - const date = calendar.dateAdd( - createDate(fields), - // don't let calendar round time fields to day - overrideDuration(duration, zeroDurationTimeFields), - options, - ) - - const epochNano = isoFieldsToEpochNano(date.getISOFields()) - .add(isoTimeToNano(fields)) // restore original time-of-day - .add(durationTimeToNano(duration)) // add duration time parts - - return epochNanoToISOFields(epochNano) -} - -export function translateDate( - dateFields: LocalDateFields, - durationFields: DurationFields, - calendarImpl: CalendarImpl, - overflowHandling: OverflowHandlingInt, -): ISODateFields { - dateFields = addYears(dateFields, durationFields.years, calendarImpl, overflowHandling) - dateFields = addMonths(dateFields, durationFields.months, calendarImpl, overflowHandling) - - let epochMilli = calendarImpl.epochMilliseconds(dateFields.year, dateFields.month, dateFields.day) - - const daysFromTime = Math.trunc(durationTimeToNano(durationFields).div(nanoInDay).toNumber()) - const days = durationFields.weeks * 7 + durationFields.days + daysFromTime - epochMilli = addDaysMilli(epochMilli, days) - - return epochMilliToISOFields(epochMilli) -} - -export function addYears( - { year, month, day }: LocalDateFields, - yearsToAdd: number, - calendarImpl: CalendarImpl, - overflowHandling: OverflowHandlingInt, -): LocalDateFields { - year += yearsToAdd - const newMonth = constrainInt(month, 1, calendarImpl.monthsInYear(year), overflowHandling) - let newDay = month === newMonth ? day : 1 // month was constrained? reset day - newDay = constrainInt(newDay, 1, calendarImpl.daysInMonth(year, newMonth), overflowHandling) - return { year, month: newMonth, day: newDay } -} - -export function addMonths( - { year, month, day }: LocalDateFields, - monthsToAdd: number, - calendarImpl: CalendarImpl, - overflowHandling: OverflowHandlingInt, -): LocalDateFields { - if (monthsToAdd) { - month += monthsToAdd - - if (monthsToAdd < 0) { - while (month < 1) { - month += calendarImpl.monthsInYear(--year) - } - } else { - let monthsInYear - while (month > (monthsInYear = calendarImpl.monthsInYear(year))) { - month -= monthsInYear - year++ - } - } - - day = constrainInt(day, 1, calendarImpl.daysInMonth(year, month), overflowHandling) - } - return { year, month, day } -} - -export function addDays( - { isoYear, isoMonth, isoDay }: ISODateFields, - days: number, -): ISODateFields { - if (days) { - let epochMilli = isoToEpochMilli(isoYear, isoMonth, isoDay) - epochMilli = addDaysMilli(epochMilli, days) - ;({ isoYear, isoMonth, isoDay } = epochMilliToISOFields(epochMilli)) - } - return { isoYear, isoMonth, isoDay } -} - -export function translateTime( - timeFields: ISOTimeFields, - durationFields: DurationFields, -): ISOTimeFields { - // TODO: will loss of precision cause a bug? - const nano = isoTimeToNano(timeFields) + durationTimeToNano(durationFields).toNumber() - const [newTimeFields] = nanoToISOTime(nano) - return newTimeFields -} - -export function translateEpochNano(epochNano: LargeInt, durationFields: DurationFields): LargeInt { - const largestUnit = computeLargestDurationUnit(durationFields) - - if (largestUnit >= DAY) { - throw new RangeError('Duration cant have units >= days') - } - - return epochNano.add(durationTimeToNano(durationFields)) -} - -// duration - -export function addDurationFields( - d0: DurationFields, // should be added to relativeToArg FIRST - d1: DurationFields, // should be added to relativeToArg SECOND - relativeTo: DiffableObj | undefined, - calendar: Temporal.CalendarProtocol | undefined, -): DurationFields { - const largestUnit = Math.max( - computeLargestDurationUnit(d0), - computeLargestDurationUnit(d1), - ) as DayTimeUnitInt - - if (relativeTo === undefined && largestUnit <= DAY) { - return nanoToDuration( - durationDayTimeToNano(d0).add(durationDayTimeToNano(d1)), - largestUnit, - ) - } - - if (!relativeTo) { - throw new RangeError('Need relativeTo') - } - - const translated = relativeTo.add(d0).add(d1) - return diffAccurate(relativeTo, translated, calendar!, largestUnit) -} diff --git a/packages/temporal-polyfill/src/dateUtils/units.ts b/packages/temporal-polyfill/src/dateUtils/units.ts deleted file mode 100644 index 51dd792b..00000000 --- a/packages/temporal-polyfill/src/dateUtils/units.ts +++ /dev/null @@ -1,54 +0,0 @@ -// TODO: rename everything to 'sec'/'min'? - -export type TimeUnitInt = 0 | 1 | 2 | 3 | 4 | 5 -export type YearMonthUnitInt = 8 | 9 -export type DateUnitInt = 6 | 7 | YearMonthUnitInt -export type UnitInt = TimeUnitInt | DateUnitInt -export type DayTimeUnitInt = 6 | TimeUnitInt - -export const NANOSECOND = 0 -export const MICROSECOND = 1 -export const MILLISECOND = 2 -export const SECOND = 3 -export const MINUTE = 4 -export const HOUR = 5 -export const DAY = 6 -export const WEEK = 7 -export const MONTH = 8 -export const YEAR = 9 - -export const nanoInMicro = 1000 -export const nanoInMilli = 1000000 -export const nanoInSecond = 1000000000 -export const nanoInMinute = 60000000000 -export const nanoInHour = 3600000000000 -export const nanoInDay = 86400000000000 -export const nanoIn = [ - 1, - nanoInMicro, - nanoInMilli, - nanoInSecond, - nanoInMinute, - nanoInHour, - nanoInDay, -] - -export const milliInDay = 86400000 -export const milliInHour = 3600000 -export const milliInMinute = 60000 -export const milliInSecond = 1000 -export const secondsInDay = 24 * 60 * 60 - -export const unitDigitMap = [ // how many digits after the decimal point for a seconds value - 9, // nanoseconds - 6, // microseconds - 3, // milliseconds -] - -export function isDayTimeUnit(unit: UnitInt): unit is DayTimeUnitInt { - return unit <= DAY -} - -export function isDateUnit(unit: UnitInt): unit is DateUnitInt { - return unit >= DAY -} diff --git a/packages/temporal-polyfill/src/dateUtils/week.ts b/packages/temporal-polyfill/src/dateUtils/week.ts deleted file mode 100644 index d5502b40..00000000 --- a/packages/temporal-polyfill/src/dateUtils/week.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { isoCalendarImpl } from '../calendarImpl/isoCalendarImpl' -import { positiveModulo } from '../utils/math' -import { computeDayOfYear, computeDaysInYear } from './calendar' -import { computeISODayOfWeek } from './epoch' - -// TODO: fix lots of 1-index problems!!! - -export function computeWeekOfISOYear( - isoYear: number, - isoMonth: number, - isoDay: number, - firstDay: number, - minimalDays: number, -): number { - // Days to ignore till first week - const weekOffset = computeFirstWeekOffset( - isoYear, - firstDay, - minimalDays, - ) - // Current week # - const week = - Math.floor( - (computeDayOfYear(isoCalendarImpl, isoYear, isoMonth, isoDay) - weekOffset - 1) / 7, - ) + 1 - - // Go to previous year if 0 weeks - if (week < 1) { - return week + computeWeeksInISOYear(isoYear - 1, firstDay, minimalDays) - } - - const weeksYear = computeWeeksInISOYear(isoYear, firstDay, minimalDays) - - // Go to next year if greater than weeks in current year - if (week > weeksYear) { - return week - weeksYear - } - - return week -} - -function computeFirstWeekOffset( - isoYear: number, - firstDay: number, - minimalDays: number, -): number { - // Which january is always in the first week (4 for iso, 1 for other) - const firstWeekDay = 7 + firstDay - minimalDays - - // Which local weekday is first week day - const localWeek = positiveModulo( - computeISODayOfWeek(isoYear, 1, firstWeekDay) - firstDay, - 7, - ) - - return -localWeek + firstWeekDay - 1 -} - -function computeWeeksInISOYear( - isoYear: number, - firstDay: number, - minimalDays: number, -): number { - const weekOffset = computeFirstWeekOffset(isoYear, firstDay, minimalDays) - const weekOffsetNext = computeFirstWeekOffset( - isoYear + 1, - firstDay, - minimalDays, - ) - return ( - (computeDaysInYear(isoCalendarImpl, isoYear) - - weekOffset + - weekOffsetNext) / - 7 - ) -} diff --git a/packages/temporal-polyfill/src/funcApi/duration.ts b/packages/temporal-polyfill/src/funcApi/duration.ts new file mode 100644 index 00000000..43eaecb2 --- /dev/null +++ b/packages/temporal-polyfill/src/funcApi/duration.ts @@ -0,0 +1,109 @@ +import { NumSign, identityFunc } from '../internal/utils' +import { UnitName } from '../internal/units' +import { queryNativeTimeZone } from '../internal/timeZoneNative' +import { DurationRoundOptions, RelativeToOptions, TotalUnitOptionsWithRel } from '../internal/optionsRefine' +import { DurationSlots, PlainDateSlots, ZonedDateTimeSlots } from '../internal/slots' +import { createNativeDiffOps } from '../internal/calendarNativeQuery' +import { constructDurationSlots } from '../internal/construct' +import { parseDuration } from '../internal/parseIso' +import { durationWithFields, refineDurationBag } from '../internal/bag' +import { absDuration, addDurations, negateDuration, queryDurationBlank, queryDurationSign, roundDuration } from '../internal/durationMath' +import { totalDuration } from '../internal/total' +import { formatDurationIso } from '../internal/formatIso' +import { compareDurations } from '../internal/compare' + +type RelativeToArg = ZonedDateTimeSlots | PlainDateSlots + +export const create = constructDurationSlots + +export const fromString = parseDuration + +export const fromFields = refineDurationBag + +export const withFields = durationWithFields + +export function add( + slots: DurationSlots, + otherSlots: DurationSlots, + options?: RelativeToOptions, +): DurationSlots { + return addDurations( + identityFunc, + createNativeDiffOps, + queryNativeTimeZone, + slots, + otherSlots, + options, + ) +} + +export function subtract( + slots: DurationSlots, + otherSlots: DurationSlots, + options?: RelativeToOptions, +): DurationSlots { + return addDurations( + identityFunc, + createNativeDiffOps, + queryNativeTimeZone, + slots, + otherSlots, + options, + true, + ) +} + +export const negated = negateDuration + +export const abs = absDuration + +export function round( + slots: DurationSlots, + options: DurationRoundOptions, +): DurationSlots { + return roundDuration( + identityFunc, + createNativeDiffOps, + queryNativeTimeZone, + slots, + options, + ) +} + +export function total( + slots: DurationSlots, + options: TotalUnitOptionsWithRel | UnitName, +): number { + return totalDuration( + identityFunc, + createNativeDiffOps, + queryNativeTimeZone, + slots, + options, + ) +} + +export const toString = formatDurationIso + +export function toJSON(slots: DurationSlots): string { + return toString(slots) +} + +export const sign = queryDurationSign // TODO: prevent other args + +export const blank = queryDurationBlank // TODO: prevent other args + +export function compare( + durationSlots0: DurationSlots, + durationSlots1: DurationSlots, + options?: RelativeToOptions, +): NumSign { + return compareDurations( + identityFunc, + createNativeDiffOps, + queryNativeTimeZone, + durationSlots0, + durationSlots1, + options, + ) +} diff --git a/packages/temporal-polyfill/src/funcApi/formatIntlCached.ts b/packages/temporal-polyfill/src/funcApi/formatIntlCached.ts new file mode 100644 index 00000000..972d12cb --- /dev/null +++ b/packages/temporal-polyfill/src/funcApi/formatIntlCached.ts @@ -0,0 +1,45 @@ +import { BasicZonedDateTimeSlots, FormatQuerier, LocalesArg, OptionsTransformer, createFormat, createFormatPrepper, getCommonTimeZoneId, instantConfig, plainDateConfig, plainDateTimeConfig, plainMonthDayConfig, plainTimeConfig, plainYearMonthConfig, zonedDateTimeConfig } from '../internal/formatIntl' +import { createLazyGenerator } from '../internal/utils' + +function createFormatCache( + hashSlots?: (slots0: S, slots1?: S) => string, +): FormatQuerier { + const queryFormatFactory = createLazyGenerator((options: Intl.DateTimeFormatOptions) => { + const map = new Map() + + return ( + locales: LocalesArg | undefined, + transformOptions: OptionsTransformer, + slots0: S, + slots1?: S, + ) => { + const key = ([] as string[]).concat( + hashSlots ? [hashSlots(slots0, slots1)] : [], + locales || [], + ).join() + + let format = map.get(key) + if (!format) { + format = createFormat(locales, options, transformOptions, slots0, slots1) + map.set(key, format) + } + + return format + } + }, WeakMap) + + return (locales, options, transformOptions, slots0, slots1) => { + return queryFormatFactory(options)(locales, transformOptions, slots0, slots1) + } +} + +export const prepCachedPlainYearMonthFormat = createFormatPrepper(plainYearMonthConfig, createFormatCache()) +export const prepCachedPlainMonthDayFormat = createFormatPrepper(plainMonthDayConfig, createFormatCache()) +export const prepCachedPlainDateFormat = createFormatPrepper(plainDateConfig, createFormatCache()) +export const prepCachedPlainDateTimeFormat = createFormatPrepper(plainDateTimeConfig, createFormatCache()) +export const prepCachedPlainTimeFormat = createFormatPrepper(plainTimeConfig, createFormatCache()) +export const prepCachedInstantFormat = createFormatPrepper(instantConfig, createFormatCache()) +export const prepCachedZonedDateTimeFormat = createFormatPrepper( + zonedDateTimeConfig, + createFormatCache(getCommonTimeZoneId), +) diff --git a/packages/temporal-polyfill/src/funcApi/instant.ts b/packages/temporal-polyfill/src/funcApi/instant.ts new file mode 100644 index 00000000..5aa3935b --- /dev/null +++ b/packages/temporal-polyfill/src/funcApi/instant.ts @@ -0,0 +1,137 @@ +import { LocalesArg } from '../internal/formatIntl' +import { queryNativeTimeZone } from '../internal/timeZoneNative' +import { DiffOptions, InstantDisplayOptions } from '../internal/optionsRefine' +import { DurationSlots, InstantSlots, ZonedDateTimeSlots, refineCalendarSlotString, refineTimeZoneSlotString } from '../internal/slots' +import { constructInstantSlots } from '../internal/construct' +import { parseInstant } from '../internal/parseIso' +import { epochMicroToInstant, epochMilliToInstant, epochNanoToInstant, epochSecToInstant, instantToZonedDateTime } from '../internal/convert' +import { moveInstant } from '../internal/move' +import { compareInstants, instantsEqual } from '../internal/compare' +import { formatInstantIso } from '../internal/formatIso' +import { diffInstants } from '../internal/diff' +import { roundInstant } from '../internal/round' +import { prepCachedInstantFormat } from './formatIntlCached' + +export const create = constructInstantSlots + +export const fromString = parseInstant + +export const fromEpochSeconds = epochSecToInstant + +export const fromEpochMilliseconds = epochMilliToInstant + +export const fromEpochMicroseconds = epochMicroToInstant + +export const fromEpochNanoseconds = epochNanoToInstant + +export function add(instantSlots: InstantSlots, durationSlots: DurationSlots): InstantSlots { + return moveInstant(instantSlots, durationSlots) +} + +export function subtract(instantSlots: InstantSlots, durationSlots: DurationSlots): InstantSlots { + return moveInstant(instantSlots, durationSlots, true) +} + +export function until( + instantSlots0: InstantSlots, + instantSlots1: InstantSlots, + options?: DiffOptions, +): DurationSlots { + return diffInstants(instantSlots0, instantSlots1, options) +} + +export function since( + instantSlots0: InstantSlots, + instantSlots1: InstantSlots, + options?: DiffOptions, +): DurationSlots { + return diffInstants(instantSlots0, instantSlots1, options, true) +} + +export const round = roundInstant + +export const equals = instantsEqual + +export const compare = compareInstants + +export function toString( + instantSlots: InstantSlots, + options?: InstantDisplayOptions, +): string { + return formatInstantIso( + refineTimeZoneSlotString, + queryNativeTimeZone, + instantSlots, + options, + ) +} + +export function toJSON( + instantSlots: InstantSlots, +): string { + return formatInstantIso( + refineTimeZoneSlotString, + queryNativeTimeZone, + instantSlots, + ) +} + +export function toZonedDateTimeISO( + instantSlots: InstantSlots, + timeZoneSlot: string, +): ZonedDateTimeSlots { + return instantToZonedDateTime( + instantSlots, + refineTimeZoneSlotString(timeZoneSlot), + ) +} + +export function toZonedDateTime( + instantSlots: InstantSlots, + options: { timeZone: string, calendar: string } +): ZonedDateTimeSlots { + return instantToZonedDateTime( + instantSlots, + refineTimeZoneSlotString(options.timeZone), + refineCalendarSlotString(options.calendar), + ) +} + +export function toLocaleString( + slots: InstantSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli] = prepCachedInstantFormat(locales, options, slots) + return format.format(epochMilli) +} + +export function toLocaleStringParts( + slots: InstantSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): Intl.DateTimeFormatPart[] { + const [format, epochMilli] = prepCachedInstantFormat(locales, options, slots) + return format.formatToParts(epochMilli) +} + +export function rangeToLocaleString( + slots0: InstantSlots, + slots1: InstantSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli0, epochMilli1] = prepCachedInstantFormat(locales, options, slots0, slots1) + return (format as any).formatRange(epochMilli0, epochMilli1!) +} + +export function rangeToLocaleStringParts( + slots0: InstantSlots, + slots1: InstantSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, + ): Intl.DateTimeFormatPart[] { + const [format, epochMilli0, epochMilli1] = prepCachedInstantFormat(locales, options, slots0, slots1) + return (format as any).formatRangeToParts(epochMilli0, epochMilli1!) +} + diff --git a/packages/temporal-polyfill/src/funcApi/now.ts b/packages/temporal-polyfill/src/funcApi/now.ts new file mode 100644 index 00000000..1c577744 --- /dev/null +++ b/packages/temporal-polyfill/src/funcApi/now.ts @@ -0,0 +1,77 @@ +import { getCurrentEpochNanoseconds, getCurrentIsoDateTime, getCurrentTimeZoneId } from '../internal/current' +import { InstantBranding, InstantSlots, PlainDateBranding, PlainDateSlots, PlainDateTimeBranding, PlainDateTimeSlots, PlainTimeBranding, PlainTimeSlots, ZonedDateTimeBranding, ZonedDateTimeSlots, createInstantSlots, createPlainDateTimeSlots, createPlainDateSlots, createPlainTimeSlots, createZonedDateTimeSlots, refineCalendarSlotString, refineTimeZoneSlotString } from '../internal/slots' +import { isoCalendarId } from '../internal/calendarConfig' +import { queryNativeTimeZone } from '../internal/timeZoneNative' + +export const timeZoneId = getCurrentTimeZoneId + +export function instant(): InstantSlots { + return createInstantSlots( + getCurrentEpochNanoseconds(), + ) +} + +export function zonedDateTime( + calendarId: string, + timeZoneId: string = getCurrentTimeZoneId(), +): ZonedDateTimeSlots { + return createZonedDateTimeSlots( + getCurrentEpochNanoseconds(), + refineTimeZoneSlotString(timeZoneId), + refineCalendarSlotString(calendarId), + ) +} + +export function zonedDateTimeISO( + timeZoneId: string = getCurrentTimeZoneId(), +): ZonedDateTimeSlots { + return createZonedDateTimeSlots( + getCurrentEpochNanoseconds(), + refineTimeZoneSlotString(timeZoneId), + isoCalendarId, + ) +} + +export function plainDateTime( + calendarId: string, + timeZoneId: string = getCurrentTimeZoneId(), +): PlainDateTimeSlots { + return createPlainDateTimeSlots( + getCurrentIsoDateTime(queryNativeTimeZone(refineTimeZoneSlotString(timeZoneId))), + refineCalendarSlotString(calendarId), + ) +} + +export function plainDateTimeISO( + timeZoneId: string = getCurrentTimeZoneId(), +): PlainDateTimeSlots { + return createPlainDateTimeSlots( + getCurrentIsoDateTime(queryNativeTimeZone(refineTimeZoneSlotString(timeZoneId))), + isoCalendarId, + ) +} + +export function plainDate( + calendarId: string, + timeZoneId: string = getCurrentTimeZoneId(), +): PlainDateSlots { + return createPlainDateSlots( + getCurrentIsoDateTime(queryNativeTimeZone(refineTimeZoneSlotString(timeZoneId))), + refineCalendarSlotString(calendarId), + ) +} + +export function plainDateISO( + timeZoneId: string = getCurrentTimeZoneId(), +): PlainDateSlots { + return createPlainDateSlots( + getCurrentIsoDateTime(queryNativeTimeZone(refineTimeZoneSlotString(timeZoneId))), + isoCalendarId, + ) +} + +export function plainTimeISO(timeZoneId: string): PlainTimeSlots { + return createPlainTimeSlots( + getCurrentIsoDateTime(queryNativeTimeZone(refineTimeZoneSlotString(timeZoneId))), + ) +} diff --git a/packages/temporal-polyfill/src/funcApi/plainDate.ts b/packages/temporal-polyfill/src/funcApi/plainDate.ts new file mode 100644 index 00000000..679da062 --- /dev/null +++ b/packages/temporal-polyfill/src/funcApi/plainDate.ts @@ -0,0 +1,214 @@ +import { DateBag } from '../internal/calendarFields' +import { identityFunc } from '../internal/utils' +import { LocalesArg } from '../internal/formatIntl' +import { queryNativeTimeZone } from '../internal/timeZoneNative' +import { DiffOptions, OverflowOptions } from '../internal/optionsRefine' +import { DurationSlots, PlainDateSlots, PlainMonthDaySlots, PlainTimeSlots, PlainYearMonthSlots, ZonedDateTimeSlots, getCalendarIdFromBag, refineCalendarSlotString, refineTimeZoneSlotString } from '../internal/slots' +import { createNativeDateModOps, createNativeDateRefineOps, createNativeDiffOps, createNativeMonthDayRefineOps, createNativeMoveOps, createNativePartOps, createNativeYearMonthRefineOps } from '../internal/calendarNativeQuery' +import { constructPlainDateSlots } from '../internal/construct' +import { parsePlainDate } from '../internal/parseIso' +import { plainDateWithFields, refinePlainDateBag } from '../internal/bag' +import { slotsWithCalendar } from '../internal/mod' +import { movePlainDate } from '../internal/move' +import { diffPlainDates } from '../internal/diff' +import { plainDatesEqual, compareIsoDateFields } from '../internal/compare' +import { formatPlainDateIso } from '../internal/formatIso' +import { plainDateToPlainDateTime, plainDateToPlainMonthDay, plainDateToPlainYearMonth, plainDateToZonedDateTime } from '../internal/convert' +import { prepCachedPlainDateFormat } from './formatIntlCached' +import { getDayOfYear, getDaysInMonth, getDaysInYear, getInLeapYear, getMonthsInYear, getDateFields } from './utils' +import { computeIsoDayOfWeek, computeIsoDaysInWeek, computeIsoWeekOfYear, computeIsoYearOfWeek } from '../internal/calendarIso' + +// TODO: do Readonly<> everywhere? + +export function create( + isoYear: number, + isoMonth: number, + isoDay: number, + calendar?: string, +): PlainDateSlots { + return constructPlainDateSlots(refineCalendarSlotString, isoYear, isoMonth, isoDay, calendar) +} + +export const fromString = parsePlainDate + +export function fromFields( + fields: DateBag & { calendar?: string }, + options?: OverflowOptions, +): PlainDateSlots { + return refinePlainDateBag( + createNativeDateRefineOps(getCalendarIdFromBag(fields)), + fields, + options, + ) +} + +// TODO: add specific types +export const getFields = getDateFields +export const dayOfWeek = computeIsoDayOfWeek +export const daysInWeek = computeIsoDaysInWeek +export const weekOfYear = computeIsoWeekOfYear +export const yearOfWeek = computeIsoYearOfWeek +export const dayOfYear = getDayOfYear +export const daysInMonth = getDaysInMonth +export const daysInYear = getDaysInYear +export const monthsInYear = getMonthsInYear +export const inLeapYear = getInLeapYear + +export function withFields( + slots: PlainDateSlots, + newFields: DateBag, + options?: OverflowOptions, +): PlainDateSlots { + return plainDateWithFields( + createNativeDateModOps, + slots, + getFields(slots), // TODO: just use y/m/d? + newFields, + options, + ) +} + +export function withCalendar( + slots: PlainDateSlots, + calendarId: string, +): PlainDateSlots { + return slotsWithCalendar(slots, refineCalendarSlotString(calendarId)) +} + +export function add( + slots: PlainDateSlots, + durationSlots: DurationSlots, + options?: OverflowOptions, +): PlainDateSlots { + return movePlainDate( + createNativeMoveOps, + slots, + durationSlots, + options, + ) +} + +export function subtract( + slots: PlainDateSlots, + durationSlots: DurationSlots, + options?: OverflowOptions, +): PlainDateSlots { + return movePlainDate( + createNativeMoveOps, + slots, + durationSlots, + options, + true, + ) +} + +export function until( + slots0: PlainDateSlots, + slots1: PlainDateSlots, + options?: DiffOptions, +): DurationSlots { + return diffPlainDates( + createNativeDiffOps, + slots0, + slots1, + options, + ) +} + +export function since( + slots0: PlainDateSlots, + slots1: PlainDateSlots, + options?: DiffOptions, +): DurationSlots { + return diffPlainDates( + createNativeDiffOps, + slots0, + slots1, + options, + true, + ) +} + +// TODO: specific args +export const equals = plainDatesEqual +export const compare = compareIsoDateFields +export const toString = formatPlainDateIso + +export function toZonedDateTime( + slots: PlainDateSlots, + options: string | { timeZone: string, plainTime?: PlainTimeSlots }, +): ZonedDateTimeSlots { + const optionsObj = typeof options === 'string' + ? { timeZone: options } + : options + + return plainDateToZonedDateTime( + refineTimeZoneSlotString, + identityFunc, + queryNativeTimeZone, + slots, + optionsObj, + ) +} + +export const toPlainDateTime = plainDateToPlainDateTime + +export function toPlainYearMonth(slots: PlainDateSlots): PlainYearMonthSlots { + const calenadarOps = createNativePartOps(slots.calendar) + const [year, month, day] = calenadarOps.dateParts(slots) // TODO: DRY + + return plainDateToPlainYearMonth( + createNativeYearMonthRefineOps, + slots, + { year, month, day }, + ) +} + +export function toPlainMonthDay(slots: PlainDateSlots): PlainMonthDaySlots { + const calenadarOps = createNativePartOps(slots.calendar) + const [year, month, day] = calenadarOps.dateParts(slots) // TODO: DRY + + return plainDateToPlainMonthDay( + createNativeMonthDayRefineOps, + slots, + { year, month, day }, + ) +} + +export function toLocaleString( + slots: PlainDateSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli] = prepCachedPlainDateFormat(locales, options, slots) + return format.format(epochMilli) +} + +export function toLocaleStringParts( + slots: PlainDateSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): Intl.DateTimeFormatPart[] { + const [format, epochMilli] = prepCachedPlainDateFormat(locales, options, slots) + return format.formatToParts(epochMilli) +} + +export function rangeToLocaleString( + slots0: PlainDateSlots, + slots1: PlainDateSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli0, epochMilli1] = prepCachedPlainDateFormat(locales, options, slots0, slots1) + return (format as any).formatRange(epochMilli0, epochMilli1!) +} + +export function rangeToLocaleStringParts( + slots0: PlainDateSlots, + slots1: PlainDateSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, + ): Intl.DateTimeFormatPart[] { + const [format, epochMilli0, epochMilli1] = prepCachedPlainDateFormat(locales, options, slots0, slots1) + return (format as any).formatRangeToParts(epochMilli0, epochMilli1!) +} diff --git a/packages/temporal-polyfill/src/funcApi/plainDateTime.ts b/packages/temporal-polyfill/src/funcApi/plainDateTime.ts new file mode 100644 index 00000000..6f2f1934 --- /dev/null +++ b/packages/temporal-polyfill/src/funcApi/plainDateTime.ts @@ -0,0 +1,208 @@ +import { DateTimeBag, DateTimeFields, EraYearFields } from '../internal/calendarFields' +import { LocalesArg } from '../internal/formatIntl' +import { queryNativeTimeZone } from '../internal/timeZoneNative' +import { DiffOptions, EpochDisambigOptions, OverflowOptions } from '../internal/optionsRefine' +import { DurationSlots, PlainDateTimeSlots, PlainMonthDaySlots, PlainYearMonthSlots, ZonedDateTimeSlots, createPlainDateSlots, createPlainTimeSlots, refineCalendarSlotString } from '../internal/slots' +import { createNativeDateModOps, createNativeDiffOps, createNativeMonthDayRefineOps, createNativeMoveOps, createNativePartOps, createNativeYearMonthRefineOps } from '../internal/calendarNativeQuery' +import { constructPlainDateTimeSlots } from '../internal/construct' +import { parsePlainDateTime } from '../internal/parseIso' +import { isoTimeFieldsToCal, plainDateTimeWithFields, refinePlainDateTimeBag } from '../internal/bag' +import { plainDateTimeWithPlainDate, plainDateTimeWithPlainTime, slotsWithCalendar } from '../internal/mod' +import { movePlainDateTime } from '../internal/move' +import { diffPlainDateTimes } from '../internal/diff' +import { roundPlainDateTime } from '../internal/round' +import { plainDateTimesEqual, compareIsoDateTimeFields } from '../internal/compare' +import { formatPlainDateTimeIso } from '../internal/formatIso' +import { plainDateTimeToPlainMonthDay, plainDateTimeToPlainYearMonth, plainDateTimeToZonedDateTime } from '../internal/convert' +import { prepCachedPlainDateTimeFormat } from './formatIntlCached' +import { getDayOfYear, getDaysInMonth, getDaysInYear, getInLeapYear, getMonthsInYear, getDateFields } from './utils' +import { computeIsoDayOfWeek, computeIsoDaysInWeek, computeIsoWeekOfYear, computeIsoYearOfWeek } from '../internal/calendarIso' + +// TODO: do Readonly<> everywhere? + +export function create( + isoYear: number, + isoMonth: number, + isoDay: number, + isoHour?: number, + isoMinute?: number, + isoSecond?: number, + isoMillisecond?: number, + isoMicrosecond?: number, + isoNanosecond?: number, + calendar?: string, +): PlainDateTimeSlots { + return constructPlainDateTimeSlots( + refineCalendarSlotString, + isoYear, isoMonth, isoDay, + isoHour, isoMinute, isoSecond, + isoMillisecond, isoMicrosecond, isoNanosecond, + calendar, + ) +} + +export const fromString = parsePlainDateTime + +export const fromFields = refinePlainDateTimeBag + +export function getFields(slots: PlainDateTimeSlots): DateTimeFields & Partial { + return { + ...getDateFields(slots), + ...isoTimeFieldsToCal(slots), + } +} + +// TODO: add specific types +export const dayOfWeek = computeIsoDayOfWeek +export const daysInWeek = computeIsoDaysInWeek +export const weekOfYear = computeIsoWeekOfYear +export const yearOfWeek = computeIsoYearOfWeek +export const dayOfYear = getDayOfYear +export const daysInMonth = getDaysInMonth +export const daysInYear = getDaysInYear +export const monthsInYear = getMonthsInYear +export const inLeapYear = getInLeapYear + +export function withFields( + plainDateTimeSlots: PlainDateTimeSlots, + newFields: DateTimeBag, + options?: OverflowOptions, +): PlainDateTimeSlots { + return plainDateTimeWithFields( + createNativeDateModOps, + plainDateTimeSlots, + getFields(plainDateTimeSlots), // TODO: just use y/m/d? + newFields, + options, + ) +} + +export const withPlainTime = plainDateTimeWithPlainTime // TODO: more specific type for PlainTimeSlots + +export const withPlainDate = plainDateTimeWithPlainDate + +export function withCalendar( + plainDateTimeSlots: PlainDateTimeSlots, + calendarId: string, +): PlainDateTimeSlots { + return slotsWithCalendar( + plainDateTimeSlots, + refineCalendarSlotString(calendarId), + ) +} + +export function add( + plainDateTimeSlots: PlainDateTimeSlots, + durationSlots: DurationSlots, + options?: OverflowOptions, +): PlainDateTimeSlots { + return movePlainDateTime(createNativeMoveOps, plainDateTimeSlots, durationSlots, options) +} + +export function subtract( + plainDateTimeSlots: PlainDateTimeSlots, + durationSlots: DurationSlots, + options?: OverflowOptions, +): PlainDateTimeSlots { + return movePlainDateTime(createNativeMoveOps, plainDateTimeSlots, durationSlots, options, true) +} + +export function until( + plainDateTimeSlots0: PlainDateTimeSlots, + plainDateTimeSlots1: PlainDateTimeSlots, + options?: DiffOptions, +): DurationSlots { + return diffPlainDateTimes(createNativeDiffOps, plainDateTimeSlots0, plainDateTimeSlots1, options) +} + +export function since( + plainDateTimeSlots0: PlainDateTimeSlots, + plainDateTimeSlots1: PlainDateTimeSlots, + options?: DiffOptions, +): DurationSlots { + return diffPlainDateTimes(createNativeDiffOps, plainDateTimeSlots0, plainDateTimeSlots1, options, true) +} + +export const round = roundPlainDateTime + +export const compare = compareIsoDateTimeFields + +export const equals = plainDateTimesEqual + +export const toString = formatPlainDateTimeIso + +export function toZonedDateTime( + plainDateTimeSlots: PlainDateTimeSlots, + timeZoneId: string, + options?: EpochDisambigOptions, +): ZonedDateTimeSlots { + return plainDateTimeToZonedDateTime(queryNativeTimeZone, plainDateTimeSlots, timeZoneId, options) +} + +export const toPlainDate = createPlainDateSlots // TODO: better type + +export function toPlainYearMonth( + plainDateTimeSlots: PlainDateTimeSlots, +): PlainYearMonthSlots { + const calendarOps = createNativePartOps(plainDateTimeSlots.calendar) + const [year, month, day] = calendarOps.dateParts(plainDateTimeSlots) // TODO: DRY + + return plainDateTimeToPlainYearMonth( + createNativeYearMonthRefineOps, + plainDateTimeSlots, + { year, month, day }, + ) +} + +export function toPlainMonthDay( + plainDateTimeSlots: PlainDateTimeSlots, +): PlainMonthDaySlots { + const calendarOps = createNativePartOps(plainDateTimeSlots.calendar) + const [year, month, day] = calendarOps.dateParts(plainDateTimeSlots) // TODO: DRY + + return plainDateTimeToPlainMonthDay( + createNativeMonthDayRefineOps, + plainDateTimeSlots, + { year, month, day }, + ) +} + +export const toPlainTime = createPlainTimeSlots // TODO: better type + +export function toLocaleString( + slots: PlainDateTimeSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli] = prepCachedPlainDateTimeFormat(locales, options, slots) + return format.format(epochMilli) +} + +export function toLocaleStringParts( + slots: PlainDateTimeSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): Intl.DateTimeFormatPart[] { + const [format, epochMilli] = prepCachedPlainDateTimeFormat(locales, options, slots) + return format.formatToParts(epochMilli) +} + +export function rangeToLocaleString( + slots0: PlainDateTimeSlots, + slots1: PlainDateTimeSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli0, epochMilli1] = prepCachedPlainDateTimeFormat(locales, options, slots0, slots1) + return (format as any).formatRange(epochMilli0, epochMilli1!) +} + +export function rangeToLocaleStringParts( + slots0: PlainDateTimeSlots, + slots1: PlainDateTimeSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, + ): Intl.DateTimeFormatPart[] { + const [format, epochMilli0, epochMilli1] = prepCachedPlainDateTimeFormat(locales, options, slots0, slots1) + return (format as any).formatRangeToParts(epochMilli0, epochMilli1!) +} diff --git a/packages/temporal-polyfill/src/funcApi/plainMonthDay.ts b/packages/temporal-polyfill/src/funcApi/plainMonthDay.ts new file mode 100644 index 00000000..27c9aec1 --- /dev/null +++ b/packages/temporal-polyfill/src/funcApi/plainMonthDay.ts @@ -0,0 +1,122 @@ +import { isoCalendarId } from '../internal/calendarConfig' +import { MonthDayBag, MonthDayFields, YearFields } from '../internal/calendarFields' +import { LocalesArg } from '../internal/formatIntl' +import { OverflowOptions } from '../internal/optionsRefine' +import { PlainDateSlots, PlainMonthDaySlots, extractCalendarIdFromBag, refineCalendarSlotString } from '../internal/slots' +import { createNativeDateModOps, createNativeMonthDayModOps, createNativeMonthDayParseOps, createNativeMonthDayRefineOps, createNativePartOps } from '../internal/calendarNativeQuery' +import { computeMonthDayFields } from '../internal/calendarNative' +import { constructPlainMonthDaySlots } from '../internal/construct' +import { parsePlainMonthDay } from '../internal/parseIso' +import { plainMonthDayWithFields, refinePlainMonthDayBag } from '../internal/bag' +import { plainMonthDaysEqual } from '../internal/compare' +import { formatPlainMonthDayIso } from '../internal/formatIso' +import { plainMonthDayToPlainDate } from '../internal/convert' +import { prepCachedPlainMonthDayFormat } from './formatIntlCached' + +export function create( + isoMonth: number, + isoDay: number, + calendar?: string, + referenceIsoYear?: number, +): PlainMonthDaySlots { + return constructPlainMonthDaySlots( + refineCalendarSlotString, + isoMonth, + isoDay, + calendar, + referenceIsoYear, + ) +} + +export function fromString(s: string): PlainMonthDaySlots { + return parsePlainMonthDay(createNativeMonthDayParseOps, s) +} + +export function fromFields( + fields: MonthDayBag & { calendar?: string }, + options?: OverflowOptions, +): PlainMonthDaySlots { + const calendarMaybe = extractCalendarIdFromBag(fields) + const calendar = calendarMaybe || isoCalendarId + + return refinePlainMonthDayBag( + createNativeMonthDayRefineOps(calendar), + !calendarMaybe, + fields, + options, + ) +} + +// TODO: put this in utils +export function getFields(slots: PlainMonthDaySlots): MonthDayFields { + const calendarOps = createNativePartOps(slots.calendar) + return computeMonthDayFields(calendarOps, slots) +} + +export function withFields( + plainMonthDaySlots: PlainMonthDaySlots, + modFields: MonthDayBag, + options?: OverflowOptions, +): PlainMonthDaySlots { + return plainMonthDayWithFields( + createNativeMonthDayModOps, + plainMonthDaySlots, + getFields(plainMonthDaySlots), + modFields, + options, + ) +} + +export const equals = plainMonthDaysEqual + +export const toString = formatPlainMonthDayIso + +export function toPlainDate( + plainMonthDaySlots: PlainMonthDaySlots, + bag: YearFields, +): PlainDateSlots { + return plainMonthDayToPlainDate( + createNativeDateModOps, + plainMonthDaySlots, + getFields(plainMonthDaySlots), + bag, + ) +} + +export function toLocaleString( + slots: PlainMonthDaySlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli] = prepCachedPlainMonthDayFormat(locales, options, slots) + return format.format(epochMilli) +} + +export function toLocaleStringParts( + slots: PlainMonthDaySlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): Intl.DateTimeFormatPart[] { + const [format, epochMilli] = prepCachedPlainMonthDayFormat(locales, options, slots) + return format.formatToParts(epochMilli) +} + +export function rangeToLocaleString( + slots0: PlainMonthDaySlots, + slots1: PlainMonthDaySlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli0, epochMilli1] = prepCachedPlainMonthDayFormat(locales, options, slots0, slots1) + return (format as any).formatRange(epochMilli0, epochMilli1!) +} + +export function rangeToLocaleStringParts( + slots0: PlainMonthDaySlots, + slots1: PlainMonthDaySlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, + ): Intl.DateTimeFormatPart[] { + const [format, epochMilli0, epochMilli1] = prepCachedPlainMonthDayFormat(locales, options, slots0, slots1) + return (format as any).formatRangeToParts(epochMilli0, epochMilli1!) +} diff --git a/packages/temporal-polyfill/src/funcApi/plainTime.ts b/packages/temporal-polyfill/src/funcApi/plainTime.ts new file mode 100644 index 00000000..1be24b77 --- /dev/null +++ b/packages/temporal-polyfill/src/funcApi/plainTime.ts @@ -0,0 +1,124 @@ +import { TimeBag, TimeFields } from '../internal/calendarFields' +import { LocalesArg } from '../internal/formatIntl' +import { queryNativeTimeZone } from '../internal/timeZoneNative' +import { DiffOptions, OverflowOptions } from '../internal/optionsRefine' +import { DurationSlots, PlainDateSlots, PlainTimeSlots, refineTimeZoneSlotString } from '../internal/slots' +import { identityFunc } from '../internal/utils' +import { constructPlainTimeSlots } from '../internal/construct' +import { isoTimeFieldsToCal, plainTimeWithFields, refinePlainTimeBag } from '../internal/bag' +import { parsePlainTime } from '../internal/parseIso' +import { movePlainTime } from '../internal/move' +import { diffPlainTimes } from '../internal/diff' +import { roundPlainTime } from '../internal/round' +import { plainTimesEqual, compareIsoTimeFields } from '../internal/compare' +import { formatPlainTimeIso } from '../internal/formatIso' +import { plainTimeToPlainDateTime, plainTimeToZonedDateTime } from '../internal/convert' +import { prepCachedPlainTimeFormat } from './formatIntlCached' + +export const create = constructPlainTimeSlots + +export const fromFields = refinePlainTimeBag + +export const fromString = parsePlainTime + +export const getFields = isoTimeFieldsToCal // TODO: improve type + +export function withFields( + slots: PlainTimeSlots, + mod: TimeBag, + options?: OverflowOptions, +): PlainTimeSlots { + return plainTimeWithFields(getFields(slots), mod, options) +} + +export function add( + slots: PlainTimeSlots, + durationSlots: DurationSlots, +): PlainTimeSlots { + return movePlainTime(slots, durationSlots) +} + +export function subtract( + slots: PlainTimeSlots, + durationSlots: DurationSlots, +): PlainTimeSlots { + return movePlainTime(slots, durationSlots, true) +} + +export function until( + plainTimeSlots0: PlainTimeSlots, + plainTimeSlots1: PlainTimeSlots, + options?: DiffOptions, +): DurationSlots { + return diffPlainTimes(plainTimeSlots0, plainTimeSlots1, options) +} + +export function since( + plainTimeSlots0: PlainTimeSlots, + plainTimeSlots1: PlainTimeSlots, + options?: DiffOptions, +): DurationSlots { + return diffPlainTimes(plainTimeSlots0, plainTimeSlots1, options, true) +} + +export const round = roundPlainTime + +export const compare = compareIsoTimeFields + +export const equals = plainTimesEqual + +export const toString = formatPlainTimeIso + +// TODO: ensure options isn't undefined before accessing +export function toZonedDateTime( + slots: PlainTimeSlots, + options: { timeZone: string, plainDate: PlainDateSlots }, +) { + return plainTimeToZonedDateTime( + refineTimeZoneSlotString, + identityFunc, + queryNativeTimeZone, + slots, + options, + ) +} + +export const toPlainDateTime = plainTimeToPlainDateTime + +export function toLocaleString( + slots: PlainTimeSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli] = prepCachedPlainTimeFormat(locales, options, slots) + return format.format(epochMilli) +} + +export function toLocaleStringParts( + slots: PlainTimeSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): Intl.DateTimeFormatPart[] { + const [format, epochMilli] = prepCachedPlainTimeFormat(locales, options, slots) + return format.formatToParts(epochMilli) +} + +export function rangeToLocaleString( + slots0: PlainTimeSlots, + slots1: PlainTimeSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli0, epochMilli1] = prepCachedPlainTimeFormat(locales, options, slots0, slots1) + return (format as any).formatRange(epochMilli0, epochMilli1!) +} + +export function rangeToLocaleStringParts( + slots0: PlainTimeSlots, + slots1: PlainTimeSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, + ): Intl.DateTimeFormatPart[] { + const [format, epochMilli0, epochMilli1] = prepCachedPlainTimeFormat(locales, options, slots0, slots1) + return (format as any).formatRangeToParts(epochMilli0, epochMilli1!) +} diff --git a/packages/temporal-polyfill/src/funcApi/plainYearMonth.ts b/packages/temporal-polyfill/src/funcApi/plainYearMonth.ts new file mode 100644 index 00000000..e951bc06 --- /dev/null +++ b/packages/temporal-polyfill/src/funcApi/plainYearMonth.ts @@ -0,0 +1,178 @@ +import { EraYearFields, YearMonthBag, YearMonthFields, YearMonthFieldsIntl } from '../internal/calendarFields' +import { LocalesArg } from '../internal/formatIntl' +import { DiffOptions, OverflowOptions } from '../internal/optionsRefine' +import { DurationSlots, PlainDateSlots, PlainYearMonthSlots, getCalendarIdFromBag, refineCalendarSlotString } from '../internal/slots' +import { createNativeDateModOps, createNativePartOps, createNativeYearMonthDiffOps, createNativeYearMonthModOps, createNativeYearMonthMoveOps, createNativeYearMonthParseOps, createNativeYearMonthRefineOps } from '../internal/calendarNativeQuery' +import { computeYearMonthFields } from '../internal/calendarNative' +import { constructPlainYearMonthSlots } from '../internal/construct' +import { parsePlainYearMonth } from '../internal/parseIso' +import { plainYearMonthWithFields, refinePlainYearMonthBag } from '../internal/bag' +import { movePlainYearMonth } from '../internal/move' +import { diffPlainYearMonth } from '../internal/diff' +import { plainYearMonthsEqual, compareIsoDateFields } from '../internal/compare' +import { formatPlainYearMonthIso } from '../internal/formatIso' +import { plainYearMonthToPlainDate } from '../internal/convert' +import { prepCachedPlainYearMonthFormat } from './formatIntlCached' +import { getDaysInMonth, getDaysInYear, getInLeapYear, getMonthsInYear } from './utils' + +export function create( + isoYear: number, + isoMonth: number, + calendar?: string, + referenceIsoDay?: number, +): PlainYearMonthSlots { + return constructPlainYearMonthSlots(refineCalendarSlotString, isoYear, isoMonth, calendar, referenceIsoDay) +} + +export function fromString(s: string): PlainYearMonthSlots { + return parsePlainYearMonth(createNativeYearMonthParseOps, s) +} + +export function fromFields( + bag: YearMonthBag & { calendar?: string }, + options?: OverflowOptions, +): PlainYearMonthSlots { + return refinePlainYearMonthBag( + createNativeYearMonthRefineOps(getCalendarIdFromBag(bag)), + bag, + options, + ) +} + +// TODO: put this in utils +export function getFields(slots: PlainYearMonthSlots): YearMonthFields & Partial { + const calendarOps = createNativePartOps(slots.calendar) + return computeYearMonthFields(calendarOps, slots) +} + +// TODO: add specific types +export const daysInMonth = getDaysInMonth +export const daysInYear = getDaysInYear +export const monthsInYear = getMonthsInYear +export const inLeapYear = getInLeapYear + +export function withFields( + plainYearMonthSlots: PlainYearMonthSlots, + initialFields: YearMonthFieldsIntl, + mod: YearMonthBag, + options?: OverflowOptions, +): PlainYearMonthSlots { + return plainYearMonthWithFields( + createNativeYearMonthModOps, + plainYearMonthSlots, + initialFields, + mod, + options, + ) +} + +export function add( + plainYearMonthSlots: PlainYearMonthSlots, + durationSlots: DurationSlots, + options?: OverflowOptions, +): PlainYearMonthSlots { + return movePlainYearMonth( + createNativeYearMonthMoveOps, + plainYearMonthSlots, + durationSlots, + options, + ) +} + +export function subtract( + plainYearMonthSlots: PlainYearMonthSlots, + durationSlots: DurationSlots, + options?: OverflowOptions, +): PlainYearMonthSlots { + return movePlainYearMonth( + createNativeYearMonthMoveOps, + plainYearMonthSlots, + durationSlots, + options, + true, + ) +} + +export function until( + plainYearMonthSlots0: PlainYearMonthSlots, + plainYearMonthSlots1: PlainYearMonthSlots, + options?: DiffOptions, +): DurationSlots { + return diffPlainYearMonth( + createNativeYearMonthDiffOps, + plainYearMonthSlots0, + plainYearMonthSlots1, + options, + ) +} + +export function since( + plainYearMonthSlots0: PlainYearMonthSlots, + plainYearMonthSlots1: PlainYearMonthSlots, + options?: DiffOptions, +): DurationSlots { + return diffPlainYearMonth( + createNativeYearMonthDiffOps, + plainYearMonthSlots0, + plainYearMonthSlots1, + options, + true, + ) +} + +export const compare = compareIsoDateFields + +export const equals = plainYearMonthsEqual + +export const toString = formatPlainYearMonthIso + +export function toPlainDate( + plainYearMonthSlots: PlainYearMonthSlots, + plainYearMonthFields: YearMonthFieldsIntl, + bag: { day: number }, +): PlainDateSlots { + return plainYearMonthToPlainDate( + createNativeDateModOps, + plainYearMonthSlots, + plainYearMonthFields, + bag, + ) +} + +export function toLocaleString( + slots: PlainYearMonthSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli] = prepCachedPlainYearMonthFormat(locales, options, slots) + return format.format(epochMilli) +} + +export function toLocaleStringParts( + slots: PlainYearMonthSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): Intl.DateTimeFormatPart[] { + const [format, epochMilli] = prepCachedPlainYearMonthFormat(locales, options, slots) + return format.formatToParts(epochMilli) +} + +export function rangeToLocaleString( + slots0: PlainYearMonthSlots, + slots1: PlainYearMonthSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli0, epochMilli1] = prepCachedPlainYearMonthFormat(locales, options, slots0, slots1) + return (format as any).formatRange(epochMilli0, epochMilli1!) +} + +export function rangeToLocaleStringParts( + slots0: PlainYearMonthSlots, + slots1: PlainYearMonthSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, + ): Intl.DateTimeFormatPart[] { + const [format, epochMilli0, epochMilli1] = prepCachedPlainYearMonthFormat(locales, options, slots0, slots1) + return (format as any).formatRangeToParts(epochMilli0, epochMilli1!) +} diff --git a/packages/temporal-polyfill/src/funcApi/utils.ts b/packages/temporal-polyfill/src/funcApi/utils.ts new file mode 100644 index 00000000..eda4930b --- /dev/null +++ b/packages/temporal-polyfill/src/funcApi/utils.ts @@ -0,0 +1,34 @@ +import { DateFields, EraYearFields } from '../internal/calendarFields' +import { createNativeDayOfYearOps, createNativeDaysInMonthOps, createNativeDaysInYearOps, createNativeInLeapYearOps, createNativeMonthsInYearOps, createNativePartOps } from '../internal/calendarNativeQuery' +import { computeDateFields } from '../internal/calendarNative' +import { DateSlots } from '../internal/slots' + +export function getDateFields(slots: DateSlots): DateFields & Partial { + const calendarOp = createNativePartOps(slots.calendar) + return computeDateFields(calendarOp, slots) +} + +export function getInLeapYear(slots: DateSlots): boolean { + const calendarOps = createNativeInLeapYearOps(slots.calendar) + return calendarOps.inLeapYear(slots) +} + +export function getMonthsInYear(slots: DateSlots): number { + const calendarOps = createNativeMonthsInYearOps(slots.calendar) + return calendarOps.monthsInYear(slots) +} + +export function getDaysInMonth(slots: DateSlots): number { + const calendarOps = createNativeDaysInMonthOps(slots.calendar) + return calendarOps.daysInMonth(slots) +} + +export function getDaysInYear(slots: DateSlots): number { + const calendarOps = createNativeDaysInYearOps(slots.calendar) + return calendarOps.daysInYear(slots) +} + +export function getDayOfYear(slots: DateSlots): number { + const calendarOps = createNativeDayOfYearOps(slots.calendar) + return calendarOps.dayOfYear(slots) +} diff --git a/packages/temporal-polyfill/src/funcApi/zonedDateTime.ts b/packages/temporal-polyfill/src/funcApi/zonedDateTime.ts new file mode 100644 index 00000000..1c496e41 --- /dev/null +++ b/packages/temporal-polyfill/src/funcApi/zonedDateTime.ts @@ -0,0 +1,349 @@ +import { DateBag, DateTimeBag, DateTimeFields, EraYearFields } from '../internal/calendarFields' +import { UnitName } from '../internal/units' +import { NumSign } from '../internal/utils' +import { formatOffsetNano, formatZonedDateTimeIso } from '../internal/formatIso' +import { ZonedIsoDateTimeSlots, computeHoursInDay, computeStartOfDay, getZonedIsoDateTimeSlots, zonedInternalsToIso } from '../internal/timeZoneOps' +import { LocalesArg } from '../internal/formatIntl' +import { queryNativeTimeZone } from '../internal/timeZoneNative' +import { DiffOptions, OverflowOptions, RoundingOptions, ZonedDateTimeDisplayOptions, ZonedFieldOptions } from '../internal/optionsRefine' +import { DurationSlots, PlainDateSlots, PlainDateTimeSlots, PlainMonthDaySlots, PlainTimeSlots, PlainYearMonthSlots, ZonedDateTimeSlots, getCalendarIdFromBag, refineCalendarSlotString, refineTimeZoneSlotString } from '../internal/slots' +import { computeIsoDayOfWeek, computeIsoDaysInWeek, computeIsoWeekOfYear, computeIsoYearOfWeek } from '../internal/calendarIso' +import { createNativeDateModOps, createNativeDateRefineOps, createNativeDiffOps, createNativeMonthDayRefineOps, createNativeMoveOps, createNativeYearMonthRefineOps } from '../internal/calendarNativeQuery' +import { DurationFields } from '../internal/durationFields' +import { ZonedDateTimeBag, isoTimeFieldsToCal, refineZonedDateTimeBag, zonedDateTimeWithFields } from '../internal/bag' +import { constructZonedDateTimeSlots } from '../internal/construct' +import { parseZonedDateTime } from '../internal/parseIso' +import { slotsWithTimeZone, zonedDateTimeWithPlainDate, zonedDateTimeWithPlainTime } from '../internal/mod' +import { moveZonedDateTime } from '../internal/move' +import { diffZonedDateTimes } from '../internal/diff' +import { roundZonedDateTime } from '../internal/round' +import { compareZonedDateTimes, zonedDateTimesEqual } from '../internal/compare' +import { zonedDateTimeToPlainDate, zonedDateTimeToPlainDateTime, zonedDateTimeToPlainMonthDay, zonedDateTimeToPlainTime, zonedDateTimeToPlainYearMonth } from '../internal/convert' +import { prepCachedZonedDateTimeFormat } from './formatIntlCached' +import { getDayOfYear, getDaysInMonth, getDaysInYear, getInLeapYear, getMonthsInYear, getDateFields } from './utils' + +export function create( + epochNano: bigint, + timeZoneArg: string, + calendarArg?: string, +): ZonedDateTimeSlots { + return constructZonedDateTimeSlots( + refineCalendarSlotString, + refineTimeZoneSlotString, + epochNano, + timeZoneArg, + calendarArg, + ) +} + +export const fromString = parseZonedDateTime + +export function fromFields( + fields: ZonedDateTimeBag, + options?: ZonedFieldOptions, +): ZonedDateTimeSlots { + const calendarId = getCalendarIdFromBag(fields) + return refineZonedDateTimeBag( + refineTimeZoneSlotString, + queryNativeTimeZone, + createNativeDateRefineOps(calendarId), + calendarId, + fields, + options, + ) +} + +export function getISOFields( + zonedDateTimeSlots: ZonedDateTimeSlots, +): ZonedIsoDateTimeSlots { + return getZonedIsoDateTimeSlots(queryNativeTimeZone, zonedDateTimeSlots) +} + +export type ZonedDateTimeFields = DateTimeFields & Partial & { offset: string } + +export function getFields( + zonedDateTimeSlots: ZonedDateTimeSlots, +): ZonedDateTimeFields { + const isoFields = zonedInternalsToIso(zonedDateTimeSlots, queryNativeTimeZone(zonedDateTimeSlots.timeZone)) + const offsetString = formatOffsetNano(isoFields.offsetNanoseconds) + + return { + ...getDateFields({ ...isoFields, calendar: zonedDateTimeSlots.calendar }), + ...isoTimeFieldsToCal(isoFields), + offset: offsetString, + } +} + +export function dayOfWeek(zonedDateTimeSlots: ZonedDateTimeSlots): number { + const isoFields = zonedInternalsToIso(zonedDateTimeSlots, queryNativeTimeZone(zonedDateTimeSlots.timeZone)) + return computeIsoDayOfWeek(isoFields) +} + +export function daysInWeek(zonedDateTimeSlots: ZonedDateTimeSlots): number { + const isoFields = zonedInternalsToIso(zonedDateTimeSlots, queryNativeTimeZone(zonedDateTimeSlots.timeZone)) + return computeIsoDaysInWeek(isoFields) +} + +export function weekOfYear(zonedDateTimeSlots: ZonedDateTimeSlots): number { + const isoFields = zonedInternalsToIso(zonedDateTimeSlots, queryNativeTimeZone(zonedDateTimeSlots.timeZone)) + return computeIsoWeekOfYear(isoFields) +} + +export function yearOfWeek(zonedDateTimeSlots: ZonedDateTimeSlots): number { + const isoFields = zonedInternalsToIso(zonedDateTimeSlots, queryNativeTimeZone(zonedDateTimeSlots.timeZone)) + return computeIsoYearOfWeek(isoFields) +} + +export function dayOfYear(zonedDateTimeSlots: ZonedDateTimeSlots): number { + const isoFields = zonedInternalsToIso(zonedDateTimeSlots, queryNativeTimeZone(zonedDateTimeSlots.timeZone)) + return getDayOfYear({ ...isoFields, calendar: zonedDateTimeSlots.calendar }) +} + +export function daysInMonth(zonedDateTimeSlots: ZonedDateTimeSlots): number { + const isoFields = zonedInternalsToIso(zonedDateTimeSlots, queryNativeTimeZone(zonedDateTimeSlots.timeZone)) + return getDaysInMonth({ ...isoFields, calendar: zonedDateTimeSlots.calendar }) +} + +export function daysInYear(zonedDateTimeSlots: ZonedDateTimeSlots): number { + const isoFields = zonedInternalsToIso(zonedDateTimeSlots, queryNativeTimeZone(zonedDateTimeSlots.timeZone)) + return getDaysInYear({ ...isoFields, calendar: zonedDateTimeSlots.calendar }) +} + +export function monthsInYear(zonedDateTimeSlots: ZonedDateTimeSlots): number { + const isoFields = zonedInternalsToIso(zonedDateTimeSlots, queryNativeTimeZone(zonedDateTimeSlots.timeZone)) + return getMonthsInYear({ ...isoFields, calendar: zonedDateTimeSlots.calendar }) +} + +export function inLeapYear(zonedDateTimeSlots: ZonedDateTimeSlots): boolean { + const isoFields = zonedInternalsToIso(zonedDateTimeSlots, queryNativeTimeZone(zonedDateTimeSlots.timeZone)) + return getInLeapYear({ ...isoFields, calendar: zonedDateTimeSlots.calendar }) +} + +export function withFields( + zonedDateTimeSlots: ZonedDateTimeSlots, + modFields: DateTimeBag, + options?: ZonedFieldOptions, +): ZonedDateTimeSlots { + return zonedDateTimeWithFields( + createNativeDateModOps, + queryNativeTimeZone, + zonedDateTimeSlots, + getFields(zonedDateTimeSlots), + modFields, + options, + ) +} + +export function withPlainTime( + zonedDateTimeSlots: ZonedDateTimeSlots, + plainTimeSlots?: PlainTimeSlots, +): ZonedDateTimeSlots { + return zonedDateTimeWithPlainTime( + queryNativeTimeZone, + zonedDateTimeSlots, + plainTimeSlots, + ) +} + +export function withPlainDate( + zonedDateTimeSlots: ZonedDateTimeSlots, + plainDateSlots: PlainDateSlots, +): ZonedDateTimeSlots { + return zonedDateTimeWithPlainDate( + queryNativeTimeZone, + zonedDateTimeSlots, + plainDateSlots, + ) +} + +export const withTimeZone = slotsWithTimeZone + +export const withCalendar = slotsWithTimeZone + +export function add( + zonedDateTimeSlots: ZonedDateTimeSlots, + durationSlots: DurationSlots, + options?: OverflowOptions, +): ZonedDateTimeSlots { + return moveZonedDateTime( + createNativeMoveOps, + queryNativeTimeZone, + zonedDateTimeSlots, + durationSlots, + options, + ) +} + +export function subtract( + zonedDateTimeSlots: ZonedDateTimeSlots, + durationSlots: DurationSlots, + options?: OverflowOptions, +): ZonedDateTimeSlots { + return moveZonedDateTime( + createNativeMoveOps, + queryNativeTimeZone, + zonedDateTimeSlots, + durationSlots, + options, + true, + ) +} + +export function until( + zonedDateTimeSlots0: ZonedDateTimeSlots, + zonedDateTimeSlots1: ZonedDateTimeSlots, + options?: DiffOptions, +): DurationFields { + return diffZonedDateTimes( + createNativeDiffOps, + queryNativeTimeZone, + zonedDateTimeSlots0, + zonedDateTimeSlots1, + options, + ) +} + +export function since( + zonedDateTimeSlots0: ZonedDateTimeSlots, + zonedDateTimeSlots1: ZonedDateTimeSlots, + options?: DiffOptions, +): DurationFields { + return diffZonedDateTimes( + createNativeDiffOps, + queryNativeTimeZone, + zonedDateTimeSlots0, + zonedDateTimeSlots1, + options, + true, + ) +} + +export function round( + zonedDateTimeSlots: ZonedDateTimeSlots, + options: RoundingOptions | UnitName, +): ZonedDateTimeSlots { + return roundZonedDateTime( + queryNativeTimeZone, + zonedDateTimeSlots, + options, + ) +} + +export function startOfDay( + zonedDateTimeSlots: ZonedDateTimeSlots, +): ZonedDateTimeSlots { + return computeStartOfDay( + queryNativeTimeZone, + zonedDateTimeSlots, + ) +} + +export function hoursInDay(zonedDateTimeSlots: ZonedDateTimeSlots): number { + return computeHoursInDay( + queryNativeTimeZone, + zonedDateTimeSlots, + ) +} + +export function compare( + zonedDateTimeSlots0: ZonedDateTimeSlots, + zonedDateTimeSlots1: ZonedDateTimeSlots, +): NumSign { + return compareZonedDateTimes(zonedDateTimeSlots0, zonedDateTimeSlots1) // just forwards +} + +export function equals( + zonedDateTimeSlots0: ZonedDateTimeSlots, + zonedDateTimeSlots1: ZonedDateTimeSlots, +): boolean { + return zonedDateTimesEqual(zonedDateTimeSlots0, zonedDateTimeSlots1) // just forwards +} + +export function toString( + zonedDateTimeSlots0: ZonedDateTimeSlots, + options?: ZonedDateTimeDisplayOptions, +): string { + return formatZonedDateTimeIso( + queryNativeTimeZone, + zonedDateTimeSlots0, + options + ) +} + +export function toPlainDate( + zonedDateTimeSlots0: ZonedDateTimeSlots, +): PlainDateSlots { + return zonedDateTimeToPlainDate(queryNativeTimeZone, zonedDateTimeSlots0) +} + +export function toPlainTime( + zonedDateTimeSlots0: ZonedDateTimeSlots, +): PlainTimeSlots { + return zonedDateTimeToPlainTime(queryNativeTimeZone, zonedDateTimeSlots0) +} + +export function toPlainDateTime( + zonedDateTimeSlots0: ZonedDateTimeSlots, +): PlainDateTimeSlots { + return zonedDateTimeToPlainDateTime(queryNativeTimeZone, zonedDateTimeSlots0) +} + +export function toPlainYearMonth( + zonedDateTimeSlots0: ZonedDateTimeSlots, + zonedDateTimeFields: DateBag, // TODO: DateBag correct type? +): PlainYearMonthSlots { + return zonedDateTimeToPlainYearMonth( + createNativeYearMonthRefineOps, + zonedDateTimeSlots0, + zonedDateTimeFields, + ) +} + +export function toPlainMonthDay( + zonedDateTimeSlots0: ZonedDateTimeSlots, + zonedDateTimeFields: DateBag, // TODO: DateBag correct type? +): PlainMonthDaySlots { + return zonedDateTimeToPlainMonthDay( + createNativeMonthDayRefineOps, + zonedDateTimeSlots0, + zonedDateTimeFields, + ) +} + +export function toLocaleString( + slots: ZonedDateTimeSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli] = prepCachedZonedDateTimeFormat(locales, options, slots) + return format.format(epochMilli) +} + +export function toLocaleStringParts( + slots: ZonedDateTimeSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): Intl.DateTimeFormatPart[] { + const [format, epochMilli] = prepCachedZonedDateTimeFormat(locales, options, slots) + return format.formatToParts(epochMilli) +} + +export function rangeToLocaleString( + slots0: ZonedDateTimeSlots, + slots1: ZonedDateTimeSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, +): string { + const [format, epochMilli0, epochMilli1] = prepCachedZonedDateTimeFormat(locales, options, slots0, slots1) + return (format as any).formatRange(epochMilli0, epochMilli1!) +} + +export function rangeToLocaleStringParts( + slots0: ZonedDateTimeSlots, + slots1: ZonedDateTimeSlots, + locales?: LocalesArg, + options?: Intl.DateTimeFormatOptions, + ): Intl.DateTimeFormatPart[] { + const [format, epochMilli0, epochMilli1] = prepCachedZonedDateTimeFormat(locales, options, slots0, slots1) + return (format as any).formatRangeToParts(epochMilli0, epochMilli1!) +} diff --git a/packages/temporal-polyfill/src/global.build.ts b/packages/temporal-polyfill/src/global.build.ts deleted file mode 100644 index b0af9cd9..00000000 --- a/packages/temporal-polyfill/src/global.build.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { shim } from './shim' - -shim() diff --git a/packages/temporal-polyfill/src/global.ts b/packages/temporal-polyfill/src/global.ts index 4a889e48..f8cbfefa 100644 --- a/packages/temporal-polyfill/src/global.ts +++ b/packages/temporal-polyfill/src/global.ts @@ -1 +1,6 @@ -export * from 'temporal-spec/global' +import { createPropDescriptors } from './internal/utils' +import { DateTimeFormat, Temporal, toTemporalInstant } from './impl' + +Object.defineProperties(globalThis, createPropDescriptors({ Temporal })) +Object.defineProperties(Intl, createPropDescriptors({ DateTimeFormat })) +Object.defineProperties(Date.prototype, createPropDescriptors({ toTemporalInstant })) diff --git a/packages/temporal-polyfill/src/impl.build.ts b/packages/temporal-polyfill/src/impl.build.ts deleted file mode 100644 index 2e1ef3fd..00000000 --- a/packages/temporal-polyfill/src/impl.build.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type * as Spec from 'temporal-spec' -import { toTemporalInstant as toTemporalInstantImpl } from './native/date' -import { DateTimeFormat } from './native/intlTemporal' -import { Temporal as TemporalImpl } from './public/temporal' -import { getGlobalThis } from './utils/dom' - -export const Temporal: typeof Spec.Temporal = TemporalImpl -export const Intl: typeof Spec.Intl = { ...getGlobalThis().Intl, DateTimeFormat } -export const toTemporalInstant = toTemporalInstantImpl diff --git a/packages/temporal-polyfill/src/impl.ts b/packages/temporal-polyfill/src/impl.ts index 8746380b..3a74d4ef 100644 --- a/packages/temporal-polyfill/src/impl.ts +++ b/packages/temporal-polyfill/src/impl.ts @@ -1 +1,3 @@ -export * from 'temporal-spec' +export { Temporal } from './classApi/temporal' +export { DateTimeFormat } from './classApi/dateTimeFormat' // NOT CORRECT w/ temporal-spec, need Intl +export { toTemporalInstant } from './classApi/instant' diff --git a/packages/temporal-polyfill/src/index.build.ts b/packages/temporal-polyfill/src/index.build.ts deleted file mode 100644 index d9d8b9e0..00000000 --- a/packages/temporal-polyfill/src/index.build.ts +++ /dev/null @@ -1,24 +0,0 @@ - -/* CONDITIONAL, based on presence of native implementation. disable until Stage 4 - -import type * as Spec from 'temporal-spec' -import { getGlobalThis } from './utils/dom' -import { - Intl as IntlImpl, - Temporal as TemporalImpl, - toTemporalInstant as toTemporalInstantImpl, -} from './impl' - -const theGlobalThis = getGlobalThis() -const TemporalNative = theGlobalThis.Temporal - -export const Temporal: typeof Spec.Temporal = TemporalNative || TemporalImpl -export const Intl: typeof Spec.Intl = TemporalNative - ? (theGlobalThis.Intl as any) // because of DateTimeFormatOptions shortcoming temporal-spec/global - : IntlImpl -export const toTemporalInstant = TemporalNative - ? theGlobalThis.Date.prototype.toTemporalInstant - : toTemporalInstantImpl -*/ - -export { Intl, Temporal, toTemporalInstant } from './impl' diff --git a/packages/temporal-polyfill/src/index.ts b/packages/temporal-polyfill/src/index.ts index 8746380b..f356c4c3 100644 --- a/packages/temporal-polyfill/src/index.ts +++ b/packages/temporal-polyfill/src/index.ts @@ -1 +1 @@ -export * from 'temporal-spec' +export * from './impl' diff --git a/packages/temporal-polyfill/src/internal/bag.ts b/packages/temporal-polyfill/src/internal/bag.ts new file mode 100644 index 00000000..a761f2f8 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/bag.ts @@ -0,0 +1,958 @@ +import { isoCalendarId, japaneseCalendarId } from './calendarConfig' +import { DateBag, DateFields, DateFieldsIntl, DateTimeBag, DateTimeFields, DayFields, DurationBag, EraYearFields, EraYearOrYear, MonthDayBag, MonthDayFields, MonthFields, TimeBag, TimeFields, YearFields, YearMonthBag, YearMonthFieldsIntl, allYearFieldNames, dateFieldNamesAlpha, dayFieldNames, eraYearFieldNames, monthCodeDayFieldNames, monthDayFieldNames, monthFieldNames, offsetFieldNames, timeAndOffsetFieldNames, timeAndZoneFieldNames, timeFieldDefaults, timeFieldNamesAlpha, timeFieldNamesAsc, timeZoneFieldNames, yearFieldNames, yearMonthCodeFieldNames, yearMonthFieldNames } from './calendarFields' +import { computeIsoDaysInMonth, isoMonthsInYear } from './calendarIso' +import { NativeDateRefineDeps, NativeMonthDayRefineOps, NativeYearMonthRefineDeps, eraYearToYear, getCalendarEraOrigins, getCalendarLeapMonthMeta, monthCodeNumberToMonth, monthToMonthCodeNumber, parseMonthCode } from './calendarNative' +import { IsoDateTimeFields, IsoTimeFields, isoTimeFieldDefaults, isoTimeFieldNamesAsc } from './calendarIsoFields' +import { isoEpochFirstLeapYear, constrainIsoTimeFields } from './calendarIso' +import { checkIsoDateInBounds, checkIsoDateTimeInBounds, checkIsoYearMonthInBounds } from './epochAndTime' +import { EpochDisambig, OffsetDisambig, Overflow } from './options' +import { BoundArg, Callable, bindArgs, clampEntity, clampProp, getDefinedProp, mapPropNamesToConstant, pluckProps, remapProps } from './utils' +import { OverflowOptions, ZonedFieldOptions, overflowMapNames, overrideOverflowOptions, copyOptions, refineOverflowOptions, refineZonedFieldOptions } from './optionsRefine' +import { DurationFields, durationFieldDefaults, durationFieldNamesAlpha, durationFieldNamesAsc } from './durationFields' +import { TimeZoneOps, getMatchingInstantFor } from './timeZoneOps' +import { DayTimeNano } from './dayTimeNano' +import { DateModOps, DateRefineOps, FieldsOp, MergeFieldsOp, MonthDayModOps, MonthDayRefineOps, YearMonthModOps, YearMonthRefineOps } from './calendarOps' +import { parseOffsetNano } from './parseIso' +import { requireNumberIsPositive, requireObjectlike, toInteger, toPositiveInteger, toStrictInteger, toStringViaPrimitive } from './cast' +import { MarkerSlotsNoCalendar, checkDurationFields } from './durationMath' +import { DateSlots, DurationBranding, DurationSlots, PlainDateBranding, PlainDateSlots, PlainDateTimeBranding, PlainDateTimeSlots, PlainMonthDayBranding, PlainMonthDaySlots, PlainTimeBranding, PlainTimeSlots, PlainYearMonthBranding, PlainYearMonthSlots, ZonedDateTimeBranding, ZonedDateTimeSlots, createDurationSlots, createPlainDateTimeSlots, createPlainDateSlots, createPlainMonthDaySlots, createPlainTimeSlots, createPlainYearMonthSlots, createZonedDateTimeSlots } from './slots' +import * as errorMessages from './errorMessages' + +export type PlainDateBag = DateBag & { calendar?: C } +export type PlainDateTimeBag = DateBag & TimeBag & { calendar?: C } +export type ZonedDateTimeBag = PlainDateTimeBag & { timeZone: T, offset?: string } +export type PlainTimeBag = TimeBag +export type PlainYearMonthBag = YearMonthBag & { calendar?: C } +export type PlainMonthDayBag = MonthDayBag & { calendar?: C } + +// Config +// ------------------------------------------------------------------------------------------------- + +const dateFieldRefiners = { + era: toStringViaPrimitive, + eraYear: toInteger, + year: toInteger, + month: toPositiveInteger, + monthCode: toStringViaPrimitive, + day: toPositiveInteger, +} + +const timeFieldRefiners = mapPropNamesToConstant(timeFieldNamesAsc, toInteger) + +const durationFieldRefiners = mapPropNamesToConstant(durationFieldNamesAsc, toStrictInteger) + +const builtinRefiners = { + ...dateFieldRefiners, + ...timeFieldRefiners, + ...durationFieldRefiners, + offset: toStringViaPrimitive, +} + +// High-Level Refining +// ------------------------------------------------------------------------------------------------- + +/* +TODO: make more DRY with other methods +*/ +export function refineMaybeZonedDateTimeBag( + refineTimeZoneArg: (timeZoneArg: TA) => T, + getTimeZoneOps: (timeZoneArg: T) => TimeZoneOps, + calendarOps: DateRefineOps, + bag: ZonedDateTimeBag, +): MarkerSlotsNoCalendar { + const fields = refineCalendarFields( + calendarOps, + bag, + dateFieldNamesAlpha, // validFieldNames + [], // requireFields + timeAndZoneFieldNames, // forcedValidFieldNames + ) as ZonedDateTimeBag + + if (fields.timeZone !== undefined) { + const isoDateFields = calendarOps.dateFromFields(fields as any) + const isoTimeFields = refineTimeBag(fields) + + // must happen after datetime fields + const timeZoneSlot = refineTimeZoneArg(fields.timeZone) + const timeZoneOps = getTimeZoneOps(timeZoneSlot) + + const epochNanoseconds = getMatchingInstantFor( + timeZoneOps, + { ...isoDateFields, ...isoTimeFields }, + fields.offset !== undefined ? parseOffsetNano(fields.offset) : undefined, + ) + + return { epochNanoseconds, timeZone: timeZoneSlot } + } else { + const isoDateInternals = calendarOps.dateFromFields(fields as any) + return { ...isoDateInternals, ...isoTimeFieldDefaults } + } +} + +export function refineZonedDateTimeBag( + refineTimeZoneArg: (timeZoneArg: TA) => T, + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + calendarOps: DateRefineOps, + calendarSlot: C, + bag: ZonedDateTimeBag, + options: ZonedFieldOptions | undefined, +): ZonedDateTimeSlots { + const fields = refineCalendarFields( + calendarOps, + bag, + dateFieldNamesAlpha, // validFieldNames + timeZoneFieldNames, // requireFields + timeAndZoneFieldNames, // forcedValidFieldNames + ) as ZonedDateTimeBag + + const timeZoneSlot = refineTimeZoneArg(fields.timeZone!) // guaranteed via refineCalendarFields + const [overflow, offsetDisambig, epochDisambig] = refineZonedFieldOptions(options) + const isoDateFields = calendarOps.dateFromFields( + fields as any, + overrideOverflowOptions(options, overflow), + ) + const isoTimeFields = refineTimeBag(fields, overflow) + const timeZoneOps = getTimeZoneOps(timeZoneSlot) + + const epochNanoseconds = getMatchingInstantFor( + timeZoneOps, + { ...isoDateFields, ...isoTimeFields }, + fields.offset !== undefined ? parseOffsetNano(fields.offset) : undefined, + offsetDisambig, + epochDisambig, + ) + + return createZonedDateTimeSlots( + epochNanoseconds, + timeZoneSlot, + calendarSlot, + ) +} + +export function refinePlainDateTimeBag( + calendarOps: DateRefineOps, + bag: DateTimeBag, + options: OverflowOptions | undefined, +): PlainDateTimeSlots { + const fields = refineCalendarFields( + calendarOps, + bag, + dateFieldNamesAlpha, + [], // requiredFields + timeFieldNamesAsc, // forcedValidFieldNames + ) as DateTimeBag + + const overflow = refineOverflowOptions(options) + const isoDateInternals = calendarOps.dateFromFields( + fields as any, + overrideOverflowOptions(options, overflow), + ) + const isoTimeFields = refineTimeBag(fields, overflow) + + const isoFields = checkIsoDateTimeInBounds({ + ...isoDateInternals, + ...isoTimeFields, + }) + + return createPlainDateTimeSlots(isoFields) +} + +export function refinePlainDateBag( + calendarOps: DateRefineOps, + bag: DateBag, + options: OverflowOptions | undefined, + requireFields: string[] = [], +): PlainDateSlots { + const fields = refineCalendarFields( + calendarOps, + bag, + dateFieldNamesAlpha, + requireFields, + ) + + return calendarOps.dateFromFields(fields as any, options) +} + +export function refinePlainYearMonthBag( + calendarOps: YearMonthRefineOps, + bag: YearMonthBag, + options: OverflowOptions | undefined, + requireFields?: string[], +): PlainYearMonthSlots { + const fields = refineCalendarFields( + calendarOps, + bag, + yearMonthFieldNames, + requireFields, + ) + + return calendarOps.yearMonthFromFields(fields, options) +} + +export function refinePlainMonthDayBag( + calendarOps: MonthDayRefineOps, + calendarAbsent: boolean, + bag: MonthDayBag, + options?: OverflowOptions, + requireFields: string[] = [], // when called from Calendar +): PlainMonthDaySlots { + const fields = refineCalendarFields( + calendarOps, + bag, + dateFieldNamesAlpha, + requireFields, + ) + + // Callers who omit the calendar are not writing calendar-independent + // code. In that case, `monthCode`/`year` can be omitted; `month` and + // `day` are sufficient. Add a `year` to satisfy calendar validation. + if (calendarAbsent && fields.month !== undefined && fields.monthCode === undefined && fields.year === undefined) { + fields.year = isoEpochFirstLeapYear + } + + return calendarOps.monthDayFromFields(fields, options) +} + +export function refinePlainTimeBag( + bag: TimeBag, + options: OverflowOptions | undefined, +): PlainTimeSlots { + const overflow = refineOverflowOptions(options) // spec says overflow parsed first + const fields = refineFields(bag, timeFieldNamesAlpha, [], true) as TimeBag // disallowEmpty + + return createPlainTimeSlots(refineTimeBag(fields, overflow)) +} + +export function refineDurationBag(bag: DurationBag): DurationSlots { + // refine in 'partial' mode + const durationFields = refineFields(bag, durationFieldNamesAlpha) as DurationBag + + return createDurationSlots( + checkDurationFields({ + ...durationFieldDefaults, + ...durationFields + }), + ) +} + +// Low-level Refining +// ------------------------------------------------------------------------------------------------- + +function refineCalendarFields( + calendarOps: { fields: FieldsOp }, + bag: Record, + validFieldNames: string[], // does NOT need to be alphabetized + requiredFieldNames: string[] = [], // a subset of validFieldNames + forcedValidFieldNames: string[] = [], +): Record { + const fieldNames = [ + ...calendarOps.fields(validFieldNames), + ...forcedValidFieldNames, + ].sort() + + return refineFields(bag, fieldNames, requiredFieldNames) +} + +/* +If `requiredFieldNames` is undefined, assume 'partial' mode where defaults don't apply +*/ +function refineFields( + bag: Record, + validFieldNames: string[], // must be alphabetized!!! + requiredFieldNames?: string[], + disallowEmpty: boolean = !requiredFieldNames, +): Record { + const res: Record = {} + let anyMatching = false + let prevFieldName: undefined | string + + for (const fieldName of validFieldNames) { + if (fieldName === prevFieldName) { + throw new RangeError(errorMessages.duplicateFields(fieldName)) + } + if (fieldName === 'constructor' || fieldName === '__proto__') { + throw new RangeError(errorMessages.forbiddenField(fieldName)) + } + + let fieldVal = bag[fieldName] + + if (fieldVal !== undefined) { + anyMatching = true + + if (builtinRefiners[fieldName as keyof typeof builtinRefiners]) { + fieldVal = (builtinRefiners[fieldName as keyof typeof builtinRefiners] as Callable)(fieldVal, fieldName) + } + + res[fieldName] = fieldVal + } else if (requiredFieldNames) { + if (requiredFieldNames.includes(fieldName)) { // TODO: have caller use a Set + throw new TypeError(errorMessages.missingField(fieldName)) + } + + res[fieldName] = timeFieldDefaults[fieldName as keyof typeof timeFieldDefaults] + } + + prevFieldName = fieldName + } + + // only check zero fields during .with() calls + // for .from() calls, empty-bag-checking will happen within the CalendarImpl + if (disallowEmpty && !anyMatching) { + throw new TypeError(errorMessages.noValidFields) + } + + return res +} + +function refineTimeBag(fields: TimeBag, overflow?: Overflow): IsoTimeFields { + return constrainIsoTimeFields( + timeFieldsToIso({ ...timeFieldDefaults, ...fields }), + overflow, + ) +} + +const timeFieldsToIso = bindArgs( + remapProps, + timeFieldNamesAsc, + isoTimeFieldNamesAsc, +) + +export const isoTimeFieldsToCal = bindArgs( + remapProps, + isoTimeFieldNamesAsc, + timeFieldNamesAsc, +) + +// High-Level Mod +// ------------------------------------------------------------------------------------------------- + +export function zonedDateTimeWithFields( + getCalendarOps: (calendarSlot: C) => DateModOps, + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + zonedDateTimeSlots: ZonedDateTimeSlots, + initialFields: DateTimeFields & Partial, // TODO: allow offset + modFields: DateTimeBag, + options?: ZonedFieldOptions, +): ZonedDateTimeSlots { + const optionsCopy = copyOptions(options) + const { calendar, timeZone } = zonedDateTimeSlots + const calendarOps = getCalendarOps(calendar) + const timeZoneOps = getTimeZoneOps(timeZone) + + return createZonedDateTimeSlots( + mergeZonedDateTimeBag( + calendarOps, + timeZoneOps, + initialFields, + modFields, + optionsCopy, + ), + timeZone, + calendar, + ) +} + +export function plainDateTimeWithFields( + getCalendarOps: (calendarSlot: C) => DateModOps, + plainDateTimeSlots: PlainDateTimeSlots, + initialFields: DateTimeFields & Partial, + modFields: DateTimeBag, + options?: OverflowOptions, +): PlainDateTimeSlots { + const optionsCopy = copyOptions(options) + const calendarSlot = plainDateTimeSlots.calendar + const calendarOps = getCalendarOps(calendarSlot) + + return createPlainDateTimeSlots( + mergePlainDateTimeBag( + calendarOps, + initialFields, + modFields, + optionsCopy, + ), + ) +} + +export function plainDateWithFields( + getCalendarOps: (calendarSlot: C) => DateModOps, + plainDateSlots: PlainDateSlots, + initialFields: DateFields & Partial, + modFields: DateBag, + options?: OverflowOptions, +): PlainDateSlots { + const optionsCopy = copyOptions(options) + const calendarSlot = plainDateSlots.calendar + const calendarOps = getCalendarOps(calendarSlot) + + return mergePlainDateBag(calendarOps, initialFields, modFields, optionsCopy) +} + +export function plainYearMonthWithFields( + getCalendarOps: (calendar: C) => YearMonthModOps, + plainYearMonthSlots: PlainYearMonthSlots, + initialFields: YearMonthFieldsIntl, + mod: YearMonthBag, + options?: OverflowOptions, +): PlainYearMonthSlots { + const optionsCopy = copyOptions(options) + const calendarSlot = plainYearMonthSlots.calendar + const calendarOps = getCalendarOps(calendarSlot) + + return createPlainYearMonthSlots( + mergePlainYearMonthBag(calendarOps, initialFields, mod, optionsCopy) + ) +} + +export function plainMonthDayWithFields( + getCalendarOps: (calendarSlot: C) => MonthDayModOps, + plainMonthDaySlots: PlainMonthDaySlots, + initialFields: MonthDayFields, + modFields: MonthDayBag, + options?: OverflowOptions, +): PlainMonthDaySlots { + const optionsCopy = copyOptions(options) + const calendarSlot = plainMonthDaySlots.calendar + const calendarOps = getCalendarOps(calendarSlot) + + return mergePlainMonthDayBag(calendarOps, initialFields, modFields, optionsCopy) +} + +export function plainTimeWithFields( + initialFields: TimeFields, + mod: TimeBag, + options?: OverflowOptions, +): PlainTimeSlots { + return createPlainTimeSlots(mergePlainTimeBag(initialFields, mod, options)) +} + +export function durationWithFields( + slots: DurationSlots, + fields: DurationBag, +): DurationSlots { + return createDurationSlots( + mergeDurationBag(slots, fields), + ) +} + +// Low-Level Mod ("merging") +// ------------------------------------------------------------------------------------------------- + +function mergeZonedDateTimeBag( + calendarOps: DateModOps, + timeZoneOps: TimeZoneOps, + zonedDateTime: any, + mod: DateTimeBag, // TODO: allow offset. correct base type tho? + options: ZonedFieldOptions | undefined, +): DayTimeNano { + const fields = mergeCalendarFields( + calendarOps, + zonedDateTime as any, + mod, + dateFieldNamesAlpha, // validFieldNames + timeAndOffsetFieldNames, // forcedValidFieldNames + offsetFieldNames, // requiredObjFieldNames + ) as ZonedDateTimeBag + + const [overflow, offsetDisambig, epochDisambig] = refineZonedFieldOptions(options, OffsetDisambig.Prefer) + const isoDateFields = calendarOps.dateFromFields( + fields as any, + overrideOverflowOptions(options, overflow), + ) + const isoTimeFields = refineTimeBag(fields, overflow) + + return getMatchingInstantFor( + timeZoneOps, + { ...isoDateFields, ...isoTimeFields }, + parseOffsetNano(fields.offset!), // guaranteed via mergeCalendarFields + offsetDisambig, + epochDisambig, + ) +} + +function mergePlainDateTimeBag( + calendarOps: DateModOps, + plainDateTime: any, + mod: DateTimeBag, + options: OverflowOptions | undefined, +): IsoDateTimeFields & { calendar: C } { + const fields = mergeCalendarFields( + calendarOps, + plainDateTime, + mod, + dateFieldNamesAlpha, // validFieldNames + timeFieldNamesAsc, // forcedValidFieldNames + ) as DateTimeBag + + const overflow = refineOverflowOptions(options) + const isoDateInternals = calendarOps.dateFromFields( + fields as any, + overrideOverflowOptions(options, overflow), + ) + const isoTimeFields = refineTimeBag(fields, overflow) + + return checkIsoDateTimeInBounds({ + ...isoDateInternals, + ...isoTimeFields, + }) +} + +function mergePlainDateBag( + calendarOps: DateModOps, + plainDate: any, + mod: DateBag, + options: OverflowOptions | undefined, +): PlainDateSlots { + const fields = mergeCalendarFields( + calendarOps, + plainDate, + mod, + dateFieldNamesAlpha, + ) + + return calendarOps.dateFromFields(fields as any, options) +} + +function mergePlainYearMonthBag( + calendarOps: YearMonthModOps, + plainYearMonth: any, + bag: YearMonthBag, + options: OverflowOptions | undefined, +): PlainYearMonthSlots { + const fields = mergeCalendarFields( + calendarOps, + plainYearMonth, + bag, + yearMonthFieldNames, + ) + + return calendarOps.yearMonthFromFields(fields, options) +} + +function mergePlainMonthDayBag( + calendarOps: MonthDayModOps, + plainMonthDay: any, + bag: MonthDayBag, + options: OverflowOptions | undefined, +): PlainMonthDaySlots { + const fields = mergeCalendarFields( + calendarOps, + plainMonthDay, + bag, + dateFieldNamesAlpha, + ) + + return calendarOps.monthDayFromFields(fields, options) +} + +function mergePlainTimeBag( + plainTime: any, + bag: TimeBag, + options: OverflowOptions | undefined, +): IsoTimeFields { + const overflow = refineOverflowOptions(options) // spec says overflow parsed first + const origFields = pluckProps(timeFieldNamesAlpha, plainTime) + const newFields = refineFields(bag, timeFieldNamesAlpha) + const mergedFields = { ...origFields, ...newFields } + + return refineTimeBag(mergedFields, overflow) +} + +function mergeDurationBag( + durationFields: DurationFields, + bag: DurationBag +): DurationFields { + const newFields = refineFields(bag, durationFieldNamesAlpha) + const mergedFields = { ...durationFields, ...newFields } + + return checkDurationFields(mergedFields) +} + +function mergeCalendarFields( + calendarOps: { fields: FieldsOp, mergeFields: MergeFieldsOp }, + obj: Record, + bag: Record, + validFieldNames: string[], // does NOT need to be alphabetized + forcedValidFieldNames: string[] = [], + requiredObjFieldNames: string[] = [], +): Record { + const fieldNames = [ + ...calendarOps.fields(validFieldNames), + ...forcedValidFieldNames + ].sort() + + let fields = refineFields(obj, fieldNames, requiredObjFieldNames) + const partialFields = refineFields(bag, fieldNames) + + fields = calendarOps.mergeFields(fields, partialFields) + return refineFields(fields, fieldNames, []) // guard against ridiculous .mergeField results +} + +// Conversion that involves bags +// ------------------------------------------------------------------------------------------------- + +export function convertToPlainMonthDay( + calendarOps: MonthDayRefineOps, + input: any, +): PlainMonthDaySlots { + const fields = refineCalendarFields( + calendarOps, + input as any, + monthCodeDayFieldNames, + ) + + return calendarOps.monthDayFromFields(fields) +} + +export function convertToPlainYearMonth( + calendarOps: YearMonthRefineOps, + input: any, + options?: OverflowOptions, +): PlainYearMonthSlots { + const fields = refineCalendarFields( + calendarOps, + input as any, + yearMonthCodeFieldNames, + ) + + return calendarOps.yearMonthFromFields(fields, options) +} + +/* +Responsible for ensuring bag is an object. Best place? +*/ +export function convertPlainMonthDayToDate( + calendarOps: DateModOps, + plainMonthDay: any, + bag: YearFields, +): PlainDateSlots { + return convertToIso( + calendarOps, + plainMonthDay, // input + monthCodeDayFieldNames, // inputFieldNames + requireObjectlike(bag), // extra + yearFieldNames, // extraFieldNames + ) +} + +/* +Responsible for ensuring bag is an object. Best place? +*/ +export function convertPlainYearMonthToDate( + calendarOps: DateModOps, + plainYearMonth: any, + bag: DayFields, +): PlainDateSlots { + return convertToIso( + calendarOps, + plainYearMonth, // input + yearMonthCodeFieldNames, // inputFieldNames + requireObjectlike(bag), // extra + dayFieldNames, // extraFieldNames + ) +} + +function convertToIso( + calendarOps: DateModOps, + input: any, + inputFieldNames: string[], // must be alphabetized!!! + extra: {}, + extraFieldNames: string[], // must be alphabetized!!! +): PlainDateSlots { + inputFieldNames = calendarOps.fields(inputFieldNames) + input = pluckProps(inputFieldNames, input as Record) + + extraFieldNames = calendarOps.fields(extraFieldNames) + extra = refineFields(extra, extraFieldNames, []) + + let mergedFields = calendarOps.mergeFields(input, extra) + mergedFields = refineFields(mergedFields, [...inputFieldNames, ...extraFieldNames].sort(), []) + + return calendarOps.dateFromFields(mergedFields as any) +} + +// Native *-from-fields +// ------------------------------------------------------------------------------------------------- + +export function nativeDateFromFields( + this: NativeDateRefineDeps, + fields: DateBag, + options?: OverflowOptions, +): PlainDateSlots { + const overflow = refineOverflowOptions(options) + const year = refineYear(this, fields) + const month = refineMonth(this, fields, year, overflow) + const day = refineDay(this, fields as DayFields, month, year, overflow) + const isoFields = this.isoFields(year, month, day) + + return createPlainDateSlots( + checkIsoDateInBounds(isoFields), + this.id || isoCalendarId, + ) +} + +export function nativeYearMonthFromFields( + this: NativeYearMonthRefineDeps, + fields: YearMonthBag, + options?: OverflowOptions, +): PlainYearMonthSlots { + const overflow = refineOverflowOptions(options) + const year = refineYear(this, fields) + const month = refineMonth(this, fields, year, overflow) + const isoFields = this.isoFields(year, month, 1) + + return createPlainYearMonthSlots( + checkIsoYearMonthInBounds(isoFields), + this.id || isoCalendarId, + ) +} + +export function nativeMonthDayFromFields( + this: NativeMonthDayRefineOps, + fields: DateBag, + options?: OverflowOptions +): PlainMonthDaySlots { + const overflow = refineOverflowOptions(options) + let isIso = !this.id + let { monthCode, year, month } = fields as Partial // correct type? + let monthCodeNumber: number + let isLeapMonth: boolean + let normalYear: number | undefined + let normalMonth: number | undefined + let normalDay: number + + if (monthCode !== undefined) { + [monthCodeNumber, isLeapMonth] = parseMonthCode(monthCode) + normalDay = getDefinedProp(fields, 'day') + + // query calendar for year/month + const res = this.yearMonthForMonthDay(monthCodeNumber, isLeapMonth, normalDay) + if (!res) { throw new RangeError(errorMessages.failedYearGuess) } + [normalYear, normalMonth] = res + + // monthCode conflicts with month? + if (month !== undefined && month !== normalMonth) { + throw new RangeError(errorMessages.mismatchingMonthAndCode) + } + + // constrain (what refineMonth/refineDay would normally do) + if (isIso) { + normalMonth = clampEntity('month', normalMonth, 1, isoMonthsInYear, Overflow.Reject) // reject because never leap months + normalDay = clampEntity('day', normalDay, 1, computeIsoDaysInMonth(year !== undefined ? year : normalYear, normalMonth), overflow) + } + } else { + // refine year/month/day + normalYear = (year === undefined && isIso) ? isoEpochFirstLeapYear : refineYear(this, fields as EraYearOrYear) + normalMonth = refineMonth(this, fields, normalYear, overflow) + normalDay = refineDay(this, fields as DayFields, normalMonth, normalYear, overflow) + + // compute monthCode + const leapMonth = this.leapMonth(normalYear) + isLeapMonth = normalMonth === leapMonth + monthCodeNumber = monthToMonthCodeNumber(normalMonth, leapMonth) + + // query calendar for normalized year/month + const res = this.yearMonthForMonthDay(monthCodeNumber, isLeapMonth, normalDay) + if (!res) { throw new RangeError(errorMessages.failedYearGuess) } + [normalYear, normalMonth] = res + } + + return createPlainMonthDaySlots( + this.isoFields(normalYear, normalMonth, normalDay), + this.id || isoCalendarId, + ) +} + +export function nativeFieldsMethod( + this: NativeYearMonthRefineDeps, + fieldNames: string[], +): string[] { + if (getCalendarEraOrigins(this) && fieldNames.includes('year')) { + return [...fieldNames, ...eraYearFieldNames] + } + return fieldNames +} + +export function nativeMergeFields( + this: NativeYearMonthRefineDeps, + baseFields: Record, + additionalFields: Record +): Record { + const merged = Object.assign(Object.create(null), baseFields) + + spliceFields(merged, additionalFields, monthFieldNames) + + if (getCalendarEraOrigins(this)) { + spliceFields(merged, additionalFields, allYearFieldNames) + + // eras begin mid-year? + if (this.id === japaneseCalendarId) { + spliceFields( + merged, + additionalFields, + monthDayFieldNames, // any found? + eraYearFieldNames, // then, delete these + ) + } + } + + return merged +} + +// Low-level Native Utils +// ------------------------------------------------------------------------------------------------- + +function refineYear( + calendarNative: NativeYearMonthRefineDeps, + fields: DateBag +): number { + let { era, eraYear, year } = fields + const eraOrigins = getCalendarEraOrigins(calendarNative) + + if (era !== undefined || eraYear !== undefined) { + if (era === undefined || eraYear === undefined) { + throw new TypeError(errorMessages.mismatchingEraParts) + } + + if (!eraOrigins) { + throw new RangeError(errorMessages.forbiddenEraParts) + } + + const eraOrigin = eraOrigins[era] + if (eraOrigin === undefined) { + throw new RangeError(errorMessages.invalidEra(era)) + } + + const yearByEra = eraYearToYear(eraYear, eraOrigin) + if (year !== undefined && year !== yearByEra) { + throw new RangeError(errorMessages.mismatchingYearAndEra) + } + + year = yearByEra + } else if (year === undefined) { + throw new TypeError(errorMessages.missingYear(eraOrigins)) + } + + return year +} + +function refineMonth( + calendarNative: NativeYearMonthRefineDeps, + fields: Partial, + year: number, + overflow: Overflow +): number { + let { month, monthCode } = fields + + if (monthCode !== undefined) { + const monthByCode = refineMonthCode(calendarNative, monthCode, year, overflow) + + if (month !== undefined && month !== monthByCode) { + throw new RangeError(errorMessages.mismatchingMonthAndCode) + } + + month = monthByCode + overflow = Overflow.Reject // monthCode parsing doesn't constrain + } else if (month === undefined) { + throw new TypeError(errorMessages.missingMonth) + } + + return clampEntity( + 'month', + month, + 1, + calendarNative.monthsInYearPart(year), + overflow + ) +} + +function refineMonthCode( + calendarNative: NativeYearMonthRefineDeps, + monthCode: string, + year: number, + overflow: Overflow, +) { + const leapMonth = calendarNative.leapMonth(year) + const [monthCodeNumber, wantsLeapMonth] = parseMonthCode(monthCode) + let month = monthCodeNumberToMonth(monthCodeNumber, wantsLeapMonth, leapMonth) + + if (wantsLeapMonth) { + const leapMonthMeta = getCalendarLeapMonthMeta(calendarNative) + + // calendar does not support leap years + if (leapMonthMeta === undefined) { + throw new RangeError(errorMessages.invalidLeapMonth) + } + + // leap year has a maximum + else if (leapMonthMeta > 0) { + if (month > leapMonthMeta) { + throw new RangeError(errorMessages.invalidLeapMonth) + } + if (leapMonth === undefined) { + if (overflow === Overflow.Reject) { + throw new RangeError(errorMessages.invalidLeapMonth) + } else { + month-- // M05L -> M05 + } + } + } + + // leap year is constant + else { + if (month !== -leapMonthMeta) { + throw new RangeError(errorMessages.invalidLeapMonth) + } + if (leapMonth === undefined) { + if (overflow === Overflow.Reject) { + throw new RangeError(errorMessages.invalidLeapMonth) + } else { + // ex: M05L -> M06 + } + } + } + } + + return month +} + +function refineDay( + calendarNative: NativeDateRefineDeps, + fields: DayFields, + month: number, + year: number, + overflow?: Overflow +): number { + return clampProp( + fields, + 'day', + 1, + calendarNative.daysInMonthParts(year, month), + overflow + ) +} + +function spliceFields( + dest: any, + additional: any, + allPropNames: string[], + deletablePropNames?: string[], +): void { + let anyMatching = false + const nonMatchingPropNames: string[] = [] + + for (const propName of allPropNames) { + if (additional[propName] !== undefined) { + anyMatching = true + } else { + nonMatchingPropNames.push(propName) + } + } + + Object.assign(dest, additional) + + if (anyMatching) { + for (const deletablePropName of (deletablePropNames || nonMatchingPropNames)) { + delete dest[deletablePropName] + } + } +} diff --git a/packages/temporal-polyfill/src/internal/calendarConfig.ts b/packages/temporal-polyfill/src/internal/calendarConfig.ts new file mode 100644 index 00000000..7dbb8308 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/calendarConfig.ts @@ -0,0 +1,84 @@ +import { dayFieldNames, yearFieldNames } from "./calendarFields" + +export const isoCalendarId = 'iso8601' +export const gregoryCalendarId = 'gregory' +export const japaneseCalendarId = 'japanese' + +/* +for converting from [era,eraYear] -> year +if origin is >=0, + year = origin + eraYear +if origin is <0, consider the era to be 'reverse' direction + year = -origin - eraYear, same as... + year = -(origin + eraYear) +*/ +export const eraOriginsByCalendarId: { + [calendarId: string]: Record +} = { + [gregoryCalendarId]: { + 'bce': -1, + 'ce': 0, + }, + [japaneseCalendarId]: { + 'bce': -1, + 'ce': 0, + 'meiji': 1867, + 'taisho': 1911, + 'showa': 1925, + 'heisei': 1988, + 'reiwa': 2018, + }, + 'ethioaa': { + 'era0': 0, + }, + 'ethiopic': { + 'era0': 0, + 'era1': 5500, + }, + 'coptic': { + 'era0': -1, + 'era1': 0, + }, + 'roc': { + 'beforeroc': -1, + 'minguo': 0, + }, + 'buddhist': { + 'be': 0, + }, + 'islamic': { + 'ah': 0, + }, + 'indian': { + 'saka': 0, + }, + 'persian': { + 'ap': 0, + }, +} + +export const eraRemaps: Record = { + 'bc': 'bce', + 'ad': 'ce', +} + +export const leapMonthMetas: Record = { + 'chinese': 13, // (positive) max possible leap month + 'dangi': 13, // " + 'hebrew': -6, // (negative) constant leap month +} + +// only used by calendar +// --------------------- + +export function getRequiredYearMonthFields(calendarId: string): string[] { + return calendarId === isoCalendarId ? yearFieldNames : [] +} + +export function getRequiredMonthDayFields(calendarId: string): string[] { + return calendarId === isoCalendarId ? dayFieldNames : [] +} + +export function getRequiredDateFields(calendarId: string): string[] { + return calendarId === isoCalendarId ? ['year', 'day'] : [] +} diff --git a/packages/temporal-polyfill/src/internal/calendarFields.ts b/packages/temporal-polyfill/src/internal/calendarFields.ts new file mode 100644 index 00000000..6c09b542 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/calendarFields.ts @@ -0,0 +1,134 @@ +import { mapPropNamesToConstant } from './utils' +import { DurationFields } from './durationFields' +import { Unit, unitNamesAsc } from './units' + +// Year/Month/Day (no era/eraYear) +// ------------------------------------------------------------------------------------------------- + +export interface YearFields { + year: number +} + +export interface MonthFields { + monthCode: string + month: number +} + +export interface DayFields { + day: number +} + +export type YearMonthFields = YearFields & MonthFields +export type DateFields = YearMonthFields & DayFields +export type MonthDayFields = MonthFields & DayFields + +// Fields with era/eraYear +// ------------------------------------------------------------------------------------------------- + +export interface EraYearFields { + era: string + eraYear: number +} + +export type YearFieldsIntl = EraYearFields & YearFields +export type YearMonthFieldsIntl = EraYearFields & YearMonthFields +export type DateFieldsIntl = EraYearFields & DateFields +export type MonthDayFieldsIntl = MonthDayFields // this is stupid + +// Simple Bag (all props optional) +// ------------------------------------------------------------------------------------------------- +// TODO: move to bag.ts? + +export type YearMonthBag = Partial +export type DateBag = Partial +export type MonthDayBag = Partial +export type DurationBag = Partial + +// Strict Bag (with complex expressions) +// ------------------------------------------------------------------------------------------------- + +export type EraYearOrYear = EraYearFields | YearFields +export type MonthCodeOrMonthAndYear = { monthCode: string } | ({ month: number } & EraYearOrYear) +export type MonthCodeOrMonth = { monthCode: string } | { month: number } + +export type YearMonthBagStrict = EraYearOrYear & MonthCodeOrMonth +export type DateBagStrict = EraYearOrYear & MonthCodeOrMonth & DayFields +export type MonthDayBagStrict = MonthCodeOrMonthAndYear & DayFields + +// ------------------------------------------------------------------------------------------------- + +export interface TimeFields { + hour: number + microsecond: number + millisecond: number + minute: number + nanosecond: number + second: number +} + +export type TimeBag = Partial +export type DateTimeBag = DateBag & TimeBag // TODO: use for PlainDateTime? +export type DateTimeFields = DateFields & TimeFields + +export interface YearMonthBasics { + year: number + month: number +} + +export interface MonthDayBasics { + monthCode: string + day: number +} + +export interface YearStats { + daysInYear: number + inLeapYear: boolean + monthsInYear: number +} + +export interface YearMonthStats extends YearStats { + daysInMonth: number +} + +export interface DateStats extends YearMonthStats { + dayOfWeek: number + dayOfYear: number + weekOfYear: number + yearOfWeek: number + daysInWeek: number +} + +// Field Names +// ------------------------------------------------------------------------------------------------- + +export const timeFieldNamesAsc = unitNamesAsc.slice(0, Unit.Day) as (keyof TimeFields)[] +export const timeFieldNamesAlpha = timeFieldNamesAsc.slice().sort() + +export const offsetFieldNames = ['offset'] +export const timeZoneFieldNames = ['timeZone'] + +export const timeAndOffsetFieldNames = [...timeFieldNamesAsc, ...offsetFieldNames] +export const timeAndZoneFieldNames = [...timeAndOffsetFieldNames, ...timeZoneFieldNames] + +// pre-sorted!!!... + +export const eraYearFieldNames = ['era', 'eraYear'] +export const allYearFieldNames = [...eraYearFieldNames, 'year'] + +export const yearFieldNames = ['year'] +export const monthCodeFieldNames = ['monthCode'] +export const monthFieldNames = ['month', ...monthCodeFieldNames] // month/monthCode +export const dayFieldNames = ['day'] + +export const yearMonthFieldNames = [...monthFieldNames, ...yearFieldNames] // month/monthCode/year +export const yearMonthCodeFieldNames = [...monthCodeFieldNames, ...yearFieldNames] // monthCode/year + +export const dateFieldNamesAlpha = [...dayFieldNames, ...yearMonthFieldNames] + +export const monthDayFieldNames = [...dayFieldNames, ...monthFieldNames] // day/month/monthCode +export const monthCodeDayFieldNames = [...dayFieldNames, ...monthCodeFieldNames] // day/monthCode + +// Defaults +// ------------------------------------------------------------------------------------------------- + +export const timeFieldDefaults = mapPropNamesToConstant(timeFieldNamesAsc, 0) diff --git a/packages/temporal-polyfill/src/internal/calendarIntl.ts b/packages/temporal-polyfill/src/internal/calendarIntl.ts new file mode 100644 index 00000000..d846cd32 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/calendarIntl.ts @@ -0,0 +1,348 @@ +import { eraOriginsByCalendarId, eraRemaps } from './calendarConfig' +import { diffEpochMilliByDay } from './diff' +import { OrigDateTimeFormat, hashIntlFormatParts, standardLocaleId } from './formatIntl' +import { IsoDateFields, isoTimeFieldDefaults } from './calendarIsoFields' +import { isoEpochFirstLeapYear, isoEpochOriginYear, isoMonthsInYear } from './calendarIso' +import { checkIsoDateInBounds, epochMilliToIso, isoArgsToEpochMilli, isoToEpochMilli } from './epochAndTime' +import { utcTimeZoneId } from './timeZoneNative' +import { milliInDay } from './units' +import { compareNumbers, createLazyGenerator, mapPropNamesToIndex } from './utils' +import { DateParts, EraParts, MonthCodeParts, NativeCalendar, YearMonthParts, computeCalendarIdBase, eraYearToYear, getCalendarLeapMonthMeta, monthCodeNumberToMonth, monthToMonthCodeNumber } from './calendarNative' +import * as errorMessages from './errorMessages' + +interface IntlDateFields { + era: string | undefined + eraYear: number | undefined + year: number + month: string // string! + day: number +} + +interface IntlYearMonths { + monthEpochMilli: number[], + monthStrToIndex: Record +} + +export interface IntlCalendar extends NativeCalendar { + queryFields: (isoFields: IsoDateFields) => IntlDateFields + queryYearMonths: (year: number) => IntlYearMonths +} + +// ------------------------------------------------------------------------------------------------- + +/* +Expects an already-normalized calendarId +*/ +export const queryIntlCalendar = createLazyGenerator(createIntlCalendar) + +function createIntlCalendar(calendarId: string): IntlCalendar { + const intlFormat = buildIntlFormat(calendarId) + const calendarIdBase = computeCalendarIdBase(calendarId) + + if (calendarIdBase !== computeCalendarIdBase(intlFormat.resolvedOptions().calendar)) { + throw new RangeError(errorMessages.invalidCalendar(calendarId)) + } + + function epochMilliToIntlFields(epochMilli: number) { + const intlPartsHash = hashIntlFormatParts(intlFormat, epochMilli) + return parseIntlDateFields(intlPartsHash, calendarIdBase) + } + + return { + id: calendarId, + queryFields: createIntlFieldCache(epochMilliToIntlFields), + queryYearMonths: createIntlYearMonthCache(epochMilliToIntlFields), + } +} + +// Caches +// ------------------------------------------------------------------------------------------------- + +function createIntlFieldCache( + epochMilliToIntlFields: (epochMilli: number) => IntlDateFields, +) { + return createLazyGenerator((isoDateFields: IsoDateFields) => { + const epochMilli = isoToEpochMilli(isoDateFields)! + return epochMilliToIntlFields(epochMilli) + }, WeakMap) +} + +function createIntlYearMonthCache( + epochMilliToIntlFields: (epochMilli: number) => IntlDateFields, +): ( + (year: number) => IntlYearMonths +) { + const yearAtEpoch = epochMilliToIntlFields(0).year + const yearCorrection = yearAtEpoch - isoEpochOriginYear + + function buildYear(year: number) { + let epochMilli = isoArgsToEpochMilli(year - yearCorrection)! + let intlFields + const milliReversed: number[] = [] + const monthStrsReversed: string[] = [] + + // move beyond current year + do { + epochMilli += 400 * milliInDay + } while ((intlFields = epochMilliToIntlFields(epochMilli)).year <= year) + + do { + // move to start-of-month + epochMilli += (1 - intlFields.day) * milliInDay + + // only record the epochMilli if current year + if (intlFields.year === year) { + milliReversed.push(epochMilli) + monthStrsReversed.push(intlFields.month) + } + + // move to last day of previous month + epochMilli -= milliInDay + } while ((intlFields = epochMilliToIntlFields(epochMilli)).year >= year) + + return { + monthEpochMilli: milliReversed.reverse(), + monthStrToIndex: mapPropNamesToIndex(monthStrsReversed.reverse()), + } + } + + return createLazyGenerator(buildYear) +} + +// DateTimeFormat Utils +// ------------------------------------------------------------------------------------------------- + +function parseIntlDateFields( + intlPartsHash: Record, + calendarIdBase: string, +): IntlDateFields { + return { + ...parseIntlYear(intlPartsHash, calendarIdBase), + month: intlPartsHash.month, // a short month string + day: parseInt(intlPartsHash.day), + } +} + +export function parseIntlYear( + intlPartsHash: Record, + calendarIdBase: string, +): { + era: string | undefined + eraYear: number | undefined + year: number +} { + let year = parseInt(intlPartsHash.relatedYear || intlPartsHash.year) + let era: string | undefined + let eraYear: number | undefined + + if (intlPartsHash.era) { + const eraOrigins = eraOriginsByCalendarId[calendarIdBase] + if (eraOrigins !== undefined) { + era = normalizeShortEra(intlPartsHash.era) + eraYear = year // TODO: will this get optimized to next line? + year = eraYearToYear(eraYear, eraOrigins[era] || 0) + } + } + + return { era, eraYear, year } +} + +// TODO: rename to be about calendar +export function buildIntlFormat(calendarId: string): Intl.DateTimeFormat { + return new OrigDateTimeFormat(standardLocaleId, { + calendar: calendarId, + timeZone: utcTimeZoneId, + era: 'short', // 'narrow' is too terse for japanese months + year: 'numeric', + month: 'short', // easier to identify monthCodes + day: 'numeric', + }) +} + +function normalizeShortEra(formattedEra: string): string { + formattedEra = formattedEra + .normalize('NFD') // 'Shōwa' -> 'Showa' + .toLowerCase() // 'Before R.O.C.' -> 'before r.o.c.' + .replace(/[^a-z0-9]/g, '') // 'before r.o.c.' -> 'beforeroc' + + return eraRemaps[formattedEra] || formattedEra +} + +// Intl-Calendar methods +// ------------------------------------------------------------------------------------------------- + +export function computeIntlYear(this: IntlCalendar, isoFields: IsoDateFields): number { + return this.queryFields(isoFields).year +} + +export function computeIntlMonth(this: IntlCalendar, isoFields: IsoDateFields): number { + const { year, month } = this.queryFields(isoFields) + const { monthStrToIndex } = this.queryYearMonths(year) + return monthStrToIndex[month] + 1 +} + +export function computeIntlDay(this: IntlCalendar, isoFields: IsoDateFields): number { + return this.queryFields(isoFields).day +} + +export function computeIntlDateParts(this: IntlCalendar, isoFields: IsoDateFields): DateParts { + const { year, month, day } = this.queryFields(isoFields) + const { monthStrToIndex } = this.queryYearMonths(year) + return [year, monthStrToIndex[month] + 1, day] +} + +export function computeIsoFieldsFromIntlParts( + this: IntlCalendar, + year: number, + month?: number, + day?: number, +): IsoDateFields { + // check might be redundant if happens in epochMilliToIso/queryDateStart + // TODO: i don't like that this is happening here + return checkIsoDateInBounds({ + ...epochMilliToIso(computeIntlEpochMilli.call(this, year, month, day)), + }) +} + +export function computeIntlEpochMilli( + this: IntlCalendar, + year: number, + month: number = 1, + day: number = 1, +): number { + return this.queryYearMonths(year).monthEpochMilli[month - 1] + + (day - 1) * milliInDay +} + +export function computeIntlMonthCodeParts( + this: IntlCalendar, + year: number, + month: number, +): MonthCodeParts { + const leapMonth = computeIntlLeapMonth.call(this, year) + const monthCodeNumber = monthToMonthCodeNumber(month, leapMonth) + const isLeapMonth = leapMonth === month + return [monthCodeNumber, isLeapMonth] +} + +export function computeIntlLeapMonth( + this: IntlCalendar, + year: number, +): number | undefined { + const currentMonthStrs = queryMonthStrs(this, year) + const prevMonthStrs = queryMonthStrs(this, year - 1) + const currentLength = currentMonthStrs.length + + if (currentLength > prevMonthStrs.length) { + // hardcoded leap month. usually means complex month-code schemes + const leapMonthMeta = getCalendarLeapMonthMeta(this) as number // hack for <0 + if (leapMonthMeta < 0) { + return -leapMonthMeta + } + + for (let i = 0; i < currentLength; i++) { + if (currentMonthStrs[i] !== prevMonthStrs[i]) { + return i + 1 // convert to 1-based + } + } + } +} + +export function computeIntlInLeapYear(this: IntlCalendar, year: number): boolean { + const days = computeIntlDaysInYear.call(this, year) + return days > computeIntlDaysInYear.call(this, year - 1) && + days > computeIntlDaysInYear.call(this, year + 1) +} + +export function computeIntlDaysInYear(this: IntlCalendar, year: number): number { + const milli = computeIntlEpochMilli.call(this, year) + const milliNext = computeIntlEpochMilli.call(this, year + 1) + return diffEpochMilliByDay(milli, milliNext) +} + +export function computeIntlDaysInMonth(this: IntlCalendar, year: number, month: number): number { + const { monthEpochMilli } = this.queryYearMonths(year) + let nextMonth = month + 1 + let nextMonthEpochMilli = monthEpochMilli + + if (nextMonth > monthEpochMilli.length) { + nextMonth = 1 + nextMonthEpochMilli = this.queryYearMonths(year + 1).monthEpochMilli + } + + return diffEpochMilliByDay( + monthEpochMilli[month - 1], + nextMonthEpochMilli[nextMonth - 1], + ) +} + +export function computeIntlDayOfYear( + this: IntlCalendar, + isoFields: IsoDateFields, +): number { + const dayEpochMilli = isoToEpochMilli({ + ...isoFields, + ...isoTimeFieldDefaults, + })! + const { year } = this.queryFields(isoFields) + const yearStartEpochMilli = computeIntlEpochMilli.call(this, year) + return diffEpochMilliByDay(yearStartEpochMilli, dayEpochMilli) +} + +export function computeIntlMonthsInYear(this: IntlCalendar, year: number): number { + return this.queryYearMonths(year).monthEpochMilli.length +} + +export function computeIntlEraParts(this: IntlCalendar, isoFields: IsoDateFields): EraParts { + const intlFields = this.queryFields(isoFields) + return [intlFields.era, intlFields.eraYear] +} + +export function computeIntlYearMonthForMonthDay( + this: IntlCalendar, + monthCodeNumber: number, + isLeapMonth: boolean, + day: number, +): YearMonthParts | undefined { + let [startYear, startMonth, startDay] = computeIntlDateParts.call(this, { + isoYear: isoEpochFirstLeapYear, + isoMonth: isoMonthsInYear, + isoDay: 31, + }) + const startYearLeapMonth = computeIntlLeapMonth.call(this, startYear) + const startMonthCodeNumber = monthToMonthCodeNumber(startMonth, startYearLeapMonth) + const startMonthIsLeap = startMonth === startYearLeapMonth + + // If startYear doesn't span isoEpochFirstLeapYear, walk backwards + // TODO: smaller way to do this with epochMilli comparison? + if ( + ( + compareNumbers(monthCodeNumber, startMonthCodeNumber) || + compareNumbers(Number(isLeapMonth), Number(startMonthIsLeap)) || + compareNumbers(day, startDay) + ) === 1 + ) { + startYear-- + } + + // Walk backwards until finding a year with monthCode/day + for (let yearMove = 0; yearMove < 100; yearMove++) { + const tryYear = startYear - yearMove + const tryLeapMonth = computeIntlLeapMonth.call(this, tryYear) + const tryMonth = monthCodeNumberToMonth(monthCodeNumber, isLeapMonth, tryLeapMonth) + const tryMonthIsLeap = tryMonth === tryLeapMonth + + if ( + isLeapMonth === tryMonthIsLeap && + day <= computeIntlDaysInMonth.call(this, tryYear, tryMonth) + ) { + return [tryYear, tryMonth] + } + } +} + +// ------------------------------------------------------------------------------------------------- + +function queryMonthStrs(intlCalendar: IntlCalendar, year: number): string[] { + return Object.keys(intlCalendar.queryYearMonths(year).monthStrToIndex) +} diff --git a/packages/temporal-polyfill/src/internal/calendarIso.ts b/packages/temporal-polyfill/src/internal/calendarIso.ts new file mode 100644 index 00000000..43334eca --- /dev/null +++ b/packages/temporal-polyfill/src/internal/calendarIso.ts @@ -0,0 +1,226 @@ +import { isoArgsToEpochMilli, isoToEpochMilli, isoToLegacyDate } from './epochAndTime' +import { IsoDateFields, IsoDateTimeFields, IsoTimeFields, isoDateFieldNamesAsc, isoTimeFieldDefaults, isoTimeFieldNamesAsc } from './calendarIsoFields' +import { diffEpochMilliByDay } from './diff' +import { allFieldsEqual, clampProp, createLazyGenerator, modFloor, zipProps } from './utils' +import { DateParts, EraParts, MonthCodeParts, NativeCalendar, YearMonthParts } from './calendarNative' +import { gregoryCalendarId, japaneseCalendarId } from './calendarConfig' +import { buildIntlFormat, parseIntlYear } from './calendarIntl' +import { hashIntlFormatParts } from './formatIntl' +import { Overflow } from './options' + +export const isoEpochOriginYear = 1970 +export const isoEpochFirstLeapYear = 1972 +export const isoMonthsInYear = 12 +export const isoDaysInWeek = 7 + +export function computeIsoYear(isoFields: IsoDateFields): number { + return isoFields.isoYear +} + +export function computeIsoMonth(isoFields: IsoDateFields): number { + return isoFields.isoMonth +} + +export function computeIsoDay(isoFields: IsoDateFields): number { + return isoFields.isoDay +} + +export function computeIsoDateParts(isoFields: IsoDateFields): DateParts { + return [isoFields.isoYear, isoFields.isoMonth, isoFields.isoDay] +} + +export function computeIsoMonthCodeParts(isoYear: number, isoMonth: number): MonthCodeParts { + return [isoMonth, false] +} + +export function computeIsoYearMonthForMonthDay( + monthCodeNumber: number, + isLeapMonth: boolean, + day: number, +): YearMonthParts | undefined { + if (!isLeapMonth) { + return [isoEpochFirstLeapYear, monthCodeNumber] + } +} + +export function computeIsoFieldsFromParts(year: number, month: number, day: number): IsoDateFields { + return { isoYear: year, isoMonth: month, isoDay: day } +} + +export function computeIsoDaysInWeek(isoDateFields: IsoDateFields) { + return isoDaysInWeek +} + +export function computeIsoMonthsInYear(isoYear: number): number { // for methods + return isoMonthsInYear +} + +export function computeIsoDaysInMonth(isoYear: number, isoMonth: number): number { + switch (isoMonth) { + case 2: + return computeIsoInLeapYear(isoYear) ? 29 : 28 + case 4: + case 6: + case 9: + case 11: + return 30 + } + return 31 +} + +export function computeIsoDaysInYear(isoYear: number): number { + return computeIsoInLeapYear(isoYear) ? 366 : 365 +} + +export function computeIsoInLeapYear(isoYear: number): boolean { + // % is dangerous, but comparing 0 with -0 is fine + return isoYear % 4 === 0 && (isoYear % 100 !== 0 || isoYear % 400 === 0) +} + +export function computeIsoDayOfYear(isoDateFields: IsoDateFields): number { + return diffEpochMilliByDay( + isoToEpochMilli(isoDateYearStart(isoDateFields))!, + isoToEpochMilli({ ...isoDateFields, ...isoTimeFieldDefaults })!, + ) + 1 +} + +export function computeIsoDayOfWeek(isoDateFields: IsoDateFields): number { + const [legacyDate, nudge] = isoToLegacyDate( + isoDateFields.isoYear, + isoDateFields.isoMonth, + isoDateFields.isoDay, + ) + + return modFloor(legacyDate.getDay() + 1 - nudge, 7) || 7 +} + +export function computeIsoYearOfWeek(isoDateFields: IsoDateFields): number { + return computeIsoWeekParts(isoDateFields)[0] +} + +export function computeIsoWeekOfYear(isoDateFields: IsoDateFields): number { + return computeIsoWeekParts(isoDateFields)[1] +} + +type WeekParts = [ + isoYear: number, + isoWeek: number, +] + +function computeIsoWeekParts(isoDateFields: IsoDateFields): WeekParts { + const doy = computeIsoDayOfYear(isoDateFields) + const dow = computeIsoDayOfWeek(isoDateFields) + const doj = computeIsoDayOfWeek(isoDateYearStart(isoDateFields)) + const isoWeek = Math.floor((doy - dow + 10) / isoDaysInWeek) + const { isoYear } = isoDateFields + + if (isoWeek < 1) { + return [ + isoYear - 1, + (doj === 5 || (doj === 6 && computeIsoInLeapYear(isoYear - 1))) ? 53 : 52, + ] + } + if (isoWeek === 53) { + if (computeIsoDaysInYear(isoYear) - doy < 4 - dow) { + return [ + isoYear + 1, + 1, + ] + } + } + + return [isoYear, isoWeek] +} + +function isoDateYearStart(isoDateFields: IsoDateFields): IsoDateFields { + return { + ...isoDateFields, + isoMonth: 1, + isoDay: 1, + ...isoTimeFieldDefaults, + } +} + +// Era (complicated stuff) +// ------------------------------------------------------------------------------------------------- + +export function computeIsoEraParts(this: NativeCalendar, isoFields: IsoDateFields): EraParts { + if (this.id === gregoryCalendarId) { + return computeGregoryEraParts(isoFields) + } + + if (this.id === japaneseCalendarId) { + return queryJapaneseEraParts(isoFields) + } + + return [undefined, undefined] // iso +} + +function computeGregoryEraParts({ isoYear }: IsoDateFields): EraParts { + if (isoYear < 1) { + return ['bce', -isoYear + 1] + } + return ['ce', isoYear] +} + +const queryJapaneseEraParts = createLazyGenerator(computeJapaneseEraParts, WeakMap) +const primaryJapaneseEraMilli = isoArgsToEpochMilli(1868, 9, 8)! +let japeneseEraFormat: Intl.DateTimeFormat + +function computeJapaneseEraParts(isoFields: IsoDateFields): EraParts { + const epochMilli = isoToEpochMilli(isoFields)! + + if (epochMilli < primaryJapaneseEraMilli) { + return computeGregoryEraParts(isoFields) + } + + japeneseEraFormat ||= buildIntlFormat(japaneseCalendarId) + const intlPartsHash = hashIntlFormatParts(japeneseEraFormat, epochMilli) + const { era, eraYear } = parseIntlYear(intlPartsHash, japaneseCalendarId) + return [era, eraYear] +} + +// Checking Fields +// ------------------------------------------------------------------------------------------------- + +export function checkIsoDateTimeFields

(isoDateTimeFields: P): P { + checkIsoDateFields(isoDateTimeFields) + constrainIsoTimeFields(isoDateTimeFields, Overflow.Reject) + return isoDateTimeFields +} + +export function checkIsoDateFields

(isoInternals: P): P { + constrainIsoDateFields(isoInternals, Overflow.Reject) + return isoInternals +} + +export function isIsoDateFieldsValid(isoFields: IsoDateFields): boolean { + return allFieldsEqual(isoDateFieldNamesAsc, isoFields, constrainIsoDateFields(isoFields)) +} + +// Constraining +// ------------------------------------------------------------------------------------------------- + +function constrainIsoDateFields( + isoFields: IsoDateFields, + overflow?: Overflow, +): IsoDateFields { + const { isoYear } = isoFields + const isoMonth = clampProp(isoFields, 'isoMonth', 1, computeIsoMonthsInYear(isoYear), overflow) + const isoDay = clampProp(isoFields, 'isoDay', 1, computeIsoDaysInMonth(isoYear, isoMonth), overflow) + return { isoYear, isoMonth, isoDay } +} + +export function constrainIsoTimeFields( + isoTimeFields: IsoTimeFields, + overflow?: Overflow, +): IsoTimeFields { + return zipProps(isoTimeFieldNamesAsc, [ + clampProp(isoTimeFields, 'isoHour', 0, 23, overflow), + clampProp(isoTimeFields, 'isoMinute', 0, 59, overflow), + clampProp(isoTimeFields, 'isoSecond', 0, 59, overflow), + clampProp(isoTimeFields, 'isoMillisecond', 0, 999, overflow), + clampProp(isoTimeFields, 'isoMicrosecond', 0, 999, overflow), + clampProp(isoTimeFields, 'isoNanosecond', 0, 999, overflow), + ]) +} diff --git a/packages/temporal-polyfill/src/internal/calendarIsoFields.ts b/packages/temporal-polyfill/src/internal/calendarIsoFields.ts new file mode 100644 index 00000000..f73963f0 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/calendarIsoFields.ts @@ -0,0 +1,51 @@ +import { mapPropNamesToConstant } from './utils' + +export interface IsoDateFields { + isoDay: number + isoMonth: number + isoYear: number +} + +export interface IsoTimeFields { + isoNanosecond: number, + isoMicrosecond: number, + isoMillisecond: number, + isoSecond: number, + isoMinute: number, + isoHour: number, +} + +export type IsoDateTimeFields = IsoDateFields & IsoTimeFields + +// Property Names +// ------------------------------------------------------------------------------------------------- + +export const isoTimeFieldNamesAsc: (keyof IsoTimeFields)[] = [ + 'isoNanosecond', + 'isoMicrosecond', + 'isoMillisecond', + 'isoSecond', + 'isoMinute', + 'isoHour', +] + +export const isoDateFieldNamesAsc: (keyof IsoDateFields)[] = [ + 'isoDay', + 'isoMonth', + 'isoYear', +] + +export const isoDateTimeFieldNamesAsc: (keyof IsoDateTimeFields)[] = [ + ...isoTimeFieldNamesAsc, + ...isoDateFieldNamesAsc, +] + +// alphabetical (for getISOFields) +export const isoDateFieldNamesAlpha = isoDateFieldNamesAsc.slice().sort() +export const isoTimeFieldNamesAlpha = isoTimeFieldNamesAsc.slice().sort() +export const isoDateTimeFieldNamesAlpha = isoDateTimeFieldNamesAsc.slice().sort() + +// Defaults +// ------------------------------------------------------------------------------------------------- + +export const isoTimeFieldDefaults = mapPropNamesToConstant(isoTimeFieldNamesAlpha, 0) diff --git a/packages/temporal-polyfill/src/internal/calendarNative.ts b/packages/temporal-polyfill/src/internal/calendarNative.ts new file mode 100644 index 00000000..c7101a2a --- /dev/null +++ b/packages/temporal-polyfill/src/internal/calendarNative.ts @@ -0,0 +1,413 @@ +import { nativeDateFromFields, nativeMonthDayFromFields, nativeYearMonthFromFields, nativeFieldsMethod, nativeMergeFields } from './bag' +import { DateRefineOps, DayOp, DiffOps, MergeFieldsOp, MonthDayRefineOps, MoveOps, YearMonthRefineOps } from './calendarOps' +import { nativeDateUntil } from './diff' +import { IsoDateFields } from './calendarIsoFields' +import { computeIsoDayOfWeek, computeIsoDaysInWeek, computeIsoWeekOfYear, computeIsoYearOfWeek } from './calendarIso' +import { nativeDateAdd } from './move' +import { padNumber2 } from './utils' +import { eraOriginsByCalendarId, isoCalendarId, leapMonthMetas } from './calendarConfig' +import * as errorMessages from './errorMessages' + +// Struct Types +export type DateParts = [year: number, month: number, day: number] +export type EraParts = [era: string | undefined, eraYear: number | undefined ] +export type MonthCodeParts = [monthCodeNumber: number, isLeapMonth: boolean] +export type YearMonthParts = [year: number, month: number] + +// Function Types +// (Must always be called from a CalendarOps object) +export type DatePartsOp = (isoFields: IsoDateFields) => DateParts +export type EraOp = (isoFields: IsoDateFields) => string | undefined +export type EraYearOp = (isoFields: IsoDateFields) => number | undefined +export type EraPartsOp = (isoFields: IsoDateFields) => EraParts +export type MonthCodeOp = (isoFields: IsoDateFields) => string +export type MonthCodePartsOp = (year: number, month: number) => MonthCodeParts +export type YearMonthForMonthDayOp = (monthCodeNumber: number, isLeapMonth: boolean, day: number) => YearMonthParts | undefined // receives positive monthCode integer +export type InLeapYearOp = (isoFields: IsoDateFields) => boolean +export type InLeapYearPartOp = (year: number) => boolean +export type LeapMonthOp = (year: number) => number | undefined +export type MonthsInYearOp = (isoDateFields: IsoDateFields) => number +export type MonthsInYearPartOp = (year: number) => number +export type MonthsInYearSpanOp = (yearDelta: number, yearStart: number) => number +export type DaysInMonthOp = (isoFields: IsoDateFields) => number +export type DaysInMonthPartsOp = (year: number, month: number) => number +export type DaysInYearOp = (isoFields: IsoDateFields) => number +export type DaysInYearPartOp = (year: number) => number +export type DayOfYearOp = (isoFields: IsoDateFields) => number +export type EpochMilliOp = (year: number, month?: number, day?: number) => number +export type IsoFieldsOp = (year: number, month: number, day: number) => IsoDateFields +export type MonthAddOp = (year: number, month: number, monthDelta: number) => YearMonthParts +export type GetEraOrigins = () => Record | undefined +export type GetLeapMonthMeta = () => number | undefined + +// Internal State +export interface NativeCalendar { + id?: string // if not defined, then iso calendar +} + +// Refine +// ------------------------------------------------------------------------------------------------- + +export type NativeYearMonthRefineDeps = NativeCalendar & { + leapMonth: LeapMonthOp + monthsInYearPart: MonthsInYearPartOp + isoFields: IsoFieldsOp +} + +export type NativeDateRefineDeps = NativeYearMonthRefineDeps & { + daysInMonthParts: DaysInMonthPartsOp +} + +export type NativeMonthDayRefineDeps = NativeDateRefineDeps & { + yearMonthForMonthDay: YearMonthForMonthDayOp +} + +export type NativeYearMonthRefineOps = YearMonthRefineOps & NativeYearMonthRefineDeps +export type NativeDateRefineOps = DateRefineOps & NativeDateRefineDeps +export type NativeMonthDayRefineOps = MonthDayRefineOps & NativeMonthDayRefineDeps + +// Base + +export const nativeYearMonthRefineBase: YearMonthRefineOps = { + yearMonthFromFields: nativeYearMonthFromFields, + fields: nativeFieldsMethod, +} + +export const nativeDateRefineBase: DateRefineOps = { + dateFromFields: nativeDateFromFields, + fields: nativeFieldsMethod, +} + +export const nativeMonthDayRefineBase: MonthDayRefineOps = { + monthDayFromFields: nativeMonthDayFromFields, + fields: nativeFieldsMethod, +} + +// Mod +// ------------------------------------------------------------------------------------------------- + +export type NativeYearMonthModOps = NativeYearMonthRefineOps & { mergeFields: MergeFieldsOp } +export type NativeDateModOps = NativeDateRefineOps & { mergeFields: MergeFieldsOp } +export type NativeMonthDayModOps = NativeMonthDayRefineOps & { mergeFields: MergeFieldsOp } + +// Math +// ------------------------------------------------------------------------------------------------- + +export interface NativeMathOps { + dateParts: DatePartsOp + monthCodeParts: MonthCodePartsOp + monthsInYearPart: MonthsInYearPartOp + daysInMonthParts: DaysInMonthPartsOp + monthAdd: MonthAddOp +} + +export type NativeMoveOps = MoveOps & NativeMathOps & { + leapMonth: LeapMonthOp + epochMilli: EpochMilliOp +} + +export type NativeDiffOps = DiffOps & NativeMathOps & { + monthsInYearSpan: MonthsInYearSpanOp +} + +export type NativeYearMonthMoveOps = NativeMoveOps & { day: DayOp } +export type NativeYearMonthDiffOps = NativeDiffOps & { day: DayOp } + +// Base + +export const nativeMoveBase: MoveOps = { + dateAdd: nativeDateAdd, +} + +export const nativeDiffBase: DiffOps = { + dateAdd: nativeDateAdd, + dateUntil: nativeDateUntil, +} + +// Parts & Stats +// ------------------------------------------------------------------------------------------------- + +export interface NativeInLeapYearOps { + inLeapYear: InLeapYearOp + dateParts: DatePartsOp + inLeapYearPart: InLeapYearPartOp +} + +export interface NativeMonthsInYearOps { + monthsInYear: MonthsInYearOp + dateParts: DatePartsOp + monthsInYearPart: MonthsInYearPartOp +} + +export interface NativeDaysInMonthOps { + daysInMonth: DaysInMonthOp + dateParts: DatePartsOp + daysInMonthParts: DaysInMonthPartsOp +} + +export interface NativeDaysInYearOps { + daysInYear: DaysInYearOp + dateParts: DatePartsOp + daysInYearPart: DaysInYearPartOp +} + +export interface NativeDayOfYearOps { + dayOfYear: DayOfYearOp +} + +export interface NativeEraOps { + era: EraOp + eraParts: EraPartsOp +} + +export interface NativeEraYearOps { + eraYear: EraYearOp + eraParts: EraPartsOp +} + +export interface NativeMonthCodeOps { + monthCode: MonthCodeOp + monthCodeParts: MonthCodePartsOp + dateParts: DatePartsOp +} + +export interface NativePartOps { + dateParts: DatePartsOp + eraParts: EraPartsOp + monthCodeParts: MonthCodePartsOp +} + +// String Parsing +// ------------------------------------------------------------------------------------------------- + +export interface NativeYearMonthParseOps { + day: DayOp +} + +export interface NativeMonthDayParseOps { + dateParts: DatePartsOp + monthCodeParts: MonthCodePartsOp + yearMonthForMonthDay: YearMonthForMonthDayOp + isoFields: IsoFieldsOp +} + +// Standard +// ------------------------------------------------------------------------------------------------- + +export type NativeStandardOps = + NativeYearMonthRefineOps & + NativeDateRefineOps & + NativeMonthDayRefineOps & + NativeMoveOps & + NativeDiffOps & + NativeYearMonthModOps & + NativeYearMonthDiffOps & + NativeInLeapYearOps & + NativeMonthsInYearOps & + NativeDaysInMonthOps & + NativeDaysInYearOps & + NativeDayOfYearOps & + NativeEraOps & + NativeEraYearOps & + NativeMonthCodeOps & + NativePartOps & + NativeYearMonthParseOps & + NativeMonthDayParseOps & + { + mergeFields: MergeFieldsOp // for 'mod' ops + + dayOfWeek(isoFields: IsoDateFields): number + weekOfYear(isoFields: IsoDateFields): number + yearOfWeek(isoFields: IsoDateFields): number + daysInWeek(isoFields: IsoDateFields): number + + year(isoFields: IsoDateFields): number + month(isoFields: IsoDateFields): number + day(isoFields: IsoDateFields): number + } + +export const nativeStandardBase = { + dateAdd: nativeDateAdd, + dateUntil: nativeDateUntil, + dateFromFields: nativeDateFromFields, + yearMonthFromFields: nativeYearMonthFromFields, + monthDayFromFields: nativeMonthDayFromFields, + fields: nativeFieldsMethod, + mergeFields: nativeMergeFields, + + inLeapYear: computeInLeapYear, + monthsInYear: computeMonthsInYear, + daysInMonth: computeDaysInMonth, + daysInYear: computeDaysInYear, + era: computeEra, + eraYear: computeEraYear, + monthCode: computeMonthCode, + + dayOfWeek: computeIsoDayOfWeek, + weekOfYear: computeIsoWeekOfYear, + yearOfWeek: computeIsoYearOfWeek, + daysInWeek: computeIsoDaysInWeek, +} + +// 'Super' methods that all native implementations use +// ------------------------------------------------------------------------------------------------- + +export function computeInLeapYear( + this: NativeInLeapYearOps, + isoFields: IsoDateFields, +): boolean { + const [year] = this.dateParts(isoFields) + return this.inLeapYearPart(year) +} + +export function computeMonthsInYear( + this: NativeMonthsInYearOps, + isoFields: IsoDateFields, +): number { + const [year] = this.dateParts(isoFields) + return this.monthsInYearPart(year) +} + +export function computeDaysInMonth( + this: NativeDaysInMonthOps, + isoFields: IsoDateFields, +): number { + const [year, month] = this.dateParts(isoFields) + return this.daysInMonthParts(year, month) +} + +export function computeDaysInYear( + this: NativeDaysInYearOps, + isoFields: IsoDateFields, +): number { + const [year] = this.dateParts(isoFields) + return this.daysInYearPart(year) +} + +export function computeEra( + this: NativeEraOps, + isoFields: IsoDateFields, +): string | undefined { + return this.eraParts(isoFields)[0] +} + +export function computeEraYear( + this: NativeEraYearOps, + isoFields: IsoDateFields, +): number | undefined { + return this.eraParts(isoFields)[1] +} + +export function computeMonthCode( + this: NativeMonthCodeOps, + isoFields: IsoDateFields, +): string { + const [year, month] = this.dateParts(isoFields) + const [monthCodeNumber, isLeapMonth] = this.monthCodeParts(year, month) + return formatMonthCode(monthCodeNumber, isLeapMonth) +} + +// Operations enacted upon passed-in CalendarNative object +// ------------------------------------------------------------------------------------------------- + +export function computeYearMonthFields( + calendarNative: NativePartOps, + isoFields: IsoDateFields, +) { // TODO: type + const [year, month] = calendarNative.dateParts(isoFields) + const [era, eraYear] = calendarNative.eraParts(isoFields) + const [monthCodeNumber, isLeapMonth] = calendarNative.monthCodeParts(year, month) + const monthCode = formatMonthCode(monthCodeNumber, isLeapMonth) + return { era, eraYear, year, monthCode, month } +} + +export function computeDateFields( + calendarNative: NativePartOps, + isoFields: IsoDateFields, +) { // TODO: type + const [year, month, day] = calendarNative.dateParts(isoFields) + const [era, eraYear] = calendarNative.eraParts(isoFields) + const [monthCodeNumber, isLeapMonth] = calendarNative.monthCodeParts(year, month) + const monthCode = formatMonthCode(monthCodeNumber, isLeapMonth) + return { era, eraYear, year, monthCode, month, day } +} + +export function computeMonthDayFields( + calendarNative: NativePartOps, + isoFields: IsoDateFields, +) { // TODO: type + const [year, month, day] = calendarNative.dateParts(isoFields) + const [monthCodeNumber, isLeapMonth] = calendarNative.monthCodeParts(year, month) + const monthCode = formatMonthCode(monthCodeNumber, isLeapMonth) + return { monthCode, month, day } +} + +// Month Code Utils +// ------------------------------------------------------------------------------------------------- + +const monthCodeRegExp = /^M(\d{2})(L?)$/ + +export function parseMonthCode(monthCode: string): [ + monthCodeNumber: number, + isLeapMonth: boolean, +] { + const m = monthCodeRegExp.exec(monthCode) + if (!m) { + throw new RangeError(errorMessages.invalidMonthCode(monthCode)) + } + + return [ + parseInt(m[1]), // monthCodeNumber + Boolean(m[2]), + ] +} + +function formatMonthCode(monthCodeNumber: number, isLeapMonth: boolean): string { + return 'M' + padNumber2(monthCodeNumber) + (isLeapMonth ? 'L' : '') +} + +export function monthCodeNumberToMonth( + monthCodeNumber: number, + isLeapMonth: boolean | undefined, + leapMonth: number | undefined, +): number { + return monthCodeNumber + ( + (isLeapMonth || (leapMonth && monthCodeNumber >= leapMonth)) + ? 1 + : 0 + ) +} + +export function monthToMonthCodeNumber(month: number, leapMonth?: number): number { + return month - ( + (leapMonth && month >= leapMonth) + ? 1 + : 0 + ) +} + +// Era Utils +// ------------------------------------------------------------------------------------------------- + +export function eraYearToYear(eraYear: number, eraOrigin: number): number { + // see the origin format in calendarConfig + return (eraOrigin + eraYear) * (Math.sign(eraOrigin) || 1) || 0 // protect against -0 +} + +// ------------------------------------------------------------------------------------------------- + +export function getCalendarEraOrigins(native: NativeCalendar): Record | undefined { + return eraOriginsByCalendarId[getCalendarIdBase(native)] +} + +export function getCalendarLeapMonthMeta(native: NativeCalendar): number | undefined { + return leapMonthMetas[getCalendarIdBase(native)] +} + +function getCalendarIdBase(native: NativeCalendar): string { + return computeCalendarIdBase(native.id || isoCalendarId) +} + +export function computeCalendarIdBase(calendarId: string): string { + return calendarId.split('-')[0] +} diff --git a/packages/temporal-polyfill/src/internal/calendarNativeQuery.ts b/packages/temporal-polyfill/src/internal/calendarNativeQuery.ts new file mode 100644 index 00000000..6771dc90 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/calendarNativeQuery.ts @@ -0,0 +1,426 @@ +import { nativeMergeFields } from './bag' +import { gregoryCalendarId, isoCalendarId, japaneseCalendarId } from './calendarConfig' +import { computeIntlDateParts, computeIntlDay, computeIntlDayOfYear, computeIntlDaysInMonth, computeIntlDaysInYear, computeIntlEpochMilli, computeIntlEraParts, computeIntlInLeapYear, computeIntlLeapMonth, computeIntlMonth, computeIntlMonthCodeParts, computeIntlMonthsInYear, computeIntlYear, computeIntlYearMonthForMonthDay, computeIsoFieldsFromIntlParts, queryIntlCalendar } from './calendarIntl' +import { computeIsoDateParts, computeIsoDay, computeIsoDayOfYear, computeIsoDaysInMonth, computeIsoDaysInYear, computeIsoEraParts, computeIsoFieldsFromParts, computeIsoInLeapYear, computeIsoMonth, computeIsoMonthCodeParts, computeIsoMonthsInYear, computeIsoYear, computeIsoYearMonthForMonthDay } from './calendarIso' +import { EpochMilliOp, LeapMonthOp, NativeCalendar, NativeDateModOps, NativeDateRefineOps, NativeDayOfYearOps, NativeDaysInMonthOps, NativeDaysInYearOps, NativeDiffOps, NativeEraOps, NativeEraYearOps, NativeInLeapYearOps, NativeMonthCodeOps, NativeMonthDayModOps, NativeMonthDayParseOps, NativeMonthDayRefineOps, NativeMonthsInYearOps, NativeMoveOps, NativePartOps, NativeStandardOps, NativeYearMonthDiffOps, NativeYearMonthModOps, NativeYearMonthMoveOps, NativeYearMonthParseOps, NativeYearMonthRefineOps, computeDaysInMonth, computeDaysInYear, computeEra, computeEraYear, computeInLeapYear, computeMonthCode, computeMonthsInYear, nativeDateRefineBase, nativeDiffBase, nativeMonthDayRefineBase, nativeMoveBase, nativeStandardBase, nativeYearMonthRefineBase } from './calendarNative' +import { computeIntlMonthsInYearSpan, computeIsoMonthsInYearSpan } from './diff' +import { isoArgsToEpochMilli } from './epochAndTime' +import { intlMonthAdd, isoMonthAdd } from './move' +import { noop } from './utils' + +// ISO +// ------------------------------------------------------------------------------------------------- + +// Refine +// ------ + +const isoYearMonthRefineDeps = { + leapMonth: noop as LeapMonthOp, + monthsInYearPart: computeIsoMonthsInYear, + isoFields: computeIsoFieldsFromParts, +} + +const isoDateRefineDeps = { + ...isoYearMonthRefineDeps, + daysInMonthParts: computeIsoDaysInMonth, +} + +const isoMonthDayRefineDeps = { + ...isoDateRefineDeps, + yearMonthForMonthDay: computeIsoYearMonthForMonthDay, +} + +export const isoYearMonthRefineOps: NativeYearMonthRefineOps = { + ...nativeYearMonthRefineBase, + ...isoYearMonthRefineDeps, +} + +export const isoDateRefineOps: NativeDateRefineOps = { + ...nativeDateRefineBase, + ...isoMonthDayRefineDeps, +} + +export const isoMonthDayRefineOps: NativeMonthDayRefineOps = { + ...nativeMonthDayRefineBase, + ...isoMonthDayRefineDeps, +} + +// Mod +// --- + +export const isoYearMonthModOps: NativeYearMonthModOps = { + ...isoYearMonthRefineOps, + mergeFields: nativeMergeFields, +} + +export const isoDateModOps: NativeDateModOps = { + ...isoDateRefineOps, + mergeFields: nativeMergeFields, +} + +export const isoMonthDayModOps: NativeMonthDayModOps = { + ...isoMonthDayRefineOps, + mergeFields: nativeMergeFields, +} + +// Math +// ---- + +const isoMathOps = { + dateParts: computeIsoDateParts, + monthCodeParts: computeIsoMonthCodeParts, + monthsInYearPart: computeIsoMonthsInYear, + daysInMonthParts: computeIsoDaysInMonth, + monthAdd: isoMonthAdd, +} + +export const isoMoveOps: NativeMoveOps = { + ...nativeMoveBase, + ...isoMathOps, + leapMonth: noop as LeapMonthOp, + epochMilli: isoArgsToEpochMilli as EpochMilliOp, +} + +export const isoDiffOps: NativeDiffOps = { + ...nativeDiffBase, + ...isoMathOps, + monthsInYearSpan: computeIsoMonthsInYearSpan, +} + +export const isoYearMonthMoveOps: NativeYearMonthMoveOps = { + ...isoMoveOps, + day: computeIsoDay, +} + +export const isoYearMonthDiffOps: NativeYearMonthDiffOps = { + ...isoDiffOps, + day: computeIsoDay, +} + +// Parts & Stats +// ------------- + +export const isoPartOps: NativePartOps = { + dateParts: computeIsoDateParts, + eraParts: computeIsoEraParts, + monthCodeParts: computeIsoMonthCodeParts, +} + +export const isoInLeapYearOps: NativeInLeapYearOps = { + inLeapYear: computeInLeapYear, + dateParts: computeIsoDateParts, + inLeapYearPart: computeIsoInLeapYear, +} + +export const isoMonthsInYearOps: NativeMonthsInYearOps = { + monthsInYear: computeMonthsInYear, + dateParts: computeIsoDateParts, + monthsInYearPart: computeIsoMonthsInYear, +} + +export const isoDaysInMonthOps: NativeDaysInMonthOps = { + daysInMonth: computeDaysInMonth, + dateParts: computeIsoDateParts, + daysInMonthParts: computeIsoDaysInMonth, +} + +export const isoDaysInYearOps: NativeDaysInYearOps = { + daysInYear: computeDaysInYear, + dateParts: computeIsoDateParts, + daysInYearPart: computeIsoDaysInYear, +} + +export const isoDayOfYearOps: NativeDayOfYearOps = { + dayOfYear: computeIsoDayOfYear, +} + +// String Parsing +// -------------- + +export const isoYearMonthParseOps: NativeYearMonthParseOps = { + day: computeIsoDay, +} + +export const isoMonthDayParseOps: NativeMonthDayParseOps = { + dateParts: computeIsoDateParts, + monthCodeParts: computeIsoMonthCodeParts, + yearMonthForMonthDay: computeIsoYearMonthForMonthDay, + isoFields: computeIsoFieldsFromParts, +} + +// Standard +// -------- + +export const isoStandardOps: NativeStandardOps = { + ...nativeStandardBase, + dateParts: computeIsoDateParts, + eraParts: computeIsoEraParts, + monthCodeParts: computeIsoMonthCodeParts, + yearMonthForMonthDay: computeIsoYearMonthForMonthDay, + inLeapYearPart: computeIsoInLeapYear, + leapMonth: noop as LeapMonthOp, + monthsInYearPart: computeIsoMonthsInYear, + monthsInYearSpan: computeIsoMonthsInYearSpan, + daysInMonthParts: computeIsoDaysInMonth, + daysInYearPart: computeIsoDaysInYear, + dayOfYear: computeIsoDayOfYear, + isoFields: computeIsoFieldsFromParts, + epochMilli: isoArgsToEpochMilli as EpochMilliOp, + monthAdd: isoMonthAdd, + year: computeIsoYear, + month: computeIsoMonth, + day: computeIsoDay, +} + +// Intl +// ------------------------------------------------------------------------------------------------- + +// Refine +// ------ + +const intlYearMonthRefineDeps = { + leapMonth: computeIntlLeapMonth, + monthsInYearPart: computeIntlMonthsInYear, + isoFields: computeIsoFieldsFromIntlParts, +} + +const intlDateRefineDeps = { + ...intlYearMonthRefineDeps, + daysInMonthParts: computeIntlDaysInMonth, +} + +const intlMonthDayRefineDeps = { + ...intlDateRefineDeps, + yearMonthForMonthDay: computeIntlYearMonthForMonthDay, +} + +export const intlYearMonthRefineOps: Omit = { + ...nativeYearMonthRefineBase, + ...intlYearMonthRefineDeps, +} + +export const intlDateRefineOps: Omit = { + ...nativeDateRefineBase, + ...intlDateRefineDeps, +} + +export const intlMonthDayRefineOps: Omit = { + ...nativeMonthDayRefineBase, + ...intlMonthDayRefineDeps, +} + +// Mod +// --- + +export const intlYearMonthModOps: Omit = { + ...intlYearMonthRefineOps, + mergeFields: nativeMergeFields, +} + +export const intlDateModOps: Omit = { + ...intlDateRefineOps, + mergeFields: nativeMergeFields, +} + +export const intlMonthDayModOps: Omit = { + ...intlMonthDayRefineOps, + mergeFields: nativeMergeFields, +} + +// Math +// ---- + +const intlMathOps = { + dateParts: computeIntlDateParts, + monthCodeParts: computeIntlMonthCodeParts, + monthsInYearPart: computeIntlMonthsInYear, + daysInMonthParts: computeIntlDaysInMonth, + monthAdd: intlMonthAdd, +} + +export const intlMoveOps: NativeMoveOps = { + ...nativeMoveBase, + ...intlMathOps, + leapMonth: computeIntlLeapMonth, + epochMilli: computeIntlEpochMilli, +} + +export const intlDiffOps: NativeDiffOps = { + ...nativeDiffBase, + ...intlMathOps, + monthsInYearSpan: computeIntlMonthsInYearSpan, +} + +export const intlYearMonthMoveOps: NativeYearMonthMoveOps = { + ...intlMoveOps, + day: computeIntlDay, +} + +export const intlYearMonthDiffOps: NativeYearMonthDiffOps = { + ...intlDiffOps, + day: computeIntlDay, +} + +// Parts & Stats +// ------------- + +export const intlInLeapYearOps: NativeInLeapYearOps = { + inLeapYear: computeInLeapYear, + dateParts: computeIntlDateParts, + inLeapYearPart: computeIntlInLeapYear, +} + +export const intlMonthsInYearOps: NativeMonthsInYearOps = { + monthsInYear: computeMonthsInYear, + dateParts: computeIntlDateParts, + monthsInYearPart: computeIntlMonthsInYear, +} + +export const intlDaysInMonthOps: NativeDaysInMonthOps = { + daysInMonth: computeDaysInMonth, + dateParts: computeIntlDateParts, + daysInMonthParts: computeIntlDaysInMonth, +} + +export const intlDaysInYearOps: NativeDaysInYearOps = { + daysInYear: computeDaysInYear, + dateParts: computeIntlDateParts, + daysInYearPart: computeIntlDaysInYear, +} + +export const intlDayOfYearOps: NativeDayOfYearOps = { + dayOfYear: computeIntlDayOfYear, +} + +export const intlEraOps: NativeEraOps = { + era: computeEra, + eraParts: computeIntlEraParts, +} + +export const intlEraYearOps: NativeEraYearOps = { + eraYear: computeEraYear, + eraParts: computeIntlEraParts, +} + +export const intlMonthCodeOps: NativeMonthCodeOps = { + monthCode: computeMonthCode, + monthCodeParts: computeIntlMonthCodeParts, + dateParts: computeIntlDateParts, +} + +export const intlPartOps: NativePartOps = { + dateParts: computeIntlDateParts, + eraParts: computeIntlEraParts, + monthCodeParts: computeIntlMonthCodeParts, +} + +// String Parsing +// -------------- + +export const intlYearMonthParseOps: NativeYearMonthParseOps = { + day: computeIntlDay, +} + +export const intlMonthDayParseOps: NativeMonthDayParseOps = { + dateParts: computeIntlDateParts, + monthCodeParts: computeIntlMonthCodeParts, + yearMonthForMonthDay: computeIntlYearMonthForMonthDay, + isoFields: computeIsoFieldsFromIntlParts, +} + +// Standard +// -------- + +export const intlStandardOps: Omit = { + ...nativeStandardBase, + dateParts: computeIntlDateParts, + eraParts: computeIntlEraParts, + monthCodeParts: computeIntlMonthCodeParts, + yearMonthForMonthDay: computeIntlYearMonthForMonthDay, + inLeapYearPart: computeIntlInLeapYear, + leapMonth: computeIntlLeapMonth, + monthsInYearPart: computeIntlMonthsInYear, + monthsInYearSpan: computeIntlMonthsInYearSpan, + daysInMonthParts: computeIntlDaysInMonth, + daysInYearPart: computeIntlDaysInYear, + dayOfYear: computeIntlDayOfYear, + isoFields: computeIsoFieldsFromIntlParts, + epochMilli: computeIntlEpochMilli, + monthAdd: intlMonthAdd, + year: computeIntlYear, + month: computeIntlMonth, + day: computeIntlDay, +} + +// ------------------------------------------------------------------------------------------------- + +/* +All functions expect realized/normalized calendarId +*/ + +// Refine +export const createNativeYearMonthRefineOps = createNativeOpsCreator(isoYearMonthRefineOps, intlYearMonthRefineOps) +export const createNativeDateRefineOps = createNativeOpsCreator(isoDateRefineOps, intlDateRefineOps) +export const createNativeMonthDayRefineOps = createNativeOpsCreator(isoMonthDayRefineOps, intlMonthDayRefineOps) + +// Mod +export const createNativeYearMonthModOps = createNativeOpsCreator(isoYearMonthModOps, intlYearMonthModOps) +export const createNativeDateModOps = createNativeOpsCreator(isoDateModOps, intlDateModOps) +export const createNativeMonthDayModOps = createNativeOpsCreator(isoMonthDayModOps, intlMonthDayModOps) + +// Math +export const createNativeMoveOps = createNativeOpsCreator(isoMoveOps, intlMoveOps) +export const createNativeDiffOps = createNativeOpsCreator(isoDiffOps, intlDiffOps) +export const createNativeYearMonthMoveOps = createNativeOpsCreator(isoYearMonthMoveOps, intlYearMonthMoveOps) +export const createNativeYearMonthDiffOps = createNativeOpsCreator(isoYearMonthDiffOps, intlYearMonthDiffOps) + +// Parts & Stats +export const createNativeInLeapYearOps = createNativeOpsCreator(isoInLeapYearOps, intlInLeapYearOps) +export const createNativeMonthsInYearOps = createNativeOpsCreator(isoMonthsInYearOps, intlMonthsInYearOps) +export const createNativeDaysInMonthOps = createNativeOpsCreator(isoDaysInMonthOps, intlDaysInMonthOps) +export const createNativeDaysInYearOps = createNativeOpsCreator(isoDaysInYearOps, intlDaysInYearOps) +export const createNativeDayOfYearOps = createNativeOpsCreator(isoDayOfYearOps, intlDayOfYearOps) +export const createNativePartOps = createNativeOpsCreator(isoPartOps, intlPartOps) + +// String Parsing +export const createNativeYearMonthParseOps = createNativeOpsCreator(isoYearMonthParseOps, intlYearMonthParseOps) +export const createNativeMonthDayParseOps = createNativeOpsCreator(isoMonthDayParseOps, intlMonthDayParseOps) + +// Standard +export const createNativeStandardOps = createNativeOpsCreator(isoStandardOps, intlStandardOps) + +function createNativeOpsCreator(isoOps: O, intlOps: O): ( + (calendarId: string) => O & NativeCalendar +) { + return (calendarId) => { + if (calendarId === isoCalendarId) { + return isoOps + } else if (calendarId === gregoryCalendarId || calendarId === japaneseCalendarId) { + return Object.assign(Object.create(isoOps), { id: calendarId }) + } + return Object.assign(Object.create(intlOps), queryIntlCalendar(calendarId)) + } +} + +// ------------------------------------------------------------------------------------------------- + +export function realizeCalendarId(calendarId: string): string { + calendarId = normalizeCalendarId(calendarId) + + // check that it's valid. similar to switch in createNativeOpsCreator + if (calendarId !== isoCalendarId && calendarId !== gregoryCalendarId) { + queryIntlCalendar(calendarId) + } + + return calendarId // return original instead of using queried-result. keeps id extensions +} + +export function normalizeCalendarId(calendarId: string): string { + calendarId = calendarId.toLocaleLowerCase() + + if (calendarId === 'islamicc') { + calendarId = 'islamic-civil' + } + + return calendarId +} diff --git a/packages/temporal-polyfill/src/internal/calendarOps.ts b/packages/temporal-polyfill/src/internal/calendarOps.ts new file mode 100644 index 00000000..da890624 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/calendarOps.ts @@ -0,0 +1,38 @@ +import { DateBag, YearMonthBag } from './calendarFields' +import { DurationFields } from './durationFields' +import { IsoDateFields } from './calendarIsoFields' +import { Unit } from './units' +import { DiffOptions, OverflowOptions } from './optionsRefine' +import { PlainDateSlots, PlainMonthDaySlots, PlainYearMonthSlots } from './slots' + +// Operations for internal use! + +// Function Types +// (Must always be called from a CalendarOps object) +export type DateFromFieldsOp = (fields: DateBag, options?: OverflowOptions) => PlainDateSlots +export type YearMonthFromFieldsOp = (fields: YearMonthBag, options?: OverflowOptions) => PlainYearMonthSlots +export type MonthDayFromFieldsOp = (fields: DateBag, options?: OverflowOptions) => PlainMonthDaySlots +export type FieldsOp = (fieldNames: string[]) => string[] +export type MergeFieldsOp = (fields: DateBag, additionalFields: DateBag) => DateBag +export type DateAddOp = (isoFields: IsoDateFields, durationFields: DurationFields, options?: OverflowOptions) => IsoDateFields +export type DateUntilOp = (isoFields0: IsoDateFields, isoFields1: IsoDateFields, largestUnit: Unit, origOptions?: DiffOptions) => DurationFields +export type DayOp = (isoFields: IsoDateFields) => number + +// Refine +// (assumes received fields are ALREADY refined) +// TODO: have these functions return the branding too? +export type DateRefineOps = { dateFromFields: DateFromFieldsOp, fields: FieldsOp } +export type YearMonthRefineOps = { yearMonthFromFields: YearMonthFromFieldsOp, fields: FieldsOp } +export type MonthDayRefineOps = { monthDayFromFields: MonthDayFromFieldsOp, fields: FieldsOp } + +// Mod +// (assumes received fields are ALREADY refined) +export type YearMonthModOps = YearMonthRefineOps & { mergeFields: MergeFieldsOp } +export type DateModOps = DateRefineOps & { mergeFields: MergeFieldsOp } +export type MonthDayModOps = MonthDayRefineOps & { mergeFields: MergeFieldsOp } + +// Math +export type MoveOps = { dateAdd: DateAddOp } +export type DiffOps = { dateAdd: DateAddOp, dateUntil: DateUntilOp } +export type YearMonthMoveOps = MoveOps & { day: DayOp } +export type YearMonthDiffOps = DiffOps & { day: DayOp } diff --git a/packages/temporal-polyfill/src/internal/cast.ts b/packages/temporal-polyfill/src/internal/cast.ts new file mode 100644 index 00000000..68ca5d1c --- /dev/null +++ b/packages/temporal-polyfill/src/internal/cast.ts @@ -0,0 +1,140 @@ +import { Callable, bindArgs, isObjectLike } from './utils' +import * as errorMessages from './errorMessages' + +// Require +// ------------------------------------------------------------------------------------------------- + +export function requireObjectlike(arg: O): O { + if (!isObjectLike(arg)) { + throw new TypeError(errorMessages.invalidObject) + } + return arg +} + +function requireType(typeName: string, arg: A, entityName: string = typeName): A { + if (typeof arg !== typeName) { + throw new TypeError(errorMessages.invalidEntity(entityName, arg)) + } + return arg +} + +export const requireString = bindArgs(requireType, 'string') +export const requireBoolean = bindArgs(requireType, 'boolean') +export const requireNumber = bindArgs(requireType, 'number') +export const requireFunction = bindArgs(requireType, 'function') + +export function requireStringOrUndefined(input: string | undefined): string | undefined { + if (input !== undefined && typeof input !== 'string') { + throw new TypeError(errorMessages.expectedStringOrUndefined) + } + return input +} + +export function requireIntegerOrUndefined(input: number | undefined): number | undefined { + if (typeof input === 'number') { + requireNumberIsInteger(input) + } else if (input !== undefined) { + throw new TypeError(errorMessages.expectedIntegerOrUndefined) + } + return input +} + +export function requireInteger(arg: number): number { + return requireNumberIsInteger(requireNumber(arg)) +} + +export function requirePositiveInteger(arg: number): number { + return requireNumberIsPositive(requireInteger(arg)) +} + +/* +Also, responsible for ensuring not -0 +Other top-level funcs handle this themselves +*/ +function requireNumberIsInteger(num: number, entityName: string = 'number'): number { + if (!Number.isInteger(num)) { + throw new RangeError(errorMessages.expectedInteger(entityName, num)) + } + return num || 0 // ensure no -0 +} + +export function requireNumberIsPositive(num: number, entityName: string = 'number'): number { + if (num <= 0) { + throw new RangeError(errorMessages.expectedPositive(entityName, num)) + } + return num +} + +export function requireNonNullish(o: T): T { + if (o == null) { + throw new TypeError(errorMessages.forbiddenNullish) + } + return o +} + +/* +Disallows undefined/null. Does RangeError +*/ +export function requirePropDefined(optionName: string, optionVal: V | null | undefined): V { + if (optionVal == null) { + throw new RangeError(errorMessages.missingField(optionName)) + } + return optionVal +} + + +// Casting +// ------------------------------------------------------------------------------------------------- + +export function toString(arg: string): string { + if (typeof arg === 'symbol') { + throw new TypeError(errorMessages.forbiddenSymbolToString) + } + return String(arg) +} + +/* +see ToPrimitiveAndRequireString +*/ +export function toStringViaPrimitive(arg: string, entityName?: string): string { + if (isObjectLike(arg)) { + return String(arg) + } + return requireString(arg, entityName) +} + +export function toBigInt(bi: bigint): bigint { + if (typeof bi === 'string') { + return BigInt(bi) + } + if (typeof bi !== 'bigint') { + throw new TypeError(errorMessages.invalidBigInt(bi)) + } + return bi +} + +export function toNumber(arg: number, entityName: string = 'number'): number { + if (typeof arg === 'bigint') { + throw new TypeError(errorMessages.forbiddenBigIntToNumber(entityName)) + } + + arg = Number(arg) + + if (!Number.isFinite(arg)) { + throw new RangeError(errorMessages.expectedFinite(entityName, arg)) + } + + return arg +} + +export function toInteger(arg: number, entityName?: string): number { + return Math.trunc(toNumber(arg, entityName)) || 0 // ensure no -0 +} + +export function toStrictInteger(arg: number, entityName?: string): number { + return requireNumberIsInteger(toNumber(arg, entityName), entityName) +} + +export function toPositiveInteger(arg: number, entityName?: string): number { + return requireNumberIsPositive(toInteger(arg, entityName), entityName) +} diff --git a/packages/temporal-polyfill/src/internal/compare.ts b/packages/temporal-polyfill/src/internal/compare.ts new file mode 100644 index 00000000..85b3be17 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/compare.ts @@ -0,0 +1,164 @@ +import { compareDayTimeNanos } from './dayTimeNano' +import { Unit, givenFieldsToDayTimeNano } from './units' +import { NumSign, allFieldsEqual, compareNumbers } from './utils' +import { durationFieldNamesAsc } from './durationFields' +import { DiffOps } from './calendarOps' +import { TimeZoneOps } from './timeZoneOps' +import { DurationSlots, IdLike, InstantSlots, PlainDateSlots, PlainDateTimeSlots, PlainMonthDaySlots, PlainTimeSlots, PlainYearMonthSlots, ZonedDateTimeSlots, isIdLikeEqual, isTimeZoneSlotsEqual } from './slots' +import { RelativeToOptions, normalizeOptions } from './optionsRefine' +import { MarkerSlots, getLargestDurationUnit, createMarkerSystem, MarkerSystem } from './durationMath' +import { isoTimeFieldsToNano, isoToEpochMilli } from './epochAndTime' +import { IsoDateFields, IsoDateTimeFields, IsoTimeFields } from './calendarIsoFields' +import * as errorMessages from './errorMessages' + +// High-Level Compare +// ------------------------------------------------------------------------------------------------- + +export function compareInstants( + instantSlots0: InstantSlots, + instantSlots1: InstantSlots, +): NumSign { + return compareDayTimeNanos(instantSlots0.epochNanoseconds, instantSlots1.epochNanoseconds) +} + +export function compareZonedDateTimes( + zonedDateTimeSlots0: ZonedDateTimeSlots, + zonedDateTimeSlots1: ZonedDateTimeSlots, +): NumSign { + return compareDayTimeNanos( + zonedDateTimeSlots0.epochNanoseconds, + zonedDateTimeSlots1.epochNanoseconds, + ) +} + +export function compareDurations( + refineRelativeTo: (relativeToArg: RA) => MarkerSlots | undefined, + getCalendarOps: (calendarSlot: C) => DiffOps, + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + durationSlots0: DurationSlots, + durationSlots1: DurationSlots, + options?: RelativeToOptions +): NumSign { + const normalOptions = normalizeOptions(options) + const markerSlots = refineRelativeTo(normalOptions.relativeTo) + const largestUnit = Math.max( + getLargestDurationUnit(durationSlots0), + getLargestDurationUnit(durationSlots1) + ) as Unit + + // fast-path if fields identical + if (allFieldsEqual(durationFieldNamesAsc, durationSlots0, durationSlots1)) { + return 0 + } + + if (largestUnit < Unit.Day || ( + largestUnit === Unit.Day && + // has uniform days? + !(markerSlots && (markerSlots as any).epochNanoseconds) + )) { + return compareDayTimeNanos( + givenFieldsToDayTimeNano(durationSlots0, Unit.Day, durationFieldNamesAsc), + givenFieldsToDayTimeNano(durationSlots1, Unit.Day, durationFieldNamesAsc) + ) + } + + if (!markerSlots) { + throw new RangeError(errorMessages.missingRelativeTo) + } + + const [marker, markerToEpochNano, moveMarker] = createMarkerSystem(getCalendarOps, getTimeZoneOps, markerSlots) as MarkerSystem + + return compareDayTimeNanos( + markerToEpochNano(moveMarker(marker, durationSlots0)), + markerToEpochNano(moveMarker(marker, durationSlots1)) + ) +} + +// Low-Level Compare +// ------------------------------------------------------------------------------------------------- + +export function compareIsoDateTimeFields( + isoFields0: IsoDateTimeFields, + isoFields1: IsoDateTimeFields, +): NumSign { + return compareIsoDateFields(isoFields0, isoFields1) || + compareIsoTimeFields(isoFields0, isoFields1) +} + +export function compareIsoDateFields( + isoFields0: IsoDateFields, + isoFields1: IsoDateFields, +): NumSign { + return compareNumbers( + isoToEpochMilli(isoFields0)!, + isoToEpochMilli(isoFields1)! + ) +} + +export function compareIsoTimeFields( + isoFields0: IsoTimeFields, + isoFields1: IsoTimeFields, +): NumSign { + return compareNumbers( + isoTimeFieldsToNano(isoFields0), + isoTimeFieldsToNano(isoFields1), + ) +} + +// Is-equal +// ------------------------------------------------------------------------------------------------- + +export function instantsEqual( + instantSlots0: InstantSlots, + instantSlots1: InstantSlots, +): boolean { + return !compareInstants(instantSlots0, instantSlots1) +} + +export function zonedDateTimesEqual( + zonedDateTimeSlots0: ZonedDateTimeSlots, + zonedDateTimeSlots1: ZonedDateTimeSlots, +): boolean { + return !compareZonedDateTimes(zonedDateTimeSlots0, zonedDateTimeSlots1) && + isTimeZoneSlotsEqual(zonedDateTimeSlots0.timeZone, zonedDateTimeSlots1.timeZone) && + isIdLikeEqual(zonedDateTimeSlots0.calendar, zonedDateTimeSlots1.calendar) +} + +export function plainDateTimesEqual( + plainDateTimeSlots0: PlainDateTimeSlots, + plainDateTimeSlots1: PlainDateTimeSlots, +): boolean { + return !compareIsoDateTimeFields(plainDateTimeSlots0, plainDateTimeSlots1) && + isIdLikeEqual(plainDateTimeSlots0.calendar, plainDateTimeSlots1.calendar) +} + +export function plainDatesEqual( + plainDateSlots0: PlainDateSlots, + plainDateSlots1: PlainDateSlots, +): boolean { + return !compareIsoDateFields(plainDateSlots0, plainDateSlots1) && + isIdLikeEqual(plainDateSlots0.calendar, plainDateSlots1.calendar) +} + +export function plainYearMonthsEqual( + plainYearMonthSlots0: PlainYearMonthSlots, + plainYearMonthSlots1: PlainYearMonthSlots, +): boolean { + return !compareIsoDateFields(plainYearMonthSlots0, plainYearMonthSlots1) && + isIdLikeEqual(plainYearMonthSlots0.calendar, plainYearMonthSlots1.calendar) +} + +export function plainMonthDaysEqual( + plainMonthDaySlots0: PlainMonthDaySlots, + plainMonthDaySlots1: PlainMonthDaySlots, +): boolean { + return !compareIsoDateFields(plainMonthDaySlots0, plainMonthDaySlots1) && + isIdLikeEqual(plainMonthDaySlots0.calendar, plainMonthDaySlots1.calendar) +} + +export function plainTimesEqual( + plainTimeSlots0: PlainTimeSlots, + plainTimeSlots1: PlainTimeSlots, +): boolean { + return !compareIsoTimeFields(plainTimeSlots0, plainTimeSlots1) +} diff --git a/packages/temporal-polyfill/src/internal/construct.ts b/packages/temporal-polyfill/src/internal/construct.ts new file mode 100644 index 00000000..eeee7845 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/construct.ts @@ -0,0 +1,190 @@ +import { isoCalendarId } from './calendarConfig' +import { isoEpochFirstLeapYear } from './calendarIso' +import { checkIsoDateFields, checkIsoDateTimeFields, constrainIsoTimeFields } from './calendarIso' +import { toBigInt, toInteger, toStrictInteger } from './cast' +import { bigIntToDayTimeNano } from './dayTimeNano' +import { checkDurationFields } from './durationMath' +import { checkEpochNanoInBounds, checkIsoDateInBounds, checkIsoDateTimeInBounds, checkIsoYearMonthInBounds } from './epochAndTime' +import { Overflow } from './options' +import { DurationSlots, InstantSlots, PlainDateSlots, PlainDateTimeSlots, PlainMonthDaySlots, PlainTimeSlots, PlainYearMonthSlots, ZonedDateTimeSlots, createDurationSlots, createInstantSlots, createPlainDateTimeSlots, createPlainDateSlots, createPlainMonthDaySlots, createPlainTimeSlots, createPlainYearMonthSlots, createZonedDateTimeSlots } from './slots' +import { isoDateTimeFieldNamesAsc, isoTimeFieldNamesAsc } from './calendarIsoFields' +import { durationFieldNamesAsc } from './durationFields' +import { mapProps, zipProps } from './utils' + +export function constructInstantSlots(epochNano: bigint): InstantSlots { + return createInstantSlots( + checkEpochNanoInBounds(bigIntToDayTimeNano(toBigInt(epochNano))), + ) +} + +export function constructZonedDateTimeSlots( + refineCalendarArg: (calendarArg: CA) => C, + refineTimeZoneArg: (timeZoneArg: TA) => T, + epochNano: bigint, + timeZoneArg: TA, + calendarArg: CA = isoCalendarId as any, +): ZonedDateTimeSlots { + return createZonedDateTimeSlots( + checkEpochNanoInBounds(bigIntToDayTimeNano(toBigInt(epochNano))), + refineTimeZoneArg(timeZoneArg), + refineCalendarArg(calendarArg), + ) +} + +export function constructPlainDateTimeSlots( + refineCalendarArg: (calendarArg: CA) => C, + isoYear: number, + isoMonth: number, + isoDay: number, + isoHour: number = 0, + isoMinute: number = 0, + isoSecond: number = 0, + isoMillisecond: number = 0, + isoMicrosecond: number = 0, + isoNanosecond: number = 0, + calendarArg: CA = isoCalendarId as any, +): PlainDateTimeSlots { + const isoFields = zipProps(isoDateTimeFieldNamesAsc, [ + isoYear, + isoMonth, + isoDay, + isoHour, + isoMinute, + isoSecond, + isoMillisecond, + isoMicrosecond, + isoNanosecond, + ]) + return createPlainDateTimeSlots( + checkIsoDateTimeInBounds( + checkIsoDateTimeFields( + mapProps(toInteger, isoFields) + ) + ), + refineCalendarArg(calendarArg), + ) +} + +export function constructPlainDateSlots( + refineCalendarArg: (calendarArg: CA) => C, + isoYear: number, + isoMonth: number, + isoDay: number, + calendarArg: CA = isoCalendarId as any, +): PlainDateSlots { + return createPlainDateSlots( + checkIsoDateInBounds( + checkIsoDateFields( + mapProps(toInteger, { + isoYear, + isoMonth, + isoDay, + }) + ) + ), + refineCalendarArg(calendarArg), + ) +} + +export function constructPlainYearMonthSlots( + refineCalendarArg: (calendarArg: CA) => C, + isoYear: number, + isoMonth: number, + calendar: CA = isoCalendarId as any, + referenceIsoDay: number = 1, +): PlainYearMonthSlots { + const isoYearInt = toInteger(isoYear) + const isoMonthInt = toInteger(isoMonth) + const calendarSlot = refineCalendarArg(calendar) + const isoDayInt = toInteger(referenceIsoDay) + + return createPlainYearMonthSlots( + checkIsoYearMonthInBounds( + checkIsoDateFields({ + isoYear: isoYearInt, + isoMonth: isoMonthInt, + isoDay: isoDayInt + }) + ), + calendarSlot, + ) +} + +export function constructPlainMonthDaySlots( + refineCalendarArg: (calendarArg: CA) => C, + isoMonth: number, + isoDay: number, + calendar: CA = isoCalendarId as any, + referenceIsoYear: number = isoEpochFirstLeapYear +): PlainMonthDaySlots { + const isoMonthInt = toInteger(isoMonth) + const isoDayInt = toInteger(isoDay) + const calendarSlot = refineCalendarArg(calendar) + const isoYearInt = toInteger(referenceIsoYear) + + return createPlainMonthDaySlots( + checkIsoDateInBounds( + checkIsoDateFields({ + isoYear: isoYearInt, + isoMonth: isoMonthInt, + isoDay: isoDayInt + }) + ), + calendarSlot, + ) +} + +export function constructPlainTimeSlots( + isoHour: number = 0, + isoMinute: number = 0, + isoSecond: number = 0, + isoMillisecond: number = 0, + isoMicrosecond: number = 0, + isoNanosecond: number = 0, +): PlainTimeSlots { + const isoFields = zipProps(isoTimeFieldNamesAsc, [ + isoHour, + isoMinute, + isoSecond, + isoMillisecond, + isoMicrosecond, + isoNanosecond, + ]) + return createPlainTimeSlots( + constrainIsoTimeFields( + mapProps(toInteger, isoFields), + Overflow.Reject, + ) + ) +} + +export function constructDurationSlots( + years: number = 0, + months: number = 0, + weeks: number = 0, + days: number = 0, + hours: number = 0, + minutes: number = 0, + seconds: number = 0, + milliseconds: number = 0, + microseconds: number = 0, + nanoseconds: number = 0, +): DurationSlots { + const durationFields = zipProps(durationFieldNamesAsc, [ + years, + months, + weeks, + days, + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, + ]) + return createDurationSlots( + checkDurationFields( + mapProps(toStrictInteger, durationFields), + ), + ) +} diff --git a/packages/temporal-polyfill/src/internal/convert.ts b/packages/temporal-polyfill/src/internal/convert.ts new file mode 100644 index 00000000..3cb48989 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/convert.ts @@ -0,0 +1,295 @@ +import { convertPlainMonthDayToDate, convertPlainYearMonthToDate, convertToPlainMonthDay, convertToPlainYearMonth } from './bag' +import { isoCalendarId } from './calendarConfig' +import { DateBag, MonthDayFields, YearFields, YearMonthFieldsIntl } from './calendarFields' +import { IsoDateTimeFields, IsoTimeFields, isoTimeFieldDefaults } from './calendarIsoFields' +import { DateModOps, MonthDayRefineOps, YearMonthRefineOps } from './calendarOps' +import { toBigInt } from './cast' +import { DayTimeNano, bigIntToDayTimeNano, numberToDayTimeNano } from './dayTimeNano' +import { checkEpochNanoInBounds, checkIsoDateTimeInBounds } from './epochAndTime' +import { EpochDisambigOptions, refineEpochDisambigOptions } from './optionsRefine' +import { InstantBranding, InstantSlots, PlainDateBranding, PlainDateSlots, PlainDateTimeBranding, PlainDateTimeSlots, PlainMonthDayBranding, PlainMonthDaySlots, PlainTimeBranding, PlainTimeSlots, PlainYearMonthBranding, PlainYearMonthSlots, ZonedDateTimeBranding, ZonedDateTimeSlots, createInstantSlots, createPlainDateTimeSlots, createPlainDateSlots, createPlainMonthDaySlots, createPlainTimeSlots, createPlainYearMonthSlots, createZonedDateTimeSlots } from './slots' +import { SimpleTimeZoneOps, TimeZoneOps, getSingleInstantFor, zonedInternalsToIso } from './timeZoneOps' +import { nanoInMicro, nanoInMilli, nanoInSec } from './units' + +// Instant -> * +// ------------------------------------------------------------------------------------------------- + +export function instantToZonedDateTime( + instantSlots: InstantSlots, + timeZoneSlot: T, + calendarSlot: C = isoCalendarId as any, +): ZonedDateTimeSlots { + return createZonedDateTimeSlots( + instantSlots.epochNanoseconds, + timeZoneSlot, + calendarSlot, + ) +} + +// ZonedDateTime -> * +// ------------------------------------------------------------------------------------------------- + +export function zonedDateTimeToInstant( + zonedDateTimeSlots0: ZonedDateTimeSlots +): InstantSlots { + return createInstantSlots(zonedDateTimeSlots0.epochNanoseconds) +} + +export function zonedDateTimeToPlainDateTime( + getTimeZoneOps: (timeZoneSlot: T) => SimpleTimeZoneOps, + zonedDateTimeSlots0: ZonedDateTimeSlots, +): PlainDateTimeSlots { + return createPlainDateTimeSlots( + zonedInternalsToIso(zonedDateTimeSlots0 as any, getTimeZoneOps(zonedDateTimeSlots0.timeZone)), + zonedDateTimeSlots0.calendar, + ) +} + +export function zonedDateTimeToPlainDate( + getTimeZoneOps: (timeZoneSlot: T) => SimpleTimeZoneOps, + zonedDateTimeSlots0: ZonedDateTimeSlots, +): PlainDateSlots { + return createPlainDateSlots( + zonedInternalsToIso(zonedDateTimeSlots0 as any, getTimeZoneOps(zonedDateTimeSlots0.timeZone)), + zonedDateTimeSlots0.calendar, + ) +} + +export function zonedDateTimeToPlainYearMonth( + getCalendarOps: (calendarSlot: C) => YearMonthRefineOps, + zonedDateTimeSlots0: ZonedDateTimeSlots, + zonedDateTimeFields: DateBag, // TODO: DateBag correct type? +): PlainYearMonthSlots { + const calendarSlot = zonedDateTimeSlots0.calendar + const calendarOps = getCalendarOps(calendarSlot) + + return convertToPlainYearMonth(calendarOps, zonedDateTimeFields) +} + +export function zonedDateTimeToPlainMonthDay( + getCalendarOps: (calendarSlot: C) => MonthDayRefineOps, + zonedDateTimeSlots0: ZonedDateTimeSlots, + zonedDateTimeFields: DateBag, // TODO: DateBag correct type? +): PlainMonthDaySlots { + const calendarSlot = zonedDateTimeSlots0.calendar + const calendarOps = getCalendarOps(calendarSlot) + + return convertToPlainMonthDay(calendarOps, zonedDateTimeFields) +} + +export function zonedDateTimeToPlainTime( + getTimeZoneOps: (timeZoneSlot: T) => SimpleTimeZoneOps, + zonedDateTimeSlots0: ZonedDateTimeSlots, +): PlainTimeSlots { + return createPlainTimeSlots( + zonedInternalsToIso( + zonedDateTimeSlots0 as any, // !!! + getTimeZoneOps(zonedDateTimeSlots0.timeZone) + ), + ) +} + +// PlainDateTime -> * +// ------------------------------------------------------------------------------------------------- + +export function plainDateTimeToZonedDateTime( + getTimeZoneOps: (timeZoneSlot: TZ) => TimeZoneOps, + plainDateTimeSlots: PlainDateTimeSlots, + timeZoneSlot: TZ, + options?: EpochDisambigOptions, +): ZonedDateTimeSlots { + return createZonedDateTimeSlots( + dateToEpochNano(getTimeZoneOps, timeZoneSlot, plainDateTimeSlots, options), + timeZoneSlot, + plainDateTimeSlots.calendar, + ) +} + +export function plainDateTimeToPlainYearMonth( + getCalendarOps: (calendarSlot: C) => YearMonthRefineOps, + plainDateTimeSlots: PlainDateTimeSlots, + plainDateFields: DateBag, // TODO: DateBag correct type? +): PlainYearMonthSlots { + const calendarOps = getCalendarOps(plainDateTimeSlots.calendar) + + return createPlainYearMonthSlots({ + ...plainDateTimeSlots, // isoTimeFields and calendar + ...convertToPlainYearMonth(calendarOps, plainDateFields), + }) +} + +export function plainDateTimeToPlainMonthDay( + getCalendarOps: (calendarSlot: C) => MonthDayRefineOps, + plainDateTimeSlots: PlainDateTimeSlots, + plainDateFields: DateBag, // TODO: DateBag correct type? +): PlainMonthDaySlots { + const calendarOps = getCalendarOps(plainDateTimeSlots.calendar) + + return convertToPlainMonthDay(calendarOps, plainDateFields) +} + +function dateToEpochNano( + getTimeZoneOps: (timeZoneSlot: TZ) => TimeZoneOps, + timeZoneSlot: TZ, + isoFields: IsoDateTimeFields, + options?: EpochDisambigOptions, +): DayTimeNano { + const epochDisambig = refineEpochDisambigOptions(options) + const timeZoneOps = getTimeZoneOps(timeZoneSlot) + + return checkEpochNanoInBounds( + getSingleInstantFor(timeZoneOps, isoFields, epochDisambig), + ) +} + +// PlainDate -> * +// ------------------------------------------------------------------------------------------------- + +export function plainDateToZonedDateTime( + refineTimeZoneArg: (timeZoneArg: TA) => T, + refinePlainTimeArg: (plainTimeArg: PA) => IsoTimeFields, + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + plainDateSlots: PlainDateSlots, + options: { timeZone: TA, plainTime?: PA }, +): ZonedDateTimeSlots { + const timeZoneSlot = refineTimeZoneArg(options.timeZone) + const plainTimeArg = options.plainTime + const isoTimeFields = plainTimeArg !== undefined + ? refinePlainTimeArg(plainTimeArg) + : isoTimeFieldDefaults + + const timeZoneOps = getTimeZoneOps(timeZoneSlot) + + return createZonedDateTimeSlots( + getSingleInstantFor(timeZoneOps, { ...plainDateSlots, ...isoTimeFields }), + timeZoneSlot, + plainDateSlots.calendar, + ) +} + +export function plainDateToPlainDateTime( + plainDateSlots: PlainDateSlots, + plainTimeFields: IsoTimeFields = isoTimeFieldDefaults, +): PlainDateTimeSlots { + return createPlainDateTimeSlots( + checkIsoDateTimeInBounds({ + ...plainDateSlots, + ...plainTimeFields, + }), + ) +} + +export function plainDateToPlainYearMonth( + getCalendarOps: (calendarSlot: C) => YearMonthRefineOps, + plainDateSlots: { calendar: C }, + plainDateFields: DateBag, // TODO: DateBag correct type? +): PlainYearMonthSlots { + const calendarSlot = plainDateSlots.calendar + const calendarOps = getCalendarOps(calendarSlot) + + return convertToPlainYearMonth(calendarOps, plainDateFields) +} + +export function plainDateToPlainMonthDay( + getCalendarOps: (calendarSlot: C) => MonthDayRefineOps, + plainDateSlots: { calendar: C }, + plainDateFields: DateBag, // TODO: DateBag correct type? +): PlainMonthDaySlots { + const calendarSlot = plainDateSlots.calendar + const calendarOps = getCalendarOps(calendarSlot) + + return convertToPlainMonthDay(calendarOps, plainDateFields) +} + +// PlainYearMonth -> * +// ------------------------------------------------------------------------------------------------- + +export function plainYearMonthToPlainDate( + getCalendarOps: (calendar: C) => DateModOps, + plainYearMonthSlots: PlainYearMonthSlots, + plainYearMonthFields: YearMonthFieldsIntl, + bag: { day: number }, +): PlainDateSlots { + const calendarSlot = plainYearMonthSlots.calendar + const calendarOps = getCalendarOps(calendarSlot) + + return convertPlainYearMonthToDate(calendarOps, plainYearMonthFields, bag) +} + +// PlainMonthDay -> * +// ------------------------------------------------------------------------------------------------- + +export function plainMonthDayToPlainDate( + getCalendarOps: (calendar: C) => DateModOps, + plainMonthDaySlots: PlainMonthDaySlots, + plainMonthDayFields: MonthDayFields, + bag: YearFields, +): PlainDateSlots { + const calendarSlot = plainMonthDaySlots.calendar + const calendarOps = getCalendarOps(calendarSlot) + + return convertPlainMonthDayToDate(calendarOps, plainMonthDayFields, bag) +} + +// PlainTime -> * +// ------------------------------------------------------------------------------------------------- + +export function plainTimeToZonedDateTime( + refineTimeZoneArg: (timeZoneArg: TA) => T, + refinePlainDateArg: (plainDateArg: PA) => PlainDateSlots, + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + slots: PlainTimeSlots, + options: { timeZone: TA, plainDate: PA }, +): ZonedDateTimeSlots { + const plainDateSlots = refinePlainDateArg(options.plainDate) + const timeZoneSlot = refineTimeZoneArg(options.timeZone) + const timeZoneOps = getTimeZoneOps(timeZoneSlot) + + return createZonedDateTimeSlots( + getSingleInstantFor( + timeZoneOps, + { ...plainDateSlots, ...slots }, + ), + timeZoneSlot, + plainDateSlots.calendar, + ) +} + +export function plainTimeToPlainDateTime( + plainTimeSlots0: PlainTimeSlots, + plainDateSlots1: PlainDateSlots, +): PlainDateTimeSlots { + return createPlainDateTimeSlots( + checkIsoDateTimeInBounds({ + ...plainTimeSlots0, + ...plainDateSlots1, + }), + ) +} + +// Epoch-* -> Instant +// ------------------------------------------------------------------------------------------------- + +export function epochSecToInstant(epochSec: number): InstantSlots { + return createInstantSlots( + checkEpochNanoInBounds(numberToDayTimeNano(epochSec, nanoInSec)) + ) +} + +export function epochMilliToInstant(epochMilli: number): InstantSlots { + return createInstantSlots( + checkEpochNanoInBounds(numberToDayTimeNano(epochMilli, nanoInMilli)), + ) +} + +export function epochMicroToInstant(epochMicro: bigint): InstantSlots { + return createInstantSlots( + checkEpochNanoInBounds(bigIntToDayTimeNano(toBigInt(epochMicro), nanoInMicro)) + ) +} + +export function epochNanoToInstant(epochNano: bigint): InstantSlots { + return createInstantSlots( + checkEpochNanoInBounds(bigIntToDayTimeNano(toBigInt(epochNano))), + ) +} diff --git a/packages/temporal-polyfill/src/internal/current.ts b/packages/temporal-polyfill/src/internal/current.ts new file mode 100644 index 00000000..11fb807c --- /dev/null +++ b/packages/temporal-polyfill/src/internal/current.ts @@ -0,0 +1,28 @@ +import { IsoDateFields, IsoDateTimeFields, IsoTimeFields, isoDateFieldNamesAsc, isoDateTimeFieldNamesAsc, isoTimeFieldNamesAsc } from "./calendarIsoFields" +import { DayTimeNano } from './dayTimeNano' +import { epochMilliToNano } from './epochAndTime' +import { OrigDateTimeFormat } from './formatIntl' +import { SimpleTimeZoneOps, zonedInternalsToIso } from './timeZoneOps' +import { pluckProps } from './utils' + +export function getCurrentIsoDateTime(timeZoneOps: SimpleTimeZoneOps): IsoDateTimeFields { + const zonedSlots = { epochNanoseconds: getCurrentEpochNanoseconds() } + return pluckProps(isoDateTimeFieldNamesAsc, zonedInternalsToIso(zonedSlots, timeZoneOps)) +} + +export function getCurrentEpochNanoseconds(): DayTimeNano { + return epochMilliToNano(Date.now()) +} + +// ------------------------------------------------------------------------------------------------- + +let currentTimeZoneId: string | undefined + +export function getCurrentTimeZoneId(): string { + return currentTimeZoneId || (currentTimeZoneId = computeCurrentTimeZoneId()) +} + +function computeCurrentTimeZoneId(): string { + return new OrigDateTimeFormat().resolvedOptions().timeZone +} + diff --git a/packages/temporal-polyfill/src/internal/dayTimeNano.ts b/packages/temporal-polyfill/src/internal/dayTimeNano.ts new file mode 100644 index 00000000..c9559c6b --- /dev/null +++ b/packages/temporal-polyfill/src/internal/dayTimeNano.ts @@ -0,0 +1,101 @@ +import { NumSign, compareNumbers, divModFloor, divModTrunc } from './utils' +import { nanoInUtcDay } from './units' + +export type DayTimeNano = [days: number, timeNano: number] + +/* +does balancing +*/ +export function createDayTimeNano(days: number, timeNano: number): DayTimeNano { + let [extraDays, newTimeNano] = divModTrunc(timeNano, nanoInUtcDay) + let newDays = days + extraDays + const newDaysSign = Math.sign(newDays) + + // ensure nonconflicting signs + if (newDaysSign && newDaysSign === -Math.sign(newTimeNano)) { + newDays -= newDaysSign + newTimeNano += newDaysSign * nanoInUtcDay + } + + return [newDays, newTimeNano] +} + +// Math +// ------------------------------------------------------------------------------------------------- + +export function addDayTimeNanoAndNumber(a: DayTimeNano, b: number): DayTimeNano { + return createDayTimeNano(a[0], a[1] + b) +} + +// TODO: converge with diffDayTimeNanos +export function addDayTimeNanos(a: DayTimeNano, b: DayTimeNano, sign: NumSign = 1): DayTimeNano { + return createDayTimeNano(a[0] + b[0] * sign, a[1] + b[1] * sign) +} + +export function diffDayTimeNanos(a: DayTimeNano, b: DayTimeNano): DayTimeNano { + return createDayTimeNano(b[0] - a[0], b[1] - a[1]) +} + +// Compare +// ------------------------------------------------------------------------------------------------- + +export function compareDayTimeNanos(a: DayTimeNano, b: DayTimeNano): NumSign { + return compareNumbers(a[0], b[0]) || compareNumbers(a[1], b[1]) +} + +// Conversion +// ------------------------------------------------------------------------------------------------- + +// other -> DayTimeNano +// (DayTimeNano needs trunc) + +export function bigIntToDayTimeNano(num: bigint, multiplierNano: number = 1): DayTimeNano { + const wholeInDay = BigInt(nanoInUtcDay / multiplierNano) + const days = Number(num / wholeInDay) // does trunc + const remainder = Number(num % wholeInDay) // does trunc + + return [days, remainder * multiplierNano] // scaled. doesn't need balancing +} + +export function numberToDayTimeNano(num: number, multiplierNano: number = 1): DayTimeNano { + const wholeInDay = nanoInUtcDay / multiplierNano + const [days, remainder] = divModTrunc(num, wholeInDay) + + return [days, remainder * multiplierNano] // scaled. doesn't need balancing +} + +// DayTimeNano -> other +// (other units need floor) +// (divisorNano always a denominator of day-nanoseconds, always positive) + +export function dayTimeNanoToBigInt(dayTimeNano: DayTimeNano, divisorNano: number = 1): bigint { + const [days, timeNano] = dayTimeNano + const timeUnits = Math.floor(timeNano / divisorNano) + const timeUnitsInDay = nanoInUtcDay / divisorNano + + return BigInt(days) * BigInt(timeUnitsInDay) + BigInt(timeUnits) +} + +export function dayTimeNanoToNumber( + dayTimeNano: DayTimeNano, + divisorNano: number = 1, + exact?: boolean, +): number { + const [whole, remainder] = dayTimeNanoToNumberRemainder(dayTimeNano, divisorNano) + + return whole + (exact ? remainder / divisorNano : 0) +} + +export function dayTimeNanoToNumberRemainder( + dayTimeNano: DayTimeNano, + divisorNano: number, +): [ + whole: number, + remainder: number, +] { + const [days, timeNano] = dayTimeNano + const [whole, remainderNano] = divModFloor(timeNano, divisorNano) + const wholeInDay = nanoInUtcDay / divisorNano + + return [days * wholeInDay + whole, remainderNano] +} diff --git a/packages/temporal-polyfill/src/internal/diff.ts b/packages/temporal-polyfill/src/internal/diff.ts new file mode 100644 index 00000000..2aa2640e --- /dev/null +++ b/packages/temporal-polyfill/src/internal/diff.ts @@ -0,0 +1,620 @@ +import { DayTimeNano, compareDayTimeNanos, dayTimeNanoToNumber, diffDayTimeNanos } from './dayTimeNano' +import { + DurationFields, + durationFieldDefaults, +} from './durationFields' +import { + negateDurationFields, + nanoToDurationDayTimeFields, + nanoToDurationTimeFields, +} from './durationMath' +import { IsoDateFields, IsoTimeFields, IsoDateTimeFields, isoTimeFieldDefaults, isoTimeFieldNamesAsc } from './calendarIsoFields' +import { + isoDaysInWeek, + isoMonthsInYear, +} from './calendarIso' +import { + isoTimeFieldsToNano, + isoToEpochMilli, + isoToEpochNano +} from './epochAndTime' +import { moveByIsoDays, moveDateTime, moveToMonthStart, moveZonedEpochNano } from './move' +import { RoundingMode } from './options' +import { computeNanoInc, roundByInc, roundDayTimeNano, roundRelativeDuration } from './round' +import { TimeZoneOps, getSingleInstantFor, zonedEpochNanoToIso } from './timeZoneOps' +import { + DayTimeUnit, + TimeUnit, + Unit, + milliInDay, + nanoInUtcDay, +} from './units' +import { NumSign, bindArgs, divModTrunc, identityFunc, pluckProps } from './utils' +import { NativeDiffOps } from './calendarNative' +import { IntlCalendar, computeIntlMonthsInYear } from './calendarIntl' +import { DiffOps, YearMonthDiffOps } from './calendarOps' +import { DurationBranding, DurationSlots, IdLike, InstantSlots, PlainDateSlots, PlainDateTimeSlots, PlainYearMonthSlots, ZonedDateTimeSlots, createDurationSlots, getCommonCalendarSlot, getCommonTimeZoneSlot } from './slots' +import { DiffOptions, copyOptions, refineDiffOptions } from './optionsRefine' +import * as errorMessages from './errorMessages' + +// High-level +// ------------------------------------------------------------------------------------------------- + +export function diffInstants( + instantSlots0: InstantSlots, + instantSlots1: InstantSlots, + options: DiffOptions | undefined, + invert?: boolean, +): DurationSlots { + const optionsCopy = copyOptions(options) + const optionsTuple = refineDiffOptions(invert, optionsCopy, Unit.Second, Unit.Hour) as + [TimeUnit, TimeUnit, number, RoundingMode] + + let durationFields = diffEpochNano( + instantSlots0.epochNanoseconds, + instantSlots1.epochNanoseconds, + ...optionsTuple, + ) + + return createDurationSlots(invert ? negateDurationFields(durationFields): durationFields) +} + +export function diffZonedDateTimes( + getCalendarOps: (calendarSlot: C) => DiffOps, + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + zonedDateTimeSlots0: ZonedDateTimeSlots, + zonedDateTimeSlots1: ZonedDateTimeSlots, + options: DiffOptions | undefined, + invert?: boolean, +): DurationSlots { + const calendarSlot = getCommonCalendarSlot(zonedDateTimeSlots0.calendar, zonedDateTimeSlots1.calendar) + const optionsCopy = copyOptions(options) + const [largestUnit, smallestUnit, roundingInc, roundingMode] = refineDiffOptions(invert, optionsCopy, Unit.Hour) + + const startEpochNano = zonedDateTimeSlots0.epochNanoseconds + const endEpochNano = zonedDateTimeSlots1.epochNanoseconds + const sign = compareDayTimeNanos(endEpochNano, startEpochNano) + let durationFields: DurationFields + + if (!sign) { + durationFields = durationFieldDefaults + } else if (largestUnit < Unit.Day) { + durationFields = diffEpochNano( + startEpochNano, + endEpochNano, + largestUnit as TimeUnit, + smallestUnit as TimeUnit, + roundingInc, + roundingMode, + ) + } else { + const timeZoneSlot = getCommonTimeZoneSlot(zonedDateTimeSlots0.timeZone, zonedDateTimeSlots1.timeZone) + const timeZoneOps = getTimeZoneOps(timeZoneSlot) + const calendarOps = getCalendarOps(calendarSlot) + + durationFields = diffZonedEpochNanoViaCalendar( + calendarOps, + timeZoneOps, + sign, + startEpochNano, + endEpochNano, + largestUnit, + optionsCopy, + ) + + if (sign && !(smallestUnit === Unit.Nanosecond && roundingInc === 1)) { + durationFields = roundRelativeDuration( + durationFields, + endEpochNano, + largestUnit, + smallestUnit, + roundingInc, + roundingMode, + startEpochNano, // marker + identityFunc, // markerToEpochNano + bindArgs(moveZonedEpochNano, calendarOps, timeZoneOps), // moveMarker + ) + } + } + + return createDurationSlots(invert ? negateDurationFields(durationFields): durationFields) +} + +export function diffPlainDateTimes( + getCalendarOps: (calendarSlot: C) => DiffOps, + plainDateTimeSlots0: PlainDateTimeSlots, + plainDateTimeSlots1: PlainDateTimeSlots, + options: DiffOptions | undefined, + invert?: boolean, +): DurationSlots { + const calendarSlot = getCommonCalendarSlot(plainDateTimeSlots0.calendar, plainDateTimeSlots1.calendar) + const optionsCopy = copyOptions(options) + const [largestUnit, smallestUnit, roundingInc, roundingMode] = refineDiffOptions(invert, optionsCopy, Unit.Day) + + const startEpochNano = isoToEpochNano(plainDateTimeSlots0)! + const endEpochNano = isoToEpochNano(plainDateTimeSlots1)! + const sign = compareDayTimeNanos(endEpochNano, startEpochNano) + let durationFields: DurationFields + + if (!sign) { + durationFields = durationFieldDefaults + } else if (largestUnit <= Unit.Day) { + durationFields = diffEpochNano( + startEpochNano, + endEpochNano, + largestUnit as DayTimeUnit, + smallestUnit as DayTimeUnit, + roundingInc, + roundingMode, + ) + } else { + const calendarOps = getCalendarOps(calendarSlot) + + durationFields = diffDateTimesViaCalendar( + calendarOps, + sign, + plainDateTimeSlots0, + plainDateTimeSlots1, + largestUnit, + optionsCopy, + ) + + if (sign && !(smallestUnit === Unit.Nanosecond && roundingInc === 1)) { + durationFields = roundRelativeDuration( + durationFields, + endEpochNano, + largestUnit, + smallestUnit, + roundingInc, + roundingMode, + plainDateTimeSlots0, // marker + isoToEpochNano as (isoFields: IsoDateTimeFields) => DayTimeNano, // markerToEpochNano + bindArgs(moveDateTime, calendarOps), // moveMarker + ) + } + } + + return createDurationSlots(invert ? negateDurationFields(durationFields): durationFields) +} + +export function diffPlainDates( + getCalendarOps: (calendarSlot: C) => DiffOps, + plainDateSlots0: PlainDateSlots, + plainDateSlots1: PlainDateSlots, + options: DiffOptions | undefined, + invert?: boolean, +): DurationSlots { + const calendarSlot = getCommonCalendarSlot(plainDateSlots0.calendar, plainDateSlots1.calendar) + const optionsCopy = copyOptions(options) + const optionsTuple = refineDiffOptions(invert, optionsCopy, Unit.Day, Unit.Year, Unit.Day) + + return diffDateLike( + invert || false, + () => getCalendarOps(calendarSlot), + plainDateSlots0, + plainDateSlots1, + ...optionsTuple, + optionsCopy, + ) +} + +export function diffPlainYearMonth( + getCalendarOps: (calendar: C) => YearMonthDiffOps, + plainYearMonthSlots0: PlainYearMonthSlots, + plainYearMonthSlots1: PlainYearMonthSlots, + options: DiffOptions | undefined, + invert?: boolean, +): DurationSlots { + const calendarSlot = getCommonCalendarSlot(plainYearMonthSlots0.calendar, plainYearMonthSlots1.calendar) + const optionsCopy = copyOptions(options) + const optionsTuple = refineDiffOptions(invert, optionsCopy, Unit.Year, Unit.Year, Unit.Month) + const calendarOps = getCalendarOps(calendarSlot) + + return diffDateLike( + invert || false, + () => calendarOps, + moveToMonthStart(calendarOps, plainYearMonthSlots0), + moveToMonthStart(calendarOps, plainYearMonthSlots1), + ...optionsTuple, + optionsCopy, + ) +} + +function diffDateLike( + invert: boolean, + getCalendarOps: () => DiffOps, + startIsoFields: IsoDateFields, + endIsoFields: IsoDateFields, + largestUnit: Unit, // TODO: large field + smallestUnit: Unit, // TODO: large field + roundingInc: number, + roundingMode: RoundingMode, + origOptions: DiffOptions | undefined, +): DurationSlots { + const startEpochNano = isoToEpochNano(startIsoFields)! + const endEpochNano = isoToEpochNano(endIsoFields)! + const sign = compareDayTimeNanos(endEpochNano, startEpochNano) + let durationFields: DurationFields + + if (!sign) { + durationFields = durationFieldDefaults + } else { + let calendarOps: DiffOps + + if (largestUnit === Unit.Day) { + durationFields = diffByDay(startIsoFields, endIsoFields) + } else { + calendarOps = getCalendarOps() + durationFields = calendarOps.dateUntil(startIsoFields, endIsoFields, largestUnit, origOptions) + } + + if (!(smallestUnit === Unit.Day && roundingInc === 1)) { + calendarOps ||= getCalendarOps() + durationFields = roundRelativeDuration( + durationFields, + endEpochNano, + largestUnit, + smallestUnit, + roundingInc, + roundingMode, + startIsoFields, // marker + isoToEpochNano as (isoFields: IsoDateFields) => DayTimeNano, // markerToEpochNano + (m: IsoDateFields, d: DurationFields) => calendarOps.dateAdd(m, d), // moveMarker + ) + } + } + + return createDurationSlots(invert ? negateDurationFields(durationFields): durationFields) +} + +export function diffPlainTimes( + plainTimeSlots0: IsoTimeFields, + plainTimeSlots1: IsoTimeFields, + options: DiffOptions | undefined, + invert?: boolean, +): DurationSlots { + const optionsCopy = copyOptions(options) + const [largestUnit, smallestUnit, roundingInc, roundingMode] = refineDiffOptions(invert, optionsCopy, Unit.Hour, Unit.Hour) + + const startTimeNano = isoTimeFieldsToNano(plainTimeSlots0) + const endTimeNano = isoTimeFieldsToNano(plainTimeSlots1) + const nanoInc = computeNanoInc(smallestUnit as TimeUnit, roundingInc) + const timeNano = roundByInc(endTimeNano - startTimeNano, nanoInc, roundingMode) + + let durationFields = { + ...durationFieldDefaults, + ...nanoToDurationTimeFields(timeNano, largestUnit as TimeUnit), + } + + return createDurationSlots(invert ? negateDurationFields(durationFields): durationFields) +} + +// Exact Diffing +// ------------------------------------------------------------------------------------------------- + +export function diffZonedEpochNanoExact( + calendarOps: DiffOps, + timeZoneOps: TimeZoneOps, + startEpochNano: DayTimeNano, + endEpochNano: DayTimeNano, + largestUnit: Unit, + origOptions?: DiffOptions, +): DurationFields { + const sign = compareDayTimeNanos(endEpochNano, startEpochNano) + + if (!sign) { + return durationFieldDefaults + } + if (largestUnit < Unit.Day) { + return diffEpochNanoExact(startEpochNano, endEpochNano, largestUnit as DayTimeUnit) + } + + return diffZonedEpochNanoViaCalendar( + calendarOps, + timeZoneOps, + sign, + startEpochNano, + endEpochNano, + largestUnit, + origOptions, + ) +} + +export function diffDateTimesExact( + calendarOps: DiffOps, + startIsoFields: IsoDateTimeFields, + endIsoFields: IsoDateTimeFields, + largestUnit: Unit, + origOptions?: DiffOptions, +): DurationFields { + const startEpochNano = isoToEpochNano(startIsoFields)! + const endEpochNano = isoToEpochNano(endIsoFields)! + const sign = compareDayTimeNanos(endEpochNano, startEpochNano) + + if (!sign) { + return durationFieldDefaults + } + if (largestUnit <= Unit.Day) { + return diffEpochNanoExact(startEpochNano, endEpochNano, largestUnit as DayTimeUnit) + } + + return diffDateTimesViaCalendar( + calendarOps, + sign, + startIsoFields, + endIsoFields, + largestUnit, + origOptions, + ) +} + +// Diffing Via Calendar +// ------------------------------------------------------------------------------------------------- + +function diffZonedEpochNanoViaCalendar( + calendarOps: DiffOps, + timeZoneOps: TimeZoneOps, + sign: NumSign, + startEpochNano: DayTimeNano, + endEpochNano: DayTimeNano, + largestUnit: Unit, + origOptions?: DiffOptions, +): DurationFields { + const startIsoFields = zonedEpochNanoToIso(timeZoneOps, startEpochNano) + const startIsoTimeFields = pluckProps(isoTimeFieldNamesAsc, startIsoFields) + const endIsoFields = zonedEpochNanoToIso(timeZoneOps, endEpochNano) + const isoToZonedEpochNano = bindArgs(getSingleInstantFor, timeZoneOps) + let midIsoFields: IsoDateTimeFields + let midEpochNano: DayTimeNano + let midSign: NumSign + let cnt = 0 + + // Might need multiple backoffs: one for simple time overage, other for end being in DST gap + do { + if (cnt > 2) { + throw new RangeError(errorMessages.invalidProtocolResults) + } + + midIsoFields = { ...moveByIsoDays(endIsoFields, cnt++ * -sign), ...startIsoTimeFields } + midEpochNano = isoToZonedEpochNano(midIsoFields) + midSign = compareDayTimeNanos(endEpochNano, midEpochNano) + } while (midSign === -sign) + + const dateDiff = largestUnit === Unit.Day + ? diffByDay(startIsoFields, midIsoFields) + : calendarOps.dateUntil(startIsoFields, midIsoFields, largestUnit, origOptions) + + const timeDiffNano = dayTimeNanoToNumber(diffDayTimeNanos(midEpochNano, endEpochNano)) // could be over 24 hour, so we need to consider day too + const timeDiff = nanoToDurationTimeFields(timeDiffNano) + const dateTimeDiff = { ...dateDiff, ...timeDiff } + + return dateTimeDiff +} + +function diffDateTimesViaCalendar( + calendarOps: DiffOps, + sign: NumSign, + startIsoFields: IsoDateTimeFields, + endIsoFields: IsoDateTimeFields, + largestUnit: Unit, + origOptions?: DiffOptions, +): DurationFields { + const startTimeNano = isoTimeFieldsToNano(startIsoFields) + const endTimeNano = isoTimeFieldsToNano(endIsoFields) + let timeNano = endTimeNano - startTimeNano + const timeSign = Math.sign(timeNano) + + // simulate startDate plus time fields (because that happens before adding date) + let midIsoFields: IsoDateFields = startIsoFields + + // move start-fields forward so time-diff-sign matches date-diff-sign + if (timeSign === -sign) { + midIsoFields = moveByIsoDays(startIsoFields, sign) + timeNano += nanoInUtcDay * sign + } + + const dateDiff = calendarOps.dateUntil( + { ...midIsoFields, ...isoTimeFieldDefaults }, + { ...endIsoFields, ...isoTimeFieldDefaults }, + largestUnit, + origOptions, + ) + const timeDiff = nanoToDurationTimeFields(timeNano) + const dateTimeDiff = { ...dateDiff, ...timeDiff } + + return dateTimeDiff +} + +// Diffing Via Epoch Nanoseconds +// ------------------------------------------------------------------------------------------------- + +function diffEpochNano( + startEpochNano: DayTimeNano, + endEpochNano: DayTimeNano, + largestUnit: DayTimeUnit, + smallestUnit: DayTimeUnit, + roundingInc: number, + roundingMode: RoundingMode, +): DurationFields { + return { + ...durationFieldDefaults, + ...nanoToDurationDayTimeFields( + roundDayTimeNano( + diffDayTimeNanos(startEpochNano, endEpochNano), + smallestUnit, + roundingInc, + roundingMode, + ), + largestUnit, + ), + } +} + +function diffEpochNanoExact( + startEpochNano: DayTimeNano, + endEpochNano: DayTimeNano, + largestUnit: DayTimeUnit, +): DurationFields { + return { + ...durationFieldDefaults, + ...nanoToDurationDayTimeFields( + diffDayTimeNanos(startEpochNano, endEpochNano), + largestUnit as DayTimeUnit, + ) + } +} + +function diffByDay( + startIsoFields: IsoDateFields, + endIsoFields: IsoDateFields, +): DurationFields { + return { ...durationFieldDefaults, days: diffDays(startIsoFields, endIsoFields) } +} + +function diffDays( + startIsoFields: IsoDateFields, + endIsoFields: IsoDateFields, +): number { + return diffEpochMilliByDay( + isoToEpochMilli(startIsoFields)!, + isoToEpochMilli(endIsoFields)!, + ) +} + +/* +Must always be given start-of-day +*/ +export function diffEpochMilliByDay( + epochMilli0: number, + epochMilli1: number, +): number { + return Math.round((epochMilli1 - epochMilli0) / milliInDay) +} + +// Native +// ------------------------------------------------------------------------------------------------- + +export function nativeDateUntil( + this: NativeDiffOps, + startIsoFields: IsoDateFields, + endIsoFields: IsoDateFields, + largestUnit: Unit, +): DurationFields { + if (largestUnit <= Unit.Week) { + let weeks = 0 + let days = diffDays(startIsoFields, endIsoFields) + + if (largestUnit === Unit.Week) { + [weeks, days] = divModTrunc(days, isoDaysInWeek) + } + + return { ...durationFieldDefaults, weeks, days } + } + + const yearMonthDayStart = this.dateParts(startIsoFields) + const yearMonthDayEnd = this.dateParts(endIsoFields) + let [years, months, days] = diffYearMonthDay( + this, + ...yearMonthDayStart, + ...yearMonthDayEnd, + ) + + if (largestUnit === Unit.Month) { + months += this.monthsInYearSpan(years, yearMonthDayStart[0]) + years = 0 + } + + return { ...durationFieldDefaults, years, months, days } +} + +function diffYearMonthDay( + calendarNative: NativeDiffOps, + year0: number, + month0: number, + day0: number, + year1: number, + month1: number, + day1: number, +): [ + yearDiff: number, + monthDiff: number, + dayDiff: number, +] { + let yearDiff!: number + let monthsInYear1!: number + let monthDiff!: number + let daysInMonth1!: number + let dayDiff!: number + + function updateYearMonth() { + let [monthCodeNumber0, isLeapYear0] = calendarNative.monthCodeParts(year0, month0) + let [monthCodeNumber1, isLeapYear1] = calendarNative.monthCodeParts(year1, month1) + + yearDiff = year1 - year0 + monthsInYear1 = calendarNative.monthsInYearPart(year1) + monthDiff = yearDiff + // crossing years + ? (monthCodeNumber1 - monthCodeNumber0) || (Number(isLeapYear1) - Number(isLeapYear0)) + // same year + : month1 - Math.min(month0, monthsInYear1) + } + + function updateYearMonthDay() { + updateYearMonth() + daysInMonth1 = calendarNative.daysInMonthParts(year1, month1) + dayDiff = day1 - Math.min(day0, daysInMonth1) + } + + updateYearMonthDay() + const daySign = Math.sign(dayDiff) as NumSign + const sign = (Math.sign(yearDiff) || Math.sign(monthDiff) || daySign) as NumSign + + if (sign) { + // overshooting day? correct by moving to penultimate month + if (daySign === -sign) { + const oldDaysInMonth1 = daysInMonth1 + ;([year1, month1] = calendarNative.monthAdd(year1, month1, -sign)) + updateYearMonthDay() + dayDiff += sign < 0 // correct with days-in-month further in past + ? -oldDaysInMonth1 // correcting from past -> future + : daysInMonth1 // correcting from future -> past + } + + // overshooting month? correct by moving to penultimate year + const monthSign = Math.sign(monthDiff) as NumSign + if (monthSign === -sign) { + const oldMonthsInYear1 = monthsInYear1 + year1 -= sign + updateYearMonth() + monthDiff += sign < 0 // correct with months-in-year further in past + ? -oldMonthsInYear1 // correcting from past -> future + : monthsInYear1 // correcting from future -> past + } + } + + return [yearDiff, monthDiff, dayDiff] +} + +// Month Span for ISO/Intl +// ------------------------------------------------------------------------------------------------- + +export function computeIsoMonthsInYearSpan(yearDelta: number): number { + return yearDelta * isoMonthsInYear +} + +export function computeIntlMonthsInYearSpan( + this: IntlCalendar, + yearDelta: number, + yearStart: number, +): number { + const yearEnd = yearStart + yearDelta + const yearSign = Math.sign(yearDelta) + const yearCorrection = yearSign < 0 ? -1 : 0 + let months = 0 + + for (let year = yearStart; year !== yearEnd; year += yearSign) { + months += computeIntlMonthsInYear.call(this, year + yearCorrection) + } + + return months +} diff --git a/packages/temporal-polyfill/src/internal/durationFields.ts b/packages/temporal-polyfill/src/internal/durationFields.ts new file mode 100644 index 00000000..ff224c1c --- /dev/null +++ b/packages/temporal-polyfill/src/internal/durationFields.ts @@ -0,0 +1,39 @@ +import { Unit, unitNamesAsc } from './units' +import { + mapPropNamesToConstant, + mapPropNamesToIndex, +} from './utils' + +export interface DurationDateFields { + days: number + weeks: number + months: number + years: number +} + +export interface DurationTimeFields { + nanoseconds: number + microseconds: number + milliseconds: number + seconds: number + minutes: number + hours: number +} + +export type DurationFields = DurationDateFields & DurationTimeFields + +// Field Names +// ------------------------------------------------------------------------------------------------- + +export const durationFieldNamesAsc = unitNamesAsc.map((unitName) => unitName + 's') as (keyof DurationFields)[] +export const durationFieldNamesAlpha = durationFieldNamesAsc.slice().sort() +export const durationTimeFieldNamesAsc = durationFieldNamesAsc.slice(0, Unit.Day) +export const durationDateFieldNamesAsc = durationFieldNamesAsc.slice(Unit.Day) + +export const durationFieldIndexes = mapPropNamesToIndex(durationFieldNamesAsc) + +// Field Defaults +// ------------------------------------------------------------------------------------------------- + +export const durationFieldDefaults = mapPropNamesToConstant(durationFieldNamesAsc, 0) +export const durationTimeFieldDefaults = mapPropNamesToConstant(durationTimeFieldNamesAsc, 0) diff --git a/packages/temporal-polyfill/src/internal/durationMath.ts b/packages/temporal-polyfill/src/internal/durationMath.ts new file mode 100644 index 00000000..f3285936 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/durationMath.ts @@ -0,0 +1,384 @@ +import { DayTimeNano, addDayTimeNanos } from './dayTimeNano' +import { DayTimeUnit, TimeUnit, Unit, givenFieldsToDayTimeNano, nanoInUtcDay, nanoToGivenFields, unitNanoMap } from './units' +import { NumSign, bindArgs, createLazyGenerator, identityFunc } from './utils' +import { DurationFields, durationFieldDefaults, durationFieldNamesAsc, durationDateFieldNamesAsc, DurationTimeFields } from './durationFields' +import { DiffOps } from './calendarOps' +import { TimeZoneOps } from './timeZoneOps' +import { DurationSlots, createDurationSlots } from './slots' +import { DurationRoundOptions, RelativeToOptions, normalizeOptions, refineDurationRoundOptions } from './optionsRefine' +import { moveDateTime, moveZonedEpochNano } from './move' +import { IsoDateFields, IsoDateTimeFields, isoTimeFieldDefaults } from './calendarIsoFields' +import { diffDateTimesExact, diffZonedEpochNanoExact } from './diff' +import { isoToEpochNano } from './epochAndTime' +import { roundDayTimeDuration, roundRelativeDuration } from './round' +import * as errorMessages from './errorMessages' + +// Marker System +// ------------------------------------------------------------------------------------------------- + +export type MarkerSlotsNoCalendar = { + epochNanoseconds: DayTimeNano, + timeZone: T, +} | IsoDateTimeFields + +export type MarkerSlots = + { epochNanoseconds: DayTimeNano, timeZone: T, calendar: C } | + (IsoDateFields & { calendar: C }) + +export type MarkerToEpochNano = (marker: M) => DayTimeNano +export type MoveMarker = (marker: M, durationFields: DurationFields) => M +export type DiffMarkers = (marker0: M, marker1: M, largeUnit: Unit) => DurationFields +export type MarkerSystem = [ + M, + MarkerToEpochNano, + MoveMarker, + DiffMarkers +] + +export function createMarkerSystem( + getCalendarOps: (calendarSlot: C) => DiffOps, + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + markerSlots: MarkerSlots, +): MarkerSystem | MarkerSystem { + const { calendar, timeZone, epochNanoseconds } = markerSlots as + { calendar: C, timeZone?: T, epochNanoseconds?: DayTimeNano } + + const calendarOps = getCalendarOps(calendar) + + if (epochNanoseconds) { + const timeZoneOps = getTimeZoneOps(timeZone!) + + return [ + epochNanoseconds, + identityFunc as MarkerToEpochNano, + bindArgs(moveZonedEpochNano, calendarOps, timeZoneOps), + bindArgs(diffZonedEpochNanoExact, calendarOps, timeZoneOps), + ] + } else { + return [ + { ...markerSlots, ...isoTimeFieldDefaults } as IsoDateTimeFields, + isoToEpochNano as MarkerToEpochNano, + bindArgs(moveDateTime, calendarOps), + bindArgs(diffDateTimesExact, calendarOps), + ] + } +} + +/* +Rebalances duration(s) +*/ +export function spanDuration( + durationFields0: DurationFields, + durationFields1: DurationFields | undefined, // HACKy + largestUnit: Unit, // TODO: more descrimination? + // marker system... + marker: M, + markerToEpochNano: MarkerToEpochNano, + moveMarker: MoveMarker, + diffMarkers: DiffMarkers, +): [ + DurationFields, + DayTimeNano, +] { + let endMarker = moveMarker(marker, durationFields0) + + // better way to do this? + if (durationFields1) { + endMarker = moveMarker(endMarker, durationFields1) + } + + let balancedDuration = diffMarkers(marker, endMarker, largestUnit) + + return [ + balancedDuration, + markerToEpochNano(endMarker), + ] +} + +// Adding +// ------------------------------------------------------------------------------------------------- + +export function addDurations( + refineRelativeTo: (relativeToArg: RA) => MarkerSlots | undefined, + getCalendarOps: (calendarSlot: C) => DiffOps, + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + slots: DurationSlots, + otherSlots: DurationSlots, + options?: RelativeToOptions, + doSubtract?: boolean, +): DurationSlots { + const normalOptions = normalizeOptions(options) + const markerSlots = refineRelativeTo(normalOptions.relativeTo) + const largestUnit = Math.max( + getLargestDurationUnit(slots), + getLargestDurationUnit(otherSlots), + ) as Unit + + if ( + largestUnit < Unit.Day || ( + largestUnit === Unit.Day && + // has uniform days? + !(markerSlots && (markerSlots as any).epochNanoseconds) + ) + ) { + return createDurationSlots( + addDayTimeDurations(slots, otherSlots, largestUnit as DayTimeUnit, doSubtract), + ) + } + + if (!markerSlots) { + throw new RangeError(errorMessages.missingRelativeTo) + } + + if (doSubtract) { + otherSlots = negateDurationFields(otherSlots) as any // !!! + } + + const markerSystem = createMarkerSystem(getCalendarOps, getTimeZoneOps, markerSlots) as + MarkerSystem + + return createDurationSlots( + spanDuration( + slots, + otherSlots, + largestUnit, + ...markerSystem, + )[0] + ) +} + +function addDayTimeDurations( + a: DurationFields, + b: DurationFields, + largestUnit: DayTimeUnit, + doSubtract?: boolean, +): DurationFields { + const dayTimeNano0 = durationFieldsToDayTimeNano(a, Unit.Day) + const dayTimeNano1 = durationFieldsToDayTimeNano(b, Unit.Day) + const combined = addDayTimeNanos(dayTimeNano0, dayTimeNano1, doSubtract ? -1 : 1) + + if (!Number.isFinite(combined[0])) { + throw new RangeError(errorMessages.outOfBoundsDate) + } + + return { + ...durationFieldDefaults, + ...nanoToDurationDayTimeFields(combined, largestUnit) + } +} + +// Rounding +// ------------------------------------------------------------------------------------------------- + +export function roundDuration( + refineRelativeTo: (relativeToArg: RA) => MarkerSlots | undefined, + getCalendarOps: (calendarSlot: C) => DiffOps, + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + slots: DurationSlots, + options: DurationRoundOptions, +): DurationSlots { + const durationLargestUnit = getLargestDurationUnit(slots) + const [ + largestUnit, + smallestUnit, + roundingInc, + roundingMode, + markerSlots, + ] = refineDurationRoundOptions(options, durationLargestUnit, refineRelativeTo) + + const maxLargestUnit = Math.max(durationLargestUnit, largestUnit) + + if ( + maxLargestUnit < Unit.Day || ( + maxLargestUnit === Unit.Day && + // has uniform days? + !(markerSlots && (markerSlots as any).epochNanoseconds) + ) + ) { + return createDurationSlots( + roundDayTimeDuration( + slots, + largestUnit as DayTimeUnit, // guaranteed <= maxLargestUnit <= Unit.Day + smallestUnit as DayTimeUnit, + roundingInc, + roundingMode, + ), + ) + } + + if (!markerSlots) { + throw new RangeError(errorMessages.missingRelativeTo) + } + + const markerSystem = createMarkerSystem(getCalendarOps, getTimeZoneOps, markerSlots) as + MarkerSystem + + let transplantedWeeks = 0 + if (slots.weeks && smallestUnit === Unit.Week) { + transplantedWeeks = slots.weeks + slots = { ...slots, weeks: 0 } + } + + let [balancedDuration, endEpochNano] = spanDuration(slots, undefined, largestUnit, ...markerSystem) + + const origSign = queryDurationSign(slots) + const balancedSign = queryDurationSign(balancedDuration) + if (origSign && balancedSign && origSign !== balancedSign) { + throw new RangeError(errorMessages.invalidProtocolResults) + } + + if (balancedSign && !(smallestUnit === Unit.Nanosecond && roundingInc === 1)) { + balancedDuration = roundRelativeDuration( + balancedDuration, + endEpochNano, + largestUnit, + smallestUnit, + roundingInc, + roundingMode, + ...markerSystem, + ) + } + + balancedDuration.weeks += transplantedWeeks // HACK (mutating) + + return createDurationSlots(balancedDuration) +} + +// Sign / Abs / Blank +// ------------------------------------------------------------------------------------------------- + +export function negateDuration(slots: DurationSlots): DurationSlots { + return createDurationSlots(negateDurationFields(slots)) +} + +export function negateDurationFields(fields: DurationFields): DurationFields { + const res = {} as DurationFields + + for (const fieldName of durationFieldNamesAsc) { + res[fieldName] = fields[fieldName] * -1 || 0 + } + + return res +} + +export function absDuration(slots: DurationSlots): DurationSlots { + return createDurationSlots(absDurationFields(slots)) +} + +export function absDurationFields(fields: DurationFields): DurationFields { + if (queryDurationSign(fields) === -1) { + return negateDurationFields(fields) + } + + return fields +} + +export function queryDurationBlank(durationFields: DurationFields): boolean { + return !queryDurationSign(durationFields) +} + +export const queryDurationSign = createLazyGenerator(computeDurationSign, WeakMap) + +function computeDurationSign( + fields: DurationFields, + fieldNames = durationFieldNamesAsc +): NumSign { + let sign: NumSign = 0 + + for (const fieldName of fieldNames) { + const fieldSign = Math.sign(fields[fieldName]) as NumSign + + if (fieldSign) { + if (sign && sign !== fieldSign) { + throw new RangeError(errorMessages.forbiddenDurationSigns) + } + sign = fieldSign + } + } + + return sign +} + +export function checkDurationFields(fields: DurationFields): DurationFields { + queryDurationSign(fields) // check and prime cache + return fields +} + +// Field <-> Nanosecond Conversion +// ------------------------------------------------------------------------------------------------- + +export function durationTimeFieldsToLargeNanoStrict(fields: DurationFields): DayTimeNano { + if (durationHasDateParts(fields)) { + throw new RangeError(errorMessages.invalidLargeUnits) + } + + return durationFieldsToDayTimeNano(fields, Unit.Hour) +} + +export function durationFieldsToDayTimeNano(fields: DurationFields, largestUnit: DayTimeUnit): DayTimeNano { + return givenFieldsToDayTimeNano(fields, largestUnit, durationFieldNamesAsc) +} + +export function nanoToDurationDayTimeFields(largeNano: DayTimeNano): { days: number } & DurationTimeFields +export function nanoToDurationDayTimeFields(largeNano: DayTimeNano, largestUnit?: DayTimeUnit): Partial +export function nanoToDurationDayTimeFields( + dayTimeNano: DayTimeNano, + largestUnit: DayTimeUnit = Unit.Day, +): Partial { + const [days, timeNano] = dayTimeNano + const dayTimeFields = nanoToGivenFields(timeNano, largestUnit, durationFieldNamesAsc) + + dayTimeFields[durationFieldNamesAsc[largestUnit]]! += + days * (nanoInUtcDay / unitNanoMap[largestUnit]) + + if (!Number.isFinite(dayTimeFields[durationFieldNamesAsc[largestUnit]]!)) { + throw new RangeError(errorMessages.outOfBoundsDate) + } + + return dayTimeFields +} + +// audit +export function nanoToDurationTimeFields(nano: number): DurationTimeFields +export function nanoToDurationTimeFields(nano: number, largestUnit: TimeUnit): Partial +export function nanoToDurationTimeFields( + nano: number, + largestUnit: TimeUnit = Unit.Hour, +): Partial { + return nanoToGivenFields(nano, largestUnit, durationFieldNamesAsc as (keyof DurationTimeFields)[]) +} + +/* +Returns all units +*/ +export function clearDurationFields( + durationFields: DurationFields, + largestUnitToClear: Unit, +): DurationFields { + const copy = { ...durationFields } + + for (let unit: Unit = Unit.Nanosecond; unit <= largestUnitToClear; unit++) { + copy[durationFieldNamesAsc[unit]] = 0 + } + + return copy +} + +// Utils +// ------------------------------------------------------------------------------------------------- + +export function durationHasDateParts(fields: DurationFields): boolean { + return Boolean(computeDurationSign(fields, durationDateFieldNamesAsc)) +} + +export function getLargestDurationUnit(fields: DurationFields): Unit { + let unit: Unit = Unit.Year + + for (; unit > Unit.Nanosecond; unit--) { + if (fields[durationFieldNamesAsc[unit]]) { + break + } + } + + return unit +} diff --git a/packages/temporal-polyfill/src/internal/epochAndTime.ts b/packages/temporal-polyfill/src/internal/epochAndTime.ts new file mode 100644 index 00000000..19f4741e --- /dev/null +++ b/packages/temporal-polyfill/src/internal/epochAndTime.ts @@ -0,0 +1,307 @@ +import { Overflow } from './options' +import { + IsoDateTimeFields, + IsoDateFields, + IsoTimeFields, + isoTimeFieldNamesAsc, + isoTimeFieldDefaults, + isoDateTimeFieldNamesAsc +} from './calendarIsoFields' +import { + Unit, + givenFieldsToDayTimeNano, + milliInDay, + milliInSec, + nanoInMicro, + nanoInMilli, + nanoInSec, + nanoInUtcDay, + nanoToGivenFields +} from './units' +import { divModFloor, clampProp, divModTrunc, zipProps } from './utils' +import { DayTimeNano, addDayTimeNanoAndNumber, compareDayTimeNanos, dayTimeNanoToBigInt, dayTimeNanoToNumber, dayTimeNanoToNumberRemainder, numberToDayTimeNano } from './dayTimeNano' +import * as errorMessages from './errorMessages' + +const maxDays = 100000000 +const epochNanoMax: DayTimeNano = [maxDays, 0] +const epochNanoMin: DayTimeNano = [-maxDays, 0] +const isoYearMax = 275760 // optimization. isoYear at epochNanoMax +const isoYearMin = -271821 // optimization. isoYear at epochNanoMin + +export function checkIsoYearMonthInBounds(isoFields: T): T { + // TODO: just authenticate based on hardcoded min/max isoYear/Month/Day. for other types too + clampProp(isoFields, 'isoYear' as any, isoYearMin, isoYearMax, Overflow.Reject) + + if (isoFields.isoYear === isoYearMin) { + clampProp(isoFields, 'isoMonth' as any, 4, 12, Overflow.Reject) + } else if (isoFields.isoYear === isoYearMax) { + clampProp(isoFields, 'isoMonth' as any, 1, 9, Overflow.Reject) + } + + return isoFields +} + +export function checkIsoDateInBounds(isoFields: T): T { + checkIsoDateTimeInBounds({ + ...isoFields, + ...isoTimeFieldDefaults, + isoHour: 12, // Noon avoids trouble at edges of DateTime range (excludes midnight) ? + }) + return isoFields +} + +export function checkIsoDateTimeInBounds(isoFields: T): T { + const isoYear = clampProp(isoFields as IsoDateFields, 'isoYear', isoYearMin, isoYearMax, Overflow.Reject) + const nudge = isoYear === isoYearMin ? 1 : isoYear === isoYearMax ? -1 : 0 + + if (nudge) { + // needs to be within 23:59:59.999 of min/max epochNano + checkEpochNanoInBounds( + isoToEpochNano({ + ...isoFields, + isoDay: isoFields.isoDay + nudge, + isoNanosecond: isoFields.isoNanosecond - nudge + }) + ) + } + + return isoFields +} + +export function checkEpochNanoInBounds(epochNano: DayTimeNano | undefined): DayTimeNano { + if ( + !epochNano || + compareDayTimeNanos(epochNano, epochNanoMin) === -1 || // epochNano < epochNanoMin + compareDayTimeNanos(epochNano, epochNanoMax) === 1 // epochNano > epochNanoMax + ) { + throw new RangeError(errorMessages.outOfBoundsDate) + } + return epochNano +} + +// Field <-> Nanosecond Conversion +// ------------------------------------------------------------------------------------------------- + +export function isoTimeFieldsToNano(isoTimeFields: IsoTimeFields): number { + return givenFieldsToDayTimeNano(isoTimeFields, Unit.Hour, isoTimeFieldNamesAsc)[1] +} + +export function nanoToIsoTimeAndDay(nano: number): [IsoTimeFields, number] { + const [dayDelta, timeNano] = divModFloor(nano, nanoInUtcDay) + const isoTimeFields = nanoToGivenFields(timeNano, Unit.Hour, isoTimeFieldNamesAsc) as IsoTimeFields + + return [isoTimeFields, dayDelta] +} + +// Epoch Unit Conversion +// ------------------------------------------------------------------------------------------------- +// nano -> [micro/milli/sec] + +export function epochNanoToSec(epochNano: DayTimeNano): number { + return dayTimeNanoToNumber(epochNano, nanoInSec) +} + +export function epochNanoToSecRemainder(epochNano: DayTimeNano): [number, number] { + return dayTimeNanoToNumberRemainder(epochNano, nanoInSec) +} + +export function epochNanoToMilli(epochNano: DayTimeNano): number { + return dayTimeNanoToNumber(epochNano, nanoInMilli) +} + +export function epochNanoToMicro(epochNano: DayTimeNano): bigint { + return dayTimeNanoToBigInt(epochNano, nanoInMicro) +} + +// [micro/milli/sec] -> nano + +export function epochMilliToNano(epochMilli: number): DayTimeNano { + return numberToDayTimeNano(epochMilli, nanoInMilli) +} + +// Epoch Getters +// ------------------------------------------------------------------------------------------------- + +export const epochGetters = { + epochSeconds: epochNanoToSec, + epochMilliseconds: epochNanoToMilli, + epochMicroseconds: epochNanoToMicro, + epochNanoseconds: dayTimeNanoToBigInt, +} + +// ISO <-> Epoch Conversion +// ------------------------------------------------------------------------------------------------- +// ISO Fields -> Epoch + +export function isoToEpochSec(isoDateTimeFields: IsoDateTimeFields): [number, number] { + // assume valid + // TODO: nicer way to splice this (while still excluding subsec) + const epochSec = isoArgsToEpochSec( + isoDateTimeFields.isoYear, + isoDateTimeFields.isoMonth, + isoDateTimeFields.isoDay, + isoDateTimeFields.isoHour, + isoDateTimeFields.isoMinute, + isoDateTimeFields.isoSecond + ) + + const subsecNano = isoDateTimeFields.isoMillisecond * nanoInMilli + + isoDateTimeFields.isoMicrosecond * nanoInMicro + + isoDateTimeFields.isoNanosecond + + return [epochSec, subsecNano] +} + +/* +If out-of-bounds, returns undefined +*/ +export function isoToEpochMilli( + isoDateTimeFields: IsoDateTimeFields | IsoDateFields +): number | undefined { + return isoArgsToEpochMilli( + isoDateTimeFields.isoYear, + isoDateTimeFields.isoMonth, + isoDateTimeFields.isoDay, + (isoDateTimeFields as IsoDateTimeFields).isoHour, + (isoDateTimeFields as IsoDateTimeFields).isoMinute, + (isoDateTimeFields as IsoDateTimeFields).isoSecond, + (isoDateTimeFields as IsoDateTimeFields).isoMillisecond + ) +} + +/* +For converting to fake epochNano values for math +If out-of-bounds, returns undefined +*/ +export function isoToEpochNano( + isoFields: IsoDateTimeFields | IsoDateFields +): DayTimeNano | undefined { + const epochMilli = isoToEpochMilli(isoFields) + + if (epochMilli !== undefined) { + const [days, milliRemainder] = divModTrunc(epochMilli, milliInDay) + + const timeNano = milliRemainder * nanoInMilli + + ((isoFields as IsoDateTimeFields).isoMicrosecond || 0) * nanoInMicro + + ((isoFields as IsoDateTimeFields).isoNanosecond || 0) + + return [days, timeNano] + } +} + +/* +For converting to proper epochNano values +Ensures in bounds +*/ +export function isoToEpochNanoWithOffset(isoFields: IsoDateTimeFields, offsetNano: number): DayTimeNano { + const [newIsoTimeFields, dayDelta] = nanoToIsoTimeAndDay(isoTimeFieldsToNano(isoFields) - offsetNano) + const epochNano = isoToEpochNano({ + ...isoFields, + isoDay: isoFields.isoDay + dayDelta, + ...newIsoTimeFields, + }) + + return checkEpochNanoInBounds(epochNano) +} + +// ISO Arguments -> Epoch + +export type IsoTuple = [ + isoYear: number, + isoMonth?: number, + isoDay?: number, + isoHour?: number, + isoMinute?: number, + isoSecond?: number, + isoMilli?: number +] + +/* +Assumes in-bounds +*/ +export function isoArgsToEpochSec(...args: IsoTuple): number { + return isoArgsToEpochMilli(...args)! / milliInSec +} + +/* +If out-of-bounds, returns undefined +*/ +export function isoArgsToEpochMilli(...args: IsoTuple): number | undefined { + const [legacyDate, nudge] = isoToLegacyDate(...args) + const epochMilli = legacyDate.getTime() + + if (!isNaN(epochMilli)) { + return epochMilli - nudge * milliInDay + } +} + +export function isoToLegacyDate( + isoYear: number, + isoMonth: number = 1, + isoDay: number = 1, + isoHour: number = 0, + isoMinute: number = 0, + isoSec: number = 0, + isoMilli: number = 0 +): [Date, number] { + // allows this function to accept values beyond valid Instants + // (PlainDateTime allows values within 24hrs) + const nudge = isoYear === isoYearMin ? 1 : isoYear === isoYearMax ? -1 : 0 + + // Note: Date.UTC() interprets one and two-digit years as being in the + // 20th century, so don't use it + const legacyDate = new Date() // should throw out-of-range error here? + legacyDate.setUTCHours(isoHour, isoMinute, isoSec, isoMilli) + legacyDate.setUTCFullYear(isoYear, isoMonth - 1, isoDay + nudge) + + return [legacyDate, nudge] +} + +// Epoch -> ISO Fields + +export function epochNanoToIso(epochNano: DayTimeNano, offsetNano: number): IsoDateTimeFields { + let [days, timeNano] = addDayTimeNanoAndNumber(epochNano, offsetNano) + + // convert to start-of-day and time-of-day + if (timeNano < 0) { + timeNano += nanoInUtcDay + days -= 1 + } + + const [timeMilli, nanoRemainder] = divModFloor(timeNano, nanoInMilli) + const [isoMicrosecond, isoNanosecond] = divModFloor(nanoRemainder, nanoInMicro) + const epochMilli = days * milliInDay + timeMilli + + return { + ...epochMilliToIso(epochMilli), + isoMicrosecond, + isoNanosecond, + } +} + +/* +Given epochMilli assumed to be within PlainDateTime's range +*/ +export function epochMilliToIso(epochMilli: number): { + // return value + isoYear: number + isoMonth: number + isoDay: number + isoHour: number + isoMinute: number + isoSecond: number + isoMillisecond: number +} { + const nudge = epochMilli < -milliInDay * maxDays ? 1 : epochMilli > milliInDay * maxDays ? -1 : 0 + const legacyDate = new Date(epochMilli + nudge * milliInDay) + + return zipProps(isoDateTimeFieldNamesAsc as any, [ + legacyDate.getUTCFullYear(), + legacyDate.getUTCMonth() + 1, + legacyDate.getUTCDate() - nudge, + legacyDate.getUTCHours(), + legacyDate.getUTCMinutes(), + legacyDate.getUTCSeconds(), + legacyDate.getUTCMilliseconds(), + ]) +} diff --git a/packages/temporal-polyfill/src/internal/errorMessages.ts b/packages/temporal-polyfill/src/internal/errorMessages.ts new file mode 100644 index 00000000..81f23f8e --- /dev/null +++ b/packages/temporal-polyfill/src/internal/errorMessages.ts @@ -0,0 +1,72 @@ + +// Low-Level +export const expectedInteger = (entityName: string, num: number) => `Non-integer ${entityName}: ${num}` +export const expectedPositive = (entityName: string, num: number) => `Non-positive ${entityName}: ${num}` +export const expectedFinite = (entityName: string, num: number) => `Non-finite ${entityName}: ${num}` +export const forbiddenBigIntToNumber = (entityName: string) => `Cannot convert bigint to ${entityName}` +export const invalidBigInt = (arg: any) => `Invalid bigint: ${arg}` +export const forbiddenSymbolToString = 'Cannot convert Symbol to string' +export const forbiddenNullish = 'Cannot be null or undefined' +export const expectedStringOrUndefined = 'Expected string or undefined' +export const expectedIntegerOrUndefined = 'Expected integer or undefined' +export const invalidObject = 'Invalid object' +export const numberOutOfRange = (entityName: string, val: number, min: number, max: number) => ( + `${entityName} ${val} must be between ${min}-${max}` +) + +// Entity/Fields/Bags +export const invalidEntity = (fieldName: string, val: any) => `Invalid ${fieldName}: ${val}` +export const missingField = (fieldName: string) => `Missing ${fieldName}` +export const forbiddenField = (fieldName: string) => `Invalid field ${fieldName}` +export const duplicateFields = (fieldName: string) => `Duplicate field ${fieldName}` +export const noValidFields = 'No valid fields' +export const invalidBag = 'Invalid bag' + +// Class-related +export const forbiddenValueOf = 'Cannot use valueOf' +export const invalidCallingContext = 'Invalid calling context' + +// Calendar Fields/Parts +export const forbiddenEraParts = 'Forbidden era/eraYear' +export const mismatchingEraParts = 'Mismatching era/eraYear' +export const mismatchingYearAndEra = 'Mismatching year/eraYear' +export const invalidEra = (era: string) => `Invalid era: ${era}` +export const missingYear = (allowEra: any) => `Missing year${allowEra ? '/era/eraYear' : ''}` +export const invalidMonthCode = (monthCode: string) => `Invalid monthCode: ${monthCode}` +export const mismatchingMonthAndCode = 'Mismatching month/monthCode' +export const missingMonth = 'Missing month/monthCode' +export const failedYearGuess = 'Cannot guess year' +export const invalidLeapMonth = 'Invalid leap month' + +// Calendar/TimeZone-PROTOCOL (very vague, I know, but rare) +export const invalidProtocol = 'Invalid protocol' +export const invalidProtocolResults = 'Invalid protocol results' + +// Calendar/TimeZone +export const invalidCalendar = (calendarId: string) => `Invalid Calendar: ${calendarId}` +export const mismatchingCalendars = 'Mismatching Calendars' +export const mismatchingTimeZones = 'Mismatching TimeZones' + +// TimeZone Offset +export const outOfBoundsOffset = 'Out-of-bounds offset' +export const invalidOffsetForTimeZone = 'Invalid offset for TimeZone' +export const ambigOffset = 'Ambiguous offset' + +// Date/Duration Math +export const outOfBoundsDate = 'Out-of-bounds date' +export const forbiddenDurationSigns = 'Cannot mix duration signs' +export const missingRelativeTo = 'Missing relativeTo' +export const invalidLargeUnits = 'Cannot use large units' // for Instant math + +// Options Refining +export const missingSmallestLargestUnit = 'Required smallestUnit or largestUnit' +export const flippedSmallestLargestUnit = 'smallestUnit > largestUnit' + +// Parsing +export const failedParse = (s: string) => `Cannot parse: ${s}` +export const invalidSubstring = (substring: string) => `Invalid substring: ${substring}` + +// Formatting +export const invalidFormatType = (branding: string) => `Cannot format ${branding}` +export const mismatchingFormatTypes = 'Mismatching types for formatting' +export const forbiddenFormatTimeZone = 'Forbidden timeZone' diff --git a/packages/temporal-polyfill/src/internal/formatIntl.ts b/packages/temporal-polyfill/src/internal/formatIntl.ts new file mode 100644 index 00000000..f3b7c4a2 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/formatIntl.ts @@ -0,0 +1,309 @@ +import { isoCalendarId } from './calendarConfig' +import { DayTimeNano } from './dayTimeNano' +import { IsoDateFields, IsoDateTimeFields, IsoTimeFields, isoTimeFieldDefaults } from './calendarIsoFields' +import { isoEpochOriginYear } from './calendarIso' +import { epochNanoToMilli } from './epochAndTime' +import { excludePropsByName, hasAnyPropsByName } from './utils' +import { getSingleInstantFor } from './timeZoneOps' +import { queryNativeTimeZone } from './timeZoneNative' +import { IdLike, getId } from './slots' +import * as errorMessages from './errorMessages' + +export type LocalesArg = string | string[] +export const OrigDateTimeFormat = Intl.DateTimeFormat + +/* +RULES: +DateTimeFormat always determines calendar and timeZone. If given date object conflicts, throw error. +However, for ZonedDateTimeFormat::toLocaleString, timeZone is forced by obj and can't be provided. +*/ + +// Options Transformers +// ------------------------------------------------------------------------------------------------- + +export type OptionNames = (keyof Intl.DateTimeFormatOptions)[] + +const numericStr = 'numeric' +const timeZoneNameStrs: OptionNames = ['timeZoneName'] + +// Fallbacks +// --------- + +const monthDayFallbacks: Intl.DateTimeFormatOptions = { month: numericStr, day: numericStr } +const yearMonthFallbacks: Intl.DateTimeFormatOptions = { year: numericStr, month: numericStr } +const dateFallbacks: Intl.DateTimeFormatOptions = { ...yearMonthFallbacks, day: numericStr } +const timeFallbacks: Intl.DateTimeFormatOptions = { + hour: numericStr, + minute: numericStr, + second: numericStr, +} +const dateTimeFallbacks: Intl.DateTimeFormatOptions = { ...dateFallbacks, ...timeFallbacks } +const zonedFallbacks: Intl.DateTimeFormatOptions = { ...dateTimeFallbacks, timeZoneName: 'short' } + +// Valid Names +// ----------- +// TODO: rename to 'standard'. sounds like others are invalid + +const monthDayValidNames = Object.keys(monthDayFallbacks) as OptionNames +const yearMonthValidNames = Object.keys(yearMonthFallbacks) as OptionNames +const dateValidNames: OptionNames = [ + ...(Object.keys(dateFallbacks) as OptionNames), + 'weekday', + 'dateStyle', +] +const timeValidNames: OptionNames = [ + ...(Object.keys(timeFallbacks) as OptionNames), + 'dayPeriod', + 'timeStyle', +] +const dateTimeValidNames: OptionNames = [...dateValidNames, ...timeValidNames] +const zonedValidNames: OptionNames = [...dateTimeValidNames, ...timeZoneNameStrs] + +// Exclusions +// ---------- + +const dateExclusions: OptionNames = [...timeZoneNameStrs, ...timeValidNames] +const timeExclusions: OptionNames = [...timeZoneNameStrs, ...dateValidNames] +const yearMonthExclusions: OptionNames = [ + ...timeZoneNameStrs, + 'day', + 'weekday', + 'dateStyle', + ...timeValidNames, +] +const monthDayExclusions: OptionNames = [ + ...timeZoneNameStrs, + 'year', + 'weekday', + 'dateStyle', + ...timeValidNames, +] + +// Transformer Funcs +// ----------------- + +/* +slots0 is only provided if doing toLocaleString/etc +slots1 is only provided if doing toLocaleString/etc AND formatting a range +*/ +export type OptionsTransformer = ( + options: Intl.DateTimeFormatOptions, + slots0?: S, + slots1?: S +) => Intl.DateTimeFormatOptions + +function createOptionsTransformer( + validNames: OptionNames, + fallbacks: Intl.DateTimeFormatOptions, + excludedNames: OptionNames = [], +): OptionsTransformer { + const excludedNameSet = new Set(excludedNames) + + return (options: Intl.DateTimeFormatOptions) => { + options = excludePropsByName(options, excludedNameSet) + + if (!hasAnyPropsByName(options, validNames)) { + Object.assign(options, fallbacks) + } + + return options + } +} + +const transformMonthDayOptions = createOptionsTransformer(monthDayValidNames, monthDayFallbacks, monthDayExclusions) +const transformYearMonthOptions = createOptionsTransformer(yearMonthValidNames, yearMonthFallbacks, yearMonthExclusions) +const transformDateOptions = createOptionsTransformer(dateValidNames, dateFallbacks, dateExclusions) +const transformDateTimeOptions = createOptionsTransformer(dateTimeValidNames, dateTimeFallbacks, timeZoneNameStrs) +const transformTimeOptions = createOptionsTransformer(timeValidNames, timeFallbacks, timeExclusions) +const transformEpochOptions = createOptionsTransformer(dateTimeValidNames, dateTimeFallbacks) // TOOD: rename to 'instant'? +const transformZonedEpochOptionsBasic = createOptionsTransformer(zonedValidNames, zonedFallbacks) + +// HACK: only ever called with toLocaleString/etc, so can assume slots0 +function transformZonedEpochOptions( + options: Intl.DateTimeFormatOptions, + slots0?: { timeZone: IdLike }, + slots1?: { timeZone: IdLike }, +): Intl.DateTimeFormatOptions { + options = transformZonedEpochOptionsBasic(options) + if (options.timeZone !== undefined) { + throw new TypeError(errorMessages.forbiddenFormatTimeZone) + } + options.timeZone = getCommonTimeZoneId(slots0!, slots1) + return options +} + +// Specific Epoch Nano Converters +// ------------------------------------------------------------------------------------------------- + +function isoDateFieldsToEpochNano( + isoFields: IsoDateTimeFields | IsoDateFields, + resolvedOptions: Intl.ResolvedDateTimeFormatOptions, +): DayTimeNano { + const timeZoneNative = queryNativeTimeZone(resolvedOptions.timeZone) + + return getSingleInstantFor(timeZoneNative, { + ...isoTimeFieldDefaults, + isoHour: 12, // for whole-day dates, will not dst-shift into prev/next day + ...isoFields, + }) +} + +function isoTimeFieldsToEpochNano( + internals: IsoTimeFields, + resolvedOptions: Intl.ResolvedDateTimeFormatOptions, +): DayTimeNano { + const timeZoneNative = queryNativeTimeZone(resolvedOptions.timeZone) + + return getSingleInstantFor(timeZoneNative, { + isoYear: isoEpochOriginYear, + isoMonth: 1, + isoDay: 1, + ...internals, + }) +} + +function extractEpochNano(slots: BasicInstantSlots): DayTimeNano { + return slots.epochNanoseconds +} + +// Configs +// ------------------------------------------------------------------------------------------------- + +export type ClassFormatConfig = [ + OptionsTransformer, + EpochNanoConverter, + boolean?, // strictCalendarChecks +] + +type EpochNanoConverter = ( + slots: S, + resolvedOptions: Intl.ResolvedDateTimeFormatOptions, +) => DayTimeNano + +// TODO: weird +export type BasicInstantSlots = { epochNanoseconds: DayTimeNano } +export type BasicZonedDateTimeSlots = { timeZone: IdLike, epochNanoseconds: DayTimeNano } + +export const plainYearMonthConfig: ClassFormatConfig = [transformYearMonthOptions, isoDateFieldsToEpochNano, true] +export const plainMonthDayConfig: ClassFormatConfig = [transformMonthDayOptions, isoDateFieldsToEpochNano, true] +export const plainDateConfig: ClassFormatConfig = [transformDateOptions, isoDateFieldsToEpochNano] +export const plainDateTimeConfig: ClassFormatConfig = [transformDateTimeOptions, isoDateFieldsToEpochNano] +export const plainTimeConfig: ClassFormatConfig = [transformTimeOptions, isoTimeFieldsToEpochNano] +export const instantConfig: ClassFormatConfig = [transformEpochOptions, extractEpochNano] +export const zonedDateTimeConfig: ClassFormatConfig = [transformZonedEpochOptions, extractEpochNano] + +const emptyOptions: Intl.DateTimeFormatOptions = {} // constant reference for caching + +export type FormatPrepper = ( + locales: LocalesArg | undefined, + options: Intl.DateTimeFormatOptions | undefined, + slots0: S, + slots1?: S, +) => [Intl.DateTimeFormat, number, number?] + +export type FormatQuerier = ( + locales: LocalesArg | undefined, + options: Intl.DateTimeFormatOptions, + transformOptions: OptionsTransformer, + slots0: S, + slots1?: S, +) => Intl.DateTimeFormat + +export function createFormatPrepper( + config: ClassFormatConfig, + queryFormat: FormatQuerier = createFormat, +): FormatPrepper { + const [transformOptions] = config + return (locales, options = emptyOptions, slots0, slots1) => { + const subformat = queryFormat(locales, options, transformOptions, slots0, slots1) + const resolvedOptions = subformat.resolvedOptions() + return [subformat, ...toEpochMillis(config, resolvedOptions, slots0, slots1)] + } +} + +export function createFormat( // TODO: better name b/c public + locales: LocalesArg | undefined, + options: Intl.DateTimeFormatOptions, + transformOptions: OptionsTransformer, + slots0: S, + slots1?: S, +): Intl.DateTimeFormat { + return new OrigDateTimeFormat(locales, transformOptions(options, slots0, slots1)) +} + +// General Epoch Conversion +// ------------------------------------------------------------------------------------------------- + +export function toEpochMillis( + config: ClassFormatConfig, + resolvedOptions: Intl.ResolvedDateTimeFormatOptions, + slots0: S, + slots1?: S, +): [number, number?] { + const epochMilli0 = toEpochMilli(config, resolvedOptions, slots0) + const epochMilli1 = slots1 !== undefined + ? toEpochMilli(config, resolvedOptions, slots1) + : undefined + + return [epochMilli0, epochMilli1] +} + +function toEpochMilli( + [, slotsToEpochNano, strictCalendarCheck]: ClassFormatConfig, + resolvedOptions: Intl.ResolvedDateTimeFormatOptions, + slots: S, +): number { + if ((slots as any).calendar) { + checkCalendarsCompatible( + getId((slots as any).calendar), + resolvedOptions.calendar, + strictCalendarCheck, + ) + } + + const epochNano = slotsToEpochNano(slots, resolvedOptions) + return epochNanoToMilli(epochNano) +} + +function checkCalendarsCompatible( + internalCalendarId: string, + resolveCalendarId: string, + strictCalendarCheck: boolean | undefined, +): void { + if ( + (strictCalendarCheck || internalCalendarId !== isoCalendarId) && + (internalCalendarId !== resolveCalendarId) + ) { + throw new RangeError(errorMessages.mismatchingCalendars) + } +} + +// Utils +// ------------------------------------------------------------------------------------------------- + +export const standardLocaleId = 'en-GB' // gives 24-hour clock + +export function hashIntlFormatParts( + intlFormat: Intl.DateTimeFormat, + epochMilliseconds: number, +): Record { + const parts = intlFormat.formatToParts(epochMilliseconds) + const hash = {} as Record + + for (const part of parts) { + hash[part.type] = part.value + } + + return hash +} + +export function getCommonTimeZoneId( + slots0: { timeZone: IdLike }, + slots1?: { timeZone: IdLike }, +): string { + const timeZoneId = getId(slots0!.timeZone) + if (slots1 && getId(slots1.timeZone) !== timeZoneId) { + throw new RangeError(errorMessages.mismatchingTimeZones) + } + return timeZoneId +} diff --git a/packages/temporal-polyfill/src/internal/formatIso.ts b/packages/temporal-polyfill/src/internal/formatIso.ts new file mode 100644 index 00000000..7dae2bc0 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/formatIso.ts @@ -0,0 +1,462 @@ +import { isoCalendarId } from './calendarConfig' +import { DayTimeNano, dayTimeNanoToNumberRemainder } from './dayTimeNano' +import { DurationFields, durationFieldNamesAsc } from './durationFields' +import { getLargestDurationUnit, negateDurationFields, queryDurationSign } from './durationMath' +import { IsoDateFields, IsoTimeFields, IsoDateTimeFields } from './calendarIsoFields' +import { epochNanoToIso } from './epochAndTime' +import { CalendarDisplay, OffsetDisplay, RoundingMode, SubsecDigits, TimeZoneDisplay } from './options' +import { balanceDayTimeDurationByInc, roundDateTimeToNano, roundDayTimeNanoByInc, roundTimeToNano, roundToMinute } from './round' +import { + givenFieldsToDayTimeNano, + nanoInHour, + nanoInMicro, + nanoInMilli, + nanoInMinute, + nanoInSec, + Unit, +} from './units' +import { divModFloor, padNumber, padNumber2 } from './utils' +import { SimpleTimeZoneOps } from './timeZoneOps' +import { DurationSlots, IdLike, InstantSlots, PlainDateSlots, PlainDateTimeSlots, PlainMonthDaySlots, PlainTimeSlots, PlainYearMonthSlots, ZonedDateTimeSlots, getId } from './slots' +import { DateTimeDisplayOptions, InstantDisplayOptions, TimeDisplayOptions, ZonedDateTimeDisplayOptions, refineDateDisplayOptions, refineDateTimeDisplayOptions, refineInstantDisplayOptions, refineTimeDisplayOptions, refineZonedDateTimeDisplayOptions } from './optionsRefine' +import { utcTimeZoneId } from './timeZoneNative' + +// High-level +// ------------------------------------------------------------------------------------------------- + +export function formatInstantIso( + refineTimeZoneArg: (timeZoneArg: TA) => T, + getTimeZoneOps: (timeSlotSlot: T) => SimpleTimeZoneOps, + instantSlots: InstantSlots, + options?: InstantDisplayOptions, +): string { + const [ + timeZoneArg, + roundingMode, + nanoInc, + subsecDigits, + ] = refineInstantDisplayOptions(options) + + const providedTimeZone = timeZoneArg !== undefined + const timeZoneOps = getTimeZoneOps( + providedTimeZone + ? refineTimeZoneArg(timeZoneArg) + : utcTimeZoneId as any, + ) + + return formatEpochNanoIso( + providedTimeZone, + timeZoneOps, + instantSlots.epochNanoseconds, + roundingMode, + nanoInc, + subsecDigits, + ) +} + +export function formatZonedDateTimeIso( + getTimeZoneOps: (timeZoneSlot: T) => SimpleTimeZoneOps, + zonedDateTimeSlots0: ZonedDateTimeSlots, + options?: ZonedDateTimeDisplayOptions, +): string { + return formatZonedEpochNanoIso( + getTimeZoneOps, + zonedDateTimeSlots0.calendar, + zonedDateTimeSlots0.timeZone, + zonedDateTimeSlots0.epochNanoseconds, + ...refineZonedDateTimeDisplayOptions(options), + ) +} + +export function formatPlainDateTimeIso( + plainDateTimeSlots0: PlainDateTimeSlots, + options?: DateTimeDisplayOptions, +): string { + return formatDateTimeIso(plainDateTimeSlots0.calendar, plainDateTimeSlots0, ...refineDateTimeDisplayOptions(options)) +} + +export function formatPlainDateIso( + plainDateSlots: PlainDateSlots, + options?: DateTimeDisplayOptions, +): string { + return formatDateIso(plainDateSlots.calendar, plainDateSlots, refineDateDisplayOptions(options)) +} + +export function formatPlainYearMonthIso( + plainYearMonthSlots: PlainYearMonthSlots, + options?: DateTimeDisplayOptions, +): string { + return formatDateLikeIso( + plainYearMonthSlots.calendar, + formatIsoYearMonthFields, + plainYearMonthSlots, + refineDateDisplayOptions(options), + ) +} + +export function formatPlainMonthDayIso( + plainMonthDaySlots: PlainMonthDaySlots, + options?: DateTimeDisplayOptions, +): string { + return formatDateLikeIso( + plainMonthDaySlots.calendar, + formatIsoMonthDayFields, + plainMonthDaySlots, + refineDateDisplayOptions(options), + ) +} + +export function formatPlainTimeIso( + slots: PlainTimeSlots, + options?: TimeDisplayOptions +): string { + return formatTimeIso(slots, ...refineTimeDisplayOptions(options)) +} + +export function formatDurationIso(slots: DurationSlots, options?: TimeDisplayOptions): string { + const [roundingMode, nanoInc, subsecDigits] = refineTimeDisplayOptions(options, Unit.Second) + + // for performance AND for not losing precision when no rounding + if (nanoInc > 1) { + slots = { + ...slots, + ...balanceDayTimeDurationByInc( + slots, + Math.min(getLargestDurationUnit(slots), Unit.Day), + nanoInc, + roundingMode, + ), + } + } + + return formatDurationFields( + slots, + subsecDigits as (SubsecDigits | undefined), // -1 won't happen (units can't be minutes) + ) +} + +// Medium-Level (receives refined options, also for formatDateLikeIso meta) +// ------------------------------------------------------------------------------------------------- + +function formatEpochNanoIso( + providedTimeZone: boolean, + timeZoneOps: SimpleTimeZoneOps, + epochNano: DayTimeNano, + roundingMode: RoundingMode, + nanoInc: number, + subsecDigits: SubsecDigits | -1 | undefined, +): string { + epochNano = roundDayTimeNanoByInc( + epochNano, + nanoInc, + roundingMode, + true, // useDayOrigin + ) + + let offsetNano = timeZoneOps.getOffsetNanosecondsFor(epochNano) + const isoFields = epochNanoToIso(epochNano, offsetNano) + + return formatIsoDateTimeFields(isoFields, subsecDigits) + + (providedTimeZone + ? formatOffsetNano(roundToMinute(offsetNano)) + : 'Z' + ) +} + +function formatZonedEpochNanoIso( + getTimeZoneOps: (timeZoneSlot: T) => SimpleTimeZoneOps, + calendarSlot: IdLike, + timeZoneSlot: T, + epochNano: DayTimeNano, + calendarDisplay: CalendarDisplay, + timeZoneDisplay: TimeZoneDisplay, + offsetDisplay: OffsetDisplay, + roundingMode: RoundingMode, + nanoInc: number, + subsecDigits: SubsecDigits | -1 | undefined, +): string { + epochNano = roundDayTimeNanoByInc(epochNano, nanoInc, roundingMode, true) + const timeZoneOps = getTimeZoneOps(timeZoneSlot) + const offsetNano = timeZoneOps.getOffsetNanosecondsFor(epochNano) + const isoFields = epochNanoToIso(epochNano, offsetNano) + + return formatIsoDateTimeFields(isoFields, subsecDigits) + + formatOffsetNano(roundToMinute(offsetNano), offsetDisplay) + + formatTimeZone(timeZoneSlot, timeZoneDisplay) + + formatCalendar(calendarSlot, calendarDisplay) +} + +function formatDateTimeIso( + calendarIdLike: IdLike, + isoFields: IsoDateTimeFields, + calendarDisplay: CalendarDisplay, + roundingMode: RoundingMode, + nanoInc: number, + subsecDigits: SubsecDigits | -1 | undefined, +): string { + const roundedIsoFields = roundDateTimeToNano(isoFields, nanoInc, roundingMode) + + return formatIsoDateTimeFields(roundedIsoFields, subsecDigits) + + formatCalendar(calendarIdLike, calendarDisplay) +} + +function formatDateIso( + calendarIdLike: IdLike, + isoFields: IsoDateFields, + calendarDisplay: CalendarDisplay, +): string { + return formatIsoDateFields(isoFields) + formatCalendar(calendarIdLike, calendarDisplay) +} + +function formatDateLikeIso( + calendarIdLike: IdLike, + formatSimple: (isoFields: IsoDateFields) => string, + isoFields: IsoDateFields, + calendarDisplay: CalendarDisplay, +) { + const calendarId = getId(calendarIdLike) + const showCalendar = + calendarDisplay > CalendarDisplay.Never || // critical or always + (calendarDisplay === CalendarDisplay.Auto && calendarId !== isoCalendarId) + + if (calendarDisplay === CalendarDisplay.Never) { + if (calendarId === isoCalendarId) { + return formatSimple(isoFields) + } else { + return formatIsoDateFields(isoFields) + } + } else if (showCalendar) { + return formatIsoDateFields(isoFields) + formatCalendarId(calendarId, calendarDisplay === CalendarDisplay.Critical) + } else { + return formatSimple(isoFields) + } +} + +function formatTimeIso( + fields: IsoTimeFields, + roundingMode: RoundingMode, + nanoInc: number, + subsecDigits: SubsecDigits | -1 | undefined, +): string { + return formatIsoTimeFields( + roundTimeToNano(fields, nanoInc, roundingMode)[0], + subsecDigits, + ) +} + +function formatDurationFields( + durationFields: DurationFields, // already balanced + subsecDigits: SubsecDigits | undefined, +): string { + const sign = queryDurationSign(durationFields) + const abs = sign === -1 ? negateDurationFields(durationFields): durationFields + const { hours, minutes } = abs + + const [wholeSeconds, subsecNano] = dayTimeNanoToNumberRemainder( + givenFieldsToDayTimeNano(abs, Unit.Second, durationFieldNamesAsc), + nanoInSec, + ) + + const subsecNanoString = formatSubsecNano(subsecNano, subsecDigits) + + const forceSeconds = + // a numeric subsecDigits specified? + // allow `undefined` in comparison - will evaluate to false + (subsecDigits as number) >= 0 || + // completely empty? display 'PT0S' + !sign || + subsecNanoString + + return (sign < 0 ? '-' : '') + 'P' + formatDurationFragments({ + 'Y': formatNumberUnscientific(abs.years), + 'M': formatNumberUnscientific(abs.months), + 'W': formatNumberUnscientific(abs.weeks), + 'D': formatNumberUnscientific(abs.days), + }) + ( + (hours || minutes || wholeSeconds || forceSeconds) + ? 'T' + formatDurationFragments({ + 'H': formatNumberUnscientific(hours), + 'M': formatNumberUnscientific(minutes), + 'S': formatNumberUnscientific(wholeSeconds, forceSeconds) + subsecNanoString + }) + : '' + ) +} + +/* +Values are guaranteed to be non-negative +*/ +function formatDurationFragments(fragObj: Record): string { + const parts = [] + + for (const fragName in fragObj) { + const fragVal = fragObj[fragName] + if (fragVal) { + parts.push(fragVal, fragName) + } + } + + return parts.join('') +} + +// Low-Level (Rounding already happened. Just fields) +// ------------------------------------------------------------------------------------------------- + +function formatIsoDateTimeFields( + isoDateTimeFields: IsoDateTimeFields, + subsecDigits: SubsecDigits | -1 | undefined, +) { + return formatIsoDateFields(isoDateTimeFields) + + 'T' + formatIsoTimeFields(isoDateTimeFields, subsecDigits) +} + +function formatIsoDateFields(isoDateFields: IsoDateFields): string { + return formatIsoYearMonthFields(isoDateFields) + '-' + padNumber2(isoDateFields.isoDay) +} + +function formatIsoYearMonthFields(isoDateFields: IsoDateFields): string { + const { isoYear } = isoDateFields + return ( + (isoYear < 0 || isoYear > 9999) + ? getSignStr(isoYear) + padNumber(6, Math.abs(isoYear)) + : padNumber(4, isoYear) + ) + '-' + padNumber2(isoDateFields.isoMonth) +} + +function formatIsoMonthDayFields(isoDateFields: IsoDateFields): string { + return padNumber2(isoDateFields.isoMonth) + '-' + padNumber2(isoDateFields.isoDay) +} + +function formatIsoTimeFields( + isoTimeFields: IsoTimeFields, + subsecDigits: SubsecDigits | -1 | undefined, +): string { + const parts = [ + padNumber2(isoTimeFields.isoHour), + padNumber2(isoTimeFields.isoMinute), + ] + + if (subsecDigits !== -1) { // show seconds? + parts.push( + padNumber2(isoTimeFields.isoSecond) + + formatSubsec( + isoTimeFields.isoMillisecond, + isoTimeFields.isoMicrosecond, + isoTimeFields.isoNanosecond, + subsecDigits, + ), + ) + } + + return parts.join(':') +} + +export function formatOffsetNano( + offsetNano: number, + offsetDisplay: OffsetDisplay = OffsetDisplay.Auto, +): string { + if (offsetDisplay === OffsetDisplay.Never) { + return '' + } + + const [hour, nanoRemainder0] = divModFloor(Math.abs(offsetNano), nanoInHour) + const [minute, nanoRemainder1] = divModFloor(nanoRemainder0, nanoInMinute) + const [second, nanoRemainder2] = divModFloor(nanoRemainder1, nanoInSec) + + return getSignStr(offsetNano) + + padNumber2(hour) + ':' + + padNumber2(minute) + + ((second || nanoRemainder2) + ? ':' + padNumber2(second) + formatSubsecNano(nanoRemainder2) + : '') +} + +// TimeZone / Calendar +// ------------------------------------------------------------------------------------------------- + +function formatTimeZone( + timeZoneNative: IdLike, + timeZoneDisplay: TimeZoneDisplay, +): string { + if (timeZoneDisplay !== TimeZoneDisplay.Never) { + return '[' + + (timeZoneDisplay === TimeZoneDisplay.Critical ? '!' : '') + + getId(timeZoneNative) + + ']' + } + return '' +} + +function formatCalendar( + calendarIdLike: IdLike, + calendarDisplay: CalendarDisplay, +): string { + if (calendarDisplay !== CalendarDisplay.Never) { + const calendarId = getId(calendarIdLike) + + if ( + calendarDisplay > CalendarDisplay.Never || // critical or always + (calendarDisplay === CalendarDisplay.Auto && calendarId !== isoCalendarId) + ) { + return formatCalendarId(calendarId, calendarDisplay === CalendarDisplay.Critical) + } + } + + return '' +} + +function formatCalendarId(calendarId: string, isCritical: boolean): string { + return '[' + + (isCritical ? '!' : '') + + 'u-ca=' + calendarId + + ']' +} + +// Utils +// ------------------------------------------------------------------------------------------------- + +function formatSubsec( + isoMillisecond: number, + isoMicrosecond: number, + isoNanosecond: number, + subsecDigits: SubsecDigits | undefined, +): string { + return formatSubsecNano( + isoMillisecond * nanoInMilli + + isoMicrosecond * nanoInMicro + + isoNanosecond, + subsecDigits, + ) +} + +const trailingZerosRE = /0+$/ + +function formatSubsecNano( + totalNano: number, + subsecDigits?: SubsecDigits, +): string { + let s = padNumber(9, totalNano) + + s = subsecDigits === undefined + ? s.replace(trailingZerosRE, '') + : s.slice(0, subsecDigits) + + return s ? '.' + s : '' +} + +function getSignStr(num: number): string { + return num < 0 ? '-' : '+' +} + +/* +Only good at non-negative numbers, because of HACK +*/ +function formatNumberUnscientific(n: number, force?: any): string { + if (!n && !force) { + return '' // TODO: rename this whole func + } + + // avoid outputting scientific notation + // https://stackoverflow.com/a/50978675/96342 + return n.toLocaleString('fullwide', { useGrouping: false }) +} diff --git a/packages/temporal-polyfill/src/internal/mod.ts b/packages/temporal-polyfill/src/internal/mod.ts new file mode 100644 index 00000000..36043e07 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/mod.ts @@ -0,0 +1,102 @@ +import { IsoTimeFields, isoTimeFieldDefaults } from './calendarIsoFields' +import { OffsetDisambig } from './options' +import { IdLike, PlainDateSlots, PlainDateTimeSlots, ZonedDateTimeSlots, createPlainDateTimeSlots, createZonedDateTimeSlots, getPreferredCalendarSlot } from './slots' +import { TimeZoneOps, getMatchingInstantFor, zonedInternalsToIso } from './timeZoneOps' + +// ZonedDateTime with * +// ------------------------------------------------------------------------------------------------- + +export function zonedDateTimeWithPlainTime( + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + zonedDateTimeSlots: ZonedDateTimeSlots, + plainTimeSlots: IsoTimeFields = isoTimeFieldDefaults, +): ZonedDateTimeSlots { + const timeZoneSlot = zonedDateTimeSlots.timeZone + const timeZoneOps = getTimeZoneOps(timeZoneSlot) + + const isoFields = { + ...zonedInternalsToIso(zonedDateTimeSlots as any, timeZoneOps), + ...plainTimeSlots, + } + + const epochNano = getMatchingInstantFor( + timeZoneOps, + isoFields, + isoFields.offsetNanoseconds, + OffsetDisambig.Prefer, // OffsetDisambig + ) + + return createZonedDateTimeSlots( + epochNano, + timeZoneSlot, + zonedDateTimeSlots.calendar, + ) +} + +export function zonedDateTimeWithPlainDate( + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + zonedDateTimeSlots: ZonedDateTimeSlots, + plainDateSlots: PlainDateSlots, +): ZonedDateTimeSlots { + const timeZoneSlot = zonedDateTimeSlots.timeZone + const timeZoneOps = getTimeZoneOps(timeZoneSlot) + + const isoFields = { + ...zonedInternalsToIso(zonedDateTimeSlots as any, timeZoneOps), + ...plainDateSlots, + } + const calendar = getPreferredCalendarSlot(zonedDateTimeSlots.calendar, plainDateSlots.calendar) + + const epochNano = getMatchingInstantFor( + timeZoneOps, + isoFields, + isoFields.offsetNanoseconds, + OffsetDisambig.Prefer, // OffsetDisambig + ) + + return createZonedDateTimeSlots( + epochNano, + timeZoneSlot, + calendar, + ) +} + +// PlainDateTime with * +// ------------------------------------------------------------------------------------------------- + +export function plainDateTimeWithPlainTime( + plainDateTimeSlots: PlainDateTimeSlots, + plainTimeSlots: IsoTimeFields = isoTimeFieldDefaults, +): PlainDateTimeSlots { + return createPlainDateTimeSlots({ + ...plainDateTimeSlots, + ...plainTimeSlots, + }) +} + +export function plainDateTimeWithPlainDate( + plainDateTimeSlots: PlainDateTimeSlots, + plainDateSlots: PlainDateSlots, +) { + return createPlainDateTimeSlots({ + ...plainDateTimeSlots, + ...plainDateSlots, + }, getPreferredCalendarSlot(plainDateTimeSlots.calendar, plainDateSlots.calendar)) +} + +// Anything with calendar/timeZone +// ------------------------------------------------------------------------------------------------- + +export function slotsWithCalendar( + slots: S, + calendarSlot: C, +): S { + return { ...slots, calendar: calendarSlot } +} + +export function slotsWithTimeZone( + slots: S, + timeZoneSlot: T, +): S { + return { ...slots, timeZone: timeZoneSlot } +} diff --git a/packages/temporal-polyfill/src/internal/move.ts b/packages/temporal-polyfill/src/internal/move.ts new file mode 100644 index 00000000..1bc9a5c4 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/move.ts @@ -0,0 +1,377 @@ +import { DayTimeNano, addDayTimeNanos } from './dayTimeNano' +import { + DurationFields, + durationFieldNamesAsc, + durationTimeFieldDefaults, + durationFieldDefaults, +} from './durationFields' +import { durationFieldsToDayTimeNano, durationHasDateParts, durationTimeFieldsToLargeNanoStrict, negateDuration, negateDurationFields, queryDurationSign } from './durationMath' +import { IsoDateTimeFields, IsoDateFields, IsoTimeFields, isoTimeFieldNamesAsc } from './calendarIsoFields' +import { + isoDaysInWeek, + isoMonthsInYear, +} from './calendarIso' +import { + checkEpochNanoInBounds, + checkIsoDateInBounds, + checkIsoDateTimeInBounds, + epochMilliToIso, isoTimeFieldsToNano, + isoToEpochMilli, + nanoToIsoTimeAndDay +} from './epochAndTime' +import { TimeZoneOps, getSingleInstantFor, zonedEpochNanoToIso } from './timeZoneOps' +import { Unit, givenFieldsToDayTimeNano, milliInDay } from './units' +import { clampEntity, divTrunc, modTrunc, pluckProps } from './utils' +import { isoCalendarId } from './calendarConfig' +import { NativeMoveOps, YearMonthParts, monthCodeNumberToMonth } from './calendarNative' +import { IntlCalendar, computeIntlMonthsInYear } from './calendarIntl' +import { DayOp, MoveOps, YearMonthMoveOps } from './calendarOps' +import { OverflowOptions, refineOverflowOptions } from './optionsRefine' +import { DurationSlots, InstantBranding, InstantSlots, PlainDateSlots, PlainDateTimeBranding, PlainDateTimeSlots, PlainTimeBranding, PlainTimeSlots, PlainYearMonthBranding, PlainYearMonthSlots, ZonedDateTimeSlots, createInstantSlots, createPlainDateTimeSlots, createPlainTimeSlots, createPlainYearMonthSlots } from './slots' +import * as errorMessages from './errorMessages' + +// High-Level +// ------------------------------------------------------------------------------------------------- + +export function moveInstant( + instantSlots: InstantSlots, + durationSlots: DurationSlots, + doSubtract?: boolean, +): InstantSlots { + return createInstantSlots( + moveEpochNano( + instantSlots.epochNanoseconds, + doSubtract ? negateDurationFields(durationSlots) : durationSlots, + ), + ) +} + +export function moveZonedDateTime( + getCalendarOps: (calendarSlot: C) => MoveOps, + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + zonedDateTimeSlots: ZonedDateTimeSlots, + durationSlots: DurationSlots, + options: OverflowOptions = Object.create(null), // so internal Calendar knows options *could* have been passed in + doSubtract?: boolean, +): ZonedDateTimeSlots { + // correct calling order. switch moveZonedEpochNano arg order? + const timeZoneOps = getTimeZoneOps(zonedDateTimeSlots.timeZone) + const calendarOps = getCalendarOps(zonedDateTimeSlots.calendar) + + const movedEpochNanoseconds = moveZonedEpochNano( + calendarOps, + timeZoneOps, + zonedDateTimeSlots.epochNanoseconds, + doSubtract ? negateDurationFields(durationSlots) : durationSlots, + options, + ) + + return { + ...zonedDateTimeSlots, + epochNanoseconds: movedEpochNanoseconds, + } +} + +export function movePlainDateTime( + getCalendarOps: (calendarSlot: C) => MoveOps, + plainDateTimeSlots: PlainDateTimeSlots, + durationSlots: DurationSlots, + options: OverflowOptions = Object.create(null), // so internal Calendar knows options *could* have been passed in + doSubtract?: boolean, +): PlainDateTimeSlots { + return createPlainDateTimeSlots({ + ...plainDateTimeSlots, + ...moveDateTime( + getCalendarOps(plainDateTimeSlots.calendar), + plainDateTimeSlots, + doSubtract ? negateDurationFields(durationSlots) : durationSlots, + options, + ), + }) +} + +export function movePlainDate( + getCalendarOps: (calendarSlot: C) => MoveOps, + plainDateSlots: PlainDateSlots, + durationSlots: DurationSlots, + options?: OverflowOptions, + doSubtract?: boolean, +): PlainDateSlots { + return { + ...plainDateSlots, + ...moveDateEfficient( + getCalendarOps(plainDateSlots.calendar), + plainDateSlots, + doSubtract ? negateDurationFields(durationSlots) : durationSlots, + options, + ) + } +} + +export function movePlainYearMonth( + getCalendarOps: (calendar: C) => YearMonthMoveOps, + plainYearMonthSlots: PlainYearMonthSlots, + durationFields: DurationSlots, + options: OverflowOptions = Object.create(null), // b/c CalendarProtocol likes empty object, + doSubtract?: boolean, +): PlainYearMonthSlots { + const calendarSlot = plainYearMonthSlots.calendar + const calendarOps = getCalendarOps(calendarSlot) + let isoDateFields = moveToMonthStart(calendarOps, plainYearMonthSlots) + + if (doSubtract) { + durationFields = negateDuration(durationFields) + } + + // if moving backwards in time, set to last day of month + if (queryDurationSign(durationFields) < 0) { + isoDateFields = calendarOps.dateAdd(isoDateFields, { ...durationFieldDefaults, months: 1 }) + isoDateFields = moveByIsoDays(isoDateFields, -1) + } + + const movedIsoDateFields = calendarOps.dateAdd( + isoDateFields, + durationFields, + options, + ) + + return createPlainYearMonthSlots( + moveToMonthStart(calendarOps, movedIsoDateFields), + calendarSlot, + ) +} + +export function movePlainTime( + slots: PlainTimeSlots, + durationSlots: DurationFields, + doSubtract?: boolean, +): PlainTimeSlots { + return createPlainTimeSlots( + moveTime(slots, doSubtract ? negateDurationFields(durationSlots) : durationSlots)[0], + ) +} + +// Low-Level +// ------------------------------------------------------------------------------------------------- + +function moveEpochNano(epochNano: DayTimeNano, durationFields: DurationFields): DayTimeNano { + return checkEpochNanoInBounds( + addDayTimeNanos( + epochNano, + durationTimeFieldsToLargeNanoStrict(durationFields), + ), + ) +} + +export function moveZonedEpochNano( + calendarOps: MoveOps, + timeZoneOps: TimeZoneOps, + epochNano: DayTimeNano, + durationFields: DurationFields, + options?: OverflowOptions, +): DayTimeNano { + const dayTimeNano = durationFieldsToDayTimeNano(durationFields, Unit.Hour) // better name: timed nano + + if (!durationHasDateParts(durationFields)) { + epochNano = addDayTimeNanos(epochNano, dayTimeNano) + refineOverflowOptions(options) // for validation only + } else { + const isoDateTimeFields = zonedEpochNanoToIso(timeZoneOps, epochNano) + const movedIsoDateFields = moveDateEfficient( + calendarOps, + isoDateTimeFields, + { + ...durationFields, // date parts + ...durationTimeFieldDefaults, // time parts + }, + options, + ) + const movedIsoDateTimeFields = { + ...movedIsoDateFields, // date parts (could be a superset) + ...pluckProps(isoTimeFieldNamesAsc, isoDateTimeFields), // time parts + calendar: isoCalendarId, // NOT USED but whatever + } + epochNano = addDayTimeNanos( + getSingleInstantFor(timeZoneOps, movedIsoDateTimeFields), + dayTimeNano + ) + } + + return checkEpochNanoInBounds(epochNano) +} + +export function moveDateTime( + calendarOps: MoveOps, + isoDateTimeFields: IsoDateTimeFields, + durationFields: DurationFields, + options?: OverflowOptions, +): IsoDateTimeFields { + // could have over 24 hours in certain zones + const [movedIsoTimeFields, dayDelta] = moveTime(isoDateTimeFields, durationFields) + + const movedIsoDateFields = moveDateEfficient( + calendarOps, + isoDateTimeFields, // only date parts will be used + { + ...durationFields, // date parts + ...durationTimeFieldDefaults, // time parts (zero-out so no balancing-up to days) + days: durationFields.days + dayDelta, + }, + options, + ) + + return checkIsoDateTimeInBounds({ + ...movedIsoDateFields, + ...movedIsoTimeFields, + }) +} + +/* +Skips calendar if moving days only +*/ +function moveDateEfficient( + calendarOps: MoveOps, + isoDateFields: IsoDateFields, + durationFields: DurationFields, + options?: OverflowOptions, +): IsoDateFields { + if (durationFields.years || durationFields.months || durationFields.weeks) { + return calendarOps.dateAdd( + isoDateFields, + durationFields, + options + ) + } + + refineOverflowOptions(options) // for validation only + + const days = durationFields.days + givenFieldsToDayTimeNano(durationFields, Unit.Hour, durationFieldNamesAsc)[0] + if (days) { + return checkIsoDateInBounds(moveByIsoDays(isoDateFields, days)) + } + + return isoDateFields +} + +export function moveToMonthStart( + calendarOps: { day: DayOp }, + isoFields: IsoDateFields, +): IsoDateFields { + return moveByIsoDays(isoFields, 1 - calendarOps.day(isoFields)) +} + +function moveTime( + isoFields: IsoTimeFields, + durationFields: DurationFields, +): [IsoTimeFields, number] { + const [durDays, durTimeNano] = givenFieldsToDayTimeNano(durationFields, Unit.Hour, durationFieldNamesAsc) + const [newIsoFields, overflowDays] = nanoToIsoTimeAndDay(isoTimeFieldsToNano(isoFields) + durTimeNano) + + return [ + newIsoFields, + durDays + overflowDays, + ] +} + +// Native +// ------------------------------------------------------------------------------------------------- + +export function nativeDateAdd( + this: NativeMoveOps, + isoDateFields: IsoDateFields, + durationFields: DurationFields, + options?: OverflowOptions, +): IsoDateFields { + const overflow = refineOverflowOptions(options) + let { years, months, weeks, days } = durationFields + let epochMilli: number | undefined + + // convert time fields to days + days += givenFieldsToDayTimeNano(durationFields, Unit.Hour, durationFieldNamesAsc)[0] + + if (years || months) { + let [year, month, day] = this.dateParts(isoDateFields) + + if (years) { + const [monthCodeNumber, isLeapMonth] = this.monthCodeParts(year, month) + year += years + month = monthCodeNumberToMonth(monthCodeNumber, isLeapMonth, this.leapMonth(year)) + month = clampEntity('month', month, 1, this.monthsInYearPart(year), overflow) + } + + if (months) { + ([year, month] = this.monthAdd(year, month, months)) + } + + day = clampEntity('day', day, 1, this.daysInMonthParts(year, month), overflow) + + epochMilli = this.epochMilli(year, month, day) + } else if (weeks || days) { + epochMilli = isoToEpochMilli(isoDateFields) + } else { + return isoDateFields + } + + epochMilli! += (weeks * isoDaysInWeek + days) * milliInDay + + return checkIsoDateInBounds(epochMilliToIso(epochMilli!)) +} + +// ISO / Intl Utils +// ------------------------------------------------------------------------------------------------- + +export function isoMonthAdd(year: number, month: number, monthDelta: number): YearMonthParts { + year += divTrunc(monthDelta, isoMonthsInYear) + month += modTrunc(monthDelta, isoMonthsInYear) + + if (month < 1) { + year-- + month += isoMonthsInYear + } else if (month > isoMonthsInYear) { + year++ + month -= isoMonthsInYear + } + + return [year, month] +} + +export function intlMonthAdd( + this: IntlCalendar, + year: number, + month: number, + monthDelta: number, +): YearMonthParts { + if (monthDelta) { + month += monthDelta + + if (monthDelta < 0) { + if (month < Number.MIN_SAFE_INTEGER) { + throw new RangeError(errorMessages.outOfBoundsDate) + } + while (month < 1) { + month += computeIntlMonthsInYear.call(this, --year) + } + } else { + if (month > Number.MAX_SAFE_INTEGER) { + throw new RangeError(errorMessages.outOfBoundsDate) + } + let monthsInYear + while (month > (monthsInYear = computeIntlMonthsInYear.call(this, year))) { + month -= monthsInYear + year++ + } + } + } + + return [year, month] +} + +export function moveByIsoDays( + isoDateFields: IsoDateFields, + days: number, +): IsoDateFields { + if (days) { + isoDateFields = epochMilliToIso(isoToEpochMilli(isoDateFields)! + days * milliInDay) + } + return isoDateFields +} diff --git a/packages/temporal-polyfill/src/internal/options.ts b/packages/temporal-polyfill/src/internal/options.ts new file mode 100644 index 00000000..c12442df --- /dev/null +++ b/packages/temporal-polyfill/src/internal/options.ts @@ -0,0 +1,72 @@ +import { roundExpand, roundHalfCeil, roundHalfEven, roundHalfExpand, roundHalfFloor, roundHalfTrunc } from './utils' + +export const enum Overflow { + Constrain, + Reject +} + +export const enum EpochDisambig { + Compat, + Reject, + Earlier, + Later +} + +export const enum OffsetDisambig { + Reject, + Use, + Prefer, + Ignore +} + +export const enum CalendarDisplay { + Auto, + Never, + Critical, + Always +} + +export const enum TimeZoneDisplay { + Auto, + Never, + Critical +} + +export const enum OffsetDisplay { + Auto, + Never +} + +export const enum RoundingMode { + // modes that get inverted (see invertRoundingMode) + Floor, + HalfFloor, + Ceil, + HalfCeil, + // other modes + Trunc, + HalfTrunc, + Expand, + HalfExpand, + HalfEven +} + +export const roundingModeFuncs = [ + Math.floor, + roundHalfFloor, + Math.ceil, + roundHalfCeil, + Math.trunc, + roundHalfTrunc, + roundExpand, + roundHalfExpand, + roundHalfEven, +] + +export type SubsecDigits = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 + +/* +common SubsecDigits addons: + -1 means hide seconds + undefined means 'auto' (display all digits but no trailing zeros) +*/ diff --git a/packages/temporal-polyfill/src/internal/optionsRefine.ts b/packages/temporal-polyfill/src/internal/optionsRefine.ts new file mode 100644 index 00000000..e81888d5 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/optionsRefine.ts @@ -0,0 +1,615 @@ +import { DurationFields, durationFieldIndexes } from './durationFields' +import { toString, requireObjectlike, toInteger, requirePropDefined } from './cast' +import { DayTimeUnit, TimeUnit, Unit, UnitName, nanoInUtcDay, unitNameMap, unitNanoMap } from './units' +import { BoundArg, bindArgs, clampEntity, isObjectLike } from './utils' +import { Overflow, OffsetDisambig, EpochDisambig, RoundingMode, CalendarDisplay, TimeZoneDisplay, OffsetDisplay, SubsecDigits } from './options' +import * as errorMessages from './errorMessages' + +// Types +// ------------------------------------------------------------------------------------------------- + +export type ZonedFieldOptions = OverflowOptions & EpochDisambigOptions & OffsetDisambigOptions + +export type ZonedFieldTuple = [ + Overflow, + OffsetDisambig, + EpochDisambig, +] + +export type DiffOptions = LargestUnitOptions & SmallestUnitOptions & RoundingIncOptions & RoundingModeOptions + +export type DiffTuple = [ + Unit, // largestUnit + Unit, // smallestUnit + number, // roundingInc + RoundingMode +] + +export type RoundTuple = [ + Unit, // smallestUnit + number, + RoundingMode, +] + +export type RoundingOptions = SmallestUnitOptions & RoundingIncOptions & RoundingModeOptions + +export type DurationRoundOptions = DiffOptions & RelativeToOptions + +export type DurationRoundTuple = [...DiffTuple, R] + +export type TimeDisplayTuple = [ + roundingMode: RoundingMode, + nanoInc: number, + subsecDigits: SubsecDigits | -1 | undefined // TODO: change -1 to null? +] + +// TODO: lock-down subset of Unit here? +export type TimeDisplayOptions = SmallestUnitOptions & RoundingModeOptions & SubsecDigitsOptions + +export type ZonedDateTimeDisplayOptions = + & CalendarDisplayOptions + & TimeZoneDisplayOptions + & OffsetDisplayOptions + & TimeDisplayOptions + +export type ZonedDateTimeDisplayTuple = [ + CalendarDisplay, + TimeZoneDisplay, + OffsetDisplay, + ...TimeDisplayTuple, +] + +export type RelativeToOptions = { relativeTo: RA } +export type TotalUnitOptionsWithRel = TotalUnitOptions & RelativeToOptions + +export type DateTimeDisplayOptions = CalendarDisplayOptions & TimeDisplayOptions + +export type DateTimeDisplayTuple = [ + CalendarDisplay, + ...TimeDisplayTuple, +] + +interface SmallestUnitOptions { + smallestUnit?: UnitName | keyof DurationFields +} + +// TODO: rename to CalendarDiffOptions? +export interface LargestUnitOptions { + largestUnit?: UnitName | keyof DurationFields +} + +interface TotalUnitOptions { + unit: UnitName | keyof DurationFields +} + +export type InstantDisplayOptions = { timeZone: TA } & TimeDisplayOptions + +export type InstantDisplayTuple = [TA, ...TimeDisplayTuple] + +export interface OverflowOptions { + overflow?: keyof typeof overflowMap +} + +export interface EpochDisambigOptions { + disambiguation?: keyof typeof epochDisambigMap +} + +export interface OffsetDisambigOptions { + offset?: keyof typeof offsetDisambigMap +} + +export interface CalendarDisplayOptions { + calendarName?: keyof typeof calendarDisplayMap +} + +export interface TimeZoneDisplayOptions { + timeZoneName?: keyof typeof timeZoneDisplayMap +} + +export interface OffsetDisplayOptions { + offset?: keyof typeof offsetDisplayMap +} + +export interface RoundingModeOptions { + roundingMode?: keyof typeof roundingModeMap +} + +export interface RoundingIncOptions { + roundingIncrement?: number +} + +export interface SubsecDigitsOptions { + fractionalSecondDigits?: SubsecDigits +} + +// Config +// ------------------------------------------------------------------------------------------------- + +// TODO: rename to 'label'? +const smallestUnitStr = 'smallestUnit' +const largestUnitStr = 'largestUnit' +const totalUnitStr = 'unit' +const roundingIncName = 'roundingIncrement' +const subsecDigitsName = 'fractionalSecondDigits' +const relativeToName = 'relativeTo' + +const overflowMap = { + constrain: Overflow.Constrain, + reject: Overflow.Reject, +} + +export const overflowMapNames = Object.keys(overflowMap) as (keyof typeof overflowMap)[] + +const epochDisambigMap = { + compatible: EpochDisambig.Compat, + reject: EpochDisambig.Reject, + earlier: EpochDisambig.Earlier, + later: EpochDisambig.Later, +} + +const offsetDisambigMap = { + reject: OffsetDisambig.Reject, + use: OffsetDisambig.Use, + prefer: OffsetDisambig.Prefer, + ignore: OffsetDisambig.Ignore, +} + +const calendarDisplayMap = { + auto: CalendarDisplay.Auto, + never: CalendarDisplay.Never, + critical: CalendarDisplay.Critical, + always: CalendarDisplay.Always, +} + +const timeZoneDisplayMap = { + auto: TimeZoneDisplay.Auto, + never: TimeZoneDisplay.Never, + critical: TimeZoneDisplay.Critical, +} + +const offsetDisplayMap = { + auto: OffsetDisplay.Auto, + never: OffsetDisplay.Never, +} + +const roundingModeMap = { + floor: RoundingMode.Floor, + halfFloor: RoundingMode.HalfFloor, + ceil: RoundingMode.Ceil, + halfCeil: RoundingMode.HalfCeil, + trunc: RoundingMode.Trunc, + halfTrunc: RoundingMode.HalfTrunc, + expand: RoundingMode.Expand, + halfExpand: RoundingMode.HalfExpand, + halfEven: RoundingMode.HalfEven, +} + +// Compound Options +// ------------------------------------------------------------------------------------------------- + +export function refineOverflowOptions( + options: OverflowOptions | undefined, +): Overflow { + return options === undefined ? Overflow.Constrain : refineOverflow(requireObjectlike(options)) +} + +export function refineZonedFieldOptions( + options: ZonedFieldOptions | undefined, + defaultOffsetDisambig: OffsetDisambig = OffsetDisambig.Reject, +): ZonedFieldTuple { + options = normalizeOptions(options) + + // alphabetical + const epochDisambig = refineEpochDisambig(options) // "disambig" + const offsetDisambig = refineOffsetDisambig(options, defaultOffsetDisambig) // "offset" + const overflow = refineOverflow(options) // "overflow" + + return [ + overflow, + offsetDisambig, + epochDisambig, + ] +} + +export function refineEpochDisambigOptions(options: EpochDisambigOptions | undefined): EpochDisambig { + return refineEpochDisambig(normalizeOptions(options)) +} + +/* +For simple Calendar class diffing (only y/m/w/d units) +*/ +export function refineCalendarDiffOptions( + options: LargestUnitOptions | undefined, // TODO: definitely make large-unit type via generics +): Unit { // TODO: only year/month/week/day? + options = normalizeOptions(options) + return refineLargestUnit(options, Unit.Year, Unit.Day, true)! +} + +export function refineDiffOptions( + roundingModeInvert: boolean | undefined, + options: DiffOptions | undefined, + defaultLargestUnit: Unit, + maxUnit = Unit.Year, + minUnit = Unit.Nanosecond, + defaultRoundingMode: RoundingMode = RoundingMode.Trunc, +): DiffTuple { + options = normalizeOptions(options) + + let largestUnit = refineLargestUnit(options, maxUnit, minUnit) + let roundingInc = parseRoundingIncInteger(options) + let roundingMode = refineRoundingMode(options, defaultRoundingMode) + let smallestUnit = refineSmallestUnit(options, maxUnit, minUnit, true)! + + if (largestUnit == null) { + largestUnit = Math.max(defaultLargestUnit, smallestUnit) + } else { + checkLargestSmallestUnit(largestUnit, smallestUnit) + } + + roundingInc = refineRoundingInc(roundingInc, smallestUnit as DayTimeUnit, true) + + if (roundingModeInvert) { + roundingMode = invertRoundingMode(roundingMode) + } + + return [largestUnit, smallestUnit, roundingInc, roundingMode] +} + +export function refineDurationRoundOptions( + options: DurationRoundOptions, + defaultLargestUnit: Unit, + refineRelativeTo: (relativeTo: RA) => R, +): DurationRoundTuple { + options = normalizeUnitNameOptions(options, smallestUnitStr) + + // alphabetcal + let largestUnit = refineLargestUnit(options) + const relativeToInternals = refineRelativeTo(options[relativeToName]) + let roundingInc = parseRoundingIncInteger(options) + const roundingMode = refineRoundingMode(options, RoundingMode.HalfExpand) + let smallestUnit = refineSmallestUnit(options) + + if (largestUnit === undefined && smallestUnit === undefined) { + throw new RangeError(errorMessages.missingSmallestLargestUnit) + } + + if (smallestUnit == null) { + smallestUnit = Unit.Nanosecond + } + if (largestUnit == null) { + largestUnit = Math.max(smallestUnit, defaultLargestUnit) + } + + checkLargestSmallestUnit(largestUnit, smallestUnit) + roundingInc = refineRoundingInc(roundingInc, smallestUnit as DayTimeUnit, true) + + return [largestUnit, smallestUnit, roundingInc, roundingMode, relativeToInternals] +} + +/* +Always related to time +*/ +export function refineRoundOptions( + options: RoundingOptions | UnitName, + maxUnit: DayTimeUnit = Unit.Day, + solarMode?: boolean, +): RoundTuple { + options = normalizeUnitNameOptions(options, smallestUnitStr) + + // alphabetical + let roundingInc = parseRoundingIncInteger(options) + const roundingMode = refineRoundingMode(options, RoundingMode.HalfExpand) + let smallestUnit = refineSmallestUnit(options, maxUnit) + + smallestUnit = requirePropDefined(smallestUnitStr, smallestUnit) + roundingInc = refineRoundingInc(roundingInc, smallestUnit as DayTimeUnit, undefined, solarMode) + + return [smallestUnit, roundingInc, roundingMode] +} + +export function refineTotalOptions( + options: TotalUnitOptionsWithRel | UnitName, + refineRelativeTo: (relativeTo: RA) => R, +): [ + Unit, + R, +] { + options = normalizeUnitNameOptions(options, totalUnitStr) + + // alphabetical + const relativeToInternals = refineRelativeTo(options[relativeToName]) + let totalUnit = refineTotalUnit(options) + totalUnit = requirePropDefined(totalUnitStr, totalUnit) + + return [ + totalUnit, // required + relativeToInternals, + ] +} + +export function refineDateTimeDisplayOptions(options: DateTimeDisplayOptions | undefined): DateTimeDisplayTuple { + options = normalizeOptions(options) + return [ + refineCalendarDisplay(options), + ...refineTimeDisplayTuple(options), + ] +} + +export function refineDateDisplayOptions(options: CalendarDisplayOptions | undefined): CalendarDisplay { + return refineCalendarDisplay(normalizeOptions(options)) +} + +export function refineTimeDisplayOptions( + options: TimeDisplayOptions | undefined, + maxSmallestUnit?: TimeUnit +): TimeDisplayTuple { + return refineTimeDisplayTuple(normalizeOptions(options), maxSmallestUnit) +} + +export function refineZonedDateTimeDisplayOptions( + options: ZonedDateTimeDisplayOptions | undefined, +): ZonedDateTimeDisplayTuple { + options = normalizeOptions(options) + + // alphabetical + const calendarDisplay = refineCalendarDisplay(options) + const subsecDigits = refineSubsecDigits(options) // "fractionalSecondDigits". rename in our code? + const offsetDisplay = refineOffsetDisplay(options) + const roundingMode = refineRoundingMode(options, RoundingMode.Trunc) + const smallestUnit = refineSmallestUnit(options, Unit.Minute) + const timeZoneDisplay = refineTimeZoneDisplay(options) + + return [ + calendarDisplay, + timeZoneDisplay, + offsetDisplay, + roundingMode, + ...refineSmallestUnitAndSubsecDigits(smallestUnit, subsecDigits), + ] +} + +export function refineInstantDisplayOptions( + options: InstantDisplayOptions | undefined, +): InstantDisplayTuple { + options = normalizeOptions(options) + + // alphabetical + const timeDisplayTuple = refineTimeDisplayTuple(options) + const timeZoneArg: TA = options.timeZone + + return [ + timeZoneArg, + ...timeDisplayTuple, + ] +} + +// Utils for compound options +// ------------------------------------------------------------------------------------------------- + +function refineTimeDisplayTuple( + options: TimeDisplayOptions, + maxSmallestUnit: TimeUnit = Unit.Minute +): TimeDisplayTuple { + // alphabetical + const subsecDigits = refineSubsecDigits(options) // "fractionalSecondDigits". rename in our code? + const roundingMode = refineRoundingMode(options, RoundingMode.Trunc) + const smallestUnit = refineSmallestUnit(options, maxSmallestUnit) + + return [ + roundingMode, + ...refineSmallestUnitAndSubsecDigits(smallestUnit, subsecDigits), + ] +} + +function refineSmallestUnitAndSubsecDigits( + smallestUnit: Unit | undefined | null, + subsecDigits: SubsecDigits | undefined, +): [ + nanoInc: number, + subsecDigits: SubsecDigits | -1 | undefined, +] { + if (smallestUnit != null) { + return [ + unitNanoMap[smallestUnit], + (smallestUnit < Unit.Minute) + ? (9 - (smallestUnit * 3)) as SubsecDigits + : -1, // hide seconds (not relevant when maxSmallestUnit is smaller then minute) + ] + } + + return [ + subsecDigits === undefined ? 1 : 10 ** (9 - subsecDigits), + subsecDigits, + ] +} + +// Individual Refining (simple) +// ------------------------------------------------------------------------------------------------- + +const refineSmallestUnit = bindArgs(refineUnitOption, smallestUnitStr) +const refineLargestUnit = bindArgs(refineUnitOption, largestUnitStr) +const refineTotalUnit = bindArgs(refineUnitOption, totalUnitStr) +const refineOverflow = bindArgs(refineChoiceOption, 'overflow', overflowMap) +const refineEpochDisambig = bindArgs(refineChoiceOption, 'disambiguation', epochDisambigMap) +const refineOffsetDisambig = bindArgs(refineChoiceOption, 'offset', offsetDisambigMap) +const refineCalendarDisplay = bindArgs(refineChoiceOption, 'calendarName', calendarDisplayMap) +const refineTimeZoneDisplay = bindArgs(refineChoiceOption, 'timeZoneName', timeZoneDisplayMap) +const refineOffsetDisplay = bindArgs(refineChoiceOption, 'offset', offsetDisplayMap) +const refineRoundingMode = bindArgs(refineChoiceOption, 'roundingMode', roundingModeMap) // Caller should always supply default + +// Individual Refining (custom logic) +// ------------------------------------------------------------------------------------------------- + +function parseRoundingIncInteger(options: RoundingIncOptions): number { + let roundingInc = options[roundingIncName] + if (roundingInc === undefined) { + return 1 + } + return toInteger(roundingInc, roundingIncName) +} + +function refineRoundingInc( + roundingInc: number, // results from parseRoundingIncInteger + smallestUnit: DayTimeUnit, + allowManyLargeUnits?: boolean, + solarMode?: boolean, +): number { + const upUnitNano = solarMode ? nanoInUtcDay : unitNanoMap[smallestUnit + 1] + + if (upUnitNano) { + const unitNano = unitNanoMap[smallestUnit] + const maxRoundingInc = upUnitNano / unitNano + roundingInc = clampEntity( + roundingIncName, + roundingInc, + 1, + maxRoundingInc - (solarMode ? 0 : 1), + Overflow.Reject + ) + + // % is dangerous, but -0 will be falsy just like 0 + if (upUnitNano % (roundingInc * unitNano)) { + throw new RangeError(errorMessages.invalidEntity(roundingIncName, roundingInc)) + } + } else { + roundingInc = clampEntity( + roundingIncName, + roundingInc, + 1, + allowManyLargeUnits ? 10 ** 9 : 1, + Overflow.Reject + ) + } + + return roundingInc +} + +function refineSubsecDigits(options: SubsecDigitsOptions): SubsecDigits | undefined { + let subsecDigits = options[subsecDigitsName] + + if (subsecDigits !== undefined) { + if (typeof subsecDigits !== 'number') { + if (toString(subsecDigits) === 'auto') { + return + } + throw new RangeError(errorMessages.invalidEntity(subsecDigitsName, subsecDigits)) + } + + subsecDigits = clampEntity(subsecDigitsName, Math.floor(subsecDigits), 0, 9, Overflow.Reject) as SubsecDigits + } + + return subsecDigits +} + +// Normalization of whole options object +// ------------------------------------------------------------------------------------------------- + +/* +For ensuring options is an object +*/ +export function normalizeOptions(options: O | undefined): O { + if (options === undefined) { + return {} as O + } + return requireObjectlike(options) +} + +function normalizeUnitNameOptions( + options: O | UnitName, + optionName: keyof O, +): O { + if (typeof options === 'string') { + return { [optionName]: options } as O + } + return requireObjectlike(options) +} + +/* +For validating and copying. If undefined, leave as undefined +Used for to* and diff* and `with` functions +*/ +export function copyOptions(options: O): O { + if (options === undefined) { + return undefined as any + } + if (isObjectLike(options)) { + return Object.assign(Object.create(null), options) + } + throw new TypeError(errorMessages.invalidObject) +} + +export function overrideOverflowOptions( + options: OverflowOptions | undefined, + overflow: Overflow, +): OverflowOptions { + return options && Object.assign(Object.create(null), options, { overflow: overflowMapNames[overflow] }) +} + +// Utils +// ------------------------------------------------------------------------------------------------- + +function invertRoundingMode(roundingMode: RoundingMode): RoundingMode { + if (roundingMode < 4) { + return (roundingMode + 2) % 4 + } + return roundingMode +} + +/* +`null` means 'auto' +TODO: create better type where if ensureDefined, then return-type is non null/defined +*/ +function refineUnitOption( + optionName: (keyof O) & string, + options: O, + maxUnit: Unit = Unit.Year, + minUnit: Unit = Unit.Nanosecond, // used less frequently than maxUnit + ensureDefined?: boolean, // will return minUnit if undefined or auto +): Unit | null | undefined { + let unitStr = options[optionName] as (string | undefined) + if (unitStr === undefined) { + return ensureDefined ? minUnit : undefined + } + + unitStr = toString(unitStr) + if (unitStr === 'auto') { + return ensureDefined ? minUnit : null + } + + let unit = unitNameMap[unitStr as UnitName] + + if (unit === undefined) { + unit = durationFieldIndexes[unitStr as (keyof DurationFields)] + } + if (unit === undefined) { + throw new RangeError(errorMessages.invalidEntity(optionName, unitStr)) + } + + clampEntity(optionName, unit, minUnit, maxUnit, Overflow.Reject) + return unit +} + +function refineChoiceOption( + optionName: keyof O & string, + enumNameMap: Record, + options: O, + defaultChoice = 0, // TODO: improve this type? +): number { + const enumArg = options[optionName] + if (enumArg === undefined) { + return defaultChoice + } + + const enumStr = toString(enumArg as string) + const enumNum = enumNameMap[enumStr] + if (enumNum === undefined) { + throw new RangeError(errorMessages.invalidEntity(optionName, enumStr)) + } + return enumNum +} + +function checkLargestSmallestUnit(largestUnit: Unit, smallestUnit: Unit): void { + if (smallestUnit > largestUnit) { + throw new RangeError(errorMessages.flippedSmallestLargestUnit) + } +} diff --git a/packages/temporal-polyfill/src/internal/parseIso.ts b/packages/temporal-polyfill/src/internal/parseIso.ts new file mode 100644 index 00000000..42b774da --- /dev/null +++ b/packages/temporal-polyfill/src/internal/parseIso.ts @@ -0,0 +1,655 @@ +import { unitNanoMap, nanoInSec, nanoInUtcDay } from './units' +import { isoCalendarId } from './calendarConfig' +import { + DurationFields, + durationFieldNamesAsc, +} from './durationFields' +import { negateDurationFields } from './durationMath' +import { + IsoDateFields, + IsoDateTimeFields, + IsoTimeFields, +} from './calendarIsoFields' +import { + constrainIsoTimeFields, + checkIsoDateFields, + checkIsoDateTimeFields, + isIsoDateFieldsValid, + isoEpochFirstLeapYear, +} from './calendarIso' +import { + checkIsoDateInBounds, + checkIsoDateTimeInBounds, + checkIsoYearMonthInBounds, isoToEpochNanoWithOffset, + nanoToIsoTimeAndDay +} from './epochAndTime' +import { EpochDisambig, OffsetDisambig, Overflow } from './options' +import { FixedTimeZone } from './timeZoneNative' +import { queryNativeTimeZone } from './timeZoneNative' +import { getMatchingInstantFor, validateTimeZoneOffset } from './timeZoneOps' +import { + TimeUnit, + Unit, + nanoInHour, + nanoInMinute, + nanoToGivenFields, +} from './units' +import { divModFloor, pluckProps, zipProps } from './utils' +import { DayTimeNano } from './dayTimeNano' +import { utcTimeZoneId } from './timeZoneNative' +import { NativeMonthDayParseOps, NativeYearMonthParseOps } from './calendarNative' +import { moveToMonthStart } from './move' +import { ZonedFieldOptions, refineZonedFieldOptions } from './optionsRefine' +import { DateSlots, DurationBranding, DurationSlots, InstantBranding, InstantSlots, PlainDateBranding, PlainDateSlots, PlainDateTimeBranding, PlainDateTimeSlots, PlainMonthDayBranding, PlainMonthDaySlots, PlainTimeBranding, PlainTimeSlots, PlainYearMonthBranding, PlainYearMonthSlots, ZonedDateTimeBranding, ZonedDateTimeSlots, ZonedEpochSlots, createDurationSlots, createInstantSlots, createPlainDateTimeSlots, createPlainDateSlots, createPlainMonthDaySlots, createPlainTimeSlots, createPlainYearMonthSlots, createZonedDateTimeSlots } from './slots' +import { requireString, toStringViaPrimitive } from './cast' +import { realizeCalendarId } from './calendarNativeQuery' +import * as errorMessages from './errorMessages' + +// High-level +// ------------------------------------------------------------------------------------------------- + +export function parseInstant(s: string): InstantSlots { + // instead of 'requiring' like other types, + // coerce, because there's no fromFields, so no need to differentiate param type + s = toStringViaPrimitive(s) + + const organized = parseDateTimeLike(s) + if (!organized) { + throw new RangeError(errorMessages.failedParse(s)) + } + + let offsetNano + + if (organized.hasZ) { + offsetNano = 0 + } else if (organized.offset) { + offsetNano = parseOffsetNano(organized.offset) + } else { + throw new RangeError(errorMessages.failedParse(s)) + } + + // validate timezone + if (organized.timeZone) { + parseOffsetNanoMaybe(organized.timeZone, true) // onlyHourMinute=true + } + + const epochNanoseconds = isoToEpochNanoWithOffset( + checkIsoDateTimeFields(organized), + offsetNano, + ) + + return createInstantSlots(epochNanoseconds) +} + +export function parseZonedOrPlainDateTime(s: string): ( + DateSlots | + ZonedEpochSlots +) { + const organized = parseDateTimeLike(requireString(s)) + + if (!organized) { + throw new RangeError(errorMessages.failedParse(s)) + } + if (organized.timeZone) { + return finalizeZonedDateTime( + organized as ZonedDateTimeOrganized, + organized.offset ? parseOffsetNano(organized.offset) : undefined, + ) + } + if (organized.hasZ) { + // PlainDate doesn't support Z + throw new RangeError(errorMessages.failedParse(s)) + } + + return finalizeDate(organized) +} + +export function parseZonedDateTime( + s: string, + options?: ZonedFieldOptions, +): ZonedDateTimeSlots { + const organized = parseDateTimeLike(requireString(s)) + + if (!organized || !organized.timeZone) { + throw new RangeError(errorMessages.failedParse(s)) + } + + const { offset } = organized + const offsetNano = offset ? parseOffsetNano(offset) : undefined + const [, offsetDisambig, epochDisambig] = refineZonedFieldOptions(options) + + return finalizeZonedDateTime( + organized as ZonedDateTimeOrganized, + offsetNano, // HACK + offsetDisambig, + epochDisambig, + ) +} + +/* +`s` already validated as a string +*/ +export function parseOffsetNano(s: string): number { + const offsetNano = parseOffsetNanoMaybe(s) + if (offsetNano === undefined) { + throw new RangeError(errorMessages.failedParse(s)) // Invalid offset string + } + return offsetNano +} + +export function parsePlainDateTime(s: string): PlainDateTimeSlots { + const organized = parseDateTimeLike(requireString(s)) + + if (!organized || organized.hasZ) { + throw new RangeError(errorMessages.failedParse(s)) + } + + return createPlainDateTimeSlots( + finalizeDateTime(organized), + ) +} + +export function parsePlainDate(s: string): PlainDateSlots { + const organized = parseDateTimeLike(requireString(s)) + + if (!organized || organized.hasZ) { + throw new RangeError(errorMessages.failedParse(s)) + } + + return createPlainDateSlots( + organized.hasTime + ? finalizeDateTime(organized) + : finalizeDate(organized) + ) +} + +export function parsePlainYearMonth( + getCalendarOps: (calendarId: string) => NativeYearMonthParseOps, + s: string, +): PlainYearMonthSlots { + const organized = parseYearMonthOnly(requireString(s)) + + if (organized) { + requireIsoCalendar(organized) + return createPlainYearMonthSlots( + checkIsoYearMonthInBounds(checkIsoDateFields(organized)), + ) + } + + const isoFields = parsePlainDate(s) + const calendarOps = getCalendarOps(isoFields.calendar) + const movedIsoFields = moveToMonthStart(calendarOps, isoFields) + + return createPlainYearMonthSlots({ + ...isoFields, // has calendar + ...movedIsoFields, + }) +} + +function requireIsoCalendar(organized: { calendar: string }): void { + if (organized.calendar !== isoCalendarId) { + throw new RangeError(errorMessages.invalidSubstring(organized.calendar)) + } +} + +export function parsePlainMonthDay( + getCalendarOps: (calendarId: string) => NativeMonthDayParseOps, + s: string, +): PlainMonthDaySlots { + const organized = parseMonthDayOnly(requireString(s)) + + if (organized) { + requireIsoCalendar(organized) + return createPlainMonthDaySlots( + checkIsoDateFields(organized), // `organized` has isoEpochFirstLeapYear + ) + } + + const dateSlots = parsePlainDate(s) + const { calendar } = dateSlots + const calendarOps = getCalendarOps(calendar) + + // normalize year&month to be as close as possible to epoch + const [origYear, origMonth, day] = calendarOps.dateParts(dateSlots) + const [monthCodeNumber, isLeapMonth] = calendarOps.monthCodeParts(origYear, origMonth) + const [year, month] = calendarOps.yearMonthForMonthDay(monthCodeNumber, isLeapMonth, day)! // !HACK + const isoFields = calendarOps.isoFields(year, month, day) + + return createPlainMonthDaySlots(isoFields, calendar) +} + +export function parsePlainTime(s: string): PlainTimeSlots { + let organized: IsoTimeFields | DateTimeLikeOrganized | undefined = parseTimeOnly(requireString(s)) + + if (!organized) { + organized = parseDateTimeLike(s) + + if (organized) { + if (!(organized as DateTimeLikeOrganized).hasTime) { + throw new RangeError(errorMessages.failedParse(s)) // Must have time for PlainTime + } + if ((organized as DateTimeLikeOrganized).hasZ) { + throw new RangeError(errorMessages.invalidSubstring('Z')) // Cannot have Z for PlainTime + } + requireIsoCalendar(organized as DateTimeLikeOrganized) + } else { + throw new RangeError(errorMessages.failedParse(s)) + } + } + + let altParsed: DateOrganized | undefined + if ((altParsed = parseYearMonthOnly(s)) && isIsoDateFieldsValid(altParsed)) { + throw new RangeError(errorMessages.failedParse(s)) + } + if ((altParsed = parseMonthDayOnly(s)) && isIsoDateFieldsValid(altParsed)) { + throw new RangeError(errorMessages.failedParse(s)) + } + + return createPlainTimeSlots(constrainIsoTimeFields(organized, Overflow.Reject)) +} + +export function parseDuration(s: string): DurationSlots { + const parsed = parseDurationFields(requireString(s)) + if (!parsed) { + throw new RangeError(errorMessages.failedParse(s)) + } + return createDurationSlots(parsed) +} + +export function parseCalendarId(s: string): string { + const res = parseDateTimeLike(s) || parseYearMonthOnly(s) || parseMonthDayOnly(s) + return res ? res.calendar : s +} + +export function parseTimeZoneId(s: string): string { + const parsed = parseDateTimeLike(s) + return parsed && ( + parsed.timeZone || + (parsed.hasZ && utcTimeZoneId) || + parsed.offset + ) || s +} + +// Finalizing 'organized' structs to slots +// ------------------------------------------------------------------------------------------------- + +/* +Unlike others, return slots +*/ +function finalizeZonedDateTime( + organized: ZonedDateTimeOrganized, + offsetNano: number | undefined, + offsetDisambig: OffsetDisambig = OffsetDisambig.Reject, + epochDisambig: EpochDisambig = EpochDisambig.Compat, +): ZonedDateTimeSlots { + const timeZoneImpl = queryNativeTimeZone(organized.timeZone) + + const epochNanoseconds = getMatchingInstantFor( + timeZoneImpl, + checkIsoDateTimeFields(organized), + offsetNano, + offsetDisambig, + epochDisambig, + !(timeZoneImpl instanceof FixedTimeZone), // only allow fuzzy minute-rounding matching if named-timezone + // TODO: ^^^ do this for 'UTC'? (which is normalized to FixedTimeZoneImpl?). Probably not. + organized.hasZ, + ) + + return createZonedDateTimeSlots( + epochNanoseconds, + timeZoneImpl.id, + realizeCalendarId(organized.calendar), + ) +} + +function finalizeDateTime(organized: DateTimeLikeOrganized): IsoDateTimeFields & { calendar: string } { + return realizeCalendarSlot(checkIsoDateTimeInBounds(checkIsoDateTimeFields(organized))) +} + +function finalizeDate(organized: DateOrganized): DateSlots { + return realizeCalendarSlot(checkIsoDateInBounds(checkIsoDateFields(organized))) +} + +function realizeCalendarSlot(organized: T): T { + return { + ...organized, + calendar: realizeCalendarId(organized.calendar), + } +} + +// RegExp +// ------------------------------------------------------------------------------------------------- + +const signRegExpStr = '([+\u2212-])' // outer captures +const fractionRegExpStr = '(?:[.,](\\d{1,9}))?' // only afterDecimal captures + +const yearMonthRegExpStr = + `(?:(?:${signRegExpStr}(\\d{6}))|(\\d{4}))` + // 1:yearSign, 2:yearDigits6, 3:yearDigits4 + '-?(\\d{2})' // 4:month + +const dateRegExpStr = + yearMonthRegExpStr + // 1:yearSign, 2:yearDigits6, 3:yearDigits4, 4:month + '-?(\\d{2})' // 5:day + +const monthDayRegExpStr = + '(?:--)?(\\d{2})' + // 1:month + '-?(\\d{2})' // 2:day + +const timeRegExpStr = + '(\\d{2})' + // 1:hour + '(?::?(\\d{2})' + // 2:minute + '(?::?(\\d{2})' + // 3:second + fractionRegExpStr + // 4:afterDecimal + ')?' + + ')?' + +const offsetRegExpStr = + signRegExpStr + // 1:offsetSign + timeRegExpStr // 2:hour, 3:minute, 4:second, 5:afterDecimal + +const dateTimeRegExpStr = + dateRegExpStr + // // 1:yearSign, 2:yearDigits6, 3:yearDigits4, 4:month, 5:day + '(?:[T ]' + + timeRegExpStr + // 6:hour, 7:minute, 8:second, 9:afterDecimal + '(Z|' + // 10:zOrOffset + offsetRegExpStr + // 11:offsetSign, 12:hour, 13:minute, 14:second, 15:afterDecimal + ')?' + + ')?' + +const annotationRegExpStr = '\\[(!?)([^\\]]*)\\]' // critical:1, annotation:2 +const annotationsRegExpStr = `((?:${annotationRegExpStr})*)` // multiple + +const yearMonthRegExp = createRegExp(yearMonthRegExpStr + annotationsRegExpStr) +const monthDayRegExp = createRegExp(monthDayRegExpStr + annotationsRegExpStr) +const dateTimeRegExp = createRegExp(dateTimeRegExpStr + annotationsRegExpStr) +const timeRegExp = createRegExp( + 'T?' + + timeRegExpStr + // 1-4 + '(?:' + offsetRegExpStr + ')?' + // 5-9 + annotationsRegExpStr, // 10 +) +const offsetRegExp = createRegExp(offsetRegExpStr) +const annotationRegExp = new RegExp(annotationRegExpStr, 'g') + +const durationRegExp = createRegExp( + `${signRegExpStr}?P` + // 1:sign + '(\\d+Y)?' + // 2:years + '(\\d+M)?' + // 3:months + '(\\d+W)?' + // 4:weeks + '(\\d+D)?' + // 5:days + '(?:T' + + `(?:(\\d+)${fractionRegExpStr}H)?` + // 6:hours, 7:partialHour + `(?:(\\d+)${fractionRegExpStr}M)?` + // 8:minutes, 9:partialMinute + `(?:(\\d+)${fractionRegExpStr}S)?` + // 10:seconds, 11:partialSecond + ')?', +) + +// Maybe-parsing +// ------------------------------------------------------------------------------------------------- + +function parseDateTimeLike(s: string): DateTimeLikeOrganized | undefined { + const parts = dateTimeRegExp.exec(s) + return parts ? organizeDateTimeLikeParts(parts) : undefined +} + +function parseYearMonthOnly(s: string): DateOrganized | undefined { + const parts = yearMonthRegExp.exec(s) + return parts ? organizeYearMonthParts(parts) : undefined +} + +function parseMonthDayOnly(s: string): DateOrganized | undefined { + const parts = monthDayRegExp.exec(s) + return parts ? organizeMonthDayParts(parts) : undefined +} + +function parseTimeOnly(s: string): IsoTimeFields | undefined { + const parts = timeRegExp.exec(s) + return parts + ? (organizeAnnotationParts(parts[10]), organizeTimeParts(parts)) // validate annotations + : undefined +} + +function parseDurationFields(s: string): DurationFields | undefined { + const parts = durationRegExp.exec(s) + return parts ? organizeDurationParts(parts) : undefined +} + +export function parseOffsetNanoMaybe(s: string, onlyHourMinute?: boolean): number | undefined { + const parts = offsetRegExp.exec(s) + return parts ? organizeOffsetParts(parts, onlyHourMinute) : undefined +} + +// Parts Organization +// ------------------------------------------------------------------------------------------------- + +type DateTimeLikeOrganized = IsoDateTimeFields & { + hasTime: boolean + hasZ: boolean + offset: string | undefined + calendar: string + timeZone: string | undefined +} + +type ZonedDateTimeOrganized = IsoDateTimeFields & { + hasTime: boolean + hasZ: boolean + offset: string | undefined + calendar: string + timeZone: string +} + +type WithCalendarStr = { calendar: string } +type DateOrganized = IsoDateFields & WithCalendarStr + +function organizeDateTimeLikeParts(parts: string[]): DateTimeLikeOrganized { + const zOrOffset = parts[10] + const hasZ = (zOrOffset || '').toUpperCase() === 'Z' + + return { + isoYear: organizeIsoYearParts(parts), + isoMonth: parseInt(parts[4]), + isoDay: parseInt(parts[5]), + ...organizeTimeParts(parts.slice(5)), // slice one index before, to similate 0 being whole-match + ...organizeAnnotationParts(parts[16]), + hasTime: Boolean(parts[6]), + hasZ, + // TODO: figure out a way to pre-process into a number + // (problems with TimeZone needing the full string?) + offset: hasZ ? undefined : zOrOffset, + } +} + +/* +Result assumed to be ISO +*/ +function organizeYearMonthParts(parts: string[]): DateSlots { + return { + isoYear: organizeIsoYearParts(parts), + isoMonth: parseInt(parts[4]), + isoDay: 1, + ...organizeAnnotationParts(parts[5]), + } +} + +function organizeMonthDayParts(parts: string[]): DateSlots { + return { + isoYear: isoEpochFirstLeapYear, + isoMonth: parseInt(parts[1]), + isoDay: parseInt(parts[2]), + ...organizeAnnotationParts(parts[3]), + } +} + +function organizeIsoYearParts(parts: string[]): number { + const yearSign = parseSign(parts[1]) + const year = parseInt(parts[2] || parts[3]) + + if (yearSign < 0 && !year) { + throw new RangeError(errorMessages.invalidSubstring(-0 as unknown as string)) + } + + return yearSign * year +} + +function organizeTimeParts(parts: string[]): IsoTimeFields { + const isoSecond = parseInt0(parts[3]) + + return { + ...nanoToIsoTimeAndDay(parseSubsecNano(parts[4] || ''))[0], + isoHour: parseInt0(parts[1]), + isoMinute: parseInt0(parts[2]), + isoSecond: isoSecond === 60 ? 59 : isoSecond, // massage leap-second + } +} + +function organizeOffsetParts(parts: string[], onlyHourMinute?: boolean): number { + const firstSubMinutePart = parts[4] || parts[5] + + if (onlyHourMinute && firstSubMinutePart) { + throw new RangeError(errorMessages.invalidSubstring(firstSubMinutePart)) + } + + const offsetNanoPos = ( + parseInt0(parts[2]) * nanoInHour + + parseInt0(parts[3]) * nanoInMinute + + parseInt0(parts[4]) * nanoInSec + + parseSubsecNano(parts[5] || '') + ) + + return validateTimeZoneOffset( + offsetNanoPos * parseSign(parts[1]) + ) +} + +function organizeDurationParts(parts: string[]): DurationFields { + let hasAny = false + let hasAnyFrac = false + let leftoverNano = 0 + let durationFields = { + ...zipProps(durationFieldNamesAsc, [ + parseUnit(parts[2]), + parseUnit(parts[3]), + parseUnit(parts[4]), + parseUnit(parts[5]), + parseUnit(parts[6], parts[7], Unit.Hour), + parseUnit(parts[8], parts[9], Unit.Minute), + parseUnit(parts[10], parts[11], Unit.Second), + ]), + ...nanoToGivenFields(leftoverNano, Unit.Millisecond, durationFieldNamesAsc), + } as DurationFields + + if (!hasAny) { + throw new RangeError(errorMessages.noValidFields) + } + + if (parseSign(parts[1]) < 0) { + durationFields = negateDurationFields(durationFields) + } + + return durationFields + + function parseUnit(wholeStr: string): number + function parseUnit(wholeStr: string, fracStr: string, timeUnit: TimeUnit): number + function parseUnit(wholeStr: string, fracStr?: string, timeUnit?: TimeUnit): number { + let leftoverUnits = 0 // from previous round + let wholeUnits = 0 + + if (timeUnit) { + [leftoverUnits, leftoverNano] = divModFloor(leftoverNano, unitNanoMap[timeUnit]) + } + + if (wholeStr !== undefined) { + if (hasAnyFrac) { + throw new RangeError(errorMessages.invalidSubstring(wholeStr)) // Fraction must be last one + } + + wholeUnits = parseIntSafe(wholeStr) + hasAny = true + + if (fracStr) { + // convert seconds to other units + // more precise version of `frac / nanoInSec * nanoInUnit` + leftoverNano = parseSubsecNano(fracStr) * (unitNanoMap[timeUnit!] / nanoInSec) + hasAnyFrac = true + } + } + + return leftoverUnits + wholeUnits + } +} + +// Annotations +// ------------------------------------------------------------------------------------------------- + +interface AnnotationsOrganized { + calendar: string, + timeZone: string | undefined, +} + +function organizeAnnotationParts(s: string): AnnotationsOrganized { + let calendarIsCritical: boolean | undefined + let timeZoneId: string | undefined + const calendarIds: string[] = [] + + // iterate through matches + s.replace(annotationRegExp, (whole, criticalStr, mainStr) => { + const isCritical = Boolean(criticalStr) + const [val, name] = mainStr.split('=').reverse() as [string, string?] + + if (!name) { + if (timeZoneId) { + throw new RangeError(errorMessages.invalidSubstring(whole)) // Cannot specify timeZone multiple times + } + timeZoneId = val + } else if (name === 'u-ca') { + calendarIds.push(val) + calendarIsCritical ||= isCritical + } else if (isCritical) { + throw new RangeError(errorMessages.invalidSubstring(whole)) // Critical annotation not used + } + + return '' + }) + + if (calendarIds.length > 1 && calendarIsCritical) { + throw new RangeError(errorMessages.invalidSubstring(s)) // Multiple calendars when one is critical + } + + return { + timeZone: timeZoneId, + calendar: calendarIds[0] || isoCalendarId, + } +} + +// Utils +// ------------------------------------------------------------------------------------------------- + +function parseSubsecNano(fracStr: string): number { + return parseInt(fracStr.padEnd(9, '0')) +} + +function createRegExp(meat: string): RegExp { + return new RegExp(`^${meat}$`, 'i') +} + +function parseSign(s: string | undefined): number { + return !s || s === '+' ? 1 : -1 +} + +function parseInt0(s: string | undefined): number { + return s === undefined ? 0 : parseInt(s) +} + +/* +Guaranteed to be non-Infinity (which can happen if number beyond maxint I think) +Only needs to be called when we know RegExp allows infinite repeating character +*/ +function parseIntSafe(s: string): number { + const n = parseInt(s) + + if (!Number.isFinite(n)) { + throw new RangeError(errorMessages.invalidSubstring(s)) + } + + return n +} diff --git a/packages/temporal-polyfill/src/internal/round.ts b/packages/temporal-polyfill/src/internal/round.ts new file mode 100644 index 00000000..c7161908 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/round.ts @@ -0,0 +1,580 @@ +import { DayTimeNano, addDayTimeNanoAndNumber, addDayTimeNanos, createDayTimeNano, dayTimeNanoToNumber, diffDayTimeNanos } from './dayTimeNano' +import { + DurationFields, + durationFieldDefaults, + durationFieldNamesAsc, + durationTimeFieldDefaults, +} from './durationFields' +import { + DiffMarkers, + MarkerToEpochNano, + MoveMarker, + queryDurationSign, + durationFieldsToDayTimeNano, + nanoToDurationDayTimeFields, + nanoToDurationTimeFields, + clearDurationFields, +} from './durationMath' +import { IsoTimeFields, isoTimeFieldDefaults, IsoDateTimeFields } from './calendarIsoFields' +import { checkIsoDateTimeInBounds, epochNanoToIso, isoTimeFieldsToNano, nanoToIsoTimeAndDay } from './epochAndTime' +import { EpochDisambig, OffsetDisambig, RoundingMode, roundingModeFuncs } from './options' +import { TimeZoneOps, computeNanosecondsInDay, getMatchingInstantFor } from './timeZoneOps' +import { + nanoInMinute, + nanoInUtcDay, + unitNanoMap, + Unit, + DayTimeUnit, + TimeUnit, + givenFieldsToDayTimeNano, + UnitName, +} from './units' +import { divModFloor, divTrunc, identityFunc } from './utils' +import { moveByIsoDays } from './move' +import { clampRelativeDuration, computeEpochNanoFrac, totalDayTimeNano } from './total' +import { InstantBranding, InstantSlots, PlainDateTimeBranding, PlainDateTimeSlots, PlainTimeBranding, PlainTimeSlots, ZonedDateTimeBranding, ZonedDateTimeSlots, createInstantSlots, createPlainDateTimeSlots, createPlainTimeSlots, createZonedDateTimeSlots } from './slots' +import { RoundingOptions, refineRoundOptions } from './optionsRefine' + +// High-Level +// ------------------------------------------------------------------------------------------------- + +export function roundInstant( + instantSlots: InstantSlots, + options: RoundingOptions | UnitName +): InstantSlots { + const [smallestUnit, roundingInc, roundingMode] = refineRoundOptions( // TODO: inline this + options, + Unit.Hour, + true, // solarMode + ) + + return createInstantSlots( + roundDayTimeNano( + instantSlots.epochNanoseconds, + smallestUnit as TimeUnit, + roundingInc, + roundingMode, + true, // useDayOrigin + ), + ) +} + +export function roundZonedDateTime( + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + zonedDateTimeSlots: ZonedDateTimeSlots, + options: RoundingOptions | UnitName, +): ZonedDateTimeSlots { + let { epochNanoseconds, timeZone, calendar } = zonedDateTimeSlots + const [smallestUnit, roundingInc, roundingMode] = refineRoundOptions(options) + + // short circuit (elsewhere? consolidate somehow?) + if (smallestUnit === Unit.Nanosecond && roundingInc === 1) { + return zonedDateTimeSlots + } + + const timeZoneOps = getTimeZoneOps(timeZone) + const offsetNano = timeZoneOps.getOffsetNanosecondsFor(epochNanoseconds) + let isoDateTimeFields = { + ...epochNanoToIso(epochNanoseconds, offsetNano), + calendar, // repeat below? + } + + isoDateTimeFields = { + calendar, + ...roundDateTime( + isoDateTimeFields, + smallestUnit as DayTimeUnit, + roundingInc, + roundingMode, + timeZoneOps, + ) + } + + epochNanoseconds = getMatchingInstantFor( + timeZoneOps, + isoDateTimeFields, + offsetNano, + OffsetDisambig.Prefer, // keep old offsetNano if possible + EpochDisambig.Compat, + true, // fuzzy + ) + + return createZonedDateTimeSlots( + epochNanoseconds, + timeZone, + calendar, + ) +} + +export function roundPlainDateTime( + plainDateTimeSlots: PlainDateTimeSlots, + options: RoundingOptions | UnitName, +): PlainDateTimeSlots { + const roundedIsoFields = roundDateTime( + plainDateTimeSlots, + ...(refineRoundOptions(options) as [DayTimeUnit, number, RoundingMode]), + ) + + return createPlainDateTimeSlots( + roundedIsoFields, + plainDateTimeSlots.calendar, + ) +} + +export function roundPlainTime( + slots: PlainTimeSlots, + options: RoundingOptions | UnitName, +): PlainTimeSlots { + return createPlainTimeSlots( + roundTime( + slots, + ...(refineRoundOptions(options, Unit.Hour) as [TimeUnit, number, RoundingMode]) + ), + ) +} + +// Low-Level +// ------------------------------------------------------------------------------------------------- + +function roundDateTime( + isoFields: IsoDateTimeFields, + smallestUnit: DayTimeUnit, + roundingInc: number, + roundingMode: RoundingMode, + timeZoneOps?: TimeZoneOps | undefined, +): IsoDateTimeFields { + if (smallestUnit === Unit.Day) { + return roundDateTimeToDay(isoFields, timeZoneOps, roundingMode) + } + + return roundDateTimeToNano( + isoFields, + computeNanoInc(smallestUnit, roundingInc), + roundingMode, + ) +} + +function roundDateTimeToDay( + isoFields: IsoDateTimeFields, + timeZoneOps: TimeZoneOps | undefined, + roundingMode: RoundingMode, +): IsoDateTimeFields { + if (timeZoneOps) { + const nanoInDay = computeNanosecondsInDay(timeZoneOps, isoFields) + const roundedTimeNano = roundByInc(isoTimeFieldsToNano(isoFields), nanoInDay, roundingMode) + const dayDelta = roundedTimeNano / nanoInDay + + return checkIsoDateTimeInBounds({ + ...moveByIsoDays(isoFields, dayDelta), + ...isoTimeFieldDefaults, + }) + } else { + return roundDateTimeToNano(isoFields, nanoInUtcDay, roundingMode) + } +} + +export function roundDateTimeToNano( + isoFields: IsoDateTimeFields, + nanoInc: number, + roundingMode: RoundingMode, +): IsoDateTimeFields { + const [roundedIsoFields, dayDelta] = roundTimeToNano(isoFields, nanoInc, roundingMode) + + return checkIsoDateTimeInBounds({ + ...moveByIsoDays(isoFields, dayDelta), + ...roundedIsoFields, + }) +} + +function roundTime( + isoFields: IsoTimeFields, + smallestUnit: TimeUnit, + roundingInc: number, + roundingMode: RoundingMode, +): IsoTimeFields { + return roundTimeToNano( + isoFields, + computeNanoInc(smallestUnit, roundingInc), + roundingMode, + )[0] +} + +export function roundTimeToNano( + isoFields: IsoTimeFields, + nanoInc: number, + roundingMode: RoundingMode, +): [ + IsoTimeFields, + number, +] { + return nanoToIsoTimeAndDay( + roundByInc(isoTimeFieldsToNano(isoFields), nanoInc, roundingMode), + ) +} + +// Rounding Duration +// TODO: consolidate with durationMath? +// ------------------------------------------------------------------------------------------------- + +export function roundDayTimeDuration( + durationFields: DurationFields, + largestUnit: DayTimeUnit, + smallestUnit: DayTimeUnit, + roundingInc: number, + roundingMode: RoundingMode, +): DurationFields { + return { + ...durationFieldDefaults, + ...balanceDayTimeDuration( + durationFields, + largestUnit, + smallestUnit, + roundingInc, + roundingMode, + ), + } +} + +function balanceDayTimeDuration( + durationFields: DurationFields, + largestUnit: DayTimeUnit, + smallestUnit: DayTimeUnit, + roundingInc: number, + roundingMode: RoundingMode, +): Partial { + const dayTimeNano = durationFieldsToDayTimeNano(durationFields, Unit.Day) + const roundedLargeNano = roundDayTimeNano(dayTimeNano, smallestUnit, roundingInc, roundingMode) + return nanoToDurationDayTimeFields(roundedLargeNano, largestUnit) +} + +export function balanceDayTimeDurationByInc( + durationFields: DurationFields, + largestUnit: DayTimeUnit, + nanoInc: number, // REQUIRED: not larger than a day + roundingMode: RoundingMode, +): Partial { + const dayTimeNano = durationFieldsToDayTimeNano(durationFields, largestUnit) + const roundedLargeNano = roundDayTimeNanoByInc(dayTimeNano, nanoInc, roundingMode) + return nanoToDurationDayTimeFields(roundedLargeNano, largestUnit) +} + +/* +TODO: caller should short-circuit if + !sign || (smallestUnit === Unit.Nanosecond && roundingInc === 1) +*/ +export function roundRelativeDuration( + durationFields: DurationFields, // must be balanced & top-heavy in day or larger (so, small time-fields) + // ^has sign + endEpochNano: DayTimeNano, + largestUnit: Unit, + smallestUnit: Unit, + roundingInc: number, + roundingMode: RoundingMode, + // marker system... + marker: M, + markerToEpochNano: MarkerToEpochNano, + moveMarker: MoveMarker, + diffMarkers?: DiffMarkers, // unused +): DurationFields { + const nudgeFunc = ( + (markerToEpochNano === identityFunc) // is zoned? + ? smallestUnit > Unit.Day + ? nudgeRelativeDuration + : smallestUnit === Unit.Day + ? nudgeDurationDayTime // doesn't worry about DST + : nudgeRelativeDurationTime // handles DST + : smallestUnit > Unit.Day + ? nudgeRelativeDuration + : nudgeDurationDayTime // doesn't worry about DST + ) as typeof nudgeRelativeDuration // accepts all units + + let [roundedDurationFields, roundedEpochNano, grewBigUnit] = nudgeFunc( + durationFields, + endEpochNano, + largestUnit, + smallestUnit, + roundingInc, + roundingMode, + // marker system only needed for nudgeRelativeDuration... + marker, + markerToEpochNano, + moveMarker, + ) + + // grew a day/week/month/year? + if (grewBigUnit) { + roundedDurationFields = bubbleRelativeDuration( + roundedDurationFields, + roundedEpochNano, + largestUnit, + Math.max(Unit.Day, smallestUnit), + // marker system... + marker, + markerToEpochNano, + moveMarker, + ) + } + + return roundedDurationFields +} + +// Rounding Numbers +// ------------------------------------------------------------------------------------------------- + +export function computeNanoInc(smallestUnit: DayTimeUnit, roundingInc: number): number { + return unitNanoMap[smallestUnit] * roundingInc +} + +export function roundByInc(num: number, inc: number, roundingMode: RoundingMode): number { + return roundWithMode(num / inc, roundingMode) * inc +} + +export function roundToMinute(offsetNano: number): number { + return roundByInc(offsetNano, nanoInMinute, RoundingMode.HalfExpand) +} + +export function roundDayTimeNano( + dayTimeNano: DayTimeNano, + smallestUnit: DayTimeUnit, + roundingInc: number, + roundingMode: RoundingMode, + useDayOrigin?: boolean +): DayTimeNano { + if (smallestUnit === Unit.Day) { + return [ + roundByInc(totalDayTimeNano(dayTimeNano, Unit.Day), roundingInc, roundingMode), + 0, + ] + } + + return roundDayTimeNanoByInc( + dayTimeNano, + computeNanoInc(smallestUnit, roundingInc), + roundingMode, + useDayOrigin + ) +} + +export function roundDayTimeNanoByInc( + dayTimeNano: DayTimeNano, + nanoInc: number, // REQUIRED: not larger than a day + roundingMode: RoundingMode, + useDayOrigin?: boolean +): DayTimeNano { + let [days, timeNano] = dayTimeNano + + if (useDayOrigin && timeNano < 0) { + timeNano += nanoInUtcDay + days -= 1 + } + + const [dayDelta, roundedTimeNano] = divModFloor( + roundByInc(timeNano, nanoInc, roundingMode), + nanoInUtcDay, + ) + + return createDayTimeNano(days + dayDelta, roundedTimeNano) +} + +function roundWithMode(num: number, roundingMode: RoundingMode): number { + return roundingModeFuncs[roundingMode](num) +} + +// Nudge +// ------------------------------------------------------------------------------------------------- +/* +These functions actually do the heavy-lifting of rounding to a higher/lower marker, +and return the (day) delta. Also return the (potentially) unbalanced new duration. +*/ + +function nudgeDurationDayTime( + durationFields: DurationFields, // must be balanced & top-heavy in day or larger (so, small time-fields) + endEpochNano: DayTimeNano, // NOT NEEDED, just for adding result to + largestUnit: DayTimeUnit, + smallestUnit: DayTimeUnit, // always <=Day + roundingInc: number, + roundingMode: RoundingMode, +): [ + nudgedDurationFields: DurationFields, + nudgedEpochNano: DayTimeNano, + expandedBigUnit: boolean, // grew year/month/week/day? +] { + const sign = queryDurationSign(durationFields) + const dayTimeNano = durationFieldsToDayTimeNano(durationFields, Unit.Day) + const roundedDayTimeNano = roundDayTimeNano(dayTimeNano, smallestUnit, roundingInc, roundingMode) + const nanoDiff = diffDayTimeNanos(dayTimeNano, roundedDayTimeNano) + const expandedBigUnit = Math.sign(roundedDayTimeNano[0] - dayTimeNano[0]) === sign + + const roundedDayTimeFields = nanoToDurationDayTimeFields( + roundedDayTimeNano, + Math.min(largestUnit, Unit.Day), + ) + const nudgedDurationFields = { + ...durationFields, + ...roundedDayTimeFields, + } + + return [ + nudgedDurationFields, + addDayTimeNanos(endEpochNano, nanoDiff), + expandedBigUnit, + ] +} + +/* +Handles crazy DST edge case +Time ONLY. Days must use full-on marker moving +*/ +function nudgeRelativeDurationTime( + durationFields: DurationFields, // must be balanced & top-heavy in day or larger (so, small time-fields) + endEpochNano: DayTimeNano, // NOT NEEDED, just for conformance + largestUnit: Unit, + smallestUnit: TimeUnit, // always , + moveMarker: MoveMarker, +): [ + nudgedDurationFields: DurationFields, + nudgedEpochNano: DayTimeNano, + expandedBigUnit: boolean, // grew year/month/week/day? +] { + const sign = queryDurationSign(durationFields) + let [dayDelta, timeNano] = givenFieldsToDayTimeNano(durationFields, Unit.Hour, durationFieldNamesAsc) + const nanoInc = computeNanoInc(smallestUnit, roundingInc) + let roundedTimeNano = roundByInc(timeNano, nanoInc, roundingMode) + + const [dayEpochNano0, dayEpochNano1] = clampRelativeDuration( + { ...durationFields, ...durationTimeFieldDefaults }, + Unit.Day, + sign, + // marker system... + marker, + markerToEpochNano, + moveMarker, + ) + + const daySpanEpochNanoseconds = dayTimeNanoToNumber( + diffDayTimeNanos(dayEpochNano0, dayEpochNano1), + ) + const beyondDay = roundedTimeNano - daySpanEpochNanoseconds + + // TODO: document. somthing to do with rounding a zdt to the next day + if (!beyondDay || Math.sign(beyondDay) === sign) { + dayDelta += sign + roundedTimeNano = roundByInc(beyondDay, nanoInc, roundingMode) + endEpochNano = addDayTimeNanoAndNumber(dayEpochNano1, roundedTimeNano) + } else { + endEpochNano = addDayTimeNanoAndNumber(dayEpochNano0, roundedTimeNano) + } + + const durationTimeFields = nanoToDurationTimeFields(roundedTimeNano) + + const nudgedDurationFields = { + ...durationFields, + ...durationTimeFields, + days: durationFields.days + dayDelta, + } + + return [nudgedDurationFields, endEpochNano, Boolean(dayDelta)] +} + +function nudgeRelativeDuration( + durationFields: DurationFields, // must be balanced & top-heavy in day or larger (so, small time-fields) + endEpochNano: DayTimeNano, + largestUnit: Unit, + smallestUnit: Unit, // always >Day + roundingInc: number, + roundingMode: RoundingMode, + // marker system... + marker: M, + markerToEpochNano: MarkerToEpochNano, + moveMarker: MoveMarker, +): [ + durationFields: DurationFields, + movedEpochNano: DayTimeNano, + expandedBigUnit: boolean, // grew year/month/week/day? +] { + const sign = queryDurationSign(durationFields) + const smallestUnitFieldName = durationFieldNamesAsc[smallestUnit] + + const baseDurationFields = clearDurationFields(durationFields, smallestUnit - 1) + const truncedVal = divTrunc(durationFields[smallestUnitFieldName], roundingInc) * roundingInc + baseDurationFields[smallestUnitFieldName] = truncedVal + + const [epochNano0, epochNano1] = clampRelativeDuration( + baseDurationFields, + smallestUnit, + roundingInc * sign, + // marker system... + marker, + markerToEpochNano, + moveMarker, + ) + + // usually between 0-1, however can be higher when weeks aren't bounded by months + const frac = computeEpochNanoFrac(epochNano0, epochNano1, endEpochNano) + + const exactVal = truncedVal + (frac * sign * roundingInc) + const roundedVal = roundByInc(exactVal, roundingInc, roundingMode) + const expanded = Math.sign(roundedVal - exactVal) === sign + + baseDurationFields[smallestUnitFieldName] = roundedVal + + return [ + baseDurationFields, + expanded ? epochNano1 : epochNano0, + expanded, // guaranteed to be a big unit because of big smallestUnit + ] +} + +// Bubbling +// (for when larger units might bubble up) +// ------------------------------------------------------------------------------------------------- + +function bubbleRelativeDuration( + durationFields: DurationFields, // must be balanced & top-heavy in day or larger (so, small time-fields) + endEpochNano: DayTimeNano, + largestUnit: Unit, + smallestUnit: Unit, // guaranteed Day/Week/Month/Year + // marker system... + marker: M, + markerToEpochNano: MarkerToEpochNano, + moveMarker: MoveMarker, +): DurationFields { + const sign = queryDurationSign(durationFields) + + for ( + let currentUnit: Unit = smallestUnit + 1; + currentUnit <= largestUnit; + currentUnit++ + ) { + // if balancing day->month->year, skip weeks + if ( + currentUnit === Unit.Week && + largestUnit !== Unit.Week + ) { + continue + } + + const baseDurationFields = clearDurationFields(durationFields, currentUnit - 1) + baseDurationFields[durationFieldNamesAsc[currentUnit]] += sign + + const thresholdEpochNano = markerToEpochNano( + moveMarker(marker, baseDurationFields), + ) + const beyondThreshold = dayTimeNanoToNumber( + diffDayTimeNanos(thresholdEpochNano, endEpochNano), + ) + + if (!beyondThreshold || Math.sign(beyondThreshold) === sign) { + durationFields = baseDurationFields + } else { + break + } + } + + return durationFields +} diff --git a/packages/temporal-polyfill/src/internal/slots.ts b/packages/temporal-polyfill/src/internal/slots.ts new file mode 100644 index 00000000..07ca99ea --- /dev/null +++ b/packages/temporal-polyfill/src/internal/slots.ts @@ -0,0 +1,243 @@ +import { DayTimeNano } from './dayTimeNano' +import { DurationFields, durationFieldNamesAlpha } from './durationFields' +import { IsoDateFields, IsoDateTimeFields, IsoTimeFields, isoDateFieldNamesAlpha, isoDateTimeFieldNamesAlpha, isoTimeFieldNamesAlpha } from './calendarIsoFields' +import { requireString } from './cast' +import { isoCalendarId } from './calendarConfig' +import { parseCalendarId, parseOffsetNanoMaybe, parseTimeZoneId } from './parseIso' +import { realizeTimeZoneId, utcTimeZoneId } from './timeZoneNative' +import { realizeCalendarId } from './calendarNativeQuery' +import { pluckProps } from './utils' +import * as errorMessages from './errorMessages' + +export const PlainYearMonthBranding = 'PlainYearMonth' as const +export const PlainMonthDayBranding = 'PlainMonthDay' as const +export const PlainDateBranding = 'PlainDate' as const +export const PlainDateTimeBranding = 'PlainDateTime' as const +export const PlainTimeBranding = 'PlainTime' as const +export const ZonedDateTimeBranding = 'ZonedDateTime' as const +export const InstantBranding = 'Instant' as const +export const DurationBranding = 'Duration' as const + +// Slot-creation helpers +// ------------------------------------------------------------------------------------------------- + +export function createInstantSlots(epochNano: DayTimeNano): InstantSlots { + return { + branding: InstantBranding, + epochNanoseconds: epochNano, + } +} + +/* +NOTE: parseZonedDateTime still uses ZonedDateTimeBranding +*/ +export function createZonedDateTimeSlots( + epochNano: DayTimeNano, + timeZone: T, + calendar: C, +): ZonedDateTimeSlots { + return { + branding: ZonedDateTimeBranding, + calendar, + timeZone, + epochNanoseconds: epochNano, + } +} + +export function createPlainDateTimeSlots(isoFields: IsoDateTimeFields & { calendar: C }): PlainDateTimeSlots +export function createPlainDateTimeSlots(isoFields: IsoDateTimeFields, calendar: C): PlainDateTimeSlots +export function createPlainDateTimeSlots( + isoFields: IsoDateTimeFields & { calendar?: C }, + calendar = isoFields.calendar, +): PlainDateTimeSlots { + return { + branding: PlainDateTimeBranding, + calendar: calendar!, + ...pluckProps(isoDateTimeFieldNamesAlpha, isoFields), + } +} + +export function createPlainDateSlots(isoFields: IsoDateFields & { calendar: C }): PlainDateSlots +export function createPlainDateSlots(isoFields: IsoDateFields, calendar: C): PlainDateSlots +export function createPlainDateSlots( + isoFields: IsoDateFields & { calendar?: C }, + calendar = isoFields.calendar, +): PlainDateSlots { + return { + branding: PlainDateBranding, + calendar: calendar!, + ...pluckProps(isoDateFieldNamesAlpha, isoFields), + } +} + +export function createPlainYearMonthSlots(isoFields: IsoDateFields & { calendar: C }): PlainYearMonthSlots +export function createPlainYearMonthSlots(isoFields: IsoDateFields, calendar: C): PlainYearMonthSlots +export function createPlainYearMonthSlots( + isoFields: IsoDateFields & { calendar?: C }, + calendar = isoFields.calendar, +): PlainYearMonthSlots { + return { + branding: PlainYearMonthBranding, + calendar: calendar!, + ...pluckProps(isoDateFieldNamesAlpha, isoFields), + } +} + +export function createPlainMonthDaySlots(isoFields: IsoDateFields & { calendar: C }): PlainMonthDaySlots +export function createPlainMonthDaySlots(isoFields: IsoDateFields, calendar: C): PlainMonthDaySlots +export function createPlainMonthDaySlots( + isoFields: IsoDateFields & { calendar?: C }, + calendar = isoFields.calendar, +): PlainMonthDaySlots { + return { + branding: PlainMonthDayBranding, + calendar: calendar!, + ...pluckProps(isoDateFieldNamesAlpha, isoFields), + } +} + +export function createPlainTimeSlots(isoFields: IsoTimeFields): PlainTimeSlots { + return { + branding: PlainTimeBranding, + ...pluckProps(isoTimeFieldNamesAlpha, isoFields), + } +} + +export function createDurationSlots(durationFields: DurationFields): DurationSlots { + return { + branding: DurationBranding, + ...pluckProps(durationFieldNamesAlpha, durationFields) + } +} + +// getISOFields +// ------------------------------------------------------------------------------------------------- + +export function removeBranding(slots: S): Omit { + slots = { ...slots } + delete (slots as any).branding + return slots +} + +// ------------------------------------------------------------------------------------------------- + +export interface BrandingSlots { + branding: string +} + +export interface EpochSlots { + epochNanoseconds: DayTimeNano +} + +export type DateSlots = IsoDateFields & { calendar: C } +export type ZonedEpochSlots = EpochSlots & { timeZone: T, calendar: C } + +export type PlainDateSlots = IsoDateFields & { calendar: C, branding: typeof PlainDateBranding } +export type PlainTimeSlots = IsoTimeFields & { branding: typeof PlainTimeBranding } +export type PlainDateTimeSlots = IsoDateTimeFields & { calendar: C, branding: typeof PlainDateTimeBranding } +export type ZonedDateTimeSlots = { epochNanoseconds: DayTimeNano, calendar: C, timeZone: T, branding: typeof ZonedDateTimeBranding } +export type PlainMonthDaySlots = IsoDateFields & { calendar: C, branding: typeof PlainMonthDayBranding } +export type PlainYearMonthSlots = IsoDateFields & { calendar: C, branding: typeof PlainYearMonthBranding } +export type DurationSlots = DurationFields & { branding: typeof DurationBranding } +export type InstantSlots = { epochNanoseconds: DayTimeNano, branding: typeof InstantBranding } + +// Calendar +// ------------------------------------------------------------------------------------------------- + +export function getCommonCalendarSlot(a: C, b: C): C { + if (!isIdLikeEqual(a, b)) { + throw new RangeError(errorMessages.mismatchingCalendars) + } + + return a +} + +export function getPreferredCalendarSlot(a: C, b: C): C { + // fast path. doesn't read IDs + // similar to isIdLikeEqual + if (a === b) { + return a + } + + const aId = getId(a) + const bId = getId(b) + + if (aId === bId || aId === isoCalendarId) { + return b + } else if (bId === isoCalendarId) { + return a + } + + throw new RangeError(errorMessages.mismatchingCalendars) +} + +export function refineCalendarSlotString(calendarArg: string): string { + return realizeCalendarId(parseCalendarId(requireString(calendarArg))) +} + +// bag +// --- + +export function getCalendarIdFromBag(bag: { calendar?: string }): string { + return extractCalendarIdFromBag(bag) || isoCalendarId +} + +export function extractCalendarIdFromBag(bag: { calendar?: string }): string | undefined { + const { calendar } = bag + if (calendar !== undefined) { + return refineCalendarSlotString(calendar) + } +} + +// TimeZone +// ------------------------------------------------------------------------------------------------- + +export function getCommonTimeZoneSlot(a: C, b: C): C { + if (!isTimeZoneSlotsEqual(a, b, true)) { + throw new RangeError(errorMessages.mismatchingTimeZones) + } + + return a +} + +export function isTimeZoneSlotsEqual(a: IdLike, b: IdLike, loose?: boolean): boolean { + return a === b || getTimeZoneSlotRaw(a, loose) === getTimeZoneSlotRaw(b, loose) +} + +/* +TODO: pre-parse offset somehow? not very performant +*/ +function getTimeZoneSlotRaw(slot: IdLike, loose?: boolean): string | number { + const id = getId(slot) + + if (loose && id === utcTimeZoneId) { + return 0 + } + + const offsetNano = parseOffsetNanoMaybe(id) + if (offsetNano !== undefined) { + return offsetNano + } + + return id +} + +export function refineTimeZoneSlotString(arg: string): string { + return realizeTimeZoneId(parseTimeZoneId(requireString(arg))) +} + +// ID-like +// ------------------------------------------------------------------------------------------------- + +export type IdLike = string | { id: string } + +export function getId(idLike: IdLike): string { + return typeof idLike === 'string' ? idLike : requireString(idLike.id) +} + +export function isIdLikeEqual( + calendarSlot0: IdLike, + calendarSlot1: IdLike, +): boolean { + return calendarSlot0 === calendarSlot1 || getId(calendarSlot0) === getId(calendarSlot1) +} diff --git a/packages/temporal-polyfill/src/internal/timeZoneNative.ts b/packages/temporal-polyfill/src/internal/timeZoneNative.ts new file mode 100644 index 00000000..f6946b5d --- /dev/null +++ b/packages/temporal-polyfill/src/internal/timeZoneNative.ts @@ -0,0 +1,302 @@ +import { isoCalendarId } from './calendarConfig' +import { parseIntlYear } from './calendarIntl' +import { DayTimeNano, addDayTimeNanoAndNumber, numberToDayTimeNano } from './dayTimeNano' +import { OrigDateTimeFormat, hashIntlFormatParts, standardLocaleId } from './formatIntl' +import { IsoDateTimeFields } from './calendarIsoFields' +import { formatOffsetNano } from './formatIso' +import { + checkEpochNanoInBounds, + epochNanoToSec, + epochNanoToSecRemainder, + isoArgsToEpochSec, + isoToEpochNanoWithOffset, + isoToEpochSec +} from './epochAndTime' +import { parseOffsetNanoMaybe } from './parseIso' +import { milliInSec, nanoInSec, secInDay } from './units' +import { clampNumber, compareNumbers, createLazyGenerator } from './utils' + +export const utcTimeZoneId = 'UTC' + +const periodDur = secInDay * 60 +const minPossibleTransition = isoArgsToEpochSec(1847) +const maxPossibleTransition = isoArgsToEpochSec(new Date().getUTCFullYear() + 10) + +export interface NativeTimeZone { // TODO: rename to NativeTimeZoneOps? + id: string + getOffsetNanosecondsFor(epochNano: DayTimeNano): number + getPossibleInstantsFor(isoFields: IsoDateTimeFields): DayTimeNano[] + getTransition(epochNano: DayTimeNano, direction: -1 | 1): DayTimeNano | undefined +} + +// Query +// ------------------------------------------------------------------------------------------------- + +const queryNonFixedTimeZone = createLazyGenerator((timeZoneId: string): NativeTimeZone => { + return timeZoneId === utcTimeZoneId + ? new FixedTimeZone(0, timeZoneId) // override ID + : new IntlTimeZone(timeZoneId) +}) + +export function queryNativeTimeZone(timeZoneId: string): NativeTimeZone { + // normalize for cache-key. choose uppercase for 'UTC' + timeZoneId = timeZoneId.toLocaleUpperCase() + + const offsetNano = parseOffsetNanoMaybe(timeZoneId, true) // onlyHourMinute=true + if (offsetNano !== undefined) { + return new FixedTimeZone(offsetNano) + } + + return queryNonFixedTimeZone(timeZoneId) +} + +export function realizeTimeZoneId(calendarId: string): string { + return queryNativeTimeZone(calendarId).id // queryNativeTimeZone will normalize the id +} + +// Fixed +// ------------------------------------------------------------------------------------------------- + +export class FixedTimeZone implements NativeTimeZone { + constructor( + public offsetNano: number, + public id: string = formatOffsetNano(offsetNano) + ) {} + + getOffsetNanosecondsFor(epochNano: DayTimeNano): number { + return this.offsetNano + } + + getPossibleInstantsFor(isoDateTimeFields: IsoDateTimeFields): DayTimeNano[] { + return [ + isoToEpochNanoWithOffset(isoDateTimeFields, this.offsetNano) + ] + } + + getTransition(epochNano: DayTimeNano, direction: -1 | 1): DayTimeNano | undefined { + return undefined // hopefully minifier will remove + } +} + +// Intl +// ------------------------------------------------------------------------------------------------- + +interface IntlTimeZoneStore { + getPossibleEpochSec: (zonedEpochSec: number) => number[] + getOffsetSec: (epochSec: number) => number + getTransition: (epochSec: number, direction: -1 | 1) => number | undefined +} + +export class IntlTimeZone implements NativeTimeZone { + id: string + store: IntlTimeZoneStore + + constructor(id: string) { + const format = buildIntlFormat(id) + this.id = format.resolvedOptions().timeZone + this.store = createIntlTimeZoneStore(createComputeOffsetSec(format)) + } + + getOffsetNanosecondsFor(epochNano: DayTimeNano): number { + return this.store.getOffsetSec(epochNanoToSec(epochNano)) * nanoInSec + } + + getPossibleInstantsFor(isoFields: IsoDateTimeFields): DayTimeNano[] { + const [zonedEpochSec, subsecNano] = isoToEpochSec(isoFields) + + return this.store.getPossibleEpochSec(zonedEpochSec).map((epochSec) => { + return checkEpochNanoInBounds( + addDayTimeNanoAndNumber(numberToDayTimeNano(epochSec, nanoInSec), subsecNano) + ) + }) + } + + /* + exclusive for both directions + */ + getTransition(epochNano: DayTimeNano, direction: -1 | 1): DayTimeNano | undefined { + const [epochSec, subsecNano] = epochNanoToSecRemainder(epochNano) + const resEpochSec = this.store.getTransition( + epochSec + ((direction > 0 || subsecNano) ? 1 : 0), + direction, + ) + + if (resEpochSec !== undefined) { + return numberToDayTimeNano(resEpochSec, nanoInSec) + } + } +} + +function createIntlTimeZoneStore( + computeOffsetSec: (epochSec: number) => number, +): IntlTimeZoneStore { + const getSample = createLazyGenerator(computeOffsetSec) // always given startEpochSec/endEpochSec + const getSplit = createLazyGenerator(createSplitTuple) + let minTransition = minPossibleTransition + let maxTransition = maxPossibleTransition + + function getPossibleEpochSec(zonedEpochSec: number): number[] { + const wideOffsetSec0 = getOffsetSec(zonedEpochSec - secInDay) + const wideOffsetSec1 = getOffsetSec(zonedEpochSec + secInDay) + + const wideUtcEpochSec0 = zonedEpochSec - wideOffsetSec0 + const wideUtcEpochSec1 = zonedEpochSec - wideOffsetSec1 // could move below + + if (wideOffsetSec0 === wideOffsetSec1) { + return [wideUtcEpochSec0] + } + + const narrowOffsetSec0 = getOffsetSec(wideUtcEpochSec0) + const narrowOffsetSec1 = getOffsetSec(wideUtcEpochSec1) + + if (narrowOffsetSec0 === narrowOffsetSec1) { + return [zonedEpochSec - narrowOffsetSec0] + } + + // narrow could be too narrow + if (wideOffsetSec0 > wideOffsetSec1) { + return [wideUtcEpochSec0, wideUtcEpochSec1] + } + + return [] + + } + + function getOffsetSec(epochSec: number): number { + const clampedEpochSec = clampNumber(epochSec, minTransition, maxTransition) + const [startEpochSec, endEpochSec] = computePeriod(clampedEpochSec) + const startOffsetSec = getSample(startEpochSec) + const endOffsetSec = getSample(endEpochSec) + + if (startOffsetSec === endOffsetSec) { + return startOffsetSec + } + + const split = getSplit(startEpochSec, endEpochSec) + return pinch(split, startOffsetSec, endOffsetSec, epochSec) + } + + /* + inclusive for positive direction, exclusive for negative + */ + function getTransition(epochSec: number, direction: -1 | 1): number | undefined { + const clampedEpochSec = clampNumber(epochSec, minTransition, maxTransition) + let [startEpochSec, endEpochSec] = computePeriod(clampedEpochSec) + + const inc = periodDur * direction + const inBounds = direction < 0 + ? () => endEpochSec > minTransition || (minTransition = clampedEpochSec, false) + : () => startEpochSec < maxTransition || (maxTransition = clampedEpochSec, false) + + while (inBounds()) { + const startOffsetSec = getSample(startEpochSec) + const endOffsetSec = getSample(endEpochSec) + + if (startOffsetSec !== endOffsetSec) { + const split = getSplit(startEpochSec, endEpochSec) + pinch(split, startOffsetSec, endOffsetSec) + const transitionEpochSec = split[0] + + if ((compareNumbers(transitionEpochSec, epochSec) || 1) === direction) { + return transitionEpochSec + } + } + + startEpochSec += inc + endEpochSec += inc + } + } + + /* + everything outside of 'split' is know that transition doesn't happen + transition is the first reading of a new offset period + just one isolated sample doesn't make it known + */ + function pinch( + split: [number, number], + startOffsetSec: number, + endOffsetSec: number, + ): undefined + function pinch( + split: [number, number], + startOffsetSec: number, + endOffsetSec: number, + forEpochSec: number, + ): number + function pinch( + split: [number, number], + startOffsetSec: number, + endOffsetSec: number, + forEpochSec?: number, + ): number | undefined { + let offsetSec: number | undefined + let splitDurSec: number | undefined + + while ( + (forEpochSec === undefined || + (offsetSec = ( + forEpochSec < split[0] + ? startOffsetSec + : forEpochSec >= split[1] + ? endOffsetSec + : undefined + )) === undefined + ) && + (splitDurSec = split[1] - split[0]) + ) { + const middleEpochSec = split[0] + Math.floor(splitDurSec / 2) + const middleOffsetSec = computeOffsetSec(middleEpochSec) + + if (middleOffsetSec === endOffsetSec) { + split[1] = middleEpochSec + } else { // middleOffsetSec === startOffsetSec + split[0] = middleEpochSec + 1 + } + } + + return offsetSec + } + + return { getPossibleEpochSec, getOffsetSec, getTransition } +} + +function createSplitTuple(startEpochSec: number, endEpochSec: number): [number, number] { + return [startEpochSec, endEpochSec] +} + +function computePeriod(epochSec: number): [number, number] { + const startEpochSec = Math.floor(epochSec / periodDur) * periodDur + const endEpochSec = startEpochSec + periodDur + return [startEpochSec, endEpochSec] +} + +function createComputeOffsetSec(format: Intl.DateTimeFormat): ( + (epochSec: number) => number +) { + return (epochSec: number) => { + const intlParts = hashIntlFormatParts(format, epochSec * milliInSec) + const zonedEpochSec = isoArgsToEpochSec( + parseIntlYear(intlParts, isoCalendarId).year, + parseInt(intlParts.month), + parseInt(intlParts.day), + parseInt(intlParts.hour), + parseInt(intlParts.minute), + parseInt(intlParts.second), + ) + return zonedEpochSec - epochSec + } +} + +function buildIntlFormat(timeZoneId: string): Intl.DateTimeFormat { + // format will ALWAYS do gregorian. need to parse year + return new OrigDateTimeFormat(standardLocaleId, { + timeZone: timeZoneId, + era: 'short', + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + }) +} diff --git a/packages/temporal-polyfill/src/internal/timeZoneOps.ts b/packages/temporal-polyfill/src/internal/timeZoneOps.ts new file mode 100644 index 00000000..64703933 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/timeZoneOps.ts @@ -0,0 +1,274 @@ +import { DayTimeNano, addDayTimeNanoAndNumber, dayTimeNanoToNumber, diffDayTimeNanos } from './dayTimeNano' +import { IsoDateFields, IsoDateTimeFields, isoDateTimeFieldNamesAlpha, isoTimeFieldDefaults } from './calendarIsoFields' +import { epochNanoToIso, isoToEpochNano, isoToEpochNanoWithOffset } from './epochAndTime' +import { EpochDisambig, OffsetDisambig } from './options' +import { roundToMinute } from './round' +import { nanoInHour, nanoInUtcDay } from './units' +import { createLazyGenerator, pluckProps } from './utils' +import { moveByIsoDays } from './move' +import { ZonedDateTimeBranding, ZonedDateTimeSlots, createZonedDateTimeSlots } from './slots' +import { formatOffsetNano } from './formatIso' +import * as errorMessages from './errorMessages' + +export type OffsetNanosecondsOp = (epochNano: DayTimeNano) => number +export type PossibleInstantsOp = (isoFields: IsoDateTimeFields) => DayTimeNano[] + +export type TimeZoneOps = { + getOffsetNanosecondsFor: OffsetNanosecondsOp, + getPossibleInstantsFor: PossibleInstantsOp, +} + +export type SimpleTimeZoneOps = { + getOffsetNanosecondsFor: OffsetNanosecondsOp, +} + +export type ZonedIsoDateTimeSlots = IsoDateTimeFields & { calendar: C, timeZone: T, offset: string } + +// ISO <-> Epoch conversions (on passed-in instances) +// ------------------------------------------------------------------------------------------------- + +// TODO: rename to be about 'slots' +export const zonedInternalsToIso = createLazyGenerator(_zonedInternalsToIso, WeakMap) + +/* +TODO: ensure returning in desc order, so we don't need to pluck +IMPORTANT: given timeZoneOps must be associated with the `internal` timeZone (even tho not present) +*/ +function _zonedInternalsToIso( + internals: { epochNanoseconds: DayTimeNano }, // goes first because key + timeZoneOps: SimpleTimeZoneOps, +): IsoDateTimeFields & { offsetNanoseconds: number } { + const { epochNanoseconds } = internals + const offsetNanoseconds = timeZoneOps.getOffsetNanosecondsFor(epochNanoseconds) + const isoDateTimeFields = epochNanoToIso(epochNanoseconds, offsetNanoseconds) + + return { + ...isoDateTimeFields, + offsetNanoseconds, + } +} + +/* +for getISOFields() +*/ +export function getZonedIsoDateTimeSlots( + getTimeZoneOps: (timeZoneSlot: T) => SimpleTimeZoneOps, + zonedDateTimeSlots: ZonedDateTimeSlots +): ZonedIsoDateTimeSlots { + const isoFields = zonedInternalsToIso(zonedDateTimeSlots as any, getTimeZoneOps(zonedDateTimeSlots.timeZone)) + + return { + calendar: zonedDateTimeSlots.calendar, + ...pluckProps(isoDateTimeFieldNamesAlpha, isoFields), + offset: formatOffsetNano(isoFields.offsetNanoseconds), + timeZone: zonedDateTimeSlots.timeZone, + } +} + +export function zonedEpochNanoToIso( + timeZoneOps: TimeZoneOps, + epochNano: DayTimeNano +): IsoDateTimeFields { + const offsetNano = timeZoneOps.getOffsetNanosecondsFor(epochNano) + return epochNanoToIso(epochNano, offsetNano) +} + +export function getMatchingInstantFor( + timeZoneOps: TimeZoneOps, + isoFields: IsoDateTimeFields, + offsetNano: number | undefined, + offsetDisambig: OffsetDisambig = OffsetDisambig.Reject, + epochDisambig: EpochDisambig = EpochDisambig.Compat, + epochFuzzy?: boolean, + hasZ?: boolean, +): DayTimeNano { + if (offsetNano !== undefined && offsetDisambig === OffsetDisambig.Use) { + // we ALWAYS use Z as a zero offset + if (offsetDisambig === OffsetDisambig.Use || hasZ) { + return isoToEpochNanoWithOffset(isoFields, offsetNano) + } + } + + const possibleEpochNanos = timeZoneOps.getPossibleInstantsFor(isoFields) + + if (offsetNano !== undefined && offsetDisambig !== OffsetDisambig.Ignore) { + const matchingEpochNano = findMatchingEpochNano( + possibleEpochNanos, + isoFields, + offsetNano, + epochFuzzy + ) + + if (matchingEpochNano !== undefined) { + return matchingEpochNano + } + + if (offsetDisambig === OffsetDisambig.Reject) { + throw new RangeError(errorMessages.invalidOffsetForTimeZone) + } + // else (offsetDisambig === 'prefer') ... + } + + if (hasZ) { + return isoToEpochNano(isoFields)! + } + + return getSingleInstantFor(timeZoneOps, isoFields, epochDisambig, possibleEpochNanos) +} + +export function getSingleInstantFor( + timeZoneOps: TimeZoneOps, + isoFields: IsoDateTimeFields, + disambig: EpochDisambig = EpochDisambig.Compat, + possibleEpochNanos: DayTimeNano[] = timeZoneOps.getPossibleInstantsFor(isoFields) +): DayTimeNano { + if (possibleEpochNanos.length === 1) { + return possibleEpochNanos[0] + } + + if (disambig === EpochDisambig.Reject) { + throw new RangeError(errorMessages.ambigOffset) + } + + // within a transition that jumps back + // ('compatible' means 'earlier') + if (possibleEpochNanos.length) { + return possibleEpochNanos[disambig === EpochDisambig.Later + ? 1 + : 0 // 'earlier' and 'compatible' + ] + } + + // within a transition that jumps forward... + // ('compatible' means 'later') + const zonedEpochNano = isoToEpochNano(isoFields)! + const gapNano = computeGapNear(timeZoneOps, zonedEpochNano) + + const shiftNano = gapNano * ( + disambig === EpochDisambig.Earlier + ? -1 + : 1) // 'later' or 'compatible' + + possibleEpochNanos = timeZoneOps.getPossibleInstantsFor( + epochNanoToIso(zonedEpochNano, shiftNano), + ) + + return possibleEpochNanos[disambig === EpochDisambig.Earlier + ? 0 + : possibleEpochNanos.length - 1 // 'later' or 'compatible' + ] +} + +function findMatchingEpochNano( + possibleEpochNanos: DayTimeNano[], + isoDateTimeFields: IsoDateTimeFields, + offsetNano: number, + fuzzy?: boolean +): DayTimeNano | undefined { + const zonedEpochNano = isoToEpochNano(isoDateTimeFields)! + + if (fuzzy) { + offsetNano = roundToMinute(offsetNano) + } + + for (const possibleEpochNano of possibleEpochNanos) { + let possibleOffsetNano = dayTimeNanoToNumber( + diffDayTimeNanos(possibleEpochNano, zonedEpochNano) + ) + + if (fuzzy) { + possibleOffsetNano = roundToMinute(possibleOffsetNano) + } + + if (possibleOffsetNano === offsetNano) { + return possibleEpochNano + } + } +} + +function computeGapNear( + timeZoneOps: SimpleTimeZoneOps, + zonedEpochNano: DayTimeNano +): number { + const startOffsetNano = timeZoneOps.getOffsetNanosecondsFor( + addDayTimeNanoAndNumber(zonedEpochNano, -nanoInUtcDay) + ) + const endOffsetNano = timeZoneOps.getOffsetNanosecondsFor( + addDayTimeNanoAndNumber(zonedEpochNano, nanoInUtcDay) + ) + return endOffsetNano - startOffsetNano +} + +// Computations (on passed-in instances) +// ------------------------------------------------------------------------------------------------- + +export function computeStartOfDay( + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + zonedDateTimeSlots: ZonedDateTimeSlots, +): ZonedDateTimeSlots { + let { epochNanoseconds, timeZone, calendar } = zonedDateTimeSlots + const timeZoneOps = getTimeZoneOps(timeZone) + + const isoFields = { + ...zonedInternalsToIso(zonedDateTimeSlots as any, timeZoneOps), + ...isoTimeFieldDefaults, + } + + epochNanoseconds = getMatchingInstantFor( + timeZoneOps, + isoFields, + undefined, // offsetNanoseconds + OffsetDisambig.Reject, + EpochDisambig.Compat, + true, // fuzzy + ) + + return createZonedDateTimeSlots( + epochNanoseconds, + timeZone, + calendar, + ) +} + +export function computeHoursInDay( + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + zonedDateTimeSlots: ZonedDateTimeSlots, +): number { + const timeZoneOps = getTimeZoneOps(zonedDateTimeSlots.timeZone) + + return computeNanosecondsInDay( + timeZoneOps, + zonedInternalsToIso(zonedDateTimeSlots as any, timeZoneOps), + ) / nanoInHour +} + +export function computeNanosecondsInDay( + timeZoneOps: TimeZoneOps, + isoFields: IsoDateFields +): number { + isoFields = { ...isoFields, ...isoTimeFieldDefaults } + + // TODO: have getSingleInstantFor accept IsoDateFields? + const epochNano0 = getSingleInstantFor(timeZoneOps, { ...isoFields, ...isoTimeFieldDefaults, }) + const epochNano1 = getSingleInstantFor(timeZoneOps, { ...moveByIsoDays(isoFields, 1), ...isoTimeFieldDefaults }) + + const nanoInDay = dayTimeNanoToNumber( + diffDayTimeNanos(epochNano0, epochNano1) + ) + + if (nanoInDay <= 0) { + throw new RangeError(errorMessages.invalidProtocolResults) // 'Bad nanoseconds in day' + } + + return nanoInDay +} + +// Utils +// ------------------------------------------------------------------------------------------------- + +export function validateTimeZoneOffset(offsetNano: number): number { + if (Math.abs(offsetNano) >= nanoInUtcDay) { + throw new RangeError(errorMessages.outOfBoundsOffset) + } + return offsetNano +} diff --git a/packages/temporal-polyfill/src/internal/total.ts b/packages/temporal-polyfill/src/internal/total.ts new file mode 100644 index 00000000..090403ef --- /dev/null +++ b/packages/temporal-polyfill/src/internal/total.ts @@ -0,0 +1,121 @@ +import { DayTimeNano, dayTimeNanoToNumber, diffDayTimeNanos } from './dayTimeNano' +import { DayTimeUnit, Unit, UnitName, unitNanoMap } from './units' +import { DurationFields, durationFieldDefaults, durationFieldNamesAsc } from './durationFields' +import { DiffOps } from './calendarOps' +import { TimeZoneOps } from './timeZoneOps' +import { DurationSlots } from './slots' +import { TotalUnitOptionsWithRel, refineTotalOptions } from './optionsRefine' +import { MarkerSlots, getLargestDurationUnit, createMarkerSystem, MarkerSystem, spanDuration, MarkerToEpochNano, MoveMarker, DiffMarkers, queryDurationSign, durationFieldsToDayTimeNano, clearDurationFields } from './durationMath' +import * as errorMessages from './errorMessages' + +export function totalDuration( + refineRelativeTo: (relativeToArg: RA) => MarkerSlots | undefined, + getCalendarOps: (calendarSlot: C) => DiffOps, + getTimeZoneOps: (timeZoneSlot: T) => TimeZoneOps, + slots: DurationSlots, + options: TotalUnitOptionsWithRel | UnitName +): number { + const durationLargestUnit = getLargestDurationUnit(slots) + const [totalUnit, markerSlots] = refineTotalOptions(options, refineRelativeTo) + const maxLargestUnit = Math.max(totalUnit, durationLargestUnit) + + if ( + maxLargestUnit < Unit.Day || ( + maxLargestUnit === Unit.Day && + !(markerSlots && (markerSlots as any).epochNanoseconds) // has uniform days? + ) + ) { + return totalDayTimeDuration(slots, totalUnit as DayTimeUnit) + } + + if (!markerSlots) { + throw new RangeError(errorMessages.missingRelativeTo) + } + + const markerSystem = createMarkerSystem(getCalendarOps, getTimeZoneOps, markerSlots) as MarkerSystem + + return totalRelativeDuration( + ...spanDuration(slots, undefined, totalUnit, ...markerSystem), + totalUnit, + ...markerSystem + ) +} + +function totalRelativeDuration( + durationFields: DurationFields, + endEpochNano: DayTimeNano, + totalUnit: Unit, + // marker system... + marker: M, + markerToEpochNano: MarkerToEpochNano, + moveMarker: MoveMarker, + diffMarkers?: DiffMarkers +): number { + const sign = queryDurationSign(durationFields) + + const [epochNano0, epochNano1] = clampRelativeDuration( + clearDurationFields(durationFields, totalUnit - 1), + totalUnit, + sign, + // marker system... + marker, + markerToEpochNano, + moveMarker + ) + + const frac = computeEpochNanoFrac(epochNano0, epochNano1, endEpochNano) + return durationFields[durationFieldNamesAsc[totalUnit]] + frac * sign +} + +function totalDayTimeDuration( + durationFields: DurationFields, + totalUnit: DayTimeUnit +): number { + return totalDayTimeNano( + durationFieldsToDayTimeNano(durationFields, Unit.Day), + totalUnit + ) +} + +// Utils for points-within-intervals +// ------------------------------------------------------------------------------------------------- + +export function totalDayTimeNano( + dayTimeNano: DayTimeNano, + totalUnit: DayTimeUnit, +): number { + return dayTimeNanoToNumber(dayTimeNano, unitNanoMap[totalUnit], true) // exact +} + +export function clampRelativeDuration( + durationFields: DurationFields, + clampUnit: Unit, + clampDistance: number, + // marker system... + marker: M, + markerToEpochNano: MarkerToEpochNano, + moveMarker: MoveMarker, +) { + const clampDurationFields = { + ...durationFieldDefaults, + [durationFieldNamesAsc[clampUnit]]: clampDistance, + } + const marker0 = moveMarker(marker, durationFields) + const marker1 = moveMarker(marker0, clampDurationFields) + const epochNano0 = markerToEpochNano(marker0) + const epochNano1 = markerToEpochNano(marker1) + return [epochNano0, epochNano1] +} + +export function computeEpochNanoFrac( + epochNano0: DayTimeNano, + epochNano1: DayTimeNano, + epochNanoProgress: DayTimeNano, +): number { + const denom = dayTimeNanoToNumber(diffDayTimeNanos(epochNano0, epochNano1)) + if (!denom) { + throw new RangeError(errorMessages.invalidProtocolResults) + } + const numer = dayTimeNanoToNumber(diffDayTimeNanos(epochNano0, epochNanoProgress)) + return numer / denom +} diff --git a/packages/temporal-polyfill/src/internal/units.ts b/packages/temporal-polyfill/src/internal/units.ts new file mode 100644 index 00000000..e444ffca --- /dev/null +++ b/packages/temporal-polyfill/src/internal/units.ts @@ -0,0 +1,120 @@ +import { DayTimeNano } from './dayTimeNano' +import { divModTrunc, divTrunc, modTrunc } from './utils' + +/* +TODO: use short names? +*/ +export const enum Unit { + Nanosecond, // 0 + Microsecond, // 1 + Millisecond, // 2 + Second, // 3 + Minute, // 4 + Hour, // 5 + Day, // 6 + Week, // 7 + Month, // 8 + Year, // 9 +} + +export type UnitName = keyof typeof unitNameMap +// TODO: more convenient type for OR-ing with DurationFields (for plural?) + +export type TimeUnit = + Unit.Nanosecond | + Unit.Microsecond | + Unit.Millisecond | + Unit.Second | + Unit.Minute | + Unit.Hour + +export type DayTimeUnit = Unit.Day | TimeUnit + +// ------------------------------------------------------------------------------------------------- + +export const unitNameMap = { + nanosecond: Unit.Nanosecond, + microsecond: Unit.Microsecond, + millisecond: Unit.Millisecond, + second: Unit.Second, + minute: Unit.Minute, + hour: Unit.Hour, + day: Unit.Day, + week: Unit.Week, + month: Unit.Month, + year: Unit.Year, +} + +export const unitNamesAsc = Object.keys(unitNameMap) as (keyof typeof unitNameMap)[] + +// Nanoseconds +// ------------------------------------------------------------------------------------------------- + +export const secInDay = 86400 +export const milliInDay = 86400000 +export const milliInSec = 1000 + +export const nanoInMicro = 1000 // consolidate with other 1000 units +export const nanoInMilli = 1_000_000 +export const nanoInSec = 1_000_000_000 +export const nanoInMinute = 60_000_000_000 +export const nanoInHour = 3_600_000_000_000 +export const nanoInUtcDay = 86_400_000_000_000 + +export const unitNanoMap = [ + 1, // nano-in-nano + nanoInMicro, + nanoInMilli, + nanoInSec, + nanoInMinute, + nanoInHour, + nanoInUtcDay, +] + +// Utils +// ------------------------------------------------------------------------------------------------- + +/* +When largestUnit=hour, returned `Day` value is "days worth of hours" +*/ +export function givenFieldsToDayTimeNano( + fields: Record, + largestUnit: DayTimeUnit, + fieldNames: K[], +): DayTimeNano { + let timeNano = 0 + let days = 0 + + for (let unit = Unit.Nanosecond; unit <= largestUnit; unit++) { + const fieldVal = fields[fieldNames[unit]] + const unitNano = unitNanoMap[unit] + + // absorb whole-days from current unit, to prevent overflow + const unitInDay = nanoInUtcDay / unitNano + const [unitDays, leftoverUnits] = divModTrunc(fieldVal, unitInDay) + + timeNano += leftoverUnits * unitNano + days += unitDays + } + + // absorb whole-days from timeNano + const [timeDays, leftoverNano] = divModTrunc(timeNano, nanoInUtcDay) + return [days + timeDays, leftoverNano] +} + +export function nanoToGivenFields( + nano: number, + largestUnit: DayTimeUnit, // stops populating at this unit + fieldNames: (keyof F)[], +): { [Key in keyof F]?: number } { + const fields = {} as { [Key in keyof F]: number } + + for (let unit = largestUnit; unit >= Unit.Nanosecond; unit--) { + const divisor = unitNanoMap[unit] + + fields[fieldNames[unit]] = divTrunc(nano, divisor) + nano = modTrunc(nano, divisor) + } + + return fields +} diff --git a/packages/temporal-polyfill/src/internal/utils.ts b/packages/temporal-polyfill/src/internal/utils.ts new file mode 100644 index 00000000..2b36e6c7 --- /dev/null +++ b/packages/temporal-polyfill/src/internal/utils.ts @@ -0,0 +1,386 @@ +import { Overflow } from './options' +import * as errorMessages from './errorMessages' + +export type FilterPropValues = { + [K in keyof P as (P[K] extends F ? K : never)]: P[K] +} + +export function bindArgs( + f: (...args: [...BA, ...DA]) => R, + ...boundArgs: BA +): (...dynamicArgs: DA) => R { + return (...dynamicArgs: DA) => { + return f(...boundArgs, ...dynamicArgs) + } +} + +/* +Will linter make [any] for bind okay? If so, this is unnecessary +*/ +export type BoundArg = any + +/* +For programmatically-generated functions that have overly-complex inferred types +*/ +export type Callable = (...args: any[]) => any + +export type Classlike = any + +const objectLikeRE = /object|function/ + +export function isObjectLike(arg: unknown): arg is {} { + return arg !== null && objectLikeRE.test(typeof arg) +} + +/* +TODO: abandon this? See mapPropNames note. +*/ +export function mapProps( + transformer: (propVal: P[keyof P], propName: keyof P, extraArg?: E) => R, + props: P, + extraArg?: E +): { [K in keyof P]: R } { + const res = {} as { [K in keyof P]: R } + + for (const propName in props) { + res[propName] = transformer(props[propName], propName, extraArg) + } + + return res +} + + +export function zipProps

(propNamesRev: (keyof P)[], args: (P[keyof P])[]): P { + const res = {} as any + let i = propNamesRev.length + + for (const arg of args) { + res[propNamesRev[--i]] = arg + } + + return res +} + +/* +TODO: audit uses of this contributing to HIGHER bundle size. Just inline? Often more readable. +See createAdapterCompoundOps/createAdapterOps. Bigger after using mapPropNames. +*/ +export function mapPropNames( + generator: (propName: keyof P, i: number, extraArg?: E) => R, + propNames: (keyof P)[], + extraArg?: E +): { [K in keyof P]: R } { + const props = {} as { [K in keyof P]: R } + + for (let i = 0; i < propNames.length; i++) { + const propName = propNames[i] + props[propName] = generator(propName, i, extraArg) + } + + return props +} + +export const mapPropNamesToIndex = bindArgs( + mapPropNames, + (propVal: any, i: number) => i, +) as ( +

(propNames: (keyof P)[]) => { [K in keyof P]: number } +) + +export const mapPropNamesToConstant = bindArgs( + mapPropNames, + (propVal: unknown, i: number, constant: unknown) => constant, +) as ( + (propNames: (keyof P)[], c: C) => { [K in keyof P]: C } +) + +export function remapProps( + oldNames: (keyof O)[], + newNames: (keyof N)[], + oldProps: O +): N { + const newProps = {} as N + + for (let i = 0; i < oldNames.length; i++) { + newProps[newNames[i]] = oldProps[oldNames[i]] as any + } + + return newProps +} + +export function pluckProps

(propNames: (keyof P)[], props: P): P { + const res = {} as P + + for (const propName of propNames) { + res[propName] = props[propName] + } + + return res +} + +export function excludePropsByName( + props: P, + propNames: Set +): Omit { + const filteredProps = {} as any + + for (const propName in props) { + if (!propNames.has(propName)) { + filteredProps[propName] = props[propName] + } + } + + return filteredProps +} + +export function excludeUndefinedProps

(props: P): Partial

{ + props = { ...props } + const propNames = Object.keys(props) as (keyof P)[] + + for (const propName of propNames) { + if (props[propName] === undefined) { + delete props[propName] + } + } + + return props +} + +export function hasAnyPropsByName

( + props: P, + names: (keyof P)[] +): boolean { + for (const name of names) { + if (name in props) { + return true + } + } + return false +} + +export function hasAllPropsByName

( + props: P, + names: (keyof P)[] +): boolean { + for (const name of names) { + if (!(name in props)) { + return false + } + } + return true +} + +export function allFieldsEqual(fieldNames: string[], obj0: any, obj1: any): boolean { + for (const fieldName of fieldNames) { + if (obj0[fieldName] !== obj1[fieldName]) { + return false + } + } + return true +} + +// interface MapInterface { +// has(key: K): boolean +// get(key: K): V, +// set(key: K, val: V): void +// } + +export function createLazyGenerator( + generator: (key: K, ...otherArgs: A) => V, + MapClass: { new(): any } = Map, // TODO: better type +): ( + (key: K, ...otherArgs: A) => V +) { + const map = new MapClass() + + return (key: K, ...otherArgs: A) => { + if (map.has(key)) { + return map.get(key) as V + } else { + const val = generator(key, ...otherArgs) + map.set(key, val) + return val + } + } +} + +// descriptor stuff +// ---------------- + +export function createNameDescriptors(name: string) { + return createPropDescriptors({ name }, true) +} + +export function createPropDescriptors( + propVals: { [propName: string]: unknown }, + readonly?: boolean, +): PropertyDescriptorMap { + return mapProps((value) => ({ + value, + configurable: true, + writable: !readonly, + }), propVals) +} + +export function createGetterDescriptors( + getters: { [propName: string]: () => unknown }, +): PropertyDescriptorMap { + return mapProps((getter) => ({ + get: getter, + configurable: true, + }), getters) +} + +export function createStringTagDescriptors(value: string): { + // crazy + [Symbol.toStringTag]: { + value: string, + configurable: true, + }, +} { + return { + [Symbol.toStringTag]: { + value, + configurable: true, + }, + } +} + +// former lang +// ----------- + +export function identityFunc(arg: T): T { + return arg +} + +export function noop(): void { +} + +export function padNumber(digits: number, num: number): string { + return String(num).padStart(digits, '0') +} + +export const padNumber2 = bindArgs(padNumber, 2) + +export type NumSign = -1 | 0 | 1 + +/* +-1 if a comes before b + 0 if equal + 1 if a comes after b +*/ +export function compareNumbers(a: number, b: number): NumSign { + return Math.sign(a - b) as NumSign +} + +/* +min/max are inclusive +*/ +export function clampNumber(num: number, min: number, max: number): number { + return Math.min(Math.max(num, min), max) +} + +export function clampEntity( + entityName: string, + num: number, + min: number, + max: number, + overflow?: Overflow, +): number { + const clamped = clampNumber(num, min, max) + + if (overflow && num !== clamped) { + throw new RangeError(errorMessages.numberOutOfRange(entityName, num, min, max)) + } + + return clamped +} + +export function clampProp

( + props: P, + propName: keyof FilterPropValues & string, + min: number, + max: number, + overflow?: Overflow, +): number { + return clampEntity(propName, getDefinedProp(props, propName), min, max, overflow) +} + +export function getDefinedProp(props: any, propName: string): any { + const propVal = props[propName] + if (propVal === undefined) { + throw new TypeError(errorMessages.missingField(propName)) + } + return propVal +} + +export function divModFloor(num: number, divisor: number): [number, number] { + const quotient = Math.floor(num / divisor) + const remainder = modFloor(num, divisor) + return [quotient, remainder] +} + +export function modFloor(num: number, divisor: number): number { + return (num % divisor + divisor) % divisor +} + +export function divModTrunc(num: number, divisor: number): [number, number] { + return [ + divTrunc(num, divisor), + modTrunc(num, divisor), + ] +} + +/* +FIX-FOR: using Math.trunc often results in -0 +Only useful for Numbers. BigInts don't have this problem +NOTE: anywhere else Math.trunc is directly used, do ||0 +*/ +export function divTrunc(num: number, divisor: number): number { + return Math.trunc(num / divisor) || 0 +} + +/* +FIX-FOR: using % often results in -0 +Only useful for Numbers. BigInts don't have this problem +NOTE: anywhere else % is directly used, do ||0 +*/ +export function modTrunc(num: number, divisor: number): number { + return (num % divisor) || 0 +} + +// rounding +// -------- + +export function roundExpand(num: number): number { + return num < 0 ? Math.floor(num) : Math.ceil(num) +} + +/* +Similar to Math.round, but rounds negative half-numbers to floor (-1.5 => -2) +*/ +export function roundHalfExpand(num: number): number { + return Math.sign(num) * Math.round(Math.abs(num)) || 0 // prevent -0 +} + +export function roundHalfFloor(num: number): number { + return hasHalf(num) ? Math.floor(num) : Math.round(num) +} + +export function roundHalfCeil(num: number): number { + return hasHalf(num) ? Math.ceil(num) : Math.round(num) +} + +export function roundHalfTrunc(num: number): number { + return hasHalf(num) ? (Math.trunc(num) || 0) : Math.round(num) +} + +export function roundHalfEven(num: number): number { + return hasHalf(num) + ? (num = Math.trunc(num) || 0) + (num % 2) + : Math.round(num) +} + +function hasHalf(num: number): boolean { + return Math.abs(num % 1) === 0.5 +} diff --git a/packages/temporal-polyfill/src/native/date.ts b/packages/temporal-polyfill/src/native/date.ts deleted file mode 100644 index 437abab4..00000000 --- a/packages/temporal-polyfill/src/native/date.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { Instant } from '../public/instant' - -// types - -export interface DateTemporalMethods { - toTemporalInstant(): Instant -} - -export type DateWithTemporal = Date & DateTemporalMethods - -// implementation - -export function toTemporalInstant(this: Date): Temporal.Instant { - return Instant.fromEpochMilliseconds(this.valueOf()) -} diff --git a/packages/temporal-polyfill/src/native/intlFactory.ts b/packages/temporal-polyfill/src/native/intlFactory.ts deleted file mode 100644 index bb1d675d..00000000 --- a/packages/temporal-polyfill/src/native/intlFactory.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { isoCalendarID } from '../calendarImpl/isoCalendarImpl' -import { zeroISOTimeFields } from '../dateUtils/dayAndTime' -import { isoFieldsToEpochMilli } from '../dateUtils/epoch' -import { createDateTime } from '../public/plainDateTime' -import { TimeZone } from '../public/timeZone' -import { OrigDateTimeFormat } from './intlUtils' - -// factory types - -export interface BaseEntity { - calendar?: Temporal.CalendarProtocol - timeZone?: Temporal.TimeZoneProtocol -} - -export interface FormatFactory { - buildKey: KeyFactory - buildFormat: (calendarID: string, timeZoneID: string) => Intl.DateTimeFormat - buildEpochMilli: (entity: Entity) => number -} - -export type FormatFactoryFactory = ( - locales: string[], - options: Intl.DateTimeFormatOptions, -) => FormatFactory - -// zoned format factory - -interface ZonedEntity extends BaseEntity { - epochMilliseconds: number -} - -export function createZonedFormatFactoryFactory( - greedyDefaults: Intl.DateTimeFormatOptions, - nonGreedyDefaults: Intl.DateTimeFormatOptions, - finalOptions: Intl.DateTimeFormatOptions, -): FormatFactoryFactory { - return (locales: string[], options: Intl.DateTimeFormatOptions): FormatFactory => { - const defaults = anyDefaultsOverridden(greedyDefaults, options) - ? {} - : { ...greedyDefaults, ...nonGreedyDefaults } - - function buildFormat(calendarID: string, timeZoneID: string): Intl.DateTimeFormat { - return new OrigDateTimeFormat(locales, { - calendar: calendarID, - timeZone: timeZoneID || undefined, // empty string should mean current timezone - ...defaults, - ...options, - ...finalOptions, - }) - } - - return { - buildKey: createKeyFactory(locales, options, false), - buildFormat, - buildEpochMilli: getEpochMilliFromZonedEntity, - } - } -} - -function getEpochMilliFromZonedEntity(entity: ZonedEntity): number { - return entity.epochMilliseconds -} - -// plain format factory - -interface PlainEntity extends BaseEntity { - getISOFields: () => Temporal.PlainDateISOFields // might have time fields too -} - -export function createPlainFormatFactoryFactory( - greedyDefaults: Intl.DateTimeFormatOptions, - finalOptions: Intl.DateTimeFormatOptions, - strictCalendar?: boolean, -): FormatFactoryFactory { - return (locales: string[], options: Intl.DateTimeFormatOptions): FormatFactory => { - const defaults = anyDefaultsOverridden(greedyDefaults, options) ? {} : greedyDefaults - - function buildFormat(calendarID: string, timeZoneID: string) { - return new OrigDateTimeFormat(locales, { - calendar: calendarID, - ...defaults, - ...options, - ...finalOptions, - timeZone: timeZoneID, // guaranteed to be defined because of above 'UTC' - timeZoneName: undefined, // never show timeZone name - }) - } - - // TODO: investigate if Intl.DateTimeFormat allows passing in an object/protocol `TimeZone` - // value. If so, we shouldn't call `new TimeZone`, but instead ensureTimeZoneProtocol - - return { - buildKey: createKeyFactory(locales, options, strictCalendar), - buildFormat, - buildEpochMilli: options.timeZone !== undefined - ? computeEpochMilliViaTimeZone.bind(null, new TimeZone(options.timeZone)) - : computeEpochMilliViaISO, - } - } -} - -function computeEpochMilliViaTimeZone(timeZone: TimeZone, entity: PlainEntity): number { - const plainDateTime = createDateTime({ // necessary? pass directly into getInstantFor? - ...zeroISOTimeFields, - ...entity.getISOFields(), - }) - return timeZone.getInstantFor(plainDateTime).epochMilliseconds -} - -function computeEpochMilliViaISO(entity: PlainEntity): number { - return isoFieldsToEpochMilli(entity.getISOFields()) -} - -// cached format factory - -export type CachedFormatFactory = { - buildFormat: (entity: Entity, otherEntity?: Entity) => Intl.DateTimeFormat - buildEpochMilli: (entity: Entity) => number -} - -export function buildCachedFormatFactory( - formatFactory: FormatFactory, -): CachedFormatFactory { - const cachedFormats: { [key: string]: Intl.DateTimeFormat } = {} - - function buildFormat(entity: Entity, otherEntity?: Entity): Intl.DateTimeFormat { - const keys = formatFactory.buildKey(entity, otherEntity) - const key = keys.join('|') - - return cachedFormats[key] || - (cachedFormats[key] = formatFactory.buildFormat(...keys)) - } - - return { - buildFormat, - buildEpochMilli: formatFactory.buildEpochMilli, - } -} - -// keys - -export type KeyFactory = ( - entity: Entity, - otherEntity?: Entity -) => [string, string] // [calendarID, timeZoneID] - -function createKeyFactory( - locales: string[], - options: Intl.DateTimeFormatOptions, - strictCalendar: boolean | undefined, -): KeyFactory { - const optionsCalendarID = options.calendar ?? extractUnicodeCalendar(locales) - const optionsTimeZoneID = options.timeZone - - return function(entity: Entity, otherEntity?: Entity): [string, string] { - const entityCalendarID = entity.calendar?.id - const entityTimeZoneID = entity.timeZone?.id - - if (otherEntity) { - // TODO: use ensureCalendarsEqual somehow? - if (otherEntity.calendar?.id !== entityCalendarID) { - throw new RangeError('Mismatching calendar') - } - if (otherEntity.timeZone?.id !== entityTimeZoneID) { - throw new RangeError('Mismatching timeZone') - } - } - - if ( - (strictCalendar || entityCalendarID !== isoCalendarID) && - entityCalendarID !== undefined && - optionsCalendarID !== undefined && - optionsCalendarID !== entityCalendarID - ) { - throw new RangeError('Non-iso calendar mismatch') - } - - if ( - entityTimeZoneID !== undefined && - optionsTimeZoneID !== undefined && - optionsTimeZoneID !== entityTimeZoneID - ) { - throw new RangeError('Given timeZone must agree') - } - - const calendarID = optionsCalendarID || entityCalendarID || isoCalendarID - const timeZoneID = optionsTimeZoneID || entityTimeZoneID || 'UTC' - - return [calendarID, timeZoneID] - } -} - -function extractUnicodeCalendar(locales: string[]): string | undefined { - for (const locale of locales) { - const m = locale.match(/-u-ca-(.*)$/) - if (m) { - return m[1] - } - } - - return undefined -} - -// general utils -// TODO: move elsewhere? - -function anyDefaultsOverridden(defaults: any, overrides: any): boolean { - for (const propName in defaults) { - if (overrides[propName] !== undefined) { - return true - } - } - return false -} diff --git a/packages/temporal-polyfill/src/native/intlMixins.ts b/packages/temporal-polyfill/src/native/intlMixins.ts deleted file mode 100644 index 5002f699..00000000 --- a/packages/temporal-polyfill/src/native/intlMixins.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { formatFactoryFactorySymbol } from '../dateUtils/abstract' -import { BaseEntity, FormatFactoryFactory } from './intlFactory' -import { LocalesArg, normalizeAndCopyLocalesArg } from './intlUtils' - -export interface ToLocaleStringMethods { - toLocaleString(localesArg?: LocalesArg, options?: Intl.DateTimeFormatOptions): string -} - -export function mixinLocaleStringMethods( - ObjClass: { prototype: Entity }, - buildFormatFactory: FormatFactoryFactory, -): void { - ObjClass.prototype.toLocaleString = function( - this: Entity, - localesArg?: LocalesArg, - options?: Intl.DateTimeFormatOptions, - ): string { - const formatFactory = buildFormatFactory( - normalizeAndCopyLocalesArg(localesArg), - options || {}, - ) - return formatFactory.buildFormat( - ...formatFactory.buildKey(this), - ).format( - formatFactory.buildEpochMilli(this), - ) - } - - ;(ObjClass.prototype as any)[formatFactoryFactorySymbol] = buildFormatFactory -} - -export function extractFormatFactoryFactory( - obj: any, -): FormatFactoryFactory | undefined { // HACK - return obj?.[formatFactoryFactorySymbol] -} diff --git a/packages/temporal-polyfill/src/native/intlTemporal.ts b/packages/temporal-polyfill/src/native/intlTemporal.ts deleted file mode 100644 index 6a7aa35e..00000000 --- a/packages/temporal-polyfill/src/native/intlTemporal.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { Intl as IntlSpec } from 'temporal-spec' -import { CachedFormatFactory, FormatFactoryFactory, buildCachedFormatFactory } from './intlFactory' -import { extractFormatFactoryFactory } from './intlMixins' -import { LocalesArg, flattenOptions, normalizeAndCopyLocalesArg } from './intlUtils' - -type DateArg = IntlSpec.Formattable | number - -const origLocalesSymbol = Symbol() -const origOptionsSymbol = Symbol() -const factoryMapSymbol = Symbol() - -/* -We can't monkeypatch Intl.DateTimeFormat because this auto-bound .format would be inaccessible -to our override method. - -TODO: accept timeZone and calendar OBJECTS in options -*/ -interface _DateTimeFormat { - [origLocalesSymbol]: string[] - [origOptionsSymbol]: Intl.DateTimeFormatOptions - [factoryMapSymbol]: Map, CachedFormatFactory> -} -class _DateTimeFormat extends Intl.DateTimeFormat { - constructor(localesArg?: LocalesArg, options?: IntlSpec.DateTimeFormatOptions) { - const normLocales = normalizeAndCopyLocalesArg(localesArg) - const normOptions = flattenOptions(options || {}) // so that props aren't accessed again - - super(normLocales, normOptions) - - this[origLocalesSymbol] = normLocales - this[origOptionsSymbol] = normOptions - this[factoryMapSymbol] = new Map() - } - - format(dateArg?: DateArg): string { - const parts = createSingleArgs(this, dateArg) - - // HACK to overcome .format being bounded - // See NOTE here: https://tc39.es/ecma402/#sec-intl.datetimeformat.prototype.format - if (parts[0] === this) { - return super.format(parts[1]) - } - - return parts[0].format(parts[1]) - } - - formatToParts(dateArg?: DateArg): Intl.DateTimeFormatPart[] { - return super.formatToParts.call(...createSingleArgs(this, dateArg)) - } - - formatRange( - startArg: T, - endArg: T, - ): string { - return super.formatRange.call(...createRangeArgs(this, startArg, endArg)) - } - - formatRangeToParts( - startArg: T, - endArg: T, - ): IntlSpec.DateTimeFormatRangePart[] { - return super.formatRangeToParts.call( - ...createRangeArgs(this, startArg, endArg), - ) as IntlSpec.DateTimeFormatRangePart[] - } -} - -// compliance with globalThis.Intl.DateTimeFormat -export const DateTimeFormat = _DateTimeFormat as ((typeof _DateTimeFormat) & { - // constructor without `new`. this comes for free - (locales?: string | string[], options?: IntlSpec.DateTimeFormatOptions): _DateTimeFormat; - - // retrofitted to accept better DateTimeFormatOptions - // TODO: implement this!? - supportedLocalesOf( - locales: string | string[], - options?: IntlSpec.DateTimeFormatOptions - ): string[]; -}) - -function createSingleArgs( - origDateTimeFormat: _DateTimeFormat, - dateArg: DateArg | undefined, -): [Intl.DateTimeFormat, Date | number | undefined] { - const buildFormatFactory = extractFormatFactoryFactory(dateArg) - - if (buildFormatFactory) { - const formatFactory = queryFormatFactoryForType(origDateTimeFormat, buildFormatFactory) - return [ - formatFactory.buildFormat(dateArg), - formatFactory.buildEpochMilli(dateArg), - ] - } - - return [origDateTimeFormat, dateArg as (Date | undefined)] -} - -function createRangeArgs( - origDateTimeFormat: _DateTimeFormat, - startArg: IntlSpec.Formattable, - endArg: IntlSpec.Formattable, -): [Intl.DateTimeFormat, Date, Date] { - const buildFormatFactory = extractFormatFactoryFactory(startArg) - const buildFormatFactoryOther = extractFormatFactoryFactory(endArg) - - if (buildFormatFactory !== buildFormatFactoryOther) { - throw new TypeError('Mismatch of types') - } - - if (buildFormatFactory) { - const formatFactory = queryFormatFactoryForType(origDateTimeFormat, buildFormatFactory) - return [ - formatFactory.buildFormat(startArg, endArg), - new Date(formatFactory.buildEpochMilli(startArg)), // TODO: sure it needs to be Date? - new Date(formatFactory.buildEpochMilli(endArg)), // " - ] - } - - return [origDateTimeFormat, startArg as Date, endArg as Date] -} - -function queryFormatFactoryForType( - origDateTimeFormat: _DateTimeFormat, - buildFormatFactory: FormatFactoryFactory, // HACK -): CachedFormatFactory { // HACK - const formatFactoryMap = origDateTimeFormat[factoryMapSymbol] - let formatFactory = formatFactoryMap.get(buildFormatFactory) - - if (!formatFactory) { - formatFactory = buildCachedFormatFactory( - buildFormatFactory( - origDateTimeFormat[origLocalesSymbol], - origDateTimeFormat[origOptionsSymbol], - ), - ) - formatFactoryMap.set(buildFormatFactory, formatFactory) - } - - return formatFactory -} diff --git a/packages/temporal-polyfill/src/native/intlUtils.ts b/packages/temporal-polyfill/src/native/intlUtils.ts deleted file mode 100644 index abc58914..00000000 --- a/packages/temporal-polyfill/src/native/intlUtils.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Intl as IntlSpec } from 'temporal-spec' -import { isObjectLike } from '../argParse/refine' - -export type LocalesArg = string | string[] - -// TODO: unify this as a class/const, to just export DateTimeFormat, -// and have whole src reference it only, not Intl.DateTimeFormat -export const OrigDateTimeFormat = Intl.DateTimeFormat - -export function normalizeAndCopyLocalesArg(localesArg: LocalesArg | undefined): string[] { - return ([] as string[]).concat(localesArg || []) -} - -// TODO: more efficient way to do this, mapping resolvedOptions -export function flattenOptions( - options: IntlSpec.DateTimeFormatOptions, -): Intl.DateTimeFormatOptions { - const newOptions: Intl.DateTimeFormatOptions = {} - - for (const name in options) { - let val = (options as any)[name] - - if (isObjectLike(val)) { - val = val.toString() - } - - (newOptions as any)[name] = val - } - - return newOptions -} diff --git a/packages/temporal-polyfill/src/public/calendar.ts b/packages/temporal-polyfill/src/public/calendar.ts deleted file mode 100644 index acdedadb..00000000 --- a/packages/temporal-polyfill/src/public/calendar.ts +++ /dev/null @@ -1,444 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { calendarFromObj, ensureCalendarsEqual, getCommonCalendar } from '../argParse/calendar' -import { dateFieldMap, monthDayFieldMap, yearMonthFieldMap } from '../argParse/fieldStr' -import { parseOverflowOption } from '../argParse/overflowHandling' -import { ensureOptionsObj, isObjectLike, refineFields } from '../argParse/refine' -import { ensureThisContext } from '../argParse/thisContext' -import { parseUnit } from '../argParse/unitStr' -import { checkEpochMilliBuggy } from '../calendarImpl/bugs' -import { CalendarImpl, CalendarImplFields, convertEraYear } from '../calendarImpl/calendarImpl' -import { queryCalendarImpl } from '../calendarImpl/calendarImplQuery' -import { isoCalendarID } from '../calendarImpl/isoCalendarImpl' -import { JsonMethods, ensureObj, mixinJsonMethods } from '../dateUtils/abstract' -import { - computeDayOfYear, - computeDaysInYear, - getExistingDateISOFields, - queryDateFields, - queryDateISOFields, -} from '../dateUtils/calendar' -import { diffDateFields } from '../dateUtils/diff' -import { computeISODayOfWeek, isoEpochLeapYear, isoToEpochMilli } from '../dateUtils/epoch' -import { attachStringTag } from '../dateUtils/mixins' -import { tryParseDateTime } from '../dateUtils/parse' -import { translateDate } from '../dateUtils/translate' -import { DAY, DateUnitInt, YEAR } from '../dateUtils/units' -import { computeWeekOfISOYear } from '../dateUtils/week' -import { createWeakMap } from '../utils/obj' -import { Duration, DurationArg, createDuration } from './duration' -import { PlainDate, PlainDateArg } from './plainDate' -import { PlainMonthDay } from './plainMonthDay' -import { PlainYearMonth } from './plainYearMonth' - -// FYI: the Temporal.CalendarLike type includes `string`, -// unlike many other object types - -const [getImpl, setImpl] = createWeakMap() - -export class Calendar implements Temporal.Calendar { - constructor(id: string) { - if (id === 'islamicc') { // deprecated... TODO: use conversion map - id = 'islamic-civil' - } - - setImpl(this, queryCalendarImpl(id)) - } - - static from(arg: Temporal.CalendarLike): Temporal.CalendarProtocol { - if (isObjectLike(arg)) { - return calendarFromObj(arg) - } - - const parsed = tryParseDateTime(String(arg), false, true) // allowZ=true - return new Calendar( - parsed // a date-time string? - ? parsed.calendar || isoCalendarID - : String(arg), // any other type of string - ) - } - - get id(): string { - return this.toString() - } - - era( - arg: Temporal.PlainDate | Temporal.PlainDateTime | Temporal.PlainDateLike | string, - ): string | undefined { - const isoFields = getExistingDateISOFields(arg, true) // disallowMonthDay=true - return isoToEpochNanoSafe( - getImpl(this), - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - ).era - } - - eraYear( - arg: Temporal.PlainDate | Temporal.PlainDateTime | Temporal.PlainDateLike | string, - ): number | undefined { - const isoFields = getExistingDateISOFields(arg, true) // disallowMonthDay=true - return isoToEpochNanoSafe( - getImpl(this), - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - ).eraYear - } - - year( - arg: - | Temporal.PlainDate - | Temporal.PlainDateTime - | Temporal.PlainYearMonth - | Temporal.PlainDateLike - | string, - ): number { - const isoFields = getExistingDateISOFields(arg, true) // disallowMonthDay=true - return isoToEpochNanoSafe( - getImpl(this), - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - ).year - } - - month( - arg: - | Temporal.PlainDate - | Temporal.PlainDateTime - | Temporal.PlainYearMonth - | Temporal.PlainMonthDay - | Temporal.PlainDateLike - | string, - ): number { - const isoFields = getExistingDateISOFields(arg, true) // disallowMonthDay=true - return isoToEpochNanoSafe( - getImpl(this), - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - ).month - } - - monthCode( - arg: - | Temporal.PlainDate - | Temporal.PlainDateTime - | Temporal.PlainYearMonth - | Temporal.PlainMonthDay - | Temporal.PlainDateLike - | string, - ): string { - const fields = queryDateFields(arg, this) - return getImpl(this).monthCode(fields.month, fields.year) - } - - day( - arg: - | Temporal.PlainDate - | Temporal.PlainDateTime - | Temporal.PlainMonthDay - | Temporal.PlainDateLike - | string, - ): number { - const isoFields = getExistingDateISOFields(arg) - return isoToEpochNanoSafe( - getImpl(this), - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - ).day - } - - dayOfWeek( - arg: Temporal.PlainDate | Temporal.PlainDateTime | Temporal.PlainDateLike | string, - ): number { - const isoFields = getExistingDateISOFields(arg, true) // disallowMonthDay=true - return computeISODayOfWeek(isoFields.isoYear, isoFields.isoMonth, isoFields.isoDay) - } - - dayOfYear( - arg: Temporal.PlainDate | Temporal.PlainDateTime | Temporal.PlainDateLike | string, - ): number { - const fields = queryDateFields(arg, this, true) // disallowMonthDay=true - return computeDayOfYear(getImpl(this), fields.year, fields.month, fields.day) - } - - weekOfYear( - arg: Temporal.PlainDate | Temporal.PlainDateTime | Temporal.PlainDateLike | string, - ): number { - const isoFields = getExistingDateISOFields(arg, true) // disallowMonthDay=true - return computeWeekOfISOYear( - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - 1, // TODO: document what this means - 4, // " - ) - } - - daysInWeek( - arg: Temporal.PlainDate | Temporal.PlainDateTime | Temporal.PlainDateLike | string, - ): number { - // will throw error if invalid type - getExistingDateISOFields(arg, true) // disallowMonthDay=true - - // All calendars seem to have 7-day weeks - return 7 - } - - daysInMonth( - arg: - | Temporal.PlainDate - | Temporal.PlainDateTime - | Temporal.PlainYearMonth - | Temporal.PlainDateLike - | string, - ): number { - const fields = queryDateFields(arg, this, true) // disallowMonthDay=true - return getImpl(this).daysInMonth(fields.year, fields.month) - } - - daysInYear( - arg: - | Temporal.PlainDate - | Temporal.PlainDateTime - | Temporal.PlainYearMonth - | Temporal.PlainDateLike - | string, - ): number { - const fields = queryDateFields(arg, this, true) // disallowMonthDay=true - return computeDaysInYear(getImpl(this), fields.year) - } - - monthsInYear( - arg: - | Temporal.PlainDate - | Temporal.PlainDateTime - | Temporal.PlainYearMonth - | Temporal.PlainDateLike - | string, - ): number { - const calFields = queryDateFields(arg, this, true) // disallowMonthDay=true - return getImpl(this).monthsInYear(calFields.year) - } - - inLeapYear( - arg: - | Temporal.PlainDate - | Temporal.PlainDateTime - | Temporal.PlainYearMonth - | Temporal.PlainDateLike - | string, - ): boolean { - return getImpl(this).inLeapYear(this.year(arg)) - } - - dateFromFields( - fields: Temporal.YearOrEraAndEraYear & Temporal.MonthOrMonthCode & { day: number }, - options?: Temporal.AssignmentOptions, - ): Temporal.PlainDate { - const refinedFields = refineFields(fields, dateFieldMap) - const isoFields = queryDateISOFields(refinedFields, getImpl(this), options) - - return new PlainDate( - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - this, - ) - } - - yearMonthFromFields( - fields: Temporal.YearOrEraAndEraYear & Temporal.MonthOrMonthCode, - options?: Temporal.AssignmentOptions, - ): Temporal.PlainYearMonth { - const refinedFields = refineFields(fields, yearMonthFieldMap) - const isoFields = queryDateISOFields({ ...refinedFields, day: 1 }, getImpl(this), options) - - return new PlainYearMonth( - isoFields.isoYear, - isoFields.isoMonth, - this, - isoFields.isoDay, - ) - } - - monthDayFromFields( - fields: Temporal.MonthCodeOrMonthAndYear & { day: number }, - options?: Temporal.AssignmentOptions, - ): Temporal.PlainMonthDay { - const impl = getImpl(this) - let { era, eraYear, year, month, monthCode, day } = refineFields(fields, monthDayFieldMap) - - if (day === undefined) { - throw new TypeError('required property \'day\' missing or undefined') - } - - if (monthCode !== undefined) { - year = isoEpochLeapYear - } else if (era !== undefined && eraYear !== undefined) { - year = convertEraYear(impl.id, eraYear, era) - } - - if (year === undefined) { - if (monthCode !== undefined) { - year = impl.guessYearForMonthDay(monthCode, day) - } else { - throw new TypeError('either year or monthCode required with month') - } - } - - const isoFields = queryDateISOFields( - { year, month: month!, monthCode: monthCode!, day }, // HACKs! - impl, - options, - ) - - return new PlainMonthDay( - isoFields.isoMonth, - isoFields.isoDay, - this, - impl.normalizeISOYearForMonthDay(isoFields.isoYear), - ) - } - - dateAdd( - dateArg: PlainDateArg, - durationArg: DurationArg, - options?: Temporal.ArithmeticOptions, - ): Temporal.PlainDate { - const impl = getImpl(this) - const date = ensureObj(PlainDate, dateArg, options) - const duration = ensureObj(Duration, durationArg) - const overflowHandling = parseOverflowOption(options) - const isoFields = translateDate(date, duration, impl, overflowHandling) - - return new PlainDate( - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - this, - ) - } - - dateUntil( - dateArg0: PlainDateArg, - dateArg1: PlainDateArg, - options?: Temporal.DifferenceOptions<'year' | 'month' | 'week' | 'day'>, - ): Temporal.Duration { - const impl = getImpl(this) - const d0 = ensureObj(PlainDate, dateArg0) - const d1 = ensureObj(PlainDate, dateArg1) - const largestUnitStr = ensureOptionsObj(options).largestUnit - const largestUnit = largestUnitStr === 'auto' - ? DAY // TODO: util for this? - : parseUnit(largestUnitStr, DAY, DAY, YEAR) - - ensureCalendarsEqual(this, getCommonCalendar(d0, d1)) - - return createDuration( - diffDateFields(d0, d1, impl, largestUnit), - ) - } - - /* - Given a date-type's core field names, returns the field names that should be - given to Calendar::yearMonthFromFields/monthDayFromFields/dateFromFields - */ - // TODO: for inFields, use Iterable - fields(inFields: string[]): string[] { - return inFields.slice() // copy - } - - /* - Given a date-instance, and fields to override, returns the fields that should be - given to Calendar::yearMonthFromFields/monthDayFromFields/dateFromFields - */ - // TODO: use Record - mergeFields(baseFields: any, additionalFields: any): any { - return mergeCalFields(baseFields, additionalFields) - } - - toString(): string { - return getImpl(this).id - } -} - -// mixins -export interface Calendar extends JsonMethods {} -mixinJsonMethods(Calendar) -// -export interface Calendar { [Symbol.toStringTag]: 'Temporal.Calendar' } -attachStringTag(Calendar, 'Calendar') -// -ensureThisContext(Calendar) - -export function createDefaultCalendar(): Calendar { - return new Calendar(isoCalendarID) -} - -// TODO: better types? -export function mergeCalFields(baseFields: any, additionalFields: any): any { - const merged = { ...baseFields, ...additionalFields } as any - - if (baseFields.year !== undefined) { - delete merged.era - delete merged.eraYear - delete merged.year - - let anyAdditionalYear = false - - if (additionalFields.era !== undefined || additionalFields.eraYear !== undefined) { - merged.era = additionalFields.era - merged.eraYear = additionalFields.eraYear - anyAdditionalYear = true - } - if (additionalFields.year !== undefined) { - merged.year = additionalFields.year - anyAdditionalYear = true - } - if (!anyAdditionalYear) { - merged.year = baseFields.year - } - } - - if (baseFields.monthCode !== undefined) { - delete merged.monthCode - delete merged.month - - let anyAdditionalMonth = false - - if (additionalFields.month !== undefined) { - merged.month = additionalFields.month - anyAdditionalMonth = true - } - if (additionalFields.monthCode !== undefined) { - merged.monthCode = additionalFields.monthCode - anyAdditionalMonth = true - } - if (!anyAdditionalMonth) { - merged.monthCode = baseFields.monthCode - } - } - - if (baseFields.day !== undefined) { - merged.day = additionalFields.day ?? baseFields.day - } - - return merged -} - -// utils - -// TODO: can we eliminate this now that it's checked in public date classes? -function isoToEpochNanoSafe( - calendarImpl: CalendarImpl, - isoYear: number, - isoMonth: number, - isoDay: number, -): CalendarImplFields { - const epochMilli = isoToEpochMilli(isoYear, isoMonth, isoDay) - checkEpochMilliBuggy(epochMilli, calendarImpl.id) - return calendarImpl.computeFields(epochMilli) -} diff --git a/packages/temporal-polyfill/src/public/duration.ts b/packages/temporal-polyfill/src/public/duration.ts deleted file mode 100644 index f35484fc..00000000 --- a/packages/temporal-polyfill/src/public/duration.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { parseDiffOptions } from '../argParse/diffOptions' -import { DurationToStringUnitInt, parseTimeToStringOptions } from '../argParse/isoFormatOptions' -import { ensureOptionsObj, isObjectLike } from '../argParse/refine' -import { parseTotalConfig } from '../argParse/totalOptions' -import { NoValueMethods, ensureObj, mixinNoValueMethods } from '../dateUtils/abstract' -import { compareDurations } from '../dateUtils/compare' -import { - DurationFields, - UnsignedDurationFields, - absDuration, - computeLargestDurationUnit, - negateDuration, - refineDurationNumbers, -} from '../dateUtils/durationFields' -import { processDurationFields } from '../dateUtils/fromAndWith' -import { formatDurationISO } from '../dateUtils/isoFormat' -import { attachStringTag } from '../dateUtils/mixins' -import { parseDuration } from '../dateUtils/parseDuration' -import { extractRelativeTo } from '../dateUtils/relativeTo' -import { roundDuration } from '../dateUtils/roundingDuration' -import { computeTotalUnits } from '../dateUtils/totalUnits' -import { addDurationFields } from '../dateUtils/translate' -import { NANOSECOND, SECOND, UnitInt, YEAR } from '../dateUtils/units' -import { LocalesArg } from '../native/intlUtils' -import { createWeakMap } from '../utils/obj' -import { PlainDateTimeArg } from './plainDateTime' -import { ZonedDateTimeArg } from './zonedDateTime' - -export type DurationArg = Temporal.Duration | Temporal.DurationLike | string - -// guaranteed options object -type DurationRoundingOptions = Temporal.DifferenceOptions & { - relativeTo?: ZonedDateTimeArg | PlainDateTimeArg -} - -const [getFields, setFields] = createWeakMap() - -export class Duration implements Temporal.Duration { - constructor( - years = 0, - months = 0, - weeks = 0, - days = 0, - hours = 0, - minutes = 0, - seconds = 0, - milliseconds = 0, - microseconds = 0, - nanoseconds = 0, - ) { - const numberFields = processDurationFields({ // TODO: overkill. does hasAnyProps - years, - months, - weeks, - days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - }) - setFields(this, refineDurationNumbers(numberFields)) - } - - static from(arg: DurationArg): Temporal.Duration { - return createDuration( - typeof arg === 'object' - ? processDurationFields(arg) - : parseDuration(arg), - ) - } - - static compare( - a: DurationArg, - b: DurationArg, - options?: Temporal.DurationArithmeticOptions, - ): Temporal.ComparisonResult { - return compareDurations( - ensureObj(Duration, a), - ensureObj(Duration, b), - extractRelativeTo(ensureOptionsObj(options).relativeTo), - ) - } - - get years(): number { return getFields(this).years } - get months(): number { return getFields(this).months } - get weeks(): number { return getFields(this).weeks } - get days(): number { return getFields(this).days } - get hours(): number { return getFields(this).hours } - get minutes(): number { return getFields(this).minutes } - get seconds(): number { return getFields(this).seconds } - get milliseconds(): number { return getFields(this).milliseconds } - get microseconds(): number { return getFields(this).microseconds } - get nanoseconds(): number { return getFields(this).nanoseconds } - get sign(): Temporal.ComparisonResult { return getFields(this).sign } - get blank(): boolean { return !this.sign } - - with(fields: Temporal.DurationLike): Temporal.Duration { - return createDuration({ - ...getFields(this), - ...processDurationFields(fields), - }) - } - - negated(): Temporal.Duration { - return createDuration(negateDuration(getFields(this))) - } - - abs(): Temporal.Duration { - return createDuration(absDuration(getFields(this))) - } - - add(other: DurationArg, options?: Temporal.DurationArithmeticOptions): Temporal.Duration { - return addDurations(this, ensureObj(Duration, other), options) - } - - subtract(other: DurationArg, options?: Temporal.DurationArithmeticOptions): Temporal.Duration { - return addDurations(this, negateDuration(ensureObj(Duration, other)), options) - } - - round(options: Temporal.DurationRoundTo): Temporal.Duration { - const optionsObj: DurationRoundingOptions = typeof options === 'string' - ? { smallestUnit: options } - : options - - if (!isObjectLike(optionsObj)) { - throw new TypeError('Must specify options') // best place for this? - } else if (optionsObj.largestUnit === undefined && optionsObj.smallestUnit === undefined) { - throw new RangeError('Must specify either largestUnit or smallestUnit') - } - - const defaultLargestUnit = computeLargestDurationUnit(this) - const diffConfig = parseDiffOptions( - optionsObj, - defaultLargestUnit, // largestUnitDefault - NANOSECOND, // smallestUnitDefault - NANOSECOND, // minUnit - YEAR, // maxUnit - true, // forDurationRounding - ) - - const relativeTo = extractRelativeTo((optionsObj as DurationRoundingOptions).relativeTo) - // weird - - return createDuration( - roundDuration(this, diffConfig, relativeTo, relativeTo ? relativeTo.calendar : undefined), - ) - } - - total(options: Temporal.DurationTotalOf): number { - const totalConfig = parseTotalConfig(options) - const relativeTo = extractRelativeTo(totalConfig.relativeTo) - - return computeTotalUnits( - this, - totalConfig.unit, - relativeTo, - relativeTo ? relativeTo.calendar : undefined, - ) - } - - toString(options?: Temporal.ToStringPrecisionOptions): string { - const formatConfig = parseTimeToStringOptions( - options, SECOND, - ) - return formatDurationISO(getFields(this), formatConfig) - } - - toLocaleString(_locales?: LocalesArg, _options?: unknown): string { - // the spec recommends this in the absence of Intl.DurationFormat - return this.toString() - } -} - -// mixins -export interface Duration extends NoValueMethods {} -mixinNoValueMethods(Duration) -// -export interface Duration { [Symbol.toStringTag]: 'Temporal.Duration' } -attachStringTag(Duration, 'Duration') - -export function createDuration(fields: UnsignedDurationFields): Duration { - return new Duration( - fields.years, - fields.months, - fields.weeks, - fields.days, - fields.hours, - fields.minutes, - fields.seconds, - fields.milliseconds, - fields.microseconds, - fields.nanoseconds, - ) -} - -function addDurations( - d0: DurationFields, - d1: DurationFields, - options?: Temporal.DurationArithmeticOptions, -): Duration { - const relativeTo = extractRelativeTo(ensureOptionsObj(options).relativeTo) - - return createDuration( - addDurationFields(d0, d1, relativeTo, relativeTo ? relativeTo.calendar : undefined), - ) -} diff --git a/packages/temporal-polyfill/src/public/instant.ts b/packages/temporal-polyfill/src/public/instant.ts deleted file mode 100644 index cb4d1420..00000000 --- a/packages/temporal-polyfill/src/public/instant.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { parseDiffOptions } from '../argParse/diffOptions' -import { OVERFLOW_REJECT } from '../argParse/overflowHandling' -import { ensureOptionsObj, isObjectLike } from '../argParse/refine' -import { parseRoundingOptions } from '../argParse/roundingOptions' -import { NoValueMethods, ensureObj, mixinNoValueMethods } from '../dateUtils/abstract' -import { compareEpochObjs } from '../dateUtils/compare' -import { constrainDateTimeISO } from '../dateUtils/constrain' -import { diffEpochNanos } from '../dateUtils/diff' -import { negateDuration } from '../dateUtils/durationFields' -import { epochNanoSymbol, isoFieldsToEpochNano } from '../dateUtils/epoch' -import { validateInstant } from '../dateUtils/isoFieldValidation' -import { ComputedEpochFields, attachStringTag, mixinEpochFields } from '../dateUtils/mixins' -import { parseZonedDateTime } from '../dateUtils/parse' -import { roundEpochNano } from '../dateUtils/rounding' -import { translateEpochNano } from '../dateUtils/translate' -import { - HOUR, - NANOSECOND, - SECOND, - nanoInMicro, - nanoInMilli, - nanoInSecond, -} from '../dateUtils/units' -import { createZonedFormatFactoryFactory } from '../native/intlFactory' -import { ToLocaleStringMethods, mixinLocaleStringMethods } from '../native/intlMixins' -import { LargeInt, LargeIntArgStrict, createLargeInt } from '../utils/largeInt' -import { Duration, createDuration } from './duration' -import { ZonedDateTime } from './zonedDateTime' - -export type InstantArg = Temporal.Instant | string - -type TranslateArg = Omit< -Temporal.Duration | Temporal.DurationLike, -'years' | 'months' | 'weeks' | 'days' -> | string - -type DiffOptions = Temporal.DifferenceOptions< -'hour' | 'minute' | 'second' | 'millisecond' | 'microsecond' | 'nanosecond' -> - -type RoundOptions = Temporal.RoundTo< -'hour' | 'minute' | 'second' | -'millisecond' | 'microsecond' | 'nanosecond' -> - -type ToZonedDateTimeOptions = { - timeZone: Temporal.TimeZoneLike - calendar: Temporal.CalendarLike -} - -export interface Instant { - [epochNanoSymbol]: LargeInt -} -export class Instant implements Temporal.Instant { - constructor(epochNanoseconds: LargeIntArgStrict) { - const epochNano = createLargeInt(epochNanoseconds, true) // strict=true - validateInstant(epochNano) - this[epochNanoSymbol] = epochNano - } - - static from(arg: InstantArg): Instant { // okay to have return-type be Instant? needed - if (arg instanceof Instant) { - return new Instant(arg.epochNanoseconds) - } - - const fields = parseZonedDateTime(String(arg)) - const offsetNano = fields.offsetNanoseconds - if (offsetNano === undefined) { - throw new RangeError('Must specify an offset') - } - - return new Instant( - isoFieldsToEpochNano(constrainDateTimeISO(fields, OVERFLOW_REJECT)) - .sub(offsetNano), - ) - } - - static fromEpochSeconds(epochSeconds: number): Temporal.Instant { - return new Instant(createLargeInt(epochSeconds).mult(nanoInSecond)) - } - - static fromEpochMilliseconds(epochMilliseconds: number): Temporal.Instant { - return new Instant(createLargeInt(epochMilliseconds).mult(nanoInMilli)) - } - - static fromEpochMicroseconds(epochMicroseconds: bigint): Temporal.Instant { - return new Instant(epochMicroseconds * BigInt(nanoInMicro)) - } - - static fromEpochNanoseconds(epochNanoseconds: bigint): Temporal.Instant { - return new Instant(epochNanoseconds) - } - - static compare(a: InstantArg, b: InstantArg): Temporal.ComparisonResult { - return compareEpochObjs( - ensureObj(Instant, a), - ensureObj(Instant, b), - ) - } - - add(durationArg: TranslateArg): Temporal.Instant { - return new Instant( - translateEpochNano(this[epochNanoSymbol], ensureObj(Duration, durationArg)), - ) - } - - subtract(durationArg: TranslateArg): Temporal.Instant { - return new Instant( - translateEpochNano(this[epochNanoSymbol], negateDuration(ensureObj(Duration, durationArg))), - ) - } - - until(other: InstantArg, options?: DiffOptions): Temporal.Duration { - return diffInstants(this, ensureObj(Instant, other), options) - } - - since(other: InstantArg, options?: DiffOptions): Temporal.Duration { - return diffInstants(ensureObj(Instant, other), this, options) - } - - round(options: RoundOptions): Temporal.Instant { - const roundingConfig = parseRoundingOptions(options, NANOSECOND, HOUR, true) - - return new Instant( - roundEpochNano(this[epochNanoSymbol], roundingConfig), - ) - } - - equals(other: InstantArg): boolean { - return !compareEpochObjs(this, ensureObj(Instant, other)) - } - - toString(options?: Temporal.InstantToStringOptions): string { - const timeZoneArg = ensureOptionsObj(options).timeZone - const zonedDateTime = this.toZonedDateTimeISO(timeZoneArg ?? 'UTC') // TODO: don't use util!!! - return zonedDateTime.toString({ - ...options, - offset: timeZoneArg === undefined ? 'never' : 'auto', - timeZoneName: 'never', - }) + (timeZoneArg === undefined ? 'Z' : '') - } - - toZonedDateTimeISO(timeZoneArg: Temporal.TimeZoneLike): Temporal.ZonedDateTime { - return new ZonedDateTime(this.epochNanoseconds, timeZoneArg) - } - - toZonedDateTime(options: ToZonedDateTimeOptions): Temporal.ZonedDateTime { - // TODO: more official options-processing utils for this - if (!isObjectLike(options)) { - throw new TypeError('Must specify options') - } else if (options.calendar === undefined) { - throw new TypeError('Must specify a calendar') - } else if (options.timeZone === undefined) { - throw new TypeError('Must specify a timeZone') - } - - return new ZonedDateTime( - this.epochNanoseconds, - options.timeZone, - options.calendar, - ) - } -} - -// mixins -export interface Instant extends NoValueMethods {} -mixinNoValueMethods(Instant) -// -export interface Instant { [Symbol.toStringTag]: 'Temporal.Instant' } -attachStringTag(Instant, 'Instant') -// -export interface Instant extends ComputedEpochFields {} -mixinEpochFields(Instant) -// -export interface Instant extends ToLocaleStringMethods {} -mixinLocaleStringMethods(Instant, createZonedFormatFactoryFactory({ - year: 'numeric', - month: 'numeric', - day: 'numeric', - weekday: undefined, - hour: 'numeric', - minute: '2-digit', - second: '2-digit', -}, { - timeZoneName: undefined, -}, {})) - -function diffInstants( - inst0: Instant, - inst1: Instant, - options: DiffOptions | undefined, -): Duration { - const diffConfig = parseDiffOptions( - options, - SECOND, - NANOSECOND, - NANOSECOND, - HOUR, - ) - - return createDuration( - diffEpochNanos(inst0[epochNanoSymbol], inst1[epochNanoSymbol], diffConfig), - ) -} diff --git a/packages/temporal-polyfill/src/public/now.ts b/packages/temporal-polyfill/src/public/now.ts deleted file mode 100644 index e844698c..00000000 --- a/packages/temporal-polyfill/src/public/now.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { isoCalendarID } from '../calendarImpl/isoCalendarImpl' -import { ensureObj } from '../dateUtils/abstract' -import { ISODateTimeFields } from '../dateUtils/isoFields' -import { attachStringTag } from '../dateUtils/mixins' -import { nanoInMilli } from '../dateUtils/units' -import { OrigDateTimeFormat } from '../native/intlUtils' -import { LargeInt, createLargeInt } from '../utils/largeInt' -import { Calendar } from './calendar' -import { Instant } from './instant' -import { createDate } from './plainDate' -import { createDateTime } from './plainDateTime' -import { createTime } from './plainTime' -import { TimeZone } from './timeZone' -import { - buildZonedDateTimeISOFields, - createZonedDateTimeFromFields, -} from './zonedDateTime' - -const _Now = { - zonedDateTimeISO: getZonedDateTimeISO, - zonedDateTime: getZonedDateTime, - plainDateTimeISO: getPlainDateTimeISO, - plainDateTime: getPlainDateTime, - plainDateISO: getPlainDateISO, - plainDate: getPlainDate, - plainTimeISO: getPlainTimeISO, - instant: getInstant, - timeZone: getTimeZone, -} - -attachStringTag(_Now, 'Now') - -export const Now = _Now as (typeof _Now & { [Symbol.toStringTag]: 'Temporal.Now' }) - -function getZonedDateTimeISO(timeZoneArg?: Temporal.TimeZoneLike): Temporal.ZonedDateTime { - return createZonedDateTimeFromFields(buidZonedFields(isoCalendarID, timeZoneArg)) -} - -function getZonedDateTime( - calendarArg: Temporal.CalendarLike, - timeZoneArg?: Temporal.TimeZoneLike, -): Temporal.ZonedDateTime { - return createZonedDateTimeFromFields(buidZonedFields(calendarArg, timeZoneArg)) -} - -function getPlainDateTimeISO(timeZoneArg?: Temporal.TimeZoneLike): Temporal.PlainDateTime { - return createDateTime(buidZonedFields(isoCalendarID, timeZoneArg)) -} - -function getPlainDateTime( - calendarArg: Temporal.CalendarLike, - timeZoneArg?: Temporal.TimeZoneLike, -): Temporal.PlainDateTime { - return createDateTime(buidZonedFields(calendarArg, timeZoneArg)) -} - -function getPlainDateISO(timeZoneArg?: Temporal.TimeZoneLike): Temporal.PlainDate { - return createDate(buidZonedFields(isoCalendarID, timeZoneArg)) -} - -function getPlainDate( - calendarArg: Temporal.CalendarLike, - timeZoneArg?: Temporal.TimeZoneLike, -): Temporal.PlainDate { - return createDate(buidZonedFields(calendarArg, timeZoneArg)) -} - -function getPlainTimeISO(timeZoneArg?: Temporal.TimeZoneLike): Temporal.PlainTime { - return createTime(buidZonedFields(isoCalendarID, timeZoneArg)) -} - -function getInstant(): Temporal.Instant { - return new Instant(getEpochNano()) -} - -function getTimeZone(): Temporal.TimeZone { - return new TimeZone(new OrigDateTimeFormat().resolvedOptions().timeZone) -} - -// utils - -function buidZonedFields( - calendarArg: Temporal.CalendarLike, - timeZoneArg: Temporal.TimeZoneLike = getTimeZone(), -): ISODateTimeFields & { - timeZone: Temporal.TimeZoneProtocol, - calendar: Temporal.CalendarProtocol, - } { - const timeZone = ensureObj(TimeZone, timeZoneArg) - return { - ...buildZonedDateTimeISOFields(getEpochNano(), timeZone)[0], - // build these in to buildZonedDateTimeISOFields? - timeZone, - calendar: ensureObj(Calendar, calendarArg), - } -} - -function getEpochNano(): LargeInt { - return createLargeInt(Date.now()).mult(nanoInMilli) -} diff --git a/packages/temporal-polyfill/src/public/plainDate.ts b/packages/temporal-polyfill/src/public/plainDate.ts deleted file mode 100644 index df48ff67..00000000 --- a/packages/temporal-polyfill/src/public/plainDate.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { getCommonCalendar } from '../argParse/calendar' -import { parseCalendarDisplayOption } from '../argParse/calendarDisplay' -import { parseDiffOptions } from '../argParse/diffOptions' -import { OVERFLOW_REJECT, parseOverflowOption } from '../argParse/overflowHandling' -import { - IsoMasterMethods, - ensureObj, - initIsoMaster, - mixinIsoMasterMethods, -} from '../dateUtils/abstract' -import { compareDateTimes } from '../dateUtils/compare' -import { constrainDateISO } from '../dateUtils/constrain' -import { zeroISOTimeFields } from '../dateUtils/dayAndTime' -import { diffDates } from '../dateUtils/diff' -import { processDateFromFields, processDateWithFields } from '../dateUtils/fromAndWith' -import { validateDate } from '../dateUtils/isoFieldValidation' -import { formatCalendarID, formatDateISO } from '../dateUtils/isoFormat' -import { - DateCalendarFields, - attachStringTag, - dateCalendarFields, - mixinCalendarFields, - mixinISOFields, -} from '../dateUtils/mixins' -import { parseDateTime } from '../dateUtils/parse' -import { refineBaseObj } from '../dateUtils/parseRefine' -import { DAY, DateUnitInt, YEAR } from '../dateUtils/units' -import { createPlainFormatFactoryFactory } from '../native/intlFactory' -import { ToLocaleStringMethods, mixinLocaleStringMethods } from '../native/intlMixins' -import { Calendar, createDefaultCalendar } from './calendar' -import { Duration, DurationArg, createDuration } from './duration' -import { createDateTime } from './plainDateTime' -import { PlainTime, PlainTimeArg, ensureLooseTime } from './plainTime' -import { createYearMonth } from './plainYearMonth' -import { TimeZone } from './timeZone' -import { createZonedDateTimeFromFields } from './zonedDateTime' - -export type PlainDateArg = Temporal.PlainDate | Temporal.PlainDateLike | string - -// inlined in the spec. not easy to get out -type ToZonedDateTimeOptions = Temporal.TimeZoneProtocol | string | { - timeZone: Temporal.TimeZoneLike - plainTime?: Temporal.PlainTime | Temporal.PlainTimeLike | string -} - -type DiffOptions = Temporal.DifferenceOptions<'year' | 'month' | 'week' | 'day'> - -export class PlainDate implements Temporal.PlainDate { - constructor( - isoYear: number, - isoMonth: number, - isoDay: number, - calendarArg: Temporal.CalendarLike = createDefaultCalendar(), - ) { - const constrained = constrainDateISO({ isoYear, isoMonth, isoDay }, OVERFLOW_REJECT) - const calendar = ensureObj(Calendar, calendarArg) - - validateDate(constrained, calendar.toString()) - - initIsoMaster(this, { - ...constrained, - calendar, - }) - } - - static from(arg: PlainDateArg, options?: Temporal.AssignmentOptions): Temporal.PlainDate { - parseOverflowOption(options) // unused, but need to validate, regardless of input type - - if (arg instanceof PlainDate) { - return createDate(arg.getISOFields()) // optimization - } - - if (typeof arg === 'object') { - return processDateFromFields(arg, options) - } - - return createDate(refineBaseObj(parseDateTime(String(arg)))) - } - - static compare(a: PlainDateArg, b: PlainDateArg): Temporal.ComparisonResult { - return compareDateTimes( - ensureObj(PlainDate, a), - ensureObj(PlainDate, b), - ) - } - - with(fields: Temporal.PlainDateLike, options?: Temporal.AssignmentOptions): Temporal.PlainDate { - return processDateWithFields(this, fields, options) - } - - withCalendar(calendarArg: Temporal.CalendarLike): Temporal.PlainDate { - const isoFields = this.getISOFields() - return new PlainDate( - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - calendarArg, - ) - } - - add(durationArg: DurationArg, options?: Temporal.ArithmeticOptions): Temporal.PlainDate { - return this.calendar.dateAdd(this, durationArg, options) - } - - subtract(durationArg: DurationArg, options?: Temporal.ArithmeticOptions): Temporal.PlainDate { - return this.calendar.dateAdd(this, ensureObj(Duration, durationArg).negated(), options) - } - - until(other: PlainDateArg, options?: DiffOptions): Temporal.Duration { - return diffPlainDates( - this, - ensureObj(PlainDate, other), - false, - options, - ) - } - - since(other: PlainDateArg, options?: DiffOptions): Temporal.Duration { - return diffPlainDates( - this, - ensureObj(PlainDate, other), - true, - options, - ) - } - - equals(other: PlainDateArg): boolean { - return !compareDateTimes(this, ensureObj(PlainDate, other)) - } - - toString(options?: Temporal.ShowCalendarOption): string { - const calendarDisplay = parseCalendarDisplayOption(options) - const fields = this.getISOFields() - - return formatDateISO(fields) + - formatCalendarID(fields.calendar.toString(), calendarDisplay) - } - - toZonedDateTime(options: ToZonedDateTimeOptions): Temporal.ZonedDateTime { - const refinedOptions = processToZonedDateTimeOptions(options) - const timeZone = ensureObj(TimeZone, refinedOptions.timeZone) - const plainTime = refinedOptions.plainTime === undefined - ? undefined - : ensureObj(PlainTime, refinedOptions.plainTime) - - return createZonedDateTimeFromFields({ - ...this.getISOFields(), - ...(plainTime ? plainTime.getISOFields() : zeroISOTimeFields), - timeZone, - }) - } - - toPlainDateTime(timeArg?: PlainTimeArg): Temporal.PlainDateTime { - return createDateTime({ - ...this.getISOFields(), - ...ensureLooseTime(timeArg).getISOFields(), - }) - } - - toPlainYearMonth(): Temporal.PlainYearMonth { - return createYearMonth(this.getISOFields()) - } - - toPlainMonthDay(): Temporal.PlainMonthDay { - return this.calendar.monthDayFromFields(this) - } -} - -// mixins -export interface PlainDate extends IsoMasterMethods {} -mixinIsoMasterMethods(PlainDate) -// -export interface PlainDate { [Symbol.toStringTag]: 'Temporal.PlainDate' } -attachStringTag(PlainDate, 'PlainDate') -// -export interface PlainDate extends DateCalendarFields { calendar: Temporal.CalendarProtocol } -mixinCalendarFields(PlainDate, dateCalendarFields) -mixinISOFields(PlainDate) -// -export interface PlainDate extends ToLocaleStringMethods {} -mixinLocaleStringMethods(PlainDate, createPlainFormatFactoryFactory({ - year: 'numeric', - month: 'numeric', - day: 'numeric', - weekday: undefined, -}, { - hour: undefined, - minute: undefined, - second: undefined, -})) - -// creation -export function createDate(isoFields: Temporal.PlainDateISOFields): PlainDate { - return new PlainDate( - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - isoFields.calendar, - ) -} - -// argument processing -function processToZonedDateTimeOptions( - options?: ToZonedDateTimeOptions, -): { - plainTime?: PlainTimeArg, - timeZone: Temporal.TimeZoneLike, - } { - let plainTime: PlainTimeArg | undefined - let timeZone: Temporal.TimeZoneLike | undefined - - if (typeof options === 'string') { - timeZone = options - } else if (typeof options === 'object') { - if ((options as Temporal.TimeZoneProtocol).id !== undefined) { - timeZone = options as Temporal.TimeZoneProtocol - } else { - timeZone = options.timeZone - plainTime = (options as { plainTime?: PlainTimeArg }).plainTime - } - if (timeZone === undefined) { - throw new TypeError('Invalid timeZone argument') - } - } else { - throw new TypeError('Invalid options/timeZone argument') - } - - return { plainTime, timeZone } -} - -function diffPlainDates( - pd0: PlainDate, - pd1: PlainDate, - flip: boolean, - options: DiffOptions | undefined, -): Duration { - return createDuration( - diffDates( - pd0, - pd1, - getCommonCalendar(pd0, pd1), - flip, - parseDiffOptions(options, DAY, DAY, DAY, YEAR), - ), - ) -} diff --git a/packages/temporal-polyfill/src/public/plainDateTime.ts b/packages/temporal-polyfill/src/public/plainDateTime.ts deleted file mode 100644 index ee5f6fde..00000000 --- a/packages/temporal-polyfill/src/public/plainDateTime.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { getCommonCalendar, getStrangerCalendar } from '../argParse/calendar' -import { parseCalendarDisplayOption } from '../argParse/calendarDisplay' -import { parseDiffOptions } from '../argParse/diffOptions' -import { parseDisambigOption } from '../argParse/disambig' -import { parseTimeToStringOptions } from '../argParse/isoFormatOptions' -import { OVERFLOW_REJECT, parseOverflowOption } from '../argParse/overflowHandling' -import { parseRoundingOptions } from '../argParse/roundingOptions' -import { timeUnitNames } from '../argParse/unitStr' -import { - IsoMasterMethods, - ensureObj, - initIsoMaster, - mixinIsoMasterMethods, -} from '../dateUtils/abstract' -import { compareDateTimes, dateTimesEqual } from '../dateUtils/compare' -import { constrainDateTimeISO } from '../dateUtils/constrain' -import { DayTimeUnit } from '../dateUtils/dayAndTime' -import { diffDateTimes } from '../dateUtils/diff' -import { DurationFields, negateDuration } from '../dateUtils/durationFields' -import { processDateTimeFromFields, processDateTimeWithFields } from '../dateUtils/fromAndWith' -import { validateDateTime } from '../dateUtils/isoFieldValidation' -import { formatCalendarID, formatDateTimeISO } from '../dateUtils/isoFormat' -import { LocalTimeFields } from '../dateUtils/localFields' -import { - DateCalendarFields, - attachStringTag, - dateCalendarFields, - mixinCalendarFields, - mixinISOFields, -} from '../dateUtils/mixins' -import { parseDateTime } from '../dateUtils/parse' -import { refineBaseObj } from '../dateUtils/parseRefine' -import { roundDateTime } from '../dateUtils/rounding' -import { getInstantFor } from '../dateUtils/timeZone' -import { translateDateTime } from '../dateUtils/translate' -import { DAY, DayTimeUnitInt, NANOSECOND, UnitInt, YEAR } from '../dateUtils/units' -import { createPlainFormatFactoryFactory } from '../native/intlFactory' -import { ToLocaleStringMethods, mixinLocaleStringMethods } from '../native/intlMixins' -import { Calendar, createDefaultCalendar } from './calendar' -import { Duration, DurationArg, createDuration } from './duration' -import { PlainDate, PlainDateArg, createDate } from './plainDate' -import { PlainTimeArg, createTime, ensureLooseTime } from './plainTime' -import { createYearMonth } from './plainYearMonth' -import { TimeZone } from './timeZone' -import { ZonedDateTime } from './zonedDateTime' - -export type PlainDateTimeArg = Temporal.PlainDateTime | Temporal.PlainDateTimeLike | string - -type DiffOptions = Temporal.DifferenceOptions< -'year' | 'month' | 'week' | 'day' | -'hour' | 'minute' | 'second' | 'millisecond' | 'microsecond' | 'nanosecond' -> - -type RoundOptions = Temporal.RoundTo< -'day' | 'hour' | 'minute' | 'second' | -'millisecond' | 'microsecond' | 'nanosecond' -> - -export class PlainDateTime implements Temporal.PlainDateTime { - constructor( - isoYear: number, - isoMonth: number, - isoDay: number, - isoHour = 0, - isoMinute = 0, - isoSecond = 0, - isoMillisecond = 0, - isoMicrosecond = 0, - isoNanosecond = 0, - calendarArg: Temporal.CalendarLike = createDefaultCalendar(), - ) { - const constrained = constrainDateTimeISO({ - isoYear, - isoMonth, - isoDay, - isoHour, - isoMinute, - isoSecond, - isoMillisecond, - isoMicrosecond, - isoNanosecond, - }, OVERFLOW_REJECT) - const calendar = ensureObj(Calendar, calendarArg) - - validateDateTime(constrained, calendar.toString()) - - initIsoMaster(this, { - ...constrained, - calendar, - }) - } - - static from(arg: PlainDateTimeArg, options?: Temporal.AssignmentOptions): Temporal.PlainDateTime { - const overflowHandling = parseOverflowOption(options) - - return createDateTime( - arg instanceof PlainDateTime - ? arg.getISOFields() // optimization - : typeof arg === 'object' - ? processDateTimeFromFields(arg, overflowHandling, options) - : refineBaseObj(parseDateTime(String(arg))), - ) - } - - static compare(a: PlainDateTimeArg, b: PlainDateTimeArg): Temporal.ComparisonResult { - return compareDateTimes( - ensureObj(PlainDateTime, a), - ensureObj(PlainDateTime, b), - ) - } - - with( - fields: Temporal.PlainDateTimeLike, - options?: Temporal.AssignmentOptions, - ): Temporal.PlainDateTime { - const overflowHandling = parseOverflowOption(options) - return createDateTime( - processDateTimeWithFields(this, fields, overflowHandling, options), - ) - } - - withPlainDate(dateArg: PlainDateArg): Temporal.PlainDateTime { - const date = ensureObj(PlainDate, dateArg) - return createDateTime({ - ...this.getISOFields(), // provides time fields - ...date.getISOFields(), - calendar: getStrangerCalendar(this, date), - }) - } - - withPlainTime(timeArg?: PlainTimeArg): Temporal.PlainDateTime { - return createDateTime({ - ...this.getISOFields(), // provides date & calendar fields - ...ensureLooseTime(timeArg).getISOFields(), - }) - } - - withCalendar(calendarArg: Temporal.CalendarLike): Temporal.PlainDateTime { - return createDateTime({ - ...this.getISOFields(), - calendar: ensureObj(Calendar, calendarArg), - }) - } - - add(durationArg: DurationArg, options?: Temporal.ArithmeticOptions): Temporal.PlainDateTime { - return translatePlainDateTime(this, ensureObj(Duration, durationArg), options) - } - - subtract(durationArg: DurationArg, options?: Temporal.ArithmeticOptions): Temporal.PlainDateTime { - return translatePlainDateTime(this, negateDuration(ensureObj(Duration, durationArg)), options) - } - - until(other: PlainDateTimeArg, options?: DiffOptions): Temporal.Duration { - return diffPlainDateTimes( - this, - ensureObj(PlainDateTime, other), - false, - options, - ) - } - - since(other: PlainDateTimeArg, options?: DiffOptions): Temporal.Duration { - return diffPlainDateTimes( - this, - ensureObj(PlainDateTime, other), - true, - options, - ) - } - - round(options: RoundOptions): Temporal.PlainDateTime { - const roundingConfig = parseRoundingOptions( - options, - NANOSECOND, // minUnit - DAY, // maxUnit - ) - - return createDateTime({ - ...roundDateTime(this.getISOFields(), roundingConfig), - calendar: this.calendar, - }) - } - - equals(other: PlainDateTimeArg): boolean { - return dateTimesEqual(this, ensureObj(PlainDateTime, other)) - } - - toString(options?: Temporal.CalendarTypeToStringOptions): string { - const formatConfig = parseTimeToStringOptions(options) - const calendarDisplay = parseCalendarDisplayOption(options) - const isoFields = roundDateTime(this.getISOFields(), formatConfig) - - return formatDateTimeISO(isoFields, formatConfig) + - formatCalendarID(this.calendar.toString(), calendarDisplay) - } - - toZonedDateTime( - timeZoneArg: Temporal.TimeZoneLike, - options?: Temporal.ToInstantOptions, - ): Temporal.ZonedDateTime { - const timeZone = ensureObj(TimeZone, timeZoneArg) - const instant = getInstantFor(timeZone, this, parseDisambigOption(options)) - - // more succinct than createZonedDateTimeFromFields - return new ZonedDateTime(instant.epochNanoseconds, timeZone, this.calendar) - } - - toPlainYearMonth(): Temporal.PlainYearMonth { return createYearMonth(this.getISOFields()) } - toPlainMonthDay(): Temporal.PlainMonthDay { return this.calendar.monthDayFromFields(this) } - toPlainDate(): Temporal.PlainDate { return createDate(this.getISOFields()) } - toPlainTime(): Temporal.PlainTime { return createTime(this.getISOFields()) } -} - -// mixins -export interface PlainDateTime extends IsoMasterMethods {} -mixinIsoMasterMethods(PlainDateTime) -// -export interface PlainDateTime { [Symbol.toStringTag]: 'Temporal.PlainDateTime' } -attachStringTag(PlainDateTime, 'PlainDateTime') -// -export interface PlainDateTime extends DateCalendarFields { calendar: Temporal.CalendarProtocol } -mixinCalendarFields(PlainDateTime, dateCalendarFields) -// -export interface PlainDateTime extends LocalTimeFields {} -mixinISOFields(PlainDateTime, timeUnitNames) -// -export interface PlainDateTime extends ToLocaleStringMethods {} -mixinLocaleStringMethods(PlainDateTime, createPlainFormatFactoryFactory({ - year: 'numeric', - month: 'numeric', - day: 'numeric', - weekday: undefined, - hour: 'numeric', - minute: '2-digit', - second: '2-digit', -}, {})) - -// creation -export function createDateTime(isoFields: Temporal.PlainDateTimeISOFields): PlainDateTime { - return new PlainDateTime( - isoFields.isoYear, - isoFields.isoMonth, - isoFields.isoDay, - isoFields.isoHour, - isoFields.isoMinute, - isoFields.isoSecond, - isoFields.isoMillisecond, - isoFields.isoMicrosecond, - isoFields.isoNanosecond, - isoFields.calendar, - ) -} - -function translatePlainDateTime( - pdt0: PlainDateTime, - dur: DurationFields, - options: Temporal.ArithmeticOptions | undefined, -): PlainDateTime { - const isoFields = translateDateTime(pdt0.getISOFields(), dur, options) - return createDateTime({ - ...isoFields, - calendar: pdt0.calendar, - }) -} - -function diffPlainDateTimes( - pdt0: PlainDateTime, - pdt1: PlainDateTime, - flip: boolean, - options: DiffOptions | undefined, -): Duration { - const diffConfig = parseDiffOptions( - options, - DAY, // largestUnitDefault - NANOSECOND, // smallestUnitDefault - NANOSECOND, // minUnit - YEAR, // maxUnit - ) - - return createDuration( - diffDateTimes(pdt0, pdt1, getCommonCalendar(pdt0, pdt1), flip, diffConfig), - ) -} diff --git a/packages/temporal-polyfill/src/public/plainMonthDay.ts b/packages/temporal-polyfill/src/public/plainMonthDay.ts deleted file mode 100644 index 68efd1fe..00000000 --- a/packages/temporal-polyfill/src/public/plainMonthDay.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { parseCalendarDisplayOption } from '../argParse/calendarDisplay' -import { OVERFLOW_REJECT, parseOverflowOption } from '../argParse/overflowHandling' -import { isoCalendarID } from '../calendarImpl/isoCalendarImpl' -import { - IsoMasterMethods, - ensureObj, - initIsoMaster, - mixinIsoMasterMethods, -} from '../dateUtils/abstract' -import { compareDateTimes } from '../dateUtils/compare' -import { constrainDateISO } from '../dateUtils/constrain' -import { isoEpochLeapYear } from '../dateUtils/epoch' -import { processMonthDayFromFields, processMonthDayWithFields } from '../dateUtils/fromAndWith' -import { formatCalendarID, formatDateISO, formatMonthDayISO } from '../dateUtils/isoFormat' -import { - MonthDayCalendarFields, - attachStringTag, - mixinCalendarFields, - mixinISOFields, - monthDayCalendarFields, -} from '../dateUtils/mixins' -import { parseMonthDay } from '../dateUtils/parse' -import { refineBaseObj } from '../dateUtils/parseRefine' -import { createPlainFormatFactoryFactory } from '../native/intlFactory' -import { ToLocaleStringMethods, mixinLocaleStringMethods } from '../native/intlMixins' -import { Calendar, createDefaultCalendar } from './calendar' - -export type PlainMonthDayArg = Temporal.PlainMonthDay | Temporal.PlainMonthDayLike | string - -export class PlainMonthDay implements Temporal.PlainMonthDay { - constructor( - isoMonth: number, - isoDay: number, - calendar: Temporal.CalendarLike = createDefaultCalendar(), - referenceISOYear: number = isoEpochLeapYear, - ) { - initIsoMaster(this, { - ...constrainDateISO({ isoYear: referenceISOYear, isoMonth, isoDay }, OVERFLOW_REJECT), - calendar: ensureObj(Calendar, calendar), - }) - } - - static from(arg: PlainMonthDayArg, options?: Temporal.AssignmentOptions): Temporal.PlainMonthDay { - parseOverflowOption(options) // unused, but need to validate, regardless of input type - - if (arg instanceof PlainMonthDay) { - return createMonthDay(arg.getISOFields()) // optimization - } - - if (typeof arg === 'object') { - return processMonthDayFromFields(arg, options) - } - - // a string... - const parsed = parseMonthDay(String(arg)) - - // for strings, force ISO year if no calendar specified - // TODO: more DRY with processMonthDayLike? - if (parsed.calendar === undefined) { - parsed.isoYear = isoEpochLeapYear - } - - return createMonthDay(refineBaseObj(parsed)) - } - - with( - fields: Temporal.PlainMonthDayLike, - options?: Temporal.AssignmentOptions, - ): Temporal.PlainMonthDay { - return processMonthDayWithFields(this, fields, options) - } - - equals(other: PlainMonthDayArg): boolean { - return !compareDateTimes(this, ensureObj(PlainMonthDay, other)) - } - - toString(options?: Temporal.ShowCalendarOption): string { - const fields = this.getISOFields() - const calendarID = fields.calendar.toString() // see note in formatCalendarID - const calendarDisplay = parseCalendarDisplayOption(options) - - return ( - calendarID === isoCalendarID - ? formatMonthDayISO(fields) - : formatDateISO(fields) - ) + formatCalendarID(calendarID, calendarDisplay) - } - - toPlainDate(fields: { year: number }): Temporal.PlainDate { - return this.calendar.dateFromFields({ - year: fields.year, - monthCode: this.monthCode, - day: this.day, - }, { - overflow: 'reject', // always reject - }) - } -} - -// mixins -export interface PlainMonthDay extends IsoMasterMethods {} -mixinIsoMasterMethods(PlainMonthDay) -// -export interface PlainMonthDay { [Symbol.toStringTag]: 'Temporal.PlainMonthDay' } -attachStringTag(PlainMonthDay, 'PlainMonthDay') -// -export interface PlainMonthDay extends MonthDayCalendarFields { - calendar: Temporal.CalendarProtocol -} -mixinCalendarFields(PlainMonthDay, monthDayCalendarFields) -mixinISOFields(PlainMonthDay) -// -export interface PlainMonthDay extends ToLocaleStringMethods {} -mixinLocaleStringMethods(PlainMonthDay, createPlainFormatFactoryFactory({ - month: 'numeric', - day: 'numeric', -}, { - weekday: undefined, - year: undefined, - hour: undefined, - minute: undefined, - second: undefined, -}, true)) // strictCalendar - -// create -export function createMonthDay(isoFields: Temporal.PlainDateISOFields): PlainMonthDay { - return new PlainMonthDay( - isoFields.isoMonth, - isoFields.isoDay, - isoFields.calendar, - isoFields.isoYear, - ) -} diff --git a/packages/temporal-polyfill/src/public/plainTime.ts b/packages/temporal-polyfill/src/public/plainTime.ts deleted file mode 100644 index ab17bf43..00000000 --- a/packages/temporal-polyfill/src/public/plainTime.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { parseDiffOptions } from '../argParse/diffOptions' -import { parseTimeToStringOptions } from '../argParse/isoFormatOptions' -import { OVERFLOW_REJECT, parseOverflowOption } from '../argParse/overflowHandling' -import { parseRoundingOptions } from '../argParse/roundingOptions' -import { timeUnitNames } from '../argParse/unitStr' -import { - IsoMasterMethods, - ensureObj, - initIsoMaster, - mixinIsoMasterMethods, -} from '../dateUtils/abstract' -import { compareTimes } from '../dateUtils/compare' -import { constrainTimeISO } from '../dateUtils/constrain' -import { isoTimeToNano } from '../dateUtils/dayAndTime' -import { diffTimes } from '../dateUtils/diff' -import { DurationFields, negateDuration } from '../dateUtils/durationFields' -import { processTimeFromFields, processTimeWithFields } from '../dateUtils/fromAndWith' -import { ISOTimeFields } from '../dateUtils/isoFields' -import { formatTimeISO } from '../dateUtils/isoFormat' -import { LocalTimeFields } from '../dateUtils/localFields' -import { attachStringTag, mixinISOFields } from '../dateUtils/mixins' -import { parseTime } from '../dateUtils/parse' -import { roundTime } from '../dateUtils/rounding' -import { translateTime } from '../dateUtils/translate' -import { HOUR, NANOSECOND, TimeUnitInt, nanoInMilli } from '../dateUtils/units' -import { FormatFactory } from '../native/intlFactory' -import { ToLocaleStringMethods, mixinLocaleStringMethods } from '../native/intlMixins' -import { OrigDateTimeFormat } from '../native/intlUtils' -import { createDefaultCalendar } from './calendar' -import { Duration, createDuration } from './duration' -import { PlainDate, PlainDateArg } from './plainDate' -import { TimeZone } from './timeZone' -import { createZonedDateTimeFromFields } from './zonedDateTime' - -export type PlainTimeArg = Temporal.PlainTime | Temporal.PlainTimeLike | string - -type DiffOptions = Temporal.DifferenceOptions< -'hour' | 'minute' | 'second' | -'millisecond' | 'microsecond' | 'nanosecond' -> - -type RoundOptions = Temporal.RoundTo< -'hour' | 'minute' | 'second' | -'millisecond' | 'microsecond' | 'nanosecond' -> - -type ToZonedDateTimeOptions = { - timeZone: Temporal.TimeZoneLike - plainDate: Temporal.PlainDate | Temporal.PlainDateLike | string -} - -export class PlainTime implements Temporal.PlainTime { - constructor( - isoHour = 0, - isoMinute = 0, - isoSecond = 0, - isoMillisecond = 0, - isoMicrosecond = 0, - isoNanosecond = 0, - ) { - initIsoMaster(this, { - ...constrainTimeISO({ - isoHour, - isoMinute, - isoSecond, - isoMillisecond, - isoMicrosecond, - isoNanosecond, - }, OVERFLOW_REJECT), - calendar: createDefaultCalendar(), - }) - } - - static from(arg: PlainTimeArg, options?: Temporal.AssignmentOptions): Temporal.PlainTime { - const overflowHandling = parseOverflowOption(options) - - return createTime( - arg instanceof PlainTime - ? arg.getISOFields() // optimization - : typeof arg === 'object' - ? processTimeFromFields(arg, overflowHandling) - : parseTime(String(arg)), - ) - } - - static compare(a: PlainTimeArg, b: PlainTimeArg): Temporal.ComparisonResult { - return compareTimes(ensureObj(PlainTime, a), ensureObj(PlainTime, b)) - } - - with(fields: Temporal.PlainTimeLike, options?: Temporal.AssignmentOptions): Temporal.PlainTime { - return createTime( - processTimeWithFields(this, fields, parseOverflowOption(options)), - ) - } - - add(durationArg: Temporal.Duration | Temporal.DurationLike | string): Temporal.PlainTime { - return translatePlainTime(this, ensureObj(Duration, durationArg)) - } - - subtract(durationArg: Temporal.Duration | Temporal.DurationLike | string): Temporal.PlainTime { - return translatePlainTime(this, negateDuration(ensureObj(Duration, durationArg))) - } - - until(other: PlainTimeArg, options?: DiffOptions): Temporal.Duration { - return diffPlainTimes(this, ensureObj(PlainTime, other), options) - } - - since(other: PlainTimeArg, options?: DiffOptions): Temporal.Duration { - return diffPlainTimes(ensureObj(PlainTime, other), this, options) - } - - round(options: RoundOptions): Temporal.PlainTime { - const roundingConfig = parseRoundingOptions( - options, - NANOSECOND, // minUnit - HOUR, // maxUnit - ) - - return createTime(roundTime(this.getISOFields(), roundingConfig)) - } - - equals(other: Temporal.PlainTime | Temporal.PlainTimeLike | string): boolean { - return !compareTimes(this, ensureObj(PlainTime, other)) - } - - toString(options?: Temporal.ToStringPrecisionOptions): string { - const formatConfig = parseTimeToStringOptions(options) - const roundedISOFields: ISOTimeFields = roundTime(this.getISOFields(), formatConfig) - return formatTimeISO(roundedISOFields, formatConfig) - } - - toZonedDateTime(options: ToZonedDateTimeOptions): Temporal.ZonedDateTime { - // TODO: ensure options object first? - const plainDate = ensureObj(PlainDate, options.plainDate) - const timeZone = ensureObj(TimeZone, options.timeZone) - - return createZonedDateTimeFromFields({ - ...plainDate.getISOFields(), - ...this.getISOFields(), - timeZone, - }) - } - - toPlainDateTime(dateArg: PlainDateArg): Temporal.PlainDateTime { - return ensureObj(PlainDate, dateArg).toPlainDateTime(this) - } -} - -// mixins -export interface PlainTime extends IsoMasterMethods {} -mixinIsoMasterMethods(PlainTime) -// -export interface PlainTime { [Symbol.toStringTag]: 'Temporal.PlainTime' } -attachStringTag(PlainTime, 'PlainTime') -// -export interface PlainTime extends LocalTimeFields { calendar: Temporal.Calendar } -mixinISOFields(PlainTime, timeUnitNames) -// -export interface PlainTime extends ToLocaleStringMethods {} -mixinLocaleStringMethods(PlainTime, createPlainTimeFormatFactory) - -function createPlainTimeFormatFactory( - locales: string[], - options: Intl.DateTimeFormatOptions, -): FormatFactory { - return { - buildKey: () => ['', ''], - buildFormat: () => new OrigDateTimeFormat(locales, { - hour: 'numeric', - minute: '2-digit', - second: '2-digit', - ...options, - timeZone: 'UTC', // options can't override - timeZoneName: undefined, - year: undefined, - month: undefined, - day: undefined, - weekday: undefined, - }), - buildEpochMilli: (plainTime: PlainTime) => ( - Math.trunc(isoTimeToNano(plainTime.getISOFields()) / nanoInMilli) - ), - } -} - -export function createTime(isoFields: ISOTimeFields): PlainTime { - return new PlainTime( - isoFields.isoHour, - isoFields.isoMinute, - isoFields.isoSecond, - isoFields.isoMillisecond, - isoFields.isoMicrosecond, - isoFields.isoNanosecond, - ) -} - -// Normally ensureObj and ::from would fail when undefined is specified -// Fallback to 00:00 time -export function ensureLooseTime(arg: PlainTimeArg | undefined): PlainTime { - return ensureObj(PlainTime, arg ?? { hour: 0 }) -} - -function translatePlainTime(pt: PlainTime, dur: DurationFields): PlainTime { - return createTime(translateTime(pt.getISOFields(), dur)) -} - -function diffPlainTimes( - pt0: PlainTime, - pt1: PlainTime, - options: DiffOptions | undefined, -): Duration { - const diffConfig = parseDiffOptions( - options, - HOUR, // largestUnitDefault - NANOSECOND, // smallestUnitDefault - NANOSECOND, // minUnit - HOUR, // maxUnit - ) - - return createDuration( - // TODO: use local-time-fields as-is somehow??? - diffTimes(pt0.getISOFields(), pt1.getISOFields(), diffConfig), - ) -} diff --git a/packages/temporal-polyfill/src/public/plainYearMonth.ts b/packages/temporal-polyfill/src/public/plainYearMonth.ts deleted file mode 100644 index cb0de8bb..00000000 --- a/packages/temporal-polyfill/src/public/plainYearMonth.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { getCommonCalendar } from '../argParse/calendar' -import { parseCalendarDisplayOption } from '../argParse/calendarDisplay' -import { parseDiffOptions } from '../argParse/diffOptions' -import { OVERFLOW_REJECT, parseOverflowOption } from '../argParse/overflowHandling' -import { isoCalendarID } from '../calendarImpl/isoCalendarImpl' -import { - IsoMasterMethods, - ensureObj, - initIsoMaster, - mixinIsoMasterMethods, -} from '../dateUtils/abstract' -import { compareDateTimes } from '../dateUtils/compare' -import { constrainDateISO } from '../dateUtils/constrain' -import { diffDates } from '../dateUtils/diff' -import { DurationFields, negateDuration } from '../dateUtils/durationFields' -import { processYearMonthFromFields, processYearMonthWithFields } from '../dateUtils/fromAndWith' -import { validateYearMonth } from '../dateUtils/isoFieldValidation' -import { formatCalendarID, formatDateISO, formatYearMonthISO } from '../dateUtils/isoFormat' -import { - YearMonthCalendarFields, - attachStringTag, - mixinCalendarFields, - mixinISOFields, - yearMonthCalendarFields, -} from '../dateUtils/mixins' -import { parseYearMonth } from '../dateUtils/parse' -import { refineBaseObj } from '../dateUtils/parseRefine' -import { MONTH, YEAR, YearMonthUnitInt } from '../dateUtils/units' -import { createPlainFormatFactoryFactory } from '../native/intlFactory' -import { ToLocaleStringMethods, mixinLocaleStringMethods } from '../native/intlMixins' -import { Calendar, createDefaultCalendar } from './calendar' -import { Duration, DurationArg, createDuration } from './duration' - -export type PlainYearMonthArg = Temporal.PlainYearMonth | Temporal.PlainYearMonthLike | string - -type YearMonthUnit = 'year' | 'month' -type DiffOptions = Temporal.DifferenceOptions - -const day1 = { day: 1 } - -export class PlainYearMonth implements Temporal.PlainYearMonth { - constructor( - isoYear: number, - isoMonth: number, - calendarArg: Temporal.CalendarLike = createDefaultCalendar(), - referenceISODay = 1, - ) { - const constrained = constrainDateISO({ - isoYear, - isoMonth, - isoDay: referenceISODay, - }, OVERFLOW_REJECT) - const calendar = ensureObj(Calendar, calendarArg) - - validateYearMonth(constrained, calendar.toString()) - - initIsoMaster(this, { - ...constrained, - calendar, - }) - } - - static from( - arg: PlainYearMonthArg, - options?: Temporal.AssignmentOptions, - ): Temporal.PlainYearMonth { - parseOverflowOption(options) // unused, but need to validate, regardless of input type - - if (arg instanceof PlainYearMonth) { - return createYearMonth(arg.getISOFields()) // optimization - } - - if (typeof arg === 'object') { - return processYearMonthFromFields(arg, options) - } - - // a string... - const parsed = parseYearMonth(String(arg)) - - // don't allow day-numbers in ISO strings - if (parsed.calendar === undefined) { - parsed.isoDay = 1 - } - - return createYearMonth(refineBaseObj(parsed)) - } - - static compare(a: PlainYearMonthArg, b: PlainYearMonthArg): Temporal.ComparisonResult { - return compareDateTimes( - ensureObj(PlainYearMonth, a), - ensureObj(PlainYearMonth, b), - ) - } - - with( - fields: Temporal.PlainYearMonthLike, - options?: Temporal.AssignmentOptions, - ): Temporal.PlainYearMonth { - return processYearMonthWithFields(this, fields, options) - } - - add( - durationArg: DurationArg, - options?: Temporal.ArithmeticOptions, - ): Temporal.PlainYearMonth { - return translatePlainYearMonth(this, ensureObj(Duration, durationArg), options) - } - - subtract( - durationArg: DurationArg, - options?: Temporal.ArithmeticOptions, - ): Temporal.PlainYearMonth { - return translatePlainYearMonth(this, negateDuration(ensureObj(Duration, durationArg)), options) - } - - until(other: PlainYearMonthArg, options?: DiffOptions): Temporal.Duration { - return diffPlainYearMonths(this, ensureObj(PlainYearMonth, other), false, options) - } - - since(other: PlainYearMonthArg, options?: DiffOptions): Temporal.Duration { - return diffPlainYearMonths(this, ensureObj(PlainYearMonth, other), true, options) - } - - equals(other: PlainYearMonthArg): boolean { - return !compareDateTimes(this, ensureObj(PlainYearMonth, other)) - } - - toString(options?: Temporal.ShowCalendarOption): string { - const fields = this.getISOFields() - const calendarID = fields.calendar.toString() // see note in formatCalendarID - const calendarDisplay = parseCalendarDisplayOption(options) - - return ( - calendarID === isoCalendarID - ? formatYearMonthISO(fields) - : formatDateISO(fields) - ) + formatCalendarID(calendarID, calendarDisplay) - } - - toPlainDate(fields: { day: number }): Temporal.PlainDate { - return this.calendar.dateFromFields({ - year: this.year, - month: this.month, - day: fields.day, - }) - } -} - -// mixins -export interface PlainYearMonth extends IsoMasterMethods {} -mixinIsoMasterMethods(PlainYearMonth) -// -export interface PlainYearMonth { [Symbol.toStringTag]: 'Temporal.PlainYearMonth' } -attachStringTag(PlainYearMonth, 'PlainYearMonth') -// -export interface PlainYearMonth extends YearMonthCalendarFields { - calendar: Temporal.CalendarProtocol -} -mixinISOFields(PlainYearMonth) -mixinCalendarFields(PlainYearMonth, yearMonthCalendarFields) -// -export interface PlainYearMonth extends ToLocaleStringMethods {} -mixinLocaleStringMethods(PlainYearMonth, createPlainFormatFactoryFactory({ - year: 'numeric', - month: 'numeric', -}, { - weekday: undefined, - day: undefined, - hour: undefined, - minute: undefined, - second: undefined, -}, true)) // strictCalendar - -export function createYearMonth(isoFields: Temporal.PlainDateISOFields): PlainYearMonth { - return new PlainYearMonth( - isoFields.isoYear, - isoFields.isoMonth, - isoFields.calendar, - isoFields.isoDay, - ) -} - -function translatePlainYearMonth( - yearMonth: PlainYearMonth, - duration: DurationFields, - options?: Temporal.ArithmeticOptions, -): PlainYearMonth { - return yearMonth.toPlainDate({ - day: duration.sign < 0 - ? yearMonth.daysInMonth - : 1, - }) - .add(duration, options) - .toPlainYearMonth() -} - -function diffPlainYearMonths( - pym0: PlainYearMonth, - pym1: PlainYearMonth, - flip: boolean, - options: DiffOptions | undefined, -): Duration { - return createDuration( - diffDates( - pym0.toPlainDate(day1), - pym1.toPlainDate(day1), - getCommonCalendar(pym0, pym1), - flip, - parseDiffOptions(options, YEAR, MONTH, MONTH, YEAR), - ), - ) -} diff --git a/packages/temporal-polyfill/src/public/timeZone.ts b/packages/temporal-polyfill/src/public/timeZone.ts deleted file mode 100644 index f7acc659..00000000 --- a/packages/temporal-polyfill/src/public/timeZone.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { parseDisambigOption } from '../argParse/disambig' -import { isObjectLike } from '../argParse/refine' -import { timeZoneFromObj } from '../argParse/timeZone' -import { JsonMethods, ensureObj, mixinJsonMethods } from '../dateUtils/abstract' -import { epochNanoSymbol, epochNanoToISOFields, isoFieldsToEpochNano } from '../dateUtils/epoch' -import { formatOffsetISO } from '../dateUtils/isoFormat' -import { attachStringTag } from '../dateUtils/mixins' -import { checkInvalidOffset } from '../dateUtils/offset' -import { tryParseZonedDateTime } from '../dateUtils/parse' -import { refineZonedObj } from '../dateUtils/parseRefine' -import { getInstantFor } from '../dateUtils/timeZone' -import { TimeZoneImpl } from '../timeZoneImpl/timeZoneImpl' -import { queryTimeZoneImpl } from '../timeZoneImpl/timeZoneImplQuery' -import { createWeakMap } from '../utils/obj' -import { Calendar, createDefaultCalendar } from './calendar' -import { Instant, InstantArg } from './instant' -import { PlainDateTime, PlainDateTimeArg, createDateTime } from './plainDateTime' - -// FYI: the Temporal.TimeZoneLike type includes `string` -// unlike many other object types - -const [getImpl, setImpl] = createWeakMap() - -export class TimeZone implements Temporal.TimeZone { - constructor(id: string) { - if (!id) { - throw new RangeError('Invalid timezone ID') - } - setImpl(this, queryTimeZoneImpl(id)) - } - - static from(arg: Temporal.TimeZoneLike): Temporal.TimeZoneProtocol { - if (isObjectLike(arg)) { - return timeZoneFromObj(arg) - } - - const parsed = tryParseZonedDateTime(String(arg)) - - if (parsed) { - if (parsed.timeZone) { - const refined = refineZonedObj(parsed) // TODO: we don't need the calendar - checkInvalidOffset(refined) - return refined.timeZone - } else if (parsed.Z) { - return new TimeZone('UTC') - } else if (parsed.offsetNanoseconds !== undefined) { - return new TimeZone(formatOffsetISO(parsed.offsetNanoseconds)) - } - } - - return new TimeZone(String(arg)) // consider arg the literal time zone ID string - } - - get id(): string { - return this.toString() - } - - getOffsetStringFor(instantArg: InstantArg): string { - return formatOffsetISO(this.getOffsetNanosecondsFor(instantArg)) - } - - getOffsetNanosecondsFor(instantArg: InstantArg): number { - const instant = ensureObj(Instant, instantArg) - return getImpl(this).getOffset(instant[epochNanoSymbol]) - } - - getPlainDateTimeFor( - instantArg: InstantArg, - calendarArg: Temporal.CalendarLike = createDefaultCalendar(), - ): Temporal.PlainDateTime { - const instant = ensureObj(Instant, instantArg) - const isoFields = epochNanoToISOFields( - instant[epochNanoSymbol].add(this.getOffsetNanosecondsFor(instant)), - ) - return createDateTime({ - ...isoFields, - calendar: ensureObj(Calendar, calendarArg), - }) - } - - getInstantFor( - dateTimeArg: PlainDateTimeArg, - options?: Temporal.ToInstantOptions, - ): Temporal.Instant { - return getInstantFor(this, ensureObj(PlainDateTime, dateTimeArg), parseDisambigOption(options)) - } - - getPossibleInstantsFor(dateTimeArg: PlainDateTimeArg): Temporal.Instant[] { - const isoFields = ensureObj(PlainDateTime, dateTimeArg).getISOFields() - const zoneNano = isoFieldsToEpochNano(isoFields) - const possibleOffsetNanos = getImpl(this).getPossibleOffsets(zoneNano) - - return possibleOffsetNanos.map((offsetNano) => ( - new Instant(zoneNano.sub(offsetNano)) - )) - } - - getPreviousTransition(instantArg: InstantArg): Temporal.Instant | null { - const instant = ensureObj(Instant, instantArg) - const rawTransition = getImpl(this).getTransition(instant[epochNanoSymbol], -1) - if (rawTransition) { - return new Instant(rawTransition[0]) - } - return null - } - - getNextTransition(instantArg: InstantArg): Temporal.Instant | null { - const instant = ensureObj(Instant, instantArg) - const rawTransition = getImpl(this).getTransition(instant[epochNanoSymbol], 1) - if (rawTransition) { - return new Instant(rawTransition[0]) - } - return null - } - - toString(): string { - return getImpl(this).id - } -} - -// mixins -export interface TimeZone extends JsonMethods {} -mixinJsonMethods(TimeZone) -// -export interface TimeZone { [Symbol.toStringTag]: 'Temporal.TimeZone' } -attachStringTag(TimeZone, 'TimeZone') diff --git a/packages/temporal-polyfill/src/public/zonedDateTime.ts b/packages/temporal-polyfill/src/public/zonedDateTime.ts deleted file mode 100644 index 7e01b50d..00000000 --- a/packages/temporal-polyfill/src/public/zonedDateTime.ts +++ /dev/null @@ -1,376 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { getCommonCalendar, getStrangerCalendar } from '../argParse/calendar' -import { parseCalendarDisplayOption } from '../argParse/calendarDisplay' -import { parseDiffOptions } from '../argParse/diffOptions' -import { parseDisambigOption } from '../argParse/disambig' -import { parseTimeToStringOptions } from '../argParse/isoFormatOptions' -import { OFFSET_DISPLAY_AUTO, parseOffsetDisplayOption } from '../argParse/offsetDisplay' -import { - OFFSET_PREFER, - OFFSET_REJECT, - OffsetHandlingInt, - parseOffsetHandlingOption, -} from '../argParse/offsetHandling' -import { parseOverflowOption } from '../argParse/overflowHandling' -import { RoundingConfig, parseRoundingOptions } from '../argParse/roundingOptions' -import { parseTimeZoneDisplayOption } from '../argParse/timeZoneDisplay' -import { timeUnitNames } from '../argParse/unitStr' -import { - IsoMasterMethods, - ensureObj, - initIsoMaster, - mixinIsoMasterMethods, -} from '../dateUtils/abstract' -import { compareEpochObjs, zonedDateTimesEqual } from '../dateUtils/compare' -import { DayTimeUnit, zeroISOTimeFields } from '../dateUtils/dayAndTime' -import { diffDateTimes } from '../dateUtils/diff' -import { DurationFields, negateDuration } from '../dateUtils/durationFields' -import { epochNanoSymbol, epochNanoToISOFields } from '../dateUtils/epoch' -import { - processZonedDateTimeFromFields, - processZonedDateTimeWithFields, -} from '../dateUtils/fromAndWith' -import { validateDateTime } from '../dateUtils/isoFieldValidation' -import { ISODateTimeFields } from '../dateUtils/isoFields' -import { - formatCalendarID, - formatDateTimeISO, - formatOffsetISO, - formatTimeZoneID, -} from '../dateUtils/isoFormat' -import { LocalTimeFields } from '../dateUtils/localFields' -import { - ComputedEpochFields, - DateCalendarFields, - attachStringTag, - dateCalendarFields, - mixinCalendarFields, - mixinEpochFields, - mixinISOFields, -} from '../dateUtils/mixins' -import { - OffsetComputableFields, - computeNanoInDay, - computeZonedDateTimeEpochNano, -} from '../dateUtils/offset' -import { parseZonedDateTime } from '../dateUtils/parse' -import { refineZonedObj } from '../dateUtils/parseRefine' -import { roundZonedDateTimeFields } from '../dateUtils/rounding' -import { getInstantFor } from '../dateUtils/timeZone' -import { translateZonedDateTimeFields } from '../dateUtils/translate' -import { - DAY, - DayTimeUnitInt, - HOUR, - NANOSECOND, - UnitInt, - YEAR, - nanoInHour, -} from '../dateUtils/units' -import { createZonedFormatFactoryFactory } from '../native/intlFactory' -import { ToLocaleStringMethods, mixinLocaleStringMethods } from '../native/intlMixins' -import { LargeInt, LargeIntArg, createLargeInt } from '../utils/largeInt' -import { roundToMinute } from '../utils/math' -import { Calendar, createDefaultCalendar } from './calendar' -import { Duration, DurationArg, createDuration } from './duration' -import { Instant } from './instant' -import { PlainDate, PlainDateArg, createDate } from './plainDate' -import { createDateTime } from './plainDateTime' -import { PlainTime, PlainTimeArg, createTime } from './plainTime' -import { createYearMonth } from './plainYearMonth' -import { TimeZone } from './timeZone' - -export type ZonedDateTimeArg = Temporal.ZonedDateTime | Temporal.ZonedDateTimeLike | string - -type DiffOptions = Temporal.DifferenceOptions< -'year' | 'month' | 'week' | 'day' | -'hour' | 'minute' | 'second' | 'millisecond' | 'microsecond' | 'nanosecond' -> - -type RoundOptions = Temporal.RoundTo< -'day' | 'hour' | 'minute' | 'second' | -'millisecond' | 'microsecond' | 'nanosecond' -> - -const offsetNanoSymbol = Symbol() - -export interface ZonedDateTime { - [offsetNanoSymbol]: number - [epochNanoSymbol]: LargeInt -} -export class ZonedDateTime implements Temporal.ZonedDateTime { - constructor( - epochNanoseconds: LargeIntArg, - timeZoneArg: Temporal.TimeZoneLike, - calendarArg: Temporal.CalendarLike = createDefaultCalendar(), - ) { - // TODO: throw error when number? - const timeZone = ensureObj(TimeZone, timeZoneArg) - const calendar = ensureObj(Calendar, calendarArg) - - const epochNano = createLargeInt(epochNanoseconds) // TODO: do strict, like Instant? - const [isoFields, offsetNano] = buildZonedDateTimeISOFields(epochNano, timeZone) - validateDateTime(isoFields, calendar.toString()) - - initIsoMaster(this, { - ...isoFields, - calendar, - timeZone, - // NOTE: must support TimeZone protocols that don't implement getOffsetStringFor - // TODO: more DRY with getOffsetStringFor - offset: formatOffsetISO(offsetNano), - }) - - this[epochNanoSymbol] = epochNano - this[offsetNanoSymbol] = offsetNano - } - - // okay to have return-type be ZonedDateTime? needed - static from(arg: ZonedDateTimeArg, options?: Temporal.AssignmentOptions): ZonedDateTime { - const offsetHandling = parseOffsetHandlingOption(options, OFFSET_REJECT) - const overflowHandling = parseOverflowOption(options) - - if (arg instanceof ZonedDateTime) { - return new ZonedDateTime(arg.epochNanoseconds, arg.timeZone, arg.calendar) - } - - const isObject = typeof arg === 'object' - const fields = isObject - ? processZonedDateTimeFromFields(arg, overflowHandling, options) - : refineZonedObj(parseZonedDateTime(String(arg))) - - return createZonedDateTimeFromFields( - fields, - !isObject, // fuzzyMatching (if string) - offsetHandling, - options, - ) - } - - static compare(a: ZonedDateTimeArg, b: ZonedDateTimeArg): Temporal.ComparisonResult { - return compareEpochObjs( - ensureObj(ZonedDateTime, a), - ensureObj(ZonedDateTime, b), - ) - } - - get timeZone(): Temporal.TimeZoneProtocol { return this.getISOFields().timeZone } - get offsetNanoseconds(): number { return this[offsetNanoSymbol] } - get offset(): string { return this.getISOFields().offset } - - with( - fields: Temporal.ZonedDateTimeLike, - options?: Temporal.AssignmentOptions, - ): Temporal.ZonedDateTime { - parseDisambigOption(options) // for validation - const overflowHandling = parseOverflowOption(options) // for validation (?) - const offsetHandling = parseOffsetHandlingOption(options, OFFSET_PREFER) - const refined = processZonedDateTimeWithFields(this, fields, overflowHandling, options) - - return createZonedDateTimeFromFields(refined, false, offsetHandling, options) - } - - withPlainDate(dateArg: PlainDateArg): Temporal.ZonedDateTime { - const date = ensureObj(PlainDate, dateArg) - const dateTime = date.toPlainDateTime(this) // timeArg=this - const { timeZone } = this - const instant = getInstantFor(timeZone, dateTime) - - return new ZonedDateTime( - instant.epochNanoseconds, - timeZone, - getStrangerCalendar(this, date), - ) - } - - withPlainTime(timeArg?: PlainTimeArg): Temporal.ZonedDateTime { - return createZonedDateTimeFromFields({ - ...this.getISOFields(), - ...( - timeArg === undefined - ? zeroISOTimeFields - : ensureObj(PlainTime, timeArg).getISOFields() - ), - }) - } - - withCalendar(calendarArg: Temporal.CalendarLike): Temporal.ZonedDateTime { - return new ZonedDateTime( - this.epochNanoseconds, - this.timeZone, - calendarArg, - ) - } - - withTimeZone(timeZoneArg: Temporal.TimeZoneLike): Temporal.ZonedDateTime { - return new ZonedDateTime( - this.epochNanoseconds, - timeZoneArg, - this.calendar, - ) - } - - add(durationArg: DurationArg, options?: Temporal.ArithmeticOptions): Temporal.ZonedDateTime { - return translateZonedDateTime(this, ensureObj(Duration, durationArg), options) - } - - subtract(durationArg: DurationArg, options?: Temporal.ArithmeticOptions): Temporal.ZonedDateTime { - return translateZonedDateTime(this, negateDuration(ensureObj(Duration, durationArg)), options) - } - - until(other: ZonedDateTimeArg, options?: DiffOptions): Temporal.Duration { - return diffZonedDateTimes(this, ensureObj(ZonedDateTime, other), false, options) - } - - since(other: ZonedDateTimeArg, options?: DiffOptions): Temporal.Duration { - return diffZonedDateTimes(this, ensureObj(ZonedDateTime, other), true, options) - } - - round(options: RoundOptions): Temporal.ZonedDateTime { - const roundingConfig = parseRoundingOptions( - options, - NANOSECOND, // minUnit - DAY, // maxUnit - ) - - return roundZonedDateTime(this, roundingConfig) - } - - equals(other: ZonedDateTimeArg): boolean { - return zonedDateTimesEqual(this, ensureObj(ZonedDateTime, other)) - } - - startOfDay(): Temporal.ZonedDateTime { - return createZonedDateTimeFromFields({ - ...this.getISOFields(), - ...zeroISOTimeFields, - offsetNanoseconds: this.offsetNanoseconds, - }, false, OFFSET_PREFER) - } - - // TODO: turn into a lazy-getter, like what mixinCalendarFields does - get hoursInDay(): number { - return computeNanoInDay(this.getISOFields()) / nanoInHour - } - - toString(options?: Temporal.CalendarTypeToStringOptions): string { - const formatConfig = parseTimeToStringOptions(options) - const offsetDisplay = parseOffsetDisplayOption(options) - const timeZoneDisplay = parseTimeZoneDisplayOption(options) - const calendarDisplay = parseCalendarDisplayOption(options) - const roundedZdt = roundZonedDateTime(this, formatConfig) - - return formatDateTimeISO(roundedZdt.getISOFields(), formatConfig) + - (offsetDisplay === OFFSET_DISPLAY_AUTO - ? formatOffsetISO(roundToMinute(roundedZdt.offsetNanoseconds)) - : '' - ) + - formatTimeZoneID(this.timeZone.toString(), timeZoneDisplay) + - formatCalendarID(this.calendar.toString(), calendarDisplay) - } - - toPlainYearMonth(): Temporal.PlainYearMonth { return createYearMonth(this.getISOFields()) } - toPlainMonthDay(): Temporal.PlainMonthDay { return this.calendar.monthDayFromFields(this) } - toPlainDateTime(): Temporal.PlainDateTime { return createDateTime(this.getISOFields()) } - toPlainDate(): Temporal.PlainDate { return createDate(this.getISOFields()) } - toPlainTime(): Temporal.PlainTime { return createTime(this.getISOFields()) } - toInstant(): Temporal.Instant { return new Instant(this.epochNanoseconds) } -} - -// mixins -export interface ZonedDateTime extends IsoMasterMethods {} -mixinIsoMasterMethods(ZonedDateTime) -// -export interface ZonedDateTime { [Symbol.toStringTag]: 'Temporal.ZonedDateTime' } -attachStringTag(ZonedDateTime, 'ZonedDateTime') -// -export interface ZonedDateTime extends DateCalendarFields { calendar: Temporal.CalendarProtocol } -mixinCalendarFields(ZonedDateTime, dateCalendarFields) -// -export interface ZonedDateTime extends LocalTimeFields {} -mixinISOFields(ZonedDateTime, timeUnitNames) -// -export interface ZonedDateTime extends ComputedEpochFields {} -mixinEpochFields(ZonedDateTime) -// -export interface ZonedDateTime extends ToLocaleStringMethods {} -mixinLocaleStringMethods(ZonedDateTime, createZonedFormatFactoryFactory({ - year: 'numeric', - month: 'numeric', - day: 'numeric', - weekday: undefined, - hour: 'numeric', - minute: '2-digit', - second: '2-digit', -}, { - timeZoneName: 'short', -}, {})) - -export function createZonedDateTimeFromFields( - fields: OffsetComputableFields, - fuzzyMatching?: boolean, - offsetHandling?: OffsetHandlingInt, - disambigOptions?: Temporal.AssignmentOptions, -): ZonedDateTime { - const epochNano = computeZonedDateTimeEpochNano( - fields, - fuzzyMatching, - offsetHandling, - disambigOptions, - ) - return new ZonedDateTime(epochNano, fields.timeZone, fields.calendar) -} - -export function buildZonedDateTimeISOFields( - epochNano: LargeInt, - timeZone: Temporal.TimeZoneProtocol, -): [ISODateTimeFields, number] { - const instant = new Instant(epochNano) // will do validation - const offsetNano = timeZone.getOffsetNanosecondsFor(instant) - const isoFields = epochNanoToISOFields(epochNano.add(offsetNano)) - return [isoFields, offsetNano] -} - -function translateZonedDateTime( - zdt: ZonedDateTime, - dur: DurationFields, - options: Temporal.ArithmeticOptions | undefined, -): ZonedDateTime { - const isoFields = zdt.getISOFields() - const epochNano = translateZonedDateTimeFields(isoFields, dur, options) - return new ZonedDateTime(epochNano, isoFields.timeZone, isoFields.calendar) -} - -function roundZonedDateTime( - zdt: ZonedDateTime, - roundingConfig: RoundingConfig, -): ZonedDateTime { - const isoFields = zdt.getISOFields() - const epochNano = roundZonedDateTimeFields(isoFields, zdt.offsetNanoseconds, roundingConfig) - return new ZonedDateTime(epochNano, isoFields.timeZone, isoFields.calendar) -} - -// TODO: make common util with PlainDateTime, because leverages same diffDateTimes? -function diffZonedDateTimes( - dt0: ZonedDateTime, - dt1: ZonedDateTime, - flip: boolean, - options: DiffOptions | undefined, -): Duration { - const diffConfig = parseDiffOptions( - options, - HOUR, // largestUnitDefault - NANOSECOND, // smallestUnitDefault - NANOSECOND, // minUnit - YEAR, // maxUnit - ) - const { largestUnit } = diffConfig - - if (largestUnit >= DAY && dt0.timeZone.id !== dt1.timeZone.id) { - throw new Error('Must be same timeZone') - } - - return createDuration( - diffDateTimes(dt0, dt1, getCommonCalendar(dt0, dt1), flip, diffConfig), - ) -} diff --git a/packages/temporal-polyfill/src/shim.ts b/packages/temporal-polyfill/src/shim.ts deleted file mode 100644 index 49a3a8df..00000000 --- a/packages/temporal-polyfill/src/shim.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { toTemporalInstant } from './native/date' -import { DateTimeFormat } from './native/intlTemporal' -import { Temporal } from './public/temporal' -import { getGlobalThis } from './utils/dom' - -// TODO: better way to extend already-polyfilled rootObj -// somehow WRAP the whole lib, UMD-style? - -export function shim(): void { - const theGlobalThis = getGlobalThis() - - // TODO: when Temporal is in stage 4, make polyfill conditional - // if (!theGlobalThis.Temporal) { - - theGlobalThis.Temporal = Temporal - Intl.DateTimeFormat = DateTimeFormat - // eslint-disable-next-line no-extend-native - Date.prototype.toTemporalInstant = toTemporalInstant - - // } -} diff --git a/packages/temporal-polyfill/src/timeZoneImpl/fixedTimeZoneImpl.ts b/packages/temporal-polyfill/src/timeZoneImpl/fixedTimeZoneImpl.ts deleted file mode 100644 index 299b2d81..00000000 --- a/packages/temporal-polyfill/src/timeZoneImpl/fixedTimeZoneImpl.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { RawTransition, TimeZoneImpl } from './timeZoneImpl' - -export class FixedTimeZoneImpl extends TimeZoneImpl { - constructor( - id: string, - private offsetNano: number, - ) { - super(id) - } - - getPossibleOffsets(): number[] { - return [this.offsetNano] - } - - getOffset(): number { - return this.offsetNano - } - - getTransition(): RawTransition | undefined { - return undefined - } -} diff --git a/packages/temporal-polyfill/src/timeZoneImpl/intlTimeZoneImpl.ts b/packages/temporal-polyfill/src/timeZoneImpl/intlTimeZoneImpl.ts deleted file mode 100644 index 0774e8f2..00000000 --- a/packages/temporal-polyfill/src/timeZoneImpl/intlTimeZoneImpl.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { epochNanoToISOYear, isoToEpochMilli, isoYearToEpochSeconds } from '../dateUtils/epoch' -import { hashIntlFormatParts, normalizeShortEra } from '../dateUtils/intlFormat' -import { milliInSecond, nanoInSecond, secondsInDay } from '../dateUtils/units' -import { OrigDateTimeFormat } from '../native/intlUtils' -import { LargeInt, compareLargeInts, createLargeInt } from '../utils/largeInt' -import { specialCases } from './specialCases' -import { RawTransition, TimeZoneImpl } from './timeZoneImpl' - -const DST_EARLIEST_YEAR = 1847 // year with the first DST transitions -const DST_PERSIST_YEAR = new Date().getUTCFullYear() + 10 // DST won't change on or after this - -const ISLAND_SEARCH_DAYS = [ - 182, // 50% through year - 91, // 25% through year - 273, // 75% through year -] - -// TODO: general question: why not use minutes internally instead of seconds? -// No... Temporal.ZonedDateTime.from({ year: 1971, month: 1, day: 1, timeZone: 'Africa/Monrovia' }) -// .offset => '-00:44:30' // seconds - -export class IntlTimeZoneImpl extends TimeZoneImpl { - private format: Intl.DateTimeFormat - - // a cache of second offsets at the last second of each year - private yearEndOffsets: { [year: string]: number } - - private transitionsInYear: { [year: string]: RawTransition[] } - - constructor(id: string) { - const format = new OrigDateTimeFormat('en-GB', { // gives 24-hour clock - era: 'short', - year: 'numeric', - month: 'numeric', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - timeZone: id, - }) - super(format.resolvedOptions().timeZone) - this.format = format - this.yearEndOffsets = {} - this.transitionsInYear = specialCases[id] || {} - } - - // `zoneNano` is like epochNano, but from zone's pseudo-epoch - getPossibleOffsets(zoneNano: LargeInt): number[] { - let lastOffsetNano: number | undefined - - const transitions = [ - this.getTransition(zoneNano, -1), - this.getTransition(zoneNano.sub(1), 1), - // ^subtract 1 b/c getTransition is always exclusive - ].filter(Boolean) as RawTransition[] - - // loop transitions from past to future - for (const transition of transitions) { - const [transitionEpochNano, offsetNanoBefore, offsetNanoAfter] = transition - // FYI, a transition's switchover to offsetNanoAfter happens - // *inclusively* as transitionEpochNano - - // two possibilities (no guarantee of chronology) - const epochNanoA = zoneNano.sub(offsetNanoBefore) - const epochNanoB = zoneNano.sub(offsetNanoAfter) - - // is the transition after both possibilities? - if ( - compareLargeInts(transitionEpochNano, epochNanoA) > 0 && // > - compareLargeInts(transitionEpochNano, epochNanoB) > 0 // > - ) { - return [offsetNanoBefore] - - // is the transition before both possibilities? - } else if ( - compareLargeInts(transitionEpochNano, epochNanoA) <= 0 && // <= - compareLargeInts(transitionEpochNano, epochNanoB) <= 0 // <= - ) { - // keep looping... - - // stuck in a transition? - } else { - // if the offset increases, we're inside a forward transition that looses an hour - // return an empty result because zoneNano lives within this empty region - if (offsetNanoBefore < offsetNanoAfter) { - return [] - } else { - return [offsetNanoBefore, offsetNanoAfter] - } - } - - lastOffsetNano = offsetNanoAfter - } - - // only found transitions before zoneSecs - if (lastOffsetNano !== undefined) { - return [lastOffsetNano] - } - - // found no transitions? - return [ - this.getYearEndOffsetSec(epochNanoToISOYear(zoneNano)) * nanoInSecond, - ] - } - - getOffset(epochNano: LargeInt): number { - return this.getOffsetForEpochSecs( - epochNano.div(nanoInSecond).toNumber(), - ) * nanoInSecond - } - - private getOffsetForEpochSecs(epochSec: number): number { - // NOTE: if Intl.DateTimeFormat's timeZoneName:'shortOffset' option were available, - // we could parse that. - const map = hashIntlFormatParts(this.format, epochSec * milliInSecond) - - let year = parseInt(map.year) - if (normalizeShortEra(map.era) === 'bce') { - year = -(year - 1) - } - - const zoneMilli = isoToEpochMilli( - year, - parseInt(map.month), - parseInt(map.day), - parseInt(map.hour), - parseInt(map.minute), - parseInt(map.second), - ) - const zoneSecs = Math.floor(zoneMilli / milliInSecond) - - return zoneSecs - epochSec - } - - /* - Always exclusive. Will never return a transition that starts exactly on epochNano - */ - getTransition(epochNano: LargeInt, direction: -1 | 1): RawTransition | undefined { - let year = epochNanoToISOYear(epochNano) - - if (year > DST_PERSIST_YEAR) { - // look ahead or behind ONE year - const res = this.getTransitionFrom(year, year + direction, direction, epochNano) - if (res || direction > 0) { - return res - } - // fast-backwards in-bounds - year = DST_PERSIST_YEAR - } - - return this.getTransitionFrom( - Math.max(year, DST_EARLIEST_YEAR), - direction < 0 - ? DST_EARLIEST_YEAR - 1 // inclusive -> exclusive - : DST_PERSIST_YEAR, - direction, - epochNano, - ) - } - - getTransitionFrom( - year: number, - endYear: number, // exclusive - direction: -1 | 1, - epochNano: LargeInt, - ): RawTransition | undefined { - for (; year !== endYear; year += direction) { - let transitions = this.getTransitionsInYear(year) - - if (direction < 0) { - transitions = transitions.slice().reverse() - } - - for (const transition of transitions) { - // does the current transition overtake epochNano in the direction of travel? - if (compareLargeInts(transition[0], epochNano) === direction) { - return transition - } - } - } - } - - private getYearEndOffsetSec(utcYear: number): number { - const { yearEndOffsets } = this - return yearEndOffsets[utcYear] || - (yearEndOffsets[utcYear] = this.getOffsetForEpochSecs( - isoYearToEpochSeconds(utcYear + 1) - 1, - )) - } - - private getTransitionsInYear(utcYear: number): RawTransition[] { - const { transitionsInYear } = this - return transitionsInYear[utcYear] || - (transitionsInYear[utcYear] = this.computeTransitionsInYear(utcYear)) - } - - private computeTransitionsInYear(utcYear: number): RawTransition[] { - const startOffsetSec = this.getYearEndOffsetSec(utcYear - 1) // right before start of year - const endOffsetSec = this.getYearEndOffsetSec(utcYear) // at end of year - // FYI, a transition could be in the first second of the year, thus the exclusiveness - - // TODO: make a isoYearEndEpochSeconds util? use in getYearEndOffsetSec? - const startEpochSec = isoYearToEpochSeconds(utcYear) - 1 - const endEpochSec = isoYearToEpochSeconds(utcYear + 1) - 1 - - if (startOffsetSec !== endOffsetSec) { - return [this.searchTransition(startEpochSec, endEpochSec, startOffsetSec, endOffsetSec)] - } - - const island = this.searchIsland(startOffsetSec, startEpochSec) - if (island !== undefined) { - return [ - this.searchTransition(startEpochSec, island[0], startOffsetSec, island[1]), - this.searchTransition(island[0], endEpochSec, island[1], endOffsetSec), - ] - } - - return [] - } - - // assumes the offset changes at some point between startSecs -> endSecs. - // finds the point where it switches over to the new offset. - private searchTransition( - startEpochSec: number, - endEpochSec: number, - startOffsetSec: number, - endOffsetSec: number, - ): RawTransition { - // keep doing binary search until start/end are 1 second apart - while (endEpochSec - startEpochSec > 1) { - const middleEpochSecs = Math.floor(startEpochSec + (endEpochSec - startEpochSec) / 2) - const middleOffsetSecs = this.getOffsetForEpochSecs(middleEpochSecs) - - if (middleOffsetSecs === startOffsetSec) { - // middle is same as start. move start to the middle - startEpochSec = middleEpochSecs - } else { - // middle is same as end. move end to the middle - endEpochSec = middleEpochSecs - } - } - return [ - createLargeInt(endEpochSec).mult(nanoInSecond), - startOffsetSec * nanoInSecond, - endOffsetSec * nanoInSecond, - ] - } - - // assumes the offset is the same at startSecs and endSecs. - // pokes around the time in-between to see if there's a temporary switchover. - private searchIsland( - outerOffsetSec: number, - startEpochSec: number, - ): [number, number] | undefined { // [epochSec, offsetSec] - for (const days of ISLAND_SEARCH_DAYS) { - const epochSec = startEpochSec + days * secondsInDay - const offsetSec = this.getOffsetForEpochSecs(epochSec) - if (offsetSec !== outerOffsetSec) { - return [epochSec, offsetSec] - } - } - } -} diff --git a/packages/temporal-polyfill/src/timeZoneImpl/specialCases.ts b/packages/temporal-polyfill/src/timeZoneImpl/specialCases.ts deleted file mode 100644 index a050416c..00000000 --- a/packages/temporal-polyfill/src/timeZoneImpl/specialCases.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { nanoInMilli } from '../dateUtils/units' -import { LargeInt, createLargeInt } from '../utils/largeInt' -import { RawTransition } from './timeZoneImpl' - -export const specialCases: { - [timeZoneID: string]: { [year: string]: RawTransition[] } -} = { - 'Pacific/Apia': { - 2011: [ - // TODO: this was much nicer when specified in seconds - // TODO: have RawTransitions be seconds again? - [toNano(1301752800000), -36000000000000, -39600000000000], // start DST - [toNano(1316872800000), -39600000000000, -36000000000000], // end DST - [toNano(1325239200000), -36000000000000, 50400000000000], // change of time zone - ], - }, -} - -function toNano(milli: number): LargeInt { - return createLargeInt(milli).mult(nanoInMilli) -} diff --git a/packages/temporal-polyfill/src/timeZoneImpl/timeZoneImpl.ts b/packages/temporal-polyfill/src/timeZoneImpl/timeZoneImpl.ts deleted file mode 100644 index c3c90e28..00000000 --- a/packages/temporal-polyfill/src/timeZoneImpl/timeZoneImpl.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { LargeInt } from '../utils/largeInt' - -export type RawTransition = [ - LargeInt, // epochNano - number, // offsetNanoBefore - number, // offsetNanoAfter -] - -export abstract class TimeZoneImpl { - constructor(public id: string) {} - - abstract getPossibleOffsets(zoneNano: LargeInt): number[] // offsetNanos - abstract getOffset(epochNano: LargeInt): number // offsetNano - abstract getTransition(epochNano: LargeInt, direction: -1 | 1): RawTransition | undefined -} diff --git a/packages/temporal-polyfill/src/timeZoneImpl/timeZoneImplQuery.ts b/packages/temporal-polyfill/src/timeZoneImpl/timeZoneImplQuery.ts deleted file mode 100644 index 541f30a7..00000000 --- a/packages/temporal-polyfill/src/timeZoneImpl/timeZoneImplQuery.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { formatOffsetISO } from '../dateUtils/isoFormat' -import { tryParseOffsetNano } from '../dateUtils/parse' -import { nanoInDay } from '../dateUtils/units' -import { FixedTimeZoneImpl } from './fixedTimeZoneImpl' -import { IntlTimeZoneImpl } from './intlTimeZoneImpl' -import { TimeZoneImpl } from './timeZoneImpl' - -const implCache: { [zoneName: string]: TimeZoneImpl } = { - UTC: new FixedTimeZoneImpl('UTC', 0), -} - -export function queryTimeZoneImpl(id: string): TimeZoneImpl { - id = String(id) - const key = id.toLocaleUpperCase() // uppercase is better for 'UTC' - - if (implCache[key]) { - return implCache[key] - } - - // parse a literal time zone offset - const offsetNano = tryParseOffsetNano(id) - if (offsetNano !== undefined) { - if (Math.abs(offsetNano) > nanoInDay) { - throw new RangeError('Offset out of bounds') - } - // don't store fixed-offset zones in cache. there could be many - return new FixedTimeZoneImpl( - formatOffsetISO(offsetNano), - offsetNano, - ) - } - - return (implCache[key] = new IntlTimeZoneImpl(id)) -} diff --git a/packages/temporal-polyfill/src/typeOverrides/global.d.ts b/packages/temporal-polyfill/src/typeOverrides/global.d.ts new file mode 100644 index 00000000..4a889e48 --- /dev/null +++ b/packages/temporal-polyfill/src/typeOverrides/global.d.ts @@ -0,0 +1 @@ +export * from 'temporal-spec/global' diff --git a/packages/temporal-polyfill/src/typeOverrides/impl.d.ts b/packages/temporal-polyfill/src/typeOverrides/impl.d.ts new file mode 100644 index 00000000..8746380b --- /dev/null +++ b/packages/temporal-polyfill/src/typeOverrides/impl.d.ts @@ -0,0 +1 @@ +export * from 'temporal-spec' diff --git a/packages/temporal-polyfill/src/typeOverrides/index.d.ts b/packages/temporal-polyfill/src/typeOverrides/index.d.ts new file mode 100644 index 00000000..8746380b --- /dev/null +++ b/packages/temporal-polyfill/src/typeOverrides/index.d.ts @@ -0,0 +1 @@ +export * from 'temporal-spec' diff --git a/packages/temporal-polyfill/src/utils/dom.ts b/packages/temporal-polyfill/src/utils/dom.ts deleted file mode 100644 index f7cd6bd5..00000000 --- a/packages/temporal-polyfill/src/utils/dom.ts +++ /dev/null @@ -1,8 +0,0 @@ - -declare const window: any - -// BAD because `any` -// TODO: make this variaible available as part of intro/outro that wraps everything -export function getGlobalThis(): any { - return typeof globalThis !== 'undefined' ? globalThis : window -} diff --git a/packages/temporal-polyfill/src/utils/largeInt.ts b/packages/temporal-polyfill/src/utils/largeInt.ts deleted file mode 100644 index 749a79b3..00000000 --- a/packages/temporal-polyfill/src/utils/largeInt.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { compareValues, numSign } from './math' -import { padEnd } from './string' - -// MAX_SAFE_INTEGER has 16 digits, but go lower so low value doesn't overflow -const maxLowDigits = 8 -const maxLowNum = Math.pow(10, maxLowDigits) - -export type LargeIntArgStrict = LargeInt | bigint | string -export type LargeIntArg = LargeIntArgStrict | number // allow number, which might loose precision - -export class LargeInt { - constructor( - public high: number, - public low: number, - ) {} - - sign(): Temporal.ComparisonResult { - return numSign(this.high) || numSign(this.low) - } - - neg(): LargeInt { - return new LargeInt(-this.high || 0, -this.low || 0) // prevents -0 - } - - abs(): LargeInt { - return this.sign() < 0 ? this.neg() : this - } - - add(input: LargeInt | number): LargeInt { - const [high, low] = getHighLow(input) - return balanceAndCreate(this.high + high, this.low + low) - } - - sub(input: LargeInt | number): LargeInt { - const [high, low] = getHighLow(input) - return balanceAndCreate(this.high - high, this.low - low) - } - - mult(n: number): LargeInt { - return balanceAndCreate(this.high * n, this.low * n) - } - - div(n: number): LargeInt { - const highFloat = this.high / n - let highStr = String(highFloat) // more exact output than toFixed - - if (highStr.indexOf('e-') !== -1) { // has negative scientific notation? - highStr = highFloat.toFixed(20) // return maximum-guaranteed precision - } - - const highDot = highStr.indexOf('.') - let lowScraps = 0 - - if (highDot !== -1) { - let afterDot = highStr.substr(highDot + 1) - afterDot = padEnd(afterDot, maxLowDigits, '0') - afterDot = afterDot.substr(0, maxLowDigits) - lowScraps = parseInt(afterDot) * (numSign(highFloat) || 1) - } - - const high = Math.trunc(highFloat) || 0 // prevent -0 - const low = Math.trunc(this.low / n) + lowScraps - - return balanceAndCreate(high, low) - } - - toNumber(): number { - return this.high * maxLowNum + this.low - } - - toBigInt(): bigint { - return BigInt(this.high) * BigInt(maxLowNum) + BigInt(this.low) - } - - // valueOf(): void { - // throw new Error('Cant get valueOf of LargeInt') - // } -} - -export function createLargeInt(input: LargeIntArg): LargeInt -export function createLargeInt(input: LargeIntArgStrict, strict: true): LargeInt -export function createLargeInt(input: LargeIntArg, strict?: true): LargeInt { - let high: number - let low: number - if (input instanceof LargeInt) { - high = input.high - low = input.low - } else if (typeof input === 'number') { // TODO: don't allow this in Instant or ZonedDateTime - if (strict) { - throw new TypeError('Must supply bigint, not number') - } - high = Math.trunc(input / maxLowNum) - low = input % maxLowNum || 0 - } else if (typeof input === 'bigint') { - const maxNumBI = BigInt(maxLowNum) - high = Number(input / maxNumBI) - low = Number(input % maxNumBI || 0) - } else if (typeof input === 'string') { // TODO: write test - input = input.trim() - if (input.match(/\D/)) { - throw new SyntaxError(`Cannot parse ${input} to a BigInt`) - } - const gapIndex = input.length - maxLowDigits - high = Number(input.substr(gapIndex)) - low = Number(input.substr(0, gapIndex)) - } else { - throw new Error('Invalid type of BigNano') - } - return new LargeInt(high, low) -} - -export function compareLargeInts(a: LargeInt, b: LargeInt): Temporal.ComparisonResult { - return compareValues(a.high, b.high) || compareValues(a.low, b.low) -} - -function getHighLow(input: LargeInt | number): [number, number] { - if (typeof input === 'number') { - return [0, input] - } - return [input.high, input.low] -} - -function balanceAndCreate(high: number, low: number): LargeInt { - let newLow = low % maxLowNum || 0 - let newHigh = high + Math.trunc(low / maxLowNum) - const signHigh = numSign(newHigh) // all signs must equal this - const signLow = numSign(newLow) - - // ensure same signs. more performant way to do this? - if (signLow && signHigh && signLow !== signHigh) { - newHigh += signLow - newLow -= maxLowNum * signLow - } - - return new LargeInt(newHigh, newLow) -} diff --git a/packages/temporal-polyfill/src/utils/math.ts b/packages/temporal-polyfill/src/utils/math.ts deleted file mode 100644 index eb961937..00000000 --- a/packages/temporal-polyfill/src/utils/math.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Temporal } from 'temporal-spec' -import { nanoInMinute } from '../dateUtils/units' -import { LargeInt } from './largeInt' - -export type RoundingFunc = (n: number) => number - -export function compareValues(a: number, b: number): Temporal.ComparisonResult { - if (a < b) { - return -1 - } else if (a > b) { - return 1 - } - return 0 -} - -export function numSign(num: number): Temporal.ComparisonResult { - return compareValues(num, 0) -} - -export function roundToIncrement( - num: number, - inc: number, - roundingFunc: RoundingFunc, -): number { - return roundingFunc(num / inc) * inc -} - -export function roundToMinute(nano: number): number { - return roundToIncrement(nano, nanoInMinute, halfExpand) -} - -// like round, but does rounds negatives "down" (closer to -0.9) -// use elsewhere? -function halfExpand(n: number) { - return Math.round(Math.abs(n)) * numSign(n) -} - -export function roundToIncrementBI( - num: LargeInt, - inc: number, - roundingFunc: RoundingFunc, -): LargeInt { - const wholeUnits = num.div(inc) - const wholeNum = wholeUnits.mult(inc) - const leftover = num.sub(wholeNum).toNumber() - return wholeNum.add(roundingFunc(leftover / inc) * inc) -} - -// wraps `n` to 0...max (not including max) -export function positiveModulo(n: number, max: number): number { - return (n % max + max) % max -} diff --git a/packages/temporal-polyfill/src/utils/obj.ts b/packages/temporal-polyfill/src/utils/obj.ts deleted file mode 100644 index b4ae2c9d..00000000 --- a/packages/temporal-polyfill/src/utils/obj.ts +++ /dev/null @@ -1,79 +0,0 @@ -export type ValueOf = T[keyof T] -type GenericHash = Record - -// TODO: just use Symbols instead? - -// The `object` type is literally required for WeakMap -// eslint-disable-next-line @typescript-eslint/ban-types -export function createWeakMap(): [ - (obj: Subject) => Storage, // getter - (obj: Subject, storage: Storage) => void // setter -] { - const map = new WeakMap() - return [ - map.get.bind(map) as (obj: Subject) => Storage, // assume always previously set - map.set.bind(map), - ] -} - -export function attachGetters( - ObjClass: { prototype: Obj }, - getters: { [methodName: string]: (this: Obj) => unknown }, -): void { - Object.defineProperties( - ObjClass.prototype, - mapHash( - getters, - (func) => ({ - get: func, - configurable: true, // what classes do. TODO: ensure everywhere - }), - ), - ) -} - -export function mapHash( - hash: Hash, - func: (input: ValueOf, key: string) => ResType, -): { [Key in keyof Hash]: ResType } { - const res = {} as { [Key in keyof Hash]: ResType } - for (const key in hash) { - res[key] = func(hash[key], key) - } - return res -} - -export function mapHashByKeys( - input: { [prop: string]: PropInputType }, - keys: string[], - func: (inputProp: PropInputType) => PropOutputType, -): { [prop: string]: PropOutputType } { - const output = {} as { [prop: string]: PropOutputType } - - for (const key of keys) { - output[key] = func(input[key]) - } - - return output -} - -export function excludeUndefined(obj: GenericHash): GenericHash { - const res: GenericHash = {} - for (const key in obj) { - if (obj[key] !== undefined) { - res[key] = obj[key] - } - } - return res -} - -export function strArrayToHash( - strs: string[], - func: (str: string, index: number) => FieldType, -): { [key: string]: FieldType } { - const res: { [key: string]: FieldType } = {} - strs.forEach((str, i) => { - res[str] = func(str, i) - }) - return res -} diff --git a/packages/temporal-polyfill/src/utils/string.ts b/packages/temporal-polyfill/src/utils/string.ts deleted file mode 100644 index 8c35d5f1..00000000 --- a/packages/temporal-polyfill/src/utils/string.ts +++ /dev/null @@ -1,27 +0,0 @@ - -export function capitalizeFirstLetter(s: string): string { - return s.charAt(0).toUpperCase() + s.slice(1) -} - -/* -converts a positive integer to a string with a guaranteed length, padding zeros on the left side -*/ -export function padZeros(num: number, length: number): string { - return padStart(String(num), length, '0') -} - -export function padStart(str: string, len: number, padChar: string): string { - return buildPadding(str, len, padChar) + str -} - -export function padEnd(str: string, len: number, padChar: string): string { - return str + buildPadding(str, len, padChar) -} - -function buildPadding(str: string, len: number, padChar: string): string { - return new Array(Math.max(0, len - str.length + 1)).join(padChar) -} - -export function getSignStr(num: number): string { - return num < 0 ? '-' : '+' -} diff --git a/test.html b/packages/temporal-polyfill/test.html similarity index 82% rename from test.html rename to packages/temporal-polyfill/test.html index 430354ea..33a96868 100644 --- a/test.html +++ b/packages/temporal-polyfill/test.html @@ -2,7 +2,7 @@ - +