type DateFormatToken = | 'd' | 'D' | 'j' | 'l' | 'N' | 'S' | 'w' | 'z' | 'W' | 'F' | 'm' | 'M' | 'n' | 't' | 'L' | 'o' | 'Y' | 'y' | 'a' | 'A' | 'B' | 'g' | 'G' | 'h' | 'H' | 'i' | 's' | 'u' | 'e' | 'I' | 'O' | 'P' | 'T' | 'Z' | 'c' | 'r' | 'U'
type DateFormatterMap = { [K in DateFormatToken]: () => string | number }
export function date(format: string, timestamp?: number | Date | string): string {
let jsdate = new Date() let f!: DateFormatterMap const txtWords = [ 'Sun', 'Mon', 'Tues', 'Wednes', 'Thurs', 'Fri', 'Satur', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ] const formatChr = /\\?(.?)/gi const hasFormatterToken = (token: string): token is DateFormatToken => Object.hasOwn(f, token) const formatChrCb = function (t: string, s: string): string { return hasFormatterToken(t) ? String(f[t]()) : s } const _pad = function (n: string | number, c: number): string { let str = String(n) while (str.length < c) { str = '0' + str } return str } f = { d: function () { return _pad(f.j(), 2) }, D: function () { return String(f.l()).slice(0, 3) }, j: function () { return jsdate.getDate() }, l: function () { return (txtWords[Number(f.w())] ?? '') + 'day' }, N: function () { return Number(f.w()) || 7 }, S: function () { const j = Number(f.j()) let i = j % 10 if (i <= 3 && Number.parseInt(String((j % 100) / 10), 10) === 1) { i = 0 } return ['st', 'nd', 'rd'][i - 1] || 'th' }, w: function () { return jsdate.getDay() }, z: function () { const a = new Date(Number(f.Y()), Number(f.n()) - 1, Number(f.j())) const b = new Date(Number(f.Y()), 0, 1) return Math.round((a.getTime() - b.getTime()) / 864e5) },
W: function () { const a = new Date(Number(f.Y()), Number(f.n()) - 1, Number(f.j()) - Number(f.N()) + 3) const b = new Date(a.getFullYear(), 0, 4) return _pad(1 + Math.round((a.getTime() - b.getTime()) / 864e5 / 7), 2) },
F: function () { return txtWords[6 + Number(f.n())] ?? '' }, m: function () { return _pad(f.n(), 2) }, M: function () { return String(f.F()).slice(0, 3) }, n: function () { return jsdate.getMonth() + 1 }, t: function () { return new Date(Number(f.Y()), Number(f.n()), 0).getDate() },
L: function () { const j = Number(f.Y()) return (j % 4 === 0 && j % 100 !== 0) || j % 400 === 0 ? 1 : 0 }, o: function () { const n = Number(f.n()) const W = Number(f.W()) const Y = Number(f.Y()) return Y + (n === 12 && W < 9 ? 1 : n === 1 && W > 9 ? -1 : 0) }, Y: function () { return jsdate.getFullYear() }, y: function () { return String(f.Y()).slice(-2) },
a: function () { return jsdate.getHours() > 11 ? 'pm' : 'am' }, A: function () { return String(f.a()).toUpperCase() }, B: function () { const H = jsdate.getUTCHours() * 36e2 const i = jsdate.getUTCMinutes() * 60 const s = jsdate.getUTCSeconds() return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3) }, g: function () { return Number(f.G()) % 12 || 12 }, G: function () { return jsdate.getHours() }, h: function () { return _pad(f.g(), 2) }, H: function () { return _pad(f.G(), 2) }, i: function () { return _pad(jsdate.getMinutes(), 2) }, s: function () { return _pad(jsdate.getSeconds(), 2) }, u: function () { return _pad(jsdate.getMilliseconds() * 1000, 6) },
e: function () {
const msg = 'Not supported (see source code of date() for timezone on how to add support)' throw new Error(msg) }, I: function () { const a = new Date(Number(f.Y()), 0) const c = Date.UTC(Number(f.Y()), 0) const b = new Date(Number(f.Y()), 6) const d = Date.UTC(Number(f.Y()), 6) return a.getTime() - c !== b.getTime() - d ? 1 : 0 }, O: function () { const tzo = jsdate.getTimezoneOffset() const a = Math.abs(tzo) return (tzo > 0 ? '-' : '+') + _pad(Math.floor(a / 60) * 100 + (a % 60), 4) }, P: function () { const O = String(f.O()) return O.slice(0, 3) + ':' + O.slice(3, 5) }, T: function () {
return 'UTC' }, Z: function () { return -jsdate.getTimezoneOffset() * 60 },
c: function () { return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb) }, r: function () { return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb) }, U: function () { return (jsdate.getTime() / 1000) | 0 }, }
const _date = function (formatStr: string, timestampValue?: number | Date | string): string { jsdate = timestampValue === undefined ? new Date() : timestampValue instanceof Date ? new Date(timestampValue) : new Date(Number(timestampValue) * 1000) return formatStr.replace(formatChr, formatChrCb) }
return _date(format, timestamp) }
|