ISO8601基本介绍
概述
看到ISO就知道这是国际标准化组织为了实现统一标准而定义的规范,规范是反复使用的、多数人知晓的、更容易被大家都理解的一系列共识和要求,了解规范是很重要的。虽然我们根本不可能把众多的规范都系统学习,但是常见、常用的规范还是应该花点时间看一下,毕竟,不看不学,它们还是会不断地在你面前出现,一直让你感到困惑。
最近在学习BPMN 2.0
规范的定时器事件(Timer Events)时,第一次见到PT2M
这样的表达式,于是搜索学习了一下,发现它们使用的是ISO 8601
日期时间表示法。 本文简单记录一下ISO8601
规范是如何表示日期、日期时间、时间间隔和循环时间。
规范介绍
如何表示日期
符号
年
:由4数字组成,格式为YYYY
月
:由2位数字组成,格式为MM
日
:由2位数字组成,格式为DD
周
:由字母W
加1位数字组成,格式为W
n
格式
日期的表示可以分为基本格式(只使用数字)和扩展格式(使用短横线“-
”分隔年、月、日)。
表示法
日历日期
也就是我们最常见的日历形式,用4位数字表示年、2位数字表示月、2位数字表示日。
- 2020年的第7个月可以表示为:2020-07或者202007。
- 2020年7月20日可以表示为:2020-07-20或20200720。
- 2020年第19个星期可以表示为:2020-W19或2020W19。
- 2020年就只能用2020表示。
顺序日期
某个日期在一年中的第几天,用3位数字表示这一天在一年中的序数,平年365天、闰年366天。例如:2020年7月20日是2020年的第202天,可以表示为2020-202或2020202。
星期日历
同时使用星期
和天
,即一年中的某个星期中的第几天,用字母W
加2位数字表示是一年中的最几周,再加上1位数字表示是一周内的第几天。例如:2020年7月20日(星期一)是2020年的第30周的第一天,可以表示为2020-W30-1或2020W301。
W
字母后的2位数字表示的是一年中的第几周,因为一年是52周/53周,所以要用2数字表示;而一周只有7天,所以用1位数字表示就够了。
敲黑板、划重点:
那么一年中的第一周是怎么确定的呢,是不是元旦所在的那一周就算是第一周了?
答案是否定的,元旦在的周未必就是第一周,具体来讲ISO 8601
确定一年中的第一周有下面四种等效的方法(等效就是说这4种判定方法效果是一样的,无论用哪个判断出来的结果都是一样的):
- 本年度第一个星期四所在的星期
- 1月4日所在的星期
- 本年度第一个至少有4天在同一星期内的星期
- 星期一在去年12月29日至今年1月4日以内的星期
让我们验证一下:
- 本年度第一个星期四所在的星期:也就是1月2日所在的那一周,即,2019-12-30 ~ 2020-01-05
- 1月4日所在的星期:直接看图,1月4日就是在2019-12-30 ~ 2020-01-05的这一周
- 本年度第一个至少有4天在同一星期内的星期:2020-01-01到2020-01-05,已经有5天了
- 星期一在去年12月29日至今年1月4日以内的星期:星期一是2019-12-30,正好是在2019-12-29到2020-01-04这个区间
再举个例子:2020-W01-3,这个表示法是要定位在2020年的第一周的第3天,参照上面的介绍,2020年的第一周是2019-12-30 ~ 2020-01-05的这一周,这一周的第3天就是2020-01-01。可以访问这个网站或者这个网站来验证一下。
如何表示时间
符号
时
:由2数字组成,格式为hh
(00~24,其中24仅用于表示日历一天结束时的午夜)分
:由2位数字组成,格式为mm
(00~59)秒
:由2位数字组成,格式为ss
(00~60,其中60只用来表示闰秒)
格式
ISO8601使用24小时制时钟系统,时间的表示可以分为基本格式(只使用数字)和扩展格式(使用冒号“:
”分隔时、分、秒)。
表示法
包含时分秒毫秒,
hhmmss.sss
或hh:mm:ss.sss
,例如:212805.355或21:28:05.355。包含时分秒,
hhmmss
或hh:mm:ss
,例如:212805或21:28:05。包含时分,省略秒,
hhmm
或hh:mm
,例如:2128或21:28。只包含小时,
hh
,例如21。
时区
- 如果时间是UTC时间,那么直接在时间的后面添加一个大写字母“
Z
”(Zone的缩写),注意没有空格。 - 这个Z其实是UTC时间零时区的标识符,因此如果时间是在零时区,那么就可以这样表示时间。
- 表示完整的时、分、秒,例如,晚上11点08分05秒:23:08:05Z或230805Z。
- 只表示小时和分,例如,晚上11点08分:23:08Z或2308Z。
- 只表示小时,例如,晚上11点:23Z。
- 如果不是UTC的零时区,用实际的时间加上时差表示,例如,东八区的时间表示为UTC+8,晚上11点08分05秒就可以表示为230805+0800或23:08:05+08:00,也可以简化为230805+08。
如何表示日期和时间
将日期和时间合并表示时,要在时间的前面加一个大写字母“T
”(Time的缩写)。
例如,表示东八区时间2020年7月21日22点06分15秒,就可以写成:20200721T220615+08:00或2020-07-21T22:06:15+08:00。
如何表示一段时间
以一个大写字母“P
”(Period)开头,后面跟着代表日期、时间或者星期的大写字母符号。Y, M, W, D, H, M, S。注意,这里为什么会有两个M稍后会解释到。
例如,在一年三个月五天六小时七分三十秒内,可以写成P1Y3M5DT6H7M30S。
当值为0时,表示日期和时间的值是可以省略的,但是日期和时间必须要有一天,不能都省略,即只有一个字母P这种形式是不允许的,如果你想表示一段0秒钟的时间,PT0S
或者P0D
,都是可以的,而且是等效的。
之前我们提到了字母M,其实M既可以表示月(Month
)也可以表示分钟(Minute
),为了解决这个模棱两可的问题,就需要重点看这个大写字母T
,主要是看它放在什么位置上,如果放在字母T
后面,表示的是分钟,否则表示的是月。例如:P1M
表示一个月,而PT1M
表示是一分钟。
Y, M, W, D, H, M, S这些表示单位的大写字母前面的数值,最小单位是可以为小数的,例如:P0.5Y
,就表示半年。小数部分可以用逗号或者句号来指定,即PT0.5Y
或PT0,5Y
。
ISO8601
规范不限制日期和时间的值超过“转换点”
(比如分钟,59就是它的转换点,当过了59,就该轮转回到00了),因此PT36H
和PT1D12H
是一样的。但是一定要记住,当与夏令时间转换时,PT36H
就与PT1D12H
是不同的了(在一些实现夏时令的国家到了夏天一天只有23小时)。
如何表示时间间隔
从一个时间开始到另外一个时间结束,或者从一个时间开始持续一段时间。格式是在开始时间和结束时间,或者是开始时间和持续的一段时间之间用一个反斜线“/
”分隔。例如,19871111/20200721或19871111/P33Y。
总共有4种方式表示时间间隔:
包含开始时间和结束时间,即
/ ,例如:2007-03-01T13:00:00Z/2008-05-11T15:30:00Z。 包含开始时间和持续时间,即
/ ,例如:2007-03-01T13:00:00Z/P1Y2M10DT2H30M。 包含结束时间和持续时间,即
/ ,例如:P1Y2M10DT2H30M/2008-05-11T15:30:00Z。 只包含持续时间,即
,例如:P1Y2M10DT2H30M。
如何表示循环重复
格式
R[循环次数][/开始时间]/时间段[/结束时间]
注:字符串的第一位固定是大写字母R,后面的循环次数、开始时间、结束时间是非必需存在项;时间段必须存在。
分段说明
循环次数:
整数值,表示要循环的次数,如R1表示循环1次;R123表示循环123次。当不指定次数时,即只有一个字母R表示一直循环。
开始时间、结束时间:
分别表示第一次运行的时间和截止时间(到了这个时间循环就要中止)。
使用字母“T”来分隔日期和时间。
可以在时间后面跟上时区,比如+8:00表示东八区、-7:00表示西七区,默认是0时区,可以用字母“Z”表示,也可以不写。
时间段:
以字母“P”开始,后面要跟上代表时间的大写字母(Y、M、D、H、M、S、W)。
使用字母“T”来分隔日期和时间。
如果没有年月日,字母“T”也不能省略。比如:P1DT1M(每隔1天零1分钟执行一次)、PT1H(每隔一小时执行一次)。
示例
R3/20200718T130000+08/P0Y6M5DT3H0M0S
在这个例子中只包含循环次数、开始时间和时间间隔,因些解释为:从北京时间2020年7月18日的下午1点起开始循环,每次循环的间隔是6个月零5天3小时,一共循环3次。
R/P1Y2M/20250101
在这个例子中,只包含时间间隔和结束时间,因此解释为:每次循环的时间间隔是1年零2个月,一直循环下去,直到2025年的1月1日为止。
案例分析
如果还有印象的话,2020年元旦很多技术人的公众号订阅中都收到了类似”因为使用YYYY-MM-dd来格式化时间,元旦当天被老板喊回去改代码”这样标题的文章推送,我们简单看一下这个问题。
造成这个问题的根本原因在于格式化符号YYYY
和yyyy
在Java中表示年份的时候是两套日期体系的东西。YYYY
默认是ISO8601
标准的年份模式,而yyyy
是格里高利历
的年份(也就是我们日常用的那个公历)。
Java’s DateTimeFormatter pattern “YYYY” gives you the week-based-year, (by default, ISO-8601 standard) the year of the Thursday of that week.
ISO8601
的年是基于周历的年,也就适用前面提到的4种等效判定法来找到这一年的第一周,我这里习惯使用第一个周四所在的那个星期或者1月4日所在的那个星期,下面的2020年1月的第一周就是2019-12-30到2020-01-05的这一周是属于2020年的第一周。这样就解释了,为什么明明应该格式化日期为2019-12-31,使用了YYYY却显示成了2020-12-31,整整多出一年:因为2019-12-31这一天正好是的2020年的第一周了,由此得出的基于周的年,就是2020。
题外话
- 正是在
ISO8601
的定义中将星期一定义为了每周的第1天,星期日定义为了一周的最后1天。在一些美国使用的规范里是将星期日作为第一天的。 ISO 8601:2004
不再允许缺省(默认)世纪仅用两位数字表示年,而ISO 8601:2000的中国的国际GB/T 7408-2005
还是可以用两位数字表示年的,这样就会存在年与小时数混淆的问题。ISO 8601-1:2009
不再允许使用24:00。- 小数可以加到时、分、秒这三个时间元素中的任何一个上。用逗号“
,
”或者句号“.
”分隔,在规范中小数部分的小数位数没有限制,但是还是要让通信的参与方都协商一致,比如Microsoft SQL Server
就是使用3位小数,例如:yyyy-mm-ddThh:mm:ss[.mmm]。
积累
daylight saving time(DST):翻译成夏令时
我们国家曾经在1986年的4月开始实行过夏令时,操作办法是在4月的第一个星期日凌晨2点整的时候,把表的时针拨快1小时,也就是直接变成了凌晨3点。等到9月份的第一个星期日的凌晨2点整的时候,再把表的时针拨慢1小时,也就是直接变成凌晨1点。这段时间就是夏令时,每天都比非夏时令少一个小时。
实行夏令时说是为了节约电能,夏天嘛,天亮的早,5点就亮天了,人为的给你提前一时,直接给你改成6点, 也就是说你从零点开始睡到天亮,实际才睡了5个钟头,人家就非说是到6点了,是不是比周扒皮还鸡贼。不过到1992年的时候夏令时就不再执行了。
a full stop:翻译成号句,原文语境是either a comma or a full stop,难道不可以用dot咩?
to resolve ambiguity[ˌæmbɪˈɡjuːəti]:翻译成解决歧义,以后可以在代码注释和文档里经常拽一下。
carry over points:翻译成转换点,但是好像没有上下文的时候这个转换点又很难理解,不知道可不可以译成
进位点
。
参考资料
- BPMN Modeling Reference - All BPMN 2.0 Symbols explained | Camunda BPM
- Timer Events | docs.camunda.org
- ISO 8601 - 维基百科,自由的百科全书
- ISO 8601 - Wikipedia
- The ISO Week Date Calendar