Swift Date Timezone Issue

Working with Date() in Swift and having issues adjusting timezones for storing and reading back in Firestore/Firebase

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.

Edited to fix return 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.

Edit:

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)
}

Edit #2:

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.

Swift Date Timezone Issue

You should use Calendar method dateComponents(in: TimeZone) to check the relative date components in a different time zone as follow:

let dateString = "2017-10-09T18:00:00.000Z"
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
let date = formatter.date(from: dateString)! // "Oct 9, 2017 at 3:00 PM" in Brazil
// "Oct 9, 2017 at 2:00 PM" in New York
let components = Calendar.current.dateComponents(in: TimeZone(identifier: "America/New_York")!, from: date) //calendar: gregorian (fixed) timeZone: America/New_York (fixed) era: 1 year: 2017 month: 10 day: 9 hour: 14 minute: 0 second: 0 nanosecond: 0 weekday: 2 weekdayOrdinal: 2 quarter: 0 weekOfMonth: 2 weekOfYear: 41 yearForWeekOfYear: 2017 isLeapMonth: false

if 8..<16 ~= components.hour! {
print("store is open in NY"). // "store is open in NY\n"
}

What is wrong with this Swift TimeZone for abbreviation for a date in another timezone assumption?

Do not rely on timezone abbreviations. MST is being interpreted as "Mountain Standard Time" but it is being interpreted as Phoenix, Arizona ("America/Phoenix") instead of Denver, Colorado ("America/Denver"). You should always use timezone identifiers instead of abbreviations which are ambiguous. The timezone identifier doesn't change based on a date. What you need is to check if it is daylight saving or not for the desired date/timezone and get a timezone localized name based on it:

extension TimeZone {
static let denverCO = Self(identifier: "America/Denver")!
func localizedName(for date: Date) -> String { localizedName(for: isDaylightSavingTime(for: date) ? .daylightSaving : .standard, locale: .current) ?? "" }
func localizedNameShort(for date: Date) -> String { localizedName(for: isDaylightSavingTime(for: date) ? .shortDaylightSaving : .shortStandard, locale: .current) ?? "" }
}


let mst = Date(timeIntervalSinceReferenceDate: 636327068) // "Mar 1, 2021 at 2:31 PM"
let mdt = Date(timeIntervalSinceReferenceDate: 637709468) // "Mar 17, 2021 at 3:31 PM"


TimeZone.denverCO.localizedName(for: mst)  // "Mountain Standard Time"
TimeZone.denverCO.localizedName(for: mdt) // "Mountain Daylight Time"

TimeZone.denverCO.localizedNameShort(for: mst) // "MST"
TimeZone.denverCO.localizedNameShort(for: mdt) // "MDT"

TimeZone Date Formatting Issue

Probably because there is more than one timezone that matches the timezone abbreviation or the date formatter's default date (January 1st) doesn't match the daylight savings of the timezone abbreviation used. Not all countries uses daylight savings time as well it might change at any time. Check this link. This will probably happen for all non US timezones abbreviation as well. For example CST it is used for "China Standard Time" and "Chicago Standard Time" as well. You can workaround that issue setting your date formatter property isLenient to true. Note that this will result in a date of January 1st 2000 with a probably incorrect timezone offset. If you have control of your string input you should use the timezone identifiers instead of its abbreviations to avoid ambiguity. You should also set your date formatter's locale to "en_US_POSIX" when parsing fixed date format to avoid date formatter's reflecting the users device locale and settings:

let timeString = "17:32 (EET)"
let formatter = DateFormatter()
formatter.locale = .init(identifier: "en_US_POSIX")
formatter.dateFormat = "HH:mm (zzz)"
formatter.isLenient = true
let date = formatter.date(from: timeString) // "Jan 1, 2000 at 1:32 PM" "BRT Brazilian Standard Time"

So you should use "GMT+2" or "GMT-3" to avoid ambiguity or as I have already suggested use its identifiers "HH:mm (VV)" i.e. "Europe/Athens" or "America/Sao_Paulo"

Swift 3 timezone issue

let locale = NSTimeZone.init(abbreviation: "BST")
NSTimeZone.default = locale as! TimeZone

Try this

Getting wrong date when converting string with timezone

// This lets us parse a date from the server using the RFC3339 format
let rfc3339DateFormatter = DateFormatter()
rfc3339DateFormatter.locale = Locale(identifier: "en_US_POSIX")
rfc3339DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
rfc3339DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)

// This string is just a human readable format.
// The timezone at the end of this string does not mean your date
// will magically contain this timezone.
// It just tells the parser what timezone to use to convert this
// string into a date which is basically just seconds since epoch.
let string = "2019-01-14T00:00:00+08:00"

// At this point the date object has no timezone
let shiftDate = rfc3339DateFormatter.date(from: string)!

// If you want to keep printing in SGT, you have to give the formatter an SGT timezone.
let printFormatter = DateFormatter()
printFormatter.dateStyle = .none
printFormatter.timeStyle = .full
printFormatter.timeZone = TimeZone(abbreviation: "SGT")!
let formattedDate = printFormatter.string(from: shiftDate)

You will notice that it prints 12am. There is nothing wrong with your code. You just misunderstand the Date object. Most people do.

Edit: I used the RFC formatter found in the Apple docs here. The result is the same if you use your formatter. And yes, as rmatty said, there are a few things wrong with your formatter (I stand corrected :))

Issues with FSCalendar and current timezone

Well, finally solved it after transforming to localDate() all the dates of the database before adding them to the FSCalendar, and also, transforming to localDate() every date returned by the FSCalendar delegates/listeners before using them to display events of each day etc... Then, the events are correctly placed in their days.

I used this extension to transform dates into local timezone dates:

public extension Date {
func localDate() -> Date {
let timeZoneOffset = Double(TimeZone.current.secondsFromGMT(for: self))
guard let localDate = Calendar.current.date(byAdding: .second, value: Int(timeZoneOffset), to: self) else {return Date()}

return localDate
}
}

iOS Date timezone conversion not working

Please try this:

let dateString = "2017-12-01 10:00:00 +0000"

print("original date = \(dateString)")

var formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
formatter.timeZone = TimeZone.init(abbreviation: "UTC")

if let date = formatter.date(from: dateString) {
formatter.timeZone = TimeZone(identifier: "Asia/Shanghai")
let localTime = formatter.string(from: date)
print(localTime)
}
print("test")

Output:

original date = 2017-12-01 10:00:00 +0000
2017-12-01 18:00:00 +0800
test


Related Topics



Leave a reply



Submit