Skip to main content
Module

x/ptera/datetime.ts

Ptera is DateTime library for Deno
Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
import { adjustedTS } from "./diff.ts";import { formatDate, formatDateObj } from "./format.ts";import { getLocalName } from "./local_time.ts";import { tzOffset } from "./timezone.ts";import { toOtherZonedTime, zonedTimeToUTC } from "./zoned_time.ts";import { Locale } from "./locale.ts";import { parseDateStr, parseISO } from "./parse_date.ts";import { arrayToDate, dateToArray, dateToDayOfYear, dateToJSDate, dateToTS, dateToWeekDay, tsToDate,} from "./convert.ts";import { daysInMonth, INVALID_DATE, isLeapYear, isValidDate, weeksInWeekYear,} from "./utils.ts";import { DateArray, DateDiff, DateObj, Option, Timezone } from "./types.ts";import { MILLISECONDS_IN_DAY, MILLISECONDS_IN_HOUR, MILLISECONDS_IN_MINUTE,} from "./constants.ts";
export type DateArg = Partial<DateObj> | Date | number[] | string | number;
function isDateObj(arg: DateArg): arg is DateObj { return (arg as DateObj).year !== undefined;}
function isArray(arg: DateArg): arg is number[] { return (Array.isArray(arg));}
function parseArg(date: DateArg): DateObj { if (typeof date === "number") { return tsToDate(date, { isLocal: true }); }
if (date instanceof Date) { return tsToDate(date.getTime(), { isLocal: true }); }
if (isDateObj(date)) { return date; }
if (isArray(date)) { return arrayToDate(date); }
if (typeof date === "string") { const parsed = parseISO(date); const offset = parsed.offsetMillisec; if (!offset || offset === 0) return parsed;
const normalizeSign = offset > 0 ? false : true; return tsToDate( adjustedTS(parsed, { millisecond: offset }, { positive: normalizeSign }), ); }
return INVALID_DATE;}
export type DateTimeOption = Omit<Option, "offsetMillisec">;
export function latestDateTime(datetimes: DateTime[]) { return datetimes.reduce((a, b) => a.toUTC().toMilliseconds() > b.toUTC().toMilliseconds() ? a : b );}
export function oldestDateTime(datetimes: DateTime[]) { return datetimes.reduce((a, b) => a.toUTC().toMilliseconds() < b.toUTC().toMilliseconds() ? a : b );}
type DiffOption = { showDecimal: boolean };
export function diffInMillisec( baseDate: DateTime, otherDate: DateTime,): number { return Math.abs( baseDate.toUTC().toMilliseconds() - otherDate.toUTC().toMilliseconds(), );}
export function diffInSec(baseDate: DateTime, otherDate: DateTime): number { return Math.floor(diffInMillisec(baseDate, otherDate) / 1000);}
export function diffInMin( baseDate: DateTime, otherDate: DateTime, option: DiffOption = { showDecimal: false },): number { const diff = diffInMillisec(baseDate, otherDate) / MILLISECONDS_IN_MINUTE; return option.showDecimal ? diff : Math.floor(diff);}
export function diffInHours( baseDate: DateTime, otherDate: DateTime, option: DiffOption = { showDecimal: false },): number { const diff = diffInMillisec(baseDate, otherDate) / MILLISECONDS_IN_HOUR; return option.showDecimal ? diff : Math.floor(diff);}
export function diffInDays( baseDate: DateTime, otherDate: DateTime, option: DiffOption = { showDecimal: false },): number { const diff = diffInMillisec(baseDate, otherDate) / MILLISECONDS_IN_DAY; return option.showDecimal ? diff : Math.floor(diff);}
export function datetime(date?: DateArg, option?: DateTimeOption) { if (date) { return new DateTime(date, option); } return DateTime.now(option);}
export class DateTime { readonly year: number; readonly month: number; readonly day: number; readonly hour: number; readonly minute: number; readonly second: number; readonly millisecond: number; readonly timezone: Timezone; readonly valid: boolean; readonly locale: string; readonly #localeClass: Locale;
constructor(date: DateArg, option?: DateTimeOption) { this.timezone = option?.timezone ?? getLocalName(); this.locale = option?.locale ?? "en"; this.#localeClass = new Locale(this.locale);
const dateObj = parseArg(date); const { year, month, day, hour, minute, second, millisecond } = dateObj; this.valid = isValidDate(dateObj);
if (this.valid) { this.year = year; this.month = month; this.day = day ?? 1; this.hour = hour ?? 0; this.minute = minute ?? 0; this.second = second ?? 0; this.millisecond = millisecond ?? 0; } else { this.year = NaN; this.month = NaN; this.day = NaN; this.hour = NaN; this.minute = NaN; this.second = NaN; this.millisecond = NaN; } }
static now(option?: Option): DateTime { const localTime = new DateTime(new Date().getTime(), option); if (option?.timezone) { return localTime.toZonedTime(option?.timezone).setOption(option); }
return localTime; }
toLocal(): DateTime { return this.toZonedTime(getLocalName()); }
isValidZone(): boolean { try { new Intl.DateTimeFormat("en-US", { timeZone: this.timezone }).format(); return true; } catch { return false; } }
isValid(): boolean { return isValidDate(this.toDateObj()); }
toDateObj(): DateObj { const { year, month, day, hour, minute, second, millisecond } = this; return { year, month, day, hour, minute, second, millisecond, }; }
parse( dateStr: string, formatStr: string, option?: DateTimeOption, ): DateTime { const { year, month, day, hour, minute, second, millisecond, } = parseDateStr(dateStr, formatStr, { locale: option?.locale ?? "en" });
const tz = option?.timezone ?? getLocalName(); return new DateTime({ year, month, day, hour, minute, second, millisecond, }, { ...option, timezone: tz }); }
toISO(): string { const offset = formatDate(this.toDateObj(), "Z", this.#option()); const tz = this.timezone === "UTC" ? "Z" : offset; return `${this.toISODate()}T${this.toISOTime()}${tz}`; }
toISODate(): string { return formatDate(this.toDateObj(), "YYYY-MM-dd"); }
toISOWeekDate(): string { return formatDate(this.toDateObj(), "YYYY-'W'WW-w"); }
toISOTime(): string { return formatDate(this.toDateObj(), "HH:mm:ss.S"); }
format(formatStr: string) { return formatDate(this.toDateObj(), formatStr, this.#option()); }
toUTC(): DateTime { const utcDateObj = zonedTimeToUTC( this.toDateObj(), this.timezone, ); return datetime(utcDateObj, { ...this.#option(), timezone: "UTC" }); }
toZonedTime(tz: Timezone): DateTime { const zonedDateObj = toOtherZonedTime( this.toDateObj(), this.timezone, tz, ); return datetime(zonedDateObj, { ...this.#option, timezone: tz }); }
toJSDate(): Date { return dateToJSDate(this.toUTC().toDateObj()); }
toArray(): DateArray { return dateToArray(this.toDateObj()); }
toMilliseconds(): number { return dateToTS(this.toUTC().toDateObj()); }
dayOfYear(): number { return dateToDayOfYear(this.toDateObj()); }
weeksInWeekYear(): number { return weeksInWeekYear(this.year); }
weekDay(): number { return dateToWeekDay(this.toDateObj()); }
weekDayShort(): string { return formatDateObj(this.toDateObj(), "www", this.#option()); }
weekDayLong(): string { return formatDateObj(this.toDateObj(), "wwww", this.#option()); }
monthShort(): string { return formatDateObj(this.toDateObj(), "MMM", this.#option()); }
monthLong(): string { return formatDateObj(this.toDateObj(), "MMMM", this.#option()); }
quarter(): number { return Math.ceil(this.month / 3); }
isBefore(otherDate?: DateTime): boolean { return otherDate ? this.toMilliseconds() < otherDate.toMilliseconds() : this.toMilliseconds() < new Date().getTime(); }
isAfter(otherDate?: DateTime): boolean { return otherDate ? this.toMilliseconds() > otherDate.toMilliseconds() : this.toMilliseconds() > new Date().getTime(); }
isBetween(startDate: DateTime, endDate: DateTime): boolean { return this.toMilliseconds() >= startDate.toMilliseconds() && this.toMilliseconds() <= endDate.toMilliseconds(); }
isLeapYear(): boolean { return isLeapYear(this.year); }
startOfYear(): DateTime { return datetime({ ...this.toDateObj(), month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }, this.#option()); }
startOfMonth(): DateTime { return datetime({ ...this.toDateObj(), day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }, this.#option()); }
startOfDay(): DateTime { return datetime({ ...this.toDateObj(), hour: 0, minute: 0, second: 0, millisecond: 0, }, this.#option()); }
startOfHour(): DateTime { return datetime({ ...this.toDateObj(), minute: 0, second: 0, millisecond: 0, }, this.#option()); }
startOfMinute(): DateTime { return datetime({ ...this.toDateObj(), second: 0, millisecond: 0, }, this.#option()); }
startOfSecond(): DateTime { return datetime({ ...this.toDateObj(), millisecond: 0, }, this.#option()); }
startOfQuarter(): DateTime { return datetime({ ...this.toDateObj(), month: 1 + (this.quarter() - 1) * 3, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }, this.#option()); }
endOfYear(): DateTime { return datetime({ ...this.toDateObj(), month: 12, day: 31, hour: 23, minute: 59, second: 59, millisecond: 999, }, this.#option()); }
endOfMonth(): DateTime { const dateObj = this.toDateObj(); return datetime({ ...dateObj, day: daysInMonth(dateObj.year, dateObj.month), hour: 23, minute: 59, second: 59, millisecond: 999, }, this.#option()); }
endOfDay(): DateTime { return datetime({ ...this.toDateObj(), hour: 23, minute: 59, second: 59, millisecond: 999, }, this.#option()); }
endOfHour(): DateTime { return datetime({ ...this.toDateObj(), minute: 59, second: 59, millisecond: 999, }, this.#option()); }
endOfMinute(): DateTime { return datetime({ ...this.toDateObj(), second: 59, millisecond: 999, }, this.#option()); }
endOfSecond(): DateTime { return datetime({ ...this.toDateObj(), millisecond: 999, }, this.#option()); }
endOfQuarter(): DateTime { const month = 3 * this.quarter(); const dateObj = this.toDateObj(); return datetime({ ...dateObj, month, day: daysInMonth(dateObj.year, month), hour: 23, minute: 59, second: 59, millisecond: 999, }, this.#option()); }
add(diff: DateDiff): DateTime { return datetime( adjustedTS(this.toUTC().toDateObj(), diff, { positive: true }), this.#option(), ); }
subtract(diff: DateDiff): DateTime { return datetime( adjustedTS(this.toUTC().toDateObj(), diff, { positive: false, }), this.#option(), ); }
offsetMillisec(): number { if (this.valid) { return tzOffset( new Date( this.year, this.month - 1, this.day ?? 0, this.hour ?? 0, this.minute ?? 0, this.second ?? 0, this.millisecond ?? 0, ), this?.timezone ?? "UTC", ); } else { return 0; } }
offsetSec(): number { return this.offsetMillisec ? this.offsetMillisec() / 1000 : 0; }
offsetMin(): number { return this.offsetMillisec ? this.offsetMillisec() / MILLISECONDS_IN_MINUTE : 0; }
offsetHour(): number { return this.offsetMillisec ? this.offsetMillisec() / MILLISECONDS_IN_HOUR : 0; }
toDateTimeFormat(options?: Intl.DateTimeFormatOptions) { return this.#localeClass.dtfFormat(this.toJSDate(), options); }
toDateTimeFormatParts(options?: Intl.DateTimeFormatOptions) { return this.#localeClass.dtfFormatToParts(this.toJSDate(), options); }
setOption(option: DateTimeOption) { return datetime(this.toDateObj(), { ...this.#option, ...option }); }
setLocale(locale: string) { return datetime(this.toDateObj(), { ...this.#option, locale }); }
#option(): Option { return { offsetMillisec: this.offsetMillisec(), timezone: this.timezone, locale: this.locale, }; }}