iOS 日期處理 (Swift3.0 NSDate),swift3.0nsdate
處理日期的常見情景
NSDate -> String & String -> NSDate
日期比較
日期計算(基於參考日期 +/- 一定時間)
計算日期間的差異
拆解NSDate對象(分解成year/month/day/hour/minute/second 等)
NSDate相關類
NSDate
DateFormatter
DateComponents
DateComponentFormatter
Calendar
Date structure: Swift3.0中引入了Date structure, 和NSDate提供的功能相似, 並且Date結構體和NSDate類可以在Swift中交替使用以此達到和Objective-C APIs的互動. Date結構體的引入和Swift中引入了String, Array等類型一樣, 都是為了讓對應的類橋接Foundation中對應的類(String -> NSString, Array -> NSArray)
注: 在下面的程式碼片段中, NSDate和Date會交替使用,功能都是相同的.
基本概念
NSDate和String之間的轉換
獲得當前的日期和時間
let currentDate = Date() print(currentDate) // 2016-08-19 05:33:48 +0000 格林威治時間
初始化DateFormatter類
let dateFormatter = DateFormatter()dateFormatter.locale = Locale.current() // 設定時區
使用系統內建樣式輸出日期
在將NSDate對象轉換成String類型前, 首先需要告訴電腦你想要輸出什麼樣的日期格式. 這裡有兩種方式. 第一就是使用系統內建的格式, 第二個方法是手動使用特定的說明符來指定輸出格式.這裡先看系統內建格式的輸出.
dateFormatter.dateStyle = DateFormatter.Style.noStylevar stringDate = dateFormatter.string(from: currentDate)print(stringDate) // "無輸出" dateFormatter.dateStyle = DateFormatter.Style.shortStylestringDate = dateFormatter.string(from: currentDate)print(stringDate) // 8/19/16 dateFormatter.dateStyle = DateFormatter.Style.mediumStylestringDate = dateFormatter.string(from: currentDate)print(stringDate) // Aug 19, 2016 dateFormatter.dateStyle = DateFormatter.Style.longStylestringDate = dateFormatter.string(from: currentDate)print(stringDate) // August 19, 2016 dateFormatter.dateStyle = DateFormatter.Style.fullStylestringDate = dateFormatter.string(from: currentDate)print(stringDate) // Friday, August 19, 2016
使用自訂說明符輸出日期
EEEE: 代表一天的全名,比如Monday.使用1-3個E就代表簡寫,比如Mon.
MMMM: 代表一個月的全名,比如July.使用1-3個M就代表簡寫,比如Jul.
dd: 代表一個月裡的幾號,比如07或者30.
yyyy: 代表4個數字表示的年份,比如2016.
HH: 代表2個數字表示的小時,比如08或17.
mm: 代表2個數字表示的分鐘,比如01或59.
ss: 代表2個數字表示的秒,比如2016.
zzz: 代表3個字母表示的時區,比如GTM(格林尼治標準時間,GMT+8為北京所在的時區,俗稱東八區)
GGG: BC或者AD, 即公元前或者公元
系統內建的樣式不夠用時, 就可以使用自訂說明符自訂Date的輸出格式.
自訂說明符的另一個巨大的作用就是可以將複雜的字元類型的日期格式(比如Fri, 08 Aug 2016 09:22:33 GMT)轉換成Date類型.
自訂格式的使用最重要的就是自訂說明符的使用,說明符是一些對日期對象有特點含義的簡單的字元.下面首先列舉一些這裡會用到的說明符:
關於說明符的具體介紹,請參照官方文檔
繼續來看自訂說明符的實際使用, 下面將現在的日期轉換成字串類型, 並且輸出星期和月份的全名, 年份和天數用數字表示:
dateFormatter.dateFormat = "EEEE, MMMM, dd, yyyy"stringDate = dateFormatter.string(from: currentDate)print(stringDate) // Friday, August, 19, 2016
從例子中可以很直觀的看到其實自訂輸出格式的技術很簡單, 具體的輸出格式根據具體的需求而定, 最後再舉一個例子(輸出格式--> 小時:分鐘:秒 星期簡寫 月份顯示數字 天數顯示數字 時區 公元):
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轉String, 這個轉換過程的逆轉換更加有趣. 之前用到的系統內建的輸出格式和自訂的說明符在String轉Date的過程中同樣適用. String轉Date過程中最重要的一點就是要設定合適的格式對應與String, 否則輸出會是nil.
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
這裡需要注意的是字串的時間是18:15:59, 而輸出的時間是10:15:59, 原因是因為我所在的時區是北京所在的時區,即東八區,而預設輸出是按格林尼治標準時間輸出的,即相差8小時.
下面舉一個更複雜的例子, 包括時區的輸出:
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
在很多情景中,你可能需要將日期的值進行拆解,進而提取其中特定的值.比如你可能需要得到一個日期中天數和月份的值,或者從時間裡面得到小時和分鐘的值,這部分將會介紹DateComponents類以此來解決這個問題. 需要注意的是,DateComponents類會經常和Calendar搭配使用,具體來講,Calendar類的方法是真正實現了Date到DateComponents的轉換,以及逆轉換.記住這一點之後,首先先得到currentCalendar對象,並且將它賦值給常量以便於後面的使用.
let calendar = Calendar.current()
下面的代碼通過一個典型的例子示範一遍Date -> DateComponents的過程.
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:)
方法. 這個方法接收兩個參數, 第二個傳入的參數是將要被拆解的日期對象,第一個參數比較有意思, 這個參數是一個數組,裡面放入組成日期的各個成分單位(Calendar.Unit),比如月(Calendar.Unit.month), 日(Calendar.Unit.day).
Calendar.Unit是一個結構體, 它裡面的所有屬性及說明可以在官方文檔中查看.
還需要注意的一點就是在components(_:from:)
方法的第一個數組參數中,如果沒有傳入想要解析的單位名稱,之後從DateComponents對象中是得不到這個單位的, 比如上面的方法中沒有傳入Calendar.Unit.month, 那麼最後列印的時候如果也列印了該值, 得到的值會是nil.
dateComponents = calendar.components([Calendar.Unit.year], from: currentDate)print("year:(dateComponents.year) month:(dateComponents.month)")// Optional(2016) month:nil
DateComponents -> Date
DateComponents -> Date的轉換也十分簡單, 只需要初始化一個DateComponents對象, 然後指定特定的components, 最後調用Calendar類的dateFromComponents:方法完成轉換
var components = DateComponents()components.year = 1985components.month = 02components.day = 05components.hour = 07components.minute = 08components.second = 44let dateFromComponents = calendar.date(from: components)print(dateFromComponents) // Optional(1985-02-04 23:08:44 +0000)
這裡同樣可以設定不同的時區來得到當地的時間:
components.timeZone = TimeZone(abbreviation: "GMT") // Greenwich Mean Timecomponents.timeZone = TimeZone(abbreviation: "CST") // China Standard Timecomponents.timeZone = TimeZone(abbreviation: "CET") // Central European Time
比較日期和時間
除了以上提到的應用情境, 另一個關於日期和時間常見的應用情境就是比較. 通過對兩個Date對象的比較以此來判斷哪個日期更早或者更遲,或者是否日期相同. 這裡將會介紹3種方法來做比較, 方法不分好壞, 適合自己的需求最重要.
在開始進行比較之前, 先建立兩個Date對象用於下面的比較:
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:)
當比較date1和date2兩個日期哪個更早或更晚時, 使用NSDate類提供的earlierDate:
和laterDate:
方法就可以實現.
print((date1! as NSDate).earlierDate(date2!)) // 2016-05-08 00:00:00 +0000print((date1! as NSDate).laterDate(date2!)) // 2016-05-10 00:00:00 +0000
當使用earlierDate:
方法時, 傳回值是日期更早的NSDate對象; laterDate:
的傳回值是日期更晚的NSDate對象.
上面的方法中, 因為date1和date2屬於Date類,而earlierDate:
和laterDate:
方法想要接收的參數類型是NSDate, 所以先進行了類型轉換.
方法2 (compare: )
第二個比較的方法是使用Date結構體提供的compare:
方法, 傳回值是ComparisonResult類型的枚舉.
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)
第三個方法有點不同, 原理是分別將date1 和 date2 與一個參考日期進行比對, 然後通過判斷兩個日期和參考日期的差距, 進而判斷兩個日期的差距.
舉一個例子: 比較4和10兩個數字, 先選取6作為一個參考數字, 4-6=-2;10-6=4,4-(-2)=6.
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")}
日期的計算
處理日期的另一個有趣的情境就是日期的計算. 比如計算2016-08-12號加2個月零12天是多少諸如此類的計算. 這裡將介紹兩種不同的方法來進行日期計算. 第一個方法使用了Calendar類的CalendarUnit結構體; 第二個方法使用到了DateComponents類.
首先記住當前的日期:
print(NSDate()) // 2016-08-19 08:29:12 +0000
下面假設想計算當前日期再加2個月零5天
let monthsToAdd = 2let daysToAdd = 5
方法1
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:
方法. 這個方法就是將特定的日期單位值加到現有的日期值上, 然後返回一個新的Date對象. 因為這裡的例子要加兩個值, 所以需要調用該方法兩次.
方法2
方法1雖然可行, 但有缺點, 即每一個單位的計算都需要分別調用方法, 假如想給當前日期加上7年3個月4天8小時23分鐘34秒, 那麼就要調用dateByAddingUnit:value:toDate:options:
方法6次! 而方法2則可以對這個問題迎刃而解. 而方法2有一次使用到了DateComponents這個類.
下面我將首先初始化一個新的DateComponents對象, 然後設定月份和天數, 然後再調用Calendar類中名為dateByAddingComponents:toDate:options:
的方法, 該方法的傳回值就是計算好的Date對象.
var newDateComponents = DateComponents()newDateComponents.month = 2newDateComponents.day = 5calculatedDate = calendar.date(byAdding: newDateComponents, to: currentDate, options: Calendar.Options.init(rawValue: 0))print(calculatedDate) // Optional(2016-10-24 08:47:57 +0000)
日期差值的計算
計算兩個日期間具體的差值也是在處理日期對象時經常會遇到的問題. 這裡將介紹3中方法來解決該問題.
首先, 先自訂兩個日期對象:
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類中的一個對象方法components:from:to:options:
.
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:
方法中的第一個參數還是接收一個包含了Calendar.Unit結構體的數組. 因為是從date2比date1,升序的傳回值就是正數,如果是反過來比,傳回值就是負數.
方法2
這裡介紹的第二個方法將會第一次使用到DateComponentsFormatter類. DateComponentsFormatter類提供了很多不同的方法進行日期間自動的計算,並且傳回值是格式化的字串.
首先初始化一個DateComponentsFormatter對象, 然後先指定它的一個屬性值.
let dateComponentsFormatter = DateComponentsFormatter()dateComponentsFormatter.unitsStyle = DateComponentsFormatter.UnitsStyle.full
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屬性會指定最後結果輸出的格式. 這裡使用full,就代表結果輸出的單位會全名顯示,如Monday,June等.如果想用簡寫,就可以重新設定屬性.官方文檔中列出了所有的屬性值。
方法3
dateComponentsFormatter.allowedUnits = [Calendar.Unit.year]diffString = dateComponentsFormatter.string(from: date1!, to: date2!)print(diffString) // Optional("-31 years")
最後一個方法調用了stringFrom:to:
方法來計算. 注意使用該方法之前, 必須至少在allowedUnits屬性中設定一個calendar unit, 否則這個方法將會返回nil, 所以在使用該方法之前, 先指定想要怎麼樣看到輸出, 然後再讓執行輸出的方法。
總結
就像我在摘要中說的, 處理日期很常見, 在日常代碼中也不可避免, 這裡我通過一些小的程式碼片段介紹了處理日期的一些常見方法. 不管是NSDate類,Date結構體還是與之相關聯的類, 它們的目的只有一個, 就是能夠快速的處理日期. 如果想深入掌握有關日期的處理, 還是要在日常編碼過程中多多練習, 多多閱讀官方文檔。
學習收藏,