@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
JavaScript
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();
}
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 };