现行计时系统和Java.Time简介

2020-06-25  本文已影响0人  IQException

在闰月的最后几天,简单聊聊目前主流的计时系统以及Java的时间相关api。

公历、阴历和农历以及闰年(leap year)、闰月(leap month)

  1. 中国农历俗称阴历, 但实际上是阴阳合历
  2. 阳历是指依据地球围绕太阳公转位置的不同确定日期
  3. 阴历是指依据月球围绕地球的运动来确定日期
  4. 由于太阳对地球环境的决定作用, 阳历更便于指导农业生产
  5. 公历 (格里高利历) 就是典型的阳历, 而伊斯兰历则是典型的阴历
  6. 中国传统立法结合了二者的特点:用阳历来确定年, 再用月相的变化来确定月和日

详情见 农历就是阴历吗?二十四节气和闰月咋回事?李永乐老师讲中国历法

UTC和GMT

格林尼治平均时间(英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。

自1924年2月5日开始,格林尼治天文台负责每隔一小时向全世界发放调时信息。

格林尼治标准时间的正午是指当平太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治平时基于天文观测本身的缺陷,已经被原子钟报时的协调世界时(UTC)所取代。

人们最初确定时间的方式是直接观测太阳在当地天空中的位置,例如使用日晷,这样测量出来的时间被称为地方真太阳时(local apparent solar time / local apparent time)。后来,人们为了解决地球公转轨道不是正圆和黄道与赤道之间存在夹角而造成的测出的时间的流逝不均匀的问题,以假想天体“平太阳”(mean Sun)为基准测量时间,而不再以真太阳为基准,这样测量出来的时间被称为地方平太阳时(local mean solar time / local mean time)。地方真太阳时和地方平太阳时的差异被称为均时差(equation of time)。

后来,格林尼治天文台所在地的地方平太阳时被定义为全世界的时间标准,被称为格林尼治平时(Greenwich Mean Time),“平时(mean time)”就是“平太阳时(mean solar time)”的意思。

后来,由于1925年以前人们在天文观测中,常常把每天的起始(0时)定为正午,而不是通常民用的午夜,给格林尼治平时的意义造成含糊,人们使用世界时(Universal Time, UT)一词来明确表示每天从午夜开始的格林尼治平时。

目前使用的世界时测算标准又称UT1。在UT1之前人们曾使用过UT0,但由于UT0没有考虑极移导致的天文台地理坐标变动的问题,因此测出的世界时不准确,现在已经不再被使用。在UT1之后,由于人们发现,因为地球自转本身不均匀的问题,UT1定义的时间的流逝仍然不均匀,于是人们又发展了一些对UT1进行平滑处理后的时间标准,包括UT1R和UT2,但它们都未能彻底解决定义的时间的流逝不均匀的问题,这些时间标准现在都不再被使用。

后来,人们为了彻底解决定义的时间的流逝不均匀的问题,开始使用原子钟定义时间。人们首先用全世界的原子钟共同为地球确立了一个均匀流动的时间,称为国际原子时(International Atomic Time, TAI)。然后,为了使定义的时间与地球自转相配合,人们通过在TAI的基础上不定期增减闰秒的方式,使定义的时间与世界时(UT1)保持差异在0.9秒以内,这样定义的时间就是协调世界时(Coordinated Universal Time, UTC)。UTC是目前全世界使用的时间标准。UTC与UT1之间的差异被称为DUT1。

目前,“格林尼治标准时间”一词在民用领域常常被认为与UTC相同,不过它在航海领域仍旧指UT1。

简单来说 :GMT是曾经的民用计时系统,由于精度问题被精度更高的UTC(使用原子钟计时)取代了,但是GMT并没有被废弃,只是不再作为全球时间的参照标准,现在的GMT通常指UTC+0(0是时差),也就是专为时差为0的区域使用,就像CST(北京时间)指的是UTC+8,只有北京时间的区域会使用到。PS:也有一些地方会使用GMT+8这种表示,这时候其实GMT就等价于UTC。

时区(timezone)与夏令时、冬令时(daylight saving time)

时间表示当前地区相对于UTC的时间偏移量,如北京时间=UTC+8,这个8小时的偏移就是时区。

夏时制(英语:daylight time,英国与其他地区),又称夏令时、日光节约时间(英语:daylight saving time, DST,美国),是一种在夏季月份牺牲正常的日出时间,而将时间调快的做法。通常使用夏时制的地区,会在接近春季开始的时候,将时间调快一小时,并在秋季调回正常时间。实际上,夏时制会造成在春季转换当日的睡眠时间减少一小时,而在秋季转换当日则会多出一小时的睡眠时间。

夏令时的优点就是充分利用光照进行社会生产生活,从而减少照明等能源消耗。

闰秒(leap seconds)

闰秒是在在UTC(中文“世界标准时间”或“世界协调时间”/英文“Coordinated Universal Time”/法文“Temps Universel Cordonné”)是基于Atomic Clock(原子时钟)的一种时间,向太阳时(Solar Time )对齐的一种方法,因为太阳时是根据地球公转来计算的。所以,1972年制定的UTC为了确保其时间相对于UTC的时间误差不能超过0.9秒,因此在过一段时间后需要加一秒。

关于闰秒

注意,java的时间相关类并没有对闰秒做特殊处理,而是依赖操作系统定期回拨将这一秒修正,所以也就意味着这一秒会出现时间回溯的现象。

对于一些时序敏感的应用,比如发号服务(snowflake这种) 需要特别注意这种情况,防止发号重复;但是也很难消弭掉闰秒的影响,比较简单的实现方案就是在这一秒内不提供服务,等时间正常了恢复服务

NTP

网络时间协议(英语:Network Time Protocol,缩写:NTP)是在数据网络潜伏时间可变的计算机系统之间通过分组交换进行时钟同步的一个网络协议,位于OSI模型的应用层。自1985年以来,NTP是目前仍在使用的最古老的互联网协议之一。NTP由特拉华大学的David L. Mills设计。

NTP意图将所有参与计算机的协调世界时(UTC)时间同步到几毫秒的误差内。它使用Marzullo算法的修改版来选择准确的时间服务器,其设计旨在减轻可变网络延迟造成的影响。NTP通常可以在公共互联网保持几十毫秒的误差,并且在理想的局域网环境中可以实现超过1毫秒的精度。不对称路由和拥塞控制可能导致100毫秒(或更高)的错误。

该协议通常描述为一种主从式架构,但它也可以用在点对点网络中,对等体双方可将另一端认定为潜在的时间源。发送和接收时间戳采用用户数据报协议(UDP)的端口123实现。这也可以使用广播或多播,其中的客户端在最初的往返校准交换后被动地监听时间更新。NTP提供一个即将到来闰秒调整的警告,但不会传输有关本地时区或夏时制的信息。

linux的时间精度

  1. 绝对时间
    linux绝对时间等于从主板上CMOS读取的时间,加上系统启动之后的时间。由于CMOS时间精度低(毫秒或以下),且误差大,一天可能有好几秒,所以需要定时通过NTP同步时间,但是由于网络误差,NTP同步的时间精度也在毫秒级以上。所以不同机器之间不能依赖精确的时间序列去处理业务,因为不同机器之间的时间差距是比较大的。

  2. 相对时间
    linux从2.6.16开始加入了高精度定时器架构,只要主板提供了高精度的时钟源,理论上可以达到CPU频率和时钟源二者其一的极限(频率较低的)。所以相对时间可以获取到较精确的数值,例如微秒和纳秒。

jdk 1.8 之前的时间api

  1. date
    date的问题在于不能传递时区,只能处理当前时区的日期。

  2. calendar
    calendar对象是可变的,非线程安全。

这俩类最大的缺点是api不友好,所以产生了一些流行的第三方库,如Joda-time.

Joda-time
Joda-Time简介

值得一提的是,Joda-Time框架的作者正是Java新的日期API(JSR-310)规范的倡导者,所以能从Java 8的日期API中看到很多Joda-Time的特性

jdk 1.8 之后的时间api

java.time包在1.8新增了几个子包下。

  1. java.time.chrono: 历法相关类,既包括被广泛使用的国际标准历(IsoChronology),也包括一些地区特有的历法,如泰国佛历(ThaiBuddhistChronology)等,还提供了SPI的方式加载自定义历法。通常来说,历法只影响到日期,并不会影响时间,所以时间需要使用国际标准时。

  2. java.time.temporal: 计时相关类,计时系统的高级抽象。

    2.1. TemporalAccessor:抽象了计时类的可访问属性,如是否支持访问,取值范围以及各种属性(TemporalField)。
    2.2. Temporal:继承TemporalAccessor,除了访问属性之外还定义了构造和“修改”(时间类都是不可变对象,所以实际上是以当前类为模版创建了新对象)的方法,如增减时间(TemporalAmount)。
    2.3. TemporalAmount:时间数量接口,Duration和Period都实现了该接口。
    2.4. TemporalField:时间属性接口,ChronoField实现了该接口。
    2.5. TemporalUnit:时间单位接口,ChronoUnit实现了改接口。 Duration和Period都提供了用TemporalUnit和数量来构造对象的方法。

    2.6. TemporalQuery & TemporyQueries:TemporalQuery是查询任意TemporalAccessor类的函数式接口,也可以通过TemporalAccessor直接获取属性,但是直接获取到的只是long值,而这个接口可以自定义类型。TemporyQueries是个工具类,提供了一些常用的TemporalQuery的集合。

    查询LocalDateTime的精度:LocalDateTime.now().query(TemporalQueries.precision());
    输出:Nanos

    2.7. TemporalAdjuster & TemporalAdjusters:TemporalAdjuster是修改Temporal属性的接口,前面说过Temporal本身已经定义了一些构造对象和“修改”属性的方法,但是这些方法是分开的,TemporalQuery可以将他们灵活地组合在一起,并且通过TemporalAdjusters提供了一些常用的TemporalAdjuster的集合。

    获取本月的第一天:TemporalAdjusters.firstDayOfMonth().adjustInto(LocalDate.now());
    输出:2020-06-01

    另外大多数时间相关类都实现了TemporalAdjuster接口,用以将自己适配到其他类型的时间对象。

    获取今年的国庆节:Year.now().adjustInto(LocalDate.of(1949,10,1));
    输出:2020-10-01

  1. java.time.zone:时区相关类,既提供了标准的时区规则,也支持SPI 的方式加载自定义的时区规则。

  2. java.time.format: 格式化相关类,1.8之前的SimpleDateFormat是非线程安全的,而新的格式化类DateTimeFormatter是线程安全的,也就意味着可以定义一些static的格式类对象,共享使用。

    打印当前时间: DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss").format(LocalDateTime.now());
    输出:2020-06-18 09:57:42

java.time包在1.8新增了几个重要的类。

1.8之后时间相关类与之前相比最大的不同就是不可变,这是设计思路的变化。 时间是固定的,要么是绝对的时间点,要么是绝对的时间段,所有属性在创建对象的时候就已经确定下来了,之后无法再改变。

  1. LocalDate & LocalTime & LocalDateTime :这三个类可能是最常用的时间类了,分别表示某个时区的日期、时间和日期+时间,LocalDateTime其实就是持有一个LocalDate和一个LocalTime。如前面所说,在创建对象的时候已经将时区、夏令时等规则纳入计算得出了日期和时间,并且不会再改变。需要注意的是这三个类并没有保存时区属性,所以不能用作时区相关的计算,比如现在是夏令时,但是三天后夏令时就结束了,如果将当前日期增加三天,则它的时间应该根据夏令时的结束而有所调整(回拨一小时),但是由于没有时区属性,这点没办法实现。如果想要实现这种功能,应该使用ZonedDateTime。

  2. Instant:EpochTime,记录从1970-01-01 00:00:00 到现在的时间跨度,为绝对值,无时区。

  3. Duration & Period:时间段类,区别是Duration是基于时间的,Period是基于天数的。需要注意的是,前面说过ZonedDateTime可以作时区相关的计算,但是如果是以Duration表示TemporaAmount的话,则不将时区纳入计算规则,而以Period表示TemporalAmount的话,则会将时区也计算在内。

  4. ZonedDateTime:可以作时区相关计算的时间类。

总结

汝果欲学诗,功夫在诗外

程序世界不过是对现实世界的抽象,想要理解代码,写好代码还是需要先对现实世界有充分认知,否则代码就是无源之水无本之木,难以长久保持生命力。

上一篇下一篇

猜你喜欢

热点阅读