This solution uses Intl.DateTimeFormat()
so that we can utilize the FormatToParts()
function then apply a custom map()
then finally reduce()
to get the desired output.
Within the map()
function we append the day suffixes to output 'st', 'nd', 'rd', and 'th'. E.g. 1st, 2nd, 3rd, 4th. The suffix is appended to the day itself, as opposed to the corresponding literal
key, because we need to know the value of the day so this was the easiest route to take.
Where we do replace the value of the literal
it is mostly to remove extra commas (there by default) and to also to display at
before the the time.
References at MDN:
const d = new Date();
const formatOptions = {
weekday: 'long', month: 'long', day: 'numeric', hour: 'numeric',
minute: 'numeric', year: 'numeric', timeZoneName: 'short', hour12: true
};
// Using Intl.DateTimeFormat so we have access to
// formatToParts function which produces
// an Array of objects containing the formatted date in parts
const dateFormat = new Intl.DateTimeFormat(undefined, formatOptions);
// Keep track of 'literal' key counts (`literal` is the separator)
let separatorIndex = 0;
// Where the magic happens using map() and reduce()
const dateString = dateFormat.formatToParts(d).map(({type, value}) => {
switch (type) {
case 'literal' :
separatorIndex++;
switch (separatorIndex) {
case 3 : // Day separator
return ' ';
break;
case 4 : // Year separator
return ' at ';
default: return value;
}
break;
case 'day' :
// Append appropriate suffix
switch (value) {
case (value.slice(-1) === 1) :
return value.concat('st');
break;
case (value.slice(-1) === 2) :
return value.concat('nd');
break;
case (value.slice(-1) === 3) :
return value.concat('rd');
break;
default: return value.concat('th');
}
break;
case 'hour' :
// Prepend 0 when less than 10 o'clock
return (value < 10) ? '0'.concat(value) : value;
break;
default: return value;
}
}).reduce((string, part) => {
return string.concat(part)
});
// Not part of answer; only for output result
document.write(dateString);