如何在Go中使用日期和时间

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Diversity in Tech Fund 来接受捐赠。

介绍

软件旨在使完成工作变得更容易,对于许多人来说,这包括与日期和时间进行交互。 日期和时间值在现代软件中随处可见。 例如,跟踪汽车何时需要维修并让车主知道,跟踪数据库中的更改以创建审计日志,或者只是将一个时间与另一个时间进行比较以确定一个过程需要多长时间。 因此,检索当前时间、操作时间值以从中提取信息并以易于理解的格式向用户显示它们是应用程序的基本属性。

在本教程中,您将创建一个 Go 程序来获取计算机的当前本地时间,然后以更易于阅读的格式将其打印到屏幕上。 接下来,您将解释一个字符串以提取日期和时间信息。 您还将转换两个时区之间的日期和时间值,以及添加或减去时间值以确定两次之间的间隔。

先决条件

要遵循本教程,您将需要:

  • Go version 1.16 or greater installed. To set this up, follow the How To Install Go tutorial for your operating system.

获取当前时间

在本节中,您将使用 Go 的 time 包获取当前时间。 Go 标准库中的 time 包提供了各种与日期和时间相关的函数,可用于使用 time.Time 类型表示特定时间点。 除了时间和日期之外,它还可以保存有关表示的日期和时间所在时区的信息。

要开始创建程序来探索 time 包,您需要为文件创建一个目录。 可以在计算机上的任意位置创建此目录,但许多开发人员倾向于为他们的项目创建一个目录。 在本教程中,您将使用一个名为 projects 的目录。

创建 projects 目录并导航到它:

mkdir projects
cd projects

projects 目录,运行 mkdir 以创建 datetime 目录,然后使用 cd 导航到它:

mkdir datetime
cd datetime

创建项目目录后,使用 nano 或您喜欢的编辑器打开一个 main.go 文件:

nano main.go

main.go 文件中,添加一个 main 函数,该函数将获取当前时间并打印出来:

项目/日期时间/main.go

package main

import (
    "fmt"
    "time"
)

func main() {
    currentTime := time.Now()
    fmt.Println("The time is", currentTime)
}

在这个程序中,time包中的time.Now函数用于获取当前本地时间作为time.Time值,然后将其存储在[X157X ] 多变的。 一旦存储在变量中,fmt.Println 函数就会使用 time.Time 的默认字符串输出格式将 currentTime 打印到屏幕上。

使用 go runmain.go 文件运行程序:

go run main.go

显示您当前日期和时间的输出将类似于以下内容:

OutputThe time is 2021-08-15 14:30:45.0000001 -0500 CDT m=+0.000066626

输出将显示您当前的日期和时间,这与示例不同。 此外,您看到的时区(此输出中的 -0500 CDT)可能会有所不同,因为 time.Now() 返回本地时区的时间。

您可能还会注意到输出中的 m= 值。 该值是 单调时钟 ,Go 在测量时间差异时在内部使用。 单调时钟旨在补偿程序运行时计算机系统时钟的日期和时间的任何潜在变化。 通过使用单调时钟,与五分钟后的 time.Now 值相比,time.Now 值仍然会以正确的结果结束(五分钟间隔),即使系统时钟为在这五分钟的时间间隔内,计算机向前或向后改变一个小时。 对于本教程中的代码或示例,您不需要完全理解它,但如果您想了解更多关于单调时钟以及 Go 如何使用它们的信息,请参阅 time 软件包文档提供了更多详细信息。

现在,虽然您确实显示了当前时间,但它可能对用户没有用。 它可能不是您正在寻找的格式,或者它可能包含的日期或时间部分比您想要显示的要多。

幸运的是,time.Time 类型包括各种方法来获取您想要的日期或时间的特定部分。 例如,如果您只想知道 currentTime 变量的年份部分,您可以使用 Year 方法,或使用 Hour 方法获取当前小时。

再次打开您的 main.go 文件并将一些 time.Time 方法添加到您的输出中以查看它们产生的结果:

项目/日期时间/main.go

...

func main() {
    currentTime := time.Now()
    fmt.Println("The time is", currentTime)
    
    fmt.Println("The year is", currentTime.Year())
    fmt.Println("The month is", currentTime.Month())
    fmt.Println("The day is", currentTime.Day())
    fmt.Println("The hour is", currentTime.Hour())
    fmt.Println("The minute is", currentTime.Hour())
    fmt.Println("The second is", currentTime.Second())
}

接下来,使用 go run 再次运行您的程序:

go run main.go

输出将类似于以下内容:

OutputThe time is 2021-08-15 14:30:45.0000001 -0500 CDT m=+0.000066626
The year is 2021
The month is August
The day is 15
The hour is 14
The minute is 14
The second is 45

与前面的输出一样,您当前的日期和时间将与示例不同,但格式应该相似。 这次在输出中,您将看到与以前一样打印的完整日期和时间,但您还有一个包含年、月、日、小时、分钟和秒的列表。 请注意,月份不是作为数字打印(就像在完整日期中那样),而是显示为英文字符串 August。 这是因为 Month 方法将月份返回为 time.Month 类型而不仅仅是数字,并且该类型旨在在打印为 [ 时打印出完整的英文名称X189X]。

现在,再次更新您程序中的 main.go 文件,并用对 fmt.Printf 的单个函数调用替换各种函数输出,这样您就可以以更接近的格式打印当前日期和时间您可能希望向用户显示:

项目/日期时间/main.go

...

func main() {
    currentTime := time.Now()
    fmt.Println("The time is", currentTime)
    
    fmt.Printf("%d-%d-%d %d:%d:%d\n",
        currentTime.Year(),
        currentTime.Month(),
        currentTime.Day(),
        currentTime.Hour(),
        currentTime.Hour(),
        currentTime.Second())
}

将更新保存到 main.go 文件后,像以前一样使用 go run 命令运行它:

go run main.go

输出将类似于以下内容:

OutputThe time is 2021-08-15 14:30:45.0000001 -0500 CDT m=+0.000066626
2021-8-15 14:14:45

这次你的输出可能更接近你想要的,但仍有一些关于输出的东西可以调整。 月份现在再次以数字格式显示,因为 fmt.Printf 格式使用 %d 告诉 time.Month 类型它应该使用数字而不是 [X160X ],但它只显示为一个数字。 如果您想显示两位数,您可以更改 fmt.Printf 格式来表示,但是如果您还想显示 12 小时时间而不是 24 小时时间,如上面的输出所示? 使用 fmt.Printf 方法,您必须自己进行数学计算。 使用 fmt.Printf 打印日期和时间是可能的,但正如您所见,它最终会变得很麻烦。 这样做,您可能会为要显示的每个部分得到大量行,或者您需要进行一些自己的计算以确定要显示的内容。

在本节中,您创建了一个新程序来使用 time.Now 获取当前时间。 获得当前时间后,您可以使用各种函数,例如 time.Time 类型上的 YearHour 来打印有关当前时间的信息。 不过,以自定义格式显示它确实开始成为很多工作。 为了使这种常见的工作更容易,包括 Go 在内的许多编程语言都提供了一种特殊的方式来格式化日期和时间,类似于 fmt.Printf 可以用来格式化字符串。

打印和格式化特定日期

除了 YearHourtime.Time 类型提供的其他数据相关方法外,它还提供了一个名为 Format 的方法。 Format 方法允许您提供布局 string,类似于提供 fmt.Printffmt.Sprintf 的格式,这将告诉 Format 方法您希望如何打印日期和时间。 在本节中,您将复制在上一节中添加的时间输出,但使用 Format 方法以更简洁的方式。

但是,在使用 Format 方法之前,如果每次运行程序都不会更改,那么更容易看到 Format 如何影响日期和时间的输出。 到目前为止,您使用 time.Now 获取当前时间,因此每次运行时都会显示不同的数字。 Go time 包提供了另一个有用的函数,time.Date 函数,它允许你指定一个特定的日期和时间来表示 time.Time

要开始在程序中使用 time.Date 而不是 time.Now,请再次打开 main.go 文件并更新它以将 time.Now 替换为 time.Date

项目/日期时间/main.go

...

func main() {
    theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local)
    fmt.Println("The time is", theTime)
}

time.Date 函数的参数包括您想要 time.Time 的日期和时间的年、月、日、小时、分钟和秒。 最后两个参数中的第一个占纳秒,最后一个参数是创建时间的时区。 本教程后面将介绍使用时区本身。

保存更新后,使用 go run 运行程序:

go run main.go

输出将类似于以下内容:

OutputThe time is 2021-08-15 14:30:45.0000001 -0500 CDT

您看到的输出现在应该更接近上面的输出,因为您在运行程序时使用的是特定的本地日期和时间,而不是当前日期和时间。 (但是,根据您的计算机设置的时区,时区的显示可能会有所不同。)输出格式仍然与您之前看到的相似,因为它仍然使用默认的 time.Time 格式。 现在您有了一个标准的日期和时间可以使用,您可以使用它来开始调整使用 Format 方法显示时的时间格式。

使用 Format 方法自定义日期字符串

许多其他编程语言包含类似的方式来格式化要显示的日期和时间,但是 Go 构建这些格式的布局的方式可能与您在其他语言中使用过的方式略有不同。 在其他语言中,日期格式使用类似于 Printf 在 Go 中的工作方式,使用 % 字符后跟一个表示要插入的日期或时间部分的字母。 例如,一个 4 位数的年份可能由 %Y 表示。 但是,在 Go 中,日期或时间的这些部分由表示特定日期的字符表示。 要在 Go 日期格式中包含 4 位数的年份,您实际上应该在字符串本身中包含 2006。 这种布局的好处是,您在代码中看到的内容实际上代表了您将在输出中看到的内容。 当您能够看到输出的表示时,可以更轻松地仔细检查您的格式是否与您正在寻找的内容相匹配,并且还可以让其他开发人员更容易理解程序的输出,而无需运行先程序。

Go 在字符串格式中用于日期和时间布局的具体日期是 01/02 03:04:05PM '06 -0700。 如果您查看日期和时间的每个组成部分,您会发现它们每部分都增加一个。 月份首先出现在 01,然后是日期在 02,然后是小时在 03,分钟在 04,第二个在 05,年份在 06(或 2006),最后是时区在 07。 记住此顺序将使将来更容易创建日期和时间格式。 可用于格式化的选项示例也可以在 Go 的 时间包文档 中找到。

现在,使用这个新的 Format 方法来复制和清理您在上一节中打印的日期格式。 它需要几行和函数调用才能显示,使用 Format 方法应该可以更轻松、更清晰地复制。

打开 main.go 文件并添加一个新的 fmt.Println 调用并将其传递给 theTime 并使用 Format 方法格式化日期:

项目/日期时间/main.go

...

func main() {
    theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local)
    fmt.Println("The time is", theTime)

    fmt.Println(theTime.Format("2006-1-2 15:4:5"))
}

如果您查看用于格式的布局,您会看到它使用上面的相同时间来指定日期和时间的格式(2006 年 1 月 2 日)。 需要注意的一点是,小时使用 15 而不是 03 像前面的示例一样。 这表明您希望小时以 24 小时格式而不是 12 小时格式显示。

要查看这种新格式的输出,请保存您的程序并使用 go run 运行它:

go run main.go

输出将类似于以下内容:

OutputThe time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-8-15 14:30:45

您现在看到的输出将类似于上一节末尾的输出,但完成起来要简单得多。 您只需要包含一行代码和一个布局字符串。 Format 函数为您完成剩下的工作。

根据您显示的日期或时间,使用可变长度格式(如您在直接打印数字时最终使用的格式)可能难以让您自己、您的用户或其他试图读取该值的代码阅读。 使用 1 作为月份格式会导致 March 显示为 3,而 October 将使用两个字符并显示为 10。 现在,打开 main.go 并使用另一个更结构化的布局向您的程序添加额外的一行。 在此布局中,在组件上包含 0 前缀并更新小时以使用 12 小时格式:

项目/日期时间/main.go

...

func main() {
    theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local)
    fmt.Println("The time is", theTime)

    fmt.Println(theTime.Format("2006-1-2 15:4:5"))
    fmt.Println(theTime.Format("2006-01-02 03:04:05 pm"))
}

保存代码后,使用 go run 再次运行程序:

go run main.go

输出将类似于以下内容:

OutputThe time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-8-15 14:30:45
2021-08-15 02:30:45 pm

您会看到,通过向布局字符串中的数字添加 0 前缀,新输出中月份的 8 变为 08 以匹配布局。 现在采用 12 小时格式的小时也有自己的 0 前缀。 不过,最终您在输出中看到的内容反映了您在代码中看到的格式,因此如果需要,调整格式会更容易。

很多时候,格式化的日期旨在由其他程序解释,并且每次您想要使用它们时重新创建这些格式可能会成为一种负担。 在某些情况下,您可以使用预定义的格式。

使用预定义格式

有许多常用的日期格式,例如日志消息的时间戳,每次你想使用它们时重新创建它们可能会很麻烦。 对于其中一些情况,time 包包括您可以使用的预定义格式。

一种可用且经常使用的格式是 RFC 3339 中定义的格式。 RFC 是用于定义 Internet 上的标准如何工作的文档,然后其他 RFC 可以相互构建。 例如,有一个 RFC 定义了 HTTP 的工作方式(RFC 2616),还有一些在此基础上进一步定义 HTTP 的 RFC。 因此,对于 RFC 3339,RFC 定义了一种标准格式,用于互联网上的时间戳。 该格式在互联网上广为人知并得到很好的支持,因此在其他地方看到它的机会很高。

time 包中的每个预定义时间格式都由一个 const string 表示,该格式以它们所代表的格式命名。 RFC 3339 格式恰好有两种可用的格式,一种称为 time.RFC3339,另一种称为 time.RFC3339Nano。 格式之间的区别在于 time.RFC3339Nano 版本在格式中包含纳秒。

现在,再次打开 main.go 文件并更新程序以使用 time.RFC3339Nano 格式输出:

项目/日期时间/main.go

...

func main() {
    theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local)
    fmt.Println("The time is", theTime)
    
    fmt.Println(theTime.Format(time.RFC3339Nano))
}

由于预定义格式是具有所需格式的 string 值,因此您只需将通常使用的格式替换为其中一种。

要查看输出,请再次使用 go run 运行您的程序:

go run main.go

输出将类似于以下内容:

OutputThe time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-08-15T14:30:45.0000001-05:00

如果您需要在某处将时间值保存为 string,则可以使用 RFC 3339 格式。 它可以被许多其他编程语言和应用程序读取,并且与日期和时间一样紧凑,可以采用灵活的 string 格式。

在本节中,您更新了程序以使用 Format 方法打印出日期和时间。 使用这种灵活的布局还可以让您的代码看起来与最终输出相似。 最后,您使用了一个预定义的布局字符串,使用一种支持良好的格式打印出日期和时间。 在下一部分中,您将更新程序以将相同的 string 值转换回您可以使用的 time.Time 值。

解析字符串中的日期和时间

通常在开发应用程序时,您会遇到表示为 string 值的日期,您需要以某种方式对其进行解释。 有时,您需要知道值的日期部分,有时您可能需要知道时间部分,而其他时候您可能需要整个值。 除了使用 Format 方法从 time.Time 值创建 string 值之外,Go time 包提供了 time.Parse 函数来转换将 string 转换为 time.Time 值。 time.Parse 函数的工作方式类似于 Format 方法,因为它将预期的日期和时间布局以及 string 值作为参数。

现在,在程序中打开 main.go 文件并更新它以使用 time.Parse 函数将 timeString 解析为 time.Time 变量:

项目/日期时间/main.go

...

func main() {
    timeString := "2021-08-15 02:30:45"
    theTime, err := time.Parse("2006-01-02 03:04:05", timeString)
    if err != nil {
        fmt.Println("Could not parse time:", err)
    }
    fmt.Println("The time is", theTime)
    
    fmt.Println(theTime.Format(time.RFC3339Nano))
}

Format 方法不同,time.Parse 方法还返回一个潜在的 error 值,以防传入的 string 值与第一个提供的布局不匹配范围。 如果您查看使用的布局,您会发现 time.Parse 的布局使用相同的 1 表示月份,2 表示月份等,即在 Format 方法中使用。

保存更新后,您可以使用 go run 运行更新后的程序:

go run main.go

输出将类似于以下内容:

OutputThe time is 2021-08-15 02:30:45 +0000 UTC
2021-08-15T02:30:45Z

在这个输出中有几件事需要注意。 一种是从 timeString 解析的时区使用默认时区,即 +0 偏移量,称为 协调世界时 (UTC)。 由于时间值和布局都不包含时区,因此 time.Parse 函数不知道与哪个时区相关联。 但是,如果您在某些时候需要它,time 包确实包含一个 time.ParseInLocation 函数,因此您可以提供要使用的位置。 需要注意的另一部分在 RFC 3339 输出中。 输出使用 time.RFC3339Nano 布局,但输出不包括任何纳秒。 这是因为 time.Parse 函数不解析任何纳秒,因此该值设置为默认值 0。 当纳秒为 0 时,time.RFC3339Nano 格式将不会在输出中包含纳秒。

time.Parse 方法还可以在解析 string 值时使用 time 包中提供的任何预定义时间布局。 要在实践中看到这一点,请打开 main.go 文件并更新 timeString 值以匹配之前 time.RFC3339Nano 的输出,并更新 time.Parse 参数以匹配:

项目/日期时间/main.go

...

func main() {
    timeString := "2021-08-15T14:30:45.0000001-05:00"
    theTime, err := time.Parse(time.RFC3339Nano, timeString)
    if err != nil {
        fmt.Println("Could not parse time:", err)
    }
    fmt.Println("The time is", theTime)
    
    fmt.Println(theTime.Format(time.RFC3339Nano))
}

更新代码后,您可以保存程序并使用 go run 再次运行它:

go run main.go

输出将类似于以下内容:

OutputThe time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-08-15T14:30:45.0000001-05:00

这一次,Format 方法的输出显示 time.Parse 能够从 timeString 解析时区和纳秒。

在本节中,您使用了 time.Parse 函数来解析任意格式的日期和时间字符串值以及预定义的布局。 但是,到目前为止,您还没有与您所看到的各种时区进行交互。 Go 为 time.Time 类型提供的另一组功能是在不同时区之间转换的能力,您将在下一节中看到。

使用时区

当与世界各地的用户一起开发应用程序时,甚至只跨越几个时区,一种常见的做法是使用 协调世界时 (UTC) 存储日期和时间,然后转换为用户的本地时间,当必需的。 这允许以一致的格式存储数据并使它们之间的计算更容易,因为只有在向用户显示日期和时间时才需要转换。

在本教程的前面部分中,您创建了一个程序,该程序主要根据您自己的本地时区的时间工作。 要将 time.Time 值保存为 UTC,您首先需要将它们转换为 UTC。 您将使用 UTC 方法执行此操作,该方法将返回您调用它的时间的副本,但采用 UTC。

注意: 这部分转换您的计算机的本地时区和UTC之间的时间。 如果您使用的计算机将本地时区设置为与 UTC 匹配的时区,您会注意到在 UTC 之间转换并再次转换不会显示时间差异。


现在,打开您的 main.go 文件以更新您的程序以使用 theTime 上的 UTC 方法返回时间的 UTC 版本:

项目/日期时间/main.go

...

func main() {
    theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local)
    fmt.Println("The time is", theTime)
    fmt.Println(theTime.Format(time.RFC3339Nano))
    
    utcTime := theTime.UTC()
    fmt.Println("The UTC time is", utcTime)
    fmt.Println(utcTime.Format(time.RFC3339Nano))
}

这次您的程序在您的本地时区创建 theTime 作为 time.Time 值,以两种不同的格式打印出来,然后使用 UTC 方法从您的本地时间到 UTC 时间。

要查看程序的输出,您可以使用 go run 运行它:

go run main.go

输出将类似于以下内容:

OutputThe time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-08-15T14:30:45.0000001-05:00
The UTC time is 2021-08-15 19:30:45.0000001 +0000 UTC
2021-08-15T19:30:45.0000001Z

您的输出将根据您的本地时区而有所不同,但在上面的输出中,您会看到第一次打印出来的时间是 CDT(北美中部夏令时间),即 [ X201X] 小时,从 UTC 开始。 调用 UTC 方法并打印 UTC 时间后,您可以看到时间中的小时从 14 变为 19,因为将时间转换为 UTC 增加了五个小时.

也可以使用 time.Time 上的 Local 方法以相同的方式将 UTC 时间转换回本地时间。 再次打开您的 main.go 文件并更新它以添加对 utcTime 上的 Local 方法的调用,以将其转换回您的本地时区:

项目/日期时间/main.go

...

func main() {
    theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local)
    fmt.Println("The time is", theTime)
    fmt.Println(theTime.Format(time.RFC3339Nano))
    
    utcTime := theTime.UTC()
    fmt.Println("The UTC time is", utcTime)
    fmt.Println(utcTime.Format(time.RFC3339Nano))

    localTime := utcTime.Local()
    fmt.Println("The Local time is", localTime)
    fmt.Println(localTime.Format(time.RFC3339Nano))
}

保存文件后,使用 go run 运行程序:

go run main.go

输出将类似于以下内容:

OutputThe time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-08-15T14:30:45.0000001-05:00
The UTC time is 2021-08-15 19:30:45.0000001 +0000 UTC
2021-08-15T19:30:45.0000001Z
The Local time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-08-15T14:30:45.0000001-05:00

您会看到 UTC 时间已转换回您的本地时区。 在上面的输出中,UTC 转换回 CDT 意味着从 UTC 中减去了五个小时,将小时从 19 更改为 14

在本节中,您更新了程序以使用 UTC 方法在本地时区和标准 UTC 时区之间转换日期和时间,然后使用 Local 方法再次返回。

获得日期和时间值后,Go time 包提供的许多附加功能可在您的应用程序中使用。 这些特征之一是确定给定时间是在另一个之前还是之后。

比较两次

由于在比较它们时需要考虑所有变量,因此有时将两个日期相互比较可能非常困难。 例如,需要考虑时区,或者月份之间的天数不同。 time 包提供了一些方法来简化此操作。

time 包提供了两种方法来简化这些比较:BeforeAfter 方法,可用于 time.Time 类型。 这些方法都接受单个时间值并返回 truefalse 取决于它们被调用的时间是在提供的时间之前还是之后。

要查看这些函数的示例,请打开 main.go 文件并更新它以包含两个不同的日期,然后使用 BeforeAfter 比较这些日期以查看输出:

项目/日期时间/main.go

...

func main() {
    firstTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.UTC)
    fmt.Println("The first time is", firstTime)

    secondTime := time.Date(2021, 12, 25, 16, 40, 55, 200, time.UTC)
    fmt.Println("The second time is", secondTime)

    fmt.Println("First time before second?", firstTime.Before(secondTime))
    fmt.Println("First time after second?", firstTime.After(secondTime))

    fmt.Println("Second time before first?", secondTime.Before(firstTime))
    fmt.Println("Second time after first?", secondTime.After(firstTime))
}

更新文件后,保存并使用 go run 运行它:

go run main.go

输出将类似于以下内容:

OutputThe first time is 2021-08-15 14:30:45.0000001 +0000 UTC
The second time is 2021-12-25 16:40:55.0000002 +0000 UTC
First time before second? true
First time after second? false
Second time before first? false
Second time after first? true

由于代码使用 UTC 时区中的明确日期,因此您的输出应与上面的输出匹配。 您会看到,在 firstTime 上使用 Before 方法并提供 secondTime 进行比较时,它是说 true 是 [ X144X] 在 2021-12-25 之前。 当使用firstTimeAfter方法并提供secondTime时,它说2021-08-152021-12-25之后是false . 正如预期的那样,更改调用 secondTime 上的方法的顺序会显示相反的结果。

使用 time 包比较两个日期和时间的另一种方法是 Sub 方法。 Sub 方法将从另一个日期减去一个日期,并使用新类型 time.Duration 返回一个值。 与表示绝对时间点的 time.Time 值不同,time.Duration 值表示时间差。 例如,“在一小时内”将是一个持续时间,因为它的含义基于一天中的当前时间而有所不同,但“中午”代表一个特定的绝对时间。 Go 在很多地方都使用了 time.Duration 类型,例如,当您想要定义函数在返回错误之前应该等待多长时间时,或者在这里,您需要知道一次之间的时间间隔和另一个。

现在,更新您的 main.go 文件以在 firstTimesecondTime 值上使用 Sub 方法并打印出结果:

项目/日期时间/main.go

...

func main() {
    firstTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.UTC)
    fmt.Println("The first time is", firstTime)

    secondTime := time.Date(2021, 12, 25, 16, 40, 55, 200, time.UTC)
    fmt.Println("The second time is", secondTime)

    fmt.Println("Duration between first and second time is", firstTime.Sub(secondTime))
    fmt.Println("Duration between second and first time is", secondTime.Sub(firstTime))

保存文件,然后使用 go run 运行它:

go run main.go

输出将类似于以下内容:

OutputThe first time is 2021-08-15 14:30:45.0000001 +0000 UTC
The second time is 2021-12-25 16:40:55.0000002 +0000 UTC
Duration between first and second time is -3170h10m10.0000001s
Duration between second and first time is 3170h10m10.0000001s

上面的输出表明两个日期之间有 3,170 小时、10 分钟、10 秒和 100 纳秒,关于输出有几点需要注意。 第一个是第一次和第二次之间的持续时间是负值。 这表示第二次是在第一次之后,并且类似于从 0 中减去 5 并得到 -5。 第二要注意的是,持续时间的最大计量单位是一小时,因此它不会将其分解为天或月。 由于一个月中的天数不一致,并且“天”在切换夏令时期间可能具有不同的含义,因此一小时是不会波动的最准确的度量。

在本节中,您更新了程序以使用三种不同的方法进行两次比较。 首先,您使用 BeforeAfter 方法来确定某个时间是在另一个时间之前还是之后,然后您使用 Sub 来查看两次之间的时间间隔。 但是,获得两次之间的持续时间并不是 time 包使用 time.Duration 的唯一方法。 您还可以使用它在 time.Time 值中添加或删除时间,您将在下一节中看到。

添加或减去时间

在编写应用程序时,使用日期和时间的一个常见操作是根据另一个时间确定过去或未来的时间。 它可以用于功能,例如确定下一次订阅何时进行续订,或者自检查某些值以来是否已经有一定的时间。 无论哪种方式,Go time 包都提供了一种处理它的方法。 但是,要根据您已知的日期查找另一个日期,您需要能够定义自己的 time.Duration 值。

创建 time.Duration 值类似于在纸上写持续时间的方式,只是时间单位的乘法。 例如,要创建一个 time.Duration 来表示一个小时,您可以使用 time.Hour 值乘以您希望 time.Duration 表示的小时数来定义它:

oneHour := 1 * time.Hour
twoHours := 2 * time.Hour
tenHours := 10 * time.Hour

除了使用 time.Minutetime.Second 等之外,声明更小的时间单位的处理方式类似:

tenMinutes := 10 * time.Minute
fiveSeconds := 5 * time.Second

一个持续时间也可以添加到另一个持续时间以获得持续时间的总和。 要查看实际情况,请打开 main.go 文件并更新它以声明 toAdd 持续时间变量并向其添加不同的持续时间:

项目/日期时间/main.go

...

func main() {
    toAdd := 1 * time.Hour
    fmt.Println("1:", toAdd)

    toAdd += 1 * time.Minute
    fmt.Println("2:", toAdd)

    toAdd += 1 * time.Second
    fmt.Println("3:", toAdd)
}

完成更新后,保存文件并使用 go run 运行程序:

go run main.go

输出将类似于以下内容:

Output1: 1h0m0s
2: 1h1m0s
3: 1h1m1s

查看输出时,您会看到打印的第一个持续时间是一小时,对应于代码中的 1 * time.Hour。 接下来,您将 1 * time.Minute 添加到 toAdd 值,显示为一小时一分钟的值。 最后,您将 1 * time.Second 添加到 toAdd,这导致 time.Duration 的值为一小时一分一秒。

也可以在一个语句中将持续时间相加,或者从另一个语句中减去持续时间:

oneHourOneMinute := 1 * time.Hour + 1 * time.Minute
tenMinutes := 1 * time.Hour - 50 * time.Minute

接下来,打开您的 main.go 文件并更新您的程序以使用这样的组合从 toAdd 中减去一分一秒:

项目/日期时间/main.go

...

func main() {
    
    ...
    
    toAdd += 1 * time.Second
    fmt.Println("3:", toAdd)
    
    toAdd -= 1*time.Minute + 1*time.Second
    fmt.Println("4:", toAdd)
}

保存代码后,您可以使用 go run 运行程序:

go run main.go

输出将类似于以下内容:

Output1: 1h0m0s
2: 1h1m0s
3: 1h1m1s
4: 1h0m0s

添加到输出中的新第四行显示了您包含的新代码,用于减去 1 * time.Minute1 * time.Second 的总和正常工作,再次得到原始的一小时值。

将这些持续时间与 time.Time 类型的 Add 方法配对使用,您可以获取一个已有的时间值,并确定该时间之前或之后某个其他任意点的时间。 要查看此运行的示例,请打开 main.go 文件并更新它以将 toAdd 值设置为 24 小时,或 24 * time.Hour。 然后,对 time.Time 值使用 Add 方法来查看该点之后 24 小时的时间:

项目/日期时间/main.go

...

func main() {
    theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.UTC)
    fmt.Println("The time is", theTime)

    toAdd := 24 * time.Hour
    fmt.Println("Adding", toAdd)

    newTime := theTime.Add(toAdd)
    fmt.Println("The new time is", newTime)
}

保存文件后,使用 go run 运行程序:

go run main.go

输出将类似于以下内容:

OutputThe time is 2021-08-15 14:30:45.0000001 +0000 UTC
Adding 24h0m0s
The new time is 2021-08-16 14:30:45.0000001 +0000 UTC

查看输出,您会看到将 24 小时添加到 2021-08-15 会导致新日期为 2021-08-16

要减去时间,您还可以使用 Add 方法,这有点违反直觉。 由于 Sub 方法用于获取两个日期之间的时间差,因此您可以使用带有负值的 Addtime.Time 值中减去时间。

再一次,要在你的程序中看到这个运行,打开你的 main.go 文件并更新它以将 24 小时 toAdd 值更改为负值:

项目/日期时间/main.go

...

func main() {
    theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.UTC)
    fmt.Println("The time is", theTime)

    toAdd := -24 * time.Hour
    fmt.Println("Adding", toAdd)

    newTime := theTime.Add(toAdd)
    fmt.Println("The new time is", newTime)
}

保存文件后,使用 go run 再次运行程序:

go run main.go

输出将类似于以下内容:

OutputThe time is 2021-08-15 14:30:45.0000001 +0000 UTC
Adding -24h0m0s
The new time is 2021-08-14 14:30:45.0000001 +0000 UTC

这次在输出中,您将看到新日期不是在原始时间之前添加 24 小时,而是在原始时间之前 24 小时。

在本节中,您使用 time.Hourtime.Minutetime.Second 创建了不同程度的 time.Duration 值。 您还使用 time.Duration 值和 Add 方法在原始值之前和之后获取新的 time.Time 值。 通过结合 time.NowAdd 方法和 BeforeAfter 方法,您可以为您的应用程序访问强大的日期和时间功能。

结论

在本教程中,您使用 time.Now 检索计算机上当前本地时间的 time.Time 值,然后使用 YearMonth、[X148X ] 和其他方法来访问有关该时间的特定信息。 然后,您使用 Format 方法使用自定义格式和预定义格式打印时间。 接下来,您使用 time.Parse 函数来解释包含时间的 string 值,并从中提取时间值。 获得时间值后,您可以使用 UTCLocal 方法来转换 UTC 和本地时区之间的时间。 之后,您使用 Sub 方法获取两个不同时间之间的持续时间,然后使用 Add 方法查找相对于不同时间值的时间。

使用本教程中描述的各种函数和方法将对您的应用程序大有帮助,但如果您有兴趣,Go time 包还包括许多其他功能。

本教程也是 DigitalOcean How to Code in Go 系列的一部分。 该系列涵盖了许多 Go 主题,从第一次安装 Go 到如何使用语言本身。