├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── lib ├── absolute.mjs ├── date.mjs ├── datetime.mjs ├── duration.mjs ├── local.mjs ├── monthday.mjs ├── shared.mjs ├── temporal.mjs ├── time.mjs ├── timezone.mjs ├── unimap.mjs ├── yearmonth.mjs ├── zoned.mjs └── zones.mjs ├── package.json ├── rollup.config.js └── test ├── absolute.mjs ├── all.mjs ├── date.mjs ├── datetime.mjs ├── difference.mjs ├── exports.mjs ├── global.config.js ├── resolve.compiled.mjs ├── resolve.source.mjs └── time.mjs /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | node_modules/ 3 | index.js 4 | index.js.map 5 | local.js 6 | local.js.map 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | rollup.config.js 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018-2019 Bloomberg LP 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | and associated documentation files (the "Software"), to deal in the Software without restriction, 5 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 6 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 7 | subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial 10 | portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 13 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ***This is deprecated in favour of the polyfill in the [proposal repository](https://github.com/tc39/proposal-temporal)*** 2 | -------------------------------------------------------------------------------- /lib/absolute.mjs: -------------------------------------------------------------------------------- 1 | 2 | import { DATA, TZ, OF, DT } from './shared.mjs'; 3 | import { TimeZone } from './timezone.mjs'; 4 | import { REGEX } from './shared.mjs'; 5 | import { DateTime } from './datetime.mjs'; 6 | import { assert } from './shared.mjs'; 7 | import { cast as castDuration } from './duration.mjs'; 8 | import { balanceMinus, balancePlus, EARLIER, LATER } from './shared.mjs'; 9 | import { copyProps } from './shared.mjs'; 10 | 11 | const RE = new RegExp(`^${REGEX.DATETIME.source}(?:Z|(${REGEX.TZOFF.source})(?:${REGEX.TIMEZONE.source})?)$`); 12 | 13 | export class Absolute { 14 | constructor(epochNanoseconds, timeZone = 'UTC') { 15 | assert.bigint(epochNanoseconds); 16 | this[TZ] = TimeZone.for(timeZone); 17 | this[DATA] = BigInt(epochNanoseconds); 18 | } 19 | get year() { return this.getDateTime().year; } 20 | get month() { return this.getDateTime().month; } 21 | get day() { return this.getDateTime().day; } 22 | get hour() { return this.getDateTime().hour; } 23 | get minute() { return this.getDateTime().minute; } 24 | get second() { return this.getDateTime().second; } 25 | get millisecond() { return this.getDateTime().millisecond; } 26 | get microsecond() { return this.getDateTime().microsecond; } 27 | get nanosecond() { return this.getDateTime().nanosecond; } 28 | get year() { return this.getDateTime().year; } 29 | get dayOfWeek() { return this.getDateTime().dayOfWeek; } 30 | get dayOfYear() { return this.getDateTime().dayOfYear; } 31 | get weekOfYear() { return this.getDateTime().weekOfYear; } 32 | get timeZone() { return this[TZ]; } 33 | get offset() { return (this[OF] = this[OF] || this.timeZone.getOffsetFor(this)); } 34 | 35 | getDateTime() { return (this[DT] = this[DT] || this.timeZone.getDateTimeFor(this)); } 36 | getDate() { return this.getDateTime().getDate(); } 37 | getTime() { return this.getDateTime().getTime(); } 38 | getYearMonth() { return this.getDateTime().getYearMonth(); } 39 | getMonthDay() { return this.getDateTime().getMonthDay(); } 40 | 41 | getEpochSeconds() { 42 | return Number(this[DATA] / 1000000000n); 43 | } 44 | getEpochMilliseconds() { 45 | return Number(this[DATA] / 1000000n); 46 | } 47 | getEpochMicroseconds() { 48 | return this[DATA] / 1000n; 49 | } 50 | getEpochNanoseconds() { 51 | return this[DATA]; 52 | } 53 | 54 | toString() { 55 | const dateTime = this.getDateTime(); 56 | const timeZone = this.timeZone.name; 57 | const offset = this.offset; 58 | 59 | const date = dateTime.toString(); 60 | switch(true) { 61 | case 'UTC' === timeZone: return `${date}Z`; 62 | case timeZone === offset: return `${date}${offset}`; 63 | default: return `${date}${offset}[${timeZone}]`; 64 | } 65 | } 66 | toJSON() { 67 | return this.toString(); 68 | } 69 | 70 | withZone(tz = 'UTC') { 71 | return Object.create(Absolute.prototype, { 72 | [DATA]: { value: this[DATA], configurable: true, writable: true }, 73 | [TZ]: { value: TimeZone.for(`${tz}`), configurable: true, writable: true }, 74 | }); 75 | } 76 | with(data = {}, choice = EARLIER) { 77 | const { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = copyProps(this, data); 78 | const timeZone = this.timeZone; 79 | const dateTime = new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); 80 | 81 | const instants = timeZone.getAbsoluteFor(dateTime); 82 | let instant; 83 | switch (choice) { 84 | case EARLIER: 85 | instant = instants.shift(); 86 | break; 87 | case LATER: 88 | instant = instants.pop(); 89 | break; 90 | default: 91 | instant = instant.find((instant) => (instant.offset === choice)); 92 | break; 93 | } 94 | if (!instant) throw TypeError('invalid DateTime data for TimeZone'); 95 | 96 | return instant; 97 | } 98 | 99 | difference(other) { 100 | assert.type(other, Absolute); 101 | const one = this[DATA] <= other[DATA] ? this[DATA] : other[DATA]; 102 | const two = this[DATA] <= other[DATA] ? other[DATA] : this[DATA]; 103 | let diff = two - one; 104 | 105 | const nanoseconds = Number((diff / 1n) % 1000n); 106 | const microseconds = Number((diff / 1000n) % 1000n); 107 | const milliseconds = Number((diff / 1000000n) % 1000n); 108 | const seconds = Number((diff / 1000000000n) % 60n); 109 | const minutes = Number((diff / 60000000000n) % 60n); 110 | const hours = Number((diff / 3600000000000n)); 111 | 112 | return castDuration({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds }); 113 | } 114 | plus(duration) { 115 | duration = castDuration(duration); 116 | 117 | let inter = this; 118 | 119 | if (duration.years || duration.months || duration.days) { 120 | const { year, month, day } = this; 121 | const data = { year, month, day }; 122 | 123 | data.year += duration.years; 124 | balancePlus(data); 125 | 126 | data.month += duration.months; 127 | balancePlus(data, true); 128 | 129 | data.day += duration.days; 130 | balancePlus(data); 131 | inter = this.with(data, EARLIER); 132 | } 133 | 134 | let nanos = inter[DATA]; 135 | nanos += BigInt(duration.nanoseconds || 0); 136 | nanos += BigInt(duration.microseconds || 0) * 1000n; 137 | nanos += BigInt(duration.milliseconds || 0) * 1000000n; 138 | nanos += BigInt(duration.seconds || 0) * 1000000000n; 139 | nanos += BigInt(duration.minutes || 0) * 60000000000n; 140 | nanos += BigInt(duration.hours || 0) * 3600000000000n; 141 | 142 | return new Absolute(nanos, this.timeZone); 143 | } 144 | minus(duration) { 145 | duration = castDuration(duration); 146 | 147 | let nanos = this[DATA]; 148 | nanos -= BigInt(duration.nanoseconds || 0); 149 | nanos -= BigInt(duration.microseconds || 0) * 1000n; 150 | nanos -= BigInt(duration.milliseconds || 0) * 1000000n; 151 | nanos -= BigInt(duration.seconds || 0) * 1000000000n; 152 | nanos -= BigInt(duration.minutes || 0) * 60000000000n; 153 | nanos -= BigInt(duration.hours || 0) * 3600000000000n; 154 | 155 | let inter = (this[DATA] === nanos) ? this : new Absolute(nanos, this.timeZone); 156 | if (!duration.years && !duration.months && !duration.days) return inter; 157 | 158 | const data = { year: inter.year, month: inter.month, day: inter.day }; 159 | data.day -= duration.days; 160 | balanceMinus(data); 161 | data.month -= duration.months || 0; 162 | balanceMinus(data, true); 163 | data.year -= duration.years || 0; 164 | balanceMinus(data); 165 | 166 | inter = inter.with(data, LATER); 167 | return inter; 168 | } 169 | 170 | static fromEpochSeconds(seconds, timeZone = 'UTC') { 171 | return new Absolute(BigInt(seconds) * 1000000000n, timeZone); 172 | } 173 | static fromEpochMilliseconds(milliseconds, timeZone = 'UTC') { 174 | return new Absolute(BigInt(milliseconds) * 1000000n, timeZone); 175 | } 176 | static fromEpochMicroseconds(microseconds, timeZone = 'UTC') { 177 | return new Absolute(BigInt(microseconds) * 1000n, timeZone); 178 | } 179 | static fromEpochNanoseconds(nanoseconds, timeZone = 'UTC') { 180 | return new Absolute(BigInt(nanoseconds), timeZone); 181 | } 182 | static fromString(iso) { 183 | const match = RE.exec(iso); 184 | if (!match) throw new TypeError('invalid DateTime string'); 185 | const year = +match[1]; 186 | const month = +match[2]; 187 | const day = +match[3]; 188 | const hour = +match[4]; 189 | const minute = +match[5]; 190 | const second = match[6] === undefined ? 0 : +match[6]; 191 | const millisecond = +`${match[7] === undefined ? '' : match[7]}000000000`.slice(0, 3); 192 | const microsecond = +`${match[7] === undefined ? '' : match[7]}000000000`.slice(3, 6); 193 | const nanosecond = +`${match[7] === undefined ? '' : match[7]}000000000`.slice(6, 9); 194 | 195 | const dt = new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); 196 | const tz = match[9] || match[8] || 'UTC'; 197 | const off = match[8] || '+00:00'; 198 | 199 | const timeZone = TimeZone.for(tz); 200 | const options = timeZone.getAbsoluteFor(dt); 201 | const absolute = options.find((a)=>(a.offset===off)); 202 | 203 | if (!absolute) throw new TypeError('invalid date-time values'); 204 | return absolute; 205 | } 206 | static compare(one, two) { 207 | assert.bigint(one[DATA]); 208 | assert.bigint(two[DATA]); 209 | return Number(two[DATA] - one[DATA]); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /lib/date.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import { 7 | DATA, 8 | REGEX, 9 | assert, 10 | daysInMonth, 11 | pad, 12 | padYear, 13 | copyProps, 14 | toDayOfWeek, 15 | toDayOfYear, 16 | toWeekOfYear 17 | } from './shared.mjs'; 18 | 19 | const RE_DT = new RegExp(`^${REGEX.DATE.source}$`); 20 | 21 | import { YearMonth } from './yearmonth.mjs'; 22 | import { MonthDay } from './monthday.mjs'; 23 | import { DateTime } from './datetime.mjs'; 24 | import { cast as castDuration } from './duration.mjs'; 25 | import { Time } from './time.mjs'; 26 | import { balance, balancePlus, balanceMinus } from './shared.mjs'; 27 | 28 | export class Date { 29 | constructor(year, month, day) { 30 | assert.integer(year); 31 | assert.range(month, 1, 12); 32 | assert.range(day, 1, daysInMonth(year, month)); 33 | this[DATA] = { year, month, day }; 34 | } 35 | 36 | get year() { 37 | return this[DATA].year; 38 | } 39 | get month() { 40 | return this[DATA].month; 41 | } 42 | get day() { 43 | return this[DATA].day; 44 | } 45 | get dayOfWeek() { 46 | return toDayOfWeek(this.year, this.month, this.day); 47 | } 48 | get dayOfYear() { 49 | return toDayOfYear(this.year, this.month, this.day); 50 | } 51 | get weekOfYear() { 52 | return toWeekOfYear(this.year, this.month, this.day); 53 | } 54 | 55 | getYearMonth() { 56 | return new YearMonth(this.year, this.month); 57 | } 58 | getMonthDay() { 59 | return new MonthDay(this.month, this.day); 60 | } 61 | 62 | withTime(timelike = {}) { 63 | const data = copyProps(this, 'string' === typeof timelike ? Time.fromString(timelike) : timelike); 64 | balance(data); 65 | return new DateTime(this.year, this.month, this.day, data.hour, data.minute, data.second, data.millisecond, data.microsecond, data.nanosecond); 66 | } 67 | with(datelike) { 68 | const data = copyProps(this, 'string' === typeof datelike ? Date.fromString(datelike) : datelike); 69 | balance(data); 70 | return new Date(data.year, data.month, data.day); 71 | } 72 | 73 | difference(other = {}) { 74 | other = copyProps(this, 'string' === typeof other ? Date.fromString(other) : other); 75 | const [one, two] = [this, other].sort(Date.compare); 76 | 77 | let years = two.year - one.year; 78 | let months = two.month - one.month; 79 | let days = two.day - one.day; 80 | 81 | if (months < 0) { 82 | years -= 1; 83 | months = 12 + months; 84 | } 85 | if (days < 0) { 86 | months -= 1; 87 | days = daysInMonth(one.year + years, (one.month + months) % 12) + days; 88 | } 89 | 90 | return castDuration({ years, months, days }); 91 | } 92 | plus(duration) { 93 | duration = castDuration(duration); 94 | if ( 95 | duration.hours || 96 | duration.minutes || 97 | duration.seconds || 98 | duration.milliseconds || 99 | duration.milliseconds || 100 | duration.nanoseconds 101 | ) { 102 | throw new RangeError(`invalid duration: ${duration}`); 103 | } 104 | 105 | const data = copyProps(this); 106 | 107 | data.year += duration.years; 108 | data.month += duration.months; 109 | balancePlus(data, true); 110 | data.day += duration.days; 111 | balancePlus(data); 112 | 113 | const { year, month, day } = data; 114 | return new Date(year, month, day); 115 | } 116 | minus(duration) { 117 | duration = castDuration(duration); 118 | if ( 119 | duration.hours || 120 | duration.minutes || 121 | duration.seconds || 122 | duration.milliseconds || 123 | duration.milliseconds || 124 | duration.nanoseconds 125 | ) { 126 | throw new RangeError(`invalid duration: ${duration}`); 127 | } 128 | 129 | const data = copyProps(this); 130 | 131 | data.day -= duration.days; 132 | balanceMinus(data); 133 | 134 | data.month -= duration.months; 135 | balanceMinus(data, true); 136 | 137 | data.year -= duration.years; 138 | balanceMinus(data); 139 | 140 | const { year, month, day } = data; 141 | return new Date(year, month, day); 142 | } 143 | 144 | toString() { 145 | const year = padYear(this.year); 146 | const month = pad(this.month); 147 | const day = pad(this.day); 148 | const date = `${year}-${month}-${day}`; 149 | return `${date}`; 150 | } 151 | toJSON() { 152 | return this.toString(); 153 | } 154 | 155 | static fromString(iso) { 156 | const match = RE_DT.exec(iso); 157 | if (!match) throw new TypeError('invalid Date string'); 158 | const year = +match[1]; 159 | const month = +match[2]; 160 | const day = +match[3]; 161 | return new Date(year, month, day); 162 | } 163 | static compare(one, two) { 164 | assert.datelike(one); 165 | assert.datelike(two); 166 | if (one.year !== two.year) return one.year - two.year; 167 | if (one.month !== two.month) return one.month - two.month; 168 | if (one.day !== two.day) return one.day - two.day; 169 | return 0; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lib/datetime.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import { 7 | DATA, 8 | REGEX, 9 | pad, 10 | padYear, 11 | assert, 12 | daysInMonth, 13 | copyProps, 14 | toDayOfWeek, 15 | toDayOfYear, 16 | toWeekOfYear, 17 | EARLIER, 18 | LATER 19 | } from './shared.mjs'; 20 | import { Date } from './date.mjs'; 21 | import { Time } from './time.mjs'; 22 | import { YearMonth } from './yearmonth.mjs'; 23 | import { MonthDay } from './monthday.mjs'; 24 | import { cast as castDuration } from './duration.mjs'; 25 | import { Absolute } from './absolute.mjs'; 26 | import { TimeZone } from './timezone.mjs'; 27 | import { balance, balancePlus, balanceMinus } from './shared.mjs'; 28 | 29 | const RE_DT = new RegExp(`^${REGEX.DATETIME.source}$`); 30 | const NULLDT = { year: 0, month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, microsecond:0, nanosecond: 0 }; 31 | 32 | export class DateTime { 33 | constructor(year, month, day, hour, minute, second = 0, millisecond = 0, microsecond = 0, nanosecond = 0) { 34 | assert.integer(year); 35 | assert.range(month, 1, 12); 36 | assert.range(day, 1, daysInMonth(year, month)); 37 | assert.range(hour, 0, 23); 38 | assert.range(minute, 0, 59); 39 | assert.range(second, 0, 60); 40 | assert.range(millisecond, 0, 999); 41 | assert.range(microsecond, 0, 999); 42 | assert.range(nanosecond, 0, 999); 43 | second = Math.min(59, second); 44 | this[DATA] = { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond }; 45 | } 46 | 47 | get year() { 48 | return this[DATA].year; 49 | } 50 | get month() { 51 | return this[DATA].month; 52 | } 53 | get day() { 54 | return this[DATA].day; 55 | } 56 | get hour() { 57 | return this[DATA].hour; 58 | } 59 | get minute() { 60 | return this[DATA].minute; 61 | } 62 | get second() { 63 | return this[DATA].second; 64 | } 65 | get millisecond() { 66 | return this[DATA].millisecond; 67 | } 68 | get microsecond() { 69 | return this[DATA].microsecond; 70 | } 71 | get nanosecond() { 72 | return this[DATA].nanosecond; 73 | } 74 | get dayOfWeek() { 75 | return toDayOfWeek(this.year, this.month, this.day); 76 | } 77 | get dayOfYear() { 78 | return toDayOfYear(this.year, this.month, this.day); 79 | } 80 | get weekOfYear() { 81 | return toWeekOfYear(this.year, this.month, this.day); 82 | } 83 | 84 | getDate() { 85 | return new Date(this.year, this.month, this.day); 86 | } 87 | getTime() { 88 | return new Time(this.hour, this.minute, this.second, this.millisecond, this.microsecond, this.nanosecond); 89 | } 90 | getYearMonth() { 91 | return new YearMonth(this.year, this.month); 92 | } 93 | getMonthDay() { 94 | return new MonthDay(this.month, this.day); 95 | } 96 | 97 | with(datetimelike) { 98 | const data = copyProps(this, 'string' === typeof datetimelike ? DateTime.fromString(datetimelike) : datetimelike); 99 | balance(data); 100 | return new DateTime(data.year, data.month, data.day, data.hour, data.minute, data.second, data.millisecond, data.microsecond, data.nanosecond); 101 | } 102 | withZone(timeZone, choice = EARLIER) { 103 | timeZone = TimeZone.for(`${timeZone}`); 104 | assert.type(timeZone, TimeZone); 105 | const dateTime = this; 106 | const instants = timeZone.absolute(dateTime); 107 | let instant; 108 | switch (choice) { 109 | case EARLIER: 110 | instant = instants.shift(); 111 | break; 112 | case LATER: 113 | instant = instants.pop(); 114 | break; 115 | default: 116 | instant = instant.find((instant) => timeZone.offset(instant) === choice); 117 | break; 118 | } 119 | if (!instant) throw TypeError('invalid DateTime int TimeZone'); 120 | return instant 121 | } 122 | 123 | difference(other = {}) { 124 | other = copyProps(NULLDT, 'string' === typeof other ? DateTime.fromString(other) : other); 125 | const [one, two] = [this, other].sort(DateTime.compare); 126 | 127 | let years = two.year - one.year; 128 | let months = two.month - one.month; 129 | let days = two.day - one.day; 130 | let hours = two.hour - one.hour; 131 | let minutes = two.minute - one.minute; 132 | let seconds = two.second - one.second; 133 | let milliseconds = two.millisecond - one.millisecond; 134 | let microseconds = two.microsecond - one.microsecond; 135 | let nanoseconds = two.nanosecond - one.nanosecond; 136 | 137 | if (nanoseconds < 0) { 138 | nanoseconds += 1e3; 139 | microseconds -= 1; 140 | } 141 | if (microseconds < 0) { 142 | microseconds += 1e3; 143 | milliseconds -= 1; 144 | } 145 | if (milliseconds < 0) { 146 | milliseconds += 1e3; 147 | seconds -= 1; 148 | } 149 | if (seconds < 0) { 150 | seconds += 60; 151 | minutes -= 1; 152 | } 153 | if (minutes < 0) { 154 | minutes += 60; 155 | hours -= 1; 156 | } 157 | if (hours < 0) { 158 | hours += 24; 159 | days -= 1; 160 | } 161 | 162 | if (months < 0) { 163 | years -= 1; 164 | months = 12 + months; 165 | } 166 | if (days < 0) { 167 | months -= 1; 168 | days = daysInMonth(one.year + years, (one.month + months) % 12) + days; 169 | } 170 | 171 | return castDuration({ 172 | years, 173 | months, 174 | days, 175 | hours, 176 | minutes, 177 | seconds, 178 | milliseconds, 179 | microseconds, 180 | nanoseconds 181 | }); 182 | } 183 | plus(duration) { 184 | duration = castDuration(duration); 185 | const data = copyProps(this); 186 | 187 | data.year += duration.years; 188 | data.month += duration.months; 189 | balancePlus(data, true); 190 | data.day += duration.days; 191 | balancePlus(data); 192 | 193 | data.hour += duration.hours; 194 | data.minute += duration.minutes; 195 | data.second += duration.seconds; 196 | data.millisecond += duration.milliseconds; 197 | data.microsecond += duration.microseconds; 198 | data.nanosecond += duration.nanoseconds; 199 | balancePlus(data); 200 | 201 | const { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = data; 202 | return new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); 203 | } 204 | minus(duration) { 205 | duration = castDuration(duration); 206 | const data = copyProps(this); 207 | 208 | data.hour -= duration.hours; 209 | data.minute -= duration.minutes; 210 | data.second -= duration.seconds; 211 | data.millisecond -= duration.milliseconds; 212 | data.microsecond -= duration.microseconds; 213 | data.nanosecond -= duration.nanoseconds; 214 | balanceMinus(data); 215 | 216 | data.day -= duration.days; 217 | balanceMinus(data); 218 | 219 | data.month -= duration.months; 220 | balanceMinus(data, true); 221 | 222 | data.year -= duration.years; 223 | balanceMinus(data); 224 | 225 | const { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = data; 226 | return new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); 227 | } 228 | 229 | toString() { 230 | const year = padYear(this.year); 231 | const month = pad(this.month); 232 | const day = pad(this.day); 233 | 234 | const tp = []; 235 | tp.push(pad(this.hour, 2)); 236 | tp.push(pad(this.minute, 2)); 237 | 238 | const sp = []; 239 | if (this.nanosecond) sp.unshift(pad(this.nanosecond, 3)); 240 | if (this.microsecond || sp.length) sp.unshift(pad(this.microsecond, 3)); 241 | if (this.millisecond || sp.length) sp.unshift(pad(this.millisecond, 3)); 242 | if (sp.length) { 243 | tp.push(`${pad(this.second, 2)}.${sp.join('')}`); 244 | } else { 245 | tp.push(pad(this.second, 2)); 246 | } 247 | 248 | const date = `${year}-${month}-${day}`; 249 | const time = tp.join(':'); 250 | return `${date}T${time}`; 251 | } 252 | toJSON() { 253 | return this.toString(); 254 | } 255 | 256 | static fromString(iso) { 257 | const match = RE_DT.exec(iso); 258 | if (!match) throw new TypeError('invalid DateTime string'); 259 | const year = +match[1]; 260 | const month = +match[2]; 261 | const day = +match[3]; 262 | const hour = +match[4]; 263 | const minute = +match[5]; 264 | const second = match[6] === undefined ? 0 : +match[6]; 265 | const millisecond = +`${match[7] === undefined ? '' : match[7]}000000000`.slice(0, 3); 266 | const microsecond = +`${match[7] === undefined ? '' : match[7]}000000000`.slice(3, 6); 267 | const nanosecond = +`${match[7] === undefined ? '' : match[7]}000000000`.slice(6, 9); 268 | return new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); 269 | } 270 | static compare(one, two) { 271 | assert.datetimelike(one); 272 | assert.datetimelike(two); 273 | if (one.year !== two.year) return one.year - two.year; 274 | if (one.month !== two.month) return one.month - two.month; 275 | if (one.day !== two.day) return one.day - two.day; 276 | if (one.hour !== two.hour) return one.hour - two.hour; 277 | if (one.minute !== two.minute) return one.minute - two.minute; 278 | if (one.second !== two.second) return one.second - two.second; 279 | if (one.millisecond !== two.millisecond) return one.millisecond - two.millisecond; 280 | if (one.microsecond !== two.microsecond) return one.microsecond - two.microsecond; 281 | if (one.nanosecond !== two.nanosecond) return other.nanosecond - two.nanosecond; 282 | return 0; 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /lib/duration.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import { DATA, pad, assert } from './shared.mjs'; 7 | 8 | const Y = /(?:(\d+)Y)?/; 9 | const M = /(?:(\d+)M)?/; 10 | const D = /(?:(\d+)D)?/; 11 | const DT = new RegExp(`${Y.source}${M.source}${D.source}`); 12 | 13 | const H = /(?:(\d+)H)?/; 14 | const S = /(?:(\d+)(?:\.(\d+))?S)?/; 15 | const TM = new RegExp(`T${H.source}${M.source}${S.source}`); 16 | 17 | const RE = new RegExp(`^P(?:${DT.source})(?:${TM.source})?$`); 18 | 19 | export class Duration { 20 | constructor() { 21 | throw new TypeError('Duration is not a constructor'); 22 | } 23 | get years() { 24 | return this[DATA].years; 25 | } 26 | get months() { 27 | return this[DATA].months; 28 | } 29 | get days() { 30 | return this[DATA].days; 31 | } 32 | get hours() { 33 | return this[DATA].hours; 34 | } 35 | get minutes() { 36 | return this[DATA].minutes; 37 | } 38 | get seconds() { 39 | return this[DATA].seconds; 40 | } 41 | get milliseconds() { 42 | return this[DATA].milliseconds; 43 | } 44 | get microseconds() { 45 | return this[DATA].microseconds; 46 | } 47 | get nanoseconds() { 48 | return this[DATA].nanoseconds; 49 | } 50 | 51 | getDateDuration() { 52 | const { years, months, days } = this[DATA]; 53 | return Object.create(Duration.prototype, { [DATA]: { value: Object.freeze({ years, months, days }) } }); 54 | } 55 | getTimeDuration() { 56 | const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = this[DATA]; 57 | return Object.create(Duration.prototype, { 58 | [DATA]: { value: Object.freeze({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds }) } 59 | }); 60 | } 61 | 62 | toString() { 63 | const date = []; 64 | if (this.years) date.push(`${this.years}Y`); 65 | if (this.months) date.push(`${this.months}M`); 66 | if (this.days) date.push(`${this.days}D`); 67 | 68 | const time = []; 69 | if (this.hours) time.push(`${this.hours}H`); 70 | if (this.minutes) time.push(`${this.minutes}M`); 71 | 72 | if ( 73 | undefined !== typeof this.seconds && 74 | (this.seconds || this.milliseconds || this.microseconds || this.nanoseconds) 75 | ) { 76 | const parts = []; 77 | if (this.nanoseconds) parts.unshift(pad(this.nanoseconds, 3)); 78 | if (this.microseconds || this.nanoseconds) parts.unshift(pad(this.microseconds, 3)); 79 | if (this.milliseconds || this.microseconds || this.nanoseconds) parts.unshift(pad(this.milliseconds, 3)); 80 | parts.unshift(`${this.seconds}${parts.length ? '.' : ''}`); 81 | time.push(`${parts.join('')}S`); 82 | } 83 | if (time.length) time.unshift('T'); 84 | return `P${date.join('')}${time.join('')}`; 85 | } 86 | toJSON() { 87 | const { years, months, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = this; 88 | return { years, months, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }; 89 | } 90 | static fromString(iso) { 91 | const match = RE.exec(iso); 92 | if (!match) throw new TypeError(`invalid Duration string: ${iso}`); 93 | 94 | const data = {}; 95 | 96 | let years = match[1]; 97 | let months = match[2]; 98 | let days = match[3]; 99 | if (isDef(years) || isDef(months) || isDef(days)) { 100 | assert.positive((years = +(years || 0))); 101 | assert.positive((months = +(months || 0))); 102 | assert.positive((days = +(days || 0))); 103 | Object.assign(data, { years, months, days }); 104 | } 105 | 106 | let hours = match[4]; 107 | let minutes = match[5]; 108 | let seconds = match[6]; 109 | let subseconds = match[7]; 110 | let milliseconds, microseconds, nanoseconds; 111 | if (isDef(hours) || isDef(minutes) || isDef(seconds) || isDef(subseconds)) { 112 | assert.positive((hours = +(hours || 0))); 113 | assert.positive((minutes = +(minutes || 0))); 114 | assert.positive((seconds = +(seconds || 0))); 115 | subseconds = `${subseconds || ''}000000000`.slice(0, 9); 116 | assert.positive((milliseconds = +subseconds.slice(0, 3))); 117 | assert.positive((microseconds = +subseconds.slice(3, 6))); 118 | assert.positive((nanoseconds = +subseconds.slice(6, 9))); 119 | Object.assign(data, { hours, minutes, seconds, milliseconds, microseconds, nanoseconds }); 120 | } 121 | 122 | return Object.create(Duration.prototype, { [DATA]: { value: Object.freeze(data) } }); 123 | } 124 | } 125 | 126 | export function cast(dl) { 127 | if ('string' === typeof dl) dl = Duration.fromString(dl); 128 | let { years, months, days } = dl; 129 | let { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = dl; 130 | 131 | const data = {}; 132 | assert.positive((years = +(years || 0))); 133 | assert.positive((months = +(months || 0))); 134 | assert.positive((days = +(days || 0))); 135 | Object.assign(data, { years, months, days }); 136 | 137 | assert.positive((hours = +(hours || 0))); 138 | assert.positive((minutes = +(minutes || 0))); 139 | assert.positive((seconds = +(seconds || 0))); 140 | assert.positive((milliseconds = +(milliseconds || 0))); 141 | assert.positive((microseconds = +(microseconds || 0))); 142 | assert.positive((nanoseconds = +(nanoseconds || 0))); 143 | Object.assign(data, { hours, minutes, seconds, milliseconds, microseconds, nanoseconds }); 144 | 145 | data.microseconds += Math.floor(data.nanoseconds / 1000); 146 | data.nanoseconds = data.nanoseconds % 1000; 147 | 148 | data.milliseconds += Math.floor(data.microseconds / 1000); 149 | data.microseconds = data.microseconds % 1000; 150 | 151 | data.seconds += Math.floor(data.milliseconds / 1000); 152 | data.milliseconds = data.milliseconds % 1000; 153 | 154 | data.minutes += Math.floor(data.seconds / 60); 155 | data.seconds = data.seconds % 60; 156 | 157 | data.hours += Math.floor(data.minutes / 60); 158 | data.minutes = data.minutes % 60; 159 | 160 | return Object.create(Duration.prototype, { [DATA]: { value: Object.freeze(data) } }); 161 | } 162 | 163 | function isDef(v) { 164 | return 'undefined' !== typeof v; 165 | } 166 | -------------------------------------------------------------------------------- /lib/local.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import { TimeZone } from './timezone.mjs'; 7 | import { Absolute } from './absolute.mjs'; 8 | 9 | const now = (function(){ 10 | if (('undefined' !== typeof process) && ('function' === typeof process.hrtime) && ('function' === typeof process.hrtime.bigint)) { 11 | return ()=>((BigInt(Date.now()) * 1000000n) + (process.hrtime.bigint() % 1000000n)); 12 | } 13 | let v = 0; 14 | return ()=>{ 15 | v = (v+1) % 100; 16 | return (BigInt(Date.now()) * 1000000n) + (BigInt(v) * 10000n) + BigInt(Math.floor(Math.random() * 9999)); 17 | }; 18 | })(); 19 | 20 | export function absolute(tz = timeZone()) { 21 | return Absolute.fromEpochNanoseconds(now(), tz); 22 | } 23 | export function timeZone() { 24 | const fmt = Intl.DateTimeFormat('en-iso'); 25 | return TimeZone.for(fmt.resolvedOptions().timeZone); 26 | } 27 | export function dateTime() { 28 | return absolute(timeZone()).getDateTime(); 29 | } 30 | export function date() { 31 | return dateTime().getDate(); 32 | } 33 | export function time() { 34 | return dateTime().getTime(); 35 | } 36 | export function dayMonth() { 37 | return date().getMonthDay(); 38 | } 39 | export function monthYear() { 40 | return date().getYearMonth(); 41 | } 42 | -------------------------------------------------------------------------------- /lib/monthday.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import { DATA, REGEX, assert, pad, daysInMonth, copyProps } from './shared.mjs'; 7 | 8 | const RE_DT = new RegExp(`^(${REGEX.MONTHS})-(${REGEX.DAYS})$`); 9 | 10 | export class MonthDay { 11 | constructor(month, day) { 12 | assert.range(month, 1, 12); 13 | assert.range(day, 1, daysInMonth(1970, month)); // 1970 is non-leap-year 14 | this[DATA] = { month, day }; 15 | } 16 | 17 | get month() { 18 | return this[DATA].month; 19 | } 20 | get day() { 21 | return this[DATA].day; 22 | } 23 | 24 | withYear(year) { 25 | return new Date(+year, this.month, this.day); 26 | } 27 | with(monthdaylike = {}) { 28 | const data = copyProps(this, 'string' === typeof monthdaylike ? MonthDay.fromString(monthdaylike) : monthdaylike); 29 | return new MonthDay(data.month, data.day); 30 | } 31 | 32 | difference(other = {}) { 33 | other = copyProps(this, other); 34 | let months = (two.month - one.month) % 12; 35 | let days = two.day - one.day; 36 | 37 | if (days < 0) { 38 | days = daysInMonth(1976, one.month + months) + days; 39 | months -= 1; 40 | } 41 | 42 | return castDuration({ months, days }); 43 | } 44 | 45 | toString() { 46 | const month = pad(this.month); 47 | const day = pad(this.day); 48 | const date = `${month}-${day}`; 49 | return `${date}`; 50 | } 51 | toJSON() { 52 | return this.toString(); 53 | } 54 | 55 | static fromString(iso) { 56 | const match = RE_DT.exec(iso); 57 | if (!match) throw new TypeError('invalid MonthDay string'); 58 | const year = +match[1]; 59 | const month = +match[2]; 60 | return new MonthDay(year, month); 61 | } 62 | static compare(one, two) { 63 | assert.type(one, Object); 64 | assert.range(one.month, 1, 12); 65 | assert.range(one.day, 1, daysInMonth(1970, one.month)); // 1970 is non-leap-year 66 | assert.type(two, Object); 67 | assert.range(two.month, 1, 12); 68 | assert.range(two.day, 1, daysInMonth(1970, two.month)); // 1970 is non-leap-year 69 | if (one.month !== two.month) return one.month - two.month; 70 | if (one.day !== two.month) return one.day - two.day; 71 | return 0; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/shared.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | export const DATA = Symbol('data'); 7 | export const TZ = Symbol('tz'); 8 | export const OF = Symbol('of'); 9 | export const DT = Symbol('dt'); 10 | 11 | export function pad(num, dig = 2) { 12 | const sign = num < 0 ? '-' : ''; 13 | const padding = Array(dig) 14 | .fill('0') 15 | .join(''); 16 | const value = Math.abs(num); 17 | const padded = `${padding}${value}`.slice(-1 * dig); 18 | return `${sign}${padded}`; 19 | } 20 | 21 | export const REGEX = {}; 22 | 23 | REGEX.YEARS = /-?\d{4}|[+-]\d{6}/; 24 | REGEX.MONTHS = /0?[1-9]|1[0-2]/; 25 | REGEX.DAYS = /0?[1-9]|[1-2][0-9]|3[0-1]/; 26 | REGEX.DATE = new RegExp(`(${REGEX.YEARS.source})-(${REGEX.MONTHS.source})-(${REGEX.DAYS.source})`); 27 | 28 | REGEX.HOURS = /[0-1][0-9]|2[0-3]/; 29 | REGEX.MINUTES = /[0-5][0-9]/; 30 | REGEX.SECONDS = /[0-5][0-9]|60/; 31 | REGEX.SUBSECONDS = /\d{1,9}/; 32 | REGEX.TIME = new RegExp( 33 | `(${REGEX.HOURS.source}):(${REGEX.MINUTES.source})(?::(${REGEX.SECONDS.source})(?:\.(${REGEX.SUBSECONDS.source}))?)?` 34 | ); 35 | 36 | REGEX.DATETIME = new RegExp(`${REGEX.DATE.source}T${REGEX.TIME.source}`); 37 | REGEX.OFFSET = /((?:\+?|\-)(?:0?[0-9]|1[0-9]|2[0-3])):?([0-5][0-9])/; 38 | REGEX.TIMEZONE = /\[([a-zA-Z/_-]+)\]/; 39 | REGEX.TZOFF = /(?:\+?|\-)(?:[0-1][0-9]|2[0-3]):[0-5][0-9]/; 40 | 41 | const OFFRE = new RegExp(`^${REGEX.OFFSET.source}$`); 42 | 43 | const DoM = { 44 | standard: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], 45 | leapyear: [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 46 | }; 47 | export function daysInMonth(year, month) { 48 | return DoM[isLeapYear(year) ? 'leapyear' : 'standard'][month - 1]; 49 | } 50 | 51 | export function isLeapYear(year) { 52 | if (undefined === year) return false; 53 | const isDiv4 = year % 4 === 0; 54 | const isDiv100 = year % 100 === 0; 55 | const isDiv400 = year % 400 === 0; 56 | return isDiv4 && (!isDiv100 || isDiv400); 57 | } 58 | 59 | export const assert = { 60 | type: (value, type) => { 61 | if ('object' !== typeof value || !(value instanceof type)) throw new TypeError('value of invalid type'); 62 | return value; 63 | }, 64 | enum: (value, ...values) => { 65 | if (!values.includes(value)) throw new TypeError('invalid value'); 66 | return value; 67 | }, 68 | bigint: (value) => { 69 | if ('bigint' !== typeof value) throw new TypeError('value has to be BigInt'); 70 | return value; 71 | }, 72 | integer: (value) => { 73 | if ('number' !== typeof value || Number.isNaN(value) || !Number.isFinite(value) || !Number.isInteger(value)) 74 | throw new TypeError('value has to be integer'); 75 | return value; 76 | }, 77 | positive: (value) => { 78 | if ( 79 | 'number' !== typeof value || 80 | Number.isNaN(value) || 81 | !Number.isFinite(value) || 82 | !Number.isInteger(value) || 83 | value < 0 84 | ) 85 | throw new TypeError('value has to be positive integer'); 86 | return value; 87 | }, 88 | range(value, min, max) { 89 | if ( 90 | 'number' !== typeof value || 91 | Number.isNaN(value) || 92 | !Number.isFinite(value) || 93 | !Number.isInteger(value) || 94 | value < min || 95 | value > max 96 | ) 97 | throw new TypeError(`${value} has to be an integer between ${min} and ${max}`); 98 | return value; 99 | }, 100 | datelike(value) { 101 | assert.type(value, Object); 102 | assert.integer(value.year); 103 | assert.range(value.month, 1, 12); 104 | assert.range(value.day, 1, daysInMonth(value.year, value.month)); 105 | return value; 106 | }, 107 | timelike(value) { 108 | assert.type(value, Object); 109 | assert.range(value.hour, 0, 23); 110 | assert.range(value.minute, 0, 59); 111 | if ('undefined' !== value.second) assert.range(value.second, 0, 60); 112 | if ('undefined' !== value.millisecond) assert.range(value.millisecond, 0, 999); 113 | if ('undefined' !== value.microsecond) assert.range(value.microsecond, 0, 999); 114 | if ('undefined' !== value.nanosecond) assert.range(value.nanosecond, 0, 999); 115 | return value; 116 | }, 117 | datetimelike(value) { 118 | assert.datelike(value); 119 | assert.timelike(value); 120 | return value; 121 | } 122 | }; 123 | 124 | export function copyProps(...objs) { 125 | const result = {}; 126 | for (let obj of objs) { 127 | if ((undefined !== obj.year) && Number.isInteger(obj.year)) result.year = obj.year; 128 | if ((undefined !== obj.month) && Number.isInteger(obj.month)) result.month = obj.month; 129 | if ((undefined !== obj.day) && Number.isInteger(obj.day)) result.day = obj.day; 130 | if ((undefined !== obj.hour) && Number.isInteger(obj.hour)) result.hour = obj.hour; 131 | if ((undefined !== obj.minute) && Number.isInteger(obj.minute)) result.minute = obj.minute; 132 | if ((undefined !== obj.second) && Number.isInteger(obj.second)) result.second = obj.second; 133 | if ((undefined !== obj.millisecond) && Number.isInteger(obj.millisecond)) result.millisecond = obj.millisecond; 134 | if ((undefined !== obj.microsecond) && Number.isInteger(obj.microsecond)) result.microsecond = obj.microsecond; 135 | if ((undefined !== obj.nanosecond) && Number.isInteger(obj.nanosecond)) result.nanosecond = obj.nanosecond; 136 | } 137 | return result; 138 | } 139 | 140 | export function toDayOfWeek(year, month, day) { 141 | const m = month + (month < 3 ? 10 : -2); 142 | const Y = year - (month < 3 ? 1 : 0); 143 | 144 | const c = Math.floor(Y / 100); 145 | const y = Y - c * 100; 146 | const d = day; 147 | 148 | const pD = d; 149 | const pM = Math.floor(2.6 * m - 0.2); 150 | const pY = y + Math.floor(y / 4); 151 | const pC = Math.floor(c / 4) - 2 * c; 152 | 153 | const dow = (pD + pM + pY + pC) % 7; 154 | 155 | return dow + (dow < 0 ? 7 : 0); 156 | } 157 | export function toDayOfYear(year, month, day) { 158 | let days = day; 159 | for (let m = month - 1; m > 0; m--) { 160 | days += daysInMonth(year, m); 161 | } 162 | return days; 163 | } 164 | export function toWeekOfYear(year, month, day) { 165 | let doy = toDayOfYear(year, month, day); 166 | let dow = toDayOfWeek(year, month, day) || 7; 167 | let doj = toDayOfWeek(year, 1, 1); 168 | 169 | const week = Math.floor((doy - dow + 10) / 7); 170 | 171 | if (week < 1) { 172 | if (doj === (isLeapYear(year) ? 5 : 6)) { 173 | return 53; 174 | } else { 175 | return 52; 176 | } 177 | } 178 | if (week === 53) { 179 | if ((isLeapYear(year) ? 366 : 365) - doy < 4 - dow) { 180 | return 1; 181 | } 182 | } 183 | 184 | return week; 185 | } 186 | 187 | export function offsetString(offsetMilliseconds) { 188 | const direction = offsetMilliseconds < 0 ? '-' : '+'; 189 | const offsetMinutes = Math.floor(Math.abs(offsetMilliseconds) / 6e4); 190 | const hours = Math.floor(offsetMinutes / 60); 191 | const minutes = Math.floor(offsetMinutes % 60); 192 | return `${direction}${('00' + hours).slice(-2)}:${('00' + minutes).slice(-2)}`; 193 | } 194 | 195 | export function isOffset(string) { 196 | return !!OFFRE.exec(string); 197 | } 198 | export function parseOffsetString(string) { 199 | const match = OFFRE.exec(string); 200 | if (!match) return; 201 | const hours = +match[1]; 202 | const minutes = +match[2]; 203 | return (hours * 60 + minutes) * 60 * 1000; 204 | } 205 | 206 | export function padYear(value) { 207 | if (value > 999 && value < 9999) return pad(value, 4); 208 | const sign = value < 0 ? '-' : '+'; 209 | value = Math.abs(value); 210 | return `${sign}${value}`; 211 | } 212 | 213 | export const EARLIER = Symbol('earlier'); 214 | export const LATER = Symbol('later'); 215 | 216 | export function balanceMinus(data, truncate) { 217 | if (isNum(data.nanosecond) && isNum(data.microsecond) && (data.nanosecond < 0)) { 218 | data.microsecond += Math.floor(data.nanosecond / 1000); 219 | data.nanosecond = 1000 + (data.nanosecond % 1000); 220 | } 221 | if (isNum(data.microsecond) && isNum(data.millisecond) && (data.microsecond > 999)) { 222 | data.millisecond += Math.floor(data.microsecond / 1000); 223 | data.microsecond = data.microsecond % 1000; 224 | } 225 | if (isNum(data.microsecond) && isNum(data.millisecond) && (data.microsecond < 0)) { 226 | data.millisecond += Math.floor(data.microsecond / 1000); 227 | data.microsecond = 1000 + (data.microsecond % 1000); 228 | } 229 | if (isNum(data.millisecond) && isNum(data.second) && (data.millisecond > 999)) { 230 | data.second += Math.floor(data.millisecond / 1000); 231 | data.millisecond = data.millisecond % 1000; 232 | } 233 | if (isNum(data.millisecond) && isNum(data.second) && (data.millisecond < 0)) { 234 | data.second += Math.floor(data.millisecond / 1000); 235 | data.millisecond = 1000 + (data.millisecond % 1000); 236 | } 237 | if (isNum(data.second) && isNum(data.minute) && (data.second > 59)) { 238 | data.minute += Math.floor(data.second / 60); 239 | data.second = data.second % 60; 240 | } 241 | if (isNum(data.second) && isNum(data.minute) && (data.second < 0)) { 242 | data.minute += Math.floor(data.second / 1000); 243 | data.second = 60 + (data.second % 60); 244 | } 245 | if (isNum(data.minute) && isNum(data.hour) && (data.minute > 59)) { 246 | data.hour += Math.floor(data.minute / 60); 247 | data.minute = data.minute % 60; 248 | } 249 | if (isNum(data.minute) && isNum(data.hour) && (data.minute < 0)) { 250 | data.hour += Math.floor(data.minute / 60); 251 | data.minute = 60 + (data.minute % 60); 252 | } 253 | if (isNum(data.hour) && isNum(data.day) && (data.hour > 23)) { 254 | data.day += Math.floor(data.hour / 24); 255 | data.hour = data.hour % 24; 256 | } 257 | if (isNum(data.hour) && (data.hour < 0)) { 258 | if (isNum(data.day)) data.day += Math.floor(data.hour / 24); 259 | data.hour = 24 + (data.hour % 24); 260 | } 261 | if (isNum(data.hour) && !isNum(data.day) && (data.hour > 23)) { 262 | data.hour = data.hour % 24; 263 | } 264 | 265 | if (truncate && isNum(data.day) && isNum(data.month)) { 266 | if (isNum(data.month) && isNum(data.year) && (data.month < 1)) { 267 | data.year += Math.floor(data.month / 12); 268 | data.month = 12 + data.month % 12; 269 | } 270 | data.day = Math.min(data.day, daysInMonth(isNum(data.year) ? data.year : 1976, data.month)); 271 | } 272 | while (isNum(data.day) && isNum(data.month) && (data.day < ((isNum(data.year) && isLeapYear(data.year)) ? -366 : -365))) { 273 | data.day += (isNum(data.year) && isLeapYear(data.year) ? 366 : 365); 274 | if (isNum(data.year)) data.year -= 1; 275 | } 276 | while (isNum(data.day) && isNum(data.month) && (data.day > daysInMonth(isNum(data.year) ? data.year : 1976, data.month))) { 277 | data.day -= daysInMonth(isNum(data.year) ? data.year : 1976, data.month); 278 | data.month += 1; 279 | if (data.month > 12) { 280 | data.year += 1; 281 | data.month = data.month % 12; 282 | } 283 | } 284 | while (isNum(data.day) && isNum(data.month) && (data.day < 1)) { 285 | data.month -= 1; 286 | if (data.month < 1) { 287 | data.month = 12; 288 | if (isNum(data.year)) data.year -= 1; 289 | } 290 | data.day += daysInMonth(isNum(data.year) ? data.year : 1976, data.month); 291 | } 292 | if (isNum(data.month) && isNum(data.year) && (data.month > 12)) { 293 | data.year += Math.floor(data.month / 12); 294 | data.month = data.month % 12; 295 | } 296 | if (isNum(data.month) && isNum(data.year) && (data.month < 1)) { 297 | data.year += Math.floor(data.month / 12); 298 | data.month = 12 + data.month % 12; 299 | } 300 | } 301 | export function balancePlus(data, truncate) { 302 | if (isNum(data.year) && isNum(data.month) && (data.month > 12)) { 303 | data.year += Math.floor(data.month / 12); 304 | data.month = data.month % 12; 305 | } 306 | if (truncate && isNum(data.month) && isNum(data.day)) { 307 | data.day = Math.min(data.day, daysInMonth(isNum(data.year) ? data.year : 1976, data.month)); 308 | } 309 | 310 | balance(data); 311 | } 312 | export function balance(data) { 313 | if (isNum(data.nanosecond) && isNum(data.microsecond) && (data.nanosecond > 999)) { 314 | data.microsecond += Math.floor(data.nanosecond / 1000); 315 | data.nanosecond = data.nanosecond % 1000; 316 | } 317 | if (isNum(data.microsecond) && isNum(data.millisecond) && (data.microsecond > 999)) { 318 | data.millisecond += Math.floor(data.microsecond / 1000); 319 | data.microsecond = data.microsecond % 1000; 320 | } 321 | if (isNum(data.millisecond) && isNum(data.second) && (data.millisecond > 999)) { 322 | data.second += Math.floor(data.millisecond / 1000); 323 | data.millisecond = data.millisecond % 1000; 324 | } 325 | if (isNum(data.second) && isNum(data.minute) && (data.second > 59)) { 326 | data.minute += Math.floor(data.second / 60); 327 | data.second = data.second % 60; 328 | } 329 | if (isNum(data.minute) && isNum(data.hour) && (data.minute > 59)) { 330 | data.hour += Math.floor(data.minute / 60); 331 | data.minute = data.minute % 60; 332 | } 333 | if (isNum(data.hour) && isNum(data.day) && (data.hour > 23)) { 334 | data.day += Math.floor(data.hour / 24); 335 | data.hour = data.hour % 24; 336 | } 337 | if (isNum(data.hour) && (data.hour > 23)) { 338 | data.hour = data.hour % 24; 339 | } 340 | while (isNum(data.day) && isNum(data.month) && (data.day > (isNum(data.year) && isLeapYear(data.year) ? 366 : 365))) { 341 | data.day -= (isNum(data.year) && isLeapYear(data.year) ? 366 : 365); 342 | if (isNum(data.year)) data.year += 1; 343 | } 344 | while (isNum(data.day) && isNum(data.month) && (data.day < (isNum(data.year) && isLeapYear(data.year) ? -366 : -365))) { 345 | data.day += (isNum(data.year) && isLeapYear(data.year) ? 366 : 365); 346 | if (isNum(data.year)) data.year -= 1; 347 | } 348 | while (isNum(data.day) && isNum(data.month) && (data.day > daysInMonth(isNum(data.year) ? data.year : 1976, data.month))) { 349 | data.day -= daysInMonth(isNum(data.year) ? data.year : 1976, data.month); 350 | data.month += 1; 351 | if (data.month > 12) { 352 | data.year += 1; 353 | data.month = data.month = 1; 354 | } 355 | } 356 | if (isNum(data.month) && isNum(data.year) && (data.month > 12)) { 357 | data.year += Math.floor(data.month / 12); 358 | data.month = data.month % 12; 359 | } 360 | } 361 | 362 | function isNum(v) { 363 | return (('undefined' !== v) && Number.isInteger(v)); 364 | } 365 | -------------------------------------------------------------------------------- /lib/temporal.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import { Absolute } from './absolute.mjs'; 7 | import { DateTime } from './datetime.mjs'; 8 | import { TimeZone } from './timezone.mjs'; 9 | import { Duration } from './duration.mjs'; 10 | import { Date } from './date.mjs'; 11 | import { YearMonth } from './yearmonth.mjs'; 12 | import { MonthDay } from './monthday.mjs'; 13 | import { Time } from './time.mjs'; 14 | import { EARLIER, LATER } from './shared.mjs'; 15 | import * as Local from './local.mjs'; 16 | export default { 17 | Absolute, 18 | DateTime, 19 | TimeZone, 20 | Duration, 21 | Date, 22 | Time, 23 | YearMonth, 24 | MonthDay, 25 | EARLIER, 26 | LATER, 27 | Local 28 | }; 29 | -------------------------------------------------------------------------------- /lib/time.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import { DATA, REGEX, assert, pad, copyProps } from './shared.mjs'; 7 | import { cast as castDuration } from './duration.mjs'; 8 | import { DateTime } from './datetime.mjs'; 9 | import { balance, balancePlus, balanceMinus } from './shared.mjs'; 10 | 11 | const RE_DT = new RegExp(`^${REGEX.TIME.source}$`); 12 | 13 | export class Time { 14 | constructor(hour, minute, second = 0, millisecond = 0, microsecond = 0, nanosecond = 0) { 15 | assert.range(hour, 0, 23); 16 | assert.range(minute, 0, 59); 17 | assert.range(second, 0, 60); 18 | assert.range(millisecond, 0, 999); 19 | assert.range(microsecond, 0, 999); 20 | assert.range(nanosecond, 0, 999); 21 | second = Math.min(59, second); 22 | this[DATA] = { hour, minute, second, millisecond, microsecond, nanosecond }; 23 | } 24 | 25 | get hour() { 26 | return this[DATA].hour; 27 | } 28 | get minute() { 29 | return this[DATA].minute; 30 | } 31 | get second() { 32 | return this[DATA].second; 33 | } 34 | get millisecond() { 35 | return this[DATA].millisecond; 36 | } 37 | get microsecond() { 38 | return this[DATA].microsecond; 39 | } 40 | get nanosecond() { 41 | return this[DATA].nanosecond; 42 | } 43 | 44 | withDate(datelike = {}) { 45 | if ('string' === typeof datelike) datelike = Date.fromString(datelike); 46 | const data = {}; 47 | data.year = +datelike.year; data.year = Number.isInteger(data.year) ? data.year : this.year; 48 | data.month = +datelike.month; data.month = Number.isInteger(data.month) ? data.month : this.month; 49 | data.day = +datelike.day; data.day = Number.isInteger(data.day) ? data.day : this.day; 50 | balance(data); 51 | return new DateTime( 52 | data.year, 53 | data.month, 54 | data.day, 55 | this.hour, 56 | this.minute, 57 | this.second, 58 | this.millisecond, 59 | this.microsecond, 60 | this.nanosecond 61 | ); 62 | } 63 | with(timelike = {}) { 64 | if ('string' === typeof timelike) timelike = DateTime.fromString(timelike); 65 | const data = {}; 66 | data.hour = +timelike.hour; data.hour = Number.isInteger(data.hour) ? data.hour : this.hour; 67 | data.minute = +timelike.minute; data.minute = Number.isInteger(data.minute) ? data.minute : this.minute; 68 | data.second = +timelike.second; data.second = Number.isInteger(data.second) ? data.second : this.second; 69 | data.millisecond = +timelike.millisecond; data.millisecond = Number.isInteger(data.millisecond) ? data.millisecond : this.millisecond; 70 | data.microsecond = +timelike.microsecond; data.microsecond = Number.isInteger(data.microsecond) ? data.microsecond : this.microsecond; 71 | data.nanosecond = +timelike.nanosecond; data.nanosecond = Number.isInteger(data.nanosecond) ? data.nanosecond : this.nanosecond; 72 | balance(data); 73 | return new Time(data.hour, data.minute, data.second, data.millisecond, data.microsecond, data.nanosecond); 74 | } 75 | 76 | difference(other = {}) { 77 | other = copyProps(this, 'string' === typeof other ? Time.fromString(other) : other); 78 | const [one, two] = [this, other].sort(Time.compare); 79 | 80 | let hours = two.hour - one.hour; 81 | let minutes = two.minute - one.minute; 82 | let seconds = two.second - one.second; 83 | let milliseconds = two.millisecond - one.millisecond; 84 | let microseconds = two.microsecond - one.microsecond; 85 | let nanoseconds = two.nanosecond - one.nanosecond; 86 | 87 | while (nanoseconds < 0) { 88 | nanoseconds += 1e3; 89 | microseconds -= 1; 90 | } 91 | while (nanoseconds >= 1e3) { 92 | nanoseconds -= 1e3; 93 | microseconds += 1; 94 | } 95 | 96 | while (microseconds < 0) { 97 | microseconds += 1e3; 98 | milliseconds -= 1; 99 | } 100 | while (microseconds >= 1e3) { 101 | microseconds -= 1e3; 102 | milliseconds += 1; 103 | } 104 | 105 | while (milliseconds < 0) { 106 | milliseconds += 1e3; 107 | seconds -= 1; 108 | } 109 | while (milliseconds >= 1e3) { 110 | milliseconds -= 1e3; 111 | seconds += 1; 112 | } 113 | 114 | while (seconds < 0) { 115 | seconds += 60; 116 | minutes -= 1; 117 | } 118 | while (seconds >= 60) { 119 | seconds -= 60; 120 | minutes += 1; 121 | } 122 | 123 | while (minutes < 0) { 124 | minutes += 60; 125 | hours -= 1; 126 | } 127 | while (minutes >= 60) { 128 | minutes -= 60; 129 | hours += 1; 130 | } 131 | 132 | while (hours < 0) { 133 | hours += 24; 134 | days -= 1; 135 | day -= 1; 136 | } 137 | while (hours >= 24) { 138 | hours -= 24; 139 | days += 1; 140 | day += 1; 141 | } 142 | 143 | return castDuration({ 144 | hours, 145 | minutes, 146 | seconds, 147 | milliseconds, 148 | microseconds, 149 | nanoseconds 150 | }); 151 | } 152 | plus(duration) { 153 | duration = castDuration(duration); 154 | if (duration.years || duration.months || duration.days) { 155 | throw new RangeError(`invalid duration: ${duration}`); 156 | } 157 | 158 | const data = copyProps(this); 159 | 160 | data.hour += duration.hours; 161 | data.minute += duration.minutes; 162 | data.second += duration.seconds; 163 | data.millisecond += duration.milliseconds; 164 | data.microsecond += duration.microseconds; 165 | data.nanosecond += duration.nanoseconds; 166 | balancePlus(data); 167 | 168 | const { hour, minute, second, millisecond, microsecond, nanosecond } = data; 169 | return new Time(hour, minute, second, millisecond, microsecond, nanosecond); 170 | } 171 | minus(duration) { 172 | duration = castDuration(duration); 173 | if (duration.years || duration.months || duration.days) { 174 | throw new RangeError(`invalid duration: ${duration}`); 175 | } 176 | 177 | const data = copyProps(this); 178 | 179 | data.hour -= duration.hours; 180 | data.minute -= duration.minutes; 181 | data.second -= duration.seconds; 182 | data.millisecond -= duration.milliseconds; 183 | data.microsecond -= duration.microseconds; 184 | data.nanosecond -= duration.nanoseconds; 185 | balanceMinus(data); 186 | 187 | const { hour, minute, second, millisecond, microsecond, nanosecond } = data; 188 | return new Time(hour, minute, second, millisecond, microsecond, nanosecond); 189 | } 190 | 191 | toString() { 192 | const tp = []; 193 | tp.push(pad(this.hour, 2)); 194 | tp.push(pad(this.minute, 2)); 195 | 196 | const sp = []; 197 | if (this.nanosecond) sp.unshift(pad(this.nanosecond, 3)); 198 | if (this.microsecond || sp.length) sp.unshift(pad(this.microsecond, 3)); 199 | if (this.millisecond || sp.length) sp.unshift(pad(this.millisecond, 3)); 200 | if (this.second || sp.length) { 201 | if (sp.length) { 202 | tp.push(`${pad(this.second, 2)}.${sp.join('')}`); 203 | } else { 204 | tp.push(pad(this.second, 2)); 205 | } 206 | } 207 | return tp.join(':'); 208 | } 209 | toJSON() { 210 | return this.toString(); 211 | } 212 | 213 | static fromString(iso) { 214 | const match = RE_DT.exec(iso); 215 | if (!match) throw new TypeError('invalid Time string'); 216 | const hour = +match[1]; 217 | const minute = +match[2]; 218 | const second = match[3] === undefined ? 0 : +match[3]; 219 | const millisecond = +`${match[4] === undefined ? '' : match[4]}000000000`.slice(0, 3); 220 | const microsecond = +`${match[4] === undefined ? '' : match[4]}000000000`.slice(3, 6); 221 | const nanosecond = +`${match[4] === undefined ? '' : match[4]}000000000`.slice(6, 9); 222 | return new Time(hour, minute, second, millisecond, microsecond, nanosecond); 223 | } 224 | static compare(one, two) { 225 | assert.timelike(one); 226 | assert.timelike(two); 227 | if (one.hour !== two.hour) return one.hour - two.hour; 228 | if (one.minute !== two.minute) return one.minute - two.minute; 229 | if (one.second !== two.second) return one.second - two.second; 230 | if (one.millisecond !== two.millisecond) return one.millisecond - two.millisecond; 231 | if (one.microsecond !== two.microsecond) return one.microsecond - two.microsecond; 232 | if (one.nanosecond !== two.nanosecond) return other.nanosecond - two.nanosecond; 233 | return 0; 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /lib/timezone.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import { DATA, TZ, OF, DT, assert, parseOffsetString, offsetString } from './shared.mjs'; 7 | import { DateTime } from './datetime.mjs'; 8 | import { UniqueMap } from './unimap.mjs'; 9 | import { ZONES } from './zones.mjs'; 10 | import { isOffset } from './shared.mjs'; 11 | import { Absolute } from './absolute.mjs'; 12 | 13 | const NAME = Symbol('name'); 14 | const LONG = Symbol('formatter'); 15 | const SHORT = Symbol('formatter'); 16 | const OFFSET = Symbol('offset'); 17 | 18 | const FormatterOptions = { 19 | hour12: false, 20 | year: 'numeric', 21 | month: 'numeric', 22 | day: 'numeric', 23 | hour: 'numeric', 24 | minute: 'numeric', 25 | second: 'numeric' 26 | }; 27 | 28 | const TRANSSEGMENTS = 27; 29 | 30 | const cache = new UniqueMap(); 31 | 32 | export class TimeZone { 33 | constructor() { 34 | throw new TypeError('TimeZone is not a constructor'); 35 | } 36 | get name() { 37 | return this[NAME]; 38 | } 39 | 40 | getTimezoneName(instant) { 41 | throw new TypeError('TimeZone is an abstract class'); 42 | } 43 | getDateTimeFor(instant) { 44 | throw new TypeError('TimeZone is an abstract class'); 45 | } 46 | getOffsetFor(instant) { 47 | throw new TypeError('TimeZone is an abstract class'); 48 | } 49 | getAbsoluteFor(datetime) { 50 | throw new TypeError('TimeZone is an abstract class'); 51 | } 52 | 53 | getOffsetsInYear(year) { 54 | throw new TypeError('TimeZone is an abstract class'); 55 | } 56 | getTransitionsInYear(year) { 57 | throw new TypeError('TimeZone is an abstract class'); 58 | } 59 | 60 | toString() { 61 | return this.name; 62 | } 63 | 64 | static for(name) { 65 | if (isOffset(name)) return OffsetZone.for(name); 66 | return IanaZone.for(name); 67 | } 68 | static list() { 69 | return ZONES.map((name) => { 70 | try { 71 | const zone = TimeZone.for(name); 72 | return zone.name; 73 | } catch (err) { 74 | // Ignore 75 | } 76 | }).filter((z) => !!z); 77 | } 78 | } 79 | 80 | class OffsetZone extends TimeZone { 81 | constructor() { 82 | throw new TypeError('TimeZone is not a constructor'); 83 | } 84 | zoneName(instant) { 85 | return this.name; 86 | } 87 | getDateTimeFor(instant) { 88 | const ns = instant[DATA] - BigInt(this[OFFSET]) * 1000000n; 89 | const dt = new Date(Number(ns / 1000000n)); 90 | 91 | const year = dt.getUTCFullYear(); 92 | const month = dt.getUTCMonth() + 1; 93 | const day = dt.getUTCDate(); 94 | const hour = dt.getUTCHours(); 95 | const minute = dt.getUTCMinutes(); 96 | const second = dt.getUTCSeconds(); 97 | const millisecond = dt.getUTCMilliseconds(); 98 | const microsecond = Number((ns / 1000n) % 1000n); 99 | const nanosecond = Number(ns % 1000n); 100 | 101 | return new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); 102 | } 103 | getOffsetFor(instant) { 104 | return this.name; 105 | } 106 | getAbsoluteFor(datetime) { 107 | const { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = datetime; 108 | const ms = Date.UTC(year, month - 1, day, hour, minute, second, millisecond) + this[OFFSET]; 109 | const ns = BigInt(ms) * 1000000n + BigInt(microsecond * 1000) + BigInt(nanosecond); 110 | return [new Absolute(ns, this.name)]; 111 | } 112 | 113 | getOffsetsInYear(year) { 114 | return [this.offset]; 115 | } 116 | getTransitionsInYear(year) { 117 | return []; 118 | } 119 | 120 | static for(offset) { 121 | const name = offsetString(offset); 122 | const zone = Object.create(OffsetZone.prototype, { [NAME]: { value: name }, [OFFSET]: { value: offset } }); 123 | return zone; 124 | } 125 | } 126 | OffsetZone.prototype[Symbol.toStringTag] = 'TimeZone'; 127 | 128 | class IanaZone extends TimeZone { 129 | constructor() { 130 | throw new TypeError('TimeZone is not a constructor'); 131 | } 132 | getTimezoneName(instant, format = 'short') { 133 | assert.type(instant, Absolute); 134 | assert.enum(format, 'short', 'long'); 135 | const epochNanoSeconds = instant[DATA]; 136 | const fmt = this[format === 'long' ? LONG : SHORT]; 137 | const { timeZoneName } = fmt.formatToParts(Number(epochNanoSeconds / 1000000n)).reduce((res, item) => { 138 | if (item.type !== 'literal') 139 | res[item.type] = item.type === 'timeZoneName' ? item.value : parseInt(item.value, 10); 140 | return res; 141 | }, {}); 142 | return timeZoneName; 143 | } 144 | getDateTimeFor(instant) { 145 | assert.type(instant, Absolute); 146 | const epochNanoSeconds = instant[DATA]; 147 | const ms = Number(epochNanoSeconds / 1000000n); 148 | const microsecond = (epochNanoSeconds < 0n) ? Math.abs(Number((epochNanoSeconds / 1000n) % 1000n)) : Number((epochNanoSeconds / 1000n) % 1000n); 149 | const nanosecond = (epochNanoSeconds < 0n) ? Math.abs(Number(epochNanoSeconds % 1000n)) : Number(epochNanoSeconds % 1000n); 150 | const { year, month, day, hour, minute, second, millisecond } = timeparts.call(this, ms); 151 | return new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); 152 | } 153 | getOffsetFor(instant) { 154 | assert.type(instant, Absolute); 155 | const epochNanoSeconds = instant[DATA]; 156 | const { offset } = timeparts.call(this, Number(epochNanoSeconds / 1000000n)); 157 | return offset; 158 | } 159 | getAbsoluteFor(datetime) { 160 | assert.type(datetime, DateTime); 161 | const { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = datetime; 162 | const base = Date.UTC(year, month - 1, day, hour, minute, second, millisecond); 163 | return offsets 164 | .call(this, year) 165 | .sort() 166 | .map((offset) => { 167 | const ms = base - offset; 168 | const info = timeparts.call(this, ms); 169 | if (info.hour !== hour) return undefined; 170 | if (info.year !== year) return undefined; 171 | if (info.month !== month) return undefined; 172 | if (info.day !== day) return undefined; 173 | if (info.minute !== minute) return undefined; 174 | if (info.second !== second) return undefined; 175 | const ns = BigInt(ms) * 1000000n + BigInt(microsecond) * 1000n + BigInt(nanosecond); 176 | return Object.create(Absolute.prototype, { 177 | [DATA]: { value: ns, configurable: true, writable: true }, 178 | [TZ]: { value: this, configurable: true, writable: true }, 179 | [DT]: { value: datetime, configurable: true, writable: true }, 180 | [OF]: { value: offsetString(offset), configurable: true, writable: true } 181 | }); 182 | }) 183 | .filter((x) => !!x) 184 | .sort((a, b) => { 185 | return Number(a[DATA] - b[DATA]); 186 | }); 187 | } 188 | getOffsetsInYear(year) { 189 | const off = offsets.call(this, year); 190 | return off.map(offsetString); 191 | } 192 | getTransitionsInYear(year) { 193 | const start = this.getAbsoluteFor(new DateTime(year, 1, 1, 0, 0)).shift(); 194 | const end = this.getAbsoluteFor(new DateTime(year, 12, 31, 23, 59, 59, 999, 999, 999)).pop(); 195 | const length = end[DATA] - start[DATA]; 196 | const points = new Array(TRANSSEGMENTS) 197 | .fill(0) 198 | .map((_, idx) => Number((start[DATA] + BigInt(idx) * (length / BigInt(TRANSSEGMENTS))) / 1000000n)); 199 | points.push(Number(end[DATA] / 1000000n)); 200 | const trans = []; 201 | const getOffset = (value) => { 202 | const { offset } = timeparts.call(this, value); 203 | return offset; 204 | }; 205 | points.forEach((right, idx) => { 206 | if (!idx) return; 207 | const left = points[idx - 1]; 208 | const offset = getOffset(right); 209 | if (getOffset(left) !== offset) { 210 | const epochMillis = bisect(getOffset, left, right); 211 | trans.push(new Absolute(BigInt(epochMillis) * 1000000n), this.name); 212 | } 213 | }); 214 | return trans; 215 | } 216 | static for(timeZone) { 217 | let formatters = cache.get(timeZone); 218 | if (!formatters) { 219 | const long = new Intl.DateTimeFormat( 220 | 'en-iso', 221 | Object.assign({ timeZone: timeZone, timeZoneName: 'long' }, FormatterOptions) 222 | ); 223 | const short = new Intl.DateTimeFormat( 224 | 'en-iso', 225 | Object.assign({ timeZone: timeZone, timeZoneName: 'short' }, FormatterOptions) 226 | ); 227 | timeZone = long.resolvedOptions().timeZone; 228 | formatters = { long, short }; 229 | cache.set(timeZone, formatters); 230 | } 231 | 232 | const { long, short } = formatters; 233 | const zone = Object.create(IanaZone.prototype, { 234 | [NAME]: { value: timeZone }, 235 | [LONG]: { value: long }, 236 | [SHORT]: { value: short } 237 | }); 238 | return zone; 239 | } 240 | } 241 | IanaZone.prototype[Symbol.toStringTag] = 'TimeZone'; 242 | 243 | function timeparts(ms) { 244 | const { year, month, day, hour, minute, second, timeZoneName: timeZoneLong } = this[LONG].formatToParts(ms).reduce( 245 | (res, item) => { 246 | if (item.type !== 'literal') 247 | res[item.type] = item.type === 'timeZoneName' ? item.value : parseInt(item.value, 10); 248 | return res; 249 | }, 250 | {} 251 | ); 252 | const { timeZoneName: timeZoneShort } = this[SHORT].formatToParts(ms).reduce((res, item) => { 253 | if (item.type !== 'literal') res[item.type] = item.type === 'timeZoneName' ? item.value : parseInt(item.value, 10); 254 | return res; 255 | }, {}); 256 | const millisecond = (ms < 0) ? (1000 + ms % 1000) : (ms % 1000); 257 | const offsetMilliseconds = Date.UTC(year, month - 1, day, hour, minute, second, millisecond) - ms; 258 | const offset = offsetString(offsetMilliseconds); 259 | return { 260 | year, 261 | month, 262 | day, 263 | hour, 264 | minute, 265 | second, 266 | millisecond, 267 | offset, 268 | offsetMilliseconds, 269 | timeZoneLong, 270 | timeZoneShort 271 | }; 272 | } 273 | function offsets(year) { 274 | const base = new Date(year, 0, 2, 9); 275 | const res = new Set(); 276 | new Array(12).fill(0).forEach((_, month) => { 277 | base.setMonth(month); 278 | const { offsetMilliseconds } = timeparts.call(this, base.getTime()); 279 | res.add(offsetMilliseconds); 280 | }); 281 | return Array.from(res); 282 | } 283 | 284 | function bisect(getState, left, right, lstate = getState(left), rstate = getState(right)) { 285 | if (right - left < 2) return right; 286 | let middle = Math.ceil((left + right) / 2); 287 | if (middle === right) middle -= 1; 288 | const mstate = getState(middle); 289 | if (mstate === lstate) return bisect(getState, middle, right, mstate, rstate); 290 | if (mstate === rstate) return bisect(getState, left, middle, lstate, mstate); 291 | throw new Error('invalid state in bisection'); 292 | } 293 | -------------------------------------------------------------------------------- /lib/unimap.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | const STRONG = Symbol('strong'); 7 | const WEAK = Symbol('weak'); 8 | const CLEANING = Symbol('cleaning'); 9 | 10 | export class UniqueMap { 11 | constructor() { 12 | this[STRONG] = new Map(); 13 | this[WEAK] = new WeakMap(); 14 | this[CLEANING] = null; 15 | } 16 | has(key) { 17 | const strong = this[STRONG].get(key); 18 | if (!strong) return false; 19 | return this[WEAK].has(strong); 20 | } 21 | get(key) { 22 | this[CLEANING] = this[CLEANING] || Promise.resolve(this).then(clean); 23 | const strong = this[STRONG].get(key); 24 | if (!strong) return; 25 | return this[WEAK].get(strong); 26 | } 27 | set(key, value) { 28 | this[CLEANING] = this[CLEANING] || Promise.resolve(this).then(clean); 29 | const strong = this[STRONG].get(key) || {}; 30 | this[STRONG].set(key, strong); 31 | this[WEAK].set(strong, value); 32 | } 33 | delete(key) { 34 | this[CLEANING] = this[CLEANING] || Promise.resolve(this).then(clean); 35 | const strong = this[STRONG].get(key); 36 | this[STRONG].delete(key); 37 | if (strong) this[WEAK].delete(strong); 38 | } 39 | clear() { 40 | this[STRONG].clear(); 41 | this[WEAK].clear(); 42 | } 43 | } 44 | 45 | function clean(unique) { 46 | try { 47 | for (const [key, strong] of unique[STRONG].entries()) { 48 | if (!unique[WEAK].has(strong)) unique[STRONG].delete(key); 49 | } 50 | } catch (err) { 51 | // Ignore 52 | } finally { 53 | unique[CLEANING] = null; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/yearmonth.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import { DATA, REGEX, assert, pad, copyProps } from './shared.mjs'; 7 | import { Date } from './date.mjs'; 8 | import { cast as castDuration } from './duration.mjs'; 9 | 10 | const RE_DT = new RegExp(`^(${REGEX.YEARS})-(${REGEX.MONTHS})$`); 11 | 12 | export class YearMonth { 13 | constructor(year, month) { 14 | assert.integer(year); 15 | assert.range(month, 1, 12); 16 | this[DATA] = { year, month }; 17 | } 18 | 19 | get year() { 20 | return this[DATA].year; 21 | } 22 | get month() { 23 | return this[DATA].month; 24 | } 25 | 26 | withDay(day) { 27 | return new Date(this.year, this.month, +day); 28 | } 29 | with(yearmonthlike = {}) { 30 | const data = copyProps(this, 'string' === typeof yearmonthlike ? YearMonth.fromString(yearmonthlike) : yearmonthlike); 31 | return new YearMonth(data.year, data.month); 32 | } 33 | 34 | difference(other = {}) { 35 | other = copyProps(this, other); 36 | const [one, two] = [this, other].sort(YearMonth.compare); 37 | 38 | let years = two.year - one.year; 39 | let months = two.month - one.month; 40 | 41 | if (months < 0) { 42 | years -= 1; 43 | months = 12 + months; 44 | } 45 | 46 | return castDuration({ years, months }); 47 | } 48 | toString() { 49 | const year = pad(this.year, 4); 50 | const month = pad(this.month); 51 | const date = `${year}-${month}`; 52 | return `${date}`; 53 | } 54 | toJSON() { 55 | return this.toString(); 56 | } 57 | 58 | static fromString(iso) { 59 | const match = RE_DT.exec(iso); 60 | if (!match) throw new TypeError('invalid YearMonth string'); 61 | const year = +match[1]; 62 | const month = +match[2]; 63 | return new YearMonth(year, month); 64 | } 65 | static compare(one, two) { 66 | assert.type(one, Object); 67 | assert.integer(one.year); 68 | assert.range(one.month, 1, 12); 69 | assert.type(two, Object); 70 | assert.integer(two.year); 71 | assert.range(two.month, 1, 12); 72 | if (one.year !== two.year) return one.year - two.year; 73 | if (one.month !== two.month) return one.month - two.month; 74 | return 0; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/zoned.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import { DATA, REGEX, assert, parseOffsetString, offsetString, EARLIER } from './shared.mjs'; 7 | import { Instant } from './instant.mjs'; 8 | import { TimeZone } from './timezone.mjs'; 9 | import { DateTime } from './datetime.mjs'; 10 | 11 | const RE_DT = new RegExp(`^${REGEX.DATETIME.source}${REGEX.OFFSET.source}(?:${REGEX.TIMEZONE.source})?$`); 12 | 13 | export class ZonedDateTime { 14 | constructor(instant, timeZone) { 15 | timeZone = TimeZone.for(`${timeZone}`); 16 | assert.type(timeZone, TimeZone); 17 | assert.type(instant, Instant); 18 | const dateTime = timeZone.dateTime(instant); 19 | this[DATA] = { instant, timeZone, dateTime }; 20 | } 21 | get year() { 22 | return this[DATA].dateTime.year; 23 | } 24 | get month() { 25 | return this[DATA].dateTime.month; 26 | } 27 | get day() { 28 | return this[DATA].dateTime.day; 29 | } 30 | get hour() { 31 | return this[DATA].dateTime.hour; 32 | } 33 | get minute() { 34 | return this[DATA].dateTime.minute; 35 | } 36 | get second() { 37 | return this[DATA].dateTime.second; 38 | } 39 | get millisecond() { 40 | return this[DATA].dateTime.millisecond; 41 | } 42 | get microsecond() { 43 | return this[DATA].dateTime.microsecond; 44 | } 45 | get nanosecond() { 46 | return this[DATA].dateTime.nanosecond; 47 | } 48 | get dayOfWeek() { 49 | return this[DATA].dateTime.dayOfWeek; 50 | } 51 | get dayOfYear() { 52 | return this[DATA].dateTime.dayOfYear; 53 | } 54 | get weekOfYear() { 55 | return this[DATA].dateTime.weekOfYear; 56 | } 57 | get timeZone() { 58 | return this[DATA].timeZone.name; 59 | } 60 | 61 | getInstant() { 62 | return this[DATA].instant; 63 | } 64 | getTimeZone() { 65 | return this[DATA].timeZone; 66 | } 67 | getTimeZoneName(mode = 'raw') { 68 | assert.enum(mode, 'raw', 'long', 'short'); 69 | return mode === 'raw' ? this.getTimeZone().name : this.getTimeZone().zoneName(this.getInstant(), mode); 70 | } 71 | getTimeZoneOffset() { 72 | return this.getTimeZone().offset(this.getInstant()); 73 | } 74 | getDateTime() { 75 | return this[DATA].dateTime; 76 | } 77 | getDate() { 78 | return this.getDateTime().getDate(); 79 | } 80 | getTime() { 81 | return this.getDateTime().getTime(); 82 | } 83 | getYearMonth() { 84 | return this.getDateTime().getYearMonth(); 85 | } 86 | getMonthDay() { 87 | return this.getDateTime().getYearMonth(); 88 | } 89 | 90 | with( 91 | { 92 | year = this.year, 93 | month = this.month, 94 | day = this.day, 95 | hour = this.hour, 96 | minute = this.minute, 97 | second = this.second, 98 | millisecond = this.millisecond, 99 | microsecond = this.microsecond, 100 | nanosecond = this.nanosecond 101 | }, 102 | choice = EARLIER 103 | ) { 104 | const timeZone = this.getTimeZone(); 105 | const dateTime = new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); 106 | 107 | const instants = timeZone.instants(dateTime); 108 | let instant; 109 | switch (choice) { 110 | case EARLIER: 111 | instant = instants.shift(); 112 | break; 113 | case LATER: 114 | instant = instants.pop(); 115 | break; 116 | default: 117 | instant = instant.find((instant) => timeZone.offset(instant) === choice); 118 | break; 119 | } 120 | if (!instant) throw TypeError('invalid DateTime data for TimeZone'); 121 | 122 | return Object.create(ZonedDateTime.prototype, { 123 | [DATA]: { value: Object.freeze({ instant, timeZone, dateTime }) } 124 | }); 125 | } 126 | sameDateTimeWithZone(timeZone, choice = EARLIER) { 127 | timeZone = TimeZone.for(`${timeZone}`); 128 | assert.type(timeZone, TimeZone); 129 | const dateTime = this.getDateTime(); 130 | 131 | const instants = timeZone.instants(dateTime); 132 | let instant; 133 | switch (choice) { 134 | case EARLIER: 135 | instant = instants.shift(); 136 | break; 137 | case LATER: 138 | instant = instants.pop(); 139 | break; 140 | default: 141 | instant = instant.find((instant) => timeZone.offset(instant) === choice); 142 | break; 143 | } 144 | if (!instant) throw TypeError('invalid DateTime data for TimeZone'); 145 | 146 | return Object.create(ZonedDateTime.prototype, { 147 | [DATA]: { value: Object.freeze({ instant, timeZone, dateTime }) } 148 | }); 149 | } 150 | sameInstantWithZone(timeZone) { 151 | timeZone = TimeZone.for(`${timeZone}`); 152 | assert.type(timeZone, TimeZone); 153 | 154 | const instant = this.getInstant(); 155 | const dateTime = timeZone.dateTime(instant); 156 | 157 | return Object.create(ZonedDateTime.prototype, { 158 | [DATA]: { value: Object.freeze({ instant, timeZone, dateTime }) } 159 | }); 160 | } 161 | 162 | toString() { 163 | const instant = this.getInstant(); 164 | const timeZone = this.getTimeZone(); 165 | const offset = timeZone.offset(instant); 166 | const zoneName = timeZone.name === offset ? '' : `[${timeZone.name}]`; 167 | return `${this.getDateTime()}${offset}${zoneName}`; 168 | } 169 | toJSON() { 170 | return this.toString(); 171 | } 172 | 173 | static fromString(iso) { 174 | const match = RE_DT.exec(iso); 175 | if (!match) throw new TypeError('invalid ZonedDateTime string'); 176 | const year = +match[1]; 177 | const month = +match[2]; 178 | const day = +match[3]; 179 | const hour = +match[4]; 180 | const minute = +match[5]; 181 | const second = match[6] === undefined ? 0 : +match[6]; 182 | const millisecond = +`${match[7] === undefined ? '' : match[7]}000000000`.slice(0, 3); 183 | const microsecond = +`${match[7] === undefined ? '' : match[7]}000000000`.slice(3, 6); 184 | const nanosecond = +`${match[7] === undefined ? '' : match[7]}000000000`.slice(6, 9); 185 | const offset = offsetString(parseOffsetString(`${match[8]}:${match[9]}`)); 186 | const tzname = match[10] || offset; 187 | const timeZone = TimeZone.for(tzname); 188 | const dateTime = new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); 189 | const instant = timeZone 190 | .instants(dateTime) 191 | .filter((instant) => timeZone.offset(instant) === offset) 192 | .shift(); 193 | if (!instant) throw TypeError('invalid ZonedDateTime string'); 194 | return Object.create(ZonedDateTime.prototype, { 195 | [DATA]: { value: Object.freeze({ instant, timeZone, dateTime }) } 196 | }); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /lib/zones.mjs: -------------------------------------------------------------------------------- 1 | export const ZONES = [ 2 | 'Africa/Abidjan', 3 | 'Africa/Accra', 4 | 'Africa/Addis_Ababa', 5 | 'Africa/Algiers', 6 | 'Africa/Asmara', 7 | 'Africa/Bamako', 8 | 'Africa/Bangui', 9 | 'Africa/Banjul', 10 | 'Africa/Bissau', 11 | 'Africa/Blantyre', 12 | 'Africa/Brazzaville', 13 | 'Africa/Bujumbura', 14 | 'Africa/Cairo', 15 | 'Africa/Casablanca', 16 | 'Africa/Ceuta', 17 | 'Africa/Conakry', 18 | 'Africa/Dakar', 19 | 'Africa/Dar_es_Salaam', 20 | 'Africa/Djibouti', 21 | 'Africa/Douala', 22 | 'Africa/El_Aaiun', 23 | 'Africa/Freetown', 24 | 'Africa/Gaborone', 25 | 'Africa/Harare', 26 | 'Africa/Johannesburg', 27 | 'Africa/Juba', 28 | 'Africa/Kampala', 29 | 'Africa/Khartoum', 30 | 'Africa/Kigali', 31 | 'Africa/Kinshasa', 32 | 'Africa/Lagos', 33 | 'Africa/Libreville', 34 | 'Africa/Lome', 35 | 'Africa/Luanda', 36 | 'Africa/Lubumbashi', 37 | 'Africa/Lusaka', 38 | 'Africa/Malabo', 39 | 'Africa/Maputo', 40 | 'Africa/Maseru', 41 | 'Africa/Mbabane', 42 | 'Africa/Mogadishu', 43 | 'Africa/Monrovia', 44 | 'Africa/Nairobi', 45 | 'Africa/Ndjamena', 46 | 'Africa/Niamey', 47 | 'Africa/Nouakchott', 48 | 'Africa/Ouagadougou', 49 | 'Africa/Porto-Novo', 50 | 'Africa/Sao_Tome', 51 | 'Africa/Timbuktu', 52 | 'Africa/Tripoli', 53 | 'Africa/Tunis', 54 | 'Africa/Windhoek', 55 | 'America/Adak', 56 | 'America/Anchorage', 57 | 'America/Anguilla', 58 | 'America/Antigua', 59 | 'America/Araguaina', 60 | 'America/Argentina/Buenos_Aires', 61 | 'America/Argentina/Catamarca', 62 | 'America/Argentina/ComodRivadavia', 63 | 'America/Argentina/Cordoba', 64 | 'America/Argentina/Jujuy', 65 | 'America/Argentina/La_Rioja', 66 | 'America/Argentina/Mendoza', 67 | 'America/Argentina/Rio_Gallegos', 68 | 'America/Argentina/Salta', 69 | 'America/Argentina/San_Juan', 70 | 'America/Argentina/San_Luis', 71 | 'America/Argentina/Tucuman', 72 | 'America/Argentina/Ushuaia', 73 | 'America/Aruba', 74 | 'America/Asuncion', 75 | 'America/Atikokan', 76 | 'America/Atka', 77 | 'America/Bahia', 78 | 'America/Bahia_Banderas', 79 | 'America/Barbados', 80 | 'America/Belem', 81 | 'America/Belize', 82 | 'America/Blanc-Sablon', 83 | 'America/Boa_Vista', 84 | 'America/Bogota', 85 | 'America/Boise', 86 | 'America/Buenos_Aires', 87 | 'America/Cambridge_Bay', 88 | 'America/Campo_Grande', 89 | 'America/Cancun', 90 | 'America/Caracas', 91 | 'America/Catamarca', 92 | 'America/Cayenne', 93 | 'America/Cayman', 94 | 'America/Chicago', 95 | 'America/Chihuahua', 96 | 'America/Coral_Harbour', 97 | 'America/Cordoba', 98 | 'America/Costa_Rica', 99 | 'America/Creston', 100 | 'America/Cuiaba', 101 | 'America/Curacao', 102 | 'America/Danmarkshavn', 103 | 'America/Dawson', 104 | 'America/Dawson_Creek', 105 | 'America/Denver', 106 | 'America/Detroit', 107 | 'America/Dominica', 108 | 'America/Edmonton', 109 | 'America/Eirunepe', 110 | 'America/El_Salvador', 111 | 'America/Ensenada', 112 | 'America/Fort_Nelson', 113 | 'America/Fort_Wayne', 114 | 'America/Fortaleza', 115 | 'America/Glace_Bay', 116 | 'America/Godthab', 117 | 'America/Goose_Bay', 118 | 'America/Grand_Turk', 119 | 'America/Grenada', 120 | 'America/Guadeloupe', 121 | 'America/Guatemala', 122 | 'America/Guayaquil', 123 | 'America/Guyana', 124 | 'America/Halifax', 125 | 'America/Havana', 126 | 'America/Hermosillo', 127 | 'America/Indiana/Indianapolis', 128 | 'America/Indiana/Knox', 129 | 'America/Indiana/Marengo', 130 | 'America/Indiana/Petersburg', 131 | 'America/Indiana/Tell_City', 132 | 'America/Indiana/Vevay', 133 | 'America/Indiana/Vincennes', 134 | 'America/Indiana/Winamac', 135 | 'America/Indianapolis', 136 | 'America/Inuvik', 137 | 'America/Iqaluit', 138 | 'America/Jamaica', 139 | 'America/Jujuy', 140 | 'America/Juneau', 141 | 'America/Kentucky/Louisville', 142 | 'America/Kentucky/Monticello', 143 | 'America/Knox_IN', 144 | 'America/Kralendijk', 145 | 'America/La_Paz', 146 | 'America/Lima', 147 | 'America/Los_Angeles', 148 | 'America/Louisville', 149 | 'America/Lower_Princes', 150 | 'America/Maceio', 151 | 'America/Managua', 152 | 'America/Manaus', 153 | 'America/Marigot', 154 | 'America/Martinique', 155 | 'America/Matamoros', 156 | 'America/Mazatlan', 157 | 'America/Mendoza', 158 | 'America/Menominee', 159 | 'America/Merida', 160 | 'America/Metlakatla', 161 | 'America/Mexico_City', 162 | 'America/Miquelon', 163 | 'America/Moncton', 164 | 'America/Monterrey', 165 | 'America/Montevideo', 166 | 'America/Montreal', 167 | 'America/Montserrat', 168 | 'America/Nassau', 169 | 'America/New_York', 170 | 'America/Nipigon', 171 | 'America/Nome', 172 | 'America/Noronha', 173 | 'America/North_Dakota/Beulah', 174 | 'America/North_Dakota/Center', 175 | 'America/North_Dakota/New_Salem', 176 | 'America/Ojinaga', 177 | 'America/Panama', 178 | 'America/Pangnirtung', 179 | 'America/Paramaribo', 180 | 'America/Phoenix', 181 | 'America/Port_of_Spain', 182 | 'America/Port-au-Prince', 183 | 'America/Porto_Acre', 184 | 'America/Porto_Velho', 185 | 'America/Puerto_Rico', 186 | 'America/Punta_Arenas', 187 | 'America/Rainy_River', 188 | 'America/Rankin_Inlet', 189 | 'America/Recife', 190 | 'America/Regina', 191 | 'America/Resolute', 192 | 'America/Rio_Branco', 193 | 'America/Rosario', 194 | 'America/Santa_Isabel', 195 | 'America/Santarem', 196 | 'America/Santiago', 197 | 'America/Santo_Domingo', 198 | 'America/Sao_Paulo', 199 | 'America/Scoresbysund', 200 | 'America/Shiprock', 201 | 'America/Sitka', 202 | 'America/St_Barthelemy', 203 | 'America/St_Johns', 204 | 'America/St_Kitts', 205 | 'America/St_Lucia', 206 | 'America/St_Thomas', 207 | 'America/St_Vincent', 208 | 'America/Swift_Current', 209 | 'America/Tegucigalpa', 210 | 'America/Thule', 211 | 'America/Thunder_Bay', 212 | 'America/Tijuana', 213 | 'America/Toronto', 214 | 'America/Tortola', 215 | 'America/Vancouver', 216 | 'America/Virgin', 217 | 'America/Whitehorse', 218 | 'America/Winnipeg', 219 | 'America/Yakutat', 220 | 'America/Yellowknife', 221 | 'Antarctica/Casey', 222 | 'Antarctica/Davis', 223 | 'Antarctica/DumontDUrville', 224 | 'Antarctica/Macquarie', 225 | 'Antarctica/Mawson', 226 | 'Antarctica/McMurdo', 227 | 'Antarctica/Palmer', 228 | 'Antarctica/Rothera', 229 | 'Antarctica/South_Pole', 230 | 'Antarctica/Syowa', 231 | 'Antarctica/Troll', 232 | 'Antarctica/Vostok', 233 | 'Arctic/Longyearbyen', 234 | 'Asia/Aden', 235 | 'Asia/Almaty', 236 | 'Asia/Amman', 237 | 'Asia/Anadyr', 238 | 'Asia/Aqtau', 239 | 'Asia/Aqtobe', 240 | 'Asia/Ashgabat', 241 | 'Asia/Ashkhabad', 242 | 'Asia/Atyrau', 243 | 'Asia/Baghdad', 244 | 'Asia/Bahrain', 245 | 'Asia/Baku', 246 | 'Asia/Bangkok', 247 | 'Asia/Barnaul', 248 | 'Asia/Beirut', 249 | 'Asia/Bishkek', 250 | 'Asia/Brunei', 251 | 'Asia/Calcutta', 252 | 'Asia/Chita', 253 | 'Asia/Choibalsan', 254 | 'Asia/Chongqing', 255 | 'Asia/Chungking', 256 | 'Asia/Colombo', 257 | 'Asia/Dacca', 258 | 'Asia/Damascus', 259 | 'Asia/Dhaka', 260 | 'Asia/Dili', 261 | 'Asia/Dubai', 262 | 'Asia/Dushanbe', 263 | 'Asia/Famagusta', 264 | 'Asia/Gaza', 265 | 'Asia/Harbin', 266 | 'Asia/Hebron', 267 | 'Asia/Ho_Chi_Minh', 268 | 'Asia/Hong_Kong', 269 | 'Asia/Hovd', 270 | 'Asia/Irkutsk', 271 | 'Asia/Istanbul', 272 | 'Asia/Jakarta', 273 | 'Asia/Jayapura', 274 | 'Asia/Jerusalem', 275 | 'Asia/Kabul', 276 | 'Asia/Kamchatka', 277 | 'Asia/Karachi', 278 | 'Asia/Kashgar', 279 | 'Asia/Kathmandu', 280 | 'Asia/Katmandu', 281 | 'Asia/Kuala_Lumpur', 282 | 'Asia/Kuching', 283 | 'Asia/Kuwait', 284 | 'Asia/Macao', 285 | 'Asia/Macau', 286 | 'Asia/Magadan', 287 | 'Asia/Makassar', 288 | 'Asia/Manila', 289 | 'Asia/Muscat', 290 | 'Asia/Novokuznetsk', 291 | 'Asia/Novosibirsk', 292 | 'Asia/Omsk', 293 | 'Asia/Oral', 294 | 'Asia/Phnom_Penh', 295 | 'Asia/Pontianak', 296 | 'Asia/Pyongyang', 297 | 'Asia/Qatar', 298 | 'Asia/Qyzylorda', 299 | 'Asia/Rangoon', 300 | 'Asia/Riyadh', 301 | 'Asia/Saigon', 302 | 'Asia/Sakhalin', 303 | 'Asia/Samarkand', 304 | 'Asia/Seoul', 305 | 'Asia/Shanghai', 306 | 'Asia/Singapore', 307 | 'Asia/Srednekolymsk', 308 | 'Asia/Taipei', 309 | 'Asia/Tashkent', 310 | 'Asia/Tbilisi', 311 | 'Asia/Tehran', 312 | 'Asia/Tel_Aviv', 313 | 'Asia/Thimbu', 314 | 'Asia/Thimphu', 315 | 'Asia/Tokyo', 316 | 'Asia/Tomsk', 317 | 'Asia/Ujung_Pandang', 318 | 'Asia/Ulaanbaatar', 319 | 'Asia/Ulan_Bator', 320 | 'Asia/Urumqi', 321 | 'Asia/Ust-Nera', 322 | 'Asia/Vientiane', 323 | 'Asia/Vladivostok', 324 | 'Asia/Yakutsk', 325 | 'Asia/Yangon', 326 | 'Asia/Yekaterinburg', 327 | 'Asia/Yerevan', 328 | 'Atlantic/Azores', 329 | 'Atlantic/Bermuda', 330 | 'Atlantic/Canary', 331 | 'Atlantic/Cape_Verde', 332 | 'Atlantic/Faeroe', 333 | 'Atlantic/Faroe', 334 | 'Atlantic/Jan_Mayen', 335 | 'Atlantic/Madeira', 336 | 'Atlantic/Reykjavik', 337 | 'Atlantic/South_Georgia', 338 | 'Atlantic/St_Helena', 339 | 'Atlantic/Stanley', 340 | 'Australia/Adelaide', 341 | 'Australia/Brisbane', 342 | 'Australia/Broken_Hill', 343 | 'Australia/Canberra', 344 | 'Australia/Currie', 345 | 'Australia/Darwin', 346 | 'Australia/Eucla', 347 | 'Australia/Hobart', 348 | 'Australia/Lindeman', 349 | 'Australia/Lord_Howe', 350 | 'Australia/Melbourne', 351 | 'Australia/Perth', 352 | 'Australia/Sydney', 353 | 'Australia/Yancowinna', 354 | 'Europe/Amsterdam', 355 | 'Europe/Andorra', 356 | 'Europe/Astrakhan', 357 | 'Europe/Athens', 358 | 'Europe/Belfast', 359 | 'Europe/Belgrade', 360 | 'Europe/Berlin', 361 | 'Europe/Bratislava', 362 | 'Europe/Brussels', 363 | 'Europe/Bucharest', 364 | 'Europe/Budapest', 365 | 'Europe/Busingen', 366 | 'Europe/Chisinau', 367 | 'Europe/Copenhagen', 368 | 'Europe/Dublin', 369 | 'Europe/Gibraltar', 370 | 'Europe/Guernsey', 371 | 'Europe/Helsinki', 372 | 'Europe/Isle_of_Man', 373 | 'Europe/Istanbul', 374 | 'Europe/Jersey', 375 | 'Europe/Kaliningrad', 376 | 'Europe/Kiev', 377 | 'Europe/Kirov', 378 | 'Europe/Lisbon', 379 | 'Europe/Ljubljana', 380 | 'Europe/London', 381 | 'Europe/Luxembourg', 382 | 'Europe/Madrid', 383 | 'Europe/Malta', 384 | 'Europe/Mariehamn', 385 | 'Europe/Minsk', 386 | 'Europe/Monaco', 387 | 'Europe/Moscow', 388 | 'Asia/Nicosia', 389 | 'Europe/Oslo', 390 | 'Europe/Paris', 391 | 'Europe/Podgorica', 392 | 'Europe/Prague', 393 | 'Europe/Riga', 394 | 'Europe/Rome', 395 | 'Europe/Samara', 396 | 'Europe/San_Marino', 397 | 'Europe/Sarajevo', 398 | 'Europe/Saratov', 399 | 'Europe/Simferopol', 400 | 'Europe/Skopje', 401 | 'Europe/Sofia', 402 | 'Europe/Stockholm', 403 | 'Europe/Tallinn', 404 | 'Europe/Tirane', 405 | 'Europe/Tiraspol', 406 | 'Europe/Ulyanovsk', 407 | 'Europe/Uzhgorod', 408 | 'Europe/Vaduz', 409 | 'Europe/Vatican', 410 | 'Europe/Vienna', 411 | 'Europe/Vilnius', 412 | 'Europe/Volgograd', 413 | 'Europe/Warsaw', 414 | 'Europe/Zagreb', 415 | 'Europe/Zaporozhye', 416 | 'Europe/Zurich', 417 | 'Indian/Antananarivo', 418 | 'Indian/Chagos', 419 | 'Indian/Christmas', 420 | 'Indian/Cocos', 421 | 'Indian/Comoro', 422 | 'Indian/Kerguelen', 423 | 'Indian/Mahe', 424 | 'Indian/Maldives', 425 | 'Indian/Mauritius', 426 | 'Indian/Mayotte', 427 | 'Indian/Reunion', 428 | 'Pacific/Apia', 429 | 'Pacific/Auckland', 430 | 'Pacific/Bougainville', 431 | 'Pacific/Chatham', 432 | 'Pacific/Chuuk', 433 | 'Pacific/Easter', 434 | 'Pacific/Efate', 435 | 'Pacific/Enderbury', 436 | 'Pacific/Fakaofo', 437 | 'Pacific/Fiji', 438 | 'Pacific/Funafuti', 439 | 'Pacific/Galapagos', 440 | 'Pacific/Gambier', 441 | 'Pacific/Guadalcanal', 442 | 'Pacific/Guam', 443 | 'Pacific/Honolulu', 444 | 'Pacific/Johnston', 445 | 'Pacific/Kiritimati', 446 | 'Pacific/Kosrae', 447 | 'Pacific/Kwajalein', 448 | 'Pacific/Majuro', 449 | 'Pacific/Marquesas', 450 | 'Pacific/Midway', 451 | 'Pacific/Nauru', 452 | 'Pacific/Niue', 453 | 'Pacific/Norfolk', 454 | 'Pacific/Noumea', 455 | 'Pacific/Pago_Pago', 456 | 'Pacific/Palau', 457 | 'Pacific/Pitcairn', 458 | 'Pacific/Pohnpei', 459 | 'Pacific/Ponape', 460 | 'Pacific/Port_Moresby', 461 | 'Pacific/Rarotonga', 462 | 'Pacific/Saipan', 463 | 'Pacific/Samoa', 464 | 'Pacific/Tahiti', 465 | 'Pacific/Tarawa', 466 | 'Pacific/Tongatapu', 467 | 'Pacific/Truk', 468 | 'Pacific/Wake', 469 | 'Pacific/Wallis', 470 | 'Pacific/Yap', 471 | 'UTC' 472 | ]; 473 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@std-proposal/temporal", 3 | "version": "0.0.2", 4 | "description": "TC39 Proposal: Temporal", 5 | "main": "index.js", 6 | "directories": {}, 7 | "scripts": { 8 | "test:source": "node --no-warnings --experimental-modules --loader ./test/resolve.source.mjs ./test/all.mjs", 9 | "test:compiled": "node --no-warnings --experimental-modules --loader ./test/resolve.compiled.mjs ./test/all.mjs", 10 | "test": "npm run test:compiled", 11 | "prettier": "prettier --write lib/*.mjs test/*.mjs package.json", 12 | "prepublishOnly": "npm run build", 13 | "build": "rollup -c rollup.config.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/std-proposal/temporal.git" 18 | }, 19 | "keywords": [ 20 | "TC39", 21 | "Polyfill", 22 | "Temporal" 23 | ], 24 | "author": "Philipp Dunkel ", 25 | "license": "MIT", 26 | "devDependencies": { 27 | "@pipobscure/demitasse": "^1.0.10", 28 | "@pipobscure/demitasse-pretty": "^1.0.10", 29 | "@pipobscure/demitasse-run": "^1.0.10", 30 | "husky": "^3.0.5", 31 | "lint-staged": "^9.2.5", 32 | "prettier": "^1.18.2", 33 | "rollup": "^1.7.0", 34 | "rollup-plugin-license": "^0.12.1" 35 | }, 36 | "husky": { 37 | "hooks": { 38 | "pre-commit": "lint-staged && npm run build" 39 | } 40 | }, 41 | "lint-staged": { 42 | "*.{ts,js,json,css,md}": [ 43 | "prettier --write", 44 | "git add" 45 | ] 46 | }, 47 | "prettier": { 48 | "printWidth": 120, 49 | "trailingComma": "none", 50 | "tabWidth": 2, 51 | "semi": true, 52 | "singleQuote": true, 53 | "bracketSpacing": true, 54 | "arrowParens": "always" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import license from 'rollup-plugin-license'; 7 | import { join } from 'path'; 8 | 9 | export default { 10 | input: 'lib/temporal.mjs', 11 | plugins: [ 12 | license({ 13 | banner: { 14 | content: { 15 | file: join(__dirname, 'LICENSE'), 16 | encoding: 'utf-8' 17 | } 18 | } 19 | }) 20 | ], 21 | output: { 22 | file: 'index.js', 23 | name: 'temporal', 24 | format: 'umd', 25 | sourcemap: true 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /test/absolute.mjs: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env -S node --experimental-modules 2 | 3 | /* 4 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 5 | ** This code is governed by the license found in the LICENSE file. 6 | */ 7 | 8 | import Demitasse from '@pipobscure/demitasse'; 9 | const { describe, it, report } = Demitasse; 10 | 11 | import Pretty from '@pipobscure/demitasse-pretty'; 12 | const { reporter } = Pretty; 13 | 14 | import Assert from 'assert'; 15 | const { ok: assert, equal, throws } = Assert; 16 | 17 | import Temporal from '@std-proposal/temporal'; 18 | const { Absolute } = Temporal; 19 | 20 | describe('Absolute', () => { 21 | describe('Structure', () => { 22 | it('Absolute is a Function', () => { 23 | equal(typeof Absolute, 'function'); 24 | }); 25 | it('Absolute has a prototype', () => { 26 | assert(Absolute.prototype); 27 | equal(typeof Absolute.prototype, 'object'); 28 | }); 29 | describe('Absolute.prototype', () => { 30 | it('Absolute.prototype has getEpochSeconds', () => { 31 | assert('getEpochSeconds' in Absolute.prototype); 32 | }); 33 | it('Absolute.prototype has getEpochMilliseconds', () => { 34 | assert('getEpochMilliseconds' in Absolute.prototype); 35 | }); 36 | it('Absolute.prototype has getEpochMicroseconds', () => { 37 | assert('getEpochMicroseconds' in Absolute.prototype); 38 | }); 39 | it('Absolute.prototype has getEpochNanoseconds', () => { 40 | assert('getEpochNanoseconds' in Absolute.prototype); 41 | }); 42 | it('Absolute.prototype.withZone is a Function', () => { 43 | equal(typeof Absolute.prototype.withZone, 'function'); 44 | }); 45 | it('Absolute.prototype.toString is a Function', () => { 46 | equal(typeof Absolute.prototype.toString, 'function'); 47 | }); 48 | it('Absolute.prototype.toJSON is a Function', () => { 49 | equal(typeof Absolute.prototype.toJSON, 'function'); 50 | }); 51 | it('Absolute.prototype has year', () => { 52 | assert('year' in Absolute.prototype); 53 | }); 54 | it('Absolute.prototype has month', () => { 55 | assert('month' in Absolute.prototype); 56 | }); 57 | it('Absolute.prototype has day', () => { 58 | assert('day' in Absolute.prototype); 59 | }); 60 | it('Absolute.prototype has hour', () => { 61 | assert('hour' in Absolute.prototype); 62 | }); 63 | it('Absolute.prototype has minute', () => { 64 | assert('minute' in Absolute.prototype); 65 | }); 66 | it('Absolute.prototype has second', () => { 67 | assert('second' in Absolute.prototype); 68 | }); 69 | it('Absolute.prototype has millisecond', () => { 70 | assert('millisecond' in Absolute.prototype); 71 | }); 72 | it('Absolute.prototype has microsecond', () => { 73 | assert('microsecond' in Absolute.prototype); 74 | }); 75 | it('Absolute.prototype has nanosecond', () => { 76 | assert('nanosecond' in Absolute.prototype); 77 | }); 78 | it('Absolute.prototype has dayOfWeek', () => { 79 | assert('dayOfWeek' in Absolute.prototype); 80 | }); 81 | it('Absolute.prototype has dayOfYear', () => { 82 | assert('dayOfYear' in Absolute.prototype); 83 | }); 84 | it('Absolute.prototype has weekOfYear', () => { 85 | assert('weekOfYear' in Absolute.prototype); 86 | }); 87 | it('Absolute.prototype.with is a Function', () => { 88 | equal(typeof Absolute.prototype.with, 'function'); 89 | }); 90 | it('Absolute.prototype.getDateTime is a Function', () => { 91 | equal(typeof Absolute.prototype.getDateTime, 'function'); 92 | }); 93 | it('Absolute.prototype.getDate is a Function', () => { 94 | equal(typeof Absolute.prototype.getDate, 'function'); 95 | }); 96 | it('Absolute.prototype.getTime is a Function', () => { 97 | equal(typeof Absolute.prototype.getTime, 'function'); 98 | }); 99 | it('Absolute.prototype.getYearMonth is a Function', () => { 100 | equal(typeof Absolute.prototype.getYearMonth, 'function'); 101 | }); 102 | it('Absolute.prototype.getMonthDay is a Function', () => { 103 | equal(typeof Absolute.prototype.getMonthDay, 'function'); 104 | }); 105 | }); 106 | it('Absolute.fromEpochSeconds is a Function', () => { 107 | equal(typeof Absolute.fromEpochSeconds, 'function'); 108 | }); 109 | it('Absolute.fromEpochMicroseconds is a Function', () => { 110 | equal(typeof Absolute.fromEpochMicroseconds, 'function'); 111 | }); 112 | it('Absolute.fromEpochMilliseconds is a Function', () => { 113 | equal(typeof Absolute.fromEpochMilliseconds, 'function'); 114 | }); 115 | it('Absolute.fromEpochNanoseconds is a Function', () => { 116 | equal(typeof Absolute.fromEpochNanoseconds, 'function'); 117 | }); 118 | it('Absolute.fromString is a Function', () => { 119 | equal(typeof Absolute.fromString, 'function'); 120 | }); 121 | }); 122 | describe('Construction', () => { 123 | it('can construct', () => { 124 | const instant = new Absolute(BigInt(Date.UTC(1976, 10, 18, 14, 23, 30, 123)) * BigInt(1e6) + BigInt(456789)); 125 | assert(instant); 126 | equal(typeof instant, 'object'); 127 | equal(instant.getEpochSeconds(), Math.floor(Date.UTC(1976, 10, 18, 14, 23, 30, 123) / 1e3), 'getEpochSeconds'); 128 | equal(instant.getEpochMilliseconds(), Date.UTC(1976, 10, 18, 14, 23, 30, 123), 'getEpochMilliseconds'); 129 | }); 130 | it('throws on number', () => throws(() => new Absolute(1234))); 131 | it('throws on string', () => throws(() => new Absolute('1234'))); 132 | }); 133 | describe('absolute.toString() works', () => { 134 | it('`1976-11-18T14:23:30.123456789Z`.toString()', () => { 135 | const instant = new Absolute(BigInt(Date.UTC(1976, 10, 18, 14, 23, 30, 123)) * BigInt(1e6) + BigInt(456789)); 136 | assert(instant); 137 | equal(`${instant}`, '1976-11-18T14:23:30.123456789Z'); 138 | }); 139 | it('`1963-02-13T09:36:29.877456789Z`.toString()', () => { 140 | const instant = new Absolute( 141 | BigInt(-1) * (BigInt(Date.UTC(1976, 10, 18, 14, 23, 30, 123)) * BigInt(1e6) + BigInt(456789)) 142 | ); 143 | assert(instant); 144 | equal(`${instant}`, '1963-02-13T09:36:29.877456789Z'); 145 | }); 146 | }); 147 | describe('Absolute.fromEpochSeconds() works', () => { 148 | it('1976-11-18T15:23:30', () => { 149 | const epochSeconds = Math.floor(Date.UTC(1976, 10, 18, 15, 23, 30, 123) / 1e3); 150 | const instant = Absolute.fromEpochSeconds(epochSeconds); 151 | equal(instant.getEpochSeconds(), epochSeconds); 152 | }); 153 | it('1963-02-13T09:36:29', () => { 154 | const epochSeconds = Math.floor(Date.UTC(1963, 1, 13, 9, 36, 29, 123) / 1e3); 155 | const instant = Absolute.fromEpochSeconds(epochSeconds); 156 | equal(instant.getEpochSeconds(), epochSeconds); 157 | }); 158 | }); 159 | describe('Absolute.fromEpochMilliseconds() works', () => { 160 | it('1976-11-18T15:23:30.123', () => { 161 | const epochMilliseconds = Date.UTC(1976, 10, 18, 15, 23, 30, 123); 162 | const instant = Absolute.fromEpochMilliseconds(epochMilliseconds); 163 | equal(instant.getEpochMilliseconds(), epochMilliseconds); 164 | }); 165 | it('1963-02-13T09:36:29.123', () => { 166 | const epochMilliseconds = Date.UTC(1963, 1, 13, 9, 36, 29, 123); 167 | const instant = Absolute.fromEpochMilliseconds(epochMilliseconds); 168 | equal(instant.getEpochMilliseconds(), epochMilliseconds); 169 | }); 170 | }); 171 | describe('Absolute.fromEpochMicroseconds() works', () => { 172 | it('1976-11-18T15:23:30.123456', () => { 173 | const epochMicroseconds = BigInt(Date.UTC(1976, 10, 18, 15, 23, 30, 123)) * BigInt(1e3) + BigInt(456); 174 | const instant = Absolute.fromEpochMicroseconds(epochMicroseconds); 175 | equal(instant.getEpochMicroseconds(), epochMicroseconds); 176 | }); 177 | it('1963-02-13T09:36:29.123456', () => { 178 | const epochMicroseconds = BigInt(Date.UTC(1963, 1, 13, 9, 36, 29, 123)) * BigInt(1e3) + BigInt(456); 179 | const instant = Absolute.fromEpochMicroseconds(epochMicroseconds); 180 | equal(instant.getEpochMicroseconds(), epochMicroseconds); 181 | }); 182 | }); 183 | describe('Absolute.fromEpochNanoseconds() works', () => { 184 | it('1976-11-18T15:23:30.123456789', () => { 185 | const epochNanoseconds = BigInt(Date.UTC(1976, 10, 18, 15, 23, 30, 123)) * BigInt(1e6) + BigInt(456789); 186 | const instant = Absolute.fromEpochNanoseconds(epochNanoseconds); 187 | equal(instant.getEpochNanoseconds(), epochNanoseconds); 188 | }); 189 | it('1963-02-13T09:36:29.123456789', () => { 190 | const epochNanoseconds = BigInt(Date.UTC(1963, 1, 13, 9, 36, 29, 123)) * BigInt(1e6) + BigInt(456789); 191 | const instant = Absolute.fromEpochNanoseconds(epochNanoseconds); 192 | equal(instant.getEpochNanoseconds(), epochNanoseconds); 193 | }); 194 | }); 195 | describe('Absolute.fromString() works', () => { 196 | it('1976-11-18T15:23Z', () => { 197 | equal(Absolute.fromString('1976-11-18T15:23Z').getEpochMilliseconds(), Date.UTC(1976, 10, 18, 15, 23)); 198 | }); 199 | it('1976-11-18T15:23:30Z', () => { 200 | equal(Absolute.fromString('1976-11-18T15:23:30Z').getEpochMilliseconds(), Date.UTC(1976, 10, 18, 15, 23, 30)); 201 | }); 202 | it('1976-11-18T15:23:30.123Z', () => { 203 | equal( 204 | Absolute.fromString('1976-11-18T15:23:30.123Z').getEpochMilliseconds(), 205 | Date.UTC(1976, 10, 18, 15, 23, 30, 123) 206 | ); 207 | }); 208 | it('1976-11-18T15:23:30.123456Z', () => { 209 | equal( 210 | Absolute.fromString('1976-11-18T15:23:30.123456Z').getEpochMicroseconds(), 211 | BigInt(Date.UTC(1976, 10, 18, 15, 23, 30, 123)) * BigInt(1e3) + BigInt(456) 212 | ); 213 | }); 214 | it('1976-11-18T15:23:30.123456789Z', () => { 215 | equal( 216 | Absolute.fromString('1976-11-18T15:23:30.123456789Z').getEpochNanoseconds(), 217 | BigInt(Date.UTC(1976, 10, 18, 15, 23, 30, 123)) * BigInt(1e6) + BigInt(456789) 218 | ); 219 | }); 220 | }); 221 | }); 222 | 223 | import { normalize } from 'path'; 224 | if (normalize(import.meta.url.slice(8)) === normalize(process.argv[1])) report(reporter); 225 | -------------------------------------------------------------------------------- /test/all.mjs: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env -S node --experimental-modules 2 | 3 | /* 4 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 5 | ** This code is governed by the license found in the LICENSE file. 6 | */ 7 | 8 | import Demitasse from '@pipobscure/demitasse'; 9 | import Pretty from '@pipobscure/demitasse-pretty'; 10 | 11 | import * as exports from './exports.mjs'; 12 | 13 | import * as absolute from './absolute.mjs'; 14 | import * as date from './date.mjs'; 15 | import * as time from './time.mjs'; 16 | import * as datetime from './datetime.mjs'; 17 | import * as difference from './difference.mjs'; 18 | 19 | Promise.resolve() 20 | .then(() => { 21 | return Demitasse.report(Pretty.reporter); 22 | }) 23 | .catch((e) => console.error(e)); 24 | -------------------------------------------------------------------------------- /test/date.mjs: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env -S node --experimental-modules 2 | 3 | /* 4 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 5 | ** This code is governed by the license found in the LICENSE file. 6 | */ 7 | 8 | import Demitasse from '@pipobscure/demitasse'; 9 | const { describe, it, report } = Demitasse; 10 | 11 | import Pretty from '@pipobscure/demitasse-pretty'; 12 | const { reporter } = Pretty; 13 | 14 | import Assert from 'assert'; 15 | const { ok: assert, equal } = Assert; 16 | 17 | import Temporal from '@std-proposal/temporal'; 18 | const { Date } = Temporal; 19 | 20 | describe('Date', () => { 21 | describe('Structure', () => { 22 | it('Date is a Function', () => { 23 | equal(typeof Date, 'function'); 24 | }); 25 | it('Date has a prototype', () => { 26 | assert(Date.prototype); 27 | equal(typeof Date.prototype, 'object'); 28 | }); 29 | describe('Date.prototype', () => { 30 | it('Date.prototype has year', () => { 31 | assert('year' in Date.prototype); 32 | }); 33 | it('Date.prototype has month', () => { 34 | assert('month' in Date.prototype); 35 | }); 36 | it('Date.prototype has day', () => { 37 | assert('day' in Date.prototype); 38 | }); 39 | it('Date.prototype has dayOfWeek', () => { 40 | assert('dayOfWeek' in Date.prototype); 41 | }); 42 | it('Date.prototype has dayOfYear', () => { 43 | assert('dayOfYear' in Date.prototype); 44 | }); 45 | it('Date.prototype has weekOfYear', () => { 46 | assert('weekOfYear' in Date.prototype); 47 | }); 48 | it('Date.prototype.with is a Function', () => { 49 | equal(typeof Date.prototype.with, 'function'); 50 | }); 51 | it('Date.prototype.plus is a Function', () => { 52 | equal(typeof Date.prototype.plus, 'function'); 53 | }); 54 | it('Date.prototype.minus is a Function', () => { 55 | equal(typeof Date.prototype.minus, 'function'); 56 | }); 57 | it('Date.prototype.difference is a Function', () => { 58 | equal(typeof Date.prototype.difference, 'function'); 59 | }); 60 | it('Date.prototype.withTime is a Function', () => { 61 | equal(typeof Date.prototype.withTime, 'function'); 62 | }); 63 | it('Date.prototype.getYearMonth is a Function', () => { 64 | equal(typeof Date.prototype.getYearMonth, 'function'); 65 | }); 66 | it('Date.prototype.getMonthDay is a Function', () => { 67 | equal(typeof Date.prototype.getMonthDay, 'function'); 68 | }); 69 | it('Date.prototype.toString is a Function', () => { 70 | equal(typeof Date.prototype.toString, 'function'); 71 | }); 72 | it('Date.prototype.toJSON is a Function', () => { 73 | equal(typeof Date.prototype.toJSON, 'function'); 74 | }); 75 | }); 76 | it('Date.fromString is a Function', () => { 77 | equal(typeof Date.fromString, 'function'); 78 | }); 79 | }); 80 | describe('Construction', () => { 81 | let date; 82 | it('date can be constructed', () => { 83 | date = new Date(1976, 11, 18); 84 | assert(date); 85 | equal(typeof date, 'object'); 86 | }); 87 | it('date.year is 1976', () => equal(date.year, 1976)); 88 | it('date.month is 11', () => equal(date.month, 11)); 89 | it('date.day is 18', () => equal(date.day, 18)); 90 | it('date.dayOfWeek is 4', () => equal(date.dayOfWeek, 4)); 91 | it('date.dayOfYear is 323', () => equal(date.dayOfYear, 323)); 92 | it('date.weekOfYear is 47', () => equal(date.weekOfYear, 47)); 93 | it('`${date}` is 1976-11-18', () => equal(`${date}`, '1976-11-18')); 94 | }); 95 | describe('.with manipulation', () => { 96 | const original = new Date(1976, 11, 18); 97 | it('date.with({ year: 2019 } works', () => { 98 | const date = original.with({ year: 2019 }); 99 | equal(`${date}`, '2019-11-18'); 100 | }); 101 | it('date.with({ month: 5 } works', () => { 102 | const date = original.with({ month: 5 }); 103 | equal(`${date}`, '1976-05-18'); 104 | }); 105 | it('date.with({ day: 17 } works', () => { 106 | const date = original.with({ day: 17 }); 107 | equal(`${date}`, '1976-11-17'); 108 | }); 109 | }); 110 | describe('date.difference() works', () => { 111 | const date = new Date(1976, 11, 18); 112 | it('date.difference({ year: 1976, month: 10, day: 5 })', () => { 113 | const duration = date.difference({ year: 1976, month: 10, day: 5 }); 114 | 115 | equal(duration.years, 0); 116 | equal(duration.months, 1); 117 | equal(duration.days, 13); 118 | equal(duration.hours, 0); 119 | equal(duration.minutes, 0); 120 | equal(duration.seconds, 0); 121 | equal(duration.milliseconds, 0); 122 | equal(duration.microseconds, 0); 123 | equal(duration.nanoseconds, 0); 124 | }); 125 | it('date.difference({ year: 2019, month: 11, day: 18 })', () => { 126 | const duration = date.difference({ year: 2019, month: 11, day: 18 }); 127 | equal(duration.years, 43); 128 | equal(duration.months, 0); 129 | equal(duration.days, 0); 130 | equal(duration.hours, 0); 131 | equal(duration.minutes, 0); 132 | equal(duration.seconds, 0); 133 | equal(duration.milliseconds, 0); 134 | equal(duration.microseconds, 0); 135 | equal(duration.nanoseconds, 0); 136 | }); 137 | }); 138 | describe('date.plus() works', () => { 139 | let date = new Date(1976, 11, 18); 140 | it('date.plus({ years: 43 })', () => { 141 | equal(`${date.plus({ years: 43 })}`, '2019-11-18'); 142 | }); 143 | it('date.plus({ months: 3 })', () => { 144 | equal(`${date.plus({ months: 3 })}`, '1977-02-18'); 145 | }); 146 | it('date.plus({ days: 20 })', () => { 147 | equal(`${date.plus({ days: 20 })}`, '1976-12-08'); 148 | }); 149 | it('new Date(2019, 1, 31).plus({ months: 1 })', () => { 150 | equal(`${new Date(2019, 1, 31).plus({ months: 1 })}`, '2019-02-28'); 151 | }); 152 | }); 153 | describe('date.minus() works', () => { 154 | const date = new Date(1976, 11, 18); 155 | it('date.minus({ years: 21 })', () => { 156 | equal(`${date.minus({ years: 21 })}`, '1955-11-18'); 157 | }); 158 | it('date.minus({ months: 13 })', () => { 159 | equal(`${date.minus({ months: 13 })}`, '1975-10-18'); 160 | }); 161 | it('date.minus({ days: 20 })', () => { 162 | equal(`${date.minus('P20D').plus('P20D')}`, `${date}`); 163 | equal(`${date.minus('P20D')}`, '1976-10-29'); 164 | }); 165 | }); 166 | describe('date.toString() works', () => { 167 | it('new Date(1976, 11, 18).toString()', () => { 168 | equal(new Date(1976, 11, 18).toString(), '1976-11-18'); 169 | }); 170 | it('new Date(1914, 2, 23).toString()', () => { 171 | equal(new Date(1914, 2, 23).toString(), '1914-02-23'); 172 | }); 173 | }); 174 | describe('Date.fromString() works', () => { 175 | it('Date.fromString("1976-11-18")', () => { 176 | const date = Date.fromString('1976-11-18'); 177 | equal(date.year, 1976); 178 | equal(date.month, 11); 179 | equal(date.day, 18); 180 | }); 181 | it('Date.fromString("2019-06-30")', () => { 182 | const date = Date.fromString('2019-06-30'); 183 | equal(date.year, 2019); 184 | equal(date.month, 6); 185 | equal(date.day, 30); 186 | }); 187 | it('Date.fromString("+000050-06-30")', () => { 188 | const date = Date.fromString('+000050-06-30'); 189 | equal(date.year, 50); 190 | equal(date.month, 6); 191 | equal(date.day, 30); 192 | }); 193 | it('Date.fromString("+010583-06-30")', () => { 194 | const date = Date.fromString('+010583-06-30'); 195 | equal(date.year, 10583); 196 | equal(date.month, 6); 197 | equal(date.day, 30); 198 | }); 199 | it('Date.fromString("-010583-06-30")', () => { 200 | const date = Date.fromString('-010583-06-30'); 201 | equal(date.year, -10583); 202 | equal(date.month, 6); 203 | equal(date.day, 30); 204 | }); 205 | it('Date.fromString("-000333-06-30")', () => { 206 | const date = Date.fromString('-000333-06-30'); 207 | equal(date.year, -333); 208 | equal(date.month, 6); 209 | equal(date.day, 30); 210 | }); 211 | }); 212 | }); 213 | 214 | import { normalize } from 'path'; 215 | if (normalize(import.meta.url.slice(8)) === normalize(process.argv[1])) report(reporter); 216 | -------------------------------------------------------------------------------- /test/datetime.mjs: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env -S node --experimental-modules 2 | 3 | /* 4 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 5 | ** This code is governed by the license found in the LICENSE file. 6 | */ 7 | 8 | import Demitasse from '@pipobscure/demitasse'; 9 | const { describe, it, report } = Demitasse; 10 | 11 | import Pretty from '@pipobscure/demitasse-pretty'; 12 | const { reporter } = Pretty; 13 | 14 | import Assert from 'assert'; 15 | const { ok: assert, equal } = Assert; 16 | 17 | import Temporal from '@std-proposal/temporal'; 18 | const { DateTime } = Temporal; 19 | 20 | describe('DateTime', () => { 21 | describe('Structure', () => { 22 | it('DateTime is a Function', () => { 23 | equal(typeof DateTime, 'function'); 24 | }); 25 | it('DateTime has a prototype', () => { 26 | assert(DateTime.prototype); 27 | equal(typeof DateTime.prototype, 'object'); 28 | }); 29 | describe('DateTime.prototype', () => { 30 | it('DateTime.prototype has year', () => { 31 | assert('year' in DateTime.prototype); 32 | }); 33 | it('DateTime.prototype has month', () => { 34 | assert('month' in DateTime.prototype); 35 | }); 36 | it('DateTime.prototype has day', () => { 37 | assert('day' in DateTime.prototype); 38 | }); 39 | it('DateTime.prototype has hour', () => { 40 | assert('hour' in DateTime.prototype); 41 | }); 42 | it('DateTime.prototype has minute', () => { 43 | assert('minute' in DateTime.prototype); 44 | }); 45 | it('DateTime.prototype has second', () => { 46 | assert('second' in DateTime.prototype); 47 | }); 48 | it('DateTime.prototype has millisecond', () => { 49 | assert('millisecond' in DateTime.prototype); 50 | }); 51 | it('DateTime.prototype has microsecond', () => { 52 | assert('microsecond' in DateTime.prototype); 53 | }); 54 | it('DateTime.prototype has nanosecond', () => { 55 | assert('nanosecond' in DateTime.prototype); 56 | }); 57 | it('DateTime.prototype has dayOfWeek', () => { 58 | assert('dayOfWeek' in DateTime.prototype); 59 | }); 60 | it('DateTime.prototype has dayOfYear', () => { 61 | assert('dayOfYear' in DateTime.prototype); 62 | }); 63 | it('DateTime.prototype has weekOfYear', () => { 64 | assert('weekOfYear' in DateTime.prototype); 65 | }); 66 | it('DateTime.prototype.with is a Function', () => { 67 | equal(typeof DateTime.prototype.with, 'function'); 68 | }); 69 | it('DateTime.prototype.plus is a Function', () => { 70 | equal(typeof DateTime.prototype.plus, 'function'); 71 | }); 72 | it('DateTime.prototype.minus is a Function', () => { 73 | equal(typeof DateTime.prototype.minus, 'function'); 74 | }); 75 | it('DateTime.prototype.difference is a Function', () => { 76 | equal(typeof DateTime.prototype.difference, 'function'); 77 | }); 78 | it('DateTime.prototype.withZone is a Function', () => { 79 | equal(typeof DateTime.prototype.withZone, 'function'); 80 | }); 81 | it('DateTime.prototype.getDate is a Function', () => { 82 | equal(typeof DateTime.prototype.getDate, 'function'); 83 | }); 84 | it('DateTime.prototype.getTime is a Function', () => { 85 | equal(typeof DateTime.prototype.getTime, 'function'); 86 | }); 87 | it('DateTime.prototype.toString is a Function', () => { 88 | equal(typeof DateTime.prototype.toString, 'function'); 89 | }); 90 | it('DateTime.prototype.toJSON is a Function', () => { 91 | equal(typeof DateTime.prototype.toJSON, 'function'); 92 | }); 93 | }); 94 | it('DateTime.fromString is a Function', () => { 95 | equal(typeof DateTime.fromString, 'function'); 96 | }); 97 | }); 98 | describe('Construction', () => { 99 | describe('new DateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789)', () => { 100 | let datetime; 101 | it('datetime can be constructed', () => { 102 | datetime = new DateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789); 103 | assert(datetime); 104 | equal(typeof datetime, 'object'); 105 | }); 106 | it('datetime.year is 1976', () => equal(datetime.year, 1976)); 107 | it('datetime.month is 11', () => equal(datetime.month, 11)); 108 | it('datetime.day is 18', () => equal(datetime.day, 18)); 109 | it('datetime.hour is 15', () => equal(datetime.hour, 15)); 110 | it('datetime.minute is 23', () => equal(datetime.minute, 23)); 111 | it('datetime.second is 30', () => equal(datetime.second, 30)); 112 | it('datetime.millisecond is 123', () => equal(datetime.millisecond, 123)); 113 | it('datetime.microsecond is 456', () => equal(datetime.microsecond, 456)); 114 | it('datetime.nanosecond is 789', () => equal(datetime.nanosecond, 789)); 115 | it('datetime.dayOfWeek is 4', () => equal(datetime.dayOfWeek, 4)); 116 | it('datetime.dayOfYear is 323', () => equal(datetime.dayOfYear, 323)); 117 | it('datetime.weekOfYear is 47', () => equal(datetime.weekOfYear, 47)); 118 | it('`${datetime}` is 1976-11-18T15:23:30.123456789', () => equal(`${datetime}`, '1976-11-18T15:23:30.123456789')); 119 | }); 120 | describe('new DateTime(1976, 11, 18, 15, 23, 30, 123, 456)', () => { 121 | let datetime; 122 | it('datetime can be constructed', () => { 123 | datetime = new DateTime(1976, 11, 18, 15, 23, 30, 123, 456); 124 | assert(datetime); 125 | equal(typeof datetime, 'object'); 126 | }); 127 | it('datetime.year is 1976', () => equal(datetime.year, 1976)); 128 | it('datetime.month is 11', () => equal(datetime.month, 11)); 129 | it('datetime.day is 18', () => equal(datetime.day, 18)); 130 | it('datetime.hour is 15', () => equal(datetime.hour, 15)); 131 | it('datetime.minute is 23', () => equal(datetime.minute, 23)); 132 | it('datetime.second is 30', () => equal(datetime.second, 30)); 133 | it('datetime.millisecond is 123', () => equal(datetime.millisecond, 123)); 134 | it('datetime.microsecond is 456', () => equal(datetime.microsecond, 456)); 135 | it('datetime.nanosecond is 0', () => equal(datetime.nanosecond, 0)); 136 | it('datetime.dayOfWeek is 4', () => equal(datetime.dayOfWeek, 4)); 137 | it('datetime.dayOfYear is 323', () => equal(datetime.dayOfYear, 323)); 138 | it('datetime.weekOfYear is 47', () => equal(datetime.weekOfYear, 47)); 139 | it('`${datetime}` is 1976-11-18T15:23:30.123456', () => equal(`${datetime}`, '1976-11-18T15:23:30.123456')); 140 | }); 141 | describe('new DateTime(1976, 11, 18, 15, 23, 30, 123)', () => { 142 | let datetime; 143 | it('datetime can be constructed', () => { 144 | datetime = new DateTime(1976, 11, 18, 15, 23, 30, 123); 145 | assert(datetime); 146 | equal(typeof datetime, 'object'); 147 | }); 148 | it('datetime.year is 1976', () => equal(datetime.year, 1976)); 149 | it('datetime.month is 11', () => equal(datetime.month, 11)); 150 | it('datetime.day is 18', () => equal(datetime.day, 18)); 151 | it('datetime.hour is 15', () => equal(datetime.hour, 15)); 152 | it('datetime.minute is 23', () => equal(datetime.minute, 23)); 153 | it('datetime.second is 30', () => equal(datetime.second, 30)); 154 | it('datetime.millisecond is 123', () => equal(datetime.millisecond, 123)); 155 | it('datetime.microsecond is 0', () => equal(datetime.microsecond, 0)); 156 | it('datetime.nanosecond is 0', () => equal(datetime.nanosecond, 0)); 157 | it('datetime.dayOfWeek is 4', () => equal(datetime.dayOfWeek, 4)); 158 | it('datetime.dayOfYear is 323', () => equal(datetime.dayOfYear, 323)); 159 | it('datetime.weekOfYear is 47', () => equal(datetime.weekOfYear, 47)); 160 | it('`${datetime}` is 1976-11-18T15:23:30.123', () => equal(`${datetime}`, '1976-11-18T15:23:30.123')); 161 | }); 162 | describe('new DateTime(1976, 11, 18, 15, 23, 30)', () => { 163 | let datetime; 164 | it('datetime can be constructed', () => { 165 | datetime = new DateTime(1976, 11, 18, 15, 23, 30); 166 | assert(datetime); 167 | equal(typeof datetime, 'object'); 168 | }); 169 | it('datetime.year is 1976', () => equal(datetime.year, 1976)); 170 | it('datetime.month is 11', () => equal(datetime.month, 11)); 171 | it('datetime.day is 18', () => equal(datetime.day, 18)); 172 | it('datetime.hour is 15', () => equal(datetime.hour, 15)); 173 | it('datetime.minute is 23', () => equal(datetime.minute, 23)); 174 | it('datetime.second is 30', () => equal(datetime.second, 30)); 175 | it('datetime.millisecond is 0', () => equal(datetime.millisecond, 0)); 176 | it('datetime.microsecond is 0', () => equal(datetime.microsecond, 0)); 177 | it('datetime.nanosecond is 0', () => equal(datetime.nanosecond, 0)); 178 | it('datetime.dayOfWeek is 4', () => equal(datetime.dayOfWeek, 4)); 179 | it('datetime.dayOfYear is 323', () => equal(datetime.dayOfYear, 323)); 180 | it('datetime.weekOfYear is 47', () => equal(datetime.weekOfYear, 47)); 181 | it('`${datetime}` is 1976-11-18T15:23:30', () => equal(`${datetime}`, '1976-11-18T15:23:30')); 182 | }); 183 | describe('new DateTime(1976, 11, 18, 15, 23)', () => { 184 | let datetime; 185 | it('datetime can be constructed', () => { 186 | datetime = new DateTime(1976, 11, 18, 15, 23); 187 | assert(datetime); 188 | equal(typeof datetime, 'object'); 189 | }); 190 | it('datetime.year is 1976', () => equal(datetime.year, 1976)); 191 | it('datetime.month is 11', () => equal(datetime.month, 11)); 192 | it('datetime.day is 18', () => equal(datetime.day, 18)); 193 | it('datetime.hour is 15', () => equal(datetime.hour, 15)); 194 | it('datetime.minute is 23', () => equal(datetime.minute, 23)); 195 | it('datetime.second is 0', () => equal(datetime.second, 0)); 196 | it('datetime.millisecond is 0', () => equal(datetime.millisecond, 0)); 197 | it('datetime.microsecond is 0', () => equal(datetime.microsecond, 0)); 198 | it('datetime.nanosecond is 0', () => equal(datetime.nanosecond, 0)); 199 | it('datetime.dayOfWeek is 4', () => equal(datetime.dayOfWeek, 4)); 200 | it('datetime.dayOfYear is 323', () => equal(datetime.dayOfYear, 323)); 201 | it('datetime.weekOfYear is 47', () => equal(datetime.weekOfYear, 47)); 202 | it('`${datetime}` is 1976-11-18T15:23:00', () => equal(`${datetime}`, '1976-11-18T15:23:00')); 203 | }); 204 | }); 205 | describe('.with manipulation', () => { 206 | const datetime = new DateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789); 207 | it('datetime.with({ year: 2019 } works', () => { 208 | equal(`${datetime.with({ year: 2019 })}`, '2019-11-18T15:23:30.123456789'); 209 | }); 210 | it('datetime.with({ month: 5 } works', () => { 211 | equal(`${datetime.with({ month: 5 })}`, '1976-05-18T15:23:30.123456789'); 212 | }); 213 | it('datetime.with({ day: 5 } works', () => { 214 | equal(`${datetime.with({ day: 5 })}`, '1976-11-05T15:23:30.123456789'); 215 | }); 216 | it('datetime.with({ hour: 5 } works', () => { 217 | equal(`${datetime.with({ hour: 5 })}`, '1976-11-18T05:23:30.123456789'); 218 | }); 219 | it('datetime.with({ minute: 5 } works', () => { 220 | equal(`${datetime.with({ minute: 5 })}`, '1976-11-18T15:05:30.123456789'); 221 | }); 222 | it('datetime.with({ second: 5 } works', () => { 223 | equal(`${datetime.with({ second: 5 })}`, '1976-11-18T15:23:05.123456789'); 224 | }); 225 | it('datetime.with({ millisecond: 5 } works', () => { 226 | equal(`${datetime.with({ millisecond: 5 })}`, '1976-11-18T15:23:30.005456789'); 227 | }); 228 | it('datetime.with({ microsecond: 5 } works', () => { 229 | equal(`${datetime.with({ microsecond: 5 })}`, '1976-11-18T15:23:30.123005789'); 230 | }); 231 | it('datetime.with({ nanosecond: 5 } works', () => { 232 | equal(`${datetime.with({ nanosecond: 5 })}`, '1976-11-18T15:23:30.123456005'); 233 | }); 234 | it('datetime.with({ month: 5, second: 15 } works', () => { 235 | equal(`${datetime.with({ month: 5, second: 15 })}`, '1976-05-18T15:23:15.123456789'); 236 | }); 237 | }); 238 | describe('datetime.difference() works', () => { 239 | const datetime = new DateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789); 240 | it('datetime.difference({ year: 1976, month: 10, day: 5, hour: 15, minute: 23, second: 30, millisecond: 123, microsecond: 456, nanosecond: 789 })', () => { 241 | const duration = datetime.difference({ 242 | year: 1976, 243 | month: 10, 244 | day: 5, 245 | hour: 15, 246 | minute: 23, 247 | second: 30, 248 | millisecond: 123, 249 | microsecond: 456, 250 | nanosecond: 789 251 | }); 252 | equal(duration.years, 0, 'years', `${duration} years`); 253 | equal(duration.months, 1, 'months', `${duration} months`); 254 | equal(duration.days, 13, 'days', `${duration} days`); 255 | equal(duration.hours, 0, `${duration} hours`); 256 | equal(duration.minutes, 0, `${duration} minutes`); 257 | equal(duration.seconds, 0, `${duration} seconds`); 258 | equal(duration.milliseconds, 0, `${duration} milliseconds`); 259 | equal(duration.microseconds, 0, `${duration} microseconds`); 260 | equal(duration.nanoseconds, 0, `${duration} nanoseconds`); 261 | }); 262 | it('datetime.difference({ year: 1976, month: 10, day: 5, hour: 15, minute: 23, second: 30 })', () => { 263 | const duration = datetime.difference({ year: 1976, month: 10, day: 5, hour: 15, minute: 23, second: 30 }); 264 | equal(duration.years, 0, 'years', `${duration} years`); 265 | equal(duration.months, 1, 'months', `${duration} months`); 266 | equal(duration.days, 13, 'days', `${duration} days`); 267 | equal(duration.hours, 0, `${duration} hours`); 268 | equal(duration.minutes, 0, `${duration} minutes`); 269 | equal(duration.seconds, 0, `${duration} seconds`); 270 | equal(duration.milliseconds, 123, `${duration} milliseconds`); 271 | equal(duration.microseconds, 456, `${duration} microseconds`); 272 | equal(duration.nanoseconds, 789, `${duration} nanoseconds`); 273 | }); 274 | describe('datetime.plus() works', () => { 275 | const datetime = new DateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789); 276 | it('datetime.plus({ years: 43 })', () => { 277 | equal(`${datetime.plus({ years: 43 })}`, '2019-11-18T15:23:30.123456789'); 278 | }); 279 | it('datetime.plus({ months: 3 })', () => { 280 | equal(`${datetime.plus({ months: 3 })}`, '1977-02-18T15:23:30.123456789'); 281 | }); 282 | it('datetime.plus({ days: 20 })', () => { 283 | equal(`${datetime.plus({ days: 20 })}`, '1976-12-08T15:23:30.123456789'); 284 | }); 285 | it('datetime.plus({ hours: 20 })', () => { 286 | equal(`${datetime.plus({ hours: 20 })}`, '1976-11-19T11:23:30.123456789'); 287 | }); 288 | it('datetime.plus({ minutes: 40 })', () => { 289 | equal(`${datetime.plus({ minutes: 40 })}`, '1976-11-18T16:03:30.123456789'); 290 | }); 291 | it('datetime.plus({ seconds: 40 })', () => { 292 | equal(`${datetime.plus({ seconds: 40 })}`, '1976-11-18T15:24:10.123456789'); 293 | }); 294 | }); 295 | describe('datetime.minus() works', () => { 296 | const datetime = new DateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789); 297 | it('datetime.minus({ years: 21 })', () => { 298 | equal(`${datetime.minus({ years: 21 })}`, '1955-11-18T15:23:30.123456789'); 299 | }); 300 | it('datetime.minus({ months: 13 })', () => { 301 | equal(`${datetime.minus({ months: 13 })}`, '1975-10-18T15:23:30.123456789'); 302 | }); 303 | it('datetime.minus({ days: 20 })', () => { 304 | equal(`${datetime.minus({ days: 20 })}`, '1976-10-29T15:23:30.123456789'); 305 | }); 306 | it('datetime.minus({ hours: 20 })', () => { 307 | equal(`${datetime.minus({ hours: 20 })}`, '1976-11-17T19:23:30.123456789'); 308 | }); 309 | it('datetime.minus({ minutes: 40 })', () => { 310 | equal(`${datetime.minus({ minutes: 40 })}`, '1976-11-18T14:43:30.123456789'); 311 | }); 312 | it('datetime.minus({ seconds: 40 })', () => { 313 | equal(`${datetime.minus({ seconds: 40 })}`, '1976-11-18T15:22:50.123456789'); 314 | }); 315 | }); 316 | describe('date.toString() works', () => { 317 | it('new DateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789).toString()', () => { 318 | equal(new DateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789).toString(), '1976-11-18T15:23:30.123456789'); 319 | }); 320 | it('new DateTime(1976, 11, 18, 15, 23, 30, 123, 456).toString()', () => { 321 | equal(new DateTime(1976, 11, 18, 15, 23, 30, 123, 456).toString(), '1976-11-18T15:23:30.123456'); 322 | }); 323 | it('new DateTime(1976, 11, 18, 15, 23, 30, 123).toString()', () => { 324 | equal(new DateTime(1976, 11, 18, 15, 23, 30, 123).toString(), '1976-11-18T15:23:30.123'); 325 | }); 326 | it('new DateTime(1976, 11, 18, 15, 23, 30).toString()', () => { 327 | equal(new DateTime(1976, 11, 18, 15, 23, 30).toString(), '1976-11-18T15:23:30'); 328 | }); 329 | it('new DateTime(1976, 11, 18, 15, 23).toString()', () => { 330 | equal(new DateTime(1976, 11, 18, 15, 23).toString(), '1976-11-18T15:23:00'); 331 | }); 332 | }); 333 | describe('DateTime.fromString() works', () => { 334 | it('DateTime.fromString("1976-11-18T15:23:30.123456789")', () => { 335 | equal(`${DateTime.fromString('1976-11-18T15:23:30.123456789')}`, '1976-11-18T15:23:30.123456789'); 336 | }); 337 | it('DateTime.fromString("1976-11-18T15:23:30.123456")', () => { 338 | equal(`${DateTime.fromString('1976-11-18T15:23:30.123456')}`, '1976-11-18T15:23:30.123456'); 339 | }); 340 | it('DateTime.fromString("1976-11-18T15:23:30.123")', () => { 341 | equal(`${DateTime.fromString('1976-11-18T15:23:30.123')}`, '1976-11-18T15:23:30.123'); 342 | }); 343 | it('DateTime.fromString("1976-11-18T15:23:30")', () => { 344 | equal(`${DateTime.fromString('1976-11-18T15:23:30')}`, '1976-11-18T15:23:30'); 345 | }); 346 | it('DateTime.fromString("1976-11-18T15:23")', () => { 347 | equal(`${DateTime.fromString('1976-11-18T15:23')}`, '1976-11-18T15:23:00'); 348 | }); 349 | }); 350 | }); 351 | }); 352 | 353 | import { normalize } from 'path'; 354 | if (normalize(import.meta.url.slice(8)) === normalize(process.argv[1])) report(reporter); 355 | -------------------------------------------------------------------------------- /test/difference.mjs: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env -S node --experimental-modules 2 | 3 | /* 4 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 5 | ** This code is governed by the license found in the LICENSE file. 6 | */ 7 | 8 | import Demitasse from '@pipobscure/demitasse'; 9 | const { describe, it, report } = Demitasse; 10 | 11 | import Pretty from '@pipobscure/demitasse-pretty'; 12 | const { reporter } = Pretty; 13 | 14 | import Assert from 'assert'; 15 | const { ok: assert, equal } = Assert; 16 | 17 | import Temporal from '@std-proposal/temporal'; 18 | 19 | describe('Date.difference', ()=>{ 20 | const today = Temporal.Local.date(); 21 | const other = new Temporal.Date(1976, 11, 18); 22 | const difference = today.difference(other); 23 | it(`(${today}).minus('${difference}') == ${other}`, ()=>equal(`${today.minus(difference)}`, `${other}`)); 24 | it(`(${other}).plus('${difference}') == ${today}`, ()=>equal(`${other.plus(difference)}`, `${today}`)); 25 | 26 | it(`(${other}).minus('${difference}').plus('${difference}') == ${other}`, ()=>equal(`${other.minus(difference).plus(difference)}`, `${other}`)); 27 | it(`(${other}).plus('${difference}').minus('${difference}') == ${other}`, ()=>equal(`${other.plus(difference).minus(difference)}`, `${other}`)); 28 | 29 | it(`(${today}).minus('${difference}').plus('${difference}') == ${today}`, ()=>equal(`${today.minus(difference).plus(difference)}`, `${today}`)); 30 | it(`(${today}).plus('${difference}').minus('${difference}') == ${today}`, ()=>equal(`${today.plus(difference).minus(difference)}`, `${today}`)); 31 | 32 | it(`(${other}).minus('${'P43D'}').plus('${'P43D'}') == ${other}`, ()=>equal(`${other.minus('P43D').plus('P43D')}`, `${other}`)); 33 | it(`(${other}).plus('${'P43D'}').minus('${'P43D'}') == ${other}`, ()=>equal(`${other.plus('P43D').minus('P43D')}`, `${other}`)); 34 | 35 | it(`(${today}).minus('${'P43D'}').plus('${'P43D'}') == ${today}`, ()=>equal(`${today.minus('P43D').plus('P43D')}`, `${today}`)); 36 | it(`(${today}).plus('${'P43D'}').minus('${'P43D'}') == ${today}`, ()=>equal(`${today.plus('P43D').minus('P43D')}`, `${today}`)); 37 | }); 38 | describe('Time.difference', ()=>{ 39 | const [ one, two ] = [ Temporal.Local.time(), new Temporal.Time(15, 23, 30, 123, 456, 789) ].sort(Temporal.Time.compare); 40 | const difference = one.difference(two); 41 | it(`(${two}).minus('${difference}') == ${one}`, ()=>equal(`${two.minus(difference)}`, `${one}`)); 42 | it(`(${one}).plus('${difference}') == ${two}`, ()=>equal(`${one.plus(difference)}`, `${two}`)); 43 | 44 | it(`(${two}).minus('${difference}').plus('${difference}') == ${two}`, ()=>equal(`${two.minus(difference).plus(difference)}`, `${two}`)); 45 | it(`(${two}).plus('${difference}').minus('${difference}') == ${two}`, ()=>equal(`${two.plus(difference).minus(difference)}`, `${two}`)); 46 | it(`(${two}).minus('${'PT96M'}').plus('${'PT96M'}') == ${two}`, ()=>equal(`${two.minus('PT96M').plus('PT96M')}`, `${two}`)); 47 | it(`(${two}).plus('${'PT96M'}').minus('${'PT96M'}') == ${two}`, ()=>equal(`${two.plus('PT96M').minus('PT96M')}`, `${two}`)); 48 | }); 49 | describe('DateTime.difference', ()=>{ 50 | const [ one, two ] = [ Temporal.Local.dateTime(), new Temporal.DateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789) ].sort(Temporal.DateTime.compare); 51 | const difference = one.difference(two); 52 | it(`(${two}).minus('${difference}') == ${one}`, ()=>equal(`${two.minus(difference)}`, `${one}`)); 53 | it(`(${one}).plus('${difference}') == ${two}`, ()=>equal(`${one.plus(difference)}`, `${two}`)); 54 | it(`(${two}).minus('${difference}').plus('${difference}') == ${two}`, ()=>equal(`${two.minus(difference).plus(difference)}`, `${two}`)); 55 | it(`(${two}).plus('${difference}').minus('${difference}') == ${two}`, ()=>equal(`${two.plus(difference).minus(difference)}`, `${two}`)); 56 | it(`(${two}).minus('${'P42DT96M'}').plus('${'P42DT96M'}') == ${two}`, ()=>equal(`${two.minus('P42DT96M').plus('P42DT96M')}`, `${two}`)); 57 | it(`(${two}).plus('${'P42DT96M'}').minus('${'P42DT96M'}') == ${two}`, ()=>equal(`${two.plus('P42DT96M').minus('P42DT96M')}`, `${two}`)); 58 | }); 59 | describe('Absolute.difference', ()=>{ 60 | const one = Temporal.Absolute.fromString('1976-11-18T14:23:30.123456789Z'); 61 | const two = Temporal.Local.absolute('UTC'); // Temporal.('2019-09-11T14:45:16.133694800Z'); 62 | const difference = one.difference(two); 63 | it(`(${two}).minus('${difference}') == ${one}`, ()=>equal(`${two.minus(difference)}`, `${one}`)); 64 | it(`(${one}).plus('${difference}') == ${two}`, ()=>equal(`${one.plus(difference)}`, `${two}`)); 65 | it(`(${two}).minus('${difference}').plus('${difference}') == ${two}`, ()=>equal(`${two.minus(difference).plus(difference)}`, `${two}`)); 66 | it(`(${two}).plus('${difference}').minus('${difference}') == ${two}`, ()=>equal(`${two.plus(difference).minus(difference)}`, `${two}`)); 67 | it(`(${two}).minus('${'P45DT89M'}').plus('${'P45DT89M'}') == ${two}`, ()=>equal(`${two.minus('P45DT89M').plus('P45DT89M')}`, `${two}`)); 68 | it(`(${two}).plus('${'P45DT89M'}').minus('${'P45DT89M'}') == ${two}`, ()=>equal(`${two.plus('P45DT89M').minus('P45DT89M')}`, `${two}`)); 69 | }); 70 | 71 | import { normalize } from 'path'; 72 | if (normalize(import.meta.url.slice(8)) === normalize(process.argv[1])) report(reporter); 73 | -------------------------------------------------------------------------------- /test/exports.mjs: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env -S node --experimental-modules 2 | 3 | /* 4 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 5 | ** This code is governed by the license found in the LICENSE file. 6 | */ 7 | 8 | import Demitasse from '@pipobscure/demitasse'; 9 | const { describe, it, report } = Demitasse; 10 | 11 | import Pretty from '@pipobscure/demitasse-pretty'; 12 | const { reporter } = Pretty; 13 | 14 | import Assert from 'assert'; 15 | const { ok: assert, equal } = Assert; 16 | 17 | import Temporal from '@std-proposal/temporal'; 18 | 19 | describe('Exports', () => { 20 | const named = Object.keys(Temporal); 21 | it('should be 12 things', () => { 22 | equal(named.length, 11); 23 | }); 24 | it('should contain `Absolute`', () => { 25 | assert(named.includes('Absolute')); 26 | }); 27 | it('should contain `TimeZone`', () => { 28 | assert(named.includes('TimeZone')); 29 | }); 30 | it('should contain `Date`', () => { 31 | assert(named.includes('Date')); 32 | }); 33 | it('should contain `Time`', () => { 34 | assert(named.includes('Time')); 35 | }); 36 | it('should contain `DateTime`', () => { 37 | assert(named.includes('DateTime')); 38 | }); 39 | it('should contain `YearMonth`', () => { 40 | assert(named.includes('YearMonth')); 41 | }); 42 | it('should contain `MonthDay`', () => { 43 | assert(named.includes('MonthDay')); 44 | }); 45 | it('should contain `Duration`', () => { 46 | assert(named.includes('Duration')); 47 | }); 48 | it('should contain `EARLIER`', () => { 49 | assert(named.includes('EARLIER')); 50 | }); 51 | it('should contain `LATER`', () => { 52 | assert(named.includes('LATER')); 53 | }); 54 | it('should contain `Local`', () => { 55 | assert(named.includes('Local')); 56 | }); 57 | describe('Local', () => { 58 | const expected = ['absolute', 'timeZone', 'dateTime', 'date', 'time', 'dayMonth', 'monthYear']; 59 | const named = Object.keys(Temporal.Local); 60 | it(`should be ${expected.length} things`, () => { 61 | equal(named.length, expected.length); 62 | }); 63 | expected.forEach((prop) => { 64 | it(`should contain '${prop}'`, () => { 65 | assert(named.includes(prop)); 66 | assert('function' === typeof Temporal.Local[prop], `Temporal.Local.${prop} is a function`); 67 | }); 68 | }); 69 | }); 70 | }); 71 | 72 | import { normalize } from 'path'; 73 | if (normalize(import.meta.url.slice(8)) === normalize(process.argv[1])) report(reporter); 74 | -------------------------------------------------------------------------------- /test/global.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import('@std-proposal/temporal').then( 7 | (mod) => { 8 | global.Temporal = mod.default; 9 | }, 10 | (err) => { 11 | console.error(err); 12 | } 13 | ); 14 | -------------------------------------------------------------------------------- /test/resolve.compiled.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import PKG from '../package.json'; 7 | export function resolve(specifier, parent, defaultResolve) { 8 | if (specifier === PKG.name) { 9 | specifier = new URL('../index.js', import.meta.url).toString(); 10 | } 11 | return defaultResolve(specifier, parent); 12 | } 13 | -------------------------------------------------------------------------------- /test/resolve.source.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 3 | ** This code is governed by the license found in the LICENSE file. 4 | */ 5 | 6 | import PKG from '../package.json'; 7 | export function resolve(specifier, parent, defaultResolve) { 8 | if (specifier === PKG.name) { 9 | specifier = new URL('../lib/temporal.mjs', import.meta.url).toString(); 10 | } 11 | return defaultResolve(specifier, parent); 12 | } 13 | -------------------------------------------------------------------------------- /test/time.mjs: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env -S node --experimental-modules 2 | 3 | /* 4 | ** Copyright (C) 2018-2019 Bloomberg LP. All rights reserved. 5 | ** This code is governed by the license found in the LICENSE file. 6 | */ 7 | 8 | import Demitasse from '@pipobscure/demitasse'; 9 | const { describe, it, report } = Demitasse; 10 | 11 | import Pretty from '@pipobscure/demitasse-pretty'; 12 | const { reporter } = Pretty; 13 | 14 | import Assert from 'assert'; 15 | const { ok: assert, equal } = Assert; 16 | 17 | import Temporal from '@std-proposal/temporal'; 18 | const { Time } = Temporal; 19 | 20 | describe('Time', () => { 21 | describe('Structure', () => { 22 | it('Time is a Function', () => { 23 | equal(typeof Time, 'function'); 24 | }); 25 | it('Time has a prototype', () => { 26 | assert(Time.prototype); 27 | equal(typeof Time.prototype, 'object'); 28 | }); 29 | describe('Time.prototype', () => { 30 | it('Time.prototype has hour', () => { 31 | assert('hour' in Time.prototype); 32 | }); 33 | it('Time.prototype has minute', () => { 34 | assert('minute' in Time.prototype); 35 | }); 36 | it('Time.prototype has second', () => { 37 | assert('second' in Time.prototype); 38 | }); 39 | it('Time.prototype has millisecond', () => { 40 | assert('millisecond' in Time.prototype); 41 | }); 42 | it('Time.prototype has microsecond', () => { 43 | assert('microsecond' in Time.prototype); 44 | }); 45 | it('Time.prototype has nanosecond', () => { 46 | assert('nanosecond' in Time.prototype); 47 | }); 48 | it('Time.prototype.with is a Function', () => { 49 | equal(typeof Time.prototype.with, 'function'); 50 | }); 51 | it('Time.prototype.plus is a Function', () => { 52 | equal(typeof Time.prototype.plus, 'function'); 53 | }); 54 | it('Time.prototype.minus is a Function', () => { 55 | equal(typeof Time.prototype.minus, 'function'); 56 | }); 57 | it('Time.prototype.difference is a Function', () => { 58 | equal(typeof Time.prototype.difference, 'function'); 59 | }); 60 | it('Time.prototype.withDate is a Function', () => { 61 | equal(typeof Time.prototype.withDate, 'function'); 62 | }); 63 | it('Time.prototype.toString is a Function', () => { 64 | equal(typeof Time.prototype.toString, 'function'); 65 | }); 66 | it('Time.prototype.toJSON is a Function', () => { 67 | equal(typeof Time.prototype.toJSON, 'function'); 68 | }); 69 | }); 70 | it('Time.fromString is a Function', () => { 71 | equal(typeof Time.fromString, 'function'); 72 | }); 73 | }); 74 | describe('Construction', () => { 75 | describe('complete', () => { 76 | let time; 77 | it('time can be constructed', () => { 78 | time = new Time(15, 23, 30, 123, 456, 789); 79 | assert(time); 80 | equal(typeof time, 'object'); 81 | }); 82 | it('time.hour is 15', () => equal(time.hour, 15)); 83 | it('time.minute is 23', () => equal(time.minute, 23)); 84 | it('time.second is 30', () => equal(time.second, 30)); 85 | it('time.millisecond is 123', () => equal(time.millisecond, 123)); 86 | it('time.microsecond is 456', () => equal(time.microsecond, 456)); 87 | it('time.nanosecond is 789', () => equal(time.nanosecond, 789)); 88 | it('`${time}` is 15:23:30.123456789', () => equal(`${time}`, '15:23:30.123456789')); 89 | }); 90 | describe('missing nanosecond', () => { 91 | let time; 92 | it('time can be constructed', () => { 93 | time = new Time(15, 23, 30, 123, 456); 94 | assert(time); 95 | equal(typeof time, 'object'); 96 | }); 97 | it('time.hour is 15', () => equal(time.hour, 15)); 98 | it('time.minute is 23', () => equal(time.minute, 23)); 99 | it('time.second is 30', () => equal(time.second, 30)); 100 | it('time.millisecond is 123', () => equal(time.millisecond, 123)); 101 | it('time.microsecond is 456', () => equal(time.microsecond, 456)); 102 | it('time.nanosecond is 0', () => equal(time.nanosecond, 0)); 103 | it('`${time}` is 15:23:30.123456', () => equal(`${time}`, '15:23:30.123456')); 104 | }); 105 | describe('missing microsecond', () => { 106 | let time; 107 | it('time can be constructed', () => { 108 | time = new Time(15, 23, 30, 123); 109 | assert(time); 110 | equal(typeof time, 'object'); 111 | }); 112 | it('time.hour is 15', () => equal(time.hour, 15)); 113 | it('time.minute is 23', () => equal(time.minute, 23)); 114 | it('time.second is 30', () => equal(time.second, 30)); 115 | it('time.millisecond is 123', () => equal(time.millisecond, 123)); 116 | it('time.microsecond is 0', () => equal(time.microsecond, 0)); 117 | it('time.nanosecond is 0', () => equal(time.nanosecond, 0)); 118 | it('`${time}` is 15:23:30.123', () => equal(`${time}`, '15:23:30.123')); 119 | }); 120 | describe('missing millisecond', () => { 121 | let time; 122 | it('time can be constructed', () => { 123 | time = new Time(15, 23, 30); 124 | assert(time); 125 | equal(typeof time, 'object'); 126 | }); 127 | it('time.hour is 15', () => equal(time.hour, 15)); 128 | it('time.minute is 23', () => equal(time.minute, 23)); 129 | it('time.second is 30', () => equal(time.second, 30)); 130 | it('time.millisecond is 0', () => equal(time.millisecond, 0)); 131 | it('time.microsecond is 0', () => equal(time.microsecond, 0)); 132 | it('time.nanosecond is 0', () => equal(time.nanosecond, 0)); 133 | it('`${time}` is 15:23:30', () => equal(`${time}`, '15:23:30')); 134 | }); 135 | describe('missing second', () => { 136 | let time; 137 | it('time can be constructed', () => { 138 | time = new Time(15, 23); 139 | assert(time); 140 | equal(typeof time, 'object'); 141 | }); 142 | it('time.hour is 15', () => equal(time.hour, 15)); 143 | it('time.minute is 23', () => equal(time.minute, 23)); 144 | it('time.second is 0', () => equal(time.second, 0)); 145 | it('time.millisecond is 0', () => equal(time.millisecond, 0)); 146 | it('time.microsecond is 0', () => equal(time.microsecond, 0)); 147 | it('time.nanosecond is 0', () => equal(time.nanosecond, 0)); 148 | it('`${time}` is 15:23', () => equal(`${time}`, '15:23')); 149 | }); 150 | describe('.with manipulation', () => { 151 | const time = new Time(15, 23, 30, 123, 456, 789); 152 | it('time.with({ hour: 3 } works', () => { 153 | equal(`${time.with({ hour: 3 })}`, '03:23:30.123456789'); 154 | }); 155 | it('time.with({ minute: 3 } works', () => { 156 | equal(`${time.with({ minute: 3 })}`, '15:03:30.123456789'); 157 | }); 158 | it('time.with({ second: 3 } works', () => { 159 | equal(`${time.with({ second: 3 })}`, '15:23:03.123456789'); 160 | }); 161 | it('time.with({ millisecond: 3 } works', () => { 162 | equal(`${time.with({ millisecond: 3 })}`, '15:23:30.003456789'); 163 | }); 164 | it('time.with({ microsecond: 3 } works', () => { 165 | equal(`${time.with({ microsecond: 3 })}`, '15:23:30.123003789'); 166 | }); 167 | it('time.with({ nanosecond: 3 } works', () => { 168 | equal(`${time.with({ nanosecond: 3 })}`, '15:23:30.123456003'); 169 | }); 170 | it('time.with({ minute: 8, nanosecond: 3 } works', () => { 171 | equal(`${time.with({ minute: 8, nanosecond: 3 })}`, '15:08:30.123456003'); 172 | }); 173 | }); 174 | describe('time.difference() works', () => { 175 | const time = new Time(15, 23, 30, 123, 456, 789); 176 | it('time.difference({ hour: 14, minute: 23, second: 30, millisecond: 123, microsecond: 456, nanosecond: 789 })', () => { 177 | const duration = time.difference({ 178 | hour: 14, 179 | minute: 23, 180 | second: 30, 181 | millisecond: 123, 182 | microsecond: 456, 183 | nanosecond: 789 184 | }); 185 | equal(duration.years, 0); 186 | equal(duration.months, 0); 187 | equal(duration.days, 0); 188 | equal(duration.hours, 1); 189 | equal(duration.minutes, 0); 190 | equal(duration.seconds, 0); 191 | equal(duration.milliseconds, 0); 192 | equal(duration.microseconds, 0); 193 | equal(duration.nanoseconds, 0); 194 | }); 195 | it('time.difference({ hour: 13, minute: 30, second: 30, millisecond: 123, microsecond: 456, nanosecond: 789 })', () => { 196 | const duration = time.difference({ 197 | hour: 13, 198 | minute: 30, 199 | second: 30, 200 | millisecond: 123, 201 | microsecond: 456, 202 | nanosecond: 789 203 | }); 204 | equal(duration.years, 0, `${duration} years`); 205 | equal(duration.months, 0, `${duration} months`); 206 | equal(duration.days, 0, `${duration} days`); 207 | equal(duration.hours, 1, `${duration} hours`); 208 | equal(duration.minutes, 53, `${duration} minutes`); 209 | equal(duration.seconds, 0, `${duration} seconds`); 210 | equal(duration.milliseconds, 0, `${duration} milliseconds`); 211 | equal(duration.microseconds, 0, `${duration} microseconds`); 212 | equal(duration.nanoseconds, 0, `${duration} nnoseconds`); 213 | }); 214 | }); 215 | describe('time.plus() works', () => { 216 | const time = new Time(15, 23, 30, 123, 456, 789); 217 | it('time.plus({ hours: 16 })', () => { 218 | equal(`${time.plus({ hours: 16 })}`, '07:23:30.123456789'); 219 | }); 220 | it('time.plus({ minutes: 45 })', () => { 221 | equal(`${time.plus({ minutes: 45 })}`, '16:08:30.123456789'); 222 | }); 223 | it('time.plus({ nanoseconds: 300 })', () => { 224 | equal(`${time.plus({ nanoseconds: 300 })}`, '15:23:30.123457089'); 225 | }); 226 | }); 227 | describe('time.minus() works', () => { 228 | const time = new Time(15, 23, 30, 123, 456, 789); 229 | it('time.minus({ hours: 16 })', () => { 230 | equal(`${time.minus({ hours: 16 })}`, '23:23:30.123456789'); 231 | }); 232 | it('time.minus({ minutes: 45 })', () => { 233 | equal(`${time.minus({ minutes: 45 })}`, '14:38:30.123456789'); 234 | }); 235 | it('time.minus({ nanoseconds: 800 })', () => { 236 | equal(`${time.minus({ nanoseconds: 800 })}`, '15:23:30.123455989'); 237 | }); 238 | }); 239 | describe('time.toString() works', () => { 240 | it('new Time(15, 23).toString()', () => { 241 | equal(new Time(15, 23).toString(), '15:23'); 242 | }); 243 | it('new Time(15, 23, 30).toString()', () => { 244 | equal(new Time(15, 23, 30).toString(), '15:23:30'); 245 | }); 246 | it('new Time(15, 23, 30, 123).toString()', () => { 247 | equal(new Time(15, 23, 30, 123).toString(), '15:23:30.123'); 248 | }); 249 | it('new Time(15, 23, 30, 123, 456).toString()', () => { 250 | equal(new Time(15, 23, 30, 123, 456).toString(), '15:23:30.123456'); 251 | }); 252 | it('new Time(15, 23, 30, 123, 456, 789).toString()', () => { 253 | equal(new Time(15, 23, 30, 123, 456, 789).toString(), '15:23:30.123456789'); 254 | }); 255 | }); 256 | describe('Time.fromString() works', () => { 257 | it('Time.fromString("15:23")', () => { 258 | equal(`${Time.fromString('15:23')}`, '15:23'); 259 | }); 260 | it('Time.fromString("15:23:30")', () => { 261 | equal(`${Time.fromString('15:23:30')}`, '15:23:30'); 262 | }); 263 | it('Time.fromString("15:23:30.123")', () => { 264 | equal(`${Time.fromString('15:23:30.123')}`, '15:23:30.123'); 265 | }); 266 | it('Time.fromString("15:23:30.123456")', () => { 267 | equal(`${Time.fromString('15:23:30.123456')}`, '15:23:30.123456'); 268 | }); 269 | it('Time.fromString("15:23:30.123456789")', () => { 270 | equal(`${Time.fromString('15:23:30.123456789')}`, '15:23:30.123456789'); 271 | }); 272 | }); 273 | }); 274 | }); 275 | 276 | import { normalize } from 'path'; 277 | if (normalize(import.meta.url.slice(8)) === normalize(process.argv[1])) report(reporter); 278 | --------------------------------------------------------------------------------