UNPKG

chronos-ts

Version:

A comprehensive TypeScript library for date and time manipulation, inspired by Carbon PHP. Features immutable API, intervals, periods, timezones, and i18n support.

1,260 lines (1,259 loc) 44.9 kB
"use strict"; /** * Chronos - The ultimate TypeScript date/time library * @module Chronos */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Chronos = void 0; const types_1 = require("../types"); const timezone_1 = require("./timezone"); const utils_1 = require("../utils"); const locales_1 = require("../locales"); // ============================================================================ // Global Configuration // ============================================================================ let globalConfig = { timezone: undefined, locale: 'en', weekStartsOn: types_1.DayOfWeek.Sunday, firstWeekContainsDate: 1, strict: false, }; let testNow = null; // ============================================================================ // Chronos Class // ============================================================================ /** * Chronos - A comprehensive date/time manipulation library * * @example * ```typescript * // Create instances * const now = Chronos.now(); * const date = Chronos.parse('2024-01-15'); * const birthday = Chronos.create(1990, 5, 15); * * // Manipulate * const future = now.add(3, 'months').startOf('day'); * const past = now.subtract(1, 'year').endOf('month'); * * // Compare * const isAfter = date.isAfter(birthday); * const diff = date.diff(birthday, 'years'); * * // Format * const formatted = now.format('YYYY-MM-DD HH:mm:ss'); * const relative = birthday.fromNow(); // "34 years ago" * ``` */ class Chronos { // ============================================================================ // Constructors // ============================================================================ /** * Create a new Chronos instance * * @param input - Date input (string, number, Date, or Chronos) * @param timezone - Optional timezone */ constructor(input, timezone) { var _a; this._locale = (0, locales_1.getLocale)((_a = globalConfig.locale) !== null && _a !== void 0 ? _a : 'en'); this._timezone = timezone !== null && timezone !== void 0 ? timezone : globalConfig.timezone; if (testNow && input === undefined) { this._date = new Date(testNow._date); } else if (input === null || input === undefined) { this._date = new Date(); } else if (typeof input === 'number') { this._date = new Date(input); } else if (typeof input === 'string') { this._date = this.parseString(input); } else if ((0, utils_1.isDate)(input)) { this._date = new Date(input); } else if ((0, utils_1.isChronosLike)(input)) { this._date = input.toDate(); } else { this._date = new Date(); } if (!(0, utils_1.isValidDate)(this._date)) { throw new Error(`Invalid date: ${input}`); } } /** * Parse a date string */ parseString(input) { // Try ISO 8601 first const isoDate = new Date(input); if ((0, utils_1.isValidDate)(isoDate)) { return isoDate; } // Try common formats const formats = [ /^(\d{4})-(\d{2})-(\d{2})$/, /^(\d{2})\/(\d{2})\/(\d{4})$/, /^(\d{4})\/(\d{2})\/(\d{2})$/, ]; for (const format of formats) { const match = input.match(format); if (match) { const parsed = new Date(input); if ((0, utils_1.isValidDate)(parsed)) { return parsed; } } } throw new Error(`Unable to parse date: ${input}`); } /** * Helper to create a date from components in a specific timezone */ static dateFromComponents(components, timezone) { var _a, _b, _c, _d, _e, _f, _g; const utcTime = Date.UTC((_a = components.year) !== null && _a !== void 0 ? _a : 1970, ((_b = components.month) !== null && _b !== void 0 ? _b : 1) - 1, (_c = components.day) !== null && _c !== void 0 ? _c : 1, (_d = components.hour) !== null && _d !== void 0 ? _d : 0, (_e = components.minute) !== null && _e !== void 0 ? _e : 0, (_f = components.second) !== null && _f !== void 0 ? _f : 0, (_g = components.millisecond) !== null && _g !== void 0 ? _g : 0); const tz = new timezone_1.ChronosTimezone(timezone); let offset = tz.getOffsetMinutes(new Date(utcTime)); let date = new Date(utcTime - offset * 60000); // Refine offset (handle DST transitions) for (let i = 0; i < 3; i++) { const newOffset = tz.getOffsetMinutes(date); if (newOffset === offset) break; offset = newOffset; date = new Date(utcTime - offset * 60000); } return date; } // ============================================================================ // Static Factory Methods // ============================================================================ /** * Create a Chronos instance from various inputs * * @example * ```typescript * Chronos.parse('2024-01-15') * Chronos.parse(1705276800000) * Chronos.parse(new Date()) * ``` */ static parse(input, timezone) { return new Chronos(input, timezone); } /** * Create a Chronos instance for the current moment * * @example * ```typescript * const now = Chronos.now(); * ``` */ static now(timezone) { return new Chronos(undefined, timezone); } /** * Create a Chronos instance for today at midnight */ static today(timezone) { return Chronos.now(timezone).startOf('day'); } /** * Create a Chronos instance for tomorrow at midnight */ static tomorrow(timezone) { return Chronos.today(timezone).add(1, 'day'); } /** * Create a Chronos instance for yesterday at midnight */ static yesterday(timezone) { return Chronos.today(timezone).subtract(1, 'day'); } /** * Create a Chronos instance from individual components * Month is 1-12 (like Carbon PHP) * * @example * ```typescript * Chronos.create(2024, 1, 15, 10, 30, 0) // Jan 15, 2024 10:30:00 * ``` */ static create(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, millisecond = 0, timezone) { if (timezone) { const date = Chronos.dateFromComponents({ year, month, day, hour, minute, second, millisecond, }, timezone); return new Chronos(date, timezone); } const date = new Date(year, month - 1, day, hour, minute, second, millisecond); return new Chronos(date, timezone); } /** * Create a Chronos instance from a Unix timestamp (seconds) */ static fromUnix(timestamp, timezone) { return new Chronos(timestamp * 1000, timezone); } /** * Create a Chronos instance from a Unix timestamp (milliseconds) */ static fromMillis(timestamp, timezone) { return new Chronos(timestamp, timezone); } /** * Create a Chronos instance from components object * Month in components is 1-12 (like Carbon PHP) */ static fromObject(components, timezone) { var _a, _b, _c, _d, _e, _f, _g; if (timezone) { const date = Chronos.dateFromComponents(components, timezone); return new Chronos(date, timezone); } const now = new Date(); const date = new Date((_a = components.year) !== null && _a !== void 0 ? _a : now.getFullYear(), ((_b = components.month) !== null && _b !== void 0 ? _b : now.getMonth() + 1) - 1, (_c = components.day) !== null && _c !== void 0 ? _c : now.getDate(), (_d = components.hour) !== null && _d !== void 0 ? _d : 0, (_e = components.minute) !== null && _e !== void 0 ? _e : 0, (_f = components.second) !== null && _f !== void 0 ? _f : 0, (_g = components.millisecond) !== null && _g !== void 0 ? _g : 0); return new Chronos(date, timezone); } /** * Create a Chronos instance from a format string * * @example * ```typescript * Chronos.fromFormat('15-01-2024', 'DD-MM-YYYY') * ``` */ static fromFormat(input, format, timezone) { const components = {}; let inputIndex = 0; const tokens = format.match(/(YYYY|YY|MM|M|DD|D|HH|H|mm|m|ss|s|SSS)/g) || []; const literals = format.split(/(YYYY|YY|MM|M|DD|D|HH|H|mm|m|ss|s|SSS)/); for (let i = 0; i < literals.length; i++) { const literal = literals[i]; if (tokens.includes(literal)) { let length = literal.length; if (literal === 'M' || literal === 'D' || literal === 'H' || literal === 'm' || literal === 's') { // Variable length, find next non-digit length = input.slice(inputIndex).search(/\D/); if (length === -1) length = input.length - inputIndex; } const value = parseInt(input.slice(inputIndex, inputIndex + length), 10); inputIndex += length; switch (literal) { case 'YYYY': components.year = value; break; case 'YY': components.year = value + (value >= 70 ? 1900 : 2000); break; case 'MM': case 'M': components.month = value; // Month is 1-12, fromObject expects 1-12 break; case 'DD': case 'D': components.day = value; break; case 'HH': case 'H': components.hour = value; break; case 'mm': case 'm': components.minute = value; break; case 'ss': case 's': components.second = value; break; case 'SSS': components.millisecond = value; break; } } else { inputIndex += literal.length; } } return Chronos.fromObject(components, timezone); } /** * Create the minimum possible date */ static min() { return new Chronos(new Date(-8640000000000000)); } /** * Create the maximum possible date */ static max() { return new Chronos(new Date(8640000000000000)); } /** * Get the earliest of multiple dates */ static earliest(...dates) { const parsed = dates.map((d) => Chronos.parse(d)); return parsed.reduce((min, d) => (d.isBefore(min) ? d : min)); } /** * Get the latest of multiple dates */ static latest(...dates) { const parsed = dates.map((d) => Chronos.parse(d)); return parsed.reduce((max, d) => (d.isAfter(max) ? d : max)); } // ============================================================================ // Getters // ============================================================================ /** Get the year */ get year() { if (this._timezone) { return new timezone_1.ChronosTimezone(this._timezone).getComponents(this._date).year; } return this._date.getFullYear(); } /** Get the month (1-12) */ get month() { if (this._timezone) { return new timezone_1.ChronosTimezone(this._timezone).getComponents(this._date) .month; } return this._date.getMonth() + 1; } /** Get the day of month (1-31) */ get date() { if (this._timezone) { return new timezone_1.ChronosTimezone(this._timezone).getComponents(this._date).day; } return this._date.getDate(); } /** Alias for date */ get day() { return this.date; } /** Get the day of week (0-6, Sunday = 0) */ get dayOfWeek() { if (this._timezone) { return new timezone_1.ChronosTimezone(this._timezone).getComponents(this._date) .dayOfWeek; } return this._date.getDay(); } /** Get the hour (0-23) */ get hour() { if (this._timezone) { return new timezone_1.ChronosTimezone(this._timezone).getComponents(this._date).hour; } return this._date.getHours(); } /** Get the minute (0-59) */ get minute() { if (this._timezone) { return new timezone_1.ChronosTimezone(this._timezone).getComponents(this._date) .minute; } return this._date.getMinutes(); } /** Get the second (0-59) */ get second() { if (this._timezone) { return new timezone_1.ChronosTimezone(this._timezone).getComponents(this._date) .second; } return this._date.getSeconds(); } /** Get the millisecond (0-999) */ get millisecond() { return this._date.getMilliseconds(); } /** Get Unix timestamp (seconds) */ get unix() { return Math.floor(this._date.getTime() / 1000); } /** Get Unix timestamp (milliseconds) */ get timestamp() { return this._date.getTime(); } /** Get the quarter (1-4) */ get quarter() { return (0, utils_1.getQuarter)(this._date); } /** Get the day of year (1-366) */ get dayOfYear() { return (0, utils_1.getDayOfYear)(this._date); } /** Get the ISO week number (1-53) */ get week() { return (0, utils_1.getISOWeek)(this._date); } /** Get the ISO week year */ get weekYear() { return (0, utils_1.getISOWeekYear)(this._date); } /** Get the number of days in the current month */ get daysInMonth() { return (0, utils_1.getDaysInMonth)(this.year, this.month - 1); } /** Get the number of days in the current year */ get daysInYear() { return (0, utils_1.getDaysInYear)(this.year); } /** Get the number of weeks in the current year */ get weeksInYear() { return (0, utils_1.getISOWeek)(new Date(this.year, 11, 28)); } /** Check if the year is a leap year */ get isLeapYear() { return (0, utils_1.isLeapYear)(this.year); } /** Get the timezone offset in minutes */ get offset() { return this._date.getTimezoneOffset(); } /** Get the timezone offset as string (+05:30) */ get offsetString() { const offset = this.offset; const sign = offset <= 0 ? '+' : '-'; const hours = Math.floor(Math.abs(offset) / 60); const minutes = Math.abs(offset) % 60; return `${sign}${(0, utils_1.padStart)(hours, 2)}:${(0, utils_1.padStart)(minutes, 2)}`; } // ============================================================================ // Setters (Immutable) // ============================================================================ /** * Set specific date/time components * Returns a new Chronos instance */ /** * Set multiple date/time values at once * Month is 1-12 (like Carbon PHP) */ set(values) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; if (this._timezone) { const tz = new timezone_1.ChronosTimezone(this._timezone); const current = tz.getComponents(this._date); const components = { year: (_a = values.year) !== null && _a !== void 0 ? _a : current.year, month: (_b = values.month) !== null && _b !== void 0 ? _b : current.month, day: (_d = (_c = values.date) !== null && _c !== void 0 ? _c : values.day) !== null && _d !== void 0 ? _d : current.day, hour: (_e = values.hour) !== null && _e !== void 0 ? _e : current.hour, minute: (_f = values.minute) !== null && _f !== void 0 ? _f : current.minute, second: (_g = values.second) !== null && _g !== void 0 ? _g : current.second, millisecond: (_h = values.millisecond) !== null && _h !== void 0 ? _h : this._date.getMilliseconds(), }; const date = Chronos.dateFromComponents(components, this._timezone); return new Chronos(date, this._timezone); } const date = (0, utils_1.cloneDate)(this._date); if (values.year !== undefined) date.setFullYear(values.year); if (values.month !== undefined) date.setMonth(values.month - 1); if (values.date !== undefined || values.day !== undefined) { date.setDate((_j = values.date) !== null && _j !== void 0 ? _j : values.day); } if (values.hour !== undefined) date.setHours(values.hour); if (values.minute !== undefined) date.setMinutes(values.minute); if (values.second !== undefined) date.setSeconds(values.second); if (values.millisecond !== undefined) date.setMilliseconds(values.millisecond); return new Chronos(date, this._timezone); } /** Set the year */ setYear(year) { return this.set({ year }); } /** Set the month (1-12) */ setMonth(month) { return this.set({ month }); } /** Set the day of month (1-31) */ setDate(date) { return this.set({ date }); } /** Set the hour (0-23) */ setHour(hour) { return this.set({ hour }); } /** Set the minute (0-59) */ setMinute(minute) { return this.set({ minute }); } /** Set the second (0-59) */ setSecond(second) { return this.set({ second }); } /** Set the millisecond (0-999) */ setMillisecond(millisecond) { return this.set({ millisecond }); } // ============================================================================ // Manipulation Methods // ============================================================================ /** * Add time to the date * * @example * ```typescript * chronos.add(5, 'days') * chronos.add(2, 'months') * chronos.add({ years: 1, months: 2 }) * ``` */ add(amount, unit) { if ((0, utils_1.isDuration)(amount)) { let result = this.clone(); const duration = amount; if (duration.years) result = result.add(duration.years, 'years'); if (duration.months) result = result.add(duration.months, 'months'); if (duration.weeks) result = result.add(duration.weeks, 'weeks'); if (duration.days) result = result.add(duration.days, 'days'); if (duration.hours) result = result.add(duration.hours, 'hours'); if (duration.minutes) result = result.add(duration.minutes, 'minutes'); if (duration.seconds) result = result.add(duration.seconds, 'seconds'); if (duration.milliseconds) result = result.add(duration.milliseconds, 'milliseconds'); return result; } if (unit === undefined) { throw new Error('Unit is required when amount is a number'); } const normalizedUnit = (0, utils_1.normalizeUnit)(unit); const newDate = (0, utils_1.addUnits)(this._date, amount, normalizedUnit); return new Chronos(newDate, this._timezone); } /** * Subtract time from the date */ subtract(amount, unit) { if ((0, utils_1.isDuration)(amount)) { const negated = {}; for (const [key, value] of Object.entries(amount)) { if (typeof value === 'number') { negated[key] = -value; } } return this.add(negated); } return this.add(-amount, unit); } /** * Get the start of a time unit * * @example * ```typescript * chronos.startOf('day') // 00:00:00.000 * chronos.startOf('month') // First day of month * chronos.startOf('year') // January 1st * ``` */ startOf(unit) { const normalizedUnit = (0, utils_1.normalizeUnit)(unit); const newDate = (0, utils_1.startOf)(this._date, normalizedUnit); return new Chronos(newDate, this._timezone); } /** * Get the end of a time unit * * @example * ```typescript * chronos.endOf('day') // 23:59:59.999 * chronos.endOf('month') // Last day of month * chronos.endOf('year') // December 31st * ``` */ endOf(unit) { const normalizedUnit = (0, utils_1.normalizeUnit)(unit); const newDate = (0, utils_1.endOf)(this._date, normalizedUnit); return new Chronos(newDate, this._timezone); } // ============================================================================ // Convenience Add/Subtract Methods // ============================================================================ addMilliseconds(amount) { return this.add(amount, 'milliseconds'); } addSeconds(amount) { return this.add(amount, 'seconds'); } addMinutes(amount) { return this.add(amount, 'minutes'); } addHours(amount) { return this.add(amount, 'hours'); } addDays(amount) { return this.add(amount, 'days'); } addWeeks(amount) { return this.add(amount, 'weeks'); } addMonths(amount) { return this.add(amount, 'months'); } addQuarters(amount) { return this.add(amount * 3, 'months'); } addYears(amount) { return this.add(amount, 'years'); } subtractMilliseconds(amount) { return this.subtract(amount, 'milliseconds'); } subtractSeconds(amount) { return this.subtract(amount, 'seconds'); } subtractMinutes(amount) { return this.subtract(amount, 'minutes'); } subtractHours(amount) { return this.subtract(amount, 'hours'); } subtractDays(amount) { return this.subtract(amount, 'days'); } subtractWeeks(amount) { return this.subtract(amount, 'weeks'); } subtractMonths(amount) { return this.subtract(amount, 'months'); } subtractQuarters(amount) { return this.subtract(amount * 3, 'months'); } subtractYears(amount) { return this.subtract(amount, 'years'); } // ============================================================================ // Comparison Methods // ============================================================================ /** * Check if this date is before another */ isBefore(other, unit) { const otherDate = Chronos.parse(other); if (unit) { const normalizedUnit = (0, utils_1.normalizeUnit)(unit); return ((0, utils_1.startOf)(this._date, normalizedUnit) < (0, utils_1.startOf)(otherDate._date, normalizedUnit)); } return this._date < otherDate._date; } /** * Check if this date is after another */ isAfter(other, unit) { const otherDate = Chronos.parse(other); if (unit) { const normalizedUnit = (0, utils_1.normalizeUnit)(unit); return ((0, utils_1.startOf)(this._date, normalizedUnit) > (0, utils_1.startOf)(otherDate._date, normalizedUnit)); } return this._date > otherDate._date; } /** * Check if this date is the same as another */ isSame(other, unit) { const otherDate = Chronos.parse(other); if (unit) { const normalizedUnit = (0, utils_1.normalizeUnit)(unit); return ((0, utils_1.startOf)(this._date, normalizedUnit).getTime() === (0, utils_1.startOf)(otherDate._date, normalizedUnit).getTime()); } return this._date.getTime() === otherDate._date.getTime(); } /** * Check if this date is same or before another */ isSameOrBefore(other, unit) { return this.isSame(other, unit) || this.isBefore(other, unit); } /** * Check if this date is same or after another */ isSameOrAfter(other, unit) { return this.isSame(other, unit) || this.isAfter(other, unit); } /** * Check if this date is between two others */ isBetween(start, end, unit, inclusivity = '()') { const startDate = Chronos.parse(start); const endDate = Chronos.parse(end); const leftInclusive = inclusivity[0] === '['; const rightInclusive = inclusivity[1] === ']'; const afterStart = leftInclusive ? this.isSameOrAfter(startDate, unit) : this.isAfter(startDate, unit); const beforeEnd = rightInclusive ? this.isSameOrBefore(endDate, unit) : this.isBefore(endDate, unit); return afterStart && beforeEnd; } // ============================================================================ // Day Type Checks // ============================================================================ /** Check if this date is today */ isToday() { return this.isSame(Chronos.today(), 'day'); } /** Check if this date is tomorrow */ isTomorrow() { return this.isSame(Chronos.tomorrow(), 'day'); } /** Check if this date is yesterday */ isYesterday() { return this.isSame(Chronos.yesterday(), 'day'); } /** Check if this date is in the past */ isPast() { return this.isBefore(Chronos.now()); } /** Check if this date is in the future */ isFuture() { return this.isAfter(Chronos.now()); } /** Check if this is a weekend (Saturday or Sunday) */ isWeekend() { return (this.dayOfWeek === types_1.DayOfWeek.Saturday || this.dayOfWeek === types_1.DayOfWeek.Sunday); } /** Check if this is a weekday (Monday-Friday) */ isWeekday() { return !this.isWeekend(); } // ============================================================================ // Specific Day Checks // ============================================================================ isSunday() { return this.dayOfWeek === types_1.DayOfWeek.Sunday; } isMonday() { return this.dayOfWeek === types_1.DayOfWeek.Monday; } isTuesday() { return this.dayOfWeek === types_1.DayOfWeek.Tuesday; } isWednesday() { return this.dayOfWeek === types_1.DayOfWeek.Wednesday; } isThursday() { return this.dayOfWeek === types_1.DayOfWeek.Thursday; } isFriday() { return this.dayOfWeek === types_1.DayOfWeek.Friday; } isSaturday() { return this.dayOfWeek === types_1.DayOfWeek.Saturday; } // ============================================================================ // Difference Methods // ============================================================================ /** * Get the difference between two dates in a specific unit */ diff(other, unit = 'millisecond', precise = false) { const otherDate = Chronos.parse(other); const normalizedUnit = (0, utils_1.normalizeUnit)(unit); if (precise) { const diffMs = this._date.getTime() - otherDate._date.getTime(); switch (normalizedUnit) { case 'second': return diffMs / utils_1.MILLISECONDS_PER_SECOND; case 'minute': return diffMs / utils_1.MILLISECONDS_PER_MINUTE; case 'hour': return diffMs / utils_1.MILLISECONDS_PER_HOUR; case 'day': return diffMs / utils_1.MILLISECONDS_PER_DAY; case 'week': return diffMs / (utils_1.MILLISECONDS_PER_DAY * 7); default: break; } } return (0, utils_1.diffInUnits)(this._date, otherDate._date, normalizedUnit); } /** * Get a detailed diff breakdown */ diffDetailed(other) { const otherDate = Chronos.parse(other); const diffMs = Math.abs(this._date.getTime() - otherDate._date.getTime()); let remaining = diffMs; const years = Math.floor(remaining / (365.25 * utils_1.MILLISECONDS_PER_DAY)); remaining -= years * 365.25 * utils_1.MILLISECONDS_PER_DAY; const months = Math.floor(remaining / (30.44 * utils_1.MILLISECONDS_PER_DAY)); remaining -= months * 30.44 * utils_1.MILLISECONDS_PER_DAY; const weeks = Math.floor(remaining / (7 * utils_1.MILLISECONDS_PER_DAY)); remaining -= weeks * 7 * utils_1.MILLISECONDS_PER_DAY; const days = Math.floor(remaining / utils_1.MILLISECONDS_PER_DAY); remaining -= days * utils_1.MILLISECONDS_PER_DAY; const hours = Math.floor(remaining / utils_1.MILLISECONDS_PER_HOUR); remaining -= hours * utils_1.MILLISECONDS_PER_HOUR; const minutes = Math.floor(remaining / utils_1.MILLISECONDS_PER_MINUTE); remaining -= minutes * utils_1.MILLISECONDS_PER_MINUTE; const seconds = Math.floor(remaining / utils_1.MILLISECONDS_PER_SECOND); remaining -= seconds * utils_1.MILLISECONDS_PER_SECOND; return { years, months, weeks, days, hours, minutes, seconds, milliseconds: remaining, totalMilliseconds: diffMs, }; } // ============================================================================ // Human Readable Methods // ============================================================================ /** * Get a human-readable relative time string * * @example * ```typescript * date.fromNow() // "2 days ago" * date.from(other) // "in 3 months" * date.fromNow({ short: true }) // "2d ago" * ``` */ fromNow(options = {}) { return this.from(Chronos.now(), options); } /** * Get relative time from another date */ from(other, options = {}) { var _a, _b; const otherDate = Chronos.parse(other); const diffMs = this._date.getTime() - otherDate._date.getTime(); const absDiff = Math.abs(diffMs); const isFuture = diffMs > 0; // eslint-disable-next-line @typescript-eslint/no-unused-vars const { short: _short = false, absolute = false } = options; const relative = this._locale.relativeTime; let value; let unit; if (absDiff < utils_1.MILLISECONDS_PER_MINUTE) { value = Math.round(absDiff / utils_1.MILLISECONDS_PER_SECOND); unit = value === 1 ? 's' : 'ss'; } else if (absDiff < utils_1.MILLISECONDS_PER_HOUR) { value = Math.round(absDiff / utils_1.MILLISECONDS_PER_MINUTE); unit = value === 1 ? 'm' : 'mm'; } else if (absDiff < utils_1.MILLISECONDS_PER_DAY) { value = Math.round(absDiff / utils_1.MILLISECONDS_PER_HOUR); unit = value === 1 ? 'h' : 'hh'; } else if (absDiff < utils_1.MILLISECONDS_PER_DAY * 7) { value = Math.round(absDiff / utils_1.MILLISECONDS_PER_DAY); unit = value === 1 ? 'd' : 'dd'; } else if (absDiff < utils_1.MILLISECONDS_PER_DAY * 30) { value = Math.round(absDiff / (utils_1.MILLISECONDS_PER_DAY * 7)); unit = value === 1 ? 'w' : 'ww'; } else if (absDiff < utils_1.MILLISECONDS_PER_DAY * 365) { value = Math.round(absDiff / (utils_1.MILLISECONDS_PER_DAY * 30)); unit = value === 1 ? 'M' : 'MM'; } else { value = Math.round(absDiff / (utils_1.MILLISECONDS_PER_DAY * 365)); unit = value === 1 ? 'y' : 'yy'; } const relativeStr = (_b = (_a = relative[unit]) === null || _a === void 0 ? void 0 : _a.replace('%d', String(value))) !== null && _b !== void 0 ? _b : `${value} ${unit}`; if (absolute) { return relativeStr; } return isFuture ? relative.future.replace('%s', relativeStr) : relative.past.replace('%s', relativeStr); } /** * Get relative time to another date */ to(other, options = {}) { return Chronos.parse(other).from(this, options); } /** * Get relative time to now */ toNow(options = {}) { return this.to(Chronos.now(), options); } // ============================================================================ // Formatting Methods // ============================================================================ /** * Format the date using a format string * * Supported tokens: * - YYYY: 4-digit year * - YY: 2-digit year * - MMMM: Full month name * - MMM: Short month name * - MM: 2-digit month * - M: 1-2 digit month * - DD: 2-digit day * - D: 1-2 digit day * - dddd: Full weekday name * - ddd: Short weekday name * - dd: Min weekday name * - d: Day of week number * - HH: 2-digit hour (24h) * - H: 1-2 digit hour (24h) * - hh: 2-digit hour (12h) * - h: 1-2 digit hour (12h) * - mm: 2-digit minute * - m: 1-2 digit minute * - ss: 2-digit second * - s: 1-2 digit second * - SSS: 3-digit millisecond * - A: AM/PM * - a: am/pm * - Z: Timezone offset (+05:00) * - ZZ: Timezone offset (+0500) */ format(formatStr = 'YYYY-MM-DDTHH:mm:ssZ') { const tokens = { YYYY: () => String(this.year), YY: () => String(this.year).slice(-2), MMMM: () => this._locale.months[this.month - 1], MMM: () => this._locale.monthsShort[this.month - 1], MM: () => (0, utils_1.padStart)(this.month, 2), M: () => String(this.month), DD: () => (0, utils_1.padStart)(this.date, 2), D: () => String(this.date), dddd: () => this._locale.weekdays[this.dayOfWeek], ddd: () => this._locale.weekdaysShort[this.dayOfWeek], dd: () => this._locale.weekdaysMin[this.dayOfWeek], d: () => String(this.dayOfWeek), HH: () => (0, utils_1.padStart)(this.hour, 2), H: () => String(this.hour), hh: () => (0, utils_1.padStart)(this.hour % 12 || 12, 2), h: () => String(this.hour % 12 || 12), mm: () => (0, utils_1.padStart)(this.minute, 2), m: () => String(this.minute), ss: () => (0, utils_1.padStart)(this.second, 2), s: () => String(this.second), SSS: () => (0, utils_1.padStart)(this.millisecond, 3), A: () => this._locale.meridiem ? this._locale.meridiem(this.hour, this.minute, false) : this.hour < 12 ? 'AM' : 'PM', a: () => this._locale.meridiem ? this._locale.meridiem(this.hour, this.minute, true) : this.hour < 12 ? 'am' : 'pm', Z: () => this.offsetString, ZZ: () => this.offsetString.replace(':', ''), Q: () => String(this.quarter), Do: () => (0, utils_1.ordinalSuffix)(this.date), W: () => String(this.week), WW: () => (0, utils_1.padStart)(this.week, 2), X: () => String(this.unix), x: () => String(this.timestamp), }; // Sort tokens by length (longest first) to avoid partial matches const sortedTokens = Object.keys(tokens).sort((a, b) => b.length - a.length); const regex = new RegExp(`\\[([^\\]]+)\\]|(${sortedTokens.join('|')})`, 'g'); return formatStr.replace(regex, (match, escaped, token) => { var _a, _b; if (escaped) { return escaped; } return (_b = (_a = tokens[token]) === null || _a === void 0 ? void 0 : _a.call(tokens)) !== null && _b !== void 0 ? _b : match; }); } // ============================================================================ // Standard Format Methods // ============================================================================ /** Format as ISO 8601 */ toISOString() { return this._date.toISOString(); } /** Format as ISO date (YYYY-MM-DD) */ toDateString() { return this.format('YYYY-MM-DD'); } /** Format as time (HH:mm:ss) */ toTimeString() { return this.format('HH:mm:ss'); } /** Format as datetime (YYYY-MM-DD HH:mm:ss) */ toDateTimeString() { return this.format('YYYY-MM-DD HH:mm:ss'); } /** Format as RFC 2822 */ toRFC2822() { return this.format('ddd, DD MMM YYYY HH:mm:ss ZZ'); } /** Format as RFC 3339 */ toRFC3339() { return this.format('YYYY-MM-DDTHH:mm:ssZ'); } /** Format as ATOM */ toAtomString() { return this.format('YYYY-MM-DDTHH:mm:ssZ'); } /** Format as Cookie */ toCookieString() { return this.format('dddd, DD-MMM-YYYY HH:mm:ss Z'); } /** Format as RSS */ toRSSString() { return this.format('ddd, DD MMM YYYY HH:mm:ss ZZ'); } /** Format as W3C */ toW3CString() { return this.format('YYYY-MM-DDTHH:mm:ssZ'); } // ============================================================================ // Conversion Methods // ============================================================================ /** * Convert to a specific timezone */ toTimezone(timezone) { return new Chronos(this._date, timezone); } /** Convert to native Date object */ toDate() { return new Date(this._date); } /** Convert to array [year, month, day, hour, minute, second, millisecond] */ toArray() { return [ this.year, this.month, this.date, this.hour, this.minute, this.second, this.millisecond, ]; } /** Convert to object */ toObject() { return { year: this.year, month: this.month, day: this.date, hour: this.hour, minute: this.minute, second: this.second, millisecond: this.millisecond, }; } /** Convert to JSON-serializable object */ toJSON() { var _a; return { iso: this.toISOString(), timestamp: this.timestamp, timezone: (_a = this._timezone) !== null && _a !== void 0 ? _a : 'local', }; } /** Get primitive value (timestamp) */ valueOf() { return this._date.getTime(); } /** Convert to string */ toString() { return this.toISOString(); } // ============================================================================ // Cloning and Locale // ============================================================================ /** * Create a clone of this instance */ clone() { const cloned = new Chronos(this._date, this._timezone); cloned._locale = this._locale; return cloned; } /** * Set the locale for this instance */ locale(code) { const cloned = this.clone(); cloned._locale = (0, locales_1.getLocale)(code); return cloned; } /** * Get the current locale code */ getLocale() { return this._locale.code; } // ============================================================================ // Navigation Methods // ============================================================================ /** * Get the next occurrence of a specific day */ next(day) { const current = this.dayOfWeek; const daysToAdd = (day - current + 7) % 7 || 7; return this.addDays(daysToAdd); } /** * Get the previous occurrence of a specific day */ previous(day) { const current = this.dayOfWeek; const daysToSubtract = (current - day + 7) % 7 || 7; return this.subtractDays(daysToSubtract); } /** * Get the closest date (either this or other) */ closest(date1, date2) { const d1 = Chronos.parse(date1); const d2 = Chronos.parse(date2); const diff1 = Math.abs(this.diff(d1)); const diff2 = Math.abs(this.diff(d2)); return diff1 <= diff2 ? d1 : d2; } /** * Get the farthest date (either this or other) */ farthest(date1, date2) { const d1 = Chronos.parse(date1); const d2 = Chronos.parse(date2); const diff1 = Math.abs(this.diff(d1)); const diff2 = Math.abs(this.diff(d2)); return diff1 >= diff2 ? d1 : d2; } // ============================================================================ // Static Configuration Methods // ============================================================================ /** * Set global configuration */ static configure(config) { globalConfig = Object.assign(Object.assign({}, globalConfig), config); } /** * Get current global configuration */ static getConfig() { return Object.assign({}, globalConfig); } /** * Set test time (for testing purposes) */ static setTestNow(date) { testNow = date ? Chronos.parse(date) : null; } /** * Check if test mode is active */ static hasTestNow() { return testNow !== null; } /** * Get the test time */ static getTestNow() { var _a; return (_a = testNow === null || testNow === void 0 ? void 0 : testNow.clone()) !== null && _a !== void 0 ? _a : null; } // ============================================================================ // Validation Methods // ============================================================================ /** * Check if the date is valid */ isValid() { return (0, utils_1.isValidDate)(this._date); } /** * Check if this date is the same day as another (regardless of time) */ isSameDay(other) { return this.isSame(other, 'day'); } /** * Check if this date is in the same month as another */ isSameMonth(other) { return this.isSame(other, 'month'); } /** * Check if this date is in the same year as another */ isSameYear(other) { return this.isSame(other, 'year'); } // ============================================================================ // Calendar Methods // ============================================================================ /** * Get calendar output for the current month */ calendar(referenceDate) { const ref = referenceDate ? Chronos.parse(referenceDate) : Chronos.now(); const diffDays = this.diff(ref, 'days'); if (this.isSame(ref, 'day')) { return `Today at ${this.format('h:mm A')}`; } else if (this.isSame(ref.addDays(1), 'day')) { return `Tomorrow at ${this.format('h:mm A')}`; } else if (this.isSame(ref.subtractDays(1), 'day')) { return `Yesterday at ${this.format('h:mm A')}`; } else if (diffDays > 0 && diffDays < 7) { return `${this.format('dddd')} at ${this.format('h:mm A')}`; } else if (diffDays < 0 && diffDays > -7) { return `Last ${this.format('dddd')} at ${this.format('h:mm A')}`; } else { return this.format('MM/DD/YYYY'); } } } exports.Chronos = Chronos; // ============================================================================ // Export Default // ============================================================================ exports.default = Chronos;