在iOS開發(fā)中,經(jīng)常會(huì)遇到各種各樣的時(shí)間問題,8小時(shí)時(shí)差,時(shí)間戳,求時(shí)間間隔,農(nóng)歷等等。解決辦法網(wǎng)上比比皆是,但大多零零散散,很多資料并沒有說明其中問題。這里集中總結(jié)一下,以便于以后查閱和供大家參考。有我自己的理解,錯(cuò)漏之處請大家吐槽。
NSDate的8小時(shí)問題
NSDate轉(zhuǎn)字符串時(shí)間
初始化一個(gè)NSDate時(shí)間[NSDate date],獲取的是零時(shí)區(qū)的時(shí)間(格林尼治的時(shí)間: 年-月-日 時(shí):分:秒: +時(shí)區(qū)),而北京時(shí)間是東八區(qū)時(shí)間,因?yàn)闀r(shí)區(qū)不同,所以打印的時(shí)間相差了8小時(shí)。此刻表示的時(shí)間是一樣的。
NSDate *date = [NSDate date];
NSLog(@"date時(shí)間 = %@", date);
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss Z"];
NSString *dateStr = [formatter stringFromDate:date];
NSLog(@"字符串時(shí)間 = %@", dateStr);
打印結(jié)果:
2016-12-07 10:44:24.470 timeTest[32743:2995134] date時(shí)間 = 2016-12-07 02:44:24 +0000
2016-12-07 10:44:24.471 timeTest[32743:2995134] 字符串時(shí)間 = 2016-12-07 10:44:24 +0800
打印結(jié)果前面的時(shí)間是北京時(shí)間:2016-12-07 10:44:24.470。而date打印出來的時(shí)間顯示少了8小時(shí),因?yàn)樗硎镜氖橇銜r(shí)區(qū)(+0000)時(shí)間02:44:24。此刻對應(yīng)東八區(qū)的北京時(shí)間就是10:44:24。只是時(shí)區(qū)不同,表示的時(shí)間點(diǎn)是一樣的。好比1公斤和2斤,重量是一樣的。[NSDate date]獲取的時(shí)間單位是零時(shí)區(qū)(+0000),我們所要的北京時(shí)間的單位是東八區(qū)(+0800)。
系統(tǒng)會(huì)默認(rèn)[NSDate date]獲取的時(shí)間為零時(shí)區(qū)時(shí)間,而經(jīng)過NSDateFormatter轉(zhuǎn)化為字符串時(shí)間就是當(dāng)前所在時(shí)區(qū)的準(zhǔn)確時(shí)間,并沒有8小時(shí)誤差。
轉(zhuǎn)字符串時(shí)間的時(shí)區(qū)設(shè)定
上文中NSDate時(shí)間轉(zhuǎn)為字符串時(shí)間并沒有設(shè)置NSDateFormatter的timeZone。不設(shè)置會(huì)默認(rèn)使用當(dāng)前所在的時(shí)區(qū),與設(shè)置系統(tǒng)時(shí)區(qū)formatter.timeZone = [NSTimeZone systemTimeZone]的效果是一樣的。
也可以設(shè)置時(shí)區(qū),獲取指定時(shí)區(qū)的字符串時(shí)間
NSDate *date = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
formatter.timeZone = [NSTimeZone timeZoneWithName:@"Asia/Shanghai"];//東八區(qū)時(shí)間
NSString *dateStr = [formatter stringFromDate:date];
NSLog(@"字符串時(shí)間 = %@", dateStr);
這時(shí)獲取的時(shí)間就是東八區(qū)時(shí)間,哪怕手機(jī)拿到零時(shí)區(qū)的格林尼治,獲取的也是東八區(qū)的時(shí)間,因?yàn)檫@里指定時(shí)區(qū)了。也有如下時(shí)區(qū)指定:
formatter.timeZone = [NSTimeZone timeZoneWithName:@"Asia/Tokyo"];//東九區(qū)時(shí)間
formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"];//零區(qū)時(shí)間
通過下面方法可得到系統(tǒng)支持的時(shí)區(qū)對應(yīng)的字符串常量:
NSArray *zones = [NSTimeZone knownTimeZoneNames];
for (NSString *zone in zones) {
NSLog(@"時(shí)區(qū)名 = %@", zone);
}
字符串時(shí)間轉(zhuǎn)NSDate
字符串時(shí)間轉(zhuǎn)為NSDate時(shí)間也會(huì)有時(shí)區(qū)問題。也會(huì)遇到有所謂的8小時(shí)誤差,其實(shí)就是時(shí)區(qū)不同。比如下面的例子:
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);
打印結(jié)果:
2016-12-07 14:12:17.468 timeTest[34279:3155380] newDate = 2016-12-07 06:06:24 +0000
NSDateFormatter的指定格式是:@"yyyy-MM-dd HH:mm:ss Z"。這里面的Z指的是時(shí)區(qū)。要轉(zhuǎn)化的字符串時(shí)間格式必須和這個(gè)格式匹配,上面給定的字符串時(shí)間是:@"2016-12-07 14:06:24 +0800",是一個(gè)東八區(qū)時(shí)間,轉(zhuǎn)化為NSDate后是零區(qū)時(shí)間2016-12-07 06:06:24 +0000,字面顯示上少了8小時(shí),其實(shí)時(shí)間一樣。
其實(shí)如果上面給定的字符串時(shí)間為@"2016-12-07 14:06:24 +0000",轉(zhuǎn)化出來的NSDate時(shí)間會(huì)完全一樣,因?yàn)樽址畷r(shí)間為零時(shí)區(qū)時(shí)間,不存在時(shí)區(qū)誤差。大家可以試一下。
當(dāng)不指定字符串時(shí)間的時(shí)區(qū)時(shí),即沒有后面的+0800,同時(shí)要把NSDateFormatter時(shí)間格式里的Z去掉,保證格式匹配。系統(tǒng)會(huì)認(rèn)為字符串時(shí)間是系統(tǒng)所在時(shí)區(qū)的時(shí)間,轉(zhuǎn)化為NSDate時(shí)間是零時(shí)區(qū)時(shí)間。
同樣,也可以使用formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"];這種方式指定字符串時(shí)間的時(shí)區(qū),和用Z對應(yīng)+0000是一樣的。
NSDate轉(zhuǎn)當(dāng)前時(shí)區(qū)的NSDate時(shí)間
因?yàn)閇NSDate date]得出的時(shí)間是零時(shí)區(qū)時(shí)間,當(dāng)我們要獲取當(dāng)前所在時(shí)區(qū)的NSDate時(shí)間時(shí),通常會(huì)用以下方法:
NSDate *date = [NSDate date];
NSTimeZone *zone = [NSTimeZone systemTimeZone];
NSInteger interval = [zone secondsFromGMTForDate:date];
NSDate *localDate = [date? dateByAddingTimeInterval:interval];
NSLog(@"localDate = %@",localDate);
打印結(jié)果:
2016-12-07 14:49:03.777 timeTest[34519:3183548] localDate = 2016-12-07 14:49:03 +0000
上面代碼中zone是當(dāng)前時(shí)區(qū),interval是當(dāng)前時(shí)區(qū)和零時(shí)區(qū)時(shí)間的差值,最后結(jié)果localDate是零時(shí)區(qū)時(shí)間date加上這個(gè)差值interval,得到當(dāng)前時(shí)區(qū)的NSDate時(shí)間。更有甚者,在開發(fā)中直接加8*60*60或28800這樣的值,因?yàn)橄嗖?小時(shí)嘛。這樣在東八區(qū)沒問題,在其他時(shí)區(qū)時(shí)間就錯(cuò)了。
其實(shí)這種做法是不科學(xué)的,因?yàn)榈玫降淖罱K時(shí)間還是零時(shí)區(qū)時(shí)間,時(shí)間后面明顯是+0000,在使用中一般不顯示時(shí)區(qū),所以認(rèn)為當(dāng)做當(dāng)前時(shí)區(qū)的時(shí)間使用也未嘗不可。此為大坑!
坑1:這時(shí)如果轉(zhuǎn)為字符串時(shí)間,又會(huì)增加8小時(shí)。因?yàn)樽鰰r(shí)間轉(zhuǎn)換的時(shí)候,系統(tǒng)會(huì)認(rèn)為這個(gè)NSDate是零時(shí)區(qū),得到的字符串時(shí)間是東八區(qū)的。
解決辦法是:將錯(cuò)就錯(cuò),字符串時(shí)間也設(shè)置為零時(shí)區(qū)的字符串時(shí)間。從深坑跌入更深的坑!
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(@"字符串時(shí)間 = %@", dateStr);
這里的@"UTC"是指世界標(biāo)準(zhǔn)時(shí)間,也是現(xiàn)在用的時(shí)間標(biāo)準(zhǔn),東八區(qū)比這個(gè)時(shí)間也是快8小時(shí),這里填@"GMT"也是可以的。
坑2:在與后臺交互時(shí),有時(shí)需要+0000時(shí)區(qū),這時(shí)只能手動(dòng)拼接字符串更改這個(gè)時(shí)區(qū)字段,改為正確的時(shí)區(qū)。
所以,在開發(fā)中盡量不要這么做,當(dāng)時(shí)間要求顯示、存儲(chǔ)或與后臺交互的時(shí)候,使用字符串時(shí)間!不要使用轉(zhuǎn)化的NSDate。
時(shí)間換算,時(shí)間戳的概念
當(dāng)前時(shí)間轉(zhuǎn)時(shí)間戳
時(shí)間戳是指1970年1月1日0時(shí)0分0秒到當(dāng)前時(shí)間的秒數(shù)。注意:這里的當(dāng)前時(shí)間是指零時(shí)區(qū)的NSDate時(shí)間。
NSDate *date = [NSDate date];
NSTimeInterval timeIn = [date timeIntervalSince1970];
NSLog(@"時(shí)間戳 = %.0f", timeIn);
打印結(jié)果:
2016-12-07 15:41:04.000 timeTest[34994:3232390] 時(shí)間戳 = 1481096464
時(shí)間戳轉(zhuǎn)當(dāng)前時(shí)間
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(@"初始化時(shí)間 = %@,時(shí)間戳=%.0f,時(shí)間戳轉(zhuǎn)為NSDate時(shí)間 = %@,轉(zhuǎn)為字符串時(shí)間 = %@", date, timeIn, newDate, newTime);
打印結(jié)果:
2016-12-07 16:11:56.146 timeTest[35186:3253589] 初始化時(shí)間 = 2016-12-07 08:11:56 +0000,時(shí)間戳=1481098316,時(shí)間戳轉(zhuǎn)為NSDate時(shí)間 = 2016-12-07 08:11:56 +0000,轉(zhuǎn)為字符串時(shí)間 = 2016-12-07 16:11:56 +0800
注意時(shí)間戳使用的NSDate時(shí)間是當(dāng)前零時(shí)區(qū)的時(shí)間!當(dāng)前零時(shí)區(qū)時(shí)間!當(dāng)前零時(shí)區(qū)時(shí)間!重要的事情說三遍!不要進(jìn)行NSDate轉(zhuǎn)當(dāng)前時(shí)區(qū)的NSDate時(shí)間,再轉(zhuǎn)時(shí)間戳。下面是驗(yàn)證:
NSDate *date = [NSDate date];
NSLog(@"系統(tǒng)零時(shí)區(qū)NSDate時(shí)間 = %@", date);
NSTimeInterval timeIn = [date timeIntervalSince1970];
NSLog(@"系統(tǒng)零時(shí)區(qū)NSDate時(shí)間轉(zhuǎn)化為時(shí)間戳 = %.0f", timeIn);
NSTimeZone *zone = [NSTimeZone systemTimeZone];
NSInteger interval = [zone secondsFromGMTForDate:date];
NSDate *localDate = [date? dateByAddingTimeInterval:interval];
NSLog(@"轉(zhuǎn)化為本地NSDate時(shí)間 = %@", localDate);
NSTimeInterval timeIn2 = [localDate timeIntervalSince1970];
NSLog(@"本地NSDate時(shí)間轉(zhuǎn)化為時(shí)間戳 = %.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(@"最終轉(zhuǎn)為字符串時(shí)間1 = %@, 時(shí)間2 = %@", newTime, newTime2);
打印結(jié)果:
2016-12-07 16:13:57.834 timeTest[35211:3255842] 系統(tǒng)零時(shí)區(qū)NSDate時(shí)間 = 2016-12-07 08:13:57 +0000
2016-12-07 16:13:57.834 timeTest[35211:3255842] 系統(tǒng)零時(shí)區(qū)NSDate時(shí)間轉(zhuǎn)化為時(shí)間戳 = 1481098438
2016-12-07 16:13:57.835 timeTest[35211:3255842] 轉(zhuǎn)化為本地NSDate時(shí)間 = 2016-12-07 16:13:57 +0000
2016-12-07 16:13:57.835 timeTest[35211:3255842] 本地NSDate時(shí)間轉(zhuǎn)化為時(shí)間戳 = 1481127238
2016-12-07 16:13:57.836 timeTest[35211:3255842] 最終轉(zhuǎn)為字符串時(shí)間1 = 2016-12-07 16:13:57 +0800, 時(shí)間2 = 2016-12-08 00:13:57 +0800
問題解釋詳見上文的NSDate轉(zhuǎn)當(dāng)前時(shí)區(qū)的NSDate時(shí)間。
時(shí)間操作與比較
時(shí)間初始化和比較方法
幾個(gè)時(shí)間初始化方法:
//初始化當(dāng)前時(shí)間,返回零時(shí)區(qū)時(shí)間
NSDate *date = [NSDate date];
//以當(dāng)前時(shí)間為準(zhǔn),正數(shù)超前指定秒數(shù),負(fù)數(shù)延后指定秒數(shù)
NSDate *laterDate = [NSDate dateWithTimeIntervalSinceNow:60];
//以2001-01-01 00:00:00 +0000為基準(zhǔn),正數(shù)超前指定秒數(shù),負(fù)數(shù)延后指定秒數(shù)
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:60];
//以1970-01-01 00:00:00 +0000為基準(zhǔn),正數(shù)超前指定秒數(shù),負(fù)數(shù)延后指定秒數(shù)
NSDate *newDate1 = [NSDate dateWithTimeIntervalSince1970:60];
//實(shí)例方法,以指定時(shí)間為基準(zhǔn),正數(shù)超前指定秒數(shù),負(fù)數(shù)延后指定秒數(shù)
NSDate *newDate2 = [date dateByAddingTimeInterval:60];
//很久以后的某一天
NSDate *newDate3 = [NSDate distantFuture];
//很久以前的某一天
NSDate *newDate4 = [NSDate distantPast];
幾個(gè)時(shí)間比較方法:
//比較兩個(gè)時(shí)間是否相等
- (BOOL)isEqualToDate:(NSDate *)otherDate;
//兩個(gè)時(shí)間比較,返回較早時(shí)間
- (NSDate *)earlierDate:(NSDate *)anotherDate;
//兩個(gè)時(shí)間比較,返回較晚時(shí)間
- (NSDate *)laterDate:(NSDate *)anotherDate;
//兩個(gè)時(shí)間比較,返回枚舉類型
- (NSComparisonResult)compare:(NSDate *)other;
幾個(gè)計(jì)算時(shí)間間隔的方法:
//返回實(shí)例時(shí)間與refDate時(shí)間間隔秒數(shù)
- (NSTimeInterval)timeIntervalSinceDate:(NSDate *)refDate;
//返回實(shí)例時(shí)間與當(dāng)前時(shí)間間隔秒數(shù)
- (NSTimeInterval)timeIntervalSinceNow;
//返回實(shí)例時(shí)間的時(shí)間戳
- (NSTimeInterval)timeIntervalSince1970;
//返回實(shí)例時(shí)間和2001-01-01 00:00:00 +0000的間隔秒數(shù)
- (NSTimeInterval)timeIntervalSinceReferenceDate;
//返回當(dāng)前時(shí)間和2001-01-01 00:00:00 +0000的間隔秒數(shù)
+ (NSTimeInterval)timeIntervalSinceReferenceDate;
獲取年月日時(shí)分秒周時(shí)區(qū)
OC里的時(shí)間坑太多,根本沒辦法像其他語言那樣直接time.year就能獲取年份。要想獲取NSDate的年月日需要使用日歷對象NSCalendar。
NSDate *date = [NSDate date];
NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents *dateComps = [cal components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond|NSCalendarUnitWeekday|NSCalendarUnitWeekOfMonth|NSCalendarUnitWeekOfYear|NSCalendarUnitTimeZone fromDate:date];
NSLog(@"時(shí)間 = %@", date);
NSLog(@"年=%ld,月=%ld,日=%ld,時(shí)=%ld,分=%ld,秒=%ld,周=%ld,本月第%ld周,本年第%ld周,時(shí)區(qū)=%@", dateComps.year, dateComps.month, dateComps.day, dateComps.hour, dateComps.minute, dateComps.second, dateComps.weekday, dateComps.weekOfMonth, dateComps.weekOfYear, dateComps.timeZone.name);
打印結(jié)果:
2016-12-07 17:20:41.639 timeTest[35734:3311752] 時(shí)間 = 2016-12-07 09:20:41 +0000
2016-12-07 17:20:41.640 timeTest[35734:3311752] 年=2016,月=12,日=7,時(shí)=17,分=20,秒=41,周=4,本月第2周,本年第50周,時(shí)區(qū)=Asia/Shanghai
NSDateComponents創(chuàng)建方法中添加的枚舉NSCalendarUnit,是后面要獲取的年月日時(shí)分秒必須對應(yīng)添加的。比如要獲取年dateComps.year,就需要添加枚舉NSCalendarUnitYear。
可以看到,[NSDate date]時(shí)間可以使用NSCalendar直接獲取當(dāng)前時(shí)區(qū)的時(shí)分秒,打印的時(shí)和時(shí)區(qū)即可看出。這是[NSCalendar currentCalendar]日歷對象初始化的原因,也可以用[[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]指定Identifier的方式初始化陽歷日歷。可以試試指定Identifier為NSCalendarIdentifierChinese,打印的是中國農(nóng)歷。
dateComps.weekOfMonth是今天屬于本月的第幾周。
dateComps.weekOfYear是今天屬于本年的第幾周。
dateComps.weekday是星期,這個(gè)和日常使用有些不同。上述程序打印的是周=4,但2016-12-07是周三。這里weekday的對應(yīng)關(guān)系是:周日-1,周一-2,周二-3,周三-4,周四-5,周五-6,周六-7。畢竟國外慣例周日是每周的第一天。