├── .github └── workflows │ ├── node.js.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .vscode └── launch.json ├── README.md ├── as-pect.config.js ├── asconfig.empty.json ├── asconfig.json ├── assembly ├── __tests__ │ ├── README.md │ ├── as-pect.d.ts │ ├── duration.spec.ts │ ├── empty.ts │ ├── empty.wat │ ├── instant.spec.ts │ ├── plaindate.spec.ts │ ├── plaindatetime.spec.ts │ ├── plainmonthday.spec.ts │ ├── plaintime.spec.ts │ └── plainyearmonth.spec.ts ├── duration.ts ├── enums.ts ├── env.ts ├── index.ts ├── instant.ts ├── now.ts ├── plaindate.ts ├── plaindatetime.ts ├── plainmonthday.ts ├── plaintime.ts ├── plainyearmonth.ts ├── tsconfig.json └── util │ ├── calendar.ts │ ├── constants.ts │ ├── format.ts │ └── index.ts ├── development.md ├── package-lock.json └── package.json /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Test 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [15.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - run: npm ci 29 | - run: npm run build --if-present 30 | - run: npm test 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Release 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-18.04 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v1 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: 15 21 | - name: Install dependencies 22 | run: npm ci 23 | - name: Run tests 24 | run: npm test 25 | - name: Release 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 29 | run: npx semantic-release@18 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | assembly/**/__tests__/*.map 4 | assembly/**/__tests__/*.wat 5 | !assembly/**/__tests__/empty.wat -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColinEberhardt/assemblyscript-temporal/0512b9e0e6268625a7b34d243980999de17c65c7/.gitmodules -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-node", 9 | "request": "launch", 10 | "name": "Launch TZDB Generation", 11 | "skipFiles": ["/**"], 12 | "program": "${workspaceFolder}/tzdb/index.mjs", 13 | "outFiles": ["${workspaceFolder}/**/*.js"], 14 | // "runtimeExecutable": "/Users/colineberhardt/.nvm/versions/node/v10.16.3/bin/node" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## assemblyscript-temporal 2 | 3 | An implementation of [TC39 Temporal](https://github.com/tc39/proposal-temporal) for AssemblyScript, with an focus on non-timezone-aware classes and functionality. 4 | 5 | ### Why? 6 | 7 | AssemblyScript has minimal `Date` support, however, the JS Date API itself is terrible and people tend not to use it that often. As a result libraries like moment / luxon have become staple replacements. However, there is now a [relatively mature TC39 proposal](https://github.com/tc39/proposal-temporal) that adds greatly improved date support to JS. 8 | 9 | ### Usage 10 | 11 | This library currently supports the following types: 12 | 13 | #### `PlainDateTime` 14 | 15 | A `PlainDateTime` represents a calendar date and wall-clock time that does not carry time zone information, e.g. December 7th, 1995 at 3:00 PM (in the Gregorian calendar). For detailed documentation see the [TC39 Temporal proposal website](https://tc39.es/proposal-temporal/docs/plaindatetime.html), this implementation follows the specification as closely as possible. 16 | 17 | You can create a `PlainDateTime` from individual components, a string or an object literal: 18 | 19 | ```javascript 20 | datetime = new PlainDateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789); 21 | datetime.year; // 2019; 22 | datetime.month; // 11; 23 | // ... 24 | datetime.nanosecond; // 789; 25 | 26 | datetime = PlainDateTime.from("1976-11-18T12:34:56"); 27 | datetime.toString(); // "1976-11-18T12:34:56" 28 | 29 | datetime = PlainDateTime.from({ year: 1966, month: 3, day: 3 }); 30 | datetime.toString(); // "1966-03-03T00:00:00" 31 | ``` 32 | 33 | There are various ways you can manipulate a date: 34 | 35 | ```javascript 36 | // use 'with' to copy a date but with various property values overriden 37 | datetime = new PlainDateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789); 38 | datetime.with({ year: 2019 }).toString(); // "2019-11-18T15:23:30.123456789" 39 | 40 | // use 'add' or 'substract' to add / subtract a duration 41 | datetime = PlainDateTime.from("2020-01-12T15:00"); 42 | datetime.add({ months: 1 }).toString(); // "2020-02-12T15:00:00"); 43 | 44 | // add / subtract support Duration objects or object literals 45 | datetime.add(new Duration(1)).toString(); // "2021-01-12T15:00:00"); 46 | ``` 47 | 48 | You can compare dates and check for equality 49 | 50 | ```javascript 51 | dt1 = PlainDateTime.from("1976-11-18"); 52 | dt2 = PlainDateTime.from("2019-10-29"); 53 | PlainDateTime.compare(dt1, dt1); // 0 54 | PlainDateTime.compare(dt1, dt2); // -1 55 | dt1.equals(dt1); // true 56 | ``` 57 | 58 | Currently `PlainDateTime` only supports the ISO 8601 (Gregorian) calendar. 59 | 60 | #### `PlainDate` 61 | 62 | A `PlainDate` object represents a calendar date that is not associated with a particular time or time zone, e.g. August 24th, 2006. For detailed documentation see the [TC39 Temporal proposal website](https://tc39.es/proposal-temporal/docs/plaindate.html), this implementation follows the specification as closely as possible. 63 | 64 | The `PlainDate` API is almost identical to `PlainDateTime`, so see above for API usage examples. 65 | 66 | #### `PlainTime` 67 | 68 | A `PlainTime` object represents a wall-clock time that is not associated with a particular date or time zone, e.g. 7:39 PM. For detailed documentation see the [TC39 Temporal proposal website](https://tc39.es/proposal-temporal/docs/plaintime.html), this implementation follows the specification as closely as possible. 69 | 70 | The `PlainTime` API is almost identical to `PlainDateTime`, so see above for API usage examples. 71 | 72 | #### `PlainMonthDay` 73 | 74 | A date without a year component. This is useful to express things like "Bastille Day is on the 14th of July". 75 | For detailed documentation see the 76 | [TC39 Temporal proposal website](https://tc39.es/proposal-temporal/docs/plainmonthday.html) 77 | , this implementation follows the specification as closely as possible. 78 | 79 | ```javascript 80 | const monthDay = PlainMonthDay.from({ month: 7, day: 14 }); // => 07-14 81 | const date = monthDay.toPlainDate({ year: 2030 }); // => 2030-07-14 82 | date.dayOfWeek; // => 7 83 | ``` 84 | 85 | The `PlainMonthDay` API is almost identical to `PlainDateTime`, so see above for more API usage examples. 86 | 87 | #### `PlainYearMonth` 88 | 89 | A date without a day component. This is useful to express things like "the October 2020 meeting". 90 | For detailed documentation see the 91 | [TC39 Temporal proposal website](https://tc39.es/proposal-temporal/docs/plainyearmonth.html) 92 | , this implementation follows the specification as closely as possible. 93 | 94 | The `PlainYearMonth` API is almost identical to `PlainDateTime`, so see above for API usage examples. 95 | 96 | #### `Duration` 97 | 98 | A `Duration` represents a duration of time which can be used in date/time arithmetic. For detailed documentation see the 99 | [TC39 Temporal proposal website](https://tc39.es/proposal-temporal/docs/duration.html) 100 | 101 | Here's a small example, showing just some of wha you can do with durations: 102 | 103 | ```javascript 104 | // create a duration 105 | const duration = Duration.from({ days: 1, minutes: 5 }); 106 | // add another duration to the first one 107 | const duration2 = duration.add({ days: 2, minutes: 5 })}; 108 | duration2.toString(); // "P3DT10M" 109 | ``` 110 | 111 | 112 | #### `Now` 113 | 114 | The `Now` object has several methods which give information about the current time and date. 115 | 116 | ```javascript 117 | dateTime = Now.plainDateTimeISO(); 118 | dateTime.toString(); // 2021-04-01T12:05:47.357 119 | ``` 120 | 121 | ## Contributing 122 | 123 | This project is open source, MIT licensed and your contributions are very much welcomed. 124 | 125 | There is a [brief document that outlines implementation progress and priorities](./development.md). 126 | 127 | 128 | -------------------------------------------------------------------------------- /as-pect.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * A set of globs passed to the glob package that qualify typescript files for testing. 4 | */ 5 | include: ["assembly/**/__tests__/**/*.spec.ts"], 6 | /** 7 | * A set of globs passed to the glob package that quality files to be added to each test. 8 | */ 9 | add: ["assembly/**/__tests__/**/*.include.ts"], 10 | /** 11 | * All the compiler flags needed for this test suite. Make sure that a binary file is output. 12 | */ 13 | flags: { 14 | /** To output a wat file, uncomment the following line. */ 15 | // "--textFile": ["output.wat"], 16 | /** A runtime must be provided here. */ 17 | "--runtime": ["stub"], // Acceptable values are: full, half, stub (arena), and none 18 | }, 19 | /** 20 | * A set of regexp that will disclude source files from testing. 21 | */ 22 | disclude: [/node_modules/], 23 | /** 24 | * Add your required AssemblyScript imports here. 25 | */ 26 | imports(memory, createImports, instantiateSync, binary) { 27 | let instance; // Imports can reference this 28 | const myImports = { 29 | // put your web assembly imports here, and return the module 30 | }; 31 | instance = instantiateSync(binary, createImports(myImports)); 32 | return instance; 33 | }, 34 | /** 35 | * Add a custom reporter here if you want one. The following example is in typescript. 36 | * 37 | * @example 38 | * import { TestReporter, TestGroup, TestResult, TestContext } from "as-pect"; 39 | * 40 | * export class CustomReporter extends TestReporter { 41 | * // implement each abstract method here 42 | * public abstract onStart(suite: TestContext): void; 43 | * public abstract onGroupStart(group: TestGroup): void; 44 | * public abstract onGroupFinish(group: TestGroup): void; 45 | * public abstract onTestStart(group: TestGroup, result: TestResult): void; 46 | * public abstract onTestFinish(group: TestGroup, result: TestResult): void; 47 | * public abstract onFinish(suite: TestContext): void; 48 | * } 49 | */ 50 | // reporter: new CustomReporter(), 51 | /** 52 | * Specify if the binary wasm file should be written to the file system. 53 | */ 54 | outputBinary: false, 55 | }; 56 | -------------------------------------------------------------------------------- /asconfig.empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "runtime": "stub", 4 | "textFile": "build/empty.wat", 5 | "debug": true 6 | }, 7 | "entries": ["assembly/__tests__/empty.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /asconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": { 3 | "debug": { 4 | "binaryFile": "build/untouched.wasm", 5 | "textFile": "build/untouched.wat", 6 | "sourceMap": true, 7 | "debug": true 8 | }, 9 | "release": { 10 | "binaryFile": "build/optimized.wasm", 11 | "textFile": "build/optimized.wat", 12 | "sourceMap": true, 13 | "optimizeLevel": 3, 14 | "shrinkLevel": 1, 15 | "converge": false, 16 | "noAssert": false 17 | } 18 | }, 19 | "options": { 20 | "exportRuntime": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /assembly/__tests__/README.md: -------------------------------------------------------------------------------- 1 | ## Test Strategy 2 | 3 | - tests are copied from the [polyfill implementation](https://github.com/tc39/proposal-temporal/tree/main/polyfill/test) 4 | - tests should be removed if they relate to features that do not make sense for TS/AS, i.e. tests that validate the shape of an object do not make sense in a language with compile-time type checking 5 | - tests that fail because a feature has not been implemented yet should be left as failures. 6 | -------------------------------------------------------------------------------- /assembly/__tests__/as-pect.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /assembly/__tests__/empty.ts: -------------------------------------------------------------------------------- 1 | import * as temporal from ".."; -------------------------------------------------------------------------------- /assembly/__tests__/empty.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (memory $0 0) 3 | (table $0 1 funcref) 4 | (export "memory" (memory $0)) 5 | ) 6 | -------------------------------------------------------------------------------- /assembly/__tests__/plainmonthday.spec.ts: -------------------------------------------------------------------------------- 1 | import { Duration, DurationLike } from "../duration"; 2 | import { PlainDate } from "../plaindate"; 3 | import { PlainDateTime } from "../plaindatetime"; 4 | import { MonthDayLike, PlainMonthDay } from "../plainmonthday"; 5 | 6 | let one: PlainMonthDay, 7 | two: PlainMonthDay, 8 | md: PlainMonthDay, 9 | md1: PlainMonthDay, 10 | md2: PlainMonthDay; 11 | 12 | describe("MonthDay", () => { 13 | // describe('Structure', () => { 14 | // it('MonthDay is a Function', () => { 15 | // equal(typeof PlainMonthDay, 'function'); 16 | // }); 17 | // it('MonthDay has a prototype', () => { 18 | // assert(PlainMonthDay.prototype); 19 | // equal(typeof PlainMonthDay.prototype, 'object'); 20 | // }); 21 | // describe('MonthDay.prototype', () => { 22 | // it('MonthDay.prototype has monthCode', () => { 23 | // assert('monthCode' in PlainMonthDay.prototype); 24 | // }); 25 | // it('MonthDay.prototype.equals is a Function', () => { 26 | // equal(typeof PlainMonthDay.prototype.equals, 'function'); 27 | // }); 28 | // it('MonthDay.prototype.toString is a Function', () => { 29 | // equal(typeof PlainMonthDay.prototype.toString, 'function'); 30 | // }); 31 | // it('MonthDay.prototype.getISOFields is a Function', () => { 32 | // equal(typeof PlainMonthDay.prototype.getISOFields, 'function'); 33 | // }); 34 | // }); 35 | // }); 36 | describe("Construction", () => { 37 | it("Leap day", () => { 38 | expect(new PlainMonthDay(2, 29).toString()).toBe("02-29"); 39 | }); 40 | describe(".from()", () => { 41 | it("MonthDay.from(10-01) == 10-01", () => { 42 | expect(PlainMonthDay.from("10-01").toString()).toBe("10-01"); 43 | }); 44 | it("MonthDay.from(2019-10-01T09:00:00Z) == 10-01", () => { 45 | expect(PlainMonthDay.from("2019-10-01T09:00:00Z").toString()).toBe( 46 | "10-01" 47 | ); 48 | }); 49 | it("MonthDay.from('11-18') == (11-18)", () => { 50 | expect(PlainMonthDay.from("11-18").toString()).toBe("11-18"); 51 | }); 52 | it("MonthDay.from('1976-11-18') == (11-18)", () => { 53 | expect(PlainMonthDay.from("1976-11-18").toString()).toBe("11-18"); 54 | }); 55 | xit('MonthDay.from({ monthCode: "M11", day: 18 }) == 11-18', () => { 56 | // expect( 57 | // PlainMonthDay.from({ monthCode: "M11", day: 18 }).toString() 58 | // ).toBe("11-18"); 59 | }); 60 | // it("ignores year when determining the ISO reference year from month/day", () => { 61 | // const one = PlainMonthDay.from({ year: 2019, month: 11, day: 18 }); 62 | // const two = PlainMonthDay.from({ year: 1979, month: 11, day: 18 }); 63 | // equal(one.getISOFields().isoYear, two.getISOFields().isoYear); 64 | // }); 65 | // it("ignores era/eraYear when determining the ISO reference year from month/day", () => { 66 | // const one = PlainMonthDay.from({ 67 | // era: "ce", 68 | // eraYear: 2019, 69 | // month: 11, 70 | // day: 18, 71 | // calendar: "gregory", 72 | // }); 73 | // const two = PlainMonthDay.from({ 74 | // era: "ce", 75 | // eraYear: 1979, 76 | // month: 11, 77 | // day: 18, 78 | // calendar: "gregory", 79 | // }); 80 | // equal(one.getISOFields().isoYear, two.getISOFields().isoYear); 81 | // }); 82 | // it("ignores year when determining the ISO reference year from monthCode/day", () => { 83 | // const one = PlainMonthDay.from({ 84 | // year: 2019, 85 | // monthCode: "M11", 86 | // day: 18, 87 | // }); 88 | // const two = PlainMonthDay.from({ 89 | // year: 1979, 90 | // monthCode: "M11", 91 | // day: 18, 92 | // }); 93 | // equal(one.getISOFields().isoYear, two.getISOFields().isoYear); 94 | // }); 95 | // it("ignores era/eraYear when determining the ISO reference year from monthCode/day", () => { 96 | // const one = PlainMonthDay.from({ 97 | // era: "ce", 98 | // eraYear: 2019, 99 | // monthCode: "M11", 100 | // day: 18, 101 | // calendar: "gregory", 102 | // }); 103 | // const two = PlainMonthDay.from({ 104 | // era: "ce", 105 | // eraYear: 1979, 106 | // monthCode: "M11", 107 | // day: 18, 108 | // calendar: "gregory", 109 | // }); 110 | // equal(one.getISOFields().isoYear, two.getISOFields().isoYear); 111 | // }); 112 | it("MonthDay.from(11-18) is not the same object", () => { 113 | one = new PlainMonthDay(11, 18); 114 | two = PlainMonthDay.from(one); 115 | expect(one).not.toBe(two); 116 | }); 117 | // it("ignores year when determining the ISO reference year from other Temporal object", () => { 118 | // const plainDate1 = Temporal.PlainDate.from("2019-11-18"); 119 | // const plainDate2 = Temporal.PlainDate.from("1976-11-18"); 120 | // const one = PlainMonthDay.from(plainDate1); 121 | // const two = PlainMonthDay.from(plainDate2); 122 | // equal(one.getISOFields().isoYear, two.getISOFields().isoYear); 123 | // }); 124 | it("MonthDay.from({month, day}) allowed if calendar absent", () => { 125 | expect(PlainMonthDay.from({ month: 11, day: 18 }).toString()).toBe( 126 | "11-18" 127 | ); 128 | }); 129 | xit("MonthDay.from({month, day}) not allowed in explicit ISO calendar", () => {}); 130 | // throws( 131 | // () => PlainMonthDay.from({ month: 11, day: 18, calendar: "iso8601" }), 132 | // TypeError 133 | // ) 134 | xit("MonthDay.from({month, day}) not allowed in other calendar", () => {}); 135 | // throws( 136 | // () => PlainMonthDay.from({ month: 11, day: 18, calendar: "gregory" }), 137 | // TypeError) 138 | xit("MonthDay.from({year, month, day}) allowed in other calendar", () => { 139 | // equal( 140 | // `${PlainMonthDay.from({ 141 | // year: 1970, 142 | // month: 11, 143 | // day: 18, 144 | // calendar: "gregory", 145 | // })}`, 146 | // "1972-11-18[u-ca=gregory]" 147 | // ); 148 | }); 149 | xit("MonthDay.from({era, eraYear, month, day}) allowed in other calendar", () => { 150 | // equal( 151 | // `${PlainMonthDay.from({ 152 | // era: "ce", 153 | // eraYear: 1970, 154 | // month: 11, 155 | // day: 18, 156 | // calendar: "gregory", 157 | // })}`, 158 | // "1972-11-18[u-ca=gregory]" 159 | // ); 160 | }); 161 | it("MonthDay.from({ day: 15 }) throws", () => { 162 | expect(() => { 163 | PlainMonthDay.from({ day: 15 }); 164 | }).toThrow(); 165 | }); 166 | // it('MonthDay.from({ monthCode: "M12" }) throws', () => 167 | // throws(() => PlainMonthDay.from({ monthCode: "M12" }), TypeError)); 168 | // it("MonthDay.from({}) throws", () => 169 | // throws(() => PlainMonthDay.from({}), TypeError)); 170 | // it("MonthDay.from(required prop undefined) throws", () => 171 | // throws( 172 | // () => PlainMonthDay.from({ monthCode: undefined, day: 15 }), 173 | // TypeError 174 | // )); 175 | // it("MonthDay.from(number) is converted to string", () => 176 | // assert(PlainMonthDay.from(1201).equals(PlainMonthDay.from("12-01")))); 177 | it("basic format", () => { 178 | expect(PlainMonthDay.from("1118").toString()).toBe("11-18"); 179 | }); 180 | it("mixture of basic and extended format", () => { 181 | expect(PlainMonthDay.from("1976-11-18T152330.1+00:00").toString()).toBe( 182 | "11-18" 183 | ); 184 | expect(PlainMonthDay.from("19761118T15:23:30.1+00:00").toString()).toBe( 185 | "11-18" 186 | ); 187 | expect( 188 | PlainMonthDay.from("1976-11-18T15:23:30.1+0000").toString() 189 | ).toBe("11-18"); 190 | expect(PlainMonthDay.from("1976-11-18T152330.1+0000").toString()).toBe( 191 | "11-18" 192 | ); 193 | expect(PlainMonthDay.from("19761118T15:23:30.1+0000").toString()).toBe( 194 | "11-18" 195 | ); 196 | expect(PlainMonthDay.from("19761118T152330.1+00:00").toString()).toBe( 197 | "11-18" 198 | ); 199 | expect(PlainMonthDay.from("19761118T152330.1+0000").toString()).toBe( 200 | "11-18" 201 | ); 202 | expect( 203 | PlainMonthDay.from("+001976-11-18T152330.1+00:00").toString() 204 | ).toBe("11-18"); 205 | expect( 206 | PlainMonthDay.from("+0019761118T15:23:30.1+00:00").toString() 207 | ).toBe("11-18"); 208 | expect( 209 | PlainMonthDay.from("+001976-11-18T15:23:30.1+0000").toString() 210 | ).toBe("11-18"); 211 | expect( 212 | PlainMonthDay.from("+001976-11-18T152330.1+0000").toString() 213 | ).toBe("11-18"); 214 | expect( 215 | PlainMonthDay.from("+0019761118T15:23:30.1+0000").toString() 216 | ).toBe("11-18"); 217 | expect( 218 | PlainMonthDay.from("+0019761118T152330.1+00:00").toString() 219 | ).toBe("11-18"); 220 | expect(PlainMonthDay.from("+0019761118T152330.1+0000").toString()).toBe( 221 | "11-18" 222 | ); 223 | }); 224 | it("optional parts", () => { 225 | expect(PlainMonthDay.from("1976-11-18T15:23").toString()).toBe("11-18"); 226 | expect(PlainMonthDay.from("1976-11-18T15").toString()).toBe("11-18"); 227 | expect(PlainMonthDay.from("1976-11-18").toString()).toBe("11-18"); 228 | }); 229 | it("RFC 3339 month-day syntax", () => { 230 | expect(PlainMonthDay.from("--11-18").toString()).toBe("11-18"); 231 | expect(PlainMonthDay.from("--1118").toString()).toBe("11-18"); 232 | }); 233 | it("ignores year when determining the ISO reference year from string", () => { 234 | one = PlainMonthDay.from("2019-11-18"); 235 | two = PlainMonthDay.from("1976-11-18"); 236 | expect(one.referenceISOYear).toBe(two.referenceISOYear); 237 | }); 238 | it("no junk at end of string", () => { 239 | expect(() => { 240 | PlainMonthDay.from("11-18junk"); 241 | }).toThrow(); 242 | }); 243 | // it("options may only be an object or undefined", () => { 244 | // [null, 1, "hello", true, Symbol("foo"), 1n].forEach((badOptions) => 245 | // throws( 246 | // () => PlainMonthDay.from({ month: 11, day: 18 }, badOptions), 247 | // TypeError 248 | // ) 249 | // ); 250 | // [{}, () => {}, undefined].forEach((options) => 251 | // equal( 252 | // `${PlainMonthDay.from({ month: 11, day: 18 }, options)}`, 253 | // "11-18" 254 | // ) 255 | // ); 256 | // }); 257 | // describe("Overflow", () => { 258 | // const bad = { month: 1, day: 32 }; 259 | // it("reject", () => 260 | // throws( 261 | // () => PlainMonthDay.from(bad, { overflow: "reject" }), 262 | // RangeError 263 | // )); 264 | // it("constrain", () => { 265 | // equal(`${PlainMonthDay.from(bad)}`, "01-31"); 266 | // equal( 267 | // `${PlainMonthDay.from(bad, { overflow: "constrain" })}`, 268 | // "01-31" 269 | // ); 270 | // }); 271 | // it("throw on bad overflow", () => { 272 | // [new PlainMonthDay(11, 18), { month: 1, day: 1 }, "01-31"].forEach( 273 | // (input) => { 274 | // ["", "CONSTRAIN", "balance", 3, null].forEach((overflow) => 275 | // throws( 276 | // () => PlainMonthDay.from(input, { overflow }), 277 | // RangeError 278 | // ) 279 | // ); 280 | // } 281 | // ); 282 | // }); 283 | // it("constrain has no effect on invalid ISO string", () => { 284 | // throws( 285 | // () => PlainMonthDay.from("13-34", { overflow: "constrain" }), 286 | // RangeError 287 | // ); 288 | // }); 289 | // }); 290 | // describe("Leap day", () => { 291 | // ["reject", "constrain"].forEach((overflow) => 292 | // it(overflow, () => 293 | // equal( 294 | // `${PlainMonthDay.from({ month: 2, day: 29 }, { overflow })}`, 295 | // "02-29" 296 | // ) 297 | // ) 298 | // ); 299 | // it("rejects when year isn't a leap year", () => 300 | // throws( 301 | // () => 302 | // PlainMonthDay.from( 303 | // { month: 2, day: 29, year: 2001 }, 304 | // { overflow: "reject" } 305 | // ), 306 | // RangeError 307 | // )); 308 | // it("constrains non-leap year", () => 309 | // equal( 310 | // `${PlainMonthDay.from( 311 | // { month: 2, day: 29, year: 2001 }, 312 | // { overflow: "constrain" } 313 | // )}`, 314 | // "02-28" 315 | // )); 316 | // }); 317 | // describe("Leap day with calendar", () => { 318 | // it("requires year with calendar", () => 319 | // throws( 320 | // () => 321 | // PlainMonthDay.from( 322 | // { month: 2, day: 29, calendar: "iso8601" }, 323 | // { overflow: "reject" } 324 | // ), 325 | // TypeError 326 | // )); 327 | // it("rejects leap day with non-leap year", () => 328 | // throws( 329 | // () => 330 | // PlainMonthDay.from( 331 | // { month: 2, day: 29, year: 2001, calendar: "iso8601" }, 332 | // { overflow: "reject" } 333 | // ), 334 | // RangeError 335 | // )); 336 | // it("constrains leap day", () => 337 | // equal( 338 | // `${PlainMonthDay.from( 339 | // { month: 2, day: 29, year: 2001, calendar: "iso8601" }, 340 | // { overflow: "constrain" } 341 | // )}`, 342 | // "02-28" 343 | // )); 344 | // it("accepts leap day with monthCode", () => 345 | // equal( 346 | // `${PlainMonthDay.from( 347 | // { monthCode: "M02", day: 29, calendar: "iso8601" }, 348 | // { overflow: "reject" } 349 | // )}`, 350 | // "02-29" 351 | // )); 352 | // }); 353 | // it("object must contain at least the required correctly-spelled properties", () => { 354 | // throws(() => PlainMonthDay.from({}), TypeError); 355 | // throws(() => PlainMonthDay.from({ months: 12, day: 31 }), TypeError); 356 | // }); 357 | // it("incorrectly-spelled properties are ignored", () => { 358 | // equal( 359 | // `${PlainMonthDay.from({ month: 12, day: 1, days: 31 })}`, 360 | // "12-01" 361 | // ); 362 | // }); 363 | }); 364 | describe("getters", () => { 365 | md = new PlainMonthDay(1, 15); 366 | it("(1-15).monthCode === '1'", () => { 367 | expect(md.monthCode).toBe("M01"); 368 | }); 369 | it("(1-15).day === '15'", () => { 370 | expect(md.day.toString()).toBe("15"); 371 | }); 372 | // it("month is undefined", () => equal(md.month, undefined)); 373 | }); 374 | describe(".with()", () => { 375 | md = PlainMonthDay.from("01-22"); 376 | // it("with(12-)", () => equal(`${md.with({ monthCode: "M12" })}`, "12-22")); 377 | it("with(12-)", () => { 378 | expect(md.with({ month: 12 }).toString()).toBe("12-22"); 379 | }); 380 | it("with(-15)", () => { 381 | expect(md.with({ day: 15 }).toString()).toBe("01-15"); 382 | }); 383 | }); 384 | }); 385 | describe("MonthDay.with()", () => { 386 | md = PlainMonthDay.from("01-15"); 387 | it("with({day})", () => { 388 | expect(md.with({ day: 3 }).toString()).toBe("01-03"); 389 | }); 390 | it("with({month})", () => { 391 | expect(md.with({ month: 12 }).toString()).toBe("12-15"); 392 | }); 393 | xit("with({monthCode})", () => {}); 394 | // equal(`${md.with({ monthCode: "M12" })}`, "12-15")); 395 | xit("with({month}) not accepted", () => { 396 | expect(() => { 397 | md.with({ month: 12 }); 398 | }).toThrow(); 399 | }); 400 | xit("with({month, monthCode}) accepted", () => {}); 401 | // equal(`${md.with({ month: 12, monthCode: "M12" })}`, "12-15")); 402 | xit("month and monthCode must agree", () => { 403 | // throws(() => md.with({ month: 12, monthCode: "M11" }), RangeError); 404 | }); 405 | xit("with({year, month}) accepted", () => {}); 406 | // equal(`${md.with({ year: 2000, month: 12 })}`, "12-15")); 407 | xit("throws on bad overflow", () => { 408 | // ["", "CONSTRAIN", "balance", 3, null].forEach((overflow) => 409 | // throws(() => md.with({ day: 1 }, { overflow }), RangeError) 410 | // ); 411 | }); 412 | xit("throws with calendar property", () => { 413 | // throws(() => md.with({ day: 1, calendar: "iso8601" }), TypeError); 414 | }); 415 | xit("throws with timeZone property", () => { 416 | // throws(() => md.with({ day: 1, timeZone: "UTC" }), TypeError); 417 | }); 418 | // it("options may only be an object or undefined", () => { 419 | // [null, 1, "hello", true, Symbol("foo"), 1n].forEach((badOptions) => 420 | // throws(() => md.with({ day: 1 }, badOptions), TypeError) 421 | // ); 422 | // [{}, () => {}, undefined].forEach((options) => 423 | // equal(`${md.with({ day: 1 }, options)}`, "01-01") 424 | // ); 425 | // }); 426 | // it("object must contain at least one correctly-spelled property", () => { 427 | // throws(() => md.with({}), TypeError); 428 | // throws(() => md.with({ months: 12 }), TypeError); 429 | // }); 430 | // it("incorrectly-spelled properties are ignored", () => { 431 | // equal(`${md.with({ monthCode: "M12", days: 1 })}`, "12-15"); 432 | // }); 433 | // it("year is ignored when determining ISO reference year", () => { 434 | // equal( 435 | // md.with({ year: 1900 }).getISOFields().isoYear, 436 | // md.getISOFields().isoYear 437 | // ); 438 | // }); 439 | }); 440 | describe("MonthDay.equals()", () => { 441 | md1 = PlainMonthDay.from("01-22"); 442 | md2 = PlainMonthDay.from("12-15"); 443 | it("equal", () => { 444 | expect(md1).toBe(md1); 445 | }); 446 | it("unequal", () => { 447 | expect(md1).not.toBe(md2); 448 | }); 449 | xit("casts argument", () => { 450 | // assert(md1.equals("01-22")); 451 | // assert(md1.equals({ month: 1, day: 22 })); 452 | }); 453 | // it("object must contain at least the required properties", () => { 454 | // throws(() => md1.equals({ month: 1 }), TypeError); 455 | // }); 456 | xit("takes [[ISOYear]] into account", () => { 457 | // const iso = Temporal.Calendar.from("iso8601"); 458 | // const md1 = new PlainMonthDay(1, 1, iso, 1972); 459 | // const md2 = new PlainMonthDay(1, 1, iso, 2000); 460 | // assert(!md1.equals(md2)); 461 | }); 462 | }); 463 | // describe("Comparison operators don't work", () => { 464 | // const md1 = PlainMonthDay.from("02-13"); 465 | // const md1again = PlainMonthDay.from("02-13"); 466 | // const md2 = PlainMonthDay.from("11-18"); 467 | // it("=== is object equality", () => equal(md1, md1)); 468 | // it("!== is object equality", () => notEqual(md1, md1again)); 469 | // it("<", () => throws(() => md1 < md2)); 470 | // it(">", () => throws(() => md1 > md2)); 471 | // it("<=", () => throws(() => md1 <= md2)); 472 | // it(">=", () => throws(() => md1 >= md2)); 473 | // }); 474 | describe("MonthDay.toPlainDate()", () => { 475 | md = PlainMonthDay.from("01-22"); 476 | // it("doesn't take a primitive argument", () => { 477 | // [2002, "2002", false, 2002n, Symbol("2002"), null].forEach((bad) => { 478 | // throws(() => md.toPlainDate(bad), TypeError); 479 | // }); 480 | // }); 481 | it("takes year", () => { 482 | // it("takes an object argument with year property", () => { 483 | // equal(`${md.toPlainDate({ year: 2002 })}`, "2002-01-22"); 484 | expect(md.toPlainDate(2002).toString()).toBe("2002-01-22"); 485 | }); 486 | xit("needs at least a year property on the object in the ISO calendar", () => { 487 | // throws(() => md.toPlainDate({ something: "nothing" }), TypeError); 488 | }); 489 | xit("constrains if the MonthDay doesn't exist in the year", () => { 490 | // const leapDay = PlainMonthDay.from("02-29"); 491 | // equal(`${leapDay.toPlainDate({ year: 2019 })}`, "2019-02-28"); 492 | // equal( 493 | // `${leapDay.toPlainDate({ year: 2019 }, { overflow: "constrain" })}`, 494 | // "2019-02-28" 495 | // ); 496 | }); 497 | }); 498 | describe("MonthDay.toString()", () => { 499 | // const md1 = PlainMonthDay.from("11-18"); 500 | // const md2 = PlainMonthDay.from({ 501 | // monthCode: "M11", 502 | // day: 18, 503 | // calendar: "gregory", 504 | // }); 505 | it("new MonthDay(11, 18).toString()", () => { 506 | expect(new PlainMonthDay(11, 18).toString()).toBe("11-18"); 507 | }); 508 | xit("shows only non-ISO calendar if calendarName = auto", () => { 509 | // equal(md1.toString({ calendarName: "auto" }), "11-18"); 510 | // equal(md2.toString({ calendarName: "auto" }), "1972-11-18[u-ca=gregory]"); 511 | }); 512 | xit("shows ISO calendar if calendarName = always", () => { 513 | // equal(md1.toString({ calendarName: "always" }), "11-18[u-ca=iso8601]"); 514 | }); 515 | xit("omits non-ISO calendar, but not year, if calendarName = never", () => { 516 | // equal(md1.toString({ calendarName: "never" }), "11-18"); 517 | // equal(md2.toString({ calendarName: "never" }), "1972-11-18"); 518 | }); 519 | xit("default is calendar = auto", () => { 520 | // equal(md1.toString(), "11-18"); 521 | // equal(md2.toString(), "1972-11-18[u-ca=gregory]"); 522 | }); 523 | // it("throws on invalid calendar", () => { 524 | // ["ALWAYS", "sometimes", false, 3, null].forEach((calendarName) => { 525 | // throws(() => md1.toString({ calendarName }), RangeError); 526 | // }); 527 | // }); 528 | }); 529 | // describe("monthDay.getISOFields() works", () => { 530 | // const md1 = PlainMonthDay.from("11-18"); 531 | // const fields = md1.getISOFields(); 532 | // it("fields", () => { 533 | // equal(fields.isoMonth, 11); 534 | // equal(fields.isoDay, 18); 535 | // equal(fields.calendar.id, "iso8601"); 536 | // equal(typeof fields.isoYear, "number"); 537 | // }); 538 | // it("enumerable", () => { 539 | // const fields2 = { ...fields }; 540 | // equal(fields2.isoMonth, 11); 541 | // equal(fields2.isoDay, 18); 542 | // equal(fields2.calendar, fields.calendar); 543 | // equal(typeof fields2.isoYear, "number"); 544 | // }); 545 | // it("as input to constructor", () => { 546 | // const md2 = new PlainMonthDay( 547 | // fields.isoMonth, 548 | // fields.isoDay, 549 | // fields.calendar, 550 | // fields.isoYear 551 | // ); 552 | // assert(md2.equals(md1)); 553 | // }); 554 | // }); 555 | }); 556 | -------------------------------------------------------------------------------- /assembly/__tests__/plainyearmonth.spec.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { Duration, DurationLike } from "../duration"; 4 | import { Overflow, TimeComponent } from "../enums"; 5 | import { PlainDate } from "../plaindate"; 6 | import { PlainDateTime } from "../plaindatetime"; 7 | import { PlainYearMonth, YearMonthLike } from "../plainyearmonth"; 8 | 9 | let ym: PlainYearMonth, 10 | ym1: PlainYearMonth, 11 | ym2: PlainYearMonth, 12 | orig: PlainYearMonth, 13 | actu: PlainYearMonth, 14 | plainDate1: PlainYearMonth, 15 | plainDate2: PlainYearMonth, 16 | one: PlainYearMonth, 17 | two: PlainYearMonth, 18 | nov94: PlainYearMonth, 19 | jun13: PlainYearMonth, 20 | feb21: PlainYearMonth, 21 | feb20: PlainYearMonth; 22 | 23 | let diff: Duration; 24 | 25 | describe("YearMonth", () => { 26 | describe("Construction", () => { 27 | ym = new PlainYearMonth(1976, 11); 28 | // it("YearMonth can be constructed", () => { 29 | // assert(ym); 30 | // expect(typeof ym, "object"); 31 | // }); 32 | it("ym.year is 1976", () => { 33 | expect(ym.year).toBe(1976); 34 | }); 35 | it("ym.month is 11", () => { 36 | expect(ym.month).toBe(11); 37 | }); 38 | xit('ym.monthCode is "M11"', () => { 39 | // expect(ym.monthCode, "M11"); 40 | }); 41 | it("ym.daysInMonth is 30", () => { 42 | expect(ym.daysInMonth).toBe(30); 43 | }); 44 | it("ym.daysInYear is 366", () => { 45 | expect(ym.daysInYear).toBe(366); 46 | }); 47 | it("ym.monthsInYear is 12", () => { 48 | expect(ym.monthsInYear).toBe(12); 49 | }); 50 | describe(".from()", () => { 51 | it("YearMonth.from(2019-10) == 2019-10", () => { 52 | expect(PlainYearMonth.from("2019-10").toString()).toBe("2019-10"); 53 | }); 54 | it("YearMonth.from(2019-10-01T09:00:00Z) == 2019-10", () => { 55 | expect(PlainYearMonth.from("2019-10-01T09:00:00Z").toString()).toBe( 56 | "2019-10" 57 | ); 58 | }); 59 | it("YearMonth.from('1976-11') == (1976-11)", () => { 60 | expect(PlainYearMonth.from("1976-11").toString()).toBe("1976-11"); 61 | }); 62 | it("YearMonth.from('1976-11-18') == (1976-11)", () => { 63 | expect(PlainYearMonth.from("1976-11-18").toString()).toBe("1976-11"); 64 | }); 65 | xit("can be constructed with monthCode and without month", () => { 66 | // expect( 67 | // PlainYearMonth.from({ year: 2019, monthCode: "M11" }).toString() 68 | // ).toBe("2019-11"); 69 | }); 70 | it("can be constructed with month and without monthCode", () => { 71 | expect(PlainYearMonth.from({ year: 2019, month: 11 }).toString()).toBe( 72 | "2019-11" 73 | ); 74 | }); 75 | xit("month and monthCode must agree", () => 76 | // expect(() => { 77 | // PlainYearMonth.from({ year: 2019, month: 11, monthCode: "M12" }); 78 | // }).toThrow() 79 | {}); 80 | it("ignores day when determining the ISO reference day from year/month", () => { 81 | // const one = PlainYearMonth.from({ year: 2019, month: 11, day: 1 }); 82 | // const two = PlainYearMonth.from({ year: 2019, month: 11, day: 2 }); 83 | // expect(one.getISOFields().isoDay).toBe(two.getISOFields().isoDay); 84 | }); 85 | xit("ignores day when determining the ISO reference day from year/monthCode", () => { 86 | // const one = PlainYearMonth.from({ 87 | // year: 2019, 88 | // monthCode: "M11", 89 | // day: 1, 90 | // }); 91 | // const two = PlainYearMonth.from({ 92 | // year: 2019, 93 | // monthCode: "M11", 94 | // day: 2, 95 | // }); 96 | // expect(one.getISOFields().isoDay, two.getISOFields().isoDay); 97 | }); 98 | xit("ignores day when determining the ISO reference day from era/eraYear/month", () => { 99 | // const one = PlainYearMonth.from({ 100 | // era: "ce", 101 | // eraYear: 2019, 102 | // month: 11, 103 | // day: 1, 104 | // calendar: "gregory", 105 | // }); 106 | // const two = PlainYearMonth.from({ 107 | // era: "ce", 108 | // eraYear: 2019, 109 | // month: 11, 110 | // day: 2, 111 | // calendar: "gregory", 112 | // }); 113 | // expect(one.getISOFields().isoDay, two.getISOFields().isoDay); 114 | }); 115 | xit("ignores day when determining the ISO reference day from era/eraYear/monthCode", () => { 116 | // const one = PlainYearMonth.from({ 117 | // era: "ce", 118 | // eraYear: 2019, 119 | // monthCode: "M11", 120 | // day: 1, 121 | // calendar: "gregory", 122 | // }); 123 | // const two = PlainYearMonth.from({ 124 | // era: "ce", 125 | // eraYear: 2019, 126 | // monthCode: "M11", 127 | // day: 2, 128 | // calendar: "gregory", 129 | // }); 130 | // expect(one.getISOFields().isoDay, two.getISOFields().isoDay); 131 | }); 132 | xit("YearMonth.from(2019-11) is not the same object", () => { 133 | orig = new PlainYearMonth(2019, 11); 134 | actu = PlainYearMonth.from(orig); 135 | expect(actu).not.toBe(orig); 136 | }); 137 | xit("ignores day when determining the ISO reference day from other Temporal object", () => { 138 | // plainDate1 = PlainDate.from("1976-11-01"); 139 | // plainDate2 = PlainDate.from("1976-11-18"); 140 | // one = PlainYearMonth.from(plainDate1); 141 | // two = PlainYearMonth.from(plainDate2); 142 | // expect(one.getISOFields().isoDay, two.getISOFields().isoDay); 143 | }); 144 | it("YearMonth.from({ year: 2019 }) throws", () => { 145 | expect(() => { 146 | PlainYearMonth.from({ year: 2019 }); 147 | }).toThrow(); 148 | }); 149 | it("YearMonth.from({ month: 6 }) throws", () => { 150 | expect(() => { 151 | PlainYearMonth.from({ month: 6 }); 152 | }).toThrow(); 153 | }); 154 | xit('YearMonth.from({ monthCode: "M06" }) throws', () => { 155 | // expect(() => PlainYearMonth.from({ monthCode: "M06" })).toThrow(); 156 | }); 157 | it("YearMonth.from({}) throws", () => { 158 | expect(() => { 159 | PlainYearMonth.from({}); 160 | }); 161 | }); 162 | // it("YearMonth.from(required prop undefined) throws", () => { 163 | // expect(() => { 164 | // PlainYearMonth.from({ year: null, month: 6 }); 165 | // }).toThrows(); 166 | // }); 167 | xit("YearMonth.from(number) is converted to string", () => 168 | // assert( 169 | // PlainYearMonth.from(201906).expects(PlainYearMonth.from("201906")) 170 | // ) 171 | {}); 172 | it("basic format", () => { 173 | expect(PlainYearMonth.from("197611").toString()).toBe("1976-11"); 174 | expect(PlainYearMonth.from("+00197611").toString()).toBe("1976-11"); 175 | }); 176 | it("variant minus sign", () => { 177 | expect(PlainYearMonth.from("\u2212009999-11").toString()).toBe( 178 | "-009999-11" 179 | ); 180 | expect( 181 | PlainYearMonth.from("1976-11-18T15:23:30.1\u221202:00").toString() 182 | ).toBe("1976-11"); 183 | }); 184 | it("mixture of basic and extended format", () => { 185 | expect( 186 | PlainYearMonth.from("1976-11-18T152330.1+00:00").toString() 187 | ).toBe("1976-11"); 188 | expect( 189 | PlainYearMonth.from("19761118T15:23:30.1+00:00").toString() 190 | ).toBe("1976-11"); 191 | expect( 192 | PlainYearMonth.from("1976-11-18T15:23:30.1+0000").toString() 193 | ).toBe("1976-11"); 194 | expect(PlainYearMonth.from("1976-11-18T152330.1+0000").toString()).toBe( 195 | "1976-11" 196 | ); 197 | expect(PlainYearMonth.from("19761118T15:23:30.1+0000").toString()).toBe( 198 | "1976-11" 199 | ); 200 | expect(PlainYearMonth.from("19761118T152330.1+00:00").toString()).toBe( 201 | "1976-11" 202 | ); 203 | expect(PlainYearMonth.from("19761118T152330.1+0000").toString()).toBe( 204 | "1976-11" 205 | ); 206 | expect( 207 | PlainYearMonth.from("+001976-11-18T152330.1+00:00").toString() 208 | ).toBe("1976-11"); 209 | expect( 210 | PlainYearMonth.from("+0019761118T15:23:30.1+00:00").toString() 211 | ).toBe("1976-11"); 212 | expect( 213 | PlainYearMonth.from("+001976-11-18T15:23:30.1+0000").toString() 214 | ).toBe("1976-11"); 215 | expect( 216 | PlainYearMonth.from("+001976-11-18T152330.1+0000").toString() 217 | ).toBe("1976-11"); 218 | expect( 219 | PlainYearMonth.from("+0019761118T15:23:30.1+0000").toString() 220 | ).toBe("1976-11"); 221 | expect( 222 | PlainYearMonth.from("+0019761118T152330.1+00:00").toString() 223 | ).toBe("1976-11"); 224 | expect( 225 | PlainYearMonth.from("+0019761118T152330.1+0000").toString() 226 | ).toBe("1976-11"); 227 | }); 228 | it("optional components", () => { 229 | expect(PlainYearMonth.from("1976-11-18T15:23").toString()).toBe( 230 | "1976-11" 231 | ); 232 | expect(PlainYearMonth.from("1976-11-18T15").toString()).toBe("1976-11"); 233 | expect(PlainYearMonth.from("1976-11-18").toString()).toBe("1976-11"); 234 | }); 235 | it("ignores day when determining the ISO reference day from string", () => { 236 | one = PlainYearMonth.from("1976-11-01"); 237 | two = PlainYearMonth.from("1976-11-18"); 238 | expect(one.referenceISODay).toBe(two.referenceISODay); 239 | }); 240 | it("no junk at end of string", () => { 241 | expect(() => { 242 | PlainYearMonth.from("1976-11junk"); 243 | }).toThrow(); 244 | }); 245 | // it("options may only be an object or undefined", () => { 246 | // [null, 1, "hello", true, Symbol("foo"), 1n].forEach((badOptions) => 247 | // expect( 248 | // () => PlainYearMonth.from({ year: 1976, month: 11 }, badOptions), 249 | // TypeError 250 | // ) 251 | // ); 252 | // [{}, () => {}, undefined].forEach((options) => 253 | // expect( 254 | // PlainYearMonth.from({ year: 1976, month: 11 }, options).toString() 255 | // ).toBe("1976-11") 256 | // ); 257 | // }); 258 | describe("Overflow", () => { 259 | // const bad = { year: 2019, month: 13 }; 260 | xit("reject", () => 261 | // expect( 262 | // () => PlainYearMonth.from(bad, { overflow: "reject" }), 263 | // RangeError 264 | // ) 265 | {}); 266 | xit("constrain", () => { 267 | // expect(PlainYearMonth.from(bad).toString()).toBe("2019-12"); 268 | // expect( 269 | // PlainYearMonth.from(bad, Overflow.Constrain).toString() 270 | // ).toBe("2019-12"); 271 | }); 272 | xit("throw on bad overflow", () => { 273 | // [ 274 | // new PlainYearMonth(2019, 1), 275 | // { year: 2019, month: 1 }, 276 | // "2019-01", 277 | // ].forEach((input) => { 278 | // ["", "CONSTRAIN", "balance", 3, null].forEach((overflow) => 279 | // expect(() => PlainYearMonth.from(input, { overflow }), RangeError) 280 | // ); 281 | // }); 282 | }); 283 | xit("constrain has no effect on invalid ISO string", () => { 284 | // expect( 285 | // () => PlainYearMonth.from("2020-13", { overflow: "constrain" }), 286 | // RangeError 287 | // ); 288 | }); 289 | }); 290 | // it("object must contain at least the required correctly-spelled properties", () => { 291 | // expect(() => PlainYearMonth.from({}), TypeError); 292 | // expect( 293 | // () => PlainYearMonth.from({ year: 1976, months: 11 }), 294 | // TypeError 295 | // ); 296 | // }); 297 | // it("incorrectly-spelled properties are ignored", () => { 298 | // expect( 299 | // PlainYearMonth.from({ year: 1976, month: 11, months: 12 }).toString() 300 | // ).toBe("1976-11"); 301 | // }); 302 | }); 303 | describe(".with()", () => { 304 | ym = PlainYearMonth.from("2019-10"); 305 | it("with(2020)", () => { 306 | expect(ym.with({ year: 2020 }).toString()).toBe("2020-10"); 307 | }); 308 | it("with(09)", () => { 309 | expect(ym.with({ month: 9 }).toString()).toBe("2019-09"); 310 | }); 311 | xit("with(monthCode)", () => { 312 | // expect(ym.with({ monthCode: "M09" }).toString()).toBe("2019-09"); 313 | }); 314 | xit("month and monthCode must agree", () => { 315 | // expect(() => { 316 | // ym.with({ month: 9, monthCode: "M10" }); 317 | // }).toThrow(); 318 | }); 319 | }); 320 | }); 321 | 322 | describe("YearMonth.with() works", () => { 323 | ym = PlainYearMonth.from("2019-10"); 324 | it("YearMonth.with({month}) works", () => { 325 | expect(ym.with({ month: 12 }).toString()).toBe("2019-12"); 326 | }); 327 | it("YearMonth.with({year}) works", () => { 328 | expect(ym.with({ year: 2025 }).toString()).toBe("2025-10"); 329 | }); 330 | xit("throws with calendar property", () => { 331 | // throws(() => ym.with({ year: 2021, calendar: 'iso8601' }), TypeError); 332 | }); 333 | xit("throws with timeZone property", () => { 334 | // throws(() => ym.with({ year: 2021, timeZone: 'UTC' }), TypeError); 335 | }); 336 | // it('options may only be an object or undefined', () => { 337 | // [null, 1, 'hello', true, Symbol('foo'), 1n].forEach((badOptions) => 338 | // throws(() => ym.with({ year: 2020 }, badOptions), TypeError) 339 | // ); 340 | // [{}, () => {}, undefined].forEach((options) => expect("${ym.with({ year: 2020 }, options)}", '2020-10')); 341 | // }); 342 | // it('object must contain at least one correctly-spelled property', () => { 343 | // throws(() => ym.with({}), TypeError); 344 | // throws(() => ym.with({ months: 12 }), TypeError); 345 | // }); 346 | // it('incorrectly-spelled properties are ignored', () => { 347 | // expect("${ym.with({ month: 1, years: 2020 })}", '2019-01'); 348 | // }); 349 | // it('day is ignored when determining ISO reference day', () => { 350 | // expect(ym.with({ year: ym.year, day: 31 }).getISOFields().isoDay, ym.getISOFields().isoDay); 351 | // }); 352 | }); 353 | 354 | describe("YearMonth.compare() works", () => { 355 | nov94 = PlainYearMonth.from("1994-11"); 356 | jun13 = PlainYearMonth.from("2013-06"); 357 | it("expect", () => { 358 | expect(PlainYearMonth.compare(nov94, nov94)).toBe(0); 359 | }); 360 | it("smaller/larger", () => { 361 | expect(PlainYearMonth.compare(nov94, jun13)).toBe(-1); 362 | }); 363 | it("larger/smaller", () => { 364 | expect(PlainYearMonth.compare(jun13, nov94)).toBe(1); 365 | }); 366 | xit("casts first argument", () => { 367 | // expect(PlainYearMonth.compare({ year: 1994, month: 11 }, jun13), -1); 368 | // expect(PlainYearMonth.compare('1994-11', jun13), -1); 369 | }); 370 | xit("casts second argument", () => { 371 | // expect(PlainYearMonth.compare(nov94, { year: 2013, month: 6 }), -1); 372 | // expect(PlainYearMonth.compare(nov94, '2013-06'), -1); 373 | }); 374 | // it('object must contain at least the required properties', () => { 375 | // throws(() => PlainYearMonth.compare({ year: 1994 }, jun13), TypeError); 376 | // throws(() => PlainYearMonth.compare(nov94, { year: 2013 }), TypeError); 377 | // }); 378 | it("takes [[ISODay]] into account", () => { 379 | // const iso = Temporal.Calendar.from('iso8601'); 380 | const ym1 = new PlainYearMonth(2000, 1, 1); 381 | const ym2 = new PlainYearMonth(2000, 1, 2); 382 | expect(PlainYearMonth.compare(ym1, ym2)).toBe(-1); 383 | }); 384 | }); 385 | 386 | describe("YearMonth.equals() works", () => { 387 | nov94 = PlainYearMonth.from("1994-11"); 388 | jun13 = PlainYearMonth.from("2013-06"); 389 | it("equal", () => { 390 | expect(nov94.equals(nov94)).toBe(true); 391 | }); 392 | it("unequal", () => { 393 | expect(nov94.equals(jun13)).toBe(false); 394 | }); 395 | xit("casts argument", () => { 396 | // assert(nov94.equals({ year: 1994, month: 11 })); 397 | // assert(nov94.equals("1994-11")); 398 | }); 399 | // it("object must contain at least the required properties", () => { 400 | // throws(() => nov94.equals({ year: 1994 }), TypeError); 401 | }); 402 | it("takes [[ISODay]] into account", () => { 403 | ym1 = new PlainYearMonth(2000, 1, 1); 404 | ym2 = new PlainYearMonth(2000, 1, 2); 405 | expect(ym1.equals(ym2)).toBe(false); 406 | }); 407 | 408 | describe("YearMonth.until() works", () => { 409 | nov94 = PlainYearMonth.from("1994-11"); 410 | jun13 = PlainYearMonth.from("2013-06"); 411 | diff = nov94.until(jun13); 412 | it( 413 | "(" + 414 | jun13.toString() + 415 | ").until((" + 416 | nov94.toString() + 417 | ")) == (" + 418 | nov94.toString() + 419 | ").until((" + 420 | jun13.toString() + 421 | ")).negated()", 422 | () => { 423 | expect(jun13.until(nov94).toString()).toBe(diff.negated().toString()) 424 | } 425 | ); 426 | 427 | it( 428 | "(" + 429 | nov94.toString() + 430 | ").add(" + 431 | diff.toString() + 432 | ") == (" + 433 | jun13.toString() + 434 | ")", 435 | () => { 436 | expect(nov94.add(diff).equals(jun13)).toBe(true); 437 | } 438 | ); 439 | it( 440 | "(" + 441 | jun13.toString() + 442 | ").subtract(" + 443 | diff.toString() + 444 | ") == (" + 445 | nov94.toString() + 446 | ")", 447 | () => { 448 | expect(jun13.subtract(diff).equals(nov94)).toBe(true); 449 | } 450 | ); 451 | it( 452 | "(" + 453 | nov94.toString() + 454 | ").until((" + 455 | jun13.toString() + 456 | ")) == (" + 457 | jun13.toString() + 458 | ").since((" + 459 | nov94.toString() + 460 | "))", 461 | () => { 462 | expect(diff.toString()).toBe(jun13.since(nov94).toString()); 463 | } 464 | ); 465 | it("casts argument", () => { 466 | expect(`${nov94.until({ year: 2013, month: 6 })}`).toBe(diff.toString()); 467 | expect(`${nov94.until("2013-06")}`).toBe(diff.toString()); 468 | }); 469 | // it("object must contain at least the required properties", () => { 470 | // throws(() => nov94.until({ year: 2013 }), TypeError); 471 | // }); 472 | feb20 = PlainYearMonth.from("2020-02"); 473 | feb21 = PlainYearMonth.from("2021-02"); 474 | // it("defaults to returning years", () => { 475 | // equal("${feb20.until(feb21)}", "P1Y"); 476 | // equal("${feb20.until(feb21, { largestUnit: "auto" })}", "P1Y"); 477 | // equal("${feb20.until(feb21, { largestUnit: "years" })}", "P1Y"); 478 | // }); 479 | it("can return months", () => { 480 | expect(feb20.until(feb21, TimeComponent.Months).toString()).toBe("P12M"); 481 | }); 482 | it("cannot return lower units", () => { 483 | expect(() => { 484 | feb20.until(feb21, TimeComponent.Weeks); 485 | }).toThrow(); 486 | expect(() => { 487 | feb20.until(feb21, TimeComponent.Days); 488 | }).toThrow(); 489 | expect(() => { 490 | feb20.until(feb21, TimeComponent.Hours); 491 | }).toThrow(); 492 | expect(() => { 493 | feb20.until(feb21, TimeComponent.Minutes); 494 | }).toThrow(); 495 | expect(() => { 496 | feb20.until(feb21, TimeComponent.Seconds); 497 | }).toThrow(); 498 | expect(() => { 499 | feb20.until(feb21, TimeComponent.Milliseconds); 500 | }).toThrow(); 501 | expect(() => { 502 | feb20.until(feb21, TimeComponent.Microseconds); 503 | }).toThrow(); 504 | expect(() => { 505 | feb20.until(feb21, TimeComponent.Nanoseconds); 506 | }).toThrow(); 507 | }); 508 | // it("no two different calendars", () => { 509 | // const ym1 = new PlainYearMonth(2000, 1); 510 | // const ym2 = new PlainYearMonth( 511 | // 2000, 512 | // 1, 513 | // Temporal.Calendar.from("japanese") 514 | // ); 515 | // throws(() => ym1.until(ym2), RangeError); 516 | // }); 517 | // it("options may only be an object or undefined", () => { 518 | // [null, 1, "hello", true, Symbol("foo"), 1n].forEach((badOptions) => 519 | // throws(() => feb20.until(feb21, badOptions), TypeError) 520 | // ); 521 | // [{}, () => {}, undefined].forEach((options) => 522 | // equal("${feb20.until(feb21, options)}", "P1Y") 523 | // ); 524 | // }); 525 | // const earlier = PlainYearMonth.from("2019-01"); 526 | // const later = PlainYearMonth.from("2021-09"); 527 | // it("throws on disallowed or invalid smallestUnit", () => { 528 | // [ 529 | // "era", 530 | // "weeks", 531 | // "days", 532 | // "hours", 533 | // "minutes", 534 | // "seconds", 535 | // "milliseconds", 536 | // "microseconds", 537 | // "nanoseconds", 538 | // "nonsense", 539 | // ].forEach((smallestUnit) => { 540 | // throws(() => earlier.until(later, { smallestUnit }), RangeError); 541 | // }); 542 | // }); 543 | // it("throws if smallestUnit is larger than largestUnit", () => { 544 | // throws( 545 | // () => 546 | // earlier.until(later, { 547 | // largestUnit: "months", 548 | // smallestUnit: "years", 549 | // }), 550 | // RangeError 551 | // ); 552 | // }); 553 | // it("throws on invalid roundingMode", () => { 554 | // throws(() => earlier.until(later, { roundingMode: "cile" }), RangeError); 555 | // }); 556 | // const incrementOneNearest = [ 557 | // ["years", "P3Y"], 558 | // ["months", "P2Y8M"], 559 | // ]; 560 | // incrementOneNearest.forEach(([smallestUnit, expected]) => { 561 | // const roundingMode = "halfExpand"; 562 | // it("rounds to nearest ${smallestUnit}", () => { 563 | // equal( 564 | // "${earlier.until(later, { smallestUnit, roundingMode })}", 565 | // expected 566 | // ); 567 | // equal( 568 | // "${later.until(earlier, { smallestUnit, roundingMode })}", 569 | // "-${expected}" 570 | // ); 571 | // }); 572 | // }); 573 | // const incrementOneCeil = [ 574 | // ["years", "P3Y", "-P2Y"], 575 | // ["months", "P2Y8M", "-P2Y8M"], 576 | // ]; 577 | // incrementOneCeil.forEach( 578 | // ([smallestUnit, expectedPositive, expectedNegative]) => { 579 | // const roundingMode = "ceil"; 580 | // it("rounds up to ${smallestUnit}", () => { 581 | // equal( 582 | // "${earlier.until(later, { smallestUnit, roundingMode })}", 583 | // expectedPositive 584 | // ); 585 | // equal( 586 | // "${later.until(earlier, { smallestUnit, roundingMode })}", 587 | // expectedNegative 588 | // ); 589 | // }); 590 | // } 591 | // ); 592 | // const incrementOneFloor = [ 593 | // ["years", "P2Y", "-P3Y"], 594 | // ["months", "P2Y8M", "-P2Y8M"], 595 | // ]; 596 | // incrementOneFloor.forEach( 597 | // ([smallestUnit, expectedPositive, expectedNegative]) => { 598 | // const roundingMode = "floor"; 599 | // it("rounds down to ${smallestUnit}", () => { 600 | // equal( 601 | // "${earlier.until(later, { smallestUnit, roundingMode })}", 602 | // expectedPositive 603 | // ); 604 | // equal( 605 | // "${later.until(earlier, { smallestUnit, roundingMode })}", 606 | // expectedNegative 607 | // ); 608 | // }); 609 | // } 610 | // ); 611 | // const incrementOneTrunc = [ 612 | // ["years", "P2Y"], 613 | // ["months", "P2Y8M"], 614 | // ]; 615 | // incrementOneTrunc.forEach(([smallestUnit, expected]) => { 616 | // const roundingMode = "trunc"; 617 | // it("truncates to ${smallestUnit}", () => { 618 | // equal( 619 | // "${earlier.until(later, { smallestUnit, roundingMode })}", 620 | // expected 621 | // ); 622 | // equal( 623 | // "${later.until(earlier, { smallestUnit, roundingMode })}", 624 | // "-${expected}" 625 | // ); 626 | // }); 627 | // }); 628 | // it("trunc is the default", () => { 629 | // equal("${earlier.until(later, { smallestUnit: "years" })}", "P2Y"); 630 | // equal("${later.until(earlier, { smallestUnit: "years" })}", "-P2Y"); 631 | // }); 632 | // it("rounds to an increment of years", () => { 633 | // equal( 634 | // "${earlier.until(later, { 635 | // smallestUnit: "years", 636 | // roundingIncrement: 4, 637 | // roundingMode: "halfExpand", 638 | // })}", 639 | // "P4Y" 640 | // ); 641 | // }); 642 | // it("rounds to an increment of months", () => { 643 | // equal( 644 | // "${earlier.until(later, { 645 | // smallestUnit: "months", 646 | // roundingIncrement: 5, 647 | // })}", 648 | // "P2Y5M" 649 | // ); 650 | // equal( 651 | // "${earlier.until(later, { 652 | // largestUnit: "months", 653 | // smallestUnit: "months", 654 | // roundingIncrement: 10, 655 | // })}", 656 | // "P30M" 657 | // ); 658 | // }); 659 | // it("accepts singular units", () => { 660 | // equal( 661 | // "${earlier.until(later, { largestUnit: "year" })}", 662 | // "${earlier.until(later, { largestUnit: "years" })}" 663 | // ); 664 | // equal( 665 | // "${earlier.until(later, { smallestUnit: "year" })}", 666 | // "${earlier.until(later, { smallestUnit: "years" })}" 667 | // ); 668 | // equal( 669 | // "${earlier.until(later, { largestUnit: "month" })}", 670 | // "${earlier.until(later, { largestUnit: "months" })}" 671 | // ); 672 | // equal( 673 | // "${earlier.until(later, { smallestUnit: "month" })}", 674 | // "${earlier.until(later, { smallestUnit: "months" })}" 675 | // ); 676 | // }); 677 | }); 678 | 679 | describe("YearMonth.since() works", () => { 680 | nov94 = PlainYearMonth.from("1994-11"); 681 | jun13 = PlainYearMonth.from("2013-06"); 682 | diff = jun13.since(nov94); 683 | xit( 684 | `(` + 685 | nov94.toString() + 686 | `).since(` + 687 | jun13.toString() + 688 | `) == (` + 689 | jun13.toString() + 690 | `).since((` + 691 | nov94.toString() + 692 | `)).negated()`, 693 | () => { 694 | //expect(nov94.since(jun13).toString()).toBe(`${diff.negated()}`) 695 | } 696 | ); 697 | it( 698 | `(` + nov94.toString() + `).add(${diff}) == (` + jun13.toString() + `)`, 699 | () => { 700 | expect(nov94.add(diff).equals(jun13)).toBe(true); 701 | } 702 | ); 703 | it( 704 | `(` + 705 | jun13.toString() + 706 | `).subtract(${diff}) == (` + 707 | nov94.toString() + 708 | `)`, 709 | () => { 710 | expect(jun13.subtract(diff).equals(nov94)).toBe(true); 711 | } 712 | ); 713 | it( 714 | `(` + 715 | jun13.toString() + 716 | `).since((` + 717 | nov94.toString() + 718 | `)) == (` + 719 | nov94.toString() + 720 | `).until((` + 721 | jun13.toString() + 722 | `))`, 723 | () => { 724 | expect(diff.toString()).toBe(nov94.until(jun13).toString()); 725 | } 726 | ); 727 | xit("casts argument", () => { 728 | //expect(jun13.since({ year: 1994, month: 11 }).toString()).toBe(`${diff}`); 729 | //expect(jun13.since("1994-11").toString()).toBe(`${diff}`); 730 | }); 731 | // it("object must contain at least the required properties", () => { 732 | // throws(() => jun13.since({ year: 1994 }), TypeError); 733 | // }); 734 | feb20 = PlainYearMonth.from("2020-02"); 735 | feb21 = PlainYearMonth.from("2021-02"); 736 | it("defaults to returning years", () => { 737 | expect(feb21.since(feb20).toString()).toBe("P1Y"); 738 | expect(feb21.since(feb20, TimeComponent.Years).toString()).toBe("P1Y"); 739 | }); 740 | it("can return months", () => { 741 | expect(feb21.since(feb20, TimeComponent.Months).toString()).toBe("P12M"); 742 | }); 743 | it("cannot return lower units", () => { 744 | expect(() => { 745 | feb20.since(feb21, TimeComponent.Weeks); 746 | }).toThrow(); 747 | expect(() => { 748 | feb20.since(feb21, TimeComponent.Days); 749 | }).toThrow(); 750 | expect(() => { 751 | feb20.since(feb21, TimeComponent.Hours); 752 | }).toThrow(); 753 | expect(() => { 754 | feb20.since(feb21, TimeComponent.Minutes); 755 | }).toThrow(); 756 | expect(() => { 757 | feb20.since(feb21, TimeComponent.Seconds); 758 | }).toThrow(); 759 | expect(() => { 760 | feb20.since(feb21, TimeComponent.Milliseconds); 761 | }).toThrow(); 762 | expect(() => { 763 | feb20.since(feb21, TimeComponent.Microseconds); 764 | }).toThrow(); 765 | expect(() => { 766 | feb20.since(feb21, TimeComponent.Nanoseconds); 767 | }).toThrow(); 768 | }); 769 | // it('no two different calendars', () => { 770 | // const ym1 = new PlainYearMonth(2000, 1); 771 | // const ym2 = new PlainYearMonth(2000, 1, Temporal.Calendar.from('japanese')); 772 | // throws(() => ym1.since(ym2), RangeError); 773 | // }); 774 | // it('options may only be an object or undefined', () => { 775 | // [null, 1, 'hello', true, Symbol('foo'), 1n].forEach((badOptions) => 776 | // throws(() => feb21.since(feb20, badOptions), TypeError) 777 | // ); 778 | // [{}, () => {}, undefined].forEach((options) =>expect(feb21.since(feb20, options).toString()).toBe('P1Y')); 779 | // }); 780 | // const earlier = PlainYearMonth.from('2019-01'); 781 | // const later = PlainYearMonth.from('2021-09'); 782 | // it('throws on disallowed or invalid smallestUnit', () => { 783 | // [ 784 | // 'era', 785 | // 'weeks', 786 | // 'days', 787 | // 'hours', 788 | // 'minutes', 789 | // 'seconds', 790 | // 'milliseconds', 791 | // 'microseconds', 792 | // 'nanoseconds', 793 | // 'nonsense' 794 | // ].forEach((smallestUnit) => { 795 | // throws(() => later.since(earlier, { smallestUnit }), RangeError); 796 | // }); 797 | // }); 798 | // it('throws if smallestUnit is larger than largestUnit', () => { 799 | // throws(() => later.since(earlier, { largestUnit: 'months', smallestUnit: 'years' }), RangeError); 800 | // }); 801 | // it('throws on invalid roundingMode', () => { 802 | // throws(() => later.since(earlier, { roundingMode: 'cile' }), RangeError); 803 | // }); 804 | // const incrementOneNearest = [ 805 | // ['years', 'P3Y'], 806 | // ['months', 'P2Y8M'] 807 | // ]; 808 | // incrementOneNearest.forEach(([smallestUnit, expected]) => { 809 | // const roundingMode = 'halfExpand'; 810 | // it(`rounds to nearest ${smallestUnit.toString()).toBe(() => { 811 | // expect(later.since(earlier, { smallestUnit, roundingMode }).toString()).toBe(expected); 812 | // expect(earlier.since(later, { smallestUnit, roundingMode }).toString()).toBe(`-${expected}`); 813 | // }); 814 | // }); 815 | // const incrementOneCeil = [ 816 | // ['years', 'P3Y', '-P2Y'], 817 | // ['months', 'P2Y8M', '-P2Y8M'] 818 | // ]; 819 | // incrementOneCeil.forEach(([smallestUnit, expectedPositive, expectedNegative]) => { 820 | // const roundingMode = 'ceil'; 821 | // it(`rounds up to ${smallestUnit.toString()).toBe(() => { 822 | // expect(later.since(earlier, { smallestUnit, roundingMode }).toString()).toBe(expectedPositive); 823 | // expect(earlier.since(later, { smallestUnit, roundingMode }).toString()).toBe(expectedNegative); 824 | // }); 825 | // }); 826 | // const incrementOneFloor = [ 827 | // ['years', 'P2Y', '-P3Y'], 828 | // ['months', 'P2Y8M', '-P2Y8M'] 829 | // ]; 830 | // incrementOneFloor.forEach(([smallestUnit, expectedPositive, expectedNegative]) => { 831 | // const roundingMode = 'floor'; 832 | // it(`rounds down to ${smallestUnit.toString()).toBe(() => { 833 | // expect(later.since(earlier, { smallestUnit, roundingMode }).toString()).toBe(expectedPositive); 834 | // expect(earlier.since(later, { smallestUnit, roundingMode }).toString()).toBe(expectedNegative); 835 | // }); 836 | // }); 837 | // const incrementOneTrunc = [ 838 | // ['years', 'P2Y'], 839 | // ['months', 'P2Y8M'] 840 | // ]; 841 | // incrementOneTrunc.forEach(([smallestUnit, expected]) => { 842 | // const roundingMode = 'trunc'; 843 | // it(`truncates to ${smallestUnit.toString()).toBe(() => { 844 | // expect(later.since(earlier, { smallestUnit, roundingMode }).toString()).toBe(expected); 845 | // expect(earlier.since(later, { smallestUnit, roundingMode }).toString()).toBe(`-${expected}`); 846 | // }); 847 | // }); 848 | // it('trunc is the default', () => { 849 | // expect(later.since(earlier, { smallestUnit: 'years' }).toString()).toBe('P2Y'); 850 | // expect(earlier.since(later, { smallestUnit: 'years' }).toString()).toBe('-P2Y'); 851 | // }); 852 | // it('rounds to an increment of years', () => { 853 | // equal( 854 | // `${later.since(earlier, { smallestUnit: 'years', roundingIncrement: 4, roundingMode: 'halfExpand' })}`, 855 | // 'P4Y' 856 | // ); 857 | // }); 858 | // it('rounds to an increment of months', () => { 859 | // expect(later.since(earlier, { smallestUnit: 'months', roundingIncrement: 5 }).toString()).toBe('P2Y5M'); 860 | // equal( 861 | // `${later.since(earlier, { largestUnit: 'months', smallestUnit: 'months', roundingIncrement: 10 })}`, 862 | // 'P30M' 863 | // ); 864 | // }); 865 | // it('accepts singular units', () => { 866 | // expect(later.since(earlier, { largestUnit: 'year' }).toString()).toBe(`${later.since(earlier, { largestUnit: 'years' })}`); 867 | // expect(later.since(earlier, { smallestUnit: 'year' }).toString()).toBe(`${later.since(earlier, { smallestUnit: 'years' })}`); 868 | // expect(later.since(earlier, { largestUnit: 'month' }).toString()).toBe(`${later.since(earlier, { largestUnit: 'months' })}`); 869 | // equal( 870 | // `${later.since(earlier, { smallestUnit: 'month' })}`, 871 | // `${later.since(earlier, { smallestUnit: 'months' })}` 872 | // ); 873 | // }); 874 | }); 875 | 876 | describe("YearMonth.add() works", () => { 877 | ym = PlainYearMonth.from("2019-11"); 878 | it("(2019-11) plus 2 months === 2020-01", () => { 879 | expect(ym.add({ months: 2 }).toString()).toBe("2020-01"); 880 | expect(ym.add({ months: 2 }, Overflow.Constrain).toString()).toBe( 881 | "2020-01" 882 | ); 883 | expect(ym.add({ months: 2 }, Overflow.Reject).toString()).toBe("2020-01"); 884 | }); 885 | it("(2019-11) plus 1 year === 2020-11", () => { 886 | expect(ym.add({ years: 1 }).toString()).toBe("2020-11"); 887 | expect(ym.add({ years: 1 }, Overflow.Constrain).toString()).toBe( 888 | "2020-11" 889 | ); 890 | expect(ym.add({ years: 1 }, Overflow.Reject).toString()).toBe("2020-11"); 891 | }); 892 | it("symmetrical with regard to negative durations", () => { 893 | expect( 894 | PlainYearMonth.from("2020-01").add({ months: -2 }).toString() 895 | ).toBe("2019-11"); 896 | expect(PlainYearMonth.from("2020-11").add({ years: -1 }).toString()).toBe( 897 | "2019-11" 898 | ); 899 | }); 900 | it("yearMonth.add(durationObj)", () => { 901 | expect(ym.add(new Duration(0, 2)).toString()).toBe("2020-01"); 902 | }); 903 | it("casts argument", () => {; 904 | expect(ym.add("P2M").toString()).toBe("2020-01"); 905 | }); 906 | it("ignores lower units that don't balance up to the length of the month", () => { 907 | expect(ym.add({ days: 1 }).toString()).toBe("2019-11"); 908 | expect(ym.add({ days: 29 }).toString()).toBe("2019-11"); 909 | expect(ym.add({ hours: 1 }).toString()).toBe("2019-11"); 910 | expect(ym.add({ minutes: 1 }).toString()).toBe("2019-11"); 911 | expect(ym.add({ seconds: 1 }).toString()).toBe("2019-11"); 912 | expect(ym.add({ milliseconds: 1 }).toString()).toBe("2019-11"); 913 | expect(ym.add({ microseconds: 1 }).toString()).toBe("2019-11"); 914 | expect(ym.add({ nanoseconds: 1 }).toString()).toBe("2019-11"); 915 | }); 916 | it("adds lower units that balance up to a month or more", () => { 917 | expect(ym.add({ days: 30 }).toString()).toBe("2019-12"); 918 | expect(ym.add({ days: 31 }).toString()).toBe("2019-12"); 919 | expect(ym.add({ days: 60 }).toString()).toBe("2019-12"); 920 | expect(ym.add({ days: 61 }).toString()).toBe("2020-01"); 921 | expect(ym.add({ hours: 720 }).toString()).toBe("2019-12"); 922 | expect(ym.add({ minutes: 43200 }).toString()).toBe("2019-12"); 923 | expect(ym.add({ seconds: 2592000 }).toString()).toBe("2019-12"); 924 | 925 | // // These are overflowing due to i32 926 | // expect(ym.add({ milliseconds: 2592000_000 }).toString()).toBe("2019-12"); 927 | // expect(ym.add({ microseconds: 2592000_000_000 }).toString()).toBe( 928 | // "2019-12" 929 | // ); 930 | // expect(ym.add({ nanoseconds: 2592000_000_000_000 }).toString()).toBe( 931 | // "2019-12" 932 | // ); 933 | }); 934 | it("balances days to months based on the number of days in the ISO month", () => { 935 | expect(PlainYearMonth.from("2019-02").add({ days: 27 }).toString()).toBe( 936 | "2019-02" 937 | ); 938 | expect(PlainYearMonth.from("2019-02").add({ days: 28 }).toString()).toBe( 939 | "2019-03" 940 | ); 941 | expect(PlainYearMonth.from("2020-02").add({ days: 28 }).toString()).toBe( 942 | "2020-02" 943 | ); 944 | expect(PlainYearMonth.from("2020-02").add({ days: 29 }).toString()).toBe( 945 | "2020-03" 946 | ); 947 | expect(PlainYearMonth.from("2019-11").add({ days: 29 }).toString()).toBe( 948 | "2019-11" 949 | ); 950 | expect(PlainYearMonth.from("2019-11").add({ days: 30 }).toString()).toBe( 951 | "2019-12" 952 | ); 953 | expect(PlainYearMonth.from("2020-01").add({ days: 30 }).toString()).toBe( 954 | "2020-01" 955 | ); 956 | expect(PlainYearMonth.from("2020-01").add({ days: 31 }).toString()).toBe( 957 | "2020-02" 958 | ); 959 | }); 960 | // it("invalid overflow", () => { 961 | // ["", "CONSTRAIN", "balance", 3, null].forEach((overflow) => 962 | // throws(() => ym.add({ months: 1 }, { overflow }), RangeError) 963 | // ); 964 | // }); 965 | xit("mixed positive and negative values always throw", () => { 966 | // ["constrain", "reject"].forEach((overflow) => 967 | // throws(() => ym.add({ years: 1, months: -6 }, { overflow }), RangeError) 968 | // ); 969 | }); 970 | // it("options may only be an object or undefined", () => { 971 | // [null, 1, "hello", true, Symbol("foo"), 1n].forEach((badOptions) => 972 | // throws(() => ym.add({ months: 1 }, badOptions), TypeError) 973 | // ); 974 | // [{}, () => {}, undefined].forEach((options) => 975 | // expect(ym.add({ months: 1 }, options).toString()).toBe("2019-12") 976 | // ); 977 | // }); 978 | // it("object must contain at least one correctly-spelled property", () => { 979 | // throws(() => ym.add({}), TypeError); 980 | // throws(() => ym.add({ month: 12 }), TypeError); 981 | // }); 982 | // it("incorrectly-spelled properties are ignored", () => { 983 | // expect(ym.add({ month: 1, years: 1 }).toString()).toBe("2020-11"); 984 | // }); 985 | }); 986 | 987 | describe("YearMonth.subtract() works", () => { 988 | ym = PlainYearMonth.from("2019-11"); 989 | it("(2019-11) minus 11 months === 2018-12", () => { 990 | expect(ym.subtract({ months: 11 }).toString()).toBe("2018-12"); 991 | expect(ym.subtract({ months: 11 }, Overflow.Constrain).toString()).toBe( 992 | "2018-12" 993 | ); 994 | expect(ym.subtract({ months: 11 }, Overflow.Reject).toString()).toBe( 995 | "2018-12" 996 | ); 997 | }); 998 | it("(2019-11) minus 12 years === 2007-11", () => { 999 | expect(ym.subtract({ years: 12 }).toString()).toBe("2007-11"); 1000 | expect(ym.subtract({ years: 12 }, Overflow.Constrain).toString()).toBe( 1001 | "2007-11" 1002 | ); 1003 | expect(ym.subtract({ years: 12 }, Overflow.Reject).toString()).toBe( 1004 | "2007-11" 1005 | ); 1006 | }); 1007 | it("symmetrical with regard to negative durations", () => { 1008 | expect( 1009 | PlainYearMonth.from("2018-12").subtract({ months: -11 }).toString() 1010 | ).toBe("2019-11"); 1011 | expect( 1012 | PlainYearMonth.from("2007-11").subtract({ years: -12 }).toString() 1013 | ).toBe("2019-11"); 1014 | }); 1015 | it("yearMonth.subtract(durationObj)", () => { 1016 | expect(ym.subtract(new Duration(0, 11)).toString()).toBe("2018-12"); 1017 | }); 1018 | it("casts argument", () => {; 1019 | expect(ym.subtract("P11M").toString()).toBe("2018-12"); 1020 | }); 1021 | it("ignores lower units that don't balance up to the length of the month", () => { 1022 | expect(ym.subtract({ days: 1 }).toString()).toBe("2019-11"); 1023 | expect(ym.subtract({ hours: 1 }).toString()).toBe("2019-11"); 1024 | expect(ym.subtract({ minutes: 1 }).toString()).toBe("2019-11"); 1025 | expect(ym.subtract({ seconds: 1 }).toString()).toBe("2019-11"); 1026 | expect(ym.subtract({ milliseconds: 1 }).toString()).toBe("2019-11"); 1027 | expect(ym.subtract({ microseconds: 1 }).toString()).toBe("2019-11"); 1028 | expect(ym.subtract({ nanoseconds: 1 }).toString()).toBe("2019-11"); 1029 | }); 1030 | it("subtracts lower units that balance up to a day or more", () => { 1031 | expect(ym.subtract({ days: 29 }).toString()).toBe("2019-11"); 1032 | expect(ym.subtract({ days: 30 }).toString()).toBe("2019-10"); 1033 | expect(ym.subtract({ days: 60 }).toString()).toBe("2019-10"); 1034 | expect(ym.subtract({ days: 61 }).toString()).toBe("2019-09"); 1035 | expect(ym.subtract({ hours: 720 }).toString()).toBe("2019-10"); 1036 | expect(ym.subtract({ minutes: 43200 }).toString()).toBe("2019-10"); 1037 | expect(ym.subtract({ seconds: 2592000 }).toString()).toBe("2019-10"); 1038 | // expect(ym.subtract({ milliseconds: 2592000_000 }).toString()).toBe( 1039 | // "2019-10" 1040 | // ); 1041 | // expect(ym.subtract({ microseconds: 2592000_000_000 }).toString()).toBe( 1042 | // "2019-10" 1043 | // ); 1044 | // expect(ym.subtract({ nanoseconds: 2592000_000_000_000 }).toString()).toBe( 1045 | // "2019-10" 1046 | // ); 1047 | }); 1048 | it("balances days to months based on the number of days in the ISO month", () => { 1049 | expect( 1050 | PlainYearMonth.from("2019-02").subtract({ days: 27 }).toString() 1051 | ).toBe("2019-02"); 1052 | expect( 1053 | PlainYearMonth.from("2019-02").subtract({ days: 28 }).toString() 1054 | ).toBe("2019-01"); 1055 | expect( 1056 | PlainYearMonth.from("2020-02").subtract({ days: 28 }).toString() 1057 | ).toBe("2020-02"); 1058 | expect( 1059 | PlainYearMonth.from("2020-02").subtract({ days: 29 }).toString() 1060 | ).toBe("2020-01"); 1061 | expect( 1062 | PlainYearMonth.from("2019-11").subtract({ days: 29 }).toString() 1063 | ).toBe("2019-11"); 1064 | expect( 1065 | PlainYearMonth.from("2019-11").subtract({ days: 30 }).toString() 1066 | ).toBe("2019-10"); 1067 | expect( 1068 | PlainYearMonth.from("2020-01").subtract({ days: 30 }).toString() 1069 | ).toBe("2020-01"); 1070 | expect( 1071 | PlainYearMonth.from("2020-01").subtract({ days: 31 }).toString() 1072 | ).toBe("2019-12"); 1073 | }); 1074 | // it("invalid overflow", () => { 1075 | // ["", "CONSTRAIN", "balance", 3, null].forEach((overflow) => 1076 | // throws(() => ym.subtract({ months: 1 }, { overflow }), RangeError) 1077 | // ); 1078 | // }); 1079 | xit("mixed positive and negative values always throw", () => { 1080 | // ["constrain", "reject"].forEach((overflow) => 1081 | // throws( 1082 | // () => ym.subtract({ years: 1, months: -6 }, { overflow }), 1083 | // RangeError 1084 | // ) 1085 | // ); 1086 | }); 1087 | // it("options may only be an object or undefined", () => { 1088 | // [null, 1, "hello", true, Symbol("foo"), 1n].forEach((badOptions) => 1089 | // throws(() => ym.subtract({ months: 1 }, badOptions), TypeError) 1090 | // ); 1091 | // [{}, () => {}, undefined].forEach((options) => 1092 | // expect(ym.subtract({ months: 1 }, options).toString()).toBe("2019-10") 1093 | // ); 1094 | // }); 1095 | // it("object must contain at least one correctly-spelled property", () => { 1096 | // throws(() => ym.subtract({}), TypeError); 1097 | // throws(() => ym.subtract({ month: 12 }), TypeError); 1098 | // }); 1099 | // it("incorrectly-spelled properties are ignored", () => { 1100 | // expect(ym.subtract({ month: 1, years: 1 }).toString()).toBe("2018-11"); 1101 | // }); 1102 | }); 1103 | 1104 | describe("YearMonth.toPlainDate()", () => { 1105 | ym = PlainYearMonth.from("2002-01"); 1106 | it("takes a day parameter", () => { 1107 | expect(ym.toPlainDate(22).toString()).toBe("2002-01-22"); 1108 | }); 1109 | // it("doesn't take a primitive argument", () => { 1110 | // [22, '22', false, 22n, Symbol('22'), null].forEach((bad) => { 1111 | // throws(() => ym.toPlainDate(bad), TypeError); 1112 | // }); 1113 | // }); 1114 | // it('takes an object argument with day property', () => { 1115 | // expect(ym.toPlainDate({ day: 22 }).toString()).toBe('2002-01-22'); 1116 | // }); 1117 | // it('needs at least a day property on the object in the ISO calendar', () => { 1118 | // throws(() => ym.toPlainDate({ something: 'nothing' }), TypeError); 1119 | // }); 1120 | }); 1121 | 1122 | describe("YearMonth.toString()", () => { 1123 | ym1 = PlainYearMonth.from("1976-11"); 1124 | // ym2 = PlainYearMonth.from({ year: 1976, month: 11, calendar: 'gregory' }); 1125 | it("YearMonth(1976, 11).toString() == 1976-11", () => { 1126 | expect(ym1.toString()).toBe("1976-11"); 1127 | }); 1128 | // it('shows only non-ISO calendar if calendarName = auto', () => { 1129 | // equal(ym1.toString({ calendarName: 'auto' }), '1976-11'); 1130 | // equal(ym2.toString({ calendarName: 'auto' }), '1976-11-01[u-ca=gregory]'); 1131 | // }); 1132 | // it('shows ISO calendar if calendarName = always', () => { 1133 | // equal(ym1.toString({ calendarName: 'always' }), '1976-11[u-ca=iso8601]'); 1134 | // }); 1135 | // it('omits non-ISO calendar, but not day, if calendarName = never', () => { 1136 | // equal(ym1.toString({ calendarName: 'never' }), '1976-11'); 1137 | // equal(ym2.toString({ calendarName: 'never' }), '1976-11-01'); 1138 | // }); 1139 | // it('default is calendar = auto', () => { 1140 | // equal(ym1.toString(), '1976-11'); 1141 | // equal(ym2.toString(), '1976-11-01[u-ca=gregory]'); 1142 | // }); 1143 | // it('throws on invalid calendar', () => { 1144 | // ['ALWAYS', 'sometimes', false, 3, null].forEach((calendarName) => { 1145 | // throws(() => ym1.toString({ calendarName }), RangeError); 1146 | // }); 1147 | // }); 1148 | }); 1149 | }); 1150 | -------------------------------------------------------------------------------- /assembly/duration.ts: -------------------------------------------------------------------------------- 1 | import { RegExp } from "assemblyscript-regex"; 2 | 3 | import { PlainDateTime } from "./plaindatetime"; 4 | import { TimeComponent } from "./enums"; 5 | import { coalesce, sign } from "./util"; 6 | import { 7 | MILLIS_PER_SECOND, 8 | NANOS_PER_DAY, 9 | NANOS_PER_SECOND 10 | } from "./util/constants"; 11 | 12 | // @ts-ignore 13 | @lazy 14 | const NULL = i32.MAX_VALUE; 15 | 16 | export class DurationLike { 17 | years: i32 = NULL; 18 | months: i32 = NULL; 19 | weeks: i32 = NULL; 20 | days: i32 = NULL; 21 | hours: i32 = NULL; 22 | minutes: i32 = NULL; 23 | seconds: i32 = NULL; 24 | milliseconds: i64 = NULL; 25 | microseconds: i64 = NULL; 26 | nanoseconds: i64 = NULL; 27 | 28 | toDuration(): Duration { 29 | return new Duration( 30 | this.years != NULL ? this.years : 0, 31 | this.months != NULL ? this.months : 0, 32 | this.weeks != NULL ? this.weeks : 0, 33 | this.days != NULL ? this.days : 0, 34 | this.hours != NULL ? this.hours : 0, 35 | this.minutes != NULL ? this.minutes : 0, 36 | this.seconds != NULL ? this.seconds : 0, 37 | this.milliseconds != NULL ? this.milliseconds : 0, 38 | this.microseconds != NULL ? this.microseconds : 0, 39 | this.nanoseconds != NULL ? this.nanoseconds : 0, 40 | ); 41 | } 42 | } 43 | 44 | export class Duration { 45 | static from(duration: T): Duration { 46 | if (isString()) { 47 | return Duration.fromString(changetype(duration)); 48 | } else if (duration instanceof DurationLike) { 49 | return Duration.fromDurationLike(duration); 50 | } else if (duration instanceof Duration) { 51 | return Duration.fromDuration(duration); 52 | } 53 | throw new TypeError("invalid duration type"); 54 | } 55 | 56 | private static fromDuration(duration: Duration): Duration { 57 | return new Duration( 58 | duration.years, 59 | duration.months, 60 | duration.weeks, 61 | duration.days, 62 | duration.hours, 63 | duration.minutes, 64 | duration.seconds, 65 | duration.milliseconds, 66 | duration.microseconds, 67 | duration.nanoseconds 68 | ); 69 | } 70 | 71 | private static fromString(duration: string): Duration { 72 | const regex = new RegExp( 73 | "([+−-])?P(?:(\\d+)Y)?(?:(\\d+)M)?(?:(\\d+)W)?(?:(\\d+)D)?(?:T?(?:(\\d+)?H)?(?:(\\d+)?M)?(?:(\\d+)(?:[.,](\\d{1,9}))?S)?)?$", 74 | "i" 75 | ); 76 | const match = regex.exec(duration); 77 | if (match == null) { 78 | throw new RangeError("invalid duration: " + duration); 79 | } 80 | if (match.matches.slice(2).join("") == "") { 81 | throw new RangeError("invalid duration"); 82 | } 83 | const sign = match.matches[1] == '-' || match.matches[1] == '\u2212' ? -1 : 1; 84 | const years = match.matches[2] != "" ? I32.parseInt(match.matches[2]) * sign : 0; 85 | const months = match.matches[3] != "" ? I32.parseInt(match.matches[3]) * sign : 0; 86 | const weeks = match.matches[4] != "" ? I32.parseInt(match.matches[4]) * sign : 0; 87 | const days = match.matches[5] != "" ? I32.parseInt(match.matches[5]) * sign : 0; 88 | const hours = match.matches[6] != "" ? I32.parseInt(match.matches[6]) * sign : 0; 89 | const minutes = match.matches[7] != "" ? I32.parseInt(match.matches[7]) * sign : 0; 90 | const seconds = match.matches[8] != "" ? I32.parseInt(match.matches[8]) * sign : 0; 91 | const fraction = match.matches[9] + "000000000"; 92 | const millisecond = I32.parseInt(fraction.substring(0, 3)) * sign; 93 | const microsecond = I32.parseInt(fraction.substring(3, 6)) * sign; 94 | const nanosecond = I32.parseInt(fraction.substring(6, 9)) * sign; 95 | 96 | return new Duration( 97 | years, months, weeks, days, 98 | hours, minutes, seconds, 99 | millisecond, 100 | microsecond, 101 | nanosecond 102 | ); 103 | } 104 | 105 | private static fromDurationLike(d: DurationLike): Duration { 106 | return d.toDuration(); 107 | } 108 | 109 | constructor( 110 | public years: i32 = 0, 111 | public months: i32 = 0, 112 | public weeks: i32 = 0, 113 | public days: i32 = 0, 114 | public hours: i32 = 0, 115 | public minutes: i32 = 0, 116 | public seconds: i32 = 0, 117 | public milliseconds: i64 = 0, 118 | public microseconds: i64 = 0, 119 | public nanoseconds: i64 = 0 120 | ) { 121 | // durationSign returns the sign of the first non-zero component 122 | const sig = this.sign; 123 | if ( 124 | (years && sign(years) != sig) || 125 | (months && sign(months) != sig) || 126 | (weeks && sign(weeks) != sig) || 127 | (days && sign(days) != sig) || 128 | (hours && sign(hours) != sig) || 129 | (minutes && sign(minutes) != sig) || 130 | (seconds && sign(seconds) != sig) || 131 | (milliseconds && sign(milliseconds) != sig) || 132 | (microseconds && sign(microseconds) != sig) || 133 | (nanoseconds && sign(nanoseconds) != sig) 134 | ) { 135 | throw new RangeError("mixed-sign values not allowed as duration fields"); 136 | } 137 | } 138 | 139 | with(durationLike: DurationLike): Duration { 140 | return new Duration( 141 | coalesce(durationLike.years, this.years, NULL), 142 | coalesce(durationLike.months, this.months, NULL), 143 | coalesce(durationLike.weeks, this.weeks, NULL), 144 | coalesce(durationLike.days, this.days, NULL), 145 | coalesce(durationLike.hours, this.hours, NULL), 146 | coalesce(durationLike.minutes, this.minutes, NULL), 147 | coalesce(durationLike.seconds, this.seconds, NULL), 148 | coalesce(durationLike.milliseconds, this.milliseconds, NULL), 149 | coalesce(durationLike.microseconds, this.microseconds, NULL), 150 | coalesce(durationLike.nanoseconds, this.nanoseconds, NULL) 151 | ); 152 | } 153 | 154 | get sign(): i32 { 155 | if (this.years) return sign(this.years); 156 | if (this.months) return sign(this.months); 157 | if (this.weeks) return sign(this.weeks); 158 | if (this.days) return sign(this.days); 159 | if (this.hours) return sign(this.hours); 160 | if (this.minutes) return sign(this.minutes); 161 | if (this.seconds) return sign(this.seconds); 162 | if (this.milliseconds) return sign(this.milliseconds as i32); 163 | if (this.microseconds) return sign(this.microseconds as i32); 164 | if (this.nanoseconds) return sign(this.nanoseconds as i32); 165 | return 0; 166 | } 167 | 168 | get blank(): bool { 169 | return this.sign == 0; 170 | } 171 | 172 | // P1Y1M1DT1H1M1.1S 173 | toString(): string { 174 | let nanoseconds: i64 = 175 | this.nanoseconds + 176 | this.microseconds * 1000 + 177 | this.milliseconds * 1000_000; 178 | const seconds: i64 = i64(this.seconds) + nanoseconds / NANOS_PER_SECOND; 179 | const negative = this.sign < 0; 180 | nanoseconds %= NANOS_PER_SECOND; 181 | 182 | if ( 183 | this.years == 0 && 184 | this.months == 0 && 185 | this.weeks == 0 && 186 | this.days == 0 && 187 | this.hours == 0 && 188 | this.minutes == 0 && 189 | seconds == 0 && 190 | nanoseconds == 0 191 | ) { 192 | return "PT0S"; 193 | } 194 | 195 | let isoString = negative ? "-P" : "P"; 196 | if (this.years) isoString += abs(this.years).toString() + "Y"; 197 | if (this.months) isoString += abs(this.months).toString() + "M"; 198 | if (this.weeks) isoString += abs(this.weeks).toString() + "W"; 199 | if (this.days) isoString += abs(this.days).toString() + "D"; 200 | 201 | if (!this.hours && !this.minutes && !seconds && !nanoseconds) { 202 | return isoString; 203 | } 204 | isoString += "T"; 205 | 206 | if (this.hours) isoString += abs(this.hours).toString() + "H"; 207 | if (this.minutes) isoString += abs(this.minutes).toString() + "M"; 208 | 209 | if (seconds || nanoseconds) { 210 | let decimalPart: string | null = null; 211 | if (nanoseconds) { 212 | const fraction = abs(nanoseconds); 213 | decimalPart = fraction.toString().padStart(9, "0"); 214 | // precision would truncate this string here 215 | while (decimalPart.endsWith("0")) { 216 | decimalPart = decimalPart.slice(0, -1); 217 | } 218 | } 219 | if (decimalPart != null) { 220 | isoString += abs(seconds).toString() + "." + decimalPart! + "S"; 221 | } else { 222 | isoString += abs(seconds).toString() + "S"; 223 | } 224 | } 225 | 226 | return isoString; 227 | } 228 | 229 | add(durationToAdd: T, relativeTo: PlainDateTime | null = null): Duration { 230 | const duration = Duration.from(durationToAdd); 231 | const largestUnit = min(largestUnitOf(this), largestUnitOf(duration)); 232 | 233 | if (!relativeTo) { 234 | if ( 235 | largestUnit == TimeComponent.Years || 236 | largestUnit == TimeComponent.Months || 237 | largestUnit == TimeComponent.Weeks 238 | ) throw new RangeError("relativeTo is required for years, months, or weeks arithmetic"); 239 | 240 | const balanced = balancedDuration( 241 | this.days + duration.days, 242 | this.hours + duration.hours, 243 | this.minutes + duration.minutes, 244 | this.seconds + duration.seconds, 245 | this.milliseconds + duration.milliseconds, 246 | this.microseconds + duration.microseconds, 247 | this.nanoseconds + duration.nanoseconds, 248 | largestUnit 249 | ); 250 | return balanced; 251 | } else { 252 | const datePart = relativeTo.toPlainDate(); 253 | 254 | const dateDuration1 = new Duration(this.years, this.months, this.weeks, this.days); 255 | const dateDuration2 = new Duration(duration.years, duration.months, duration.weeks, duration.days); 256 | 257 | const intermediate = datePart.add(dateDuration1); 258 | const end = intermediate.add(dateDuration2); 259 | 260 | const dateLargestUnit = min(largestUnit, TimeComponent.Days) as TimeComponent; 261 | const dateDiff = datePart.until(end, dateLargestUnit); 262 | 263 | const dur = balancedDuration( 264 | dateDiff.days, 265 | this.hours + duration.hours, 266 | this.minutes + duration.minutes, 267 | this.seconds + duration.seconds, 268 | this.milliseconds + duration.milliseconds, 269 | this.microseconds + duration.microseconds, 270 | this.nanoseconds + duration.nanoseconds, 271 | largestUnit 272 | ); 273 | 274 | return new Duration( 275 | dateDiff.years, dateDiff.months, dateDiff.weeks, 276 | dur.days, dur.hours, dur.minutes, dur.seconds, 277 | dur.milliseconds, dur.microseconds, dur.nanoseconds 278 | ); 279 | } 280 | } 281 | 282 | subtract(durationToAdd: T, relativeTo: PlainDateTime | null = null): Duration { 283 | return this.add(Duration.from(durationToAdd).negated(), relativeTo); 284 | } 285 | 286 | negated(): Duration { 287 | return new Duration( 288 | -this.years, 289 | -this.months, 290 | -this.weeks, 291 | -this.days, 292 | -this.hours, 293 | -this.minutes, 294 | -this.seconds, 295 | -this.milliseconds, 296 | -this.microseconds, 297 | -this.nanoseconds 298 | ); 299 | } 300 | 301 | abs(): Duration { 302 | return new Duration( 303 | abs(this.years), 304 | abs(this.months), 305 | abs(this.weeks), 306 | abs(this.days), 307 | abs(this.hours), 308 | abs(this.minutes), 309 | abs(this.seconds), 310 | abs(this.milliseconds), 311 | abs(this.microseconds), 312 | abs(this.nanoseconds) 313 | ); 314 | } 315 | } 316 | 317 | function totalDurationNanoseconds( 318 | days: i64, 319 | hours: i64, 320 | minutes: i64, 321 | seconds: i64, 322 | milliseconds: i64, 323 | microseconds: i64, 324 | nanoseconds: i64 325 | ): i64 { 326 | hours += days * 24; 327 | minutes += hours * 60; 328 | seconds += minutes * 60; 329 | milliseconds += seconds * MILLIS_PER_SECOND; 330 | microseconds += milliseconds * 1000; 331 | return nanoseconds + microseconds * 1000; 332 | } 333 | 334 | export function balancedDuration( 335 | days: i32, 336 | hours: i32, 337 | minutes: i32, 338 | seconds: i32, 339 | milliseconds: i64, 340 | microseconds: i64, 341 | nanoseconds: i64, 342 | largestUnit: TimeComponent 343 | ): Duration { 344 | const durationNs = totalDurationNanoseconds( 345 | days as i64, 346 | hours as i64, 347 | minutes as i64, 348 | seconds as i64, 349 | milliseconds, 350 | microseconds, 351 | nanoseconds 352 | ); 353 | 354 | let 355 | nanosecondsI64: i64 = 0, 356 | microsecondsI64: i64 = 0, 357 | millisecondsI64: i64 = 0, 358 | secondsI64: i64 = 0, 359 | minutesI64: i64 = 0, 360 | hoursI64: i64 = 0, 361 | daysI64: i64 = 0; 362 | 363 | if ( 364 | largestUnit >= TimeComponent.Years && 365 | largestUnit <= TimeComponent.Days 366 | ) { 367 | if (durationNs != 0) { 368 | daysI64 = durationNs / NANOS_PER_DAY; 369 | nanosecondsI64 = durationNs % NANOS_PER_DAY; 370 | } 371 | } else { 372 | nanosecondsI64 = durationNs; 373 | } 374 | 375 | const sig = i32(sign(nanosecondsI64)); 376 | nanosecondsI64 = abs(nanosecondsI64); 377 | 378 | switch (largestUnit) { 379 | case TimeComponent.Years: 380 | case TimeComponent.Months: 381 | case TimeComponent.Weeks: 382 | case TimeComponent.Days: 383 | case TimeComponent.Hours: 384 | microsecondsI64 = nanosecondsI64 / 1000; 385 | nanosecondsI64 = nanosecondsI64 % 1000; 386 | 387 | millisecondsI64 = microsecondsI64 / 1000; 388 | microsecondsI64 = microsecondsI64 % 1000; 389 | 390 | secondsI64 = millisecondsI64 / 1000; 391 | millisecondsI64 = millisecondsI64 % 1000; 392 | 393 | minutesI64 = secondsI64 / 60; 394 | secondsI64 = secondsI64 % 60; 395 | 396 | hoursI64 = minutesI64 / 60; 397 | minutesI64 = minutesI64 % 60; 398 | break; 399 | 400 | case TimeComponent.Minutes: 401 | microsecondsI64 = nanosecondsI64 / 1000; 402 | nanosecondsI64 = nanosecondsI64 % 1000; 403 | 404 | millisecondsI64 = microsecondsI64 / 1000; 405 | microsecondsI64 = microsecondsI64 % 1000; 406 | 407 | secondsI64 = millisecondsI64 / 1000; 408 | millisecondsI64 = millisecondsI64 % 1000; 409 | 410 | minutesI64 = secondsI64 / 60; 411 | secondsI64 = secondsI64 % 60; 412 | break; 413 | 414 | case TimeComponent.Seconds: 415 | microsecondsI64 = nanosecondsI64 / 1000; 416 | nanosecondsI64 = nanosecondsI64 % 1000; 417 | 418 | millisecondsI64 = microsecondsI64 / 1000; 419 | microsecondsI64 = microsecondsI64 % 1000; 420 | 421 | secondsI64 = millisecondsI64 / 1000; 422 | millisecondsI64 = millisecondsI64 % 1000; 423 | break; 424 | 425 | case TimeComponent.Milliseconds: 426 | microsecondsI64 = nanosecondsI64 / 1000; 427 | nanosecondsI64 = nanosecondsI64 % 1000; 428 | 429 | millisecondsI64 = microsecondsI64 / 1000; 430 | microsecondsI64 = microsecondsI64 % 1000; 431 | break; 432 | 433 | case TimeComponent.Microseconds: 434 | microsecondsI64 = nanosecondsI64 / 1000; 435 | nanosecondsI64 = nanosecondsI64 % 1000; 436 | break; 437 | 438 | case TimeComponent.Nanoseconds: 439 | break; 440 | } 441 | 442 | return new Duration( 443 | 0, 444 | 0, 445 | 0, 446 | i32(daysI64), 447 | i32(hoursI64) * sig, 448 | i32(minutesI64) * sig, 449 | i32(secondsI64) * sig, 450 | i32(millisecondsI64) * sig, 451 | i32(microsecondsI64) * sig, 452 | i32(nanosecondsI64) * sig 453 | ); 454 | } 455 | 456 | //@ts-ignore: decorator 457 | @inline 458 | function largestUnitOf(duration: Duration): TimeComponent { 459 | if (duration.years) return TimeComponent.Years; 460 | if (duration.months) return TimeComponent.Months; 461 | if (duration.weeks) return TimeComponent.Weeks; 462 | if (duration.days) return TimeComponent.Days; 463 | if (duration.hours) return TimeComponent.Hours; 464 | if (duration.minutes) return TimeComponent.Minutes; 465 | if (duration.seconds) return TimeComponent.Seconds; 466 | if (duration.milliseconds) return TimeComponent.Milliseconds; 467 | if (duration.microseconds) return TimeComponent.Microseconds; 468 | if (duration.nanoseconds) return TimeComponent.Nanoseconds; 469 | return TimeComponent.Nanoseconds; 470 | } 471 | -------------------------------------------------------------------------------- /assembly/enums.ts: -------------------------------------------------------------------------------- 1 | export const enum Overflow { 2 | Reject, 3 | Constrain, 4 | } 5 | 6 | export const enum Disambiguation { 7 | Compatible, 8 | Earlier, 9 | Later, 10 | Reject, 11 | } 12 | 13 | export const enum TimeComponent { 14 | Years, 15 | Months, 16 | Weeks, 17 | Days, 18 | Hours, 19 | Minutes, 20 | Seconds, 21 | Milliseconds, 22 | Microseconds, 23 | Nanoseconds, 24 | } 25 | -------------------------------------------------------------------------------- /assembly/env.ts: -------------------------------------------------------------------------------- 1 | export declare function log(str: string): void; -------------------------------------------------------------------------------- /assembly/index.ts: -------------------------------------------------------------------------------- 1 | import "./env"; 2 | 3 | export { PlainTime } from "./plaintime"; 4 | export { PlainDate } from "./plaindate"; 5 | export { PlainDateTime } from "./plaindatetime"; 6 | export { Duration } from "./duration"; 7 | export * from "./enums"; 8 | export { Now } from "./now"; 9 | export { Instant } from "./instant"; 10 | 11 | // @deprecated `now` is not a standard API use `Now` instead 12 | export { Now as now } from "./now"; 13 | -------------------------------------------------------------------------------- /assembly/instant.ts: -------------------------------------------------------------------------------- 1 | import { NANOS_PER_SECOND } from "./util/constants"; 2 | import { balancedDuration, Duration, DurationLike } from "./duration"; 3 | import { TimeComponent } from "./enums"; 4 | import { PlainDateTime } from "./plaindatetime"; 5 | import { ord } from "./util"; 6 | import { formatISOString } from "./util/format"; 7 | 8 | export class Instant { 9 | @inline 10 | public static from(instant: T): Instant { 11 | if (isString()) { 12 | // @ts-ignore: cast 13 | const pdt = PlainDateTime.from(instant); 14 | return new Instant(pdt.epochNanoseconds); 15 | } else { 16 | if (isReference()) { 17 | if (instant instanceof Instant) { 18 | return new Instant(instant.epochNanoseconds); 19 | } 20 | } 21 | throw new TypeError("invalid instant type"); 22 | } 23 | } 24 | 25 | @inline 26 | static compare(i1: Instant, i2: Instant): i32 { 27 | return ord(i1.epochNanoseconds, i2.epochNanoseconds); 28 | } 29 | 30 | @inline 31 | public static fromEpochSeconds(seconds: i64): Instant { 32 | return new Instant(seconds * NANOS_PER_SECOND); 33 | } 34 | 35 | @inline 36 | public static fromEpochMilliseconds(millis: i64): Instant { 37 | return new Instant(millis * 1_000_000); 38 | } 39 | 40 | @inline 41 | public static fromEpochMicroseconds(micros: i64): Instant { 42 | return new Instant(micros * 1_000); 43 | } 44 | 45 | @inline 46 | public static fromEpochNanoseconds(nanos: i64): Instant { 47 | return new Instant(nanos); 48 | } 49 | 50 | constructor(public readonly epochNanoseconds: i64) {} 51 | 52 | @inline 53 | get epochMicroseconds(): i64 { 54 | return this.epochNanoseconds / 1_000; 55 | } 56 | 57 | @inline 58 | get epochMilliseconds(): i64 { 59 | return this.epochNanoseconds / 1_000_000; 60 | } 61 | 62 | @inline 63 | get epochSeconds(): i64 { 64 | return this.epochNanoseconds / NANOS_PER_SECOND; 65 | } 66 | 67 | add(durationToAdd: T): Instant { 68 | const duration = Duration.from(durationToAdd); 69 | if ( 70 | duration.years != 0 || 71 | duration.months != 0 || 72 | duration.weeks != 0 || 73 | duration.days != 0 74 | ) { 75 | throw new RangeError("invalid duration field"); 76 | } 77 | return new Instant(this.epochNanoseconds + durationToNanos(duration)); 78 | } 79 | 80 | subtract(durationToAdd: T): Instant { 81 | const duration = Duration.from(durationToAdd); 82 | if ( 83 | duration.years != 0 || 84 | duration.months != 0 || 85 | duration.weeks != 0 || 86 | duration.days != 0 87 | ) { 88 | throw new RangeError("invalid duration field"); 89 | } 90 | return new Instant(this.epochNanoseconds - durationToNanos(duration)); 91 | } 92 | 93 | since( 94 | instant: Instant, 95 | largestUnit: TimeComponent = TimeComponent.Seconds 96 | ): Duration { 97 | if (largestUnit <= TimeComponent.Days) { 98 | throw new RangeError("Largest unit must be smaller than days"); 99 | } 100 | const diffNanos = this.epochNanoseconds - instant.epochNanoseconds; 101 | return balancedDuration(0, 0, 0, 0, 0, 0, diffNanos, largestUnit); 102 | } 103 | 104 | until( 105 | instant: Instant, 106 | largestUnit: TimeComponent = TimeComponent.Seconds 107 | ): Duration { 108 | if (largestUnit <= TimeComponent.Days) { 109 | throw new RangeError("Largest unit must be smaller than days"); 110 | } 111 | const diffNanos = instant.epochNanoseconds - this.epochNanoseconds; 112 | return balancedDuration(0, 0, 0, 0, 0, 0, diffNanos, largestUnit); 113 | } 114 | 115 | equals(other: T): boolean { 116 | const otherInstant = other instanceof Instant ? other : Instant.from(other); 117 | return this.epochNanoseconds == otherInstant.epochNanoseconds; 118 | } 119 | 120 | @inline 121 | toString(): string { 122 | const quotient = this.epochNanoseconds / 1_000_000; 123 | const remainder = this.epochNanoseconds % 1_000_000; 124 | let epochMilliseconds = +quotient; 125 | let nanos = +remainder; 126 | if (nanos < 0) { 127 | nanos += 1_000_000; 128 | epochMilliseconds -= 1; 129 | } 130 | const microsecond = i32((nanos / 1_000) % 1_000); 131 | const nanosecond = i32(nanos % 1_000); 132 | 133 | const item = new Date(epochMilliseconds); 134 | const year = item.getUTCFullYear(); 135 | const month = item.getUTCMonth() + 1; 136 | const day = item.getUTCDate(); 137 | const hour = item.getUTCHours(); 138 | const minute = item.getUTCMinutes(); 139 | const second = item.getUTCSeconds(); 140 | const millisecond = item.getUTCMilliseconds(); 141 | 142 | return ( 143 | formatISOString( 144 | year, 145 | month, 146 | day, 147 | hour, 148 | minute, 149 | second, 150 | millisecond, 151 | microsecond, 152 | nanosecond 153 | ) + "Z" 154 | ); 155 | } 156 | } 157 | 158 | function durationToNanos(duration: Duration): i64 { 159 | return ( 160 | i64(duration.nanoseconds) + 161 | i64(duration.microseconds) * 1_000 + 162 | i64(duration.milliseconds) * 1_000_000 + 163 | i64(duration.seconds) * NANOS_PER_SECOND + 164 | i64(duration.minutes) * 60 * NANOS_PER_SECOND + 165 | i64(duration.hours) * 60 * 60 * NANOS_PER_SECOND 166 | ); 167 | } 168 | -------------------------------------------------------------------------------- /assembly/now.ts: -------------------------------------------------------------------------------- 1 | import { PlainDateTime } from "./plaindatetime"; 2 | import { PlainDate } from "./plaindate"; 3 | import { PlainTime } from "./plaintime"; 4 | import { Instant } from "./instant"; 5 | 6 | export class Now { 7 | static plainDateTimeISO(): PlainDateTime { 8 | const epochMillis = Date.now(); 9 | const date = new Date(epochMillis); 10 | return new PlainDateTime( 11 | date.getUTCFullYear(), 12 | date.getUTCMonth() + 1, 13 | date.getUTCDate(), 14 | date.getUTCHours(), 15 | date.getUTCMinutes(), 16 | date.getUTCSeconds(), 17 | date.getUTCMilliseconds() 18 | ); 19 | } 20 | 21 | static plainDateISO(): PlainDate { 22 | const epochMillis = Date.now(); 23 | const date = new Date(epochMillis); 24 | return new PlainDate( 25 | date.getUTCFullYear(), 26 | date.getUTCMonth() + 1, 27 | date.getUTCDate() 28 | ); 29 | } 30 | 31 | static plainTimeISO(): PlainTime { 32 | const epochMillis = Date.now(); 33 | const date = new Date(epochMillis); 34 | return new PlainTime( 35 | date.getUTCHours(), 36 | date.getUTCMinutes(), 37 | date.getUTCSeconds(), 38 | date.getUTCMilliseconds() 39 | ); 40 | } 41 | 42 | static instant(): Instant { 43 | const epochMillis = Date.now(); 44 | return Instant.fromEpochMilliseconds(epochMillis); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /assembly/plaindate.ts: -------------------------------------------------------------------------------- 1 | import { balancedDuration, Duration, DurationLike } from "./duration"; 2 | import { Overflow, TimeComponent } from "./enums"; 3 | import { PlainDateTime } from "./plaindatetime"; 4 | import { PlainMonthDay } from "./plainmonthday"; 5 | import { PlainTime } from "./plaintime"; 6 | import { PlainYearMonth, balancedYearMonth } from "./plainyearmonth"; 7 | import { parseISOString } from "./util/format"; 8 | import { 9 | dayOfWeek, 10 | leapYear, 11 | dayOfYear, 12 | weekOfYear, 13 | daysInMonth, 14 | daysInYear, 15 | checkDateTimeRange 16 | } from "./util/calendar"; 17 | import { 18 | ord, 19 | sign, 20 | clamp, 21 | toPaddedString, 22 | coalesce, 23 | checkRange, 24 | floorDiv, 25 | } from "./util"; 26 | 27 | export class DateLike { 28 | year: i32 = -1; 29 | month: i32 = -1; 30 | day: i32 = -1; 31 | } 32 | 33 | export class PlainDate { 34 | @inline 35 | static from(date: T): PlainDate { 36 | if (isString()) { 37 | // @ts-ignore: cast 38 | return PlainDate.fromString(date); 39 | } else { 40 | if (isReference()) { 41 | if (date instanceof PlainDate) { 42 | return PlainDate.fromPlainDate(date); 43 | } else if (date instanceof DateLike) { 44 | return PlainDate.fromDateLike(date); 45 | } 46 | } 47 | throw new TypeError("invalid date type"); 48 | } 49 | } 50 | 51 | @inline 52 | private static fromPlainDate(date: PlainDate): PlainDate { 53 | return new PlainDate(date.year, date.month, date.day); 54 | } 55 | 56 | @inline 57 | private static fromDateLike(date: DateLike): PlainDate { 58 | if (date.year == -1 || date.month == -1 || date.day == -1) { 59 | throw new TypeError("missing required property"); 60 | } 61 | return new PlainDate(date.year, date.month, date.day); 62 | } 63 | 64 | private static fromString(date: string): PlainDate { 65 | const parsed = parseISOString(date); 66 | return new PlainDate(parsed.year, parsed.month, parsed.day); 67 | } 68 | 69 | constructor(readonly year: i32, readonly month: i32, readonly day: i32) { 70 | rejectDate(year, month, day); 71 | 72 | if (!checkDateTimeRange(year, month, day, 12)) { 73 | throw new RangeError("DateTime outside of supported range"); 74 | } 75 | } 76 | 77 | @inline 78 | get dayOfWeek(): i32 { 79 | return dayOfWeek(this.year, this.month, this.day); 80 | } 81 | 82 | @inline 83 | get dayOfYear(): i32 { 84 | return dayOfYear(this.year, this.month, this.day); 85 | } 86 | 87 | @inline 88 | get weekOfYear(): i32 { 89 | return weekOfYear(this.year, this.month, this.day); 90 | } 91 | 92 | @inline 93 | get daysInWeek(): i32 { 94 | return 7; 95 | } 96 | 97 | @inline 98 | get daysInMonth(): i32 { 99 | return daysInMonth(this.year, this.month); 100 | } 101 | 102 | @inline 103 | get daysInYear(): i32 { 104 | return daysInYear(this.year); 105 | } 106 | 107 | @inline 108 | get monthsInYear(): i32 { 109 | return 12; 110 | } 111 | 112 | @inline 113 | get inLeapYear(): bool { 114 | return leapYear(this.year); 115 | } 116 | 117 | @inline 118 | get monthCode(): string { 119 | return (this.month >= 10 ? "M" : "M0") + this.month.toString(); 120 | } 121 | 122 | toString(): string { 123 | return ( 124 | this.year.toString() + 125 | "-" + 126 | toPaddedString(this.month) + 127 | "-" + 128 | toPaddedString(this.day) 129 | ); 130 | } 131 | 132 | @inline 133 | equals(other: PlainDate): bool { 134 | if (this === other) return true; 135 | return ( 136 | this.day == other.day && 137 | this.month == other.month && 138 | this.year == other.year 139 | ); 140 | } 141 | 142 | // @ts-ignore 143 | until( 144 | dateLike: T, 145 | largestUnit: TimeComponent = TimeComponent.Days 146 | ): Duration { 147 | const date = PlainDate.from(dateLike); 148 | 149 | switch (largestUnit) { 150 | case TimeComponent.Years: 151 | case TimeComponent.Months: { 152 | let sign = -PlainDate.compare(this, date); 153 | if (sign == 0) return new Duration(); 154 | 155 | let startYear = this.year; 156 | let startMonth = this.month; 157 | 158 | let endYear = date.year; 159 | let endMonth = date.month; 160 | let endDay = date.day; 161 | 162 | let years = endYear - startYear; 163 | let mid = new PlainDate(this.year, this.month, this.day).add(new Duration(years)); 164 | let midSign = -PlainDate.compare(mid, date); 165 | 166 | if (midSign === 0) { 167 | return largestUnit === TimeComponent.Years 168 | ? new Duration(years) 169 | : new Duration(0, years * 12); 170 | } 171 | 172 | let months = endMonth - startMonth; 173 | 174 | if (midSign !== sign) { 175 | years -= sign; 176 | months += sign * 12; 177 | } 178 | 179 | mid = new PlainDate(this.year, this.month, this.day).add(new Duration(years, months)); 180 | midSign = -PlainDate.compare(mid, date); 181 | 182 | if (midSign === 0) { 183 | return largestUnit === TimeComponent.Years 184 | ? new Duration(years, months) 185 | : new Duration(0, months + years * 12); 186 | } 187 | 188 | if (midSign !== sign) { 189 | // The end date is later in the month than mid date (or earlier for 190 | // negative durations). Back up one month. 191 | months -= sign; 192 | 193 | if (months === -sign) { 194 | years -= sign; 195 | months = sign * 11; 196 | } 197 | 198 | mid = new PlainDate(this.year, this.month, this.day).add(new Duration(years, months)); 199 | } 200 | 201 | let days = endDay - mid.day; // If we get here, months and years are correct (no overflow), and `mid` 202 | // is within the range from `start` to `end`. To count the days between 203 | // `mid` and `end`, there are 3 cases: 204 | // 1) same month: use simple subtraction 205 | // 2) end is previous month from intermediate (negative duration) 206 | // 3) end is next month from intermediate (positive duration) 207 | 208 | if (mid.month === endMonth && mid.year === endYear) { 209 | // 1) same month: use simple subtraction 210 | } else if (sign < 0) { 211 | // 2) end is previous month from intermediate (negative duration) 212 | // Example: intermediate: Feb 1, end: Jan 30, DaysInMonth = 31, days = -2 213 | days -= daysInMonth(endYear, endMonth); 214 | } else { 215 | // 3) end is next month from intermediate (positive duration) 216 | // Example: intermediate: Jan 29, end: Feb 1, DaysInMonth = 31, days = 3 217 | days += daysInMonth(mid.year, mid.month); 218 | } 219 | 220 | if (largestUnit === TimeComponent.Months) { 221 | months += years * 12; 222 | years = 0; 223 | } 224 | 225 | return new Duration(years, months, 0, days); 226 | } 227 | 228 | case TimeComponent.Weeks: 229 | case TimeComponent.Days: { 230 | let neg = PlainDate.compare(this, date) < 0; 231 | 232 | let smallerYear = neg ? this.year : date.year; 233 | let smallerMonth = neg ? this.month : date.month; 234 | let smallerDay = neg ? this.day : date.day; 235 | 236 | let largerYear = neg ? date.year : this.year; 237 | let largerMonth = neg ? date.month : this.month; 238 | let largerDay = neg ? date.day : this.day; 239 | 240 | let years = largerYear - smallerYear; 241 | 242 | let days = ( 243 | dayOfYear(largerYear, largerMonth, largerDay) - 244 | dayOfYear(smallerYear, smallerMonth, smallerDay) 245 | ); 246 | 247 | while (years > 0) { 248 | days += daysInYear(smallerYear + years - 1); 249 | years -= 1; 250 | } 251 | 252 | let weeks = 0; 253 | if (largestUnit === TimeComponent.Weeks) { 254 | weeks = floorDiv(days, 7); 255 | days -= weeks * 7; 256 | } 257 | 258 | return new Duration( 259 | 0, 0, 260 | neg ? weeks : -weeks, 261 | neg ? days : -days 262 | ); 263 | } 264 | 265 | default: 266 | throw new Error('differenceDate - cannot support TimeComponent < Days'); 267 | } 268 | } 269 | 270 | since( 271 | dateLike: T, 272 | largestUnit: TimeComponent = TimeComponent.Days 273 | ): Duration { 274 | return PlainDate.from(dateLike).until(this, largestUnit); 275 | } 276 | 277 | with(dateLike: DateLike): PlainDate { 278 | return new PlainDate( 279 | coalesce(dateLike.year, this.year, -1), 280 | coalesce(dateLike.month, this.month, -1), 281 | coalesce(dateLike.day, this.day, -1) 282 | ); 283 | } 284 | 285 | add( 286 | durationToAdd: T, 287 | overflow: Overflow = Overflow.Constrain 288 | ): PlainDate { 289 | const duration = Duration.from(durationToAdd); 290 | 291 | const balancedDur = balancedDuration( 292 | duration.days, 293 | duration.hours, 294 | duration.minutes, 295 | duration.seconds, 296 | duration.milliseconds, 297 | duration.microseconds, 298 | duration.nanoseconds, 299 | TimeComponent.Days 300 | ); 301 | 302 | const yearMonth = balancedYearMonth( 303 | this.year + duration.years, 304 | this.month + duration.months 305 | ); 306 | 307 | const regulatedDate = regulateDate( 308 | yearMonth.year, 309 | yearMonth.month, 310 | this.day, 311 | overflow 312 | ); 313 | 314 | return balancedDate( 315 | regulatedDate.year, regulatedDate.month, 316 | regulatedDate.day + balancedDur.days + duration.weeks * 7 317 | ); 318 | } 319 | 320 | subtract( 321 | durationToSubtract: T, 322 | overflow: Overflow = Overflow.Constrain 323 | ): PlainDate { 324 | const duration = Duration.from(durationToSubtract); 325 | return this.add(duration.negated(), overflow); 326 | } 327 | 328 | toPlainDateTime(time: PlainTime | null = null): PlainDateTime { 329 | if (time) { 330 | return new PlainDateTime( 331 | this.year, 332 | this.month, 333 | this.day, 334 | time.hour, 335 | time.minute, 336 | time.second, 337 | time.millisecond, 338 | time.microsecond, 339 | time.nanosecond 340 | ); 341 | } else { 342 | return new PlainDateTime(this.year, this.month, this.day); 343 | } 344 | } 345 | 346 | toPlainYearMonth(): PlainYearMonth { 347 | return new PlainYearMonth(this.year, this.month); 348 | } 349 | 350 | toPlainMonthDay(): PlainMonthDay { 351 | return new PlainMonthDay(this.month, this.day); 352 | } 353 | 354 | static compare(a: PlainDate, b: PlainDate): i32 { 355 | if (a === b) return 0; 356 | 357 | let res = a.year - b.year; 358 | if (res) return sign(res); 359 | 360 | res = a.month - b.month; 361 | if (res) return sign(res); 362 | 363 | return ord(a.day, b.day); 364 | } 365 | } 366 | 367 | function rejectDate(year: i32, month: i32, day: i32): void { 368 | if (!checkRange(month, 1, 12)) { 369 | throw new RangeError("month out of range"); 370 | } 371 | if (!checkRange(day, 1, daysInMonth(year, month))) { 372 | throw new RangeError("day out of range"); 373 | } 374 | } 375 | 376 | // https://github.com/tc39/proposal-temporal/blob/49629f785eee61e9f6641452e01e995f846da3a1/polyfill/lib/ecmascript.mjs#L2617 377 | function constrainDate(year: i32, month: i32, day: i32): PlainDate { 378 | month = clamp(month, 1, 12); 379 | day = clamp(day, 1, daysInMonth(year, month)); 380 | return new PlainDate(year, month, day); 381 | } 382 | 383 | // https://github.com/tc39/proposal-temporal/blob/49629f785eee61e9f6641452e01e995f846da3a1/polyfill/lib/ecmascript.mjs#L2617 384 | function regulateDate( 385 | year: i32, 386 | month: i32, 387 | day: i32, 388 | overflow: Overflow 389 | ): PlainDate { 390 | switch (overflow) { 391 | case Overflow.Reject: 392 | rejectDate(year, month, day); 393 | break; 394 | 395 | case Overflow.Constrain: 396 | const date = constrainDate(year, month, day); 397 | year = date.year; 398 | month = date.month; 399 | day = date.day; 400 | break; 401 | } 402 | 403 | return new PlainDate(year, month, day); 404 | } 405 | 406 | 407 | export function balancedDate(year: i32, month: i32, day: i32): PlainDate { 408 | const yearMonth = balancedYearMonth(year, month); 409 | 410 | year = yearMonth.year; 411 | month = yearMonth.month; 412 | 413 | let daysPerYear = 0; 414 | let testYear = month > 2 ? year : year - 1; 415 | 416 | while (((daysPerYear = daysInYear(testYear)), day < -daysPerYear)) { 417 | year -= 1; 418 | testYear -= 1; 419 | day += daysPerYear; 420 | } 421 | 422 | testYear += 1; 423 | 424 | while (((daysPerYear = daysInYear(testYear)), day > daysPerYear)) { 425 | year += 1; 426 | testYear += 1; 427 | day -= daysPerYear; 428 | } 429 | 430 | while (day < 1) { 431 | const yearMonth = balancedYearMonth(year, month - 1); 432 | year = yearMonth.year; 433 | month = yearMonth.month; 434 | day += daysInMonth(year, month); 435 | } 436 | 437 | let monthDays = 0; 438 | while (monthDays = daysInMonth(year, month), day > monthDays) { 439 | const yearMonth = balancedYearMonth(year, month + 1); 440 | year = yearMonth.year; 441 | month = yearMonth.month; 442 | day -= monthDays; 443 | } 444 | 445 | return new PlainDate(year, month, day); 446 | } 447 | -------------------------------------------------------------------------------- /assembly/plaindatetime.ts: -------------------------------------------------------------------------------- 1 | import { balancedDuration, Duration, DurationLike } from "./duration"; 2 | import { Overflow, TimeComponent } from "./enums"; 3 | import { balancedTime, PlainTime } from "./plaintime"; 4 | import { balancedDate, PlainDate } from "./plaindate"; 5 | import { PlainYearMonth } from "./plainyearmonth"; 6 | import { PlainMonthDay } from "./plainmonthday"; 7 | import { ord, sign, coalesce } from "./util"; 8 | import { formatISOString, parseISOString } from "./util/format"; 9 | import { 10 | dayOfWeek, 11 | leapYear, 12 | dayOfYear, 13 | weekOfYear, 14 | daysInMonth, 15 | daysInYear, 16 | epochFromParts 17 | } from "./util/calendar"; 18 | 19 | // @ts-ignore 20 | @lazy 21 | const NULL = -1; 22 | 23 | export class DateTimeLike { 24 | year: i32 = -1; 25 | month: i32 = -1; 26 | day: i32 = -1; 27 | hour: i32 = -1; 28 | minute: i32 = -1; 29 | second: i32 = -1; 30 | millisecond: i32 = -1; 31 | microsecond: i32 = -1; 32 | nanosecond: i32 = -1; 33 | 34 | toPlainDateTime(): PlainDateTime { 35 | if (this.year == NULL || this.month == NULL || this.day == NULL) { 36 | throw new TypeError("missing required property"); 37 | } 38 | return new PlainDateTime( 39 | this.year != NULL ? this.year : 0, 40 | this.month != NULL ? this.month : 0, 41 | this.day != NULL ? this.day : 0, 42 | this.hour != NULL ? this.hour : 0, 43 | this.minute != NULL ? this.minute : 0, 44 | this.second != NULL ? this.second : 0, 45 | this.millisecond != NULL ? this.millisecond : 0, 46 | this.microsecond != NULL ? this.microsecond : 0, 47 | this.nanosecond != NULL ? this.nanosecond : 0 48 | ); 49 | } 50 | } 51 | 52 | export class PlainDateTime { 53 | @inline 54 | static from(date: T): PlainDateTime { 55 | if (isString()) { 56 | // @ts-ignore: cast 57 | return PlainDateTime.fromString(date); 58 | } else { 59 | if (isReference()) { 60 | if (date instanceof PlainDateTime) { 61 | return PlainDateTime.fromPlainDateTime(date); 62 | } else if (date instanceof DateTimeLike) { 63 | return PlainDateTime.fromDateTimeLike(date); 64 | } 65 | } 66 | throw new TypeError("invalid date type"); 67 | } 68 | } 69 | 70 | @inline 71 | private static fromPlainDateTime(date: PlainDateTime): PlainDateTime { 72 | return new PlainDateTime( 73 | date.year, 74 | date.month, 75 | date.day, 76 | date.hour, 77 | date.minute, 78 | date.second, 79 | date.millisecond, 80 | date.microsecond, 81 | date.nanosecond 82 | ); 83 | } 84 | 85 | @inline 86 | private static fromDateTimeLike(date: DateTimeLike): PlainDateTime { 87 | return date.toPlainDateTime(); 88 | } 89 | 90 | private static fromString(date: string): PlainDateTime { 91 | const parsed = parseISOString(date); 92 | return new PlainDateTime( 93 | parsed.year, 94 | parsed.month, 95 | parsed.day, 96 | parsed.hour, 97 | parsed.minute, 98 | parsed.second, 99 | parsed.millisecond, 100 | parsed.microsecond, 101 | parsed.nanosecond 102 | ); 103 | } 104 | 105 | constructor( 106 | readonly year: i32, 107 | readonly month: i32, 108 | readonly day: i32, 109 | readonly hour: i32 = 0, 110 | readonly minute: i32 = 0, 111 | readonly second: i32 = 0, 112 | readonly millisecond: i32 = 0, 113 | readonly microsecond: i32 = 0, 114 | readonly nanosecond: i32 = 0 115 | ) {} 116 | 117 | @inline 118 | get dayOfWeek(): i32 { 119 | return dayOfWeek(this.year, this.month, this.day); 120 | } 121 | 122 | @inline 123 | get dayOfYear(): i32 { 124 | return dayOfYear(this.year, this.month, this.day); 125 | } 126 | 127 | @inline 128 | get weekOfYear(): i32 { 129 | return weekOfYear(this.year, this.month, this.day); 130 | } 131 | 132 | @inline 133 | get daysInWeek(): i32 { 134 | return 7; 135 | } 136 | 137 | @inline 138 | get daysInMonth(): i32 { 139 | return daysInMonth(this.year, this.month); 140 | } 141 | 142 | @inline 143 | get daysInYear(): i32 { 144 | return daysInYear(this.year); 145 | } 146 | 147 | @inline 148 | get monthsInYear(): i32 { 149 | return 12; 150 | } 151 | 152 | @inline 153 | get inLeapYear(): bool { 154 | return leapYear(this.year); 155 | } 156 | 157 | @inline 158 | get epochNanoseconds(): i64 { 159 | return epochFromParts( 160 | this.year, 161 | this.month, 162 | this.day, 163 | this.hour, 164 | this.minute, 165 | this.second, 166 | this.millisecond, 167 | this.microsecond, 168 | this.nanosecond 169 | ); 170 | } 171 | 172 | with(dateTimeLike: DateTimeLike): PlainDateTime { 173 | return new PlainDateTime( 174 | coalesce(dateTimeLike.year, this.year), 175 | coalesce(dateTimeLike.month, this.month), 176 | coalesce(dateTimeLike.day, this.day), 177 | coalesce(dateTimeLike.hour, this.hour), 178 | coalesce(dateTimeLike.minute, this.minute), 179 | coalesce(dateTimeLike.second, this.second), 180 | coalesce(dateTimeLike.millisecond, this.millisecond), 181 | coalesce(dateTimeLike.microsecond, this.microsecond), 182 | coalesce(dateTimeLike.nanosecond, this.nanosecond) 183 | ); 184 | } 185 | 186 | until( 187 | otherLike: T, 188 | largestUnit: TimeComponent = TimeComponent.Days 189 | ): Duration { 190 | const other = PlainDateTime.from(otherLike); 191 | 192 | const diffTime = this.toPlainTime().until(other.toPlainTime()); 193 | 194 | const balanced = balancedDate( 195 | this.year, 196 | this.month, 197 | this.day + diffTime.days 198 | ); 199 | const diffDate = balanced.until( 200 | other.toPlainDate(), 201 | min(largestUnit, TimeComponent.Days) 202 | ); 203 | 204 | // Signs of date part and time part may not agree; balance them together 205 | const balancedBoth = balancedDuration( 206 | diffDate.days, 207 | diffTime.hours, 208 | diffTime.minutes, 209 | diffTime.seconds, 210 | diffTime.milliseconds, 211 | diffTime.microseconds, 212 | diffTime.nanoseconds, 213 | largestUnit 214 | ); 215 | return new Duration( 216 | diffDate.years, 217 | diffDate.months, 218 | diffDate.weeks, 219 | balancedBoth.days, 220 | balancedBoth.hours, 221 | balancedBoth.minutes, 222 | balancedBoth.seconds, 223 | balancedBoth.milliseconds, 224 | balancedBoth.microseconds, 225 | balancedBoth.nanoseconds 226 | ); 227 | } 228 | 229 | since( 230 | otherLike: T, 231 | largestUnit: TimeComponent = TimeComponent.Days 232 | ): Duration { 233 | return PlainDateTime.from(otherLike).until(this, largestUnit); 234 | } 235 | 236 | toString(): string { 237 | return formatISOString( 238 | this.year, 239 | this.month, 240 | this.day, 241 | this.hour, 242 | this.minute, 243 | this.second, 244 | this.millisecond, 245 | this.microsecond, 246 | this.nanosecond 247 | ); 248 | } 249 | 250 | toPlainTime(): PlainTime { 251 | return new PlainTime( 252 | this.hour, 253 | this.minute, 254 | this.second, 255 | this.millisecond, 256 | this.microsecond, 257 | this.nanosecond 258 | ); 259 | } 260 | 261 | toPlainDate(): PlainDate { 262 | return new PlainDate(this.year, this.month, this.day); 263 | } 264 | 265 | toPlainYearMonth(): PlainYearMonth { 266 | return new PlainYearMonth(this.year, this.month); 267 | } 268 | 269 | toPlainMonthDay(): PlainMonthDay { 270 | return new PlainMonthDay(this.month, this.day); 271 | } 272 | 273 | static compare(a: PlainDateTime, b: PlainDateTime): i32 { 274 | if (a === b) return 0; 275 | 276 | let res = a.year - b.year; 277 | if (res) return sign(res); 278 | 279 | res = a.month - b.month; 280 | if (res) return sign(res); 281 | 282 | res = a.day - b.day; 283 | if (res) return sign(res); 284 | 285 | res = a.hour - b.hour; 286 | if (res) return sign(res); 287 | 288 | res = a.minute - b.minute; 289 | if (res) return sign(res); 290 | 291 | res = a.second - b.second; 292 | if (res) return sign(res); 293 | 294 | res = a.millisecond - b.millisecond; 295 | if (res) return sign(res); 296 | 297 | res = a.microsecond - b.microsecond; 298 | if (res) return sign(res); 299 | 300 | return ord(a.nanosecond, b.nanosecond); 301 | } 302 | 303 | @inline 304 | equals(other: PlainDateTime): bool { 305 | if (this === other) return true; 306 | return ( 307 | this.nanosecond == other.nanosecond && 308 | this.microsecond == other.microsecond && 309 | this.millisecond == other.millisecond && 310 | this.second == other.second && 311 | this.minute == other.minute && 312 | this.hour == other.hour && 313 | this.day == other.day && 314 | this.month == other.month && 315 | this.year == other.year 316 | ); 317 | } 318 | 319 | add( 320 | durationToAdd: T, 321 | overflow: Overflow = Overflow.Constrain 322 | ): PlainDateTime { 323 | const duration = Duration.from(durationToAdd); 324 | 325 | const addedTime = balancedTime( 326 | duration.hours + this.hour, 327 | duration.minutes + this.minute, 328 | duration.seconds + this.second, 329 | (duration.milliseconds + this.millisecond) as i64, 330 | (duration.microseconds + this.microsecond) as i64, 331 | (duration.nanoseconds + this.nanosecond) as i64 332 | ); 333 | 334 | const addedDate = new PlainDate(this.year, this.month, this.day).add( 335 | new Duration( 336 | duration.years, 337 | duration.months, 338 | duration.weeks, 339 | duration.days + addedTime.deltaDays 340 | ), 341 | overflow 342 | ); 343 | 344 | return new PlainDateTime( 345 | addedDate.year, 346 | addedDate.month, 347 | addedDate.day, 348 | addedTime.hour, 349 | addedTime.minute, 350 | addedTime.second, 351 | addedTime.millisecond, 352 | addedTime.microsecond, 353 | addedTime.nanosecond 354 | ); 355 | } 356 | 357 | subtract( 358 | durationToSubtract: T, 359 | overflow: Overflow = Overflow.Constrain 360 | ): PlainDateTime { 361 | return this.add(Duration.from(durationToSubtract).negated(), overflow); 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /assembly/plainmonthday.ts: -------------------------------------------------------------------------------- 1 | import { RegExp } from "assemblyscript-regex"; 2 | import { PlainDateTime } from "./plaindatetime"; 3 | import { PlainDate } from "./plaindate"; 4 | import { checkDateTimeRange } from "./util/calendar"; 5 | import { coalesce, toPaddedString } from "./util"; 6 | 7 | export class MonthDayLike { 8 | month: i32 = -1; 9 | day: i32 = -1; 10 | referenceISOYear: i32 = 1972; 11 | } 12 | 13 | export class PlainMonthDay { 14 | @inline 15 | static from(monthDay: T): PlainMonthDay { 16 | if (isString()) { 17 | // @ts-ignore: cast 18 | return this.fromString(monthDay); 19 | } else { 20 | if (isReference()) { 21 | if (monthDay instanceof PlainMonthDay) { 22 | return this.fromPlainMonthDay(monthDay); 23 | } else if (monthDay instanceof MonthDayLike) { 24 | return this.fromMonthDayLike(monthDay); 25 | } 26 | } 27 | throw new TypeError("invalid date type"); 28 | } 29 | } 30 | 31 | @inline 32 | private static fromPlainMonthDay(monthDay: PlainMonthDay): PlainMonthDay { 33 | return new PlainMonthDay( 34 | monthDay.month, 35 | monthDay.day, 36 | monthDay.referenceISOYear 37 | ); 38 | } 39 | 40 | private static fromMonthDayLike(monthDay: MonthDayLike): PlainMonthDay { 41 | if (monthDay.month == -1 || monthDay.day == -1) { 42 | throw new TypeError("missing required property"); 43 | } 44 | return new PlainMonthDay( 45 | monthDay.month, 46 | monthDay.day, 47 | monthDay.referenceISOYear 48 | ); 49 | } 50 | 51 | private static fromString(monthDay: string): PlainMonthDay { 52 | const dateRegex = new RegExp("^(?:--)?(\\d{2})-?(\\d{2})$", "i"); 53 | const match = dateRegex.exec(monthDay); 54 | if (match != null) { 55 | return new PlainMonthDay( 56 | I32.parseInt(match.matches[1]), 57 | I32.parseInt(match.matches[2]) 58 | ); 59 | } else { 60 | const datetime = PlainDateTime.from(monthDay); 61 | return new PlainMonthDay(datetime.month, datetime.day); 62 | } 63 | } 64 | 65 | constructor( 66 | readonly month: i32, 67 | readonly day: i32, 68 | readonly referenceISOYear: i32 = 1972 69 | ) { 70 | if (!checkDateTimeRange(referenceISOYear, month, day, 12)) { 71 | throw new RangeError("DateTime outside of supported range"); 72 | } 73 | } 74 | 75 | @inline 76 | get monthCode(): string { 77 | return (this.month >= 10 ? "M" : "M0") + this.month.toString(); 78 | } 79 | 80 | @inline 81 | toString(): string { 82 | return toPaddedString(this.month) + "-" + toPaddedString(this.day); 83 | } 84 | 85 | @inline 86 | toPlainDate(year: i32): PlainDate { 87 | return new PlainDate(year, this.month, this.day); 88 | } 89 | 90 | @inline 91 | equals(other: PlainMonthDay): bool { 92 | if (this === other) return true; 93 | return ( 94 | this.day == other.day && 95 | this.month == other.month && 96 | this.referenceISOYear == other.referenceISOYear 97 | ); 98 | } 99 | 100 | with(monthDay: MonthDayLike): PlainMonthDay { 101 | return new PlainMonthDay( 102 | coalesce(monthDay.month, this.month), 103 | coalesce(monthDay.day, this.day), 104 | coalesce(monthDay.referenceISOYear, this.referenceISOYear, 1972) 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /assembly/plaintime.ts: -------------------------------------------------------------------------------- 1 | import { balancedDuration, Duration, DurationLike } from "./duration"; 2 | import { Overflow, TimeComponent } from "./enums"; 3 | import { RegExp } from "assemblyscript-regex"; 4 | import { PlainDateTime } from "./plaindatetime"; 5 | import { DateLike } from "./plaindate"; 6 | import { 7 | sign, 8 | ord, 9 | toPaddedString, 10 | coalesce, 11 | clamp, 12 | floorDiv, 13 | checkRange 14 | } from "./util"; 15 | 16 | export class TimeLike { 17 | hour: i32 = -1; 18 | minute: i32 = -1; 19 | second: i32 = -1; 20 | millisecond: i32 = -1; 21 | microsecond: i32 = -1; 22 | nanosecond: i32 = -1; 23 | } 24 | 25 | export class BalancedTime { 26 | deltaDays: i32; 27 | hour: i32; 28 | minute: i32; 29 | second: i32; 30 | millisecond: i32; 31 | microsecond: i32; 32 | nanosecond: i32; 33 | } 34 | 35 | export class PlainTime { 36 | @inline 37 | static from(time: T): PlainTime { 38 | if (isString()) { 39 | // @ts-ignore: cast 40 | return PlainTime.fromString(time); 41 | } else { 42 | if (isReference()) { 43 | if (time instanceof PlainTime) { 44 | return PlainTime.fromPlainTime(time); 45 | } else if (time instanceof TimeLike) { 46 | return PlainTime.fromTimeLike(time); 47 | } else if (time instanceof PlainDateTime) { 48 | return PlainTime.fromPlainDateTime(time); 49 | } 50 | } 51 | throw new TypeError("invalid time type"); 52 | } 53 | } 54 | 55 | @inline 56 | private static fromPlainTime(time: PlainTime): PlainTime { 57 | return new PlainTime( 58 | time.hour, 59 | time.minute, 60 | time.second, 61 | time.millisecond, 62 | time.microsecond, 63 | time.nanosecond 64 | ); 65 | } 66 | 67 | @inline 68 | private static fromPlainDateTime(date: PlainDateTime): PlainTime { 69 | return new PlainTime( 70 | date.hour, 71 | date.minute, 72 | date.second, 73 | date.millisecond, 74 | date.microsecond, 75 | date.nanosecond 76 | ); 77 | } 78 | 79 | @inline 80 | private static fromTimeLike(time: TimeLike): PlainTime { 81 | return new PlainTime( 82 | coalesce(time.hour, 0), 83 | coalesce(time.minute, 0), 84 | coalesce(time.second, 0), 85 | coalesce(time.millisecond, 0), 86 | coalesce(time.microsecond, 0), 87 | coalesce(time.nanosecond, 0) 88 | ); 89 | } 90 | 91 | private static fromString(time: string): PlainTime { 92 | const timeRegex = new RegExp( 93 | "^(\\d{2})(?::(\\d{2})(?::(\\d{2})(?:[.,](\\d{1,9}))?)?|(\\d{2})(?:(\\d{2})(?:[.,](\\d{1,9}))?)?)?(?:(?:([zZ])|(?:([+\u2212-])([01][0-9]|2[0-3])(?::?([0-5][0-9])(?::?([0-5][0-9])(?:[.,](\\d{1,9}))?)?)?)?)(?:\\[((?:(?:\\.[-A-Za-z_]|\\.\\.[-A-Za-z._]{1,12}|\\.[-A-Za-z_][-A-Za-z._]{0,12}|[A-Za-z_][-A-Za-z._]{0,13})(?:\\/(?:\\.[-A-Za-z_]|\\.\\.[-A-Za-z._]{1,12}|\\.[-A-Za-z_][-A-Za-z._]{0,12}|[A-Za-z_][-A-Za-z._]{0,13}))*|Etc\\/GMT[-+]\\d{1,2}|(?:[+\\u2212-][0-2][0-9](?::?[0-5][0-9](?::?[0-5][0-9](?:[.,]\\d{1,9})?)?)?)))\\])?)?(?:\\[u-ca=((?:[A-Za-z0-9]{3,8}(?:-[A-Za-z0-9]{3,8})*))\\])?$", 94 | "i" 95 | ); 96 | const match = timeRegex.exec(time); 97 | let hour: i32, 98 | minute: i32, 99 | second: i32, 100 | millisecond: i32, 101 | microsecond: i32, 102 | nanosecond: i32; 103 | if (match != null) { 104 | hour = I32.parseInt(match.matches[1]); 105 | // see https://github.com/ColinEberhardt/assemblyscript-regex/issues/38 106 | minute = I32.parseInt( 107 | match.matches[2] != "" 108 | ? match.matches[2] 109 | : match.matches[5] != "" 110 | ? match.matches[5] 111 | : match.matches[13] 112 | ); 113 | second = I32.parseInt( 114 | match.matches[3] != "" 115 | ? match.matches[3] 116 | : match.matches[6] != "" 117 | ? match.matches[6] 118 | : match.matches[14] 119 | ); 120 | 121 | if (second === 60) second = 59; 122 | const fraction = 123 | (match.matches[4] != "" 124 | ? match.matches[4] 125 | : match.matches[7] != "" 126 | ? match.matches[7] 127 | : match.matches[15]) + "000000000"; 128 | millisecond = I32.parseInt(fraction.substring(0, 3)); 129 | microsecond = I32.parseInt(fraction.substring(3, 6)); 130 | nanosecond = I32.parseInt(fraction.substring(6, 9)); 131 | return new PlainTime( 132 | hour, 133 | minute, 134 | second, 135 | millisecond, 136 | microsecond, 137 | nanosecond 138 | ); 139 | } else { 140 | const dateTime = PlainDateTime.from(time); 141 | return new PlainTime( 142 | dateTime.hour, 143 | dateTime.minute, 144 | dateTime.second, 145 | dateTime.millisecond, 146 | dateTime.microsecond, 147 | dateTime.nanosecond 148 | ); 149 | } 150 | } 151 | 152 | constructor( 153 | readonly hour: i32 = 0, 154 | readonly minute: i32 = 0, 155 | readonly second: i32 = 0, 156 | readonly millisecond: i32 = 0, 157 | readonly microsecond: i32 = 0, 158 | readonly nanosecond: i32 = 0 159 | ) { 160 | rejectTime(hour, minute, second, millisecond, microsecond, nanosecond); 161 | } 162 | 163 | with(timeLike: TimeLike): PlainTime { 164 | return new PlainTime( 165 | coalesce(timeLike.hour, this.hour), 166 | coalesce(timeLike.minute, this.minute), 167 | coalesce(timeLike.second, this.second), 168 | coalesce(timeLike.millisecond, this.millisecond), 169 | coalesce(timeLike.microsecond, this.microsecond), 170 | coalesce(timeLike.nanosecond, this.nanosecond) 171 | ); 172 | } 173 | 174 | toString(): string { 175 | // 22:54:31 176 | return ( 177 | toPaddedString(this.hour) + 178 | ":" + 179 | toPaddedString(this.minute) + 180 | ":" + 181 | toPaddedString(this.second) + 182 | ((this.nanosecond | this.microsecond | this.millisecond) != 0 183 | ? ( 184 | f64(this.nanosecond) / 1_000_000_000.0 + 185 | f64(this.microsecond) / 1_000_000.0 + 186 | f64(this.millisecond) / 1_000.0 187 | ) 188 | .toString() 189 | .substring(1) 190 | : "") 191 | ); 192 | } 193 | 194 | toPlainDateTime(dateLike: DateLike | null = null): PlainDateTime { 195 | let year = 0, month = 0, day = 0; 196 | if (dateLike !== null) { 197 | year = coalesce(dateLike.year, 0); 198 | month = coalesce(dateLike.month, 0); 199 | day = coalesce(dateLike.day, 0); 200 | } 201 | return new PlainDateTime( 202 | year, 203 | month, 204 | day, 205 | this.hour, 206 | this.minute, 207 | this.second, 208 | this.millisecond, 209 | this.microsecond, 210 | this.nanosecond 211 | ); 212 | } 213 | 214 | until( 215 | otherLike: T, 216 | largestUnit: TimeComponent = TimeComponent.Hours 217 | ): Duration { 218 | if (largestUnit >= TimeComponent.Years && largestUnit <= TimeComponent.Days) { 219 | throw new RangeError("higher units are not allowed"); 220 | } 221 | 222 | const other = PlainTime.from(otherLike); 223 | 224 | let diffTime = differenceTime( 225 | this.hour, 226 | this.minute, 227 | this.second, 228 | this.millisecond, 229 | this.microsecond, 230 | this.nanosecond, 231 | other.hour, 232 | other.minute, 233 | other.second, 234 | other.millisecond, 235 | other.microsecond, 236 | other.nanosecond, 237 | ) 238 | return balancedDuration( 239 | // diffTime.days, 240 | 0, 241 | diffTime.hours, 242 | diffTime.minutes, 243 | diffTime.seconds, 244 | diffTime.milliseconds, 245 | diffTime.microseconds, 246 | diffTime.nanoseconds, 247 | largestUnit 248 | ); 249 | } 250 | 251 | since( 252 | otherLike: T, 253 | largestUnit: TimeComponent = TimeComponent.Hours 254 | ): Duration { 255 | if (largestUnit >= TimeComponent.Years && largestUnit <= TimeComponent.Days) { 256 | throw new RangeError("higher units are not allowed"); 257 | } 258 | 259 | const other = PlainTime.from(otherLike); 260 | 261 | let diffTime = differenceTime( 262 | this.hour, 263 | this.minute, 264 | this.second, 265 | this.millisecond, 266 | this.microsecond, 267 | this.nanosecond, 268 | other.hour, 269 | other.minute, 270 | other.second, 271 | other.millisecond, 272 | other.microsecond, 273 | other.nanosecond, 274 | ) 275 | return balancedDuration( 276 | // diffTime.days, 277 | 0, 278 | -diffTime.hours, 279 | -diffTime.minutes, 280 | -diffTime.seconds, 281 | -diffTime.milliseconds, 282 | -diffTime.microseconds, 283 | -diffTime.nanoseconds, 284 | largestUnit 285 | ); 286 | } 287 | 288 | equals(other: PlainTime): bool { 289 | if (this === other) return true; 290 | return ( 291 | this.nanosecond == other.nanosecond && 292 | this.microsecond == other.microsecond && 293 | this.millisecond == other.millisecond && 294 | this.second == other.second && 295 | this.minute == other.minute && 296 | this.hour == other.hour 297 | ); 298 | } 299 | 300 | static compare(a: PlainTime, b: PlainTime): i32 { 301 | if (a === b) return 0; 302 | 303 | let res = a.hour - b.hour; 304 | if (res) return sign(res); 305 | 306 | res = a.minute - b.minute; 307 | if (res) return sign(res); 308 | 309 | res = a.second - b.second; 310 | if (res) return sign(res); 311 | 312 | res = a.millisecond - b.millisecond; 313 | if (res) return sign(res); 314 | 315 | res = a.microsecond - b.microsecond; 316 | if (res) return sign(res); 317 | 318 | return ord(a.nanosecond, b.nanosecond); 319 | } 320 | 321 | add(durationToAdd: T): PlainTime { 322 | const duration = Duration.from(durationToAdd); 323 | 324 | const newTime = addTime( 325 | this.hour, 326 | this.minute, 327 | this.second, 328 | this.millisecond, 329 | this.microsecond, 330 | this.nanosecond, 331 | duration.hours, 332 | duration.minutes, 333 | duration.seconds, 334 | duration.milliseconds, 335 | duration.microseconds, 336 | duration.nanoseconds 337 | ); 338 | 339 | return regulateTime( 340 | newTime.hour, 341 | newTime.minute, 342 | newTime.second, 343 | newTime.millisecond, 344 | newTime.microsecond, 345 | newTime.nanosecond, 346 | Overflow.Reject 347 | ) 348 | } 349 | 350 | subtract(durationToSubtract: T): PlainTime { 351 | const duration = Duration.from(durationToSubtract); 352 | 353 | const newTime = addTime( 354 | this.hour, 355 | this.minute, 356 | this.second, 357 | this.millisecond, 358 | this.microsecond, 359 | this.nanosecond, 360 | -duration.hours, 361 | -duration.minutes, 362 | -duration.seconds, 363 | -duration.milliseconds, 364 | -duration.microseconds, 365 | -duration.nanoseconds 366 | ); 367 | 368 | return regulateTime( 369 | newTime.hour, 370 | newTime.minute, 371 | newTime.second, 372 | newTime.millisecond, 373 | newTime.microsecond, 374 | newTime.nanosecond, 375 | Overflow.Reject 376 | ); 377 | } 378 | } 379 | 380 | // https://github.com/tc39/proposal-temporal/blob/515ee6e339bb4a1d3d6b5a42158f4de49f9ed953/polyfill/lib/ecmascript.mjs#L2874-L2910 381 | export function differenceTime( 382 | h1: i32, m1: i32, s1: i32, ms1: i32, µs1: i32, ns1: i32, 383 | h2: i32, m2: i32, s2: i32, ms2: i32, µs2: i32, ns2: i32 384 | ): Duration { 385 | let hours = h2 - h1; 386 | let minutes = m2 - m1; 387 | let seconds = s2 - s1; 388 | let milliseconds = ms2 - ms1; 389 | let microseconds = µs2 - µs1; 390 | let nanoseconds = ns2 - ns1; 391 | 392 | let sig = 0; 393 | if (hours) sig = sign(hours); 394 | else if (minutes) sig = sign(minutes); 395 | else if (seconds) sig = sign(seconds); 396 | else if (milliseconds) sig = sign(milliseconds); 397 | else if (microseconds) sig = sign(microseconds); 398 | else if (nanoseconds) sig = sign(nanoseconds); 399 | 400 | const balanced = balancedTime( 401 | hours * sig, 402 | minutes * sig, 403 | seconds * sig, 404 | milliseconds * sig, 405 | microseconds * sig, 406 | nanoseconds * sig 407 | ); 408 | 409 | return new Duration( 410 | 0, 411 | 0, 412 | 0, 413 | balanced.deltaDays * sig, 414 | balanced.hour * sig, 415 | balanced.minute * sig, 416 | balanced.second * sig, 417 | balanced.millisecond * sig, 418 | balanced.microsecond * sig, 419 | balanced.nanosecond * sig 420 | ); 421 | } 422 | 423 | function addTime( 424 | hour: i32, 425 | minute: i32, 426 | second: i32, 427 | millisecond: i32, 428 | microsecond: i32, 429 | nanosecond: i32, 430 | hours: i32, 431 | minutes: i32, 432 | seconds: i32, 433 | milliseconds: i64, 434 | microseconds: i64, 435 | nanoseconds: i64 436 | ): BalancedTime { 437 | 438 | hours += hour; 439 | minutes += minute; 440 | seconds += second; 441 | milliseconds += millisecond; 442 | microseconds += microsecond; 443 | nanoseconds += nanosecond; 444 | 445 | return balancedTime( 446 | hours, minutes, seconds, 447 | milliseconds, microseconds, nanoseconds 448 | ); 449 | } 450 | 451 | // https://github.com/tc39/proposal-temporal/blob/515ee6e339bb4a1d3d6b5a42158f4de49f9ed953/polyfill/lib/ecmascript.mjs#L2676-L2684 452 | function constrainTime( 453 | hour: i32, 454 | minute: i32, 455 | second: i32, 456 | millisecond: i32, 457 | microsecond: i32, 458 | nanosecond: i32, 459 | ): PlainTime { 460 | hour = clamp(hour, 0, 23); 461 | minute = clamp(minute, 0, 59); 462 | second = clamp(second, 0, 59); 463 | millisecond = clamp(millisecond, 0, 999); 464 | microsecond = clamp(microsecond, 0, 999); 465 | nanosecond = clamp(nanosecond, 0, 999); 466 | return new PlainTime(hour, minute, second, millisecond, microsecond, nanosecond); 467 | } 468 | 469 | // https://github.com/tc39/proposal-temporal/blob/515ee6e339bb4a1d3d6b5a42158f4de49f9ed953/polyfill/lib/ecmascript.mjs#L407-L422 470 | function regulateTime( 471 | hour: i32, 472 | minute: i32, 473 | second: i32, 474 | millisecond: i32, 475 | microsecond: i32, 476 | nanosecond: i32, 477 | overflow: Overflow 478 | ): PlainTime { 479 | switch (overflow) { 480 | case Overflow.Reject: 481 | rejectTime(hour, minute, second, millisecond, microsecond, nanosecond); 482 | break; 483 | 484 | case Overflow.Constrain: 485 | const time = constrainTime( 486 | hour, 487 | minute, 488 | second, 489 | millisecond, 490 | microsecond, 491 | nanosecond 492 | ); 493 | hour = time.hour; 494 | minute = time.minute; 495 | second = time.second; 496 | millisecond = time.millisecond; 497 | microsecond = time.microsecond; 498 | nanosecond = time.nanosecond; 499 | break; 500 | } 501 | 502 | return new PlainTime(hour, minute, second, millisecond, microsecond, nanosecond); 503 | } 504 | 505 | function rejectTime( 506 | hour: i32, 507 | minute: i32, 508 | second: i32, 509 | millisecond: i32, 510 | microsecond: i32, 511 | nanosecond: i32 512 | ): void { 513 | if (!( 514 | checkRange(hour, 0, 23) && 515 | checkRange(minute, 0, 59) && 516 | checkRange(second, 0, 59) && 517 | checkRange(millisecond, 0, 999) && 518 | checkRange(microsecond, 0, 999) && 519 | checkRange(nanosecond, 0, 999) 520 | )) throw new RangeError("time out of range"); 521 | } 522 | 523 | export function balancedTime( 524 | hour: i64, 525 | minute: i64, 526 | second: i64, 527 | millisecond: i64, 528 | microsecond: i64, 529 | nanosecond: i64 530 | ): BalancedTime { 531 | 532 | let quotient = floorDiv(nanosecond, 1000); 533 | microsecond += quotient; 534 | nanosecond -= quotient * 1000; 535 | 536 | quotient = floorDiv(microsecond, 1000); 537 | millisecond += quotient; 538 | microsecond -= quotient * 1000; 539 | 540 | quotient = floorDiv(millisecond, 1000); 541 | second += quotient; 542 | millisecond -= quotient * 1000; 543 | 544 | quotient = floorDiv(second, 60); 545 | minute += quotient; 546 | second -= quotient * 60; 547 | 548 | quotient = floorDiv(minute, 60); 549 | hour += quotient; 550 | minute -= quotient * 60; 551 | 552 | let deltaDays = floorDiv(hour, 24); 553 | hour -= deltaDays * 24; 554 | 555 | return { 556 | deltaDays: i32(deltaDays), 557 | hour: i32(hour), 558 | minute: i32(minute), 559 | second: i32(second), 560 | millisecond: i32(millisecond), 561 | microsecond: i32(microsecond), 562 | nanosecond: i32(nanosecond) 563 | }; 564 | } 565 | -------------------------------------------------------------------------------- /assembly/plainyearmonth.ts: -------------------------------------------------------------------------------- 1 | import { RegExp } from "assemblyscript-regex"; 2 | import { balancedDuration, Duration, DurationLike } from "./duration"; 3 | import { Overflow, TimeComponent } from "./enums"; 4 | import { PlainDate } from "./plaindate"; 5 | import { PlainDateTime } from "./plaindatetime"; 6 | import { isoYearString } from "./util/format"; 7 | import { 8 | leapYear, 9 | daysInMonth, 10 | daysInYear, 11 | checkDateTimeRange 12 | } from "./util/calendar" 13 | import { 14 | coalesce, 15 | toPaddedString, 16 | floorDiv, 17 | sign, 18 | ord 19 | } from "./util"; 20 | 21 | export class YearMonthLike { 22 | year: i32 = -1; 23 | month: i32 = -1; 24 | referenceISODay: i32 = -1; 25 | } 26 | 27 | export class PlainYearMonth { 28 | @inline 29 | static from(yearMonth: T): PlainYearMonth { 30 | if (isString()) { 31 | // @ts-ignore: cast 32 | return PlainYearMonth.fromString(yearMonth); 33 | } else { 34 | if (isReference()) { 35 | if (yearMonth instanceof PlainYearMonth) { 36 | return PlainYearMonth.fromPlainYearMonth(yearMonth); 37 | } else if (yearMonth instanceof YearMonthLike) { 38 | return PlainYearMonth.fromYearMonthLike(yearMonth); 39 | } 40 | } 41 | throw new TypeError("invalid yearMonth type"); 42 | } 43 | } 44 | 45 | @inline 46 | private static fromPlainYearMonth(yearMonth: PlainYearMonth): PlainYearMonth { 47 | return new PlainYearMonth( 48 | yearMonth.year, 49 | yearMonth.month, 50 | yearMonth.referenceISODay 51 | ); 52 | } 53 | 54 | @inline 55 | private static fromYearMonthLike(yearMonth: YearMonthLike): PlainYearMonth { 56 | if (yearMonth.year == -1 || yearMonth.month == -1) { 57 | throw new TypeError("missing required property"); 58 | } 59 | 60 | if (yearMonth.referenceISODay == -1) yearMonth.referenceISODay = 1; 61 | 62 | return new PlainYearMonth( 63 | yearMonth.year, 64 | yearMonth.month, 65 | yearMonth.referenceISODay 66 | ); 67 | } 68 | 69 | @inline 70 | private static fromString(yearMonth: string): PlainYearMonth { 71 | const dateRegex = new RegExp( 72 | "^((?:[+\u2212-]\\d{6}|\\d{4}))-?(\\d{2})$", 73 | "i" 74 | ); 75 | const match = dateRegex.exec(yearMonth); 76 | if (match != null) { 77 | let yearStr = match.matches[1]; 78 | 79 | if (yearStr.charAt(0) === "\u2212") yearStr = "-" + yearStr.slice(1); 80 | // workaround for parsing "-009999" year strings 81 | if (yearStr.charAt(0) == "−") 82 | yearStr = "-" + I32.parseInt(yearStr.slice(1)).toString(); 83 | 84 | return new PlainYearMonth( 85 | I32.parseInt(yearStr), 86 | I32.parseInt(match.matches[2]) 87 | ); 88 | } else { 89 | const dateTime = PlainDateTime.from(yearMonth); 90 | return new PlainYearMonth(dateTime.year, dateTime.month); 91 | } 92 | } 93 | 94 | constructor( 95 | readonly year: i32, 96 | readonly month: i32, 97 | readonly referenceISODay: i32 = 1 98 | ) { 99 | if (!checkDateTimeRange(year, month, referenceISODay, 12)) { 100 | throw new RangeError("DateTime outside of supported range"); 101 | } 102 | } 103 | 104 | @inline 105 | get daysInMonth(): i32 { 106 | return daysInMonth(this.year, this.month); 107 | } 108 | 109 | @inline 110 | get daysInYear(): i32 { 111 | return daysInYear(this.year); 112 | } 113 | 114 | @inline 115 | get monthsInYear(): i32 { 116 | return 12; 117 | } 118 | 119 | @inline 120 | get inLeapYear(): bool { 121 | return leapYear(this.year); 122 | } 123 | 124 | @inline 125 | get monthCode(): string { 126 | return (this.month >= 10 ? "M" : "M0") + this.month.toString(); 127 | } 128 | 129 | @inline 130 | toString(): string { 131 | return isoYearString(this.year) + "-" + toPaddedString(this.month); 132 | } 133 | 134 | @inline 135 | toPlainDate(day: i32): PlainDate { 136 | return new PlainDate(this.year, this.month, day); 137 | } 138 | 139 | @inline 140 | equals(other: PlainYearMonth): bool { 141 | if (this === other) return true; 142 | return ( 143 | this.month == other.month && 144 | this.year == other.year && 145 | this.referenceISODay == other.referenceISODay 146 | ); 147 | } 148 | 149 | until( 150 | yearMonthLike: T, 151 | largestUnit: TimeComponent = TimeComponent.Years 152 | ): Duration { 153 | if (largestUnit > TimeComponent.Months) 154 | throw new RangeError("lower units are not allowed"); 155 | 156 | const yearMonth = PlainYearMonth.from(yearMonthLike); 157 | 158 | const thisDate = new PlainDate(this.year, this.month, this.referenceISODay); 159 | const otherDate = new PlainDate( 160 | yearMonth.year, 161 | yearMonth.month, 162 | yearMonth.referenceISODay 163 | ); 164 | const result = thisDate.until(otherDate, largestUnit); 165 | return new Duration(result.years, result.months); 166 | } 167 | 168 | since( 169 | yearMonthLike: T, 170 | largestUnit: TimeComponent = TimeComponent.Years 171 | ): Duration { 172 | if (largestUnit > TimeComponent.Months) 173 | throw new RangeError("lower units are not allowed"); 174 | 175 | const yearMonth = PlainYearMonth.from(yearMonthLike); 176 | 177 | const thisDate = new PlainDate(this.year, this.month, this.referenceISODay); 178 | const otherDate = new PlainDate( 179 | yearMonth.year, 180 | yearMonth.month, 181 | yearMonth.referenceISODay 182 | ); 183 | const result = thisDate.since(otherDate, largestUnit); 184 | return new Duration(result.years, result.months); 185 | } 186 | 187 | with(yearMonth: YearMonthLike): PlainYearMonth { 188 | return new PlainYearMonth( 189 | coalesce(yearMonth.year, this.year), 190 | coalesce(yearMonth.month, this.month), 191 | coalesce(yearMonth.referenceISODay, this.referenceISODay) 192 | ); 193 | } 194 | 195 | add( 196 | durationToAdd: T, 197 | overflow: Overflow = Overflow.Constrain 198 | ): PlainYearMonth { 199 | const duration = Duration.from(durationToAdd); 200 | 201 | const balancedDur = balancedDuration( 202 | duration.days, 203 | duration.hours, 204 | duration.minutes, 205 | duration.seconds, 206 | duration.milliseconds, 207 | duration.microseconds, 208 | duration.nanoseconds, 209 | TimeComponent.Days 210 | ); 211 | 212 | let sig = 0; 213 | if (duration.years) sig = sign(duration.years); 214 | else if (duration.months) sig = sign(duration.months); 215 | else if (duration.weeks) sig = sign(duration.weeks); 216 | else if (balancedDur.days) sig = sign(balancedDur.days); 217 | 218 | const day = sig < 0 ? daysInMonth(this.year, this.month) : 1; 219 | const startDate = new PlainDate(this.year, this.month, day); 220 | const addedDate = startDate.add(duration, overflow); 221 | return new PlainYearMonth(addedDate.year, addedDate.month); 222 | } 223 | 224 | subtract( 225 | durationToAdd: T, 226 | overflow: Overflow = Overflow.Constrain 227 | ): PlainYearMonth { 228 | let duration = Duration.from(durationToAdd); 229 | 230 | duration = new Duration( 231 | -duration.years, 232 | -duration.months, 233 | -duration.weeks, 234 | -duration.days, 235 | -duration.hours, 236 | -duration.minutes, 237 | -duration.seconds, 238 | -duration.milliseconds, 239 | -duration.microseconds, 240 | -duration.nanoseconds 241 | ); 242 | 243 | const balancedDur = balancedDuration( 244 | duration.days, 245 | duration.hours, 246 | duration.minutes, 247 | duration.seconds, 248 | duration.milliseconds, 249 | duration.microseconds, 250 | duration.nanoseconds, 251 | TimeComponent.Days 252 | ); 253 | 254 | let sig = 0; 255 | if (duration.years) sig = sign(duration.years); 256 | else if (duration.months) sig = sign(duration.months); 257 | else if (duration.weeks) sig = sign(duration.weeks); 258 | else if (balancedDur.days) sig = sign(balancedDur.days); 259 | 260 | const day = sig < 0 ? daysInMonth(this.year, this.month) : 1; 261 | const startdate = new PlainDate(this.year, this.month, day); 262 | const subtractedDate = startdate.add(duration, overflow); 263 | return new PlainYearMonth(subtractedDate.year, subtractedDate.month); 264 | } 265 | 266 | static compare(a: PlainYearMonth, b: PlainYearMonth): i32 { 267 | if (a === b) return 0; 268 | 269 | let res = a.year - b.year; 270 | if (res) return sign(res); 271 | 272 | res = a.month - b.month; 273 | if (res) return sign(res); 274 | 275 | return ord(a.referenceISODay, b.referenceISODay); 276 | } 277 | } 278 | 279 | export function balancedYearMonth(year: i32, month: i32): PlainYearMonth { 280 | month -= 1; 281 | year += floorDiv(month, 12); 282 | month %= 12; 283 | month += month < 0 ? 13 : 1; 284 | return new PlainYearMonth(year, month); 285 | } 286 | -------------------------------------------------------------------------------- /assembly/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "assemblyscript/std/assembly.json", 3 | "include": [ 4 | "./**/*.ts" 5 | ] 6 | } -------------------------------------------------------------------------------- /assembly/util/calendar.ts: -------------------------------------------------------------------------------- 1 | import { checkRange, floorDiv } from "."; 2 | 3 | // @ts-ignore 4 | @lazy 5 | const YEAR_MIN = -271821; 6 | 7 | // @ts-ignore 8 | @lazy 9 | const YEAR_MAX = 275760; 10 | 11 | // @ts-ignore 12 | @lazy 13 | let __null = false; 14 | 15 | // modified of 16 | // https://github.com/tc39/proposal-temporal/blob/49629f785eee61e9f6641452e01e995f846da3a1/polyfill/lib/ecmascript.mjs#L2157 17 | // @ts-ignore: decorator 18 | @inline 19 | export function leapYear(year: i32): bool { 20 | return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; 21 | } 22 | 23 | // modified of 24 | // https://github.com/tc39/proposal-temporal/blob/49629f785eee61e9f6641452e01e995f846da3a1/polyfill/lib/ecmascript.mjs#L2188 25 | export function dayOfYear(year: i32, month: i32, day: i32): i32 { 26 | const cumsumMonthDays = memory.data([ 27 | 0, 28 | 31, // Jan 29 | 31 + 28, // Feb 30 | 31 + 28 + 31, // Mar 31 | 31 + 28 + 31 + 30, // Apr 32 | 31 + 28 + 31 + 30 + 31, // May 33 | 31 + 28 + 31 + 30 + 31 + 30, // Jun 34 | 31 + 28 + 31 + 30 + 31 + 30 + 31, // Jul 35 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, // Aug 36 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, // Sep 37 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, // Oct 38 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, // Nov 39 | ]); 40 | return ( 41 | day + 42 | i32(load(cumsumMonthDays + ((month - 1) << 1))) + 43 | i32(month >= 3 && leapYear(year)) 44 | ); 45 | } 46 | 47 | // modified of 48 | // https://github.com/tc39/proposal-temporal/blob/49629f785eee61e9f6641452e01e995f846da3a1/polyfill/lib/ecmascript.mjs#L2164 49 | export function daysInMonth(year: i32, month: i32): i32 { 50 | return month == 2 51 | ? 28 + i32(leapYear(year)) 52 | : 30 + ((month + i32(month >= 8)) & 1); 53 | } 54 | 55 | // @ts-ignore: decorator 56 | @inline 57 | export function daysInYear(year: i32): i32 { 58 | return 365 + i32(leapYear(year)); 59 | } 60 | 61 | // Original: Disparate variation 62 | // Modified: TomohikoSakamoto algorithm from https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week 63 | // https://github.com/tc39/proposal-temporal/blob/49629f785eee61e9f6641452e01e995f846da3a1/polyfill/lib/ecmascript.mjs#L2171 64 | // returns day of week in range [1,7], where 7 = Sunday 65 | export function dayOfWeek(year: i32, month: i32, day: i32): i32 { 66 | const tab = memory.data([0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]); 67 | 68 | year -= i32(month < 3); 69 | year += year / 4 - year / 100 + year / 400; 70 | month = load(tab + month - 1); 71 | const w = (year + month + day) % 7; 72 | // Use ISO 8601 which has [1, 7] range to represent Monday-Sunday 73 | return w + (w <= 0 ? 7 : 0); 74 | } 75 | 76 | // https://github.com/tc39/proposal-temporal/blob/49629f785eee61e9f6641452e01e995f846da3a1/polyfill/lib/ecmascript.mjs#L2667 77 | export function checkDateTimeRange( 78 | year: i32, 79 | month: i32, 80 | day: i32, 81 | hour: i32 = 0, 82 | minute: i32 = 0, 83 | second: i32 = 0, 84 | millisecond: i32 = 0, 85 | microsecond: i32 = 0, 86 | nanosecond: i32 = 0 87 | ): bool { 88 | if (!checkRange(year, YEAR_MIN, YEAR_MAX)) { 89 | return false; 90 | } 91 | // reject any DateTime 24 hours or more outside the Instant range 92 | if ((year == YEAR_MIN && (epochFromParts( 93 | year, month, day + 1, hour, minute, second, 94 | millisecond, microsecond, nanosecond - 1 95 | ), __null))) return false; 96 | 97 | if ((year == YEAR_MAX && (epochFromParts( 98 | year, month, day - 1, hour, minute, second, 99 | millisecond, microsecond, nanosecond + 1 100 | ), __null))) return false; 101 | 102 | return true; 103 | } 104 | 105 | export function epochFromParts( 106 | year: i32, 107 | month: i32, 108 | day: i32, 109 | hour: i32, 110 | minute: i32, 111 | second: i32, 112 | millisecond: i32, 113 | microsecond: i32, 114 | nanosecond: i32 115 | ): i64 { 116 | const millis = Date.UTC(year, month - 1, day, hour, minute, second, millisecond); 117 | return millis * 1_000_000 + microsecond * 1_000 + nanosecond; 118 | } 119 | 120 | 121 | // https://github.com/tc39/proposal-temporal/blob/49629f785eee61e9f6641452e01e995f846da3a1/polyfill/lib/ecmascript.mjs#L2135 122 | export function weekOfYear(year: i32, month: i32, day: i32): i32 { 123 | let doy = dayOfYear(year, month, day); 124 | let dow = dayOfWeek(year, month, day) || 7; 125 | let doj = dayOfWeek(year, 1, 1); 126 | 127 | const week = floorDiv(doy - dow + 10, 7); 128 | 129 | if (week < 1) { 130 | return doj === 5 || (doj === 6 && leapYear(year - 1)) ? 53 : 52; 131 | } 132 | 133 | if (week === 53) { 134 | if (daysInYear(year) - doy < 4 - dow) { 135 | return 1; 136 | } 137 | } 138 | 139 | return week; 140 | } -------------------------------------------------------------------------------- /assembly/util/constants.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | @lazy 3 | export const MILLIS_PER_DAY = 1_000 * 60 * 60 * 24; 4 | 5 | // @ts-ignore 6 | @lazy 7 | export const MILLIS_PER_HOUR = 1_000 * 60 * 60; 8 | 9 | // @ts-ignore 10 | @lazy 11 | export const MILLIS_PER_MINUTE = 1_000 * 60; 12 | 13 | // @ts-ignore 14 | @lazy 15 | export const MILLIS_PER_SECOND = 1_000; 16 | 17 | // @ts-ignore 18 | @lazy 19 | export const MICROS_PER_SECOND = 1_000_000; 20 | 21 | // @ts-ignore 22 | @lazy 23 | export const NANOS_PER_SECOND = 1_000_000_000; 24 | 25 | // @ts-ignore 26 | @lazy 27 | export const NANOS_PER_DAY = i64(MILLIS_PER_DAY) * 1_000_000; 28 | -------------------------------------------------------------------------------- /assembly/util/format.ts: -------------------------------------------------------------------------------- 1 | import { RegExp } from "assemblyscript-regex"; 2 | 3 | import { MICROS_PER_SECOND, MILLIS_PER_SECOND, NANOS_PER_SECOND } from "./constants"; 4 | import { toPaddedString } from "."; 5 | 6 | export class DTZ { 7 | year: i32; 8 | month: i32; 9 | day: i32; 10 | hour: i32; 11 | minute: i32; 12 | second: i32; 13 | millisecond: i32; 14 | microsecond: i32; 15 | nanosecond: i32; 16 | timezone: string; 17 | } 18 | 19 | export function isoYearString(year: i32): string { 20 | if (year < 1000 || year > 9999) { 21 | let sign = year < 0 ? '-' : '+'; 22 | return sign + `000000${abs(year)}`.slice(-6); 23 | } else { 24 | return year.toString(); 25 | } 26 | } 27 | 28 | export function parseISOString(date: string): DTZ { 29 | const dateRegex = new RegExp( 30 | "^((?:[+\u2212-]\\d{6}|\\d{4}))(?:-(\\d{2})-(\\d{2})|(\\d{2})(\\d{2}))(?:(?:T|\\s+)(\\d{2})(?::(\\d{2})(?::(\\d{2})(?:[.,](\\d{1,9}))?)?|(\\d{2})(?:(\\d{2})(?:[.,](\\d{1,9}))?)?)?)?(?:(?:([zZ])|(?:([+\u2212-])([01][0-9]|2[0-3])(?::?([0-5][0-9])(?::?([0-5][0-9])(?:[.,](\\d{1,9}))?)?)?)?)(?:\\[((?:(?:\\.[-A-Za-z_]|\\.\\.[-A-Za-z._]{1,12}|\\.[-A-Za-z_][-A-Za-z._]{0,12}|[A-Za-z_][-A-Za-z._]{0,13})(?:\\/(?:\\.[-A-Za-z_]|\\.\\.[-A-Za-z._]{1,12}|\\.[-A-Za-z_][-A-Za-z._]{0,12}|[A-Za-z_][-A-Za-z._]{0,13}))*|Etc\\/GMT[-+]\\d{1,2}|(?:[+\u2212-][0-2][0-9](?::?[0-5][0-9](?::?[0-5][0-9](?:[.,]\\d{1,9})?)?)?)))\\])?)?(?:\\[u-ca=((?:[A-Za-z0-9]{3,8}(?:-[A-Za-z0-9]{3,8})*))\\])?$", 31 | "i" 32 | ); 33 | const match = dateRegex.exec(date); 34 | if (match == null) { 35 | throw new RangeError("invalid ISO 8601 string: " + date); 36 | } 37 | // see https://github.com/ColinEberhardt/assemblyscript-regex/issues/38 38 | const fraction = ( 39 | match.matches[7] != "" ? match.matches[7] : match.matches[18] 40 | ) + "000000000"; 41 | 42 | return { 43 | year: I32.parseInt(match.matches[1]), 44 | month: I32.parseInt( 45 | match.matches[2] != "" ? match.matches[2] : match.matches[19] 46 | ), 47 | day: I32.parseInt( 48 | match.matches[3] != "" ? match.matches[3] : match.matches[20] 49 | ), 50 | hour: I32.parseInt(match.matches[4]), 51 | minute: I32.parseInt(match.matches[5] != "" ? match.matches[5] : match.matches[16]), 52 | second: I32.parseInt(match.matches[6] != "" ? match.matches[6] : match.matches[17]), 53 | millisecond: I32.parseInt(fraction.substring(0, 3)), 54 | microsecond: I32.parseInt(fraction.substring(3, 6)), 55 | nanosecond: I32.parseInt(fraction.substring(6, 9)), 56 | timezone: match.matches[9] 57 | } 58 | } 59 | 60 | export function formatISOString(year: i32, month: i32, day: i32, hour: i32, minute: i32, 61 | second: i32, millisecond: i32, microsecond: i32, nanosecond: i32): string { 62 | return ( 63 | year.toString() + 64 | "-" + 65 | toPaddedString(month) + 66 | "-" + 67 | toPaddedString(day) + 68 | "T" + 69 | toPaddedString(hour) + 70 | ":" + 71 | toPaddedString(minute) + 72 | ":" + 73 | toPaddedString(second) + 74 | (nanosecond != 0 || microsecond != 0 || millisecond != 0 75 | ? ( 76 | f64(nanosecond) / NANOS_PER_SECOND + 77 | f64(microsecond) / MICROS_PER_SECOND + 78 | f64(millisecond) / MILLIS_PER_SECOND 79 | ) 80 | .toString() 81 | .substring(1) 82 | : "") 83 | ); 84 | } -------------------------------------------------------------------------------- /assembly/util/index.ts: -------------------------------------------------------------------------------- 1 | // for the proposal-temporal implementation, most of the business logic 2 | // sits within the ecmascript.mjs file: 3 | // 4 | // https://github.com/tc39/proposal-temporal/blob/49629f785eee61e9f6641452e01e995f846da3a1/polyfill/lib/ecmascript.mjs 5 | // 6 | // here we use the same structure to make it easier to audit this implementation 7 | // to ensure correctess 8 | 9 | import { TimeComponent } from "../enums"; 10 | 11 | 12 | // @ts-ignore: decorator 13 | @inline 14 | export function sign(x: T): T { 15 | // optimized variant of x < 0 ? -1 : 1 16 | // i32: x >> 31 | 1 17 | // i64: x >> 63 | 1 18 | // @ts-ignore 19 | return ((x >> (sizeof() * 8 - 1)) | 1) as T; 20 | } 21 | 22 | // @ts-ignore: decorator 23 | @inline 24 | export function ord(x: T, y: T): i32 { 25 | return i32(x > y) - i32(x < y); 26 | } 27 | 28 | // @ts-ignore: decorator 29 | @inline 30 | export function floorDiv(a: T, b: T): T { 31 | return (a >= 0 ? a : a - b + 1) / b as T; 32 | } 33 | 34 | // @ts-ignore: decorator 35 | @inline 36 | export function nonNegativeModulo(x: T, y: T): T { 37 | x = x % y as T; 38 | return (x < 0 ? x + y : x) as T; 39 | } 40 | 41 | // https://github.com/tc39/proposal-temporal/blob/49629f785eee61e9f6641452e01e995f846da3a1/polyfill/lib/ecmascript.mjs#L2616 42 | // @ts-ignore: decorator 43 | @inline 44 | export function clamp(value: i32, lo: i32, hi: i32): i32 { 45 | return min(max(value, lo), hi); 46 | } 47 | 48 | // https://github.com/tc39/proposal-temporal/blob/51c6c5138b5b73817f5e0ff2694fe0134f09b0a7/polyfill/lib/ecmascript.mjs#L2704 49 | // @ts-ignore: decorator 50 | @inline 51 | export function checkRange(value: i32, lo: i32, hi: i32): bool { 52 | return u32(value - lo) <= u32(hi - lo); 53 | } 54 | 55 | // @ts-ignore: decorator 56 | @inline 57 | export function toPaddedString(number: i32, length: i32 = 2): string { 58 | return number.toString().padStart(length, "0"); 59 | } 60 | 61 | // @ts-ignore: decorator 62 | @inline 63 | export function coalesce(a: T, b: T, nill: T = -1 as T):T { 64 | return a == nill ? b : a; 65 | } 66 | -------------------------------------------------------------------------------- /development.md: -------------------------------------------------------------------------------- 1 | ## Development and roadmap 2 | 3 | This is a very large library, therefore a prioritised roadmap is important. The following is the rough priority order: 4 | 5 | 1. Non-timezone aware classes, i.e. `PlainDate`, `PlainDateTime`, etc with a hard-coded Gregorian calendar. 6 | 2. Ancillary classes, i.e. `Instant`, `Duration` and `Now` 7 | 3. Time-zone aware classes, `TimeZone`, `ZonedDateTime`, etc 8 | 4. Non gregorian calendar systems 9 | 10 | So far much of (1 & 2) have been implemented. Also a 'spike' implementation of (3) has been created to determine a suitable approach for implementing timezone offsets. 11 | 12 | 13 | ### Implementation approach 14 | 15 | The current approach is as follows: 16 | 17 | 1. Use the temporal polyfill test cases as a means to ensure implementation correctness. Currently these test cases are cut / paste with a few tweaks. Ideally this would be automated to ensure parity going forwards 18 | 2. Use the polyfill implementation as a starting point. However, it is riddled with JS-specific code that doesn't make sense to port. However, most of the algorithmic code is [within a single file](https://github.com/tc39/proposal-temporal/blob/main/polyfill/lib/ecmascript.mjs), which can be ported relatively easily. 19 | 3. Don't bother refactoring heavily, being able to map between the polyfill implementation and this codebase will help ensure correctness 20 | 21 | ### Implementation progress 22 | 23 | #### PlainDate 24 | 25 | PlainDate is currently being implemented based on the ISO 8601 calendar. 26 | 27 | Constructor 28 | 29 | - [x] new Temporal.PlainDate 30 | 31 | Static methods 32 | 33 | - [x] from 34 | - [x] compare 35 | 36 | Properties 37 | 38 | - [x] year 39 | - [x] month 40 | - [x] monthCode 41 | - [x] day 42 | - [ ] calendar 43 | - [ ] era 44 | - [ ] eraYear 45 | - [x] dayOfWeek 46 | - [x] dayOfYear 47 | - [x] weekOfYear 48 | - [x] daysInWeek 49 | - [x] daysInMonth 50 | - [x] daysInYear 51 | - [x] monthsInYear 52 | - [x] inLeapYear 53 | 54 | Methods 55 | 56 | - [x] with 57 | - [ ] withCalendar 58 | - [x] add 59 | - [x] subtract 60 | - [x] until 61 | - [x] since 62 | - [x] equals 63 | - [x] toString 64 | - [ ] toLocaleString 65 | - [ ] toJSON 66 | - [ ] valueOf 67 | - [ ] toZonedDateTime 68 | - [x] toPlainDateTime 69 | - [x] toPlainYearMonth 70 | - [x] toPlainMonthDay 71 | - [ ] getISOFields 72 | 73 | General features 74 | 75 | - [x] overflow modes (current implementation defaults to constrain) 76 | - [ ] non ISO 8601 calendars 77 | 78 | #### PlainTime 79 | 80 | PlainTime is currently being implemented based on the ISO 8601 calendar. 81 | 82 | Constructor 83 | 84 | - [x] new PlainTime 85 | 86 | Static methods 87 | 88 | - [x] from 89 | - [x] compare 90 | 91 | Properties 92 | 93 | - [x] hour 94 | - [x] minute 95 | - [x] second 96 | - [x] millisecond 97 | - [x] microsecond 98 | - [x] nanosecond 99 | - [ ] calendar 100 | 101 | Methods 102 | 103 | - [x] with 104 | - [x] add 105 | - [x] subtract 106 | - [x] until 107 | - [x] since 108 | - [ ] round 109 | - [x] equals 110 | - [x] toString 111 | - [ ] toLocaleString 112 | - [ ] toJSON 113 | - [ ] valueOf 114 | - [ ] toZonedDateTime 115 | - [x] toPlainDateTime 116 | - [ ] getISOFields 117 | 118 | General features 119 | 120 | - [x] overflow modes (current implementation defaults to constrain) 121 | - [ ] non ISO 8601 calendars 122 | 123 | #### PlainMonthDay 124 | 125 | PlainMonthDay is currently being implemented based on the ISO 8601 calendar. 126 | 127 | Constructor 128 | 129 | - [x] new PlainMonthDay 130 | 131 | Static methods 132 | 133 | - [x] from 134 | 135 | Properties 136 | 137 | - [x] monthCode 138 | - [x] day 139 | - [ ] calendar 140 | 141 | Methods 142 | 143 | - [x] with 144 | - [x] equals 145 | - [x] toString 146 | - [ ] toLocaleString 147 | - [ ] toJSON 148 | - [ ] valueOf 149 | - [x] toPlainDate 150 | - [ ] getISOFields 151 | 152 | #### PlainYearMonth 153 | 154 | PlainYearMonth is currently being implemented based on the ISO 8601 calendar. 155 | 156 | Constructor 157 | 158 | - [x] new Temporal.PlainYearMonth 159 | 160 | Static methods 161 | 162 | - [x] from 163 | - [x] compare 164 | 165 | Properties 166 | 167 | - [x] year 168 | - [x] month 169 | - [x] monthCode 170 | - [ ] calendar 171 | - [ ] era 172 | - [ ] eraYear 173 | - [x] daysInMonth 174 | - [x] daysInYear 175 | - [x] monthsInYear 176 | - [x] inLeapYear 177 | 178 | Methods 179 | 180 | - [x] with 181 | - [x] add 182 | - [x] subtract 183 | - [x] until 184 | - [x] since 185 | - [x] equals 186 | - [x] toString 187 | - [ ] toLocaleString 188 | - [ ] toJSON 189 | - [ ] valueOf 190 | - [x] toPlainDate 191 | - [ ] getISOFields 192 | 193 | General features 194 | 195 | - [x] overflow modes (current implementation defaults to constrain) 196 | - [ ] non ISO 8601 calendars 197 | 198 | #### PlainDateTime 199 | 200 | PlainDateTime is currently being implemented based on the ISO 8601 calendar. 201 | 202 | Constructor 203 | 204 | - [x] new Temporal.PlainDateTime 205 | 206 | Static methods 207 | 208 | - [x] from 209 | - [x] compare 210 | 211 | Properties 212 | 213 | - [x] year 214 | - [x] month 215 | - [ ] monthCode 216 | - [x] day 217 | - [x] hour 218 | - [x] minute 219 | - [x] second 220 | - [x] millisecond 221 | - [x] microsecond 222 | - [x] nanosecond 223 | - [ ] calendar 224 | - [ ] era 225 | - [ ] eraYear 226 | - [x] dayOfWeek 227 | - [x] dayOfYear 228 | - [x] weekOfYear 229 | - [x] daysInWeek 230 | - [x] daysInMonth 231 | - [x] daysInYear 232 | - [x] monthsInYear 233 | - [x] inLeapYear 234 | 235 | Methods 236 | 237 | - [x] with 238 | - [ ] withCalendar 239 | - [x] add 240 | - [x] subtract 241 | - [x] until 242 | - [x] since 243 | - [x] equals 244 | - [x] toString 245 | - [ ] toLocaleString 246 | - [ ] toJSON 247 | - [ ] valueOf 248 | - [ ] toZonedDateTime 249 | - [x] toPlainDate 250 | - [x] toPlainTime 251 | - [x] toPlainYearMonth 252 | - [x] toPlainMonthDay 253 | - [ ] getISOFields 254 | 255 | General features 256 | 257 | - [x] overflow modes (current implementation defaults to constrain) 258 | - [ ] non ISO 8601 calendars 259 | 260 | #### Duration 261 | 262 | Constructor 263 | 264 | - [x] new Duration 265 | 266 | static methods 267 | 268 | - [x] from 269 | - [ ] compare 270 | 271 | Properties 272 | 273 | - [x] years 274 | - [x] months 275 | - [x] weeks 276 | - [x] days 277 | - [x] hours 278 | - [x] minutes 279 | - [x] seconds 280 | - [x] milliseconds 281 | - [x] microseconds 282 | - [x] nanoseconds 283 | - [x] sign 284 | - [x] blank 285 | 286 | Methods 287 | 288 | - [x] with 289 | - [x] add 290 | - [x] subtract 291 | - [x] negated 292 | - [x] abs 293 | - [ ] round 294 | - [ ] total 295 | - [x] toString 296 | - [ ] toJSON 297 | - [ ] toLocaleString 298 | - [ ] valueOf 299 | 300 | General features 301 | 302 | - [ ] precision - need to determine what type to use for the properties 303 | 304 | #### Instant 305 | 306 | Constructor 307 | - [x] new Temporal.Instant 308 | 309 | Static methods 310 | - [x] from 311 | - [x] fromEpochSeconds 312 | - [x] fromEpochMilliseconds 313 | - [x] fromEpochMicroseconds 314 | - [x] fromEpochNanoseconds 315 | - [x] compare 316 | 317 | Properties 318 | - [x] epochSeconds 319 | - [x] epochMilliseconds 320 | - [x] epochMicroseconds 321 | - [x] epochNanoseconds 322 | 323 | Methods 324 | - [ ] toZonedDateTimeISO 325 | - [ ] toZonedDateTime 326 | - [x] add 327 | - [x] subtract 328 | - [x] until 329 | - [x] since 330 | - [ ] round 331 | - [x] equals 332 | - [x] toString 333 | - [ ] toLocaleString 334 | - [ ] toJSON 335 | - [ ] valueOf 336 | 337 | General features 338 | 339 | - [ ] rounding and smallest unit behaviour 340 | 341 | #### Now 342 | 343 | Methods 344 | - [ ] zonedDateTimeISO 345 | - [ ] zonedDateTime 346 | - [x] instant 347 | - [ ] timeZone 348 | - [x] plainDateTimeISO 349 | - [ ] plainDateTime 350 | - [x] plainDateISO 351 | - [ ] plainDate 352 | - [x] plainTimeISO 353 | 354 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assemblyscript-temporal", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "version": "1.0.0", 9 | "license": "MIT", 10 | "dependencies": { 11 | "assemblyscript-regex": "^1.6.3" 12 | }, 13 | "devDependencies": { 14 | "@as-pect/cli": "^6.1.1", 15 | "@assemblyscript/loader": "^0.18.20", 16 | "assemblyscript": "^0.18.31", 17 | "prettier": "^2.2.1" 18 | } 19 | }, 20 | "node_modules/@as-pect/assembly": { 21 | "version": "6.1.0", 22 | "resolved": "https://registry.npmjs.org/@as-pect/assembly/-/assembly-6.1.0.tgz", 23 | "integrity": "sha512-N8bxUjIOfT6rK1HEA6tpe8Mb9pR2lG4QzgkD8PwS9gzsnE9XoQyGN8MDI5VH9tmgQZlmNUZRXkbn7JnExWSzqQ==", 24 | "dev": true, 25 | "peerDependencies": { 26 | "assemblyscript": "^0.18.7" 27 | } 28 | }, 29 | "node_modules/@as-pect/cli": { 30 | "version": "6.1.1", 31 | "resolved": "https://registry.npmjs.org/@as-pect/cli/-/cli-6.1.1.tgz", 32 | "integrity": "sha512-DXn+0Iktrfu11h4Z8sVuagEmCrO2uMhC0ovND3L2G+mF0ruC/Nh2tKzFBzkMqPai5HB6GfLQ0leKY9KwDn9pSA==", 33 | "dev": true, 34 | "dependencies": { 35 | "@as-pect/assembly": "^6.1.0", 36 | "@as-pect/core": "^6.1.1", 37 | "chalk": "^4.1.0", 38 | "glob": "^7.1.6" 39 | }, 40 | "bin": { 41 | "asp": "bin/asp", 42 | "aspect": "bin/asp" 43 | }, 44 | "optionalDependencies": { 45 | "@as-pect/csv-reporter": "^6.1.1", 46 | "@as-pect/json-reporter": "^6.1.1" 47 | }, 48 | "peerDependencies": { 49 | "assemblyscript": "^0.18.7" 50 | } 51 | }, 52 | "node_modules/@as-pect/core": { 53 | "version": "6.1.1", 54 | "resolved": "https://registry.npmjs.org/@as-pect/core/-/core-6.1.1.tgz", 55 | "integrity": "sha512-hjP2JLIK87sS3zz6RaIeFG12lq6itA/RgYTMoG2KqhoVWiE+WOm6vXy9LUJRoeDUiS8pq+MHpr4ERUkNFxMNqg==", 56 | "dev": true, 57 | "dependencies": { 58 | "@as-pect/assembly": "^6.1.0", 59 | "@as-pect/snapshots": "^6.1.0", 60 | "chalk": "^4.1.0", 61 | "long": "^4.0.0" 62 | }, 63 | "peerDependencies": { 64 | "assemblyscript": "^0.18.7" 65 | } 66 | }, 67 | "node_modules/@as-pect/csv-reporter": { 68 | "version": "6.1.1", 69 | "resolved": "https://registry.npmjs.org/@as-pect/csv-reporter/-/csv-reporter-6.1.1.tgz", 70 | "integrity": "sha512-pVfUbSw6BJ98P9OL8hynE7rtcBKtBvLKd3o8h8244S005babw2ZsD+84TrQVC58NWY7FLC/9faXtEJLn0bDClg==", 71 | "dev": true, 72 | "optional": true, 73 | "dependencies": { 74 | "@as-pect/core": "^6.1.1" 75 | } 76 | }, 77 | "node_modules/@as-pect/json-reporter": { 78 | "version": "6.1.1", 79 | "resolved": "https://registry.npmjs.org/@as-pect/json-reporter/-/json-reporter-6.1.1.tgz", 80 | "integrity": "sha512-9eTQPA8uu7dliMKli076x5Jq+azVu6ymVq+J+ZPdfnan3djNDJ4uPUdmIFVBFoYOjg03eLwwPkjYKGbQGotk/g==", 81 | "dev": true, 82 | "optional": true, 83 | "dependencies": { 84 | "@as-pect/core": "^6.1.1" 85 | } 86 | }, 87 | "node_modules/@as-pect/snapshots": { 88 | "version": "6.1.0", 89 | "resolved": "https://registry.npmjs.org/@as-pect/snapshots/-/snapshots-6.1.0.tgz", 90 | "integrity": "sha512-8N5T5lDHl11SrwX3O0C+wayYmfzdVbPEt7vdLyig3FT4/suBLP6jBf3FGbYMhNwxio1iNraSrY/PsfGJc3M8pQ==", 91 | "dev": true, 92 | "dependencies": { 93 | "diff": "^5.0.0", 94 | "nearley": "^2.20.1" 95 | } 96 | }, 97 | "node_modules/@assemblyscript/loader": { 98 | "version": "0.18.20", 99 | "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.18.20.tgz", 100 | "integrity": "sha512-IhyIZtVYHEOwOz35vgO6HJymPGcQeIvLzdV7pFxKAEB3ABjQB7fjyur3cCkryGK64eJ13YULwn/MWM/B8heQrQ==", 101 | "dev": true 102 | }, 103 | "node_modules/ansi-styles": { 104 | "version": "4.3.0", 105 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 106 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 107 | "dev": true, 108 | "dependencies": { 109 | "color-convert": "^2.0.1" 110 | }, 111 | "engines": { 112 | "node": ">=8" 113 | }, 114 | "funding": { 115 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 116 | } 117 | }, 118 | "node_modules/assemblyscript": { 119 | "version": "0.18.31", 120 | "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.18.31.tgz", 121 | "integrity": "sha512-4MtKf1nnmY0eh+v4PKVnIjT8HPTRBqJqACW3kXQoDztyFBLHN1WxGSgZ9iPsLbm/6xKNYD0l6j9y03PoMv+Qog==", 122 | "dev": true, 123 | "dependencies": { 124 | "binaryen": "100.0.0-nightly.20210413", 125 | "long": "^4.0.0" 126 | }, 127 | "bin": { 128 | "asc": "bin/asc", 129 | "asinit": "bin/asinit" 130 | }, 131 | "funding": { 132 | "type": "opencollective", 133 | "url": "https://opencollective.com/assemblyscript" 134 | } 135 | }, 136 | "node_modules/assemblyscript-regex": { 137 | "version": "1.6.3", 138 | "resolved": "https://registry.npmjs.org/assemblyscript-regex/-/assemblyscript-regex-1.6.3.tgz", 139 | "integrity": "sha512-tph+0G5QwLQnwsFI5ejqBHvZ5jxkjJ3lCSezLPeTaPPpHAhQpP32gH46pYLu8Lc30YFgFpK8d7FtwJDn5W236w==" 140 | }, 141 | "node_modules/balanced-match": { 142 | "version": "1.0.0", 143 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 144 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 145 | "dev": true 146 | }, 147 | "node_modules/binaryen": { 148 | "version": "100.0.0-nightly.20210413", 149 | "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-100.0.0-nightly.20210413.tgz", 150 | "integrity": "sha512-EeGLIxQmJS0xnYl+SH34mNBqVMoixKd9nsE7S7z+CtS9A4eoWn3Qjav+XElgunUgXIHAI5yLnYT2TUGnLX2f1w==", 151 | "dev": true, 152 | "bin": { 153 | "wasm-opt": "bin/wasm-opt" 154 | } 155 | }, 156 | "node_modules/brace-expansion": { 157 | "version": "1.1.11", 158 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 159 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 160 | "dev": true, 161 | "dependencies": { 162 | "balanced-match": "^1.0.0", 163 | "concat-map": "0.0.1" 164 | } 165 | }, 166 | "node_modules/chalk": { 167 | "version": "4.1.1", 168 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", 169 | "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", 170 | "dev": true, 171 | "dependencies": { 172 | "ansi-styles": "^4.1.0", 173 | "supports-color": "^7.1.0" 174 | }, 175 | "engines": { 176 | "node": ">=10" 177 | }, 178 | "funding": { 179 | "url": "https://github.com/chalk/chalk?sponsor=1" 180 | } 181 | }, 182 | "node_modules/color-convert": { 183 | "version": "2.0.1", 184 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 185 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 186 | "dev": true, 187 | "dependencies": { 188 | "color-name": "~1.1.4" 189 | }, 190 | "engines": { 191 | "node": ">=7.0.0" 192 | } 193 | }, 194 | "node_modules/color-name": { 195 | "version": "1.1.4", 196 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 197 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 198 | "dev": true 199 | }, 200 | "node_modules/commander": { 201 | "version": "2.20.3", 202 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 203 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 204 | "dev": true 205 | }, 206 | "node_modules/concat-map": { 207 | "version": "0.0.1", 208 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 209 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 210 | "dev": true 211 | }, 212 | "node_modules/diff": { 213 | "version": "5.0.0", 214 | "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", 215 | "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", 216 | "dev": true, 217 | "engines": { 218 | "node": ">=0.3.1" 219 | } 220 | }, 221 | "node_modules/discontinuous-range": { 222 | "version": "1.0.0", 223 | "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", 224 | "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", 225 | "dev": true 226 | }, 227 | "node_modules/fs.realpath": { 228 | "version": "1.0.0", 229 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 230 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 231 | "dev": true 232 | }, 233 | "node_modules/glob": { 234 | "version": "7.1.6", 235 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 236 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 237 | "dev": true, 238 | "dependencies": { 239 | "fs.realpath": "^1.0.0", 240 | "inflight": "^1.0.4", 241 | "inherits": "2", 242 | "minimatch": "^3.0.4", 243 | "once": "^1.3.0", 244 | "path-is-absolute": "^1.0.0" 245 | }, 246 | "engines": { 247 | "node": "*" 248 | }, 249 | "funding": { 250 | "url": "https://github.com/sponsors/isaacs" 251 | } 252 | }, 253 | "node_modules/has-flag": { 254 | "version": "4.0.0", 255 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 256 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 257 | "dev": true, 258 | "engines": { 259 | "node": ">=8" 260 | } 261 | }, 262 | "node_modules/inflight": { 263 | "version": "1.0.6", 264 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 265 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 266 | "dev": true, 267 | "dependencies": { 268 | "once": "^1.3.0", 269 | "wrappy": "1" 270 | } 271 | }, 272 | "node_modules/inherits": { 273 | "version": "2.0.4", 274 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 275 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 276 | "dev": true 277 | }, 278 | "node_modules/long": { 279 | "version": "4.0.0", 280 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 281 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", 282 | "dev": true 283 | }, 284 | "node_modules/minimatch": { 285 | "version": "3.0.4", 286 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 287 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 288 | "dev": true, 289 | "dependencies": { 290 | "brace-expansion": "^1.1.7" 291 | }, 292 | "engines": { 293 | "node": "*" 294 | } 295 | }, 296 | "node_modules/moo": { 297 | "version": "0.5.1", 298 | "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", 299 | "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==", 300 | "dev": true 301 | }, 302 | "node_modules/nearley": { 303 | "version": "2.20.1", 304 | "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", 305 | "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", 306 | "dev": true, 307 | "dependencies": { 308 | "commander": "^2.19.0", 309 | "moo": "^0.5.0", 310 | "railroad-diagrams": "^1.0.0", 311 | "randexp": "0.4.6" 312 | }, 313 | "bin": { 314 | "nearley-railroad": "bin/nearley-railroad.js", 315 | "nearley-test": "bin/nearley-test.js", 316 | "nearley-unparse": "bin/nearley-unparse.js", 317 | "nearleyc": "bin/nearleyc.js" 318 | }, 319 | "funding": { 320 | "type": "individual", 321 | "url": "https://nearley.js.org/#give-to-nearley" 322 | } 323 | }, 324 | "node_modules/once": { 325 | "version": "1.4.0", 326 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 327 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 328 | "dev": true, 329 | "dependencies": { 330 | "wrappy": "1" 331 | } 332 | }, 333 | "node_modules/path-is-absolute": { 334 | "version": "1.0.1", 335 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 336 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 337 | "dev": true, 338 | "engines": { 339 | "node": ">=0.10.0" 340 | } 341 | }, 342 | "node_modules/prettier": { 343 | "version": "2.2.1", 344 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", 345 | "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", 346 | "dev": true, 347 | "bin": { 348 | "prettier": "bin-prettier.js" 349 | }, 350 | "engines": { 351 | "node": ">=10.13.0" 352 | } 353 | }, 354 | "node_modules/railroad-diagrams": { 355 | "version": "1.0.0", 356 | "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", 357 | "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", 358 | "dev": true 359 | }, 360 | "node_modules/randexp": { 361 | "version": "0.4.6", 362 | "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", 363 | "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", 364 | "dev": true, 365 | "dependencies": { 366 | "discontinuous-range": "1.0.0", 367 | "ret": "~0.1.10" 368 | }, 369 | "engines": { 370 | "node": ">=0.12" 371 | } 372 | }, 373 | "node_modules/ret": { 374 | "version": "0.1.15", 375 | "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", 376 | "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", 377 | "dev": true, 378 | "engines": { 379 | "node": ">=0.12" 380 | } 381 | }, 382 | "node_modules/supports-color": { 383 | "version": "7.2.0", 384 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 385 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 386 | "dev": true, 387 | "dependencies": { 388 | "has-flag": "^4.0.0" 389 | }, 390 | "engines": { 391 | "node": ">=8" 392 | } 393 | }, 394 | "node_modules/wrappy": { 395 | "version": "1.0.2", 396 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 397 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 398 | "dev": true 399 | } 400 | }, 401 | "dependencies": { 402 | "@as-pect/assembly": { 403 | "version": "6.1.0", 404 | "resolved": "https://registry.npmjs.org/@as-pect/assembly/-/assembly-6.1.0.tgz", 405 | "integrity": "sha512-N8bxUjIOfT6rK1HEA6tpe8Mb9pR2lG4QzgkD8PwS9gzsnE9XoQyGN8MDI5VH9tmgQZlmNUZRXkbn7JnExWSzqQ==", 406 | "dev": true, 407 | "requires": {} 408 | }, 409 | "@as-pect/cli": { 410 | "version": "6.1.1", 411 | "resolved": "https://registry.npmjs.org/@as-pect/cli/-/cli-6.1.1.tgz", 412 | "integrity": "sha512-DXn+0Iktrfu11h4Z8sVuagEmCrO2uMhC0ovND3L2G+mF0ruC/Nh2tKzFBzkMqPai5HB6GfLQ0leKY9KwDn9pSA==", 413 | "dev": true, 414 | "requires": { 415 | "@as-pect/assembly": "^6.1.0", 416 | "@as-pect/core": "^6.1.1", 417 | "@as-pect/csv-reporter": "^6.1.1", 418 | "@as-pect/json-reporter": "^6.1.1", 419 | "chalk": "^4.1.0", 420 | "glob": "^7.1.6" 421 | } 422 | }, 423 | "@as-pect/core": { 424 | "version": "6.1.1", 425 | "resolved": "https://registry.npmjs.org/@as-pect/core/-/core-6.1.1.tgz", 426 | "integrity": "sha512-hjP2JLIK87sS3zz6RaIeFG12lq6itA/RgYTMoG2KqhoVWiE+WOm6vXy9LUJRoeDUiS8pq+MHpr4ERUkNFxMNqg==", 427 | "dev": true, 428 | "requires": { 429 | "@as-pect/assembly": "^6.1.0", 430 | "@as-pect/snapshots": "^6.1.0", 431 | "chalk": "^4.1.0", 432 | "long": "^4.0.0" 433 | } 434 | }, 435 | "@as-pect/csv-reporter": { 436 | "version": "6.1.1", 437 | "resolved": "https://registry.npmjs.org/@as-pect/csv-reporter/-/csv-reporter-6.1.1.tgz", 438 | "integrity": "sha512-pVfUbSw6BJ98P9OL8hynE7rtcBKtBvLKd3o8h8244S005babw2ZsD+84TrQVC58NWY7FLC/9faXtEJLn0bDClg==", 439 | "dev": true, 440 | "optional": true, 441 | "requires": { 442 | "@as-pect/core": "^6.1.1" 443 | } 444 | }, 445 | "@as-pect/json-reporter": { 446 | "version": "6.1.1", 447 | "resolved": "https://registry.npmjs.org/@as-pect/json-reporter/-/json-reporter-6.1.1.tgz", 448 | "integrity": "sha512-9eTQPA8uu7dliMKli076x5Jq+azVu6ymVq+J+ZPdfnan3djNDJ4uPUdmIFVBFoYOjg03eLwwPkjYKGbQGotk/g==", 449 | "dev": true, 450 | "optional": true, 451 | "requires": { 452 | "@as-pect/core": "^6.1.1" 453 | } 454 | }, 455 | "@as-pect/snapshots": { 456 | "version": "6.1.0", 457 | "resolved": "https://registry.npmjs.org/@as-pect/snapshots/-/snapshots-6.1.0.tgz", 458 | "integrity": "sha512-8N5T5lDHl11SrwX3O0C+wayYmfzdVbPEt7vdLyig3FT4/suBLP6jBf3FGbYMhNwxio1iNraSrY/PsfGJc3M8pQ==", 459 | "dev": true, 460 | "requires": { 461 | "diff": "^5.0.0", 462 | "nearley": "^2.20.1" 463 | } 464 | }, 465 | "@assemblyscript/loader": { 466 | "version": "0.18.20", 467 | "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.18.20.tgz", 468 | "integrity": "sha512-IhyIZtVYHEOwOz35vgO6HJymPGcQeIvLzdV7pFxKAEB3ABjQB7fjyur3cCkryGK64eJ13YULwn/MWM/B8heQrQ==", 469 | "dev": true 470 | }, 471 | "ansi-styles": { 472 | "version": "4.3.0", 473 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 474 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 475 | "dev": true, 476 | "requires": { 477 | "color-convert": "^2.0.1" 478 | } 479 | }, 480 | "assemblyscript": { 481 | "version": "0.18.31", 482 | "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.18.31.tgz", 483 | "integrity": "sha512-4MtKf1nnmY0eh+v4PKVnIjT8HPTRBqJqACW3kXQoDztyFBLHN1WxGSgZ9iPsLbm/6xKNYD0l6j9y03PoMv+Qog==", 484 | "dev": true, 485 | "requires": { 486 | "binaryen": "100.0.0-nightly.20210413", 487 | "long": "^4.0.0" 488 | } 489 | }, 490 | "assemblyscript-regex": { 491 | "version": "1.6.3", 492 | "resolved": "https://registry.npmjs.org/assemblyscript-regex/-/assemblyscript-regex-1.6.3.tgz", 493 | "integrity": "sha512-tph+0G5QwLQnwsFI5ejqBHvZ5jxkjJ3lCSezLPeTaPPpHAhQpP32gH46pYLu8Lc30YFgFpK8d7FtwJDn5W236w==" 494 | }, 495 | "balanced-match": { 496 | "version": "1.0.0", 497 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 498 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 499 | "dev": true 500 | }, 501 | "binaryen": { 502 | "version": "100.0.0-nightly.20210413", 503 | "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-100.0.0-nightly.20210413.tgz", 504 | "integrity": "sha512-EeGLIxQmJS0xnYl+SH34mNBqVMoixKd9nsE7S7z+CtS9A4eoWn3Qjav+XElgunUgXIHAI5yLnYT2TUGnLX2f1w==", 505 | "dev": true 506 | }, 507 | "brace-expansion": { 508 | "version": "1.1.11", 509 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 510 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 511 | "dev": true, 512 | "requires": { 513 | "balanced-match": "^1.0.0", 514 | "concat-map": "0.0.1" 515 | } 516 | }, 517 | "chalk": { 518 | "version": "4.1.1", 519 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", 520 | "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", 521 | "dev": true, 522 | "requires": { 523 | "ansi-styles": "^4.1.0", 524 | "supports-color": "^7.1.0" 525 | } 526 | }, 527 | "color-convert": { 528 | "version": "2.0.1", 529 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 530 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 531 | "dev": true, 532 | "requires": { 533 | "color-name": "~1.1.4" 534 | } 535 | }, 536 | "color-name": { 537 | "version": "1.1.4", 538 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 539 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 540 | "dev": true 541 | }, 542 | "commander": { 543 | "version": "2.20.3", 544 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 545 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 546 | "dev": true 547 | }, 548 | "concat-map": { 549 | "version": "0.0.1", 550 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 551 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 552 | "dev": true 553 | }, 554 | "diff": { 555 | "version": "5.0.0", 556 | "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", 557 | "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", 558 | "dev": true 559 | }, 560 | "discontinuous-range": { 561 | "version": "1.0.0", 562 | "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", 563 | "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", 564 | "dev": true 565 | }, 566 | "fs.realpath": { 567 | "version": "1.0.0", 568 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 569 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 570 | "dev": true 571 | }, 572 | "glob": { 573 | "version": "7.1.6", 574 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 575 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 576 | "dev": true, 577 | "requires": { 578 | "fs.realpath": "^1.0.0", 579 | "inflight": "^1.0.4", 580 | "inherits": "2", 581 | "minimatch": "^3.0.4", 582 | "once": "^1.3.0", 583 | "path-is-absolute": "^1.0.0" 584 | } 585 | }, 586 | "has-flag": { 587 | "version": "4.0.0", 588 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 589 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 590 | "dev": true 591 | }, 592 | "inflight": { 593 | "version": "1.0.6", 594 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 595 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 596 | "dev": true, 597 | "requires": { 598 | "once": "^1.3.0", 599 | "wrappy": "1" 600 | } 601 | }, 602 | "inherits": { 603 | "version": "2.0.4", 604 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 605 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 606 | "dev": true 607 | }, 608 | "long": { 609 | "version": "4.0.0", 610 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 611 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", 612 | "dev": true 613 | }, 614 | "minimatch": { 615 | "version": "3.0.4", 616 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 617 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 618 | "dev": true, 619 | "requires": { 620 | "brace-expansion": "^1.1.7" 621 | } 622 | }, 623 | "moo": { 624 | "version": "0.5.1", 625 | "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", 626 | "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==", 627 | "dev": true 628 | }, 629 | "nearley": { 630 | "version": "2.20.1", 631 | "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", 632 | "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", 633 | "dev": true, 634 | "requires": { 635 | "commander": "^2.19.0", 636 | "moo": "^0.5.0", 637 | "railroad-diagrams": "^1.0.0", 638 | "randexp": "0.4.6" 639 | } 640 | }, 641 | "once": { 642 | "version": "1.4.0", 643 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 644 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 645 | "dev": true, 646 | "requires": { 647 | "wrappy": "1" 648 | } 649 | }, 650 | "path-is-absolute": { 651 | "version": "1.0.1", 652 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 653 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 654 | "dev": true 655 | }, 656 | "prettier": { 657 | "version": "2.2.1", 658 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", 659 | "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", 660 | "dev": true 661 | }, 662 | "railroad-diagrams": { 663 | "version": "1.0.0", 664 | "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", 665 | "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", 666 | "dev": true 667 | }, 668 | "randexp": { 669 | "version": "0.4.6", 670 | "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", 671 | "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", 672 | "dev": true, 673 | "requires": { 674 | "discontinuous-range": "1.0.0", 675 | "ret": "~0.1.10" 676 | } 677 | }, 678 | "ret": { 679 | "version": "0.1.15", 680 | "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", 681 | "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", 682 | "dev": true 683 | }, 684 | "supports-color": { 685 | "version": "7.2.0", 686 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 687 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 688 | "dev": true, 689 | "requires": { 690 | "has-flag": "^4.0.0" 691 | } 692 | }, 693 | "wrappy": { 694 | "version": "1.0.2", 695 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 696 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 697 | "dev": true 698 | } 699 | } 700 | } 701 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assemblyscript-temporal", 3 | "version": "1.0.0", 4 | "description": "An implementation of temporal within AssemblyScript, with an initial focus on non-timezone-aware classes and functionality.", 5 | "main": "index.js", 6 | "ascMain": "assembly/index.ts", 7 | "types": "assembly/index.ts", 8 | "scripts": { 9 | "pretest": "npm run asbuild:empty", 10 | "test": "npm run test:empty && asp --verbose --nologo ", 11 | "test:empty": "diff build/empty.wat assembly/__tests__/empty.wat", 12 | "test:ci": "asp --summary && npm run test:empty", 13 | "asbuild:untouched": "asc assembly/index.ts --target debug", 14 | "asbuild:optimized": "asc assembly/index.ts --target release", 15 | "asbuild": "npm run asbuild:untouched && npm run asbuild:optimized", 16 | "asbuild:empty": "asc --config asconfig.empty.json" 17 | }, 18 | "author": "colin.eberhardt@gmail.com", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "@as-pect/cli": "^6.1.1", 22 | "@assemblyscript/loader": "^0.18.20", 23 | "assemblyscript": "^0.18.31", 24 | "prettier": "^2.2.1" 25 | }, 26 | "dependencies": { 27 | "assemblyscript-regex": "^1.6.3" 28 | } 29 | } 30 | --------------------------------------------------------------------------------