Java 日期时间与Date
# Java 日期时间与Date
# 日期的相关信息
# 1970年1月1日
任何一个软件开发人员对这个时间应该都不陌生,有时我们忘记初始化或者忘记赋值时,日期就会显示为1970-01-01,我们也叫日期初始值。那为什么日期的初始值是从1970-01-01开始呢?有一个说法是说遵循了Unix的时间计数,Unix认为 1970年1月1日0点 [1]是时间纪元,那为什么Unix要以这个时间为准呢?
有一处说法是说,当时操作性系统都是32位,如果每一个数值代表一秒,那么最多可以表示2^32-1,也就是2147483647秒,换算成年大概是68年。而Unix系统就是由Ken Thompson、Dennis Ritchie和Douglas McIlroy等人在贝尔实验室开发于1969年开发的,他们为了让时间尽可能的多利用起来,便用了下一年,即 1970年1月1日作为开始,然后这个约定也逐步延伸到其他各个计算机领域。
# GMT 格林威治时间
GMT的全称是 Greenwich Mean Time [2]即格林威治标准时间,是一种与地球自转相关、以太阳日为单位的时间标准。在十七世纪,格林威治皇家天文台为了海上霸权的扩张计划,选择了穿过英国伦敦格林威治天文台子午仪中心的一条经线作为零度参考线,也就是我们教科书上记载的本初子午线。
并约定从本初子午线起,经度每向东或者向西间隔15°,就划分一个新的时区[3],每个时区间隔1小时,在这个区域内,大家使用同样的标准时间。但各个国家也会基于各个国家的情况拆分或合并时区,比如中国横跨5个时区,但我们统一使用东八区;而美国则有东部时间、西部时间、夏威夷时间等等。
从 1924 年开始,格林威治天文台每小时就会向全世界播报时间,最终截止到 1979 年。至于为什么会终止,自然有它的缺点和局限性,那我们就得聊聊UTC时间了。
# UTC 世界协调时间
UTC的全称是 Coordinated Universal Time [4]协调世界时间,也称世界标准时间。据说按英语的简称是CUT,按法语的简称是TUC,然后大家相互拉扯一波后,统一叫了UTC。
上述所说GMT时间是以地球自转与围太阳公转来计时的,GMT时间认为地球自转一圈是243600秒,而地球的运动轨迹受很多方面影响,比如潮汐摩擦、气象变化、地震及地质活动等等,运动的时间周期并不是完全规律和相同的。这样会导致其实一天并不完全是243600秒,这样平均算下来GMT的一秒就不是完全意义上最精确的一秒。但偏差通常也不会很大,基本为毫秒级偏差,但日积月累如果不加以扶正,就会越差越远。
而UTC的计数是基于 原子钟(Atomic Clock) [5]的计数,比如铯原子钟采用铯-133原子的特性,在特定能级跃迁时会产生一个非常确定的频率9,192,631,770赫兹。然后基于铯-133原子的运动经过换算确定出我们需要的时间周期,据说这种误差可达每百万年内不到一秒。
UTC 最终由两部分构成:原子时间与世界时间。原子时间基于原子钟,来标准化我们钟表中每一秒时间前进的数据;世界时间是结合GMT时间,我们用多少个原子时来决定一个地球日的时间长度。从1972年开始,UTC被正式采用为国际标准时间。这年实施了一种新的时间调整机制,包括使用闰秒[6]以便对齐地球自转与原子时间。
# 1.8之前和之后的日期
# 拉胯的Data-1.8之前
Sat Dec 07 17:36:58 CST 2024 这是我们输出new Date()之后的数据,因为Date本质是某一个时刻的时间戳,导致它不能单独表示日期,更不能表示不带日期的时间。
- 不支持时区设定
Date now = Calendar.getInstance(Locale.CHINA).getTime();
曾经写过一段这样的代码,取当前的中国时间,被老板臭骂一顿。。。Date的本质是一个时间戳。当前此时此刻,全球任何一个地方的时间戳都是同一个,Date本身不支持时区。PS.本质上这行代码也指定不了时区哦~
- Date是可变的
Date是一个非常基础底层的类,但它却设计为可变。当我们计算这个data3天后是不是周末,如果程序计算中把这个date加了3天,那么你手上拿着得date也变成了3天后的日期。相比同为底层基础类的String,做得就优秀多了。
- 难当大任的Calendar
JDK刚推出就发现了问题,于是赶紧在1.1版本推出了Calendar,尝试用来解决令人诟病的Date,并将Date一众函数都标记为了deprecated。但Calendar依然是可变对象、最多也只能精确到毫秒、线程不安全、API的使用复杂且笨重等等,Calendar整体而言并没有挽回颓势。
曙光来临之JSR310
在聊JSR310之前,不得不先提一提 Joda-Time [7]这个开源Java库。Joda-Time以清晰的API、良好的时区支持、不可变性、强类型化等特性,得到了开发者社区的广泛好评,并在很多项目中被采用,被视为改善Java日期和时间处理的标杆库。Joda-Time如此优秀,Oracle也开启了收编之旅。2013年Java8发布,其中针对日期时间带来一套全新的标准规约 JSR310 [8],而JSR310的核心制作者就是Joda-Time的作者Stephen Colebourne。
# 1.8之后的时间
- Instant
Instant这个单词的中文含义是『瞬间』,严格来说Java8之前的Date就应该是现在的Instant。Instant类有维护2个核心字段,当前距离时间纪元的秒数以及秒中的纳秒部分。它指代当前这个时刻,全球任一位置这一时刻都是同一时刻。这一时刻川建国同学在高床软枕打着呼,这一时刻我泡着龙井写着文稿。
- LocalDateTime
LocalDateTime由LocalDate和LocalTime组成,分别日期和时间,以此来解决Date中不能单独表示日期和时间的问题。它们都与时区无关,只客观代表一个无时区的时间,比如2024-12-08 13:46:21,LocalDateTime记录着它的年、月、日、时、分、秒、纳秒。但具体是北京时间的13点还是伦敦时间的13点,由上下文语境自行处理。
- Duration
Duration中文含义译为『期间』,通常用来计算2个时间之前相差的周期.
注意:因为LocalDateTime是不带时区的,所以LocalDateTime是不能直接换成成Instant的。而Duration的比较也是不带时区的,或者你可以理解它是把时间放在同一个时区进行比较,来抹去时区的影响。
- ZonedDateTime
真正需要使用时区,我们就需要用到ZonedDateTime。「zoned」这个单词在英汉词典中是zone的过去分时,译为『划为区域的』。
关于1.8之后说的日期类,在Java进阶合集中有更详细的说明。这里就不展开了。
# 补充
# 什么是夏令时
夏令时[9]又称夏时制,英文原文为Daylight Saving Time,从名字上可以看出,夏令时诞生的背景是为了更好的利用白天的时间。夏令时概念的提出最早可以追溯到1895年,新西兰昆虫学家乔治·哈德逊向惠灵顿哲学学会提出,提前2小时的日光节约提案,以此在工作结束后,可以获得多出一段的白昼时间。
具体夏令时的实施,以美国为例,美国会在每年3月的第二个星期日的凌晨2:00,时钟会往前调1个小时变为3:00。再在每年11月的第一个星期日的凌晨2:00,将时钟在往后调1个小时变成1:00,此时的回拨也被称为“冬令时”。
当下全球有共约70多个国家和时区在使用夏令时,我国也曾短暂使用过夏令时,但因节约能源效果不显著,以及对日常生活工作等带来的一些影响,到1992年全国宣布取消夏令时。
# 闰年与闰秒
2008年是闰年存在2月29日,但微软一些软件在处理部分任务的时候会因为闰年导致处理错误。微软甚至在SQL Server 2008 CTP发布后曾经宣读了一份证明,建议用户不要在2月29日安装和运行软件,以减少影响。并且在Windows Small Business Server上还会出现更严重的错误:因为在微软的日历里根本没那么一天,因此就无法颁发证书。
# 为什么要闰年
闰年大家比较熟悉,闰年的设置是为了使日历年与太阳年(即地球绕太阳公转一周的时间)更精准地一致。严格来说地球绕太阳一圈的时间,大约是365.2422天。经过大约四年,累计误差将接近一天(0.2422 * 4 ≈ 0.9688天),但如果每4年就加1天,这样每128年又会多算出1天。所以基于此定义出了普通闰年与世纪闰年。
- 普通闰年:公历年份是4的倍数,且不是100的倍数的,为闰年(如2004年、2020年等就是闰年)。
- 世纪闰年:公历年份是整百数的,必须是400的倍数才是闰年(如1900年不是闰年,2000年是闰年)。
# 为什么要闰秒
闰秒[10]本质上和闰年的作用是一样的,也是解决时间解释运动中所存在的偏差。闰秒的调整是为了确保协调世界时(UTC)与地球自转时间(UT1)[11]保持一致。由于地球自转速度的不均匀性和减慢,UTC需要定期添加或删除一秒钟来进行调整,这一秒钟称为“闰秒”。
国际地球自转与参考系统服务(IERS)是负责监测和发布闰秒调整的机构。ERS会根据地球自转的实际变化和测量数据,决定是否需要调整闰秒。闰秒通常在6月30日或12月31日的最后一秒添加或删除。这意味着在某些年份,时间序列可能会变为:23:59:59 → 23:59:60 → 00:00:00。