Thursday, March 28, 2024

A Roundup of Popular JavaScript Date Parsing Libraries: Moment.js

While writing an article on Date Parsing using JavaScript and Regular Expressions I was delighted to discover a bunch of JavaScript libraries that simplify working with JS dates. Had I thought of that while writing my own script, I might have saved myself a lot work! Now that I know better, I’d like to present a few libraries that excel at parsing dates from a string. I’ll also highlight some other date and time tasks that can benefit from a library along the way. Today’s article focuses on the outstanding Moment.js library.

A Quick Overview

The Moment.js library not only parses dates from strings, but it may also be used to validate, manipulate, and format dates. It supports internationalization, which is so important with dates, as well as human-friendly formatting like “Last Friday at 9:48”.

These are all good things for sure, but today’s order of the day is date parsing, so let’s get into how that works.

Moment.js creates a wrapper for the Date object rather than extend it. To reference the wrapper object, simply call the moment() getter function. The Moment prototype is exposed through the moment.fn property, so you can add your own functions to it if you are so inclined.

Constructor Parameters

Calling moment() with no parameters returns the current date and time just like new Date() does:

var now = moment();

It can also accept a string argument, but beware. It passes the string to JavaScript’s wonky Date.parse() method, and we know how that can turn out!

var day = moment("Dec 25, 1995"); //no problem

var day = moment("1995-12-25");   //potential problem!

Passing in a format argument will yield more consistent results. The tokens used to describe date parts bear a strong resemblance to the Unicode Locale Data Markup Language (LDML), but with some minor differences regarding day of month, day of year, and day of week.

To parse the universal date format shown above, we would supply a format of “YYYY-MM-DD” to the moment() constructor:

<head>
<meta charset="UTF-8">
<script type="text/javascript" src="scripts/moment-with-langs.js"></script>
</head>
<body>
<span id="output"></span>
<script type="text/javascript">
var dt = moment("1995-12-25", "YYYY-MM-DD");

document.getElementById("output").innerHTML = dt;  //displays Mon Dec 25 1995 00:00:00 GMT-0500
</script>
</body>

 

Applying the Parser to Text Blocks

Sending in a date string is one thing, but what I really want to do is parse a date from a block of text. Here’s all that it takes to parse out the date and time from the same example that I used in my Date Parsing using JavaScript and Regular Expressions article:

var str = "You have a doctor's appointment on 2012/03/13 16:00.  Please show up on time.";
var dt = moment(str, "YYYY/MM/DD HH:mm");

document.getElementById("output").innerHTML = dt; //displays Tue Mar 13 2012 16:00:00 GMT-0400

Unless you specify a time zone offset, parsing a string will create a date in the current time zone. If you’d rather specify Greenwich Mean Time (GMT), you can do that by including the “Z” (i.e, +07:00) or “ZZ” (i.e, +0700) time zone offset tokens:

moment("2012/03/13 16:00", "YYYY/MM/DD HH:mm");   // parsed as 4:00 PM local time
moment("2012/03/13 16:00", "YYYY/MM/DD HH:mm Z"); // parsed as 4:00 PM GMT

Effects of Daylight Savings Time

Did you happen to notice that the first date displays a GMT offset of -0500 while the second shows -0400. I thought that odd considering that both dates are being set locally in my time zone of Eastern Standard Time. What gives? The answer is that Daylight Savings Time (DST) does affect the GMT offset for the summer months when DST is in effect in some areas. It doesn’t affect the actual date information so it’s safe to remove it by formatting displayed dates yourself. That’s easily done as long as you don’t try to include the time zone in the description, such as “March 13, 2012 16:00:00 EST”. Moment.js used to include it, until unit tests started failing! The problem according to the Moment.js docs is that:

Date.prototype.toString returns such different results. That is the only place to get the time zone name (PST, CST, EST, etc). If this method is not returning any time zone information (as is the case with FF10 and IE9, there is no way to get it.

If you’re really keen on getting some kind of time zone description in there, you’ve got a couple of options. You can go about writing some thing up. The native JS getTimezoneOffset()function does return a different value during DST and standard time, so you can compare the difference between the two. Here‘s a Time zone list and Epoch to time zone converter that you can reference. In the end, you’ll probably end up with something along the lines of:

function getTimezoneName() {
    tmSummer = new Date(Date.UTC(2005, 6, 30, 0, 0, 0, 0));
    so = -1 * tmSummer.getTimezoneOffset();
    tmWinter = new Date(Date.UTC(2005, 12, 30, 0, 0, 0, 0));
    wo = -1 * tmWinter.getTimezoneOffset();

    if (-660 == so && -660 == wo) return 'Pacific/Midway';
    if (-600 == so && -600 == wo) return 'Pacific/Tahiti';
    if (-570 == so && -570 == wo) return 'Pacific/Marquesas';
    if (-540 == so && -600 == wo) return 'America/Adak';
    if (-540 == so && -540 == wo) return 'Pacific/Gambier';
    if (-480 == so && -540 == wo) return 'US/Alaska';
    if (-480 == so && -480 == wo) return 'Pacific/Pitcairn';
    if (-420 == so && -480 == wo) return 'US/Pacific';
    if (-420 == so && -420 == wo) return 'US/Arizona';
    if (-360 == so && -420 == wo) return 'US/Mountain';
    if (-360 == so && -360 == wo) return 'America/Guatemala';
    if (-360 == so && -300 == wo) return 'Pacific/Easter';
    if (-300 == so && -360 == wo) return 'US/Central';
    if (-300 == so && -300 == wo) return 'America/Bogota';
    if (-240 == so && -300 == wo) return 'US/Eastern';
    if (-240 == so && -240 == wo) return 'America/Caracas';
    if (-240 == so && -180 == wo) return 'America/Santiago';
    if (-180 == so && -240 == wo) return 'Canada/Atlantic';
    if (-180 == so && -180 == wo) return 'America/Montevideo';
    if (-180 == so && -120 == wo) return 'America/Sao_Paulo';
    if (-150 == so && -210 == wo) return 'America/St_Johns';
    if (-120 == so && -180 == wo) return 'America/Godthab';
    if (-120 == so && -120 == wo) return 'America/Noronha';
    if (-60 == so && -60 == wo) return 'Atlantic/Cape_Verde';
    if (0 == so && -60 == wo) return 'Atlantic/Azores';
    if (0 == so && 0 == wo) return 'Africa/Casablanca';
    if (60 == so && 0 == wo) return 'Europe/London';
    if (60 == so && 60 == wo) return 'Africa/Algiers';
    if (60 == so && 120 == wo) return 'Africa/Windhoek';
    if (120 == so && 60 == wo) return 'Europe/Amsterdam';
    if (120 == so && 120 == wo) return 'Africa/Harare';
    if (180 == so && 120 == wo) return 'Europe/Athens';
    if (180 == so && 180 == wo) return 'Africa/Nairobi';
    if (240 == so && 180 == wo) return 'Europe/Moscow';
    if (240 == so && 240 == wo) return 'Asia/Dubai';
    if (270 == so && 210 == wo) return 'Asia/Tehran';
    if (270 == so && 270 == wo) return 'Asia/Kabul';
    if (300 == so && 240 == wo) return 'Asia/Baku';
    if (300 == so && 300 == wo) return 'Asia/Karachi';
    if (330 == so && 330 == wo) return 'Asia/Calcutta';
    if (345 == so && 345 == wo) return 'Asia/Katmandu';
    if (360 == so && 300 == wo) return 'Asia/Yekaterinburg';
    if (360 == so && 360 == wo) return 'Asia/Colombo';
    if (390 == so && 390 == wo) return 'Asia/Rangoon';
    if (420 == so && 360 == wo) return 'Asia/Almaty';
    if (420 == so && 420 == wo) return 'Asia/Bangkok';
    if (480 == so && 420 == wo) return 'Asia/Krasnoyarsk';
    if (480 == so && 480 == wo) return 'Australia/Perth';
    if (540 == so && 480 == wo) return 'Asia/Irkutsk';
    if (540 == so && 540 == wo) return 'Asia/Tokyo';
    if (570 == so && 570 == wo) return 'Australia/Darwin';
    if (570 == so && 630 == wo) return 'Australia/Adelaide';
    if (600 == so && 540 == wo) return 'Asia/Yakutsk';
    if (600 == so && 600 == wo) return 'Australia/Brisbane';
    if (600 == so && 660 == wo) return 'Australia/Sydney';
    if (630 == so && 660 == wo) return 'Australia/Lord_Howe';
    if (660 == so && 600 == wo) return 'Asia/Vladivostok';
    if (660 == so && 660 == wo) return 'Pacific/Guadalcanal';
    if (690 == so && 690 == wo) return 'Pacific/Norfolk';
    if (720 == so && 660 == wo) return 'Asia/Magadan';
    if (720 == so && 720 == wo) return 'Pacific/Fiji';
    if (720 == so && 780 == wo) return 'Pacific/Auckland';
    if (765 == so && 825 == wo) return 'Pacific/Chatham';
    if (780 == so && 780 == wo) return 'Pacific/Enderbury'
    if (840 == so && 840 == wo) return 'Pacific/Kiritimati';
    return 'US/Pacific';
}

Maybe you should hold off before writing your own client-side GMT-to-local time zone converter. Remember that one of the take-aways from this article is that there is probably already a library that does what you want. The creator of Moment.js recommends the time zone-js library. One of its many functions includes getTimezoneAbbreviation(). Once you’ve got a date, you can call it to get the time zone abbreviation:

var dt = new timezoneJS.Date('10/31/2008', 'America/New_York');
dt.getTimezoneAbbreviation(); => 'EDT'

Conclusion

Our first JS date library certainly worked beautifully for me and I’ve only scratched the surface of what it can do. As tempting as it would be to stop there, I have been working on comparing between items before settling on one as part of my New Year’s resolutions. Therefore, I’m going to try another library next month and see how it stacks up. I hope that you’ll join me for that!

Rob Gravelle
Rob Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Popular Articles

Featured