Skip to content

Instantly share code, notes, and snippets.

@patrickroberts
Last active April 18, 2020 07:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save patrickroberts/38aa2de1c47e37a6a2d45fb829232b88 to your computer and use it in GitHub Desktop.
Save patrickroberts/38aa2de1c47e37a6a2d45fb829232b88 to your computer and use it in GitHub Desktop.
Small utility library for formatting Date objects in JavaScript
/**
* Usage:
*
* const fmt = formatDate('%dd-%MMM-%yy', 'en-US');
* console.log(fmt(new Date())); // 18-Apr-20
*/
function formatDate (fmt, locales, defaultOptions) {
function formatPart (option, value, fallbackImpl, options = { [option]: value }) {
// lazily initialize format implementation
let formatImpl;
return function format (date) {
if (typeof formatImpl !== 'function' && typeof window?.Intl?.DateTimeFormat === 'function') {
const dateTimeFormat = new window.Intl.DateTimeFormat(
locales,
{ ...defaultOptions, ...options, [option]: value }
);
const resolved = Object.entries(options).every(
function ([key, expectedValue]) {
return this[key] === expectedValue;
},
dateTimeFormat.resolvedOptions()
);
if (resolved) {
formatImpl = function dateTimeImpl (date) {
return dateTimeFormat.formatToParts(date).find(
({ type }) => type === option
).value;
};
}
}
if (typeof formatImpl !== 'function' && typeof fallbackImpl === 'function') {
formatImpl = fallbackImpl;
}
if (typeof formatImpl !== 'function') {
formatImpl = function unsupportedImpl () {
throw new Error(`Unsupported format option ${option}`);
};
}
return formatImpl(date);
};
}
const part = {
d: formatPart('day', 'numeric', date => `${date.getDate()}`),
dd: formatPart('day', '2-digit', date => part.d(date).padStart(2, '0')),
ddd: formatPart('weekday', 'short'),
dddd: formatPart('weekday', 'long'),
f: date => part.fff(date).slice(0, 1),
ff: date => part.fff(date).slice(0, 2),
fff: date => `${date.getMilliseconds()}`.padStart(3, '0'),
F: date => part.f(date).replace('0', ''),
FF: date => part.ff(date).replace('00', ''),
FFF: date => part.fff(date).replace('000', ''),
g: formatPart('era', 'short'),
gg: formatPart('era', 'long'),
h: formatPart('hour', 'numeric', date => `${(date.getHours() % 12) || 12}`, { hour: 'numeric', hour12: true }),
hh: formatPart('hour', '2-digit', date => part.h(date).padStart(2, '0'), { hour: '2-digit', hour12: true }),
H: formatPart('hour', 'numeric', date => `${date.getHours()}`, { hour: 'numeric', hour12: false }),
HH: formatPart('hour', '2-digit', date => part.H(date).padStart(2, '0'), { hour: '2-digit', hour12: false }),
K: formatPart('timeZoneName', 'short'),
KK: formatPart('timeZoneName', 'long'),
m: formatPart('minute', 'numeric', date => `${date.getMinutes()}`),
mm: formatPart('minute', '2-digit', date => part.m(date).padStart(2, '0')),
M: formatPart('month', 'numeric', date => `${date.getMonth() + 1}`),
MM: formatPart('month', '2-digit', date => part.M(date).padStart(2, '0')),
MMM: formatPart('month', 'short'),
MMMM: formatPart('month', 'long'),
s: formatPart('second', 'numeric', date => `${date.getSeconds()}`),
ss: formatPart('second', '2-digit', date => part.s(date).padStart(2, '0')),
t: formatPart('dayPeriod', 'narrow', undefined, { hour: 'numeric', hour12: true }),
tt: formatPart('dayPeriod', 'short', undefined, { hour: 'numeric', hour12: true }),
ttt: formatPart('dayPeriod', 'long', undefined, { hour: 'numeric', hour12: true }),
yy: formatPart('year', '2-digit', date => `${date.getFullYear() % 100}`.padStart(2, '0')),
yyyy: formatPart('year', 'numeric', date => `${date.getFullYear()}`.padStart(4, '0'))
};
return (date = new Date()) => fmt.replace(
/%(([A-Za-z])\2*)/g,
(match, key) => {
if (part.hasOwnProperty(key)) {
return part[key](date);
}
throw new SyntaxError(`Invalid specifier ${match}`);
}
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment