preface
Time is a relatively abstract concept, Greenwich Mean Time, Universal Time, Zulu Time, GMT, UTC, cross time zone, daylight saving time and so on. However, we are in a programming language, think time is linear and irreversible, one moment, only one absolute value, description of time are the absolute value of a time line as the reference point, reference points plus the offset (in seconds or milliseconds, microseconds, nanosecond) for the unit to describe another point in time.
In the daily development process, we always deal with time in various scenarios, such as: get the current time, countdown to a certain time, method time statistics, etc. The question arises, how do you make sure you get the right time? Some limited time operations, how to synchronize with the server time? How to prevent users from modifying time cheating? While working on a recent problem in a timed action scenario, I found it necessary to take stock of the way iOS fetched time.
There are 8 ways to get time on iOS. Do you know how to use them? Let’s start by reviewing the concepts of GMT and UTC, and then go through each of the eight ways to obtain time and their characteristics.
Standard time
GMT (Greenwich Mean Time)
Greenwich Mean time. It sets the time for the sun to pass through the Royal Greenwich Observatory on the outskirts of London, England, at 12 noon each day.
GMT history:
The Royal Observatory in Greenwich began astronomical observations in the 17th century as part of a plan to expand its maritime supremacy. For astronomical observation, a meridian through the meridian center of Greenwich Observatory in London, England is chosen as the zero reference line, this line, referred to as the Greenwich meridian. In October 1884, an international meridian conference was held in Washington, THE United States. The conference set the Greenwich meridian as the prime meridian and Greenwich Mean Time (GMT) as the Universal Time standard (UT). This also determines the division of the global 24-hour natural time zones, with all time zones taking the offset from GMT as a reference. Greenwich Mean Time (GMT) was the world standard until 1972. GMT ceased to be a time standard after 1972.
UTC (Coodinated Universal Time)
Coordinated universal time, also known as world unified time, world standard time, international coordinated time. Because of the different abbreviations for CUT and TUC, the compromise was called UTC. UTC is now the universal time standard, with all parts of the world agreeing to synchronize their time. UTC time is an actuarial combination of mean solar time (based on Greenwich Mean Time (GMT)), a new time scale corrected for the motion of the Earth’s axis, and international Atomic time in seconds.
UTC consists of two parts:
1. Atomic Time (TAI, International Atomic Time) : Time obtained by combining all 400 Atomic clocks in the world determines the speed at which Time flows in each of our clocks. Atomic clocks are off by 1 second in 5 billion years. 2. UT (Universal Time) : also known as astronomical Time, or solar Time, is based on the earth’s rotation, which we use to determine how many atomic hours correspond to the length of an Earth day.
The history of the UTC
In 1960, the International Radio Advisory Committee standardized the concept of UTC and put it into practical use the following year. The name “Coordinated Universal Time” was not officially adopted until 1967. UTC was adjusted several times before 1967 because of the use of leap seconds to align UTC with the Earth’s rotation.
We can think of Greenwich Time as universal Coordinated Time (GMT=UTC), and both Greenwich time and UTC are measured in seconds.
The way to get time in iOS
A, NSDate
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).
We usually call the NSDate date method in NSFoundation to get the current time is the UTC time. Color {#1E90FF}{January 1, 2001, 00:00:00\color{#1E90FF}{January 1, 2001, 00:00:00} January 1, 2001, 00:00:00 as the reference interval. Color {#1E90FF}{January 1, 1970 00:00:00 \color{#1E90FF}{January 1, 1970 00:00:00} January 1, 1970 00:00:00} 00 is the reference interval. NSDate provides this conversion capability. Check out the NSDate API:
@property (readonly) NSTimeInterval timeIntervalSinceNow; @property (readonly) NSTimeInterval timeIntervalSince1970; / / to January 1, 1970 00:00:00 as a benchmark @ property (readonly) NSTimeInterval timeIntervalSinceReferenceDate; // Base on 00:00:00 on January 1, 2001Copy the code
Example code:
NSDate* date = [NSDate date];
NSLog(@"%@",date);
NSLog(@"timeIntervalSinceNow: %f", [date timeIntervalSinceNow]);
NSLog(@"timeIntervalSince1970: %f", [date timeIntervalSince1970]);
NSLog(@"timeIntervalSinceReferenceDate: %f", [date timeIntervalSinceReferenceDate]);
Copy the code
Output result:
2020-12-12 20:16:58.160637+0800 DateTest[6402:2425204] Sat Dec 12 20:16:58 2020 2020-12-12 20:16:58.160686+0800 DateTest[6402:2425204] timeIntervalSinceNow: DateTest[6402:2425204] timeIntervalSince1970: 1607775418.160621 the 2020-12-12 20:16:58. 160739 + 0800 DateTest [6402-2425204] timeIntervalSinceReferenceDate: 629468218.160621629467211.468034Copy the code
2020-12-12 20:16:58.160637+0800 output time is +8 hours, because Beijing is in UTC+8 time zone, then the absolute UTC time is 2020-12-12 12:16:58.160637+0800. Different time zones display different times, but the absolute time offset is the same. It depends on the NSDateFormatter and NSTimeZone to display the NSDate. The most important point is that NSDate is the obtained time, which is controlled by the system and can be changed by the user.
Second, the CFAbsoluteTimeGetCurrent
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.
Color {#1E90FF}{January 1, 2001 00:00:00\color{#1E90FF}{January 1, 2001 00:00:00\color{#1E90FF}{January 1, 2001 00:00:00\color{#1E90FF}{January 1, 2001 00:00:00\color{#1E90FF}{January 1, 2001 00:00:00} January 1, 2001 00:00:00
NSDate* date = [NSDate date]; CFAbsoluteTime absoluteTime = CFAbsoluteTimeGetCurrent(); NSLog(@"NSDate timeIntervalSinceReferenceDate: %f", [date timeIntervalSinceReferenceDate]); NSLog(@"CFAbsoluteTime: %f", absoluteTime); The 2020-12-12 20:38:19. 120249 + 0800 DateTest [6407-2430605] timeIntervalSinceReferenceDate: 629469499.120139 2020-12-12 20:38:19.120267+0800 DateTest[6407:2430605] CFAbsoluteTime: 629469499.120266Copy the code
The output can be seen that in accordance with NSDate timeIntervalSinceReferenceDate access timestamp. Also controlled by the system, users can modify.
The getTimeofday function
Int getTimeofday (struct timeVal * __RESTRICT, void * __restrict);
The 2020-12-12 20:59:29. 681949 + 0800 DateTest [6472-2441874] CFAbsoluteTime: 629470769.681292 2020-12-12 20:59:29.682059+0800 DateTest[6472:2441874] getTimeOfDay: 1607777969.000000Copy the code
In January 1, 1970 UTC 00:00:00 \ color e90ff # {1} {on January 1, 1970 00:00:00} 00:00:00 of January 1, 1970 as a benchmark, also controlled by the system, users can be modified.
Mach_absolute_time function
Returns current value of a clock that increments monotonically in tick units (starting at an arbitrary point), this clock does not increment while the system is asleep.
uint64_t nStartTick = mach_absolute_time() NSLog(@"nStartTick: %llu",nStartTick); 002634+0800 DateTest[311:4891] nStartTick: 2188669381Copy the code
uint64_t mach_absolute_time(void); This function returns the number of “ticks” that the CPU has run. After the phone is restarted, it starts counting again from an arbitrary value, not controlled by the system, but affected by the device’s restart and sleep behavior. This “ticks” can be converted to seconds by mach_timebase_info(), i.e. how long it has been running since the restart, as follows:
double machTime = (double)mach_absolute_time()*(double)timebase.numer/(double)timebase.denom/1e9; // convert to s NSLog(@"nStartTick to s: %f",machTime); 930518+0800 DateTest[359:8022] nStartTick to s: 592.234748Copy the code
Fifth, CACurrentMediaTime
Returns the current CoreAnimation absolute time. This is the result of calling mach_absolute_time () and converting the units to seconds.
This time is the CPU running time obtained by mach_Absolute_time (), which is directly in seconds and is not controlled by the system. It is only affected by device restart and hibernation behavior
2020-12-12 22:29:52.069621+0800 DateTest[368:10795] nStartTick to s: 069740+0800 DateTest[368:10795] CACurrentMediaTime :1263.375350Copy the code
Six, NSProcessInfo processInfo
The amount of time the system has been awake since the last time it was restarted.
The running time obtained from NSProcessInfo after the system restarts is not controlled by the system. It is only affected by the restart and hibernation behavior of the device
NSLog(@"CACurrentMediaTime :%f",CACurrentMediaTime()); NSLog(@"NSProcessInfo :%f", [[NSProcessInfo processInfo] systemUptime]); 2020-12-12 22:37:25.380374+0800 DateTest[372:12605] CACurrentMediaTime :1716.685811 2020-12-12 22:37:25.380374+0800 DateTest [372-12605] NSProcessInfo: 1716.685986Copy the code
Sysctl function
int sysctl(int *, u_int, void *, size_t *, void *, size_t);
Obtain the Unix time of the last restart of the device. It is controlled by the system and can be changed by users. Call as follows:
- (long)_bootTime { struct timeval bootTime; int mib[2] = {CTL_KERN,KERN_BOOTTIME}; size_t size = sizeof(bootTime); if (sysctl(mib, 2, &bootTime, &size, NULL, 0) ! = -1) { return bootTime.tv_sec; /// return boottime.tv_usec; / / / ms}; return 0; }Copy the code
The clock_gettime function
int clock_gettime(clockid_t __clock_id, struct timespec *__tp);
Common enumeration values for clockid_t:
CLOCK_REALTIME, a system-wide realtime clock.
CLOCK_PROCESS_CPUTIME_ID, high-resolution timer provided by the CPUfor each process.
CLOCK_THREAD_CPUTIME_ID, high-resolution timer provided by the CPUfor each of the threads.
CLOCK_REALTIME,a system-wide realtime clock.
CLOCK_PROCESS_CPUTIME_ID, high-resolution timer providedby the CPU for each process.
CLOCK_THREAD_CPUTIME_ID, high-resolution timer provided bythe CPU for each of the threads.
Copy the code
This function is only available on iOS10 or higher. Call as follows:
- (long)_getSystemUptime { struct timespec ts; If (@available(iOS 10.0, *)) {clock_getTime (CLOCK_REALTIME, &ts); } else { } return ts.tv_sec; // return ts.tv_nsec; DateTest[507:34153] getTimeOfDay: 1607846040.000000 DateTest[507:34153] Clock_realtimeUptime: 1607846040.000000Copy the code
CLOCK_REALTIME represents the actual time on the machine, as obtained by the gettimeofday() function, as UTC January 1, 1970 00:00:00 \color{#1E90FF}{January 1, 1970 00:00:00} January 1, 1970 00: Clock_gettime () returns the same current time as getTimeofday (), but with nanosecond accuracy. Struct timeval,struct timespec
_STRUCT_TIMEVAL
{
__darwin_time_t tv_sec; /* seconds */
__darwin_suseconds_t tv_usec; /* and microseconds */
};
_STRUCT_TIMESPEC
{
__darwin_time_t tv_sec;
long tv_nsec;
};
Copy the code
Get time mode summary
access | Time benchmark | Whether the system is affected |
---|---|---|
NSDate | UTC January 1, 2001, 00:00:00 | Under system control |
CFAbsoluteTimeGetCurrent | GMT 00:00:00, January 1, 2001 | Under system control |
gettimeofday | January 1, 1970 00:00:00 UTC | Under system control |
mach_absolute_time | After the phone restarts, the count starts again from an arbitrary value | It is not controlled by the system and is affected by device restart and hibernation |
CACurrentMediaTime | Running time after system restart (unit: second) | It is not controlled by the system and is affected by device restart and hibernation |
NSProcessInfo | Running time after system restart (unit: second) | It is not controlled by the system and is affected by device restart and hibernation |
Sysctl (Obtain the last restart time of the device) | January 1, 1970 00:00:00 UTC | Under system control |
Clock_gettime (iOS10 +, parameter: CLOCK_REALTIME) | January 1, 1970 00:00:00 UTC | Under system control |
Synchronizing server time
In our exam business scenario, time is a very critical parameter, timed exam task, test duration, test start time, test end time judgment, we need to keep consistent with the server time. What we do now is to synchronize the serverTime when the App starts, get the time given by the server, serverTime, and localTime, calculate a difference diff, and save it. The next time we call currentTime, we take localTime again plus this diff to get the real time. So how do we evaluate localTime? NSDate, CFAbsoluteTimeGetCurrent(), getTimeofDay (), sysctl() are all affected by system time. The CPU “ticks” obtained by mach_Absolute_time (), CACurrentMediaTime(), and NSProcessInfo are affected by device restart and hibernation. Referring to other client implementations, we use the function sysctl() to get the last time the system restarted, and getTimeofday () to get the current local time. Both are affected by the system time, but when the two are subtracted, the system time is irrelevant.
Code implementation:
- (NSTimeInterval)_systemUptime {struct timeval bootTime; int mib[2] = {CTL_KERN, KERN_BOOTTIME}; size_t size = sizeof(bootTime); int resutl = sysctl(mib, 2, &bootTime, &size, NULL, 0); Struct timeval now; struct timezone tz; gettimeofday(&now, &tz); NSTimeInterval uptime = -1; if (resutl ! = -1 && bootTime.tv_sec ! = 0) { uptime = now.tv_sec - bootTime.tv_sec; } return uptime; }Copy the code
Calculate the travel value timeDiff when synchronizing server time for the first time:
double interval = [[[NSString alloc]initWithData:jsonData encoding:kCFStringEncodingUTF8] doubleValue]; NSDate * serverDate = [NSDate dateWithTimeIntervalSince1970: interval / 1000.0]; NSTimeInterval upTime = [self _systemUptime]; NSDate *cpuDate = [NSDate dateWithTimeIntervalSince1970:uptime]; self.timeDiff = [[NSDate dateWithTimeIntervalSince1970:uptime] timeIntervalSinceDate:serverDate];Copy the code
UpTime + TimeDiff = upTime + TimeDiff
NSDate *cpuDate = [NSDate dateWithTimeIntervalSince1970: [self _systemUptime]];
NSDate *realCurrentDate = [cpuDate dateByAddingTimeInterval:-self.timeDiff];
Copy the code
If the current time is changed to 20 o ‘clock, the time obtained after diff is still accurate:
2020-12-13 16:47:28.830748+0800 DateTest[646:68713] ServerDate :Sun Dec 13 16:47:28 2020 --:1607849248.976000 2020-12-13 16:47:28.830919+0800 DateTest[646:68713] LocalDate :Sun Dec 13 16:47:28 2020 --:1607849248.826418 2020-12-13 16:47:28.831037+0800 DateTest[646:68713] after Sun Dec 13 16:47:28 2020 --:1607849248.976000 2020-12-13 2020-12-13 20:48:03.311147+0800 DateTest[646:68713] LocalDate :Sun Dec 13 20:48:03 2020 --: 2020-12-13 20:48:03.311223+0800 DateTest[646:68713] after Diff Date :Sun Dec 13 16:48:14-2020:1607849294.976000Copy the code
conclusion
Time problem seems to be very small, but there is a problem, and it is hard to track, screening, you don’t know what the user do the operation, has affected the time, for a more strict time scene, as much as possible and the server time synchronization, we need to do is to reduce the impact of as much as possible, to avoid affect the logic of the program. This is the summary of the iOS acquisition time problem, if you have any questions, welcome to correct.