Last active
April 20, 2024 03:48
-
-
Save westc/9e824d092306de9827ed78121d9ba69f to your computer and use it in GitHub Desktop.
A more intuitive version of a JS date formatter. This function leverages Intl.DateTimeFormat().
This file contains 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
var formatIntlDate = (() => { | |
const codeToOpts = [...'Y=year M=month D=day H=hour h=hour m=minute s=second S:1 SS:2 SSS:3 MMM=month:short MMMM=month:long DDD=weekday:short DDDD=weekday:long A=hour:2D a=hour:2D Z:Offset ZZ:Offset ZZZ:Generic ZZZZ: J=year:numeric,month:2D,day:2D,hourCycle:h23,hour:2D,minute:2D,second:2D,fractionalSecondDigits:3,timeZoneName:longOffset'.replace(/(\w)=\w+(?= |$)/g, '$&:numeric $1$&:2D').replace(/hour/g, 'hourCycle:h23,$&').replace(/Z:/g, 'Z=timeZoneName:long').replace(/S:/g, 'S=fractionalSecondDigits:').replace(/2D/g, '2-digit').matchAll(/(\w+)=(\S+)/g)] | |
.reduce( | |
(codeToOpts, [_, code, strOpts]) => { | |
codeToOpts[code] = [...strOpts.matchAll(/(\w+):([^,]+)/g)].reduce( | |
(opts, [_, key, value]) => { | |
opts[key] = value; | |
return opts; | |
}, | |
{} | |
); | |
return codeToOpts; | |
}, | |
{} | |
); | |
/** | |
* Formats a date as a string. | |
* @param {Date} date | |
* The valid date object to format. | |
* @param {string} format | |
* The format to use. You can use any of the following metacharacters: | |
* Y, YY, M, MM, MMM, MMMM, D, DD, DDD, DDDD, H, HH, h, hh, m, mm, s, ss, | |
* S, SS, SSS, Z , ZZ, ZZZ, ZZZZ, d, dd, ddd, dddd, t, tt, ttt, tttt, A, | |
* a, J | |
* | |
* Wrap substrings in single quotes or double quotes to avoid formatting the | |
* metacharacters. To escape a quote character prefix it with a backslash. | |
* @param {?FormatIntlDateOptions=} options | |
* Optional. Allows you to specify the locale and/or the timezone to use | |
* when producing the formatted date/time string. | |
* @returns {string} | |
* A string which represents the given date object. | |
*/ | |
function formatIntlDate(date, format, options) { | |
const {locale, timeZone} = Object(options); | |
return format.replace( | |
/("|')((?:(?:(?!\1)[^\\])+|\\["'])*)\1|(?=(.))(?:[AJa]|[YHhms]{1,2}|[DMZdt]{1,4}|S{1,3})/g, | |
(pattern, strDelim, strInnards, pattern0) => { | |
if (strDelim) return strInnards.replace(/\\(.)/g, '$1'); | |
if (pattern0 === 'd' || pattern0 === 't') { | |
return new Intl.DateTimeFormat(locale, { | |
[pattern0 === 'd' ? 'dateStyle' : 'timeStyle']: ['short', 'medium', 'long', 'full'][pattern.length-1], | |
timeZone | |
}).format(date); | |
} | |
const opts = codeToOpts[pattern]; | |
const dtf = new Intl.DateTimeFormat((pattern === 'J' || pattern === 'Z') ? 'en-US' : locale, {...opts, timeZone}); | |
if (pattern === 'J') { | |
return (dtf.format(date) + 'Z') | |
.replace(/^(..).(..).(....), ([\d:.]+) (?:GMT([-+\d:]+)Z|GMT(Z))$/, '$3-$1-$2T$4$5$6'); | |
} | |
let {type, value} = dtf.formatToParts(date).find(({type}) => type === 'fractionalSecond' || opts[type]); | |
if (pattern === 'Z') return value === 'GMT' ? '+00:00' : value.slice(3); | |
if (pattern === 'a') return +value < 12 ? 'am' : 'pm'; | |
if (pattern === 'A') return +value < 12 ? 'AM' : 'PM'; | |
if (pattern === 'h' || pattern === 'hh') value = value % 12 || 12; | |
const optsTypeValue = opts[type]; | |
return (optsTypeValue === 'numeric' && /^0./.test(value)) | |
? +value | |
: (optsTypeValue === '2-digit' && /^.$/.test(value)) | |
? '0' + value | |
: value; | |
} | |
); | |
} | |
return formatIntlDate; | |
/** | |
* @typedef FormatIntlDateOptions | |
* @property {string=} locale | |
* The locale to use for names in the formatted date (eg. "en", "pt-BR"). | |
* @property {string=} timeZone | |
* The timezone to use for displaying the date/time as a string (eg. | |
* "America/Los_Angeles"). | |
*/ | |
})(); |
This file contains 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
const format = ` | |
"YY":\tYY | |
"Y":\tY | |
"MMMM":\tMMMM | |
"MMM":\tMMM | |
"MM":\tMM | |
"M":\tM | |
"DDDD":\tDDDD | |
"DDD":\tDDD | |
"DD":\tDD | |
"D":\tD | |
"HH":\tHH | |
"H":\tH | |
"hh":\thh | |
"h":\th | |
"mm":\tmm | |
"m":\tm | |
"ss":\tss | |
"s":\ts | |
"SSS":\tSSS | |
"SS":\tSS | |
"S":\tS | |
"A":\tA | |
"a":\ta | |
"ZZZZ":\tZZZZ | |
"ZZZ":\tZZZ | |
"ZZ":\tZZ | |
"Z":\tZ | |
"dddd":\tdddd | |
"ddd":\tddd | |
"dd":\tdd | |
"d":\td | |
"tttt":\ttttt | |
"ttt":\tttt | |
"tt":\ttt | |
"t":\tt | |
"J":\tJ | |
`; | |
for (const timeZone of ['Australia/Adelaide', 'UTC', 'America/New_York', 'America/La_Paz']) { | |
console.group(`Timezone = ${timeZone}`); | |
for (const locale of ['en', 'es', 'pt', 'ja', 'ar']) { | |
console.log(`locale = ${locale}` + formatIntlDate(new Date('2001-02-03 16:05:06.007'), format, {locale, timeZone})); | |
} | |
console.groupEnd(); | |
} |
This file contains 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
const format = 'DDDD, D MMMM, Y @ h:mm:ss.SSS (ZZZZ)'; | |
for (const timeZone of ['Australia/Adelaide', 'UTC', 'America/New_York', 'America/La_Paz']) { | |
console.group(`Timezone = ${timeZone}`); | |
for (const locale of ['en', 'es', 'pt', 'ja', 'ar']) { | |
console.log(`locale = ${locale}: ` + formatIntlDate(new Date(), format, {locale, timeZone})); | |
} | |
console.groupEnd(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment