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: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.
(-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.
Top comments (2)
Great article! Timezones can be very confusing 😅
Thank you 😊