Javascript Date parsing returning strange results in Chrome

前端 未结 2 941
慢半拍i
慢半拍i 2021-02-08 17:37

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:

         


        
2条回答
  •  闹比i
    闹比i (楼主)
    2021-02-08 17:42

    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:

    --
    T::(.)?(Z|(+|-):)?

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

提交回复
热议问题