Search code examples
swiftstringdatenulldateformatter

DateFormatter from String bug for January 1, 1895


I am finding the weirdest bug in DateFormatter's .date(from: String) function in that it doesn't seem to like 1895-01-01 specifically. Dates before and after work but for that particular day the date is nil.

I can take the 1894-12-31 and add a day to show that the underlying NSDate is fine -- just seems to be the DateFormatter conversion.

Here is the playground code to demonstrate.

import Foundation

let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"

let fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd"
let date1 = fmt.date(from: "1894-12-31")
let date2 = fmt.date(from: "1895-01-01")
let date3 = fmt.date(from: "1895-01-02")
let calcDate2 = date1?.addingTimeInterval(86400)

print("Date 1: \(date1)")
print("Date 2: \(date2)")
print("Date 3: \(date3)")
print("\nCalculated Date 2: \(calcDate2)")

Output is:

Date 1: Optional(1894-12-31 05:17:32 +0000)
Date 2: nil
Date 3: Optional(1895-01-02 05:00:00 +0000)

Calculated Date 2: Optional(1895-01-01 05:17:32 +0000)

I also found a prior issue issue about weird behaviour with time being added around 1895 so perhaps this is somehow related?

Is this a bug? Any idea how I can get around this when JSON decoding since I can't know what dates my data will contain? So far I'm just using it as a string in my code but that isn't going to work for long.


Solution

  • Ok, the answer is that because the use of railroads required better time keeping a number of states and provinces adopted timezones in 1895 as suggested by Fleming several years earlier.

    This established Pacific, Mountain, Central, and Eastern (where I am) timezones. As a result there was an adjust to Eastern of 00:17:32 required. So the clock went from 23:59:59 to 00:17:32 at New Years.

    This can be shown with the following code:

    import Foundation
    
    let fmt = DateFormatter()
    fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
    
    let date1 = fmt.date(from: "1894-12-31 23:59:59")
    let date2 = fmt.date(from: "1895-01-01 00:00:00")
    let date3 = fmt.date(from: "1895-01-01 00:17:31")
    let date4 = fmt.date(from: "1895-01-01 00:17:32")
    
    print("Date 1 (1 sec to midnight): \(date1)")
    print("Date 2 (midnight): \(date2)"). // invalid
    print("Date 3 (17:31 past midnight): \(date3)"). // invalid
    print("Date 4 (17:32 past midnight: \(date4)")
    
    let calc1 = date1?.addingTimeInterval(1)
    print("Date 1 + 1 second (midnight): \(calc1)")
    

    Output is (for EST):

    Date 1 (1 sec to midnight): Optional(1895-01-01 05:17:31 +0000)
    Date 2 (midnight): nil
    Date 3 (17:31 past midnight): nil
    Date 4 (17:32 past midnight: Optional(1895-01-01 05:17:32 +0000)
    Date 1 + 1 second (midnight): Optional(1895-01-01 05:17:32 +0000)
    

    Thanks to all that helped.

    The solution for actual usage is to add the "Z" time zone to the end of the string format and likewise append the "Z" to the data from the API I'm calling. That forces it all to GMT which doesn't suffer from the problem.

    Thanks all for the assistance