加深理解代替单纯记忆
正确显示一个时间要考虑几个因素 最容易想到的就是--时区,中学地理学过,相邻时区差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, .month, .day], 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并不会改变时间,而是影响时间的展示