雖然“按照一定格式顯示日期/時(shí)間”這個(gè)需求已經(jīng)做了很多次了,但是好像每次做的時(shí)候都是google一下人家寫的代碼,改改必要的東西貼進(jìn)自己的代碼里,其實(shí)并不知道這其中應(yīng)該注意些什么,以及怎樣的寫法是更合適的。恰逢今天突遇了一個(gè)小bug,就認(rèn)真查看了一下相關(guān)文檔,把該注意的地方記了下來(lái)~
使用NSDateFormatter自帶的格式來(lái)顯示日期/時(shí)間
NSDateFormatter自帶了幾種格式,使用這些格式來(lái)顯示日期/時(shí)間會(huì)受到用戶在系統(tǒng)設(shè)置中設(shè)置的個(gè)人偏好的影響。這些格式分別是:
| 格式名稱 | 對(duì)應(yīng)的日期格式 | 對(duì)應(yīng)的時(shí)間格式 |
|---|---|---|
| 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 |
通過(guò)給NSDateFormatter的對(duì)象分別設(shè)置dateStyle和timeStyle,可以將NSDate對(duì)象轉(zhuǎn)換成對(duì)應(yīng)格式的字符串。
官方文檔中有這樣的示例代碼:
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".
國(guó)際化
在使用NSDateFormatter的時(shí)候來(lái)格式化一個(gè)日期時(shí),實(shí)際上用戶的一些個(gè)人偏好也會(huì)默認(rèn)的被考慮進(jìn)去。上面的這段示例代碼在我的mac(語(yǔ)言為簡(jiǎn)體中文,北京時(shí)間)中就會(huì)輸出:
formattedDateString: 2001年1月3日
NSDateFormatter類中有一個(gè)property叫做locale,這個(gè)property會(huì)影響輸出的日期格式,默認(rèn)情況下,這個(gè)locale就是用戶的currentLocale。
NSLocale是與國(guó)際化相關(guān)的基礎(chǔ)類,使用[NSLocale currentLocale]可以得到當(dāng)前用戶關(guān)于國(guó)際化的一些設(shè)定,包括語(yǔ)言、日期和時(shí)間格式等。
locale不是用戶的默認(rèn)語(yǔ)言,雖然它們有時(shí)會(huì)很相似。官方文檔上舉了一個(gè)例子:一個(gè)居住在德國(guó)的說(shuō)英語(yǔ)的人可能會(huì)選擇英語(yǔ)作為他的默認(rèn)語(yǔ)言,選擇德國(guó)作為他的地區(qū),那么系統(tǒng)的文字將是英文,但是日期、時(shí)間和數(shù)字可能會(huì)跟隨德國(guó)習(xí)慣的格式,比如在時(shí)間上使用24小時(shí)制。
在OS X中,可以到“系統(tǒng)偏好設(shè)置->語(yǔ)言與地區(qū)”中設(shè)置當(dāng)前的locale,在iOS中則可以到“設(shè)置->通用->語(yǔ)言與地區(qū)”中進(jìn)行設(shè)置。
比如,把“日歷”從“公歷”改成“日本日歷”,則上一段代碼的輸出就變成了:
平成13年1月3日
使用代碼
NSLocale *locale = [dateFormatter locale];
NSCalendar *calendar = [locale objectForKey:NSLocaleCalendar];
NSLog(@"calendar: %@", calendar.calendarIdentifier);
可以查看當(dāng)前dateFormatter使用的calendar類型:
calendar: japanese
所以,如果想要在顯示日期/時(shí)間時(shí)排除用戶的locale設(shè)置,則需要自定義一個(gè)NSLocale對(duì)象。比如對(duì)dateFormatter設(shè)置:
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
[dateFormatter setLocale:locale];
此時(shí),雖然我的設(shè)備的語(yǔ)言是簡(jiǎn)體中文,日歷是日本日歷,示例代碼輸出的卻是:
formattedDateString: Jan 3, 2001
在創(chuàng)建NSLocale對(duì)象時(shí)需要使用localeIdentifier,例如en_US,fr_FR,ja_JP和en_GB,這些標(biāo)識(shí)符包含一個(gè)語(yǔ)言碼(例如en代表英語(yǔ))和一個(gè)地區(qū)碼(例如US代表美國(guó))。
還可以在localeIdentifier中設(shè)置更多信息,比如calendar的類型:
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN@calendar=chinese"];
[dateFormatter setLocale:locale];
得到輸出:
formattedDateString: 庚辰年十二月九日
自定義格式
自定義固定的格式
通過(guò)NSDateFormatter中的setDateFormat:方法可以自定義日期/時(shí)間的格式。格式遵循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
自定義用戶友好的格式
好吧,在某個(gè)時(shí)刻之前,我只知道setDateFormat:這個(gè)方法,直到有一天,我發(fā)現(xiàn)用“MMM d”格式在英文下顯示良好(比如“Jan 13”),但是換到中文,就變成了奇怪的“1月 3”。
抓狂了很久,可是設(shè)計(jì)稿里要求不顯示年份,所以NSDateFormatter預(yù)設(shè)的格式都不符合這一需求。直到我google出了dateFormatFromTemplate:options:locale:這一方法。(實(shí)際上,需要顯示給用戶看的日期/時(shí)間不應(yīng)該用setDateFormat:來(lái)設(shè)置格式,可能會(huì)出現(xiàn)各種問(wèn)題。)
dateFormatFromTemplate:options:locale: 這個(gè)方法會(huì)重新安排給定的自定義格式,來(lái)適應(yīng)指定的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:中指定的格式只是表示了哪些元素(比如示例代碼里的月份和日期)需要加入,這些元素的順序是無(wú)關(guān)的。
官方舉的幾個(gè)例子:
| 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
需要注冊(cè)Notification來(lái)監(jiān)聽(tīng)locale和時(shí)區(qū)的改變
這兩個(gè)Notification分別是:
NSCurrentLocaleDidChangeNotification和NSSystemTimeZoneDidChangeNotification。NSDateFormatter(其實(shí)是任何一個(gè)NSFormatter)的創(chuàng)建都是很昂貴的,所以在實(shí)際的開(kāi)發(fā)中,應(yīng)該盡可能的重復(fù)利用每個(gè)formatter。推薦的做法是,每個(gè)需要用到的formatter只創(chuàng)建一次。
在iOS7和OS X v10.9之前,NSDateFormatter不是線程安全的。
可以通過(guò)設(shè)置
dateFormatter.doesRelativeDateFormatting = YES;,讓日期在某些語(yǔ)言下,顯示為用戶友好的"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
至于到底能把“今天”、“明天”、“后天”、“昨天”、“前天”等中的幾個(gè)轉(zhuǎn)換成relative format,則和語(yǔ)言有關(guān)。
(粗略嘗試了一下,中文可以支持“前天”到“后天”,英文只能支持“yesterday”到“tomorrow”)
- iOS8之后,NSDateFormatter(其實(shí)也包括其他一些NSFormatter的子類)新增了一個(gè)叫formattingContext的property,主要用來(lái)確定英文等語(yǔ)言中,輸出的字符串首字母是否需要大寫的問(wèn)題。
參考:
Internationalization and Localization Guide: Formatting Data Using the Locale Settings
Data Formatting Guide: Date Formatters
NSHipster NSFormatter
NSHipster NSLocale