暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

新优特性之DateTime

开源中间件 2021-09-10
472

这一节我们说说Java语言中的时间和日期。


时间作为一个时空中的维度,每个人时刻都会面对,程序开发中也总是会涉及到这个要素。

Java语言中对于时间日期是如何处理的呢?让我们先谈一些历史,历法,天文和部分地理方面的知识。


一 时间日期小知识


1. 闰年和历法

先看一段小程序,计算一下1583年10月4日后第三天

class DisDate {

  public static void main (String[] args) {

    Calendar c = Calendar.getInstance();

    c.set(1582, 9, 4);


    SimpleDateFormat s=new SimpleDateFormat ("yyyy'年'MM'月'dd'日'");

    System.out.println(s.format(c.getTime()));

    c.add(Calendar.DATE, 3); 计算3天后的时间

    System.out.println("3天后的时间是:" + s.format(c.getTime());

  }

}


结果是

$ java DisDate 

1582年10月04日

3天后的时间是:1582年10月17日


什么,4日后三天是17日?难道穿越了?没错,这就是历史上消失的十天。


我们知道当前我们用的公历是以地球绕太阳旋转一周记为一年,地球自传为一天,从天文学来说,一年为365.2422天左右,所有我们有了闰年,即每4年有2月29日。

在1582年之前,这个历法叫做Julian(朱利安),也翻译成儒略历法,是由公元前著名的凯撒大帝制定的。

四年一闰的方式,每年还会差出0.0078天。这样经过上千年,这个误差就累计放大到了10天。所以在1582年,当时的罗马教皇格里高利13世重新制定了Gregorian calendar(格里高利历法),把当年从10月5日当10月14日的十年人为抹去。

闰年的方式在四年一闰的基础上,加入100年不闰,而400年又一闰的规定。这样同天文学上地球公转周期基本一致了,剩余微小的差异会每隔几年用闰秒的方式补足,这个历法一直沿用至今。


2. 时区和夏令时

我们都知道地球上按照经度划分为不同的时区,中国是在东8区,用世界标准时间表示为UTC+8,世界上不同国家选用适合的时间,有的国家还会有多个时区。

在很多国家,在夏天的一段时期内,会把手表拨快一个小时,可以更多的利用日照而节约能源,这就是夏令时。

我国在20世纪80年代曾经使用过夏令时,后来因为时间的改变给生产生活带来更多的困扰,而一直没有再使用。


Rule PRC 1986 only  - May 4 0:00 1:00 D

Rule PRC 1986 1991 - Sep Sun>=11 0:00 0 S

Rule PRC 1987 1991 - Apr Sun>=10 0:00 1:00 D

这是在Joda-time(后文还会介绍)中的我国夏令时定义的规则文本,可以看出从1986年到1991年我国使用过夏令时。


在JDK中,时区和夏令时的时间信息,都存放在tzdb.dat文件中并定期维护,除了每年各国的夏令时时间,还有因为重大活动,比如体育比赛,运动等人为调整的时间。


3. 各国历法和年号

公历是世界各国应用最广泛的历法,然而各国也有特定的历法同时使用,比如我国就有农历,我们的很多传统节日,春节,元宵,端午,重阳等都是农历节日。

农历是阴阳历(而不是阴历),就是同时要考虑月亮绕地球公转(阴)和地球绕太阳公转(阳)。比起公历,考虑月亮,即阴历的成分要多一些。月亮公转一圈是29天半左右,所以农历月份有小月29天,也有大月30天(不会出现31天,28天这样的人为规定)。

至于大月小月,则是用天文学的朔望月时间确定的,所以不会出现凭空消失10天的情况,是古代最精准的历法。


中国的农历历法制定从汉武帝时确定,后一直不断完善,直到民国才改为公历为主要历法。农历在无中气的月份设置闰月,一般每隔2,3年一闰。

不过农历有一个障碍,就是人们极难计算推断出哪个月是大月还是小月,以及闰月加在哪里,这些都是通过天文知识推导出来的,不像公历那样规则简单,小学生也能很快算出某年月日是星期几。


目前在Java中,除了格里高利历法,还有佛历,日本历法等实现。但没有农历历法,可能是的确很难实现吧。

我见过很多计算农历的程序只是内置一个表格,通过查表的方法来计算农历日期。

这里值得一提的是一个计算农历的程序 https://github.com/oyyq99999/ChineseLunarCalendar 是通过天文数据来推算的,然而计算过程很复杂。


年号是始于我国古代封建王朝表示纪年的一种方法,汉武帝首创年号“建元”,后世每位皇帝在位会有一个或者若干个年号,末代皇帝溥仪使用的“宣统”是最后一个官方承认的年号。

民国起废除封建皇权年号,使用民国纪年。中华人民共和国只使用公历。

目前日本国在使用年号,2019年平成退位后会使用新的年号。我国台湾也还沿用民国几年的纪年方法。


Java语言中对于年号也有实现,比如日本近代以来(明治,大正,昭和,明仁,下表为JDK11最新代码,已经给下一个年号留出位置)和民国。


 ERA value   Era name    Since (in Gregorian)

 0       N/A         N/A

 1       Meiji       1868-01-01T00:00:00 local time

 2       Taisho      1912-07-30T00:00:00 local time

 3       Showa       1926-12-25T00:00:00 local time

 4       Heisei      1989-01-08T00:00:00 local time

 5       NewEra      2019-05-01T00:00:00 local time


二 Java8之前时间日期的不足


Date类是Java1.0开始就存在的,首先面对的问题是,表示年度要加上1900,也就是说Date方法是从1900年开始计算的。Java1.1加入Calendar类,但同样有很多问题。


比如,

  • 两者的月份都是从0开始计数的,也就是传入的月份1代表的是二月;

  • 它们都不是安全的类,多线程环境下,经常多个线程同时修改同一个引用而产生bug;

  • 还有我认为是最主要的,它们和表示时区的TimeZone,表现历法的各Calendar类,以及需要配合使用打印日期时间文本的DateFormat,设计上是混杂在一起的,编程时经常会非常困惑。


三 Java8引入的DateTime


Java8引入了新的时间日期类,都放置在java.time包中。


1. 基本日期和时间

  • LocalDate只表示日期,不可变类。

  • LocalTime只表示时间,不可变类。

  • LocalDateTime组合了日期和时间,也是不可变类,但不含有时区信息。


2. 时点和间隔

  • Instant表示的是一个时点,度量单位可以小至纳秒。

  • Duration表示的是时间间隔,如3.1秒,便于机器处理

  • Period也表示的是时间间隔,但用间隔多少年多少月多少日表现,便于人们理解。


3. 时区

  • ZoneId体现了时区,一般是一个标志地理位置,如我国用Asia/Shanghai

  • ZoneOffSet表示了时区偏移时间,如+8:00,即和UTC格林威治标准时间的偏移值。

  • ZonedDateTime表示了完整的日期,时间和时区信息


4. 日历系统

放在chrono包下,表示各个日历系统。每种日历系统都有以下接口或类实现:

  • Chronology表示日历系统

  • ChronoLocalDate日历系统的日期

  • Era日历系统中某一个时间段

  • 目前JDK中除了通用的公历之外,还实现了伊斯兰,日本,民国和泰国佛历。


5. 实用工具

  • format包中有各种日期时间文本格式处理工具

  • temporal包中有各种对时间,日期进行加减操作,查询等工具。比如,可以组合出明年5月最后一个工作日之前星期五这样的查询。


综合以上,我们可以看出新的日期时间功能不但强大,设计上更是远胜原来的类。而且对于函数式编程来说,也是不二之选。

如今主流的技术,如JDBC,JPA等都已经全面支持新的日期时间类,所以建议大家在编程时,总是要用这些新的类去实现功能。


四 JodaTime


我们必须要提及Jodatime,因为Java8的日期时间源自Jodatime的设计。Java源码中头部版权声明中提及的Stephen Colebourne就是主要其开发者。Jodatime支持早期的Java版本,所以在很多Java三方包中都能看到依赖声明,在Java8之前是事实上的日期时间标准库。


Jodatime中类定义和Java8中的基本是一致的,只是稍有区别。

如Interval和Duration相似,只表示时点之间的区间;Partial只表示时间片段等。

另外日历系统有所区别,以西方为主。同时提供了方法和JDK8中的类进行转换。因为Jodatime在软件维护时经常会遇到,所以还是得比较熟悉。


五 总结


日期时间用到的地方很多,涉及到很多跨学科知识,多阅读其实现代码,也能学到很多有趣的知识点。


个人希望以后有时间和机会,或者国内的开发者有精力能够实现我国农历历法,并提交给OpenJDK,如果能通过合并入OpenJDK中,以后我们可以都用上标准的农历了。

另外对于明清两代的年号,也可以做一个扩展包,读明史追清宫剧也能方便的用程序编写出故事主线时间轴吧。


今天就到这里。留一个思考题,中国农历的节气,在目前日期时间系统中是否存在呢?如果还没有,如果设计比较好呢?



文章转载自开源中间件,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论