An Interest In:
Web News this Week
- April 23, 2024
- April 22, 2024
- April 21, 2024
- April 20, 2024
- April 19, 2024
- April 18, 2024
- April 17, 2024
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:
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
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
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To