25 Apr, 2019
Synchronized date problem in Swift
A critical feature in your Application depends on reading the current date and you want to make sure that the user cannot influence the behavior of said feature by modifying the Date & Time value on the device's Settings.
The Game plan
On iOS, time based features will typically rely on using the built in
Date() object constructor. This returns a date value initialized to the "current" date and time. This "current" date and time refers to the value set in the device's Date & Time setting. This is in reality an Unsynchronized date because the user can change it at will.
In other words, when working on a critical feature, this date cannot be trusted.
Synchronizing your Application's date with the real world date (or any other date you want) it's a simple matter of following these steps:
- Querying your Source of truth date.
- Calculating the difference in time between your Source of truth and the local "current"
- Returning a synchronized version of
Datethat takes intro account the calculated difference between the local and Source of truth dates.
- Querying the Source of truth and calculating the difference again if we detect the user might have tampered with the device's Date & Time setting.
The Source of truth date
To be able to synchronize our local
Date with the outside world we will need a remote API that provides the real world time. There are some free options online and for the purposes of this article, we will use http://worldclockapi.com/
The endpoint providing UTC synchronized time in JSON format is: http://worldclockapi.com/api/json/utc/now
The output looks something like this:
This endpoint provides quite a lot of information but we are only really interested in the
Since iOS provides a robust Date object capable of doing transformations to any timezone, having the UTC (Coordinated Universal Time) date is all we need to synchronize time in our app.
Querying the Source of truth date
Loading the current UTC date as reported by worldclockapi.com is a straight forward networking task.
In the snippets above, i am defining the
SourceOfTruthRemoteDateProvider protocol to abstract the functionality of fetching the real world date from a remote server and then i am creating a concrete implementation of the protocol that uses the worldclockapi.com:
The practice of defining a protocol before writing a concrete implementation is a standard approach to comply with the “Dependency Inversion Principle” (DIP). You can read more about it in a previous article.
Calculating the Synchronized Date
Now that we have a Source of truth date, we can move on to the next step in the plan which is to calculate the difference between the local device date and the remote Source of truth date. This is rather trivial as well:
The concrete implementation of this functionality simply uses the
setSourceOfTruth method to calculate a value for
localTimeDifference which in turn get used when getting the
synchronizedDate property value.
If the user decides to go into the Device settings and changes the value of the local device date
synchronizedDate can account for this difference taking the Source of truth date as a point of reference.
Putting it all together
In the snippet above we are using the 2 classes we defined earlier inside a View Controller that makes a call to fetch the Source of truth date on
viewDidAppear and then passes on the result to out
synchronizedDateProvider which is used to return our synchronized date.
We are also adding an event listener to reload the date every time the app goes to the foreground. This works to keep our date synced should the user go to Settings > General > Date & Time to mess around with the clock.
Additional “corner cases”
There’s one more way that our local device date can go out of sync with our real world Source of truth date without our user going to the Device settings and purposely changing the date.
This can also happen when there is a significant change in time, for example, change to a new day (midnight), carrier time update, and change to or from daylight savings time.
In this case iOS will post the
UIApplication.significantTimeChangeNotification. We can add a few more event listeners to cover this “corner case” and make the implementation more robust:
You can find a finalized sample project at https://github.com/piterwilson/SynchronizedDate
Sources and Further reading
- Wikipedia, Coordinated Universal Time, Retrieved from: https://en.wikipedia.org/wiki/Coordinated_Universal_Time
- Appple, Date.init(), Retrieved from: https://developer.apple.com/documentation/foundation/date/1780470-init
- Appple, significantTimeChangeNotification, Retrieved from: https://developer.apple.com/documentation/uikit/uiapplication/1623059-significanttimechangenotificatio