處理日期的常見情景
NSDate -> String & String -> NSDate
日期比較
日期計(jì)算(基于參考日期 +/- 一定時(shí)間)
計(jì)算日期間的差異
拆解NSDate對(duì)象(分解成year/month/day/hour/minute/second 等)
NSDate相關(guān)類
NSDate
DateFormatter
DateComponents
DateComponentFormatter
Calendar
Date structure: Swift3.0中引入了Date structure, 和NSDate提供的功能相似, 并且Date結(jié)構(gòu)體和NSDate類可以在Swift中交替使用以此達(dá)到和Objective-C APIs的交互. Date結(jié)構(gòu)體的引入和Swift中引入了String, Array等類型一樣, 都是為了讓對(duì)應(yīng)的類橋接Foundation中對(duì)應(yīng)的類(String -> NSString, Array -> NSArray)
注: 在下面的代碼片段中, NSDate和Date會(huì)交替使用,功能都是相同的.
基本概念
在具體開始寫代碼之前, 搞清楚一些基本的概念是十分必要的:
NSDate對(duì)象: 同時(shí)可以描述日期和時(shí)間, 當(dāng)要處理日期或者時(shí)間時(shí)會(huì)使用到.
DateFormatter對(duì)象: 格式對(duì)象只要在將NSDate和String相互轉(zhuǎn)換的時(shí)候才有價(jià)值, 它是用來規(guī)定格式的. 包括系統(tǒng)自帶的格式和手動(dòng)自定義的格式,同時(shí)該類也支持時(shí)區(qū)的設(shè)置.
DateComponents類: 可以看做是NSDate的姊妹類. 因?yàn)樗峁┝撕芏鄬?shí)用的特性和操作. 最重要的一個(gè)特性就是它可以把日期或者時(shí)間拆解開來, 即日期或者時(shí)間里的每一個(gè)部分(比如年,月,日,小時(shí),分鐘等)都可以單獨(dú)取出來,并且進(jìn)行其他的操作(比如計(jì)算).
DateComponentsFormatter類: 用來將計(jì)算機(jī)讀入的日期和時(shí)間輸出為了人類可讀的字符串.
Calendar類: 日期類可以實(shí)現(xiàn)NSDate和DateComponents之間的轉(zhuǎn)換.
NSDate和String之間的轉(zhuǎn)換
獲得當(dāng)前的日期和時(shí)間
1
2
let currentDate = Date()
print(currentDate) // 2016-08-19 05:33:48 +0000 格林威治時(shí)間
初始化DateFormatter類
1
2
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current() // 設(shè)置時(shí)區(qū)
使用系統(tǒng)自帶樣式輸出日期
在將NSDate對(duì)象轉(zhuǎn)換成String類型前, 首先需要告訴計(jì)算機(jī)你想要輸出什么樣的日期格式. 這里有兩種方式. 第一就是使用系統(tǒng)自帶的格式, 第二個(gè)方法是手動(dòng)使用特定的說明符來指定輸出格式.這里先看系統(tǒng)自帶格式的輸出.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dateFormatter.dateStyle = DateFormatter.Style.noStyle
var stringDate = dateFormatter.string(from: currentDate)
print(stringDate) // "無輸出"
dateFormatter.dateStyle = DateFormatter.Style.shortStyle
stringDate = dateFormatter.string(from: currentDate)
print(stringDate) // 8/19/16
dateFormatter.dateStyle = DateFormatter.Style.mediumStyle
stringDate = dateFormatter.string(from: currentDate)
print(stringDate) // Aug 19, 2016
dateFormatter.dateStyle = DateFormatter.Style.longStyle
stringDate = dateFormatter.string(from: currentDate)
print(stringDate) // August 19, 2016
dateFormatter.dateStyle = DateFormatter.Style.fullStyle
stringDate = dateFormatter.string(from: currentDate)
print(stringDate) // Friday, August 19, 2016
使用自定義說明符輸出日期
EEEE: 代表一天的全名,比如Monday.使用1-3個(gè)E就代表簡(jiǎn)寫,比如Mon.
MMMM: 代表一個(gè)月的全名,比如July.使用1-3個(gè)M就代表簡(jiǎn)寫,比如Jul.
dd: 代表一個(gè)月里的幾號(hào),比如07或者30.
yyyy: 代表4個(gè)數(shù)字表示的年份,比如2016.
HH: 代表2個(gè)數(shù)字表示的小時(shí),比如08或17.
mm: 代表2個(gè)數(shù)字表示的分鐘,比如01或59.
ss: 代表2個(gè)數(shù)字表示的秒,比如2016.
zzz: 代表3個(gè)字母表示的時(shí)區(qū),比如GTM(格林尼治標(biāo)準(zhǔn)時(shí)間,GMT+8為北京所在的時(shí)區(qū),俗稱東八區(qū))
GGG: BC或者AD, 即公元前或者公元
系統(tǒng)自帶的樣式不夠用時(shí), 就可以使用自定義說明符自定義Date的輸出格式.
自定義說明符的另一個(gè)巨大的作用就是可以將復(fù)雜的字符類型的日期格式(比如Fri, 08 Aug 2016 09:22:33 GMT)轉(zhuǎn)換成Date類型.
自定義格式的使用最重要的就是自定義說明符的使用,說明符是一些對(duì)日期對(duì)象有特點(diǎn)含義的簡(jiǎn)單的字符.下面首先列舉一些這里會(huì)用到的說明符:
關(guān)于說明符的具體介紹,請(qǐng)參照官方文檔
繼續(xù)來看自定義說明符的實(shí)際使用, 下面將現(xiàn)在的日期轉(zhuǎn)換成字符串類型, 并且輸出星期和月份的全名, 年份和天數(shù)用數(shù)字表示:
1
2
3
dateFormatter.dateFormat = "EEEE, MMMM, dd, yyyy"
stringDate = dateFormatter.string(from: currentDate)
print(stringDate) // Friday, August, 19, 2016
從例子中可以很直觀的看到其實(shí)自定義輸出格式的技術(shù)很簡(jiǎn)單, 具體的輸出格式根據(jù)具體的需求而定, 最后再舉一個(gè)例子(輸出格式--> 小時(shí):分鐘:秒 星期簡(jiǎn)寫 月份顯示數(shù)字 天數(shù)顯示數(shù)字 時(shí)區(qū) 公元):
1
2
3
dateFormatter.dateFormat = "HH:mm:ss E M dd zzz GGG"
stringDate = dateFormatter.string(from: currentDate)
print(stringDate) // 14:20:31 Fri 8 19 GMT+8 AD
上面例子全部是Date轉(zhuǎn)String, 這個(gè)轉(zhuǎn)換過程的逆轉(zhuǎn)換更加有趣. 之前用到的系統(tǒng)自帶的輸出格式和自定義的說明符在String轉(zhuǎn)Date的過程中同樣適用. String轉(zhuǎn)Date過程中最重要的一點(diǎn)就是要設(shè)置合適的格式對(duì)應(yīng)與String, 否則輸出會(huì)是nil.
1
2
3
var dateString = "2016-12-02 18:15:59"
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(dateFormatter.date(from: dateString)) // 2016-12-02 10:15:59 +0000
這里需要注意的是字符串的時(shí)間是18:15:59, 而輸出的時(shí)間是10:15:59, 原因是因?yàn)槲宜诘臅r(shí)區(qū)是北京所在的時(shí)區(qū),即東八區(qū),而默認(rèn)輸出是按格林尼治標(biāo)準(zhǔn)時(shí)間輸出的,即相差8小時(shí).
下面舉一個(gè)更復(fù)雜的例子, 包括時(shí)區(qū)的輸出:
1
2
3
4
dateString = "Mon, 08, Aug 2008 20:00:01 GMT"
dateFormatter.dateFormat = "E, dd, MM yyyy HH:mm:ss zzz"
dateFromString = dateFormatter.date(from: dateString)
print(dateFromString) // 2008-08-08 20:00:01 +0000
DateComponents
在很多情景中,你可能需要將日期的值進(jìn)行拆解,進(jìn)而提取其中特定的值.比如你可能需要得到一個(gè)日期中天數(shù)和月份的值,或者從時(shí)間里面得到小時(shí)和分鐘的值,這部分將會(huì)介紹DateComponents類以此來解決這個(gè)問題. 需要注意的是,DateComponents類會(huì)經(jīng)常和Calendar搭配使用,具體來講,Calendar類的方法是真正實(shí)現(xiàn)了Date到DateComponents的轉(zhuǎn)換,以及逆轉(zhuǎn)換.記住這一點(diǎn)之后,首先先得到currentCalendar對(duì)象,并且將它賦值給常量以便于后面的使用.
1
let calendar = Calendar.current()
下面的代碼通過一個(gè)典型的例子演示一遍Date -> DateComponents的過程.
1
2
3
let dateComponents = calendar.components([Calendar.Unit.era, Calendar.Unit.year,? ? Calendar.Unit.month, Calendar.Unit.day, Calendar.Unit.hour, Calendar.Unit.minute,? Calendar.Unit.second], from: currentDate)
print("era:(dateComponents.era) year:(dateComponents.year) month:(dateComponents.month) day: (dateComponents.day) hour:(dateComponents.hour) minute:(dateComponents.minute) second:? (dateComponents.second)")
// era:Optional(1) year:Optional(2016) month:Optional(8) day:Optional(19) hour:Optional(15)? ? minute:Optional(29) second:Optional(13)
上面用到了Calendar類中的components(_:from:)方法. 這個(gè)方法接收兩個(gè)參數(shù), 第二個(gè)傳入的參數(shù)是將要被拆解的日期對(duì)象,第一個(gè)參數(shù)比較有意思, 這個(gè)參數(shù)是一個(gè)數(shù)組,里面放入組成日期的各個(gè)成分單位(Calendar.Unit),比如月(Calendar.Unit.month), 日(Calendar.Unit.day).
Calendar.Unit是一個(gè)結(jié)構(gòu)體, 它里面的所有屬性及說明可以在官方文檔中查看.
還需要注意的一點(diǎn)就是在components(_:from:)方法的第一個(gè)數(shù)組參數(shù)中,如果沒有傳入想要解析的單位名稱,之后從DateComponents對(duì)象中是得不到這個(gè)單位的, 比如上面的方法中沒有傳入Calendar.Unit.month, 那么最后打印的時(shí)候如果也打印了該值, 得到的值會(huì)是nil.
1
2
3
dateComponents = calendar.components([Calendar.Unit.year], from: currentDate)
print("year:(dateComponents.year) month:(dateComponents.month)")
// Optional(2016) month:nil
DateComponents -> Date
DateComponents -> Date的轉(zhuǎn)換也十分簡(jiǎn)單, 只需要初始化一個(gè)DateComponents對(duì)象, 然后指定特定的components, 最后調(diào)用Calendar類的dateFromComponents:方法完成轉(zhuǎn)換
1
2
3
4
5
6
7
8
9
var components = DateComponents()
components.year = 1985
components.month = 02
components.day = 05
components.hour = 07
components.minute = 08
components.second = 44
let dateFromComponents = calendar.date(from: components)
print(dateFromComponents) // Optional(1985-02-04 23:08:44 +0000)
這里同樣可以設(shè)置不同的時(shí)區(qū)來得到當(dāng)?shù)氐臅r(shí)間:
1
2
3
components.timeZone = TimeZone(abbreviation: "GMT") // Greenwich Mean Time
components.timeZone = TimeZone(abbreviation: "CST") // China Standard Time
components.timeZone = TimeZone(abbreviation: "CET") // Central European Time
比較日期和時(shí)間
除了以上提到的應(yīng)用場(chǎng)景, 另一個(gè)關(guān)于日期和時(shí)間常見的應(yīng)用場(chǎng)景就是比較. 通過對(duì)兩個(gè)Date對(duì)象的比較以此來判斷哪個(gè)日期更早或者更遲,或者是否日期相同. 這里將會(huì)介紹3種方法來做比較, 方法不分好壞, 適合自己的需求最重要.
在開始進(jìn)行比較之前, 先創(chuàng)建兩個(gè)Date對(duì)象用于下面的比較:
1
2
3
4
5
6
7
8
9
10
dateFormatter.dateFormat = "MMM dd, yyyy zzz"
dateString = "May 08, 2016 GMT"
var date1 = dateFormatter.date(from: dateString)
dateString = "May 10, 2016 GMT"
var date2 = dateFormatter.date(from: dateString)
print("date1:(date1)----date2:(date2)")
// date1:Optional(2016-05-08 00:00:00 +0000)
// date2:Optional(2016-05-10 00:00:00 +0000)
方法1 (earlierDate: || laterDate:)
當(dāng)比較date1和date2兩個(gè)日期哪個(gè)更早或更晚時(shí), 使用NSDate類提供的earlierDate:和laterDate:方法就可以實(shí)現(xiàn).
1
2
print((date1! as NSDate).earlierDate(date2!)) // 2016-05-08 00:00:00 +0000
print((date1! as NSDate).laterDate(date2!)) // 2016-05-10 00:00:00 +0000
當(dāng)使用earlierDate:方法時(shí), 返回值是日期更早的NSDate對(duì)象; laterDate:的返回值是日期更晚的NSDate對(duì)象.
上面的方法中, 因?yàn)閐ate1和date2屬于Date類,而earlierDate:和laterDate:方法想要接收的參數(shù)類型是NSDate, 所以先進(jìn)行了類型轉(zhuǎn)換.
方法2 (compare: )
第二個(gè)比較的方法是使用Date結(jié)構(gòu)體提供的compare:方法, 返回值是ComparisonResult類型的枚舉.
1
2
3
4
5
6
7
if date1?.compare(date2!) == ComparisonResult.orderedAscending {
print("date1 is earlier")
} else if date1?.compare(date2!) == ComparisonResult.orderedDescending {
print("date2 is earlier")
} else if date1?.compare(date2!) == ComparisonResult.orderedSame {
print("Same date!!!")
}
方法3 (timeIntervalSinceReferenceDate)
第三個(gè)方法有點(diǎn)不同, 原理是分別將date1 和 date2 與一個(gè)參考日期進(jìn)行比對(duì), 然后通過判斷兩個(gè)日期和參考日期的差距, 進(jìn)而判斷兩個(gè)日期的差距.
舉一個(gè)例子: 比較4和10兩個(gè)數(shù)字, 先選取6作為一個(gè)參考數(shù)字, 4-6=-2;10-6=4,4-(-2)=6.
1
2
3
4
5
6
7
if date1?.timeIntervalSinceReferenceDate > date2?.timeIntervalSinceReferenceDate {
print("date1 is later")
} else if date1?.timeIntervalSinceReferenceDate < date2?.timeIntervalSinceReferenceDate {
print("date2 is later")
} else if date1?.timeIntervalSinceReferenceDate == date2?.timeIntervalSinceReferenceDate {
print("Same Date")
}
日期的計(jì)算
處理日期的另一個(gè)有趣的場(chǎng)景就是日期的計(jì)算. 比如計(jì)算2016-08-12號(hào)加2個(gè)月零12天是多少諸如此類的計(jì)算. 這里將介紹兩種不同的方法來進(jìn)行日期計(jì)算. 第一個(gè)方法使用了Calendar類的CalendarUnit結(jié)構(gòu)體; 第二個(gè)方法使用到了DateComponents類.
首先記住當(dāng)前的日期:
1
print(NSDate()) // 2016-08-19 08:29:12 +0000
下面假設(shè)想計(jì)算當(dāng)前日期再加2個(gè)月零5天
1
2
let monthsToAdd = 2
let daysToAdd = 5
方法1
1
2
3
var calculatedDate = calendar.date(byAdding: Calendar.Unit.month, value: monthsToAdd, to:? currentDate, options: Calendar.Options.init(rawValue: 0))
calculatedDate = calendar.date(byAdding: Calendar.Unit.day, value: daysToAdd, to:? calculatedDate!, options: Calendar.Options.init(rawValue: 0))
print(calculatedDate) // Optional(2016-10-24 08:33:41 +0000)
如上面代碼所示, 這里使用了dateByAddingUnit:value:toDate:options:方法. 這個(gè)方法就是將特定的日期單位值加到現(xiàn)有的日期值上, 然后返回一個(gè)新的Date對(duì)象. 因?yàn)檫@里的例子要加兩個(gè)值, 所以需要調(diào)用該方法兩次.
方法2
方法1雖然可行, 但有缺點(diǎn), 即每一個(gè)單位的計(jì)算都需要分別調(diào)用方法, 假如想給當(dāng)前日期加上7年3個(gè)月4天8小時(shí)23分鐘34秒, 那么就要調(diào)用dateByAddingUnit:value:toDate:options:方法6次! 而方法2則可以對(duì)這個(gè)問題迎刃而解. 而方法2有一次使用到了DateComponents這個(gè)類.
下面我將首先初始化一個(gè)新的DateComponents對(duì)象, 然后設(shè)置月份和天數(shù), 然后再調(diào)用Calendar類中名為dateByAddingComponents:toDate:options:的方法, 該方法的返回值就是計(jì)算好的Date對(duì)象.
1
2
3
4
5
var newDateComponents = DateComponents()
newDateComponents.month = 2
newDateComponents.day = 5
calculatedDate = calendar.date(byAdding: newDateComponents, to: currentDate, options:? Calendar.Options.init(rawValue: 0))
print(calculatedDate) // Optional(2016-10-24 08:47:57 +0000)
日期差值的計(jì)算
計(jì)算兩個(gè)日期間具體的差值也是在處理日期對(duì)象時(shí)經(jīng)常會(huì)遇到的問題. 這里將介紹3中方法來解決該問題.
首先, 先自定義兩個(gè)日期對(duì)象:
1
2
3
4
5
6
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateString = "2016-08-08 18:25:17"
date1 = dateFormatter.date(from: dateString)
dateString = "1985-02-05 07:45:38"
date2 = dateFormatter.date(from: dateString)
方法1
方法1用到的是Calendar類中的一個(gè)對(duì)象方法components:from:to:options:.
1
2
3
4
var diffComponents = calendar.components([Calendar.Unit.year, Calendar.Unit.month,? Calendar.Unit.day], from: date2!, to: date1!, options: Calendar.Options.init(rawValue: 0))
print("date1 and date2 are different in (diffComponents.year)years? ? (diffComponents.month)months and (diffComponents.year)days")
// date1 and date2 are different in Optional(31)years Optional(6)months and Optional(31)days
components:from:to:options:方法中的第一個(gè)參數(shù)還是接收一個(gè)包含了Calendar.Unit結(jié)構(gòu)體的數(shù)組. 因?yàn)槭菑膁ate2比date1,升序的返回值就是正數(shù),如果是反過來比,返回值就是負(fù)數(shù).
方法2
這里介紹的第二個(gè)方法將會(huì)第一次使用到DateComponentsFormatter類. DateComponentsFormatter類提供了很多不同的方法進(jìn)行日期間自動(dòng)的計(jì)算,并且返回值是格式化的字符串.
首先初始化一個(gè)DateComponentsFormatter對(duì)象, 然后先指定它的一個(gè)屬性值.
1
2
let dateComponentsFormatter = DateComponentsFormatter()
dateComponentsFormatter.unitsStyle = DateComponentsFormatter.UnitsStyle.full
1
2
3
let interval = date2?.timeIntervalSince(date1!)
var diffString = dateComponentsFormatter.string(from: interval!)
print(diffString) // Optional("-31 years, 6 months, 0 weeks, 3 days, 10 hours, 39 minutes, 39 seconds")
UnitsStyle屬性會(huì)指定最后結(jié)果輸出的格式. 這里使用full,就代表結(jié)果輸出的單位會(huì)全名顯示,如Monday,June等.如果想用簡(jiǎn)寫,就可以重新設(shè)置屬性.官方文檔中列出了所有的屬性值。
方法3
1
2
3
dateComponentsFormatter.allowedUnits = [Calendar.Unit.year]
diffString = dateComponentsFormatter.string(from: date1!, to: date2!)
print(diffString) // Optional("-31 years")
最后一個(gè)方法調(diào)用了stringFrom:to:方法來計(jì)算. 注意使用該方法之前, 必須至少在allowedUnits屬性中設(shè)置一個(gè)calendar unit, 否則這個(gè)方法將會(huì)返回nil, 所以在使用該方法之前, 先指定想要怎么樣看到輸出, 然后再讓執(zhí)行輸出的方法。
總結(jié)
就像我在摘要中說的, 處理日期很常見, 在日常代碼中也不可避免, 這里我通過一些小的代碼片段介紹了處理日期的一些常見方法. 不管是NSDate類,Date結(jié)構(gòu)體還是與之相關(guān)聯(lián)的類, 它們的目的只有一個(gè), 就是能夠快速的處理日期. 如果想深入掌握有關(guān)日期的處理, 還是要在日常編碼過程中多多練習(xí), 多多閱讀官方文檔。
-> http://www.cocoachina.com/swift/20160823/17406.html