[iOS 开发] NSCalendar 这个坑货——获取系统当前时间年份出错

背景:最近 BI 的同学反映,根据我们 iOS 客户端收集的埋点数据显示,有一部分数据的埋点时间不对,但是我在工程代码中断点调试时却没有发现任何异常情况。

BI 同学提供的“证据”如下图所示,图中圈出来的 tracktime (00280629104535)本应显示的时间理应是 20160629104535,机智的 BI 同学分析其原因可能是,我们记录时间的代码在某些机器上可能有问题,也就是说机型适配问题,但根据我多年的开发经验来看(不要笑),问题应该不在于此,其中必定另有蹊跷。

埋点数据.png

于是,我看了一下我们记录埋点时间的相关代码:

// tt
NSDateFormatter *dateformatter=[[NSDateFormatter alloc] init];
[dateformatter setDateFormat:@"yyyyMMddHHmmss"];
[mutDict setString:[dateformatter stringFromDate:[NSDate date]] forKey:MTCLICK_KEY_TT];

第一眼看上去貌似很正常,打个断点,打印出来的时间也没问题。那么问题在哪呢?难道真是机型问题?但从没听说过机型跟这时间格式有毛关系啊。

那就只能先从 NSDateFormatter 入手了,首先求助于官方文档 Data Formatting Guide,在 Date Formatters 章节中的 Use Format Strings to Specify Custom Formats 发现其中有这样一段话:

There are two things to note about this example:

  1. It uses yyyy to specify the year component. A common mistake is to use YYYY. yyyy specifies the calendar year whereas YYYY specifies the year (of “Week of Year”), used in the ISO year-week calendar. In most cases, yyyy and YYYY yield the same number, however they may be different. Typically you should use the calendar year.

2.The representation of the time may be 13:00. In iOS, however, if the user has switched 24-Hour Time to Off, the time may be 1:00 pm.

读到此处,我感慨万分,到处都是坑?。《偈毕肫?strong>年初时的那个优惠券有效期的 bug,就是因为时间格式的年份用了 “YYYY”,而不是 “yyyy”,而导致了一个平时看不出来到跨年的时候才出现的问题。yyyy specifies the calendar year whereas YYYY specifies the year (of “Week of Year”), used in the ISO year-week calendar.这句话很关键,由此可以看出之前这个问题的根本就在于日历(calendar)的区别。

现在我们再回过头来看看这次埋点 bug 的“证据”: 20160629104535 -> (00280629104535),问题好像也是出在年份上,那么 0028 代表什么呢?于是我先后求助了度娘、stackoverflow、bing,最终在 bing 上找到了这样一篇文章 How TechCrunch Japan broke our app - Handling local calendars in Swift,里面专门提到了一个关于日本日历(Japan calendar)和公历(Gregorian calendar)之间区别的例子,当我看到这样一段令人感动的话时:

It means that any NSDate instances retrieved from CoreData would always point to the right point in time, i.e. 04 Jan 0028 in Japan calendar now always points to the same point in time as 04 Jan 2016 in Gregorian calendar (having the same Unix timestamp 1451865600).

原来公历2016年相当于日本平成28年,我顿时感觉豁然开朗,仿佛终于找到心目中的 the one 了。
找到问题所在后,接下来就是试验验证了。
还是那段代码:

NSDateFormatter *dateformatter=[[NSDateFormatter alloc] init];
[dateformatter setDateFormat:@"yyyyMMddHHmmss"]; 
NSLog(@"%@", [dateformatter stringFromDate:[NSDate date]]);

打开模拟器的设置->通用->语言与地区->日历,我们依次选择“公历”、“日本日历”、“佛教日历”,并运行工程,console 打印出来的结果是如下。

试验结果说明之前的百分百推断属实。那现在我们该怎么解决这个问题呢?打开 NSDateFormatter.h 文件和参考文档,我们可以看到一个属性 @property (null_resettable, copy) NSCalendar *calendar; ,然后我们再打开 NSCalendar 类的参考文档,可以看到 Calendar Identifiers常量的一些声明:

NSString * const NSCalendarIdentifierGregorian
NSString * const NSCalendarIdentifierBuddhist
NSString * const NSCalendarIdentifierChinese
NSString * const NSCalendarIdentifierCoptic
NSString * const NSCalendarIdentifierEthiopicAmeteMihret 
NSString * const NSCalendarIdentifierEthiopicAmeteAlem
NSString * const NSCalendarIdentifierHebrew 
NSString * const NSCalendarIdentifierISO8601
NSString * const NSCalendarIdentifierIndian
NSString * const NSCalendarIdentifierIslamic
NSString * const NSCalendarIdentifierIslamicCivil 
NSString * const NSCalendarIdentifierJapanese
NSString * const NSCalendarIdentifierPersian
NSString * const NSCalendarIdentifierRepublicOfChina
NSString * const NSCalendarIdentifierIslamicTabular
NSString * const NSCalendarIdentifierIslamicUmmAlQura

显而易见的是,这里我们应该选择 NSCalendarIdentifierGregorian(公历)。

NSDateFormatter *dateformatter=[[NSDateFormatter alloc] init];
[dateformatter setDateFormat:@"yyyyMMddHHmmss"];
[dateformatter setCalendar:[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]];
NSLog(@"%@", [dateformatter stringFromDate:[NSDate date]]);

重新修改代码,再次验证后结果如下。

公历(Gregorian).png
日本日历(Japanese).png
佛教日历(Buddhist).png
设置选项中为日本日历(Japanese),但代码中设了 calendar 属性.png

试验结果证明设置 NSDateFormatter 对象的 calendar 属性为 identifier 为公历NSCalendarIdentifierGregorian的 NSCalendar 对象就可以解决这个问题了。

NSDateFormatter 是我们经常需要使用的一个类,但在使用过程中需要注意很多有关地区,时间,性能方面的问题,要想知道如何一一避免那些坑,多读官方文档是一个上佳的选择。

One more thing,人蠢就要多读书啦!不然连日本日历是什么都不知道是什么。Just kidding! ??

参考资料:
(1)How TechCrunch Japan broke our app - Handling local calendars in Swift:http://blog.famanson.com/2016/01/07/handling-local-calendars-is-a-pain/
(2)官方文档: Data Formatting Guide


拓展延伸:
(1)日历有哪几种?什么是日本日历?什么是佛教日历?
(2)如何正确使用 NSDateFormatter ?

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

推荐阅读更多精彩内容