├── .eslintrc.yml ├── .github └── workflows │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .npmignore ├── .prettierignore ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── copy-types.mjs ├── index.d.ts ├── lib ├── assert.ts ├── bigintmath.ts ├── calendar.ts ├── debug.ts ├── duration.ts ├── durationformat.d.ts ├── ecmascript.ts ├── index.ts ├── init.ts ├── instant.ts ├── internaltypes.d.ts ├── intl.ts ├── intrinsicclass.ts ├── legacydate.ts ├── math.ts ├── now.ts ├── plaindate.ts ├── plaindatetime.ts ├── plainmonthday.ts ├── plaintime.ts ├── plainyearmonth.ts ├── regex.ts ├── slots.ts ├── temporal.ts ├── timeduration.ts └── zoneddatetime.ts ├── package-lock.json ├── package.json ├── polyfill ├── lib │ ├── slots.mjs │ └── timeduration.mjs └── test │ └── ecmascript.mjs ├── release-checklist.md ├── rollup.config.js ├── runtest262.mjs ├── test ├── all.mjs ├── cldr-timezone.json ├── datemath.mjs ├── ecmascript.mjs ├── exhaust.mjs ├── expected-failures-before-node16.txt ├── expected-failures-before-node18.txt ├── expected-failures-before-node20.txt ├── expected-failures-before-node22.txt ├── expected-failures-before-node23.txt ├── expected-failures-cldr42.txt ├── expected-failures-es5.txt ├── expected-failures-opt.txt ├── expected-failures-todo-migrated-code.txt ├── expected-failures.txt ├── math.mjs ├── parseResults.js ├── plainmonthday.mjs ├── plainyearmonth.mjs ├── preprocessor.test262.cjs ├── resolve.source.mjs ├── timeduration.mjs └── validStrings.mjs ├── tools ├── .eslintrc.yml ├── package-lock.json ├── package.json ├── rebase-upstream-commits.ts ├── rebasing.md └── tsconfig.json └── tsconfig.json /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | es6: true 4 | node: true 5 | plugins: 6 | - prettier 7 | parser: '@typescript-eslint/parser' 8 | extends: 9 | - 'eslint:recommended' 10 | - 'plugin:prettier/recommended' 11 | parserOptions: 12 | ecmaVersion: 2020 13 | sourceType: module 14 | globals: 15 | Atomics: readonly 16 | BigInt: readonly 17 | SharedArrayBuffer: readonly 18 | globalThis: readonly 19 | ignorePatterns: 20 | - node_modules/ 21 | - dist/ 22 | - tsc-out/ 23 | - test262/ 24 | rules: 25 | array-element-newline: 26 | - error 27 | - consistent 28 | arrow-parens: error 29 | arrow-spacing: error 30 | brace-style: 31 | - error 32 | - 1tbs 33 | comma-dangle: error 34 | comma-spacing: error 35 | curly: 36 | - error 37 | - multi-line 38 | func-call-spacing: off 39 | function-call-argument-newline: 40 | - error 41 | - consistent 42 | indent: 43 | - error 44 | - 2 45 | - SwitchCase: 1 46 | keyword-spacing: error 47 | max-len: 48 | - error 49 | - code: 120 50 | ignoreRegExpLiterals: true 51 | no-alert: error 52 | no-console: error 53 | no-multiple-empty-lines: 54 | - error 55 | - max: 1 56 | no-trailing-spaces: error 57 | no-param-reassign: 58 | - error 59 | - props: false 60 | object-curly-spacing: 61 | - error 62 | - always 63 | object-property-newline: 64 | - error 65 | - allowAllPropertiesOnSameLine: true 66 | prefer-const: 67 | - off # TODO re-enable 68 | # - destructuring: "all" 69 | quote-props: 70 | - error 71 | - as-needed 72 | quotes: 73 | - error 74 | - single 75 | - avoidEscape: true 76 | semi: error 77 | space-infix-ops: error 78 | overrides: 79 | - files: 80 | - lib/duration.ts 81 | - lib/init.js 82 | - test/all.mjs 83 | - test/exhaust.mjs 84 | - test/validStrings.mjs 85 | rules: 86 | no-console: off 87 | - files: 88 | - test/*.mjs 89 | - test/**/* 90 | rules: 91 | no-param-reassign: off 92 | - files: 93 | - '*.json' 94 | rules: 95 | quotes: 96 | - error 97 | - double 98 | quote-props: off 99 | max-len: off 100 | semi: off 101 | - files: 102 | - '**/*.ts' 103 | plugins: 104 | - '@typescript-eslint' 105 | parserOptions: 106 | ecmaVersion: 2020 107 | sourceType: module 108 | project: 109 | - './tsconfig.json' 110 | - './tools/tsconfig.json' 111 | extends: 112 | - 'plugin:@typescript-eslint/recommended' 113 | # TODO turn this on - it catches lots of implicit 'any's 114 | # - 'plugin:@typescript-eslint/recommended-requiring-type-checking' 115 | rules: 116 | '@typescript-eslint/explicit-module-boundary-types': off 117 | '@typescript-eslint/no-empty-function': error 118 | '@typescript-eslint/no-empty-interface': 119 | - error 120 | - allowSingleExtends: true 121 | '@typescript-eslint/no-var-requires': off 122 | '@typescript-eslint/ban-ts-comment': off 123 | '@typescript-eslint/no-explicit-any': off # Todo re-enable this 124 | '@typescript-eslint/no-unnecessary-type-assertion': error 125 | '@typescript-eslint/func-call-spacing': error 126 | '@typescript-eslint/consistent-type-exports': error 127 | '@typescript-eslint/consistent-type-imports': error 128 | prefer-const: off 129 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Run linter 2 | on: pull_request 3 | jobs: 4 | lint: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | - name: use node.js v24.x 9 | uses: actions/setup-node@v3 10 | with: 11 | node-version: 24.x 12 | - run: node --version 13 | - run: npm ci --no-optional 14 | - run: npx tsc --version 15 | - run: npm run lint 16 | lint-unused-code: # look for unused functions and unused exports 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: use node.js v24.x 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 24.x 24 | - run: npm ci --no-optional 25 | - run: npm run prune 26 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | test-demitasse: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | version: [24.x, 23.x, 22.x, 20.x, 18.x, 16.x, 14.x] 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: use node.js v${{ matrix.version }} 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: ${{ matrix.version }} 20 | - run: npm ci --no-optional 21 | - run: npm test 22 | test-test262-matrix: 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | version: [24.x, 23.x, 22.x, 20.x, 18.x, 16.x, 14.x] 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v3 30 | with: 31 | submodules: true 32 | - name: use node.js v${{ matrix.version }} 33 | uses: actions/setup-node@v3 34 | with: 35 | node-version: ${{ matrix.version }} 36 | - run: npm ci 37 | - run: npm run test262 38 | env: 39 | HEAD_SHA: ${{ github.event.pull_request.head.sha }} 40 | test-test262-matrix-opt: 41 | strategy: 42 | fail-fast: false 43 | matrix: 44 | version: [24.x, 23.x, 22.x, 20.x, 18.x, 16.x, 14.x] 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v3 48 | with: 49 | submodules: true 50 | - name: use node.js v${{ matrix.version }} 51 | uses: actions/setup-node@v3 52 | with: 53 | node-version: ${{ matrix.version }} 54 | - run: npm ci 55 | - run: npm run test262 56 | env: 57 | NODE_ENV: production 58 | test-test262-matrix-es5: 59 | strategy: 60 | fail-fast: false 61 | matrix: 62 | version: [24.x, 23.x, 22.x, 20.x, 18.x, 16.x, 14.x] 63 | runs-on: ubuntu-latest 64 | steps: 65 | - uses: actions/checkout@v3 66 | with: 67 | submodules: true 68 | - name: use node.js v${{ matrix.version }} 69 | uses: actions/setup-node@v3 70 | with: 71 | node-version: ${{ matrix.version }} 72 | - run: npm ci 73 | - run: npm run test262 74 | env: 75 | NODE_ENV: production 76 | TRANSPILE: '1' 77 | test-validstrings: 78 | runs-on: ubuntu-latest 79 | steps: 80 | - uses: actions/checkout@v3 81 | - name: use node.js v22.x 82 | uses: actions/setup-node@v3 83 | with: 84 | node-version: 22.x 85 | - run: npm ci 86 | - run: | 87 | npm ci 88 | npm run testValidStrings 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | tsc-out/ 4 | .eslintcache 5 | .vscode/* 6 | !.vscode/launch.json 7 | *.tgz 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test262"] 2 | path = test262 3 | url = https://github.com/tc39/test262 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | rollup.config.js 2 | test 3 | types 4 | tsconfig.json 5 | *.sh 6 | .vscode 7 | dist/script.js 8 | dist/playground.cjs 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | tsc-out/ 4 | test262/ 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Test262 (edit subset in launch.json)", 6 | "request": "launch", 7 | "type": "node", 8 | "runtimeExecutable": "npm", 9 | "runtimeArgs": [ 10 | "run", 11 | "test262", 12 | // Replace the glob pattern below with tests you want to debug. 13 | // Comment out to run all tests, but will be very slow! 14 | // "PlainYearMonth/prototype/subtract/*.js" 15 | ], 16 | "console": "integratedTerminal", 17 | "internalConsoleOptions": "neverOpen", 18 | "autoAttachChildProcesses": true, 19 | "env": { 20 | // Extends timeouts to 1 hour (from 10 seconds default) so you can 21 | // debug a test stopped at a breakpoint before the runner kills it. 22 | "TIMEOUT": "3600000" 23 | } 24 | }, 25 | { 26 | "name": "Test262 (current file)", 27 | "request": "launch", 28 | "type": "node", 29 | "runtimeExecutable": "npm", 30 | "runtimeArgs": ["run", "test262", "${file}"], 31 | "console": "integratedTerminal", 32 | "internalConsoleOptions": "neverOpen", 33 | "autoAttachChildProcesses": true, 34 | "env": { 35 | // Extends timeouts to 1 hour (from 10 seconds default) so you can 36 | // debug a test stopped at a breakpoint before the runner kills it. 37 | "TIMEOUT": "3600000" 38 | } 39 | }, 40 | { 41 | "name": "Demitasse tests", 42 | "request": "launch", 43 | "type": "node", 44 | "runtimeExecutable": "npm", 45 | "runtimeArgs": ["test"], 46 | "console": "integratedTerminal", 47 | "internalConsoleOptions": "neverOpen", 48 | "autoAttachChildProcesses": true, 49 | }, 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017, 2018, 2019, 2020 ECMA International 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Temporal Polyfill 2 | 3 | ## Polyfill for [TC39 Proposal: Temporal](https://github.com/tc39/proposal-temporal) 4 | 5 | This polyfill was kicked off by some of the champions of the [Temporal proposal](https://github.com/tc39/proposal-temporal). 6 | The goal is to be ready for production use when the Temporal proposal reaches Stage 4, although like with all OSS work progress is dependent on contributors. 7 | We're eagerly welcoming to contributors who want to help build and maintain this polyfill. 8 | PRs are always welcome! 9 | 10 | Note that this polyfill is not affiliated with TC39. Links to other polyfills can be found [here](https://github.com/tc39/proposal-temporal/tree/main/#polyfills). 11 | 12 | This polyfill is compatible with Node.js 14 or later. 13 | 14 | ## v0.4.0 => v0.5.0 Breaking Changes and Migration 15 | 16 | Until recently, this polyfill lagged behind the proposal repo (and its not-for-production-use polyfill). 17 | It's now caught up with the last few years of changes to the Temporal proposal, including many removed APIs and other breaking changes. 18 | While breaking changes to any TC39 proposal are always possible before [Stage 4](https://tc39.es/process-document/), Temporal is close to adoption and no significant changes are expected in the future. 19 | 20 | The [changelog](./CHANGELOG.md#050) details what's changed, including tips for migrating existing code to the latest version. 21 | 22 | ## Roadmap 23 | 24 | - [x] Fork non-production polyfill from [tc39/proposal-temporal repo](https://github.com/tc39/proposal-temporal/tree/main/polyfill) 25 | - [x] Release initial pre-alpha to NPM at [@js-temporal/polyfill](https://www.npmjs.com/package/@js-temporal/polyfill) 26 | - [x] Sync the code in this repo with the handful of polyfill changes that have recently been made in the [tc39/proposal-temporal](https://github.com/tc39/proposal-temporal) repo 27 | - [x] Release alpha version to NPM 28 | - [x] Deprecate all other earlier Temporal polyfills 29 | - [x] Optimize slow operations by reducing calls to Intl.DateTimeFormat constructor (see [#7](https://github.com/js-temporal/temporal-polyfill/issues/7), [#8](https://github.com/js-temporal/temporal-polyfill/pull/8), [#10](https://github.com/js-temporal/temporal-polyfill/pull/10), [#12](https://github.com/js-temporal/temporal-polyfill/pull/12)) 30 | - [x] Convert to TypeScript for better maintainability 31 | - [x] Improve typing of sources for better maintainability 32 | - [x] Migrate to JSBI for improved compile-time safety around BigInt operations. 33 | - [ ] Optimize performance of other slow operations 34 | - [ ] Release production version to NPM 35 | 36 | ## Bug Reports and Feedback 37 | 38 | If you think you've found a bug in the Temporal API itself (not the implementation in this polyfill), please file an issue in the [tc39/proposal-temporal issue tracker](https://github.com/tc39/proposal-temporal/issues). 39 | 40 | If you've found a bug in this polyfill—meaning that the implementation here doesn't match the [Temporal spec](https://tc39.es/proposal-temporal/)—please file an issue in this repo's [issue tracker](https://github.com/js-temporal/temporal-polyfill/issues). 41 | 42 | ## Documentation 43 | 44 | Reference documentation and examples for the Temporal API can be found [here](https://tc39.es/proposal-temporal/docs/index.html). 45 | 46 | A cookbook to help you get started and learn the ins and outs of Temporal is available [here](https://tc39.es/proposal-temporal/docs/index.html) 47 | 48 | If you find a bug in the documentation, please file a bug over in the [tc39/proposal-temporal issue tracker](https://github.com/tc39/proposal-temporal/issues) issue tracker. 49 | 50 | Note that the Temporal documentation is in the process of being migrated to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript). 51 | You can track the progress of the MDN migration [here](https://github.com/tc39/proposal-temporal/issues/1449). 52 | 53 | ## Usage 54 | 55 | To install: 56 | 57 | ```bash 58 | $ npm install @js-temporal/polyfill 59 | ``` 60 | 61 | CJS Usage: 62 | 63 | ```javascript 64 | const { Temporal, Intl, toTemporalInstant } = require('@js-temporal/polyfill'); 65 | Date.prototype.toTemporalInstant = toTemporalInstant; 66 | ``` 67 | 68 | Import the polyfill as an ES6 module: 69 | 70 | ```javascript 71 | import { Temporal, Intl, toTemporalInstant } from '@js-temporal/polyfill'; 72 | Date.prototype.toTemporalInstant = toTemporalInstant; 73 | ``` 74 | 75 | Note that this polyfill currently does not install a global `Temporal` object like a real implementation will. 76 | This behavior avoids hiding the global Temporal object in environments where a real Temporal implementation is present. 77 | See [this issue](https://github.com/tc39/proposal-temporal/issues/778) for more background on this decision. 78 | Once JS engines start shipping with Temporal, we may decide to change this behavior to match built-in behavior more closely. 79 | See [#2](https://github.com/js-temporal/temporal-polyfill/issues/2) to provide feedback or track this issue. 80 | 81 | This polyfill ships ES2020 code for both CJS and ESM bundles - if your 82 | environment does not support ES2020, then please make sure to transpile the 83 | content of this package along with the rest of your code. 84 | 85 | This polyfill uses [JSBI](https://github.com/GoogleChromeLabs/jsbi) to enable backwards-compatibility for environments that don't support native bigints. If your project only ever runs in environments that do support native bigints (see [caniuse data](https://caniuse.com/bigint)), we highly recommend configuring the [JSBI Babel plugin](https://github.com/GoogleChromeLabs/babel-plugin-transform-jsbi-to-bigint) that translates JSBI calls to their native bigint equivalent, improving code-size and performance. We are exploring ways to provide separate builds for these use-cases in [#155](https://github.com/js-temporal/temporal-polyfill/issues/155). 86 | 87 | ## Contributing / Help Wanted 88 | 89 | We're eagerly welcoming to contributors who want to help build and maintain this polyfill. 90 | PRs are always welcome! 91 | -------------------------------------------------------------------------------- /copy-types.mjs: -------------------------------------------------------------------------------- 1 | import { cp } from 'node:fs/promises'; 2 | 3 | await cp('index.d.ts', 'index.d.cts'); 4 | -------------------------------------------------------------------------------- /lib/assert.ts: -------------------------------------------------------------------------------- 1 | export function assert(condition: boolean, message: string): asserts condition { 2 | if (!condition) throw new Error(`assertion failure: ${message}`); 3 | } 4 | 5 | export function assertNotReached(message?: string): never { 6 | const reason = message ? ` because ${message}` : ''; 7 | throw new Error(`assertion failure: code should not be reached${reason}`); 8 | } 9 | -------------------------------------------------------------------------------- /lib/bigintmath.ts: -------------------------------------------------------------------------------- 1 | import JSBI from 'jsbi'; 2 | 3 | export const ZERO = JSBI.BigInt(0); 4 | export const ONE = JSBI.BigInt(1); 5 | export const TWO = JSBI.BigInt(2); 6 | export const TEN = JSBI.BigInt(10); 7 | const TWENTY_FOUR = JSBI.BigInt(24); 8 | const SIXTY = JSBI.BigInt(60); 9 | export const THOUSAND = JSBI.BigInt(1e3); 10 | export const MILLION = JSBI.BigInt(1e6); 11 | export const BILLION = JSBI.BigInt(1e9); 12 | const HOUR_SECONDS = 3600; 13 | export const HOUR_NANOS = JSBI.multiply(JSBI.BigInt(HOUR_SECONDS), BILLION); 14 | export const MINUTE_NANOS_JSBI = JSBI.multiply(SIXTY, BILLION); 15 | export const DAY_NANOS_JSBI = JSBI.multiply(HOUR_NANOS, TWENTY_FOUR); 16 | 17 | /** Handle a JSBI or native BigInt. For user input, use ES.ToBigInt instead */ 18 | export function ensureJSBI(value: JSBI | bigint) { 19 | return typeof value === 'bigint' ? JSBI.BigInt(value.toString(10)) : value; 20 | } 21 | 22 | export function isEven(value: JSBI): boolean { 23 | return JSBI.equal(JSBI.remainder(value, TWO), ZERO); 24 | } 25 | 26 | export function abs(x: JSBI): JSBI { 27 | if (JSBI.lessThan(x, ZERO)) return JSBI.unaryMinus(x); 28 | return x; 29 | } 30 | 31 | export function compare(x: JSBI, y: JSBI): -1 | 0 | 1 { 32 | return JSBI.lessThan(x, y) ? -1 : JSBI.greaterThan(x, y) ? 1 : 0; 33 | } 34 | 35 | export function divmod(x: JSBI, y: JSBI): { quotient: JSBI; remainder: JSBI } { 36 | const quotient = JSBI.divide(x, y); 37 | const remainder = JSBI.remainder(x, y); 38 | return { quotient, remainder }; 39 | } 40 | -------------------------------------------------------------------------------- /lib/debug.ts: -------------------------------------------------------------------------------- 1 | interface GlobalDebugInfo { 2 | __debug__?: boolean; 3 | __enableAsserts__?: boolean; 4 | } 5 | 6 | export const DEBUG = !!(globalThis as GlobalDebugInfo).__debug__; 7 | export const ENABLE_ASSERTS = !!(globalThis as GlobalDebugInfo).__enableAsserts__ || DEBUG; 8 | -------------------------------------------------------------------------------- /lib/durationformat.d.ts: -------------------------------------------------------------------------------- 1 | // Ambient declaration file to bridge the gap until Intl.DurationFormat is 2 | // supported in TypeScript's own type declarations. Note that it is for internal 3 | // use, meaning that it describes the DurationFormat interface _without_ support 4 | // for Temporal.Duration objects. 5 | 6 | declare namespace Intl { 7 | type DurationCalendarUnitStyle = 'long' | 'short' | 'narrow'; 8 | type DurationDigitalUnitStyle = 'long' | 'short' | 'narrow' | 'numeric' | '2-digit'; 9 | type DurationFractionalUnitStyle = 'long' | 'short' | 'narrow' | 'numeric'; 10 | type DurationDisplay = 'auto' | 'always'; 11 | 12 | interface DurationFormatOptions { 13 | localeMatcher?: 'best fit' | 'basic' | undefined; 14 | numberingSystem?: string | undefined; 15 | style?: 'long' | 'short' | 'narrow' | 'digital' | undefined; 16 | years?: DurationCalendarUnitStyle | undefined; 17 | yearsDisplay?: DurationDisplay | undefined; 18 | months?: DurationCalendarUnitStyle | undefined; 19 | monthsDisplay?: DurationDisplay | undefined; 20 | weeks?: DurationCalendarUnitStyle | undefined; 21 | weeksDisplay?: DurationDisplay | undefined; 22 | days?: DurationCalendarUnitStyle | undefined; 23 | daysDisplay?: DurationDisplay | undefined; 24 | hours?: DurationDigitalUnitStyle | undefined; 25 | hoursDisplay?: DurationDisplay | undefined; 26 | minutes?: DurationDigitalUnitStyle | undefined; 27 | minutesDisplay?: DurationDisplay | undefined; 28 | seconds?: DurationDigitalUnitStyle | undefined; 29 | secondsDisplay?: DurationDisplay | undefined; 30 | milliseconds?: DurationFractionalUnitStyle | undefined; 31 | millisecondsDisplay?: DurationDisplay | undefined; 32 | microseconds?: DurationFractionalUnitStyle | undefined; 33 | microsecondsDisplay?: DurationDisplay | undefined; 34 | nanoseconds?: DurationFractionalUnitStyle | undefined; 35 | nanosecondsDisplay?: DurationDisplay | undefined; 36 | fractionalDigits?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | undefined; 37 | } 38 | 39 | interface ResolvedDurationFormatOptions { 40 | locale: string; 41 | numberingSystem: string; 42 | style: 'long' | 'short' | 'narrow' | 'digital'; 43 | years: DurationCalendarUnitStyle; 44 | yearsDisplay: DurationDisplay; 45 | months: DurationCalendarUnitStyle; 46 | monthsDisplay: DurationDisplay; 47 | weeks: DurationCalendarUnitStyle; 48 | weeksDisplay: DurationDisplay; 49 | days: DurationCalendarUnitStyle; 50 | daysDisplay: DurationDisplay; 51 | hours: DurationDigitalUnitStyle; 52 | hoursDisplay: DurationDisplay; 53 | minutes: DurationDigitalUnitStyle; 54 | minutesDisplay: DurationDisplay; 55 | seconds: DurationDigitalUnitStyle; 56 | secondsDisplay: DurationDisplay; 57 | milliseconds: DurationFractionalUnitStyle; 58 | millisecondsDisplay: DurationDisplay; 59 | microseconds: DurationFractionalUnitStyle; 60 | microsecondsDisplay: DurationDisplay; 61 | nanoseconds: DurationFractionalUnitStyle; 62 | nanosecondsDisplay: DurationDisplay; 63 | fractionalDigits: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | undefined; 64 | } 65 | 66 | interface DurationLike { 67 | years?: number; 68 | months?: number; 69 | weeks?: number; 70 | days?: number; 71 | hours?: number; 72 | minutes?: number; 73 | seconds?: number; 74 | milliseconds?: number; 75 | microseconds?: number; 76 | nanoseconds?: number; 77 | } 78 | 79 | interface DurationFormatPart { 80 | type: string; 81 | value: string; 82 | } 83 | 84 | class DurationFormat { 85 | constructor(locales?: LocalesArgument, options?: DurationFormatOptions); 86 | 87 | static supportedLocalesOf(locales?: LocalesArgument, options?: DurationFormatOptions): LocalesArgument; 88 | 89 | format(duration: DurationLike | string): string; 90 | formatToParts(duration: DurationLike | string): DurationFormatPart[]; 91 | resolvedOptions(): ResolvedDurationFormatOptions; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | // This entry point treats Temporal as a library, and does not polyfill it onto 2 | // the global object. 3 | // This is in order to avoid breaking the web in the future, if the polyfill 4 | // gains wide adoption before the API is finalized. We do not want checks such 5 | // as `if (typeof Temporal === 'undefined')` in the wild, until browsers start 6 | // shipping the finalized API. 7 | 8 | import * as Temporal from './temporal'; 9 | import * as Intl from './intl'; 10 | import { toTemporalInstant } from './legacydate'; 11 | 12 | // Work around https://github.com/babel/babel/issues/2025. 13 | const types = [ 14 | Temporal.Instant, 15 | Temporal.PlainDate, 16 | Temporal.PlainDateTime, 17 | Temporal.Duration, 18 | Temporal.PlainMonthDay, 19 | // Temporal.Now, // plain object (not a constructor), so no `prototype` 20 | Temporal.PlainTime, 21 | Temporal.PlainYearMonth, 22 | Temporal.ZonedDateTime 23 | ]; 24 | for (const type of types) { 25 | const descriptor = Object.getOwnPropertyDescriptor(type, 'prototype') as PropertyDescriptor; 26 | if (descriptor.configurable || descriptor.enumerable || descriptor.writable) { 27 | descriptor.configurable = false; 28 | descriptor.enumerable = false; 29 | descriptor.writable = false; 30 | Object.defineProperty(type, 'prototype', descriptor); 31 | } 32 | } 33 | 34 | export { Temporal, Intl, toTemporalInstant }; 35 | -------------------------------------------------------------------------------- /lib/init.ts: -------------------------------------------------------------------------------- 1 | // This is an alternate entry point that polyfills Temporal onto the global 2 | // object. This is used only for the browser playground and the test262 tests. 3 | // See the note in index.mjs. 4 | 5 | import * as Temporal from './temporal'; 6 | import * as Intl from './intl'; 7 | import { toTemporalInstant } from './legacydate'; 8 | 9 | Object.defineProperty(globalThis, 'Temporal', { 10 | value: {}, 11 | writable: true, 12 | enumerable: false, 13 | configurable: true 14 | }); 15 | const globalTemporal = (globalThis as unknown as { Temporal: typeof Temporal }).Temporal; 16 | copy(globalTemporal, Temporal); 17 | Object.defineProperty(globalTemporal, Symbol.toStringTag, { 18 | value: 'Temporal', 19 | writable: false, 20 | enumerable: false, 21 | configurable: true 22 | }); 23 | copy(globalTemporal.Now, Temporal.Now); 24 | copy(globalThis.Intl, Intl); 25 | Object.defineProperty(globalThis.Date.prototype, 'toTemporalInstant', { 26 | value: toTemporalInstant, 27 | writable: true, 28 | enumerable: false, 29 | configurable: true 30 | }); 31 | 32 | function copy(target: Record, source: Record) { 33 | for (const prop of Object.getOwnPropertyNames(source)) { 34 | Object.defineProperty(target, prop, { 35 | value: source[prop], 36 | writable: true, 37 | enumerable: false, 38 | configurable: true 39 | }); 40 | } 41 | } 42 | 43 | export { Temporal, Intl, toTemporalInstant }; 44 | -------------------------------------------------------------------------------- /lib/instant.ts: -------------------------------------------------------------------------------- 1 | import * as ES from './ecmascript'; 2 | import { MakeIntrinsicClass } from './intrinsicclass'; 3 | import { EPOCHNANOSECONDS, GetSlot } from './slots'; 4 | import type { Temporal } from '..'; 5 | import { DateTimeFormat } from './intl'; 6 | import type { InstantParams as Params, InstantReturn as Return } from './internaltypes'; 7 | 8 | import JSBI from 'jsbi'; 9 | 10 | export class Instant implements Temporal.Instant { 11 | constructor(epochNanoseconds: bigint | JSBI) { 12 | // Note: if the argument is not passed, ToBigInt(undefined) will throw. This check exists only 13 | // to improve the error message. 14 | if (arguments.length < 1) { 15 | throw new TypeError('missing argument: epochNanoseconds is required'); 16 | } 17 | 18 | const ns = ES.ToBigInt(epochNanoseconds); 19 | ES.CreateTemporalInstantSlots(this, ns); 20 | } 21 | 22 | get epochMilliseconds(): Return['epochMilliseconds'] { 23 | ES.CheckReceiver(this, ES.IsTemporalInstant); 24 | const value = GetSlot(this, EPOCHNANOSECONDS); 25 | return ES.epochNsToMs(value, 'floor'); 26 | } 27 | get epochNanoseconds(): Return['epochNanoseconds'] { 28 | ES.CheckReceiver(this, ES.IsTemporalInstant); 29 | return ES.ToBigIntExternal(JSBI.BigInt(GetSlot(this, EPOCHNANOSECONDS))); 30 | } 31 | 32 | add(temporalDurationLike: Params['add'][0]): Return['add'] { 33 | ES.CheckReceiver(this, ES.IsTemporalInstant); 34 | return ES.AddDurationToInstant('add', this, temporalDurationLike); 35 | } 36 | subtract(temporalDurationLike: Params['subtract'][0]): Return['subtract'] { 37 | ES.CheckReceiver(this, ES.IsTemporalInstant); 38 | return ES.AddDurationToInstant('subtract', this, temporalDurationLike); 39 | } 40 | until(other: Params['until'][0], options: Params['until'][1] = undefined): Return['until'] { 41 | ES.CheckReceiver(this, ES.IsTemporalInstant); 42 | return ES.DifferenceTemporalInstant('until', this, other, options); 43 | } 44 | since(other: Params['since'][0], options: Params['since'][1] = undefined): Return['since'] { 45 | ES.CheckReceiver(this, ES.IsTemporalInstant); 46 | return ES.DifferenceTemporalInstant('since', this, other, options); 47 | } 48 | round(roundToParam: Params['round'][0]): Return['round'] { 49 | ES.CheckReceiver(this, ES.IsTemporalInstant); 50 | if (roundToParam === undefined) throw new TypeError('options parameter is required'); 51 | const roundTo = 52 | typeof roundToParam === 'string' 53 | ? (ES.CreateOnePropObject('smallestUnit', roundToParam) as Exclude) 54 | : ES.GetOptionsObject(roundToParam); 55 | const roundingIncrement = ES.GetTemporalRoundingIncrementOption(roundTo); 56 | const roundingMode = ES.GetRoundingModeOption(roundTo, 'halfExpand'); 57 | const smallestUnit = ES.GetTemporalUnitValuedOption(roundTo, 'smallestUnit', 'time', ES.REQUIRED); 58 | const maximumIncrements = { 59 | hour: 24, 60 | minute: 1440, 61 | second: 86400, 62 | millisecond: 86400e3, 63 | microsecond: 86400e6, 64 | nanosecond: 86400e9 65 | }; 66 | ES.ValidateTemporalRoundingIncrement(roundingIncrement, maximumIncrements[smallestUnit], true); 67 | const ns = GetSlot(this, EPOCHNANOSECONDS); 68 | const roundedNs = ES.RoundTemporalInstant(ns, roundingIncrement, smallestUnit, roundingMode); 69 | return ES.CreateTemporalInstant(roundedNs); 70 | } 71 | equals(otherParam: Params['equals'][0]): Return['equals'] { 72 | ES.CheckReceiver(this, ES.IsTemporalInstant); 73 | const other = ES.ToTemporalInstant(otherParam); 74 | const one = GetSlot(this, EPOCHNANOSECONDS); 75 | const two = GetSlot(other, EPOCHNANOSECONDS); 76 | return JSBI.equal(JSBI.BigInt(one), JSBI.BigInt(two)); 77 | } 78 | toString(options: Params['toString'][0] = undefined): string { 79 | ES.CheckReceiver(this, ES.IsTemporalInstant); 80 | const resolvedOptions = ES.GetOptionsObject(options); 81 | const digits = ES.GetTemporalFractionalSecondDigitsOption(resolvedOptions); 82 | const roundingMode = ES.GetRoundingModeOption(resolvedOptions, 'trunc'); 83 | const smallestUnit = ES.GetTemporalUnitValuedOption(resolvedOptions, 'smallestUnit', 'time', undefined); 84 | if (smallestUnit === 'hour') throw new RangeError('smallestUnit must be a time unit other than "hour"'); 85 | let timeZone = resolvedOptions.timeZone; 86 | if (timeZone !== undefined) timeZone = ES.ToTemporalTimeZoneIdentifier(timeZone); 87 | const { precision, unit, increment } = ES.ToSecondsStringPrecisionRecord(smallestUnit, digits); 88 | const ns = GetSlot(this, EPOCHNANOSECONDS); 89 | const roundedNs = ES.RoundTemporalInstant(ns, increment, unit, roundingMode); 90 | const roundedInstant = ES.CreateTemporalInstant(roundedNs); 91 | return ES.TemporalInstantToString(roundedInstant, timeZone, precision); 92 | } 93 | toJSON(): string { 94 | ES.CheckReceiver(this, ES.IsTemporalInstant); 95 | return ES.TemporalInstantToString(this, undefined, 'auto'); 96 | } 97 | toLocaleString( 98 | locales: Params['toLocaleString'][0] = undefined, 99 | options: Params['toLocaleString'][1] = undefined 100 | ): string { 101 | ES.CheckReceiver(this, ES.IsTemporalInstant); 102 | return new DateTimeFormat(locales, options).format(this); 103 | } 104 | valueOf(): never { 105 | ES.ValueOfThrows('Instant'); 106 | } 107 | toZonedDateTimeISO(timeZoneParam: Params['toZonedDateTimeISO'][0]): Return['toZonedDateTimeISO'] { 108 | ES.CheckReceiver(this, ES.IsTemporalInstant); 109 | const timeZone = ES.ToTemporalTimeZoneIdentifier(timeZoneParam); 110 | return ES.CreateTemporalZonedDateTime(GetSlot(this, EPOCHNANOSECONDS), timeZone, 'iso8601'); 111 | } 112 | 113 | static fromEpochMilliseconds(epochMilliseconds: Params['fromEpochMilliseconds'][0]): Return['fromEpochMilliseconds'] { 114 | const epochNanoseconds = ES.epochMsToNs(ES.ToNumber(epochMilliseconds)); 115 | return ES.CreateTemporalInstant(epochNanoseconds); 116 | } 117 | static fromEpochNanoseconds( 118 | epochNanosecondsParam: Params['fromEpochNanoseconds'][0] 119 | ): Return['fromEpochNanoseconds'] { 120 | const epochNanoseconds = ES.ToBigInt(epochNanosecondsParam); 121 | return ES.CreateTemporalInstant(epochNanoseconds); 122 | } 123 | static from(item: Params['from'][0]): Return['from'] { 124 | return ES.ToTemporalInstant(item); 125 | } 126 | static compare(oneParam: Params['compare'][0], twoParam: Params['compare'][1]): Return['compare'] { 127 | const one = ES.ToTemporalInstant(oneParam); 128 | const two = ES.ToTemporalInstant(twoParam); 129 | const oneNs = GetSlot(one, EPOCHNANOSECONDS); 130 | const twoNs = GetSlot(two, EPOCHNANOSECONDS); 131 | if (JSBI.lessThan(oneNs, twoNs)) return -1; 132 | if (JSBI.greaterThan(oneNs, twoNs)) return 1; 133 | return 0; 134 | } 135 | [Symbol.toStringTag]!: 'Temporal.Instant'; 136 | } 137 | 138 | MakeIntrinsicClass(Instant, 'Temporal.Instant'); 139 | -------------------------------------------------------------------------------- /lib/internaltypes.d.ts: -------------------------------------------------------------------------------- 1 | import type { Intl, Temporal } from '..'; 2 | import type { DateTimeFormatImpl } from './intl'; 3 | import type { TimeDuration } from './timeduration'; 4 | 5 | export type BuiltinCalendarId = 6 | | 'iso8601' 7 | | 'hebrew' 8 | | 'islamic' 9 | | 'islamic-umalqura' 10 | | 'islamic-tbla' 11 | | 'islamic-civil' 12 | | 'islamic-rgsa' 13 | | 'islamicc' 14 | | 'persian' 15 | | 'ethiopic' 16 | | 'ethioaa' 17 | | 'ethiopic-amete-alem' // see https://github.com/tc39/ecma402/issues/285 18 | | 'coptic' 19 | | 'chinese' 20 | | 'dangi' 21 | | 'roc' 22 | | 'indian' 23 | | 'buddhist' 24 | | 'japanese' 25 | | 'gregory'; 26 | 27 | export type AnySlottedType = 28 | | DateTimeFormatImpl 29 | | Temporal.Duration 30 | | Temporal.Instant 31 | | Temporal.PlainDate 32 | | Temporal.PlainDateTime 33 | | Temporal.PlainMonthDay 34 | | Temporal.PlainTime 35 | | Temporal.PlainYearMonth 36 | | Temporal.ZonedDateTime; 37 | 38 | /* 39 | // unused, but uncomment if this is needed later 40 | export type AnyTemporalConstructor = 41 | | typeof Temporal.Calendar 42 | | typeof Temporal.Duration 43 | | typeof Temporal.Instant 44 | | typeof Temporal.PlainDate 45 | | typeof Temporal.PlainDateTime 46 | | typeof Temporal.PlainMonthDay 47 | | typeof Temporal.PlainTime 48 | | typeof Temporal.PlainYearMonth 49 | | typeof Temporal.TimeZone 50 | | typeof Temporal.ZonedDateTime; 51 | */ 52 | 53 | // Used in AnyTemporalLikeType 54 | type AllTemporalLikeTypes = [ 55 | Temporal.DurationLike, 56 | Temporal.PlainDateLike, 57 | Temporal.PlainDateTimeLike, 58 | Temporal.PlainMonthDayLike, 59 | Temporal.PlainTimeLike, 60 | Temporal.PlainYearMonthLike, 61 | Temporal.ZonedDateTimeLike 62 | ]; 63 | export type AnyTemporalLikeType = AllTemporalLikeTypes[number]; 64 | 65 | // Keys is a conditionally-mapped version of keyof 66 | export type Keys = T extends Record ? keyof T : never; 67 | 68 | // Resolve copies the keys and values of a given object type so that TS will 69 | // stop using type names in error messages / autocomplete. Generally, those 70 | // names can be more useful, but sometimes having the primitive object shape is 71 | // significantly easier to reason about (e.g. deeply-nested types). 72 | // Resolve is an identity function for function types. 73 | export type Resolve = 74 | // Re-mapping doesn't work very well for functions, so exclude them 75 | T extends (...args: never[]) => unknown 76 | ? T 77 | : // Re-map all the keys in T to the same value. This forces TS into no longer 78 | // using type aliases, etc. 79 | { [K in keyof T]: T[K] }; 80 | 81 | export type AnyTemporalKey = Exclude, symbol>; 82 | 83 | export type FieldKey = Exclude>; 84 | 85 | // The properties below are all the names of Temporal properties that can be set with `with`. 86 | // `timeZone` and `calendar` are not on the list because they have special methods to set them. 87 | 88 | export type UnitSmallerThanOrEqualTo = T extends 'year' 89 | ? Temporal.DateTimeUnit 90 | : T extends 'month' 91 | ? Exclude 92 | : T extends 'week' 93 | ? Exclude 94 | : T extends 'day' 95 | ? Exclude 96 | : T extends 'hour' 97 | ? Temporal.TimeUnit 98 | : T extends 'minute' 99 | ? Exclude 100 | : T extends 'second' 101 | ? Exclude 102 | : T extends 'millisecond' 103 | ? Exclude 104 | : T extends 'microsecond' 105 | ? 'nanosecond' 106 | : never; 107 | 108 | type Method = (...args: any) => any; 109 | type NonObjectKeys = Exclude; 110 | 111 | type MethodParams any> = { 112 | // constructor parameters 113 | constructor: ConstructorParameters; 114 | } & { 115 | // static method parameters 116 | [Key in NonObjectKeys]: Type[Key] extends Method ? Parameters : never; 117 | } & { 118 | // prototype method parameters 119 | [Key in keyof InstanceType]: InstanceType[Key] extends Method 120 | ? Parameters[Key]> 121 | : never; 122 | }; 123 | 124 | type MethodReturn any> = { 125 | constructor: InstanceType; 126 | } & { 127 | [Key in NonObjectKeys]: Type[Key] extends Method ? ReturnType : Type[Key]; 128 | } & { 129 | [Key in keyof InstanceType]: InstanceType[Key] extends Method 130 | ? ReturnType[Key]> 131 | : InstanceType[Key]; 132 | }; 133 | 134 | /* Currently unused, but may use later 135 | type InterfaceReturn = { 136 | [Key in keyof Type]: Type[Key] extends Method ? ReturnType : Type[Key]; 137 | }; 138 | */ 139 | 140 | type InterfaceParams = { 141 | [Key in keyof Type]: Type[Key] extends Method ? Parameters : never; 142 | }; 143 | 144 | // Parameters of each Temporal type. Examples: 145 | // * InstantParams['compare'][1] - static methods 146 | // * PlainDateParams['since'][0] - prototype methods 147 | // * DurationParams['constructor'][3] - constructors 148 | export interface ZonedDateTimeParams extends MethodParams {} 149 | export interface DurationParams extends MethodParams {} 150 | export interface InstantParams extends MethodParams {} 151 | export interface PlainDateParams extends MethodParams {} 152 | export interface PlainDateTimeParams extends MethodParams {} 153 | export interface PlainMonthDayParams extends MethodParams {} 154 | export interface PlainTimeParams extends MethodParams {} 155 | export interface PlainYearMonthParams extends MethodParams {} 156 | export interface ZonedDateTimeParams extends MethodParams {} 157 | 158 | // Return types of static or instance methods 159 | export interface ZonedDateTimeReturn extends MethodReturn {} 160 | export interface DurationReturn extends MethodReturn {} 161 | export interface InstantReturn extends MethodReturn {} 162 | export interface PlainDateReturn extends MethodReturn {} 163 | export interface PlainDateTimeReturn extends MethodReturn {} 164 | export interface PlainMonthDayReturn extends MethodReturn {} 165 | export interface PlainTimeReturn extends MethodReturn {} 166 | export interface PlainYearMonthReturn extends MethodReturn {} 167 | export interface ZonedDateTimeReturn extends MethodReturn {} 168 | 169 | export interface DateTimeFormatParams extends MethodParams {} 170 | export interface DateTimeFormatReturn extends MethodReturn {} 171 | 172 | type OptionsAmenderFunction = (options: globalThis.Intl.DateTimeFormatOptions) => globalThis.Intl.DateTimeFormatOptions; 173 | export type FormatterOrAmender = globalThis.Intl.DateTimeFormat | OptionsAmenderFunction; 174 | 175 | export type Overflow = NonNullable; 176 | 177 | export type ISODateToFieldsType = 'date' | 'year-month' | 'month-day'; 178 | 179 | export interface CalendarDateRecord { 180 | era: string | undefined; 181 | eraYear: number | undefined; 182 | year: number; 183 | month: number; 184 | monthCode: string; 185 | day: number; 186 | dayOfWeek: number; 187 | dayOfYear: number; 188 | weekOfYear: { week: number; year: number } | undefined; 189 | daysInWeek: number; 190 | daysInMonth: number; 191 | daysInYear: number; 192 | monthsInYear: number; 193 | inLeapYear: boolean; 194 | } 195 | 196 | export interface CalendarFieldsRecord { 197 | era?: string; 198 | eraYear?: number; 199 | year?: number; 200 | month?: number; 201 | monthCode?: string; 202 | day?: number; 203 | hour?: number; 204 | minute?: number; 205 | second?: number; 206 | millisecond?: number; 207 | microsecond?: number; 208 | nanosecond?: number; 209 | offset?: string; 210 | timeZone?: string; 211 | } 212 | export type CalendarYMD = { year: number; month: number; day: number }; 213 | export type MonthDayFromFieldsObject = CalendarFieldsRecord & ({ monthCode: string; day: number } | CalendarYMD); 214 | 215 | /** Record representing YMD of an ISO calendar date */ 216 | export interface ISODate { 217 | year: number; 218 | month: number; 219 | day: number; 220 | } 221 | 222 | export type TimeRecord = Required; 223 | 224 | export interface ISODateTime { 225 | isoDate: ISODate; 226 | time: TimeRecord; 227 | } 228 | 229 | export interface DateDuration { 230 | years: number; 231 | months: number; 232 | weeks: number; 233 | days: number; 234 | } 235 | 236 | export interface InternalDuration { 237 | date: DateDuration; 238 | time: TimeDuration; 239 | } 240 | 241 | // Signal to TypeScript that not everything should be exported by default 242 | export {}; 243 | -------------------------------------------------------------------------------- /lib/intrinsicclass.ts: -------------------------------------------------------------------------------- 1 | import type JSBI from 'jsbi'; 2 | import type { Temporal } from '..'; 3 | import type { CalendarImpl } from './calendar'; 4 | import type { BuiltinCalendarId } from './internaltypes'; 5 | import type { DateTimeFormatImpl } from './intl'; 6 | 7 | import { DEBUG } from './debug'; 8 | import { GetSlot, ORIGINAL } from './slots'; 9 | 10 | type OmitConstructor = { [P in keyof T as T[P] extends new (...args: any[]) => any ? P : never]: T[P] }; 11 | 12 | type TemporalIntrinsics = { 13 | ['Intl.DateTimeFormat']: typeof globalThis.Intl.DateTimeFormat; 14 | ['Temporal.Duration']: typeof Temporal.Duration; 15 | ['Temporal.Instant']: OmitConstructor & 16 | (new (epochNanoseconds: JSBI) => Temporal.Instant) & { prototype: typeof Temporal.Instant.prototype }; 17 | ['Temporal.PlainDate']: typeof Temporal.PlainDate; 18 | ['Temporal.PlainDateTime']: typeof Temporal.PlainDateTime; 19 | ['Temporal.PlainMonthDay']: typeof Temporal.PlainMonthDay; 20 | ['Temporal.PlainTime']: typeof Temporal.PlainTime; 21 | ['Temporal.PlainYearMonth']: typeof Temporal.PlainYearMonth; 22 | ['Temporal.ZonedDateTime']: OmitConstructor & 23 | (new (epochNanoseconds: JSBI, timeZone: string, calendar?: string) => Temporal.ZonedDateTime) & { 24 | prototype: typeof Temporal.ZonedDateTime.prototype; 25 | from: typeof Temporal.ZonedDateTime.from; 26 | compare: typeof Temporal.ZonedDateTime.compare; 27 | }; 28 | }; 29 | type TemporalIntrinsicRegistrations = { 30 | [key in keyof TemporalIntrinsics]: TemporalIntrinsics[key]; 31 | }; 32 | type TemporalIntrinsicPrototypeRegistrations = { 33 | [key in keyof TemporalIntrinsics as `${key}.prototype`]: TemporalIntrinsics[key]['prototype']; 34 | }; 35 | type TemporalIntrinsicRegisteredKeys = { 36 | [key in keyof TemporalIntrinsicRegistrations as `%${key}%`]: TemporalIntrinsicRegistrations[key]; 37 | }; 38 | type TemporalIntrinsicPrototypeRegisteredKeys = { 39 | [key in keyof TemporalIntrinsicPrototypeRegistrations as `%${key}%`]: TemporalIntrinsicPrototypeRegistrations[key]; 40 | }; 41 | 42 | type OtherIntrinsics = { 43 | calendarImpl: (id: BuiltinCalendarId) => CalendarImpl; 44 | }; 45 | type OtherIntrinsicKeys = { [key in keyof OtherIntrinsics as `%${key}%`]: OtherIntrinsics[key] }; 46 | 47 | const INTRINSICS = {} as TemporalIntrinsicRegisteredKeys & 48 | TemporalIntrinsicPrototypeRegisteredKeys & 49 | OtherIntrinsicKeys; 50 | 51 | type StylizeOption = (value: unknown, type: 'number' | 'special') => string; 52 | 53 | type customFormatFunction = ( 54 | this: T & { _repr_: string }, // _repr_ is present if DEBUG 55 | depth: number, 56 | options: { stylize: StylizeOption }, 57 | inspect: (object: T, options?: { depth: number; stylize: StylizeOption }) => string 58 | ) => string; 59 | const customUtilInspectFormatters: Partial<{ 60 | [key in keyof TemporalIntrinsicRegistrations]: customFormatFunction< 61 | InstanceType 62 | >; 63 | }> = { 64 | ['Intl.DateTimeFormat'](depth, options, inspect) { 65 | return inspect(GetSlot(this as DateTimeFormatImpl, ORIGINAL), { depth, ...options }); 66 | }, 67 | ['Temporal.Duration'](depth, options) { 68 | const descr = options.stylize(this._repr_, 'special'); 69 | if (depth < 1) return descr; 70 | const entries: string[] = []; 71 | const props = [ 72 | 'years', 73 | 'months', 74 | 'weeks', 75 | 'days', 76 | 'hours', 77 | 'minutes', 78 | 'seconds', 79 | 'milliseconds', 80 | 'microseconds', 81 | 'nanoseconds' 82 | ] as const; 83 | for (let i = 0; i < props.length; i++) { 84 | const prop = props[i]; 85 | if (this[prop] !== 0) { 86 | entries.push(` ${prop}: ${options.stylize(this[prop], 'number')}`); 87 | } 88 | } 89 | return descr + ' {\n' + entries.join(',\n') + '\n}'; 90 | } 91 | }; 92 | 93 | type InspectFormatterOptions = { stylize: (str: string, styleType: string) => string }; 94 | function defaultUtilInspectFormatter(this: any, depth: number, options: InspectFormatterOptions) { 95 | return options.stylize(this._repr_, 'special'); 96 | } 97 | 98 | export function MakeIntrinsicClass( 99 | Class: TemporalIntrinsicRegistrations[typeof name], 100 | name: keyof TemporalIntrinsicRegistrations 101 | ) { 102 | Object.defineProperty(Class.prototype, Symbol.toStringTag, { 103 | value: name, 104 | writable: false, 105 | enumerable: false, 106 | configurable: true 107 | }); 108 | if (DEBUG) { 109 | Object.defineProperty(Class.prototype, Symbol.for('nodejs.util.inspect.custom'), { 110 | value: customUtilInspectFormatters[name] || defaultUtilInspectFormatter, 111 | writable: false, 112 | enumerable: false, 113 | configurable: true 114 | }); 115 | } 116 | const staticNames = Object.getOwnPropertyNames(Class); 117 | for (let i = 0; i < staticNames.length; i++) { 118 | const prop = staticNames[i]; 119 | // we know that `prop` is present, so the descriptor is never undefined 120 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 121 | const desc = Object.getOwnPropertyDescriptor(Class, prop)!; 122 | if (!desc.configurable || !desc.enumerable) continue; 123 | desc.enumerable = false; 124 | Object.defineProperty(Class, prop, desc); 125 | } 126 | const protoNames = Object.getOwnPropertyNames(Class.prototype); 127 | for (let i = 0; i < protoNames.length; i++) { 128 | const prop = protoNames[i]; 129 | // we know that `prop` is present, so the descriptor is never undefined 130 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 131 | const desc = Object.getOwnPropertyDescriptor(Class.prototype, prop)!; 132 | if (!desc.configurable || !desc.enumerable) continue; 133 | desc.enumerable = false; 134 | Object.defineProperty(Class.prototype, prop, desc); 135 | } 136 | 137 | DefineIntrinsic(name, Class); 138 | DefineIntrinsic(`${name}.prototype`, Class.prototype); 139 | } 140 | 141 | type IntrinsicDefinitionKeys = 142 | | keyof TemporalIntrinsicRegistrations 143 | | keyof TemporalIntrinsicPrototypeRegistrations 144 | | keyof OtherIntrinsics; 145 | export function DefineIntrinsic( 146 | name: KeyT, 147 | value: TemporalIntrinsicRegistrations[KeyT] 148 | ): void; 149 | export function DefineIntrinsic( 150 | name: KeyT, 151 | value: TemporalIntrinsicPrototypeRegistrations[KeyT] 152 | ): void; 153 | export function DefineIntrinsic(name: KeyT, value: OtherIntrinsics[KeyT]): void; 154 | export function DefineIntrinsic(name: KeyT, value: never): void; 155 | export function DefineIntrinsic(name: KeyT, value: unknown): void { 156 | const key: `%${IntrinsicDefinitionKeys}%` = `%${name}%`; 157 | if (INTRINSICS[key] !== undefined) throw new Error(`intrinsic ${name} already exists`); 158 | INTRINSICS[key] = value; 159 | } 160 | export function GetIntrinsic(intrinsic: KeyT): (typeof INTRINSICS)[KeyT] { 161 | return INTRINSICS[intrinsic]; 162 | } 163 | -------------------------------------------------------------------------------- /lib/legacydate.ts: -------------------------------------------------------------------------------- 1 | import * as ES from './ecmascript'; 2 | 3 | // By default, a plain function can be called as a constructor. A method such as 4 | // Date.prototype.toTemporalInstant should not be able to. We could check 5 | // new.target in the body of toTemporalInstant, but that is not sufficient for 6 | // preventing construction when passing it as the newTarget parameter of 7 | // Reflect.construct. So we create it as a method of an otherwise unused class, 8 | // and monkeypatch it onto Date.prototype. 9 | 10 | class LegacyDateImpl { 11 | toTemporalInstant(this: Date) { 12 | const epochNanoseconds = ES.epochMsToNs(Date.prototype.valueOf.call(this)); 13 | return ES.CreateTemporalInstant(epochNanoseconds); 14 | } 15 | } 16 | 17 | export const toTemporalInstant = LegacyDateImpl.prototype.toTemporalInstant; 18 | -------------------------------------------------------------------------------- /lib/math.ts: -------------------------------------------------------------------------------- 1 | import type JSBI from 'jsbi'; 2 | import type { Temporal } from '..'; 3 | 4 | // Computes trunc(x / 10**p) and x % 10**p, returning { div, mod }, with 5 | // precision loss only once in the quotient, by string manipulation. If the 6 | // quotient and remainder are safe integers, then they are exact. x must be an 7 | // integer. p must be a non-negative integer. Both div and mod have the sign of 8 | // x. 9 | export function TruncatingDivModByPowerOf10(xParam: number, p: number) { 10 | let x = xParam; 11 | if (x === 0) return { div: x, mod: x }; // preserves signed zero 12 | 13 | const sign = Math.sign(x); 14 | x = Math.abs(x); 15 | 16 | const xDigits = Math.trunc(1 + Math.log10(x)); 17 | if (p >= xDigits) return { div: sign * 0, mod: sign * x }; 18 | if (p === 0) return { div: sign * x, mod: sign * 0 }; 19 | 20 | // would perform nearest rounding if x was not an integer: 21 | const xStr = x.toPrecision(xDigits); 22 | const div = sign * Number.parseInt(xStr.slice(0, xDigits - p), 10); 23 | const mod = sign * Number.parseInt(xStr.slice(xDigits - p), 10); 24 | 25 | return { div, mod }; 26 | } 27 | 28 | // Computes x * 10**p + z with precision loss only at the end, by string 29 | // manipulation. If the result is a safe integer, then it is exact. x must be 30 | // an integer. p must be a non-negative integer. z must have the same sign as 31 | // x and be less than 10**p. 32 | export function FMAPowerOf10(xParam: number, p: number, zParam: number) { 33 | let x = xParam; 34 | let z = zParam; 35 | if (x === 0) return z; 36 | 37 | const sign = Math.sign(x) || Math.sign(z); 38 | x = Math.abs(x); 39 | z = Math.abs(z); 40 | 41 | const xStr = x.toPrecision(Math.trunc(1 + Math.log10(x))); 42 | 43 | if (z === 0) return sign * Number.parseInt(xStr + '0'.repeat(p), 10); 44 | 45 | const zStr = z.toPrecision(Math.trunc(1 + Math.log10(z))); 46 | 47 | const resStr = xStr + zStr.padStart(p, '0'); 48 | return sign * Number.parseInt(resStr, 10); 49 | } 50 | 51 | type UnsignedRoundingMode = 'half-even' | 'half-infinity' | 'half-zero' | 'infinity' | 'zero'; 52 | 53 | export function GetUnsignedRoundingMode( 54 | mode: Temporal.RoundingMode, 55 | sign: 'positive' | 'negative' 56 | ): UnsignedRoundingMode { 57 | const isNegative = sign === 'negative'; 58 | switch (mode) { 59 | case 'ceil': 60 | return isNegative ? 'zero' : 'infinity'; 61 | case 'floor': 62 | return isNegative ? 'infinity' : 'zero'; 63 | case 'expand': 64 | return 'infinity'; 65 | case 'trunc': 66 | return 'zero'; 67 | case 'halfCeil': 68 | return isNegative ? 'half-zero' : 'half-infinity'; 69 | case 'halfFloor': 70 | return isNegative ? 'half-infinity' : 'half-zero'; 71 | case 'halfExpand': 72 | return 'half-infinity'; 73 | case 'halfTrunc': 74 | return 'half-zero'; 75 | case 'halfEven': 76 | return 'half-even'; 77 | } 78 | } 79 | 80 | // Omits first step from spec algorithm so that it can be used both for 81 | // RoundNumberToIncrement and RoundTimeDurationToIncrement 82 | export function ApplyUnsignedRoundingMode( 83 | r1: T, 84 | r2: T, 85 | cmp: -1 | 0 | 1, 86 | evenCardinality: boolean, 87 | unsignedRoundingMode: UnsignedRoundingMode 88 | ) { 89 | if (unsignedRoundingMode === 'zero') return r1; 90 | if (unsignedRoundingMode === 'infinity') return r2; 91 | if (cmp < 0) return r1; 92 | if (cmp > 0) return r2; 93 | if (unsignedRoundingMode === 'half-zero') return r1; 94 | if (unsignedRoundingMode === 'half-infinity') return r2; 95 | return evenCardinality ? r1 : r2; 96 | } 97 | -------------------------------------------------------------------------------- /lib/now.ts: -------------------------------------------------------------------------------- 1 | import * as ES from './ecmascript'; 2 | import type { Temporal } from '..'; 3 | 4 | function SystemDateTime(timeZone: string) { 5 | return ES.GetISODateTimeFor(timeZone, ES.SystemUTCEpochNanoSeconds()); 6 | } 7 | 8 | const instant: (typeof Temporal.Now)['instant'] = () => { 9 | return ES.CreateTemporalInstant(ES.SystemUTCEpochNanoSeconds()); 10 | }; 11 | const plainDateTimeISO: (typeof Temporal.Now)['plainDateTimeISO'] = (temporalTimeZoneLike = ES.DefaultTimeZone()) => { 12 | const timeZone = ES.ToTemporalTimeZoneIdentifier(temporalTimeZoneLike); 13 | const isoDateTime = SystemDateTime(timeZone); 14 | return ES.CreateTemporalDateTime(isoDateTime, 'iso8601'); 15 | }; 16 | const zonedDateTimeISO: (typeof Temporal.Now)['zonedDateTimeISO'] = (temporalTimeZoneLike = ES.DefaultTimeZone()) => { 17 | const timeZone = ES.ToTemporalTimeZoneIdentifier(temporalTimeZoneLike); 18 | return ES.CreateTemporalZonedDateTime(ES.SystemUTCEpochNanoSeconds(), timeZone, 'iso8601'); 19 | }; 20 | const plainDateISO: (typeof Temporal.Now)['plainDateISO'] = (temporalTimeZoneLike = ES.DefaultTimeZone()) => { 21 | const timeZone = ES.ToTemporalTimeZoneIdentifier(temporalTimeZoneLike); 22 | const isoDateTime = SystemDateTime(timeZone); 23 | return ES.CreateTemporalDate(isoDateTime.isoDate, 'iso8601'); 24 | }; 25 | const plainTimeISO: (typeof Temporal.Now)['plainTimeISO'] = (temporalTimeZoneLike = ES.DefaultTimeZone()) => { 26 | const timeZone = ES.ToTemporalTimeZoneIdentifier(temporalTimeZoneLike); 27 | const isoDateTime = SystemDateTime(timeZone); 28 | return ES.CreateTemporalTime(isoDateTime.time); 29 | }; 30 | const timeZoneId: (typeof Temporal.Now)['timeZoneId'] = () => { 31 | return ES.DefaultTimeZone(); 32 | }; 33 | 34 | export const Now: typeof Temporal.Now = { 35 | instant, 36 | plainDateTimeISO, 37 | plainDateISO, 38 | plainTimeISO, 39 | timeZoneId, 40 | zonedDateTimeISO, 41 | [Symbol.toStringTag]: 'Temporal.Now' 42 | }; 43 | Object.defineProperty(Now, Symbol.toStringTag, { 44 | value: 'Temporal.Now', 45 | writable: false, 46 | enumerable: false, 47 | configurable: true 48 | }); 49 | -------------------------------------------------------------------------------- /lib/plaindate.ts: -------------------------------------------------------------------------------- 1 | import * as ES from './ecmascript'; 2 | import { MakeIntrinsicClass } from './intrinsicclass'; 3 | import { CALENDAR, GetSlot, ISO_DATE, TIME } from './slots'; 4 | import type { Temporal } from '..'; 5 | import { DateTimeFormat } from './intl'; 6 | import type { CalendarDateRecord, PlainDateParams as Params, PlainDateReturn as Return } from './internaltypes'; 7 | 8 | export class PlainDate implements Temporal.PlainDate { 9 | constructor( 10 | isoYear: Params['constructor'][0], 11 | isoMonth: Params['constructor'][1], 12 | isoDay: Params['constructor'][2], 13 | calendarParam: Params['constructor'][3] = 'iso8601' 14 | ) { 15 | const year = ES.ToIntegerWithTruncation(isoYear); 16 | const month = ES.ToIntegerWithTruncation(isoMonth); 17 | const day = ES.ToIntegerWithTruncation(isoDay); 18 | const calendar = ES.CanonicalizeCalendar(calendarParam === undefined ? 'iso8601' : ES.RequireString(calendarParam)); 19 | ES.RejectISODate(year, month, day); 20 | 21 | ES.CreateTemporalDateSlots(this, { year, month, day }, calendar); 22 | } 23 | get calendarId(): Return['calendarId'] { 24 | ES.CheckReceiver(this, ES.IsTemporalDate); 25 | return GetSlot(this, CALENDAR); 26 | } 27 | get era(): Return['era'] { 28 | return getCalendarProperty(this, 'era'); 29 | } 30 | get eraYear(): Return['eraYear'] { 31 | return getCalendarProperty(this, 'eraYear'); 32 | } 33 | get year(): Return['year'] { 34 | return getCalendarProperty(this, 'year'); 35 | } 36 | get month(): Return['month'] { 37 | return getCalendarProperty(this, 'month'); 38 | } 39 | get monthCode(): Return['monthCode'] { 40 | return getCalendarProperty(this, 'monthCode'); 41 | } 42 | get day(): Return['day'] { 43 | return getCalendarProperty(this, 'day'); 44 | } 45 | get dayOfWeek(): Return['dayOfWeek'] { 46 | return getCalendarProperty(this, 'dayOfWeek'); 47 | } 48 | get dayOfYear(): Return['dayOfYear'] { 49 | return getCalendarProperty(this, 'dayOfYear'); 50 | } 51 | get weekOfYear(): Return['weekOfYear'] { 52 | return getCalendarProperty(this, 'weekOfYear')?.week; 53 | } 54 | get yearOfWeek(): Return['yearOfWeek'] { 55 | return getCalendarProperty(this, 'weekOfYear')?.year; 56 | } 57 | get daysInWeek(): Return['daysInWeek'] { 58 | return getCalendarProperty(this, 'daysInWeek'); 59 | } 60 | get daysInMonth(): Return['daysInMonth'] { 61 | return getCalendarProperty(this, 'daysInMonth'); 62 | } 63 | get daysInYear(): Return['daysInYear'] { 64 | return getCalendarProperty(this, 'daysInYear'); 65 | } 66 | get monthsInYear(): Return['monthsInYear'] { 67 | return getCalendarProperty(this, 'monthsInYear'); 68 | } 69 | get inLeapYear(): Return['inLeapYear'] { 70 | return getCalendarProperty(this, 'inLeapYear'); 71 | } 72 | with(temporalDateLike: Params['with'][0], options: Params['with'][1] = undefined): Return['with'] { 73 | ES.CheckReceiver(this, ES.IsTemporalDate); 74 | if (!ES.IsObject(temporalDateLike)) { 75 | throw new TypeError('invalid argument'); 76 | } 77 | ES.RejectTemporalLikeObject(temporalDateLike); 78 | 79 | const calendar = GetSlot(this, CALENDAR); 80 | let fields = ES.ISODateToFields(calendar, GetSlot(this, ISO_DATE)); 81 | const partialDate = ES.PrepareCalendarFields( 82 | calendar, 83 | temporalDateLike, 84 | ['year', 'month', 'monthCode', 'day'], 85 | [], 86 | 'partial' 87 | ); 88 | fields = ES.CalendarMergeFields(calendar, fields, partialDate); 89 | 90 | const overflow = ES.GetTemporalOverflowOption(ES.GetOptionsObject(options)); 91 | const isoDate = ES.CalendarDateFromFields(calendar, fields, overflow); 92 | return ES.CreateTemporalDate(isoDate, calendar); 93 | } 94 | withCalendar(calendarParam: Params['withCalendar'][0]): Return['withCalendar'] { 95 | ES.CheckReceiver(this, ES.IsTemporalDate); 96 | const calendar = ES.ToTemporalCalendarIdentifier(calendarParam); 97 | return ES.CreateTemporalDate(GetSlot(this, ISO_DATE), calendar); 98 | } 99 | add(temporalDurationLike: Params['add'][0], options: Params['add'][1] = undefined): Return['add'] { 100 | ES.CheckReceiver(this, ES.IsTemporalDate); 101 | return ES.AddDurationToDate('add', this, temporalDurationLike, options); 102 | } 103 | subtract( 104 | temporalDurationLike: Params['subtract'][0], 105 | options: Params['subtract'][1] = undefined 106 | ): Return['subtract'] { 107 | ES.CheckReceiver(this, ES.IsTemporalDate); 108 | return ES.AddDurationToDate('subtract', this, temporalDurationLike, options); 109 | } 110 | until(other: Params['until'][0], options: Params['until'][1] = undefined): Return['until'] { 111 | ES.CheckReceiver(this, ES.IsTemporalDate); 112 | return ES.DifferenceTemporalPlainDate('until', this, other, options); 113 | } 114 | since(other: Params['since'][0], options: Params['since'][1] = undefined): Return['since'] { 115 | ES.CheckReceiver(this, ES.IsTemporalDate); 116 | return ES.DifferenceTemporalPlainDate('since', this, other, options); 117 | } 118 | equals(otherParam: Params['equals'][0]): Return['equals'] { 119 | ES.CheckReceiver(this, ES.IsTemporalDate); 120 | const other = ES.ToTemporalDate(otherParam); 121 | if (ES.CompareISODate(GetSlot(this, ISO_DATE), GetSlot(other, ISO_DATE)) !== 0) return false; 122 | return ES.CalendarEquals(GetSlot(this, CALENDAR), GetSlot(other, CALENDAR)); 123 | } 124 | toString(options: Params['toString'][0] = undefined): string { 125 | ES.CheckReceiver(this, ES.IsTemporalDate); 126 | const resolvedOptions = ES.GetOptionsObject(options); 127 | const showCalendar = ES.GetTemporalShowCalendarNameOption(resolvedOptions); 128 | return ES.TemporalDateToString(this, showCalendar); 129 | } 130 | toJSON(): Return['toJSON'] { 131 | ES.CheckReceiver(this, ES.IsTemporalDate); 132 | return ES.TemporalDateToString(this); 133 | } 134 | toLocaleString( 135 | locales: Params['toLocaleString'][0] = undefined, 136 | options: Params['toLocaleString'][1] = undefined 137 | ): string { 138 | ES.CheckReceiver(this, ES.IsTemporalDate); 139 | return new DateTimeFormat(locales, options).format(this); 140 | } 141 | valueOf(): never { 142 | ES.ValueOfThrows('PlainDate'); 143 | } 144 | toPlainDateTime(temporalTime: Params['toPlainDateTime'][0] = undefined): Return['toPlainDateTime'] { 145 | ES.CheckReceiver(this, ES.IsTemporalDate); 146 | const time = ES.ToTimeRecordOrMidnight(temporalTime); 147 | const isoDateTime = ES.CombineISODateAndTimeRecord(GetSlot(this, ISO_DATE), time); 148 | return ES.CreateTemporalDateTime(isoDateTime, GetSlot(this, CALENDAR)); 149 | } 150 | toZonedDateTime(item: Params['toZonedDateTime'][0]): Return['toZonedDateTime'] { 151 | ES.CheckReceiver(this, ES.IsTemporalDate); 152 | 153 | let timeZone, temporalTime; 154 | if (ES.IsObject(item)) { 155 | const timeZoneLike = item.timeZone; 156 | if (timeZoneLike === undefined) { 157 | timeZone = ES.ToTemporalTimeZoneIdentifier(item); 158 | } else { 159 | timeZone = ES.ToTemporalTimeZoneIdentifier(timeZoneLike); 160 | temporalTime = item.plainTime; 161 | } 162 | } else { 163 | timeZone = ES.ToTemporalTimeZoneIdentifier(item); 164 | } 165 | 166 | const isoDate = GetSlot(this, ISO_DATE); 167 | let epochNs; 168 | if (temporalTime === undefined) { 169 | epochNs = ES.GetStartOfDay(timeZone, isoDate); 170 | } else { 171 | temporalTime = ES.ToTemporalTime(temporalTime); 172 | const isoDateTime = ES.CombineISODateAndTimeRecord(isoDate, GetSlot(temporalTime, TIME)); 173 | epochNs = ES.GetEpochNanosecondsFor(timeZone, isoDateTime, 'compatible'); 174 | } 175 | return ES.CreateTemporalZonedDateTime(epochNs, timeZone, GetSlot(this, CALENDAR)); 176 | } 177 | toPlainYearMonth(): Return['toPlainYearMonth'] { 178 | ES.CheckReceiver(this, ES.IsTemporalDate); 179 | const calendar = GetSlot(this, CALENDAR); 180 | const fields = ES.ISODateToFields(calendar, GetSlot(this, ISO_DATE)); 181 | const isoDate = ES.CalendarYearMonthFromFields(calendar, fields, 'constrain'); 182 | return ES.CreateTemporalYearMonth(isoDate, calendar); 183 | } 184 | toPlainMonthDay(): Return['toPlainMonthDay'] { 185 | ES.CheckReceiver(this, ES.IsTemporalDate); 186 | const calendar = GetSlot(this, CALENDAR); 187 | const fields = ES.ISODateToFields(calendar, GetSlot(this, ISO_DATE)); 188 | const isoDate = ES.CalendarMonthDayFromFields(calendar, fields, 'constrain'); 189 | return ES.CreateTemporalMonthDay(isoDate, calendar); 190 | } 191 | 192 | static from(item: Params['from'][0], options: Params['from'][1] = undefined): Return['from'] { 193 | return ES.ToTemporalDate(item, options); 194 | } 195 | static compare(oneParam: Params['compare'][0], twoParam: Params['compare'][1]): Return['compare'] { 196 | const one = ES.ToTemporalDate(oneParam); 197 | const two = ES.ToTemporalDate(twoParam); 198 | return ES.CompareISODate(GetSlot(one, ISO_DATE), GetSlot(two, ISO_DATE)); 199 | } 200 | [Symbol.toStringTag]!: 'Temporal.PlainDate'; 201 | } 202 | 203 | MakeIntrinsicClass(PlainDate, 'Temporal.PlainDate'); 204 | 205 | function getCalendarProperty

( 206 | date: Temporal.PlainDate, 207 | prop: P 208 | ): CalendarDateRecord[P] { 209 | ES.CheckReceiver(date, ES.IsTemporalDate); 210 | const isoDate = GetSlot(date, ISO_DATE); 211 | return ES.calendarImplForObj(date).isoToDate(isoDate, { [prop]: true })[prop]; 212 | } 213 | -------------------------------------------------------------------------------- /lib/plaindatetime.ts: -------------------------------------------------------------------------------- 1 | import * as ES from './ecmascript'; 2 | import { MakeIntrinsicClass } from './intrinsicclass'; 3 | 4 | import { CALENDAR, GetSlot, ISO_DATE_TIME } from './slots'; 5 | import type { Temporal } from '..'; 6 | import { DateTimeFormat } from './intl'; 7 | import type { CalendarDateRecord, PlainDateTimeParams as Params, PlainDateTimeReturn as Return } from './internaltypes'; 8 | 9 | export class PlainDateTime implements Temporal.PlainDateTime { 10 | constructor( 11 | isoYear: Params['constructor'][0], 12 | isoMonth: Params['constructor'][1], 13 | isoDay: Params['constructor'][2], 14 | hourParam: Params['constructor'][3] = 0, 15 | minuteParam: Params['constructor'][4] = 0, 16 | secondParam: Params['constructor'][5] = 0, 17 | millisecondParam: Params['constructor'][6] = 0, 18 | microsecondParam: Params['constructor'][7] = 0, 19 | nanosecondParam: Params['constructor'][8] = 0, 20 | calendarParam: Params['constructor'][9] = 'iso8601' 21 | ) { 22 | const year = ES.ToIntegerWithTruncation(isoYear); 23 | const month = ES.ToIntegerWithTruncation(isoMonth); 24 | const day = ES.ToIntegerWithTruncation(isoDay); 25 | const hour = hourParam === undefined ? 0 : ES.ToIntegerWithTruncation(hourParam); 26 | const minute = minuteParam === undefined ? 0 : ES.ToIntegerWithTruncation(minuteParam); 27 | const second = secondParam === undefined ? 0 : ES.ToIntegerWithTruncation(secondParam); 28 | const millisecond = millisecondParam === undefined ? 0 : ES.ToIntegerWithTruncation(millisecondParam); 29 | const microsecond = microsecondParam === undefined ? 0 : ES.ToIntegerWithTruncation(microsecondParam); 30 | const nanosecond = nanosecondParam === undefined ? 0 : ES.ToIntegerWithTruncation(nanosecondParam); 31 | const calendar = ES.CanonicalizeCalendar(calendarParam === undefined ? 'iso8601' : ES.RequireString(calendarParam)); 32 | 33 | ES.RejectDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); 34 | 35 | ES.CreateTemporalDateTimeSlots( 36 | this, 37 | { isoDate: { year, month, day }, time: { hour, minute, second, millisecond, microsecond, nanosecond } }, 38 | calendar 39 | ); 40 | } 41 | get calendarId(): Return['calendarId'] { 42 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 43 | return GetSlot(this, CALENDAR); 44 | } 45 | get year(): Return['year'] { 46 | return getCalendarProperty(this, 'year'); 47 | } 48 | get month(): Return['month'] { 49 | return getCalendarProperty(this, 'month'); 50 | } 51 | get monthCode(): Return['monthCode'] { 52 | return getCalendarProperty(this, 'monthCode'); 53 | } 54 | get day(): Return['day'] { 55 | return getCalendarProperty(this, 'day'); 56 | } 57 | get hour(): Return['hour'] { 58 | return getTimeProperty(this, 'hour'); 59 | } 60 | get minute(): Return['minute'] { 61 | return getTimeProperty(this, 'minute'); 62 | } 63 | get second(): Return['second'] { 64 | return getTimeProperty(this, 'second'); 65 | } 66 | get millisecond(): Return['millisecond'] { 67 | return getTimeProperty(this, 'millisecond'); 68 | } 69 | get microsecond(): Return['microsecond'] { 70 | return getTimeProperty(this, 'microsecond'); 71 | } 72 | get nanosecond(): Return['nanosecond'] { 73 | return getTimeProperty(this, 'nanosecond'); 74 | } 75 | get era(): Return['era'] { 76 | return getCalendarProperty(this, 'era'); 77 | } 78 | get eraYear(): Return['eraYear'] { 79 | return getCalendarProperty(this, 'eraYear'); 80 | } 81 | get dayOfWeek(): Return['dayOfWeek'] { 82 | return getCalendarProperty(this, 'dayOfWeek'); 83 | } 84 | get dayOfYear(): Return['dayOfYear'] { 85 | return getCalendarProperty(this, 'dayOfYear'); 86 | } 87 | get weekOfYear(): Return['weekOfYear'] { 88 | return getCalendarProperty(this, 'weekOfYear')?.week; 89 | } 90 | get yearOfWeek(): Return['yearOfWeek'] { 91 | return getCalendarProperty(this, 'weekOfYear')?.year; 92 | } 93 | get daysInWeek(): Return['daysInWeek'] { 94 | return getCalendarProperty(this, 'daysInWeek'); 95 | } 96 | get daysInYear(): Return['daysInYear'] { 97 | return getCalendarProperty(this, 'daysInYear'); 98 | } 99 | get daysInMonth(): Return['daysInMonth'] { 100 | return getCalendarProperty(this, 'daysInMonth'); 101 | } 102 | get monthsInYear(): Return['monthsInYear'] { 103 | return getCalendarProperty(this, 'monthsInYear'); 104 | } 105 | get inLeapYear(): Return['inLeapYear'] { 106 | return getCalendarProperty(this, 'inLeapYear'); 107 | } 108 | with(temporalDateTimeLike: Params['with'][0], options: Params['with'][1] = undefined): Return['with'] { 109 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 110 | if (!ES.IsObject(temporalDateTimeLike)) { 111 | throw new TypeError('invalid argument'); 112 | } 113 | ES.RejectTemporalLikeObject(temporalDateTimeLike); 114 | 115 | const calendar = GetSlot(this, CALENDAR); 116 | const isoDateTime = GetSlot(this, ISO_DATE_TIME); 117 | let fields = { 118 | ...ES.ISODateToFields(calendar, isoDateTime.isoDate), 119 | ...isoDateTime.time 120 | }; 121 | const partialDateTime = ES.PrepareCalendarFields( 122 | calendar, 123 | temporalDateTimeLike, 124 | ['year', 'month', 'monthCode', 'day'], 125 | ['hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond'], 126 | 'partial' 127 | ); 128 | fields = ES.CalendarMergeFields(calendar, fields, partialDateTime); 129 | 130 | const overflow = ES.GetTemporalOverflowOption(ES.GetOptionsObject(options)); 131 | const newDateTime = ES.InterpretTemporalDateTimeFields(calendar, fields, overflow); 132 | return ES.CreateTemporalDateTime(newDateTime, calendar); 133 | } 134 | withPlainTime(temporalTime: Params['withPlainTime'][0] = undefined): Return['withPlainTime'] { 135 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 136 | const time = ES.ToTimeRecordOrMidnight(temporalTime); 137 | const isoDateTime = ES.CombineISODateAndTimeRecord(GetSlot(this, ISO_DATE_TIME).isoDate, time); 138 | return ES.CreateTemporalDateTime(isoDateTime, GetSlot(this, CALENDAR)); 139 | } 140 | withCalendar(calendarParam: Params['withCalendar'][0]): Return['withCalendar'] { 141 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 142 | const calendar = ES.ToTemporalCalendarIdentifier(calendarParam); 143 | return ES.CreateTemporalDateTime(GetSlot(this, ISO_DATE_TIME), calendar); 144 | } 145 | add(temporalDurationLike: Params['add'][0], options: Params['add'][1] = undefined): Return['add'] { 146 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 147 | return ES.AddDurationToDateTime('add', this, temporalDurationLike, options); 148 | } 149 | subtract( 150 | temporalDurationLike: Params['subtract'][0], 151 | options: Params['subtract'][1] = undefined 152 | ): Return['subtract'] { 153 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 154 | return ES.AddDurationToDateTime('subtract', this, temporalDurationLike, options); 155 | } 156 | until(other: Params['until'][0], options: Params['until'][1] = undefined): Return['until'] { 157 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 158 | return ES.DifferenceTemporalPlainDateTime('until', this, other, options); 159 | } 160 | since(other: Params['since'][0], options: Params['since'][1] = undefined): Return['since'] { 161 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 162 | return ES.DifferenceTemporalPlainDateTime('since', this, other, options); 163 | } 164 | round(roundToParam: Params['round'][0]): Return['round'] { 165 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 166 | if (roundToParam === undefined) throw new TypeError('options parameter is required'); 167 | const roundTo = 168 | typeof roundToParam === 'string' 169 | ? (ES.CreateOnePropObject('smallestUnit', roundToParam) as Exclude) 170 | : ES.GetOptionsObject(roundToParam); 171 | const roundingIncrement = ES.GetTemporalRoundingIncrementOption(roundTo); 172 | const roundingMode = ES.GetRoundingModeOption(roundTo, 'halfExpand'); 173 | const smallestUnit = ES.GetTemporalUnitValuedOption(roundTo, 'smallestUnit', 'time', ES.REQUIRED, ['day']); 174 | const maximumIncrements = { 175 | day: 1, 176 | hour: 24, 177 | minute: 60, 178 | second: 60, 179 | millisecond: 1000, 180 | microsecond: 1000, 181 | nanosecond: 1000 182 | }; 183 | const maximum = maximumIncrements[smallestUnit]; 184 | const inclusive = maximum === 1; 185 | ES.ValidateTemporalRoundingIncrement(roundingIncrement, maximum, inclusive); 186 | 187 | const isoDateTime = GetSlot(this, ISO_DATE_TIME); 188 | if (roundingIncrement === 1 && smallestUnit === 'nanosecond') { 189 | return ES.CreateTemporalDateTime(isoDateTime, GetSlot(this, CALENDAR)); 190 | } 191 | const result = ES.RoundISODateTime(isoDateTime, roundingIncrement, smallestUnit, roundingMode); 192 | 193 | return ES.CreateTemporalDateTime(result, GetSlot(this, CALENDAR)); 194 | } 195 | equals(otherParam: Params['equals'][0]): Return['equals'] { 196 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 197 | const other = ES.ToTemporalDateTime(otherParam); 198 | if (ES.CompareISODateTime(GetSlot(this, ISO_DATE_TIME), GetSlot(other, ISO_DATE_TIME)) !== 0) return false; 199 | return ES.CalendarEquals(GetSlot(this, CALENDAR), GetSlot(other, CALENDAR)); 200 | } 201 | toString(options: Params['toString'][0] = undefined): string { 202 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 203 | const resolvedOptions = ES.GetOptionsObject(options); 204 | const showCalendar = ES.GetTemporalShowCalendarNameOption(resolvedOptions); 205 | const digits = ES.GetTemporalFractionalSecondDigitsOption(resolvedOptions); 206 | const roundingMode = ES.GetRoundingModeOption(resolvedOptions, 'trunc'); 207 | const smallestUnit = ES.GetTemporalUnitValuedOption(resolvedOptions, 'smallestUnit', 'time', undefined); 208 | if (smallestUnit === 'hour') throw new RangeError('smallestUnit must be a time unit other than "hour"'); 209 | const { precision, unit, increment } = ES.ToSecondsStringPrecisionRecord(smallestUnit, digits); 210 | const result = ES.RoundISODateTime(GetSlot(this, ISO_DATE_TIME), increment, unit, roundingMode); 211 | ES.RejectDateTimeRange(result); 212 | return ES.ISODateTimeToString(result, GetSlot(this, CALENDAR), precision, showCalendar); 213 | } 214 | toJSON(): Return['toJSON'] { 215 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 216 | return ES.ISODateTimeToString(GetSlot(this, ISO_DATE_TIME), GetSlot(this, CALENDAR), 'auto'); 217 | } 218 | toLocaleString( 219 | locales: Params['toLocaleString'][0] = undefined, 220 | options: Params['toLocaleString'][1] = undefined 221 | ): string { 222 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 223 | return new DateTimeFormat(locales, options).format(this); 224 | } 225 | valueOf(): never { 226 | ES.ValueOfThrows('PlainDateTime'); 227 | } 228 | 229 | toZonedDateTime( 230 | temporalTimeZoneLike: Params['toZonedDateTime'][0], 231 | options: Params['toZonedDateTime'][1] = undefined 232 | ): Return['toZonedDateTime'] { 233 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 234 | const timeZone = ES.ToTemporalTimeZoneIdentifier(temporalTimeZoneLike); 235 | const resolvedOptions = ES.GetOptionsObject(options); 236 | const disambiguation = ES.GetTemporalDisambiguationOption(resolvedOptions); 237 | const epochNs = ES.GetEpochNanosecondsFor(timeZone, GetSlot(this, ISO_DATE_TIME), disambiguation); 238 | return ES.CreateTemporalZonedDateTime(epochNs, timeZone, GetSlot(this, CALENDAR)); 239 | } 240 | toPlainDate(): Return['toPlainDate'] { 241 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 242 | return ES.CreateTemporalDate(GetSlot(this, ISO_DATE_TIME).isoDate, GetSlot(this, CALENDAR)); 243 | } 244 | toPlainTime(): Return['toPlainTime'] { 245 | ES.CheckReceiver(this, ES.IsTemporalDateTime); 246 | return ES.CreateTemporalTime(GetSlot(this, ISO_DATE_TIME).time); 247 | } 248 | 249 | static from(item: Params['from'][0], options: Params['from'][1] = undefined): Return['from'] { 250 | return ES.ToTemporalDateTime(item, options); 251 | } 252 | static compare(oneParam: Params['compare'][0], twoParam: Params['compare'][1]): Return['compare'] { 253 | const one = ES.ToTemporalDateTime(oneParam); 254 | const two = ES.ToTemporalDateTime(twoParam); 255 | return ES.CompareISODateTime(GetSlot(one, ISO_DATE_TIME), GetSlot(two, ISO_DATE_TIME)); 256 | } 257 | [Symbol.toStringTag]!: 'Temporal.PlainDateTime'; 258 | } 259 | 260 | MakeIntrinsicClass(PlainDateTime, 'Temporal.PlainDateTime'); 261 | 262 | function getCalendarProperty

( 263 | dt: Temporal.PlainDateTime, 264 | prop: P 265 | ): CalendarDateRecord[P] { 266 | ES.CheckReceiver(dt, ES.IsTemporalDateTime); 267 | const isoDate = GetSlot(dt, ISO_DATE_TIME).isoDate; 268 | return ES.calendarImplForObj(dt).isoToDate(isoDate, { [prop]: true })[prop]; 269 | } 270 | 271 | function getTimeProperty(dt: Temporal.PlainDateTime, prop: Temporal.TimeUnit) { 272 | ES.CheckReceiver(dt, ES.IsTemporalDateTime); 273 | return GetSlot(dt, ISO_DATE_TIME).time[prop]; 274 | } 275 | -------------------------------------------------------------------------------- /lib/plainmonthday.ts: -------------------------------------------------------------------------------- 1 | import * as ES from './ecmascript'; 2 | import { MakeIntrinsicClass } from './intrinsicclass'; 3 | import { CALENDAR, GetSlot, ISO_DATE } from './slots'; 4 | import type { Temporal } from '..'; 5 | import { DateTimeFormat } from './intl'; 6 | import type { CalendarDateRecord, PlainMonthDayParams as Params, PlainMonthDayReturn as Return } from './internaltypes'; 7 | 8 | export class PlainMonthDay implements Temporal.PlainMonthDay { 9 | constructor( 10 | isoMonth: Params['constructor'][0], 11 | isoDay: Params['constructor'][0], 12 | calendarParam = 'iso8601', 13 | referenceISOYear = 1972 14 | ) { 15 | const month = ES.ToIntegerWithTruncation(isoMonth); 16 | const day = ES.ToIntegerWithTruncation(isoDay); 17 | const calendar = ES.CanonicalizeCalendar(calendarParam === undefined ? 'iso8601' : ES.RequireString(calendarParam)); 18 | const year = ES.ToIntegerWithTruncation(referenceISOYear); 19 | 20 | ES.RejectISODate(year, month, day); 21 | ES.CreateTemporalMonthDaySlots(this, { year, month, day }, calendar); 22 | } 23 | 24 | get monthCode(): Return['monthCode'] { 25 | return getCalendarProperty(this, 'monthCode'); 26 | } 27 | get day(): Return['day'] { 28 | return getCalendarProperty(this, 'day'); 29 | } 30 | get calendarId(): Return['calendarId'] { 31 | ES.CheckReceiver(this, ES.IsTemporalMonthDay); 32 | return GetSlot(this, CALENDAR); 33 | } 34 | 35 | with(temporalMonthDayLike: Params['with'][0], options: Params['with'][1] = undefined): Return['with'] { 36 | ES.CheckReceiver(this, ES.IsTemporalMonthDay); 37 | if (!ES.IsObject(temporalMonthDayLike)) { 38 | throw new TypeError('invalid argument'); 39 | } 40 | ES.RejectTemporalLikeObject(temporalMonthDayLike); 41 | 42 | const calendar = GetSlot(this, CALENDAR); 43 | let fields = ES.ISODateToFields(calendar, GetSlot(this, ISO_DATE), 'month-day'); 44 | const partialMonthDay = ES.PrepareCalendarFields( 45 | calendar, 46 | temporalMonthDayLike, 47 | ['year', 'month', 'monthCode', 'day'], 48 | [], 49 | 'partial' 50 | ); 51 | fields = ES.CalendarMergeFields(calendar, fields, partialMonthDay); 52 | 53 | const overflow = ES.GetTemporalOverflowOption(ES.GetOptionsObject(options)); 54 | const isoDate = ES.CalendarMonthDayFromFields(calendar, fields, overflow); 55 | return ES.CreateTemporalMonthDay(isoDate, calendar); 56 | } 57 | equals(otherParam: Params['equals'][0]): Return['equals'] { 58 | ES.CheckReceiver(this, ES.IsTemporalMonthDay); 59 | const other = ES.ToTemporalMonthDay(otherParam); 60 | if (ES.CompareISODate(GetSlot(this, ISO_DATE), GetSlot(other, ISO_DATE)) !== 0) return false; 61 | return ES.CalendarEquals(GetSlot(this, CALENDAR), GetSlot(other, CALENDAR)); 62 | } 63 | toString(options: Params['toString'][0] = undefined): string { 64 | ES.CheckReceiver(this, ES.IsTemporalMonthDay); 65 | const resolvedOptions = ES.GetOptionsObject(options); 66 | const showCalendar = ES.GetTemporalShowCalendarNameOption(resolvedOptions); 67 | return ES.TemporalMonthDayToString(this, showCalendar); 68 | } 69 | toJSON(): Return['toJSON'] { 70 | ES.CheckReceiver(this, ES.IsTemporalMonthDay); 71 | return ES.TemporalMonthDayToString(this); 72 | } 73 | toLocaleString( 74 | locales: Params['toLocaleString'][0] = undefined, 75 | options: Params['toLocaleString'][1] = undefined 76 | ): string { 77 | ES.CheckReceiver(this, ES.IsTemporalMonthDay); 78 | return new DateTimeFormat(locales, options).format(this); 79 | } 80 | valueOf(): never { 81 | ES.ValueOfThrows('PlainMonthDay'); 82 | } 83 | toPlainDate(item: Params['toPlainDate'][0]): Return['toPlainDate'] { 84 | ES.CheckReceiver(this, ES.IsTemporalMonthDay); 85 | if (!ES.IsObject(item)) throw new TypeError('argument should be an object'); 86 | const calendar = GetSlot(this, CALENDAR); 87 | 88 | const fields = ES.ISODateToFields(calendar, GetSlot(this, ISO_DATE), 'month-day'); 89 | const inputFields = ES.PrepareCalendarFields(calendar, item, ['year'], [], []); 90 | let mergedFields = ES.CalendarMergeFields(calendar, fields, inputFields); 91 | const isoDate = ES.CalendarDateFromFields(calendar, mergedFields, 'constrain'); 92 | return ES.CreateTemporalDate(isoDate, calendar); 93 | } 94 | 95 | static from(item: Params['from'][0], options: Params['from'][1] = undefined): Return['from'] { 96 | return ES.ToTemporalMonthDay(item, options); 97 | } 98 | [Symbol.toStringTag]!: 'Temporal.PlainMonthDay'; 99 | } 100 | 101 | MakeIntrinsicClass(PlainMonthDay, 'Temporal.PlainMonthDay'); 102 | 103 | function getCalendarProperty

( 104 | md: Temporal.PlainMonthDay, 105 | prop: P 106 | ): CalendarDateRecord[P] { 107 | ES.CheckReceiver(md, ES.IsTemporalMonthDay); 108 | const isoDate = GetSlot(md, ISO_DATE); 109 | return ES.calendarImplForObj(md).isoToDate(isoDate, { [prop]: true })[prop]; 110 | } 111 | -------------------------------------------------------------------------------- /lib/plaintime.ts: -------------------------------------------------------------------------------- 1 | import * as ES from './ecmascript'; 2 | import { MakeIntrinsicClass } from './intrinsicclass'; 3 | 4 | import { GetSlot, TIME } from './slots'; 5 | import type { Temporal } from '..'; 6 | import { DateTimeFormat } from './intl'; 7 | import type { PlainTimeParams as Params, PlainTimeReturn as Return } from './internaltypes'; 8 | 9 | export class PlainTime implements Temporal.PlainTime { 10 | constructor(isoHour = 0, isoMinute = 0, isoSecond = 0, isoMillisecond = 0, isoMicrosecond = 0, isoNanosecond = 0) { 11 | const hour = isoHour === undefined ? 0 : ES.ToIntegerWithTruncation(isoHour); 12 | const minute = isoMinute === undefined ? 0 : ES.ToIntegerWithTruncation(isoMinute); 13 | const second = isoSecond === undefined ? 0 : ES.ToIntegerWithTruncation(isoSecond); 14 | const millisecond = isoMillisecond === undefined ? 0 : ES.ToIntegerWithTruncation(isoMillisecond); 15 | const microsecond = isoMicrosecond === undefined ? 0 : ES.ToIntegerWithTruncation(isoMicrosecond); 16 | const nanosecond = isoNanosecond === undefined ? 0 : ES.ToIntegerWithTruncation(isoNanosecond); 17 | 18 | ES.RejectTime(hour, minute, second, millisecond, microsecond, nanosecond); 19 | const time = { hour, minute, second, millisecond, microsecond, nanosecond }; 20 | 21 | ES.CreateTemporalTimeSlots(this, time); 22 | } 23 | 24 | get hour(): Return['hour'] { 25 | ES.CheckReceiver(this, ES.IsTemporalTime); 26 | return GetSlot(this, TIME).hour; 27 | } 28 | get minute(): Return['minute'] { 29 | ES.CheckReceiver(this, ES.IsTemporalTime); 30 | return GetSlot(this, TIME).minute; 31 | } 32 | get second(): Return['second'] { 33 | ES.CheckReceiver(this, ES.IsTemporalTime); 34 | return GetSlot(this, TIME).second; 35 | } 36 | get millisecond(): Return['millisecond'] { 37 | ES.CheckReceiver(this, ES.IsTemporalTime); 38 | return GetSlot(this, TIME).millisecond; 39 | } 40 | get microsecond(): Return['microsecond'] { 41 | ES.CheckReceiver(this, ES.IsTemporalTime); 42 | return GetSlot(this, TIME).microsecond; 43 | } 44 | get nanosecond(): Return['nanosecond'] { 45 | ES.CheckReceiver(this, ES.IsTemporalTime); 46 | return GetSlot(this, TIME).nanosecond; 47 | } 48 | 49 | with(temporalTimeLike: Params['with'][0], options: Params['with'][1] = undefined): Return['with'] { 50 | ES.CheckReceiver(this, ES.IsTemporalTime); 51 | if (!ES.IsObject(temporalTimeLike)) { 52 | throw new TypeError('invalid argument'); 53 | } 54 | ES.RejectTemporalLikeObject(temporalTimeLike); 55 | 56 | const partialTime = ES.ToTemporalTimeRecord(temporalTimeLike, 'partial'); 57 | 58 | const fields = ES.ToTemporalTimeRecord(this); 59 | let { hour, minute, second, millisecond, microsecond, nanosecond } = Object.assign(fields, partialTime); 60 | const overflow = ES.GetTemporalOverflowOption(ES.GetOptionsObject(options)); 61 | ({ hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateTime( 62 | hour, 63 | minute, 64 | second, 65 | millisecond, 66 | microsecond, 67 | nanosecond, 68 | overflow 69 | )); 70 | return new PlainTime(hour, minute, second, millisecond, microsecond, nanosecond); 71 | } 72 | add(temporalDurationLike: Params['add'][0]): Return['add'] { 73 | ES.CheckReceiver(this, ES.IsTemporalTime); 74 | return ES.AddDurationToTime('add', this, temporalDurationLike); 75 | } 76 | subtract(temporalDurationLike: Params['subtract'][0]): Return['subtract'] { 77 | ES.CheckReceiver(this, ES.IsTemporalTime); 78 | return ES.AddDurationToTime('subtract', this, temporalDurationLike); 79 | } 80 | until(other: Params['until'][0], options: Params['until'][1] = undefined): Return['until'] { 81 | ES.CheckReceiver(this, ES.IsTemporalTime); 82 | return ES.DifferenceTemporalPlainTime('until', this, other, options); 83 | } 84 | since(other: Params['since'][0], options: Params['since'][1] = undefined): Return['since'] { 85 | ES.CheckReceiver(this, ES.IsTemporalTime); 86 | return ES.DifferenceTemporalPlainTime('since', this, other, options); 87 | } 88 | round(roundToParam: Params['round'][0]): Return['round'] { 89 | ES.CheckReceiver(this, ES.IsTemporalTime); 90 | if (roundToParam === undefined) throw new TypeError('options parameter is required'); 91 | const roundTo = 92 | typeof roundToParam === 'string' 93 | ? (ES.CreateOnePropObject('smallestUnit', roundToParam) as Exclude) 94 | : ES.GetOptionsObject(roundToParam); 95 | const roundingIncrement = ES.GetTemporalRoundingIncrementOption(roundTo); 96 | const roundingMode = ES.GetRoundingModeOption(roundTo, 'halfExpand'); 97 | const smallestUnit = ES.GetTemporalUnitValuedOption(roundTo, 'smallestUnit', 'time', ES.REQUIRED); 98 | const MAX_INCREMENTS = { 99 | hour: 24, 100 | minute: 60, 101 | second: 60, 102 | millisecond: 1000, 103 | microsecond: 1000, 104 | nanosecond: 1000 105 | }; 106 | ES.ValidateTemporalRoundingIncrement(roundingIncrement, MAX_INCREMENTS[smallestUnit], false); 107 | 108 | const time = ES.RoundTime(GetSlot(this, TIME), roundingIncrement, smallestUnit, roundingMode); 109 | return ES.CreateTemporalTime(time); 110 | } 111 | equals(otherParam: Params['equals'][0]): Return['equals'] { 112 | ES.CheckReceiver(this, ES.IsTemporalTime); 113 | const other = ES.ToTemporalTime(otherParam); 114 | return ES.CompareTimeRecord(GetSlot(this, TIME), GetSlot(other, TIME)) === 0; 115 | } 116 | 117 | toString(options: Params['toString'][0] = undefined): string { 118 | ES.CheckReceiver(this, ES.IsTemporalTime); 119 | const resolvedOptions = ES.GetOptionsObject(options); 120 | const digits = ES.GetTemporalFractionalSecondDigitsOption(resolvedOptions); 121 | const roundingMode = ES.GetRoundingModeOption(resolvedOptions, 'trunc'); 122 | const smallestUnit = ES.GetTemporalUnitValuedOption(resolvedOptions, 'smallestUnit', 'time', undefined); 123 | if (smallestUnit === 'hour') throw new RangeError('smallestUnit must be a time unit other than "hour"'); 124 | const { precision, unit, increment } = ES.ToSecondsStringPrecisionRecord(smallestUnit, digits); 125 | const time = ES.RoundTime(GetSlot(this, TIME), increment, unit, roundingMode); 126 | return ES.TimeRecordToString(time, precision); 127 | } 128 | toJSON(): Return['toJSON'] { 129 | ES.CheckReceiver(this, ES.IsTemporalTime); 130 | return ES.TimeRecordToString(GetSlot(this, TIME), 'auto'); 131 | } 132 | toLocaleString( 133 | locales: Params['toLocaleString'][0] = undefined, 134 | options: Params['toLocaleString'][1] = undefined 135 | ): string { 136 | ES.CheckReceiver(this, ES.IsTemporalTime); 137 | return new DateTimeFormat(locales, options).format(this); 138 | } 139 | valueOf(): never { 140 | ES.ValueOfThrows('PlainTime'); 141 | } 142 | 143 | static from(item: Params['from'][0], options: Params['from'][1] = undefined): Return['from'] { 144 | return ES.ToTemporalTime(item, options); 145 | } 146 | static compare(oneParam: Params['compare'][0], twoParam: Params['compare'][1]): Return['compare'] { 147 | const one = ES.ToTemporalTime(oneParam); 148 | const two = ES.ToTemporalTime(twoParam); 149 | return ES.CompareTimeRecord(GetSlot(one, TIME), GetSlot(two, TIME)); 150 | } 151 | [Symbol.toStringTag]!: 'Temporal.PlainTime'; 152 | } 153 | 154 | MakeIntrinsicClass(PlainTime, 'Temporal.PlainTime'); 155 | -------------------------------------------------------------------------------- /lib/plainyearmonth.ts: -------------------------------------------------------------------------------- 1 | import * as ES from './ecmascript'; 2 | import { MakeIntrinsicClass } from './intrinsicclass'; 3 | import { CALENDAR, GetSlot, ISO_DATE } from './slots'; 4 | import type { Temporal } from '..'; 5 | import { DateTimeFormat } from './intl'; 6 | import type { 7 | CalendarDateRecord, 8 | PlainYearMonthParams as Params, 9 | PlainYearMonthReturn as Return 10 | } from './internaltypes'; 11 | 12 | export class PlainYearMonth implements Temporal.PlainYearMonth { 13 | constructor( 14 | isoYear: Params['constructor'][0], 15 | isoMonth: Params['constructor'][1], 16 | calendarParam: Params['constructor'][2] = 'iso8601', 17 | referenceISODay: Params['constructor'][3] = 1 18 | ) { 19 | const year = ES.ToIntegerWithTruncation(isoYear); 20 | const month = ES.ToIntegerWithTruncation(isoMonth); 21 | const calendar = ES.CanonicalizeCalendar(calendarParam === undefined ? 'iso8601' : ES.RequireString(calendarParam)); 22 | const day = ES.ToIntegerWithTruncation(referenceISODay); 23 | 24 | ES.RejectISODate(year, month, day); 25 | ES.CreateTemporalYearMonthSlots(this, { year, month, day }, calendar); 26 | } 27 | get year(): Return['year'] { 28 | return getCalendarProperty(this, 'year'); 29 | } 30 | get month(): Return['month'] { 31 | return getCalendarProperty(this, 'month'); 32 | } 33 | get monthCode(): Return['monthCode'] { 34 | return getCalendarProperty(this, 'monthCode'); 35 | } 36 | get calendarId(): Return['calendarId'] { 37 | ES.CheckReceiver(this, ES.IsTemporalYearMonth); 38 | return GetSlot(this, CALENDAR); 39 | } 40 | get era(): Return['era'] { 41 | return getCalendarProperty(this, 'era'); 42 | } 43 | get eraYear(): Return['eraYear'] { 44 | return getCalendarProperty(this, 'eraYear'); 45 | } 46 | get daysInMonth(): Return['daysInMonth'] { 47 | return getCalendarProperty(this, 'daysInMonth'); 48 | } 49 | get daysInYear(): Return['daysInYear'] { 50 | return getCalendarProperty(this, 'daysInYear'); 51 | } 52 | get monthsInYear(): Return['monthsInYear'] { 53 | return getCalendarProperty(this, 'monthsInYear'); 54 | } 55 | get inLeapYear(): Return['inLeapYear'] { 56 | return getCalendarProperty(this, 'inLeapYear'); 57 | } 58 | with(temporalYearMonthLike: Params['with'][0], options: Params['with'][1] = undefined): Return['with'] { 59 | ES.CheckReceiver(this, ES.IsTemporalYearMonth); 60 | if (!ES.IsObject(temporalYearMonthLike)) { 61 | throw new TypeError('invalid argument'); 62 | } 63 | ES.RejectTemporalLikeObject(temporalYearMonthLike); 64 | 65 | const calendar = GetSlot(this, CALENDAR); 66 | let fields = ES.ISODateToFields(calendar, GetSlot(this, ISO_DATE), 'year-month'); 67 | const partialYearMonth = ES.PrepareCalendarFields( 68 | calendar, 69 | temporalYearMonthLike, 70 | ['year', 'month', 'monthCode'], 71 | [], 72 | 'partial' 73 | ); 74 | fields = ES.CalendarMergeFields(calendar, fields, partialYearMonth); 75 | 76 | const overflow = ES.GetTemporalOverflowOption(ES.GetOptionsObject(options)); 77 | const isoDate = ES.CalendarYearMonthFromFields(calendar, fields, overflow); 78 | return ES.CreateTemporalYearMonth(isoDate, calendar); 79 | } 80 | add(temporalDurationLike: Params['add'][0], options: Params['add'][1] = undefined): Return['add'] { 81 | ES.CheckReceiver(this, ES.IsTemporalYearMonth); 82 | return ES.AddDurationToYearMonth('add', this, temporalDurationLike, options); 83 | } 84 | subtract( 85 | temporalDurationLike: Params['subtract'][0], 86 | options: Params['subtract'][1] = undefined 87 | ): Return['subtract'] { 88 | ES.CheckReceiver(this, ES.IsTemporalYearMonth); 89 | return ES.AddDurationToYearMonth('subtract', this, temporalDurationLike, options); 90 | } 91 | until(other: Params['until'][0], options: Params['until'][1] = undefined): Return['until'] { 92 | ES.CheckReceiver(this, ES.IsTemporalYearMonth); 93 | return ES.DifferenceTemporalPlainYearMonth('until', this, other, options); 94 | } 95 | since(other: Params['since'][0], options: Params['since'][1] = undefined): Return['since'] { 96 | ES.CheckReceiver(this, ES.IsTemporalYearMonth); 97 | return ES.DifferenceTemporalPlainYearMonth('since', this, other, options); 98 | } 99 | equals(otherParam: Params['equals'][0]): Return['equals'] { 100 | ES.CheckReceiver(this, ES.IsTemporalYearMonth); 101 | const other = ES.ToTemporalYearMonth(otherParam); 102 | if (ES.CompareISODate(GetSlot(this, ISO_DATE), GetSlot(other, ISO_DATE)) !== 0) return false; 103 | return ES.CalendarEquals(GetSlot(this, CALENDAR), GetSlot(other, CALENDAR)); 104 | } 105 | toString(options: Params['toString'][0] = undefined): string { 106 | ES.CheckReceiver(this, ES.IsTemporalYearMonth); 107 | const resolvedOptions = ES.GetOptionsObject(options); 108 | const showCalendar = ES.GetTemporalShowCalendarNameOption(resolvedOptions); 109 | return ES.TemporalYearMonthToString(this, showCalendar); 110 | } 111 | toJSON(): Return['toJSON'] { 112 | ES.CheckReceiver(this, ES.IsTemporalYearMonth); 113 | return ES.TemporalYearMonthToString(this); 114 | } 115 | toLocaleString( 116 | locales: Params['toLocaleString'][0] = undefined, 117 | options: Params['toLocaleString'][1] = undefined 118 | ): string { 119 | ES.CheckReceiver(this, ES.IsTemporalYearMonth); 120 | return new DateTimeFormat(locales, options).format(this); 121 | } 122 | valueOf(): never { 123 | ES.ValueOfThrows('PlainYearMonth'); 124 | } 125 | toPlainDate(item: Params['toPlainDate'][0]): Return['toPlainDate'] { 126 | ES.CheckReceiver(this, ES.IsTemporalYearMonth); 127 | if (!ES.IsObject(item)) throw new TypeError('argument should be an object'); 128 | const calendar = GetSlot(this, CALENDAR); 129 | 130 | const fields = ES.ISODateToFields(calendar, GetSlot(this, ISO_DATE), 'year-month'); 131 | const inputFields = ES.PrepareCalendarFields(calendar, item, ['day'], [], []); 132 | const mergedFields = ES.CalendarMergeFields(calendar, fields, inputFields); 133 | const isoDate = ES.CalendarDateFromFields(calendar, mergedFields, 'constrain'); 134 | return ES.CreateTemporalDate(isoDate, calendar); 135 | } 136 | 137 | static from(item: Params['from'][0], options: Params['from'][1] = undefined): Return['from'] { 138 | return ES.ToTemporalYearMonth(item, options); 139 | } 140 | static compare(oneParam: Params['compare'][0], twoParam: Params['compare'][1]): Return['compare'] { 141 | const one = ES.ToTemporalYearMonth(oneParam); 142 | const two = ES.ToTemporalYearMonth(twoParam); 143 | return ES.CompareISODate(GetSlot(one, ISO_DATE), GetSlot(two, ISO_DATE)); 144 | } 145 | [Symbol.toStringTag]!: 'Temporal.PlainYearMonth'; 146 | } 147 | 148 | MakeIntrinsicClass(PlainYearMonth, 'Temporal.PlainYearMonth'); 149 | 150 | function getCalendarProperty

( 151 | ym: Temporal.PlainYearMonth, 152 | prop: P 153 | ): CalendarDateRecord[P] { 154 | ES.CheckReceiver(ym, ES.IsTemporalYearMonth); 155 | const isoDate = GetSlot(ym, ISO_DATE); 156 | return ES.calendarImplForObj(ym).isoToDate(isoDate, { [prop]: true })[prop]; 157 | } 158 | -------------------------------------------------------------------------------- /lib/regex.ts: -------------------------------------------------------------------------------- 1 | const offsetIdentifierNoCapture = /(?:[+-](?:[01][0-9]|2[0-3])(?::?[0-5][0-9])?)/; 2 | const tzComponent = /[A-Za-z._][A-Za-z._0-9+-]*/; 3 | export const timeZoneID = new RegExp( 4 | `(?:${offsetIdentifierNoCapture.source}|(?:${tzComponent.source})(?:\\/(?:${tzComponent.source}))*)` 5 | ); 6 | 7 | const yearpart = /(?:[+-]\d{6}|\d{4})/; 8 | const monthpart = /(?:0[1-9]|1[0-2])/; 9 | const daypart = /(?:0[1-9]|[12]\d|3[01])/; 10 | const datesplit = new RegExp( 11 | `(${yearpart.source})(?:-(${monthpart.source})-(${daypart.source})|(${monthpart.source})(${daypart.source}))` 12 | ); 13 | const timesplit = /(\d{2})(?::(\d{2})(?::(\d{2})(?:[.,](\d{1,9}))?)?|(\d{2})(?:(\d{2})(?:[.,](\d{1,9}))?)?)?/; 14 | export const offsetWithParts = /([+-])([01][0-9]|2[0-3])(?::?([0-5][0-9])(?::?([0-5][0-9])(?:[.,](\d{1,9}))?)?)?/; 15 | export const offset = /((?:[+-])(?:[01][0-9]|2[0-3])(?::?(?:[0-5][0-9])(?::?(?:[0-5][0-9])(?:[.,](?:\d{1,9}))?)?)?)/; 16 | const offsetpart = new RegExp(`([zZ])|${offset.source}?`); 17 | export const offsetIdentifier = /([+-])([01][0-9]|2[0-3])(?::?([0-5][0-9])?)?/; 18 | export const annotation = /\[(!)?([a-z_][a-z0-9_-]*)=([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)\]/g; 19 | 20 | export const zoneddatetime = new RegExp( 21 | [ 22 | `^${datesplit.source}`, 23 | `(?:(?:[tT]|\\s+)${timesplit.source}(?:${offsetpart.source})?)?`, 24 | `(?:\\[!?(${timeZoneID.source})\\])?`, 25 | `((?:${annotation.source})*)$` 26 | ].join('') 27 | ); 28 | 29 | export const time = new RegExp( 30 | [ 31 | `^[tT]?${timesplit.source}`, 32 | `(?:${offsetpart.source})?`, 33 | `(?:\\[!?${timeZoneID.source}\\])?`, 34 | `((?:${annotation.source})*)$` 35 | ].join('') 36 | ); 37 | 38 | // The short forms of YearMonth and MonthDay are only for the ISO calendar, but 39 | // annotations are still allowed, and will throw if the calendar annotation is 40 | // not ISO. 41 | // Non-ISO calendar YearMonth and MonthDay have to parse as a Temporal.PlainDate, 42 | // with the reference fields. 43 | // YYYYMM forbidden by ISO 8601 because ambiguous with YYMMDD, but allowed by 44 | // RFC 3339 and we don't allow 2-digit years, so we allow it. 45 | // Not ambiguous with HHMMSS because that requires a 'T' prefix 46 | // UTC offsets are not allowed, because they are not allowed with any date-only 47 | // format; also, YYYY-MM-UU is ambiguous with YYYY-MM-DD 48 | export const yearmonth = new RegExp( 49 | `^(${yearpart.source})-?(${monthpart.source})(?:\\[!?${timeZoneID.source}\\])?((?:${annotation.source})*)$` 50 | ); 51 | export const monthday = new RegExp( 52 | `^(?:--)?(${monthpart.source})-?(${daypart.source})(?:\\[!?${timeZoneID.source}\\])?((?:${annotation.source})*)$` 53 | ); 54 | 55 | const fraction = /(\d+)(?:[.,](\d{1,9}))?/; 56 | 57 | const durationDate = /(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)W)?(?:(\d+)D)?/; 58 | const durationTime = new RegExp(`(?:${fraction.source}H)?(?:${fraction.source}M)?(?:${fraction.source}S)?`); 59 | export const duration = new RegExp(`^([+-])?P${durationDate.source}(?:T(?!$)${durationTime.source})?$`, 'i'); 60 | -------------------------------------------------------------------------------- /lib/slots.ts: -------------------------------------------------------------------------------- 1 | import type JSBI from 'jsbi'; 2 | import type { Temporal } from '..'; 3 | import type { 4 | BuiltinCalendarId, 5 | AnySlottedType, 6 | FormatterOrAmender, 7 | ISODate, 8 | ISODateTime, 9 | TimeRecord 10 | } from './internaltypes'; 11 | import type { DateTimeFormatImpl } from './intl'; 12 | 13 | // Instant 14 | export const EPOCHNANOSECONDS = 'slot-epochNanoSeconds'; 15 | 16 | // DateTime, Date, Time, YearMonth, MonthDay 17 | export const ISO_DATE = 'slot-iso-date'; 18 | export const ISO_DATE_TIME = 'slot-iso-date-time'; 19 | export const TIME = 'slot-time'; 20 | export const CALENDAR = 'slot-calendar'; 21 | // Date, YearMonth, and MonthDay all have the same slots, disambiguation needed: 22 | export const DATE_BRAND = 'slot-date-brand'; 23 | export const YEAR_MONTH_BRAND = 'slot-year-month-brand'; 24 | export const MONTH_DAY_BRAND = 'slot-month-day-brand'; 25 | 26 | // ZonedDateTime 27 | export const TIME_ZONE = 'slot-time-zone'; 28 | 29 | // Duration 30 | export const YEARS = 'slot-years'; 31 | export const MONTHS = 'slot-months'; 32 | export const WEEKS = 'slot-weeks'; 33 | export const DAYS = 'slot-days'; 34 | export const HOURS = 'slot-hours'; 35 | export const MINUTES = 'slot-minutes'; 36 | export const SECONDS = 'slot-seconds'; 37 | export const MILLISECONDS = 'slot-milliseconds'; 38 | export const MICROSECONDS = 'slot-microseconds'; 39 | export const NANOSECONDS = 'slot-nanoseconds'; 40 | 41 | // DateTimeFormatImpl 42 | export const DATE = 'date'; 43 | export const YM = 'ym'; 44 | export const MD = 'md'; 45 | export const TIME_FMT = 'time'; 46 | export const DATETIME = 'datetime'; 47 | export const INST = 'instant'; 48 | export const ORIGINAL = 'original'; 49 | export const TZ_CANONICAL = 'timezone-canonical'; 50 | export const TZ_ORIGINAL = 'timezone-original'; 51 | export const CAL_ID = 'calendar-id'; 52 | export const LOCALE = 'locale'; 53 | export const OPTIONS = 'options'; 54 | 55 | interface SlotInfo { 56 | value: ValueType; 57 | usedBy: UsedByType; 58 | } 59 | 60 | interface SlotInfoRecord { 61 | [k: string]: SlotInfo; 62 | } 63 | 64 | interface Slots extends SlotInfoRecord { 65 | // Instant 66 | [EPOCHNANOSECONDS]: SlotInfo; // number? JSBI? 67 | 68 | // DateTime, Date, Time, YearMonth, MonthDay 69 | [ISO_DATE]: SlotInfo; 70 | [ISO_DATE_TIME]: SlotInfo; 71 | [TIME]: SlotInfo; 72 | [CALENDAR]: SlotInfo; 73 | 74 | // Date, YearMonth, MonthDay common slots 75 | [DATE_BRAND]: SlotInfo; 76 | [YEAR_MONTH_BRAND]: SlotInfo; 77 | [MONTH_DAY_BRAND]: SlotInfo; 78 | 79 | // ZonedDateTime 80 | [TIME_ZONE]: SlotInfo; 81 | 82 | // Duration 83 | [YEARS]: SlotInfo; 84 | [MONTHS]: SlotInfo; 85 | [WEEKS]: SlotInfo; 86 | [DAYS]: SlotInfo; 87 | [HOURS]: SlotInfo; 88 | [MINUTES]: SlotInfo; 89 | [SECONDS]: SlotInfo; 90 | [MILLISECONDS]: SlotInfo; 91 | [MICROSECONDS]: SlotInfo; 92 | [NANOSECONDS]: SlotInfo; 93 | 94 | // DateTimeFormatImpl 95 | [DATE]: SlotInfo; 96 | [YM]: SlotInfo; 97 | [MD]: SlotInfo; 98 | [TIME_FMT]: SlotInfo; 99 | [DATETIME]: SlotInfo; 100 | [INST]: SlotInfo; 101 | [ORIGINAL]: SlotInfo; 102 | [TZ_CANONICAL]: SlotInfo; 103 | [TZ_ORIGINAL]: SlotInfo; 104 | [CAL_ID]: SlotInfo; 105 | [LOCALE]: SlotInfo; 106 | [OPTIONS]: SlotInfo; 107 | } 108 | 109 | type TypesWithCalendarUnits = 110 | | Temporal.PlainDateTime 111 | | Temporal.PlainDate 112 | | Temporal.PlainYearMonth 113 | | Temporal.PlainMonthDay 114 | | Temporal.ZonedDateTime; 115 | 116 | interface SlotsToTypes { 117 | // Instant 118 | [EPOCHNANOSECONDS]: Temporal.Instant; 119 | 120 | // DateTime, Date, Time, YearMonth, MonthDay 121 | [ISO_DATE]: Temporal.PlainDate | Temporal.PlainYearMonth | Temporal.PlainMonthDay; 122 | [ISO_DATE_TIME]: Temporal.PlainDateTime; 123 | [TIME]: Temporal.PlainTime; 124 | [CALENDAR]: TypesWithCalendarUnits; 125 | 126 | // Date, YearMonth, MonthDay common slots 127 | [DATE_BRAND]: Temporal.PlainDate; 128 | [YEAR_MONTH_BRAND]: Temporal.PlainYearMonth; 129 | [MONTH_DAY_BRAND]: Temporal.PlainMonthDay; 130 | 131 | // ZonedDateTime 132 | [TIME_ZONE]: Temporal.ZonedDateTime; 133 | 134 | // Duration 135 | [YEARS]: Temporal.Duration; 136 | [MONTHS]: Temporal.Duration; 137 | [WEEKS]: Temporal.Duration; 138 | [DAYS]: Temporal.Duration; 139 | [HOURS]: Temporal.Duration; 140 | [MINUTES]: Temporal.Duration; 141 | [SECONDS]: Temporal.Duration; 142 | [MILLISECONDS]: Temporal.Duration; 143 | [MICROSECONDS]: Temporal.Duration; 144 | [NANOSECONDS]: Temporal.Duration; 145 | 146 | // DateTimeFormatImpl 147 | [DATE]: DateTimeFormatImpl; 148 | [YM]: DateTimeFormatImpl; 149 | [MD]: DateTimeFormatImpl; 150 | [TIME_FMT]: DateTimeFormatImpl; 151 | [DATETIME]: DateTimeFormatImpl; 152 | [INST]: DateTimeFormatImpl; 153 | [ORIGINAL]: DateTimeFormatImpl; 154 | [TZ_CANONICAL]: DateTimeFormatImpl; 155 | [TZ_ORIGINAL]: DateTimeFormatImpl; 156 | [CAL_ID]: DateTimeFormatImpl; 157 | [LOCALE]: DateTimeFormatImpl; 158 | [OPTIONS]: DateTimeFormatImpl; 159 | } 160 | 161 | type SlotKey = keyof SlotsToTypes; 162 | 163 | const globalSlots = new WeakMap>(); 164 | 165 | function _GetSlots(container: Slots[keyof Slots]['usedBy']) { 166 | return globalSlots.get(container); 167 | } 168 | 169 | const GetSlotsSymbol = Symbol.for('@@Temporal__GetSlots'); 170 | 171 | // expose GetSlots to avoid dual package hazards 172 | (globalThis as any)[GetSlotsSymbol] ||= _GetSlots; 173 | 174 | const GetSlots = (globalThis as any)[GetSlotsSymbol] as typeof _GetSlots; 175 | 176 | function _CreateSlots(container: Slots[keyof Slots]['usedBy']): void { 177 | globalSlots.set(container, Object.create(null)); 178 | } 179 | 180 | const CreateSlotsSymbol = Symbol.for('@@Temporal__CreateSlots'); 181 | 182 | // expose CreateSlots to avoid dual package hazards 183 | (globalThis as any)[CreateSlotsSymbol] ||= _CreateSlots; 184 | 185 | export const CreateSlots = (globalThis as any)[CreateSlotsSymbol] as typeof _CreateSlots; 186 | 187 | // TODO: is there a better way than 9 overloads to make HasSlot into a type 188 | // guard that takes a variable number of parameters? 189 | export function HasSlot(container: unknown, id1: ID1): container is Slots[ID1]['usedBy']; 190 | export function HasSlot( 191 | container: unknown, 192 | id1: ID1, 193 | id2: ID2 194 | ): container is Slots[ID1]['usedBy'] | Slots[ID2]['usedBy']; 195 | export function HasSlot( 196 | container: unknown, 197 | id1: ID1, 198 | id2: ID2, 199 | id3: ID3 200 | ): container is Slots[ID1]['usedBy'] | Slots[ID2]['usedBy'] | Slots[ID3]['usedBy']; 201 | export function HasSlot( 202 | container: unknown, 203 | id1: ID1, 204 | id2: ID2, 205 | id3: ID3, 206 | id4: ID4 207 | ): container is Slots[ID1 | ID2 | ID3 | ID4]['usedBy']; 208 | export function HasSlot< 209 | ID1 extends SlotKey, 210 | ID2 extends SlotKey, 211 | ID3 extends SlotKey, 212 | ID4 extends SlotKey, 213 | ID5 extends SlotKey 214 | >( 215 | container: unknown, 216 | id1: ID1, 217 | id2: ID2, 218 | id3: ID3, 219 | id4: ID4, 220 | id5: ID5 221 | ): container is Slots[ID1 | ID2 | ID3 | ID4 | ID5]['usedBy']; 222 | export function HasSlot< 223 | ID1 extends SlotKey, 224 | ID2 extends SlotKey, 225 | ID3 extends SlotKey, 226 | ID4 extends SlotKey, 227 | ID5 extends SlotKey, 228 | ID6 extends SlotKey 229 | >( 230 | container: unknown, 231 | id1: ID1, 232 | id2: ID2, 233 | id3: ID3, 234 | id4: ID4, 235 | id5: ID5, 236 | id6: ID6 237 | ): container is Slots[ID1 | ID2 | ID3 | ID4 | ID5 | ID6]['usedBy']; 238 | export function HasSlot< 239 | ID1 extends SlotKey, 240 | ID2 extends SlotKey, 241 | ID3 extends SlotKey, 242 | ID4 extends SlotKey, 243 | ID5 extends SlotKey, 244 | ID6 extends SlotKey, 245 | ID7 extends SlotKey 246 | >( 247 | container: unknown, 248 | id1: ID1, 249 | id2: ID2, 250 | id3: ID3, 251 | id4: ID4, 252 | id5: ID5, 253 | id6: ID6, 254 | id7: ID7 255 | ): container is Slots[ID1 | ID2 | ID3 | ID4 | ID5 | ID6 | ID7]['usedBy']; 256 | export function HasSlot< 257 | ID1 extends SlotKey, 258 | ID2 extends SlotKey, 259 | ID3 extends SlotKey, 260 | ID4 extends SlotKey, 261 | ID5 extends SlotKey, 262 | ID6 extends SlotKey, 263 | ID7 extends SlotKey, 264 | ID8 extends SlotKey 265 | >( 266 | container: unknown, 267 | id1: ID1, 268 | id2: ID2, 269 | id3: ID3, 270 | id4: ID4, 271 | id5: ID5, 272 | id6: ID6, 273 | id7: ID7, 274 | id8: ID8 275 | ): container is Slots[ID1 | ID2 | ID3 | ID4 | ID5 | ID6 | ID7 | ID8]['usedBy']; 276 | export function HasSlot< 277 | ID1 extends SlotKey, 278 | ID2 extends SlotKey, 279 | ID3 extends SlotKey, 280 | ID4 extends SlotKey, 281 | ID5 extends SlotKey, 282 | ID6 extends SlotKey, 283 | ID7 extends SlotKey, 284 | ID8 extends SlotKey, 285 | ID9 extends SlotKey 286 | >( 287 | container: unknown, 288 | id1: ID1, 289 | id2: ID2, 290 | id3: ID3, 291 | id4: ID4, 292 | id5: ID5, 293 | id6: ID6, 294 | id7: ID7, 295 | id8: ID8, 296 | id9: ID9 297 | ): container is Slots[ID1 | ID2 | ID3 | ID4 | ID5 | ID6 | ID7 | ID8 | ID9]['usedBy']; 298 | export function HasSlot(container: unknown, ...ids: (keyof Slots)[]): boolean { 299 | if (!container || 'object' !== typeof container) return false; 300 | const myslots = GetSlots(container as AnySlottedType); 301 | return !!myslots && ids.every((id) => id in myslots); 302 | } 303 | export function GetSlot( 304 | container: Slots[typeof id]['usedBy'], 305 | id: KeyT 306 | ): Slots[KeyT]['value'] { 307 | const value = GetSlots(container)?.[id]; 308 | if (value === undefined) throw new TypeError(`Missing internal slot ${id}`); 309 | return value; 310 | } 311 | export function SetSlot( 312 | container: Slots[KeyT]['usedBy'], 313 | id: KeyT, 314 | value: Slots[KeyT]['value'] 315 | ): void { 316 | const slots = GetSlots(container); 317 | 318 | if (slots === undefined) throw new TypeError('Missing slots for the given container'); 319 | 320 | const existingSlot = slots[id]; 321 | 322 | if (existingSlot) throw new TypeError(`${id} already has set`); 323 | 324 | slots[id] = value; 325 | } 326 | 327 | export function ResetSlot( 328 | container: DateTimeFormatImpl, 329 | id: KeyT, 330 | value: Slots[KeyT]['value'] 331 | ): void { 332 | const slots = GetSlots(container); 333 | 334 | if (slots === undefined) throw new TypeError('Missing slots for the given container'); 335 | 336 | const existingSlot = slots[id]; 337 | 338 | if (existingSlot === undefined) throw new TypeError(`tried to reset ${id} which was not set`); 339 | 340 | slots[id] = value; 341 | } 342 | -------------------------------------------------------------------------------- /lib/temporal.ts: -------------------------------------------------------------------------------- 1 | import './calendar'; 2 | export { Instant } from './instant'; 3 | export { PlainDate } from './plaindate'; 4 | export { PlainDateTime } from './plaindatetime'; 5 | export { Duration } from './duration'; 6 | export { PlainMonthDay } from './plainmonthday'; 7 | export { Now } from './now'; 8 | export { PlainTime } from './plaintime'; 9 | export { PlainYearMonth } from './plainyearmonth'; 10 | export { ZonedDateTime } from './zoneddatetime'; 11 | -------------------------------------------------------------------------------- /lib/timeduration.ts: -------------------------------------------------------------------------------- 1 | import JSBI from 'jsbi'; 2 | 3 | import { assert } from './assert'; 4 | import { 5 | abs, 6 | BILLION, 7 | compare, 8 | DAY_NANOS_JSBI, 9 | divmod, 10 | ensureJSBI, 11 | HOUR_NANOS, 12 | isEven, 13 | MILLION, 14 | MINUTE_NANOS_JSBI, 15 | ONE, 16 | TEN, 17 | THOUSAND, 18 | TWO, 19 | ZERO 20 | } from './bigintmath'; 21 | import { ApplyUnsignedRoundingMode, GetUnsignedRoundingMode } from './math'; 22 | import type { Temporal } from '..'; 23 | 24 | export class TimeDuration { 25 | static MAX = JSBI.BigInt('9007199254740991999999999'); 26 | static ZERO = new TimeDuration(ZERO); 27 | 28 | totalNs: JSBI; 29 | sec: number; 30 | subsec: number; 31 | 32 | constructor(totalNs: bigint | JSBI) { 33 | assert(typeof totalNs !== 'number', 'big integer required'); 34 | this.totalNs = ensureJSBI(totalNs); 35 | assert(JSBI.lessThanOrEqual(abs(this.totalNs), TimeDuration.MAX), 'integer too big'); 36 | 37 | this.sec = JSBI.toNumber(JSBI.divide(this.totalNs, BILLION)); 38 | this.subsec = JSBI.toNumber(JSBI.remainder(this.totalNs, BILLION)); 39 | assert(Number.isSafeInteger(this.sec), 'seconds too big'); 40 | assert(Math.abs(this.subsec) <= 999_999_999, 'subseconds too big'); 41 | } 42 | 43 | static validateNew(totalNs: JSBI, operation: string) { 44 | if (JSBI.greaterThan(abs(totalNs), TimeDuration.MAX)) { 45 | throw new RangeError(`${operation} of duration time units cannot exceed ${TimeDuration.MAX} s`); 46 | } 47 | return new TimeDuration(totalNs); 48 | } 49 | 50 | static fromEpochNsDiff(epochNs1: JSBI | bigint, epochNs2: JSBI | bigint) { 51 | const diff = JSBI.subtract(ensureJSBI(epochNs1), ensureJSBI(epochNs2)); 52 | // No extra validate step. Should instead fail assertion if too big 53 | return new TimeDuration(diff); 54 | } 55 | 56 | static fromComponents(h: number, min: number, s: number, ms: number, µs: number, ns: number) { 57 | const totalNs = JSBI.add( 58 | JSBI.add( 59 | JSBI.add( 60 | JSBI.add( 61 | JSBI.add(JSBI.BigInt(ns), JSBI.multiply(JSBI.BigInt(µs), THOUSAND)), 62 | JSBI.multiply(JSBI.BigInt(ms), MILLION) 63 | ), 64 | JSBI.multiply(JSBI.BigInt(s), BILLION) 65 | ), 66 | JSBI.multiply(JSBI.BigInt(min), MINUTE_NANOS_JSBI) 67 | ), 68 | JSBI.multiply(JSBI.BigInt(h), HOUR_NANOS) 69 | ); 70 | return TimeDuration.validateNew(totalNs, 'total'); 71 | } 72 | 73 | abs() { 74 | return new TimeDuration(abs(this.totalNs)); 75 | } 76 | 77 | add(other: TimeDuration) { 78 | return TimeDuration.validateNew(JSBI.add(this.totalNs, other.totalNs), 'sum'); 79 | } 80 | 81 | add24HourDays(days: number) { 82 | assert(Number.isInteger(days), 'days must be an integer'); 83 | return TimeDuration.validateNew(JSBI.add(this.totalNs, JSBI.multiply(JSBI.BigInt(days), DAY_NANOS_JSBI)), 'sum'); 84 | } 85 | 86 | addToEpochNs(epochNs: JSBI | bigint) { 87 | return JSBI.add(ensureJSBI(epochNs), this.totalNs); 88 | } 89 | 90 | cmp(other: TimeDuration) { 91 | return compare(this.totalNs, other.totalNs); 92 | } 93 | 94 | divmod(n: number) { 95 | assert(n !== 0, 'division by zero'); 96 | const { quotient, remainder } = divmod(this.totalNs, JSBI.BigInt(n)); 97 | const q = JSBI.toNumber(quotient); 98 | const r = new TimeDuration(remainder); 99 | return { quotient: q, remainder: r }; 100 | } 101 | 102 | fdiv(nParam: JSBI | bigint) { 103 | const n = ensureJSBI(nParam); 104 | assert(!JSBI.equal(n, ZERO), 'division by zero'); 105 | const nBigInt = JSBI.BigInt(n); 106 | let { quotient, remainder } = divmod(this.totalNs, nBigInt); 107 | 108 | // Perform long division to calculate the fractional part of the quotient 109 | // remainder / n with more accuracy than 64-bit floating point division 110 | const precision = 50; 111 | const decimalDigits: number[] = []; 112 | let digit; 113 | const sign = (JSBI.lessThan(this.totalNs, ZERO) ? -1 : 1) * Math.sign(JSBI.toNumber(n)); 114 | while (!JSBI.equal(remainder, ZERO) && decimalDigits.length < precision) { 115 | remainder = JSBI.multiply(remainder, TEN); 116 | ({ quotient: digit, remainder } = divmod(remainder, nBigInt)); 117 | decimalDigits.push(Math.abs(JSBI.toNumber(digit))); 118 | } 119 | return sign * Number(abs(quotient).toString() + '.' + decimalDigits.join('')); 120 | } 121 | 122 | isZero() { 123 | return JSBI.equal(this.totalNs, ZERO); 124 | } 125 | 126 | round(incrementParam: JSBI | bigint, mode: Temporal.RoundingMode) { 127 | const increment = ensureJSBI(incrementParam); 128 | if (JSBI.equal(increment, ONE)) return this; 129 | const { quotient, remainder } = divmod(this.totalNs, increment); 130 | const sign = JSBI.lessThan(this.totalNs, ZERO) ? 'negative' : 'positive'; 131 | const r1 = JSBI.multiply(abs(quotient), increment); 132 | const r2 = JSBI.add(r1, increment); 133 | const cmp = compare(abs(JSBI.multiply(remainder, TWO)), increment); 134 | const unsignedRoundingMode = GetUnsignedRoundingMode(mode, sign); 135 | const rounded = JSBI.equal(remainder, ZERO) 136 | ? r1 137 | : ApplyUnsignedRoundingMode(r1, r2, cmp, isEven(quotient), unsignedRoundingMode); 138 | const result = sign === 'positive' ? rounded : JSBI.unaryMinus(rounded); 139 | return TimeDuration.validateNew(result, 'rounding'); 140 | } 141 | 142 | sign() { 143 | return this.cmp(new TimeDuration(ZERO)); 144 | } 145 | 146 | subtract(other: TimeDuration) { 147 | return TimeDuration.validateNew(JSBI.subtract(this.totalNs, other.totalNs), 'difference'); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@js-temporal/polyfill", 3 | "version": "0.5.1", 4 | "description": "Polyfill for Temporal (https://github.com/tc39/proposal-temporal), an ECMA TC39 Stage 3 proposal", 5 | "type": "module", 6 | "main": "./dist/index.cjs", 7 | "module": "./dist/index.esm.js", 8 | "browser": "./dist/index.umd.js", 9 | "exports": { 10 | ".": [ 11 | { 12 | "import": { 13 | "types": "./index.d.ts", 14 | "default": "./dist/index.esm.js" 15 | }, 16 | "require": { 17 | "types": "./index.d.cts", 18 | "default": "./dist/index.cjs" 19 | }, 20 | "default": "./dist/index.cjs" 21 | }, 22 | "./dist/index.cjs" 23 | ] 24 | }, 25 | "sideEffects": [ 26 | "./tsc-out/calendar.js" 27 | ], 28 | "types": "./index.d.ts", 29 | "scripts": { 30 | "test": "npm run build && node --no-warnings --experimental-modules --experimental-specifier-resolution=node --icu-data-dir node_modules/full-icu --loader ./test/resolve.source.mjs ./test/all.mjs", 31 | "test262": "TEST262=1 npm run build && node runtest262.mjs \"$@\"", 32 | "testValidStrings": "npm run build && node --experimental-modules --experimental-specifier-resolution=node --no-warnings --icu-data-dir ./node_modules/full-icu/ --loader ./test/resolve.source.mjs test/validStrings.mjs", 33 | "build": "rm -rf dist/* tsc-out/* && tsc && rollup -c rollup.config.js --bundleConfigAsCjs", 34 | "prepare": "npm run build", 35 | "prepublishOnly": "node copy-types.mjs && npm run build", 36 | "update": "npx npm-check-updates -u -x @pipobscure/demitasse && npm install", 37 | "playground": "TEMPORAL_PLAYGROUND=1 npm run build && node --experimental-modules --no-warnings --icu-data-dir node_modules/full-icu -r ./dist/playground.cjs", 38 | "lint": "eslint . --ext ts,js,mjs,.d.ts --max-warnings 0 --cache \"$@\" && npm run prettier", 39 | "postlint": "npm run tscheck", 40 | "prune": "ts-prune -e -i test/tc39 -i \"(lib/index|lib/init|index.d).ts\"", 41 | "prettier": "prettier lib/* ./test/*.mjs ./*.md ./*.json ./*.d.ts ./*.js ./.*.yml --check", 42 | "prettierfix": "prettier lib/* ./test/*.mjs ./*.md ./*.json ./*.d.ts ./*.js ./.*.yml --check --write", 43 | "eslintfix": "eslint . --ext ts,js,mjs,.d.ts --fix", 44 | "fix": "npm run eslintfix && npm run prettierfix", 45 | "tscheck": "tsc index.d.ts --noEmit --strict --lib ESNext" 46 | }, 47 | "keywords": [ 48 | "Calendar", 49 | "Date", 50 | "DateTime", 51 | "Duration", 52 | "Instant", 53 | "MonthDay", 54 | "Polyfill", 55 | "Temporal", 56 | "Time", 57 | "Timestamp", 58 | "TimeZone", 59 | "YearMonth" 60 | ], 61 | "author": "Philipp Dunkel ", 62 | "contributors": [ 63 | "Daniel Ehrenberg", 64 | "Jason Williams", 65 | "Justin Grant", 66 | "James Wright", 67 | "Maggie Johnson-Pint", 68 | "Matt Johnson-Pint", 69 | "Ms2ger", 70 | "Philip Chimento", 71 | "Philipp Dunkel", 72 | "Sasha Pierson", 73 | "Shane F. Carr", 74 | "Ujjwal Sharma" 75 | ], 76 | "license": "ISC", 77 | "files": [ 78 | "index.d.ts", 79 | "index.d.cts", 80 | "dist", 81 | "lib", 82 | "CHANGELOG.md" 83 | ], 84 | "overrides": { 85 | "@rollup/pluginutils": "^5.0.2" 86 | }, 87 | "dependencies": { 88 | "jsbi": "^4.3.0" 89 | }, 90 | "devDependencies": { 91 | "@babel/core": "^7.22.5", 92 | "@babel/preset-env": "^7.22.5", 93 | "@js-temporal/temporal-test262-runner": "^0.10.1", 94 | "@pipobscure/demitasse": "^1.0.10", 95 | "@pipobscure/demitasse-pretty": "^1.0.10", 96 | "@pipobscure/demitasse-run": "^1.0.10", 97 | "@rollup/plugin-babel": "^6.0.3", 98 | "@rollup/plugin-commonjs": "^25.0.0", 99 | "@rollup/plugin-node-resolve": "^15.1.0", 100 | "@rollup/plugin-replace": "^5.0.2", 101 | "@rollup/plugin-strip": "^3.0.4", 102 | "@rollup/plugin-terser": "^0.4.3", 103 | "@rollup/plugin-typescript": "^11.1.1", 104 | "@typescript-eslint/eslint-plugin": "^5.59.9", 105 | "@typescript-eslint/parser": "^5.59.9", 106 | "eslint": "^8.42.0", 107 | "eslint-config-prettier": "^8.8.0", 108 | "eslint-plugin-prettier": "^4.2.1", 109 | "full-icu": "^1.5.0", 110 | "prettier": "^2.8.8", 111 | "rollup": "^3.24.0", 112 | "rollup-plugin-dts": "^5.3.0", 113 | "rollup-plugin-sourcemaps": "^0.6.3", 114 | "timezones.json": "^1.7.0", 115 | "ts-prune": "^0.10.3", 116 | "typescript": "^5.1.3", 117 | "yargs": "^17.7.2" 118 | }, 119 | "engines": { 120 | "node": ">=12" 121 | }, 122 | "prettier": { 123 | "printWidth": 120, 124 | "trailingComma": "none", 125 | "tabWidth": 2, 126 | "semi": true, 127 | "singleQuote": true, 128 | "bracketSpacing": true, 129 | "arrowParens": "always" 130 | }, 131 | "directories": { 132 | "lib": "lib", 133 | "test": "test" 134 | }, 135 | "repository": { 136 | "type": "git", 137 | "url": "git+https://github.com/js-temporal/temporal-polyfill.git" 138 | }, 139 | "bugs": { 140 | "url": "https://github.com/js-temporal/temporal-polyfill/issues" 141 | }, 142 | "homepage": "https://github.com/js-temporal/temporal-polyfill#readme" 143 | } 144 | -------------------------------------------------------------------------------- /polyfill/lib/slots.mjs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js-temporal/temporal-polyfill/e3136ab7bbd623fd040e5c7dac57bc4b2ccba052/polyfill/lib/slots.mjs -------------------------------------------------------------------------------- /polyfill/lib/timeduration.mjs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js-temporal/temporal-polyfill/e3136ab7bbd623fd040e5c7dac57bc4b2ccba052/polyfill/lib/timeduration.mjs -------------------------------------------------------------------------------- /polyfill/test/ecmascript.mjs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js-temporal/temporal-polyfill/e3136ab7bbd623fd040e5c7dac57bc4b2ccba052/polyfill/test/ecmascript.mjs -------------------------------------------------------------------------------- /release-checklist.md: -------------------------------------------------------------------------------- 1 | # Release Checklist 2 | 3 | - Run `npm test` and `npm run test262` to make sure that everything works, or 4 | check that the current commit at which you are going to cut a release has a 5 | passing set of GitHub Actions checks. 6 | - Determine next version number (0.1.0 → 0.2.0 for breaking changes, 0.1.0 → 0.1.1 for non-breaking changes) 7 | - Increment the version number in `package.json` 8 | - Update the CHANGELOG.md file ([example](https://github.com/js-temporal/temporal-polyfill/blob/5df9d9f659b5fe2f8f051f12e2e9fd5f81e81b2d/CHANGELOG.md)) 9 | - Look at the log (e.g. `git log -p ..`) 10 | - Summarize the major changes, grouping changes into bug fixes, non-breaking 11 | changes, and other. 12 | - Don't forget to linkify commit references in [CHANGELOG.md](./CHANGELOG.md). Specify all the commit URLS as [link labels](https://spec.commonmark.org/0.30/#link-label), and add all the links as [link reference definitions](https://spec.commonmark.org/0.30/#link-reference-definition) to the very end of the changelog. To quickly generate all the link refereces in the doc, try using `grep`: 13 | - `cat CHANGELOG.md | grep -oP '(?<=\[)([a-z0-9]{8})(?=\])' | xargs -I{} bash -c 'echo "[{}]: https://github.com/js-temporal/temporal-polyfill/commit/$(git rev-parse {})"'` 14 | - It's nice to include links back to the original proposal-temporal PRs when possible. To search through commit messages for relevant links, try `cat CHANGELOG.md | grep -oP '(?<=^\[)([a-z0-9]{8})(?=\])' | xargs -I{} bash -c 'git log --format=%B -n 1 {} | grep "tc39/proposal-temporal" && echo {}'`, which will print out all links **followed by** the relevant commit short ref. 15 | - Open a pull request with the above change, get it approved, and merge it into 16 | `main` 17 | - Create the new release on GitHub https://github.com/js-temporal/temporal-polyfill/releases/new 18 | - In the release description, include a permalink to the CHANGELOG.md file at the commit created earlier. 19 | - Thank everyone who contributed to the release (e.g. `git shortlog ..` to see the contributors) 20 | - Make the release target point at the commit created above (or, for a patch 21 | release, create a branch with the release commit + the relevant 22 | cherry-picked commits and use that as the target). 23 | - In the "Choose a Tag" dropdown, type the new version identifier to create a 24 | tag in the repository when the release is published. Don't forget the `v` in the version tag, e.g. `v0.2.0`. 25 | - Check the "This is a pre-release" box unless we've decided to release a production version. 26 | - Publish the release. Note, you can leave the release as draft and ask other 27 | maintainers to spot-check the release if you wish before publishing. 28 | - Once the GitHub release has been published, update your local repository (`git fetch --all`) to pull in the newly created tag. Checkout this 29 | tag (`git checkout v`), and make sure your working directory 30 | is clean. 31 | - Run `NODE_ENV=production npm publish` to publish the release to NPM. Note this 32 | will publish whatever version is specified in the package.json file in your working directory, so be sure this matches your intended version to release (this should have been 33 | checked in code-review when creating the Release commit, and if this is 34 | incorrect would indicate the created Tag is wrong). 35 | - This requires two-factor authentication to be active on your NPM account 36 | - Setting `NODE_ENV` is important: without this, package content will not be 37 | minified. 38 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs'; 2 | import nodeResolve from '@rollup/plugin-node-resolve'; 3 | import babel from '@rollup/plugin-babel'; 4 | import replace from '@rollup/plugin-replace'; 5 | import strip from '@rollup/plugin-strip'; 6 | import terser from '@rollup/plugin-terser'; 7 | import sourcemaps from 'rollup-plugin-sourcemaps'; 8 | import { env } from 'process'; 9 | 10 | // Uncomment and replace the code below once all supported Node versions work with import assertions. See 11 | // pawelgrzybek.com/all-you-need-to-know-to-move-from-commonjs-to-ecmascript-modules-esm-in-node-js/#importing-json 12 | // import pkg from './package.json' assert { type: 'json' }; 13 | import { createRequire } from 'module'; 14 | const require = createRequire(import.meta.url); 15 | const pkg = require('./package.json'); 16 | 17 | const isPlaygroundBuild = !!env.TEMPORAL_PLAYGROUND; 18 | const isTest262Build = !!env.TEST262; 19 | const isProduction = env.NODE_ENV === 'production'; 20 | const isTranspiledBuild = !!env.TRANSPILE; 21 | const libName = 'temporal'; 22 | 23 | function withPlugins( 24 | options = { 25 | babelConfig: undefined, 26 | optimize: false, 27 | debugBuild: true, 28 | enableAssertions: true, 29 | minifyNames: false 30 | } 31 | ) { 32 | const basePlugins = [ 33 | replace({ exclude: 'node_modules/**', 'globalThis.__debug__': options.debugBuild, preventAssignment: true }), 34 | replace({ 35 | exclude: 'node_modules/**', 36 | 'globalThis.__enableAsserts__': options.enableAssertions, 37 | preventAssignment: true 38 | }), 39 | commonjs(), 40 | nodeResolve({ preferBuiltins: false }), 41 | sourcemaps() 42 | ]; 43 | if (options.babelConfig) { 44 | if (!options.babelConfig.inputSourceMap) { 45 | options.babelConfig.inputSourceMap = true; 46 | } 47 | basePlugins.push(babel(options.babelConfig)); 48 | } 49 | if (!options.enableAssertions) { 50 | basePlugins.push( 51 | strip({ 52 | functions: ['assert', 'assertNotReached', 'assertExists', 'ES.assertExists'] 53 | }) 54 | ); 55 | } 56 | if (options.optimize) { 57 | basePlugins.push( 58 | terser({ 59 | keep_classnames: true, 60 | keep_fnames: !options.minifyNames, 61 | ecma: 2015, 62 | compress: { 63 | keep_fargs: true, 64 | keep_classnames: true, 65 | keep_fnames: !options.minifyNames, 66 | passes: 2 67 | }, 68 | mangle: { 69 | keep_classnames: true, 70 | keep_fnames: !options.minifyNames 71 | } 72 | }) 73 | ); 74 | } 75 | return basePlugins; 76 | } 77 | 78 | const input = 'tsc-out/index.js'; 79 | 80 | const external = [ 81 | // Some dependencies (e.g. es-abstract) are imported using sub-paths, so the 82 | // regex below will match these imports too 83 | ...Object.keys(pkg.dependencies || {}), 84 | ...Object.keys(pkg.peerDependencies || {}) 85 | ].map((dep) => new RegExp(dep + '*')); 86 | 87 | function outputEntry(file, format) { 88 | return { 89 | name: libName, 90 | file, 91 | format, 92 | exports: 'named', 93 | sourcemap: true 94 | }; 95 | } 96 | 97 | const es5BundleBabelConfig = { 98 | babelHelpers: 'bundled', 99 | presets: [ 100 | [ 101 | '@babel/preset-env', 102 | { 103 | targets: '> 0.25%, not dead, ie 11' 104 | } 105 | ] 106 | ] 107 | }; 108 | 109 | let builds = []; 110 | 111 | if (isTest262Build) { 112 | builds = [ 113 | { 114 | input: 'tsc-out/init.js', 115 | output: { 116 | name: libName, 117 | file: 'dist/script.js', 118 | format: 'iife', 119 | sourcemap: true 120 | }, 121 | plugins: withPlugins({ 122 | debugBuild: false, // Disable other debug features that can break test262 tests 123 | enableAssertions: true, // But enable assertions 124 | optimize: isProduction, 125 | babelConfig: isTranspiledBuild ? es5BundleBabelConfig : undefined 126 | }) 127 | } 128 | ]; 129 | } else if (isPlaygroundBuild) { 130 | builds = [ 131 | { 132 | input: 'tsc-out/init.js', 133 | output: { 134 | name: libName, 135 | file: 'dist/playground.cjs', 136 | format: 'cjs', 137 | exports: 'named', 138 | sourcemap: true 139 | }, 140 | plugins: withPlugins({}) 141 | } 142 | ]; 143 | } else { 144 | // Production / production-like builds 145 | 146 | // - an ES2020 CJS bundle for "main" 147 | // - an ES2020 ESM bundle for "module" 148 | // Note that all dependencies are marked as external and won't be included in 149 | // these bundles. 150 | const modernBuildDef = { 151 | input, 152 | external, 153 | output: [ 154 | // ESM bundle 155 | outputEntry(pkg.module, 'es'), 156 | // CJS bundle. 157 | // Note that because package.json specifies "type":"module", the name of 158 | // this file MUST end in ".cjs" in order to be treated as a CommonJS file. 159 | outputEntry(pkg.main, 'cjs') 160 | ], 161 | plugins: withPlugins({ 162 | debugBuild: !isProduction, 163 | enableAssertions: !isProduction, 164 | optimize: isProduction, 165 | minifyNames: isProduction 166 | // Here is where we could insert the JSBI -> native BigInt plugin if we 167 | // could find a way to provide a separate bundle for modern browsers 168 | // that can use native BigInt. 169 | // Maybe use node's exports + a user-defined condition? 170 | // https://nodejs.org/api/packages.html#resolving-user-conditions 171 | }) 172 | }; 173 | // A legacy build that 174 | // - bundles all our dependencies (big-integer) into this file 175 | // - transpiles down to ES5 176 | const legacyUMDBuildDef = { 177 | input, 178 | // UMD bundle for using in script tags, etc 179 | // Note that some build systems don't like reading UMD files if they end in 180 | // '.cjs', so this entry in package.json should end in a .js file extension. 181 | output: [outputEntry(pkg.browser, 'umd')], 182 | plugins: withPlugins({ 183 | debugBuild: !isProduction, 184 | enableAssertions: !isProduction, 185 | optimize: isProduction, 186 | babelConfig: es5BundleBabelConfig 187 | }) 188 | }; 189 | builds = [modernBuildDef, legacyUMDBuildDef]; 190 | } 191 | 192 | export default builds; 193 | -------------------------------------------------------------------------------- /runtest262.mjs: -------------------------------------------------------------------------------- 1 | import runTest262 from '@js-temporal/temporal-test262-runner'; 2 | import yargs from 'yargs'; 3 | import * as process from 'process'; 4 | import { hideBin } from 'yargs/helpers'; 5 | 6 | const isProduction = process.env.NODE_ENV === 'production'; 7 | const isTranspiledBuild = !!process.env.TRANSPILE; 8 | const timeoutMsecs = process.env.TIMEOUT || 60000; 9 | 10 | yargs(hideBin(process.argv)) 11 | .command( 12 | '*', 13 | 'Run test262 tests', 14 | (builder) => { 15 | builder.option('update-expected-failure-files', { 16 | requiresArg: false, 17 | default: false, 18 | type: 'boolean', 19 | description: 'Whether to update the existing expected-failure files on-disk and remove tests that now pass.' 20 | }); 21 | }, 22 | (parsedArgv) => { 23 | const expectedFailureFiles = ['test/expected-failures.txt']; 24 | if (isProduction) { 25 | expectedFailureFiles.push( 26 | isTranspiledBuild ? 'test/expected-failures-es5.txt' : 'test/expected-failures-opt.txt' 27 | ); 28 | } 29 | 30 | const nodeVersion = parseInt(process.versions.node.split('.')[0]); 31 | if (nodeVersion < 18) expectedFailureFiles.push('test/expected-failures-before-node18.txt'); 32 | if (nodeVersion < 16) expectedFailureFiles.push('test/expected-failures-before-node16.txt'); 33 | if (nodeVersion < 20) expectedFailureFiles.push('test/expected-failures-before-node20.txt'); 34 | if (nodeVersion < 22) expectedFailureFiles.push('test/expected-failures-before-node22.txt'); 35 | if (nodeVersion < 23) expectedFailureFiles.push('test/expected-failures-before-node23.txt'); 36 | // Eventually this should be fixed and this condition should be updated. 37 | if (nodeVersion >= 18) expectedFailureFiles.push('test/expected-failures-cldr42.txt'); 38 | 39 | // As we migrate commits from proposal-temporal, remove expected failures from here. 40 | expectedFailureFiles.push('test/expected-failures-todo-migrated-code.txt'); 41 | 42 | const result = runTest262({ 43 | test262Dir: 'test262', 44 | polyfillCodeFile: 'dist/script.js', 45 | expectedFailureFiles, 46 | testGlobs: parsedArgv._, 47 | timeoutMsecs, 48 | updateExpectedFailureFiles: parsedArgv.updateExpectedFailureFiles 49 | }); 50 | 51 | process.exit(result ? 0 : 1); 52 | } 53 | ) 54 | .help().argv; 55 | -------------------------------------------------------------------------------- /test/all.mjs: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env -S node --experimental-modules 2 | 3 | /* 4 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 5 | ** This code is governed by the license found in the LICENSE file. 6 | */ 7 | 8 | import Demitasse from '@pipobscure/demitasse'; 9 | import Pretty from '@pipobscure/demitasse-pretty'; 10 | 11 | // exhaustive date arithmetic tests, not suitable for test262 12 | import './datemath.mjs'; 13 | 14 | // tests of internals, not suitable for test262 15 | import './ecmascript.mjs'; 16 | 17 | // Power-of-10 math 18 | import './math.mjs'; 19 | 20 | // Internal 96-bit integer implementation, not suitable for test262 21 | import './timeduration.mjs'; 22 | 23 | Promise.resolve() 24 | .then(() => { 25 | return Demitasse.report(Pretty.reporter); 26 | }) 27 | .then((failed) => process.exit(failed ? 1 : 0)) 28 | .catch((e) => { 29 | console.error(e); 30 | process.exit(-1); 31 | }); 32 | -------------------------------------------------------------------------------- /test/datemath.mjs: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env -S node --experimental-modules 2 | 3 | /* 4 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 5 | ** This code is governed by the license found in the LICENSE file. 6 | */ 7 | 8 | import Demitasse from '@pipobscure/demitasse'; 9 | const { describe, it, report } = Demitasse; 10 | 11 | import Pretty from '@pipobscure/demitasse-pretty'; 12 | const { reporter } = Pretty; 13 | 14 | import { strict as assert } from 'assert'; 15 | const { equal } = assert; 16 | 17 | import * as Temporal from '@js-temporal/polyfill'; 18 | 19 | describe('Date.since(simple, simple)', () => { 20 | build('Before Leap Day', '2020-01-03', '2020-02-15'); 21 | build('Before Leap Day', '2020-01-28', '2020-02-15'); 22 | build('Before Leap Day', '2020-01-31', '2020-02-15'); 23 | build('Cross Leap Day', '2020-01-31', '2020-06-30'); 24 | build('After Leap Day', '2020-03-31', '2020-06-30'); 25 | build('After Leap Day', '2020-03-25', '2020-07-31'); 26 | }); 27 | describe('Date.since(normal, normal)', () => { 28 | build('Month<2 & Month<2', '2018-01-20', '2019-01-05'); 29 | build('Month>2 & Month>2', '2018-03-20', '2019-03-05'); 30 | build('Month>2 & Month>2', '2018-04-20', '2019-04-05'); 31 | build('Month<2 & Month>2', '2018-01-20', '2019-04-05'); 32 | build('Month>2 & Month<2', '2018-03-20', '2019-01-05'); 33 | build('Month>2 & Month<2', '2018-04-20', '2019-01-05'); 34 | }); 35 | describe('Date.since(leap, leap)', () => { 36 | build('Month<2 & Month<2', '2016-01-20', '2020-01-05'); 37 | build('Month>2 & Month>2', '2016-03-20', '2020-04-05'); 38 | build('Month>2 & Month>2', '2016-03-20', '2020-03-05'); 39 | build('Month<2 & Month>2', '2016-01-20', '2020-02-05'); 40 | build('Month>2 & Month<2', '2016-03-20', '2020-01-05'); 41 | build('Month>2 & Month<2', '2016-04-20', '2020-01-05'); 42 | }); 43 | describe('Date.since(leap, normal)', () => { 44 | build('Month<2 & Month<2', '2016-01-20', '2017-01-05'); 45 | build('Month>2 & Month>2', '2016-03-20', '2017-04-05'); 46 | build('Month>2 & Month>2', '2016-04-20', '2017-03-05'); 47 | build('Month<2 & Month>2', '2016-01-20', '2017-04-05'); 48 | build('Month>2 & Month<2', '2016-03-20', '2017-01-05'); 49 | build('Month>2 & Month<2', '2016-04-20', '2017-01-05'); 50 | }); 51 | describe('Date.since(normal, leap)', () => { 52 | build('Month<2 & Month<2', '2019-01-20', '2020-01-05'); 53 | build('Month>2 & Month>2', '2019-03-20', '2020-04-05'); 54 | build('Month>2 & Month>2', '2019-04-20', '2020-03-05'); 55 | build('Month<2 & Month>2', '2019-01-20', '2020-04-05'); 56 | build('Month>2 & Month<2', '2019-03-20', '2020-01-05'); 57 | build('Month>2 & Month<2', '2019-04-20', '2020-01-05'); 58 | }); 59 | 60 | function build(name, sone, stwo) { 61 | const calendars = ['iso8601', 'gregory']; 62 | describe(name, () => { 63 | const largestUnits = ['years', 'months', 'weeks', 'days']; 64 | for (const calendar of calendars) { 65 | const [one, two] = [ 66 | Temporal.PlainDate.from(sone).withCalendar(calendar), 67 | Temporal.PlainDate.from(stwo).withCalendar(calendar) 68 | ].sort(Temporal.PlainDate.compare); 69 | buildSub(one, two, largestUnits); 70 | buildSub(one.with({ day: 25 }), two.with({ day: 5 }), largestUnits); 71 | buildSub(one.with({ day: 30 }), two.with({ day: 29 }), largestUnits); 72 | buildSub(one.with({ day: 30 }), two.with({ day: 5 }), largestUnits); 73 | } 74 | }); 75 | } 76 | function buildSub(one, two, largestUnits) { 77 | largestUnits.forEach((largestUnit) => { 78 | describe(`< ${one} : ${two} (${largestUnit})>`, () => { 79 | const dif = two.since(one, { largestUnit }); 80 | const overflow = 'reject'; 81 | if (largestUnit === 'months' || largestUnit === 'years') { 82 | // For months and years, `until` and `since` won't agree because the 83 | // starting point is always `this` and month-aware arithmetic behavior 84 | // varies based on the starting point. 85 | it(`(${two}).subtract(${dif}) => ${one}`, () => assert(two.subtract(dif).equals(one))); 86 | it(`(${two}).add(-${dif}) => ${one}`, () => assert(two.add(dif.negated()).equals(one))); 87 | const difUntil = one.until(two, { largestUnit }); 88 | it(`(${one}).subtract(-${difUntil}) => ${two}`, () => assert(one.subtract(difUntil.negated()).equals(two))); 89 | it(`(${one}).add(${difUntil}) => ${two}`, () => assert(one.add(difUntil).equals(two))); 90 | } else { 91 | it('until() and since() agree', () => equal(`${dif}`, `${one.until(two, { largestUnit })}`)); 92 | it(`(${one}).add(${dif}) => ${two}`, () => assert(one.add(dif, { overflow }).equals(two))); 93 | it(`(${two}).subtract(${dif}) => ${one}`, () => assert(two.subtract(dif, { overflow }).equals(one))); 94 | it(`(${one}).subtract(-${dif}) => ${two}`, () => assert(one.subtract(dif.negated(), { overflow }).equals(two))); 95 | it(`(${two}).add(-${dif}) => ${one}`, () => assert(two.add(dif.negated(), { overflow }).equals(one))); 96 | } 97 | }); 98 | }); 99 | } 100 | 101 | import { normalize } from 'path'; 102 | if (normalize(import.meta.url.slice(8)) === normalize(process.argv[1])) { 103 | report(reporter).then((failed) => process.exit(failed ? 1 : 0)); 104 | } 105 | -------------------------------------------------------------------------------- /test/exhaust.mjs: -------------------------------------------------------------------------------- 1 | import * as Temporal from '@js-temporal/polyfill'; 2 | 3 | const all = process.argv[2] === 'all'; 4 | const start = new Temporal.PlainDate(1999, 1, 1); 5 | const end = new Temporal.PlainDate(2009, 12, 31); 6 | 7 | console.log('Tap version 13'); 8 | console.log('1..N'); 9 | let idx = 0; 10 | let cnt = 0; 11 | let fail = 0; 12 | const sts = Temporal.Now.instant(); 13 | for (let one = start; Temporal.PlainDate.compare(one, end); one = one.add('P1D')) { 14 | for (let two = one; Temporal.PlainDate.compare(two, end); two = two.add('P1D')) { 15 | const ok = test(one, two); 16 | cnt++; 17 | if (!(cnt % 100_000)) process.stderr.write('*'); 18 | if (ok) { 19 | if (all) { 20 | idx++; 21 | console.log(`${ok ? 'ok' : 'not ok'} ${idx} < ${one} : ${two} >`); 22 | } 23 | } else { 24 | fail++; 25 | idx++; 26 | console.log(`${ok ? 'ok' : 'not ok'} ${idx} < ${one} : ${two} >`); 27 | } 28 | } 29 | } 30 | const ets = Temporal.Now.instant(); 31 | console.log(`1..${idx}`); 32 | console.log(`# Sucess: ${cnt - fail}/${cnt} (${ets.since(sts)})`); 33 | 34 | function test(one, two) { 35 | const dif = one.since(two); 36 | const add = `${one.add(dif)}` === `${two}`; 37 | const subtract = `${two.subtract(dif)}` === `${one}`; 38 | return add && subtract; 39 | } 40 | -------------------------------------------------------------------------------- /test/expected-failures-before-node16.txt: -------------------------------------------------------------------------------- 1 | # Before Node 16, dateStyle/timeStyle options didn't conflict with other options 2 | intl402/Temporal/Instant/prototype/toLocaleString/options-conflict.js 3 | intl402/Temporal/PlainDate/prototype/toLocaleString/options-conflict.js 4 | intl402/Temporal/PlainDateTime/prototype/toLocaleString/options-conflict.js 5 | intl402/Temporal/PlainMonthDay/prototype/toLocaleString/options-conflict.js 6 | intl402/Temporal/PlainTime/prototype/toLocaleString/options-conflict.js 7 | intl402/Temporal/PlainYearMonth/prototype/toLocaleString/options-conflict.js 8 | intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-conflict.js 9 | 10 | # Bug where -u-ca- in locale identifier is not honored 11 | built-ins/Temporal/PlainMonthDay/prototype/toLocaleString/return-string.js 12 | built-ins/Temporal/PlainYearMonth/prototype/toLocaleString/return-string.js 13 | intl402/DateTimeFormat/prototype/format/temporal-objects-not-overlapping-options.js 14 | intl402/DateTimeFormat/prototype/formatRange/temporal-objects-not-overlapping-options.js 15 | intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-objects-not-overlapping-options.js 16 | intl402/DateTimeFormat/prototype/formatToParts/temporal-objects-not-overlapping-options.js 17 | intl402/Temporal/PlainMonthDay/prototype/toLocaleString/default-does-not-include-year-time-and-time-zone-name.js 18 | intl402/Temporal/PlainMonthDay/prototype/toLocaleString/lone-options-accepted.js 19 | intl402/Temporal/PlainYearMonth/prototype/toLocaleString/default-does-not-include-day-time-and-time-zone-name.js 20 | intl402/Temporal/PlainYearMonth/prototype/toLocaleString/lone-options-accepted.js 21 | 22 | # Bug where lone dayPeriod pulls in default date options 23 | intl402/Temporal/PlainTime/prototype/toLocaleString/lone-options-accepted.js 24 | -------------------------------------------------------------------------------- /test/expected-failures-before-node18.txt: -------------------------------------------------------------------------------- 1 | # timeZoneName: 'shortOffset' is only available starting in Node 18 2 | intl402/DateTimeFormat/constructor-options-timeZoneName-valid.js 3 | 4 | # Intl.supportedValuesOf is only available starting in Node 18 5 | intl402/Temporal/ZonedDateTime/from/timezone-case-insensitive.js 6 | intl402/Temporal/ZonedDateTime/prototype/equals/canonical-not-equal.js 7 | intl402/Temporal/ZonedDateTime/prototype/equals/timezone-case-insensitive.js 8 | intl402/Temporal/ZonedDateTime/prototype/getTimeZoneTransition/transition-at-instant-boundaries.js 9 | intl402/Temporal/ZonedDateTime/supported-values-of.js 10 | intl402/Temporal/ZonedDateTime/timezone-case-insensitive.js 11 | intl402/DateTimeFormat/timezone-case-insensitive.js 12 | staging/sm/Temporal/Calendar/compare-to-datetimeformat.js 13 | staging/sm/Temporal/PlainDate/withCalendar.js 14 | 15 | intl402/Temporal/PlainDate/prototype/toLocaleString/calendar-mismatch.js 16 | intl402/Temporal/PlainDateTime/prototype/toLocaleString/calendar-mismatch.js 17 | intl402/Temporal/PlainMonthDay/prototype/toLocaleString/calendar-mismatch.js 18 | intl402/Temporal/PlainYearMonth/prototype/toLocaleString/calendar-mismatch.js 19 | intl402/Temporal/ZonedDateTime/prototype/toLocaleString/calendar-mismatch.js 20 | 21 | # America/Ciudad_Juarez added 22 | staging/sm/Temporal/ZonedDateTime/zones-and-links.js 23 | -------------------------------------------------------------------------------- /test/expected-failures-before-node20.txt: -------------------------------------------------------------------------------- 1 | # Bug prior to Node 20 2 | intl402/DateTimeFormat/prototype/formatRange/date-x-greater-than-y-not-throws.js 3 | intl402/DateTimeFormat/prototype/formatRangeToParts/date-x-greater-than-y-not-throws.js 4 | 5 | # Link Asia/Choibalsan - Asia/Ulaanbaatar 6 | staging/sm/Temporal/ZonedDateTime/zones-and-links.js 7 | -------------------------------------------------------------------------------- /test/expected-failures-before-node22.txt: -------------------------------------------------------------------------------- 1 | # Fails until CLDR 46 (released 2024-10-24) makes its way into a Node.js release 2 | staging/Intl402/Temporal/old/non-iso-calendars-iso8601.js 3 | 4 | # These rely on Intl.DateTimeFormat supporting offset time zones. 5 | intl402/DateTimeFormat/prototype/format/offset-timezone-gmt-same.js 6 | intl402/DateTimeFormat/prototype/formatToParts/offset-timezone-correct.js 7 | intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-basic.js 8 | intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-change.js 9 | -------------------------------------------------------------------------------- /test/expected-failures-before-node23.txt: -------------------------------------------------------------------------------- 1 | # Fails until Intl.DurationFormat available in Node.js release 2 | intl402/DurationFormat/prototype/format/taint-temporal-duration-prototype.js 3 | intl402/DurationFormat/prototype/format/temporal-duration-object-arg.js 4 | intl402/DurationFormat/prototype/format/temporal-duration-string-arg.js 5 | intl402/DurationFormat/prototype/formatToParts/taint-temporal-duration-prototype.js 6 | intl402/DurationFormat/prototype/formatToParts/temporal-duration-object-arg.js 7 | intl402/DurationFormat/prototype/formatToParts/temporal-duration-string-arg.js 8 | intl402/Temporal/Duration/prototype/toLocaleString/returns-same-results-as-DurationFormat.js 9 | 10 | # A regression in V8 (https://issues.chromium.org/issues/40893567) caused 11 | # these tests to fail before Node 23. 12 | intl402/DateTimeFormat/prototype/format/timedatestyle-en.js 13 | -------------------------------------------------------------------------------- /test/expected-failures-cldr42.txt: -------------------------------------------------------------------------------- 1 | # Failures in this file are expected to fail for all Test262 tests. To record 2 | # expected test failures for the transpiled or optimized builds of the polyfill, 3 | # see expected-failures-es5.txt and expected-failures-opt.txt respectively. 4 | 5 | # A regression in V8 (https://issues.chromium.org/issues/40893567) caused 6 | # these tests to fail. We don't know when a fix is expected, but when it's fixed 7 | # then these tests should start passing again. 8 | staging/Intl402/Temporal/old/date-time-format.js 9 | staging/Intl402/Temporal/old/datetime-toLocaleString.js 10 | staging/Intl402/Temporal/old/instant-toLocaleString.js 11 | staging/Intl402/Temporal/old/time-toLocaleString.js 12 | intl402/DateTimeFormat/prototype/format/temporal-objects-resolved-time-zone.js 13 | -------------------------------------------------------------------------------- /test/expected-failures-es5.txt: -------------------------------------------------------------------------------- 1 | # Failures in this file only apply to the optimized and transpiled polyfill 2 | # sources. 3 | 4 | built-ins/Date/prototype/toTemporalInstant/not-a-constructor.js 5 | built-ins/Temporal/Duration/compare/builtin.js 6 | built-ins/Temporal/Duration/compare/not-a-constructor.js 7 | built-ins/Temporal/Duration/from/builtin.js 8 | built-ins/Temporal/Duration/from/not-a-constructor.js 9 | built-ins/Temporal/Instant/compare/builtin.js 10 | built-ins/Temporal/Instant/compare/not-a-constructor.js 11 | built-ins/Temporal/Instant/from/builtin.js 12 | built-ins/Temporal/Instant/from/not-a-constructor.js 13 | built-ins/Temporal/Instant/fromEpochMilliseconds/builtin.js 14 | built-ins/Temporal/Instant/fromEpochMilliseconds/not-a-constructor.js 15 | built-ins/Temporal/Instant/fromEpochNanoseconds/builtin.js 16 | built-ins/Temporal/Instant/fromEpochNanoseconds/not-a-constructor.js 17 | built-ins/Temporal/Now/instant/not-a-constructor.js 18 | built-ins/Temporal/Now/plainDateTimeISO/not-a-constructor.js 19 | built-ins/Temporal/Now/zonedDateTimeISO/not-a-constructor.js 20 | built-ins/Temporal/PlainDate/compare/builtin.js 21 | built-ins/Temporal/PlainDate/compare/not-a-constructor.js 22 | built-ins/Temporal/PlainDate/from/builtin.js 23 | built-ins/Temporal/PlainDate/from/not-a-constructor.js 24 | built-ins/Temporal/PlainDateTime/compare/builtin.js 25 | built-ins/Temporal/PlainDateTime/compare/not-a-constructor.js 26 | built-ins/Temporal/PlainDateTime/from/builtin.js 27 | built-ins/Temporal/PlainDateTime/from/not-a-constructor.js 28 | built-ins/Temporal/PlainMonthDay/from/builtin.js 29 | built-ins/Temporal/PlainMonthDay/from/not-a-constructor.js 30 | built-ins/Temporal/PlainTime/compare/builtin.js 31 | built-ins/Temporal/PlainTime/compare/not-a-constructor.js 32 | built-ins/Temporal/PlainTime/from/builtin.js 33 | built-ins/Temporal/PlainTime/from/not-a-constructor.js 34 | built-ins/Temporal/PlainYearMonth/compare/builtin.js 35 | built-ins/Temporal/PlainYearMonth/compare/not-a-constructor.js 36 | built-ins/Temporal/PlainYearMonth/from/builtin.js 37 | built-ins/Temporal/PlainYearMonth/from/not-a-constructor.js 38 | built-ins/Temporal/TimeZone/from/builtin.js 39 | built-ins/Temporal/TimeZone/from/not-a-constructor.js 40 | built-ins/Temporal/ZonedDateTime/compare/builtin.js 41 | built-ins/Temporal/ZonedDateTime/compare/not-a-constructor.js 42 | built-ins/Temporal/ZonedDateTime/from/builtin.js 43 | built-ins/Temporal/ZonedDateTime/from/not-a-constructor.js 44 | built-ins/Temporal/Duration/prototype/abs/builtin.js 45 | built-ins/Temporal/Duration/prototype/abs/not-a-constructor.js 46 | built-ins/Temporal/Duration/prototype/add/builtin.js 47 | built-ins/Temporal/Duration/prototype/add/not-a-constructor.js 48 | built-ins/Temporal/Duration/prototype/negated/builtin.js 49 | built-ins/Temporal/Duration/prototype/negated/not-a-constructor.js 50 | built-ins/Temporal/Duration/prototype/round/builtin.js 51 | built-ins/Temporal/Duration/prototype/round/not-a-constructor.js 52 | built-ins/Temporal/Duration/prototype/subtract/builtin.js 53 | built-ins/Temporal/Duration/prototype/subtract/not-a-constructor.js 54 | built-ins/Temporal/Duration/prototype/toJSON/builtin.js 55 | built-ins/Temporal/Duration/prototype/toJSON/not-a-constructor.js 56 | built-ins/Temporal/Duration/prototype/toLocaleString/builtin.js 57 | built-ins/Temporal/Duration/prototype/toLocaleString/not-a-constructor.js 58 | built-ins/Temporal/Duration/prototype/toString/builtin.js 59 | built-ins/Temporal/Duration/prototype/toString/not-a-constructor.js 60 | built-ins/Temporal/Duration/prototype/total/builtin.js 61 | built-ins/Temporal/Duration/prototype/total/not-a-constructor.js 62 | built-ins/Temporal/Duration/prototype/valueOf/builtin.js 63 | built-ins/Temporal/Duration/prototype/valueOf/not-a-constructor.js 64 | built-ins/Temporal/Duration/prototype/with/builtin.js 65 | built-ins/Temporal/Duration/prototype/with/name.js 66 | built-ins/Temporal/Duration/prototype/with/not-a-constructor.js 67 | built-ins/Temporal/Instant/prototype/add/builtin.js 68 | built-ins/Temporal/Instant/prototype/add/not-a-constructor.js 69 | built-ins/Temporal/Instant/prototype/equals/builtin.js 70 | built-ins/Temporal/Instant/prototype/equals/not-a-constructor.js 71 | built-ins/Temporal/Instant/prototype/round/builtin.js 72 | built-ins/Temporal/Instant/prototype/round/not-a-constructor.js 73 | built-ins/Temporal/Instant/prototype/since/builtin.js 74 | built-ins/Temporal/Instant/prototype/since/not-a-constructor.js 75 | built-ins/Temporal/Instant/prototype/subtract/builtin.js 76 | built-ins/Temporal/Instant/prototype/subtract/not-a-constructor.js 77 | built-ins/Temporal/Instant/prototype/toJSON/builtin.js 78 | built-ins/Temporal/Instant/prototype/toJSON/not-a-constructor.js 79 | built-ins/Temporal/Instant/prototype/toLocaleString/builtin.js 80 | built-ins/Temporal/Instant/prototype/toLocaleString/not-a-constructor.js 81 | built-ins/Temporal/Instant/prototype/toString/builtin.js 82 | built-ins/Temporal/Instant/prototype/toString/not-a-constructor.js 83 | built-ins/Temporal/Instant/prototype/toZonedDateTimeISO/builtin.js 84 | built-ins/Temporal/Instant/prototype/toZonedDateTimeISO/not-a-constructor.js 85 | built-ins/Temporal/Instant/prototype/until/builtin.js 86 | built-ins/Temporal/Instant/prototype/until/not-a-constructor.js 87 | built-ins/Temporal/Instant/prototype/valueOf/builtin.js 88 | built-ins/Temporal/Instant/prototype/valueOf/not-a-constructor.js 89 | built-ins/Temporal/PlainDate/prototype/add/builtin.js 90 | built-ins/Temporal/PlainDate/prototype/add/not-a-constructor.js 91 | built-ins/Temporal/PlainDate/prototype/equals/builtin.js 92 | built-ins/Temporal/PlainDate/prototype/equals/not-a-constructor.js 93 | built-ins/Temporal/PlainDate/prototype/getISOFields/builtin.js 94 | built-ins/Temporal/PlainDate/prototype/getISOFields/not-a-constructor.js 95 | built-ins/Temporal/PlainDate/prototype/since/builtin.js 96 | built-ins/Temporal/PlainDate/prototype/since/not-a-constructor.js 97 | built-ins/Temporal/PlainDate/prototype/subtract/builtin.js 98 | built-ins/Temporal/PlainDate/prototype/subtract/not-a-constructor.js 99 | built-ins/Temporal/PlainDate/prototype/toJSON/builtin.js 100 | built-ins/Temporal/PlainDate/prototype/toJSON/not-a-constructor.js 101 | built-ins/Temporal/PlainDate/prototype/toLocaleString/builtin.js 102 | built-ins/Temporal/PlainDate/prototype/toLocaleString/not-a-constructor.js 103 | built-ins/Temporal/PlainDate/prototype/toPlainDateTime/builtin.js 104 | built-ins/Temporal/PlainDate/prototype/toPlainDateTime/not-a-constructor.js 105 | built-ins/Temporal/PlainDate/prototype/toPlainMonthDay/builtin.js 106 | built-ins/Temporal/PlainDate/prototype/toPlainMonthDay/not-a-constructor.js 107 | built-ins/Temporal/PlainDate/prototype/toPlainYearMonth/builtin.js 108 | built-ins/Temporal/PlainDate/prototype/toPlainYearMonth/not-a-constructor.js 109 | built-ins/Temporal/PlainDate/prototype/toString/builtin.js 110 | built-ins/Temporal/PlainDate/prototype/toString/not-a-constructor.js 111 | built-ins/Temporal/PlainDate/prototype/toZonedDateTime/builtin.js 112 | built-ins/Temporal/PlainDate/prototype/toZonedDateTime/not-a-constructor.js 113 | built-ins/Temporal/PlainDate/prototype/until/builtin.js 114 | built-ins/Temporal/PlainDate/prototype/until/not-a-constructor.js 115 | built-ins/Temporal/PlainDate/prototype/valueOf/builtin.js 116 | built-ins/Temporal/PlainDate/prototype/valueOf/not-a-constructor.js 117 | built-ins/Temporal/PlainDate/prototype/with/builtin.js 118 | built-ins/Temporal/PlainDate/prototype/with/name.js 119 | built-ins/Temporal/PlainDate/prototype/with/not-a-constructor.js 120 | built-ins/Temporal/PlainDate/prototype/withCalendar/builtin.js 121 | built-ins/Temporal/PlainDate/prototype/withCalendar/not-a-constructor.js 122 | built-ins/Temporal/PlainDateTime/prototype/add/builtin.js 123 | built-ins/Temporal/PlainDateTime/prototype/add/not-a-constructor.js 124 | built-ins/Temporal/PlainDateTime/prototype/equals/builtin.js 125 | built-ins/Temporal/PlainDateTime/prototype/equals/not-a-constructor.js 126 | built-ins/Temporal/PlainDateTime/prototype/getISOFields/builtin.js 127 | built-ins/Temporal/PlainDateTime/prototype/getISOFields/not-a-constructor.js 128 | built-ins/Temporal/PlainDateTime/prototype/round/builtin.js 129 | built-ins/Temporal/PlainDateTime/prototype/round/not-a-constructor.js 130 | built-ins/Temporal/PlainDateTime/prototype/since/builtin.js 131 | built-ins/Temporal/PlainDateTime/prototype/since/not-a-constructor.js 132 | built-ins/Temporal/PlainDateTime/prototype/subtract/builtin.js 133 | built-ins/Temporal/PlainDateTime/prototype/subtract/not-a-constructor.js 134 | built-ins/Temporal/PlainDateTime/prototype/toJSON/builtin.js 135 | built-ins/Temporal/PlainDateTime/prototype/toJSON/not-a-constructor.js 136 | built-ins/Temporal/PlainDateTime/prototype/toLocaleString/builtin.js 137 | built-ins/Temporal/PlainDateTime/prototype/toLocaleString/not-a-constructor.js 138 | built-ins/Temporal/PlainDateTime/prototype/toPlainDate/builtin.js 139 | built-ins/Temporal/PlainDateTime/prototype/toPlainDate/not-a-constructor.js 140 | built-ins/Temporal/PlainDateTime/prototype/toPlainTime/builtin.js 141 | built-ins/Temporal/PlainDateTime/prototype/toPlainTime/not-a-constructor.js 142 | built-ins/Temporal/PlainDateTime/prototype/toString/builtin.js 143 | built-ins/Temporal/PlainDateTime/prototype/toString/not-a-constructor.js 144 | built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/builtin.js 145 | built-ins/Temporal/PlainDateTime/prototype/toZonedDateTime/not-a-constructor.js 146 | built-ins/Temporal/PlainDateTime/prototype/until/builtin.js 147 | built-ins/Temporal/PlainDateTime/prototype/until/not-a-constructor.js 148 | built-ins/Temporal/PlainDateTime/prototype/valueOf/builtin.js 149 | built-ins/Temporal/PlainDateTime/prototype/valueOf/not-a-constructor.js 150 | built-ins/Temporal/PlainDateTime/prototype/with/builtin.js 151 | built-ins/Temporal/PlainDateTime/prototype/with/name.js 152 | built-ins/Temporal/PlainDateTime/prototype/with/not-a-constructor.js 153 | built-ins/Temporal/PlainDateTime/prototype/withCalendar/builtin.js 154 | built-ins/Temporal/PlainDateTime/prototype/withCalendar/not-a-constructor.js 155 | built-ins/Temporal/PlainDateTime/prototype/withPlainTime/builtin.js 156 | built-ins/Temporal/PlainDateTime/prototype/withPlainTime/not-a-constructor.js 157 | built-ins/Temporal/PlainMonthDay/prototype/equals/builtin.js 158 | built-ins/Temporal/PlainMonthDay/prototype/equals/not-a-constructor.js 159 | built-ins/Temporal/PlainMonthDay/prototype/getISOFields/builtin.js 160 | built-ins/Temporal/PlainMonthDay/prototype/getISOFields/not-a-constructor.js 161 | built-ins/Temporal/PlainMonthDay/prototype/toJSON/builtin.js 162 | built-ins/Temporal/PlainMonthDay/prototype/toJSON/not-a-constructor.js 163 | built-ins/Temporal/PlainMonthDay/prototype/toLocaleString/builtin.js 164 | built-ins/Temporal/PlainMonthDay/prototype/toLocaleString/not-a-constructor.js 165 | built-ins/Temporal/PlainMonthDay/prototype/toPlainDate/builtin.js 166 | built-ins/Temporal/PlainMonthDay/prototype/toPlainDate/not-a-constructor.js 167 | built-ins/Temporal/PlainMonthDay/prototype/toString/builtin.js 168 | built-ins/Temporal/PlainMonthDay/prototype/toString/not-a-constructor.js 169 | built-ins/Temporal/PlainMonthDay/prototype/valueOf/builtin.js 170 | built-ins/Temporal/PlainMonthDay/prototype/valueOf/not-a-constructor.js 171 | built-ins/Temporal/PlainMonthDay/prototype/with/builtin.js 172 | built-ins/Temporal/PlainMonthDay/prototype/with/name.js 173 | built-ins/Temporal/PlainMonthDay/prototype/with/not-a-constructor.js 174 | built-ins/Temporal/PlainTime/prototype/add/builtin.js 175 | built-ins/Temporal/PlainTime/prototype/add/not-a-constructor.js 176 | built-ins/Temporal/PlainTime/prototype/equals/builtin.js 177 | built-ins/Temporal/PlainTime/prototype/equals/not-a-constructor.js 178 | built-ins/Temporal/PlainTime/prototype/getISOFields/builtin.js 179 | built-ins/Temporal/PlainTime/prototype/getISOFields/not-a-constructor.js 180 | built-ins/Temporal/PlainTime/prototype/round/builtin.js 181 | built-ins/Temporal/PlainTime/prototype/round/not-a-constructor.js 182 | built-ins/Temporal/PlainTime/prototype/since/builtin.js 183 | built-ins/Temporal/PlainTime/prototype/since/not-a-constructor.js 184 | built-ins/Temporal/PlainTime/prototype/subtract/builtin.js 185 | built-ins/Temporal/PlainTime/prototype/subtract/not-a-constructor.js 186 | built-ins/Temporal/PlainTime/prototype/toJSON/builtin.js 187 | built-ins/Temporal/PlainTime/prototype/toJSON/not-a-constructor.js 188 | built-ins/Temporal/PlainTime/prototype/toLocaleString/builtin.js 189 | built-ins/Temporal/PlainTime/prototype/toLocaleString/not-a-constructor.js 190 | built-ins/Temporal/PlainTime/prototype/toString/builtin.js 191 | built-ins/Temporal/PlainTime/prototype/toString/not-a-constructor.js 192 | built-ins/Temporal/PlainTime/prototype/until/builtin.js 193 | built-ins/Temporal/PlainTime/prototype/until/not-a-constructor.js 194 | built-ins/Temporal/PlainTime/prototype/valueOf/builtin.js 195 | built-ins/Temporal/PlainTime/prototype/valueOf/not-a-constructor.js 196 | built-ins/Temporal/PlainTime/prototype/with/builtin.js 197 | built-ins/Temporal/PlainTime/prototype/with/name.js 198 | built-ins/Temporal/PlainTime/prototype/with/not-a-constructor.js 199 | built-ins/Temporal/PlainYearMonth/prototype/add/builtin.js 200 | built-ins/Temporal/PlainYearMonth/prototype/add/not-a-constructor.js 201 | built-ins/Temporal/PlainYearMonth/prototype/equals/builtin.js 202 | built-ins/Temporal/PlainYearMonth/prototype/equals/not-a-constructor.js 203 | built-ins/Temporal/PlainYearMonth/prototype/getISOFields/builtin.js 204 | built-ins/Temporal/PlainYearMonth/prototype/getISOFields/not-a-constructor.js 205 | built-ins/Temporal/PlainYearMonth/prototype/since/builtin.js 206 | built-ins/Temporal/PlainYearMonth/prototype/since/not-a-constructor.js 207 | built-ins/Temporal/PlainYearMonth/prototype/subtract/builtin.js 208 | built-ins/Temporal/PlainYearMonth/prototype/subtract/not-a-constructor.js 209 | built-ins/Temporal/PlainYearMonth/prototype/toJSON/builtin.js 210 | built-ins/Temporal/PlainYearMonth/prototype/toJSON/not-a-constructor.js 211 | built-ins/Temporal/PlainYearMonth/prototype/toLocaleString/builtin.js 212 | built-ins/Temporal/PlainYearMonth/prototype/toLocaleString/not-a-constructor.js 213 | built-ins/Temporal/PlainYearMonth/prototype/toPlainDate/builtin.js 214 | built-ins/Temporal/PlainYearMonth/prototype/toPlainDate/not-a-constructor.js 215 | built-ins/Temporal/PlainYearMonth/prototype/toString/builtin.js 216 | built-ins/Temporal/PlainYearMonth/prototype/toString/not-a-constructor.js 217 | built-ins/Temporal/PlainYearMonth/prototype/until/builtin.js 218 | built-ins/Temporal/PlainYearMonth/prototype/until/not-a-constructor.js 219 | built-ins/Temporal/PlainYearMonth/prototype/valueOf/builtin.js 220 | built-ins/Temporal/PlainYearMonth/prototype/valueOf/not-a-constructor.js 221 | built-ins/Temporal/PlainYearMonth/prototype/with/builtin.js 222 | built-ins/Temporal/PlainYearMonth/prototype/with/name.js 223 | built-ins/Temporal/PlainYearMonth/prototype/with/not-a-constructor.js 224 | built-ins/Temporal/ZonedDateTime/prototype/add/builtin.js 225 | built-ins/Temporal/ZonedDateTime/prototype/add/not-a-constructor.js 226 | built-ins/Temporal/ZonedDateTime/prototype/equals/builtin.js 227 | built-ins/Temporal/ZonedDateTime/prototype/equals/not-a-constructor.js 228 | built-ins/Temporal/ZonedDateTime/prototype/getISOFields/builtin.js 229 | built-ins/Temporal/ZonedDateTime/prototype/getISOFields/not-a-constructor.js 230 | built-ins/Temporal/ZonedDateTime/prototype/getTimeZoneTransition/builtin.js 231 | built-ins/Temporal/ZonedDateTime/prototype/getTimeZoneTransition/not-a-constructor.js 232 | built-ins/Temporal/ZonedDateTime/prototype/round/builtin.js 233 | built-ins/Temporal/ZonedDateTime/prototype/round/not-a-constructor.js 234 | built-ins/Temporal/ZonedDateTime/prototype/since/builtin.js 235 | built-ins/Temporal/ZonedDateTime/prototype/since/not-a-constructor.js 236 | built-ins/Temporal/ZonedDateTime/prototype/startOfDay/builtin.js 237 | built-ins/Temporal/ZonedDateTime/prototype/startOfDay/not-a-constructor.js 238 | built-ins/Temporal/ZonedDateTime/prototype/subtract/builtin.js 239 | built-ins/Temporal/ZonedDateTime/prototype/subtract/not-a-constructor.js 240 | built-ins/Temporal/ZonedDateTime/prototype/toInstant/builtin.js 241 | built-ins/Temporal/ZonedDateTime/prototype/toInstant/not-a-constructor.js 242 | built-ins/Temporal/ZonedDateTime/prototype/toJSON/builtin.js 243 | built-ins/Temporal/ZonedDateTime/prototype/toJSON/not-a-constructor.js 244 | built-ins/Temporal/ZonedDateTime/prototype/toLocaleString/builtin.js 245 | built-ins/Temporal/ZonedDateTime/prototype/toLocaleString/not-a-constructor.js 246 | built-ins/Temporal/ZonedDateTime/prototype/toPlainDate/builtin.js 247 | built-ins/Temporal/ZonedDateTime/prototype/toPlainDate/not-a-constructor.js 248 | built-ins/Temporal/ZonedDateTime/prototype/toPlainDateTime/builtin.js 249 | built-ins/Temporal/ZonedDateTime/prototype/toPlainDateTime/not-a-constructor.js 250 | built-ins/Temporal/ZonedDateTime/prototype/toPlainTime/builtin.js 251 | built-ins/Temporal/ZonedDateTime/prototype/toPlainTime/not-a-constructor.js 252 | built-ins/Temporal/ZonedDateTime/prototype/toString/builtin.js 253 | built-ins/Temporal/ZonedDateTime/prototype/toString/not-a-constructor.js 254 | built-ins/Temporal/ZonedDateTime/prototype/until/builtin.js 255 | built-ins/Temporal/ZonedDateTime/prototype/until/not-a-constructor.js 256 | built-ins/Temporal/ZonedDateTime/prototype/valueOf/builtin.js 257 | built-ins/Temporal/ZonedDateTime/prototype/valueOf/not-a-constructor.js 258 | built-ins/Temporal/ZonedDateTime/prototype/with/builtin.js 259 | built-ins/Temporal/ZonedDateTime/prototype/with/not-a-constructor.js 260 | built-ins/Temporal/ZonedDateTime/prototype/with/name.js 261 | built-ins/Temporal/ZonedDateTime/prototype/withCalendar/builtin.js 262 | built-ins/Temporal/ZonedDateTime/prototype/withCalendar/not-a-constructor.js 263 | built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/builtin.js 264 | built-ins/Temporal/ZonedDateTime/prototype/withPlainTime/not-a-constructor.js 265 | built-ins/Temporal/ZonedDateTime/prototype/withTimeZone/builtin.js 266 | built-ins/Temporal/ZonedDateTime/prototype/withTimeZone/not-a-constructor.js 267 | built-ins/Temporal/Now/timeZoneId/not-a-constructor.js 268 | intl402/DateTimeFormat/prototype/builtin.js 269 | intl402/DateTimeFormat/prototype/format/builtin.js 270 | intl402/DateTimeFormat/prototype/format/format-function-builtin.js 271 | intl402/DateTimeFormat/prototype/formatRange/builtin.js 272 | intl402/DateTimeFormat/prototype/formatRangeToParts/builtin.js 273 | intl402/DateTimeFormat/prototype/resolvedOptions/builtin.js 274 | intl402/DateTimeFormat/supportedLocalesOf/builtin.js 275 | -------------------------------------------------------------------------------- /test/expected-failures-opt.txt: -------------------------------------------------------------------------------- 1 | # Failures in this file only apply to the optimized, but not transpiled, 2 | # polyfill sources. 3 | 4 | -------------------------------------------------------------------------------- /test/expected-failures-todo-migrated-code.txt: -------------------------------------------------------------------------------- 1 | # Failures in this file will be fixed after migrating the latest polyfill changes 2 | # from proposal-temporal. 3 | -------------------------------------------------------------------------------- /test/expected-failures.txt: -------------------------------------------------------------------------------- 1 | # Failures in this file are expected to fail for all Test262 tests. To record 2 | # expected test failures for the transpiled or optimized builds of the polyfill, 3 | # see expected-failures-es5.txt and expected-failures-opt.txt respectively. 4 | 5 | # Upstream commit 076f2871 introduces a second ToString call on a calendar when 6 | # canonicalizing the timezone name. 7 | staging/Intl402/Temporal/old/date-time-format.js 8 | 9 | # Possibly faulty calculations for year 0 in Hebrew calendar in ICU4C 10 | # https://unicode-org.atlassian.net/browse/ICU-23007 11 | staging/sm/Temporal/PlainDate/from-constrain-hebrew.js 12 | 13 | # Test is incorrect, no reason why this date should not exist; seems to be 14 | # working around an ICU4X bug 15 | staging/sm/Temporal/PlainDate/from-islamic-umalqura.js 16 | 17 | # Faulty leap month calculations in Chinese calendar in ICU4C 18 | # https://unicode-org.atlassian.net/browse/ICU-22230 19 | staging/sm/Temporal/PlainMonthDay/from-chinese-leap-month-uncommon.js 20 | 21 | # https://github.com/tc39/ecma402/issues/534 22 | staging/Intl402/Temporal/old/non-iso-calendars-ethiopic.js 23 | -------------------------------------------------------------------------------- /test/math.mjs: -------------------------------------------------------------------------------- 1 | import Demitasse from '@pipobscure/demitasse'; 2 | const { describe, it, report } = Demitasse; 3 | 4 | import Pretty from '@pipobscure/demitasse-pretty'; 5 | const { reporter } = Pretty; 6 | 7 | import { strict as assert } from 'assert'; 8 | const { deepEqual, equal } = assert; 9 | 10 | import { TruncatingDivModByPowerOf10 as div, FMAPowerOf10 as fma } from '../lib/math'; 11 | 12 | describe('Math', () => { 13 | describe('TruncatingDivModByPowerOf10', () => { 14 | it('12345/10**0 = 12345, 0', () => deepEqual(div(12345, 0), { div: 12345, mod: 0 })); 15 | it('12345/10**1 = 1234, 5', () => deepEqual(div(12345, 1), { div: 1234, mod: 5 })); 16 | it('12345/10**2 = 123, 45', () => deepEqual(div(12345, 2), { div: 123, mod: 45 })); 17 | it('12345/10**3 = 12, 345', () => deepEqual(div(12345, 3), { div: 12, mod: 345 })); 18 | it('12345/10**4 = 1, 2345', () => deepEqual(div(12345, 4), { div: 1, mod: 2345 })); 19 | it('12345/10**5 = 0, 12345', () => deepEqual(div(12345, 5), { div: 0, mod: 12345 })); 20 | it('12345/10**6 = 0, 12345', () => deepEqual(div(12345, 6), { div: 0, mod: 12345 })); 21 | 22 | it('-12345/10**0 = -12345, -0', () => deepEqual(div(-12345, 0), { div: -12345, mod: -0 })); 23 | it('-12345/10**1 = -1234, -5', () => deepEqual(div(-12345, 1), { div: -1234, mod: -5 })); 24 | it('-12345/10**2 = -123, -45', () => deepEqual(div(-12345, 2), { div: -123, mod: -45 })); 25 | it('-12345/10**3 = -12, -345', () => deepEqual(div(-12345, 3), { div: -12, mod: -345 })); 26 | it('-12345/10**4 = -1, -2345', () => deepEqual(div(-12345, 4), { div: -1, mod: -2345 })); 27 | it('-12345/10**5 = -0, -12345', () => deepEqual(div(-12345, 5), { div: -0, mod: -12345 })); 28 | it('-12345/10**6 = -0, -12345', () => deepEqual(div(-12345, 6), { div: -0, mod: -12345 })); 29 | 30 | it('0/10**27 = 0, 0', () => deepEqual(div(0, 27), { div: 0, mod: 0 })); 31 | it('-0/10**27 = -0, -0', () => deepEqual(div(-0, 27), { div: -0, mod: -0 })); 32 | 33 | it('1001/10**3 = 1, 1', () => deepEqual(div(1001, 3), { div: 1, mod: 1 })); 34 | it('-1001/10**3 = -1, -1', () => deepEqual(div(-1001, 3), { div: -1, mod: -1 })); 35 | 36 | it('4019125567429664768/10**3 = 4019125567429664, 768', () => 37 | deepEqual(div(4019125567429664768, 3), { div: 4019125567429664, mod: 768 })); 38 | it('-4019125567429664768/10**3 = -4019125567429664, -768', () => 39 | deepEqual(div(-4019125567429664768, 3), { div: -4019125567429664, mod: -768 })); 40 | it('3294477463410151260160/10**6 = 3294477463410151, 260160', () => 41 | deepEqual(div(3294477463410151260160, 6), { div: 3294477463410151, mod: 260160 })); 42 | it('-3294477463410151260160/10**6 = -3294477463410151, -260160', () => 43 | deepEqual(div(-3294477463410151260160, 6), { div: -3294477463410151, mod: -260160 })); 44 | it('7770017954545649059889152/10**9 = 7770017954545649, 59889152', () => 45 | deepEqual(div(7770017954545649059889152, 9), { div: 7770017954545649, mod: 59889152 })); 46 | it('-7770017954545649059889152/-10**9 = -7770017954545649, -59889152', () => 47 | deepEqual(div(-7770017954545649059889152, 9), { div: -7770017954545649, mod: -59889152 })); 48 | 49 | // Largest/smallest representable float that will result in a safe quotient, 50 | // for each of the divisors 10**3, 10**6, 10**9 51 | it('9007199254740990976/10**3 = MAX_SAFE_INTEGER-1, 976', () => 52 | deepEqual(div(9007199254740990976, 3), { div: Number.MAX_SAFE_INTEGER - 1, mod: 976 })); 53 | it('-9007199254740990976/10**3 = -MAX_SAFE_INTEGER+1, -976', () => 54 | deepEqual(div(-9007199254740990976, 3), { div: -Number.MAX_SAFE_INTEGER + 1, mod: -976 })); 55 | it('9007199254740990951424/10**6 = MAX_SAFE_INTEGER-1, 951424', () => 56 | deepEqual(div(9007199254740990951424, 6), { div: Number.MAX_SAFE_INTEGER - 1, mod: 951424 })); 57 | it('-9007199254740990951424/10**6 = -MAX_SAFE_INTEGER+1, -951424', () => 58 | deepEqual(div(-9007199254740990951424, 6), { div: -Number.MAX_SAFE_INTEGER + 1, mod: -951424 })); 59 | it('9007199254740990926258176/10**9 = MAX_SAFE_INTEGER-1, 926258176', () => 60 | deepEqual(div(9007199254740990926258176, 9), { div: Number.MAX_SAFE_INTEGER - 1, mod: 926258176 })); 61 | it('-9007199254740990926258176/10**9 = -MAX_SAFE_INTEGER+1, -926258176', () => 62 | deepEqual(div(-9007199254740990926258176, 9), { div: -Number.MAX_SAFE_INTEGER + 1, mod: -926258176 })); 63 | }); 64 | 65 | describe('FMAPowerOf10', () => { 66 | it('0*10**0+0 = 0', () => equal(fma(0, 0, 0), 0)); 67 | it('-0*10**0-0 = -0', () => equal(fma(-0, 0, -0), -0)); 68 | it('1*10**0+0 = 1', () => equal(fma(1, 0, 0), 1)); 69 | it('-1*10**0+0 = -1', () => equal(fma(-1, 0, 0), -1)); 70 | it('0*10**50+1234 = 1234', () => equal(fma(0, 50, 1234), 1234)); 71 | it('-0*10**50-1234 = -1234', () => equal(fma(-0, 50, -1234), -1234)); 72 | it('1234*10**12+0', () => equal(fma(1234, 12, 0), 1234000000000000)); 73 | it('-1234*10**12-0', () => equal(fma(-1234, 12, -0), -1234000000000000)); 74 | 75 | it('2*10**2+45 = 245', () => equal(fma(2, 2, 45), 245)); 76 | it('2*10**3+45 = 2045', () => equal(fma(2, 3, 45), 2045)); 77 | it('2*10**4+45 = 20045', () => equal(fma(2, 4, 45), 20045)); 78 | it('2*10**5+45 = 200045', () => equal(fma(2, 5, 45), 200045)); 79 | it('2*10**6+45 = 2000045', () => equal(fma(2, 6, 45), 2000045)); 80 | 81 | it('-2*10**2-45 = -245', () => equal(fma(-2, 2, -45), -245)); 82 | it('-2*10**3-45 = -2045', () => equal(fma(-2, 3, -45), -2045)); 83 | it('-2*10**4-45 = -20045', () => equal(fma(-2, 4, -45), -20045)); 84 | it('-2*10**5-45 = -200045', () => equal(fma(-2, 5, -45), -200045)); 85 | it('-2*10**6-45 = -2000045', () => equal(fma(-2, 6, -45), -2000045)); 86 | 87 | it('8692288669465520*10**9+321414345 = 8692288669465520321414345, rounded to 8692288669465520839327744', () => 88 | equal(fma(8692288669465520, 9, 321414345), 8692288669465520839327744)); 89 | it('-8692288669465520*10**9-321414345 = -8692288669465520321414345, rounded to -8692288669465520839327744', () => 90 | equal(fma(-8692288669465520, 9, -321414345), -8692288669465520839327744)); 91 | 92 | it('MAX_SAFE_INTEGER*10**3+999 rounded to 9007199254740992000', () => 93 | equal(fma(Number.MAX_SAFE_INTEGER, 3, 999), 9007199254740992000)); 94 | it('-MAX_SAFE_INTEGER*10**3-999 rounded to -9007199254740992000', () => 95 | equal(fma(-Number.MAX_SAFE_INTEGER, 3, -999), -9007199254740992000)); 96 | it('MAX_SAFE_INTEGER*10**6+999999 rounded to 9007199254740992000000', () => 97 | equal(fma(Number.MAX_SAFE_INTEGER, 6, 999999), 9007199254740992000000)); 98 | it('-MAX_SAFE_INTEGER*10**6-999999 rounded to -9007199254740992000000', () => 99 | equal(fma(-Number.MAX_SAFE_INTEGER, 6, -999999), -9007199254740992000000)); 100 | it('MAX_SAFE_INTEGER*10**3+999 rounded to 9007199254740992000', () => 101 | equal(fma(Number.MAX_SAFE_INTEGER, 9, 999999999), 9007199254740992000000000)); 102 | it('-MAX_SAFE_INTEGER*10**3-999 rounded to -9007199254740992000', () => 103 | equal(fma(-Number.MAX_SAFE_INTEGER, 9, -999999999), -9007199254740992000000000)); 104 | }); 105 | }); 106 | 107 | import { normalize } from 'path'; 108 | if (normalize(import.meta.url.slice(8)) === normalize(process.argv[1])) { 109 | report(reporter).then((failed) => process.exit(failed ? 1 : 0)); 110 | } 111 | -------------------------------------------------------------------------------- /test/parseResults.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { stdin, stdout, exit, env } = process; 4 | import fs from 'fs'; 5 | 6 | const isProduction = env.NODE_ENV === 'production'; 7 | const isTranspiledBuild = !!env.TRANSPILE; 8 | 9 | const PREFIXES = [ 10 | ['FAIL', 'PASS'], 11 | ['EXPECTED FAIL', 'UNEXPECTED PASS'] 12 | ]; 13 | 14 | let testOutput = ''; 15 | const now = Date.now(); 16 | 17 | const expectedFailures = new Set(); 18 | function includeExpectedFailures(file) { 19 | const lines = fs.readFileSync(file, { encoding: 'utf-8' }); 20 | for (let line of lines.split('\n')) { 21 | line = line.trim(); 22 | if (!line) continue; 23 | if (line.startsWith('#')) continue; 24 | expectedFailures.add(line); 25 | } 26 | } 27 | 28 | includeExpectedFailures('../../test/expected-failures.txt'); 29 | if (isProduction) { 30 | includeExpectedFailures( 31 | isTranspiledBuild ? '../../test/expected-failures-es5.txt' : '../../test/expected-failures-opt.txt' 32 | ); 33 | } 34 | 35 | stdin.setEncoding('utf8'); 36 | 37 | stdout.write('Starting Test262 tests.\n'); 38 | 39 | let testCount = 0; 40 | let nextReportCount = 100; 41 | 42 | function reportStatus(length = testCount) { 43 | const secs = (Date.now() - now) / 1000; 44 | stdout.write(`${length} tests completed in ${secs.toFixed(1)} seconds. (${Math.round(length / secs)} tests/sec)\n`); 45 | } 46 | 47 | stdin.on('data', function (chunk) { 48 | testOutput += chunk; 49 | const matches = testOutput.split('"file":').length - 1; 50 | // We skipped execution of non-strict tests via a preprocessor, but the test 51 | // is treated as passed and shows up here, so we'll divide total by 2. 52 | testCount = Math.trunc(matches / 2); 53 | if (testCount >= nextReportCount || testOutput.endsWith(']\n')) { 54 | reportStatus(); 55 | nextReportCount += 100; 56 | } 57 | }); 58 | 59 | stdin.on('end', function () { 60 | stdout.write('\n'); 61 | // filter out the non-strict tests because they were skipped via a preprocessor 62 | const tests = JSON.parse(testOutput).filter((test) => test.scenario.includes('strict')); 63 | const failedTests = []; 64 | const unexpectedPasses = []; 65 | for (const test of tests) { 66 | const { result, scenario, file } = test; 67 | if (result.message === 'Debugger attached.\nWaiting for the debugger to disconnect...\n') { 68 | // work around https://github.com/nodejs/node/issues/34799 when running tests in the debugger 69 | result.message = ''; 70 | result.pass = true; 71 | } 72 | const expectedFailure = expectedFailures.has(file); 73 | const message = `${PREFIXES[+expectedFailure][+result.pass]} ${file} (${scenario})\n`; 74 | stdout.write(message); 75 | if (result.pass === expectedFailure) { 76 | (result.pass ? unexpectedPasses : failedTests).push({ ...test, message }); 77 | } 78 | } 79 | 80 | if (failedTests.length || unexpectedPasses.length) { 81 | if (failedTests.length) { 82 | stdout.write(`\n${failedTests.length} tests failed:\n`); 83 | for (const test of failedTests) { 84 | const { message, rawResult } = test; 85 | stdout.write(`\n${message}\n`); 86 | stdout.write(`${rawResult.stderr}\n`); 87 | stdout.write(`${rawResult.stdout}\n`); 88 | stdout.write(rawResult.message ? rawResult.message + '\n' : ''); 89 | } 90 | } 91 | if (unexpectedPasses.length) { 92 | stdout.write(`\n${unexpectedPasses.length} tests passed unexpectedly:\n`); 93 | for (const { message } of unexpectedPasses) { 94 | stdout.write(`${message}\n`); 95 | } 96 | } 97 | } else { 98 | stdout.write('All results as expected.\n'); 99 | } 100 | reportStatus(tests.length); 101 | stdout.write('\n\n'); 102 | exit(failedTests.length || unexpectedPasses.length ? 1 : 0); 103 | }); 104 | -------------------------------------------------------------------------------- /test/plainmonthday.mjs: -------------------------------------------------------------------------------- 1 | import Demitasse from '@pipobscure/demitasse'; 2 | const { describe, it, report } = Demitasse; 3 | 4 | import Pretty from '@pipobscure/demitasse-pretty'; 5 | const { reporter } = Pretty; 6 | 7 | import { strict as assert } from 'assert'; 8 | const { throws, equal, notEqual } = assert; 9 | 10 | import * as Temporal from '@js-temporal/polyfill'; 11 | const { PlainMonthDay } = Temporal; 12 | 13 | describe('MonthDay', () => { 14 | describe('Structure', () => { 15 | it('MonthDay is a Function', () => { 16 | equal(typeof PlainMonthDay, 'function'); 17 | }); 18 | it('MonthDay has a prototype', () => { 19 | assert(PlainMonthDay.prototype); 20 | equal(typeof PlainMonthDay.prototype, 'object'); 21 | }); 22 | describe('MonthDay.prototype', () => { 23 | it('MonthDay.prototype has monthCode', () => { 24 | assert('monthCode' in PlainMonthDay.prototype); 25 | }); 26 | it('MonthDay.prototype.equals is a Function', () => { 27 | equal(typeof PlainMonthDay.prototype.equals, 'function'); 28 | }); 29 | it('MonthDay.prototype.toString is a Function', () => { 30 | equal(typeof PlainMonthDay.prototype.toString, 'function'); 31 | }); 32 | it('MonthDay.prototype.getISOFields is a Function', () => { 33 | equal(typeof PlainMonthDay.prototype.getISOFields, 'function'); 34 | }); 35 | }); 36 | }); 37 | describe('Construction', () => { 38 | it('Leap day', () => equal(`${new PlainMonthDay(2, 29)}`, '02-29')); 39 | describe('.from()', () => { 40 | it('MonthDay.from(10-01) == 10-01', () => equal(`${PlainMonthDay.from('10-01')}`, '10-01')); 41 | it('Z not supported', () => { 42 | throws(() => PlainMonthDay.from('2019-10-01T09:00:00Z'), RangeError); 43 | }); 44 | it("MonthDay.from('11-18') == (11-18)", () => equal(`${PlainMonthDay.from('11-18')}`, '11-18')); 45 | it("MonthDay.from('1976-11-18') == (11-18)", () => equal(`${PlainMonthDay.from('1976-11-18')}`, '11-18')); 46 | it('MonthDay.from({ monthCode: "M11", day: 18 }) == 11-18', () => 47 | equal(`${PlainMonthDay.from({ monthCode: 'M11', day: 18 })}`, '11-18')); 48 | it('ignores year when determining the ISO reference year from month/day', () => { 49 | const one = PlainMonthDay.from({ year: 2019, month: 11, day: 18 }); 50 | const two = PlainMonthDay.from({ year: 1979, month: 11, day: 18 }); 51 | equal(one.getISOFields().isoYear, two.getISOFields().isoYear); 52 | }); 53 | it('ignores era/eraYear when determining the ISO reference year from month/day', () => { 54 | const one = PlainMonthDay.from({ era: 'ce', eraYear: 2019, month: 11, day: 18, calendar: 'gregory' }); 55 | const two = PlainMonthDay.from({ era: 'ce', eraYear: 1979, month: 11, day: 18, calendar: 'gregory' }); 56 | equal(one.getISOFields().isoYear, two.getISOFields().isoYear); 57 | }); 58 | it('ignores year when determining the ISO reference year from monthCode/day', () => { 59 | const one = PlainMonthDay.from({ year: 2019, monthCode: 'M11', day: 18 }); 60 | const two = PlainMonthDay.from({ year: 1979, monthCode: 'M11', day: 18 }); 61 | equal(one.getISOFields().isoYear, two.getISOFields().isoYear); 62 | }); 63 | it('ignores era/eraYear when determining the ISO reference year from monthCode/day', () => { 64 | const one = PlainMonthDay.from({ era: 'ce', eraYear: 2019, monthCode: 'M11', day: 18, calendar: 'gregory' }); 65 | const two = PlainMonthDay.from({ era: 'ce', eraYear: 1979, monthCode: 'M11', day: 18, calendar: 'gregory' }); 66 | equal(one.getISOFields().isoYear, two.getISOFields().isoYear); 67 | }); 68 | it('MonthDay.from(11-18) is not the same object', () => { 69 | const orig = new PlainMonthDay(11, 18); 70 | const actu = PlainMonthDay.from(orig); 71 | notEqual(actu, orig); 72 | }); 73 | it('ignores year when determining the ISO reference year from other Temporal object', () => { 74 | const plainDate1 = Temporal.PlainDate.from('2019-11-18'); 75 | const plainDate2 = Temporal.PlainDate.from('1976-11-18'); 76 | const one = PlainMonthDay.from(plainDate1); 77 | const two = PlainMonthDay.from(plainDate2); 78 | equal(one.getISOFields().isoYear, two.getISOFields().isoYear); 79 | }); 80 | it('MonthDay.from({month, day}) allowed if calendar absent', () => 81 | equal(`${PlainMonthDay.from({ month: 11, day: 18 })}`, '11-18')); 82 | it('MonthDay.from({month, day}) not allowed in explicit ISO calendar', () => 83 | throws(() => PlainMonthDay.from({ month: 11, day: 18, calendar: 'iso8601' }), TypeError)); 84 | it('MonthDay.from({month, day}) not allowed in other calendar', () => 85 | throws(() => PlainMonthDay.from({ month: 11, day: 18, calendar: 'gregory' }), TypeError)); 86 | it('MonthDay.from({year, month, day}) allowed in other calendar', () => { 87 | equal( 88 | `${PlainMonthDay.from({ year: 1970, month: 11, day: 18, calendar: 'gregory' })}`, 89 | '1972-11-18[u-ca=gregory]' 90 | ); 91 | }); 92 | it('MonthDay.from({era, eraYear, month, day}) allowed in other calendar', () => { 93 | equal( 94 | `${PlainMonthDay.from({ era: 'ce', eraYear: 1970, month: 11, day: 18, calendar: 'gregory' })}`, 95 | '1972-11-18[u-ca=gregory]' 96 | ); 97 | }); 98 | it('MonthDay.from({ day: 15 }) throws', () => throws(() => PlainMonthDay.from({ day: 15 }), TypeError)); 99 | it('MonthDay.from({ monthCode: "M12" }) throws', () => 100 | throws(() => PlainMonthDay.from({ monthCode: 'M12' }), TypeError)); 101 | it('MonthDay.from({}) throws', () => throws(() => PlainMonthDay.from({}), TypeError)); 102 | it('MonthDay.from(required prop undefined) throws', () => 103 | throws(() => PlainMonthDay.from({ monthCode: undefined, day: 15 }), TypeError)); 104 | it('MonthDay.from(number) is converted to string', () => 105 | assert(PlainMonthDay.from(1201).equals(PlainMonthDay.from('12-01')))); 106 | it('basic format', () => { 107 | equal(`${PlainMonthDay.from('1118')}`, '11-18'); 108 | }); 109 | it('mixture of basic and extended format', () => { 110 | equal(`${PlainMonthDay.from('1976-11-18T152330.1+00:00')}`, '11-18'); 111 | equal(`${PlainMonthDay.from('19761118T15:23:30.1+00:00')}`, '11-18'); 112 | equal(`${PlainMonthDay.from('1976-11-18T15:23:30.1+0000')}`, '11-18'); 113 | equal(`${PlainMonthDay.from('1976-11-18T152330.1+0000')}`, '11-18'); 114 | equal(`${PlainMonthDay.from('19761118T15:23:30.1+0000')}`, '11-18'); 115 | equal(`${PlainMonthDay.from('19761118T152330.1+00:00')}`, '11-18'); 116 | equal(`${PlainMonthDay.from('19761118T152330.1+0000')}`, '11-18'); 117 | equal(`${PlainMonthDay.from('+001976-11-18T152330.1+00:00')}`, '11-18'); 118 | equal(`${PlainMonthDay.from('+0019761118T15:23:30.1+00:00')}`, '11-18'); 119 | equal(`${PlainMonthDay.from('+001976-11-18T15:23:30.1+0000')}`, '11-18'); 120 | equal(`${PlainMonthDay.from('+001976-11-18T152330.1+0000')}`, '11-18'); 121 | equal(`${PlainMonthDay.from('+0019761118T15:23:30.1+0000')}`, '11-18'); 122 | equal(`${PlainMonthDay.from('+0019761118T152330.1+00:00')}`, '11-18'); 123 | equal(`${PlainMonthDay.from('+0019761118T152330.1+0000')}`, '11-18'); 124 | }); 125 | it('optional parts', () => { 126 | equal(`${PlainMonthDay.from('1976-11-18T15:23')}`, '11-18'); 127 | equal(`${PlainMonthDay.from('1976-11-18T15')}`, '11-18'); 128 | equal(`${PlainMonthDay.from('1976-11-18')}`, '11-18'); 129 | }); 130 | it('RFC 3339 month-day syntax', () => { 131 | equal(`${PlainMonthDay.from('--11-18')}`, '11-18'); 132 | equal(`${PlainMonthDay.from('--1118')}`, '11-18'); 133 | }); 134 | it('ignores year when determining the ISO reference year from string', () => { 135 | const one = PlainMonthDay.from('2019-11-18'); 136 | const two = PlainMonthDay.from('1976-11-18'); 137 | equal(one.getISOFields().isoYear, two.getISOFields().isoYear); 138 | }); 139 | it('no junk at end of string', () => throws(() => PlainMonthDay.from('11-18junk'), RangeError)); 140 | it('options may only be an object or undefined', () => { 141 | [null, 1, 'hello', true, Symbol('foo'), 1n].forEach((badOptions) => 142 | throws(() => PlainMonthDay.from({ month: 11, day: 18 }, badOptions), TypeError) 143 | ); 144 | [{}, () => {}, undefined].forEach((options) => 145 | equal(`${PlainMonthDay.from({ month: 11, day: 18 }, options)}`, '11-18') 146 | ); 147 | }); 148 | describe('Overflow', () => { 149 | const bad = { month: 1, day: 32 }; 150 | it('reject', () => throws(() => PlainMonthDay.from(bad, { overflow: 'reject' }), RangeError)); 151 | it('constrain', () => { 152 | equal(`${PlainMonthDay.from(bad)}`, '01-31'); 153 | equal(`${PlainMonthDay.from(bad, { overflow: 'constrain' })}`, '01-31'); 154 | }); 155 | it('throw on bad overflow', () => { 156 | [new PlainMonthDay(11, 18), { month: 1, day: 1 }, '01-31'].forEach((input) => { 157 | ['', 'CONSTRAIN', 'balance', 3, null].forEach((overflow) => 158 | throws(() => PlainMonthDay.from(input, { overflow }), RangeError) 159 | ); 160 | }); 161 | }); 162 | it('constrain has no effect on invalid ISO string', () => { 163 | throws(() => PlainMonthDay.from('13-34', { overflow: 'constrain' }), RangeError); 164 | }); 165 | }); 166 | describe('Leap day', () => { 167 | ['reject', 'constrain'].forEach((overflow) => 168 | it(overflow, () => equal(`${PlainMonthDay.from({ month: 2, day: 29 }, { overflow })}`, '02-29')) 169 | ); 170 | it("rejects when year isn't a leap year", () => 171 | throws(() => PlainMonthDay.from({ month: 2, day: 29, year: 2001 }, { overflow: 'reject' }), RangeError)); 172 | it('constrains non-leap year', () => 173 | equal(`${PlainMonthDay.from({ month: 2, day: 29, year: 2001 }, { overflow: 'constrain' })}`, '02-28')); 174 | }); 175 | describe('Leap day with calendar', () => { 176 | it('requires year with calendar', () => 177 | throws( 178 | () => PlainMonthDay.from({ month: 2, day: 29, calendar: 'iso8601' }, { overflow: 'reject' }), 179 | TypeError 180 | )); 181 | it('rejects leap day with non-leap year', () => 182 | throws( 183 | () => PlainMonthDay.from({ month: 2, day: 29, year: 2001, calendar: 'iso8601' }, { overflow: 'reject' }), 184 | RangeError 185 | )); 186 | it('constrains leap day', () => 187 | equal( 188 | `${PlainMonthDay.from({ month: 2, day: 29, year: 2001, calendar: 'iso8601' }, { overflow: 'constrain' })}`, 189 | '02-28' 190 | )); 191 | it('accepts leap day with monthCode', () => 192 | equal( 193 | `${PlainMonthDay.from({ monthCode: 'M02', day: 29, calendar: 'iso8601' }, { overflow: 'reject' })}`, 194 | '02-29' 195 | )); 196 | }); 197 | it('object must contain at least the required correctly-spelled properties', () => { 198 | throws(() => PlainMonthDay.from({}), TypeError); 199 | throws(() => PlainMonthDay.from({ months: 12, day: 31 }), TypeError); 200 | }); 201 | it('incorrectly-spelled properties are ignored', () => { 202 | equal(`${PlainMonthDay.from({ month: 12, day: 1, days: 31 })}`, '12-01'); 203 | }); 204 | }); 205 | describe('getters', () => { 206 | let md = new PlainMonthDay(1, 15); 207 | it("(1-15).monthCode === '1'", () => { 208 | equal(md.monthCode, 'M01'); 209 | }); 210 | it("(1-15).day === '15'", () => { 211 | equal(`${md.day}`, '15'); 212 | }); 213 | it('month is undefined', () => equal(md.month, undefined)); 214 | }); 215 | describe('.with()', () => { 216 | const md = PlainMonthDay.from('01-22'); 217 | it('with(12-)', () => equal(`${md.with({ monthCode: 'M12' })}`, '12-22')); 218 | it('with(-15)', () => equal(`${md.with({ day: 15 })}`, '01-15')); 219 | }); 220 | }); 221 | describe('MonthDay.with()', () => { 222 | const md = PlainMonthDay.from('01-15'); 223 | it('with({monthCode})', () => equal(`${md.with({ monthCode: 'M12' })}`, '12-15')); 224 | it('with({month}) not accepted', () => { 225 | throws(() => md.with({ month: 12 }), TypeError); 226 | }); 227 | it('with({month, monthCode}) accepted', () => equal(`${md.with({ month: 12, monthCode: 'M12' })}`, '12-15')); 228 | it('month and monthCode must agree', () => { 229 | throws(() => md.with({ month: 12, monthCode: 'M11' }), RangeError); 230 | }); 231 | it('with({year, month}) accepted', () => equal(`${md.with({ year: 2000, month: 12 })}`, '12-15')); 232 | it('throws on bad overflow', () => { 233 | ['', 'CONSTRAIN', 'balance', 3, null].forEach((overflow) => 234 | throws(() => md.with({ day: 1 }, { overflow }), RangeError) 235 | ); 236 | }); 237 | it('throws with calendar property', () => { 238 | throws(() => md.with({ day: 1, calendar: 'iso8601' }), TypeError); 239 | }); 240 | it('throws with timeZone property', () => { 241 | throws(() => md.with({ day: 1, timeZone: 'UTC' }), TypeError); 242 | }); 243 | it('options may only be an object or undefined', () => { 244 | [null, 1, 'hello', true, Symbol('foo'), 1n].forEach((badOptions) => 245 | throws(() => md.with({ day: 1 }, badOptions), TypeError) 246 | ); 247 | [{}, () => {}, undefined].forEach((options) => equal(`${md.with({ day: 1 }, options)}`, '01-01')); 248 | }); 249 | it('object must contain at least one correctly-spelled property', () => { 250 | throws(() => md.with({}), TypeError); 251 | throws(() => md.with({ months: 12 }), TypeError); 252 | }); 253 | it('incorrectly-spelled properties are ignored', () => { 254 | equal(`${md.with({ monthCode: 'M12', days: 1 })}`, '12-15'); 255 | }); 256 | it('year is ignored when determining ISO reference year', () => { 257 | equal(md.with({ year: 1900 }).getISOFields().isoYear, md.getISOFields().isoYear); 258 | }); 259 | }); 260 | describe('MonthDay.equals()', () => { 261 | const md1 = PlainMonthDay.from('01-22'); 262 | const md2 = PlainMonthDay.from('12-15'); 263 | it('equal', () => assert(md1.equals(md1))); 264 | it('unequal', () => assert(!md1.equals(md2))); 265 | it('casts argument', () => { 266 | assert(md1.equals('01-22')); 267 | assert(md1.equals({ month: 1, day: 22 })); 268 | }); 269 | it('object must contain at least the required properties', () => { 270 | throws(() => md1.equals({ month: 1 }), TypeError); 271 | }); 272 | it('takes [[ISOYear]] into account', () => { 273 | const iso = Temporal.Calendar.from('iso8601'); 274 | const md1 = new PlainMonthDay(1, 1, iso, 1972); 275 | const md2 = new PlainMonthDay(1, 1, iso, 2000); 276 | assert(!md1.equals(md2)); 277 | }); 278 | }); 279 | describe("Comparison operators don't work", () => { 280 | const md1 = PlainMonthDay.from('02-13'); 281 | const md1again = PlainMonthDay.from('02-13'); 282 | const md2 = PlainMonthDay.from('11-18'); 283 | it('=== is object equality', () => equal(md1, md1)); 284 | it('!== is object equality', () => notEqual(md1, md1again)); 285 | it('<', () => throws(() => md1 < md2)); 286 | it('>', () => throws(() => md1 > md2)); 287 | it('<=', () => throws(() => md1 <= md2)); 288 | it('>=', () => throws(() => md1 >= md2)); 289 | }); 290 | describe('MonthDay.toPlainDate()', () => { 291 | const md = PlainMonthDay.from('01-22'); 292 | it("doesn't take a primitive argument", () => { 293 | [2002, '2002', false, 2002n, Symbol('2002'), null].forEach((bad) => { 294 | throws(() => md.toPlainDate(bad), TypeError); 295 | }); 296 | }); 297 | it('takes an object argument with year property', () => { 298 | equal(`${md.toPlainDate({ year: 2002 })}`, '2002-01-22'); 299 | }); 300 | it('needs at least a year property on the object in the ISO calendar', () => { 301 | throws(() => md.toPlainDate({ something: 'nothing' }), TypeError); 302 | }); 303 | it("throws if the MonthDay doesn't exist in the year", () => { 304 | const leapDay = PlainMonthDay.from('02-29'); 305 | throws(() => leapDay.toPlainDate({ year: 2019 }), RangeError); 306 | }); 307 | }); 308 | describe('MonthDay.toString()', () => { 309 | const md1 = PlainMonthDay.from('11-18'); 310 | const md2 = PlainMonthDay.from({ monthCode: 'M11', day: 18, calendar: 'gregory' }); 311 | it('shows only non-ISO calendar if calendarName = auto', () => { 312 | equal(md1.toString({ calendarName: 'auto' }), '11-18'); 313 | equal(md2.toString({ calendarName: 'auto' }), '1972-11-18[u-ca=gregory]'); 314 | }); 315 | it('shows ISO calendar if calendarName = always', () => { 316 | equal(md1.toString({ calendarName: 'always' }), '11-18[u-ca=iso8601]'); 317 | }); 318 | it('omits non-ISO calendar, but not year, if calendarName = never', () => { 319 | equal(md1.toString({ calendarName: 'never' }), '11-18'); 320 | equal(md2.toString({ calendarName: 'never' }), '1972-11-18'); 321 | }); 322 | it('default is calendar = auto', () => { 323 | equal(md1.toString(), '11-18'); 324 | equal(md2.toString(), '1972-11-18[u-ca=gregory]'); 325 | }); 326 | it('throws on invalid calendar', () => { 327 | ['ALWAYS', 'sometimes', false, 3, null].forEach((calendarName) => { 328 | throws(() => md1.toString({ calendarName }), RangeError); 329 | }); 330 | }); 331 | }); 332 | describe('monthDay.getISOFields() works', () => { 333 | const md1 = PlainMonthDay.from('11-18'); 334 | const fields = md1.getISOFields(); 335 | it('fields', () => { 336 | equal(fields.isoMonth, 11); 337 | equal(fields.isoDay, 18); 338 | equal(fields.calendar.id, 'iso8601'); 339 | equal(typeof fields.isoYear, 'number'); 340 | }); 341 | it('enumerable', () => { 342 | const fields2 = { ...fields }; 343 | equal(fields2.isoMonth, 11); 344 | equal(fields2.isoDay, 18); 345 | equal(fields2.calendar, fields.calendar); 346 | equal(typeof fields2.isoYear, 'number'); 347 | }); 348 | it('as input to constructor', () => { 349 | const md2 = new PlainMonthDay(fields.isoMonth, fields.isoDay, fields.calendar, fields.isoYear); 350 | assert(md2.equals(md1)); 351 | }); 352 | }); 353 | }); 354 | 355 | import { normalize } from 'path'; 356 | if (normalize(import.meta.url.slice(8)) === normalize(process.argv[1])) { 357 | report(reporter).then((failed) => process.exit(failed ? 1 : 0)); 358 | } 359 | -------------------------------------------------------------------------------- /test/preprocessor.test262.cjs: -------------------------------------------------------------------------------- 1 | module.exports = function (test) { 2 | // skip non-strict tests to speed up test runs 3 | if (!test.scenario.includes('strict')) { 4 | test.result = { 5 | pass: true, 6 | stdout: '', 7 | stderr: '', 8 | error: null 9 | }; 10 | } 11 | return test; 12 | }; 13 | -------------------------------------------------------------------------------- /test/resolve.source.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import fs from 'fs'; 7 | const PKG = JSON.parse(fs.readFileSync('package.json', { encoding: 'utf-8' })); 8 | export function resolve(specifier, context, defaultResolve) { 9 | if (specifier === PKG.name) { 10 | specifier = new URL('../tsc-out/temporal.js', import.meta.url).toString(); 11 | } else if (specifier.startsWith('../lib')) { 12 | specifier = new URL(specifier.replace('../lib/', '../tsc-out/'), import.meta.url).toString(); 13 | if (!specifier.endsWith('.js')) specifier += '.js'; 14 | } else if ( 15 | context.parentURL && 16 | context.parentURL.includes('/tsc-out/') && 17 | specifier.startsWith('./') && 18 | !specifier.endsWith('.js') 19 | ) { 20 | specifier += '.js'; 21 | } 22 | return defaultResolve(specifier, context); 23 | } 24 | -------------------------------------------------------------------------------- /tools/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - ../.eslintrc.yml 3 | overrides: 4 | - files: 5 | - 'rebase-upstream-commits.ts' 6 | rules: 7 | no-console: off 8 | -------------------------------------------------------------------------------- /tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripts", 3 | "version": "0.0.1", 4 | "description": "support scripts for Temporal repository", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@colors/colors": "^1.5.0", 13 | "prompt": "^1.3.0", 14 | "ts-node": "^10.8.1", 15 | "typescript": "^5.1.3", 16 | "yargs": "^17.5.1" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^18.14.2", 20 | "@types/prompt": "^1.1.5", 21 | "@types/yargs": "^17.0.10" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tools/rebasing.md: -------------------------------------------------------------------------------- 1 | # Rebasing commits from upstream 2 | 3 | In order to stay up to date with changes to the specification, we rebase changes from the upstream repository [`https://github.com/tc39/proposal-temporal`](https://github.com/tc39/proposal-temporal) into our main branch. 4 | This isn't a simple `git merge`, as our repos do not share history (and even if they did would almost never merge cleanly). 5 | To make this rebasing procedue less painful, there's some tooling that can be run to setup and walk through the rebase commit by commit, taking automated actions to resolve more trivial rebase conflicts. 6 | 7 | ## Background 8 | 9 | Some definitions / naming conventions used throughout this doc: 10 | 11 | - `pub`: a git remote naming convention for this repository (js-temporal/temporal-polyfill). 12 | Note this is probably not your git repository's default upstream, as anyone using the Pull Request model must first push their changes to their own fork repository and open a pull request. 13 | - `git remote add pub git@github.com:js-temporal/temporal-polyfill.git` to add this remote to your local repository 14 | - `main`: the local branch name for your repo fork's main branch. 15 | This should probably track your remote's main branch **not js-temporal/temporal-polyfill's main branch** so you don't accidentally try and push straight to main. 16 | - `spec-pub`: the upstream Temporal proposal repository. 17 | This remote is unlikely to be in your local repository, but to perform rebases you will need this remote's content locally. 18 | - `git remote add spec-pub https://github.com/tc39/proposal-temporal.git` to add this remote to your local repository. 19 | 20 | ## Getting the latest changes 21 | 22 | Make sure that your local copy of both the `pub` and `spec-pub` repositories are up to date: 23 | 24 | - `git fetch pub && git fetch spec-pub` 25 | 26 | Make sure your `main` branch is lined up with the `pub` main branch: 27 | 28 | - `git checkout main && git merge --ff-only pub/main` 29 | 30 | Running `git log` should now show your local `main` branch is pointed to the same commit as `pub/main`: 31 | 32 | - ```shell 33 | $ git log 34 | commit (HEAD, pub/main, main) 35 | ``` 36 | 37 | Initialize the test262 submodule if you haven't before: 38 | 39 | - `git submodule update` 40 | 41 | Using the git log, determine the most recent upstream commit that was successfully migrated over using this tooling. 42 | Each commit migrating using this tooling will add `UPSTREAM_COMMIT=` to a rebased commit description: 43 | 44 | - `git log --grep 'UPSTREAM_COMMIT=' pub/main`, take note of the commit hash from the description (not the hash of the commit itself). 45 | Example: 46 | ``` 47 | commit c0f7349a327b68543797d38e045b1fd8c1e0949b 48 | Author: Philip Chimento 49 | Date: Tue Sep 27 16:04:44 2022 -0700 50 | 51 | Avoid precision loss in AddDuration 52 | 53 | According to the spec, the addition in AddDuration must not be performed 54 | in the floating-point domain. Anba wrote some tests that verify this. 55 | 56 | For days, d1 + d2 practically speaking is still OK because the result is 57 | directly stuffed into a float64-representable integer anyway, so keep that 58 | the same for simplicity. Perform the other additions in the BigInt domain 59 | and pass BigInts to BalanceDuration. 60 | 61 | UPSTREAM_COMMIT=9a0565a8cba336a6dabbf10cc58ccbf665bfe023 62 | ``` 63 | The upstream commit hash is `9a0565a8cba336a6dabbf10cc58ccbf665bfe023`. 64 | We refer to this hash throughout the rest of this document as LATEST_UPSTREAMED_COMMIT. 65 | Consider exporting this value in your terminal so you can reference it later in commands: `export LATEST_UPSTREAMED_COMMIT=` 66 | 67 | Install the rebase-tools dependencies: 68 | 69 | - `cd tools && npm ci && cd ..` 70 | 71 | Check that you can run the tool: 72 | 73 | - `npx ts-node tools/rebase-upstream-commits.ts` should print out the tool's command-line help. 74 | 75 | Throughout this document, we refer back to this command as `trt`, and we recommend you add an alias to your current terminal window that can run the tool: 76 | 77 | - `alias trt="$(npx ts-node tools/rebase-upstream-commits.ts realcmd)"` 78 | - TODO(12wrigja): Determine how likely it is that the live-paths here are a problem in practice. 79 | 80 | ## Rebasing Guidelines 81 | 82 | Rebasing changes from the spec repository into this one follows some general rules: 83 | 84 | - where possible, we don't change the runtime behaviour of the code, and make this work by adapting TS types to fit. 85 | Sometimes this doesn't quite work or there are other restrictions that superceed this. 86 | - Where it is impossible to write correctly-typechecking TS, we will sometimes make use of `assertExists` and `uncheckedAssertNarrowedType` to influence the type-checker ([example](https://github.com/js-temporal/temporal-polyfill/blob/9d68d0cd304f730d598c6bad3712d263359ffb0e/lib/plaindate.ts#L251-L254)). 87 | - TS does not recognize that types have changed as a result of re-assigning over a local variable (or function parameter), and this is typically a common source of type-checking problems. 88 | - For function parameters, we instead rename the parameter to include a `Param` suffix, and then create a local variable of the same name that can be set to the right type 89 | Example: 90 | 91 | ```js 92 | // Code upstream 93 | static compare(one, two) { 94 | one = ES.ToTemporalDate(one); 95 | ``` 96 | 97 | becomes 98 | 99 | ```ts 100 | static compare(oneParam: Params['compare'][0], twoParam: Params['compare'][1]): Return['compare'] { 101 | // We rename the function parameter and re-use the old name so the variable's type is correctly narrowed as a result of calling `ToTemporalDate` 102 | const one = ES.ToTemporalDate(oneParam); 103 | ``` 104 | - Avoid adding dependencies to the polyfill. 105 | - upstream changes to `polyfill/lib/ecmascript.mjs` will always fail to rebase onto `lib/ecmascript.ts` because the files are now substantially different: the former defines exported functions as properties in an object, while the latter treats them as regular module exports. 106 | - We use `JSBI` for bigint compatibility, not upstream's `big-integer`, as `JSBI` accurately represents the runtime semantic differences between native `number` and `bigint` operations, resulting in type-checking errors where mixing `number` and `JSBI` occurs. 107 | It also has support for build-time transpilation of JSBI calls down to native BigInt. 108 | - The two libraries are mostly functionally equivalent, with some functions on `big-integer` objects being replaced with functions exported in `ecmascript.ts` (divmod, abs, etc). 109 | - We keep the code organized in the same files as upstream, to make rebasing easier. 110 | Please don't create arbitrary new files for the sake of code organization. 111 | 112 | ## Rebasing 113 | 114 | - Decide how much of the upstream commits you want to rebase: 115 | - From the repository root, run `git log $LATEST_UPSTREAMED_COMMIT..spec-pub/main --oneline -- ./polyfill/` to see a list of all likely-outstanding commits to rebase. 116 | Consider using `| wc -l` to count the list, and try to create rebase PRs that are no more than 20 or 30 commits long or particularly complex, to make it easier on both your reviewers and yourself. 117 | Pick a commit hash from this list, now denoted as `TARGET_UPSTREAM_COMMIT`. 118 | Consider exporting this value in your terminal so you can reference it later in commands: `export TARGET_UPSTREAM_COMMIT=` 119 | You can always stop rebasing early using the `trt finish` or `trt abort` commands which will keep or throw away any work you have already done respectively. 120 | - Decide what sort of automated testing should be done in between commits, and create a temporary script to run to test those things. 121 | - For example, if you want to validate that the code builds, is lint free, and passes most of the CI tests, an example `test.sh` script might be 122 | ```sh 123 | #!/bin/bash 124 | set -e 125 | npm run build 126 | npm run lint 127 | npm run test 128 | npm run test262 129 | ``` 130 | - Using the rebase tool command from earlier, start the rebase: 131 | - `trt $LATEST_UPSTREAMED_COMMIT $TARGET_UPSTREAM_COMMIT --onto pub/main` 132 | - Add `--exec=` if you want to run a command after each substantial change to the repo during the rebase. 133 | To continue the example from above: `--exec=./test.sh` would cause the tool to run that test script after every substantial change to the repo. 134 | - This will automatically start a git interactive rebase with all the relevant commits and start to work through them one by one, using the interactive rebase's 'edit' option on each commit to allow the tool a chance to inspect and edit each commit. 135 | - The tool will: 136 | - Automatically resolve rebase conflicts where possible, including 137 | - removing docs/ and spec/ from upstream which we don't use in this repo 138 | - Automatically updating the test262 subrepo commit if a newer commit is seen in upstream 139 | - automatically appending the relevant UPSTREAM_COMMIT "tag" in the commit description 140 | - While the tool does use a normal Git interactive rebase, it is best to **resume** the rebase (once merge conflicts are resolved) by running the tool's `continue` command (`trt continue`), as that will take over rebasing until the next set of "real" merge conflicts. 141 | - While rebasing some useful commands to note: 142 | - `trt showupstream` will show the upstream change in its entirety, including all files changed and the commit description. 143 | - `trt basediff` will show just file diffs from the upstream change. This can be particularly useful for larger files (like ecmascript.mjs) where the small diffs shown by the `showupstream` command are not enough detail. 144 | - Example: `trt basediff -U40 -- polyfill/lib/ecmascript.mjs` for the upstream diff in `ecmascript.mjs` with 40 unified diff lines on each side of a change. 145 | - You might decide halfway through to finish where you are at with the current rebase and continue later in another Pull Request. The tool has a convenience command for this: `trt finish`. 146 | Once this command is run, you will likely end up in a "detached HEAD" state, and should create a branch at that new commit to save all your hard work! `git checkout -b `. 147 | - If code being rebased fixes a Test262 test, then it will start to unexpectedly pass, and `npm run test262` will fail. 148 | There is support in the test262 runner to automatically remove tests listed in the expected-failure files that are now passing - this is done by running `npm run test262 -- --update-expected-failure-files`. I would NOT recommend having this run automatically in the `exec` script, and instead running this manually if needed to avoid accidentally cleaning up tests that are supposed to still fail. 149 | 150 | ## Code Review 151 | 152 | When opening a rebasing PR, please add [@12wrigja](https://github.com/12wrigja) as a reviewer. 153 | 154 | We have GitHub Actions CI checks that validate the extensive Test262 test suite will still pass with any changes, as well as some linters and static analysis tools to help keep the codebase reasonably clean. 155 | 156 | Some general code-review tips: 157 | - Avoid using TypeScript's `any` type. 158 | If you aren't sure what types to use, feel free to use `any` and then request help in the code review from the reviewers to suggest fixes to the types. 159 | - Try and avoid force-pushing new commits to the PR branch once a review has been started, and instead use [fixup commits](https://git-scm.com/docs/git-commit/) (search this page for 'fixup'), as the GitHub review UI handles this scenario better. 160 | 161 | - Example: `git commit --fixup ` 162 | 163 | Once all review comments are addressed, you will need to rebase that branch using autosquash to merge your fixes into the relevant commits, without squashing the overall commit history itself: 164 | 165 | - ``` 166 | git rebase -i main --autosquash 167 | ``` 168 | - without changing anything in the interactiv editor, save the file and quit the editor. This will squash all the fixup commits away while leaving the rest of the commit history intact. 169 | - Now, your branch will require force-pushing, and this is OK. 170 | - Once the branch structure looks good, a maintainer will merge (not squash) the PR into the main branch (to retain the commit history). 171 | -------------------------------------------------------------------------------- /tools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compileOnSave": false, 4 | "include": ["*.ts"], 5 | "compilerOptions": { 6 | "lib": ["ES2021"], 7 | "types": ["node"], 8 | "module": "NodeNext", 9 | "moduleResolution": "node", 10 | "allowSyntheticDefaultImports": true, 11 | "target": "es2021" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "include": ["lib/*.ts"], 4 | "compilerOptions": { 5 | "allowUnreachableCode": false, 6 | "downlevelIteration": true, 7 | "importHelpers": true, 8 | "inlineSources": true, 9 | "inlineSourceMap": false, 10 | "lib": ["dom", "ES2022"], 11 | "module": "es6", 12 | "moduleResolution": "node", 13 | "noEmitOnError": true, 14 | "noErrorTruncation": true, 15 | // "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitOverride": true, 18 | "noImplicitReturns": true, 19 | "noImplicitThis": true, 20 | "outDir": "tsc-out/", 21 | "skipDefaultLibCheck": true, 22 | "sourceMap": true, 23 | "strictBindCallApply": true, 24 | "strictFunctionTypes": true, 25 | "strictNullChecks": true, 26 | "strictPropertyInitialization": true, 27 | "stripInternal": true, 28 | "target": "es2020", 29 | "types": [], 30 | "useUnknownInCatchVariables": true, 31 | "verbatimModuleSyntax": true 32 | } 33 | } 34 | --------------------------------------------------------------------------------