Introduction
I needed a JavaScript date format function such as the Visual Basic Format
function, in which you can pass a format string; my first approach was to issue a series of consecutive and "destructive" replace calls, but upon discovering that the 5.5 (or higher) version of JScript supported the use of a function as the replaceText
argument of the replace
method, I got creative.
Here's an example call of what I wanted:
SomeDiv.innerText = (new Date()).format('dddd, mmmm dd, yyyy.');
This would display:
Saturday, July 16, 2005
So in my first approach, I globally and case-insensitively replaced dddd with the corresponding string, which "destroyed" every occurrence, so that later in the code I could replace dd with the date number.
This worked just fine, but I knew that by inspecting the format specifier for a match, I could skip the search of every format specifier; say I only want the month and the date; well, by switching upon the format specifier (or rather "datepart" specifier), the year replacement will never be issued. Get it?
The fun part relies in the use of a function in the replaceText
argument of the replace
method; this way the $1
property as a function argument always represents the last match.
Other considerations include the format or "datepart" specifiers: none other than yyyy
will be parsed as the year; months and days have the usual three flavors of fullname (mmmm
), three-letter (mmm
) or numeric (mm
); hours (hh
) can be rectified to the 12-hour format with the a/p
specifier, and minutes (nn
) and seconds (ss
) may also be specified.
Implementation
WOFA, (Without Further Adou):
var gsMonthNames = new Array(
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
);
var gsDayNames = new Array(
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday'
);
Date.prototype.format = function(f)
{
if (!this.valueOf())
return ' ';
var d = this;
return f.replace(/(yyyy|mmmm|mmm|mm|dddd|ddd|dd|hh|nn|ss|a\/p)/gi,
function($1)
{
switch ($1.toLowerCase())
{
case 'yyyy': return d.getFullYear();
case 'mmmm': return gsMonthNames[d.getMonth()];
case 'mmm': return gsMonthNames[d.getMonth()].substr(0, 3);
case 'mm': return (d.getMonth() + 1).zf(2);
case 'dddd': return gsDayNames[d.getDay()];
case 'ddd': return gsDayNames[d.getDay()].substr(0, 3);
case 'dd': return d.getDate().zf(2);
case 'hh': return ((h = d.getHours() % 12) ? h : 12).zf(2);
case 'nn': return d.getMinutes().zf(2);
case 'ss': return d.getSeconds().zf(2);
case 'a/p': return d.getHours() < 12 ? 'a' : 'p';
}
}
);
}
Notes
- A date with a value of 0 returns a non-breaking space.
- Notice how the
d
variable is available to the replacement function (but the this
object is not).
- The
zf
number prototype can be found in Extending JavaScript Objects with Prototypes, where it is called 'zp' for zero padding. It pads a number with zeroes the specified number of times, up to the number's character length, i.e. 2 turns into 02, but 16 remains 16.
- The regular expression looks for any of the bracketed pattern characters in a two+ sequence, or for the very specific a/p match.
- Defining names globally helps and serves other purposes, i.e. listing days in a calendar.
Enjoy.
Professional software engineer with 30+ years of experience delivering systems across diverse industries, looking for the next opportunity to deliver cutting edge end-to-end technology solutions.
Avid reader, disciplined writer and enthusiastic tinkerer with a background in electronics, looking inside and thinking outside the box, genuinely passionate about robust, extensible, reusable and performant code.
Framework developer leading, coaching and learning about best practices, code quality, DevOps and software and data lifecycle management with an agile mindset to create the most elegant and sustainable solutions.