iOS中的時間和日期問題

iOS開發中,經常會遇到各種各樣的時間問題,8小時時差,時間戳,求時間間隔,農歷等等。解決辦法網上比比皆是,但大多零零散散,很多資料并沒有說明其中問題。這里集中總結一下,以便于以后查閱和供大家參考。有我自己的理解,錯漏之處請大家吐槽。

NSDate的8小時問題

NSDate轉字符串時間

初始化一個NSDate時間[NSDate date],獲取的是零時區的時間(格林尼治的時間: 年-月-日 時:分:秒: +時區),而北京時間是東八區時間,因為時區不同,所以打印的時間相差了8小時。此刻表示的時間是一樣的。

7NSDate?*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);

打印結果:

22016-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小時,因為它表示的是零時區(+0000)時間02:44:24。此刻對應東八區的北京時間就是10:44:24。只是時區不同,表示的時間點是一樣的。好比1公斤和2斤,重量是一樣的。[NSDate date]獲取的時間單位是零時區(+0000),我們所要的北京時間的單位是東八區(+0800)。

系統會默認[NSDate date]獲取的時間為零時區時間,而經過NSDateFormatter轉化為字符串時間就是當前所在時區的準確時間,并沒有8小時誤差。

轉字符串時間的時區設定

上文中NSDate時間轉為字符串時間并沒有設置NSDateFormatter的timeZone。不設置會默認使用當前所在的時區,與設置系統時區formatter.timeZone = [NSTimeZone systemTimeZone]的效果是一樣的。

也可以設置時區,獲取指定時區的字符串時間

6NSDate?*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);

這時獲取的時間就是東八區時間,哪怕手機拿到零時區的格林尼治,獲取的也是東八區的時間,因為這里指定時區了。也有如下時區指定:

1

2formatter.timeZone?=?[NSTimeZone?timeZoneWithName:@"Asia/Tokyo"];//東九區時間

formatter.timeZone?=?[NSTimeZone?timeZoneWithName:@"GMT"];//零區時間

通過下面方法可得到系統支持的時區對應的字符串常量:

4NSArray?*zones?=?[NSTimeZone?knownTimeZoneNames];

for(NSString?*zoneinzones)?{

NSLog(@"時區名?=?%@",?zone);

}

時區對照表

字符串時間轉NSDate

字符串時間轉為NSDate時間也會有時區問題。也會遇到有所謂的8小時誤差,其實就是時區不同。比如下面的例子:

4NSDateFormatter?*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指的是時區。要轉化的字符串時間格式必須和這個格式匹配,上面給定的字符串時間是:@"2016-12-07 14:06:24 +0800",是一個東八區時間,轉化為NSDate后是零區時間2016-12-07 06:06:24 +0000,字面顯示上少了8小時,其實時間一樣。

其實如果上面給定的字符串時間為@"2016-12-07 14:06:24 +0000",轉化出來的NSDate時間會完全一樣,因為字符串時間為零時區時間,不存在時區誤差。大家可以試一下。

當不指定字符串時間的時區時,即沒有后面的+0800,同時要把NSDateFormatter時間格式里的Z去掉,保證格式匹配。系統會認為字符串時間是系統所在時區的時間,轉化為NSDate時間是零時區時間。

同樣,也可以使用formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"];這種方式指定字符串時間的時區,和用Z對應+0000是一樣的。

NSDate轉當前時區的NSDate時間

因為[NSDate date]得出的時間是零時區時間,當我們要獲取當前所在時區的NSDate時間時,通常會用以下方法:

5NSDate?*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是當前時區,interval是當前時區和零時區時間的差值,最后結果localDate是零時區時間date加上這個差值interval,得到當前時區的NSDate時間。更有甚者,在開發中直接加8*60*60或28800這樣的值,因為相差8小時嘛。這樣在東八區沒問題,在其他時區時間就錯了。

其實這種做法是不科學的,因為得到的最終時間還是零時區時間,時間后面明顯是+0000,在使用中一般不顯示時區,所以認為當做當前時區的時間使用也未嘗不可。此為大坑!

坑1:這時如果轉為字符串時間,又會增加8小時。因為做時間轉換的時候,系統會認為這個NSDate是零時區,得到的字符串時間是東八區的。

解決辦法是:將錯就錯,字符串時間也設置為零時區的字符串時間。從深坑跌入更深的坑!

10NSDate?*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"是指世界標準時間,也是現在用的時間標準,東八區比這個時間也是快8小時,這里填@"GMT"也是可以的。

坑2:在與后臺交互時,有時需要+0000時區,這時只能手動拼接字符串更改這個時區字段,改為正確的時區。

所以,在開發中盡量不要這么做,當時間要求顯示、存儲或與后臺交互的時候,使用字符串時間!不要使用轉化的NSDate。

時間換算,時間戳的概念

當前時間轉時間戳

時間戳是指1970年1月1日0時0分0秒到當前時間的秒數。注意:這里的當前時間是指零時區的NSDate時間。

2NSDate?*date?=?[NSDate?date];

NSTimeInterval?timeIn?=?[date?timeIntervalSince1970];

NSLog(@"時間戳?=?%.0f",?timeIn);

打印結果:

2016-12-07 15:41:04.000 timeTest[34994:3232390] 時間戳 = 1481096464

時間戳轉當前時間

7NSDate?*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時間是當前零時區的時間!當前零時區時間!當前零時區時間!重要的事情說三遍!不要進行NSDate轉當前時區的NSDate時間,再轉時間戳。下面是驗證:

17NSDate?*date?=?[NSDate?date];

NSLog(@"系統零時區NSDate時間?=?%@",?date);

NSTimeInterval?timeIn?=?[date?timeIntervalSince1970];

NSLog(@"系統零時區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);

打印結果:

52016-12-07?16:13:57.834?timeTest[35211:3255842]?系統零時區NSDate時間?=?2016-12-07?08:13:57?+0000

2016-12-07?16:13:57.834?timeTest[35211:3255842]?系統零時區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轉當前時區的NSDate時間。

時間操作與比較

時間初始化和比較方法

幾個時間初始化方法:

14//初始化當前時間,返回零時區時間

NSDate?*date?=?[NSDate?date];

//以當前時間為準,正數超前指定秒數,負數延后指定秒數

NSDate?*laterDate?=?[NSDate?dateWithTimeIntervalSinceNow:60];

//以2001-01-01?00:00:00?+0000為基準,正數超前指定秒數,負數延后指定秒數

NSDate?*newDate?=?[NSDate?dateWithTimeIntervalSinceReferenceDate:60];

//以1970-01-01?00:00:00?+0000為基準,正數超前指定秒數,負數延后指定秒數

NSDate?*newDate1?=?[NSDate?dateWithTimeIntervalSince1970:60];

//實例方法,以指定時間為基準,正數超前指定秒數,負數延后指定秒數

NSDate?*newDate2?=?[date?dateByAddingTimeInterval:60];

//很久以后的某一天

NSDate?*newDate3?=?[NSDate?distantFuture];

//很久以前的某一天

NSDate?*newDate4?=?[NSDate?distantPast];

幾個時間比較方法:

8//比較兩個時間是否相等

-?(BOOL)isEqualToDate:(NSDate?*)otherDate;

//兩個時間比較,返回較早時間

-?(NSDate?*)earlierDate:(NSDate?*)anotherDate;

//兩個時間比較,返回較晚時間

-?(NSDate?*)laterDate:(NSDate?*)anotherDate;

//兩個時間比較,返回枚舉類型

-?(NSComparisonResult)compare:(NSDate?*)other;

幾個計算時間間隔的方法:

10//返回實例時間與refDate時間間隔秒數

-?(NSTimeInterval)timeIntervalSinceDate:(NSDate?*)refDate;

//返回實例時間與當前時間間隔秒數

-?(NSTimeInterval)timeIntervalSinceNow;

//返回實例時間的時間戳

-?(NSTimeInterval)timeIntervalSince1970;

//返回實例時間和2001-01-01?00:00:00?+0000的間隔秒數

-?(NSTimeInterval)timeIntervalSinceReferenceDate;

//返回當前時間和2001-01-01?00:00:00?+0000的間隔秒數

+?(NSTimeInterval)timeIntervalSinceReferenceDate;

獲取年月日時分秒周時區

OC里的時間坑太多,根本沒辦法像其他語言那樣直接time.year就能獲取年份。要想獲取NSDate的年月日需要使用日歷對象NSCalendar。

5NSDate?*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周,時區=%@",?dateComps.year,?dateComps.month,?dateComps.day,?dateComps.hour,?dateComps.minute,?dateComps.second,?dateComps.weekday,?dateComps.weekOfMonth,?dateComps.weekOfYear,?dateComps.timeZone.name);

打印結果:

1

22016-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周,時區=Asia/Shanghai

NSDateComponents創建方法中添加的枚舉NSCalendarUnit,是后面要獲取的年月日時分秒必須對應添加的。比如要獲取年dateComps.year,就需要添加枚舉NSCalendarUnitYear。

可以看到,[NSDate date]時間可以使用NSCalendar直接獲取當前時區的時分秒,打印的時和時區即可看出。這是[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。畢竟國外慣例周日是每周的第一天。

農歷

獲取農歷的工具方法,可根據需求添加農歷節日和二十四節氣


28+?(NSString?*)LunarForSolarYear:(int)wCurYear?Month:(int)wCurMonth?Day:(int)wCurDay

{

//農歷日期名

NSArray?*cDayName?=??[NSArray?arrayWithObjects:@"*",@"初一",@"初二",@"初三",@"初四",@"初五",@"初六",@"初七",@"初八",@"初九",@"初十",@"十一",@"十二",@"十三",@"十四",@"十五",@"十六",@"十七",@"十八",@"十九",@"二十",@"廿一",@"廿二",@"廿三",@"廿四",@"廿五",@"廿六",@"廿七",@"廿八",@"廿九",@"三十",nil];

//農歷月份名

NSArray?*cMonName?=??[NSArray?arrayWithObjects:@"*",@"正月",@"二月",@"三月",@"四月",@"五月",@"六月",@"七月",@"八月",@"九月",@"十月",@"冬月",@"臘月",nil];

//公歷每月前面的天數

const?int?wMonthAdd[12]?=?{0,31,59,90,120,151,181,212,243,273,304,334};

//農歷數據

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日的天數: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個二進制位的值

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容