热身
在讲这个问题之前,先来看一道代码题:
1 | package main |
这段代码的输出是什么(假定运行时刻的时间是 2017-09-07 18:05:32)?
什么?你已经知道答案了?那你是大神,可以跳过这篇文章了。
一、神奇的日期
刚接触 Golang 时,阅读代码的时候总会在代码中发现这么一个日期,
2006-01-02 15:04:05
刚看到这段代码的时候,我当时想:这个人好随便啊,随便写一个日期在这里,但是又感觉还挺方便的,格式清晰一目了然。也没有更多的在意了。
之后一次做需求的时候轮到自己要格式化时间了,仿照它的样子,写了一个日期格式来格式化,差不多就是上面代码题上写的那样。殊不知,运行完毕后,结果令人惊呆。。。
运行结果如下:
1 | 2017-09-07 18:06:43 |
顿时就犯糊涂了:怎么就变成这个鸟样子了?format 不认识我的日期?这么标准的日期都不认识?
二、开始探究
查阅了资料,发现原来这个日期就是写死的一个日期,不是这个日期就不认识,就不能正确的格式化。记住就好了。
但是,还是觉得有点纳闷。为什么输出日期是这个乱的?仔细观察这个日期,06 年,1 月 2 日下午 3 点 4 分 5 秒,查阅相关资料还有 -7 时区,Monday,数字 1~7 都有了,而且都不重复。难道有什么深刻含义?还是单纯的为了方便记忆?
晚上睡觉前一直在心里想。突然想到:这些数字全都不重复,那岂不就是说,每个数字就能代表你需要格式化的属性了?比如,解析格式化字符串的时候,遇到了 1,就说明这个地方要填的是月份,遇到了 4,说明这个位置是分钟?
不禁觉得,发明这串时间数字的人还是很聪明的。2006-01-02 15:04:05 这个日期,不但挺好记的,而且用起来也比较方便。这个比其他编程语言的 yyyy-MM-dd HH:mm:ss 这种东西好记多了。(楼主就曾经把 yyyy 大小写弄错了,弄出一个大 bug,写成 YYYY,结果,当时没测出来,到了十二月左右的时候,年份多了一年。。。)
三、深入探究
为了一窥这个时间格式化的究竟,我们还是得阅读 go 的 time 包源代码。在$GOROOT/src/time/format.go 文件中,我们可以找到如下代码:
1 | const ( |
上面就是所能见到的所有关于日期时间的片段。基本能够涵盖所有的关于日期格式化的请求。
可以总结如下:
格式 | 含义 |
---|---|
01、 1、Jan、January | 月 |
02、 2、_2 | 日,这个_2 表示如果日期是只有一个数字,则表示出来的日期前面用个空格占位。 |
03、 3、15 | 时 |
04、4 | 分 |
05、5 | 秒 |
2006、06、6 | 年 |
| -070000、 -07:00:00、 -0700、 -07:00、 -07
Z070000、Z07:00:00、 Z0700、 Z07:00 | 时区 |
| PM、pm | 上下午 |
| Mon、Monday | 星期 |
| MST | 美国时间,如果机器设置的是中国时间则表示为 UTC |
看完了这些,心里对日期格式问题已经有数了。
所以,我们回头看一下开头的问题,我用
2017-09-07 18:05:32
这串数字来格式化这个日期
2017-09-07 18:05:32
得到的结果就是
7097-09+08 98:43:67
看了这个我就在想,如果是我,我会怎么解析这个格式呢?不禁想起来了学习《编译原理》时候的词法分析器,这个肯定需要构造一个语法树。至于文法什么的,暂时我也还弄不清。既然这样,那不如我们直接看 GO 源代码一窥究竟,看看 golang 语言团队的人是怎么解析的:
1 | func nextStdChunk(layout string) (prefix string, std int, suffix string) { |
这段代码有点长,不过逻辑还是很清楚的,我们吧上面表格中的那些常用项的先进行排序,然后根据排序结果,对首个字符进行分类,相同首字符的项放在一个 case 里面判断处理。看起来这里是简单的进行判断处理,其实这就是编译里面词法分析的一个步骤(分词)。
纵观整个 format.go 文件,其实这个日期处理还是挺复杂的,包括日期计算,格式解析,对日期进行格式化等。
作者:FredGan
链接:https://www.jianshu.com/p/c7f7fbb16932
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。