SoFunction
Updated on 2025-04-12

IOS Development APP Detailed Introduction to Time Processing

IOS time processing

When it comes to making an app, you can’t avoid dealing with time. There are many tricks about handling time, which is far from a single line of API calls, and it’s as simple as obtaining the current system time. We need to understand the differences between various APIs related to time, and then design corresponding mechanisms based on scenarios.

The form of time

Before we start a deeper discussion, we need to be sure that one premise is: time is linear. That is, at any moment, there is only one absolute time value on this earth, but because of the differences in time zones or cultures, we who are in the same time and space have different expressions or understandings of the same time. This seemingly simple truth is the cornerstone of our understanding of various complex concepts related to time. Just like UTF-8 and UTF-16 are actually Unicode, 20:00 in Beijing and 21:00 in Tokyo are actually the same absolute time value.

GMT

Humans still have limited understanding of time, but we can at least be sure: the change of time is uniform. The speed of time progressing is uniform, not fast or slow, so in order to describe time, we also need to find a value, and its changes also change forward at a uniform speed.

You may not believe it. We humans have gone through a long period of exploration in order to find this reference value and accurately describe the current time value. You can try to think about what things in life change evenly over time, and the numerical properties it has will change at an absolute uniform speed over time.

Previous people found that looking up at the sun was a good way. The sun always "rises early and sets late" according to the regular "eternal change", and "unchanged forever". The current time can be described by the position of the sun in the day. Later, cultures in different regions need to be exchanged. The sun is shining high here, and I may have already set foot down the mountain, so there needs to be a public place recognized by everyone. Using the location of the sun in this place as a reference, it will be much more convenient to communicate. The last choice is the location of the Greenwich Observatory in London, England, where Greenwich time is used as the public time, which is what we call GMT time (Greenwich Mean Time).

UTC

The change in the position of the sun is related to the rotation of the earth. In the past, people believed that the rotation rate of the earth was constant, but in 1960, this cognition was overturned. People found that the rotation rate of the earth was getting slower and slower, and the rate of time progress was still constant, so UTC was no longer considered to be used to accurately describe time.

We need to continue looking for a value that is moving forward at a constant speed. Looking up at the sky is when we look for the answer from the macro direction. The development of technology has allowed us to gain a deeper understanding in the microscopic aspects. Therefore, smart people have established an atomic clock based on the physical properties of microscopic particles and atoms. This atomic clock is used to measure the changes in time. The atomic clock will only have an error of 1 second in 5 billion years. This kind of intensive reading is far better than GMT. The time reflected by this atomic clock is the UTC (Coordinated Universal Time) standard time we are using now.

Next, let’s take a look at the various ways to record time in iOS.

NSDate

NSDate is a class that we usually use more. Let’s take a look at its definition:

NSDate objects encapsulate a single point in time,
 independent of any particular calendrical system or time zone.
 Date objects are immutable, representing an invariant time interval
 relative to an absolute reference date (00:00:00 UTC on 1 January 2001).

The NSDate object describes an absolute value on the timeline, which has nothing to do with the time zone and culture. Its reference value is: the absolute value of the time at 00:00:00 on January 1, 2001, based on UTC.

There is a very important concept here. When we use programming language to describe time, we always use the absolute value on a timeline as the reference point, and the reference point plus the offset (in units of seconds or milliseconds, microseconds, and nanoseconds) to describe another time point.

After understanding this, it will be very clear to see some API calls of NSDate, such as:

NSDate* date = [NSDate date];
NSLog(@"current date interval: %f", [date timeIntervalSinceReferenceDate]);

timeIntervalSinceReferenceDateThe return is the offset from the reference time. The value of this offset is 502945767 seconds, 502945767/86400/365=15.9483056507, 86400 is the number of seconds contained in a day, 365 is roughly the number of days in a year, and 15.94 is of course the number of years. Calculated is exactly the difference between 2001 at this moment.

For example, when I am writing an article, the current time is 11:29 am Beijing time, look at the output of the following code:

NSDate* date = [NSDate date];
NSLog(@"current date: %@", date);

current date: 2016-12-09 03:29:09 +0000. It can be seen that NSDate outputs absolute UTC time, while the time zone of Beijing time is UTC+8, and the output above is +8 hours, which happens to be my current time.

NSDate has nothing to do with urban areas and culture, so to show the specific format time, we need the assistance of NSDateFormatter and NSTimeZone.

Another important point about NSDate is: NSDate is controlled by the mobile phone system time. In other words, when you modify the time display on your phone, the output of NSDate obtaining the current time will also change accordingly. When we understand this when we are building an App, we know that NSDate is not reliable because users may modify its value.

CFAbsoluteTimeGetCurrent()

The official definition is as follows:

Absolute time is measured in seconds relative to the absolute reference date of Jan 1 2001 00:00:00 GMT.
 A positive value represents a date after the reference date, a negative value represents a date before it. 
For example, the absolute time -32940326 is equivalent to December 16th, 1999 at 17:54:34.
 Repeated calls to this function do not guarantee monotonically increasing results.
 The system time may decrease due to synchronization with external time references or due to an explicit
 user change of the clock.

From the above description, it is not difficult to see that the concept of CFAbsoluteTimeGetCurrent() is very similar to NSDate, except that the reference point is: the absolute value of the time at 00:00:00 on January 1, 2001.

Similarly, CFAbsoluteTimeGetCurrent() will also change with the system time of the current device and may also be modified by the user.

gettimeofday

This API can also return a value describing the current time, the code is as follows:

struct timeval now;
struct timezone tz;
gettimeofday(&now, &tz);
NSLog(@"gettimeofday: %ld", now.tv_sec);

The value obtained using gettimeofday is Unix time. What is Unix time?

Unix time is the number of seconds that the current time is offset from the reference point based on UTC January 1, 1970. The value returned by the above API is 1481266031, indicating that the current time has passed 1481266031 seconds from 00:00:00 on January 1, 1970.

Unix time is also a time standard that we usually use more. On the terminal of the Mac, you can convert it into readable time through the following command:

date -r 1481266031

In fact, NSDate also has an API that can return Unix time:

NSDate* date = [NSDate date];
NSLog(@"timeIntervalSince1970: %f", [date timeIntervalSince1970]);

gettimeofday, like NSDate, CFAbsoluteTimeGetCurrent(), are affected by the system time of the current device. It's just that the reference time reference points are different. When we communicate with the server, we usually use Unix time.

mach_absolute_time()

mach_absolute_time() may be used relatively few students, but this concept is very important.

As mentioned earlier, we need to find a uniformly changing attribute value to describe time, and there is just one such value on our iPhone, which is the number of clock cycles (ticks) of the CPU. The value of this tick can be used to describe the time, and mach_absolute_time() returns the number of ticks that the CPU has run. After a certain conversion, this tick can be converted into seconds, or nanoseconds, which is directly related to time.

However, this tick count will start again after each phone restarts, and the tick will also pause the counting after the iPhone lock screen enters sleep.

mach_absolute_time() will not be affected by system time, but will only be affected by device restart and hibernation behavior.

CACurrentMediaTime()

CACurrentMediaTime() may come into contact with more students, so first look at the official definition:

/* Returns the current CoreAnimation absolute time. This is the result of
 * calling mach_absolute_time () and converting the units to seconds. */

CFTimeInterval CACurrentMediaTime (void)

CACurrentMediaTime() is the result of converting the CPU tick number of mach_absolute_time() above into seconds. The following code:

double mediaTime = CACurrentMediaTime();
NSLog(@"CACurrentMediaTime: %f", mediaTime);

What returns is how many seconds the device has been running after booting (the device does not hibernate) and another API can also return the same value:

NSTimeInterval systemUptime = [[NSProcessInfo processInfo] systemUptime];
NSLog(@"systemUptime: %f", systemUptime);

CACurrentMediaTime() is not affected by system time, but is only affected by device restart and hibernation behavior.

sysctl

The iOS system also records the last device restart time. It can be obtained through the following API calls:

#include <sys/>
- (long)bootTime
{
#define MIB_SIZE 2
  int mib[MIB_SIZE];
  size_t size;  
  struct timeval boottime;

  mib[0] = CTL_KERN;
  mib[1] = KERN_BOOTTIME;
  size = sizeof(boottime);  
  if (sysctl(mib, MIB_SIZE, &boottime, &size, NULL, 0) != -1)
  {    
    return boottime.tv_sec;
  }  
  return 0;
}

The returned value is the Unix time of the last device restart.

The value returned by this API will also be affected by the system time. If the user modifies the time, the value will also change with the change.

With the above various means of obtaining time, let’s take a look at the specific applications under some scenarios.

Scenario 1, time measurement

When we do performance optimization, we often need to record the time of execution of a certain method, so we will inevitably use some of the methods mentioned above to obtain time.

When doing benchmarks of method execution time, our method of obtaining time must meet two requirements. One is that the intensive reading must be high, but the API itself consumes almost no CPU time.

The performance optimization of the client is generally for the fluency of the main thread. We know that if the UI thread encounters a blockage of more than 16.7ms, frame drops will occur, so the intensive reading of the time we are concerned about is actually at the millisecond (ms) level. When we write client code, we are basically in the ms dimension. If a method loses 0.1ms, we can think that this method is safe for fluency. If we often see methods that exceed 1ms or several ms, the chance of the main thread being stuck will become higher.

The above methods of obtaining time are sufficient for intensive reading. For example, the intensive reading returned by an NSDateAPI call is 0.000004 S, which is 4 microseconds. The intensive reading returned by CACurrentMediaTime() is also at the microsecond level, and all intensive reading meets the requirements. However, there is a view that NSDate belongs to the encapsulation of the class, and the loss caused by the OOP high-level language itself may affect the final actual result. It is not as accurate as C function calls when doing benchmarks. In order to verify this statement, I wrote a simple test code:

int testCount = 10000;
double avgCost = 0;
for (int i = 0; i < testCount; i ++) {  
  NSDate* begin = [NSDate date];  
  NSLog(@"a meaningless log");
  avgCost += -[begin timeIntervalSinceNow];
}
NSLog(@"benchmark with NSDate: %f", avgCost/testCount);

avgCost = 0;
for (int i = 0; i < testCount; i ++) {  
  double startTime = CACurrentMediaTime();  
  NSLog(@"a meaningless log");  
  double endTime = CACurrentMediaTime();
  avgCost += (endTime - startTime);
}
NSLog(@"benchmark with CACurrentMediaTime: %f", avgCost/testCount);

The output result is:

benchmark with NSDate: 0.000046
benchmark with CACurrentMediaTime: 0.000037

It can be seen that the loss difference between CACurrentMediaTime and the NSDate code itself is in a few microseconds, while the dimensions we optimize the UI performance are at the millisecond level. The difference between several microseconds will not affect our final judgment at all. So using NSDate to do benchmark is completely feasible. Here are two macros I often use:

#define TICK  NSDate *startTime = [NSDate date]
#define TOCK  NSLog(@"Time Cost: %f", -[startTime timeIntervalSinceNow])

Scenario 2: Time synchronization between client and server

This is also a scenario we often encounter, such as starting to rush to buy e-commerce apps when they are zero, such as countdown on product purchase restrictions, etc. In this scenario, we need to keep the client's time consistent with the server. The most important thing is to prevent users from modifying the system time by disconnecting the network to affect the client's logic.

The common practice is to bring server time into some commonly used Server interfaces. Every time the interface is called, the client synchronizes and records the server time. But the question is how to prevent users from modifying it?

The above mentioned NSDate, CFAbsoluteTimeGetCurrent, gettimeofday, and sysctl all follow the system time. Although mach_absolute_time and CACurrentMediaTime are based on the number of CPU clocks and are not affected by the system time, they will still be affected during sleep and restart. It doesn't seem to be suitable, so here is my personal approach.

First of all, it will rely on the interface and server time to synchronize. Each time, it will record a serverTime (Unix time) and record the current client's time value lastSyncLocalTime. When calculating the local time, first take curLocalTime, calculate the offset, and add serverTime to get the time:

uint64_t realLocalTime = 0;
if (serverTime != 0 && lastSyncLocalTime != 0) {
  realLocalTime = serverTime + (curLocalTime - lastSyncLocalTime);
}else {
  realLocalTime = [[NSDate date] timeIntervalSince1970]*1000;
}

If you have never been synchronized with the server time, you can only get the local system time, which has almost no effect, which means that the client has not started to use it.

The key is to obtain the local time, you can use a small trick to get how long the system is currently running, and use the system running time to record the current client time:

//get system uptime since last boot
- (NSTimeInterval)uptime
{  
  struct timeval boottime;  
  int mib[2] = {CTL_KERN, KERN_BOOTTIME};
  size_t size = sizeof(boottime);  
  struct timeval now;  
  struct timezone tz;
  gettimeofday(&now, &tz);  
  double uptime = -1;  
  if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0)
  {
    uptime = now.tv_sec - boottime.tv_sec;
    uptime += (double)(now.tv_usec - boottime.tv_usec) / 1000000.0;
  }  
  return uptime;
}

gettimeofday and sysctl are both affected by system time, but the value obtained by subtraction between them has nothing to do with system time. This will prevent users from modifying the time. Of course, if the user shuts down and starts up again after a period of time, it will cause the time we get to be slower than the server time. In real scenarios, slower than the server time often has a smaller impact. What we generally worry about is that the client time is faster than the server time.

If you synchronize time with the server more often, and then put the key time verification logic on the server side, there will be no unexpected bugs.

Summarize

This is the summary of the logic of time processing. The key lies in our understanding of time itself, our understanding of various ways of expressing time, and the principles behind it can only be possible to choose the right tool.

Thank you for reading, I hope it can help you. Thank you for your support for this site!