Date in iOS
加深理解代替單純記憶
正確顯示一個時間要考慮幾個因素 最容易想到的就是--時區,中學地理學過,相鄰時區差1個小時。還要考慮什麼?
日曆資訊
什麼是日曆?比如中國農曆的各種節日就用到了農曆
這個概念,還有通用的公曆
紀年如2008年、日本紀年如平成28年。這些都是日曆資訊,當然,日曆資訊包含的內容不僅僅只有這一點
關於iOS中時間相關的類,首先想到的肯定是Date
,那麼僅使用Date
能否表示上面的日曆、時區等資訊呢?
通過Date
類的API能看出,顯然不可以~ 於是有了Calendar
和DateComponents
Date
Date
實際上只是一個絕對時間,或者說它就是一個從過去某個起始時間點到這個Date實際要表達的點之間的時間差(通常用秒作為單位)。舉個例子
init(timeIntervalSinceReferenceDate ti: TimeInterval)
複製程式碼
- 該方法中ReferenceDate指的就是公曆的2001年1月1日0時0分0秒 0時區這個時間點
- 注意上面描述中公曆、0時區這些字眼,但要注意的是
Date
本身並不包含時區、日曆資訊 - 我們在列印
Date
時,會發現能夠正確的輸出一個包含時區、日曆資訊的時間- 這並不能說明
Date
包含了時區、日曆資訊 - 之所以可以正確顯示,是因為使用了日曆等資訊進行了列印
- 本身僅靠
Date
是不能表達一個完整的時間的
- 這並不能說明
- 既然
Date
沒辦法表示完整的時間資訊,所以就有了Calendar
和DateComponent
DateComponents & Calendar
DateComponents
可以將一個時間打散為一個個組成部分:年、月、日、時、分、秒,甚至還有weekday、weekend等更多精細化的分類
同一個時間點,在不同日曆中年月日可能表示年月日等資訊是不同的
所以
DateComponents
的每個組成部分的值是依賴具體的Calendar
let date = Date()
let gregorian = Calendar(identifier: .gregorian) // 公曆
let dct1 = gregorian.dateComponents([.year,.month,.day],from: date)
print (dct1)
let chinese = Calendar(identifier: .chinese) // 農曆
let dct2 = chinese.dateComponents([.year,from: date)
print(dct2)
// year: 2019 month: 9 day: 12 isLeapMonth: false
// year: 36 month: 8 day: 14 isLeapMonth: false
複製程式碼
- 使用公曆日曆得到的時間分量很容易理解
- 農曆的結果中,year表示2019年是農曆中60年一輪迴中的第36年--己亥年。農曆八月十四
Calendar
和DateComponent
的結合可以做很多事情:
Date
和DateComponent
互轉
Date -> DateComponents
calendar.dateComponents(Set<Calendar.Component>,from: Date)
複製程式碼
DateComponents -> Date
calendar.date(from: DateComponents)
複製程式碼
時間、時間差計算
calendar.date(byAdding: DateComponents,to: Date)
calendar.dateComponents(Set<Calendar.Component>,from: Date,to: Date)
複製程式碼
當然,
Calendar
和DateComponent
的結合能做很多事情,遠不僅於此
Locale
Locale
表示地區資訊,它並不會修改時間,而主要用在多語言國際化顯示方面;不僅在時間領域,Locale
用處遍及iOS各個領域。我們本次只提及與時間相關的內容
- 一方面不同地區所使用的日曆資訊可能不同
-
Locale
有一個calendar
屬性
-
- 另一方面
Locale
的作用表現在顯示時間時的格式、內容上面- 在使用
DateFormatter
對時間格式化輸出時也有類似問題
- 在使用
比如對於公曆來說,不同地區對weekday的輸出就不同
var gregorian = Calendar(identifier: .gregorian)
gregorian.locale = Locale(identifier: "en_US")
print(gregorian.weekdaySymbols)
gregorian.locale = Locale(identifier: "zh_CH")
print(gregorian.weekdaySymbols)
// ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]
// ["星期日","星期一","星期二","星期三","星期四","星期五","星期六"]
複製程式碼
DateFormatter
DateFormatter
的作用主要是將Date
轉為可讀、可展示的日期字串,和將反過來的操作
- 前面我們知道
Date
只是一個秒數,轉為可讀的時間時一定要依託日曆等資訊,所以,DateFormatter
必須能夠設定日曆、時區、還有地區資訊 -
DateFormatter
另一個關鍵概念就是--dateformat string,格式化字串;這決定了最終輸出日期的樣式
DateFormat String
- DateFormat String是從
Date
轉到可讀內容的關鍵 - DateFormat String並不是iOS特有的技術,這是一個Unicode國際標準,可見參考文件
- 下面提到的幾種型別,都是先產生一個DateFormat String,再將其設定給
DateFormatter
的dateFormat
最終來完成轉換
有三種可選擇使用的型別DateFormat String:Predefine、Fixed 和 Localized
Predefine很好理解,即系統內部定義好的一些輸出格式
- 要注意的是,這些格式都經過國際化了,所以不同地區、語言環境下展示內容可能是不同的
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .short
print(formatter.string(from: date))
formatter.locale = Locale(identifier: "zh_CH")
print(formatter.string(from: date))
// September 12,2019 at 2:54 PM
// 2019年9月12日 下午2:54
複製程式碼
Fixed,顧名思義,就是固定好的格式,所以不會有國際化的行為
formatter.dateFormat = "yyyy-M-dd H:m"
// 2019-9-12 15:4
複製程式碼
此處強烈建議初級開發者看一下參考文件中的Unicode標準,比如上面程式碼中輸出的分鐘數是4而不是04,就是因為format string中只有一個m
Localized
如果我們既不想使用預定義的輸出型別,也不喜歡固定格式中沒有國際化,那我們可以使用該方法,指定DateFormat String中要包含哪些時間分量
formatter.locale = Locale(identifier: "zh_CH")
formatter.setLocalizedDateFormatFromTemplate("dMMMM") //注意這裡我把表示天的d放到月M之前,但仍不會影響正常的顯示
print(formatter.dateFormat)
print(formatter.string(from: date))
// "M月d日"
// 9月12日
複製程式碼
注意,使用
Predefined
和Localized
兩種形式雖然可以利用系統的國際化,但具體能國際化成什麼形式,能否滿足開發的需求,還要看具體輸出結果,開發者並不能隨意定製
總結
-
Date
只是一個絕對時間點,秒數 -
DateComponents
表示時間各個分量,DateComponents
必須依賴Calendar
才有意義 -
Calendar
可以進行Date
和DateComponents
之間轉換,時間計算 -
Locale
和TimeZone
並不會改變時間,而是影響時間的展示