diff --git a/scripts/dateParseBench.ts b/scripts/dateParseBench.ts index ade82246..3c661480 100644 --- a/scripts/dateParseBench.ts +++ b/scripts/dateParseBench.ts @@ -6,9 +6,11 @@ yarn tsn dateParseBench import { runBenchScript } from '@naturalcycles/bench-lib' import { dayjs } from '@naturalcycles/time-lib' -import { localDate } from '../src' +import { IsoDate, localDate } from '../src' -const strings = localDate.range('2022-01-03', '2023-01-05').map(String) +const strings = localDate + .range('2022-01-03' as IsoDate, '2023-01-05' as IsoDate) + .map(d => d.toString()) const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/ // dayjs x 4,733 ops/sec ±0.31% (90 runs sampled) diff --git a/scripts/lazyLocalDateBench.ts b/scripts/lazyLocalDateBench.ts index 1f345499..34a36510 100644 --- a/scripts/lazyLocalDateBench.ts +++ b/scripts/lazyLocalDateBench.ts @@ -6,9 +6,9 @@ yarn tsn lazyLocalDateBench import { runBenchScript } from '@naturalcycles/bench-lib' // import { LazyLocalDate } from '../src/__exclude/lazyLocalDate' -import { localDate } from '../src' +import { IsoDate, localDate } from '../src' -const str = '1984-06-21' +const str = '1984-06-21' as IsoDate runBenchScript({ fns: { diff --git a/scripts/localDateBench.ts b/scripts/localDateBench.ts index 273b6b97..eb991fa6 100644 --- a/scripts/localDateBench.ts +++ b/scripts/localDateBench.ts @@ -6,9 +6,9 @@ yarn tsn localDateBench import { runBenchScript } from '@naturalcycles/bench-lib' import { dayjs } from '@naturalcycles/time-lib' -import { localDate, localTime } from '../src' +import { IsoDate, localDate, localTime } from '../src' -const str = '1984-06-21' +const str = '1984-06-21' as IsoDate runBenchScript({ fns: { diff --git a/scripts/todayIsoDateBench.script.ts b/scripts/todayIsoDateBench.script.ts index f1910c1b..ec3b564f 100644 --- a/scripts/todayIsoDateBench.script.ts +++ b/scripts/todayIsoDateBench.script.ts @@ -5,7 +5,7 @@ yarn tsn todayIsoDateBench */ import { runBenchScript } from '@naturalcycles/bench-lib' -import { IsoDateString } from '../src' +import { IsoDate } from '../src' runBenchScript({ fns: { @@ -20,17 +20,17 @@ runBenchScript({ }, }) -function fn1(): IsoDateString { +function fn1(): IsoDate { const d = new Date() return [ d.getFullYear(), String(d.getMonth() + 1).padStart(2, '0'), String(d.getDate()).padStart(2, '0'), - ].join('-') + ].join('-') as IsoDate } -function fn2(): IsoDateString { - return new Date().toISOString().slice(0, 10) +function fn2(): IsoDate { + return new Date().toISOString().slice(0, 10) as IsoDate } // function fn1(): IsoDateString { diff --git a/src/datetime/dateInterval.test.ts b/src/datetime/dateInterval.test.ts index 35ef0582..a2d43404 100644 --- a/src/datetime/dateInterval.test.ts +++ b/src/datetime/dateInterval.test.ts @@ -1,3 +1,4 @@ +import { IsoDate } from '../types' import { DateInterval } from './dateInterval' import { localDate } from './localDate' @@ -8,11 +9,14 @@ test('basic', () => { const int1 = DateInterval.parse(str1) expect(int1.toString()).toBe(str1) expect(JSON.stringify(int1)).toBe(`"${str1}"`) - expect(int1.start.isSame('2022-02-24')) - expect(int1.end.isSame('2022-03-30')) + expect(int1.start.isSame('2022-02-24' as IsoDate)) + expect(int1.end.isSame('2022-03-30' as IsoDate)) - const int2 = DateInterval.of('2022-02-24', '2022-03-30') - const int3 = DateInterval.of(localDate('2022-02-24'), localDate('2022-03-30')) + const int2 = DateInterval.of('2022-02-24' as IsoDate, '2022-03-30' as IsoDate) + const int3 = DateInterval.of( + localDate('2022-02-24' as IsoDate), + localDate('2022-03-30' as IsoDate), + ) expect(int1.isSame(int2)).toBe(true) expect(int1.cmp(int2)).toBe(0) @@ -38,16 +42,16 @@ test('basic', () => { test('includes', () => { const int = DateInterval.parse('2022-02-24/2022-03-30') - expect(int.includes('2022-02-23')).toBe(false) - expect(int.includes('2022-02-24')).toBe(true) - expect(int.includes('2022-02-25')).toBe(true) - expect(int.includes('2022-02-28')).toBe(true) - expect(int.includes('2022-03-01')).toBe(true) - expect(int.includes('2022-03-11')).toBe(true) - expect(int.includes('2022-03-29')).toBe(true) - expect(int.includes('2022-03-30')).toBe(true) - expect(int.includes('2022-03-31')).toBe(false) - expect(int.includes('2022-04-01')).toBe(false) + expect(int.includes('2022-02-23' as IsoDate)).toBe(false) + expect(int.includes('2022-02-24' as IsoDate)).toBe(true) + expect(int.includes('2022-02-25' as IsoDate)).toBe(true) + expect(int.includes('2022-02-28' as IsoDate)).toBe(true) + expect(int.includes('2022-03-01' as IsoDate)).toBe(true) + expect(int.includes('2022-03-11' as IsoDate)).toBe(true) + expect(int.includes('2022-03-29' as IsoDate)).toBe(true) + expect(int.includes('2022-03-30' as IsoDate)).toBe(true) + expect(int.includes('2022-03-31' as IsoDate)).toBe(false) + expect(int.includes('2022-04-01' as IsoDate)).toBe(false) }) test('getDays', () => { diff --git a/src/datetime/dateInterval.ts b/src/datetime/dateInterval.ts index 87c97e1b..345dfb1a 100644 --- a/src/datetime/dateInterval.ts +++ b/src/datetime/dateInterval.ts @@ -1,4 +1,4 @@ -import { Inclusiveness } from '../types' +import { Inclusiveness, IsoDate } from '../types' import { LocalDate, localDate, LocalDateInput, LocalDateUnit } from './localDate' export type DateIntervalConfig = DateInterval | DateIntervalString @@ -25,7 +25,7 @@ export class DateInterval { static parse(d: DateIntervalConfig): DateInterval { if (d instanceof DateInterval) return d - const [start, end] = d.split('/') + const [start, end] = d.split('/') as IsoDate[] if (!end || !start) { throw new Error(`Cannot parse "${d}" into DateInterval`) diff --git a/src/datetime/localDate.test.ts b/src/datetime/localDate.test.ts index df7f391b..9752c34f 100644 --- a/src/datetime/localDate.test.ts +++ b/src/datetime/localDate.test.ts @@ -1,6 +1,7 @@ import { dayjs } from '@naturalcycles/time-lib' import { _range } from '../array/range' import { expectWithMessage, isUTC } from '../test/test.util' +import { IsoDate } from '../types' import { localDate, LocalDateFormatter, LocalDateUnit } from './localDate' const units: LocalDateUnit[] = ['year', 'month', 'day', 'week'] @@ -13,7 +14,7 @@ const UNIT_RANGE: Record = { } test('basic', () => { - const str = '1984-06-21' + const str = '1984-06-21' as IsoDate const ld = localDate.fromInput(str) expect(ld.toString()).toBe(str) expect(ld.toStringCompact()).toBe('19840621') @@ -67,10 +68,10 @@ test('basic', () => { expect(ld.isYoungerThan(5, 'day')).toBe(false) expect(ld.isOlderThan(100, 'year')).toBe(false) expect(ld.isYoungerThan(100, 'year')).toBe(true) - expect(ld.getAgeInYears('2024-05-15')).toBe(39) - expect(ld.getAgeInMonths('2024-05-15')).toBe(478) - expect(ld.getAgeInDays('2024-05-15')).toBe(14573) - expect(ld.getAgeIn('day', '2024-05-15')).toBe(14573) + expect(ld.getAgeInYears('2024-05-15' as IsoDate)).toBe(39) + expect(ld.getAgeInMonths('2024-05-15' as IsoDate)).toBe(478) + expect(ld.getAgeInDays('2024-05-15' as IsoDate)).toBe(14573) + expect(ld.getAgeIn('day', '2024-05-15' as IsoDate)).toBe(14573) expect(ld.isToday()).toBe(false) expect(ld.isAfterToday()).toBe(false) @@ -80,10 +81,10 @@ test('basic', () => { }) test('constructors', () => { - const s = localDate('1984-06-21').toISODate() - expect(localDate.fromInput('1984-06-21').toISODate()).toBe(s) - expect(localDate.fromInput(localDate('1984-06-21')).toISODate()).toBe(s) - expect(localDate.fromString('1984-06-21').toISODate()).toBe(s) + const s = localDate('1984-06-21' as IsoDate).toISODate() + expect(localDate.fromInput('1984-06-21' as IsoDate).toISODate()).toBe(s) + expect(localDate.fromInput(localDate('1984-06-21' as IsoDate)).toISODate()).toBe(s) + expect(localDate.fromString('1984-06-21' as IsoDate).toISODate()).toBe(s) expect(localDate.fromCompactString('19840621').toISODate()).toBe(s) expect(localDate.fromInput(new Date(1984, 5, 21)).toISODate()).toBe(s) expect(localDate.fromDate(new Date(1984, 5, 21)).toISODate()).toBe(s) @@ -91,27 +92,27 @@ test('constructors', () => { }) test('isBetween', () => { - const ld = localDate.fromInput('1984-06-21') - expect(ld.isBetween('1984-06-21', '1984-06-21')).toBe(false) - expect(ld.isBetween('1984-06-21', '1984-06-21', '()')).toBe(false) - expect(ld.isBetween('1984-06-21', '1984-06-21', '[)')).toBe(false) - expect(ld.isBetween('1984-06-21', '1984-06-21', '(]')).toBe(false) - expect(ld.isBetween('1984-06-21', '1984-06-21', '[]')).toBe(true) + const ld = localDate.fromInput('1984-06-21' as IsoDate) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-21' as IsoDate)).toBe(false) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-21' as IsoDate, '()')).toBe(false) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-21' as IsoDate, '[)')).toBe(false) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-21' as IsoDate, '(]')).toBe(false) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-21' as IsoDate, '[]')).toBe(true) - expect(ld.isBetween('1984-06-21', '1984-06-22')).toBe(true) - expect(ld.isBetween('1984-06-21', '1984-06-22', '()')).toBe(false) - expect(ld.isBetween('1984-06-21', '1984-06-22', '[)')).toBe(true) - expect(ld.isBetween('1984-06-21', '1984-06-22', '(]')).toBe(false) - expect(ld.isBetween('1984-06-21', '1984-06-22', '[]')).toBe(true) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-22' as IsoDate)).toBe(true) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-22' as IsoDate, '()')).toBe(false) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-22' as IsoDate, '[)')).toBe(true) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-22' as IsoDate, '(]')).toBe(false) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-22' as IsoDate, '[]')).toBe(true) - expect(ld.isBetween('1984-06-20', '1984-06-22')).toBe(true) + expect(ld.isBetween('1984-06-20' as IsoDate, '1984-06-22' as IsoDate)).toBe(true) // invalid, max < min - expect(ld.isBetween('1984-06-22', '1984-06-20')).toBe(false) + expect(ld.isBetween('1984-06-22' as IsoDate, '1984-06-20' as IsoDate)).toBe(false) }) test('sort', () => { - const items = ['2022-01-03', '2022-01-01', '2022-01-02'].map(i => localDate(i)) + const items = ['2022-01-03', '2022-01-01', '2022-01-02'].map(i => localDate(i as IsoDate)) expect(localDate.sort(items).map(s => s.toString())).toEqual([ '2022-01-01', @@ -129,7 +130,7 @@ test('sort', () => { }) test('add basic', () => { - const ld = localDate('2022-01-01') + const ld = localDate('2022-01-01' as IsoDate) expect(ld.clone().plus(1, 'day', true).toString()).toBe('2022-01-02') expect(ld.plus(-1, 'day').toString()).toBe('2021-12-31') @@ -137,13 +138,13 @@ test('add basic', () => { expect(ld.minus(1, 'day').toString()).toBe('2021-12-31') expect(ld.plus(-1, 'month').toString()).toBe('2021-12-01') - const d = localDate('2022-05-31') + const d = localDate('2022-05-31' as IsoDate) expect(d.plusMonths(1).toISODate()).toBe('2022-06-30') expect(d.minusMonths(1).toISODate()).toBe('2022-04-30') }) test('add', () => { - const starts = ['2022-05-31', '2022-05-30', '2020-02-29', '2021-02-28', '2022-01-01'] + const starts = ['2022-05-31', '2022-05-30', '2020-02-29', '2021-02-28', '2022-01-01'] as IsoDate[] starts.forEach(start => { const ld = localDate(start) @@ -166,7 +167,7 @@ test('add', () => { }) test('diff', () => { - const starts = ['2022-05-31', '2022-05-30', '2020-02-29', '2021-02-28', '2022-01-01'] + const starts = ['2022-05-31', '2022-05-30', '2020-02-29', '2021-02-28', '2022-01-01'] as IsoDate[] starts.forEach(start => { const ld = localDate.fromInput(start) @@ -211,7 +212,7 @@ const validDates = [ '2022-01-01T22:01:01', '2022-01-01T22:01:01Z', '2022-01-01nahuy', -] +] as IsoDate[] test.each(validDates)('%s is valid', s => { expect(localDate.isValid(s)).toBe(true) @@ -228,7 +229,7 @@ const invalidDates = [ '', '2022-01-', '2022-01-x1', -] as string[] +] as IsoDate[] test.each(invalidDates)('%s is invalid', s => { expect(localDate.isValid(s)).toBe(false) @@ -239,7 +240,7 @@ test.each(invalidDates)('%s is invalid', s => { }) test('toDate', () => { - const str = '2022-03-06' + const str = '2022-03-06' as IsoDate const d = localDate.fromInput(str) if (isUTC()) { // timezone-dependent @@ -257,14 +258,15 @@ test('toDate', () => { }) test('range', () => { - expect(localDate.range('2021-12-24', '2021-12-26')).toMatchInlineSnapshot(` + expect(localDate.range('2021-12-24' as IsoDate, '2021-12-26' as IsoDate)).toMatchInlineSnapshot(` [ "2021-12-24", "2021-12-25", ] `) - expect(localDate.range('2021-12-24', '2021-12-26', '[]')).toMatchInlineSnapshot(` + expect(localDate.range('2021-12-24' as IsoDate, '2021-12-26' as IsoDate, '[]')) + .toMatchInlineSnapshot(` [ "2021-12-24", "2021-12-25", @@ -272,7 +274,8 @@ test('range', () => { ] `) - expect(localDate.range('2021-12-24', '2021-12-30', '[)', 2)).toMatchInlineSnapshot(` + expect(localDate.range('2021-12-24' as IsoDate, '2021-12-30' as IsoDate, '[)', 2)) + .toMatchInlineSnapshot(` [ "2021-12-24", "2021-12-26", @@ -280,7 +283,8 @@ test('range', () => { ] `) - expect(localDate.range('2021-12-24', '2021-12-30', '[]', 2)).toMatchInlineSnapshot(` + expect(localDate.range('2021-12-24' as IsoDate, '2021-12-30' as IsoDate, '[]', 2)) + .toMatchInlineSnapshot(` [ "2021-12-24", "2021-12-26", @@ -291,7 +295,8 @@ test('range', () => { }) test('rangeIterable', () => { - expect([...localDate.rangeIterable('2021-12-24', '2021-12-26')]).toMatchInlineSnapshot(` + expect([...localDate.rangeIterable('2021-12-24' as IsoDate, '2021-12-26' as IsoDate)]) + .toMatchInlineSnapshot(` [ "2021-12-24", "2021-12-25", @@ -301,35 +306,35 @@ test('rangeIterable', () => { test('format', () => { const fmt: LocalDateFormatter = ld => `${ld.year}-${String(ld.month).padStart(2, '0')}` - expect(localDate('1984-06-21').format(fmt)).toBe('1984-06') + expect(localDate('1984-06-21' as IsoDate).format(fmt)).toBe('1984-06') const fmt2: LocalDateFormatter = ld => `${String(ld.month).padStart(2, '0')}/${ld.year}` - expect(localDate('1984-06-21').format(fmt2)).toBe('06/1984') + expect(localDate('1984-06-21' as IsoDate).format(fmt2)).toBe('06/1984') const fmt3 = new Intl.DateTimeFormat('ru', { month: 'long', day: 'numeric', }) - expect(localDate('1984-06-21').format(fmt3)).toBe('21 июня') + expect(localDate('1984-06-21' as IsoDate).format(fmt3)).toBe('21 июня') }) test('diff2', () => { - expect(localDate('2020-03-03').diff('1991-06-21', 'year')).toBe(28) + expect(localDate('2020-03-03' as IsoDate).diff('1991-06-21' as IsoDate, 'year')).toBe(28) - const ld = localDate('2022-01-01') - expect(ld.diff('2020-12-31', 'year')).toBe(1) - expect(ld.diff('2021-01-01', 'year')).toBe(1) - expect(ld.diff('2021-01-02', 'year')).toBe(0) + const ld = localDate('2022-01-01' as IsoDate) + expect(ld.diff('2020-12-31' as IsoDate, 'year')).toBe(1) + expect(ld.diff('2021-01-01' as IsoDate, 'year')).toBe(1) + expect(ld.diff('2021-01-02' as IsoDate, 'year')).toBe(0) // day 2022-01-01 2020-12-01 - expect(localDate('2020-12-01').diff(ld, 'day')).toBe(-396) - expect(ld.diff('2020-12-01', 'day')).toBe(396) + expect(localDate('2020-12-01' as IsoDate).diff(ld, 'day')).toBe(-396) + expect(ld.diff('2020-12-01' as IsoDate, 'day')).toBe(396) - expect(localDate('2020-02-29').diff('2020-01-30', 'month')).toBe( + expect(localDate('2020-02-29' as IsoDate).diff('2020-01-30' as IsoDate, 'month')).toBe( dayjs('2020-02-29').diff('2020-01-30', 'month'), ) - expect(localDate('2020-01-30').diff('2020-02-29', 'month')).toBe( + expect(localDate('2020-01-30' as IsoDate).diff('2020-02-29' as IsoDate, 'month')).toBe( dayjs('2020-01-30').diff('2020-02-29', 'month'), ) }) @@ -342,19 +347,19 @@ test('parse weird input', () => { }) test('dayOfWeek', () => { - expect(localDate('1984-06-18').dayOfWeek).toBe(1) - expect(localDate('1984-06-19').dayOfWeek).toBe(2) - expect(localDate('1984-06-20').dayOfWeek).toBe(3) - expect(localDate('1984-06-21').dayOfWeek).toBe(4) - expect(localDate('1984-06-22').dayOfWeek).toBe(5) - expect(localDate('1984-06-23').dayOfWeek).toBe(6) - expect(localDate('1984-06-24').dayOfWeek).toBe(7) - expect(localDate('1984-06-25').dayOfWeek).toBe(1) + expect(localDate('1984-06-18' as IsoDate).dayOfWeek).toBe(1) + expect(localDate('1984-06-19' as IsoDate).dayOfWeek).toBe(2) + expect(localDate('1984-06-20' as IsoDate).dayOfWeek).toBe(3) + expect(localDate('1984-06-21' as IsoDate).dayOfWeek).toBe(4) + expect(localDate('1984-06-22' as IsoDate).dayOfWeek).toBe(5) + expect(localDate('1984-06-23' as IsoDate).dayOfWeek).toBe(6) + expect(localDate('1984-06-24' as IsoDate).dayOfWeek).toBe(7) + expect(localDate('1984-06-25' as IsoDate).dayOfWeek).toBe(1) }) // You shouldn't do it, I'm just discovering that it works, apparently test('comparison with string', () => { - const d = localDate('1984-06-21') as any + const d = localDate('1984-06-21' as IsoDate) as any expect(d < '1984-06-22').toBe(true) expect(d < '1985-06-22').toBe(true) expect(d <= '1984-06-21').toBe(true) @@ -366,14 +371,14 @@ test('comparison with string', () => { // You shouldn't do it, I'm just discovering that it works, apparently test('comparison with other LocalDates like primitives', () => { - const d = localDate('1984-06-21') as any - expect(d < localDate('1984-06-22')).toBe(true) - expect(d < localDate('1985-06-22')).toBe(true) - expect(d <= localDate('1984-06-21')).toBe(true) - expect(d < localDate('1984-06-20')).toBe(false) - expect(d >= localDate('1984-06-21')).toBe(true) - expect(d > localDate('1984-06-20')).toBe(true) - expect(d > localDate('1981-06-20')).toBe(true) + const d = localDate('1984-06-21' as IsoDate) as any + expect(d < localDate('1984-06-22' as IsoDate)).toBe(true) + expect(d < localDate('1985-06-22' as IsoDate)).toBe(true) + expect(d <= localDate('1984-06-21' as IsoDate)).toBe(true) + expect(d < localDate('1984-06-20' as IsoDate)).toBe(false) + expect(d >= localDate('1984-06-21' as IsoDate)).toBe(true) + expect(d > localDate('1984-06-20' as IsoDate)).toBe(true) + expect(d > localDate('1981-06-20' as IsoDate)).toBe(true) }) test('todayString', () => { @@ -392,7 +397,7 @@ test('todayString tz', () => { }) test('fractional unit input', () => { - const ld = localDate('1984-06-21') + const ld = localDate('1984-06-21' as IsoDate) expect(ld.plus(0.4, 'day').toISODate()).toBe('1984-06-21') expect(ld.plus(0.5, 'day').toISODate()).toBe('1984-06-21') expect(ld.plus(0.9, 'day').toISODate()).toBe('1984-06-21') @@ -402,5 +407,5 @@ test('fractional unit input', () => { expect(ld.minus(0.9, 'day').toISODate()).toBe('1984-06-20') expect(ld.minus(1.1, 'day').toISODate()).toBe('1984-06-19') - expect(localDate('1984-06-21.5').toISODate()).toBe('1984-06-21') + expect(localDate('1984-06-21.5' as IsoDate).toISODate()).toBe('1984-06-21') }) diff --git a/src/datetime/localDate.ts b/src/datetime/localDate.ts index c8e2fff1..164cb378 100644 --- a/src/datetime/localDate.ts +++ b/src/datetime/localDate.ts @@ -2,8 +2,8 @@ import { _assert } from '../error/assert' import { Iterable2 } from '../iter/iterable2' import type { Inclusiveness, - IsoDateString, - IsoDateTimeString, + IsoDate, + IsoDateTime, MonthId, SortDirection, UnixTimestamp, @@ -21,7 +21,7 @@ const MDAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)/ const COMPACT_DATE_REGEX = /^(\d\d\d\d)(\d\d)(\d\d)$/ -export type LocalDateInput = LocalDate | Date | IsoDateString +export type LocalDateInput = LocalDate | Date | IsoDate export type LocalDateInputNullable = LocalDateInput | null | undefined export type LocalDateFormatter = (ld: LocalDate) => string @@ -442,31 +442,31 @@ export class LocalDate { /** * Returns e.g: `1984-06-21` */ - toISODate(): IsoDateString { + toISODate(): IsoDate { return [ String(this.year).padStart(4, '0'), String(this.month).padStart(2, '0'), String(this.day).padStart(2, '0'), - ].join('-') + ].join('-') as IsoDate } /** * Returns e.g: `1984-06-21T00:00:00` * Hours, minutes and seconds are 0. */ - toISODateTime(): IsoDateTimeString { - return this.toISODate() + 'T00:00:00' + toISODateTime(): IsoDateTime { + return (this.toISODate() + 'T00:00:00') as IsoDateTime } /** * Returns e.g: `1984-06-21T00:00:00Z` (notice the Z at the end, which indicates UTC). * Hours, minutes and seconds are 0. */ - toISODateTimeInUTC(): IsoDateTimeString { - return this.toISODateTime() + 'Z' + toISODateTimeInUTC(): IsoDateTime { + return (this.toISODateTime() + 'Z') as IsoDateTime } - toString(): IsoDateString { + toString(): IsoDate { return this.toISODate() } @@ -502,7 +502,7 @@ export class LocalDate { return this.toDate().valueOf() as UnixTimestampMillis } - toJSON(): IsoDateString { + toJSON(): IsoDate { return this.toISODate() } @@ -547,9 +547,9 @@ class LocalDateFactory { } /** - Convenience function to return current today's IsoDateString representation, e.g `2024-06-21` + Convenience function to return current today's IsoDate representation, e.g `2024-06-21` */ - todayString(): IsoDateString { + todayString(): IsoDate { return this.fromDate(new Date()).toISODate() } @@ -605,9 +605,9 @@ class LocalDateFactory { /** * Performs STRICT parsing. - * Only allows IsoDateString input, nothing else. + * Only allows IsoDate input, nothing else. */ - fromString(s: IsoDateString): LocalDate { + fromString(s: IsoDate): LocalDate { return this.parseToLocalDate(DATE_REGEX, s) } diff --git a/src/datetime/localTime.test.ts b/src/datetime/localTime.test.ts index 15b9292b..1c721e82 100644 --- a/src/datetime/localTime.test.ts +++ b/src/datetime/localTime.test.ts @@ -1,7 +1,7 @@ import { dayjs } from '@naturalcycles/time-lib' import { _range } from '../array/range' import { expectWithMessage, isUTC } from '../test/test.util' -import { UnixTimestamp, UnixTimestampMillis } from '../types' +import { IsoDate, IsoDateTime, UnixTimestamp, UnixTimestampMillis } from '../types' import { ISODayOfWeek, localTime, LocalTimeFormatter, LocalTimeUnit } from './localTime' const units: LocalTimeUnit[] = ['year', 'month', 'day', 'hour', 'minute', 'second', 'week'] @@ -17,7 +17,7 @@ const UNIT_RANGE: Record = { } test('basic', () => { - const start = '2022-01-01T00:00:00' + const start = '2022-01-01T00:00:00' as IsoDateTime const lt = localTime.fromInput(start) expect(lt.year).toBe(2022) expect(lt.month).toBe(1) @@ -47,10 +47,10 @@ test('basic', () => { expect(lt.isOlderThan(5, 'day')).toBe(true) expect(lt.isYoungerThan(5, 'day')).toBe(false) expect(lt.isOlderThan(100, 'year')).toBe(false) - expect(lt.getAgeInYears('2024-05-15')).toBe(2) - expect(lt.getAgeInMonths('2024-05-15')).toBe(28) - expect(lt.getAgeInDays('2024-05-15')).toBe(865) - expect(lt.getAgeIn('day', '2024-05-15')).toBe(865) + expect(lt.getAgeInYears('2024-05-15' as IsoDate)).toBe(2) + expect(lt.getAgeInMonths('2024-05-15' as IsoDate)).toBe(28) + expect(lt.getAgeInDays('2024-05-15' as IsoDate)).toBe(865) + expect(lt.getAgeIn('day', '2024-05-15' as IsoDate)).toBe(865) expect(lt.isAfterNow()).toBe(false) expect(lt.isBeforeNow()).toBe(true) @@ -143,29 +143,29 @@ test('basic', () => { }) test('isBetween', () => { - const ld = localTime('1984-06-21') - expect(ld.isBetween('1984-06-21', '1984-06-21')).toBe(false) - expect(ld.isBetween('1984-06-21', '1984-06-21', '()')).toBe(false) - expect(ld.isBetween('1984-06-21', '1984-06-21', '[)')).toBe(false) - expect(ld.isBetween('1984-06-21', '1984-06-21', '(]')).toBe(false) - expect(ld.isBetween('1984-06-21', '1984-06-21', '[]')).toBe(true) + const ld = localTime('1984-06-21' as IsoDate) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-21' as IsoDate)).toBe(false) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-21' as IsoDate, '()')).toBe(false) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-21' as IsoDate, '[)')).toBe(false) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-21' as IsoDate, '(]')).toBe(false) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-21' as IsoDate, '[]')).toBe(true) - expect(ld.isBetween('1984-06-21', '1984-06-22')).toBe(true) - expect(ld.isBetween('1984-06-21', '1984-06-22', '()')).toBe(false) - expect(ld.isBetween('1984-06-21', '1984-06-22', '[)')).toBe(true) - expect(ld.isBetween('1984-06-21', '1984-06-22', '(]')).toBe(false) - expect(ld.isBetween('1984-06-21', '1984-06-22', '[]')).toBe(true) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-22' as IsoDate)).toBe(true) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-22' as IsoDate, '()')).toBe(false) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-22' as IsoDate, '[)')).toBe(true) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-22' as IsoDate, '(]')).toBe(false) + expect(ld.isBetween('1984-06-21' as IsoDate, '1984-06-22' as IsoDate, '[]')).toBe(true) - expect(ld.isBetween('1984-06-20', '1984-06-22')).toBe(true) + expect(ld.isBetween('1984-06-20' as IsoDate, '1984-06-22' as IsoDate)).toBe(true) // invalid, max < min - expect(ld.isBetween('1984-06-22', '1984-06-20')).toBe(false) + expect(ld.isBetween('1984-06-22' as IsoDate, '1984-06-20' as IsoDate)).toBe(false) }) test('fromNow', () => { - const past = localTime('2022-02-24') - const now = localTime('2022-03-28') - const future = localTime('2022-04-01') + const past = localTime('2022-02-24' as IsoDate) + const now = localTime('2022-03-28' as IsoDate) + const future = localTime('2022-04-01' as IsoDate) expect(past.toFromNowString(now)).toBe('32 days ago') expect(now.toFromNowString(now)).toBe('now') @@ -184,7 +184,7 @@ const validDates = [ ['2022-01-01T22:01:01Z', '2022-01-01T22:01:01'], ['2022-01-01 22:01:01', '2022-01-01T22:01:01'], ['2022-01-01t22:01:01', '2022-01-01T22:01:01'], -] +] as [IsoDateTime, IsoDateTime][] // prettier-ignore const looselyValidDates = [ @@ -197,7 +197,7 @@ const looselyValidDates = [ ['2022-01-31T05:0', '2022-01-31T05:00:00'], ['2022-01-31T05', '2022-01-31T05:00:00'], ['2022-01-31T0', '2022-01-31T00:00:00'], -] +] as [IsoDateTime, IsoDateTime][] // prettier-ignore const invalidDates = [ @@ -207,7 +207,7 @@ const invalidDates = [ '2022-01-x1', '20220131', '2022-01-32', -] as string[] +] as IsoDateTime[] test.each(validDates)('%s parses to %s and is strictly valid', (s, expected) => { expect(localTime.isValid(s)).toBe(true) @@ -246,7 +246,13 @@ test.each(invalidDates)('%s is invalid', s => { test('add', () => { if (!isUTC()) return - const starts = ['2022-05-31', '2022-05-30', '2020-02-29', '2021-02-28', '2022-01-01'] + const starts = [ + '2022-05-31', + '2022-05-30', + '2020-02-29', + '2021-02-28', + '2022-01-01', + ] as IsoDateTime[] starts.forEach(start => { const lt = localTime(start) @@ -327,7 +333,13 @@ test('add', () => { test('diff', () => { if (!isUTC()) return - const starts = ['2022-05-31', '2022-05-30', '2020-02-29', '2021-02-28', '2022-01-01'] + const starts = [ + '2022-05-31', + '2022-05-30', + '2020-02-29', + '2021-02-28', + '2022-01-01', + ] as IsoDateTime[] // const starts = ['2020-02-29'] starts.forEach(start => { @@ -362,7 +374,7 @@ test('diff', () => { test('timezone-full string', () => { if (!isUTC()) return // todo: this should parse to the same unixtime regardless of TZ - const lt = localTime.fromInput('2022-04-06T23:15:00+09:00') + const lt = localTime.fromInput('2022-04-06T23:15:00+09:00' as IsoDateTime) expect(lt.unix).toMatchInlineSnapshot(`1649286900`) expect(lt.toPretty()).toBe('2022-04-06 23:15:00') }) @@ -377,7 +389,7 @@ test('startOf should have 0 millis', () => { test('format', () => { const fmt: LocalTimeFormatter = ld => `${ld.year}-${String(ld.month).padStart(2, '0')}` - expect(localTime('1984-06-21').format(fmt)).toBe('1984-06') + expect(localTime('1984-06-21' as IsoDate).format(fmt)).toBe('1984-06') const fmt2 = new Intl.DateTimeFormat('ru', { month: 'long', @@ -385,11 +397,11 @@ test('format', () => { hour: '2-digit', minute: '2-digit', }) - expect(localTime('1984-06-21T05:23:13').format(fmt2)).toBe('21 июня в 05:23') + expect(localTime('1984-06-21T05:23:13' as IsoDateTime).format(fmt2)).toBe('21 июня в 05:23') }) test('dayOfWeek', () => { - const t = localTime('1984-06-21') + const t = localTime('1984-06-21' as IsoDate) expect(t.dayOfWeek).toBe(ISODayOfWeek.THURSDAY) expect(() => t.setDayOfWeek(-1 as any)).toThrowErrorMatchingInlineSnapshot( @@ -411,16 +423,16 @@ test('dayOfWeek', () => { }) test('diff2', () => { - expect(localTime('2020-03-03').diff('1991-06-21', 'year')).toBe(28) + expect(localTime('2020-03-03' as IsoDate).diff('1991-06-21' as IsoDate, 'year')).toBe(28) - const ld = localTime('2022-01-01') - expect(ld.diff('2020-12-31', 'year')).toBe(1) - expect(ld.diff('2021-01-01', 'year')).toBe(1) - expect(ld.diff('2021-01-02', 'year')).toBe(0) + const ld = localTime('2022-01-01' as IsoDate) + expect(ld.diff('2020-12-31' as IsoDate, 'year')).toBe(1) + expect(ld.diff('2021-01-01' as IsoDate, 'year')).toBe(1) + expect(ld.diff('2021-01-02' as IsoDate, 'year')).toBe(0) // day 2022-01-01 2020-12-01 - expect(localTime('2020-12-01').diff(ld, 'day')).toBe(-396) - expect(ld.diff('2020-12-01', 'day')).toBe(396) + expect(localTime('2020-12-01' as IsoDate).diff(ld, 'day')).toBe(-396) + expect(ld.diff('2020-12-01' as IsoDate, 'day')).toBe(396) }) test('fromDateTimeObject', () => { @@ -434,20 +446,68 @@ test('add edge', () => { // 2020-02-29 - 2020 year == 0000-02-29 00:00:00 // expect(localTime('2020-02-29').add(-2020, 'year').toPretty()).toBe(dayjs('2020-02-29').add(-2020, 'year').toPretty()) - expect(localTime('2022-05-31').plusMonths(21).toPretty()).toBe('2024-02-29 00:00:00') - - expect(localTime('2022-05-31').plusMonths(1).toPretty()).toBe('2022-06-30 00:00:00') - expect(localTime('2022-05-31').minusMonths(1).toPretty()).toBe('2022-04-30 00:00:00') - - expect(localTime('2020-02-29').plus(1, 'month').toPretty()).toBe('2020-03-29 00:00:00') - expect(localTime('2020-03-29').plus(-1, 'month').toPretty()).toBe('2020-02-29 00:00:00') - expect(localTime('2020-03-30').plus(-1, 'month').toPretty()).toBe('2020-02-29 00:00:00') - expect(localTime('2020-03-31').plus(-1, 'month').toPretty()).toBe('2020-02-29 00:00:00') - expect(localTime('2020-01-31').plus(1, 'month').toPretty()).toBe('2020-02-29 00:00:00') - expect(localTime('2020-01-31').plus(2, 'month').toPretty()).toBe('2020-03-31 00:00:00') - expect(localTime('2020-01-31').plus(3, 'month').toPretty()).toBe('2020-04-30 00:00:00') - expect(localTime('2020-02-29').plusYears(1).toPretty()).toBe('2021-02-28 00:00:00') - expect(localTime('2021-02-28').minusYears(1).toPretty()).toBe('2020-02-28 00:00:00') + expect( + localTime('2022-05-31' as IsoDate) + .plusMonths(21) + .toPretty(), + ).toBe('2024-02-29 00:00:00') + + expect( + localTime('2022-05-31' as IsoDate) + .plusMonths(1) + .toPretty(), + ).toBe('2022-06-30 00:00:00') + expect( + localTime('2022-05-31' as IsoDate) + .minusMonths(1) + .toPretty(), + ).toBe('2022-04-30 00:00:00') + + expect( + localTime('2020-02-29' as IsoDate) + .plus(1, 'month') + .toPretty(), + ).toBe('2020-03-29 00:00:00') + expect( + localTime('2020-03-29' as IsoDate) + .plus(-1, 'month') + .toPretty(), + ).toBe('2020-02-29 00:00:00') + expect( + localTime('2020-03-30' as IsoDate) + .plus(-1, 'month') + .toPretty(), + ).toBe('2020-02-29 00:00:00') + expect( + localTime('2020-03-31' as IsoDate) + .plus(-1, 'month') + .toPretty(), + ).toBe('2020-02-29 00:00:00') + expect( + localTime('2020-01-31' as IsoDate) + .plus(1, 'month') + .toPretty(), + ).toBe('2020-02-29 00:00:00') + expect( + localTime('2020-01-31' as IsoDate) + .plus(2, 'month') + .toPretty(), + ).toBe('2020-03-31 00:00:00') + expect( + localTime('2020-01-31' as IsoDate) + .plus(3, 'month') + .toPretty(), + ).toBe('2020-04-30 00:00:00') + expect( + localTime('2020-02-29' as IsoDate) + .plusYears(1) + .toPretty(), + ).toBe('2021-02-28 00:00:00') + expect( + localTime('2021-02-28' as IsoDate) + .minusYears(1) + .toPretty(), + ).toBe('2020-02-28 00:00:00') }) test('diff edge', () => { @@ -459,52 +519,52 @@ test('diff edge', () => { // console.log(moment('2022-05-31').diff('2022-04-30', 'month')) // console.log(moment('2022-05-31').diff('2022-04-30T23:00:00', 'month')) - expect(localTime('2022-06-30').diff('2022-05-31', 'month')).toBe( + expect(localTime('2022-06-30' as IsoDate).diff('2022-05-31' as IsoDate, 'month')).toBe( dayjs('2022-06-30').diff('2022-05-31', 'month'), ) - expect(localTime('2022-06-30').diff('2022-05-31', 'month')).toBe(1) + expect(localTime('2022-06-30' as IsoDate).diff('2022-05-31' as IsoDate, 'month')).toBe(1) // 2022-05-31 - 721 hour == 2022-04-30 23:00:00 should diff 0 month - expect(localTime('2022-05-31').diff('2022-04-30', 'month')).toBe( + expect(localTime('2022-05-31' as IsoDate).diff('2022-04-30' as IsoDate, 'month')).toBe( dayjs('2022-05-31').diff('2022-04-30', 'month'), ) - expect(localTime('2022-05-31').diff('2022-04-30T23:00:00', 'month')).toBe( - dayjs('2022-05-31').diff('2022-04-30T23:00:00', 'month'), - ) + expect( + localTime('2022-05-31' as IsoDate).diff('2022-04-30T23:00:00' as IsoDateTime, 'month'), + ).toBe(dayjs('2022-05-31').diff('2022-04-30T23:00:00', 'month')) // 2022-05-31 + 1 day == 2022-06-01 00:00:00 should diff 0 month - expect(localTime('2022-06-01').diff('2022-05-31', 'month')).toBe(0) + expect(localTime('2022-06-01' as IsoDate).diff('2022-05-31' as IsoDate, 'month')).toBe(0) }) // You shouldn't do it, I'm just discovering that it works, apparently test('comparison with string', () => { if (!isUTC()) return - const d = localTime('1984-06-21T05:00:00') as any - expect(d < localTime('1984-06-22').unix).toBe(true) - expect(d < localTime('1985-06-22').unix).toBe(true) - expect(d <= localTime('1984-06-21T05:00:00').unix).toBe(true) - expect(d < localTime('1984-06-20').unix).toBe(false) - expect(d >= localTime('1984-06-21').unix).toBe(true) - expect(d > localTime('1984-06-20').unix).toBe(true) - expect(d > localTime('1981-06-20').unix).toBe(true) + const d = localTime('1984-06-21T05:00:00' as IsoDateTime) as any + expect(d < localTime('1984-06-22' as IsoDate).unix).toBe(true) + expect(d < localTime('1985-06-22' as IsoDate).unix).toBe(true) + expect(d <= localTime('1984-06-21T05:00:00' as IsoDateTime).unix).toBe(true) + expect(d < localTime('1984-06-20' as IsoDate).unix).toBe(false) + expect(d >= localTime('1984-06-21' as IsoDate).unix).toBe(true) + expect(d > localTime('1984-06-20' as IsoDate).unix).toBe(true) + expect(d > localTime('1981-06-20' as IsoDate).unix).toBe(true) }) // You shouldn't do it, I'm just discovering that it works, apparently test('comparison with other LocalTimes like primitives', () => { if (!isUTC()) return - const d = localTime('1984-06-21T05:00:00') as any - expect(d < localTime('1984-06-22')).toBe(true) - expect(d < localTime('1985-06-22')).toBe(true) - expect(d <= localTime('1984-06-21T05:00:00')).toBe(true) - expect(d < localTime('1984-06-20')).toBe(false) - expect(d >= localTime('1984-06-21')).toBe(true) - expect(d > localTime('1984-06-20')).toBe(true) - expect(d > localTime('1981-06-20')).toBe(true) + const d = localTime('1984-06-21T05:00:00' as IsoDate) as any + expect(d < localTime('1984-06-22' as IsoDate)).toBe(true) + expect(d < localTime('1985-06-22' as IsoDate)).toBe(true) + expect(d <= localTime('1984-06-21T05:00:00' as IsoDate)).toBe(true) + expect(d < localTime('1984-06-20' as IsoDate)).toBe(false) + expect(d >= localTime('1984-06-21' as IsoDate)).toBe(true) + expect(d > localTime('1984-06-20' as IsoDate)).toBe(true) + expect(d > localTime('1981-06-20' as IsoDate)).toBe(true) }) test('nowUnix', () => { - expect(localTime.nowUnix()).toBeGreaterThan(localTime('2024-01-01').unix) - expect(localTime.nowUnixMillis()).toBeGreaterThan(localTime('2024-01-01').unixMillis) + expect(localTime.nowUnix()).toBeGreaterThan(localTime('2024-01-01' as IsoDate).unix) + expect(localTime.nowUnixMillis()).toBeGreaterThan(localTime('2024-01-01' as IsoDate).unixMillis) }) test('utcOffset', () => { @@ -519,7 +579,7 @@ test('utcOffset', () => { }) test('fromDateUTC', () => { - const s = `1984-06-21T05:00:00` + const s = `1984-06-21T05:00:00` as IsoDate const lt = localTime(s).toUTC() const ltUtc = lt.toUTC() // eslint-disable-next-line jest/prefer-equality-matcher @@ -541,7 +601,7 @@ test('fromDateUTC', () => { }) test('getUTCOffsetMinutes', () => { - const now = localTime('2024-05-14') + const now = localTime('2024-05-14' as IsoDate) expect(now.getUTCOffsetMinutes('America/Los_Angeles')).toBe(-7 * 60) expect(now.getUTCOffsetMinutes('America/New_York')).toBe(-4 * 60) expect(now.getUTCOffsetMinutes('Europe/Stockholm')).toBe(2 * 60) @@ -553,7 +613,7 @@ test('getUTCOffsetMinutes', () => { }) test('getUTCOffsetString', () => { - const now = localTime('2024-05-14') + const now = localTime('2024-05-14' as IsoDate) expect(now.getUTCOffsetString('America/Los_Angeles')).toBe('-07:00') expect(now.getUTCOffsetString('America/New_York')).toBe('-04:00') expect(now.getUTCOffsetString('Europe/Stockholm')).toBe('+02:00') @@ -562,7 +622,7 @@ test('getUTCOffsetString', () => { }) test('inTimezone', () => { - const lt = localTime(`1984-06-21T05:00:00`) + const lt = localTime(`1984-06-21T05:00:00` as IsoDate) expect(lt.toPretty()).toBe(`1984-06-21 05:00:00`) // Nope, unix doesn't match ;( @@ -574,7 +634,7 @@ test('inTimezone', () => { expect(lt.inTimezone('Asia/Tokyo').toPretty()).toBe(`1984-06-21 14:00:00`) expect(lt.inTimezone('Asia/Tokyo').toPretty(false)).toBe(`1984-06-21 14:00`) - const lt2 = localTime(`1984-02-14T21:00:00`) + const lt2 = localTime(`1984-02-14T21:00:00` as IsoDate) expect(lt2.toPretty()).toBe(`1984-02-14 21:00:00`) expect(lt2.inTimezone('Europe/Stockholm').toPretty()).toBe(`1984-02-14 22:00:00`) expect(lt2.inTimezone('America/New_York').toPretty()).toBe(`1984-02-14 16:00:00`) @@ -584,7 +644,7 @@ test('inTimezone', () => { // This test should work both in `yarn test` (UTC) and `yarn test-tz2` (JST-09) test('parsing date string in negative timezone bug', () => { - const s = '2023-03-03' + const s = '2023-03-03' as IsoDate console.log(new Date(s).toString()) const lt = localTime(s) expect(lt.toISODate()).toBe(s) @@ -592,7 +652,7 @@ test('parsing date string in negative timezone bug', () => { }) test('fractional unit input', () => { - const lt = localTime('1984-06-21') + const lt = localTime('1984-06-21' as IsoDate) expect(lt.toString()).toBe('1984-06-21T00:00:00') // Comparing only IsoDate part (no time component) @@ -616,7 +676,7 @@ test('fractional unit input', () => { expect(lt.minus(0.9, 'day').toString()).toBe('1984-06-20T00:00:00') expect(lt.minus(1.1, 'day').toString()).toBe('1984-06-19T00:00:00') - expect(() => localTime('1984-06-21.5')).toThrowErrorMatchingInlineSnapshot( + expect(() => localTime('1984-06-21.5' as IsoDate)).toThrowErrorMatchingInlineSnapshot( `"Cannot parse "1984-06-21.5" into LocalTime"`, ) }) diff --git a/src/datetime/localTime.ts b/src/datetime/localTime.ts index 07afbb98..c64e8f62 100644 --- a/src/datetime/localTime.ts +++ b/src/datetime/localTime.ts @@ -2,8 +2,8 @@ import { _assert } from '../error/assert' import { _ms } from '../time/time.util' import type { Inclusiveness, - IsoDateString, - IsoDateTimeString, + IsoDate, + IsoDateTime, MonthId, NumberOfHours, NumberOfMinutes, @@ -26,7 +26,7 @@ export enum ISODayOfWeek { SUNDAY = 7, } -export type LocalTimeInput = LocalTime | Date | IsoDateTimeString | UnixTimestamp +export type LocalTimeInput = LocalTime | Date | IsoDate | IsoDateTime | UnixTimestamp export type LocalTimeInputNullable = LocalTimeInput | null | undefined export type LocalTimeFormatter = (ld: LocalTime) => string @@ -659,8 +659,8 @@ export class LocalTime { * or (if seconds=false): * `1984-06-21 17:56` */ - toPretty(seconds = true): IsoDateTimeString { - return this.toISODate() + ' ' + this.toISOTime(seconds) + toPretty(seconds = true): IsoDateTime { + return (this.toISODate() + ' ' + this.toISOTime(seconds)) as IsoDateTime // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!) // const s = this.$date.toISOString() // return s.slice(0, 10) + ' ' + s.slice(11, seconds ? 19 : 16) @@ -669,8 +669,8 @@ export class LocalTime { /** * Returns e.g: `1984-06-21T17:56:21` */ - toISODateTime(): IsoDateTimeString { - return this.toISODate() + 'T' + this.toISOTime() + toISODateTime(): IsoDateTime { + return (this.toISODate() + 'T' + this.toISOTime()) as IsoDateTime // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!) // return this.$date.toISOString().slice(0, 19) } @@ -678,14 +678,14 @@ export class LocalTime { /** * Returns e.g: `1984-06-21`, only the date part of DateTime */ - toISODate(): IsoDateString { + toISODate(): IsoDate { const { year, month, day } = this.toDateObject() return [ String(year).padStart(4, '0'), String(month).padStart(2, '0'), String(day).padStart(2, '0'), - ].join('-') + ].join('-') as IsoDate // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!) // return this.$date.toISOString().slice(0, 10) @@ -726,7 +726,7 @@ export class LocalTime { ].join('') } - toString(): string { + toString(): IsoDateTime { return this.toISODateTime() } @@ -818,7 +818,7 @@ class LocalTimeFactory { /** * Returns true if isoString is a valid iso8601 string like `yyyy-mm-ddThh:mm:dd`. */ - isValidString(isoString: string | undefined | null): boolean { + isValidString(isoString: IsoDateTime | IsoDate | undefined | null): boolean { return !!this.parseStrictlyOrUndefined(isoString) } @@ -843,10 +843,10 @@ class LocalTimeFactory { /** * Performs STRICT parsing. - * Only allows IsoDateTimeString or IsoDateString input, nothing else. + * Only allows IsoDateTime or IsoDate input, nothing else. */ - // eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents - fromIsoDateTimeString(s: IsoDateTimeString | IsoDateString): LocalTime { + + fromIsoDateTimeString(s: IsoDateTime | IsoDate): LocalTime { const d = this.parseStrictlyOrUndefined(s) _assert(d, `Cannot parse "${s}" into LocalTime`) return new LocalTime(d) @@ -856,7 +856,7 @@ class LocalTimeFactory { * Performs LOOSE parsing. * Tries to coerce imprefect/incorrect string input into IsoDateTimeString. * Use with caution. - * Allows to input IsoDateString, will set h:m:s to zeros. + * Allows to input IsoDate, will set h:m:s to zeros. */ parse(s: string): LocalTime { const d = this.parseLooselyOrUndefined(String(s)) diff --git a/src/types.test.ts b/src/types.test.ts index 8815a415..5dc107c4 100644 --- a/src/types.test.ts +++ b/src/types.test.ts @@ -4,6 +4,7 @@ import { asUnixTimestamp, asUnixTimestamp2000, Branded, + IsoDate, localTime, UnixTimestamp, } from '.' @@ -245,9 +246,9 @@ test('UnixTimestamp branded type', () => { }) test('asUnixTimestamp2000', () => { - const valid = localTime('2022-07-14').unix - const tooOld = localTime('1984-06-21').unix - const tsInMillis = localTime('2022-07-14').unixMillis + const valid = localTime('2022-07-14' as IsoDate).unix + const tooOld = localTime('1984-06-21' as IsoDate).unix + const tsInMillis = localTime('2022-07-14' as IsoDate).unixMillis expect(asUnixTimestamp2000(valid)).toBe(valid) diff --git a/src/types.ts b/src/types.ts index f0116c3e..dde1600c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -227,22 +227,24 @@ export interface InstanceId { } /** - * Interface explicitly states that the value is an ISO Date string (without time). + * ISO 8601 date (without time). + * Branded type. * * @example '2019-06-21' */ -export type IsoDateString = string +export type IsoDate = Branded /** - * Interface explicitly states that the value is an ISO DateTime string (with time). + * ISO 8601 date (date+time). + * Branded type. * * @example '2019-06-21T05:21:73Z' */ -export type IsoDateTimeString = string +export type IsoDateTime = Branded /** * Identifies the Month. - * Like IsoDateString, but without the Day token. + * Like IsoDate, but without the Day token. * * @example '2023-09' */