你可以使用dateFromString:方法來創(chuàng)建一個代表日期的字符串,你也可以使用stringFromDate:方法把字符串解析為一個日期對象。你還可以使用getObjectValue:forString:range:error:方法對解析的字符串的范圍有更多控制,
日期格式化器中有很多可讀寫的屬性。當你要向用戶顯示信息的時候,你通常只需要使用NSDateFormatter樣式常量即可。該常量預(yù)定義了可以決定如何格式化顯示日期的屬性。但是,如果你想要生成一個精確格式的日期,你應(yīng)該使用格式字符串(format string)。
如果你需要解析日期字符串,你采用的方法取決于你想要完成的任務(wù)。如果你想要解析用戶的輸入,你通常使用樣式常量以便匹配他們的期望。如果你想解析從數(shù)據(jù)庫或網(wǎng)絡(luò)服務(wù)器得到的日期,你應(yīng)該使用格式字符串。
在所有的情況中,你都應(yīng)該考慮到格式化器使用用戶區(qū)域(currentLocale)在用戶偏好設(shè)置中疊加默認值。如果你想使用用戶的區(qū)域,但卻沒有它們獨特的設(shè)置時,你可以通過當前用戶的區(qū)域(localIdentifier)來獲取一個區(qū)域id,并用它來只做一個新的“標準”區(qū)域,然后把該標準區(qū)域設(shè)置為格式化器的locale。
使用格式化器樣式來呈現(xiàn)用戶偏好的日期和時間
NSDateFormatter可以讓你很容易的使用系統(tǒng)偏好的“國際偏好”面板中的設(shè)置來格式化日期。NSDateFormatter的樣式常量(NSDateFormatter style constants—NSDateFormatterNoStyle, NSDateFormatterShortStyle, NSDateFormatterMediumStyle, NSDateFormatterLongStyle, 和 NSDateFormatterFullStyle)指定一系列屬性,這些屬性根據(jù)用戶的偏好決定如何顯示日期。
你要分別使用setDateStyle:和setTimeStyle:方法為日期格式化器的組建指定日期和時間的格式。代碼清單 1展示了你如何使用格式化器樣式格式化一個日期。注意,使用NSDateFormatterNoStyle會抑制時間組件,并產(chǎn)生只包含日期的字符串。
代碼清單 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".
使用格式字符串來指定自定義格式
一般來說,有兩種情況你需要使用自定義格式:
- 對于固定格式字符串,例如網(wǎng)絡(luò)日期。
- 對于和任何現(xiàn)有樣式都不匹配的用戶可見的元素。
固定格式
想要為日期格式化器指定一個自定義的固定格式,你要使用setDateFormat:。格式字符串使用來自Unicode Technical Standard #35的格式模式。不同的操作系統(tǒng)版本使用不同標準的版本:
- 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讓人會考慮用戶的偏好(包括區(qū)域設(shè)置)。當使用格式字符串的時候,你必須考慮下面幾點:
- NSDateFormatter會以用戶選中的日歷的方式處理你所解析的字符串中的數(shù)字。例如,如果用戶選中了Buddhist日歷,那么Gregorian日歷的1467會被解析生成為2010的NSDate對象。(更多關(guān)于不同日歷系統(tǒng)和如何使用它們的信息,參見Date and Time Programming Guide。)
- 在iOS中,用戶可以重寫默認的AM/PM與24小時的時間設(shè)置。這可能導(dǎo)致你需要重寫你設(shè)置的格式字符串。
注意Unicode格式字符串的格式,你應(yīng)該在格式字符串中的字面量放在兩個撇號之間('')。
下面的例子說明了使用格式字符串生成一個字符串:
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
這個例子要注意兩點:
- 它使用yyyy來指定年分組件。一個常見的錯誤是使用YYYY。yyyy值得年是日歷年,而YYYY指的年是ISO的年-周(year-week)日歷。d大多數(shù)情況下,yyyy和YYYY產(chǎn)生同樣的結(jié)果,但是它們也可能會不同。通常,你應(yīng)該使用日歷年。
- 時間的表示法可能是13:00。但是在iOS中,用戶可能把24小時制關(guān)閉,那么時間的現(xiàn)實可能是1:00 pm。
顯示給用戶的日期自定義格式
想要顯示一個包含特定元素設(shè)置的日期,要使用dateFormatFromTemplate:options:locale:方法。該方法生成你想使用的日期組件的格式字符串,但是要使用正確的標點和恰當?shù)捻樞颍ㄒ簿褪牵槍τ脩舻膮^(qū)域和偏好定制)。然后你使用格式字符串創(chuàng)建格式化器。
例如,想要使用當前的區(qū)域創(chuàng)建格式化器來顯示今天的星期、日、以及月,你可以這樣寫:
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);
想要理解這種需要,你要考慮要在何處顯示星期、日、以及月。你不能使用格式化器樣式(沒有可以忽略年的樣式)來創(chuàng)建日期的這種表達。但是,使用格式字符串可以方便的始終如一的創(chuàng)建正確的表示法。雖然一開始它看上去比較簡單,但是也有復(fù)雜的地方:來自美國的用戶通常期望的日期格式是“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格式化更復(fù)雜更方便的方式將日期格式化成字符串。
getObjectValue:forString:range:error:方法允許你指定字符串的子串來進行解析,它返回被真實解析了的子串(在錯誤的情況下,它會指出發(fā)生錯誤的區(qū)域)。它還返回一個NSError對象,該對象包含比getObjectValue:forString:errorDescription:(繼承子NSFormatter)的錯誤字符串更豐富的信息。
如果你是用固定格式日期,你應(yīng)該首先要設(shè)置日期格式化器的locale屬性,用以匹配你的固定格式。大多數(shù)情況下,locale最好選擇en_US_POSIX,它專門設(shè)計用來產(chǎn)出美國英語結(jié)果,無論用戶和系統(tǒng)偏好如何。en_US_POSIX還不可變(如果美國在未來改變了格式日期的方式,en_US會做相應(yīng)改變,但是en_US_POSIX不會),且跨平臺(en_US_POSIX在iOS、OS X、以及其他平臺上的表現(xiàn)是一樣的)。
一旦你把en_US_POSIX作為日期格式化器的locale,你就可以設(shè)置格式字符串,日期格式化器為用戶提供一致的行為。
代碼清單 2 展示了如何使用NSDateFormatter解決上述兩個任務(wù)。首先創(chuàng)建en_US_POSIX日期格式化器來解析RFC 3339日期字符串,使用固定日期格式字符串和UTC時區(qū)。然后,它創(chuàng)建一個標準的日期格式化器來,已將日期轉(zhuǎn)化為字符串呈現(xiàn)給用戶。
代碼清單 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;
}
為了效率緩存格式化器
創(chuàng)建日期格式化器的操作會耗費一定資源。如果你頻繁使用格式化器,通常緩存一個單例要比創(chuàng)建和處理多個實例要更有效率。其中一種方式是使用static變量。
代碼清單 3 重新實現(xiàn)了在代碼清單 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;
}
如果你緩存了日期格式化器(或者其他任何基于用戶當前區(qū)域的對象),你應(yīng)該訂閱NSCurrentLocaleDidChangeNotification通知,并在當前區(qū)域改變的時候更新你的緩存對象。代碼清單 3中的代碼在方法之外定義了sUserVisibleDateFormatter,以便其他代碼(未顯示)可以在必要時更新它。相反,sRFC3339DateFormatterDateFormatter在方法內(nèi)被定義,根據(jù)設(shè)計,它不依賴于用戶的區(qū)域設(shè)置。
注意:理論上,你可以使用自動更新區(qū)域(autoupdatingCurrentLocale)來創(chuàng)建區(qū)域,該區(qū)域會根據(jù)用戶的區(qū)域設(shè)置改變而自動改變。在實踐中,它當前還不用于日期格式化器。
考慮固定格式化和非本地化日期的Unix函數(shù)
對于在固定的、非本地化格式中的日期和時間,它們總是可以使用相同的日歷,有時使用標準C庫函數(shù)strptime_1 和 strftime_1或許更容易也更有效率。
要注意,C庫也有當前區(qū)域的概念。要想保證固定日期格式,你應(yīng)該給這些程序的loc參數(shù)傳遞NULL。這會讓它們使用POSIX區(qū)域(也被稱為C區(qū)域),這與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