ChatGPT解决这个技术问题 Extra ChatGPT

在 Go 中解析 RFC-3339 / ISO-8601 日期时间字符串

我尝试在 Go 中解析日期字符串 "2014-09-12T11:45:26.371Z"。这种时间格式定义为:

RFC-3339 日期时间

ISO-8601 日期时间

代码

layout := "2014-09-12T11:45:26.371Z"
str := "2014-11-12T11:45:26.371Z"
t, err := time.Parse(layout , str)

我收到了这个错误:

解析时间“2014-11-12T11:47:39.489Z”:超出范围的月份

如何解析这个日期字符串?

对于未来的读者,我写了一些练习日期解析的练习github.com/soniah/date_practice
由于一些疯狂的 go 标准,您的布局应该完全是 2006-01-02T15:04:05.000Z

R
RickyA

使用所描述的确切布局编号 here 和一篇不错的博文 here

所以:

layout := "2006-01-02T15:04:05.000Z"
str := "2014-11-12T11:45:26.371Z"
t, err := time.Parse(layout, str)

if err != nil {
    fmt.Println(err)
}
fmt.Println(t)

给出:

>> 2014-11-12 11:45:26.371 +0000 UTC

我知道。令人难以置信。也是第一次抓到我。 Go 只是没有对日期时间组件 (YYYY-MM-DD) 使用抽象语法,但是这些确切的数字( 我认为 go 的第一次提交的时间 不,根据 this。有人知道?)。


布局数字?什么?为什么?啊!
他们在想什么! ?还是吸烟?
我在这里的评论可能有点晚了,但不要害怕数字布局,只需使用常量,你的代码就会很干净:) 例如 time.RFC3339
对于那些没有得到布局数字的人,我承认它乍一看很陌生,但是一旦你习惯了它,我认为它至少和典型的布局设备一样有意义('我是否使用“D”, “d”、“dd”、“DD”等?),可能更有意义。你只需要先了解它。
这是为了助记,也就是说,你只需要记住1、2、3、4、5、6、7这些字母。有一篇很棒的文章讨论了这个问题:medium.com/@simplyianm/…
C
Community

要使用的布局确实是 RickyAanswer 中描述的“2006-01-02T15:04:05.000Z”。
这不是“第一次提交 go 的时间”,而是一种记忆所述布局的助记方式。
pkg/time

布局中使用的参考时间是:

Mon Jan 2 15:04:05 MST 2006

即 Unix 时间 1136239445。由于 MST 为 GMT-0700,因此可以认为参考时间为

 01/02 03:04:05PM '06 -0700

(1,2,3,4,5,6,7,前提是你记得1代表月,2代表日,对我这种欧洲人来说不容易,习惯了日月日期格式)

如“time.parse : why does golang parses the time incorrectly?”中所示,该布局(使用 1、2、3、4、5、6、7)必须完全遵守。


是的,这也吸引了澳大利亚人! MM/DD 不适合我计算,我必须继续查看它。
@SimonWhitehead 我同意。至少,一旦我查找它,我就知道 YY、MM、DD、hh、mm、ss 代表什么,我可以轻松地重新订购它们。使用 Go,即使在查找之后,我也需要记住 1、2、3、4... 代表什么。
我记得的方式是:步骤 1)“正确”的日期顺序是年 > 月 > 日 > 小时 > 分钟 > 秒 > 等等。(因为混合端将是毫无意义的任意和不一致的,并且对于日期大 - endian 比 little-endian 更可取,因为它对排序友好。)这将使我们得到 1(年)、2(月)、3(日)、[...] 第 2 步)Go/Google 是美国人,美国人把他们的年在日期的末尾,所以改为 1(月)、2(日)、[...]、n(年) 第 3 步)时区在其他所有内容之后,因为时区是一个额外的抽象层。
@mtraceur 是的...我也很想念 web.archive.org/web/20180501100155/http://…(来自 github.com/bdotdub/fuckinggodateformat)。我的意思是,strftime FTW。
r
robstarbuck

正如回答的那样,但为了节省为布局输入 "2006-01-02T15:04:05.000Z",您可以使用包的常量 RFC3339

str := "2014-11-12T11:45:26.371Z"
t, err := time.Parse(time.RFC3339, str)

if err != nil {
    fmt.Println(err)
}
fmt.Println(t)

https://play.golang.org/p/Dgu2ZvHwTh


此外,如果您查看包常量(在上面的答案中链接),还可以使用许多其他常见格式。如果您需要稍有不同的东西,请将它们作为起点。
这个和几个答案推荐 2006-01-02T15:04:05.000Z 并提到 Go 的 time.RFC3339 也可以工作。但是time.RFC3339 = "2006-01-02T15:04:05Z07:00"。就 time.Parsetime.ParseInLocation 的作用而言,这两种格式是否完全相同?
没错@Miles,这个测试证实了它play.golang.org/p/T3dW1kTeAHl
L
Loren Osborn

这对聚会来说已经很晚了,并没有真正说任何尚未以一种或另一种形式说过的话,主要是通过上面的链接,但我想给那些注意力不集中的人做一个 TL;DR 回顾:

go 格式字符串的日期和时间非常重要。这就是 Go 知道哪个字段是哪个字段的方式。它们一般是从左到右的 1-9 如下:

一月/一月/一月/一月/01/_1(等)是一个月

02 / _2 是一个月中的一天

15 / 03 / _3 / PM / P / pm /p 是小时和子午线(下午 3 点)

04 / _4 表示分钟

05 / _5 是几秒钟

2006 / 06 是一年

-0700 / 07:00 / MST 适用于时区

.999999999 / .000000000 等是部分秒(我认为区别在于是否删除了尾随零)

Mon / Monday 是一周中的某一天(实际上是 01-02-2006),

所以,不要写“01-05-15”作为你的日期格式,除非你想要“月-秒-小时”

(......再次,这基本上是上面的总结。)


那么它如何知道 02/01/2006 是 2006 年 2 月 1 日还是 2006 年 1 月 2 日?
格式字符串 01 中的 @dragonfly02 将是月份,02 将是日期,无论它们出现在哪里。我不知道为什么 Go 决定使用这个格式规范,但这就是它的工作原理。
N
Nishant Rawat

我建议使用 time 包中的 time.RFC3339 常量。您可以从时间包中检查其他常量。 https://golang.org/pkg/time/#pkg-constants

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("Time parsing");
    dateString := "2014-11-12T11:45:26.371Z"
    time1, err := time.Parse(time.RFC3339,dateString);
    if err!=nil {
    fmt.Println("Error while parsing date :", err);
    }
    fmt.Println(time1); 
}

你的意思是使用分号吗?
s
stevenferrer

这可能太晚了,但这适用于可能偶然发现此问题并可能希望使用外部包来解析日期字符串的人。

我试过寻找一个图书馆,我找到了这个:

https://github.com/araddon/dateparse

自述文件中的示例:

package main

import (
    "flag"
    "fmt"
    "time"

    "github.com/apcera/termtables"
    "github.com/araddon/dateparse"
)

var examples = []string{
    "May 8, 2009 5:57:51 PM",
    "Mon Jan  2 15:04:05 2006",
    "Mon Jan  2 15:04:05 MST 2006",
    "Mon Jan 02 15:04:05 -0700 2006",
    "Monday, 02-Jan-06 15:04:05 MST",
    "Mon, 02 Jan 2006 15:04:05 MST",
    "Tue, 11 Jul 2017 16:28:13 +0200 (CEST)",
    "Mon, 02 Jan 2006 15:04:05 -0700",
    "Thu, 4 Jan 2018 17:53:36 +0000",
    "Mon Aug 10 15:44:11 UTC+0100 2015",
    "Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)",
    "12 Feb 2006, 19:17",
    "12 Feb 2006 19:17",
    "03 February 2013",
    "2013-Feb-03",
    //   mm/dd/yy
    "3/31/2014",
    "03/31/2014",
    "08/21/71",
    "8/1/71",
    "4/8/2014 22:05",
    "04/08/2014 22:05",
    "4/8/14 22:05",
    "04/2/2014 03:00:51",
    "8/8/1965 12:00:00 AM",
    "8/8/1965 01:00:01 PM",
    "8/8/1965 01:00 PM",
    "8/8/1965 1:00 PM",
    "8/8/1965 12:00 AM",
    "4/02/2014 03:00:51",
    "03/19/2012 10:11:59",
    "03/19/2012 10:11:59.3186369",
    // yyyy/mm/dd
    "2014/3/31",
    "2014/03/31",
    "2014/4/8 22:05",
    "2014/04/08 22:05",
    "2014/04/2 03:00:51",
    "2014/4/02 03:00:51",
    "2012/03/19 10:11:59",
    "2012/03/19 10:11:59.3186369",
    // Chinese
    "2014年04月08日",
    //   yyyy-mm-ddThh
    "2006-01-02T15:04:05+0000",
    "2009-08-12T22:15:09-07:00",
    "2009-08-12T22:15:09",
    "2009-08-12T22:15:09Z",
    //   yyyy-mm-dd hh:mm:ss
    "2014-04-26 17:24:37.3186369",
    "2012-08-03 18:31:59.257000000",
    "2014-04-26 17:24:37.123",
    "2013-04-01 22:43",
    "2013-04-01 22:43:22",
    "2014-12-16 06:20:00 UTC",
    "2014-12-16 06:20:00 GMT",
    "2014-04-26 05:24:37 PM",
    "2014-04-26 13:13:43 +0800",
    "2014-04-26 13:13:44 +09:00",
    "2012-08-03 18:31:59.257000000 +0000 UTC",
    "2015-09-30 18:48:56.35272715 +0000 UTC",
    "2015-02-18 00:12:00 +0000 GMT",
    "2015-02-18 00:12:00 +0000 UTC",
    "2017-07-19 03:21:51+00:00",
    "2014-04-26",
    "2014-04",
    "2014",
    "2014-05-11 08:20:13,787",
    // mm.dd.yy
    "3.31.2014",
    "03.31.2014",
    "08.21.71",
    //  yyyymmdd and similar
    "20140601",
    // unix seconds, ms
    "1332151919",
    "1384216367189",
}

var (
    timezone = ""
)

func main() {
    flag.StringVar(&timezone, "timezone", "UTC", "Timezone aka `America/Los_Angeles` formatted time-zone")
    flag.Parse()

    if timezone != "" {
        // NOTE:  This is very, very important to understand
        // time-parsing in go
        loc, err := time.LoadLocation(timezone)
        if err != nil {
            panic(err.Error())
        }
        time.Local = loc
    }

    table := termtables.CreateTable()

    table.AddHeaders("Input", "Parsed, and Output as %v")
    for _, dateExample := range examples {
        t, err := dateparse.ParseLocal(dateExample)
        if err != nil {
            panic(err.Error())
        }
        table.AddRow(dateExample, fmt.Sprintf("%v", t))
    }
    fmt.Println(table.Render())
}

不幸的是,日月顺序存在歧义。欧洲日期格式给出第一天和第二个月,而美国时间格式使用相反。像 2004 年 3 月 5 日这样的日期是模棱两可的。日期在美国和欧洲格式中有效,但 3 和 5 可能对应于日和月或相反。 dateParse 采用美国格式。
n
nghtstr

对于那些遇到这种情况的人,请使用 time.RFC3339"2006-01-02T15:04:05.000Z" 的字符串常量。原因如下:

regDate := "2007-10-09T22:50:01.23Z"

layout1 := "2006-01-02T15:04:05.000Z"
t1, err := time.Parse(layout1, regDate)

if err != nil {
    fmt.Println("Static format doesn't work")
} else {
    fmt.Println(t1)
}

layout2 := time.RFC3339
t2, err := time.Parse(layout2, regDate)

if err != nil {
    fmt.Println("RFC format doesn't work") // You shouldn't see this at all
} else {
    fmt.Println(t2)
}

这将产生以下结果:

Static format doesn't work
2007-10-09 22:50:01.23 +0000 UTC

这是Playground Link


u
user27111987

如果您使用过其他语言的时间/日期格式/解析,您可能已经注意到其他语言使用特殊的占位符进行时间/日期格式。例如 ruby 语言使用

%d for day
%Y for year

等等。Golang 没有使用上述代码,而是使用日期和时间格式的占位符,看起来只是日期和时间。 Go 使用标准时间,即:

Mon Jan 2 15:04:05 MST 2006  (MST is GMT-0700)
or 
01/02 03:04:05PM '06 -0700

所以如果你注意到 Go 使用

01 for the day of the month,
02 for the month
03 for hours,
04 for minutes
05 for second
and so on

因此,例如解析 2020-01-29,布局字符串应为 06-01-02 或 2006-01-02。

您可以在此链接中参考完整的占位符布局表 - https://golangbyexample.com/parse-time-in-golang/