Tips and tricks for date handling with moment.js

Handling dates when dealing with timezones is a difficult task.
Fortunately for us, there are many libraries like moment.js helping us to handle common issues like timezones, daylight saving time, manipulation and date formatting.

I recently encountered several issues in displaying dates in the correct format and timezone whilst working on a mobile application project.

An external server which sends us local date time as a string and timezone like this:

{
  localDateTime: 'YYYY-MM-DD HH:mm', // Not in UTC
  timezone: 'Indian/Reunion', // Or 'Europe/Paris''
}

I had to display and manipulate these dates. But before that, I needed to parse and store them. To be more specific with the about the use case is drawn above a diagram of the date flow in my application.

use_case

In this article, I will show you how to deal with common difficulties in date handling with one of the most standard libraries: moment.js.

What is moment ?

Moment is a very comprehensive and popular library for handling dates. It can be thought of as the standard in javascript. It provides functions to parse, manipulate and display dates and times. Moment-timezone.js adds functions to handle dates and times for different countries.

On the other hand, one should be aware that moment dates are mutable. I will deal with this issue in the manipulation part of this article.

I will focus on explaining the flow of date handling in a full JS application. I will also shed light on some tricky points of moment.

Time zone

So, what is a timezone?

Time zone is a region of the globe that observes a uniform standard time which are offset from Coordinated Universal (UTC). A time zone sets the time according to the distance from the prime meridian. Some time zones change their offset during the year. This is called daylight saving time (DST).

In the map above, you can see the time in every part on the world corresponding to the timezone without DST.

time_zone_map
Note: There is no time difference between UTC and Greenwich Mean Time (GMT). But UTC is a standard time whereas GMT is a timezone.

How to store a date

Parse the date string

When you receive a date string, you should first parse it to get the correct datetime before storing it in your database.

According to the format

Moment provides a function moment(date: string, format: string) to convert a string into a moment object according to the format.

const moment = require('moment')

const dateToStore = '2018-01-27 10:30'
const momentDate = moment(dateToStore,'YYYY-MM-DD HH:mm')
// momentObject(2018-01-27T10:30:00.000)

According to the timezone

We created a moment object which corresponds to the string. But let’s see the result in our example with a server A in Reunion (UTC+04:00) and a server B in France (UTC+01:00). If A sends a string 2018-01-27 10:30 to B, A wants to share 2018-01-27 6:30+00:00, but B will understand 2018-01-27 9:30+00:00.

Timezone

By default, moment parses the date with the machine local time (either the user or the server). You can get the local utcOffset in minutes with moment.utcOffset(). So if you don’t specify the timezone, the date will not be correctly parsed. You had to specify it using moment-timezone instead of moment.

 const moment = require('moment')
 const momentTz = require('moment-timezone')

 const dateToStore = '2018-01-27 10:30'
 moment().utcOffset(); // 60 minutes
 const timeZone = 'Indian/Reunion' // 'UTC+04:00'

 const momentDate = moment(dateToStore,'YYYY-MM-DD HH:mm')
 //momentObject(2018-01-27T10:30:00+01:00)
 
 const momentDateTz = momentTz.tz(dateToStore,'YYYY-MM-DD HH:mm',timeZone)
 //momentObject(2018-01-27T10:30:00+04:00)

Better cases

In this case, the external API sends the date without any timezone and not in UTC. The best practice is to send the date in UTC or at least with the offset from UTC so that the receiver does not have to handle timezone. In these better cases, you can parse the date string using moment.utc() or moment.parseZone() or either moment(date,'YYYY-MM-DD HH:mm:ssZZ').

const moment = require('moment')

const receveidDateInUTC= '2018-01-27 6:30:00+00:00'
const receveidDateWithTimeZone = '2018-01-27 10:30:00+04:00'

moment(receveidDateInUTC,'YYYY-MM-DD HH:mm:ssZZ')
// momentObject(2018-01-27T7:30:00+01:00)

moment.utc(receveidDateInUTC,'YYYY-MM-DD HH:mm:ssZZ')
// momentObject(2018-01-27T6:30:00+00:00)

moment(receveidDateWithTimeZone,'YYYY-MM-DD HH:mm:ssZZ')
// momentObject(2018-01-27T7:30:00+01:00)

moment.parseZone(receveidDateWithTimeZone,'YYYY-MM-DD HH:mm:ssZZ')
// momentObject(2018-01-27T10:30:00+04:00)

As you can see, there are a number of ways to parse the same date.
That is why it is a best practice to store and send your date in UTC.

Use Coordinated Universal Time (UTC)

To make the data portable I would recommend storing the datetime in UTC. It is the international time standard that expresses dates without offsets and does not adjust for daylight savings. To do this, I use the moment.utc() function which converts a moment object in UTC.

const momentTz = require('moment-timezone')

const dateToStore = '2018-01-27 10:30'
const timeZone = 'Indian/Reunion' // 'UTC+04:00'

const momentDateTz = momentTz.tz(dateToStore,'YYYY-MM-DD HH:mm',timeZone)
// momentObject(2018-01-27T10:30:00+04:00)

const momentDateTzUTC = momentTz.tz(dateToStore,'YYYY-MM-DD HH:mm',timeZone).utc()
// momentObject(2018-01-27T06:30:00+00:00)

Finally you can store the date as a TIMESTAMP_WITH_TIMEZONE attribute in your database.

Display a date

Set the local time

I created a service in my application which provides a function to format dates. We received the date from our back-end in UTC. But depending on where the person is, the locals are not the same. The locals contain informations about linguistic, cultural, and technological conventions and standards. That is why the first thing to do is to set the local time. I got the local time with the library react-native-device-info and then I set the local time with moment.locale().

const moment = require('moment')
const DeviceInfo = require('react-native-device-info');

const date = '2018-01-27T10:30:00+00:00'
moment.locale() // 'fr'
moment(date).format('DD MMMM') // 27 Janvier

const local = DeviceInfo.getDeviceLocale() // 'en'
moment.locale(local)

moment(date).format('DD MMMM') // 27 January

Format the date

Finally, I create one function for each format I had.

const plainTextDateTime = dateTime => {
return moment(dateTime).format('DD MMMM HH:mm a') // 27 January 10:30 am
}
const getDate = dateTime => {
return moment(dateTime).format('DD MMMM') // 27 January
}
const getTime = dateTime => {
return moment(dateTime).format('HH:mm a') // 10:30 am
}

Date manipulation

Moment provides a core of manipulating functions as comparison, addition and subtraction functions. It is really easy to use. The main issue is that moment objects are mutable.

For example, if you want to know if a date is in less than one hour, you have two options :

//option 1
const myDate = moment('2018-01-27 10:30:00+00:00','YYYY-MM-DD HH:mm:ssZZ');
if (myDate.isBefore(moment().add(1,'hour'))) {
// myDate is less than one hour
}

//option 2
const now = moment();
const myDate = moment('2018-01-27 6:30:00+00:00','YYYY-MM-DD HH:mm:ssZZ');
if (myDate.subtract(1,'hour').isBefore(now)) {
// myDate is less than one hour
}
console.log(myDate.format())
// myDate has been muted => 2018-01-27T05:30:00+00:00

With the first option, you add one hour to moment() which is not a problem because you don’t keep this value in memory. But with the second option, the value of myDate has changed which is not what you expected. You need to clone the value of myDate before manipulating this value.

//option 2
const myDate = moment('2018-01-27 6:30:00+00:00','YYYY-MM-DD HH:mm:ssZZ');
if (myDate.clone().subtract(1,'hour').isBefore(moment())) {
// myDate is less than one hour
}
console.log(myDate.format())
// myDate has NOT been muted => 2018-01-27T06:30:00+00:00

Conclusion

To conclude, a date has four states for the same data:

  • The received state as a string: parse the date in order to have the correct moment object
  • The storage state: store your data in UTC or at least with the time zone (TIMESTAMP WITH TIME ZONE in postgreSql)
  • The manipulation state: manipulate your dates with date object. If you are using moment, clone the object before manipulating.
  • The displaying state: set the local time and create a service to handle the different representation in your application.

You liked this article? You'd probably be a good match for our ever-growing tech team at Theodo.

Join Us

  • Gonzalo Oviedo Lambert

    Thank you ver much. Very useful article. I was very owerwhelming with all this stuff of timezone and you make myself clear. Not at all but it is a great advance.

  • guillaume

    Thank you so much for your article ! very helpful to handle mutable side effect…

  • Quoc Vu

    moment.locale() only sets the locale, not the timezone. You are still displaying dates in UTC. The users rather see dates in their timezones.