Background Information
I am developing an iOS application that is connected to a server, and requests can be made from the device towards the server to synchronize the local database with the server's database. Changes can occur on either one of the databases. The application also has an offline feature, where users can modify the data without being connected to an internet connection, and only going online in order to synchronize the device's local database with the server's database, by sending new data and receiving updates from the server.
Updated with more background information: The data handled is a form that must be completed in a certain amount of time. The time it is started is stored as a property on the model, and using said property, the application shows the user how much time left before this form is locked.
Problem
While the device is offline, the user can manipulate the time settings, which would make the data received from this device inaccurate.
I have searched for a way to detect changes to the device time settings but according to this SO answer, that does not seem to be possible unless my application is running in the background, which Apple is very strict about, and does not allow unless the application had one of the background modes enabled, which are not applicable in the application.
Updated with more information: As I mentioned in the updated part of the background information above, the data is forms that could have a time limit. Therefore a user can start a form on one device, but not finish it there. They would then synchronize the data to the server, jump to another device and complete the same form.
Proposed Solution
The closest I've come to solving this problem is to get the difference between the server time and the device time when the application is installed (It is sure to be online the first time the application is opened in order to configure it for this device), and using this difference as reference whenever I want to store a time-related value.
If the server detects a difference in the time, it will reject all data coming from this device, which will force the device to revert the data, update the time difference, and only then will data be accepted from this device.
The problem is that while the device is offline, the user could change the time settings, perform changes on the data, and then returning the time settings to what they were earlier before going online. The server would then detect no changes in time settings and will accept the invalid data.
Update to add another proposed solution: To elaborate more on my note in the questions below, and the comments with @Krypton, the uptime of the device does not help much in my situation because of the same problem as the above solution.
Taking the device's uptime and comparing it to the server time is perfect, until the user reboots the device. It is not manually changeable by the user, however all it takes it one reboot to break everything, which would yield the same inaccurate data for me. Fixing it would require calculating the new difference, which would not help when the device is offline.
Questions
- Is there an unchangeable timestamp that can be used as reference when saving a time-related value, such that it cannot be affected by changes to device time from the user? An example of this would be the time since the device was first activated. (Note: The closest I have come to finding anything related is the uptime of the device, but that doesn't help much in my situation)
- Is there a way any way to know the current time without the need to go online whenever a time-related value is going to be saved?
- Is there a way to fix my proposed solution, or any better solution?
There are three ways to get the time on a device.
NSDate
. The time the user can modify.mach_absolute_time
. This is the device's absolute time. But as you pointed out, this breaks everything on a device reboot. Also, this will get incorrect when the device is suspended.uptime
to get this.You will want to use a combination of
NSDate
anduptime
. Perhaps you could save the timestamp of both on any changes made to the form. You'll be able to calculate any difference between these values on changes and do the necessary calculations for your application. You may also want to save and compare with the server time if possible.Now there is still the issue of a restarted device. If you were able fetch the time the device booted, you can calculate the difference between your last saved
NSDate
and boot time. Luckily, that is possible. See this answer or any of the other answers for possible ways to do so. Do note that implementing this could cause a small (1-10 seconds) inaccuracy.Another option is not allowing the app to start without an internet connection after device reboot.
Also, the
NSSystemClockDidChangeNotification
should be able to help.Edit: This how I would do this. Keep in mind, this is not perfect and whatever you choose, there will be trade-offs.
You can fetch the server time every so often when you have an internet connection and save this in the model. This allows you to calculate the time difference between the device time and your server's time. You can do this by saving the
uptime
andNSDate
as timestamps in your model. If the device has not been reset, useuptime
. Calculate theNSDate
of boot (see here), and start measuring again. If these differences change significantly, you know the time's been tampered with. Keep in mind that these differences will change, even if the user does not change the time (measuring mistakes, daylight saving time...). What you consider significantly depends a lot on your application.Then you have to decide what you want to do if the time has been changed. You can consider calculating if the time taken wasn't longer than the maximum (use
uptime
if the device hasn't been reset) or maybe discard the changes altogether.I had the same problem and your solution helped thanks. The thing you missed is that when the system clock changes or you reboot your device you can tell by how much it has been changed (how many seconds). All you need to do is adjust your stored time for the number of seconds the system clock has changed by.
Something like...
if(iBootTime != iBootTimeBefore) dtMyTime.addTimeInterval(timeInterval:TimeInterval(iBootTime - iBootTimeBefore))
You can never, ever rely on two devices having the same time. I would recommend that a server should use increasing time stamps, and if you got the data from the server with a server time stamp X, then all you should record with local changes is the knowledge that your changes are "some time after server time stamp X", without any accurate time. And independent on when your device thinks the change was made. If the server gave you information with a time stamp of today, 11:20:47.003692, and you make changes and the time on your device is 10:15:15:000000, then that change is not made at 10:15, but "after 11:20:47.003692 on the server".