日期格式化器 <- 数据格式化指南

你可以使用dateFromString:方法来创建一个代表日期的字符串,你也可以使用stringFromDate:方法把字符串解析为一个日期对象。你还可以使用getObjectValue:forString:range:error:方法对解析的字符串的范围有更多控制,

日期格式化器中有很多可读写的属性。当你要向用户显示信息的时候,你通常只需要使用NSDateFormatter样式常量即可。该常量预定义了可以决定如何格式化显示日期的属性。但是,如果你想要生成一个精确格式的日期,你应该使用格式字符串(format string)。

如果你需要解析日期字符串,你采用的方法取决于你想要完成的任务。如果你想要解析用户的输入,你通常使用样式常量以便匹配他们的期望。如果你想解析从数据库或网络服务器得到的日期,你应该使用格式字符串。

在所有的情况中,你都应该考虑到格式化器使用用户区域(currentLocale)在用户偏好设置中叠加默认值。如果你想使用用户的区域,但却没有它们独特的设置时,你可以通过当前用户的区域(localIdentifier)来获取一个区域id,并用它来只做一个新的“标准”区域,然后把该标准区域设置为格式化器的locale。

使用格式化器样式来呈现用户偏好的日期和时间

NSDateFormatter可以让你很容易的使用系统偏好的“国际偏好”面板中的设置来格式化日期。NSDateFormatter的样式常量(NSDateFormatter style constants—NSDateFormatterNoStyle, NSDateFormatterShortStyle, NSDateFormatterMediumStyle, NSDateFormatterLongStyle, 和 NSDateFormatterFullStyle)指定一系列属性,这些属性根据用户的偏好决定如何显示日期。

你要分别使用setDateStyle:和setTimeStyle:方法为日期格式化器的组建指定日期和时间的格式。代码清单 1展示了你如何使用格式化器样式格式化一个日期。注意,使用NSDateFormatterNoStyle会抑制时间组件,并产生只包含日期的字符串。

代码清单 1 使用格式化器样式格式化一个日期

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".

使用格式字符串来指定自定义格式

一般来说,有两种情况你需要使用自定义格式:

  1. 对于固定格式字符串,例如网络日期。
  2. 对于和任何现有样式都不匹配的用户可见的元素。
固定格式

想要为日期格式化器指定一个自定义的固定格式,你要使用setDateFormat:。格式字符串使用来自Unicode Technical Standard #35的格式模式。不同的操作系统版本使用不同标准的版本:

  • OS X v10.9 和 iOS 7 使用 version tr35-31.
  • OS X v10.8 和 iOS 6 使用 version tr35-25.
  • iOS 5 使用 version tr35-19.
  • OS X v10.7 和 iOS 4.3 使用 version tr35-17.
  • iOS 4.0, iOS 4.1, 和 iOS 4.2 使用 version tr35-15.
  • iOS 3.2 使用 version tr35-12.
  • OS X v10.6, iOS 3.0, 和 iOS 3.1 使用 version tr35-10.
  • OS X v10.5 使用 version tr35-6.
  • OS X v10.4 使用 version tr35-4.

虽然原则上一个格式字符串指定一个固定格式,但是默认情况下,NSDateFormatter让人会考虑用户的偏好(包括区域设置)。当使用格式字符串的时候,你必须考虑下面几点:

  • NSDateFormatter会以用户选中的日历的方式处理你所解析的字符串中的数字。例如,如果用户选中了Buddhist日历,那么Gregorian日历的1467会被解析生成为2010的NSDate对象。(更多关于不同日历系统和如何使用它们的信息,参见Date and Time Programming Guide。)
  • 在iOS中,用户可以重写默认的AM/PM与24小时的时间设置。这可能导致你需要重写你设置的格式字符串。

注意Unicode格式字符串的格式,你应该在格式字符串中的字面量放在两个撇号之间('')。

下面的例子说明了使用格式字符串生成一个字符串:

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

这个例子要注意两点:

  1. 它使用yyyy来指定年分组件。一个常见的错误是使用YYYY。yyyy值得年是日历年,而YYYY指的年是ISO的年-周(year-week)日历。d大多数情况下,yyyy和YYYY产生同样的结果,但是它们也可能会不同。通常,你应该使用日历年。
  2. 时间的表示法可能是13:00。但是在iOS中,用户可能把24小时制关闭,那么时间的现实可能是1:00 pm。
显示给用户的日期自定义格式

想要显示一个包含特定元素设置的日期,要使用dateFormatFromTemplate:options:locale:方法。该方法生成你想使用的日期组件的格式字符串,但是要使用正确的标点和恰当的顺序(也就是,针对用户的区域和偏好定制)。然后你使用格式字符串创建格式化器。

例如,想要使用当前的区域创建格式化器来显示今天的星期、日、以及月,你可以这样写:

NSString *formatString = [NSDateFormatter dateFormatFromTemplate:@"EdMMM" options:0 locale:[NSLocale currentLocale]];

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:formatString];

NSString *todayString = [dateFormatter stringFromDate:[NSDate date]];
NSLog(@"todayString: %@", todayString);

想要理解这种需要,你要考虑要在何处显示星期、日、以及月。你不能使用格式化器样式(没有可以忽略年的样式)来创建日期的这种表达。但是,使用格式字符串可以方便的始终如一的创建正确的表示法。虽然一开始它看上去比较简单,但是也有复杂的地方:来自美国的用户通常期望的日期格式是“Mon, Jan 3”,然而来自英国的用户通常期望的日期格式是“Mon 31 Jan”。

下面这个例子说明了这一点:

NSLocale *usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
NSString *usFormatString = [NSDateFormatter dateFormatFromTemplate:@"EdMMM" options:0 locale:usLocale];
NSLog(@"usFormatterString: %@", usFormatString);
// Output: usFormatterString: EEE, MMM d.

NSLocale *gbLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"];
NSString *gbFormatString = [NSDateFormatter dateFormatFromTemplate:@"EdMMM" options:0 locale:gbLocale];
NSLog(@"gbFormatterString: %@", gbFormatString);
// Output: gbFormatterString: EEE d MMM.

解析日期字符串

除了继承于NSFormatter的方法(例如getObjectValue:forString:errorDescription:)之外,NSDateFormatter添加了dateFromString: 和 getObjectValue:forString:range:error:方法。这两个方法可以让你很方便的在代码中直接使用NSDateFormatter对象,并且可以比NSString格式化更复杂更方便的方式将日期格式化成字符串。

getObjectValue:forString:range:error:方法允许你指定字符串的子串来进行解析,它返回被真实解析了的子串(在错误的情况下,它会指出发生错误的区域)。它还返回一个NSError对象,该对象包含比getObjectValue:forString:errorDescription:(继承子NSFormatter)的错误字符串更丰富的信息。

如果你是用固定格式日期,你应该首先要设置日期格式化器的locale属性,用以匹配你的固定格式。大多数情况下,locale最好选择en_US_POSIX,它专门设计用来产出美国英语结果,无论用户和系统偏好如何。en_US_POSIX还不可变(如果美国在未来改变了格式日期的方式,en_US会做相应改变,但是en_US_POSIX不会),且跨平台(en_US_POSIX在iOS、OS X、以及其他平台上的表现是一样的)。

一旦你把en_US_POSIX作为日期格式化器的locale,你就可以设置格式字符串,日期格式化器为用户提供一致的行为。

代码清单 2 展示了如何使用NSDateFormatter解决上述两个任务。首先创建en_US_POSIX日期格式化器来解析RFC 3339日期字符串,使用固定日期格式字符串和UTC时区。然后,它创建一个标准的日期格式化器来,已将日期转化为字符串呈现给用户。

代码清单 2 解析RFC 3339日期时间

- (NSString *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString {
    /*
      Returns a user-visible date time string that corresponds to the specified
      RFC 3339 date time string. Note that this does not handle all possible
      RFC 3339 date time strings, just one of the most common styles.
     */

    NSDateFormatter *rfc3339DateFormatter = [[NSDateFormatter alloc] init];
    NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];

    [rfc3339DateFormatter setLocale:enUSPOSIXLocale];
    [rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
    [rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];

    // Convert the RFC 3339 date time string to an NSDate.
    NSDate *date = [rfc3339DateFormatter dateFromString:rfc3339DateTimeString];

    NSString *userVisibleDateTimeString;
    if (date != nil) {
        // Convert the date object to a user-visible date string.
        NSDateFormatter *userVisibleDateFormatter = [[NSDateFormatter alloc] init];
        assert(userVisibleDateFormatter != nil);

        [userVisibleDateFormatter setDateStyle:NSDateFormatterShortStyle];
        [userVisibleDateFormatter setTimeStyle:NSDateFormatterShortStyle];

        userVisibleDateTimeString = [userVisibleDateFormatter stringFromDate:date];
    }
    return userVisibleDateTimeString;
}

为了效率缓存格式化器

创建日期格式化器的操作会耗费一定资源。如果你频繁使用格式化器,通常缓存一个单例要比创建和处理多个实例要更有效率。其中一种方式是使用static变量。

代码清单 3 重新实现了在代码清单 2的方法,用以持有日期格式化器方便以后重用。

代码清单 3 使用缓存的格式化器来解析RFC 3339日期时间

static NSDateFormatter *sUserVisibleDateFormatter = nil;

- (NSString *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString {
    /*
      Returns a user-visible date time string that corresponds to the specified
      RFC 3339 date time string. Note that this does not handle all possible
      RFC 3339 date time strings, just one of the most common styles.
     */

    // If the date formatters aren't already set up, create them and cache them for reuse.
    static NSDateFormatter *sRFC3339DateFormatter = nil;
    if (sRFC3339DateFormatter == nil) {
        sRFC3339DateFormatter = [[NSDateFormatter alloc] init];
        NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];

        [sRFC3339DateFormatter setLocale:enUSPOSIXLocale];
        [sRFC3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
        [sRFC3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    }

    // Convert the RFC 3339 date time string to an NSDate.
    NSDate *date = [rfc3339DateFormatter dateFromString:rfc3339DateTimeString];

    NSString *userVisibleDateTimeString;
    if (date != nil) {
        if (sUserVisibleDateFormatter == nil) {
            sUserVisibleDateFormatter = [[NSDateFormatter alloc] init];
            [sUserVisibleDateFormatter setDateStyle:NSDateFormatterShortStyle];
            [sUserVisibleDateFormatter setTimeStyle:NSDateFormatterShortStyle];
        }
        // Convert the date object to a user-visible date string.
        userVisibleDateTimeString = [sUserVisibleDateFormatter stringFromDate:date];
    }
    return userVisibleDateTimeString;
}

如果你缓存了日期格式化器(或者其他任何基于用户当前区域的对象),你应该订阅NSCurrentLocaleDidChangeNotification通知,并在当前区域改变的时候更新你的缓存对象。代码清单 3中的代码在方法之外定义了sUserVisibleDateFormatter,以便其他代码(未显示)可以在必要时更新它。相反,sRFC3339DateFormatterDateFormatter在方法内被定义,根据设计,它不依赖于用户的区域设置。

注意:理论上,你可以使用自动更新区域(autoupdatingCurrentLocale)来创建区域,该区域会根据用户的区域设置改变而自动改变。在实践中,它当前还不用于日期格式化器。

考虑固定格式化和非本地化日期的Unix函数

对于在固定的、非本地化格式中的日期和时间,它们总是可以使用相同的日历,有时使用标准C库函数strptime_1 和 strftime_1或许更容易也更有效率。

要注意,C库也有当前区域的概念。要想保证固定日期格式,你应该给这些程序的loc参数传递NULL。这会让它们使用POSIX区域(也被称为C区域),这与Cocoa的en_US_POSIX是等价的。下例说明了这一点。

struct tm  sometime;
const char *formatString = "%Y-%m-%d %H:%M:%S %z";
(void) strptime_l("2005-07-01 12:00:00 -0700", formatString, &sometime, NULL);
NSLog(@"NSDate is %@", [NSDate dateWithTimeIntervalSince1970: mktime(&sometime)]);
// Output: NSDate is 2005-07-01 12:00:00 -0700
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • longaaaa =14200666; Console.WriteLine(aaaa.ToString("N0")...
    鱼落于天阅读 898评论 0 1
  • 在开发iOS程序时,有时候需要将时间格式调整成自己希望的格式,这个时候我们可以用NSDateFormatter类来...
    Rickie_Lambert阅读 1,391评论 0 0
  • 我或许是太想与你遇见,连从风里走着也觉温暖,你在原地,我过去就好。 你说:“如果我还没有睡,就跟你讲故事”,生活的...
    木木夕里阅读 220评论 0 1
  • 昨日会见新友、旧友,只喝了几盅茶,谈了几句话,静坐,无他事。 新认识的小五,开一间花店,屋顶悬下风干的花苞,荷花、...
    苏长亭阅读 746评论 113 54