Skip to content

Instantly share code, notes, and snippets.

@westc
Last active April 20, 2024 03:48
Show Gist options
  • Save westc/9e824d092306de9827ed78121d9ba69f to your computer and use it in GitHub Desktop.
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().
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").
*/
})();
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();
}
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