參考鏈接:https://blog.csdn.net/sqc3375177/article/details/53608867
在iOS開發(fā)中盈滴,經常會遇到各種各樣的時間問題,8小時時差轿钠,時間戳巢钓,求時間間隔,農歷等等疗垛。解決辦法網(wǎng)上比比皆是症汹,但大多零零散散,很多資料并沒有說明其中問題贷腕。這里集中總結一下背镇,以便于以后查閱和供大家參考。有我自己的理解泽裳,錯漏之處請大家吐槽瞒斩。
NSDate的8小時問題
NSDate轉字符串時間
初始化一個NSDate時間[NSDate date],獲取的是零時區(qū)的時間(格林尼治的時間: 年-月-日 時:分:秒: +時區(qū))涮总,而北京時間是東八區(qū)時間胸囱,因為時區(qū)不同,所以打印的時間相差了8小時瀑梗。此刻表示的時間是一樣的烹笔。
NSDate?*date?=?[NSDate?date];
NSLog(@"date時間?=?%@",?date);
NSDateFormatter?*formatter?=?[[NSDateFormatter?alloc]?init];
[formatter?setDateFormat:@"yyyy-MM-dd?HH:mm:ss?Z"];
NSString?*dateStr?=?[formatter?stringFromDate:date];
NSLog(@"字符串時間?=?%@",?dateStr);
打印結果:
2016-12-07?10:44:24.470?timeTest[32743:2995134]?date時間?=?2016-12-07?02:44:24?+0000
2016-12-07?10:44:24.471?timeTest[32743:2995134]?字符串時間?=?2016-12-07?10:44:24?+0800
打印結果前面的時間是北京時間:2016-12-07 10:44:24.470。而date打印出來的時間顯示少了8小時抛丽,因為它表示的是零時區(qū)(+0000)時間02:44:24谤职。此刻對應東八區(qū)的北京時間就是10:44:24。只是時區(qū)不同亿鲜,表示的時間點是一樣的允蜈。好比1公斤和2斤,重量是一樣的蒿柳。[NSDate date]獲取的時間單位是零時區(qū)(+0000)饶套,我們所要的北京時間的單位是東八區(qū)(+0800)。
系統(tǒng)會默認[NSDate date]獲取的時間為零時區(qū)時間其馏,而經過NSDateFormatter轉化為字符串時間就是當前所在時區(qū)的準確時間凤跑,并沒有8小時誤差。
轉字符串時間的時區(qū)設定
上文中NSDate時間轉為字符串時間并沒有設置NSDateFormatter的timeZone叛复。不設置會默認使用當前所在的時區(qū),與設置系統(tǒng)時區(qū)formatter.timeZone = [NSTimeZone systemTimeZone]的效果是一樣的。
也可以設置時區(qū)褐奥,獲取指定時區(qū)的字符串時間
NSDate?*date?=?[NSDate?date];
NSDateFormatter?*formatter?=?[[NSDateFormatter?alloc]?init];
[formatter?setDateFormat:@"yyyy-MM-dd?HH:mm:ss"];
formatter.timeZone?=?[NSTimeZone?timeZoneWithName:@"Asia/Shanghai"];//東八區(qū)時間
NSString?*dateStr?=?[formatter?stringFromDate:date];
NSLog(@"字符串時間?=?%@",?dateStr);
這時獲取的時間就是東八區(qū)時間咖耘,哪怕手機拿到零時區(qū)的格林尼治,獲取的也是東八區(qū)的時間撬码,因為這里指定時區(qū)了儿倒。也有如下時區(qū)指定:
formatter.timeZone?=?[NSTimeZone?timeZoneWithName:@"Asia/Tokyo"];//東九區(qū)時間
formatter.timeZone?=?[NSTimeZone?timeZoneWithName:@"GMT"];//零區(qū)時間
通過下面方法可得到系統(tǒng)支持的時區(qū)對應的字符串常量:
NSArray?*zones?=?[NSTimeZone?knownTimeZoneNames];
for?(NSString?*zone?in?zones)?{
???NSLog(@"時區(qū)名?=?%@",?zone);
}
時區(qū)對照表
字符串時間轉NSDate
字符串時間轉為NSDate時間也會有時區(qū)問題。也會遇到有所謂的8小時誤差呜笑,其實就是時區(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);
打印結果:
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指的是時區(qū)叫胁。要轉化的字符串時間格式必須和這個格式匹配凰慈,上面給定的字符串時間是:@"2016-12-07 14:06:24 +0800",是一個東八區(qū)時間驼鹅,轉化為NSDate后是零區(qū)時間2016-12-07 06:06:24 +0000微谓,字面顯示上少了8小時,其實時間一樣输钩。
其實如果上面給定的字符串時間為@"2016-12-07 14:06:24 +0000"豺型,轉化出來的NSDate時間會完全一樣,因為字符串時間為零時區(qū)時間买乃,不存在時區(qū)誤差姻氨。大家可以試一下。
當不指定字符串時間的時區(qū)時剪验,即沒有后面的+0800哼绑,同時要把NSDateFormatter時間格式里的Z去掉,保證格式匹配碉咆。系統(tǒng)會認為字符串時間是系統(tǒng)所在時區(qū)的時間抖韩,轉化為NSDate時間是零時區(qū)時間。
同樣疫铜,也可以使用formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"];這種方式指定字符串時間的時區(qū)茂浮,和用Z對應+0000是一樣的。
NSDate轉當前時區(qū)的NSDate時間
因為[NSDate date]得出的時間是零時區(qū)時間壳咕,當我們要獲取當前所在時區(qū)的NSDate時間時席揽,通常會用以下方法:
NSDate?*date?=?[NSDate?date];
NSTimeZone?*zone?=?[NSTimeZone?systemTimeZone];
NSInteger?interval?=?[zone?secondsFromGMTForDate:date];
NSDate?*localDate?=?[date??dateByAddingTimeInterval:interval];
NSLog(@"localDate?=?%@",localDate);
打印結果:
2016-12-07?14:49:03.777?timeTest[34519:3183548]?localDate?=?2016-12-07?14:49:03?+0000
上面代碼中zone是當前時區(qū),interval是當前時區(qū)和零時區(qū)時間的差值谓厘,最后結果localDate是零時區(qū)時間date加上這個差值interval幌羞,得到當前時區(qū)的NSDate時間。更有甚者竟稳,在開發(fā)中直接加8*60*60或28800這樣的值属桦,因為相差8小時嘛熊痴。這樣在東八區(qū)沒問題,在其他時區(qū)時間就錯了聂宾。
其實這種做法是不科學的果善,因為得到的最終時間還是零時區(qū)時間,時間后面明顯是+0000系谐,在使用中一般不顯示時區(qū)巾陕,所以認為當做當前時區(qū)的時間使用也未嘗不可。此為大坑纪他!
坑1:這時如果轉為字符串時間鄙煤,又會增加8小時。因為做時間轉換的時候茶袒,系統(tǒng)會認為這個NSDate是零時區(qū)梯刚,得到的字符串時間是東八區(qū)的。
解決辦法是:將錯就錯弹谁,字符串時間也設置為零時區(qū)的字符串時間乾巧。從深坑跌入更深的坑!
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(@"字符串時間?=?%@",?dateStr);
這里的@"UTC"是指世界標準時間预愤,也是現(xiàn)在用的時間標準沟于,東八區(qū)比這個時間也是快8小時,這里填@"GMT"也是可以的植康。
坑2:在與后臺交互時旷太,有時需要+0000時區(qū),這時只能手動拼接字符串更改這個時區(qū)字段销睁,改為正確的時區(qū)供璧。
所以,在開發(fā)中盡量不要這么做冻记,當時間要求顯示睡毒、存儲或與后臺交互的時候,使用字符串時間冗栗!不要使用轉化的NSDate演顾。
時間換算,時間戳的概念
當前時間轉時間戳
時間戳是指1970年1月1日0時0分0秒到當前時間的秒數(shù)隅居。注意:這里的當前時間是指零時區(qū)的NSDate時間钠至。
NSDate?*date?=?[NSDate?date];
NSTimeInterval?timeIn?=?[date?timeIntervalSince1970];
NSLog(@"時間戳?=?%.0f",?timeIn);
打印結果:
2016-12-07 15:41:04.000 timeTest[34994:3232390] 時間戳 = 1481096464
時間戳轉當前時間
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(@"初始化時間?=?%@,時間戳=%.0f胎源,時間戳轉為NSDate時間?=?%@棉钧,轉為字符串時間?=?%@",?date,?timeIn,?newDate,?newTime);
打印結果:
2016-12-07?16:11:56.146?timeTest[35186:3253589]?初始化時間?=?2016-12-07?08:11:56?+0000,時間戳=1481098316涕蚤,時間戳轉為NSDate時間?=?2016-12-07?08:11:56?+0000宪卿,轉為字符串時間?=?2016-12-07?16:11:56?+0800
注意時間戳使用的NSDate時間是當前零時區(qū)的時間的诵!當前零時區(qū)時間!當前零時區(qū)時間愧捕!重要的事情說三遍奢驯!不要進行NSDate轉當前時區(qū)的NSDate時間申钩,再轉時間戳次绘。下面是驗證:
NSDate?*date?=?[NSDate?date];
NSLog(@"系統(tǒng)零時區(qū)NSDate時間?=?%@",?date);????
NSTimeInterval?timeIn?=?[date?timeIntervalSince1970];
NSLog(@"系統(tǒng)零時區(qū)NSDate時間轉化為時間戳?=?%.0f",?timeIn);
NSTimeZone?*zone?=?[NSTimeZone?systemTimeZone];
NSInteger?interval?=?[zone?secondsFromGMTForDate:date];
NSDate?*localDate?=?[date??dateByAddingTimeInterval:interval];
NSLog(@"轉化為本地NSDate時間?=?%@",?localDate);
NSTimeInterval?timeIn2?=?[localDate?timeIntervalSince1970];
NSLog(@"本地NSDate時間轉化為時間戳?=?%.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(@"最終轉為字符串時間1?=?%@,?時間2?=?%@",?newTime,?newTime2);
打印結果:
2016-12-07?16:13:57.834?timeTest[35211:3255842]?系統(tǒng)零時區(qū)NSDate時間?=?2016-12-07?08:13:57?+0000
2016-12-07?16:13:57.834?timeTest[35211:3255842]?系統(tǒng)零時區(qū)NSDate時間轉化為時間戳?=?1481098438
2016-12-07?16:13:57.835?timeTest[35211:3255842]?轉化為本地NSDate時間?=?2016-12-07?16:13:57?+0000
2016-12-07?16:13:57.835?timeTest[35211:3255842]?本地NSDate時間轉化為時間戳?=?1481127238
2016-12-07?16:13:57.836?timeTest[35211:3255842]?最終轉為字符串時間1?=?2016-12-07?16:13:57?+0800撒遣,?時間2?=?2016-12-08?00:13:57?+0800
問題解釋詳見上文的NSDate轉當前時區(qū)的NSDate時間邮偎。
時間操作與比較
時間初始化和比較方法
幾個時間初始化方法:
//初始化當前時間,返回零時區(qū)時間
NSDate?*date?=?[NSDate?date];
//以當前時間為準义黎,正數(shù)超前指定秒數(shù)禾进,負數(shù)延后指定秒數(shù)
NSDate?*laterDate?=?[NSDate?dateWithTimeIntervalSinceNow:60];
//以2001-01-01?00:00:00?+0000為基準,正數(shù)超前指定秒數(shù)廉涕,負數(shù)延后指定秒數(shù)
NSDate?*newDate?=?[NSDate?dateWithTimeIntervalSinceReferenceDate:60];
//以1970-01-01?00:00:00?+0000為基準泻云,正數(shù)超前指定秒數(shù),負數(shù)延后指定秒數(shù)
NSDate?*newDate1?=?[NSDate?dateWithTimeIntervalSince1970:60];
//實例方法狐蜕,以指定時間為基準宠纯,正數(shù)超前指定秒數(shù),負數(shù)延后指定秒數(shù)
NSDate?*newDate2?=?[date?dateByAddingTimeInterval:60];
//很久以后的某一天
NSDate?*newDate3?=?[NSDate?distantFuture];
//很久以前的某一天
NSDate?*newDate4?=?[NSDate?distantPast];
幾個時間比較方法:
//比較兩個時間是否相等
-?(BOOL)isEqualToDate:(NSDate?*)otherDate;
//兩個時間比較层释,返回較早時間
-?(NSDate?*)earlierDate:(NSDate?*)anotherDate;
//兩個時間比較婆瓜,返回較晚時間
-?(NSDate?*)laterDate:(NSDate?*)anotherDate;
//兩個時間比較,返回枚舉類型
-?(NSComparisonResult)compare:(NSDate?*)other;
幾個計算時間間隔的方法:
//返回實例時間與refDate時間間隔秒數(shù)
-?(NSTimeInterval)timeIntervalSinceDate:(NSDate?*)refDate;
//返回實例時間與當前時間間隔秒數(shù)
-?(NSTimeInterval)timeIntervalSinceNow;
//返回實例時間的時間戳
-?(NSTimeInterval)timeIntervalSince1970;
//返回實例時間和2001-01-01?00:00:00?+0000的間隔秒數(shù)
-?(NSTimeInterval)timeIntervalSinceReferenceDate;
//返回當前時間和2001-01-01?00:00:00?+0000的間隔秒數(shù)
+?(NSTimeInterval)timeIntervalSinceReferenceDate;
獲取年月日時分秒周時區(qū)
OC里的時間坑太多贡羔,根本沒辦法像其他語言那樣直接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(@"時間?=?%@",?date);
NSLog(@"年=%ld,月=%ld,日=%ld,時=%ld,分=%ld,秒=%ld,周=%ld,本月第%ld周,本年第%ld周,時區(qū)=%@",?dateComps.year,?dateComps.month,?dateComps.day,?dateComps.hour,?dateComps.minute,?dateComps.second,?dateComps.weekday,?dateComps.weekOfMonth,?dateComps.weekOfYear,?dateComps.timeZone.name);
打印結果:
2016-12-07?17:20:41.639?timeTest[35734:3311752]?時間?=?2016-12-07?09:20:41?+0000
2016-12-07?17:20:41.640?timeTest[35734:3311752]?年=2016,月=12,日=7,時=17,分=20,秒=41,周=4,本月第2周,本年第50周,時區(qū)=Asia/Shanghai
NSDateComponents創(chuàng)建方法中添加的枚舉NSCalendarUnit乖寒,是后面要獲取的年月日時分秒必須對應添加的猴蹂。比如要獲取年dateComps.year,就需要添加枚舉NSCalendarUnitYear楣嘁。
可以看到磅轻,[NSDate date]時間可以使用NSCalendar直接獲取當前時區(qū)的時分秒,打印的時和時區(qū)即可看出马澈。這是[NSCalendar currentCalendar]日歷對象初始化的原因瓢省,也可以用[[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]指定Identifier的方式初始化陽歷日歷∪啵可以試試指定Identifier為NSCalendarIdentifierChinese勤婚,打印的是中國農歷。
dateComps.weekOfMonth是今天屬于本月的第幾周涤伐。
dateComps.weekOfYear是今天屬于本年的第幾周馒胆。
dateComps.weekday是星期缨称,這個和日常使用有些不同。上述程序打印的是周=4祝迂,但2016-12-07是周三睦尽。這里weekday的對應關系是:周日-1,周一-2型雳,周二-3当凡,周三-4,周四-5纠俭,周五-6沿量,周六-7。畢竟國外慣例周日是每周的第一天冤荆。
農歷
獲取農歷的工具方法朴则,可根據(jù)需求添加農歷節(jié)日和二十四節(jié)氣
+?(NSString?*)LunarForSolarYear:(int)wCurYear?Month:(int)wCurMonth?Day:(int)wCurDay
{
????//農歷日期名
????NSArray?*cDayName?=??[NSArray?arrayWithObjects:@"*",@"初一",@"初二",@"初三",@"初四",@"初五",@"初六",@"初七",@"初八",@"初九",@"初十",@"十一",@"十二",@"十三",@"十四",@"十五",@"十六",@"十七",@"十八",@"十九",@"二十",@"廿一",@"廿二",@"廿三",@"廿四",@"廿五",@"廿六",@"廿七",@"廿八",@"廿九",@"三十",nil];
????//農歷月份名
????NSArray?*cMonName?=??[NSArray?arrayWithObjects:@"*",@"正月",@"二月",@"三月",@"四月",@"五月",@"六月",@"七月",@"八月",@"九月",@"十月",@"冬月",@"臘月",nil];
????//公歷每月前面的天數(shù)
????const?int?wMonthAdd[12]?=?{0,31,59,90,120,151,181,212,243,273,304,334};
????//農歷數(shù)據(jù)
????const?int?wNongliData[100]?=?{2635,333387,1701,1748,267701,694,2391,133423,1175,396438
????????,3402,3749,331177,1453,694,201326,2350,465197,3221,3402
????????,400202,2901,1386,267611,605,2349,137515,2709,464533,1738
????????,2901,330421,1242,2651,199255,1323,529706,3733,1706,398762
????????,2741,1206,267438,2647,1318,204070,3477,461653,1386,2413
????????,330077,1197,2637,268877,3365,531109,2900,2922,398042,2395
????????,1179,267415,2635,661067,1701,1748,398772,2742,2391,330031
????????,1175,1611,200010,3749,527717,1452,2742,332397,2350,3222
????????,268949,3402,3493,133973,1386,464219,605,2349,334123,2709
????????,2890,267946,2773,592565,1210,2651,395863,1323,2707,265877};
????static?int?nTheDate,nIsEnd,m,k,n,i,nBit;
????//計算到初始時間1921年2月8日的天數(shù):1921-2-8(正月初一)
????nTheDate?=?(wCurYear?-?1921)?*?365?+?(wCurYear?-?1921)?/?4?+?wCurDay?+?wMonthAdd[wCurMonth?-?1]?-?38;
????if((!(wCurYear?%?4))?&&?(wCurMonth?>?2))
????????nTheDate?=?nTheDate?+?1;
????//計算農歷天干、地支钓简、月乌妒、日
????nIsEnd?=?0;
????m?=?0;
????while(nIsEnd?!=?1)?{
????????if(wNongliData[m]?<?4095)
????????????k?=?11;
????????else
????????????k?=?12;
????????n?=?k;
????????while(n>=0)?{
????????????//獲取wNongliData(m)的第n個二進制位的值