Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
August 23, 2022 10:11 am GMT

Timezone Shenanigans in Swift

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.

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.

...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:00let timeZone1 = TimeZone(secondsFromGMT: 60 * 60 * 1)! // Berlinlet start1 = startOfDayIn(date: date, timeZone: timeZone1)let timeZone2 = TimeZone(secondsFromGMT: 60 * 60 * -8)! // New York Citylet 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.

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

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

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.


Original Link: https://dev.to/katharinagopp/timezone-shenanigans-in-swift-1654

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To