Timezone Shenanigans in Swift
Tags: swift, beginners, ios
Published: 2022-08-23 10:11:00

## And how they can drive you crazy

There comes a time when it is inevitable to work with `Calendar`. Sometimes it is easy, but be aware, it can be very tricky 😅

So let's have some fun with it 👍

Assume you have to compare two different `Date` values.
```swift
let date1 = Date(timeIntervalSince1970: 0) // 1970-01-01 00:00:00 
let date2 = Date(timeIntervalSince1970: 60 * 60 * 24) // 1970-01-02 00:00:00 

func compareDates(date1: Date, date2: Date){
    switch date1 {
    case date2:
        print("date1 and date2 represent the same point in time")
    case ...date2:
        print("date1 is earlier in time than date2")
    case date2...:
        print("date1 is later in time than date2")
    default:
        return
    }
}
compareDates(date1: date1, date2: date2)
```

As we would assume, we get as result `"date1 is earlier in time than date2"`. Nice! 😃

So what happens, if we use the same day but different `TimeZone` values and want to compare the start of the day? (Only you can answer why you would need that, but just assume you do 😅)

Let's just say, we want to know if Berlin or New York City celebrated New Year's Day in 1971 first.

```swift
...
func startOfDayIn(date: Date, timeZone: TimeZone) -> Date {
    var calendar = Calendar.current
    calendar.timeZone = timeZone
    return calendar.startOfDay(for: date)
}

let date = Date(timeIntervalSince1970: 60 * 60 * 24 * 365) // 1971-01-01 00:00:00
let timeZone1 = TimeZone(secondsFromGMT: 60 * 60 * 1)! // Berlin
let start1 = startOfDayIn(date: date, timeZone: timeZone1)
let timeZone2 = TimeZone(secondsFromGMT: 60 * 60 * -8)! // New York City
let start2 = startOfDayIn(date: date, timeZone: timeZone2)

compareDates(date1: start1, date2: start2)
```

Well that was easy 😁 We get as a result..

Wait, what?? `"date1 is later in time than date2"` 😳🤯😱 How is that even possible? The day in Berlin starts earlier than the day in New York, it should have been `"date1 is earlier in time than date2"` !!!

So let's investigate this. 🧐

First, let's print a few startTimes to compare them. Since we used January 1st, we can be sure that it's not a problem because the date is before year 1970.

```swift
(-12...12).reversed().forEach { deviation in
    let timeZone = TimeZone(secondsFromGMT: 60 * 60 * deviation)!
    let start = startOfDayIn(date: date, timeZone: timeZone)
    
    print(start, "| UTC\(deviation >= 0 ? "+" : "")\(deviation)")
}
```
This gives us the following list:

| Date | Timezone | |
|---|---|---|
| 1970-12-31 12:00:00 +0000 | UTC+12 |
| 1970-12-31 13:00:00 +0000 | UTC+11 |
| 1970-12-31 14:00:00 +0000 | UTC+10 |
| 1970-12-31 15:00:00 +0000 | UTC+9 |
| 1970-12-31 16:00:00 +0000 | UTC+8 |
| 1970-12-31 17:00:00 +0000 | UTC+7 |
| 1970-12-31 18:00:00 +0000 | UTC+6 |
| 1970-12-31 19:00:00 +0000 | UTC+5 |
| 1970-12-31 20:00:00 +0000 | UTC+4 |
| 1970-12-31 21:00:00 +0000 | UTC+3 |
| 1970-12-31 22:00:00 +0000 | UTC+2 |
| 1970-12-31 23:00:00 +0000 | UTC+1 |
| 1971-01-01 00:00:00 +0000 | UTC+0 |
| 1970-12-31 01:00:00 +0000 | UTC-1 | Why are we back in 1970 again?? |
| 1970-12-31 02:00:00 +0000 | UTC-2 |
| 1970-12-31 03:00:00 +0000 | UTC-3 |
| 1970-12-31 04:00:00 +0000 | UTC-4 |
| 1970-12-31 05:00:00 +0000 | UTC-5 |
| 1970-12-31 06:00:00 +0000 | UTC-6 |
| 1970-12-31 07:00:00 +0000 | UTC-7 |
| 1970-12-31 08:00:00 +0000 | UTC-8 |
| 1970-12-31 09:00:00 +0000 | UTC-9 |
| 1970-12-31 10:00:00 +0000 | UTC-10 |
| 1970-12-31 11:00:00 +0000 | UTC-11 |
| 1970-12-31 12:00:00 +0000 | UTC-12 |

Ok, the first lines until `UTC+0` look as expected. But why is the rest wrong?

The solution is rather simple. There were too many (or rather too few) `TimeZone` conversions!

When we used the timezone for New York `UTC-8`, we used `"1971-01-01 00:00:00"` as date value. But this is in `UTC+0`! The date in `UTC-8` is actually `"1970-12-31 18:00:00 -0800"`. When we call `startOfDay`, we get `"1970-12-31 00:00:00 -0800"` which is in `UTC+0` `"1970-12-31 08:00:00 +0000"`. 
This means, the result is actually correct, but our function is not 🙈

So let's fix it 💪

```swift
func adjustedStartOfDayIn(date: Date, timeZone: TimeZone) -> Date {
    var calendar = Calendar.current
    calendar.timeZone = timeZone
    let correctDay = date.addingTimeInterval(TimeInterval(-calendar.timeZone.secondsFromGMT()))
    return calendar.startOfDay(for: correctDay)
}

let correctStart1 = adjustedStartOfDayIn(date: date, timeZone: timeZone1)
let correctStart2 = adjustedStartOfDayIn(date: date, timeZone: timeZone2)

compareDates(date1: correctStart1, date2: correctStart1)
```

Now we get `"date1 is earlier in time than date2"` as result, just as expected. 😁
The key for the solution here is that we didn't use `date` directly but shifted it by the offset of the specific timezone and UTC, so we get the `startOfDay` in the timezone we want to use.

Conclusion: Don't mess with timezones!

You can find the complete code [here](https://gist.github.com/KatharinaGopp/7dac4c660860b9b4130631c032bb8268/b69386341ed3a33f4fd3bfeaa9e500b56a681f7d#file-timezone-shenanigans-swift).