Dates and Times in JavaScript


by Vincent Wilson

Ask any programmer how to handle dates properly in JavaScript (or most other languages) and you are likely to elicit a: groan, sigh, or hazy look as the developer remembers some traumatizing past experience where they were bitten by a Date bug.

One of our most recent projects involved building a React Native app and GraphQL server that frequently transported date information. A large amount of data in the app was sent contextually based on a time of day, and many of the mutations in our GraphQL server needed to take Dates as inputs.

There were two primary classes of Date information that were being transported.

Specific Dates.

Dates where the specific time was important. For these dates, it was important that the client reported nearly the exact time the event occurred. Example: Timestamps, or logic to the effect of “this email should be sent out at midnight.” This is the most common class of date you will see in traditional apps.

Contextual Dates.

Dates where the user’s time of day is important. These are dates where the logic on the server was something to the effect of “if the user asks between the hours of 8AM and 10AM, send this response.” We had a lot of these dates in our app.

While building the app, we opted to use a scalar to represent the date we were transporting.


import { Kind } from 'graphql/language';

const schema = `

# string that can be parsed using new Date(), always UTC

scalar DateTime

`;

const resolvers = {

DateTime: {

__parseValue(value) {

return new Date(value); // value from the client

},

__serialize(value) {

const date = new Date(value);

return date.toJSON(); // value sent to the client

},

__parseLiteral(ast) {

if (ast.kind === Kind.INT) {

return (parseInt(ast.value, 10)); // ast value is always in string format

}

return null;

},

},

};

// Elsewhere in the app we import these to create an executable Schema using Apollo’s graphQLTools

export {

schema,

resolvers,

};

This scalar worked really well for us! We could use the DateTime type in inputs, parse dates received on the app using new Date(dateFromApollo), and time of day based logic was working fine!

Or was it?

Soon after we deployed to our staging server, we realized that something was amiss with our offset based logic. The server consistently returned results that would have made perfect sense . . . if we were testing the app in Europe! (GMT-0/UTC, see where this is going?)

That brings us to landmine #1

Our team forgot an important aspect of sending and parsing dates in Javascript.

new Date(input string) always returns a date in the system timezone.

Our servers are hosted on a Galaxy-managed EC2 instance, which has it’s timezone set to UTC (a VERY sane default). We had a lot of code that looked like:


var currentHour = dateFromClient.getHours();

isMorning = currentHour > 0 && currentHour < 10;

This code worked perfectly for us in our respective time zones. Nobody on our team noticed any bugs, even the devs working remotely from the West coast because the timezone of the server always matched the timezone of the client. When we were developing locally, we forgot the very important fact that, for the most part, the timezone of our client and the timezone of our server wouldn’t match in production.

The issue occurred in our staging environment, however, because the dates we were sending over were translated into UTC. We weren’t checking if 8:00 EST was 8:00 EST, we were checking if 13:00 UTC (8:00 EST) was 8:00 UTC.

We came up with a simple fix; we decided to add a new type to the server to handle the second class of dates we talked about above called ZonedDateTime. It looked like this.


import { Kind } from 'graphql/language';

import moment from 'moment';

const schema = `

...

# string that can be parsed using new Date() or moment.parseZone(), includes timezone info.

scalar ZonedDateTime

`;

const resolvers = {

...

ZonedDateTime: {

__parseValue(value) {

return moment.parseZone(value); // value from the client

},

__serialize(value) {

const date = moment(value);

return date.toJSON(); // value sent to the client

},

__parseLiteral(ast) {

if (ast.kind === Kind.INT) {

return (parseInt(ast.value, 10)); // ast value is always in string format

}

return null;

},

},

};

export {

schema,

resolvers,

};

We opted to use Moment (one of the most well written JS libraries, period) rather than writing a parseZone function for the native JavaScript date type because most of our code was already relying on Moment.

We committed this code, pushed it, and crossed our fingers.

That’s when we discovered landmine #2

Date.prototype.toJSON() returns an ISO8601 string with a timezone of UTC.

This was a simple oversight on our part. To send dates via JSON, our client was calling someDate.toJSON(). When our new scalar resolver ran moment.parseZone, it got back a UTC object every time. The fix for this bug was pretty simple; we modified our client to call moment(someDate).format() which returns an ISO8601 string with Time Zone data attached, perfectly parseable by moment.parseZone (pay attention to the notes in the Moment docs).

Dates can be frustrating in JavaScript. To minimize confusion and potential landmines, ensure that you:

  • Pay extra attention to any Date related logic if it depends on specific times of day .
  • Understand that JavaScript parses dates in your local timezone.
  • If your app does a lot of Date handling, do yourself a favor and use moment.

Share Share on Twitter Share on Facebook Share on LinkedIn

How Can We Help?

Reaching out doesn’t mean you’re ready to start a project, but we’d love to learn more about the challenge you’re facing, answer any questions, and see if we might be a good fit for working together.

Contact Us