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"这一点,它们离原点的距离就是我们要算的时间戳。
显然,将"2016-02-06 00:00:00"转化为格林尼治标准的时间戳。在纽约时区计算出来的值要比北京时区大。
如果将"2016-02-06 00:00:00"是服务端(东八区)下发的时间,我们在客户端需要转为时间戳,建议,把NSDateFormatter的时区设定在东八区。
[format setTime Zone:[NSTimeZone timeZoneWithName:@"Asia/Shanghai"]];
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
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.
语言和时间制
没错 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