iOS客户端NSDateFormatter那些坑

NSDateFormatter 会收到用户偏好设置的影响,所以有一些坑:

时区校验

有时候,我们需要把时间字符串转换为long类型的时间戳。比如下面例子:

    // 将"2016-02-06 00:00:00"转化为格林尼治标准的时间戳
    
    NSString *timeStr = @"2016-02-06 00:00:00" 
    NSDateFormatter *format = [[NSDateFormatter alloc] init];
    [format setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
    NSDate *fromdate = [format dateFromString:timeStr];    
    long long time = (long long)[fromdate timeIntervalSince1970];

但这里忽略了时区问题:
我们从模拟器中,“设置”-> "通用" -> "时间与日期" ->关闭自动设置,选择"纽约"时区。上面代码计算出的time值 为1454734800000。 然后我们选“北京”时区,计算出的time值为 1454688000000显然,两个值不一样,而且在纽约时区下计算出的时间戳的值更大。

UTC (Coordinated Universal Time)

我们来看timeIntervalSince1970函数,官方说明

The interval between the date object and 00:00:00 UTC on 1 January 1970。根据UTC标准,计算NSDate对象距离1970年1月1号  00:00:00 的时间戳。

整个地球分为二十四时区,每个时区都有自己的本地时间。但是在全球范围,我们需要一个标准时间。我们熟悉的标准时间是 格林尼治时间。格林尼治标准时间(中国大陆翻译:格林尼治平均时间或格林尼治标准时间,台、港、澳翻译:格林威治标准时间;英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义为通过那里的经线。自1924年2月5日开始,格林尼治天文台每隔一小时会向全世界发放调时信息。理论上来说,格林尼治标准时间的正午是指当太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球在它的椭圆轨道里的运动速度不均匀,这个时刻可能与实际的太阳时有误差,最大误差达16分钟。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治时间已经不再被作为标准时间使用。现在的标准时间,是由原子钟报时的UTC。其以原子时秒长为基础,在时刻上尽量接近于格林尼治标准时间。

北京时区是东八区,领先UTC八个小时。时区差东为正,西为负。在此,把东八区时区差记为 +0800。纽约的时区是西五区,比UTC落后五个小时,记为 -0500. 即北京时间领先纽约时间十三个小时.

UTC 时间为1970年1月1号00:00:00的时候。北京时间是1970年1月1号8点,纽约时间还是1969年12月31号19:00。想象,每个时区有自己的时间坐标轴,原点代表的时间点是UTC的“1970年1月1号00:00:00”, 在坐标上标出各自的"2016-02-06 00:00:00"这一点,它们离原点的距离就是我们要算的时间戳。

timetamp.png

显然,将"2016-02-06 00:00:00"转化为格林尼治标准的时间戳。在纽约时区计算出来的值要比北京时区大。

如果将"2016-02-06 00:00:00"是服务端(东八区)下发的时间,我们在客户端需要转为时间戳,建议,把NSDateFormatter的时区设定在东八区。

 [format setTime Zone:[NSTimeZone timeZoneWithName:@"Asia/Shanghai"]];
Paste_Image.png

timeZone

timeZone

// 这个方法的名字很委婉,known一词说明这是“他”已知的时区的名字。世界各地对自己所在的时区可能都有一定的命名,但是不一定被“他”收录。例如,中国大陆,只有重庆和上海被收录了(难道这是中国只使用一个时区的错误?!)。使用这个方法获得的时区名字,都是在iOS系统中/usr/share/zoneinfo/目录中保存时区数据。随着iOS版本的更新,这里面的数据会发生变动。当然,要是你的设备越狱了,你可以手动往该目录下添加时区文件。       
// 时区文件里面包括了一下内容:
//      当前时区相对于GMT的偏移量(s)
//      当前时区的名字缩写
//      当前时区是否使“夏时制”时区
// 因为时区文件中包含了"偏移量",所以通过“时区的名称”可以指定一个“时区”。
// 时区名称举例:
//      Africa/Abidjan
//      America/New_York
//      Asia/Shanghai
//      Asia/Hong_Kong
// 越狱的童鞋可以看出时区的名称和/usr/share/zoneinfo中的目录结构基本一一对应。
+ (NSArray *)knownTimeZoneNames;


// 获取所有的时区名称缩写
// 名称缩写与名称是一一对应的关系,例如:HKT = "Asia/Hong_Kong";
// 默认情况下,调用该方法回去/usr/share/zoneinfo目录下找时区名称缩写,但是当使用方法"+ (void)setAbbreviationDictionary:(NSDictionary *)dict;"后,将会只返回刚才设置的时区名称缩写。请看下文的代码实例!
// 名称缩写举例:
//      EST = "America/New_York";
//      GMT = GMT;
//      GST = "Asia/Dubai";
//      HKT = "Asia/Hong_Kong";
+ (NSDictionary *)abbreviationDictionary;
//  由时区的名称获得对应的NSTimeZone对象
//  通过时区名称可以获得时区文件,通过时区文件就可以获得“偏移量”,“名称缩写”,“是否使用夏时制”等信息。
+ (id)timeZoneWithName:(NSString *)tzName;

//  由时区名称缩写获得对应的NSTimeZone对象
//  这里的时区名称缩写有两种情况:
//  第一种是上面说的HKT这样的缩写,与时区名称一一对应,通过这样的缩写获得的NSTimeZone对象,与使用时区名称获得得NSTimeZone对象一样。(大概读取得是同一个时区文件)
//  第二种是"GMT+0800"这样格式得缩写,其实这就是偏移量。通过偏移量在iOS中是不能读到与之对应得时区文件的,因此就无法知道“时区名称”,“名称缩写”,“是否使用夏时制”这样的信息了。默认情况下,"时区名称"和"名称缩写"都会赋值为"GMT+0800","是否使用夏时制"则不会设置(默认不使用)。
+ (id)timeZoneWithAbbreviation:(NSString *)abbreviation;

//  由偏移量获得对应的NSTimeZone对象
//  只说一点:通过偏移量获得的NSTimeZone对象的“市区名称”,“名称缩写”都会赋值为"GMT+0800","是否使用夏时制"则不会设置(默认不使用)。
//  注意!!!!该方法不做参数的范围检查!!!
+ (id)timeZoneForSecondsFromGMT:(NSInteger)seconds;       // 不做安全性检查

日历校验

iOS 设置->通用->语言与地区->日历。有公历日本日历佛教日历.公元2016年,日本日历是平成28年。佛历2560年。
所以如果用户在设置中选日本日历,上面代码计算的l时间戳又不一样了:64189900800

补救方法: 手工设置NSDateFormatter的日历

    [format setCalendar: [[NSCalendar alloc]
                          initWithCalendarIdentifier:NSGregorianCalendar]];

或者设置locale.日历可以由NSLocale 中 NSLocaleCalendar这个属性指定
官网说明:

NSDateFormatter treats the numbers in a string you parse as if they were in the user’s chosen calendar. For example, if the user selects the Buddhist calendar, parsing the year 2010 yields an NSDate object in 1467 in the Gregorian calendar. (For more about different calendrical systems and how to use them, see Date and Time Programming Guide.)

12 小时制 和24小时制

  • HH :24小时制
  • hh :12 小时制

这是iOS SDK3.1的bug,在设置中时区自动为纽约的时候,24小时制会自动关闭。自动为法国时区的时候,24小时制会开启。但是如果法国用户手动选择12小时制,"HH"的格式不起作用,程序返回的是"01:00 PM"这12小时类型的日期。

参考博客:

First, a little background on the iPhone user interface. When iPhone users change their region format between, say, “United States” and “France”, the users’ “24-Hour Time” setting is automatically switched to the mode that is most prevalent in that region. In France, that would set 24-Hour Time to “ON”, and in the U.S., that would set it to “OFF”. The users can then manually override that setting and that’s where trouble starts.

The problem comes from NSDateFormatter somehow “getting stuck” in the 12 or 24-hour time mode that the user has manually selected. So if a French user manually selects 12-hour mode, and the application requested NSDateFormatter to output time with the 24-hour format “HHmm”, it would actually receive time in a 12-hour format, e.g. “01:00 PM”, as if the application had instead requested “hhmm aa”. The reverse would happen if a US user manually selected 24-hour mode: outputting time with the 12-hour format “hhmm aa” would actually get you time in the 24-hour format instead, e.g. “17:00″.

YYYY和yyyy

working with Date and Time

Pay special attention to the year format specifier @"yyyy". It is different than the capitalized @YYYY, which represents the year of the date’s week and not the year of the day. 99% of the time, you probably want to use @”yyyy”.
  • yyyy is ordinary calendar year.

  • YYYY is week-based calendar year.“将这一年中第一周的周日当作今年的第一天”.因此有时结果和yyyy相同,有时就会不同

Year (in "Week of Year" based calendars). Normally the length specifies the padding, but for two letters it also specifies the maximum length. This year designation is used in ISO year-week calendar as defined by ISO 8601, but can be used in non-Gregorian based calendar systems where week date processing is desired. May not always be the same value as calendar year.

Date Field Symbol Table

语言和时间制

没错 0 0 ,这也有坑。比如一些日漫粉会把语言选为 "日语",关闭"24-小时制". 这时候:

    NSDateFormatter *format = [[NSDateFormatter alloc] init];
    [format setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
    [format setTimeZone:[NSTimeZone timeZoneWithName:@"Asia/Shanghai"]];
    
    [format setCalendar: [[NSCalendar alloc]
                          initWithCalendarIdentifier:NSGregorianCalendar]];
    NSDate *fromdate = [format dateFromString:timeString];
    long long time = (long long)[fromdate timeIntervalSince1970];
    NSLog(@"%@", fromdate);
    NSLog(@"%lld", time);

这样的代码NSDate对象为null.time为0.
换"时区"或者" 地域"后手动把24-小时制关闭,还是返回null. 具体原因,暂不明白。

此时开启"24-小时制"或者format的格式改HH为hh format setDateFormat:@"yyyy-MM-dd hh:mm:ss"]又有值返回。12小时制和24小时制

方法:

    NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh"];

NSLocale对象可以指定语言。

    NSLog(@"language:%@", [NSLocale preferredLanguages]);
    NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh"];
    [format setLocale:locale];
    NSLog(@"%@",[locale objectForKey:NSLocaleLanguageCode]);

输出结果:

language:(
 "ja-US",  // 用户选择的是日本语言
"zh-Hans-US",
"en-US"
)
2016-02-14 11:47:09.527 importDemo[3025:1406067] zh

不同时区城市

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

推荐阅读更多精彩内容