Tips, Thoughts, and Gotchas Working with JavaScript Dates

Post Status: Draft/Under Development

This post is being written in conjunction with a piece of work dealing with JavaScript dates.

Draft last updated: 21 February 2018


Things to remember about JavaScript Dates:

  • JSON.stringify() in JavaScript will serialize a date to UTC.
  • Month numbers start at 0 (zero) in JavaScript, not 1 (one).
    • So, January is represented by 0, through to December being represented by 11.
    • You need to remember this when using the Date.getMonth() method;
    • And when creating a new Date() object with a constructor overload – e.g. var today = new Date(2018, 1, 21) to create a date representing 21 February 2018.
  • A JavaScript Date object is internally represented as the number of milliseconds since 1970-01-01 00:00:00 UTC;
  • But the object itself uses the local timezone of the system running it – e.g. In Melbourne, Australia it returns a date/time of UTC+1000 during Australian Easter Standard Time and UTC+1000 during Australian Easter Daylight Time (during daylight savings).
  • If you try to manually calculate the current UTC time and set a Date object to that date/time, then the value of date and time is the same as UTC, however, the Date object still returns the local system timezone.
  • The JavaScript Date.getTimezoneOffset() is non-intuitive and returns the opposite of the UTC timezone offset sign.
    • For example: If the local timezone is UTC+1100 then getTimezoneOffset() returns “-660” (that minus 660 minutes).
    • Why?
      • The definition of the method is: “The getTimezoneOffset() method returns the time zone difference, in minutes, from UTC to current locale (host system settings).”
      • In other words: it subtracts the local timezone offset (including the offset sign) from UTC.
        • Example: UTC+1100 timezone = UTC – (660 minutes) = 0 – 660 = -660 (negative 600).
        • Example: UTC-500 timezone = UTC – (-300 minutes) = 0 – (-300) = 300 (positive 300).
        • If you’re wondering how 0 – (-300) makes a positive, that’s fundamental maths: subtracting a negative value from another value adds those 2 values together.
        • The zero in the examples is the timezone offset of UTC.

With that in mind, consider this scenario:

  • We have a system that stores date/times in the database as UTC.
  • We have a web front-end that works with JavaScript date.
  • We want to pass a date from the front-end to a server-side API that records the date in the database.

I’ve seen the following:

  • A JavaScript Date variable representing the current local date and time is created:
    • var localNow = new Date();
  • We want to JavaScript Date variable representing a ‘StartDate’, and we want to assign the current UTC date/time to it:
    • var startDate = new GetUTCDate(localNow);
    • See below for the GetUTCDate() function.
  • We need to serialize startDate to JSON so send it over the wire to the API.

The problems with this are:

  • ‘startDate’ may have a value that represents the UTC equivalent of ‘localNow’ but the actual ‘startDate’ JavaScript object still retains the local timezone offset.
  • The JSON.stringify() method used to serialize a JavaScript object to JSON converts Date values to their UTC equivalent time (remember the internal representation of a Date object is an offset representing a UTC time);
    • So what ends up happening is the ‘startDate’ value represented in the JSON is actually the UTC time plus or minus the local timezone offset.
    • For example:
      • If the current local time is 21 February 2018 at 10:00am, then ‘the startDate’ JavaScript object has the value: “2018-02-21 10:00:00.00000 UTC+1100”
      • But the string/value for returned by JSON.stringify(startDate) is: “2018-02-20 23:00:00.00000 UTC” (11 hours earlier).

 

The following is written in a TypeScript:

// Takes a Date set to the local date/time, and return a Date representing the equivalent UTC date/time.
public static GetUTCDate(localDate: Date): Date {
  //UTC+1100 returns -660 (negative). Remove offset from local time to reach UTC. 
  //However, [local - (-offset)] = [local + offset], therefore equation is [local + (-offset)] which = [local - offset]
  //UTC-1100 returns 660 (positive). Add offset to local time to reach UTC.

  // NOTE: This still produces a Date object with local timezone offset
  let offsetMinutes = Math.abs(localDate.getTimezoneOffset());
  if (localDate.getTimezoneOffset() < 0) {
    //Ex: GMT+1100
    return new Date(localDate.getTime() - (offsetMinutes * 60000));
  }
  else 
  {
    //Ex: GMT-1100
    return new Date(localDate.getTime() + (offsetMinutes * 60000));
  }
}

Running Example

If you’re using Internet Explorer or there’s just a black window below, here’s the link to the code (and I suggest using a “modern” browser – Chrome and Edge work): https://stackblitz.com/edit/js-date-gotchas.

 

 

 

Other things to consider:

  • If you want to store just a date (no time component) in a database, you cannot perform any locale conversions if it is stored as UTC.
    • Why? Let’s assume you want to convert the UTC date to the date in Timezone UTC+1100 AEDT (Melbourne, Australia). There are actually 2 dates this can convert to. To understand, look at it from the point of view of the local time we want to convert to:
      • In Melbourne, for any local time from midnight to 10:59:59am the equivalent UTC time is the day before (e.g. if the local date is 21 February, then UTC date is 20 February);
      • However, for the remainder of the day – 11:00am to 23:59:50pm – the UTC date is the same as the local date (i.e. both dates are 21 February).
  • Think about how you want to handle dates that are timezone neutral. That is a date that remains the same regardless of where in the world you are. For example, a date of birth usually does not care about where in the world the person is born, nor the timezone.