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
JavaScript
"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;