iOS時(shí)間問題

在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。畢竟國外慣例周日是每周的第一天。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • iOS開發(fā)中,經(jīng)常會(huì)遇到各種各樣的時(shí)間問題,8小時(shí)時(shí)差,時(shí)間戳,求時(shí)間間隔,農(nóng)歷等等。解決辦法網(wǎng)上比比皆是,但大多...
    小李龍彪閱讀 6,747評論 1 6
  • 主要有以下幾個(gè)類: NSDate:表示一個(gè)具體的絕對的時(shí)間點(diǎn)。NSTimeZone:表示時(shí)區(qū)信息。NSLocale...
    獻(xiàn)國閱讀 5,436評論 0 4
  • 每天晚飯都是很苦惱的問題。不知道吃什么,在哪里吃?一個(gè)人的時(shí)候就想隨便解決反正不餓,但是兩個(gè)人就不能只考慮自己。 ...
    夢之初夏閱讀 328評論 0 0
  • 基礎(chǔ)組件 組件是視圖層的基本組成單元。 組件自帶一些功能與微信風(fēng)格的樣式。 一個(gè)組件通常包括開始標(biāo)簽和結(jié)束標(biāo)簽,屬...
    李瀟南閱讀 363評論 0 0

友情鏈接更多精彩內(nèi)容