阅读 103

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, .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年--己亥年。农历八月十四

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
复制代码

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日
复制代码

总结

  • Date只是一个绝对时间点,秒数
  • DateComponents表示时间各个分量,DateComponents必须依赖Calendar才有意义
  • Calendar可以进行DateDateComponents之间转换,时间计算
  • LocaleTimeZone并不会改变时间,而是影响时间的展示

参考

关注下面的标签,发现更多相似文章
评论