ISO8601 for negative years in Ruby and Javascript

Today, when working on the product we are making at Hstry, I was integrating a timeline about the history of medicine during the Roman times. This part of history takes place before Christ so I was dealing with negative times (years AD or BC). I came across a difference between the way the ISO8601 standard is implemented in Ruby and Javascript.

Both Ruby and Javascript understand ISO8601, right?

Yes, but not in the same way. My case is as follows. On the server-side, a Ruby DateTime is serialized as a string with the date in ISO8601 format.

# Gives "2013-11-01T17:00:00+00:00"
DateTime::parse("5th of November 2013 at 5pm").iso8601

This string is transported across the wire and is parsed client-side into a Javascript Date object.

// Gives a Date {Fri Nov 01 2013 18:00:00 GMT+0100 (CET)} object
new Date("2013-11-01T17:00:00+00:00")

So this works fine. What if we try to do it with a date from the Roman time?

# Gives "-0166-11-01T17:00:00+00:00"
DateTime::parse("5th of November 167 BC at 5pm").iso8601

Cool! So Ruby correctly serialized the negative year. Now we parse this date on the client.

// Gives a Date {Invalid Date} object
new Date("-0166-11-01T17:00:00+00:00")

So our date is not correctly parsed. It turns out that for Javascript to understand an ISO8601 with a negative year, you need to add two leading zeros before the year. This is documented in the ECMAScript specification.

// Gives a Date {Sat Nov 01 -0166 18:00:00 GMT+0100 (LMT)} object
new Date("-000166-11-01T17:00:00+00:00")

The specification further says

To represent years before 0 or after 9999, ISO 8601 permits the expansion of the year representation, but only by prior agreement between the sender and the receiver.

In this case, I made the sender agree with the receiver by adding the leading zeros on the server. And it works now!

# Use ISO8601 expanded notation for negative years, i.e. use 2 leading zeros
# '-0167-01-01T00:01:06.000Z' becomes '-000167-01-01T00:01:06.000Z'
def serialize_date(date)
  if date.year < 0
    a = date.iso8601.split('-')
    a[1] = "00#{a[1]}"
    a.join('-')
  else
    date.iso8601
  end
end
Write us your thoughts about this post. Be kind & Play nice.