daykit
Version:
A lightweight, type-safe date manipulation library for TypeScript/JavaScript with comprehensive timezone support
327 lines (326 loc) • 9.3 kB
JavaScript
// src/index.ts
function createMoment(date = /* @__PURE__ */ new Date()) {
return new Date(date);
}
function getAvailableTimezones() {
return Intl.supportedValuesOf("timeZone");
}
function getTimezoneInfo(date, timeZone) {
try {
const d = new Date(date);
const utcDate = new Date(d.toLocaleString("en-US", { timeZone: "UTC" }));
const tzDate = new Date(d.toLocaleString("en-US", { timeZone }));
const offset = (tzDate.getTime() - utcDate.getTime()) / 6e4;
let isDST2 = false;
if (timeZone === "America/New_York") {
isDST2 = offset !== -300;
} else if (timeZone === "UTC") {
isDST2 = false;
} else {
const jan = new Date(d.getFullYear(), 0, 1);
const jul = new Date(d.getFullYear(), 6, 1);
const janTzDate = new Date(jan.toLocaleString("en-US", { timeZone }));
const janUtcDate = new Date(
jan.toLocaleString("en-US", { timeZone: "UTC" })
);
const julTzDate = new Date(jul.toLocaleString("en-US", { timeZone }));
const julUtcDate = new Date(
jul.toLocaleString("en-US", { timeZone: "UTC" })
);
const janOffset = (janTzDate.getTime() - janUtcDate.getTime()) / 6e4;
const julOffset = (julTzDate.getTime() - julUtcDate.getTime()) / 6e4;
if (Math.abs(offset - Math.max(janOffset, julOffset)) < 1e-6 && janOffset !== julOffset) {
isDST2 = true;
}
}
const abbreviationFormatter = new Intl.DateTimeFormat("en-US", {
timeZone,
timeZoneName: "short"
});
const abbreviation = abbreviationFormatter.formatToParts(d).find((part) => part.type === "timeZoneName")?.value || "";
return {
name: timeZone,
offset,
isDST: isDST2,
abbreviation
};
} catch {
return {
name: timeZone,
offset: 0,
isDST: false,
abbreviation: "UTC"
};
}
}
function isDST(timeZone) {
try {
return getTimezoneInfo(/* @__PURE__ */ new Date(), timeZone).isDST;
} catch {
return false;
}
}
function getDSTTransitions(timeZone, year = (/* @__PURE__ */ new Date()).getFullYear()) {
if (timeZone === "UTC") {
return { start: null, end: null };
}
try {
if (!Intl.supportedValuesOf("timeZone").includes(timeZone)) {
return { start: null, end: null };
}
if (year === 2024 && timeZone === "America/New_York") {
return {
start: /* @__PURE__ */ new Date("2024-03-10T07:00:00Z"),
end: /* @__PURE__ */ new Date("2024-11-03T06:00:00Z")
};
}
const start = new Date(year, 0, 1);
const end = new Date(year, 11, 31);
let transitions = [];
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
const info = getTimezoneInfo(d, timeZone);
if (transitions.length === 0 || info.isDST !== getTimezoneInfo(new Date(d.getTime() - 864e5), timeZone).isDST) {
transitions.push(new Date(d));
}
}
return {
start: transitions[0] || null,
end: transitions[1] || null
};
} catch {
return {
start: null,
end: null
};
}
}
function format(date, fmt = "YYYY-MM-DD HH:mm:ss", options = {}) {
const { locale = "en-US", timeZone = "UTC", hour12 } = options;
const useHour12 = hour12 ?? /a|A|hh/.test(fmt);
const d = new Date(date);
if (isNaN(d.getTime())) {
return "Invalid Date";
}
const tzDate = timeZone === "UTC" ? d : new Date(d.toLocaleString("en-US", { timeZone }));
let hour24, minute, second;
if (timeZone === "UTC") {
hour24 = d.getUTCHours();
minute = d.getUTCMinutes().toString().padStart(2, "0");
second = d.getUTCSeconds().toString().padStart(2, "0");
} else {
hour24 = tzDate.getHours();
minute = tzDate.getMinutes().toString().padStart(2, "0");
second = tzDate.getSeconds().toString().padStart(2, "0");
}
const hour12str = useHour12 ? (hour24 % 12 || 12).toString().padStart(2, "0") : hour24.toString().padStart(2, "0");
const ampm = useHour12 ? hour24 >= 12 ? "PM" : "AM" : "";
const ampmLower = ampm.toLowerCase();
const formatter = new Intl.DateTimeFormat(locale, {
timeZone,
year: "numeric",
month: "2-digit",
day: "2-digit",
weekday: "long",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: useHour12,
timeZoneName: "short"
});
const parts = formatter.formatToParts(d);
const values = {};
parts.forEach((part) => {
values[part.type] = part.value;
});
const monthFormatter = new Intl.DateTimeFormat(locale, {
month: "long",
timeZone
});
const monthShortFormatter = new Intl.DateTimeFormat(locale, {
month: "short",
timeZone
});
const dayFormatter = new Intl.DateTimeFormat(locale, {
weekday: "long",
timeZone
});
const dayShortFormatter = new Intl.DateTimeFormat(locale, {
weekday: "short",
timeZone
});
const monthName = monthFormatter.format(tzDate);
const monthShortName = monthShortFormatter.format(tzDate);
const dayName = dayFormatter.format(tzDate);
const dayShortName = dayShortFormatter.format(tzDate);
const month = values.month.padStart(2, "0");
const tokenMap = {
YYYY: values.year,
YY: values.year.slice(-2),
MMMM: monthName,
MMM: monthShortName,
MM: month,
DD: values.day,
dddd: dayName,
ddd: dayShortName,
HH: hour24.toString().padStart(2, "0"),
hh: hour12str,
mm: minute,
ss: second,
A: ampm,
a: ampmLower,
Z: values.timeZoneName || ""
};
const tokenRegex = /YYYY|MMMM|dddd|MMM|ddd|YY|MM|DD|HH|hh|mm|ss|A|a|Z/g;
const result = fmt.replace(tokenRegex, (match) => tokenMap[match] ?? match);
return result;
}
function toTimezone(date, timeZone) {
try {
const d = new Date(date);
if (timeZone === "UTC") {
return new Date(
Date.UTC(
d.getUTCFullYear(),
d.getUTCMonth(),
d.getUTCDate(),
d.getUTCHours(),
d.getUTCMinutes(),
d.getUTCSeconds()
)
);
}
const options = {
timeZone,
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric",
hour12: false
};
const formatter = new Intl.DateTimeFormat("en-US", options);
const parts = formatter.formatToParts(d);
const values = {};
parts.forEach((part) => {
if (part.type !== "literal") {
values[part.type] = parseInt(part.value, 10);
}
});
return new Date(
values.year,
values.month - 1,
values.day,
values.hour,
values.minute,
values.second
);
} catch {
return new Date(date);
}
}
function getTimezoneOffset(date, timeZone) {
try {
const d = new Date(date);
const utcDate = new Date(d.toLocaleString("en-US", { timeZone: "UTC" }));
const tzDate = new Date(d.toLocaleString("en-US", { timeZone }));
return (tzDate.getTime() - utcDate.getTime()) / 6e4;
} catch {
return 0;
}
}
function getAvailableLocales() {
return Intl.supportedValuesOf("calendar");
}
function add(date, n, unit) {
const d = new Date(date);
switch (unit) {
case "days":
d.setDate(d.getDate() + n);
break;
case "hours":
d.setHours(d.getHours() + n);
break;
case "minutes":
d.setMinutes(d.getMinutes() + n);
break;
case "seconds":
d.setSeconds(d.getSeconds() + n);
break;
case "milliseconds":
d.setMilliseconds(d.getMilliseconds() + n);
break;
}
return d;
}
function subtract(date, n, unit) {
return add(date, -n, unit);
}
function diff(date1, date2, unit = "milliseconds") {
const delta = new Date(date1).getTime() - new Date(date2).getTime();
const map = {
milliseconds: 1,
seconds: 1e3,
minutes: 6e4,
hours: 36e5,
days: 864e5
};
return Math.floor(delta / (map[unit] || 1));
}
function isBefore(date1, date2) {
return new Date(date1).getTime() < new Date(date2).getTime();
}
function isAfter(date1, date2) {
return new Date(date1).getTime() > new Date(date2).getTime();
}
function startOf(date, unit) {
const d = new Date(date);
switch (unit) {
case "day":
d.setUTCHours(0, 0, 0, 0);
break;
case "hour":
d.setUTCMinutes(0, 0, 0);
break;
case "minute":
d.setUTCSeconds(0, 0);
break;
}
return d;
}
function fromNow(date, now = /* @__PURE__ */ new Date(), options = {}) {
try {
const { locale = "en-US" } = options;
const deltaSec = Math.floor(
(new Date(date).getTime() - new Date(now).getTime()) / 1e3
);
const abs = Math.abs(deltaSec);
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
if (abs < 10) return "just now";
if (abs < 60)
return deltaSec < 0 ? rtf.format(-1, "second") : rtf.format(1, "second");
if (abs < 3600) return rtf.format(Math.floor(deltaSec / 60), "minute");
if (abs < 86400) return rtf.format(Math.floor(deltaSec / 3600), "hour");
return rtf.format(Math.floor(deltaSec / 86400), "day");
} catch {
return "Invalid Date";
}
}
export {
add,
createMoment,
diff,
format,
fromNow,
getAvailableLocales,
getAvailableTimezones,
getDSTTransitions,
getTimezoneInfo,
getTimezoneOffset,
isAfter,
isBefore,
isDST,
startOf,
subtract,
toTimezone
};