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

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

代码

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" 时出错:月份超出范围

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


3
为了日后的读者,我编写了一些练习日期解析的练习题 https://github.com/soniah/date_practice - Sonia Hamilton
4
由于某些疯狂的 Go 标准,你的布局应该精确为 2006-01-02T15:04:05.000Z - tharinduwijewardane
8个回答

283

请使用此处描述的确切布局编号和这里的一个不错的博客文章。

因此:

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的第一个提交时间 不对,根据这里的信息。有人知道吗?)。


189
布局号码?什么?为什么?呃! - Darth Egregious
86
他们当时到底在想什么?还是他们在吸什么东西? - Jishnu Prathap
34
我可能对这里的评论有些晚了,但不要害怕使用数字布局,只需使用常量即可使您的代码更加清晰 :) 例如 time.RFC3339。 - Davyd Dzhahaiev
16
对于那些不理解布局编号的人,一开始看起来可能很陌生,但是一旦你习惯了,我认为它至少和典型的布局设备一样有意义(“我应该使用“D”、“d”、“dd”、“DD”等),甚至更有意义。你只需要先了解一下它。 - trey-jones
10
这是为了记忆方便,也就是说,你只需要记住这些字母:1、2、3、4、5、6、7。有一篇很棒的文章讨论了这个问题:https://medium.com/@simplyianm/how-go-solves-date-and-time-formatting-8a932117c41c - amigcamel
显示剩余4条评论

105
使用的布局确实是 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:为什么Golang解析时间不正确?”中所示,必须严格遵守该布局(使用1,2,3,4,5,6,7)。

是的,这也会让澳大利亚人感到困惑!对我来说,月/日格式真的很难理解,我不得不一直看着它。 - Simon Whitehead
3
@SimonWhitehead 我同意。至少,在查阅了资料后,我知道YY、MM、DD、hh、mm、ss代表什么,可以轻松地重新排序。但是在使用Go语言时,即使查阅了资料,我也需要记住1、2、3、4等数字代表的含义。 - VonC
2
我记得的方式是:步骤1) “正确”的日期顺序是年>月>日>小时>分钟>秒>等等。(因为混合字节序将是毫无意义的任意和不一致的,而对于日期来说,大端序比小端序更可取,因为它易于排序。)这将使我们得到1(年),2(月),3(日),[...] 步骤2) Go/Google是美国公司,而美国人将他们的年份放在日期的末尾,所以现在是1(月),2(日),[...],n(年)。步骤3) 时区放在其他所有内容之后,因为时区是一个额外的抽象层。 - mtraceur
1
@mtraceur 是的...我也想念 https://web.archive.org/web/20180501100155/http://fuckinggodateformat.com/ (来自 https://github.com/bdotdub/fuckinggodateformat)。我的意思是,strftime 赢了。 - VonC

105

已经有人回答了,但是为了避免输入 "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


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

46

这篇文章可能晚来了,而且并没有说出任何之前链接中没有提到的内容,但我想给那些注意力不够集中的人一个简短的总结:

go格式化字符串中日期和时间非常重要。它告诉Go语言哪个字段是哪个。一般情况下,它们从左到右依次为1-9:

  • January / Jan / january / jan / 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日还是1月2日呢? - dragonfly02
在格式字符串“01”中,@dragonfly02将是月份,“02”将是日期,无论它们出现在哪里。我不知道为什么Go决定采用这种格式规范,但它就是这样工作的。 - entonio

34

我建议使用time包中的time.RFC3339常量。您可以查看time包中的其他常量。 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); 
}

3
你是不是打算使用分号? - ChristoKiwi

12

可能已经有些晚了,但是对于那些可能遇到这个问题并想使用外部包来解析日期字符串的人来说,这很有帮助。

我尝试寻找一些库,最终找到了这一个:

https://github.com/araddon/dateparse

自 README 中的示例:

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())
}

4
很遗憾,日期中的日月顺序存在歧义。欧洲日期格式将日放在前面,月份放在后面,而美国时间格式则恰好相反。例如“3/5/2004”这个日期就不明确,因为它在美国和欧洲都可以表示一个合法的日期,但是其中的“3”和“5”可能对应日和月或者相反。dateParse 假设使用美国日期格式。 - chmike

2

对于那些遇到这个问题的人,请使用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


-2

如果您曾经在其他语言中使用过时间/日期格式化/解析,您可能会注意到其他语言使用特殊的占位符来进行时间/日期格式化。例如,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/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接