Created
October 17, 2025 14:40
-
-
Save fredericoo/3b9c2bae7a7f3adc9b1d5f985665d60d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| type DateInput = Date | number; | |
| function toDate(input: DateInput): Date { | |
| const d = input instanceof Date ? new Date(input.getTime()) : new Date(input); | |
| if (isNaN(d.getTime())) throw new TypeError("Invalid time value"); | |
| return d; | |
| } | |
| function startOfDay(date: DateInput): Date { | |
| const d = toDate(date); | |
| return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0); | |
| } | |
| function daysInMonth(y: number, m: number): number { | |
| return new Date(y, m + 1, 0).getDate(); | |
| } | |
| export function format(date: DateInput, pattern: string, locale: string = "default"): string { | |
| const d = toDate(date); | |
| const pad = (n: number, len = 2) => String(n).padStart(len, "0"); | |
| const parts = new Map<string, string>([ | |
| ["yyyy", String(d.getFullYear())], | |
| ["yy", pad(d.getFullYear() % 100)], | |
| ["MM", pad(d.getMonth() + 1)], | |
| ["M", String(d.getMonth() + 1)], | |
| ["dd", pad(d.getDate())], | |
| ["d", String(d.getDate())], | |
| ["HH", pad(d.getHours())], | |
| ["H", String(d.getHours())], | |
| ["mm", pad(d.getMinutes())], | |
| ["m", String(d.getMinutes())], | |
| ["ss", pad(d.getSeconds())], | |
| ["s", String(d.getSeconds())], | |
| ["MMMM", new Intl.DateTimeFormat(locale, { month: "long" }).format(d)], | |
| ["MMM", new Intl.DateTimeFormat(locale, { month: "short" }).format(d)], | |
| ["EEEE", new Intl.DateTimeFormat(locale, { weekday: "long" }).format(d)], | |
| ["EEE", new Intl.DateTimeFormat(locale, { weekday: "short" }).format(d)], | |
| ]); | |
| const tokens = Array.from(parts.keys()).sort((a, b) => b.length - a.length); | |
| const regex = new RegExp(tokens.join("|"), "g"); | |
| let output = ""; | |
| let i = 0; | |
| while (i < pattern.length) { | |
| if (pattern[i] === "'") { | |
| let j = i + 1, literal = ""; | |
| while (j < pattern.length) { | |
| if (pattern[j] === "'") { | |
| if (pattern[j + 1] === "'") { literal += "'"; j += 2; continue; } | |
| j++; break; | |
| } | |
| literal += pattern[j++]; | |
| } | |
| output += literal; | |
| i = j; | |
| } else { | |
| const match = pattern.slice(i).match(regex); | |
| if (match && match.index === 0) { | |
| const token = match[0]; | |
| output += parts.get(token) ?? token; | |
| i += token.length; | |
| } else { | |
| output += pattern[i++]; | |
| } | |
| } | |
| } | |
| return output; | |
| } | |
| export function startOfMonth(date: DateInput): Date { | |
| const d = toDate(date); | |
| return new Date(d.getFullYear(), d.getMonth(), 1, 0, 0, 0, 0); | |
| } | |
| export function endOfMonth(date: DateInput): Date { | |
| const d = toDate(date); | |
| return new Date(d.getFullYear(), d.getMonth() + 1, 0, 23, 59, 59, 999); | |
| } | |
| export function eachDayOfInterval( | |
| { start, end }: { start: DateInput; end: DateInput }, | |
| opts?: { step?: number } | |
| ): Date[] { | |
| const s = startOfDay(start); | |
| const e = startOfDay(end); | |
| const step = Math.max(1, opts?.step ?? 1); | |
| if (s.getTime() > e.getTime()) throw new RangeError("Invalid interval"); | |
| const days: Date[] = []; | |
| for (let t = s.getTime(); t <= e.getTime(); t += step * 86400000) { | |
| days.push(new Date(t)); | |
| } | |
| return days; | |
| } | |
| export function isSameMonth(a: DateInput, b: DateInput): boolean { | |
| const da = toDate(a), db = toDate(b); | |
| return da.getFullYear() === db.getFullYear() && da.getMonth() === db.getMonth(); | |
| } | |
| export function isSameDay(a: DateInput, b: DateInput): boolean { | |
| const da = toDate(a), db = toDate(b); | |
| return ( | |
| da.getFullYear() === db.getFullYear() && | |
| da.getMonth() === db.getMonth() && | |
| da.getDate() === db.getDate() | |
| ); | |
| } | |
| export function addMonths(date: DateInput, amount: number): Date { | |
| const d = toDate(date); | |
| const target = new Date(d.getFullYear(), d.getMonth() + amount, 1); | |
| const dim = daysInMonth(target.getFullYear(), target.getMonth()); | |
| target.setDate(Math.min(d.getDate(), dim)); | |
| target.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()); | |
| return target; | |
| } | |
| export const subMonths = (date: DateInput, n: number) => addMonths(date, -n); | |
| export const addDays = (date: DateInput, n: number) => new Date(toDate(date).getTime() + n * 86400000); | |
| export const subDays = (date: DateInput, n: number) => addDays(date, -n); | |
| export const addWeeks = (date: DateInput, n: number) => addDays(date, n * 7); | |
| export const subWeeks = (date: DateInput, n: number) => addWeeks(date, -n); | |
| export function getDay(date: DateInput): number { | |
| return toDate(date).getDay(); | |
| } | |
| export function startOfWeek(date: DateInput, opts?: { weekStartsOn?: number }): Date { | |
| const d = toDate(date); | |
| const weekStartsOn = ((opts?.weekStartsOn ?? 0) + 7) % 7; | |
| const diff = (d.getDay() - weekStartsOn + 7) % 7; | |
| return startOfDay(addDays(d, -diff)); | |
| } | |
| export const isAfter = (a: DateInput, b: DateInput) => toDate(a).getTime() > toDate(b).getTime(); | |
| export const isBefore = (a: DateInput, b: DateInput) => toDate(a).getTime() < toDate(b).getTime(); | |
| export const isWeekend = (date: DateInput) => [0, 6].includes(toDate(date).getDay()); | |
| export function isWithinInterval( | |
| date: DateInput, | |
| interval: { start: DateInput; end: DateInput } | |
| ): boolean { | |
| const t = toDate(date).getTime(); | |
| const s = toDate(interval.start).getTime(); | |
| const e = toDate(interval.end).getTime(); | |
| if (s > e) throw new RangeError("Invalid interval"); | |
| return t >= s && t <= e; | |
| } | |
| export function min(dates: DateInput[]): Date { | |
| if (!dates.length) throw new RangeError("min([]): empty array"); | |
| return dates.map(toDate).reduce((a, b) => (a < b ? a : b)); | |
| } | |
| export function max(dates: DateInput[]): Date { | |
| if (!dates.length) throw new RangeError("max([]): empty array"); | |
| return dates.map(toDate).reduce((a, b) => (a > b ? a : b)); | |
| } | |
| export const endOfDay = (date: DateInput) => { | |
| const d = toDate(date); | |
| return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999); | |
| }; | |
| export const startOfYear = (date: DateInput) => | |
| new Date(toDate(date).getFullYear(), 0, 1, 0, 0, 0, 0); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment