Skip to content

Commit

Permalink
fix(DateTime): add missing formats and show ordinal values without br…
Browse files Browse the repository at this point in the history
…ackets (#66)
  • Loading branch information
ValeraS authored May 27, 2024
1 parent bfc7913 commit dd9d942
Show file tree
Hide file tree
Showing 12 changed files with 812 additions and 86 deletions.
7 changes: 6 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
"jest": true
},
"rules": {
"valid-jsdoc": 0
"valid-jsdoc": 0,
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
"@typescript-eslint/consistent-type-imports": [
"error",
{"prefer": "type-imports", "fixStyle": "separate-type-imports"}
]
}
}
12 changes: 12 additions & 0 deletions src/constants/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,15 @@ export const englishFormats = {
LLL: 'MMMM D, YYYY h:mm A',
LLLL: 'dddd, MMMM D, YYYY h:mm A',
} as const satisfies LongDateFormat;

export const HTML5_INPUT_FORMATS = {
DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" />
DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" />
DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" />
DATE: 'YYYY-MM-DD', // <input type="date" />
TIME: 'HH:mm', // <input type="time" />
TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" />
TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" />
WEEK: 'GGGG-[W]WW', // <input type="week" />
MONTH: 'YYYY-MM', // <input type="month" />
} as const;
344 changes: 344 additions & 0 deletions src/dateTime/__tests__/format.ts

Large diffs are not rendered by default.

263 changes: 263 additions & 0 deletions src/dateTime/__tests__/weekYear.ts

Large diffs are not rendered by default.

105 changes: 80 additions & 25 deletions src/dateTime/dateTime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
tsToObject,
uncomputeOrdinal,
weekToGregorian,
weeksInWeekYear,
} from '../utils';
import type {DateObject} from '../utils';

Expand Down Expand Up @@ -85,8 +86,11 @@ class DateTimeImpl implements DateTime {
}

toISOString(keepOffset?: boolean): string {
// invalid date throws an error
if (keepOffset) {
return this.format('YYYY-MM-DDTHH:mm:ss.SSSZ');
return new Date(this.valueOf() + this.utcOffset() * 60 * 1000)
.toISOString()
.replace('Z', this.format('Z'));
}
return this.toDate().toISOString();
}
Expand Down Expand Up @@ -132,6 +136,10 @@ class DateTimeImpl implements DateTime {
return this._timeZone === 'system' ? guessUserTimeZone() : this._timeZone;
}

if (!this.isValid()) {
return this;
}

const zone = normalizeTimeZone(timeZone, settings.getDefaultTimeZone());
let ts = this.valueOf();
let offset = timeZoneOffset(zone, ts);
Expand Down Expand Up @@ -269,19 +277,19 @@ class DateTimeImpl implements DateTime {
}

valueOf(): number {
return this._timestamp;
return this.isValid() ? this._timestamp : NaN;
}

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

isAfter(input?: DateTimeInput, granularity?: DurationUnit): boolean {
const ts = getTimestamp(input);
const [ts] = getTimestamp(input, 'system');
if (!this.isValid() || isNaN(ts)) {
return false;
}
Expand All @@ -315,7 +323,7 @@ class DateTimeImpl implements DateTime {
const value = DateTimeImpl.isDateTime(amount)
? amount.timeZone(this._timeZone)
: createDateTime({
ts: getTimestamp(amount),
ts: getTimestamp(amount, 'system')[0],
timeZone: this._timeZone,
locale: this._locale,
offset: this._offset,
Expand Down Expand Up @@ -371,7 +379,7 @@ class DateTimeImpl implements DateTime {
}
from(formaInput: DateTimeInput, withoutSuffix?: boolean): string {
if (!this.isValid()) {
return INVALID_DATE_STRING;
return this._localeData.invalidDate || INVALID_DATE_STRING;
}
return fromTo(this, formaInput, this._localeData.relativeTime, withoutSuffix, true);
}
Expand All @@ -381,6 +389,9 @@ class DateTimeImpl implements DateTime {
if (!locale) {
return this._locale;
}
if (!this.isValid()) {
return this;
}
return createDateTime({
ts: this.valueOf(),
timeZone: this._timeZone,
Expand All @@ -392,13 +403,13 @@ class DateTimeImpl implements DateTime {
return new Date(this.valueOf());
}
unix(): number {
return Math.floor(this.valueOf() / 1000);
return this.isValid() ? Math.floor(this.valueOf() / 1000) : NaN;
}
utc(keepLocalTime?: boolean | undefined): DateTime {
return this.timeZone(UtcTimeZone, keepLocalTime);
}
daysInMonth(): number {
return daysInMonth(this._c.year, this._c.month);
return this.isValid() ? daysInMonth(this._c.year, this._c.month) : NaN;
}

// eslint-disable-next-line complexity
Expand All @@ -415,9 +426,11 @@ class DateTimeImpl implements DateTime {
const settingWeekStuff =
newComponents.day !== undefined ||
newComponents.weekNumber !== undefined ||
newComponents.weekYear !== undefined ||
newComponents.isoWeekNumber !== undefined ||
newComponents.weekday !== undefined ||
newComponents.isoWeekday !== undefined;
newComponents.isoWeekday !== undefined ||
newComponents.isoWeekYear !== undefined;

const containsDayOfYear = newComponents.dayOfYear !== undefined;
const containsYear = newComponents.year !== undefined;
Expand All @@ -435,10 +448,15 @@ class DateTimeImpl implements DateTime {

let mixed;
if (settingWeekStuff) {
const {weekday, weekNumber, isoWeekday, isoWeekNumber, day} = newComponents;
const hasLocalWeekData = weekday !== undefined || weekNumber !== undefined;
const {weekday, weekNumber, weekYear, isoWeekday, isoWeekNumber, isoWeekYear, day} =
newComponents;
const hasLocalWeekData =
weekday !== undefined || weekNumber !== undefined || weekYear !== undefined;
const hasIsoWeekData =
isoWeekday !== undefined || isoWeekNumber !== undefined || day !== undefined;
isoWeekday !== undefined ||
isoWeekNumber !== undefined ||
isoWeekYear !== undefined ||
day !== undefined;
if (hasLocalWeekData && hasIsoWeekData) {
throw new Error("Can't mix local week with ISO week");
}
Expand All @@ -448,7 +466,7 @@ class DateTimeImpl implements DateTime {
const weekData = {
weekday: (weekday ?? weekInfo.weekday) + 1,
weekNumber: weekNumber ?? weekInfo.weekNumber,
weekYear: weekInfo.weekYear,
weekYear: weekYear ?? weekInfo.weekYear,
};
mixed = {
...dateComponents,
Expand All @@ -459,7 +477,7 @@ class DateTimeImpl implements DateTime {
const weekData = {
weekday: isoWeekday ?? (day === undefined ? weekInfo.isoWeekday : day || 7),
weekNumber: isoWeekNumber ?? weekInfo.isoWeekNumber,
weekYear: weekInfo.isoWeekYear,
weekYear: isoWeekYear ?? weekInfo.isoWeekYear,
};
mixed = {...dateComponents, ...newComponents, ...weekToGregorian(weekData, 4, 1)};
}
Expand Down Expand Up @@ -589,6 +607,18 @@ class DateTimeImpl implements DateTime {
}
return this.isValid() ? this.weekInfo().weekNumber : NaN;
}
weekYear(): number;
weekYear(value: number): DateTime;
weekYear(value?: unknown): number | DateTime {
if (typeof value === 'number') {
return this.set('weekYear', value);
}
return this.isValid() ? this.weekInfo().weekYear : NaN;
}
weeksInYear(): number {
const {minDaysInFirstWeek, startOfWeek} = getLocaleWeekValues(this._localeData);
return this.isValid() ? weeksInWeekYear(this.year(), minDaysInFirstWeek, startOfWeek) : NaN;
}
isoWeek(): number;
isoWeek(value: number): DateTime;
isoWeek(value?: number): number | DateTime {
Expand All @@ -597,6 +627,17 @@ class DateTimeImpl implements DateTime {
}
return this.isValid() ? this.weekInfo().isoWeekNumber : NaN;
}
isoWeekYear(): number;
isoWeekYear(value: number): DateTime;
isoWeekYear(value?: unknown): number | DateTime {
if (typeof value === 'number') {
return this.set('isoWeekYear', value);
}
return this.isValid() ? this.weekInfo().isoWeekYear : NaN;
}
isoWeeksInYear(): number {
return this.isValid() ? weeksInWeekYear(this.year(), 4, 1) : NaN;
}
weekday(): number;
weekday(value: number): DateTime;
weekday(value?: number): number | DateTime {
Expand All @@ -616,15 +657,22 @@ class DateTimeImpl implements DateTime {
}

toString(): string {
return this.toDate().toUTCString();
return this.isValid()
? this.toDate().toUTCString()
: this._localeData.invalidDate || INVALID_DATE_STRING;
}

toJSON(): string | null {
return this.isValid() ? this.toISOString() : null;
}

/**
* Returns a string representation of this DateTime appropriate for the REPL.
* @return {string}
*/
[Symbol.for('nodejs.util.inspect.custom')]() {
if (this.isValid()) {
return `DateTime { ts: ${this.toISOString()}, zone: ${this.timeZone()}, locale: ${this.locale()} }`;
return `DateTime { ts: ${this.toISOString()}, zone: ${this.timeZone()}, offset: ${this.utcOffset()}, locale: ${this.locale()} }`;
} else {
return `DateTime { ${INVALID_DATE_STRING} }`;
}
Expand Down Expand Up @@ -713,16 +761,23 @@ function createDateTime({
return new DateTimeImpl({ts, timeZone, offset, locale: loc, localeData, isValid});
}

function getTimestamp(input: DateTimeInput, format?: string, lang?: string, utc = false) {
function getTimestamp(
input: DateTimeInput,
timezone: string,
format?: string,
lang?: string,
utc = false,
): [ts: number, offset: number] {
let ts: number;
let offset: number | undefined;
if (isDateTime(input) || typeof input === 'number' || input instanceof Date) {
ts = Number(input);
} else if (input === null || input === undefined) {
ts = Date.now();
} else if (Array.isArray(input)) {
ts = getTimestampFromArray(input, utc);
[ts, offset] = getTimestampFromArray(input, timezone);
} else if (typeof input === 'object') {
ts = getTimestampFromObject(input, utc);
[ts, offset] = getTimestampFromObject(input, timezone);
} else if (utc) {
ts = dayjs.utc(input, format, STRICT).valueOf();
} else {
Expand All @@ -733,7 +788,9 @@ function getTimestamp(input: DateTimeInput, format?: string, lang?: string, utc

ts = localDate.valueOf();
}
return ts;

offset = offset ?? timeZoneOffset(timezone, ts);
return [ts, offset];
}

/**
Expand Down Expand Up @@ -763,9 +820,7 @@ export function dateTime(opt?: {
const timeZoneOrDefault = normalizeTimeZone(timeZone, settings.getDefaultTimeZone());
const locale = dayjs.locale(lang || settings.getLocale(), undefined, true);

const ts = getTimestamp(input, format, lang);

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

const date = createDateTime({
ts,
Expand All @@ -782,7 +837,7 @@ export function dateTimeUtc(opt?: {input?: DateTimeInput; format?: FormatInput;

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

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

const date = createDateTime({
ts,
Expand Down
Loading

0 comments on commit dd9d942

Please sign in to comment.