Swift4中对于日期时间的处理(Date、DateComponents、Calendar、DateFormatter、Locale)

Swift4中对于日期时间的处理(Date、DateComponents、Calendar、DateFormatter、Locale)

说明

最近在做一些东西,涉及到了日期时间的处理,去网上查到的资料因为各种原因完全用不了,花费了一段时间解决了这个问题,我记性不行,得给它记录下来以后遇到但是忘了的话就看看。
首先上一张图,这张图记录了解决这些问题需要用到的5个类(为了方便表达姑且称之为类吧),以及它们之间的关系:
Swift4中对于日期时间的处理(Date、DateComponents、Calendar、DateFormatter、Locale)_第1张图片
本笔记参考自:http://www.swift.gg/2015/12/14/a-beginners-guide-to-nsdate-in-swift/

进入正题

这里将记录在Swift4中我遇到的一些问题的解决方案:

  • 如何获取当前日期时间
  • 如何使得时间按照自己想要的格式输出
  • 对于转换时的设置说明
  • 如何获取日期时间中的各个组成成分,比如单独获取时间中的小时,单独获取日期中的年份等
  • 如何将手动设置的各个时间成分转换为一个完整的日期时间

如何获取当前日期时间

在Swift4中,日期和时间是被一起封装到Date中的,所以一般使用Date对象就可以处理日期和时间相关的东西了

获取一个日期时间的方式比较简单:

//获取当前时间
let now = Date() //说明一下,我现在的时间是 2018年01月11日11:01:54
print("\(now)")
//输出:2018-01-11 03:02:13 +0000\n

代码片段1
于是发现一个问题:我们获取的时间对象now对应的时间是“Jan 11,2018 at 11:03 AM”也就是2018-1-11 11:03
但是print函数输出的却是03:03:43,小时少了8小时……
这是为什么呢?因为我们这里是中国,中国的时间使用的时区是东八区,如果我们的时间减去八小时得到的是零时区的时间,所以可以得到:

结论1:Date对象其本身使用的时区是零时区

记住这个结论,后面还将使用到它

如何使得时间按照自己想要的格式输出

上面的部分我们获取的当前的时间,现在我们把这个时间按照我们想要的格式进行输出。
我们明白,输出一个时间相当于将Date对象转换为String对象,Swift4中,可以使用DateFormatter对象作为桥梁来实现Date与String之间的相互转换:
Swift4中对于日期时间的处理(Date、DateComponents、Calendar、DateFormatter、Locale)_第2张图片
于是Date转String可以这样:

//创建一个DateFormatter来作为转换的桥梁
let dateFormatter = DateFormatter()
//DateFormatter对象的string方法执行转换(参数now为之前代码中所创建)
var convertedDate0 = dateFormatter.string(from: now)
//输出转换结果
print("\(convertedDate0)")//发现输出的是:"\n"

如图:
Swift4中对于日期时间的处理(Date、DateComponents、Calendar、DateFormatter、Locale)_第3张图片
为什么输出的东西什么都没有?
这里我做了一些测试,在将Date转换为String时可以先设置位置和格式,其中位置使用Locale对象,格式可以使用Style也可以使用比如yyyy代表年MM代表月的Specifer区分符。测试结论是必须要设置格式才能看见结果,位置可有可无但格式一定要有,因为没有设置转换后的时间的格式,所以默认就是“空”格式,所以输出的就是“空”,所以代码改为:

//设置时间格式(这里的dateFormatter对象在上一段代码中创建)
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
//调用string方法进行转换
convertedDate0 = dateFormatter.string(from: now)
//输出转换结果
print("\(convertedDate0)")//结果为:2018-01-11 11:45:30\n

如图:
Swift4中对于日期时间的处理(Date、DateComponents、Calendar、DateFormatter、Locale)_第4张图片
发现一个问题,我这里设置了时间格式,却没有设置时间的地区,但是输出的时间却不再是零时区,自动变成东八区了。所以明白:DateFormatter这个对象虽然默认格式为空,但是默认地区却会使用系统所在地区,于是明白,非特殊情况(你要设置的时区和系统所在地时区不同),您只需设置时间格式,而不必设置时间地区(时区)。

结论2:DateFormatter对象默认地区为设备所在地区,默认格式为“空”

对于转换时的设置说明

根据之前的转换结构图,Date与String之间互转使用DateFormatter作为转换的桥梁,对于这个DateFormatter桥梁你可以自己选择是否要设置地区,但是这个桥梁必须要设置格式。
地区的设置(使用Locale对象):
Swift4中对于日期时间的处理(Date、DateComponents、Calendar、DateFormatter、Locale)_第5张图片
代码:

//设置地区为法国(注意:NSLocale对象构造器有多个重载,视具体情况使用)
dateFormatter.locale = NSLocale(localeIdentifier: "fr_FR") as Locale!
//设置时间格式
dateFormatter.dateStyle = DateFormatter.Style.full
//调用string方法进行转换
convertedDate0 = dateFormatter.string(from: now)
//输出转换结果
print("\(convertedDate0)")


//设置地区为设备所在地
dateFormatter.locale = Locale.current
//设置时间格式
dateFormatter.dateStyle = DateFormatter.Style.full
//调用string方法进行转换
convertedDate0 = dateFormatter.string(from: now)
//输出转换结果
print("\(convertedDate0)")

而格式将有两种设置方式,其实在之前的代码中已经使用了这两种设置方式:
1. 使用Style方式设置:类似于这个样子:dateFormatter.dateStyle = DateFormatter.Style.full
如图:
Swift4中对于日期时间的处理(Date、DateComponents、Calendar、DateFormatter、Locale)_第6张图片
代码:

//设置时间位置为法国
dateFormatter.locale = NSLocale(localeIdentifier: "fr_FR") as Locale!
//设置显示格式为全显示格式
dateFormatter.dateStyle = DateFormatter.Style.full
//转换为String
let convertedDate3 = dateFormatter.string(from: now)
//设置显示格式为Long格式
dateFormatter.dateStyle = DateFormatter.Style.long
//转换为String(发现星期没了)
let convertedDate4 = dateFormatter.string(from: now)
//设置显示格式为Medium格式
dateFormatter.dateStyle = DateFormatter.Style.medium
//转换为String(更短一些了)
let convertedDate5 = dateFormatter.string(from: now)
//设置显示格式为short格式
dateFormatter.dateStyle = DateFormatter.Style.short
//转换为String(似乎好看了一些)
let convertedDate6 = dateFormatter.string(from: now)
  1. 使用Specifer区分符方式设置
    说明:
    EEEE:表示星期几(如 Monday)。使用 1-3 个字母表示周几的缩略写法。
    MMMM:月份的全写(如 October)。使用 1-3 个字母表示月份的缩略写法。
    dd:表示一个月里面日期的数字(如 09 或 15)。
    yyyy:4 个数字表示的年(如 2015)。
    HH:2 个数字表示的小时(如 08 或 19)。
    mm:2 个数字表示的分钟(如 05 或者 54)。
    ss:2 个数字表示的秒。
    zzz:3 个字母表示的时区(如 GMT)。
    GGG:BC 或者 AD。
    其他见:http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns
    如图:
    代码片段6
    代码:
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.locale = Locale.current
let convertedDate7 = dateFormatter.string(from: now)
dateFormatter.dateFormat = "yyyy-MM-dd HH"
dateFormatter.locale = Locale.current
let convertedDate8 = dateFormatter.string(from: now)

设置不同的格式,转换后就成为了不同的样子……

如何将时间字符串转为Date

如图:
Swift4中对于日期时间的处理(Date、DateComponents、Calendar、DateFormatter、Locale)_第7张图片
代码:

//设置转换格式
dateFormatter.dateFormat = "MMM dd, yyyy zzz"
//按照转换格式设置时间(月份缩写 日期 年 时区代码(GMT指格林威治时间,相当于零时区))
let str = "Jan 11, 2018 GMT"
//进行转换
let newDate = dateFormatter.date(from: str)
//输出转换结果
print("\(newDate!)")

如何获取日期时间的各个组成成分

之前的部分我们已经获取了时间,并且将这个时间转换为我们想要格式对应的String对象。现在我们来试着获取一下时间的组成成分。
时间的组成成分使用DateComponents对象进行描述。
回忆一下Date与String之间的转换,它们之间通过DateFormatter对象作为桥梁进行转换,这个桥梁可选设置地区,必须设置格式,然后才能转换。
这里我们想获取时间的成分也需要将Date转换为DateComponents对象,而它们之间也需要一个转换的桥梁——Calendar(日历)。
如图:
Swift4中对于日期时间的处理(Date、DateComponents、Calendar、DateFormatter、Locale)_第8张图片
就像DateFormatter这个桥梁在转换之前设置位置和格式一样,Calendar在转换前也需要设置一个东西——TimeZone(时区)。
让我们来看一下具体的转换过程:
代码片段9
看不清图片可以看一下具体代码:

//获取当前的Calendar(用于作为转换Date和DateComponents的桥梁)
let calendar = Calendar.current
//使用(时区+时间)重载函数进行转换(这里参数in使用TimeZone的构造器创建了一个东七区的时区)
let dateComponets1 = calendar.dateComponents(in: TimeZone.init(secondsFromGMT: 3600*7)!, from: now)
//输出结果(通过DateComponents对象点出对应的时间成分)
print("\(dateComponets1.year!)-\(dateComponets1.month!)-\(dateComponets1.day!) \(dateComponets1.hour!):\(dateComponets1.minute!)")
//使用另一个重载函数进行转换(第一个参数给一个Set集合对象,你给了哪些时间成分对象就可以在后面得到对应的成分)
let dateComponets2 = calendar.dateComponents([Calendar.Component.year,Calendar.Component.month,Calendar.Component.day], from: now)
print("\(dateComponets2.year!)-\(dateComponets2.month!)-\(dateComponets2.day!)")

如何将手动设置的各个时间成分转换为一个完整的日期时间

首先需要有一个DateComponents对象并设置好各个时间成分,然后创建一个转换桥梁Calendar,最后通过Calendar对象的date方法进行转换,如图:
Swift4中对于日期时间的处理(Date、DateComponents、Calendar、DateFormatter、Locale)_第9张图片
代码如下:

//创建一个DateComponents对象
var components = DateComponents.init()
//设置各个时间成分
components.year = 2018
components.month = 1
components.day = 10
components.hour = 8
components.minute = 0
components.second = 0
//创建一个转换桥梁
let cal0 = Calendar.current
//进行转换
var newDate0 = cal0.date(from: components)
//输出转换结果
print("\(newDate0!)")//输出:"2018-01-10 00:00:00 +0000\n"

于是发现了一个问题:
上面的代码在没有指定时区的情况下,输出日期比输入日期少了8小时(这个8小时和我们中国所在的东八区有着一些莫名的联系)……
我们进一步做多个测试看看:
Swift4中对于日期时间的处理(Date、DateComponents、Calendar、DateFormatter、Locale)_第10张图片
于是发现:设置了对应的时区,时间比对应时区向反方向走了对应小时,例如:
设置为零时区,时间不多不少,刚刚好
设置为东一区,时间向西走了一小时,于是少了一小时
设置为东二区,时间向西走了两小时,于是少了两小时
设置为西一区,时间向东走了一小时,于是多了一小时
这是为什么呢?

重点来了

还记得第一个结论吗:Date对象其本身使用的时区是零时区
于是就可以理解了:
我们设置了时间2018-1-10 8.00.00
系统默认使用当前设备的时区(东八区)
于是,components认为我输入的时间是 2018-1-10 8:0:0 +8
然而,Date对象其本身使用的时区是零时区。于是在转换为Date时,会将东八区时间转换为零时区时间,
而零时区时间比东八区时间慢了八小时,于是减去八小时得到零时区时间:2018-1-10 8:0:0 +8对应的零时区时间2018-1-10 0:0:0 +0
这个对应的零时区时间就是转换后Date对象存储的时间
于是直接输出Date对象,得到的是转换为的零时区时间
如果要得到当地时间,则需要对Date对象转换为Components对象,转换时需要将时区重新设置为对应时区,这样,转换过程中,又会基于零时区时间向相同方向走对应的小时数,就走回了当地时区的时间……


完整的代码

//: A UIKit based Playground for presenting user interface

import UIKit

//获取当前之间
let now = Date()
print("\(now)")



/*
 创建一个DateFormatter对象
 该对象用于格式化日期时间(实现Date与String之间的转换)
 */
//创建一个DateFormatter来作为转换的桥梁
let dateFormatter = DateFormatter()
//DateFormatter对象的string方法执行转换
var convertedDate0 = dateFormatter.string(from: now)
//输出转换结果
print("\(convertedDate0)")//发现输出的是:"\n"

/*
 发现上面的代码并没有显示时间,
 试着将地区和格式两个设置放到前面进行组合发现:
 没有地区没有格式       无显示
 有地区没有格式        无显示
 没有地区有格式        显示
 有地区有格式         显示
 于是明白:不显示是因为没有设定格式,于是系统不知道要显示成什么格式
 于是明白:系统默认格式是什么都没有……
 */
//设置时间格式
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
//调用string方法进行转换
convertedDate0 = dateFormatter.string(from: now)
//输出转换结果
print("\(convertedDate0)")

//发现一个问题,我这里设置了时间格式,但是没有设置时间的地区,但是输出的时间却不再是零时区,自动变成东八区了,所以明白:DateFormatter这个对象虽然默认格式为空,但是时间格式却会默认使用系统地区时区,于是明白,非特殊情况(你要设置的时区和系统所在地时区不同),您只需设置时间格式,而不必设置时间地区(时区)


//设置时间采用什么位置(默认采用设备的位置(locale))
//设置为设备所在地时间
dateFormatter.locale =  Locale.current
dateFormatter.dateStyle = DateFormatter.Style.full
let convertedDate1 = dateFormatter.string(from: now)

/*
 设置一个不同的位置,需要指定位置(locale)和对应的位置标识符(locale identifier)
 */
//设置地区为法国(注意:NSLocale对象构造器有多个重载,视具体情况使用)
dateFormatter.locale = NSLocale(localeIdentifier: "fr_FR") as Locale!
//设置时间格式
dateFormatter.dateStyle = DateFormatter.Style.full
//调用string方法进行转换
convertedDate0 = dateFormatter.string(from: now)
//输出转换结果
print("\(convertedDate0)")
//设置地区为设备所在地
dateFormatter.locale = Locale.current
//设置时间格式
dateFormatter.dateStyle = DateFormatter.Style.full
//调用string方法进行转换
convertedDate0 = dateFormatter.string(from: now)
//输出转换结果
print("\(convertedDate0)")


/*
 设置输出结果格式
 可以使用一个枚举类型实现(每一个枚举值都代表一种不同的格式样式)
 也可以使用区分符(yyyy、MM、dd、HH、mm、ss……)来手动指定可是样式
 */
//使用DateFormatter中的Style枚举实现格式设置(这是使用全显示格式)
dateFormatter.dateStyle = DateFormatter.Style.full
//转换为String
let convertedDate2 = dateFormatter.string(from: now)

//设置时间位置为法国
dateFormatter.locale = NSLocale(localeIdentifier: "fr_FR") as Locale!
//设置显示格式为全显示格式
dateFormatter.dateStyle = DateFormatter.Style.full
//转换为String
let convertedDate3 = dateFormatter.string(from: now)
//设置显示格式为Long格式
dateFormatter.dateStyle = DateFormatter.Style.long
//转换为String(发现星期没了)
let convertedDate4 = dateFormatter.string(from: now)
//设置显示格式为Medium格式
dateFormatter.dateStyle = DateFormatter.Style.medium
//转换为String(更短一些了)
let convertedDate5 = dateFormatter.string(from: now)
//设置显示格式为short格式
dateFormatter.dateStyle = DateFormatter.Style.short
//转换为String(似乎好看了一些)
let convertedDate6 = dateFormatter.string(from: now)

/*
 使用区分符设定输出结果格式
 */
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.locale = Locale.current
let convertedDate7 = dateFormatter.string(from: now)
dateFormatter.dateFormat = "yyyy-MM-dd HH"
dateFormatter.locale = Locale.current
let convertedDate8 = dateFormatter.string(from: now)

/*
 将时间字符串转换为Date
 */

//设置转换格式
dateFormatter.dateFormat = "MMM dd, yyyy zzz"
//按照转换格式设置时间
let str = "Jan 11, 2018 GMT"
//进行转换
let newDate = dateFormatter.date(from: str)
//输出转换结果
print("\(newDate!)")

/*
 获取日期时间的组成成分
 可以用NSDateComponents来获取一个日期时间的各组成成分
 NSDateComponents通常和NSCalendar一起使用,
 其中NSCalendar实现了从Date到NSDateComponents的转换
 */

//获取当前的Calendar(用于作为转换Date和DateComponents的桥梁)
let calendar = Calendar.current
//使用(时区+时间)重载函数进行转换(这里参数in使用TimeZone的构造器创建了一个东七区的时区)
let dateComponets1 = calendar.dateComponents(in: TimeZone.init(secondsFromGMT: 3600*7)!, from: now)
//输出结果(通过DateComponents对象点出对应的时间成分)
print("\(dateComponets1.year!)-\(dateComponets1.month!)-\(dateComponets1.day!) \(dateComponets1.hour!):\(dateComponets1.minute!)")
//使用另一个重载函数进行转换(第一个参数给一个Set对象,给了哪些时间成分对象就可以在后面得到对应的成分)
let dateComponets2 = calendar.dateComponents([Calendar.Component.year,Calendar.Component.month,Calendar.Component.day], from: now)
print("\(dateComponets2.year!)-\(dateComponets2.month!)-\(dateComponets2.day!)")

/*
 通过时间成分,获取Date
 即:DateComponents转换为Date
 */

//创建一个DateComponents对象
var components = DateComponents.init()
//设置各个时间成分
components.year = 2018
components.month = 1
components.day = 10
components.hour = 8
components.minute = 0
components.second = 0
//创建一个转换桥梁
let cal0 = Calendar.current
//进行转换
var newDate0 = cal0.date(from: components)
//输出转换结果
print("\(newDate0!)")

/*
 发现一个问题,上面的代码在没有指定时区的情况下,输出日期比输入日期少了8小时(这个8小时和我们中国所在的东八区有着一些莫名的联系……)……
 如果指定了时区为零时区时间,则输出与输入相同
 进一步做多个设置看看
 */

//时区设置为零时区,输出与设置时间相同
components.timeZone = TimeZone.init(secondsFromGMT: 0*3600)!
let cal1 = Calendar.current
var newDate1 = cal1.date(from: components)
print("\(newDate1!)")

//时区设置为东一区,输出比设置少一小时
components.timeZone = TimeZone.init(secondsFromGMT: 1*3600)!
let cal2 = Calendar.current
var newDate2 = cal2.date(from: components)
print("\(newDate2!)")

//时区设置为东二区,输出比设置少两小时
components.timeZone = TimeZone.init(secondsFromGMT: 2*3600)!
let cal3 = Calendar.current
var newDate3 = cal3.date(from: components)
print("\(newDate3!)")

//时区设置为西一区,输出比设置多一小时
components.timeZone = TimeZone.init(secondsFromGMT: -1*3600)!
let cal4 = Calendar.current
var newDate4 = cal4.date(from: components)
print("\(newDate4!)")

/*
 于是发现:设置了对应的时区,时间比对应时区向反方向走了对应小时,例如:
 设置为东一区,时间向西走了一小时,于是少了一小时
 设置为东二区,时间向西走了两小时,于是少了两小时
 设置为西一区,时间向东走了一小时,于是多了一小时

 为何会这样?
 可以这样理解:
 我设置了时间2018-1-10 8.00.00
 系统默认使用当前设置的时区(未设置使用设备所在地时区)
 于是,我设置为东二区时,components认为我输入的是 2018-1-10 8.0.0 +2
 然而,Date对象使用的是零时区的时间。于是在转换为Date时,会将东二区时间转换为格林威治时间,
 而格林威治时间比东二区时间慢了两小时,于是减去两小时得到东二区时间:2018-1-10 8.0.0 +2对应的格林威治时间2018-1-10 6.0.0 +0
 这个对应的格林威治时间就是转换后Date对象存储的时间
 于是直接输出Date对象,得到的是转换为的格林威治时间
 如果要得到当地时间,则需要对Date对象转换为Components对象,转换时需要将时区重新设置为对应时区,这样,转换过程中,又会基于格林威治时间向相同方向走对应的小时数
 */

你可能感兴趣的:(笔记)