虽然“按照一定格式显示日期/时间”这个需求已经做了很多次了,但是好像每次做的时候都是google一下人家写的代码,改改必要的东西贴进自己的代码里,其实并不知道这其中应该注意些什么,以及怎样的写法是更合适的。恰逢今天突遇了一个小bug,就认真查看了一下相关文档,把该注意的地方记了下来~
使用NSDateFormatter自带的格式来显示日期/时间
NSDateFormatter自带了几种格式,使用这些格式来显示日期/时间会受到用户在系统设置中设置的个人偏好的影响。这些格式分别是:
格式名称 | 对应的日期格式 | 对应的时间格式 |
---|---|---|
NSDateFormatterNoStyle | / | / |
NSDateFormatterShortStyle | 12/13/52 | 3:30pm |
NSDateFormatterMediumStyle | Jan 12, 1952 | 3:30:32pm |
NSDateFormatterLongStyle | January 12, 1952 | 3:30:32pm |
NSDateFormatterFullStyle | Tuesday, April 12, 1952 AD | 3:30:42pm PST |
通过给NSDateFormatter的对象分别设置dateStyle和timeStyle,可以将NSDate对象转换成对应格式的字符串。
官方文档中有这样的示例代码:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:162000];
NSString *formattedDateString = [dateFormatter stringFromDate:date];
NSLog(@"formattedDateString: %@", formattedDateString);
// Output for locale en_US: "formattedDateString: Jan 2, 2001".
国际化
在使用NSDateFormatter的时候来格式化一个日期时,实际上用户的一些个人偏好也会默认的被考虑进去。上面的这段示例代码在我的mac(语言为简体中文,北京时间)中就会输出:
formattedDateString: 2001年1月3日
NSDateFormatter类中有一个property叫做locale,这个property会影响输出的日期格式,默认情况下,这个locale就是用户的currentLocale。
NSLocale是与国际化相关的基础类,使用[NSLocale currentLocale]
可以得到当前用户关于国际化的一些设定,包括语言、日期和时间格式等。
locale不是用户的默认语言,虽然它们有时会很相似。官方文档上举了一个例子:一个居住在德国的说英语的人可能会选择英语作为他的默认语言,选择德国作为他的地区,那么系统的文字将是英文,但是日期、时间和数字可能会跟随德国习惯的格式,比如在时间上使用24小时制。
在OS X中,可以到“系统偏好设置->语言与地区”中设置当前的locale,在iOS中则可以到“设置->通用->语言与地区”中进行设置。
比如,把“日历”从“公历”改成“日本日历”,则上一段代码的输出就变成了:
平成13年1月3日
使用代码
NSLocale *locale = [dateFormatter locale];
NSCalendar *calendar = [locale objectForKey:NSLocaleCalendar];
NSLog(@"calendar: %@", calendar.calendarIdentifier);
可以查看当前dateFormatter使用的calendar类型:
calendar: japanese
所以,如果想要在显示日期/时间时排除用户的locale设置,则需要自定义一个NSLocale对象。比如对dateFormatter设置:
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
[dateFormatter setLocale:locale];
此时,虽然我的设备的语言是简体中文,日历是日本日历,示例代码输出的却是:
formattedDateString: Jan 3, 2001
在创建NSLocale对象时需要使用localeIdentifier,例如en_US,fr_FR,ja_JP和en_GB,这些标识符包含一个语言码(例如en代表英语)和一个地区码(例如US代表美国)。
还可以在localeIdentifier中设置更多信息,比如calendar的类型:
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN@calendar=chinese"];
[dateFormatter setLocale:locale];
得到输出:
formattedDateString: 庚辰年十二月九日
自定义格式
自定义固定的格式
通过NSDateFormatter中的setDateFormat:方法可以自定义日期/时间的格式。格式遵循Unicode Technical Standard #35。
官方文档中有这样的示例代码:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd 'at' HH:mm"];
NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:162000];
NSString *formattedDateString = [dateFormatter stringFromDate:date];
NSLog(@"formattedDateString: %@", formattedDateString);
// For US English, the output may be:
// formattedDateString: 2001-01-02 at 13:00
自定义用户友好的格式
好吧,在某个时刻之前,我只知道setDateFormat:
这个方法,直到有一天,我发现用“MMM d”格式在英文下显示良好(比如“Jan 13”),但是换到中文,就变成了奇怪的“1月 3”。
抓狂了很久,可是设计稿里要求不显示年份,所以NSDateFormatter预设的格式都不符合这一需求。直到我google出了dateFormatFromTemplate:options:locale:
这一方法。(实际上,需要显示给用户看的日期/时间不应该用setDateFormat:
来设置格式,可能会出现各种问题。)
dateFormatFromTemplate:options:locale:
这个方法会重新安排给定的自定义格式,来适应指定的locale。
官方的示例代码:
NSDateFormatter *dateFormatter = [NSDateFormatter new];
NSString *localeFormatString = [NSDateFormatter dateFormatFromTemplate:@"dMMM" options:0 locale:dateFormatter.locale];
dateFormatter.dateFormat = localeFormatString;
NSString *localizedString = [dateFormatter stringFromDate:[NSDate date]];
其中,dateFormatFromTemplate:options:locale:
中指定的格式只是表示了哪些元素(比如示例代码里的月份和日期)需要加入,这些元素的顺序是无关的。
官方举的几个例子:
Language (Region) | Date using format string “MMM d” | Date using template “dMMM” |
---|---|---|
English (United States) | Nov 13 | Nov 13 |
French (France) | nov. 13 | 13 nov. |
Chinese (China) | 11月13 | 11月13日 |
Tips
需要注册Notification来监听locale和时区的改变
这两个Notification分别是:
NSCurrentLocaleDidChangeNotification
和NSSystemTimeZoneDidChangeNotification
。NSDateFormatter(其实是任何一个NSFormatter)的创建都是很昂贵的,所以在实际的开发中,应该尽可能的重复利用每个formatter。推荐的做法是,每个需要用到的formatter只创建一次。
在iOS7和OS X v10.9之前,NSDateFormatter不是线程安全的。
可以通过设置
dateFormatter.doesRelativeDateFormatting = YES;
,让日期在某些语言下,显示为用户友好的"Today"、“Tomorrow”等。
比如:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterFullStyle];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
dateFormatter.doesRelativeDateFormatting = YES;
NSString *formattedDateString = [dateFormatter stringFromDate:[NSDate date]];
NSLog(@"formattedDateString: %@", formattedDateString);
输出:
formattedDateString: 今天 下午2:11
至于到底能把“今天”、“明天”、“后天”、“昨天”、“前天”等中的几个转换成relative format,则和语言有关。
(粗略尝试了一下,中文可以支持“前天”到“后天”,英文只能支持“yesterday”到“tomorrow”)
- iOS8之后,NSDateFormatter(其实也包括其他一些NSFormatter的子类)新增了一个叫formattingContext的property,主要用来确定英文等语言中,输出的字符串首字母是否需要大写的问题。
参考:
Internationalization and Localization Guide: Formatting Data Using the Locale Settings
Data Formatting Guide: Date Formatters
NSHipster NSFormatter
NSHipster NSLocale