1. 程式人生 > IOS開發 >Date in iOS

Date in iOS

加深理解代替單純記憶

正確顯示一個時間要考慮幾個因素 最容易想到的就是--時區,中學地理學過,相鄰時區差1個小時。還要考慮什麼?

日曆資訊

什麼是日曆?比如中國農曆的各種節日就用到了農曆這個概念,還有通用的公曆紀年如2008年、日本紀年如平成28年。這些都是日曆資訊,當然,日曆資訊包含的內容不僅僅只有這一點

關於iOS中時間相關的類,首先想到的肯定是Date,那麼僅使用Date能否表示上面的日曆、時區等資訊呢?

通過Date類的API能看出,顯然不可以~ 於是有了CalendarDateComponents

Date

Date實際上只是一個絕對時間,或者說它就是一個從過去某個起始時間點到這個Date實際要表達的點之間的時間差(通常用秒作為單位)。舉個例子

init(timeIntervalSinceReferenceDate ti: TimeInterval)
複製程式碼
  • 該方法中ReferenceDate指的就是公曆的2001年1月1日0時0分0秒 0時區這個時間點
  • 注意上面描述中公曆、0時區這些字眼,但要注意的是Date本身並不包含時區、日曆資訊
  • 我們在列印Date時,會發現能夠正確的輸出一個包含時區、日曆資訊的時間
    • 這並不能說明Date包含了時區、日曆資訊
    • 之所以可以正確顯示,是因為使用了日曆等資訊進行了列印
    • 本身僅靠Date是不能表達一個完整的時間的
  • 既然Date沒辦法表示完整的時間資訊,所以就有了CalendarDateComponent

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年--己亥年。農曆八月十四

CalendarDateComponent的結合可以做很多事情:

DateDateComponent互轉

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)
複製程式碼

當然,CalendarDateComponent的結合能做很多事情,遠不僅於此

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,再將其設定給DateFormatterdateFormat最終來完成轉換

有三種可選擇使用的型別DateFormat String:PredefineFixedLocalized

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日
複製程式碼

注意,使用PredefinedLocalized兩種形式雖然可以利用系統的國際化,但具體能國際化成什麼形式,能否滿足開發的需求,還要看具體輸出結果,開發者並不能隨意定製

總結

  • Date只是一個絕對時間點,秒數
  • DateComponents表示時間各個分量,DateComponents必須依賴Calendar才有意義
  • Calendar可以進行DateDateComponents之間轉換,時間計算
  • LocaleTimeZone並不會改變時間,而是影響時間的展示

參考