Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: parse well known date strings #81

Merged
merged 2 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 59 additions & 25 deletions src/dateTime/dateTime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ import {
weeksInWeekYear,
} from '../utils';
import type {DateObject} from '../utils';
import {getLocaleData, getLocaleWeekValues} from '../utils/locale';

import {formatDate} from './format';
import {getTimestampFromArray, getTimestampFromObject} from './parse';
import {parseDateString} from './regexParse';
import {fromTo} from './relative';

const IS_DATE_TIME = Symbol('isDateTime');
Expand Down Expand Up @@ -281,15 +283,15 @@ class DateTimeImpl implements DateTime {
}

isSame(input?: DateTimeInput, granularity?: DurationUnit): boolean {
const [ts] = getTimestamp(input, 'system');
const [ts] = getTimestamp(input, 'system', this._locale);
if (!this.isValid() || isNaN(ts)) {
return false;
}
return !this.isBefore(ts, granularity) && !this.isAfter(ts, granularity);
}

isBefore(input?: DateTimeInput, granularity?: DurationUnit): boolean {
const [ts] = getTimestamp(input, 'system');
const [ts] = getTimestamp(input, 'system', this._locale);
if (!this.isValid() || isNaN(ts)) {
return false;
}
Expand All @@ -299,7 +301,7 @@ class DateTimeImpl implements DateTime {
}

isAfter(input?: DateTimeInput, granularity?: DurationUnit): boolean {
const [ts] = getTimestamp(input, 'system');
const [ts] = getTimestamp(input, 'system', this._locale);
if (!this.isValid() || isNaN(ts)) {
return false;
}
Expand All @@ -323,7 +325,7 @@ class DateTimeImpl implements DateTime {
const value = DateTimeImpl.isDateTime(amount)
? amount.timeZone(this._timeZone)
: createDateTime({
ts: getTimestamp(amount, 'system')[0],
ts: getTimestamp(amount, 'system', this._locale)[0],
timeZone: this._timeZone,
locale: this._locale,
offset: this._offset,
Expand Down Expand Up @@ -772,11 +774,6 @@ class DateTimeImpl implements DateTime {
}
}

function getLocaleWeekValues(localeData: {yearStart?: number; weekStart?: number}) {
const {weekStart, yearStart} = localeData;
return {startOfWeek: weekStart || 7, minDaysInFirstWeek: yearStart || 1};
}

function absRound(v: number) {
const sign = Math.sign(v);
return Math.round(sign * v) * sign;
Expand Down Expand Up @@ -817,20 +814,27 @@ function createDateTime({
locale: string;
}): DateTime {
const loc = locale || 'en';
const localeData = dayjs.Ls[loc] as Locale;
const localeData = getLocaleData(loc);
const isValid = !isNaN(Number(new Date(ts)));
return new DateTimeImpl({ts, timeZone, offset, locale: loc, localeData, isValid});
return new DateTimeImpl({
ts,
timeZone,
offset,
locale: loc,
localeData,
isValid,
});
}

function getTimestamp(
input: DateTimeInput,
timezone: string,
locale: string,
format?: string,
lang?: string,
utc = false,
fixedOffset?: number,
): [ts: number, offset: number] {
let ts: number;
let offset: number | undefined;
let offset = fixedOffset;
if (
isDateTime(input) ||
typeof input === 'number' ||
Expand All @@ -841,18 +845,35 @@ function getTimestamp(
} else if (input === null || input === undefined) {
ts = Date.now();
} else if (Array.isArray(input)) {
[ts, offset] = getTimestampFromArray(input, timezone);
[ts, offset] = getTimestampFromArray(input, timezone, fixedOffset);
} else if (typeof input === 'object') {
[ts, offset] = getTimestampFromObject(input, timezone);
} else if (utc) {
ts = dayjs.utc(input, format, STRICT).valueOf();
} else {
const locale = dayjs.locale(lang || settings.getLocale(), undefined, true);
[ts, offset] = getTimestampFromObject(input, timezone, fixedOffset);
} else if (format === undefined) {
const [dateObject, timezoneOrOffset] = parseDateString(input);
if (Object.keys(dateObject).length === 0) {
return [NaN, NaN];
}
[ts] = getTimestampFromObject(
dateObject,
typeof timezoneOrOffset === 'string' ? timezoneOrOffset : 'system',
typeof timezoneOrOffset === 'number' ? timezoneOrOffset : fixedOffset,
);
if (
fixedOffset !== undefined &&
timezoneOrOffset !== null &&
timezoneOrOffset !== fixedOffset
) {
ts -= fixedOffset * 60 * 1000;
}
} else if (fixedOffset === undefined) {
const localDate = format
? dayjs(input, format, locale, STRICT)
: dayjs(input, undefined, locale);

ts = localDate.valueOf();
} else {
ts = dayjs.utc(input, format, STRICT).valueOf();
ts -= fixedOffset * 60 * 1000;
}

offset = offset ?? timeZoneOffset(timezone, ts);
Expand Down Expand Up @@ -886,7 +907,7 @@ export function dateTime(opt?: {
const timeZoneOrDefault = normalizeTimeZone(timeZone, settings.getDefaultTimeZone());
const locale = dayjs.locale(lang || settings.getLocale(), undefined, true);

const [ts, offset] = getTimestamp(input, timeZoneOrDefault, format, lang);
const [ts, offset] = getTimestamp(input, timeZoneOrDefault, locale, format);

const date = createDateTime({
ts,
Expand All @@ -898,17 +919,30 @@ export function dateTime(opt?: {
return date;
}

export function dateTimeUtc(opt?: {input?: DateTimeInput; format?: FormatInput; lang?: string}) {
const {input, format, lang} = opt || {};
/**
* Creates a DateTime instance with fixed offset.
* @param [opt]
* @param {DateTimeInput=} [opt.input] - input to parse.
* @param {string=} [opt.format] - strict {@link https://dayjs.gitee.io/docs/en/display/format format} for parsing user's input.
* @param {number=} [opt.offset=0] - specified offset.
* @param {string=} [opt.lang] - specified locale.
*/
export function dateTimeUtc(opt?: {
input?: DateTimeInput;
format?: FormatInput;
lang?: string;
offset?: number;
}): DateTime {
const {input, format, lang, offset = 0} = opt || {};

const locale = dayjs.locale(lang || settings.getLocale(), undefined, true);

const [ts] = getTimestamp(input, UtcTimeZone, format, lang, true);
const [ts] = getTimestamp(input, UtcTimeZone, locale, format, offset);

const date = createDateTime({
ts,
timeZone: UtcTimeZone,
offset: 0,
offset,
locale,
});

Expand Down
18 changes: 18 additions & 0 deletions src/dateTime/dateTimeUtc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,23 @@ describe('DateTimeUtc', () => {
const date = dateTimeUtc({input, format}).toISOString();
expect(date).toEqual(expected);
});

test.each<[string, string]>([
['2023-12-31', '2023-12-31T00:00:00.000+02:30'],
['2023-12-31T01:00', '2023-12-31T01:00:00.000+02:30'],
['2023-12-31T01:00Z', '2023-12-31T01:00:00.000+02:30'],
['2023-12-31T03:00+02:00', '2023-12-31T01:00:00.000+02:30'],
])('input option (%p) with offset', (input, expected) => {
const date = dateTimeUtc({input, offset: 150}).toISOString(true);
expect(date).toEqual(expected);
});

test.each<[string, string, string]>([
['31.12.2023', 'DD.MM.YYYY', '2023-12-31T00:00:00.000+02:30'],
['31.12.2023 01:00', 'DD.MM.YYYY HH:mm', '2023-12-31T01:00:00.000+02:30'],
])('input (%p) format (%p) with offset', (input, format, expected) => {
const date = dateTimeUtc({input, format, offset: 150}).toISOString(true);
expect(date).toEqual(expected);
});
});
});
Loading