In iOS development, there are all kinds of time issues, such as 8-hour time differences, timestamps, finding time intervals, lunar calendar, etc. Solutions abound online, but most are patchy, and much of the material fails to address the problem. Here is a concentrated summary, in order to refer to and for everyone’s reference. I have my own understanding, mistakes and omissions please ridicule.

NSDate 8-hour problem

NSDate Time of conversion to string

[NSDate date] [NSDate date] [NSDate date] [NSDate date] [NSDate date] [NSDate date] [NSDate date] [NSDate date] [NSDate date] [NSDate date] The moment represents the same time.

NSDate *date = [NSDate date]; NSLog(@"date time = %@", date); NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss Z"]; NSString *dateStr = [formatter stringFromDate:date]; NSLog(@" string time = %@", dateStr);Copy the code

Print result:

2016-12-07 10:44:24.470 timeTest[32743:2995134] Date date = 2016-12-07 02:44:24 +0000 2016-12-07 10:44:24.471 TimeTest [32743:2995134] String Time = 2016-12-07 10:44:24 +0800Copy the code

The time before the printing result is Beijing time: 2016-12-07 10:44:24.470. Date prints out eight hours less because it represents the zero hour zone (+0000) time 02:44:24. It is 10:44:24 Beijing time. It’s just a different time zone, it’s the same point in time. Like 1 kilogram and 2 jin, the weight is the same. [NSDate date] Time unit is 0 hour zone (+0000), we want Beijing time unit is east 8 zone (+0800).

By default, the time obtained by [NSDate date] is the zero time zone time. The time converted by NSDateFormatter is the exact time in the current time zone, within 8 hours.

Time zone setting for string time

The above NSDate time is converted to a string time without setting the NSDateFormatter timeZone. TimeZone = [NSTimeZone systemTimeZone] the current timeZone is used by default if the systemTimeZone is not set.

You can also set the time zone and obtain the string time of the specified time zone

NSDate *date = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
formatter.timeZone = [NSTimeZone timeZoneWithName:@"Asia/Shanghai"];//东八区时间
NSString *dateStr = [formatter stringFromDate:date];
NSLog(@"字符串时间 = %@", dateStr);Copy the code

Even if the phone is picked up in Greenwich, the time will still be picked up in GMT because the time zone is specified. You can also specify the following time zone:

formatter.timeZone = [NSTimeZone timeZoneWithName:@"Asia/Tokyo"]; TimeZone = [NSTimeZone timeZoneWithName:@"GMT"]; // Zero zone timeCopy the code

You can use the following method to obtain the string constants corresponding to the supported time zones:

NSArray *zones = [NSTimeZone knownTimeZoneNames]; For (NSString *zone in Zones) {NSLog(@" zone name = %@", zone); }Copy the code



Time zone comparison table

Change the string time to NSDate

Converting string time to NSDate time is also problematic. There’s also what’s called an 8-hour error, which is a difference in time zone. Take the following example:

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss Z"];
NSDate *newDate = [formatter dateFromString:@"2016-12-07 14:06:24 +0800"];
NSLog(@"newDate = %@", newDate);Copy the code

Print result:

TimeTest [34279:3155380] newDate = 2016-12-07 06:06:24 +0000Copy the code

NSDateFormatter specifies the format @” YYYY-MM-DD HH: MM :ss Z”. The Z here refers to the time zone. The string time format to be converted must match this format. The string time given above is: @”2016-12-07 14:06:24 +0800″, is an east 8th zone time, after converting into NSDate, is the zero zone time 2016-12-07 06:06:24 +0000, literally shows 8 hours less, in fact the same time.

In fact, if the given string time is @”2016-12-07 14:06:24 +0000″, the converted NSDate time will be exactly the same, because the string time is the zero time zone time, there is no time zone error. You can try it.

If the time zone of the string is not specified, that is, there is no +0800 after it, and the Z in the NSDateFormatter format is removed to ensure that the format matches. The system considers the string time to be the time in the system time zone, which is converted to NSDate.

TimeZone = [NSTimeZone timeZoneWithName:@”GMT”]; Specifying the time zone of the string in this way is the same as using Z for +0000.

NSDate Indicates the NSDate time of the current time zone

Because the time obtained by [NSDate date] is the zero time zone time, when we want to obtain the NSDate time of the current time zone, we usually use the following method:

NSDate *date = [NSDate date];
NSTimeZone *zone = [NSTimeZone systemTimeZone];
NSInteger interval = [zone secondsFromGMTForDate:date];
NSDate *localDate = [date  dateByAddingTimeInterval:interval];
NSLog(@"localDate = %@",localDate);Copy the code

Print result:

2016-12-07 14:49:03.777 timeTest[34519:3183548] localDate = 2016-12-07 14:49:03 +0000Copy the code

In the preceding code, zone is the current time zone, interval is the difference between the current time zone and zero time zone, and localDate is zero time zone date plus the difference interval to get the NSDate time of the current time zone. Even worse, add a value like 8*60*60 or 28800 in development because there is a difference of 8 hours. That’s fine in East 8, but wrong in other time zones.

In fact, this approach is unscientific, because the final time is still the zero time zone time, the time is obviously +0000, in use generally do not show the time zone, so it is considered as the current time zone time. This is a pit!

Pit 1: If the time is converted to string, another 8 hours will be added. Because when we do the time conversion, the system will think that this NSDate is the zero time zone, and the resulting string time is the east eighth time zone.

The solution is to go ahead and set the string time to zero time. From a deep pit into a deeper pit!

NSDate *date = [NSDate date]; NSTimeZone *zone = [NSTimeZone systemTimeZone]; NSInteger interval = [zone secondsFromGMTForDate:date]; NSDate *localDate = [date dateByAddingTimeInterval:interval]; NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; formatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; NSString *dateStr = [formatter stringFromDate:localDate]; NSLog(@" string time = %@", dateStr);Copy the code

The @”UTC” here stands for universal standard time, which is the standard of time in use today, and east 8 is 8 hours ahead of that, so @”GMT” is fine.

Pit 2: When interacting with the background, the +0000 time zone is sometimes required and you can only manually concatenate strings to change this time zone field to the correct time zone.

So try not to do this in development, and use string times when time is required to display, store, or interact with the background! Do not use transformed NSDate.

Time conversion, the concept of time stamps

Current time to timestamp

The timestamp is the number of seconds from 00:00 00:00 00:00 on January 1, 1970 to the current time. Note: The current time here refers to the NSDate time in the zero time zone.

NSDate *date = [NSDate date]; NSTimeInterval timeIn = [date timeIntervalSince1970]; NSLog(@" timestamp = %.0f", timeIn);Copy the code

Print result:

2016-12-07 15:41:04.000 timeTest[34994:3232390] Timestamp = 1481096464Copy the code

Timestamp to current time

NSDate *date = [NSDate date]; NSTimeInterval timeIn = [date timeIntervalSince1970]; NSDate *newDate = [NSDate dateWithTimeIntervalSince1970:timeIn]; NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss Z"]; NSString *newTime = [dateFormatter stringFromDate:newDate]; NSLog(@" initialization time =% @, timestamp =%.0f, timestamp to NSDate =% @, string time =% @", date, timeIn, newDate, newTime);Copy the code

Print result:

2016-12-07 16:11:56.146 timeTest[35186:3253589] Initialization time = 2016-12-07 08:11:56 +0000, timestamp =1481098316, When the timestamp is converted to NSDate = 2016-12-07 08:11:56 +0000, when the timestamp is converted to string = 2016-12-07 16:11:56 +0800Copy the code

Note that the timestamp uses the NSDate time of the current zero time zone! Current zero time zone time! Current zero time zone time! Important things say three times! Do not perform NSDate to the NSDate time of the current time zone, and then to the timestamp. Here is the verification:

NSDate *date = [NSDate date]; NSLog(@" system zero hour NSDate time = %@", date); NSTimeInterval timeIn = [date timeIntervalSince1970]; NSLog(@" system zero hour NSDate time converted to timestamp = %.0f", timeIn); NSTimeZone *zone = [NSTimeZone systemTimeZone]; NSInteger interval = [zone secondsFromGMTForDate:date]; NSDate *localDate = [date dateByAddingTimeInterval:interval]; NSLog(@" convert to local NSDate time = %@", localDate); NSTimeInterval timeIn2 = [localDate timeIntervalSince1970]; NSLog(@" local NSDate time converted to timestamp = %.0f", timeIn2); NSDate *detaildate = [NSDate dateWithTimeIntervalSince1970:timeIn]; NSDate *detaildate2 = [NSDate dateWithTimeIntervalSince1970:timeIn2]; NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss Z"]; NSString *newTime = [dateFormatter stringFromDate:detaildate]; NSString *newTime2 = [dateFormatter stringFromDate:detaildate2]; NSLog(@" finally converted to string time 1 = %@, time 2 = %@", newTime, newTime2);Copy the code

Print result:

2016-12-07 16:13:57.834 timeTest[35211:3255842] NSDate time in the zero hour zone = 2016-12-07 08:13:57 +0000 2016-12-07 16:13:57.834 TimeTest [35211:3255842] Indicates that the NSDate in the zero hour zone is converted to the timestamp = 1481098438 2016-12-07 16:13:57.835 timeTest[35211:3255842] indicates that the NSDate in the zero hour zone is converted to the local NSDate = 2016-12-07 16:13:57 +0000 2016-12-07 16:13:57.835 timeTest[35211:3255842] Local NSDate time converted to timestamp = 1481127238 2016-12-07 16:13:57.836 timeTest[35211:3255842] is a string. Time 1 = 2016-12-07 16:13:57 +0800, time 2 = 2016-12-08 00:13:57 +0800Copy the code

For details, see the NSDate time of transferring from NSDate to the current time zone.

Time manipulation and comparison

Time initialization and comparison methods

Several time initialization methods:

NSDate *date = [NSDate date]; / / will be subject to the current time, positive number specified number of seconds in advance, the negative delay the specified number of seconds NSDate * laterDate = [NSDate dateWithTimeIntervalSinceNow: 60]. / / to a 2001-01-01 00:00:00 + 0000 as a benchmark, positive number specified number of seconds in advance, the negative delay the specified number of seconds NSDate * newDate = [NSDate dateWithTimeIntervalSinceReferenceDate: 60]. / / to a 1970-01-01 00:00:00 + 0000 as a benchmark, positive number specified number of seconds in advance, the negative delay the specified number of seconds NSDate * newDate1 = [NSDate dateWithTimeIntervalSince1970:60]. / / instance method to specify the time as a benchmark, positive number specified number of seconds in advance, the negative delay the specified number of seconds NSDate * newDate2 = [60] date dateByAddingTimeInterval:; // a long time later NSDate *newDate3 = [NSDate distantFuture]; // a long time ago NSDate *newDate4 = [NSDate distantPast];Copy the code

Several time comparison methods:

- (BOOL)isEqualToDate:(NSDate *)otherDate; // if the time is low, the earlier time is returned - (NSDate *) written date :(NSDate *)anotherDate; // a later time is returned - (NSDate *)laterDate:(NSDate *)anotherDate; // return enumeration type - (NSComparisonResult)compare:(NSDate *)other;Copy the code

Several ways to calculate time intervals:

// return the number of seconds between instance time and refDate - (NSTimeInterval)timeIntervalSinceDate:(NSDate *)refDate; // Return the number of seconds between the instance time and the current time - (NSTimeInterval)timeIntervalSinceNow; // Return the timestamp of the instance time - (NSTimeInterval)timeIntervalSince1970; / / returns the instance of time and 00:00:00 + the number of seconds between 0000-2001-01-01 (NSTimeInterval) timeIntervalSinceReferenceDate; / / returns the current time and the interval of 2001-01-01 00:00:00 + 0000 seconds + (NSTimeInterval) timeIntervalSinceReferenceDate;Copy the code

Gets year month day hour minute second week time zone

Oc has too many time pits to get the year as in other languages. To get the year, month and day of NSDate, use the calendar object NSCalendar.

NSDate *date = [NSDate date]; NSCalendar *cal = [NSCalendar currentCalendar]; NSDateComponents *dateComps = [cal components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUn itSecond|NSCalendarUnitWeekday|NSCalendarUnitWeekOfMonth|NSCalendarUnitWeekOfYear|NSCalendarUnitTimeZone fromDate:date];  NSLog(@" time = %@", date); = % ld NSLog (@ "year, month = % ld, day = % ld, when = % ld, points = % ld, seconds = % ld, week = % ld, the first % ld weeks this month, this year the first % ld weeks, time zone = % @", dateComps. Year, dateComps. The month, dateComps.day, dateComps.hour, dateComps.minute, dateComps.second, dateComps.weekday, dateComps.weekOfMonth, dateComps.weekOfYear, dateComps.timeZone.name);Copy the code

Print result:

2016-12-07 17:20:41.639 timeTest[35734:3311752] Time = 2016-12-07 09:20:41 +0000 2016-12-07 17:20:41.640 TimeTest [35734:3311752] year =2016, month =12, day =7, hour =17, minute =20, second =41, week =4, 2nd week of this month, 50th week of this year, time zone =Asia/ShanghaiCopy the code

The enumeration NSCalendarUnit added to the NSDateComponents creation method must be added corresponding to the year, month, day, hour, minute, and second to be retrieved later. For example, to get datecomps.year, add the enumeration NSCalendarUnitYear.

[NSDate date] time can be obtained directly by using NSCalendar. The printed time and time zone can be seen. This is why the calendar object [NSCalendar currentCalendar] is initialized, Also can use [[NSCalendar alloc] initWithCalendarIdentifier: NSCalendarIdentifierGregorian] specifies the Identifier of method initializes the Gregorian calendar. Can try to specify the Identifier for NSCalendarIdentifierChinese, print is the Chinese lunar calendar.

Datecomps. weekOfMonth is the week of the month today.

Datecomps. weekOfYear is the number of weeks today is the year.

Datecomps. weekday is a week, which is a little different from daily use. The above program prints week =4, but 2016-12-07 is Wednesday. The weekdays are sunday-1, Monday-2, Tuesday-3, Wednesday-4, Thursday-5, Friday-6, and Saturday-7. After all, Sunday is the first day of the week.

According to the lunar calendar

Get lunar tools and methods, according to the need to add lunar festivals and 24 solar terms

+ (NSString *)LunarForSolarYear:(int)wCurYear Month:(int)wCurMonth Day:(int)wCurDay {// NSArray *cDayName = [NSArray ArrayWithObjects: @ "*," @ "first," @ "2", "@" grade, "@" four, "@" fifth "@" people, "@" the seventh, "@" day, "@" double ninth, "@" the 10th "@" 11, "@" twelve, "@" 13, "@" 14 ", @ ", 15 "@" sixteen, "@" 17, "@" 18, "@" in the 19th, "@" twenty, "@" 21, "@" 22, "@" 23, "@" 24, "@" 25 "@" with "@" 27, "@" platoon, "@" (29th, "@" 30 ", nil]; // Lunar month name NSArray *cMonName = [NSArray ArrayWithObjects: @ "*," @ "in the first month," @ "in February," @ "march," @ "in April," @ "may," @ "in June," @ "July," @ "in August," @ "in September," @ "October" @ "during," @ "lunar month", nil]; / / the number of days in front of the calendar month const int wMonthAdd [12] =,31,59,90,120,151,181,212,243,273,304,334 {0}; / / the lunar data const int wNongliData [100] = {2635333, 387170, 1174, 8267, 701694239, 1133, 423117, 5396, 438 , 2, 3402374 9331 177145 3694201 326235 0465 197322 1340 400202290 1138 6267 611605234 9137 515270 9464 533173 , 2901330, 421124, 2265, 1199, 255132, 3529, 706373, 3170, 6398, 762, 2741120 6267 438264 7131 8204 070347 7461 653138 6241 , 330077119 7263 7268 877336 5531 109290 0292 2398 042239, 1179267, 415263, 5661, 067170, 1174, 8398, 772274, 2239, 1330, 031 , 1175161, 1200, 010374, 9527, 717145, 2274, 2332, 397235, 0322, 2 268949340 2349 3133 973138 6464 219605234 9334 123270 9 , 2890267, 946277, 3592, 565121, 0265, 1395, 863132, 3270, 7265, 877}; static int nTheDate,nIsEnd,m,k,n,i,nBit; // Count the number of days up to February 8, 1921: NTheDate = (wcuryear-922) * 392 + (wcuryear-922) / 4 + wCurday-921 + wMonthAdd[wcurmonth-1] -38; if((! (wCurYear % 4)) && (wCurMonth > 2)) nTheDate = nTheDate + 1; // calculate lunar sky stem, earth branch, moon, day nIsEnd = 0; m = 0; while(nIsEnd ! = 1) { if(wNongliData[m] < 4095) k = 11; else k = 12; n = k; While (n>=0) {nBit = wNongliData[m]; for(i=1; i wNongliData[m] / 65536 + 1) wCurMonth = wCurMonth - 1; } // Generate lunar month NSString *szNongliMonth; If (wCurMonth < 1) {szNongliMonth = [NSString stringWithFormat:@" leap %@",(NSString *)[cMonName objectAtIndex:-1 * wCurMonth]]; }else{ szNongliMonth = (NSString *)[cMonName objectAtIndex:wCurMonth]; } NSString *szNongliDay = [cDayName objectAtIndex:wCurDay]; / / merge nsstrings * lunarDate = [nsstrings stringWithFormat: @ "% @ - % @" szNongliMonth, szNongliDay]; return lunarDate; }Copy the code

To read the article or read more, visit the author’s blog at http://markmiao.com/

Email: [email protected]

Long press the QR code to follow the public account: Imarkmiao, release technical articles and ridicule long articles from time to time.