UNPKG

@storm-stack/date-time

Version:

This package includes a DateTime class, various utility functions for working with dates and times, and a number of formatting options.

715 lines (714 loc) 23.6 kB
import { parse } from "@formkit/tempo"; import { Temporal } from "@js-temporal/polyfill"; import { Serializable } from "@storm-stack/serialization"; import { isBigInt, isDate, isNumber, isObject, isSet, isSetString, isString, MessageType } from "@storm-stack/types"; import { DATE_TIME_INVALID_DATE, DATE_TIME_MISSING_DATE, RFC_3339_DATE_TIME_REGEX } from "./constants.mjs"; import { DateTimeErrorCode } from "./errors.mjs"; import { isInstant } from "./utilities/is-instant.mjs"; import { validateDayOfMonth } from "./utilities/validate-day-of-month.mjs"; export function serializeStormDateTime(dateTime) { return dateTime.instant.toJSON(); } export function deserializeStormDateTime(utcString) { return isSetString(utcString) ? StormDateTime.create(utcString) : StormDateTime.create(); } @Serializable() class StormDateTime extends Date { /** * A helper function to get the default time zone * * @returns The default time zone */ static getDefaultTimeZone() { return Temporal.Now.timeZoneId() || process.env.STORM_TIMEZONE || process.env.DEFAULT_TIMEZONE || process.env.TZ || "UTC"; } /** * Type-check to determine if `obj` is a `DateTime` object * * `isDateTime` returns true if the object passed to it has a `_symbol` property that is equal to * `DATE_TIME_SYMBOL` * * @param obj - the object to check * @returns The function isDateTime is returning a boolean value. */ static isDateTime(obj) { return isDate(obj) && isSet(obj?.instant) && isSet(obj?.zonedDateTime) && isSetString(obj?.timeZoneId); } /** * The current function returns a new StormDateTime object with the current date and time * * @returns A new instance of StormDateTime with the current date and time. */ static now() { return StormDateTime.current().epochMilliseconds; } /** * The current function returns a new StormDateTime object with the current date and time * * @returns A new instance of StormDateTime with the current date and time. */ static current() { return StormDateTime.create(Temporal.Now.instant()); } /** * The maximum function returns a new StormDateTime object with the maximum date and time * * @returns A new instance of StormDateTime with the maximum date and time. */ static minimum() { return StormDateTime.create(/* @__PURE__ */ new Date(-864e13)); } /** * The maximum function returns a new StormDateTime object with the maximum date and time * * @returns A new instance of StormDateTime with the maximum date and time. */ static maximum() { return StormDateTime.create(/* @__PURE__ */ new Date(864e13)); } /** * Get the month index from the month name * * @example * StormDateTime.getMonthIndex(""); // -1 * StormDateTime.getMonthIndex("invalid"); // -1 * StormDateTime.getMonthIndex("January"); // 0 * StormDateTime.getMonthIndex("february"); // 1 * StormDateTime.getMonthIndex("JUNE"); // 5 * * @param month - The month name * @returns The month index */ static getMonthIndex(month) { if (month) { if (month.toLowerCase() === "january") { return 0; } else if (month.toLowerCase() === "february") { return 1; } else if (month.toLowerCase() === "march") { return 2; } else if (month.toLowerCase() === "april") { return 3; } else if (month.toLowerCase() === "may") { return 4; } else if (month.toLowerCase() === "june") { return 5; } else if (month.toLowerCase() === "july") { return 6; } else if (month.toLowerCase() === "august") { return 7; } else if (month.toLowerCase() === "september") { return 8; } else if (month.toLowerCase() === "october") { return 9; } else if (month.toLowerCase() === "november") { return 10; } else if (month.toLowerCase() === "december") { return 11; } } return -1; } /** * Validate the input date value * * @param dateTime - The date value to validate * @returns A boolean representing whether the value is a valid *date-time* */ static validate(value) { if ((isDate(value) || StormDateTime.isDateTime(value)) && value.toString() === DATE_TIME_INVALID_DATE) { return { code: DateTimeErrorCode.rfc_3339_format, type: MessageType.ERROR }; } if (StormDateTime.isDateTime(value)) { return value.validate(); } if (isInstant(value)) { if (value.epochMilliseconds) { return null; } return { code: DateTimeErrorCode.invalid_instant, type: MessageType.ERROR }; } let datetime; if (isDate(value) || isNumber(value) || isBigInt(value)) { const date = isNumber(value) || isBigInt(value) ? new Date(Number(value)) : value; if (Number.isNaN(date.getTime())) { return { code: DateTimeErrorCode.invalid_time, type: MessageType.ERROR }; } datetime = date.toISOString(); } else { datetime = value === null || value === void 0 ? void 0 : value.toUpperCase(); } if (!datetime) { return { code: DateTimeErrorCode.invalid_value, type: MessageType.ERROR }; } if (!RFC_3339_DATE_TIME_REGEX.test(datetime)) { return { code: DateTimeErrorCode.rfc_3339_format, type: MessageType.ERROR }; } if (!Date.parse(datetime)) { return { code: DateTimeErrorCode.rfc_3339_format, type: MessageType.ERROR }; } return validateDayOfMonth(StormDateTime.create(value)); } /** * Creates a new instance of StormDateTime from a string with a specified format. * * @param dateTime - The input value used to determine the current date and time * @param options - The options to use when creating the StormDateTime object * @returns A new instance of StormDateTime with the current date and time. */ static create(dateTime, options = {}) { return new StormDateTime(dateTime, { ...options, timeZone: StormDateTime.isDateTime(dateTime) ? dateTime.timeZoneId : options?.timeZone, calendar: StormDateTime.isDateTime(dateTime) ? dateTime.calendarId : options?.calendar }); } /** * A private accessor that stores the `Temporal.Instant` object of the DateTime object */ #instant = Temporal.Now.instant(); /** * A private accessor that stores the `Temporal.ZonedDateTime` object of the DateTime object */ #zonedDateTime = Temporal.Now.zonedDateTime( new Intl.DateTimeFormat().resolvedOptions().calendar, StormDateTime.getDefaultTimeZone() ); /** * A private accessor that stores the input value used to create the DateTime object */ #input; /** * A private accessor that stores the options used to create the DateTime object */ #options; constructor(dateTime, options = {}) { let _dateTime = dateTime; options.timeZone ??= StormDateTime.getDefaultTimeZone(); options.calendar ??= new Intl.DateTimeFormat().resolvedOptions().calendar; const input = dateTime; if (!_dateTime && options?.defaultToNow) { _dateTime = Temporal.Now.instant(); } const instant = _dateTime ? StormDateTime.isDateTime(_dateTime) ? _dateTime.instant : Temporal.Instant.from( isDate(_dateTime) ? _dateTime.toJSON() : isObject(_dateTime) && "epochMilliseconds" in _dateTime ? new Date(Number(_dateTime.epochMilliseconds)).toISOString() : isNumber(_dateTime) || isBigInt(_dateTime) ? new Date(Number(_dateTime)).toISOString() : isString(_dateTime) ? parse(_dateTime).toISOString() : _dateTime ) : void 0; super(instant ? Number(instant.epochMilliseconds) : DATE_TIME_MISSING_DATE); if (instant) { this.#instant = instant; this.#zonedDateTime = options?.calendar ? this.#instant.toZonedDateTime({ timeZone: options.timeZone, calendar: options.calendar }) : this.#instant.toZonedDateTimeISO(options.timeZone); } this.#input = input; this.#options = options; } /** * An accessor that returns the epoch milliseconds of the DateTime object */ get epochMilliseconds() { return this.instant.epochMilliseconds; } /** * An accessor that returns the `Temporal.Instant` object of the DateTime object */ get instant() { return this.#instant; } /** * An accessor that sets the `Temporal.Instant` object of the DateTime object */ set instant(instant) { this.#instant = instant; } /** * An accessor that returns the `Temporal.ZonedDateTime` object of the DateTime object */ get zonedDateTime() { return this.#zonedDateTime; } /** * An accessor that sets the `Temporal.ZonedDateTime` object of the DateTime object */ set zonedDateTime(zonedDateTime) { this.#zonedDateTime = zonedDateTime; } /** * An accessor that returns the `calendarId` string of the DateTime object */ get calendarId() { return this.#zonedDateTime.calendarId; } /** * An accessor that returns the `timeZoneId` string of the DateTime object */ get timeZoneId() { return this.#zonedDateTime.timeZoneId || StormDateTime.getDefaultTimeZone(); } /** * An accessor that returns the `valid` boolean of the DateTime object */ get valid() { return this.validate() === null; } /** * An accessor that returns the `invalid` boolean of the DateTime object */ get invalid() { return !this.valid; } /** * Returns the input value used to create the DateTime object */ get input() { return this.#input; } /** * Returns the options used to create the DateTime object */ get options() { return this.#options; } /** * A function that validates the current DateTime object * * @returns A ValidationDetails object if the DateTime object is invalid, otherwise null */ validate() { return StormDateTime.validate(this.#zonedDateTime.epochMilliseconds); } /** * Returns the stored time value in milliseconds since midnight, January 1, 1970 UTC. */ getTime() { return this.epochMilliseconds; } /** * Gets the year, using local time. */ getFullYear() { return this.#zonedDateTime.year; } /** * Gets the year using Universal Coordinated Time (UTC). */ getUTCFullYear() { return this.#instant.toZonedDateTimeISO("UTC").year; } /** * Gets the month, using local time. */ getMonth() { return this.#zonedDateTime.month; } /** * Gets the month of a Date object using Universal Coordinated Time (UTC). */ getUTCMonth() { return this.#instant.toZonedDateTimeISO("UTC").month; } /** * Gets the day-of-the-month, using local time. */ getDate() { return this.#zonedDateTime.day; } /** * Gets the day-of-the-month, using Universal Coordinated Time (UTC). */ getUTCDate() { return this.#instant.toZonedDateTimeISO("UTC").day; } /** * Gets the day of the week, using local time. */ getDay() { return this.#zonedDateTime.dayOfWeek; } /** * Gets the day of the week using Universal Coordinated Time (UTC). */ getUTCDay() { return this.#instant.toZonedDateTimeISO("UTC").dayOfWeek; } /** * Gets the hours in a date, using local time. */ getHours() { return this.#zonedDateTime.hour; } /** * Gets the hours value in a Date object using Universal Coordinated Time (UTC). */ getUTCHours() { return this.#instant.toZonedDateTimeISO("UTC").hour; } /** * Gets the minutes of a Date object, using local time. */ getMinutes() { return this.#zonedDateTime.minute; } /** * Gets the minutes of a Date object using Universal Coordinated Time (UTC). */ getUTCMinutes() { return this.#instant.toZonedDateTimeISO("UTC").minute; } /** * Gets the seconds of a Date object, using local time. */ getSeconds() { return this.#zonedDateTime.second; } /** * Gets the seconds of a Date object using Universal Coordinated Time (UTC). */ getUTCSeconds() { return this.#instant.toZonedDateTimeISO("UTC").second; } /** * Gets the milliseconds of a Date, using local time. */ getMilliseconds() { return this.#zonedDateTime.millisecond; } /** * Gets the milliseconds of a Date object using Universal Coordinated Time (UTC). */ getUTCMilliseconds() { return this.#instant.toZonedDateTimeISO("UTC").millisecond; } /** * Gets the difference in minutes between the time on the local computer and Universal Coordinated Time (UTC). */ getTimezoneOffset() { return this.#zonedDateTime.offsetNanoseconds / 1e6; } /** * Sets the date and time value in the Date object. * @param time - A numeric value representing the number of elapsed milliseconds since midnight, January 1, 1970 GMT. */ setTime(time) { this.#zonedDateTime = this.#zonedDateTime.add({ milliseconds: time - this.epochMilliseconds }); this.#instant = this.#zonedDateTime.toInstant(); return super.setTime(this.#instant.epochMilliseconds); } /** * Sets the milliseconds value in the Date object using local time. * @param millisecond - A numeric value equal to the millisecond value. */ setMilliseconds(millisecond) { this.#zonedDateTime = this.#zonedDateTime.with({ millisecond }); this.#instant = this.#zonedDateTime.toInstant(); return super.setMilliseconds( this.#instant.toZonedDateTimeISO("UTC").millisecond ); } /** * Sets the milliseconds value in the Date object using Universal Coordinated Time (UTC). * @param millisecond - A numeric value equal to the millisecond value. */ setUTCMilliseconds(millisecond) { this.#instant = this.#instant.toZonedDateTimeISO("UTC").with({ millisecond }).toInstant(); this.#zonedDateTime = this.#instant.toZonedDateTime({ timeZone: this.timeZoneId, calendar: this.calendarId }); return super.setUTCMilliseconds( this.#instant.toZonedDateTimeISO("UTC").millisecond ); } /** * Sets the seconds value in the Date object using local time. * @param second - A numeric value equal to the seconds value. * @param millisecond - A numeric value equal to the milliseconds value. */ setSeconds(second, millisecond) { this.#zonedDateTime = this.#zonedDateTime.with({ second, millisecond }); this.#instant = this.#zonedDateTime.toInstant(); return super.setSeconds( this.#zonedDateTime.second, this.#zonedDateTime.millisecond ); } /** * Sets the seconds value in the Date object using Universal Coordinated Time (UTC). * @param second - A numeric value equal to the seconds value. * @param millisecond - A numeric value equal to the milliseconds value. */ setUTCSeconds(second, millisecond) { this.#instant = this.#instant.toZonedDateTimeISO("UTC").with({ second, millisecond }).toInstant(); this.#zonedDateTime = this.#instant.toZonedDateTime({ timeZone: this.timeZoneId, calendar: this.calendarId }); const utcDateTime = this.#instant.toZonedDateTimeISO("UTC"); return super.setUTCSeconds(utcDateTime.second, utcDateTime.millisecond); } /** * Sets the minutes value in the Date object using local time. * @param minute - A numeric value equal to the minutes value. * @param second - A numeric value equal to the seconds value. * @param millisecond - A numeric value equal to the milliseconds value. */ setMinutes(minute, second, millisecond) { this.#zonedDateTime = this.#zonedDateTime.with({ minute, second, millisecond }); this.#instant = this.#zonedDateTime.toInstant(); return super.setMinutes( this.#zonedDateTime.minute, this.#zonedDateTime.second, this.#zonedDateTime.millisecond ); } /** * Sets the minutes value in the Date object using Universal Coordinated Time (UTC). * @param minute - A numeric value equal to the minutes value. * @param second - A numeric value equal to the seconds value. * @param millisecond - A numeric value equal to the milliseconds value. */ setUTCMinutes(minute, second, millisecond) { this.#instant = this.#instant.toZonedDateTimeISO("UTC").with({ minute, second, millisecond }).toInstant(); this.#zonedDateTime = this.#instant.toZonedDateTime({ timeZone: this.timeZoneId, calendar: this.calendarId }); const utcDateTime = this.#instant.toZonedDateTimeISO("UTC"); return super.setUTCMinutes( utcDateTime.minute, utcDateTime.second, utcDateTime.millisecond ); } /** * Sets the hour value in the Date object using local time. * * @param hour - A numeric value equal to the hours value. * @param minute - A numeric value equal to the minutes value. * @param second - A numeric value equal to the seconds value. * @param millisecond - A numeric value equal to the milliseconds value. */ setHours(hour, minute, second, millisecond) { this.#zonedDateTime = this.#zonedDateTime.with({ hour, minute, second, millisecond }); this.#instant = this.#zonedDateTime.toInstant(); return super.setHours( this.#zonedDateTime.hour, this.#zonedDateTime.minute, this.#zonedDateTime.second, this.#zonedDateTime.millisecond ); } /** * Sets the hours value in the Date object using Universal Coordinated Time (UTC). * * @param hour - A numeric value equal to the hours value. * @param minute - A numeric value equal to the minutes value. * @param second - A numeric value equal to the seconds value. * @param millisecond - A numeric value equal to the milliseconds value. */ setUTCHours(hour, minute, second, millisecond) { this.#instant = this.#instant.toZonedDateTimeISO("UTC").with({ hour, minute, second, millisecond }).toInstant(); this.#zonedDateTime = this.#instant.toZonedDateTime({ timeZone: this.timeZoneId, calendar: this.calendarId }); const utcDateTime = this.#instant.toZonedDateTimeISO("UTC"); return super.setUTCHours( utcDateTime.hour, utcDateTime.minute, utcDateTime.second, utcDateTime.millisecond ); } /** * Sets the numeric day-of-the-month value of the Date object using local time. * * @param day - A numeric value equal to the day of the month. */ setDate(day) { this.#zonedDateTime = this.#zonedDateTime.with({ day }); this.#instant = this.#zonedDateTime.toInstant(); return super.setDate(this.#zonedDateTime.day); } /** * Sets the numeric day of the month in the Date object using Universal Coordinated Time (UTC). * * @param day - A numeric value equal to the day of the month. */ setUTCDate(day) { this.#instant = this.#instant.toZonedDateTimeISO("UTC").with({ day }).toInstant(); this.#zonedDateTime = this.#instant.toZonedDateTime({ timeZone: this.timeZoneId, calendar: this.calendarId }); return super.setUTCDate(this.#instant.toZonedDateTimeISO("UTC").day); } /** * Sets the month value in the Date object using local time. * * @param month - A numeric value equal to the month. The value for January is 0, and other month values follow consecutively. * @param day - A numeric value representing the day of the month. If this value is not supplied, the value from a call to the getDate method is used. */ setMonth(month, day) { this.#zonedDateTime = this.#zonedDateTime.with({ month, day }); this.#instant = this.#zonedDateTime.toInstant(); return super.setMonth(this.#zonedDateTime.month, this.#zonedDateTime.day); } /** * Sets the month value in the Date object using Universal Coordinated Time (UTC). * * @param month - A numeric value equal to the month. The value for January is 0, and other month values follow consecutively. * @param day - A numeric value representing the day of the month. If it is not supplied, the value from a call to the getUTCDate method is used. */ setUTCMonth(month, day) { this.#instant = this.#instant.toZonedDateTimeISO("UTC").with({ month, day }).toInstant(); this.#zonedDateTime = this.#instant.toZonedDateTime({ timeZone: this.timeZoneId, calendar: this.calendarId }); const utcDateTime = this.#instant.toZonedDateTimeISO("UTC"); return super.setUTCMonth(utcDateTime.month, utcDateTime.day); } /** * Sets the year of the Date object using local time. * @param year - A numeric value for the year. * @param month - A zero-based numeric value for the month (0 for January, 11 for December). Must be specified if numDate is specified. * @param day - A numeric value equal for the day of the month. */ setFullYear(year, month, day) { this.#zonedDateTime = this.#zonedDateTime.with({ year, month, day }); this.#instant = this.#zonedDateTime.toInstant(); return super.setFullYear( this.#zonedDateTime.year, this.#zonedDateTime.month, this.#zonedDateTime.day ); } /** * Sets the year value in the Date object using Universal Coordinated Time (UTC). * * @param year - A numeric value equal to the year. * @param month - A numeric value equal to the month. The value for January is 0, and other month values follow consecutively. Must be supplied if numDate is supplied. * @param day - A numeric value equal to the day of the month. */ setUTCFullYear(year, month, day) { this.#instant = this.#instant.toZonedDateTimeISO("UTC").with({ year, month, day }).toInstant(); this.#zonedDateTime = this.#instant.toZonedDateTime({ timeZone: this.timeZoneId, calendar: this.calendarId }); const utcDateTime = this.#instant.toZonedDateTimeISO("UTC"); return super.setUTCFullYear( utcDateTime.year, utcDateTime.month, utcDateTime.day ); } /** * It returns a plain date object from a DateTime object * * @returns A PlainDate object. */ getPlainDate() { return StormDateTime.create( this.#zonedDateTime.toPlainDate().toZonedDateTime({ timeZone: StormDateTime.getDefaultTimeZone(), plainTime: void 0 }).epochMilliseconds, { timeZone: this.#zonedDateTime.timeZoneId, calendar: this.#zonedDateTime.calendarId } ); } /** * `getPlainTime` returns a `PlainTime` object from a `DateTime` object * * @returns A PlainTime object. */ getPlainTime() { return StormDateTime.create( this.#zonedDateTime.toPlainTime().toZonedDateTime({ timeZone: StormDateTime.getDefaultTimeZone(), plainDate: Temporal.PlainDate.from({ year: 1970, month: 0, day: 1 }) }).epochMilliseconds, { timeZone: this.#zonedDateTime.timeZoneId, calendar: this.#zonedDateTime.calendarId } ); } /** * It returns the duration between two dates. * * @param dateTimeTo - DateTime = DateTime.current * @returns A duration object. */ since(dateTimeTo = StormDateTime.current()) { return this.#instant.since(dateTimeTo.instant); } /** * It returns the duration between two date times. * * @param dateTimeTo - DateTime = DateTime.current * @returns A duration object. */ getDuration(dateTimeTo = StormDateTime.current()) { return this.instant.since(dateTimeTo.instant); } } export { StormDateTime };