From 8b8d1325d82e3b498523beba536484b6501f856a Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Mon, 30 Sep 2024 13:32:52 +0200 Subject: [PATCH] fix(dateTime): calculate ralative time via duration (#75) --- src/dateTime/dateTime.ts | 42 ++++++++++++++++++++++++++++++++++++++-- src/dateTime/relative.ts | 11 ++++------- src/duration/duration.ts | 8 ++++---- src/utils/locale.ts | 11 +++++++++++ 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/dateTime/dateTime.ts b/src/dateTime/dateTime.ts index a15cc64..a6ee1e2 100644 --- a/src/dateTime/dateTime.ts +++ b/src/dateTime/dateTime.ts @@ -377,11 +377,49 @@ class DateTimeImpl implements DateTime { fromNow(withoutSuffix?: boolean | undefined): string { return this.from(dateTime({timeZone: this._timeZone, lang: this._locale}), withoutSuffix); } - from(formaInput: DateTimeInput, withoutSuffix?: boolean): string { + from(fromInput: DateTimeInput, withoutSuffix?: boolean): string { if (!this.isValid()) { return this._localeData.invalidDate || INVALID_DATE_STRING; } - return fromTo(this, formaInput, this._localeData.relativeTime, withoutSuffix, true); + + const value = DateTimeImpl.isDateTime(fromInput) + ? fromInput.timeZone(this._timeZone) + : createDateTime({ + ts: getTimestamp(fromInput, 'system', this._locale)[0], + timeZone: this._timeZone, + locale: this._locale, + offset: this._offset, + }); + + if (!value.isValid()) { + return this._localeData.invalidDate || INVALID_DATE_STRING; + } + + let a = value; + let b: DateTime = this; + let switched = false; + if (b.isBefore(a)) { + a = this; + b = value; + switched = true; + } + + let months = b.month() - a.month() + (b.year() - a.year()) * 12; + if (a.add(months, 'months').isAfter(b)) { + months--; + } + let milliseconds = b.valueOf() - a.add(months, 'months').valueOf(); + + if (switched) { + months = -months; + milliseconds = -milliseconds; + } + + return fromTo( + duration({months, milliseconds}), + this._localeData.relativeTime, + withoutSuffix, + ); } locale(): string; locale(locale: string): DateTime; diff --git a/src/dateTime/relative.ts b/src/dateTime/relative.ts index 9a97510..8cdb373 100644 --- a/src/dateTime/relative.ts +++ b/src/dateTime/relative.ts @@ -1,7 +1,5 @@ import type {Locale, RelativeTime} from '../settings/types'; -import type {BaseUnit, DateTime, DateTimeInput} from '../typings'; - -import {dateTime} from './dateTime'; +import type {BaseUnit, Duration} from '../typings'; export interface RelativeTimeThreshold { l: Exclude; @@ -40,19 +38,18 @@ const relObj = { } satisfies RelativeTime; export function fromTo( - date: DateTime, - input: DateTimeInput, + duration: Duration, loc: Locale['relativeTime'] = relObj, withoutSuffix = false, - isFrom = true, ): string { let result = 0; let isFuture; let out = ''; + for (let i = 0; i < thresholds.length; i += 1) { let t = thresholds[i]; if (t.d) { - result = isFrom ? date.diff(input, t.d, true) : dateTime({input}).diff(date, t.d, true); + result = duration.as(t.d); } const abs = Math.round(Math.abs(result)); isFuture = result > 0; diff --git a/src/duration/duration.ts b/src/duration/duration.ts index 4926ebf..45e848e 100644 --- a/src/duration/duration.ts +++ b/src/duration/duration.ts @@ -1,7 +1,7 @@ // Copyright 2019 JS Foundation and other contributors // Copyright 2024 YANDEX LLC -import {dateTimeUtc} from '../dateTime'; +import {fromTo} from '../dateTime/relative'; import {settings} from '../settings'; import type { Duration, @@ -11,7 +11,7 @@ import type { FormatOptions, } from '../typings'; import {normalizeDateComponents, normalizeDurationUnit} from '../utils'; -import {getListFormat, getNumberFormat} from '../utils/locale'; +import {getListFormat, getLocaleData, getNumberFormat} from '../utils/locale'; import {createDuration} from './createDuration'; import {normalizeValues, orderedUnits, rescale, shiftTo} from './normalize'; @@ -294,8 +294,8 @@ export class DurationImpl implements Duration { if (!this.isValid()) { return 'Invalid Duration'; } - const now = dateTimeUtc({lang: this._locale}); - return now.add(this.valueOf(), 'ms').from(now, !withSuffix); + const localeData = getLocaleData(this._locale); + return fromTo(this, localeData.relativeTime, !withSuffix); } humanizeIntl( diff --git a/src/utils/locale.ts b/src/utils/locale.ts index 1cc51e7..6c04d23 100644 --- a/src/utils/locale.ts +++ b/src/utils/locale.ts @@ -1,3 +1,6 @@ +import dayjs from '../dayjs'; +import type {Locale} from '../settings/types'; + const dateTimeFormatCache = new Map(); export function getDateTimeFormat(locale: string, options: Intl.DateTimeFormatOptions = {}) { const key = JSON.stringify([locale, options]); @@ -33,3 +36,11 @@ export function getNumberFormat(locale: string, options: Intl.NumberFormatOption } return numberFormat; } + +export function getLocaleData(locale: string) { + const localeData = dayjs.Ls[locale]; + if (!localeData) { + throw new Error(`Locale ${locale} is not loaded`); + } + return localeData as Locale; +}