日期格式化器 <- 數據格式化指南

你可以使用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閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,775評論 18 139
  • longaaaa =14200666; Console.WriteLine(aaaa.ToString("N0")...
    魚落于天閱讀 925評論 0 1
  • 在開發iOS程序時,有時候需要將時間格式調整成自己希望的格式,這個時候我們可以用NSDateFormatter類來...
    Rickie_Lambert閱讀 1,417評論 0 0
  • 我或許是太想與你遇見,連從風里走著也覺溫暖,你在原地,我過去就好。 你說:“如果我還沒有睡,就跟你講故事”,生活的...
    木木夕里閱讀 231評論 0 1
  • 昨日會見新友、舊友,只喝了幾盅茶,談了幾句話,靜坐,無他事。 新認識的小五,開一間花店,屋頂懸下風干的花苞,荷花、...
    蘇長亭閱讀 756評論 113 54