iOS时间问题

在iOS开发中,经常会遇到各种各样的时间问题,8小时时差,时间戳,求时间间隔,农历等等。解决办法网上比比皆是,但大多零零散散,很多资料并没有说明其中问题。这里集中总结一下,以便于以后查阅和供大家参考。有我自己的理解,错漏之处请大家吐槽。

NSDate的8小时问题

NSDate转字符串时间

初始化一个NSDate时间[NSDate date],获取的是零时区的时间(格林尼治的时间: 年-月-日 时:分:秒: +时区),而北京时间是东八区时间,因为时区不同,所以打印的时间相差了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小时,因为它表示的是零时区(+0000)时间02:44:24。此刻对应东八区的北京时间就是10:44:24。只是时区不同,表示的时间点是一样的。好比1公斤和2斤,重量是一样的。[NSDate date]获取的时间单位是零时区(+0000),我们所要的北京时间的单位是东八区(+0800)。

系统会默认[NSDate date]获取的时间为零时区时间,而经过NSDateFormatter转化为字符串时间就是当前所在时区的准确时间,并没有8小时误差。

转字符串时间的时区设定

上文中NSDate时间转为字符串时间并没有设置NSDateFormatter的timeZone。不设置会默认使用当前所在的时区,与设置系统时区formatter.timeZone = [NSTimeZone systemTimeZone]的效果是一样的。

也可以设置时区,获取指定时区的字符串时间

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

这时获取的时间就是东八区时间,哪怕手机拿到零时区的格林尼治,获取的也是东八区的时间,因为这里指定时区了。也有如下时区指定:

formatter.timeZone = [NSTimeZone timeZoneWithName:@"Asia/Tokyo"];//东九区时间

formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"];//零区时间

通过下面方法可得到系统支持的时区对应的字符串常量:

NSArray *zones = [NSTimeZone knownTimeZoneNames];

for (NSString *zone in zones) {

NSLog(@"时区名 = %@", zone);

}

字符串时间转NSDate

字符串时间转为NSDate时间也会有时区问题。也会遇到有所谓的8小时误差,其实就是时区不同。比如下面的例子:

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指的是时区。要转化的字符串时间格式必须和这个格式匹配,上面给定的字符串时间是:@"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时间时,通常会用以下方法:

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是当前时区,interval是当前时区和零时区时间的差值,最后结果localDate是零时区时间date加上这个差值interval,得到当前时区的NSDate时间。更有甚者,在开发中直接加8*60*60或28800这样的值,因为相差8小时嘛。这样在东八区没问题,在其他时区时间就错了。

其实这种做法是不科学的,因为得到的最终时间还是零时区时间,时间后面明显是+0000,在使用中一般不显示时区,所以认为当做当前时区的时间使用也未尝不可。此为大坑!

坑1:这时如果转为字符串时间,又会增加8小时。因为做时间转换的时候,系统会认为这个NSDate是零时区,得到的字符串时间是东八区的。

解决办法是:将错就错,字符串时间也设置为零时区的字符串时间。从深坑跌入更深的坑!

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"是指世界标准时间,也是现在用的时间标准,东八区比这个时间也是快8小时,这里填@"GMT"也是可以的。

坑2:在与后台交互时,有时需要+0000时区,这时只能手动拼接字符串更改这个时区字段,改为正确的时区。

所以,在开发中尽量不要这么做,当时间要求显示、存储或与后台交互的时候,使用字符串时间!不要使用转化的NSDate。

时间换算,时间戳的概念

当前时间转时间戳

时间戳是指1970年1月1日0时0分0秒到当前时间的秒数。注意:这里的当前时间是指零时区的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时间是当前零时区的时间!当前零时区时间!当前零时区时间!重要的事情说三遍!不要进行NSDate转当前时区的NSDate时间,再转时间戳。下面是验证:

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

打印结果:

2016-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时间。

时间操作与比较

时间初始化和比较方法

几个时间初始化方法:

//初始化当前时间,返回零时区时间

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

几个时间比较方法:

//比较两个时间是否相等

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

//两个时间比较,返回较早时间

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

//两个时间比较,返回较晚时间

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

//两个时间比较,返回枚举类型

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

几个计算时间间隔的方法:

//返回实例时间与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。

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周,时区=%@", 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周,时区=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。毕竟国外惯例周日是每周的第一天。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容