From 3e2a624af7dd18620e554c754f0620ce98ff4414 Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Mon, 8 Apr 2024 10:19:45 +0200 Subject: [PATCH] feat(DateTime): add helper to interpret input without timezone as utc time otherwise convert to utc --- src/constants/timeZone.ts | 4 +- src/dateTime/dateTime.ts | 41 ++++++++++++--------- src/dateTime/dateTimeUtc.test.ts | 63 ++++++++++++++++++++++++++++++++ src/datemath/datemath.test.ts | 4 +- src/datemath/datemath.ts | 4 +- src/dayjs/dayjs.d.ts | 10 +++++ src/{dayjs.ts => dayjs/index.ts} | 0 src/index.ts | 5 ++- 8 files changed, 105 insertions(+), 26 deletions(-) create mode 100644 src/dateTime/dateTimeUtc.test.ts create mode 100644 src/dayjs/dayjs.d.ts rename src/{dayjs.ts => dayjs/index.ts} (100%) diff --git a/src/constants/timeZone.ts b/src/constants/timeZone.ts index 3c0c3d8..3f62501 100644 --- a/src/constants/timeZone.ts +++ b/src/constants/timeZone.ts @@ -1,3 +1 @@ -import type {TimeZone} from '../typings'; - -export const UtcTimeZone: TimeZone = 'UTC'; +export const UtcTimeZone = 'UTC'; diff --git a/src/dateTime/dateTime.ts b/src/dateTime/dateTime.ts index f14ddb1..6339761 100644 --- a/src/dateTime/dateTime.ts +++ b/src/dateTime/dateTime.ts @@ -264,12 +264,6 @@ class DateTimeImpl implements DateTime { asFloat?: boolean | undefined, ): number { const value = DateTimeImpl.isDateTime(amount) ? amount.valueOf() : amount; - // value: - // DateTimeInput !== dayjs.ConfigType; - // Array !== [number?, number?, number?, number?, number?, number?, number?] - // unit: - // the same problem as for startOf - // @ts-expect-error return this._date.diff(value, unit, asFloat); } fromNow(withoutSuffix?: boolean | undefined): string { @@ -277,9 +271,6 @@ class DateTimeImpl implements DateTime { } from(formaInput: DateTimeInput, withoutSuffix?: boolean): string { const value = DateTimeImpl.isDateTime(formaInput) ? formaInput.valueOf() : formaInput; - // DateTimeInput !== dayjs.ConfigType; - // Array !== [number?, number?, number?, number?, number?, number?, number?] - // @ts-expect-error return this._date.from(value, withoutSuffix); } locale(): string; @@ -569,14 +560,8 @@ function getTimestamp(input: DateTimeInput, format?: string, lang?: string) { ts = Number(input); } else { const localDate = format - ? // DateTimeInput !== dayjs.ConfigType; - // Array !== [number?, number?, number?, number?, number?, number?, number?] - // @ts-expect-error - dayjs(input, format, locale, STRICT) - : // DateTimeInput !== dayjs.ConfigType; - // Array !== [number?, number?, number?, number?, number?, number?, number?] - // @ts-expect-error - dayjs(input, undefined, locale); + ? dayjs(input, format, locale, STRICT) + : dayjs(input, undefined, locale); ts = localDate.valueOf(); } @@ -623,3 +608,25 @@ export function dateTime(opt?: { return date; } + +export function dateTimeUtc(opt?: {input?: DateTimeInput; format?: FormatInput; lang?: string}) { + const {input, format, lang} = opt || {}; + + const locale = dayjs.locale(lang || settings.getLocale(), undefined, true); + + let ts: number; + if (DateTimeImpl.isDateTime(input) || typeof input === 'number' || input instanceof Date) { + ts = Number(input); + } else { + ts = dayjs.utc(input, format, STRICT).valueOf(); + } + + const date = createDateTime({ + ts, + timeZone: UtcTimeZone, + offset: 0, + locale, + }); + + return date; +} diff --git a/src/dateTime/dateTimeUtc.test.ts b/src/dateTime/dateTimeUtc.test.ts new file mode 100644 index 0000000..a02a81c --- /dev/null +++ b/src/dateTime/dateTimeUtc.test.ts @@ -0,0 +1,63 @@ +import MockDate from 'mockdate'; + +import {settings} from '../settings'; + +import {dateTimeUtc, isDateTime} from './dateTime'; + +const MOCKED_DATE = '2021-08-07T12:10:00'; + +beforeEach(() => { + MockDate.set(MOCKED_DATE); +}); + +afterEach(() => { + MockDate.reset(); + settings.updateLocale({weekStart: 1, yearStart: 1}); +}); + +describe('DateTimeUtc', () => { + describe('isDateTime', () => { + it('should return true in case of DateTime checking', () => { + const date = dateTimeUtc(); + const result = isDateTime(date); + expect(result).toEqual(true); + }); + }); + + describe('dateTimeUtc', () => { + it('should return now date in case of absence of input arg', () => { + const date = dateTimeUtc(); + const now = new Date(); + expect(date.toISOString()).toEqual(now.toISOString()); + }); + + it('should return DateTime with UTC timeZone', () => { + const zone = dateTimeUtc().timeZone(); + expect(zone).toEqual('UTC'); + }); + + it('should return 0 offset', () => { + const date = dateTimeUtc(); + const offset = date.utcOffset(); + expect(offset).toEqual(0); + }); + + test.each<[string, string]>([ + ['2023-12-31', '2023-12-31T00:00:00.000Z'], + ['2023-12-31T01:00', '2023-12-31T01:00:00.000Z'], + ['2023-12-31T01:00Z', '2023-12-31T01:00:00.000Z'], + ['2023-12-31T03:00+02:00', '2023-12-31T01:00:00.000Z'], + ])('input option (%p)', (input, expected) => { + const date = dateTimeUtc({input}).toISOString(); + expect(date).toEqual(expected); + }); + + test.each<[string, string, string]>([ + ['31.12.2023', 'DD.MM.YYYY', '2023-12-31T00:00:00.000Z'], + ['31.12.2023 01:00', 'DD.MM.YYYY HH:mm', '2023-12-31T01:00:00.000Z'], + ])('input (%p) format (%p)', (input, format, expected) => { + const date = dateTimeUtc({input, format}).toISOString(); + expect(date).toEqual(expected); + }); + }); +}); diff --git a/src/datemath/datemath.test.ts b/src/datemath/datemath.test.ts index 73af08a..31846c2 100644 --- a/src/datemath/datemath.test.ts +++ b/src/datemath/datemath.test.ts @@ -17,7 +17,7 @@ describe('DateMath', () => { const format = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; let clock: SinonFakeTimers; - describe('Ошибки', () => { + describe('Issues', () => { it('should return undefined if passed empty string', () => { expect(dateMath.parse('')).toBe(undefined); }); @@ -134,7 +134,7 @@ describe('DateMath', () => { }); }); - describe('Парс части относительной даты, начинающейся после now', () => { + describe('Parsing part after now', () => { it('should handle negative time', () => { const date = dateMath.parseDateMath('-2d', dateTime({input: [2014, 1, 5]})); expect(date?.valueOf()).toEqual(dateTime({input: [2014, 1, 3]}).valueOf()); diff --git a/src/datemath/datemath.ts b/src/datemath/datemath.ts index 6ea5d25..2c6e1ad 100644 --- a/src/datemath/datemath.ts +++ b/src/datemath/datemath.ts @@ -57,7 +57,7 @@ export function parse( mathString = text.substring(index + 2); } - time = dateTime({input: parseString}); + time = dateTime({input: parseString, timeZone}); } if (!mathString.length) { @@ -71,7 +71,7 @@ export function parse( } if (isDate(text)) { - return dateTime({input: text}); + return dateTime({input: text, timeZone}); } return undefined; diff --git a/src/dayjs/dayjs.d.ts b/src/dayjs/dayjs.d.ts new file mode 100644 index 0000000..6219890 --- /dev/null +++ b/src/dayjs/dayjs.d.ts @@ -0,0 +1,10 @@ +import 'dayjs'; + +declare module 'dayjs' { + interface ConfigTypeMap { + // to fix inconsistent typing between dayjs and DateTime + // dayjs expects [number?, number?, number?, number?, number?, number?, number?] + // but DateTime allows Array + dateTimeArray: Array; + } +} diff --git a/src/dayjs.ts b/src/dayjs/index.ts similarity index 100% rename from src/dayjs.ts rename to src/dayjs/index.ts diff --git a/src/index.ts b/src/index.ts index c022f28..231100f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ -export {dateTime, isDateTime} from './dateTime'; +export {dateTime, dateTimeUtc, isDateTime} from './dateTime'; export {isValid} from './datemath'; export {dateTimeParse} from './parser'; -export {getTimeZonesList, guessUserTimeZone} from './timeZone'; +export {getTimeZonesList, guessUserTimeZone, isValidTimeZone, timeZoneOffset} from './timeZone'; export type {DateTime, DateTimeInput} from './typings'; export {settings} from './settings'; +export {UtcTimeZone} from './constants';