[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阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容