[iOS]開發中的日期--NSDate & NSTimeZone

日期/時間在開發中經常使用, 但涉及到的無非是時間轉字符串顯示出來, 或者根據字符串獲取時間對象, 其他的涉及很少. 花了點時間看了一些相關的資料, 發現相關的東西還真不少.

一. 時區--NSTimeZone

和時間相關的最重要的一個因素, 因為各個地區的時區都不一樣, 所以時間的差別是很大的, 這就是所謂的時差. 任何時區都以GMT為準:

  • GMT 0:00 格林威治標準時間
  • UTC +00:00 校準的全球時間
  • CCD +08:00 中國標準時間

而任何NSTimeZone對象所代表的時區都是相對于GMT的, iOS中的時間類NSDate所獲取到的時間, 都是相對于GMT的.

iOS 中的時區表示方法:GMT+0800 GMT-0800。(+:東區 -:西區 08:小時數 00:分鐘數)。
GMT+0830 就是指比GMT早8小時外加30分鐘的時區

 + (NSArray *)knownTimeZoneNames;

獲取已知的時區, 中國相關有:

  • Asia/Hong_Kong
  • Asia/Shanghai
  • Asia/Harbin
+ (NSDictionary<NSString *, NSString *> *)abbreviationDictionary

時區縮寫, 例如:

  • HKT = "Asia/Hong_Kong"
  • EST = "America/New_York"

獲取NSTimeZone 實例對象:
獲取當前系統時區:

+ (nullable instancetype) systemTimeZone;

通過名稱獲得, 具體名稱可通過knownTimeZoneNames得知

+ (nullable instancetype)timeZoneWithName:(NSString *)tzName;

例如:

NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"Asia/Shanghai"];
    
    NSLog(@"%@", timeZone);

輸出:

 NSDate[8744:272812] Asia/Shanghai (GMT+8) offset 28800

其中28800就是相對GMT的的偏移量, 單位秒, 即8個小時(86060 = 28800)

通過縮寫獲取, 其縮寫可通過abbreviationDictionary得知:

+ (nullable instancetype)timeZoneWithAbbreviation:(NSString *)abbreviation;

例如:

NSTimeZone *timeZone = [NSTimeZone timeZoneWithAbbreviation:@"HKT"];
    NSLog(@"%@", timeZone);

輸出結果和上面一致:

NSDate[8870:278174] Asia/Hong_Kong (GMT+8) offset 28800

另外, 此方法還可以使用GMT+0800 的縮寫獲取;
例如:

NSTimeZone *timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT+0800"];
    NSLog(@"%@", timeZone);

輸出:

NSDate[8912:280370] GMT+0800 (GMT+8) offset 28800

除了沒有名稱, 其他的和上面一樣, 這里都是北京時間的時區, 即: 東八區;

通過相對于GMT的時間偏移量(時差)來獲取時區, 注意這里的單位是秒:

+ (instancetype)timeZoneForSecondsFromGMT:(NSInteger)seconds;

例如:

NSTimeZone *timeZone = [NSTimeZone timeZoneForSecondsFromGMT:8*60*60];
    NSLog(@"%@", timeZone);

這里獲取的結果和上面的一致;

另外, 一般應用程序的默認時區, 是和手機系統設置的時區一致的, 我們可通過下面的方法 setDefaultTimeZone, 來設置應用程序的默認時區;

注意: 這只能影響本應用程序的默認時區, 不會影響手機系統和其他應用程序的時區;

[NSTimeZone setDefaultTimeZone:[NSTimeZone timeZoneWithName:@"America/New_York"]];

二. 日期時間--NSDate

一個NSDate對象, 代表一個具體的時間點, 這個時間點不是絕對的, 是相對的; 依賴于當前的時區, 會隨著時區的變化而變化; NSDate默認相對的時區是GMT, 即: 格林威治標準時間;
獲取日期實例的類方法主要有:

// 獲取當前時間
+ (instancetype)date;
// 獲取距離當前時間的為secs秒的時間
// secs 為正則為未來的一個時間; 為負則為過去的一個時間; 單位: 秒
+ (instancetype)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs;
// 距離參考時間間隔ti秒的一個時間點
// 這個參考時間默認是 2001-01-01 00:00:00
+ (instancetype)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti;
// 距離1970-01-01 00:00:00間隔secs秒的時間
+ (instancetype)dateWithTimeIntervalSince1970:(NSTimeInterval)secs;
// 距離指定時間間隔secsToBeAdded秒的時間
+ (instancetype)dateWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;

獲取日期實例的實例方法:

- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;

用法和上面相應的類方法一致;

所以, 如果我們直接使用下面的方法來獲取當前時間, 是不準確的:

NSDate *date = [NSDate date];
// 直接初始化的時間, 也是當前時間
  //NSDate *date = [[NSDate alloc]init];

結果會比我們實際時間慢了8個小時; 我們可以使用下面的方法, 來獲取當前準確的時間:

    NSDate *date = [NSDate date];
    
// 直接初始化的時間, 也是當前時間
  //NSDate *date = [[NSDate alloc]init];
    NSTimeZone *zone = [NSTimeZone systemTimeZone];
    
    NSTimeInterval interval = [zone secondsFromGMTForDate:date];
    
    NSDate *current = [date dateByAddingTimeInterval:interval];

我們可以輸出前后的時間, 比較一下:

2016-12-30 08:03:36 +0000--current: 2016-12-30 16:03:36 +0000

后面的是修正之后的時間.

舉例(以dateWithTimeIntervalSinceNow為例):

// 前一天的這個時間
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:-24*60*60];
// 明天的這個時間
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:24*60*60];

類屬性:

// 遙遠的未來的一個時間點
@property (class, readonly, copy) NSDate *distantFuture;
// 遙遠的過去的一個時間點
@property (class, readonly, copy) NSDate *distantPast;

可使用下面的方法獲取

NSDate *date = [NSDate distantFuture];
NSDate *date = [NSDate distantPast];
NSDate相關的屬性和實例方法
// 距離當前時間的時間間隔, 單位: 秒
@property (readonly) NSTimeInterval timeIntervalSinceNow;
// 距離1970的時間間隔, 單位: 秒
@property (readonly) NSTimeInterval timeIntervalSince1970;

距離某個時間點間隔一定時間的時間點

- (id)addTimeInterval:(NSTimeInterval)seconds ;
- (instancetype)dateByAddingTimeInterval:(NSTimeInterval)ti;

時間的比較, 有以下幾個方法:

// 一個實際是否比另一個時間早
// 返回較早的那個時間
- (NSDate *)earlierDate:(NSDate *)anotherDate;
// 一個時間是否比另一個時間晚
// 返回較晚的那個時間
- (NSDate *)laterDate:(NSDate *)anotherDate;
// 兩個時間比較
- (NSComparisonResult)compare:(NSDate *)other;
// 是否和另一個時間相等
- (BOOL)isEqualToDate:(NSDate *)otherDate;

這里的第三個方法的返回值是一個枚舉:

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);

輸出的結果為:

--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

修改一下比較的時間:

 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總是返回較早的那個時間, 而 laterDate**總是返回較晚的那個時間;

三. 日期格式化--NSDateFormatter

NSDateFormatter 一般是和NSDate實例結合使用的, 把NSDate對象轉換為一定格式的字符串, 或者把一定格式的字符串轉換為NSDate對象;

// 將NSDate對象格式化為字符串
- (NSString *)stringFromDate:(NSDate *)date;
// 將時間字符串格式化為NSDate對象實例
- (nullable NSDate *)dateFromString:(NSString *)string;

雖然, 系統預設了很多種格式的時期格式化方式, 但更多的我們還是自己指定其格式化方式, 主要是通過設置其dateFormat屬性;
例如 :

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
    
    dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
    
    NSDate *date = [NSDate date];
    
    NSString *dateString = [dateFormatter stringFromDate:date];
    
    NSLog(@"格式化后的時間為: %@", dateString);

輸出:

2016-12-30 17:21:50.728 NSDate[11341:374209] 格式化后的時間為: 2016-12-30 17:21:50

另外, NSDateFormatter實例有一個屬性需要注意: timeZone;
這個是會影響格式化后的字符串日期的, 其默認的值為當前系統的時區;
這里曾經遇到一個坑:
當時的需求是這樣的, 獲取當前日期的年月日, 所以我是按如下方法來獲取的:

// 獲取當前時區
    NSTimeZone *timeZone = [NSTimeZone systemTimeZone];
    // 獲取當前時間
    NSDate *current = [NSDate date];
    // 修復時差, 獲取當前時區時間
    NSInteger currentinterval = [timeZone secondsFromGMTForDate:current];
    NSDate *currentDate = [current dateByAddingTimeInterval:currentinterval];
    
    // 獲取當前年月日字符串
    NSDateFormatter *currentFormart = [[NSDateFormatter alloc]init];
    [currentFormart setDateFormat:@"yyyy-MM-dd"];
    currentFormart.timeZone = timeZone;
    NSString *currentString = [currentFormart stringFromDate:currentDate];

NSLog(@">>>>%@", currentString);

因為直接使用[NSDate date]獲取的時間是相對于GMT的, 所以這里我調整了當前具體時間, 但是格式化的結果卻晚了一天:

2016-12-30 17:30:02.809 NSDate[11484:379975] >>>>2016-12-31

而當前日期是30號;而使用下面的方法, 沒有調整獲取的時間對象:

// 獲取當前時區
    NSTimeZone *timeZone = [NSTimeZone systemTimeZone];
    // 獲取當前時間
    NSDate *current = [NSDate date];

    // 獲取當前年月日字符串
    NSDateFormatter *currentFormart = [[NSDateFormatter alloc]init];
    [currentFormart setDateFormat:@"yyyy-MM-dd"];
    currentFormart.timeZone = timeZone;
    NSString *currentString = [currentFormart stringFromDate: current];

NSLog(@">>>>%@", currentString);

這時獲取的日期是正確的:

2016-12-30 17:32:50.739 NSDate[11541:381951] >>>>2016-12-30

如果, 將格式化的結果加上小時和分鐘:

// 獲取當前時區
    NSTimeZone *timeZone = [NSTimeZone systemTimeZone];
    // 獲取當前時間
    NSDate *current = [NSDate date];
    // 修復時差, 獲取當前時區時間
    NSInteger currentinterval = [timeZone secondsFromGMTForDate:current];
    NSDate *currentDate = [current dateByAddingTimeInterval:currentinterval];
    
    // 獲取當前年月日字符串
    NSDateFormatter *currentFormart = [[NSDateFormatter alloc]init];
    [currentFormart setDateFormat:@"yyyy-MM-dd HH:mm"];
    currentFormart.timeZone = timeZone;
    NSString *currentString = [currentFormart stringFromDate:currentDate];
    
    NSLog(@">>>>%@", currentString);

會發現, 結果是這樣的:

2016-12-30 17:36:08.565 NSDate[11639:385054] >>>>2016-12-31 01:36

正好多了8個小時;

初步猜想, 這是因為, 在格式化的時候, NSDateFormatter實例對象, 會根據其當前的時區, 來自動校正為相對于GMT的時間, 因為我把時間已經校正了, 但是NSDate實例默認的時間是相對于GMT的, 所以 NSDateFormatter實例就根據當前的時區自動校正了, 這樣就相當于校正了兩次, 所以結果就會多了8個小時, 導致了這個錯誤;

更多日期/時間格式化字符請參考[iOS]NSDateFormatter時間格式化字符集.

四. 語言環境--NSLocale

在我們使用NSDateFormatter進行時間的格式化的時候,還有另外一個坑, 就是當時手機系統的語言環境, 作為一個國內的iPhone用戶, 大部分人的手機語言環境就是" 簡體中文" , 這個可以在手機的"設置-->通用-->語言與地區-->iPhone語言" (英文狀態是: Settings-->General-->Language & Region-->iPhone Language)進行查看和修改.按照上面的方法使用, 大部分情況下是沒有問題的, 但是有些人的手機語言環境并不是簡體中文, 有可能是英文或者其他的語言, 這時候如果還是按照上面的方法進行設置, 就會出現問題了.具體的問題,這里不再舉例, 可以參考NSLocale的重要性和用法簡介里面的示例.
我們可以使用下面的方法獲取當前系統的語言環境:

NSLocale *locale = [NSLocale currentLocale];
NSLog(@"%@--", locale.localeIdentifier);

這里輸出的是:

2017-01-03 09:30:28.599981 NSDate[1529:749832] en_CN--

或者使用下面的方法, 來獲取指定語言環境的實例對象:

+ (instancetype)localeWithLocaleIdentifier:(NSString *)ident 

例如:

 NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"en_CN"];

這樣就獲取了簡體中文的語言環境實例對象. 如果我們想不管手機是設置的哪一個語言環境, 我們都讓他顯示簡體中文下的數據, 可以這樣獲取實例對象后, 賦值給NSDateFormatterlocale屬性. 這樣就能保證, 在手機設置為任何語言環境下, 都能獲取到簡體中文的數據. 與此相關的還有包括貨幣、語言、國家等的信息, 特別是一些金融類的APP, 對貨幣的一些設置, 就需要使用這個類來調整.
關于獲取語言環境的標識符(localeIdentifier), 這里給出幾個常用的:

標識符 語言環境
en_CN 簡體中文
en_HK 繁體中文(香港)
en_US 英語(美國)
en_WW 英語(全球)

其他更多的可參考這篇文章:NSLocale中常用的語言代碼對照表;

五. 日歷--NSCalendar

日歷的功能很強大, 可以實現很多關于日期的場景, 更多的可以參考ios時間那點事--NSCalendar NSDateComponents, 這里所介紹的一種需求是和日期有關的, 就是從一個時間里獲取年/周/月/日等信息.
使用下面的方法, 可以獲取當前的日歷實例:

NSCalendar *calendar = [NSCalendar currentCalendar];

默認是公歷(即: 陽歷), 如果想要獲取其他的歷法, 可使用下面的方法:

+ (nullable NSCalendar *)calendarWithIdentifier:(NSCalendarIdentifier)calendarIdentifierConstant

這里的NSCalendarIdentifier預設了現有歷法類型的標識符:

NSCalendarIdentifierGregorian   公歷(陽歷)
NSCalendarIdentifierChinese      中國歷法(陰歷)

其他歷法, 可參考底層API;
如果想從NSCalendar 實例對象中獲取當前時間的年月日等信息, 還需要另一個類: NSDateComponents,他將時間表示成適合人類閱讀的格式: 年月日時分秒等, 他一般是和 ** NSCalendar一起使用的,使用 NSCalendar下面這個實例方法, 可以獲取當前歷法下的NSDateComponents*實例:

- (NSDateComponents *)components:(NSCalendarUnit)unitFlags fromDate:(NSDate *)date

這里的參數unitFlags是一個枚舉, 常用的有以下幾種:

NSCalendarUnitEra                = kCFCalendarUnitEra, // 紀元
NSCalendarUnitYear               = kCFCalendarUnitYear, //年
NSCalendarUnitMonth              = kCFCalendarUnitMonth,// 月
NSCalendarUnitDay                = kCFCalendarUnitDay, //日
NSCalendarUnitHour               = kCFCalendarUnitHour, //時
NSCalendarUnitMinute             = kCFCalendarUnitMinute, //分
NSCalendarUnitSecond             = kCFCalendarUnitSecond, // 秒
NSCalendarUnitWeekday            = kCFCalendarUnitWeekday // 周

例如, 以下是從當前日期中獲取當前的年月日周:

   // 獲取當前時間
    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, 這是因為在iOS中, 一周的第一天是周日, 這點和我們的習慣有些區別, 這里的日期實際是周二, 所以, 獲取周幾的時候需要特殊處理一下.
上面在獲取當前年月日周等信息的時候, 一定要在components方法里設置相關的值, 才能獲取到.

(完)

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

推薦閱讀更多精彩內容