翻譯自# NSCalendar Additions
日期,一個很普通的時間和它的實現(xiàn)間往往有著巨大的差異净响,里面隱藏的多方面的復(fù)雜性妻味。其中包括亞秒級的精度,重疊單元瓷式,不同的地理位置的時區(qū)邊界替饿,語言和語法上的本地化差異,以及為了夏令時的轉(zhuǎn)換和閏年調(diào)整贸典,而在標(biāo)準(zhǔn)時間中添加刪除整塊的時間等等视卢,里面有太多的東西需要處理。
再開始進(jìn)行任何日期計算相關(guān)的任務(wù)前廊驼,我們有必要深入了解一下我們手中已有的工具据过。相比寫上千個版本的dateIsTomorrow,我覺得更好的辦法是使用Foundation方法。你有在用NSdateComponents嗎妒挎?你有指定正確的日歷單元嗎绳锅?你的代碼在2100年2月28號還能正常工作嗎?
但事實上:大家一直都在使用那些已經(jīng)非常熟悉了的APIs酝掩,很少有人注意到鳞芙,NSCalendar 已經(jīng)添加了一系列功能十分強(qiáng)大的方法去操作計算日期
let calendar = NSCalendar.currentCalendar()
從全新的日期組件存取與日期比較方法,到強(qiáng)大的日期插值與枚舉方法期虾,有太多的東西被我們忽視了原朝。接下來讓我們抽點時間來了解一下吧。
便利的日期組件存取
哇, NSDateComponents 真是既實用又靈活镶苞,但當(dāng)我只是想知道間隔的小時數(shù)時竿拆,它用起來感覺又太麻煩了。不要慌宾尚, NSCalendar 來救你了丙笋!
let hour = calendar.component(.CalendarUnitHour, fromDate: NSDate())
這樣就好多了。NSCalendar煌贴,你還有哪些本事?
getEra(_:year:month:day:fromDate:)
:根據(jù)傳入的日期引用返回紀(jì)元御板,年,月牛郑,日怠肋。不需要的參數(shù)可以傳入 nil/NULL。
-getEra(_:yearForWeekOfYear:weekOfYear:weekday:fromDate:)
: 根據(jù)傳入的日期引用返回紀(jì)元淹朋,年笙各,當(dāng)年第幾周钉答,星期幾。不需要的參數(shù)可以傳入 nil/NULL杈抢。getHour(_:minute:second:nanosecond:fromDate:)
: 根據(jù)傳入的日期引用返回時間信息数尿,然后 nil/NULL 巴拉巴拉, 你懂的。
NSDateComponents惶楼,剛才我是逗你玩呢右蹦,我收回前面吐槽你的話。下面還有不少屬于你的方法:
componentsInTimeZone(_:fromDate:)
: 根據(jù)傳入的的日期和時區(qū)返回一個 NSDateComponents 實例歼捐。components(_:fromDateComponents:toDateComponents:options:)
: 返回兩個 NSDateComponents 實例間的差異何陆。如果有未賦值的組件,該方法會使用默認(rèn)值豹储,所以我們傳入的實例至少得設(shè)置了年屬性贷盲。options參數(shù)暫時沒有用,傳 nil/0 就行剥扣。
日期比較
雖然直接比較 NSDate 是件挺簡單的事巩剖,但一些更有意義的比較可能變得驚人的復(fù)雜。兩個 NSDate 實例是同一天朦乏?同一小時?亦或是同一周氧骤?
現(xiàn)在沒必要發(fā)愁了呻疹,NSCalendar 提供了大量的比較方法:
isDateInToday(_:)
: 如果傳入的日期是當(dāng)天返回 true 。
isDateInTomorrow(_:)
: 如果傳入的日期是明天返回 true 筹陵。
isDateInYesterday(_:)
: 如果傳入的日期是昨天返回 true 刽锤。
isDateInWeekend(_:)
: 如果傳入的日期是周末返回 true 。
isDate(_:inSameDayAsDate:)
: 如果兩個 NSDate 實例在同一天返回 true - 沒必要再去獲取日期部件進(jìn)行比較了朦佩。
isDate(_:equalToDate:toUnitGranularity:)
: 如果傳入的日期在同一指定單位內(nèi)返回 true 并思。這意味著,兩個在同一周的日期實例調(diào)用calendar.isDate(tuesday, equalToDate: thursday, toUnitGranularity: .CalendarUnitWeekOfYear)
方法時會返回 true 语稠,就算他們不在同一個月也是如此宋彼。
compareDate(_:toDate:toUnitGranularity:): 返回一個 NSComparisonResult,當(dāng)做和任何指定區(qū)間內(nèi)的日期相等仙畦。
date(_:matchesComponents:)`: 如果日期匹配指定的部件返回 true 输涕。
日期插值
接下來講一些根據(jù)起始點尋找下一日期的方法。你可以基于一個NSDateComponents實例慨畸,一個指定的日期組建莱坎,或者特定的時分秒,去找到下一個(或上一個)日期寸士。所有這些方法都需要一個NSCalendarOptions參數(shù)去提供更加精細(xì)的控制檐什,特別是一開始我們沒能找到準(zhǔn)確的匹配的時候碴卧,痛可以幫我們確定如何選定下一個日期。
NSCalendarOptions
對于所有的方法NSCalendarOptions來說.SearchBackwards乃正,最簡單的選擇是顛倒每個搜索的方向住册。向后搜索被構(gòu)造為返回類似于向前搜索的日期。例如烫葬,向后搜索hour11 的前一個日期會給你11點界弧,而不是11點59分,即使11:59在技術(shù)上會在“11點之前”向后搜索搭综。事實上垢箕,向后搜索是直觀的,直到你思考它兑巾,然后直到你想得更多条获,才會是直覺的。這.SearchBackwards是容易的部分應(yīng)該給你一些前瞻的想法蒋歌。
接下來的 NSCalendarOptions 選項能夠幫助我們處理那些 “消失” 的時間帅掘。舉個最直觀的例子來說,當(dāng)你進(jìn)行一個短時窗搜索時碰到夏令時調(diào)整堂油,時間提前了一個小時修档。或者搜索時遇到類似 2 月 或者 4 月 31 號府框,它都能幫我們跳過這些缺失的時間吱窝。
當(dāng)遇到缺失的時間時,如果我們設(shè)置了 NSCalendarOptions.MatchStrictly迫靖,相關(guān)方法會根據(jù)傳入的組件尋找一個 精確 的匹配院峡。如果沒有設(shè)置的話,那么必須提供 .MatchNextTime, .MatchNextTimePreservingSmallerUnits, 和 .MatchPreviousTimePreservingSmallerUnits 中的任一項系宜。這些選項決定了如何處理我們請求時遇到的時間缺失問題照激。
這種情況,往往一例勝千言:
// 2015 年情人節(jié)盹牧,早上 9 點
let valentines = cal.dateWithEra(1, year: 2015, month: 2, day: 14, hour: 9, minute: 0, second: 0, nanosecond: 0)!
// 為了找到月的最后一天俩垃, 我設(shè)置一個日期組件然后把 `day` 設(shè)成 31:
let components = NSDateComponents()
components.day = 31
使用精確匹配會在三月找到下個 31 號,如下:
calendar.nextDateAfterDate(valentines, matchingComponents: components, options: .MatchStrictly)
// Mar 31, 2015, 12:00 AM
不使用精確匹配的話汰寓,nextDateAfterDate 方法會在找到匹配的指定天數(shù)前就在二月底停了下來吆寨,然后在下個月繼續(xù)尋找。 可見踩寇,你所提供的選項決定了最終返回的具體日期啄清。舉例來說,使用 .MatchNextTime 選項找到下一個合適的日子:
calendar.nextDateAfterDate(valentines, matchingComponents: components, options: .MatchNextTime)
// Mar 1, 2015, 12:00 AM
類似的,當(dāng)使用 .MatchNextTimePreservingSmallerUnits 選項時會找到下一天辣卒,但是所有比指定單元 NSCalendarUnitDay 要小的單元會被保留下來:
calendar.nextDateAfterDate(valentines, matchingComponents: components, options: .MatchNextTimePreservingSmallerUnits)
// Mar 1, 2015, 9:00 AM
最后掷贾, 使用 .MatchPreviousTimePreservingSmallerUnits 選項會在 另一個 方向上解決缺失的時間問題, 和前面一樣荣茫,保留較小的單元想帅,然后找到匹配的前一天:
calendar.nextDateAfterDate(valentines, matchingComponents: components, options: .MatchPreviousTimePreservingSmallerUnits)
// Feb 28, 2015, 9:00 AM
除了這里的 NDateComponents 外,還值得注意的是 nextDateAfterDate 方法有兩種變化:
// 匹配指定的日歷單元
cal.nextDateAfterDate(valentines, matchingUnit: .CalendarUnitDay, value: 31, options: .MatchStrictly)
// March 31, 2015, 12:00 AM
// 匹配時啡莉,分港准,秒
cal.nextDateAfterDate(valentines, matchingHour: 15, minute: 30, second: 0, options: .MatchNextTime)
// Feb 14, 2015, 3:30 PM
枚舉插值日期
NSCalendar 提供了一個API去枚舉日期, 所以大家沒有必要反復(fù)的調(diào)用 nextDateAfterDate 方法咧欣。enumerateDatesStartingAfterDate(_:matchingComponents:options:usingBlock:) 方法根據(jù)提供的日期組件和選項浅缸,依次獲取匹配的日期∑枪荆可以將 stop 屬性設(shè)為 true 去停止枚舉衩椒。
來試試這個 NSCalendarOptions 的新方法吧,下面展示了一種獲取隨后50個閏年的方法
let leapYearComponents = NSDateComponents()
leapYearComponents.month = 2
leapYearComponents.day = 29
var dateCount = 0
cal.enumerateDatesStartingAfterDate(NSDate(), matchingComponents: leapYearComponents, options: .MatchStrictly | .SearchBackwards)
{ (date: NSDate!, exactMatch: Bool, stop: UnsafeMutablePointer<ObjCBool>) in
println(date)
if ++dateCount == 50 {
// .memory 用來獲取一個 UnsafeMutablePointer 屬性的值
stop.memory = true
}
}
// 2012-02-29 05:00:00 +0000
// 2008-02-29 05:00:00 +0000
// 2004-02-29 05:00:00 +0000
// 2000-02-29 05:00:00 +0000
// ...
處理周末
要想找周末的話哮兰,記住下面兩個 NSCalendar 方法就行:
nextWeekendStartDate(_:interval:options:afterDate)
: 根據(jù)傳入的前兩個參數(shù)返回下個周末的開始時間和長度毛萌。如果當(dāng)前的地區(qū)和日歷未提供對周末屬性的支持,該方法會返回 false 喝滞。唯一相關(guān)的屬性是 .SearchBackwards阁将。(例子在下面。)rangeOfWeekendStartDate(_:interval:containingDate)
: 根據(jù)傳入的前兩個參數(shù)返回 包含 該日期的周末右遭。如果傳入的日期并不在周末或者當(dāng)前的地區(qū)和日歷未提供對周末屬性的支持做盅,該方法會返回 false 。
本地化的日歷符號
就好像所有的新功能還不夠狸演,NSCalendar還可以訪問一整套正確本地化的日歷符號言蛇,從而可以快速訪問月份名稱僻他,一周中的幾天等等宵距。每一組符號進(jìn)一步列舉在兩個軸上:(1)符號的長度和(2)它作為獨立的名詞或作為日期的一部分使用。
理解這個第二個屬性對于本地化是非常重要的吨拗,因為一些語言满哪,尤其是斯拉夫語言,對不同的上下文使用不同的名詞用例劝篷。例如哨鸭,日歷需要standaloneMonthSymbols為其標(biāo)題使用其中一種變體,而不是monthSymbols用于格式化特定日期的變體娇妓。
為了您的細(xì)讀像鸡,下面NSCalendar是俄文專欄中獨立符號的不同值的所有可用符號表
en_US | ru_RU | |
---|---|---|
monthSymbols | January, February, March… | января, февраля, марта… |
shortMonthSymbols | Jan, Feb, Mar… | янв., февр., марта… |
veryShortMonthSymbols | J, F, M, A… | Я, Ф, М, А… |
standaloneMonthSymbols | January, February, March… | Январь, Февраль, Март… |
shortStandaloneMonthSymbols | Jan, Feb, Mar… | Янв., Февр., Март… |
veryShortStandaloneMonthSymbols | J, F, M, A… | Я, Ф, М, А… |
weekdaySymbols | Sunday, Monday, Tuesday, Wednesday… | воскресенье, понедельник, вторник, среда… |
shortWeekdaySymbols | Sun, Mon, Tue, Wed… | вс, пн, вт, ср… |
veryShortWeekdaySymbols | S, M, T, W… | вс, пн, вт, ср… |
standaloneWeekdaySymbols | Sunday, Monday, Tuesday, Wednesday… | Воскресенье, Понедельник, Вторник, Среда… |
shortStandaloneWeekdaySymbols | Sun, Mon, Tue, Wed… | Вс, Пн, Вт, Ср… |
veryShortStandaloneWeekdaySymbols | S, M, T, W… | В, П, В, С… |
AMSymbol | AM | AM |
PMSymbol | PM | PM |
quarterSymbols | 1st quarter, 2nd quarter, 3rd quarter, 4th quarter | 1-й квартал, 2-й квартал, 3-й квартал, 4-й квартал |
shortQuarterSymbols | Q1, Q2, Q3, Q4 | 1-й кв., 2-й кв., 3-й кв., 4-й кв. |
standaloneQuarterSymbols | 1st quarter, 2nd quarter, 3rd quarter, 4th quarter | 1-й квартал, 2-й квартал, 3-й квартал, 4-й квартал |
shortStandaloneQuarterSymbols | Q1, Q2, Q3, Q4 | 1-й кв., 2-й кв., 3-й кв., 4-й кв. |
eraSymbols | BC, AD | до н. э., н. э. |
longEraSymbols | Before Christ, Anno Domini | до н.э., н.э. |
注: 這些符號在 NSDateFormatter 中也可以使用。
小知識
這里給大家介紹一個非常好用的 NSCalendar
擴(kuò)展集( 點 我 )哈恰,有了它我們使用訪問日期組件和搜索周末方法時只估,可以不用把值傳進(jìn)又傳出志群。比如,獲取指定的日期組件就變得簡單的多:
// built-in
var hour = 0
var minute = 0
calendar.getHour(&hour, minute: &minute, second: nil, nanosecond: nil, fromDate: NSDate())
// Swift化
let (hour, minute, _, _) = calendar.getTimeFromDate(NSDate())
獲取下一個周末的日期范圍:
// built-in
var startDate: NSDate?
var interval: NSTimeInterval = 0
let success = cal.nextWeekendStartDate(&startDate, interval: &interval, options: nil, afterDate: NSDate())
if success, let startDate = startDate {
println("start: \(startDate), interval: \(interval)")
}
// Swift化
if let nextWeekend = cal.nextWeekendAfterDate(NSDate()) {
println("start: \(nextWeekend.startDate), interval: \(nextWeekend.interval)")
}
這下復(fù)雜的日歷計算嚇不到你們了蛔钙。有了 NSCalendar 提供的這些新功能锌云,你可以很快的解決你碰到的問題。