diff --git a/chap-28-Dates-and-time/28.md b/chap-28-Dates-and-time/28.md new file mode 100644 index 0000000000000000000000000000000000000000..70901e9b7a662cb00730818c469592e0c095c703 --- /dev/null +++ b/chap-28-Dates-and-time/28.md @@ -0,0 +1,460 @@ +# 第 28 章:日期和时间 + +![](imgs/dates-time1.jpg) + +## 1 你将在本章中学到什么? + +- 什么是世界标准时间? +- 如何获取当前时间? +- 如何解析时间? +- 如何使用特定布局格式化时间? +- 如何比较两个时间点? +- 如何将分钟,天,年,秒添加到时间? +- 什么是wall clock? +- 什么是monotonic clock? + +## 2 涉及到的技术点 +- 时区 + +- 时间偏移13:13:55 + +- 世界标准时间 + +- 桌面时钟 + +- Monotonic clock + +- Unix Epoch + +## 3 如何得到当前时间 + +在许多情况下,您需要有当前的日期和时间。要获当前时间信息,您可以调用函数 time.Now()。 +``` +// dates-and-time/main.go + +package main + +import ( + "fmt" + "time" +) + +func main() { + now := time.Now() + fmt.Printf("%s", now) +} + +``` +我们调用 time.Now() 然后打印结果。time.Now() 返回一个 time.Time 类型的变量。上述脚本的输出为: + +```2019-02-12 13:13:55.429243 +0100 CET m=+0.000329111``` + +![](imgs/time_fmt.b1d79b88.png) +fmt.Print[fig:Time-outputed-by]返回的时间 +在上图,您可以看到输出的不同部分的含义。 + +你可以看到你获得了日期和时间。同样,你还获得时区信息。 + +时间偏移。这意味着当前显示的时间是世界标准时间 (UTC) + 01 小时 00 分钟。这个时间偏移通常被命名为 UTC+01:00 + +CET 表示中欧时间。这是UTC+01:00时间偏移的另一个名称 + +时区是人与人之间交流时间的重要元素。如果您正在开发API并在字段中输出时间,则必须告诉您的用户您使用的是哪个时区。例如,巴西的“13:13:55”和斯德哥尔摩的“13:13:55”不是指同一时间点,根本不是! + +```2009-11-10 23:00:00 +0000 UTC m=+0.000000001``` + +这是 time.Now() 的另一个输出结果。 您可以看到这里的时区是不同的。此处显示的时间是协调世界时 (UTC)。 + +## 4 Uinx Epoch +“Unix Epoch”是自 1970 年 1 月 1 日 00:00:00 UTC 以来经过的秒数。 + +使用 time.Time 类型的变量,您可以使用 Unix() 方法获取 Unix Epoch +``` +now := time.Now() +log.Println(now.Unix()) +// > 2021/02/11 18:29:35 1613064575 +``` +## 5 同一个时间点,不同的时钟 + +从计算机的角度来看,我们通常所说的“时间”有不同的含义。 但是为了更好地理解这些特殊含义,我们首先需要了解计算机如何跟踪时间。 +### 5.1 硬件时钟 +您可以找到一个记录时间的小型硬件设备连接到计算机的主板上。 + +在这个设备内部,你通常可以找到一块以恒定速率振荡的石英。它只需要很少的能量便可产生震荡。 + +拔掉计算机电源后,此设备将继续跟踪时间(直到电池没电)。 +### 5.2 桌面时钟(Wall Clock) + +桌面时钟主要用于告诉用户时间。 + +### 5.3 单调时钟( Monotonic Clock) + +还有另一个时钟:单调时钟。 该时钟用于告诉两个时间点之间经过的时间。 + +挂钟可能会发生变化。例如,时钟可能会稍微提前。因此,系统可能需要将其与参考时钟同步。 + +因此,我们不能相信这个时钟能够提供经过时间的精确测量。 + +我们需要一个以恒定速率增加时间的时钟来正确测量经过的时间。这是单调时钟。 + +你可以在它的名字中找到恒定时间变化的概念:单调意味着“乏味、重复或缺乏多样性。”[@wiki-monotonous]。 + +使用这个时钟去计算持续时间。 + +## 6 格式化时间 + +在本节中,我们将研究一个非常常见的用例:格式化时间。 您可以通过多种方式格式化和显示程序中的时间; 有些是由规范指定的(例如,RFC 3339),有些不是,直接来自巧妙的开发人员的想法。 + +### 6.1 如何使用```Format```方法 + +```Format```方法只有一个参数:一个字符串 + +这个字符串告诉 Go 需要如何格式化时间。 如果你使用另一种语言,你可能会期待这样的事情: +``` +YYYY-MM-DD h:i:s +``` + +这里YYYY的意思是用四位数字显示年份; MM 指示程序用两位数显示月份...但在 Go 中,格式的写法不同: +``` +package main + +import ( + "fmt" + "time" +) + +func main() { + now := time.Now() + fmt.Println(now.Format("Mon Jan 2 ")) +} +``` +- 我们首先通过调用函数 time.Now() 来获取当前时间 +- 然后我们这次用Format方法格式化 +- 输出结果 + +此代码块将输出以下字符串: +``` +Wed Feb 13 +``` + +#### 6.1.0.1 参考时间 + +时间格式为“Mon Jan 2”。 这是因为 Go 中的格式是根据参考日期定义的: +``` +Mon Jan 2 15:04:05 -0700 MST 2006 +``` +- Monday +- January 2 from the year 2006 at 15:04:05 +- MST 指山地标准时间。 这是时区(UTC-7)。 + +### 6.2 特殊的时间格式 +| Cell 1 | Cell 2 | +在 ```time```包中,您会发现一些具有常见格式的常量: + + +| 规格 | Go 常量 |例子 | +|----------|----------|----- +| RFC 3339 | time.RFC3339 | 2019-02-13T13:48:10+01:00 | +| ANSIC | time.ANSIC | Wed Feb 13 13:49:17 2019 | +| RFC 822 | time.RFC822 | 13 Feb 19 13:49 CET | +| RFC 822 | time.RFC822Z | 13 Feb 19 13:50 +0100 | +| RFC 1123 | time.RFC1123 | Wed, 13 Feb 2019 13:51:06 CET | +| RFC 339 (with ns) | time.RFC3339Nano | 2019-02-13T13:51:44.298774+01:00 | +| UNIX | time.UnixDate | Wed Feb 13 13:52:29 CET 2019 | + +一些特殊的时间格式 + +以下是如何使用格式 **time.RFC3339**的示例: +``` +// dates-and-time/formatting/main.go +package main + +import ( + "fmt" + "time" +) + +func main() { + fmt.Println(time.Now().Format(time.RFC3339)) +} +``` + +输出: +``` +2019-02-15T13:20:36+01:00 +``` + +### 6.3 如何输出指定地点的时间 + +如果您向用户公开日期,您希望在他们各自的时区打印它们: +``` +// dates-and-time/location/main.go +package main + +//... + +func main() { + now := time.Now() + fmt.Printf("%s\n", now) + + loc, err := time.LoadLocation("America/New_York") + if err != nil { + panic(err) + } + + nowNYC := now.In(loc) + fmt.Printf("%s\n", nowNYC) +} +``` +输出: +``` +2021-02-11 18:45:36.949939 +0100 CET m=+0.000601934 +2021-02-11 12:45:36.949939 -0500 EST +``` + +这两个日期代表同一时间点,但位于两个不同的地点:法国和纽约。 + +函数 ```time.LoadLocation```接受一个字符串作为参数,它代表来自 IANA 时区数据库的时区名称。 + +## 7 如何解析字符串中包含的日期/时间 + +您可以使用时间包中的```Parse```函数。 这个函数有两个参数: +- 你要解析的时间格式(Go需要知道你输入的数据是什么格式) +- 包含您的时间格式的字符串 + +该函数将返回一个```time.Time```或一个错误(如果时间解析失败)。 这是一个例子: +``` +// dates-and-time/parse/main.go + +timeToParse := "2019-02-15T07:33-05:00" +t,err := time.Parse("2006-01-02T15:04-07:00",timeToParse) +if err != nil { + panic(err) +} + +fmt.Println(t) +``` + +- 我们将字符串“2019-02-15T07:33-05:00”放入变量timeToParse。 +- 调用```time.Parse``` + +我是如何设法找到正确的格式的? 您必须查看一个示例,然后构建格式字符串以使日期的每个部分与参考日期匹配。 + +当从 JSON 解析日期时,默认使用格式 ```time.RFC3339```。 + +## 8 如何获得两时间点之间经过的时间 + +要获取两时间之间经过的时间,您可以使用 ```time.Time``` 的方法 ```Sub``` : +``` +// dates-and-time/elapsed/main.go + +start := time.Now() +err := ioutil.WriteFile("/tmp/thisIsATest", []byte("TEST"), 0777) +if err != nil { + panic(err) +} +end := time.Now() +elapsed := end.Sub(start) +fmt.Printf("process took %s", elapsed) +``` +- 我们将当前时间保存在变量 start 中 +- 我们将数据写入文件。 +- 然后我们再次获取当前时间。 我们将时间保存到变量end中 +- 经过的持续时间是用 call ```end.Sub(start)``` 计算的 +- 我们输出返回的持续时间(elapsed 是 time.Duration 类型) + +![](imgs/3.png) + +## 9 time.Duration 类型 + +类型 ```time.Duration``` 表示两个时刻之间经过的时间。 这是它在标准库中的定义: + +``` +// Duration 表示两个瞬间之间经过的时间 +// 作为 int64 纳秒计数。 代表限制了 +// 最大可表示持续时间约为 290 年。 + type Duration int64 +``` +它的底层类型是 int64。 它表示两个时间点之间的纳秒数。 + +## 10 如何检查时间是否在两点之间 + +```time.Time``` 类型有两个方便的方法: +``` +Before + +After +``` +这两种方法都接受 time.Time 作为输入。 + +让我们举个例子: +``` +// dates-and-time/comparison/main.go + +location, err := time.LoadLocation("UTC") +if err != nil { + panic(err) +} + +firstJanuary1980 := time.Date(1980,1,1,0,0,0,0,location) + +timeToParse := "2019-02-15T07:33-02:00" +t,err := time.Parse("2006-01-02T15:04-07:00",timeToParse) +if err != nil { + panic(err) +} + +now := time.Now() +if t.After(firstJanuary1980) && t.Before(now) { + fmt.Println(t, "is between ", firstJanuary1980, "and",now) +}else { + fmt.Println("not in between") +} +``` +## 11 如何向时间添加天、小时、分钟、秒 + +- 当需要添加特定的天数、月数或年数时,可以使用 ```AddDate```方法 + + 该方法以三个整数作为参数: + 1. 年数 + + 2. 月数 + + 3. 天数 + +- 当您需要添加更具体的时间量时,您可以使用添加(例如:纳秒、微秒、毫秒、秒、分钟、小时)。 +``` +// dates-and-time/add/main.go + +now := time.Now() + +// + 12 years +// + 1 Month +// + 3 days +now = now.AddDate(12,1,3) + + +now = now.Add(time.Nanosecond * 1) +now = now.Add(time.Microsecond * 5)您希望每天在开始日期和结束日期之间向用户显示。 + +为此,您可以使用 AddDate 方法。 + +这三个数量将添加到日期中。 然后返回修改日期: +now = now.Add(time.Millisecond * 5) +now = now.Add(time.Second * 5) +now = now.Add(time.Minute * 5) +now = now.Add(time.Hour * 5) + +``` +时间包定义了以纳秒为单位表示纳秒、微秒、毫秒、秒、分钟和小时的常量。 我们可以使用这些常量来创建自定义持续时间。 + +## 12 时间迭代 + +您希望每天在开始日期和结束日期之间向用户显示。 + +为此,您可以使用 AddDate 方法。 + +这三个数量将添加到日期中。 然后返回修改日期: + +``` +start, err := time.Parse("2006-01-02", "2019-02-19") +startPlus1Day := start.AddDate(0,0,1) +``` +我们在开始日期中添加了一天、零个月和零年。 我们可以在 for 循环中使用这个函数来迭代两个限制日期之间的每一天: +``` +// dates-and-time/iterate/main.go + +func main() { + start, err := time.Parse("2006-01-02", "2019-02-19") + if err != nil { + panic(err) + } + end , err := time.Parse("2006-01-02", "2020-07-17") + if err != nil { + panic(err) + } + for i := start; i.Unix() < end.Unix(); i = i.AddDate(0, 0, 1) { + fmt.Println(i.Format(time.RFC3339)) + } +} +``` +这里我们定义了两个日期:开始和结束。 这些日期将代表我们的开始和停止时间点。 + +然后我们可以创建一个for循环。 for 循环由三部分组成: +1. init:这条指令在for循环开始前只执行一次 +2. 条件:这个表达式必须返回一个布尔值(真或假)。 在每次迭代之前都会对其进行评估。 如果它是假的,循环将停止。 +3. post语句:每次迭代后执行此语句 + +for 循环的 init 阶段很简单,我们创建了一个新变量 namedi。 然后我们在每次迭代之前检查结束日期(变量 end)是否大于当前迭代日期(变量 i)。 post 语句将在当前迭代日期上增加一天(使用 AddDate 方法)。 + +通过这个循环,我们将在开始和结束日期之间迭代每一天。 + +如果您想迭代几小时、几分钟或几秒怎么办? 您将不得不使用 Add 方法: + +``` +for i := start; i.Unix() < end.Unix(); i = i.Add(time.Minute*2) { + fmt.Println(i.Format(time.RFC3339)) +} +``` +我们没有在日期上加一天,而是加了 2 分钟。 因此,for 循环将带我们从 2019 年 2 月 2 日午夜到 2019 年 7 月 17 日,步长为 2 分钟。 这正好是 370.080 次迭代! + +## 13 自我测试 +### 13.1 问题 +1. UTC 的含义是什么? +2. 如何获取当前时间? +3. time.Duration 的基本类型是什么? +4. 正误判段. 2021-02-11 19:23:03.465196 +0100 CET, 2021-02-11 13:23:03.465196 -0500 EST 和 2021-02-11 18:25:196 分别代表三个不同的时间。 + +5. 如何将时间 t 添加 5 分钟? + +6. 如何将两年零一天添加到时间 t? + +### 13.2 答案 +1. What is the signification of UTC? + 1.协调世界时间 +2. 如何获取当前时间? + 1. time.Now() +3. time.Duration 的基本类型是什么? + 1. int64 +4. 正误判段. 2021-02-11 19:23:03.465196 +0100 CET, 2021-02-11 13:23:03.465196 -0500 EST 和 2021-02-11 18:25:196 分别代表三个不同的时间。 + 1. 错误,它们代表相同的时间点。 + 2. 2021-02-11 18:25:06.623301 +0000 UTC是这一时刻在 UTC 中的表示 + 3. 2021-02-11 19:23:03.465196 +0100 CET 是这一时刻与巴黎(法国)时区的表示 + 4. 2021-02-11 13:23:03.465196 -0500 EST 代表这一时刻与纽约(美国)时区 +5. 如何将时间 t 添加 5 分钟? + +``` +t = t.Add(time.Minute*5) +``` +6. 如何将两年零一天添加到时间 t? +``` +t = t.AddDate(2,0,1) +``` +## 14 关键要点 +- 时间包中包含了操作时间所需的一切。 + +- 要获取当前时间,您可以使用 time.Now() 返回一个 time.Time。 + +- 要将 time.Time 转换为字符串,您可以使用 Format 方法。 它接受一个字符串作为参数:layout。 + +- 要解析表示时间的字符串,可以使用函数 time.Parse(layout, value string)(time.Time, error) + +- **layout**是一个字符串,表示时间的“格式”。 + +- 布局带有参考日期:“Mon Jan 2 15:04:05 MST 2006” + +- 常用格式在时间包中定义为常量。 最常用的是```time.RFC3339``` + +- 可以使用 ```Before``` 和 ```After``` 方法进行两次比较 + +- ```time.Duration``` 表示两个时间点之间经过的持续时间。 + +- 您可以使用将 ```*time.Location``` 作为输入的方法 In 表示不同时区中的时间(可以使用 - +可以使用 ```Before``` 和 ```After``` 方法进行两次比较 + +- ```time.Duration``` 表示两个时间点之间经过的持续时间。 + +- 您可以使用将 ```*time.Location``` 作为输入的方法 In 表示不同时区中的时间(可以使用 ```time.LoadLocation(tzName)``` 加载) +----List_of_tz_database_time_zones +1. INNA:互联网号码分配机构 +2. 时区列表可参考:https://en.wikipedia.org/wiki/List_of_tz_database_time_zones diff --git a/chap-28-Dates-and-time/imgs/3.png b/chap-28-Dates-and-time/imgs/3.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec005b86d13ca0aae21a8d6e9a10ae090a56a62 Binary files /dev/null and b/chap-28-Dates-and-time/imgs/3.png differ diff --git a/chap-28-Dates-and-time/imgs/dates-time1.jpg b/chap-28-Dates-and-time/imgs/dates-time1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c75fe978113c71da835927659763616508a7431f Binary files /dev/null and b/chap-28-Dates-and-time/imgs/dates-time1.jpg differ diff --git a/chap-28-Dates-and-time/imgs/time_fmt.b1d79b88.png b/chap-28-Dates-and-time/imgs/time_fmt.b1d79b88.png new file mode 100644 index 0000000000000000000000000000000000000000..5aa240f324f3b434f3df86914786bbb5dedd2391 Binary files /dev/null and b/chap-28-Dates-and-time/imgs/time_fmt.b1d79b88.png differ