Swift 的 NSDate 初學(xué)者指南

作者:gabriel theodoropoulos,原文鏈接,原文日期:2015-10-18
譯者:ray16897188;校對:numbbbbb;定稿:Cee

如果問我在做過的所有項(xiàng)目中做的最多的事情,那處理日期絕對是榜上有名(譯注:本文中的「日期」是指代 NSDate 對象,同時(shí)包含「日(date)」 和「時(shí)(time)」這兩個(gè)元素)。毋庸置疑,無論工作量是多是少,開發(fā)者遲早需要「玩」一下 NSDate 類,去按某種方式處理一下日期。從簡單的將一個(gè)日期轉(zhuǎn)換成一個(gè)字符串到對日期做計(jì)算,總會(huì)有一個(gè)不變的事實(shí):開發(fā)者必須在 iOS 編程中學(xué)會(huì)這個(gè)知識(shí)點(diǎn)。這并不難掌握,而且可以為以后更重要任務(wù)節(jié)省時(shí)間。在新手看來,對日期的操作很麻煩;然而事實(shí)并非如此。你需要做的就是掌握它。

在應(yīng)用中對日期(NSDate)對象最常見的操作就是把它轉(zhuǎn)換成一個(gè)字符串對象,這樣就可以用正確的格式把它展示給用戶。反向操作也很常見:把字符串轉(zhuǎn)換成日期對象。然而日期的操作并不只有這些。下面是一個(gè)簡單的列表,列出了除上述操作之外可以對日期進(jìn)行的其他操作:

  • 日期之間的比較。
  • 計(jì)算未來或者過去的日期,很簡單:用一個(gè)參考日期(比如當(dāng)前日期)加上或者減去一段時(shí)間(天、月、年等等)。
  • 計(jì)算不同日期之間的差值(比如算出兩個(gè)特定日期之間的時(shí)間間隔有多久)。
  • 將一個(gè)日期按其組成元素(components)做分解,并對每個(gè)部分做分別訪問(天、月等等)。

上面列出的所有內(nèi)容,包括日期和字符串之間的相互轉(zhuǎn)換,都是這篇教程要討論的主題。在接下來的各個(gè)小節(jié)中,你會(huì)發(fā)現(xiàn)只要你知道該用什么工具以及如何使用它們,你就能隨心所欲的對日期進(jìn)行操作。

下面的鏈接清單里有很多重要的文章,供參考。如果需要深入了解某些特定知識(shí)點(diǎn),別忘了點(diǎn)擊訪問一下:

關(guān)于 Demo App

嗯,這個(gè)教程我們不使用 demo 應(yīng)用(是的,你沒看錯(cuò))。取而代之,我們這次用一個(gè) Playground 來展示你將要看到的所有例子。我是特意這么做的,因?yàn)槲业哪康氖墙o你提供豐富的、能更好的展示出關(guān)于 NSDate 方方面面的代碼。

你可以下載并在 Xcode 中打開這個(gè)寫好的 playground 文件,但我還是強(qiáng)烈建議你新建一個(gè) Playground 文件,并測試下面章節(jié)中的每一個(gè)新代碼段。這樣會(huì)讓你更容易的去理解每個(gè)示例是如何工作的,除此之外你還可以修改代碼,實(shí)時(shí)觀察你的修改會(huì)如何影響生成的結(jié)果。

我給你的 playground 文件名是 PlayingWithDates,里面包含了所有的代碼。你自己的文件可以用相同的文件名,或者換一個(gè),都無所謂。

基本概念

在我們開始查看日期相關(guān)的技術(shù)細(xì)節(jié)并思考能用它們做什么之前,先要確保每個(gè)人都已經(jīng)掌握一些基本概念,這很重要。先從一個(gè)最簡單的開始:NSDate 對象。從程序角度來說這種對象包含了對日(date)時(shí)(time)兩者的描述,所以它不僅僅可以幫我們處理「日」,還可以幫我們處理「時(shí)」。對于 NSDate 對象本身來說是沒有格式(formatting)這個(gè)概念的;和其他類中的所有屬性一樣,可以把日和時(shí)看做是屬性(properties)。只有在將一個(gè)日期對象轉(zhuǎn)換成一個(gè)字符串時(shí),格式這個(gè)概念才會(huì)派上用場,下面的內(nèi)容里我們會(huì)看到很多關(guān)于這個(gè)的細(xì)節(jié)。通常來講,記住你所需要的就是 NSDate 這個(gè)類,無論你只關(guān)心「日」、「時(shí)」或者兩者。

接下來我們會(huì)遇到的另一個(gè)類是 NSDateComponents??梢园堰@個(gè)類看做 NSDate 的「姊妹」類,因?yàn)檫@個(gè)類給開發(fā)者提供了一些極為有用的特性和操作。這個(gè)類的第一個(gè)要點(diǎn)是它可以將「日」部分或者「時(shí)」部分作為一個(gè)單獨(dú)的屬性顯示出來,所以我們可以直接訪問「日」或者「時(shí)」,然后在其他的任務(wù)中使用(比如對「日」或「時(shí)」的計(jì)算)。例如,一個(gè) NSDateComponents 實(shí)例中的天和月在下面的代碼中表示為 daymonth 屬性:

let dateComponents = NSDateComponents()
let day = dateComponents.day
let month = dateComponents.month

就這么簡單。當(dāng)然訪問日期元素并將該日期的值傳遞給一個(gè) NSDateComponents 對象需要先做一些強(qiáng)制轉(zhuǎn)換,這些我們之后再討論。

除上所述之外,NSDateComponents 這個(gè)類在用于計(jì)算未來或者過去的日期時(shí)也非常有用。當(dāng)你想得到一個(gè)在某個(gè)特定日期之后或之前的那個(gè)日期時(shí),你要做的就是加上或者減去合適的那一部分,最終就能轉(zhuǎn)換成一個(gè)新的日期。另外 NSDateComponents 也適合計(jì)算日期之間的差值?,F(xiàn)在無需深入研究這兩個(gè)內(nèi)容,我們一會(huì)兒會(huì)看到細(xì)節(jié)。

對于 NSCalendar 類,雖然它不會(huì)派上大用場,而且我們僅需要用它來實(shí)現(xiàn) NSDateNSDateComponents 相互轉(zhuǎn)換,但它在我們的日期游戲中也是重要的一員。關(guān)于它所支持的特性,本文不會(huì)再進(jìn)行介紹。將日期從 NSDate 轉(zhuǎn)換成 NSDateComponents(或者反過來)的任務(wù)屬于 NSCalendar 類,按照慣例,做轉(zhuǎn)換需要一個(gè)特定的 c alendar(日歷)對象。實(shí)際上系統(tǒng)在做任何轉(zhuǎn)換之前都需要知道要用一個(gè)怎樣的 calendar 對象,從而才可能給出正確的結(jié)果(別忘了滿世界有太多不同的 calendar 對象,轉(zhuǎn)換出來的「天」、「月」等值會(huì)千差萬別)。你可以讀一些和 calendar 有關(guān)的文章(參考簡介里的鏈接),而在這里為圖簡便,我們會(huì)用 NSCalendar 的類方法 currentCalendar() 來得到用戶設(shè)置中指定的 calendar。

此外,在下一節(jié)中我們會(huì)使用一個(gè)特別好的工具,它就是 NSDateFormatter 類。它能夠?qū)崿F(xiàn) NSDate 對象到字符串、以及字符串到 NSDate 對象的轉(zhuǎn)換。它還可以使用預(yù)定義的日期樣式(date styles)來給最終的日期字符串制定格式,或是通過給出期望格式的描述來實(shí)現(xiàn)高度格式樣式定制。下面會(huì)有一些相關(guān)的例子,其中一些例子示范了雙向轉(zhuǎn)換。一個(gè) NSDateFormatter 對象同樣也支持本地化(localization);我們所需要的就是給它提供一個(gè)有效的 NSLocale 對象,基于該給定的位置(locale)設(shè)置最終轉(zhuǎn)換出的對象就會(huì)正確顯示出來。

還有個(gè)類似的 NSDateComponentsFormatter 類,它可以將「日」和「時(shí)」部分作為輸入,輸出人類可讀的、有特定格式的日期字符串。對此這個(gè)類包含了很多方法(methods),在此教程的最后一部分我們會(huì)看見其中幾個(gè);我們只討論在教程的例子中用到的那些知識(shí)點(diǎn)。

上面已經(jīng)說了那么多,我們可以開始編程了,具體學(xué)習(xí)上面提到的每個(gè)類的用法。再說一次,建議你創(chuàng)建一個(gè)新的 playground 文件,然后把我介紹的每一條都試一下。沒有什么學(xué)習(xí)方法比親手做更有效果。

NSDate 和 String 之間的轉(zhuǎn)換

首先,我們使用 NSDate 獲得當(dāng)前日期,并將它賦給一個(gè)常量以便訪問。和其他一些語言所要求的不同,獲得當(dāng)前的日期并不需要調(diào)用類似 now() 或者 today() 的特殊方法。你所要做的就是初始化一個(gè) NSDate 對象:

let currentDate = NSDate()

在 Xcode 的 playground 里敲入上面的語句,你會(huì)看到:

注意我們會(huì)在下面的代碼中多次使用到上面的這個(gè)值?,F(xiàn)在初始化一個(gè) NSDateFormatter 對象。它用來在 dates 和 strings 之間做轉(zhuǎn)換。如下:

let dateFormatter = NSDateFormatter()

除非是有其他明確的設(shè)定,否則 dateFormatter 會(huì)默認(rèn)采用設(shè)備中的位置(locale)設(shè)置。盡管系統(tǒng)并不要求你去手動(dòng)設(shè)置當(dāng)前的位置,但如果需要的話你可以這么做:

dateFormatter.locale = NSLocale.currentLocale()

設(shè)一個(gè)不同的位置很容易:你僅需要知道與位置(locale)相匹配的位置標(biāo)識(shí)符(locale identifier)是什么,然后指定給 locale 屬性即可:

dateFormatter.locale = NSLocale(localeIdentifier: "el_GR")
dateFormatter.locale = NSLocale(localeIdentifier: "fr_FR")

這兩行代碼展示了如何給 date formatter 去設(shè)置一個(gè)不同的位置(例子里分別是希臘和法國地區(qū))。當(dāng)然設(shè)置多個(gè)位置的值沒有意義,因?yàn)槟芷鹱饔玫膬H僅是最后一個(gè)。你是不是想知道 locale 是怎么影響日期和字符串之間的轉(zhuǎn)換的呢?過會(huì)兒你就會(huì)得到答案。

用 Date formatter styles 為輸出結(jié)果設(shè)置格式

把一個(gè)日期對象(NSDate)轉(zhuǎn)換成一個(gè)字符串之前,你需要「告訴」date formatter 你要得到的字符串結(jié)果的格式是怎樣的。這里有兩種方法。第一種是使用預(yù)定義的 date formatter styles,第二種是使用某些特定的分類符(specifier)來手動(dòng)指定最終輸出結(jié)果的格式。

先用第一種方法,我們需要使用 NSDateFormatterStyle enum。這個(gè)枚舉類型的每一個(gè)枚舉值都代表一種不同的格式樣式類型。第一個(gè)樣式是 FullStyle,下面的圖片是使用它的效果:

下面是上面代碼的文本,想復(fù)制的話隨意:

dateFormatter.dateStyle = NSDateFormatterStyle.FullStyle
var convertedDate = dateFormatter.stringFromDate(currentDate)

除了日期樣式(date style)之外,上面兩行代碼中的 stringFromDate: 方法也同等重要,這個(gè)方法實(shí)現(xiàn)了真正的轉(zhuǎn)換。當(dāng)談及轉(zhuǎn)換時(shí),我們實(shí)際上說的是這個(gè)方法,其余的只不過是自定義結(jié)果格式過程中所需的一些步驟。如果你想要在你的項(xiàng)目里做日期的轉(zhuǎn)換,那么這個(gè)方法對你來說肯定非常方便。

好,來看下一個(gè)樣式,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)

現(xiàn)在你已經(jīng)知道可用的 date formatter styles 都是什么了,你可以根據(jù)項(xiàng)目需求去使用它們。每種樣式的設(shè)置都會(huì)產(chǎn)生出一個(gè)不同的結(jié)果,可能其中有一種會(huì)適合你。

之前我說過 date formatter 的 locale 可以被設(shè)置成非默認(rèn)值?,F(xiàn)在我們已經(jīng)看到如何使用 date formatter styles 做轉(zhuǎn)換,我們再來看看不同的 locale 值如何改變初始日期的字符串轉(zhuǎn)換結(jié)果。下面的例子中我會(huì)使用 Full Style,以及前面提到的兩個(gè) locale identifier(希臘和法國)。

我想現(xiàn)在 locale 能做什么你已經(jīng)很清楚了,好好使用它吧。

使用 Date format specifier

上面的 date formatter style 足以應(yīng)對多數(shù)情況,但是我們無法通過修改這些格式來獲得不同于預(yù)設(shè)格式的結(jié)果。這種情況下我們還有另一個(gè)選擇,一個(gè)能設(shè)置自定義 date format 的能力,這個(gè)自定義的 date format 能夠正確描述你理想中的 date formatter 對象的的格式樣式。一般來說設(shè)置一個(gè)自定義的 date format 對以下兩種情況很適用:當(dāng) date formatter style 實(shí)現(xiàn)不了你所期望的輸出結(jié)果的樣式時(shí)(顯然),還有當(dāng)你需要把一個(gè)復(fù)雜的日期字符串(比如“Thu, 08 Oct 2015 09:22:33 GMT”)轉(zhuǎn)換成一個(gè)日期對象時(shí)。

為了正確的設(shè)置一個(gè) date format,一定要用一組分類符(specifier) 。Specifier 不過是一些簡單的字符,這些字符對 date formatter 對象來說有著特定的意義。在我給你具體的例子之前,先列出來一些在接下來的代碼中會(huì)使用到的format specifier:

  • EEEE:表示星期幾(如 Monday)。使用 1-3 個(gè)字母表示周幾的縮略寫法。
  • MMMM:月份的全寫(如 October)。使用 1-3 個(gè)字母表示月份的縮略寫法。
  • dd:表示一個(gè)月里面日期的數(shù)字(如 09 或 15)。
  • yyyy:4 個(gè)數(shù)字表示的年(如 2015)。
  • HH:2 個(gè)數(shù)字表示的小時(shí)(如 08 或 19)。
  • mm:2 個(gè)數(shù)字表示的分鐘(如 05 或者 54)。
  • ss:2 個(gè)數(shù)字表示的秒。
  • zzz:3 個(gè)字母表示的時(shí)區(qū)(如 GMT)。
  • GGG:BC 或者 AD。

如果想查看 date format specifiers 的參考內(nèi)容,建議訪問官方技術(shù)規(guī)范,你可以找到上面給出的 specifier 的使用方法,以及那些沒有列出的 specifier。

繼續(xù)我們的例子,看一下 format specifier 具體怎么用。這回我們把當(dāng)前日期轉(zhuǎn)換成一個(gè)字符串,顯示成具有星期名稱、月的全寫,日期數(shù)字和年份的格式:

dateFormatter.dateFormat = "EEEE, MMMM dd, yyyy"
convertedDate = dateFormatter.stringFromDate(currentDate)

我想怎么用自定義的 date format 已經(jīng)不需要額外的講解了,用法十分簡單。再來一個(gè)例子,轉(zhuǎn)換一下時(shí)間:

dateFormatter.dateFormat = "HH:mm:ss"
convertedDate = dateFormatter.stringFromDate(currentDate)

到現(xiàn)在為止我們看到的所有轉(zhuǎn)換都是從 NSDate 對象變成一個(gè)有特定格式的字符串。相反的操作也很有意思,之前關(guān)于 date formatter styles 和 format specifiers 的也同樣適用。把有既定格式的字符串轉(zhuǎn)換成一個(gè) NSDate 對象的關(guān)鍵是要對 date formatter 的 dateFormat 屬性做出正確設(shè)置,然后調(diào)用 dateFromString: 方法。我們再看幾個(gè)例子:

var dateAsString = "24-12-2015 23:59"
dateFormatter.dateFormat = "dd-MM-yyyy HH:mm"
var newDate = dateFormatter.dateFromString(dateAsString)

再看一個(gè)更復(fù)雜的字符串,還包含了時(shí)區(qū):

dateAsString = "Thu, 08 Oct 2015 09:22:33 GMT"
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
newDate = dateFormatter.dateFromString(dateAsString)

注意一下時(shí)間(09:22)是如何通過簡單的、在日期字符串中引入了一個(gè)時(shí)區(qū)而發(fā)生改變的(變成了 12:22)。這里沒有任何實(shí)際上的變化,僅僅是我所在的時(shí)區(qū)(EFT)的時(shí)間在 GMT 時(shí)區(qū)中的表示,基于上面的代碼,根據(jù)你自己的情況自由發(fā)揮吧。

到這里你已經(jīng)基本上看到了為實(shí)現(xiàn)日期和字符串之間的轉(zhuǎn)換你所需要的所有知識(shí)點(diǎn)。你可以敲敲自己的代碼,試試你在上面所看到的那些,深入感受一下這些東西是如何工作的。

使用 NSDateComponents

很多時(shí)候你需要在項(xiàng)目里拆分一個(gè)日期對象,然后從中獲得特定組成元素的值。例如你可能會(huì)從一個(gè)日期對象中獲取它的日和月的值,或者從時(shí)間中獲得小時(shí)和分鐘的值。此種情況下你需要用到的工具就是 NSDateComponents 這個(gè)類。

NSDateComponents 類通常和 NSCalendar 類相結(jié)合來使用。具體點(diǎn)說,NSCalendar 方法實(shí)現(xiàn)了真正的從 NSDateNSDateComponents 對象的轉(zhuǎn)換;以及我們待會(huì)兒會(huì)看到的,從日期的組成元素到日期對象的轉(zhuǎn)換。記好了這一點(diǎn),這一節(jié)中我們首先要做的就是獲取當(dāng)前的 calendar,把它賦給一個(gè)常量以便訪問:

let calendar = NSCalendar.currentCalendar()

現(xiàn)在我們看一個(gè)典型例子,一個(gè) NSDate 對象是怎樣被轉(zhuǎn)換成一個(gè) NSDateComponents 對象,之后我會(huì)做些講解:

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:) 。該方法接受兩個(gè)參數(shù):第二個(gè)參數(shù)是原日期對象,我們要從中獲得它的組成元素。但有意思是第一個(gè)參數(shù),該方法要求第一個(gè)參數(shù)是一個(gè)元素為 NSCalendarUnit 屬性的數(shù)組,這些屬性對要從日期對象中抽取出的元素做出了描述。

NSCalendarUnit 是一個(gè)結(jié)構(gòu)體,你可以從這里看到所有可用的屬性。上面的例子中,在你看到的代碼段截圖中給定的這些 calendar unit 值返回如下構(gòu)成部分:

  • Day
  • Month
  • Year
  • Week of year
  • Hour
  • Minute
  • Second
  • Nanosecond

注意到在第一個(gè)參數(shù)數(shù)組中那些沒有列出的 calendar unit(日歷單元)在調(diào)用方法之后是不可用的。例如由于我們沒有將 NSCalendarUnit.TimeZone 這個(gè)單元包括進(jìn)去,所以在剩下獲取到的元素中是訪問不到時(shí)區(qū)(timezone)的(比如用 print(dateComponents.timezone))。這么做的話會(huì)得到一個(gè)運(yùn)行時(shí)錯(cuò)誤。如果你需要額外的部分,你就必須再調(diào)用一次該方法,指定你想要的額外的calendar units。

從 date components 轉(zhuǎn)換到日期對象也很容易。這回不會(huì)涉及到對 calendar unit 的使用。所需要的就是初始化一個(gè)新的NSDateComponents對象,然后明確指定出所有需要的components元素(當(dāng)然是根據(jù)你app的需要),然后調(diào)用 NSCalendar 類的 dateFromComponents 方法實(shí)現(xiàn)轉(zhuǎn)換。來看一下:

let components = NSDateComponents()
components.day = 5
components.month = 01
components.year = 2016
components.hour = 19
components.minute = 30
newDate = calendar.dateFromComponents(components)

前面的部分我們看過一個(gè)在把某特定格式的字符串轉(zhuǎn)換成一個(gè)日期對象時(shí)使用了 timezone 的例子。如果你足夠好奇想看看對一個(gè)日期對象設(shè)置不同 timezone 的結(jié)果,我們就將上面的代碼稍稍擴(kuò)展一下,看看 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 = 格林威治標(biāo)準(zhǔn)時(shí)間
CST = 中國標(biāo)準(zhǔn)時(shí)間
CET = 歐洲中部時(shí)間

你可以在這里找到所有 timezone 的縮寫,還有一些很棒的在線工具。

現(xiàn)在你也知道如何去處理 NSDateComponents 對象了,那么咱們繼續(xù)來研究另一個(gè)有意思的東西。

比較日期和時(shí)間

處理日期的另外一個(gè)常見情況是需要對兩個(gè)日期對象進(jìn)行比較,判斷哪一個(gè)代表著更早或者更晚,甚至比較這兩個(gè)是否為同一日期。概括來說我在下面會(huì)告訴你三種不同的比較日期對象的方式,但我不希望讓你有種哪個(gè)是最好或者最壞的觀點(diǎn)。很明顯這取決于你在你的應(yīng)用中想要干什么,而每種方式和其他兩種都有些不同,哪種方法對你幫助最有效就選哪種。

在比較日期對象的方法給出之前,我們先創(chuàng)建兩個(gè)日期對象,在本節(jié)的例子中使用。首先設(shè)定日期格式(date formatter 的 dateFormat 屬性),然后把兩個(gè)日期格式的字符串轉(zhuǎn)換成兩個(gè)日期對象:

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)!

先看看用來比較日期的第一個(gè)方式。如果你想要比較兩個(gè)日期中比較早的那一個(gè),那么 NSDate 類會(huì)給你提供較大幫助,它分別提供了兩個(gè)方法,earlierDate:laterDate:。這兩個(gè)方法的語法很簡單:

date1.earlierDate(date2)

原理如下:

  • 如果 date1 對象比 date2 更早,那么上面的方法會(huì)返回 date1 的值。
  • 如果 date2 對象比 date1 更早,那么上面的方法會(huì)返回 date2 的值。
  • 如果兩者相等,則返回 date1。

同樣道理也使用于 laterDate: 方法。

現(xiàn)在來看我們的例子,使用我們之前創(chuàng)建的那兩個(gè)日期對象。下面的兩條指令分別使用了剛才提到的那兩個(gè)方法,為我們顯示出更早的和更晚的日期:

// Comparing dates - Method #1
print("Earlier date is: \(date1.earlierDate(date2))")
print("Later date is: \(date1.laterDate(date2))")

第二種比較兩個(gè) NSDate 對象的方式使用的是 NSDate 類的 compare: 方法,以及 NSComparisonResult 枚舉類型??聪旅娴睦泳蜁?huì)明白我的意思,但是我先提一下這種方式的語法和我上面例子中的很像。比較日期所得的結(jié)果是和所有的可能值作比較,用這種方式可以很容易的判斷出兩個(gè)日期是否相等、哪一個(gè)更早或者更晚。不說了,下面的代碼已經(jīng)足夠明了:

Playground 中的結(jié)果如下:

可復(fù)制的代碼:

// 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")
}

比較兩個(gè)日期對象的第三種方式多少有些不同,因?yàn)檫@種方式引入了對 time intervals 的使用。實(shí)際上這種方式很簡單,它做的就是獲得自每個(gè)日期以來的時(shí)間間隔(每個(gè)日期和現(xiàn)在的時(shí)間間隔),然后做比較:

// 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")
}

上面的代碼也可以應(yīng)用到對時(shí)間的比較。下面我給你最后一個(gè)例子,而這次 date1date2 對象包含了對時(shí)間的表示。我再次使用 earlierDate: 方法,但另外還有一個(gè),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」這個(gè)日期之后你感覺好奇或疑惑的話,不用擔(dān)心。NSDate 如果在沒有給定任何特定日期來做轉(zhuǎn)換的情況下會(huì)默認(rèn)將其添加,它不會(huì)影響到這個(gè)日期中其他的元素(例子中其他的元素是時(shí)間)。

好了,到這里你也會(huì)怎么對日期做比較了。

計(jì)算出未來或過去的日期

處理日期另一個(gè)有趣的方面就是計(jì)算出一個(gè)將來或者過去的日期。我們之前看到的那些用法在這里會(huì)變得很方便,比如 NSCalendarUnit 結(jié)構(gòu)體,或者 NSDateComponents 類。實(shí)際上,我會(huì)給你展示兩種不同的計(jì)算出其他日期的方式,第一種使用的就是 NSCalendar 類和 NSCalendarUnit 結(jié)構(gòu)體,第二種使用的是 NSDateComponents 類。最后我會(huì)給出第三種方式,但是一般情況我不推薦使用(到那部分我會(huì)解釋為什么)。

一開始我們先記一下當(dāng)前日期(是我寫這篇教程的日期),它會(huì)被用作我們的參考日期:

現(xiàn)在假設(shè)我們想把當(dāng)前日期加上兩個(gè)月零五天,實(shí)際上還是寫下來比較好:

let monthsToAdd = 2
let daysToAdd = 5

我們現(xiàn)在就可以看一下第一種方式了,來得到想要的新日期吧。先給代碼,馬上解釋:

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: 方法。這個(gè)方法的任務(wù)就是給一個(gè)現(xiàn)有的日期加上一個(gè)特定的 calendar unit(由第一個(gè)參數(shù)指定),并將這個(gè)加法的結(jié)果做為一個(gè)新的日期返回。我們開始想的是在當(dāng)前日期的基礎(chǔ)上同時(shí)加兩個(gè)不同的 calendar unit,但很顯然這不現(xiàn)實(shí)。所以這里問題的關(guān)鍵是就要連續(xù)的調(diào)用該方法,每次設(shè)置其中的一個(gè) calendar unit,從而得到最終結(jié)果。

下面是每次疊加之后 playground 顯示的結(jié)果:

上面的方式不錯(cuò),但是僅限于你要加的只有 1~2 個(gè) calendar units,否則你得連續(xù)多次調(diào)用上面那個(gè)方法才行。

當(dāng)需要疊加更多的 units 時(shí),第二個(gè),也是更傾向的方式是使用 NSDateComponents 這個(gè)類。為了演示,我們不會(huì)再引入其他的組成元素,除上面已經(jīng)定好的月和日之外。在這兒要做的事情很簡單:首先初始化一個(gè)新的 NSDateComponents 對象,并給它設(shè)置之前定好的月和日。然后調(diào)用 NSCalendar 類的另一個(gè)叫做 dateByAddingComponents:toDate:options: 的方法,我們會(huì)立即得到一個(gè)新的 NSDate 對象,這個(gè)新對象即代表了最終想要的日期。

let newDateComponents = NSDateComponents()
newDateComponents.month = monthsToAdd
newDateComponents.day = daysToAdd

calculatedDate = NSCalendar.currentCalendar().dateByAddingComponents(newDateComponents, toDate: currentDate, options: NSCalendarOptions.init(rawValue: 0))

注意到上面的兩個(gè)代碼段中,我都沒給這兩個(gè)新介紹方法的最后一個(gè)參數(shù)做任何設(shè)置。而如果你想對這個(gè)可設(shè)選項(xiàng)了解更多的話,就去參考 NSCalendar 類的官方文檔

第三種計(jì)算另一個(gè)日期方式不推薦對時(shí)間跨度大的情況使用,因?yàn)橛捎陂c秒,閏年,夏令時(shí)等等會(huì)導(dǎo)致這種方式產(chǎn)生出錯(cuò)誤結(jié)果。該方式的想法是給當(dāng)前日期加上一個(gè)特定的時(shí)間間隔。我們會(huì)使用 NSDate 類的 dateByAddingTimeInterval: 方法來實(shí)現(xiàn)這個(gè)目的。下面的例子中我們算出來一個(gè)相當(dāng)于是 1.5 小時(shí)的時(shí)間間隔,然后把它加到當(dāng)前日期上:

let hoursToAddInSeconds: NSTimeInterval = 90 * 60
calculatedDate = currentDate.dateByAddingTimeInterval(hoursToAddInSeconds)

再強(qiáng)調(diào)一下,要做任何類型的日期計(jì)算的話,還是使用前兩種方式更安全。但這還是取決于你,選擇你更喜歡的那一種。

上面的三個(gè)例子都是給當(dāng)前日期加上某些個(gè)組成元素。那現(xiàn)在用同樣方式給當(dāng)前日期減去幾天,算出來那個(gè)過去的日期該怎么做?

下面的代碼示范了該怎么做。首先給當(dāng)前日期加上一個(gè)特定天數(shù)的負(fù)值,這就可以得到一個(gè)屬于過去的日期了。然后把結(jié)果轉(zhuǎn)換成一個(gè)有適當(dāng)格式的字符串,最后的結(jié)果...很有意思:

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!)

以上所有的小代碼段示例可以完全給你講明白怎樣通過給某個(gè)參考日期加上或正或負(fù)的 calendar unit 來算出一個(gè)新的日期。自己隨便擴(kuò)展一下上面的代碼吧,寫下你自己的代碼,你就會(huì)對這些技巧更加熟悉。

計(jì)算出日期的差值

和標(biāo)題的意思一樣,這節(jié)講的是計(jì)算出兩個(gè)日期之間的差值,它是在你編程生涯中某個(gè)時(shí)間肯定要做的一個(gè)任務(wù),顯然需要做不止一次。在這(教程的最后)一部分,我會(huì)告訴你計(jì)算出兩個(gè) NSDate 對象之間差值的三種方式,你可以根據(jù)需要選出最適合你的那一種。

開始之前先定義兩個(gè) 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 )。我們會(huì)再次用到 NSCalendar 類,還有它的一個(gè)之前我們沒見過的方法。最后把日期組成元素打印出來看一下結(jié)果。很明顯當(dāng)有了它,這個(gè)代表了日期差值的元素之后,想怎么做都取決于你了。來看下示范:

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: 方法,同樣它的第一個(gè)參數(shù)是一個(gè) NSCalendarUnit 的數(shù)組。注意下如果第一個(gè)日期比第二個(gè)晚的話,返回值就會(huì)是負(fù)數(shù)。

在計(jì)算日期差值的另外兩種方式中,我們會(huì)第一次用到 NSDateComponentsFormatter 類,這個(gè)類有很多方法,能自動(dòng)做出差值計(jì)算,然后返回一個(gè)帶有特定格式的字符串。先生成一個(gè)對象,并先指定它的一個(gè)屬性:

let dateComponentsFormatter = NSDateComponentsFormatter()
dateComponentsFormatter.unitsStyle = NSDateComponentsFormatterUnitsStyle.Full

unitsStyle 屬性告訴 dateComponentsFormatter 描述日期差值的那個(gè)字符串的格式應(yīng)該是什么樣的,顯示出的日期組成元素應(yīng)該是怎樣的樣式。比如用 Full 這個(gè)樣式,星期幾、月份等等就會(huì)顯示成常規(guī)的全寫(full-length)單詞。而如果我們用了 Abbreviated 樣式的話,則會(huì)顯示這些信息的縮寫。從這里你能看到關(guān)于單元樣式(units style)的全說明列表。

回到日期差值中來,這次我們要先算出兩個(gè)日期之間的時(shí)間間隔。然后這個(gè)間隔本身會(huì)作為一個(gè)參數(shù)傳遞給 NSDateComponentFormatter 類的 stringFromTimeInterval: 方法,結(jié)果就會(huì)以一個(gè)格式化好了的字符串形式返回。

let interval = date2.timeIntervalSinceDate(date1)
dateComponentsFormatter.stringFromTimeInterval(interval)

最后,在計(jì)算日期差值的最后一個(gè)方式中,兩個(gè)日期需要作為參數(shù)傳遞給 NSDateComponentsFormatter 類的 stringFromDate:toDate: 方法。然而用這個(gè)方法之前需要先滿足一個(gè)條件:allowedUnits 屬性必須要設(shè)置一個(gè) calendar unit,否則該方法會(huì)返回一個(gè) nil。所以我們就「告訴」這個(gè)方法我們想要怎樣的 unit,之后就等它給我們差值結(jié)果:

dateComponentsFormatter.allowedUnits = [NSCalendarUnit.Year, NSCalendarUnit.Month, NSCalendarUnit.Day, NSCalendarUnit.Hour, NSCalendarUnit.Minute, NSCalendarUnit.Second]
let autoFormattedDifference = dateComponentsFormatter.stringFromDate(date1, toDate: date2)

總結(jié)

簡介部分中我說過,處理 NSDate 對象這件事在你項(xiàng)目中非常常見,而且肯定無法避免。不可否認(rèn)它并不是程序員最喜歡討論的話題,所以我就寫了前面的那些,在小例子中告訴你其實(shí)處理日期是很容易的。這篇教程中 NSDate 的方方面面,以及其他相關(guān)的類都有一個(gè)共同目標(biāo):教你小巧高效的用法,兩三行代碼就讓你把活兒搞定。希望這篇文章能給你做個(gè)指南,尤其如果你是一個(gè)新開發(fā)者。下篇文章出來之前,好好練習(xí)吧。

本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán),最新文章請?jiān)L問 http://swift.gg。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 像小強(qiáng)一樣活著閱讀 3,734評(píng)論 6 16
  • iOS開發(fā)中,經(jīng)常會(huì)遇到各種各樣的時(shí)間問題,8小時(shí)時(shí)差,時(shí)間戳,求時(shí)間間隔,農(nóng)歷等等。解決辦法網(wǎng)上比比皆是,但大多...
    小李龍彪閱讀 6,741評(píng)論 1 6
  • ######先說下需求:選擇日期彈出日歷(跟途牛,攜程等差不多就行。。。行) 初識(shí)NSCalendar到寫完日歷的...
    只是個(gè)少年閱讀 1,168評(píng)論 0 0
  • 一、NSDate 1.NSDate對象用來表示一個(gè)具體的時(shí)間點(diǎn)。 2.NSDate是一個(gè)類簇,我們所使用的NSDa...
    hAo_JS閱讀 1,158評(píng)論 0 1
  • 一.安靜,孤獨(dú) 我,一個(gè)性格憂郁,不怎么會(huì)融入群體,喜歡自然,安靜的女孩。 安靜,是一種甘于寂寞的狀態(tài)。也...
    沐_21閱讀 692評(píng)論 1 1

友情鏈接更多精彩內(nèi)容