9

I observed some strange Date behaviour in Chrome (Version 74.0.3729.131 (Official Build) (64-bit)). Following javascript was executed in the Chrome Dev Console:

new Date('1894-01-01T00:00:00+01:00')
// result: Mon Jan 01 1894 00:00:00 GMT+0100 (Central European Standard Time)

new Date('1893-01-01T00:00:00+01:00')
// result: Sat Dec 31 1892 23:53:28 GMT+0053 (Central European Standard Time)

I have already read about non standard date parsing via the Date ctor in different browsers, although providing valid ISO8601 values. But this is more than strange o_o

In Firefox (Quantum 66.0.3 (64-Bit)) the same calls result in expected Date objects:

new Date('1894-01-01T00:00:00+01:00')
// result: > Date 1892-12-31T23:00:00.000Z

new Date('1893-01-01T00:00:00+01:00')
// result: > Date 1893-12-31T23:00:00.000Z
  • Is this a bug in Chrome?
  • My input is valid ISO8601 i guess?
  • The most important question is, how do I fix this? (hopefully without parsing the input string myself)
cmxl
  • 663
  • 12
  • 24
  • Maybe there were some events in 1893 related to time? (Requesting historians ITT) – Limbo May 09 '19 at 11:38
  • 2
    I have `Sun Jan 01 1893 01:02:04 GMT+0202 (Eastern European Standard Time)` and `Mon Jan 01 1894 01:02:04 GMT+0202 (Eastern European Standard Time)`. Looks like until 1925 the GMT was shifted or something (because I'm getting clear GMT offset only since 1925) – Limbo May 09 '19 at 11:46
  • 2
    Well, FYI, at my PC the breaking date is `1924-05-01`. I suppose, Chrome somehow uses historical information about time (in 1924 there was first time-signal from Greenwich Observatory, so, probably, this somehow affected the time worldwide). The idea is mad, I know :D – Limbo May 09 '19 at 11:57
  • 2
    Interesting: Chrome added some changes to Date parsing in version 67, due to some ecma script spec updates regarding timezonoffsets: https://chromium-review.googlesource.com/c/v8/v8/+/572148 – cmxl May 09 '19 at 12:32
  • 1
    Possibly relevant https://stackoverflow.com/a/56121124/441757 and https://stackoverflow.com/a/14282490/441757 – sideshowbarker May 14 '19 at 02:23

2 Answers2

3

Okay, seems like this behaviour cannot be avoided, so you should parse dates manually. But the way to parse it is pretty simple.

If we are parsing date in ISO 8601 format, the mask of date string looks like this:

<yyyy>-<mm>-<dd>T<hh>:<mm>:<ss>(.<ms>)?(Z|(+|-)<hh>:<mm>)?

1. Getting date and time separately

The T in string separates date from time. So, we can just split ISO string by T

var isoString = `2019-05-09T13:26:10.979Z`
var [dateString, timeString] = isoString.split("T")

2. Extracting date parameters from date string

So, we have dateString == "2019-05-09". This is pretty simple now to get this parameters separately

var [year, month, date] = dateString.split("-").map(Number)

3. Handling time string

With time string we should make more complex actions due to its variability.
We have timeString == "13:26:10Z" Also it's possible timeString == "13:26:10" and timeString == "13:26:10+01:00

var clearTimeString = timeString.split(/[Z+-]/)[0]
var [hours, minutes, seconds] = clearTimeString.split(":").map(Number)

var offset = 0 // we will store offset in minutes, but in negation of native JS Date getTimezoneOffset
if (timeString.includes("Z")) {
    // then clearTimeString references the UTC time
    offset = new Date().getTimezoneOffset() * -1
} else {
    var clearOffset = timeString.split(/[+-]/)[1]
    if (clearOffset) {
        // then we have offset tail
        var negation = timeString.includes("+") ? 1 : -1 // detecting is offset positive or negative
        var [offsetHours, offsetMinutes] = clearOffset.split(":").map(Number)
        offset = (offsetMinutes + offsetHours * 60) * negation
    } // otherwise we do nothing because there is no offset marker
}

At this point we have our data representation in numeric format:
year, month, date, hours, minutes, seconds and offset in minutes.

4. Using ...native JS Date constructor

Yes, we cannot avoid it, because it is too cool. JS Date automatically match date for all negative and too big values. So we can just pass all parameters in raw format, and the JS Date constructor will create the right date for us automatically!

new Date(year, month - 1, date, hours, minutes + offset, seconds)

Voila! Here is fully working example.

function convertHistoricalDate(isoString) {
  var [dateString, timeString] = isoString.split("T")
  var [year, month, date] = dateString.split("-").map(Number)
  
  var clearTimeString = timeString.split(/[Z+-]/)[0]
  var [hours, minutes, seconds] = clearTimeString.split(":").map(Number)
  
  var offset = 0 // we will store offset in minutes, but in negation of native JS Date getTimezoneOffset
  if (timeString.includes("Z")) {
    // then clearTimeString references the UTC time
    offset = new Date().getTimezoneOffset() * -1
  } else {
    var clearOffset = timeString.split(/[+-]/)[1]
    if (clearOffset) {
      // then we have offset tail
      var negation = timeString.includes("+") ? 1 : -1 // detecting is offset positive or negative
      var [offsetHours, offsetMinutes] =   clearOffset.split(":").map(Number)
      offset = (offsetMinutes + offsetHours * 60) * negation
    } // otherwise we do nothing because there is no offset marker
  }

  return new Date(year, month - 1, date, hours, minutes + offset, seconds)
}

var testDate1 = convertHistoricalDate("1894-01-01T00:00:00+01:00")
var testDate2 = convertHistoricalDate("1893-01-01T00:00:00+01:00")
var testDate3 = convertHistoricalDate("1894-01-01T00:00:00-01:00")
var testDate4 = convertHistoricalDate("1893-01-01T00:00:00-01:00")

console.log(testDate1.toLocaleDateString(), testDate1.toLocaleTimeString())
console.log(testDate2.toLocaleDateString(), testDate2.toLocaleTimeString())
console.log(testDate3.toLocaleDateString(), testDate3.toLocaleTimeString())
console.log(testDate4.toLocaleDateString(), testDate4.toLocaleTimeString())

Note

In this case we are getting Date instance with all its own values (like .getHours()) being normalized, including timezone offset. The testDate1.toISOString will still return weird result. But if you are working with this date, it will probably 100% fit your needings.

Hope that helped :)

Limbo
  • 2,123
  • 21
  • 41
1

This might be the case when all browsers follow their own standards for encoding date formats (but I am not sure on this part). Anyways a simple fix for this is to apply the toISOString method.

const today = new Date();
console.log(today.toISOString());
Danyal Imran
  • 2,515
  • 13
  • 21
  • ok so for displaying purpose this will do. But I need to check if calculations will still work correctly with the Chrome format. – cmxl May 09 '19 at 10:48
  • 1
    Hopefully the calculations will work fine since every JavaScript Engine converts dates into integers and then operates on them, leading to a consistent experience. @cmxl – Danyal Imran May 09 '19 at 11:32