格式化显示日期/时间的一点总结

作者: 卖萌凉 | 来源:发表于2015-04-29 18:04 被阅读3349次

    虽然“按照一定格式显示日期/时间”这个需求已经做了很多次了,但是好像每次做的时候都是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

    1. 需要注册Notification来监听locale和时区的改变
      这两个Notification分别是:
      NSCurrentLocaleDidChangeNotificationNSSystemTimeZoneDidChangeNotification

    2. NSDateFormatter(其实是任何一个NSFormatter)的创建都是很昂贵的,所以在实际的开发中,应该尽可能的重复利用每个formatter。推荐的做法是,每个需要用到的formatter只创建一次。

    3. 在iOS7和OS X v10.9之前,NSDateFormatter不是线程安全的。

    4. 可以通过设置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”)

    1. iOS8之后,NSDateFormatter(其实也包括其他一些NSFormatter的子类)新增了一个叫formattingContext的property,主要用来确定英文等语言中,输出的字符串首字母是否需要大写的问题。

    参考:

    Internationalization and Localization Guide: Formatting Data Using the Locale Settings
    Data Formatting Guide: Date Formatters
    NSHipster NSFormatter
    NSHipster NSLocale

    相关文章

      网友评论

        本文标题:格式化显示日期/时间的一点总结

        本文链接:https://www.haomeiwen.com/subject/hpdvfttx.html