Skip to content

Instantly share code, notes, and snippets.

@fredericoo
Created October 17, 2025 14:40
Show Gist options
  • Select an option

  • Save fredericoo/3b9c2bae7a7f3adc9b1d5f985665d60d to your computer and use it in GitHub Desktop.

Select an option

Save fredericoo/3b9c2bae7a7f3adc9b1d5f985665d60d to your computer and use it in GitHub Desktop.
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