作者:gabriel theodoropoulos,原文鏈接,原文日期:2015-10-18
譯者:ray16897188;校對:numbbbbb;定稿:Cee
如果問我在做過的所有項目中做的最多的事情,那處理日期絕對是榜上有名(譯注:本文中的「日期」是指代 NSDate 對象,同時包含「日(date)」 和「時(time)」這兩個元素)。毋庸置疑,無論工作量是多是少,開發者遲早需要「玩」一下 NSDate 類,去按某種方式處理一下日期。從簡單的將一個日期轉換成一個字符串到對日期做計算,總會有一個不變的事實:開發者必須在 iOS 編程中學會這個知識點。這并不難掌握,而且可以為以后更重要任務節省時間。在新手看來,對日期的操作很麻煩;然而事實并非如此。你需要做的就是掌握它。
在應用中對日期(NSDate)對象最常見的操作就是把它轉換成一個字符串對象,這樣就可以用正確的格式把它展示給用戶。反向操作也很常見:把字符串轉換成日期對象。然而日期的操作并不只有這些。下面是一個簡單的列表,列出了除上述操作之外可以對日期進行的其他操作:
- 日期之間的比較。
- 計算未來或者過去的日期,很簡單:用一個參考日期(比如當前日期)加上或者減去一段時間(天、月、年等等)。
- 計算不同日期之間的差值(比如算出兩個特定日期之間的時間間隔有多久)。
- 將一個日期按其組成元素(components)做分解,并對每個部分做分別訪問(天、月等等)。
上面列出的所有內容,包括日期和字符串之間的相互轉換,都是這篇教程要討論的主題。在接下來的各個小節中,你會發現只要你知道該用什么工具以及如何使用它們,你就能隨心所欲的對日期進行操作。
下面的鏈接清單里有很多重要的文章,供參考。如果需要深入了解某些特定知識點,別忘了點擊訪問一下:
關于 Demo App
嗯,這個教程我們不使用 demo 應用(是的,你沒看錯)。取而代之,我們這次用一個 Playground 來展示你將要看到的所有例子。我是特意這么做的,因為我的目的是給你提供豐富的、能更好的展示出關于 NSDate 方方面面的代碼。
你可以下載并在 Xcode 中打開這個寫好的 playground 文件,但我還是強烈建議你新建一個 Playground 文件,并測試下面章節中的每一個新代碼段。這樣會讓你更容易的去理解每個示例是如何工作的,除此之外你還可以修改代碼,實時觀察你的修改會如何影響生成的結果。
我給你的 playground 文件名是 PlayingWithDates,里面包含了所有的代碼。你自己的文件可以用相同的文件名,或者換一個,都無所謂。
基本概念
在我們開始查看日期相關的技術細節并思考能用它們做什么之前,先要確保每個人都已經掌握一些基本概念,這很重要。先從一個最簡單的開始:NSDate 對象。從程序角度來說這種對象包含了對日(date)和時(time)兩者的描述,所以它不僅僅可以幫我們處理「日」,還可以幫我們處理「時」。對于 NSDate 對象本身來說是沒有格式(formatting)這個概念的;和其他類中的所有屬性一樣,可以把日和時看做是屬性(properties)。只有在將一個日期對象轉換成一個字符串時,格式這個概念才會派上用場,下面的內容里我們會看到很多關于這個的細節。通常來講,記住你所需要的就是 NSDate 這個類,無論你只關心「日」、「時」或者兩者。
接下來我們會遇到的另一個類是 NSDateComponents。可以把這個類看做 NSDate 的「姊妹」類,因為這個類給開發者提供了一些極為有用的特性和操作。這個類的第一個要點是它可以將「日」部分或者「時」部分作為一個單獨的屬性顯示出來,所以我們可以直接訪問「日」或者「時」,然后在其他的任務中使用(比如對「日」或「時」的計算)。例如,一個 NSDateComponents 實例中的天和月在下面的代碼中表示為 day 和 month 屬性:
let dateComponents = NSDateComponents()
let day = dateComponents.day
let month = dateComponents.month
就這么簡單。當然訪問日期元素并將該日期的值傳遞給一個 NSDateComponents 對象需要先做一些強制轉換,這些我們之后再討論。
除上所述之外,NSDateComponents 這個類在用于計算未來或者過去的日期時也非常有用。當你想得到一個在某個特定日期之后或之前的那個日期時,你要做的就是加上或者減去合適的那一部分,最終就能轉換成一個新的日期。另外 NSDateComponents 也適合計算日期之間的差值。現在無需深入研究這兩個內容,我們一會兒會看到細節。
對于 NSCalendar 類,雖然它不會派上大用場,而且我們僅需要用它來實現 NSDate 和 NSDateComponents 相互轉換,但它在我們的日期游戲中也是重要的一員。關于它所支持的特性,本文不會再進行介紹。將日期從 NSDate 轉換成 NSDateComponents(或者反過來)的任務屬于 NSCalendar 類,按照慣例,做轉換需要一個特定的 c alendar(日歷)對象。實際上系統在做任何轉換之前都需要知道要用一個怎樣的 calendar 對象,從而才可能給出正確的結果(別忘了滿世界有太多不同的 calendar 對象,轉換出來的「天」、「月」等值會千差萬別)。你可以讀一些和 calendar 有關的文章(參考簡介里的鏈接),而在這里為圖簡便,我們會用 NSCalendar 的類方法 currentCalendar() 來得到用戶設置中指定的 calendar。
此外,在下一節中我們會使用一個特別好的工具,它就是 NSDateFormatter 類。它能夠實現 NSDate 對象到字符串、以及字符串到 NSDate 對象的轉換。它還可以使用預定義的日期樣式(date styles)來給最終的日期字符串制定格式,或是通過給出期望格式的描述來實現高度格式樣式定制。下面會有一些相關的例子,其中一些例子示范了雙向轉換。一個 NSDateFormatter 對象同樣也支持本地化(localization);我們所需要的就是給它提供一個有效的 NSLocale 對象,基于該給定的位置(locale)設置最終轉換出的對象就會正確顯示出來。
還有個類似的 NSDateComponentsFormatter 類,它可以將「日」和「時」部分作為輸入,輸出人類可讀的、有特定格式的日期字符串。對此這個類包含了很多方法(methods),在此教程的最后一部分我們會看見其中幾個;我們只討論在教程的例子中用到的那些知識點。
上面已經說了那么多,我們可以開始編程了,具體學習上面提到的每個類的用法。再說一次,建議你創建一個新的 playground 文件,然后把我介紹的每一條都試一下。沒有什么學習方法比親手做更有效果。
NSDate 和 String 之間的轉換
首先,我們使用 NSDate 獲得當前日期,并將它賦給一個常量以便訪問。和其他一些語言所要求的不同,獲得當前的日期并不需要調用類似 now() 或者 today() 的特殊方法。你所要做的就是初始化一個 NSDate 對象:
let currentDate = NSDate()
在 Xcode 的 playground 里敲入上面的語句,你會看到:
注意我們會在下面的代碼中多次使用到上面的這個值。現在初始化一個 NSDateFormatter 對象。它用來在 dates 和 strings 之間做轉換。如下:
let dateFormatter = NSDateFormatter()
除非是有其他明確的設定,否則 dateFormatter 會默認采用設備中的位置(locale)設置。盡管系統并不要求你去手動設置當前的位置,但如果需要的話你可以這么做:
dateFormatter.locale = NSLocale.currentLocale()
設一個不同的位置很容易:你僅需要知道與位置(locale)相匹配的位置標識符(locale identifier)是什么,然后指定給 locale 屬性即可:
dateFormatter.locale = NSLocale(localeIdentifier: "el_GR")
dateFormatter.locale = NSLocale(localeIdentifier: "fr_FR")
這兩行代碼展示了如何給 date formatter 去設置一個不同的位置(例子里分別是希臘和法國地區)。當然設置多個位置的值沒有意義,因為能起作用的僅僅是最后一個。你是不是想知道 locale 是怎么影響日期和字符串之間的轉換的呢?過會兒你就會得到答案。
用 Date formatter styles 為輸出結果設置格式
把一個日期對象(NSDate)轉換成一個字符串之前,你需要「告訴」date formatter 你要得到的字符串結果的格式是怎樣的。這里有兩種方法。第一種是使用預定義的 date formatter styles,第二種是使用某些特定的分類符(specifier)來手動指定最終輸出結果的格式。
先用第一種方法,我們需要使用 NSDateFormatterStyle enum。這個枚舉類型的每一個枚舉值都代表一種不同的格式樣式類型。第一個樣式是 FullStyle,下面的圖片是使用它的效果:
下面是上面代碼的文本,想復制的話隨意:
dateFormatter.dateStyle = NSDateFormatterStyle.FullStyle
var convertedDate = dateFormatter.stringFromDate(currentDate)
除了日期樣式(date style)之外,上面兩行代碼中的 stringFromDate: 方法也同等重要,這個方法實現了真正的轉換。當談及轉換時,我們實際上說的是這個方法,其余的只不過是自定義結果格式過程中所需的一些步驟。如果你想要在你的項目里做日期的轉換,那么這個方法對你來說肯定非常方便。
好,來看下一個樣式,Long Style:
文本形式的代碼:
dateFormatter.dateStyle = NSDateFormatterStyle.LongStyle
convertedDate = dateFormatter.stringFromDate(currentDate)
可以看到這種類型的樣式中不包含星期幾(和 Full Style 相比而言)。下面是 Medium Style:
dateFormatter.dateStyle = NSDateFormatterStyle.MediumStyle
convertedDate = dateFormatter.stringFromDate(currentDate)
最后是 Short Style:
dateFormatter.dateStyle = NSDateFormatterStyle.ShortStyle
convertedDate = dateFormatter.stringFromDate(currentDate)
現在你已經知道可用的 date formatter styles 都是什么了,你可以根據項目需求去使用它們。每種樣式的設置都會產生出一個不同的結果,可能其中有一種會適合你。
之前我說過 date formatter 的 locale 可以被設置成非默認值。現在我們已經看到如何使用 date formatter styles 做轉換,我們再來看看不同的 locale 值如何改變初始日期的字符串轉換結果。下面的例子中我會使用 Full Style,以及前面提到的兩個 locale identifier(希臘和法國)。
我想現在 locale 能做什么你已經很清楚了,好好使用它吧。
使用 Date format specifier
上面的 date formatter style 足以應對多數情況,但是我們無法通過修改這些格式來獲得不同于預設格式的結果。這種情況下我們還有另一個選擇,一個能設置自定義 date format 的能力,這個自定義的 date format 能夠正確描述你理想中的 date formatter 對象的的格式樣式。一般來說設置一個自定義的 date format 對以下兩種情況很適用:當 date formatter style 實現不了你所期望的輸出結果的樣式時(顯然),還有當你需要把一個復雜的日期字符串(比如“Thu, 08 Oct 2015 09:22:33 GMT”)轉換成一個日期對象時。
為了正確的設置一個 date format,一定要用一組分類符(specifier) 。Specifier 不過是一些簡單的字符,這些字符對 date formatter 對象來說有著特定的意義。在我給你具體的例子之前,先列出來一些在接下來的代碼中會使用到的format specifier:
- EEEE:表示星期幾(如 Monday)。使用 1-3 個字母表示周幾的縮略寫法。
- MMMM:月份的全寫(如 October)。使用 1-3 個字母表示月份的縮略寫法。
- dd:表示一個月里面日期的數字(如 09 或 15)。
- yyyy:4 個數字表示的年(如 2015)。
- HH:2 個數字表示的小時(如 08 或 19)。
- mm:2 個數字表示的分鐘(如 05 或者 54)。
- ss:2 個數字表示的秒。
- zzz:3 個字母表示的時區(如 GMT)。
- GGG:BC 或者 AD。
如果想查看 date format specifiers 的參考內容,建議訪問官方技術規范,你可以找到上面給出的 specifier 的使用方法,以及那些沒有列出的 specifier。
繼續我們的例子,看一下 format specifier 具體怎么用。這回我們把當前日期轉換成一個字符串,顯示成具有星期名稱、月的全寫,日期數字和年份的格式:
dateFormatter.dateFormat = "EEEE, MMMM dd, yyyy"
convertedDate = dateFormatter.stringFromDate(currentDate)
我想怎么用自定義的 date format 已經不需要額外的講解了,用法十分簡單。再來一個例子,轉換一下時間:
dateFormatter.dateFormat = "HH:mm:ss"
convertedDate = dateFormatter.stringFromDate(currentDate)
到現在為止我們看到的所有轉換都是從 NSDate 對象變成一個有特定格式的字符串。相反的操作也很有意思,之前關于 date formatter styles 和 format specifiers 的也同樣適用。把有既定格式的字符串轉換成一個 NSDate 對象的關鍵是要對 date formatter 的 dateFormat 屬性做出正確設置,然后調用 dateFromString: 方法。我們再看幾個例子:
var dateAsString = "24-12-2015 23:59"
dateFormatter.dateFormat = "dd-MM-yyyy HH:mm"
var newDate = dateFormatter.dateFromString(dateAsString)
再看一個更復雜的字符串,還包含了時區:
dateAsString = "Thu, 08 Oct 2015 09:22:33 GMT"
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
newDate = dateFormatter.dateFromString(dateAsString)
注意一下時間(09:22)是如何通過簡單的、在日期字符串中引入了一個時區而發生改變的(變成了 12:22)。這里沒有任何實際上的變化,僅僅是我所在的時區(EFT)的時間在 GMT 時區中的表示,基于上面的代碼,根據你自己的情況自由發揮吧。
到這里你已經基本上看到了為實現日期和字符串之間的轉換你所需要的所有知識點。你可以敲敲自己的代碼,試試你在上面所看到的那些,深入感受一下這些東西是如何工作的。
使用 NSDateComponents
很多時候你需要在項目里拆分一個日期對象,然后從中獲得特定組成元素的值。例如你可能會從一個日期對象中獲取它的日和月的值,或者從時間中獲得小時和分鐘的值。此種情況下你需要用到的工具就是 NSDateComponents 這個類。
NSDateComponents 類通常和 NSCalendar 類相結合來使用。具體點說,NSCalendar 方法實現了真正的從 NSDate 到 NSDateComponents 對象的轉換;以及我們待會兒會看到的,從日期的組成元素到日期對象的轉換。記好了這一點,這一節中我們首先要做的就是獲取當前的 calendar,把它賦給一個常量以便訪問:
let calendar = NSCalendar.currentCalendar()
現在我們看一個典型例子,一個 NSDate 對象是怎樣被轉換成一個 NSDateComponents 對象,之后我會做些講解:
let dateComponents = calendar.components([NSCalendarUnit.Day, NSCalendarUnit.Month, NSCalendarUnit.Year, NSCalendarUnit.WeekOfYear, NSCalendarUnit.Hour, NSCalendarUnit.Minute, NSCalendarUnit.Second, NSCalendarUnit.Nanosecond], fromDate: currentDate)
print("day = \(dateComponents.day)", "month = \(dateComponents.month)", "year = \(dateComponents.year)", "week of year = \(dateComponents.weekOfYear)", "hour = \(dateComponents.hour)", "minute = \(dateComponents.minute)", "second = \(dateComponents.second)", "nanosecond = \(dateComponents.nanosecond)" , separator: ", ", terminator: "")
上面第一行代碼用的方法是 NSCalendar 類的 components(_:fromDate:) 。該方法接受兩個參數:第二個參數是原日期對象,我們要從中獲得它的組成元素。但有意思是第一個參數,該方法要求第一個參數是一個元素為 NSCalendarUnit 屬性的數組,這些屬性對要從日期對象中抽取出的元素做出了描述。
NSCalendarUnit 是一個結構體,你可以從這里看到所有可用的屬性。上面的例子中,在你看到的代碼段截圖中給定的這些 calendar unit 值返回如下構成部分:
- Day
- Month
- Year
- Week of year
- Hour
- Minute
- Second
- Nanosecond
注意到在第一個參數數組中那些沒有列出的 calendar unit(日歷單元)在調用方法之后是不可用的。例如由于我們沒有將 NSCalendarUnit.TimeZone 這個單元包括進去,所以在剩下獲取到的元素中是訪問不到時區(timezone)的(比如用 print(dateComponents.timezone))。這么做的話會得到一個運行時錯誤。如果你需要額外的部分,你就必須再調用一次該方法,指定你想要的額外的calendar units。
從 date components 轉換到日期對象也很容易。這回不會涉及到對 calendar unit 的使用。所需要的就是初始化一個新的NSDateComponents對象,然后明確指定出所有需要的components元素(當然是根據你app的需要),然后調用 NSCalendar 類的 dateFromComponents 方法實現轉換。來看一下:
let components = NSDateComponents()
components.day = 5
components.month = 01
components.year = 2016
components.hour = 19
components.minute = 30
newDate = calendar.dateFromComponents(components)
前面的部分我們看過一個在把某特定格式的字符串轉換成一個日期對象時使用了 timezone 的例子。如果你足夠好奇想看看對一個日期對象設置不同 timezone 的結果,我們就將上面的代碼稍稍擴展一下,看看 timezone 的多種取值:
components.timeZone = NSTimeZone(abbreviation: "GMT")
newDate = calendar.dateFromComponents(components)
components.timeZone = NSTimeZone(abbreviation: "CST")
newDate = calendar.dateFromComponents(components)
components.timeZone = NSTimeZone(abbreviation: "CET")
newDate = calendar.dateFromComponents(components)
GMT = 格林威治標準時間
CST = 中國標準時間
CET = 歐洲中部時間
你可以在這里找到所有 timezone 的縮寫,還有一些很棒的在線工具。
現在你也知道如何去處理 NSDateComponents 對象了,那么咱們繼續來研究另一個有意思的東西。
比較日期和時間
處理日期的另外一個常見情況是需要對兩個日期對象進行比較,判斷哪一個代表著更早或者更晚,甚至比較這兩個是否為同一日期。概括來說我在下面會告訴你三種不同的比較日期對象的方式,但我不希望讓你有種哪個是最好或者最壞的觀點。很明顯這取決于你在你的應用中想要干什么,而每種方式和其他兩種都有些不同,哪種方法對你幫助最有效就選哪種。
在比較日期對象的方法給出之前,我們先創建兩個日期對象,在本節的例子中使用。首先設定日期格式(date formatter 的 dateFormat 屬性),然后把兩個日期格式的字符串轉換成兩個日期對象:
dateFormatter.dateFormat = "MMM dd, yyyy zzz"
dateAsString = "Oct 08, 2015 GMT"
var date1 = dateFormatter.dateFromString(dateAsString)!
dateAsString = "Oct 10, 2015 GMT"
var date2 = dateFormatter.dateFromString(dateAsString)!
先看看用來比較日期的第一個方式。如果你想要比較兩個日期中比較早的那一個,那么 NSDate 類會給你提供較大幫助,它分別提供了兩個方法,earlierDate: 和 laterDate:。這兩個方法的語法很簡單:
date1.earlierDate(date2)
原理如下:
- 如果 date1 對象比 date2 更早,那么上面的方法會返回 date1 的值。
- 如果 date2 對象比 date1 更早,那么上面的方法會返回 date2 的值。
- 如果兩者相等,則返回 date1。
同樣道理也使用于 laterDate: 方法。
現在來看我們的例子,使用我們之前創建的那兩個日期對象。下面的兩條指令分別使用了剛才提到的那兩個方法,為我們顯示出更早的和更晚的日期:
// Comparing dates - Method #1
print("Earlier date is: \(date1.earlierDate(date2))")
print("Later date is: \(date1.laterDate(date2))")
第二種比較兩個 NSDate 對象的方式使用的是 NSDate 類的 compare: 方法,以及 NSComparisonResult 枚舉類型。看下面的例子就會明白我的意思,但是我先提一下這種方式的語法和我上面例子中的很像。比較日期所得的結果是和所有的可能值作比較,用這種方式可以很容易的判斷出兩個日期是否相等、哪一個更早或者更晚。不說了,下面的代碼已經足夠明了:
Playground 中的結果如下:
可復制的代碼:
// Comparing dates - Method #2
if date1.compare(date2) == NSComparisonResult.OrderedDescending {
print("Date1 is Later than Date2")
}
else if date1.compare(date2) == NSComparisonResult.OrderedAscending {
print("Date1 is Earlier than Date2")
}
else if date1.compare(date2) == NSComparisonResult.OrderedSame {
print("Same dates")
}
比較兩個日期對象的第三種方式多少有些不同,因為這種方式引入了對 time intervals 的使用。實際上這種方式很簡單,它做的就是獲得自每個日期以來的時間間隔(每個日期和現在的時間間隔),然后做比較:
// Comparing dates - Method #3
if date1.timeIntervalSinceReferenceDate > date2.timeIntervalSinceReferenceDate {
print("Date1 is Later than Date2")
}
else if date1.timeIntervalSinceReferenceDate < date2.timeIntervalSinceReferenceDate {
print("Date1 is Earlier than Date2")
}
else {
print("Same dates")
}
上面的代碼也可以應用到對時間的比較。下面我給你最后一個例子,而這次 date1 和 date2 對象包含了對時間的表示。我再次使用 earlierDate: 方法,但另外還有一個,idEqualToDate:,很明顯,看名字就知道它是干什么的:
// Comparing time.
dateFormatter.dateFormat = "HH:mm:ss zzz"
dateAsString = "14:28:16 GMT"
date1 = dateFormatter.dateFromString(dateAsString)!
dateAsString = "19:53:12 GMT"
date2 = dateFormatter.dateFromString(dateAsString)!
if date1.earlierDate(date2) == date1 {
if date1.isEqualToDate(date2) {
print("Same time")
}
else {
print("\(date1) is earlier than \(date2)")
}
}
else {
print("\(date2) is earlier than \(date1)")
}
如果看到上面代碼中「2000-01-01」這個日期之后你感覺好奇或疑惑的話,不用擔心。NSDate 如果在沒有給定任何特定日期來做轉換的情況下會默認將其添加,它不會影響到這個日期中其他的元素(例子中其他的元素是時間)。
好了,到這里你也會怎么對日期做比較了。
計算出未來或過去的日期
處理日期另一個有趣的方面就是計算出一個將來或者過去的日期。我們之前看到的那些用法在這里會變得很方便,比如 NSCalendarUnit 結構體,或者 NSDateComponents 類。實際上,我會給你展示兩種不同的計算出其他日期的方式,第一種使用的就是 NSCalendar 類和 NSCalendarUnit 結構體,第二種使用的是 NSDateComponents 類。最后我會給出第三種方式,但是一般情況我不推薦使用(到那部分我會解釋為什么)。
一開始我們先記一下當前日期(是我寫這篇教程的日期),它會被用作我們的參考日期:
現在假設我們想把當前日期加上兩個月零五天,實際上還是寫下來比較好:
let monthsToAdd = 2
let daysToAdd = 5
我們現在就可以看一下第一種方式了,來得到想要的新日期吧。先給代碼,馬上解釋:
var calculatedDate = NSCalendar.currentCalendar().dateByAddingUnit(NSCalendarUnit.Month, value: monthsToAdd, toDate: currentDate, options: NSCalendarOptions.init(rawValue: 0))
calculatedDate = NSCalendar.currentCalendar().dateByAddingUnit(NSCalendarUnit.Day, value: daysToAdd, toDate: calculatedDate!, options: NSCalendarOptions.init(rawValue: 0))
如你所見,這里用到的方法是 NSCalendar 類的 dateByAddingUnit:value:toDate:options: 方法。這個方法的任務就是給一個現有的日期加上一個特定的 calendar unit(由第一個參數指定),并將這個加法的結果做為一個新的日期返回。我們開始想的是在當前日期的基礎上同時加兩個不同的 calendar unit,但很顯然這不現實。所以這里問題的關鍵是就要連續的調用該方法,每次設置其中的一個 calendar unit,從而得到最終結果。
下面是每次疊加之后 playground 顯示的結果:
上面的方式不錯,但是僅限于你要加的只有 1~2 個 calendar units,否則你得連續多次調用上面那個方法才行。
當需要疊加更多的 units 時,第二個,也是更傾向的方式是使用 NSDateComponents 這個類。為了演示,我們不會再引入其他的組成元素,除上面已經定好的月和日之外。在這兒要做的事情很簡單:首先初始化一個新的 NSDateComponents 對象,并給它設置之前定好的月和日。然后調用 NSCalendar 類的另一個叫做 dateByAddingComponents:toDate:options: 的方法,我們會立即得到一個新的 NSDate 對象,這個新對象即代表了最終想要的日期。
let newDateComponents = NSDateComponents()
newDateComponents.month = monthsToAdd
newDateComponents.day = daysToAdd
calculatedDate = NSCalendar.currentCalendar().dateByAddingComponents(newDateComponents, toDate: currentDate, options: NSCalendarOptions.init(rawValue: 0))
注意到上面的兩個代碼段中,我都沒給這兩個新介紹方法的最后一個參數做任何設置。而如果你想對這個可設選項了解更多的話,就去參考 NSCalendar 類的官方文檔。
第三種計算另一個日期方式不推薦對時間跨度大的情況使用,因為由于閏秒,閏年,夏令時等等會導致這種方式產生出錯誤結果。該方式的想法是給當前日期加上一個特定的時間間隔。我們會使用 NSDate 類的 dateByAddingTimeInterval: 方法來實現這個目的。下面的例子中我們算出來一個相當于是 1.5 小時的時間間隔,然后把它加到當前日期上:
let hoursToAddInSeconds: NSTimeInterval = 90 * 60
calculatedDate = currentDate.dateByAddingTimeInterval(hoursToAddInSeconds)
再強調一下,要做任何類型的日期計算的話,還是使用前兩種方式更安全。但這還是取決于你,選擇你更喜歡的那一種。
上面的三個例子都是給當前日期加上某些個組成元素。那現在用同樣方式給當前日期減去幾天,算出來那個過去的日期該怎么做?
下面的代碼示范了該怎么做。首先給當前日期加上一個特定天數的負值,這就可以得到一個屬于過去的日期了。然后把結果轉換成一個有適當格式的字符串,最后的結果...很有意思:
let numberOfDays = -5684718
calculatedDate = NSCalendar.currentCalendar().dateByAddingUnit(NSCalendarUnit.Day, value: numberOfDays, toDate: currentDate, options: NSCalendarOptions.init(rawValue: 0))
dateFormatter.dateFormat = "EEEE, MMM dd, yyyy GGG"
dateAsString = dateFormatter.stringFromDate(calculatedDate!)
以上所有的小代碼段示例可以完全給你講明白怎樣通過給某個參考日期加上或正或負的 calendar unit 來算出一個新的日期。自己隨便擴展一下上面的代碼吧,寫下你自己的代碼,你就會對這些技巧更加熟悉。
計算出日期的差值
和標題的意思一樣,這節講的是計算出兩個日期之間的差值,它是在你編程生涯中某個時間肯定要做的一個任務,顯然需要做不止一次。在這(教程的最后)一部分,我會告訴你計算出兩個 NSDate 對象之間差值的三種方式,你可以根據需要選出最適合你的那一種。
開始之前先定義兩個 NSDate 對象:
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateAsString = "2015-10-08 14:25:37"
date1 = dateFormatter.dateFromString(dateAsString)!
dateAsString = "2018-03-05 08:14:19"
date2 = dateFormatter.dateFromString(dateAsString)!
有了上面的日期對象,我們再來看一下如何獲取日期組成元素(date components)形式的日期差值(date difference )。我們會再次用到 NSCalendar 類,還有它的一個之前我們沒見過的方法。最后把日期組成元素打印出來看一下結果。很明顯當有了它,這個代表了日期差值的元素之后,想怎么做都取決于你了。來看下示范:
var diffDateComponents = NSCalendar.currentCalendar().components([NSCalendarUnit.Year, NSCalendarUnit.Month, NSCalendarUnit.Day, NSCalendarUnit.Hour, NSCalendarUnit.Minute, NSCalendarUnit.Second], fromDate: date1, toDate: date2, options: NSCalendarOptions.init(rawValue: 0))
print("The difference between dates is: \(diffDateComponents.year) years, \(diffDateComponents.month) months, \(diffDateComponents.day) days, \(diffDateComponents.hour) hours, \(diffDateComponents.minute) minutes, \(diffDateComponents.second) seconds")
這種新方式就是使用 components:fromDate:toDate:options: 方法,同樣它的第一個參數是一個 NSCalendarUnit 的數組。注意下如果第一個日期比第二個晚的話,返回值就會是負數。
在計算日期差值的另外兩種方式中,我們會第一次用到 NSDateComponentsFormatter 類,這個類有很多方法,能自動做出差值計算,然后返回一個帶有特定格式的字符串。先生成一個對象,并先指定它的一個屬性:
let dateComponentsFormatter = NSDateComponentsFormatter()
dateComponentsFormatter.unitsStyle = NSDateComponentsFormatterUnitsStyle.Full
unitsStyle 屬性告訴 dateComponentsFormatter 描述日期差值的那個字符串的格式應該是什么樣的,顯示出的日期組成元素應該是怎樣的樣式。比如用 Full 這個樣式,星期幾、月份等等就會顯示成常規的全寫(full-length)單詞。而如果我們用了 Abbreviated 樣式的話,則會顯示這些信息的縮寫。從這里你能看到關于單元樣式(units style)的全說明列表。
回到日期差值中來,這次我們要先算出兩個日期之間的時間間隔。然后這個間隔本身會作為一個參數傳遞給 NSDateComponentFormatter 類的 stringFromTimeInterval: 方法,結果就會以一個格式化好了的字符串形式返回。
let interval = date2.timeIntervalSinceDate(date1)
dateComponentsFormatter.stringFromTimeInterval(interval)
最后,在計算日期差值的最后一個方式中,兩個日期需要作為參數傳遞給 NSDateComponentsFormatter 類的 stringFromDate:toDate: 方法。然而用這個方法之前需要先滿足一個條件:allowedUnits 屬性必須要設置一個 calendar unit,否則該方法會返回一個 nil。所以我們就「告訴」這個方法我們想要怎樣的 unit,之后就等它給我們差值結果:
dateComponentsFormatter.allowedUnits = [NSCalendarUnit.Year, NSCalendarUnit.Month, NSCalendarUnit.Day, NSCalendarUnit.Hour, NSCalendarUnit.Minute, NSCalendarUnit.Second]
let autoFormattedDifference = dateComponentsFormatter.stringFromDate(date1, toDate: date2)
總結
簡介部分中我說過,處理 NSDate 對象這件事在你項目中非常常見,而且肯定無法避免。不可否認它并不是程序員最喜歡討論的話題,所以我就寫了前面的那些,在小例子中告訴你其實處理日期是很容易的。這篇教程中 NSDate 的方方面面,以及其他相關的類都有一個共同目標:教你小巧高效的用法,兩三行代碼就讓你把活兒搞定。希望這篇文章能給你做個指南,尤其如果你是一個新開發者。下篇文章出來之前,好好練習吧。
本文由 SwiftGG 翻譯組翻譯,已經獲得作者翻譯授權,最新文章請訪問 http://swift.gg。