25 Apr, 2019

Synchronized date problem in Swift

Synchronized date problem in Swift

The problem

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" Date value.
  • Returning a synchronized version of Date that 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 currentDateTime value.

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: WorldClockApiService.

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:

Sample project

You can find a finalized sample project at https://github.com/piterwilson/SynchronizedDate

Sources and Further reading