Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript Date parsing returning strange results in Chrome

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)
like image 642
cmxl Avatar asked May 09 '19 10:05

cmxl


People also ask

What can I use instead of date parse?

You can use the DATE function instead of the DATEPARSE function if the data you're working with doesn't have the DATEPARSE function or if the field you're trying to convert has a numeric data type. Step 1: The DATE function converts a date type from an integer, string, or date expression.

What is date parse in Javascript?

parse() The Date. parse() method parses a string representation of a date, and returns the number of milliseconds since January 1, 1970, 00:00:00 UTC or NaN if the string is unrecognized or, in some cases, contains illegal date values (e.g. 2015-02-31). Only the ISO 8601 format ( YYYY-MM-DDTHH:mm:ss.


Video Answer


2 Answers

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 :)

like image 154
Limbo Avatar answered Oct 19 '22 21:10

Limbo


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());
like image 43
Danyal Imran Avatar answered Oct 19 '22 20:10

Danyal Imran