UNPKG

@oxog/kairos

Version:

Revolutionary zero-dependency JavaScript date/time library with modular architecture and dynamic holiday system

491 lines 17.2 kB
import { LRUCache, memoize } from './utils/cache.js'; import { throwError } from './utils/validators.js'; const isKairosInstance = (obj) => { return obj !== null && typeof obj === 'object' && '_date' in obj && obj._date instanceof Date; }; const hasToDateMethod = (obj) => { return (obj !== null && typeof obj === 'object' && 'toDate' in obj && typeof obj.toDate === 'function'); }; const isDateLike = (obj) => { return (obj !== null && typeof obj === 'object' && (('year' in obj && 'month' in obj && 'day' in obj) || 'date' in obj)); }; const globalCache = new LRUCache(1000); export class KairosCore { constructor(input) { this._date = this.parseInput(input); } parseInput(input) { if (input === undefined) { return new Date(); } if (input instanceof Date) { return new Date(input.getTime()); } if (typeof input === 'number') { if (isNaN(input)) { return new Date(NaN); } return new Date(input); } if (typeof input === 'string') { if (input.toLowerCase() === 'invalid' || input === '') { return new Date(NaN); } const dateOnlyPattern = /^\d{4}-\d{2}-\d{2}$/; if (dateOnlyPattern.test(input)) { const [year, month, day] = input.split('-').map(Number); if (month < 1 || month > 12) { return new Date(NaN); } const date = new Date(year, month - 1, day, 0, 0, 0, 0); if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) { return new Date(NaN); } return date; } const europeanPattern = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/; if (europeanPattern.test(input)) { const match = input.match(europeanPattern); if (match) { const day = parseInt(match[1], 10); const month = parseInt(match[2], 10); const year = parseInt(match[3], 10); if (month < 1 || month > 12 || day < 1 || day > 31) { return new Date(NaN); } const date = new Date(year, month - 1, day, 0, 0, 0, 0); if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) { return new Date(NaN); } return date; } } const parsed = new Date(input); if (isNaN(parsed.getTime())) { if (KairosCore.config.strict) { throwError(`Invalid date string: ${input}`, 'INVALID_DATE'); } return new Date(NaN); } return parsed; } if (input && typeof input === 'object') { if (isKairosInstance(input)) { return new Date(input._date.getTime()); } if (hasToDateMethod(input)) { return input.toDate(); } if (isDateLike(input) && input.year !== undefined && input.month !== undefined && input.day !== undefined) { const year = input.year; const month = input.month - 1; const day = input.day; const hour = input.hour || 0; const minute = input.minute || 0; const second = input.second || 0; const millisecond = input.millisecond || 0; const date = new Date(year, month, day, hour, minute, second, millisecond); if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) { return new Date(NaN); } return date; } if (isDateLike(input) && input.date instanceof Date) { return new Date(input.date.getTime()); } } return new Date(NaN); } valueOf() { return this._date.getTime(); } toString() { return this._date.toString(); } toISOString() { return this._date.toISOString(); } offset() { if (this._isUTC) { return 0; } return -this._date.getTimezoneOffset(); } toDate() { return new Date(this._date.getTime()); } clone() { return new KairosCore(this._date); } year(value) { if (value === undefined) { return this._date.getFullYear(); } const clone = this.clone(); clone._date.setFullYear(value); return clone; } month(value) { if (value === undefined) { return this._date.getMonth() + 1; } const clone = this.clone(); clone._date.setMonth(value - 1); return clone; } date(value) { if (value === undefined) { return this._date.getDate(); } const clone = this.clone(); clone._date.setDate(value); return clone; } day() { return this._date.getDay(); } hour(value) { if (value === undefined) { return this._date.getHours(); } const clone = this.clone(); clone._date.setHours(value); return clone; } minute(value) { if (value === undefined) { return this._date.getMinutes(); } const clone = this.clone(); clone._date.setMinutes(value); return clone; } second(value) { if (value === undefined) { return this._date.getSeconds(); } const clone = this.clone(); clone._date.setSeconds(value); return clone; } millisecond(value) { if (value === undefined) { return this._date.getMilliseconds(); } const clone = this.clone(); clone._date.setMilliseconds(value); return clone; } add(amount, unit) { if (!this.isValid()) { return this.clone(); } const clone = this.clone(); const normalizedUnit = this.normalizeUnit(unit); switch (normalizedUnit) { case 'year': clone._date.setFullYear(clone._date.getFullYear() + amount); break; case 'month': { const currentDay = clone._date.getDate(); const currentMonth = clone._date.getMonth(); const currentYear = clone._date.getFullYear(); let targetMonth = currentMonth + amount; let targetYear = currentYear; while (targetMonth < 0) { targetMonth += 12; targetYear--; } while (targetMonth >= 12) { targetMonth -= 12; targetYear++; } const lastDayOfTargetMonth = new Date(targetYear, targetMonth + 1, 0).getDate(); clone._date.setDate(1); clone._date.setFullYear(targetYear); clone._date.setMonth(targetMonth); clone._date.setDate(Math.min(currentDay, lastDayOfTargetMonth)); break; } case 'week': clone._date.setDate(clone._date.getDate() + amount * 7); break; case 'day': if (amount % 1 !== 0) { const wholeDays = Math.floor(amount); const fractionalHours = (amount - wholeDays) * 24; clone._date.setDate(clone._date.getDate() + wholeDays); clone._date.setHours(clone._date.getHours() + fractionalHours); } else { clone._date.setDate(clone._date.getDate() + amount); } break; case 'hour': clone._date.setHours(clone._date.getHours() + amount); break; case 'minute': clone._date.setMinutes(clone._date.getMinutes() + amount); break; case 'second': clone._date.setSeconds(clone._date.getSeconds() + amount); break; case 'millisecond': clone._date.setMilliseconds(clone._date.getMilliseconds() + amount); break; default: throwError(`Unknown unit: ${unit}`, 'INVALID_UNIT'); } return clone; } subtract(amount, unit) { return this.add(-amount, unit); } startOf(unit) { const clone = this.clone(); const normalizedUnit = this.normalizeUnit(unit); switch (normalizedUnit) { case 'year': clone._date.setMonth(0, 1); clone._date.setHours(0, 0, 0, 0); break; case 'month': clone._date.setDate(1); clone._date.setHours(0, 0, 0, 0); break; case 'week': { const day = clone._date.getDay(); clone._date.setDate(clone._date.getDate() - day); clone._date.setHours(0, 0, 0, 0); break; } case 'day': clone._date.setHours(0, 0, 0, 0); break; case 'hour': clone._date.setMinutes(0, 0, 0); break; case 'minute': clone._date.setSeconds(0, 0); break; case 'second': clone._date.setMilliseconds(0); break; } return clone; } endOf(unit) { const clone = this.clone(); const normalizedUnit = this.normalizeUnit(unit); switch (normalizedUnit) { case 'year': clone._date.setMonth(11, 31); clone._date.setHours(23, 59, 59, 999); break; case 'month': clone._date.setMonth(clone._date.getMonth() + 1, 0); clone._date.setHours(23, 59, 59, 999); break; case 'week': { const day = clone._date.getDay(); clone._date.setDate(clone._date.getDate() + (6 - day)); clone._date.setHours(23, 59, 59, 999); break; } case 'day': clone._date.setHours(23, 59, 59, 999); break; case 'hour': clone._date.setMinutes(59, 59, 999); break; case 'minute': clone._date.setSeconds(59, 999); break; case 'second': clone._date.setMilliseconds(999); break; } return clone; } isValid() { return !isNaN(this._date.getTime()); } isBefore(other) { return this.valueOf() < other.valueOf(); } isAfter(other) { return this.valueOf() > other.valueOf(); } isSame(other) { return this.valueOf() === other.valueOf(); } format(template = 'YYYY-MM-DD') { if (!this.isValid()) { return 'Invalid Date'; } const isUtc = this._isUTC; const year = isUtc ? this._date.getUTCFullYear() : this._date.getFullYear(); const month = isUtc ? this._date.getUTCMonth() + 1 : this._date.getMonth() + 1; const date = isUtc ? this._date.getUTCDate() : this._date.getDate(); const hours = isUtc ? this._date.getUTCHours() : this._date.getHours(); const minutes = isUtc ? this._date.getUTCMinutes() : this._date.getMinutes(); const seconds = isUtc ? this._date.getUTCSeconds() : this._date.getSeconds(); if (isNaN(year) || isNaN(month) || isNaN(date)) { return 'Invalid Date'; } return template .replace(/YYYY/g, year.toString()) .replace(/MM/g, month.toString().padStart(2, '0')) .replace(/DD/g, date.toString().padStart(2, '0')) .replace(/HH/g, hours.toString().padStart(2, '0')) .replace(/mm/g, minutes.toString().padStart(2, '0')) .replace(/ss/g, seconds.toString().padStart(2, '0')); } normalizeUnit(unit) { const unitMap = { y: 'year', year: 'year', years: 'year', M: 'month', month: 'month', months: 'month', w: 'week', week: 'week', weeks: 'week', d: 'day', day: 'day', days: 'day', h: 'hour', hour: 'hour', hours: 'hour', m: 'minute', minute: 'minute', minutes: 'minute', s: 'second', second: 'second', seconds: 'second', ms: 'millisecond', millisecond: 'millisecond', milliseconds: 'millisecond', }; return unitMap[unit] || unit; } } KairosCore.config = { locale: 'en', strict: false, suppressDeprecationWarnings: false, }; export class PluginSystem { static use(plugin) { const plugins = Array.isArray(plugin) ? plugin : [plugin]; for (const p of plugins) { this.installPlugin(p); } return kairos; } static installPlugin(plugin) { if (this.installedPlugins.has(plugin.name)) { return; } if (plugin.dependencies) { for (const dep of plugin.dependencies) { if (!this.installedPlugins.has(dep)) { throwError(`Plugin ${plugin.name} depends on ${dep} which is not installed`, 'MISSING_DEPENDENCY'); } } } this.plugins.set(plugin.name, plugin); this.installedPlugins.add(plugin.name); const utils = { cache: globalCache, memoize, validateInput: (input, type) => { switch (type) { case 'date': return input instanceof Date && !isNaN(input.getTime()); case 'number': return typeof input === 'number' && !isNaN(input); case 'string': return typeof input === 'string'; default: return false; } }, throwError, }; plugin.install(kairos, utils); } static extend(methods) { Object.assign(this.extensionMethods, methods); for (const [name, method] of Object.entries(methods)) { KairosCore.prototype[name] = method; } } static addStatic(methods) { Object.assign(this.staticMethods, methods); for (const [name, method] of Object.entries(methods)) { kairos[name] = method; } } static getPlugin(name) { return this.plugins.get(name); } static isInstalled(name) { return this.installedPlugins.has(name); } static getInstalledPlugins() { return Array.from(this.installedPlugins); } } PluginSystem.plugins = new Map(); PluginSystem.installedPlugins = new Set(); PluginSystem.extensionMethods = {}; PluginSystem.staticMethods = {}; const kairos = (input) => new KairosCore(input); kairos.use = PluginSystem.use.bind(PluginSystem); kairos.extend = PluginSystem.extend.bind(PluginSystem); kairos.addStatic = PluginSystem.addStatic.bind(PluginSystem); kairos.plugins = PluginSystem.plugins; kairos.utc = (input) => { let utcDate; if (typeof input === 'string' && !input.endsWith('Z') && !input.includes('+') && !/[+-]\d{2}:?\d{2}$/.test(input)) { const dateTimePattern = /^(\d{4})-(\d{2})-(\d{2})(?:\s+|T)(\d{2}):(\d{2})(?::(\d{2}))?$/; const dateOnlyPattern = /^(\d{4})-(\d{2})-(\d{2})$/; const match = input.match(dateTimePattern) || input.match(dateOnlyPattern); if (match) { const year = parseInt(match[1], 10); const month = parseInt(match[2], 10) - 1; const day = parseInt(match[3], 10); const hour = match[4] ? parseInt(match[4], 10) : 0; const minute = match[5] ? parseInt(match[5], 10) : 0; const second = match[6] ? parseInt(match[6], 10) : 0; utcDate = new Date(Date.UTC(year, month, day, hour, minute, second)); } else { input = input.replace(' ', 'T') + 'Z'; utcDate = new Date(input); } } else { utcDate = new Date(input); } const instance = new KairosCore(utcDate); instance._isUTC = true; return instance; }; kairos.unix = (timestamp) => new KairosCore(new Date(timestamp * 1000)); export default kairos; //# sourceMappingURL=plugin-system.js.map