Reputation: 890
I am storing dates from a Swift Project in Firestore without any problem. Dates are converted into UTC format and stored in Firestore as a Timestamp. All good.
Then, back on clientside, I can read them back and apply the TimeZone.current
and the date/time are adjusted accordingly based on the timezone the user is currently in.
So, as en example, a time originally of:
9:00 pm Melbourne time (which is GMT+10),
shows as 7:00 am if the user is in New York.
Great.
But I have some items that I want to adjust for timezones (as per above) and others I don't.
So say I have two items the same as the above example, but one is an alarm and I want to keep at the time it was originally set for regardless of the new timezone... So still keep it at 9:00 pm.
I have a Bool
flag saved in my database to say ignoreTimezone
but I'm lost as to how to do this in Swift when reading back the timestamp from Firestore in UTC format and get it back to the original 9:00 pm.
All the Q&A's I've found are all about converting timezones etc. but not really on this example of ignoring one and keeping the date and time set to the timezone they were originally set for.
Thanks in advance for any help and/or suggestions.
I have now incorporated the suggested code. So have a calendar extension:
extension Calendar {
func offsetFromMidnight(for date: Date) -> TimeInterval {
return date.timeIntervalSince(startOfDay(for: date))
}
}
Then I carry out the recommended steps.
Take an offset from midnight, in this case, the current Date()
:
let offsetSinceMidnight = UInt64(Calendar.current.offsetFromMidnight(for: Date()))
This value is then stored on the server. I'm currently in Melbourne (Australia), so the date and time item used for testing is July 9 @ 2:00pm.
When it is retrieved on the client end in a different timezone, I'm using the recommended code:
//Create a calendar for the target timezone
guard let chicagoTimeZone = TimeZone(identifier: "America/Chicago") else { fatalError() }
var chicagoCalendar = Calendar(identifier: .gregorian)
chicagoCalendar.timeZone = chicagoTimeZone
//Calculate midngiht in the target calendar
let chicagoMidnight = chicagoCalendar.startOfDay(for: Date())
//calculate the same time-of-day in the new timezone
let adjustedChicagoTime = Date(timeInterval: TimeInterval(offsetSinceMidnight), since: chicagoMidnight)
The output is set to the correct time, 2:00pm in Chicago, but because of the differnent dates (Chicago is still July 8th), then the midnight timeinterval is being applied on the wrong date. So I get July 8 @ 2:00pm.
I'm assuming I will also need to capture the original date components to apply the offsetSinceMidnight
to a date in the newTimeZone that has matching date components??? Or is there a better approach to this?
Upvotes: 1
Views: 919
Reputation: 131418
Date objects store an instant in time, anywhere in the world. They don't capture the idea of a time-of-day regardless of time zone.
To do that I would suggest calculating an offsetFromMidnight value.
extension Calendar {
func offsetFromMidnight(for date: Date) -> TimeInterval {
return date.timeIntervalSince(startOfDay(for: date))
}
}
You'd call that function in the user's current calendar to get the seconds since midnight in the user's current time zone. Save that to your database. (You could round to a long integer with very little loss of precision.)
I happen to BE in the NYT time zone (EDT) so using that as the destination time zone won't work for me since it won't change anything. Instead, I'll show code to convert from my timezone to GMT:
//Run on user's local machine (in EDT in my case):
let offsetSinceMidnight = UInt64(Calendar.current.offsetFromMidnight(for: Date()))
//Save offset to FireStore
Then if you want that same time of day in a new timezone, you'd use code like this:
//Create a calendar for the target time zone (or the user's local time zone on the destination machine)
guard let gmt = TimeZone(abbreviation: "GMT") else { fatalError() }
var gmtCalendar = Calendar(identifier: .gregorian)
gmtCalendar.timeZone = gmt
//Read time offset from FireStore
let offsetFromNYC = Calendar.current.offsetFromMidnight(for: Date())
//Calculate midnight in target calendar
let gmtMidnight = gmtCalendar.startOfDay(for: Date())
//Calculate the same time-of-day in the GMT time zone
let gmtTimeToday = Date(timeInterval: TimeInterval(offsetSinceMidnight), since: gmtMidnight)
print(gmtTimeToday)
Note that the above will give you the same hours/minutes/seconds as the offsetFromMidnight time.
If your goal is to set an alarm to the next future time-of-day in the local time zone, you'd need to add logic to check if the computed date/time is in the past and adjust:
//Change adjustedChicagoTime to a var
var adjustedChicagoTime = Date(timeInterval: TimeInterval(offsetSinceMidnight), since: chicagoMidnight)
//If the alarm time is in the past, add a day to the date.
if adjustedChicagoTime < Date() {
adjustedChicagoTime = Calendar.current.date(byAdding: .day,
value: 1, to: adjustedChicagoTime, wrappingComponents: false)
}
After a back-and-forth, it sounds like you sometimes want to save a date and time that's independent of time zone, like 9:30 AM on 10 July. If I create that date in EDT, and you view it in Melborne, it's ALWAYS 9:30 AM on 10 July.
Other times, you want to upload and download dates & times that honor time zones.
In order to easily do both, I would suggest saving 2 different string date/time fields to FireStore, one with a time zone, and one without. The one with timezone (or rather offset from GMT) would capture a moment in time around the world, and could be converted to a local time.
The one without time zone would describe a day/month/year/hours/minutes in local time.
You could generate/parse those strings in Swift using date formatters like this:
let baseFormatString = "YYYY-MM-dd'T'HH:mm"
let timeZoneFormatString = baseFormatString + "ZZZ"
let noTimeZoneFormatter = DateFormatter()
noTimeZoneFormatter.dateFormat = baseFormatString
let timeZoneFormatter = DateFormatter()
timeZoneFormatter.dateFormat = timeZoneFormatString
Note that by default a date formatter uses the system's time zone, so the "no time zone formatter" would assume the local time zone. If you use it to convert a date string to a date, it will assume the date is in the local time zone.
Upvotes: 2