I need to set the timezone information of a remote clock to the one on the iOS device.
The remote clock only supports GNU lib C TZ format of:
std offset dst [offset],start[/time],end[/time]
e.g: EST+5EDT,M3.2.0/2,M11.1.0/2
So I need to produce a string similar to above from NSTimeZone.local time zone in Swift. Can't seem to access the current timezone rules as they would be in the IANA TZ database to produce the output.
Can this be done without the horrifying idea of caching a local copy of the TZ database in the app?
Update:
I haven't been able to find anything useful even through other programming languages. The best I was able to find was essentially parsing the tzfile in linux and making my own NSDictionary containing the info.
This was a fun exploration, largely because fitting the data into just the right format is pretty complex. Problem components:
We need the "current" TZ database rule that applies for a given time zone. This is a bit of a loaded concept, because:
Darwin platforms don't actually use the TZ database directly for most applications, but instead use ICU's time zone database, which comes in a different format and is more complex. Even if you produce a string in this format, it's not necessarily descriptive of the actual time behavior on device
While it is possible to read and parse the TZ database on iOS dynamically, the TZ database itself is not guaranteed to store information in the format needed here. rfc8536, the RFC governing the Time Zone Information Format says the following about the format you want:
While spelunking through the iOS TZ database, I found some database entries that do offer a rule at the end of the file in this format, but they appear to be a minority. You could parse these dynamically, but it's likely not worth it
So, we need to use APIs to produce a string in this format.
In order to produce a "rule" that is at least approximately correct on a given date, you need to know information about DST transitions around that date. This is an extremely thorny topic, because DST rules change all the time, and don't always make as much sense as you'd hope. At the very least:
Because the rules are so complex, the rest of this answer assumes you're okay with producing a "good enough" answer that represents a specific date in time, and is willing to send further strings to your clock some time in the future when corrections are needed. e.g., to describe "now", we will be assuming that producing a rule based off of the last DST transition (if any) and the next DST transition (if any) is "good enough", but this may not work for all situations in many time zones
Foundation provides DST transition information on
TimeZonein the form ofTimeZone.nextDaylightSavingTimeTransition/TimeZone.nextDaylightSavingTimeTransition(after:). Frustratingly, however, there's no way to get information about previous DST transitions, so we'll need to rectify that:Foundation's localization support (including calendars and time zones) is based directly on the ICU library, which ships internally on all Apple platforms. ICU does provide a way to get information about previous DST transitions, but Foundation just doesn't offer this as API, so we'll need to expose it ourselves
ICU is a semi-private library on Apple platforms. The library is guaranteed to be present, and Xcode will offer you
libicucore.tbdto link against in<Project> > <Target> > Build Phases > Link Binary with Libraries, but the actual headers and symbols are not directly exposed to apps. You can successfully link againstlibicucore, but you'll need to forward-declare the functionality we need in an Obj-C header imported into SwiftSomewhere in the Swift project, we need to expose the following ICU functionality:
These are all forward declarations / constants, so no need to worry about implementation (since we get that by linking against
libicucore).You can see the values in
UTimeZoneTransitionType—TimeZone.nextDaylightSavingTimeTransitionjust callsucal_getTimeZoneTransitionDatewith a value ofUCAL_TZ_TRANSITION_NEXT, so we can offer roughly the same functionality by calling the method withUCAL_TZ_TRANSITION_PREVIOUS:So, with all of this in place, we can fill out a crude method to produce a string in the format you need:
Note that there's lots to improve here, especially in clarity and performance. (Formatters are notoriously expensive, so you'll definitely want to cache them.) This also currently only produces dates in the expanded form
"Mm.w.d"and not Julian days, but that can be bolted on. The code also assumes that it's "good enough" to restrict unbounded rules to the current calendar year, since this is what the GNU C library docs seem to imply about e.g. time zones which are always in standard/daylight time. (This also doesn't recognize well-known time zones like GMT/UTC, which might be sufficient to just write out as "GMT".)I have not extensively tested this code for various time zones, and the above code should be considered a basis for additional iteration. For my time zone of
America/New_York, this produces"EST-5EDT,M3.3.2/3,M11.2.1/1", which appears correct to me at first glance, but many other edge cases might be good to explore:TRANSITION_PREVIOUSvs.TRANSITION_PREVIOUS_INCLUSIVE)There's a lot more to this, and in general, I'd recommend trying to find an alternative method of setting a time on this device (preferably using named time zones), but this might hopefully at least get you started.