日期/時(shí)間在開(kāi)發(fā)中經(jīng)常使用, 但涉及到的無(wú)非是時(shí)間轉(zhuǎn)字符串顯示出來(lái), 或者根據(jù)字符串獲取時(shí)間對(duì)象, 其他的涉及很少. 花了點(diǎn)時(shí)間看了一些相關(guān)的資料, 發(fā)現(xiàn)相關(guān)的東西還真不少.
一. 時(shí)區(qū)--NSTimeZone
和時(shí)間相關(guān)的最重要的一個(gè)因素, 因?yàn)楦鱾€(gè)地區(qū)的時(shí)區(qū)都不一樣, 所以時(shí)間的差別是很大的, 這就是所謂的時(shí)差. 任何時(shí)區(qū)都以GMT為準(zhǔn):
- GMT 0:00 格林威治標(biāo)準(zhǔn)時(shí)間
- UTC +00:00 校準(zhǔn)的全球時(shí)間
- CCD +08:00 中國(guó)標(biāo)準(zhǔn)時(shí)間
而任何NSTimeZone對(duì)象所代表的時(shí)區(qū)都是相對(duì)于GMT的, iOS中的時(shí)間類(lèi)NSDate所獲取到的時(shí)間, 都是相對(duì)于GMT的.
iOS 中的時(shí)區(qū)表示方法:GMT+0800 GMT-0800。(+:東區(qū) -:西區(qū) 08:小時(shí)數(shù) 00:分鐘數(shù))。
GMT+0830 就是指比GMT早8小時(shí)外加30分鐘的時(shí)區(qū)
+ (NSArray *)knownTimeZoneNames;
獲取已知的時(shí)區(qū), 中國(guó)相關(guān)有:
- Asia/Hong_Kong
- Asia/Shanghai
- Asia/Harbin
+ (NSDictionary<NSString *, NSString *> *)abbreviationDictionary
時(shí)區(qū)縮寫(xiě), 例如:
- HKT = "Asia/Hong_Kong"
- EST = "America/New_York"
獲取NSTimeZone 實(shí)例對(duì)象:
獲取當(dāng)前系統(tǒng)時(shí)區(qū):
+ (nullable instancetype) systemTimeZone;
通過(guò)名稱(chēng)獲得, 具體名稱(chēng)可通過(guò)knownTimeZoneNames得知
+ (nullable instancetype)timeZoneWithName:(NSString *)tzName;
例如:
NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"Asia/Shanghai"];
NSLog(@"%@", timeZone);
輸出:
NSDate[8744:272812] Asia/Shanghai (GMT+8) offset 28800
其中28800就是相對(duì)GMT的的偏移量, 單位秒, 即8個(gè)小時(shí)(86060 = 28800)
通過(guò)縮寫(xiě)獲取, 其縮寫(xiě)可通過(guò)abbreviationDictionary得知:
+ (nullable instancetype)timeZoneWithAbbreviation:(NSString *)abbreviation;
例如:
NSTimeZone *timeZone = [NSTimeZone timeZoneWithAbbreviation:@"HKT"];
NSLog(@"%@", timeZone);
輸出結(jié)果和上面一致:
NSDate[8870:278174] Asia/Hong_Kong (GMT+8) offset 28800
另外, 此方法還可以使用GMT+0800 的縮寫(xiě)獲取;
例如:
NSTimeZone *timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT+0800"];
NSLog(@"%@", timeZone);
輸出:
NSDate[8912:280370] GMT+0800 (GMT+8) offset 28800
除了沒(méi)有名稱(chēng), 其他的和上面一樣, 這里都是北京時(shí)間的時(shí)區(qū), 即: 東八區(qū);
通過(guò)相對(duì)于GMT的時(shí)間偏移量(時(shí)差)來(lái)獲取時(shí)區(qū), 注意這里的單位是秒:
+ (instancetype)timeZoneForSecondsFromGMT:(NSInteger)seconds;
例如:
NSTimeZone *timeZone = [NSTimeZone timeZoneForSecondsFromGMT:8*60*60];
NSLog(@"%@", timeZone);
這里獲取的結(jié)果和上面的一致;
另外, 一般應(yīng)用程序的默認(rèn)時(shí)區(qū), 是和手機(jī)系統(tǒng)設(shè)置的時(shí)區(qū)一致的, 我們可通過(guò)下面的方法 setDefaultTimeZone, 來(lái)設(shè)置應(yīng)用程序的默認(rèn)時(shí)區(qū);
注意: 這只能影響本應(yīng)用程序的默認(rèn)時(shí)區(qū), 不會(huì)影響手機(jī)系統(tǒng)和其他應(yīng)用程序的時(shí)區(qū);
[NSTimeZone setDefaultTimeZone:[NSTimeZone timeZoneWithName:@"America/New_York"]];
二. 日期時(shí)間--NSDate
一個(gè)NSDate對(duì)象, 代表一個(gè)具體的時(shí)間點(diǎn), 這個(gè)時(shí)間點(diǎn)不是絕對(duì)的, 是相對(duì)的; 依賴(lài)于當(dāng)前的時(shí)區(qū), 會(huì)隨著時(shí)區(qū)的變化而變化; NSDate默認(rèn)相對(duì)的時(shí)區(qū)是GMT, 即: 格林威治標(biāo)準(zhǔn)時(shí)間;
獲取日期實(shí)例的類(lèi)方法主要有:
// 獲取當(dāng)前時(shí)間
+ (instancetype)date;
// 獲取距離當(dāng)前時(shí)間的為secs秒的時(shí)間
// secs 為正則為未來(lái)的一個(gè)時(shí)間; 為負(fù)則為過(guò)去的一個(gè)時(shí)間; 單位: 秒
+ (instancetype)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs;
// 距離參考時(shí)間間隔ti秒的一個(gè)時(shí)間點(diǎn)
// 這個(gè)參考時(shí)間默認(rèn)是 2001-01-01 00:00:00
+ (instancetype)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti;
// 距離1970-01-01 00:00:00間隔secs秒的時(shí)間
+ (instancetype)dateWithTimeIntervalSince1970:(NSTimeInterval)secs;
// 距離指定時(shí)間間隔secsToBeAdded秒的時(shí)間
+ (instancetype)dateWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
獲取日期實(shí)例的實(shí)例方法:
- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
用法和上面相應(yīng)的類(lèi)方法一致;
所以, 如果我們直接使用下面的方法來(lái)獲取當(dāng)前時(shí)間, 是不準(zhǔn)確的:
NSDate *date = [NSDate date];
// 直接初始化的時(shí)間, 也是當(dāng)前時(shí)間
//NSDate *date = [[NSDate alloc]init];
結(jié)果會(huì)比我們實(shí)際時(shí)間慢了8個(gè)小時(shí); 我們可以使用下面的方法, 來(lái)獲取當(dāng)前準(zhǔn)確的時(shí)間:
NSDate *date = [NSDate date];
// 直接初始化的時(shí)間, 也是當(dāng)前時(shí)間
//NSDate *date = [[NSDate alloc]init];
NSTimeZone *zone = [NSTimeZone systemTimeZone];
NSTimeInterval interval = [zone secondsFromGMTForDate:date];
NSDate *current = [date dateByAddingTimeInterval:interval];
我們可以輸出前后的時(shí)間, 比較一下:
2016-12-30 08:03:36 +0000--current: 2016-12-30 16:03:36 +0000
后面的是修正之后的時(shí)間.
舉例(以dateWithTimeIntervalSinceNow為例):
// 前一天的這個(gè)時(shí)間
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:-24*60*60];
// 明天的這個(gè)時(shí)間
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:24*60*60];
類(lèi)屬性:
// 遙遠(yuǎn)的未來(lái)的一個(gè)時(shí)間點(diǎn)
@property (class, readonly, copy) NSDate *distantFuture;
// 遙遠(yuǎn)的過(guò)去的一個(gè)時(shí)間點(diǎn)
@property (class, readonly, copy) NSDate *distantPast;
可使用下面的方法獲取
NSDate *date = [NSDate distantFuture];
NSDate *date = [NSDate distantPast];
NSDate相關(guān)的屬性和實(shí)例方法
// 距離當(dāng)前時(shí)間的時(shí)間間隔, 單位: 秒
@property (readonly) NSTimeInterval timeIntervalSinceNow;
// 距離1970的時(shí)間間隔, 單位: 秒
@property (readonly) NSTimeInterval timeIntervalSince1970;
距離某個(gè)時(shí)間點(diǎn)間隔一定時(shí)間的時(shí)間點(diǎn)
- (id)addTimeInterval:(NSTimeInterval)seconds ;
- (instancetype)dateByAddingTimeInterval:(NSTimeInterval)ti;
時(shí)間的比較, 有以下幾個(gè)方法:
// 一個(gè)實(shí)際是否比另一個(gè)時(shí)間早
// 返回較早的那個(gè)時(shí)間
- (NSDate *)earlierDate:(NSDate *)anotherDate;
// 一個(gè)時(shí)間是否比另一個(gè)時(shí)間晚
// 返回較晚的那個(gè)時(shí)間
- (NSDate *)laterDate:(NSDate *)anotherDate;
// 兩個(gè)時(shí)間比較
- (NSComparisonResult)compare:(NSDate *)other;
// 是否和另一個(gè)時(shí)間相等
- (BOOL)isEqualToDate:(NSDate *)otherDate;
這里的第三個(gè)方法的返回值是一個(gè)枚舉:
typedef NS_ENUM(NSInteger, NSComparisonResult) {NSOrderedAscending = -1L, // 早
NSOrderedSame, // 相等
NSOrderedDescending // 晚
};
例如:
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:-60*60];
NSDate *current = [NSDate date];
NSDate *earlierDate = [date earlierDate:current];
NSDate *later = [date laterDate:current];
NSLog(@"--date: %@\n--current:%@\n--earlierDate: %@ \n--later: %@",date, current, earlierDate, later);
輸出的結(jié)果為:
--date: 2016-12-30 07:58:45 +0000
--current:2016-12-30 08:58:45 +0000
--earlierDate: 2016-12-30 07:58:45 +0000
--later: 2016-12-30 08:58:45 +0000
修改一下比較的時(shí)間:
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:60*60];
NSDate *current = [NSDate date];
NSDate *earlierDate = [date earlierDate:current];
NSDate *later = [date laterDate:current];
NSLog(@"%@\n--current:%@\n--earlierDate: %@ \n--later: %@",date, current, earlierDate, later);
輸出:
2016-12-30 10:01:11 +0000
--current:2016-12-30 09:01:11 +0000
--earlierDate: 2016-12-30 09:01:11 +0000
--later: 2016-12-30 10:01:11 +0000
可以看出** earlierDate總是返回較早的那個(gè)時(shí)間, 而 laterDate**總是返回較晚的那個(gè)時(shí)間;
三. 日期格式化--NSDateFormatter
NSDateFormatter 一般是和NSDate實(shí)例結(jié)合使用的, 把NSDate對(duì)象轉(zhuǎn)換為一定格式的字符串, 或者把一定格式的字符串轉(zhuǎn)換為NSDate對(duì)象;
// 將NSDate對(duì)象格式化為字符串
- (NSString *)stringFromDate:(NSDate *)date;
// 將時(shí)間字符串格式化為NSDate對(duì)象實(shí)例
- (nullable NSDate *)dateFromString:(NSString *)string;
雖然, 系統(tǒng)預(yù)設(shè)了很多種格式的時(shí)期格式化方式, 但更多的我們還是自己指定其格式化方式, 主要是通過(guò)設(shè)置其dateFormat屬性;
例如 :
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
NSDate *date = [NSDate date];
NSString *dateString = [dateFormatter stringFromDate:date];
NSLog(@"格式化后的時(shí)間為: %@", dateString);
輸出:
2016-12-30 17:21:50.728 NSDate[11341:374209] 格式化后的時(shí)間為: 2016-12-30 17:21:50
另外, NSDateFormatter實(shí)例有一個(gè)屬性需要注意: timeZone;
這個(gè)是會(huì)影響格式化后的字符串日期的, 其默認(rèn)的值為當(dāng)前系統(tǒng)的時(shí)區(qū);
這里曾經(jīng)遇到一個(gè)坑:
當(dāng)時(shí)的需求是這樣的, 獲取當(dāng)前日期的年月日, 所以我是按如下方法來(lái)獲取的:
// 獲取當(dāng)前時(shí)區(qū)
NSTimeZone *timeZone = [NSTimeZone systemTimeZone];
// 獲取當(dāng)前時(shí)間
NSDate *current = [NSDate date];
// 修復(fù)時(shí)差, 獲取當(dāng)前時(shí)區(qū)時(shí)間
NSInteger currentinterval = [timeZone secondsFromGMTForDate:current];
NSDate *currentDate = [current dateByAddingTimeInterval:currentinterval];
// 獲取當(dāng)前年月日字符串
NSDateFormatter *currentFormart = [[NSDateFormatter alloc]init];
[currentFormart setDateFormat:@"yyyy-MM-dd"];
currentFormart.timeZone = timeZone;
NSString *currentString = [currentFormart stringFromDate:currentDate];
NSLog(@">>>>%@", currentString);
因?yàn)橹苯邮褂?strong>[NSDate date]獲取的時(shí)間是相對(duì)于GMT的, 所以這里我調(diào)整了當(dāng)前具體時(shí)間, 但是格式化的結(jié)果卻晚了一天:
2016-12-30 17:30:02.809 NSDate[11484:379975] >>>>2016-12-31
而當(dāng)前日期是30號(hào);而使用下面的方法, 沒(méi)有調(diào)整獲取的時(shí)間對(duì)象:
// 獲取當(dāng)前時(shí)區(qū)
NSTimeZone *timeZone = [NSTimeZone systemTimeZone];
// 獲取當(dāng)前時(shí)間
NSDate *current = [NSDate date];
// 獲取當(dāng)前年月日字符串
NSDateFormatter *currentFormart = [[NSDateFormatter alloc]init];
[currentFormart setDateFormat:@"yyyy-MM-dd"];
currentFormart.timeZone = timeZone;
NSString *currentString = [currentFormart stringFromDate: current];
NSLog(@">>>>%@", currentString);
這時(shí)獲取的日期是正確的:
2016-12-30 17:32:50.739 NSDate[11541:381951] >>>>2016-12-30
如果, 將格式化的結(jié)果加上小時(shí)和分鐘:
// 獲取當(dāng)前時(shí)區(qū)
NSTimeZone *timeZone = [NSTimeZone systemTimeZone];
// 獲取當(dāng)前時(shí)間
NSDate *current = [NSDate date];
// 修復(fù)時(shí)差, 獲取當(dāng)前時(shí)區(qū)時(shí)間
NSInteger currentinterval = [timeZone secondsFromGMTForDate:current];
NSDate *currentDate = [current dateByAddingTimeInterval:currentinterval];
// 獲取當(dāng)前年月日字符串
NSDateFormatter *currentFormart = [[NSDateFormatter alloc]init];
[currentFormart setDateFormat:@"yyyy-MM-dd HH:mm"];
currentFormart.timeZone = timeZone;
NSString *currentString = [currentFormart stringFromDate:currentDate];
NSLog(@">>>>%@", currentString);
會(huì)發(fā)現(xiàn), 結(jié)果是這樣的:
2016-12-30 17:36:08.565 NSDate[11639:385054] >>>>2016-12-31 01:36
正好多了8個(gè)小時(shí);
初步猜想, 這是因?yàn)? 在格式化的時(shí)候, NSDateFormatter實(shí)例對(duì)象, 會(huì)根據(jù)其當(dāng)前的時(shí)區(qū), 來(lái)自動(dòng)校正為相對(duì)于GMT的時(shí)間, 因?yàn)槲野褧r(shí)間已經(jīng)校正了, 但是NSDate實(shí)例默認(rèn)的時(shí)間是相對(duì)于GMT的, 所以 NSDateFormatter實(shí)例就根據(jù)當(dāng)前的時(shí)區(qū)自動(dòng)校正了, 這樣就相當(dāng)于校正了兩次, 所以結(jié)果就會(huì)多了8個(gè)小時(shí), 導(dǎo)致了這個(gè)錯(cuò)誤;
更多日期/時(shí)間格式化字符請(qǐng)參考[iOS]NSDateFormatter時(shí)間格式化字符集.
四. 語(yǔ)言環(huán)境--NSLocale
在我們使用NSDateFormatter進(jìn)行時(shí)間的格式化的時(shí)候,還有另外一個(gè)坑, 就是當(dāng)時(shí)手機(jī)系統(tǒng)的語(yǔ)言環(huán)境, 作為一個(gè)國(guó)內(nèi)的iPhone用戶(hù), 大部分人的手機(jī)語(yǔ)言環(huán)境就是" 簡(jiǎn)體中文" , 這個(gè)可以在手機(jī)的"設(shè)置-->通用-->語(yǔ)言與地區(qū)-->iPhone語(yǔ)言" (英文狀態(tài)是: Settings-->General-->Language & Region-->iPhone Language)進(jìn)行查看和修改.按照上面的方法使用, 大部分情況下是沒(méi)有問(wèn)題的, 但是有些人的手機(jī)語(yǔ)言環(huán)境并不是簡(jiǎn)體中文, 有可能是英文或者其他的語(yǔ)言, 這時(shí)候如果還是按照上面的方法進(jìn)行設(shè)置, 就會(huì)出現(xiàn)問(wèn)題了.具體的問(wèn)題,這里不再舉例, 可以參考NSLocale的重要性和用法簡(jiǎn)介里面的示例.
我們可以使用下面的方法獲取當(dāng)前系統(tǒng)的語(yǔ)言環(huán)境:
NSLocale *locale = [NSLocale currentLocale];
NSLog(@"%@--", locale.localeIdentifier);
這里輸出的是:
2017-01-03 09:30:28.599981 NSDate[1529:749832] en_CN--
或者使用下面的方法, 來(lái)獲取指定語(yǔ)言環(huán)境的實(shí)例對(duì)象:
+ (instancetype)localeWithLocaleIdentifier:(NSString *)ident
例如:
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"en_CN"];
這樣就獲取了簡(jiǎn)體中文的語(yǔ)言環(huán)境實(shí)例對(duì)象. 如果我們想不管手機(jī)是設(shè)置的哪一個(gè)語(yǔ)言環(huán)境, 我們都讓他顯示簡(jiǎn)體中文下的數(shù)據(jù), 可以這樣獲取實(shí)例對(duì)象后, 賦值給NSDateFormatter的locale屬性. 這樣就能保證, 在手機(jī)設(shè)置為任何語(yǔ)言環(huán)境下, 都能獲取到簡(jiǎn)體中文的數(shù)據(jù). 與此相關(guān)的還有包括貨幣、語(yǔ)言、國(guó)家等的信息, 特別是一些金融類(lèi)的APP, 對(duì)貨幣的一些設(shè)置, 就需要使用這個(gè)類(lèi)來(lái)調(diào)整.
關(guān)于獲取語(yǔ)言環(huán)境的標(biāo)識(shí)符(localeIdentifier), 這里給出幾個(gè)常用的:
| 標(biāo)識(shí)符 | 語(yǔ)言環(huán)境 |
|---|---|
| en_CN | 簡(jiǎn)體中文 |
| en_HK | 繁體中文(香港) |
| en_US | 英語(yǔ)(美國(guó)) |
| en_WW | 英語(yǔ)(全球) |
其他更多的可參考這篇文章:NSLocale中常用的語(yǔ)言代碼對(duì)照表;
五. 日歷--NSCalendar
日歷的功能很強(qiáng)大, 可以實(shí)現(xiàn)很多關(guān)于日期的場(chǎng)景, 更多的可以參考ios時(shí)間那點(diǎn)事--NSCalendar NSDateComponents, 這里所介紹的一種需求是和日期有關(guān)的, 就是從一個(gè)時(shí)間里獲取年/周/月/日等信息.
使用下面的方法, 可以獲取當(dāng)前的日歷實(shí)例:
NSCalendar *calendar = [NSCalendar currentCalendar];
默認(rèn)是公歷(即: 陽(yáng)歷), 如果想要獲取其他的歷法, 可使用下面的方法:
+ (nullable NSCalendar *)calendarWithIdentifier:(NSCalendarIdentifier)calendarIdentifierConstant
這里的NSCalendarIdentifier預(yù)設(shè)了現(xiàn)有歷法類(lèi)型的標(biāo)識(shí)符:
NSCalendarIdentifierGregorian 公歷(陽(yáng)歷)
NSCalendarIdentifierChinese 中國(guó)歷法(陰歷)
其他歷法, 可參考底層API;
如果想從NSCalendar 實(shí)例對(duì)象中獲取當(dāng)前時(shí)間的年月日等信息, 還需要另一個(gè)類(lèi): NSDateComponents,他將時(shí)間表示成適合人類(lèi)閱讀的格式: 年月日時(shí)分秒等, 他一般是和 ** NSCalendar一起使用的,使用 NSCalendar下面這個(gè)實(shí)例方法, 可以獲取當(dāng)前歷法下的NSDateComponents*實(shí)例:
- (NSDateComponents *)components:(NSCalendarUnit)unitFlags fromDate:(NSDate *)date
這里的參數(shù)unitFlags是一個(gè)枚舉, 常用的有以下幾種:
NSCalendarUnitEra = kCFCalendarUnitEra, // 紀(jì)元
NSCalendarUnitYear = kCFCalendarUnitYear, //年
NSCalendarUnitMonth = kCFCalendarUnitMonth,// 月
NSCalendarUnitDay = kCFCalendarUnitDay, //日
NSCalendarUnitHour = kCFCalendarUnitHour, //時(shí)
NSCalendarUnitMinute = kCFCalendarUnitMinute, //分
NSCalendarUnitSecond = kCFCalendarUnitSecond, // 秒
NSCalendarUnitWeekday = kCFCalendarUnitWeekday // 周
例如, 以下是從當(dāng)前日期中獲取當(dāng)前的年月日周:
// 獲取當(dāng)前時(shí)間
NSDate *date = [NSDate date];
// NSCalendarIdentifierGregorian
// NSCalendarIdentifierChinese
NSCalendar *calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *componets = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitWeekday fromDate:date];
NSLog(@"年: %ld-- 月: %ld--日: %ld -- 周: %ld", (long)componets.year, (long)componets.month, (long)componets.day,componets.weekday);
輸出:
2017-01-03 11:40:01.989 NSDate[27741:888574] 年: 2017-- 月: 1--日: 3 -- 周: 3
這里的周輸出的是3, 這是因?yàn)樵?strong>iOS中, 一周的第一天是周日, 這點(diǎn)和我們的習(xí)慣有些區(qū)別, 這里的日期實(shí)際是周二, 所以, 獲取周幾的時(shí)候需要特殊處理一下.
上面在獲取當(dāng)前年月日周等信息的時(shí)候, 一定要在components方法里設(shè)置相關(guān)的值, 才能獲取到.