Have a Moment(.js)?

It’s been a long time since Javascript was used mostly to present a real time clock* on your homepage header, or even worse, just next to the mouse pointer. JS is now mature and script kiddies are gone. With Node, Angular (and so on, and so forth) whole applications are created using solely or mainly Javascript. Still once in a while a developer struggles with time related issues in the code. But there is a cure for that (of course there is in random, not even necessarily verb.js era). Please spend a moment on getting to know Moment.js.

*Yes, I noticed a giant clock on Moment’s homepage, but it seems to suit there, doesn’t it?

The are several main tasks that Moment.js can help you with. Parsing, validating, calculating and presenting time, time differences and time zones (when paired with moment-timezone.js). And that’s quite a relief − if you didn’t ever get something like ‘NaN -03:-01’ in your browser, you probably didn’t get your hands dirty enough. So now it’s time to look at some examples.

First, we’ll simply get a formatted date so we can e.g. display it.

> moment().format("dddd, MMMM Do YYYY, h:mm:ss a")
"Wednesday, October 21th 2015, 13:00:00 pm"

This can be applied not only to current time, but also to any particular moment.

> moment("1970-01-01 00:00:00").format("dddd, MMMM Do YYYY, h:mm:ss a")
"Thursday, January 1st 1970, 12:00:00 am"

Right, whatever fancy time and date format I’d come up with, it’s not really impressive to simply display it. Let’s find out something more interesting − the number of hours left till the end of the day:

> moment().endOf('day').fromNow()
"in 11 hours"

Or even more important issues − how much time do I have to stay here before I can go home?

> moment({'h':18,'m':00}).fromNow()
"in 5 hours"

Just kidding, I will stay here coding all day long. 😉

But thanks to the example you could see one of a few ways of creating a moment:

  • moment() – is for now
  • moment("2015-01-01") – for a string which matches ISO 8601. Not really advised due to being heavily dependent on browser support
  • moment("2015-01-01","YYYY-MM-DD") – that’s much better if we tell Moment.js what format to use

In fact, there can be multiple formats provided as an array. Moment.js will try to match one of them. Which of course is pointless when we provide date in the code, but it makes much more sense while interpreting user generated content. We can additionally provide locale or ask Moment.js to parse with strict rules.

And finally a method showed previously – object with values provided for every or some parts of a moment. The rest is set to 0 (time parts) or current date (date parts).

Ok, let’s move to some strict science and use what we have learnt in practice. Let’s assume we want to know something more about that particular moment: 2013-02-08 09:30:26.123 in the Far East, say in time zone UTC+7 (anybody in UTC+7 reading this text? Greetings from UTC+2 DST!). I wonder what day it was in Europe by then (a bit tricky since we’re not in the same time zone).

> moment('2013-02-08 09:30:26.123+07:00').format('dddd[,] DDDo [day of year]')
"Friday, 39th day of year"

And now we know.

Speaking of time zones – Moment.js have special mode which can get you on a solid UTC ground. It’s simply moment().utc() and now every method will be UTC based.

But if you are not lucky enough to work with UTC, there is still some hope for you. Hope provided by Moment Timezone. When added to Moment.js it provides tz() method for moment, allowing you to easily define a desired time zone.

moment().tz('America/New_York').format()
"2015-10-21T07:00:00-04:00"

If you got a bit impatient while I’m still displaying some magic numbers, here’s something for you. We will use moment to validate time and date. This is usually not an easy task. But now you can create a moment and simply check whether it is correct or not:

> moment().isValid(); //it'd better be valid
true

And if you would like e.g. to tell a user what part of the provided date was wrong, you can check it by

> moment("2015-01-00").invalidAt()
2

which will provide you with an index of invalid field – 0 is for years, 6 for milliseconds.

If you want to know more about parsing, you can run parsingFlags() on your invalid moment. It will produce an object like this:

> moment("2015-01-00").parsingFlags()
{
 "empty": false,
 "unusedTokens": [],
 "unusedInput": [],
 "overflow": 2,
 "charsLeftOver": 0,
 "nullInput": false,
 "invalidMonth": null,
 "invalidFormat": false,
 "userInvalidated": false,
 "iso": true
}

Parsing, validating and displaying time is a business strictly connected to user locale. Fortunately Moment.js creators (aren’t we all moment creators? apparently not until we join Moment development on GitHub) know it very well. There is locale() method to set proper options, but if that’s not enough for you, you can create your own i18n settings or overwrite default ones.

> moment().locale('pt').format("dddd, MMMM Do YYYY, hh:mm:ss a")
"Quarta-Feira, Outubro 21º 2015, 02:29:15 pm"

Please note – moment().locale(‘pt’) will set Portuguese locale only for this one methods chain. Calling moment.locale(‘pt’) will set it from now on.

Moment is a blink of an eye. What if we need something lasting a bit longer? We should use a duration. We can create a duration providing milliseconds by default, or other unit of time specifying it. Also objects (like for creating moment) and some more ways are available. Just have a look at this example:

> moment.duration(24, "hours")
> moment.duration({'h':18, 'm':30});

A duration is a large object so I won’t present the results in raw form. But when we have a duration, we can (not again…) display it. Of course we could just as well display a seed value. But a duration can be humanized!

> moment.duration(24, "hours").humanize()
"a day"
> moment.duration({'h':18, 'm':30}).humanize()
"19 hours"

19 hours… let’s hope it will be more precise in the next examples with adding and subtracting time:

> moment("2015-01-01 00:00:00").add(moment.duration({'h':18,'m':30})).format()
"2015-01-01T18:30:00+01:00"
> moment("2015-01-01 00:00:00").subtract(moment.duration({'h':18,'m':30})).format()
"2014-12-31T05:30:00+01:00"

We used explicit durations here, but in fact it would be enough to provide an object directly to add/subtract method:

> moment("2015-01-01 00:00:00").add({'h':18,'m':30}).format()
"2015-01-01T18:30:00+01:00"
> moment("2015-01-01 00:00:00").subtract({'h':18,'m':30}).format()
"2014-12-31T05:30:00+01:00"

Back to precision once more: if you read documentation for durations, you can see that a month is 30 days and year – 365. Well, that’s not totally cool, but on the other hand – durations are contextless, they are not connected to any particular moment, so there’s no way to tell if a month should last 28 or 31 days and so on. So we need to make peace with that approach.

Do you like Twitter? And what about the way it displays time? There is a Twitter plugin for Moment.js that allows you to do the very same thing. Methods twitter() (or twitterShort()) and twitterLong() will present the following outputs:

> moment().twitter()
"1s"
> moment().twitterLong()
"1 sec"
> moment(0).twitterLong()
"Jan 1"
> moment(0).twitter()
"1/1/70"

As you can see, now is already a second ago. Time flies.

But that’s not all what the Moment.js plugins have to offer. Another one is Recur. It allows you to define time- or calendar-based intervals, check dates for matching this interval or generate sequences.

> var every2days = moment().recur().every(2).days()
> every2days.matches("2015-10-16")
true
> var monthsOf2015 = moment().recur("2015-01-01", "2016-01-01").every(1).months().all();
> for (var i in monthsOf2015) { console.log(monthsOf2015[i].format()); }
2015-01-01T00:00:00+00:00
2015-02-01T00:00:00+00:00
(... you have an idea already)
2016-01-01T00:00:00+00:00

This was only a short excerpt out of Moment’s possibilities. The documentation on the project homepage is excessive and comprehensive. If it got your attention, you should go there for more.

I have one more business remark: remember not to extend support of your application further than to year 275760, since that is a max year that moment().year() will accept.

Moment is not really a new thing. It has been used for 4 years now. It is currently in 2.10.6 version. GitHub shows quite regular development effort. For me it means we are dealing with mature, reliable product and that’s always good to hear when you’re looking for some code library.

It is also well tested. But don’t take my word for it, run your own tests on momentjs.com/tests. They take around half a minute and you can see a list of all test cases.

So next time you will be up to struggle with a time in your Javascript, you can go for Moment.js. Would you like to try?

Tags:

1 comment

Comments are closed.