十五万字《JDK源码分析》之《JSR-310(java8 新日期时间API)》总结(JAVA 小虚竹,建议收藏)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 十五万字《JDK源码分析》之《JSR-310(java8 新日期时间API)》总结(JAVA 小虚竹,建议收藏)

image.png什么是JSR规范

来自百度百科的解释:


JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。


JCP又是什么神秘的组织


JCP(Java Community Process) 是一个开放的国际组织,主要由Java开发者以及被授权者组成


所以JAVA的标准化规范,都是这个组织审核发布的。感觉真的是太厉害了。


那JCP的组织成员有哪些,八卦之火熊熊燃起


从官网获取的信息:


对于2020年度选举,将消除两个批准和一个选举席位,从而将EC减少到18名成员

2020年年级选举后,将有11个批准席位,4个选举席位,2个准席位(给个人的),Oracle America持有的常任席位。成员任期 2 年,任期交错,因此 17 个席位中的 8 或 9 个通常每年需要批准/选举。


我们来看看2020年获批准和选举的名单


以下划线的,就是2020年获批准和选举入选的。image.png

Alibaba: Alibaba入选,国内企业入选JCP,厉害了。


BellSoft : BellSoft 发布并支持免费且安全的 Unified Java Runtime,Liberica JDK。它可用于大多数平台和当今的架构,包括基于微服务的架构。


BNY Mellon: 全球拥有超过 11,000 名技术专家和开发人员,支持该银行作为全球金融市场清算、支付、托管和资产管理服务的领先供应商。


Java 社区参与


- JCP 执行委员会 (EC) 的积极批准成员

- 提议的 Java Collections 2.0

- 通过工作组致力于 Java 教育

- 领导 Java 用户组

- 通过演示、博客、代码研讨会与社区互动

- 开源参与


JetBrains: IDEA用过吧,这产品就是JetBrains公司研发的。


MicroDoc: MicroDoc 是一家位于德国的面向技术的软件开发企业,为全球客户群提供嵌入式 Java 虚拟机。MicroDoc 是开放标准和商业上可用的开源软件的支持者。它是 OpenJDK 的积极贡献者。自 2014 年以来,MicroDoc 在 JCP EC 中占有一席之地,并决定参加下一个选举期以促进 Java 的发展,重点是汽车、医疗保健和智能环境中的嵌入式应用程序。


SAP SE: SAP 拥有超过 250,000 家企业客户,是全球领先的商业软件供应商。这些 SAP 客户中有很大一部分运行基于 Java 的 SAP。SAP 自 2001 年以来一直参与 JCP,并在 50 多个 JSR 中进行了合作。SAP 自 2012 年开始为 OpenJDK 做出贡献。它目前参与 OpenJDK 11 更新项目并提供 SapMachine,这是 OpenJDK 的免费构建版本。SAP 致力于 Java 的持续成功。


London Java Community: 伦敦 Java 社区, 代表全球 JUG(伦敦 Java 用户组) 社区(40 万+ 开发人员)。我们运行“采用 JSR”、“采用 OpenJDK”来提高开发人员对 Java 标准的日常参与。


Eclipse Foundation: Eclipse 基金会自 2007 年以来一直参与 JCP 执行委员会。作为 Eclipse Java IDE、Jakarta EE、MicroProfile、Adoptium、Eclipse Java 编译器和 OpenJ9 的所在地,我们非常致力于 Java 生态系统。我们对 JCP EC 的主要贡献是代表开源社区的利益以及 Java 规范的独立实现。


Ken Fogel: 我是蒙特利尔道森学院计算机科学技术课程的教授。该计划的任务是培训学生,主要是高中毕业后,成为准备工作的软件开发人员。2019 年,我成为 Java Champion。五年前,我在蒙特利尔开始了一个名为 DawsCon 的免费为期一天的会议。我吸引了一些最优秀的演讲者与学生和当地开发者社区交谈。在过去的六年里,我也在 JavaOne/CodeOne 上发表过演讲。


好!八卦结束,回归正题。


JSR规范内容

包含在三个 Java 版本的标准集合:JAVA SE、JAVA EE和 JAVA ME。


某些JSR包含在一个或多个JAVA平台中。


以下是对应的官网地址:


Java SE


Java EE


Java ME


点击JAVA SE进来看看




5 JSR的编号,同一行的内容为此JSR编号对应的名称

Description: 描述此JSR是用来干嘛的

Status: 当前此JSR是什么状态

Latest Stage: 最新阶段

Spec Lead: 规范的负责人

点击JSR编号对应的名称,会跳转到此JSR内容的详情页。


Status状态:


Status What it means

Active 在过去 12 个月内发布了里程碑的 JSR。

Final 一年多前产生了最终发布里程碑的 JSR。

Maintenance 上次产生维护审查或维护发布里程碑的 JSR 是在一年多以前

Inactive 尚未生成最终版本或维护版本且在去年未发布里程碑的 JSR

Withdrawn 在最终发布之前由规范负责人从 JCP 中撤回的 JSR。

Rejected 在该 JSR 的投票阶段之一中未得到执行委员会批准的 JSR

Dormant 被执行委员会投票为“休眠”的 JSR,或已达到其自然寿命终点的 JSR

现在大家对JSR有一定的基本认识了吧。


JSR-310介绍

博主最近在研究JAVA 日期时间的源码,以JSR-310为切入口,给大家介绍下JSR


第一部分:时间线

JSR-310从开始到发布,历时7年左右的时间。以小见大可知,每个JSR的发布是经过细细打磨的精品。




第二部分:团队

规范负责人和专家组名单




第三部分:发展时间线明细

JSR-310 的发展时间线:




该API(JSR-310)是Stephen Colebourne (斯蒂芬·科尔本)基于Joda-Time 项目进行实现的。


Stephen Colebourne (斯蒂芬·科尔本)创立并领导了 Joda-Time 项目,并且是 Apache 软件基金会的成员,主要致力于 Jakarta-Commons。Michael Nascimento Santos 创立并领导了 Genesis 项目,并为 NetBeans、AspectWerkz 和 Thinlet 做出了贡献。Michael 还拥有 5 个以前的 JSR 的经验。


第四部分:明细

JSR-310规范的描述(官方文档直接翻译)

这个 JSR 将为 Java 提供一个新的和改进的日期和时间 API。主要目标是借鉴从 Java SE 前两个 API(日期和日历)中吸取的经验教训,为日期和时间操作提供更高级和更全面的模型。


新 API 将针对所有需要日期和时间数据模型的应用程序。该模型将超越类来替换日期和日历,包括表示没有时间的日期、没有日期的时间、持续时间和间隔。这将提高应用程序代码的质量。例如,日期和时间模型将提供一个明确定义它的类,而不是使用 int 来存储持续时间,并使用 javadoc 将其描述为天数。


新 API 还将解决相关的日期和时间问题。这些包括格式化和解析,考虑到 ISO8601 标准及其实现,例如 XML。此外,还将考虑序列化和持久性领域。


新 API 的最终目标是简单易用。API 将需要包含一些强大的功能,但不能让这些功能模糊标准用例。易于使用的一部分包括与现有 Date 和 Calendar 类的交互,这将是专家组的重点。


目标Java平台

Java SE(Java EE 也适用)


提议的规范将满足 Java 社区的哪些需求(官方文档直接翻译)

目前 Java SE 有两个独立的日期和时间 API - java.util.Date 和 java.util.Calendar。Java 开发人员在博客和论坛上一致认为这两种 API 难以使用。值得注意的是,两者都使用了月份的从0开始索引,这是导致许多错误的原因。多年来,日历也遭受了许多错误和性能问题,主要是由于在内部以两种不同的方式存储其状态。

一个经典错误 (4639407) 阻止在 Calendar 对象中创建某些日期。可以编写一系列代码,可以在某些年份创建日期,但不能在其他年份创建,从而防止某些用户输入正确的出生日期。这是由于 Calendar 类只允许在夏季增加 1 小时的夏令时时间,而在历史上,它是在第二次世界大战前后增加 2 小时。虽然此错误现已修复,但如果在未来某个时间某个国家/地区选择在夏季引入 3 小时的夏令时增益,那么 Calendar 类将再次被破坏。

当前的 Java SE API 也受到多线程环境的影响。众所周知,不可变类本质上是线程安全的,因为它们的状态不能改变。但是,Date 和 Calendar 都是可变的,这需要程序员明确考虑克隆和线程化。此外,DateTimeFormat 中缺乏线程安全性并不广为人知,并且已成为许多难以追踪的线程问题的原因。

除了 Java SE 的 datetime 类的问题外,它没有用于建模其他概念的类。非时区日期或时间、持续时间、周期和间隔在 Java SE 中没有类表示。因此,开发人员经常使用 int 来表示持续时间,并使用 javadoc 指定单位。

缺乏全面的日期和时间模型也导致许多常见操作比应有的更棘手。例如,计算两个日期之间的天数是目前特别困难的问题。

该 JSR 将解决完整日期和时间模型的问题,包括日期和时间(有和没有时区)、持续时间和时间段、间隔、格式和解析。

第五部分:下载API和分析

JSR-310包含的核心代码有哪些呢


下载API文档:如图点击:




选择下载:






打开index页面






看到这个目录包结构,很熟悉是不是




java.time 包里放的是java8 新的日期和时间API

1

java.time包含基于ISO-8601标准的主API。这里定义的类表示主要的日期时间概念,包括瞬间、持续时间、日期、时间、时区和时段。它们以ISO日历系统为基础,这是遵循公历规则的事实上的世界日历。所有的类都是不可变的和线程安全的。


博主最近刚写了篇关于java新旧日期和时间的API对比文章《万字博文教你搞懂java源码的日期和时间相关用法》


有兴趣可以了解一下,可以快速了解两套API的差异和新API的好用之处。


(一)JSR-310:ZoneId 时区和偏移量

地理知识回顾

时区

由于世界各国家与地区经度不同,地方时也有所不同,因此会划分为不同的时区。

正式的时区划分包括24个时区,每一时区由一个英文字母表示,每隔经度15°划分一个时区。

为了克服时间上的混乱,1884年在华盛顿召开的一次国际经度会议(又称国际子午线会议)上,规定将全球划分为24个时区(东、西各12个时区)。规定英国(格林尼治天文台旧址)为中时区(零时区)、东1—12区,西1—12区。每个时区横跨经度15度,时间正好是1小时。最后的东、西第12区各跨经度7.5度,以东、西经180度为界。每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时,相邻两个时区的时间相差1小时。

例如,中国东8区的时间总比泰国东7区的时间早1小时,而比日本东9区的时间晚1小时。

–引用自百度百科


时区经度分布如列表所示:


时区 时区经度范围 时区中心线

UTC(0时区) 7.5°W~7.5°E 0°

UTC+1(东1区) 7.5°E~22.5°E 15°E

UTC+2(东2区) 22.5°E~37.5°E 30°E

UTC+3(东3区) 37.5°E~52.5°E 45°E

UTC+4(东4区) 52.5°E~67.5°E 60°E

UTC+5(东5区) 67.5°E~82.5°E 75°E

UTC+6(东6区) 82.5°E~97.5°E 90°E

UTC+7(东7区) 97.5°E~112.5°E 105°E

UTC+8(东8区) 112.5°E~127.5°E 120°E

UTC+9(东9区) 127.5°E~142.5°E 135°E

UTC+10(东10区) 142.5°E~157.5°E 150°E

UTC+11(东11区) 157.5°E~172.5°E 165°E

UTC12(东、西12区) 172.5°E~172.5°W 180°

UTC-11(西11区) 172.5°W~157.5°W 165°W

UTC-10(西10区) 157.5°W~142.5°W 150°W

UTC-9(西9区) 142.5°W~127.5°W 135°W

UTC-8(西8区) 127.5°W~112.5°W 120°W

UTC-7(西7区) 112.5°W~97.5°W 105°W

UTC-6(西6区) 97.5°W~82.5°W 90°W

UTC-5(西5区) 82.5°W~67.5°W 75°W

UTC-4(西4区) 67.5°W~52.5°W 60°W

UTC-3(西3区) 52.5°W~37.5°W 45°W

UTC-2(西2区) 37.5°W~22.5°W 30°W

UTC-1(西1区) 22.5°W~7.5°W 15°W

实际上,常常1个国家或1个省份同时跨着2个或更多时区,为了照顾到行政上的方便,常将1个国家或1个省份划在一起。例如,中国幅员宽广,差不多跨5个时区,但为了使用方便简单,实际上在只用东八时区的标准时即北京时间为准。


UTC

协调世界时,又称世界统一时间、世界标准时间、国际协调时间。由于英文(CUT)和法文(TUC)的缩写不同,作为妥协,简称UTC。

协调世界时是以原子时秒长为基础,在时刻上尽量接近于世界时的一种时间计量系统。

国际原子时的准确度为每日数纳秒,而世界时的准确度为每日数毫秒。许多应用部门要求时间系统接近世界时UT,对于这种情况,一种称为协调世界时的折中时标于1972年面世。为确保协调世界时与世界时相差不会超过0.9秒,在有需要的情况下会在协调世界时内加上正或负闰秒。因此协调世界时与国际原子时之间会出现若干整数秒的差别,两者之差逐年积累,便采用跳秒(闰秒)的方法使协调时与世界时的时刻相接近,其差不超过1s。它既保持时间尺度的均匀性,又能近似地反映地球自转的变化。

–引用自百度百科


协调世界时跟地区位置没有相关,不代表当前时刻某个地方的时间,所以在说某个地方时间时要加上时区。例如:中国就是UTC+8。


UTC是时间标准,这个标准把世界分成UTC-12到UTC+12共24个时区。


GMT

GMT(Greenwich Mean Time)别名:格林尼治时间(有时候翻译也叫格林威治),中文名:世界时。


GMT是指格林尼治所在地的标准时间,也是表示地球自转速率的一种形式。以地球自转为基础的时间计量系统。地球自转的角度可用地方子午线相对于地球上的基本参考点的运动来度量。为了测量地球自转,人们在地球上选取了两个基本参考点:春分点(见分至点)和平太阳点,由此确定的时间分别称为恒星时和平太阳时。

–引用自百度百科


GMT并不等于UTC,只是格林尼治刚好在0时区上。所以GMT = UTC+0才是对的。


CST

CST可视为美国、澳大利亚、古巴或中国的标准时间

美国中部时间:Central Standard Time (USA) UT-6:00

澳大利亚中部时间:Central Standard Time (Australia) UT+9:30

中国标准时间:China Standard Time UT+8:00

古巴标准时间:Cuba Standard Time UT-4:00


–引用自百度百科


所以在换算CST时间时,要注意对应的时区。这是一个坑。


美国中部时间:CST=UTC/GMT-6;


中国标准时间:CST=UTC/GMT+8;


DST

DST(Daylight Saving Time)中文名:夏令时。


表示为了节约能源,人为规定时间的意思。也叫夏时制,夏令时(Daylight Saving Time:DST),又称“日光节约时制”和“夏令时间”,在这一制度实行期间所采用的统一时间称为“夏令时间”。一般在天亮早的夏季人为将时间调快一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。各个采纳夏时制的国家具体规定不同。全世界有近110个国家每年要实行夏令时。

–引用自百度百科


中国实现DST时间范围:1986年至1991年。


ISO-8601

国际标准化组织的国际标准ISO 8601是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前最新为第三版ISO8601:2004,第一版为ISO8601:1988,第二版为ISO8601:2000

–引用自百度百科


年由4位数字组成YYYY,或者带正负号的四或五位数字表示±YYYYY。以公历公元1年为0001年,以公元前1年为0000年,公元前2年为-0001年。


月、日用两位数字表示:MM、DD。


只使用数字为基本格式。使用短横线"-"间隔开年、月、日为扩展格式。


小时、分和秒都用2位数表示,对UTC时间最后加一个大写字母Z,其他时区用实际时间加时差表示。如UTC时间下午2点30分5秒表示为14:30:05Z或143005Z,当时的北京时间表示为22:30:05+08:00或223005+0800,也可以简化成223005+08。


注:大家还记得java的Date类吗?它默认就是使用ISO-8601表示的。


JDK8之前:时区/偏移量TimeZone

在JDK8之前,我们一直用java.util.TimeZone来表示和处理时区和偏移量。


**TimeZone.getDefault()**获得当前JVM所运行的时区,那它是怎么获取默认时区的呢,之前有写过分析文章,有兴趣的可以了解下,这里就不再重复了。


JDK获取默认时区的风险和最佳实践


有时候需要做时区的时间转换,比如一个时间要用北京时间和纽约时间显示。实现:


这里没有到SimpleDateFormat 来格式化时间是因为它是线程不安全的。选用线程安全的FastDateFormat,


Apache Commons Lang包支持。


有兴趣可以了解下FastDateFormat 的源码分析:java的SimpleDateFormat线程不安全出问题了,虚竹教你多种解决方案


 String patternStr = "yyyy-MM-dd HH:mm:ss";

 // 北京时间(new出来就是默认时区的时间)

 Date bjDate = new Date();

 // 得到纽约的时区

 TimeZone newYorkTimeZone = TimeZone.getTimeZone("America/New_York");

 // 根据此时区 将北京时间转换为纽约的Date

 FastDateFormat fastDateFormat = FastDateFormat.getInstance(patternStr,newYorkTimeZone);

 System.out.println("这是北京时间:" + FastDateFormat.getInstance(patternStr).format(bjDate));

 System.out.println("这是纽约时间:" + fastDateFormat.format(bjDate));

1

2

3

4

5

6

7

8

9



19-7=12 北京时间比纽约时间快12小时。




JDK8开始支持:时区/偏移量 ZoneId/ZoneOffset

JDK8中ZoneId表示时区的ID,ZoneOffset表示Greenwich/UTC的偏移量。




ZoneId 是用来替换java.util.TimeZone 的。


我们来研究下ZoneId ,ZoneId代表一个时区的ID,它是确定的。但是时区ID是有对应的规则,规则变化为java.time.zone.ZoneRules 决定。像夏令时规则是由各国政府定的,可能会变化,不同的年还不一样,这个就交给JDK底层机制来保持同步,我们调用者不需要关心(不!要关心!当技术不再是黑盒时,才能做到心里有底! )。


时区的规则发生变化时,如何同步时区

TZUpdater 工具介绍

提供的 TZUpdater 工具 允许您使用更新的时区数据更新已安装的 Java 开发工具包 (JDK) 和 Java 运行时环境 (JRE) 软件,以适应不同国家/地区的夏令时 (DST) 更改。Oracle 依赖于通过 IANA 的时区数据库公开提供的时区数据。


如果您无法使用 Oracle 最新的 JDK 或 JRE 更新版本,或者如果最新版本上的时区数据不是最新可用的,TZUpdater 工具提供了一种更新时区数据的方法,同时保持其他系统配置和依赖项不变.


TZUpdater 工具用法

TZUpdater 工具用于执行该工具的 JDK/JRE 软件实例。每次执行都会修改 JDK/JRE 软件。要将工具管理到 JDK/JRE 软件的多个实例。


在安装的 JDK/JRE 软件上运行 TZUpdater 工具之前,您必须停止操作系统上的 JDK/JRE 软件的任何正在运行的服务。


使用以下命令运行 TZUpdater 工具:


java -jar tzupdater.jar options

1

要成功更新时区数据,您应该确保您有足够的权限来修改JDK_HOME /jre/lib或JRE_HOME /lib目录。


如果未指定任何选项,则会显示用法消息。要更新时区数据,请使用-l或-f选项。


选项 描述

-h, --help

将用法打印到stdout并退出。如果指定此选项,则其他选项将被忽略。

-V, --version 打印工具版本、JRE 中的 tzdata 版本以及工具将更新到的 tzdata 版本,然后退出。

-l, --location url-link-to-archive-file 从提供的tzdata.tar.gz包中编译、测试和更新 JRE 时区数据,例如-l https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz. 支持的 URL 协议:http://、https://、file://。如果未提供 URL 链接,该工具将使用位于 的最新 IANA tzdata 包https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz

-f, --force 强制 tzdata 更新。如果更新到较旧的 tzdata 版本,请使用此选项。

-v, --verbose 向 显示详细消息stdout。

手动升级

注意:


1、在安装的 JDK/JRE 软件上运行 TZUpdater 工具之前,您必须停止操作系统上的 JDK/JRE 软件的任何正在运行的服务。


2、要成功更新时区数据,您应该确保您有足够的权限来修改JDK_HOME /jre/lib或JRE_HOME /lib目录。(linux系统:JRE目录要有写权限;windows系统:用管理员身份运行cmd)


3、如果系统上有多个JDK/JRE ,需要将该工具用于每个JDK/JRE中(每个JDK/JRE都要操作一遍)


4、更新成功后,要重新启动此 JDK/JRE 实例上的应用程序服务(如果还没更新,重启下服务器试试)


操作步骤:

1、下载Oracle官方提供的tzupdater.jar包;下载地址


https://www.oracle.com/java/technologies/javase-tzupdater-downloads.html


把tzupdater.jar放到java目录bin目录下,比如


“C:\Program Files\JAVA\java-1.8.0-openjdk-1.8.0.201\bin\tzupdater.jar”;

1



2、查看当前时区数据库版本,以windows为例,用管理员身份运行cmd,切换到tzupdater.jar对应的目录:


java -jar tzupdater.jar -V

1



3、在线更新,以windows为例,用管理员身份运行cmd,切换到tzupdater.jar对应的目录:(第3种和第4种更新方式任选一种)


java -jar tzupdater.jar -l https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz

1



如图所示,已经更新成功到了tzdata2021a版本了。


更新后的文件是放在jre/lib/tzdb.dat ,如图所示,它有备份历史的版本。




4、离线更新:要先下载最新的时区数据,下载地址:


https://data.iana.org/time-zones/releases/




以windows为例,用管理员身份运行cmd。切换到tzupdater.jar对应的目录:


java -jar tzupdater.jar -l file:///[path]/tzdata.tar.gz

1

注:


windows建议放在C盘根目录下,路径目录也不要有中文;


用管理员身份运行cmd(需要写权限);


如上面的命令所示,file后面的/是3个


5、以上执行完后,用第2步的查看当前时区数据库版本命令,查看是否更新成功。




服务自动化升级

思路步骤:

1、设置定时任务(操作系统配置就行),执行tzupdater 更新时区的命令脚本;


2、新开一个时区服务,用来对外提供时区和夏令时规则读取服务,独立部署;


3、在时区服务中,写个同步按钮,用来执行tzupdater 更新时区的命令脚本;


4、在时区服务中,将timeZone数据定时写到自定义的时区表中。提供维护功能,可以自定义新增修改删除timeZone数据。


此思路的好处:

1、其他服务不需要停止服务来更新时间,直接通过调用时区服务的数据,可保证获取到最新的时区数据;


2、自动化的好处,避免了手动维护时区的繁琐,人工介入有引发问题的风险;


3、时区服务和其他业务服务是拆分的,方便未来的扩展。


系统默认的ZoneId

@Test

public void timeZoneTest2(){

 System.out.println("JDK 8之前做法:"+TimeZone.getDefault());


 System.out.println("JDK 8之后做法:"+ZoneId.systemDefault());

}

1

2

3

4

5

6



ZoneId.systemDefault()方法实现上是调用了TimeZone:

public static ZoneId systemDefault() {

       return TimeZone.getDefault().toZoneId();

   }

1

2

3

4

所以两个的结果是一样的(Asia/Shanghai),这个很正常。


TimeZone.toZoneId() 是java8 后加的方法。


  /**

    * Converts this {@code TimeZone} object to a {@code ZoneId}.

    *

    * @return a {@code ZoneId} representing the same time zone as this

    *         {@code TimeZone}

    * @since 1.8

    */

   public ZoneId toZoneId() {

       String id = getID();

       if (ZoneInfoFile.useOldMapping() && id.length() == 3) {

           if ("EST".equals(id))

               return ZoneId.of("America/New_York");

           if ("MST".equals(id))

               return ZoneId.of("America/Denver");

           if ("HST".equals(id))

               return ZoneId.of("America/Honolulu");

       }

       return ZoneId.of(id, ZoneId.SHORT_IDS);

   }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

指定字符串得到ZoneId和获取所有的zoneIds

 System.out.println(ZoneId.of("America/New_York"));


 System.out.println(ZoneId.of("Asia/Shanghai"));

1

2

3



@Test

public void ZoneIdTest2(){

 Set<String> zoneIds = ZoneId.getAvailableZoneIds();

 System.out.println("zoneIds长度:"+zoneIds.size());

 for(String zoneId : zoneIds){

  System.out.println(zoneId);

 }

}

1

2

3

4

5

6

7

8



指定的字符串不能乱写,不然会报错,要在ZoneId.getAvailableZoneIds() 的集合范围里。


从日期中获取时区

System.out.println(ZoneId.from(ZonedDateTime.now()));

System.out.println(ZoneId.from(ZoneOffset.of("+8")));

1

2

3



从日期中获取时区只支持带有时区的TemporalAccessor ,像LocalDateTime,LocalDate是不可以的,会报错。


 try {

  System.out.println(ZoneId.from(LocalDateTime.now()));

 }catch (Exception e){

  e.printStackTrace();

 }

 try {

  System.out.println(ZoneId.from(LocalDate.now()));

 }catch (Exception e){

  e.printStackTrace();

 }

1

2

3

4

5

6

7

8

9

10



ZoneId是抽象类,它有两个继承实现类:


ZoneOffset:时区偏移量

ZoneRegion:地理区域



ZoneOffset(时区偏移量)

时区偏移量是时区与Greenwich/UTC之间的时间差,一般是固定的小时数和分钟数。


最小/最大偏移量

@Test

public void ZoneIdTest5(){

 System.out.println("最小偏移量:" + ZoneOffset.MIN);

 System.out.println("最小偏移量:" + ZoneOffset.MAX);

 System.out.println("中心偏移量:" + ZoneOffset.UTC);

 // 超出最大范围

 System.out.println(ZoneOffset.of("+100"));

}

1

2

3

4

5

6

7

8



超出最大范围会报错


时分秒构造偏移量

@Test

public void ZoneIdTest6(){

 System.out.println(ZoneOffset.ofHours(10));

 System.out.println(ZoneOffset.ofHoursMinutes(10, 10));

 System.out.println(ZoneOffset.ofHoursMinutesSeconds(10, 10, 10));


 System.out.println(ZoneOffset.ofHours(-10));

}

1

2

3

4

5

6

7

8



挺方便的,也简单好理解。偏移量可以精确到秒级。




ZoneRegion(地理区域)

ZoneRegion表示地理区域,格式是:洲(州、国家)/城市。最常见的区域分类是时区数据库(TZDB)。


which defines regions such as ‘Europe/Paris’ and ‘Asia/Tokyo’.(TZDB使用“Europe/Paris”和“Asia/Tokyo”来区分地区。)


final class ZoneRegion extends ZoneId implements Serializable {

...

}

1

2

3

由源码可知,地理区域ZoneRegion是ZoneId的继承实现类。


但是我们发现这个不是对外使用的,ZoneRegion的修饰符是default(只能由同包下的类调用)。只能通过ZoneId来操作。


@Test

public void ZoneIdTest7(){

 ZoneId zoneId = ZoneId.systemDefault();

 System.out.println(zoneId);

}

1

2

3

4

5



博主在厦门,所以默认获取的时区ID是Asia/Shanghai。


ZoneId的实例是ZoneOffset或ZoneRegion

ZoneId of(String zoneId, boolean checkAvailable) 源码分析:


/**

    * Parses the ID, taking a flag to indicate whether {@code ZoneRulesException}

    * should be thrown or not, used in deserialization.

    *

    * @param zoneId  the time-zone ID, not null

    * @param checkAvailable  whether to check if the zone ID is available

    * @return the zone ID, not null

    * @throws DateTimeException if the ID format is invalid

    * @throws ZoneRulesException if checking availability and the ID cannot be found

    */

   static ZoneId of(String zoneId, boolean checkAvailable) {

       Objects.requireNonNull(zoneId, "zoneId");

       if (zoneId.length() <= 1 || zoneId.startsWith("+") || zoneId.startsWith("-")) {

           return ZoneOffset.of(zoneId);

       } else if (zoneId.startsWith("UTC") || zoneId.startsWith("GMT")) {

           return ofWithPrefix(zoneId, 3, checkAvailable);

       } else if (zoneId.startsWith("UT")) {

           return ofWithPrefix(zoneId, 2, checkAvailable);

       }

       return ZoneRegion.ofId(zoneId, checkAvailable);

   }



  private static ZoneId ofWithPrefix(String zoneId, int prefixLength, boolean checkAvailable) {

       String prefix = zoneId.substring(0, prefixLength);

       if (zoneId.length() == prefixLength) {

           return ofOffset(prefix, ZoneOffset.UTC);

       }

      ...

   }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30



由源码可知:


zoneId长度小于等于1位,或者以“+”或“-”开头的,创建的是ZoneOffset实例

以“UTC”,“UT”或“GMT”开头的,创建的是ZoneRegion实例

不符合以上两种的,创建的是ZoneRegion实例

@Test

public void ZoneIdTest8(){


 ZoneId zoneId1 = ZoneId.of("+8");

 ZoneId zoneId2 = ZoneId.of("+08:00");

 ZoneId zoneId3 = ZoneId.of("UT+8");

 ZoneId zoneId4 = ZoneId.of("Asia/Shanghai");

 System.out.println();

}

1

2

3

4

5

6

7

8

9



(二)JSR-310:常用的日期时间API

介绍下java8API比较常用的日期时间API,按java.time 包的类顺序:


Clock:时钟

Instant:瞬间时间。

LocalDate:本地日期。只有表示年月日

LocalDateTime:本地日期时间,LocalDate+LocalTime

LocalTime:本地时间,只有表示时分秒

OffsetDateTime:有时间偏移量的日期时间(不包含基于ZoneRegion的时间偏移量)

OffsetTime:有时间偏移量的时间

ZonedDateTime:有时间偏移量的日期时间(包含基于ZoneRegion的时间偏移量)

博主把这些类都点开看了,都是属于不可变类。而且官方也说了,java.time包 下的类都是线程安全的。


Clock

Clock类说明

public abstract class Clock {

...

}

1

2

3

Clock 是抽象类,内部提供了四个内部类,这是它的内部实现类




FixedClock :始终返回相同瞬间的时钟,通常使用于测试。

OffsetClock :偏移时钟,时间偏移量的单位是Duration。

SystemClock :系统默认本地时钟。

TickClock :偏移时钟,时间偏移量的单位是纳秒。

Clock 提供了下面这几个常用的方法(这几个方法在实现类里都有对应的实现):


// 获取时钟的当前Instant对象。

public abstract Instant instant()


// 获取时钟的当前毫秒数值

public long millis()


// 获取用于创建时钟的时区。

public abstract ZoneId getZone()


// 返回具有指定时区的当前时钟的新实例

public abstract Clock withZone(ZoneId zone)


1

2

3

4

5

6

7

8

9

10

11

12

FixedClock

Clock.fixed

public static Clock fixed(Instant fixedInstant, ZoneId zone)

1

需要传递instant和zone,并将返回具有固定瞬间的时钟。


 Instant instant = Instant.now();

 Clock fixedClock = Clock.fixed(instant, ZoneId.of("Asia/Shanghai"));

 Clock fixedClock1 = Clock.fixed(instant, ZoneId.of("GMT"));

 System.out.println("中国时区的Clock:"+fixedClock);

 System.out.println("GMT时区的Clock:"+fixedClock1);

1

2

3

4

5



由运行结果可知,返回的结果是有带对应时区的。


验证获取的时钟会不会改变:


 Clock clock = Clock.systemDefaultZone();

 Clock fixedClock = Clock.fixed(clock.instant(), ZoneId.of("Asia/Shanghai"));

 System.out.println(fixedClock.instant());

 try {

  Thread.sleep(1000);

 } catch (InterruptedException e) {

  e.printStackTrace();

 }

 System.out.println(fixedClock.instant());

1

2

3

4

5

6

7

8

9



Clock.fixed 创建一个固定的时钟,clock 对象将始终提供与指定相同的时刻。。如图所示,强制睡眠1秒,但是时刻没变。


Clock.fixed 跟 Offset 方法更配

由上面可知Clock.fixed 得到一个固定的时钟,那要添加时间或者减去时间就要用到Offset 方法


示例代码如下


 Clock clock = Clock.systemDefaultZone();

 Clock fixedClock = Clock.fixed(clock.instant(), ZoneId.of("Asia/Shanghai"));

 System.out.println(fixedClock.instant());

 Clock clockAdd = Clock.offset(clock, Duration.ofMinutes(20));

 Clock clockSub = Clock.offset(clock, Duration.ofMinutes(-10));

 System.out.println("原先的: " + clock.instant());

 System.out.println("加了20分钟: " + clockAdd.instant());

 System.out.println("减了10分钟: " + clockSub.instant());


1

2

3

4

5

6

7

8

9



OffsetClock

OffsetClock 是偏移时钟,时间偏移量的单位是Duration。


//Clock

    public static Clock offset(Clock baseClock, Duration offsetDuration) {

       Objects.requireNonNull(baseClock, "baseClock");

       Objects.requireNonNull(offsetDuration, "offsetDuration");

       if (offsetDuration.equals(Duration.ZERO)) {

           return baseClock;

       }

       return new OffsetClock(baseClock, offsetDuration);

   }

1

2

3

4

5

6

7

8

9

由源码可知,使用Clock.offset方法 返回的是OffsetClock实例对象


 Clock clock = Clock.systemDefaultZone();

 Clock fixedClock = Clock.fixed(clock.instant(), ZoneId.of("Asia/Shanghai"));

 System.out.println(fixedClock.instant());

 Clock clockAdd = Clock.offset(clock, Duration.ofMinutes(20));

 System.out.println("原先的: " + clock.instant());

 System.out.println("加了20分钟: " + clockAdd.instant());

1

2

3

4

5

6



SystemClock

SystemClock 是系统默认的本地时钟。


 Clock clock = Clock.systemDefaultZone();

 System.out.println(clock.millis());

 Clock utc = Clock.systemUTC();

 System.out.println(utc.millis());

 System.out.println(System.currentTimeMillis());

1

2

3

4

5



居然完全一样。这就要看下源码了


Clock.systemDefaultZone()

用的是系统默认的时区ZoneId.systemDefault()


   public static Clock systemDefaultZone() {

       return new SystemClock(ZoneId.systemDefault());

   }

1

2

3



最终调用的也是System.currentTimeMillis()


Clock.systemUTC()

用的是UTC时区ZoneOffset.UTC


   public static Clock systemUTC() {

       return new SystemClock(ZoneOffset.UTC);

   }

1

2

3



最终调用的也是System.currentTimeMillis()


结论

Clock.systemDefaultZone() 和Clock.systemUTC()获取的millis()时间戳是一样的,就是对应时区的差别。


TickClock

TickClock 是偏移时钟,时间偏移量的最小单位是纳秒。


如图所示,Clock主要提供下面三个方法


//构造的时钟的计时单位是自定义的偏移量单位

public static Clock tick(Clock baseClock, Duration tickDuration);

//构造的时钟的计时单位是分

public static Clock tickMinutes(ZoneId zone);

//构造的时钟的计时单位是秒

public static Clock tickSeconds(ZoneId zone) ;

1

2

3

4

5

6



实战:


 Clock tickClock = Clock.tick(Clock.systemDefaultZone(),Duration.ofHours(1L));

 Clock tickMinutes = Clock.tickMinutes(ZoneId.of("Asia/Shanghai"));

 Clock tickSeconds = Clock.tickSeconds(ZoneId.of("Asia/Shanghai"));


 LocalDateTime tickClockLocalDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(tickClock.millis()),ZoneId.of("Asia/Shanghai"));

 LocalDateTime tickMinutesLocalDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(tickMinutes.millis()),ZoneId.of("Asia/Shanghai"));

 LocalDateTime tickSecondsLocalDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(tickSeconds.millis()),ZoneId.of("Asia/Shanghai"));


 System.out.println("tickClock  :"+tickClock.millis() +" 转为date时间:"+tickClockLocalDateTime);

 System.out.println("tickMinutes:"+tickMinutes.millis() +" 转为date时间:"+tickMinutesLocalDateTime);

 System.out.println("tickSeconds:"+tickSeconds.millis() +" 转为date时间:"+tickSecondsLocalDateTime);

1

2

3

4

5

6

7

8

9

10

11

偏移量的单位支持:天,时,分,秒,豪秒,纳秒






Instant

Instant类说明

public final class Instant

       implements Temporal, TemporalAdjuster, Comparable<Instant>, Serializable {

       ...

       }

1

2

3

4

Instant表示瞬间时间。也是不可变类且是线程安全的。其实Java.time 这个包是线程安全的。


Instant是java 8新增的特性,里面有两个核心的字段


...

private final long seconds;

 

   private final int nanos;

...

1

2

3

4

5

一个是单位为秒的时间戳,另一个是单位为纳秒的时间戳。


是不是跟**System.currentTimeMillis()**返回的long时间戳很像,System.currentTimeMillis()返回的是毫秒级,Instant多了更精确的纳秒级时间戳。


Instant常用的用法

  Instant now = Instant.now();

 System.out.println("now:"+now);

 System.out.println(now.getEpochSecond()); // 秒

 System.out.println(now.toEpochMilli()); // 毫秒

1

2

3

4



Instant是没有时区的,但是Instant加上时区后,可以转化为ZonedDateTime

 Instant ins = Instant.now();

 ZonedDateTime zdt = ins.atZone(ZoneId.systemDefault());

 System.out.println(zdt);

1

2

3



long型时间戳转Instant

要注意long型时间戳的时间单位选择Instant对应的方法转化


//1626796436 为秒级时间戳

Instant ins = Instant.ofEpochSecond(1626796436);

ZonedDateTime zdt = ins.atZone(ZoneId.systemDefault());

System.out.println("秒级时间戳转化:"+zdt);

//1626796436111l 为秒级时间戳

Instant ins1 = Instant.ofEpochMilli(1626796436111l);

ZonedDateTime zdt1 = ins1.atZone(ZoneId.systemDefault());

System.out.println("毫秒级时间戳转化:"+zdt1);

1

2

3

4

5

6

7

8

Instant的坑

Instant.now()获取的时间与北京时间相差8个时区,这是一个细节,要避坑。


看源码,用的是UTC时间。


public static Instant now() {

       return Clock.systemUTC().instant();

   }

1

2

3

解决方案:


Instant now = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8));

System.out.println("now:"+now);

1

2



LocalDate

LocalDate类说明

LocalDate表示本地日期。只有表示年月日。相当于:yyyy-MM-dd。


LocalDate常用的用法

获取当前日期

 LocalDate localDate1 = LocalDate.now();

 LocalDate localDate2 = LocalDate.now(ZoneId.of("Asia/Shanghai"));

 LocalDate localDate3 = LocalDate.now(Clock.systemUTC());


 System.out.println("now         :"+localDate1);

 System.out.println("now by zone :"+localDate2);

 System.out.println("now by Clock:"+localDate3);

1

2

3

4

5

6

7



获取localDate对象

 LocalDate localDate1 = LocalDate.of(2021, 8, 14);

 LocalDate localDate2 = LocalDate.parse("2021-08-14");

 System.out.println(localDate1);

 System.out.println(localDate2);

1

2

3

4



获取指定日期的年月日

 LocalDate localDate1 = LocalDate.of(2021, 8, 14);

 // 当前日期年份:2021

 System.out.println(localDate1.getYear());

 // 当前日期月份对象:AUGUST

 System.out.println(localDate1.getMonth());

 // 当前日期月份:8

 System.out.println(localDate1.getMonthValue());

 // 该日期是当前周的第几天:6

 System.out.println(localDate1.getDayOfWeek().getValue());

 // 该日期是当前月的第几天:14

 System.out.println(localDate1.getDayOfMonth());

 // 该日期是当前年的第几天:226

 System.out.println(localDate1.getDayOfYear());

1

2

3

4

5

6

7

8

9

10

11

12

13



修改年月日

 LocalDate localDate1 = LocalDate.of(2021, 8, 14);

 // 修改该日期的年份:2022-08-14

 System.out.println(localDate1.withYear(2022));

 // 修改该日期的月份:2021-12-14

 System.out.println(localDate1.withMonth(12));

 // 修改该日期在当月的天数:2021-08-01

 System.out.println(localDate1.withDayOfMonth(1));

1

2

3

4

5

6

7



比较日期

 LocalDate localDate1 = LocalDate.of(2021, 8, 14);

 // 比较指定日期和参数日期,返回正数,那么指定日期时间较晚(数字较大):13

 int i = localDate1.compareTo(LocalDate.of(2021, 8, 1));

 System.out.println(i);

 // 比较指定日期是否比参数日期早(true为早):true

 System.out.println(localDate1.isBefore(LocalDate.of(2021,8,31)));

 // 比较指定日期是否比参数日期晚(true为晚):false

 System.out.println(localDate1.isAfter(LocalDate.of(2021,8,31)));

 // 比较两个日期是否相等:true

 System.out.println(localDate1.isEqual(LocalDate.of(2021, 8, 14)));

1

2

3

4

5

6

7

8

9

10



LocalDate 和String相互转化、Date和LocalDate相互转化

LocalDate 和String相互转化

 LocalDate localDate1 = LocalDate.of(2021, 8, 14);

 // LocalDate 转 String

 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

 String dateString = localDate1.format(dateTimeFormatter);

 System.out.println("LocalDate 转 String:"+dateString);

 // String 转 LocalDate

 String str = "2021-08-14";

 DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");

 LocalDate date = LocalDate.parse(str, fmt);

 System.out.println("String 转 LocalDate:"+date);

1

2

3

4

5

6

7

8

9

10



Date和LocalDate相互转化

// Date 转 LocalDate

 Date now = new Date();

 // 先将Date转换为ZonedDateTime

 Instant instant = now.toInstant();

 ZonedDateTime zonedDateTime = instant.atZone(ZoneId.of("Asia/Shanghai"));

 LocalDate localDate = zonedDateTime.toLocalDate();

 // Sat Aug 14 23:16:28 CST 2021

 System.out.println(now);

 // 2021-08-14

 System.out.println(localDate);


 // LocalDate 转 Date

 LocalDate now1 = LocalDate.now();

 ZonedDateTime dateTime = now1.atStartOfDay(ZoneId.of("Asia/Shanghai"));

 Date date1 = Date.from(dateTime.toInstant());

 System.out.println(date1);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16



LocalDateTime

LocalDateTime类说明

表示当前日期时间,相当于:yyyy-MM-ddTHH:mm:ss


LocalDateTime常用的用法

获取当前日期和时间

 LocalDate d = LocalDate.now(); // 当前日期

 LocalTime t = LocalTime.now(); // 当前时间

 LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间

 System.out.println(d); // 严格按照ISO 8601格式打印

 System.out.println(t); // 严格按照ISO 8601格式打印

 System.out.println(dt); // 严格按照ISO 8601格式打印

1

2

3

4

5

6



由运行结果可行,本地日期时间通过now()获取到的总是以当前默认时区返回的


获取指定日期和时间

 LocalDate d2 = LocalDate.of(2021, 07, 14); // 2021-07-14, 注意07=07月

 LocalTime t2 = LocalTime.of(13, 14, 20); // 13:14:20

 LocalDateTime dt2 = LocalDateTime.of(2021, 07, 14, 13, 14, 20);

 LocalDateTime dt3 = LocalDateTime.of(d2, t2);

 System.out.println("指定日期时间:"+dt2);

 System.out.println("指定日期时间:"+dt3);

1

2

3

4

5

6



日期时间的加减法及修改

 LocalDateTime currentTime = LocalDateTime.now(); // 当前日期和时间

 System.out.println("------------------时间的加减法及修改-----------------------");

 //3.LocalDateTime的加减法包含了LocalDate和LocalTime的所有加减,上面说过,这里就只做简单介绍

 System.out.println("3.当前时间:" + currentTime);

 System.out.println("3.当前时间加5年:" + currentTime.plusYears(5));

 System.out.println("3.当前时间加2个月:" + currentTime.plusMonths(2));

 System.out.println("3.当前时间减2天:" + currentTime.minusDays(2));

 System.out.println("3.当前时间减5个小时:" + currentTime.minusHours(5));

 System.out.println("3.当前时间加5分钟:" + currentTime.plusMinutes(5));

 System.out.println("3.当前时间加20秒:" + currentTime.plusSeconds(20));

 //还可以灵活运用比如:向后加一年,向前减一天,向后加2个小时,向前减5分钟,可以进行连写

 System.out.println("3.同时修改(向后加一年,向前减一天,向后加2个小时,向前减5分钟):" + currentTime.plusYears(1).minusDays(1).plusHours(2).minusMinutes(5));

 System.out.println("3.修改年为2025年:" + currentTime.withYear(2025));

 System.out.println("3.修改月为12月:" + currentTime.withMonth(12));

 System.out.println("3.修改日为27日:" + currentTime.withDayOfMonth(27));

 System.out.println("3.修改小时为12:" + currentTime.withHour(12));

 System.out.println("3.修改分钟为12:" + currentTime.withMinute(12));

 System.out.println("3.修改秒为12:" + currentTime.withSecond(12));

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18



LocalDateTime和Date相互转化

Date转LocalDateTime

 System.out.println("------------------方法一:分步写-----------------------");

 //实例化一个时间对象

 Date date = new Date();

 //返回表示时间轴上同一点的瞬间作为日期对象

 Instant instant = date.toInstant();

 //获取系统默认时区

 ZoneId zoneId = ZoneId.systemDefault();

 //根据时区获取带时区的日期和时间

 ZonedDateTime zonedDateTime = instant.atZone(zoneId);

 //转化为LocalDateTime

 LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();

 System.out.println("方法一:原Date = " + date);

 System.out.println("方法一:转化后的LocalDateTime = " + localDateTime);


 System.out.println("------------------方法二:一步到位(推荐使用)-----------------------");

 //实例化一个时间对象

 Date todayDate = new Date();

 //Instant.ofEpochMilli(long l)使用1970-01-01T00:00:00Z的纪元中的毫秒来获取Instant的实例

 LocalDateTime ldt = Instant.ofEpochMilli(todayDate.getTime()).atZone(ZoneId.systemDefault()).toLocalDateTime();

 System.out.println("方法二:原Date = " + todayDate);

 System.out.println("方法二:转化后的LocalDateTime = " + ldt);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21



LocalDateTime转Date

 System.out.println("------------------方法一:分步写-----------------------");

 //获取LocalDateTime对象,当前时间

 LocalDateTime localDateTime = LocalDateTime.now();

 //获取系统默认时区

 ZoneId zoneId = ZoneId.systemDefault();

 //根据时区获取带时区的日期和时间

 ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);

 //返回表示时间轴上同一点的瞬间作为日期对象

 Instant instant = zonedDateTime.toInstant();

 //转化为Date

 Date date = Date.from(instant);

 System.out.println("方法一:原LocalDateTime = " + localDateTime);

 System.out.println("方法一:转化后的Date = " + date);


 System.out.println("------------------方法二:一步到位(推荐使用)-----------------------");

 //实例化一个LocalDateTime对象

 LocalDateTime now = LocalDateTime.now();

 //转化为date

 Date dateResult = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());

 System.out.println("方法二:原LocalDateTime = " + now);

 System.out.println("方法二:转化后的Date = " + dateResult);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21



LocalTime

LocalTime类说明

LocalTime:本地时间,只有表示时分秒


LocalTime常用的用法

获取当前时间

 LocalTime localTime1 = LocalTime.now();

 LocalTime localTime2 = LocalTime.now(ZoneId.of("Asia/Shanghai"));

 LocalTime localTime3 = LocalTime.now(Clock.systemDefaultZone());


 System.out.println("now         :"+localTime1);

 System.out.println("now by zone :"+localTime2);

 System.out.println("now by Clock:"+localTime3);

1

2

3

4

5

6

7



获取LocalTime对象

 LocalTime localTime1 = LocalTime.of(23, 26, 30);

 LocalTime localTime2 = LocalTime.of(23, 26);

 System.out.println(localTime1);

 System.out.println(localTime2);

1

2

3

4



获取指定日期的时分秒

 LocalTime localTime1 = LocalTime.of(23, 26, 30);

 //当前时间的时:23

 System.out.println(localTime1.getHour());

 //当前时间的分:26

 System.out.println(localTime1.getMinute());

 //当前时间的秒:30

 System.out.println(localTime1.getSecond());

1

2

3

4

5

6

7



修改时分秒

 LocalTime localTime1 = LocalTime.of(23, 26, 30);

 //修改时间的时:00:26:30

 System.out.println(localTime1.withHour(0));

 //修改时间的分:23:30:30

 System.out.println(localTime1.withMinute(30));

 //修改时间的秒:23:26:59

 System.out.println(localTime1.withSecond(59));

1

2

3

4

5

6

7



比较时间

 LocalTime localTime1 = LocalTime.of(23, 26, 30);

 LocalTime localTime2 = LocalTime.of(23, 26, 32);

 // 两个时间进行比较 大返回1,小就返回-1,一样就返回0:-1

 System.out.println(localTime1.compareTo(localTime2));


 // 比较指定时间是否比参数时间早(true为早):true

 System.out.println(localTime1.isBefore(localTime2));

 // 比较指定时间是否比参数时间晚(true为晚):false

 System.out.println(localTime1.isAfter(localTime2));

 // 比较两个时间是否相等:true

 System.out.println(localTime1.equals(LocalTime.of(23, 26, 30)));

1

2

3

4

5

6

7

8

9

10

11



OffsetDateTime

OffsetDateTime类说明

OffsetDateTime:有时间偏移量的日期时间(不包含基于ZoneRegion的时间偏移量)


public final class OffsetDateTime

       implements Temporal, TemporalAdjuster, Comparable<OffsetDateTime>, Serializable {

   //The minimum supported {@code OffsetDateTime}, '-999999999-01-01T00:00:00+18:00'

   public static final OffsetDateTime MIN = LocalDateTime.MIN.atOffset(ZoneOffset.MAX);

   // The maximum supported {@code OffsetDateTime}, '+999999999-12-31T23:59:59.999999999-18:00'.

   public static final OffsetDateTime MAX = LocalDateTime.MAX.atOffset(ZoneOffset.MIN);

       ...

       }

1

2

3

4

5

6

7

8

上面的MIN 和MAX 是公有静态变量。


OffsetDateTime常用的用法

获取当前日期时间

 OffsetDateTime offsetDateTime1 = OffsetDateTime.now();

 OffsetDateTime offsetDateTime2 = OffsetDateTime.now(ZoneId.of("Asia/Shanghai"));

 OffsetDateTime offsetDateTime3 = OffsetDateTime.now(Clock.systemUTC());


 System.out.println("now         :"+offsetDateTime1);

 System.out.println("now by zone :"+offsetDateTime2);

 System.out.println("now by Clock:"+offsetDateTime3);

1

2

3

4

5

6

7



获取OffsetDateTime对象

 LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);

 OffsetDateTime offsetDateTime1 = OffsetDateTime.of(localDateTime1, ZoneOffset.ofHours(8));

 OffsetDateTime offsetDateTime2 = OffsetDateTime. of(2021, 8, 15, 13, 14, 20,0, ZoneOffset.ofHours(8));

 Instant now = Instant.now();

 OffsetDateTime offsetDateTime3 = OffsetDateTime.ofInstant(now, ZoneId.of("Asia/Shanghai"));


 System.out.println(offsetDateTime1);

 System.out.println(offsetDateTime2);

 System.out.println(offsetDateTime3);

1

2

3

4

5

6

7

8

9



获取指定日期的年月日时分秒

 LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);

 OffsetDateTime offsetDateTime1 = OffsetDateTime.of(localDateTime1, ZoneOffset.ofHours(8));

 //当前时间的年:2021

 System.out.println(offsetDateTime1.getYear());

 //当前时间的月:8

 System.out.println(offsetDateTime1.getMonthValue());

 //当前时间的日:15

 System.out.println(offsetDateTime1.getDayOfMonth());

 //当前时间的时:13

 System.out.println(offsetDateTime1.getHour());

 //当前时间的分:14

 System.out.println(offsetDateTime1.getMinute());

 //当前时间的秒:20

 System.out.println(offsetDateTime1.getSecond());

1

2

3

4

5

6

7

8

9

10

11

12

13

14



修改年月日时分秒

 LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);

 OffsetDateTime offsetDateTime1 = OffsetDateTime.of(localDateTime1, ZoneOffset.ofHours(8));

 //修改时间的年:2022-08-15T13:14:20+08:00

 System.out.println(offsetDateTime1.withYear(2022));

 //修改时间的月:2021-09-15T13:14:20+08:00

 System.out.println(offsetDateTime1.withMonth(9));

 //修改时间的日:2021-08-30T13:14:20+08:00

 System.out.println(offsetDateTime1.withDayOfMonth(30));

 //修改时间的时:2021-08-15T00:14:20+08:00

 System.out.println(offsetDateTime1.withHour(0));

 //修改时间的分:2021-08-15T13:30:20+08:00

 System.out.println(offsetDateTime1.withMinute(30));

 //修改时间的秒:2021-08-15T13:14:59+08:00

 System.out.println(offsetDateTime1.withSecond(59));

1

2

3

4

5

6

7

8

9

10

11

12

13

14



比较日期时间

 LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);

 OffsetDateTime offsetDateTime1 = OffsetDateTime.of(localDateTime1, ZoneOffset.ofHours(8));

 OffsetDateTime offsetDateTime3 = OffsetDateTime.of(localDateTime1, ZoneOffset.ofHours(8));


 LocalDateTime localDateTime2 = LocalDateTime.of(2021, 8, 15, 13, 14, 30);

 OffsetDateTime offsetDateTime2 = OffsetDateTime.of(localDateTime2, ZoneOffset.ofHours(8));


 // 两个时间进行比较 大返回1,小就返回-1,一样就返回0:-1

 System.out.println(offsetDateTime1.compareTo(offsetDateTime2));


 // 比较指定时间是否比参数时间早(true为早):true

 System.out.println(offsetDateTime1.isBefore(offsetDateTime2));

 // 比较指定时间是否比参数时间晚(true为晚):false

 System.out.println(offsetDateTime1.isAfter(offsetDateTime2));

 // 比较两个时间是否相等:true

 System.out.println(offsetDateTime1.equals(offsetDateTime3));

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16



字符串转化为OffsetDateTime对象

   String str = "2021-08-15T10:15:30+08:00";

 OffsetDateTime offsetDateTime1 = OffsetDateTime.parse(str);

 OffsetDateTime offsetDateTime2 = OffsetDateTime.parse(str,DateTimeFormatter.ISO_OFFSET_DATE_TIME);


 System.out.println(offsetDateTime1);

 System.out.println(offsetDateTime2);

1

2

3

4

5

6



OffsetTime

OffsetTime类说明

OffsetTime:有时间偏移量的时间


public final class OffsetTime

       implements Temporal, TemporalAdjuster, Comparable<OffsetTime>, Serializable {

  //The minimum supported {@code OffsetTime}, '00:00:00+18:00'.

   public static final OffsetTime MIN = LocalTime.MIN.atOffset(ZoneOffset.MAX);

 

   //The maximum supported {@code OffsetTime}, '23:59:59.999999999-18:00'.

   public static final OffsetTime MAX = LocalTime.MAX.atOffset(ZoneOffset.MIN);

   ...

}

1

2

3

4

5

6

7

8

9

上面的MIN 和MAX 是公有静态变量。


OffsetTime常用的用法

获取当前时间

 OffsetTime offsetTime1 = OffsetTime.now();

 OffsetTime offsetTime2 = OffsetTime.now(ZoneId.of("Asia/Shanghai"));

 OffsetTime offsetTime3 = OffsetTime.now(Clock.systemUTC());


 System.out.println("now         :"+offsetTime1);

 System.out.println("now by zone :"+offsetTime2);

 System.out.println("now by Clock:"+offsetTime3);

1

2

3

4

5

6

7



获取OffsetTime对象

 LocalTime localTime1 = LocalTime.of(13, 14, 20);

 OffsetTime offsetTime1 = OffsetTime.of(localTime1, ZoneOffset.ofHours(8));

 OffsetTime offsetTime2 = OffsetTime. of(13, 14, 20,0, ZoneOffset.ofHours(8));

 Instant now = Instant.now();

 OffsetTime offsetTime3 = OffsetTime.ofInstant(now, ZoneId.of("Asia/Shanghai"));


 System.out.println(offsetTime1);

 System.out.println(offsetTime2);

 System.out.println(offsetTime3);

1

2

3

4

5

6

7

8

9



获取指定时间的时分秒

 LocalTime localTime1 = LocalTime.of( 13, 14, 20);

 OffsetTime offsetTime1 = OffsetTime.of(localTime1, ZoneOffset.ofHours(8));


 //当前时间的时:13

 System.out.println(offsetTime1.getHour());

 //当前时间的分:14

 System.out.println(offsetTime1.getMinute());

 //当前时间的秒:20

 System.out.println(offsetTime1.getSecond());

1

2

3

4

5

6

7

8

9



修改时分秒

 LocalTime localTime1 = LocalTime.of( 13, 14, 20);

 OffsetTime offsetTime1 = OffsetTime.of(localTime1, ZoneOffset.ofHours(8));


 //修改时间的时:00:14:20+08:00

 System.out.println(offsetTime1.withHour(0));

 //修改时间的分:13:30:20+08:00

 System.out.println(offsetTime1.withMinute(30));

 //修改时间的秒:13:14:59+08:00

 System.out.println(offsetTime1.withSecond(59));

1

2

3

4

5

6

7

8

9



比较时间

 LocalTime localTime1 = LocalTime.of( 13, 14, 20);

 OffsetTime offsetTime1 = OffsetTime.of(localTime1, ZoneOffset.ofHours(8));

 OffsetTime offsetTime3 = OffsetTime.of(localTime1, ZoneOffset.ofHours(8));


 LocalTime localTime2 = LocalTime.of(13, 14, 30);

 OffsetTime offsetTime2 = OffsetTime.of(localTime2, ZoneOffset.ofHours(8));

 // 两个时间进行比较 大返回1,小就返回-1,一样就返回0:-1

 System.out.println(offsetTime1.compareTo(offsetTime2));


 // 比较指定时间是否比参数时间早(true为早):true

 System.out.println(offsetTime1.isBefore(offsetTime2));

 // 比较指定时间是否比参数时间晚(true为晚):false

 System.out.println(offsetTime1.isAfter(offsetTime2));

 // 比较两个时间是否相等:true

 System.out.println(offsetTime1.equals(offsetTime3));

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15



ZonedDateTime

ZonedDateTime类说明

表示一个带时区的日期和时间,ZonedDateTime可以理解为LocalDateTime+ZoneId


从源码可以看出来,ZonedDateTime类中定义了LocalDateTime和ZoneId两个变量。


且ZonedDateTime类也是不可变类且是线程安全的。


public final class ZonedDateTime

       implements Temporal, ChronoZonedDateTime<LocalDate>, Serializable {


   /**

    * Serialization version.

    */

   private static final long serialVersionUID = -6260982410461394882L;


   /**

    * The local date-time.

    */

   private final LocalDateTime dateTime;

   /**

    * The time-zone.

    */

   private final ZoneId zone;

 

   ...

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

ZonedDateTime常用的用法

获取当前日期时间

 // 默认时区获取当前时间

 ZonedDateTime zonedDateTime = ZonedDateTime.now();

 // 用指定时区获取当前时间,Asia/Shanghai为上海时区

 ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));

 //withZoneSameInstant为转换时区,参数为ZoneId

 ZonedDateTime zonedDateTime2 = zonedDateTime.withZoneSameInstant(ZoneId.of("America/New_York"));

 System.out.println(zonedDateTime);

 System.out.println(zonedDateTime1);

 System.out.println(zonedDateTime2);

1

2

3

4

5

6

7

8

9



 ZonedDateTime zonedDateTime1 = ZonedDateTime.now();

 ZonedDateTime zonedDateTime2 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));

 ZonedDateTime zonedDateTime3 = ZonedDateTime.now(Clock.systemUTC());


 System.out.println("now         :"+zonedDateTime1);

 System.out.println("now by zone :"+zonedDateTime2);

 System.out.println("now by Clock:"+zonedDateTime3);

1

2

3

4

5

6

7



获取ZonedDateTime对象

 LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);

 ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime1, ZoneOffset.ofHours(8));

 ZonedDateTime zonedDateTime2 = ZonedDateTime. of(2021, 8, 15, 13, 14, 20,0, ZoneOffset.ofHours(8));

 Instant now = Instant.now();

 ZonedDateTime zonedDateTime3 = ZonedDateTime.ofInstant(now, ZoneId.of("Asia/Shanghai"));


 System.out.println(zonedDateTime1);

 System.out.println(zonedDateTime2);

 System.out.println(zonedDateTime3);

1

2

3

4

5

6

7

8

9



获取指定日期的年月日时分秒

 LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);

 ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime1, ZoneOffset.ofHours(8));

 //当前时间的年:2021

 System.out.println(zonedDateTime1.getYear());

 //当前时间的月:8

 System.out.println(zonedDateTime1.getMonthValue());

 //当前时间的日:15

 System.out.println(zonedDateTime1.getDayOfMonth());

 //当前时间的时:13

 System.out.println(zonedDateTime1.getHour());

 //当前时间的分:14

 System.out.println(zonedDateTime1.getMinute());

 //当前时间的秒:20

 System.out.println(zonedDateTime1.getSecond());

1

2

3

4

5

6

7

8

9

10

11

12

13

14



修改年月日时分秒

 LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);

 ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime1, ZoneOffset.ofHours(8));

 //修改时间的年:2022-08-15T13:14:20+08:00

 System.out.println(zonedDateTime1.withYear(2022));

 //修改时间的月:2021-09-15T13:14:20+08:00

 System.out.println(zonedDateTime1.withMonth(9));

 //修改时间的日:2021-08-30T13:14:20+08:00

 System.out.println(zonedDateTime1.withDayOfMonth(30));

 //修改时间的时:2021-08-15T00:14:20+08:00

 System.out.println(zonedDateTime1.withHour(0));

 //修改时间的分:2021-08-15T13:30:20+08:00

 System.out.println(zonedDateTime1.withMinute(30));

 //修改时间的秒:2021-08-15T13:14:59+08:00

 System.out.println(zonedDateTime1.withSecond(59));

1

2

3

4

5

6

7

8

9

10

11

12

13

14



比较日期时间

 LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);

 ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime1, ZoneOffset.ofHours(8));


 ZonedDateTime zonedDateTime3 = ZonedDateTime.of(localDateTime1, ZoneOffset.ofHours(8));


 LocalDateTime localDateTime2 = LocalDateTime.of(2021, 8, 15, 13, 14, 30);

 ZonedDateTime zonedDateTime2 = ZonedDateTime.of(localDateTime2, ZoneOffset.ofHours(8));


 // 两个时间进行比较 大返回1,小就返回-1,一样就返回0:-1

 System.out.println(zonedDateTime1.compareTo(zonedDateTime2));


 // 比较指定时间是否比参数时间早(true为早):true

 System.out.println(zonedDateTime1.isBefore(zonedDateTime2));

 // 比较指定时间是否比参数时间晚(true为晚):false

 System.out.println(zonedDateTime1.isAfter(zonedDateTime2));

 // 比较两个时间是否相等:true

 System.out.println(zonedDateTime1.equals(zonedDateTime3));

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17



LocalDateTime+ZoneId变ZonedDateTime

 LocalDateTime localDateTime = LocalDateTime.now();

 ZonedDateTime zonedDateTime1 = localDateTime.atZone(ZoneId.systemDefault());

 ZonedDateTime zonedDateTime2 = localDateTime.atZone(ZoneId.of("America/New_York"));

 System.out.println(zonedDateTime1);

 System.out.println(zonedDateTime2);

1

2

3

4

5



上面的例子说明了,LocalDateTime是可以转成ZonedDateTime的。


(三)JSR-310:格式化和解析

DateTimeFormatter

DateTimeFormatter类说明

DateTimeFormatter的作用是进行格式化日期时间显示,且DateTimeFormatter是不可变类且是线程安全的。


public final class DateTimeFormatter {

...

}

1

2

3

说到时间的格式化显示,就要说老朋友SimpleDateFormat了,之前格式化Date就要用上。但是我们知道SimpleDateFormat是线程不安全的,还不清楚的,请看这篇文章java的SimpleDateFormat线程不安全出问题了,虚竹教你多种解决方案


DateTimeFormatter常用的用法

格式化

 ZonedDateTime zonedDateTime = ZonedDateTime.now();

 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ");

 System.out.println(formatter.format(zonedDateTime));


 DateTimeFormatter usFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US);

 System.out.println(usFormatter.format(zonedDateTime));


 DateTimeFormatter chinaFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);

 System.out.println(chinaFormatter.format(zonedDateTime));

1

2

3

4

5

6

7

8

9



解析

 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");

 String dateTime = "2021年08月22日 13时14分20秒";

 LocalDateTime localDateTime = LocalDateTime.parse(dateTime, formatter);

 System.out.println(localDateTime);

1

2

3

4



大家有没有注意到,parse方法 是放在LocalDateTime类中 的,而不是DateTimeFormatter类中 。这样的设计符合正常的思路想法,想解析出LocalDateTime 的日期时间,那就用LocalDateTime 。想解析其他的JSR-310的日期时间对象,那就用对应的日期时间对象去解析。


博主把常用的日期时间API都看了,这些里面除了Clock (时钟不需要解析的),其他都有实现parse方法 。




DateTimeFormatter的坑

1、在正常配置按照标准格式的字符串日期,是能够正常转换的。如果月,日,时,分,秒在不足两位的情况需要补0,否则的话会转换失败,抛出异常。

 DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

 LocalDateTime dt1 = LocalDateTime.parse("2021-7-20 23:46:43.946", DATE_TIME_FORMATTER);

 System.out.println(dt1);

1

2

3

会报错:




java.time.format.DateTimeParseException: Text '2021-7-20 23:46:43.946' could not be parsed at index 5

1

分析原因:是格式字符串与实际的时间不匹配


“yyyy-MM-dd HH:mm:ss.SSS”


“2021-7-20 23:46:43.946”


中间的月份格式是MM,实际时间是7


解决方案:保持格式字符串与实际的时间匹配


DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

 LocalDateTime dt1 = LocalDateTime.parse("2021-07-20 23:46:43.946", DATE_TIME_FORMATTER);

 System.out.println(dt1);

1

2

3



2、YYYY和DD谨慎使用

 LocalDate date = LocalDate.of(2020,12,31);

 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYYMM");

 // 结果是 202112

 System.out.println( formatter.format(date));

1

2

3

4



Java’s DateTimeFormatter pattern “YYYY” gives you the week-based-year, (by default, ISO-8601 standard) the year of the Thursday of that week.

1

YYYY是取的当前周所在的年份,week-based year 是 ISO 8601 规定的。2020年12月31号,周算年份,就是2021年




private static void tryit(int Y, int M, int D, String pat) {

       DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pat);

       LocalDate         dat = LocalDate.of(Y,M,D);

       String            str = fmt.format(dat);

       System.out.printf("Y=%04d M=%02d D=%02d " +

           "formatted with " +

           "\"%s\" -> %s\n",Y,M,D,pat,str);

   }

   public static void main(String[] args){

       tryit(2020,01,20,"MM/DD/YYYY");

       tryit(2020,01,21,"DD/MM/YYYY");

       tryit(2020,01,22,"YYYY-MM-DD");

       tryit(2020,03,17,"MM/DD/YYYY");

       tryit(2020,03,18,"DD/MM/YYYY");

       tryit(2020,03,19,"YYYY-MM-DD");

   }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Y=2020 M=01 D=20 formatted with "MM/DD/YYYY" -> 01/20/2020

Y=2020 M=01 D=21 formatted with "DD/MM/YYYY" -> 21/01/2020

Y=2020 M=01 D=22 formatted with "YYYY-MM-DD" -> 2020-01-22

Y=2020 M=03 D=17 formatted with "MM/DD/YYYY" -> 03/77/2020

Y=2020 M=03 D=18 formatted with "DD/MM/YYYY" -> 78/03/2020

Y=2020 M=03 D=19 formatted with "YYYY-MM-DD" -> 2020-03-79

1

2

3

4

5

6

最后三个日期是有问题的,因为大写的DD代表的是处于这一年中那一天,不是处于这个月的那一天,但是dd就没有问题。


例子参考于:https://www.cnblogs.com/tonyY/p/12153335.html


所以建议使用yyyy和dd。


3、DateTimeFormatter.format(Instant)会报错

报错信息:

java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: YearOfEra

1

分析原因:

代码**DateTimeFormatter.format(Instant)**是怎么处理的呢?


   public String format(TemporalAccessor temporal) {

       StringBuilder buf = new StringBuilder(32);

       formatTo(temporal, buf);

       return buf.toString();

   }



1

2

3

4

5

6

7

首先new了个StringBuilder对象,用来拼接字符串;


然后调用**formatTo(temporal, buf)**方法


public void formatTo(TemporalAccessor temporal, Appendable appendable) {

   Objects.requireNonNull(temporal, "temporal");

   Objects.requireNonNull(appendable, "appendable");

   try {

       DateTimePrintContext context = new DateTimePrintContext(temporal, this);

       if (appendable instanceof StringBuilder) {

           printerParser.format(context, (StringBuilder) appendable);

       } else {

           // buffer output to avoid writing to appendable in case of error

           StringBuilder buf = new StringBuilder(32);

           printerParser.format(context, buf);

           appendable.append(buf);

       }

   } catch (IOException ex) {

       throw new DateTimeException(ex.getMessage(), ex);

   }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

**formatTo(temporal, buf)**方法也是先判断两个入参空处理。


然后,Instant对象被封装在一个新new的DateTimePrintContext对象


运行demo有问题,进行排查


 //根据特定格式格式化日期

 DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");

 String dateStr = DateUtil.format(new Date(),dtf);

 System.out.println(dateStr);

1

2

3

4



到这里已经是jdk的源码了DateTimeFormatter.format








从上面可知,会调用 NumberPrinterParser.format() NumberPrinterParser是在DateTimeFormatterBuilder类中的。




到这一步会报错




为什么会报错呢,我们来看下context.getValue(field)发生了什么:




从上面代码可行,temporal实际上是Instant对象,Instant.getLong只支持四种字段类型。。


NANO_OF_SECOND

MICRO_OF_SECOND

MILLI_OF_SECOND

INSTANT_SECONDS

1

2

3

4



如果不是上面这几种字段类型,则抛出异常


DateUtil.format当遇到DateTimeFormatter会将Date对象首先转换为Instant,因为缺少时区,导致报错。


解决方案:

/**

* 根据特定格式格式化日期

*

* @param date   被格式化的日期

* @param format

* @return 格式化后的字符串

* @since 5.0.0

*/

public static String format(Date date, DateTimeFormatter format) {

  if (null == format || null == date) {

     return null;

  }

  Instant instant = date.toInstant();

  ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());

  LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();

  return format.format(localDateTime);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

先把date类型转化为LocalDateTime类型,然后再进行DateTimeFormatter.format(LocalDateTime)的格式化


测试demo


//根据特定格式格式化日期

String str = "2021-07-25 20:11:25";

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:dd");

Date date = DateUtil.parse(str);

String dateStr = DateUtil.format(date,dtf);

System.out.println(dateStr);

Assert.assertEquals(str, dateStr);

1

2

3

4

5

6

7



DateTimeFormatterBuilder

DateTimeFormatterBuilder类说明

DateTimeFormatter 的所有格式化器都是用DateTimeFormatterBuilder 建造器类创建的。


看下面两个ofPattern 源码:


//DateTimeFormatter

public static DateTimeFormatter ofPattern(String pattern) {

       return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter();

   }

 

    public static DateTimeFormatter ofPattern(String pattern, Locale locale) {

       return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale);

   }

1

2

3

4

5

6

7

8

解析风格配置

官方提供了四种解析风格的配置,如下枚举 SettingsParser :


static enum SettingsParser implements DateTimePrinterParser {

    // 大小写敏感

       SENSITIVE,

    // 大小写不敏感

       INSENSITIVE,

    //严格

       STRICT,

    //宽松

       LENIENT;

       ...

       }

1

2

3

4

5

6

7

8

9

10

11

对应DateTimeFormatterBuilder 类中的方法:


// 大小写敏感

public DateTimeFormatterBuilder parseCaseSensitive()

// 大小写不敏感

public DateTimeFormatterBuilder parseCaseInsensitive()

// 严格

public DateTimeFormatterBuilder parseStrict()

// 宽松

public DateTimeFormatterBuilder parseLenient()

1

2

3

4

5

6

7

8

这四个方法对应的源码如下:


// 大小写敏感

public DateTimeFormatterBuilder parseCaseSensitive() {

       appendInternal(SettingsParser.SENSITIVE);

       return this;

   }

// 大小写不敏感

public DateTimeFormatterBuilder parseCaseInsensitive() {

       appendInternal(SettingsParser.INSENSITIVE);

       return this;

   }

// 严格

public DateTimeFormatterBuilder parseStrict() {

       appendInternal(SettingsParser.STRICT);

       return this;

   }

// 宽松

public DateTimeFormatterBuilder parseLenient() {

       appendInternal(SettingsParser.LENIENT);

       return this;

   }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

可以看出,都是调用appendInternal 方法。


接着往下看 appendInternal 源码:


private int appendInternal(DateTimePrinterParser pp) {

       Objects.requireNonNull(pp, "pp");

       if (active.padNextWidth > 0) {

           if (pp != null) {

               pp = new PadPrinterParserDecorator(pp, active.padNextWidth, active.padNextChar);

           }

           active.padNextWidth = 0;

           active.padNextChar = 0;

       }

       active.printerParsers.add(pp);

       active.valueParserIndex = -1;

       return active.printerParsers.size() - 1;

   }

1

2

3

4

5

6

7

8

9

10

11

12

13

其中active 是一个DateTimeFormatterBuilder 实例,且这个DateTimeFormatterBuilder 实例内部有一个列表 List< DateTimePrinterParser > ,看了源码可知,真正做解析工作的是DateTimePrinterParser 对应的实例来做的。


DateTimePrinterParser 的源码:


interface DateTimePrinterParser {


       boolean format(DateTimePrintContext context, StringBuilder buf);


       int parse(DateTimeParseContext context, CharSequence text, int position);

   }

1

2

3

4

5

6

源码有一共有16个DateTimePrinterParser 的实例。


//1.Composite printer and parser.

static final class CompositePrinterParser implements DateTimePrinterParser {...}


//2.Pads the output to a fixed width.

static final class PadPrinterParserDecorator implements DateTimePrinterParser {...}


//3.Enumeration to apply simple parse settings.

static enum SettingsParser implements DateTimePrinterParser{...}


//4. Defaults a value into the parse if not currently present.

static class DefaultValueParser implements DateTimePrinterParser {...}


//5.Prints or parses a character literal.

static final class CharLiteralPrinterParser implements DateTimePrinterParser {...}


//6.Prints or parses a string literal.

static final class StringLiteralPrinterParser implements DateTimePrinterParser {...}


//7.Prints and parses a numeric date-time field with optional padding.

static class NumberPrinterParser implements DateTimePrinterParser {...}


//8.Prints and parses a numeric date-time field with optional padding.

static final class FractionPrinterParser implements DateTimePrinterParser {...}


//9.Prints or parses field text.

static final class TextPrinterParser implements DateTimePrinterParser  {...}


//10.Prints or parses an ISO-8601 instant.

static final class InstantPrinterParser implements DateTimePrinterParser  {...}


//11.Prints or parses an offset ID.

static final class OffsetIdPrinterParser implements DateTimePrinterParser   {...}


//12.Prints or parses an offset ID.

static final class LocalizedOffsetIdPrinterParser implements DateTimePrinterParser {...}


//13.Prints or parses a zone ID.

static class ZoneIdPrinterParser implements DateTimePrinterParser {...}


//14. Prints or parses a chronology.

static final class ChronoPrinterParser implements DateTimePrinterParser {...}


//15.Prints or parses a localized pattern.

static final class LocalizedPrinterParser implements DateTimePrinterParser {...}


//16.Prints or parses a localized pattern from a localized field.

static final class WeekBasedFieldPrinterParser implements DateTimePrinterParser {...}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

(四)JSR-310:常用计算工具

介绍下java8 中提供了几个常用于计算的类:


Duration:表示秒和纳秒的时间量

Period:表示年月日的时间量

TemporalUnit:日期时间的基本单位

TemporalField:日期时间的属性

ValueRange:表示取值范围

Duration

Duration类说明

包路径:java.time.Duration


public final class Duration

       implements TemporalAmount, Comparable<Duration>, Serializable {

   private final long seconds;

 

   private final int nanos;

       ...

       }

1

2

3

4

5

6

7

Duration 是TemporalAmount 的实现类,类里包含两个变量seconds 和 nanos ,所以Duration 是由秒和纳秒组成的时间量。


一个Duration实例是不可变的,当创建出对象后就不能改变它的值了。


Duration常用的用法

创建Duration对象

Duration 适合处理较短的时间,需要更高的精确性。我们能使用between()方法比较两个瞬间的差:


 Instant first = Instant.now();

 try {

  Thread.sleep(1000);

 } catch (InterruptedException e) {

  e.printStackTrace();

 }

 Instant second = Instant.now();

 Duration duration = Duration.between(first, second);

 System.out.println(duration);

1

2

3

4

5

6

7

8

9



可以通过LocalDateTime 类获取获取Duration对象


 LocalDateTime first = LocalDateTime.of(2021, 8, 30, 23, 14, 20);


 LocalDateTime second = LocalDateTime.of(2021, 8, 30, 23, 13, 0);


 Duration duration = Duration.between(first, second);

 System.out.println(duration);

1

2

3

4

5

6



访问Duration的时间

Duration 对象中可以获取秒和纳秒属性。但没有毫秒属性,跟System.getCurrentTimeMillis()不同。


Instant first = Instant.now();

 try {

  Thread.sleep(1000);

 } catch (InterruptedException e) {

  e.printStackTrace();

 }

 Instant second = Instant.now();

 Duration duration = Duration.between(first, second);

 System.out.println(duration);

 System.out.println("秒:"+duration.getSeconds());

 System.out.println("纳秒:"+duration.getNano());

1

2

3

4

5

6

7

8

9

10

11



可以转换整个时间成其他单位,如纳秒,毫秒,分钟,小时,天


 Instant first = Instant.now();

 try {

  Thread.sleep(1000);

 } catch (InterruptedException e) {

  e.printStackTrace();

 }

 Instant second = Instant.now();

 Duration duration = Duration.between(first, second);

 System.out.println(duration);

 System.out.println("秒:"+duration.getSeconds());

 System.out.println("纳秒:"+duration.getNano());

 System.out.println("纳秒:"+duration.toNanos());

 System.out.println("毫秒:"+duration.toMillis());

 System.out.println("分:"+duration.toMinutes());

 System.out.println("小时:"+duration.toHours());

 System.out.println("天:"+duration.toDays());

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16



由图上可知,getNano 方法和toNanos 方法不太一样,前者是获取这段时间的小于1s的部分,后者是整个时间转化为纳秒。


Duration计算

plusNanos()

plusMillis()

plusSeconds()

plusMinutes()

plusHours()

plusDays()

minusNanos()

minusMillis()

minusSeconds()

minusMinutes()

minusHours()

minusDays()

1

2

3

4

5

6

7

8

9

10

11

12

以plusSeconds 和minusSeconds 为例:


LocalDateTime first = LocalDateTime.of(2021, 8, 30, 23, 14, 20);

 LocalDateTime second = LocalDateTime.of(2021, 8, 30, 23, 13, 0);

 Duration duration = Duration.between(first, second);

 System.out.println(duration);


 Duration duration1 = duration.plusSeconds(10);

 System.out.println("plusSeconds 后:"+duration);

 System.out.println("plusSeconds 后新的Duration对象:"+duration1);


 Duration duration2 = duration.minusSeconds(10);

 System.out.println("minusSeconds 后:"+duration);

 System.out.println("minusSeconds 后新的Duration对象:"+duration2);

1

2

3

4

5

6

7

8

9

10

11

12



由上面的验证可知,这些计算方法执行后,会返回一个新的Duration对象,原先的Duration对象不变。


Period

Period类说明

包路径:java.time.Period


public final class Period

       implements ChronoPeriod, Serializable {

           /**

    * The number of years.

    */

   private final int years;

   /**

    * The number of months.

    */

   private final int months;

   /**

    * The number of days.

    */

   private final int days;

       ...

       }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Period 是ChronoPeriod 的实现类,类里包含两个变量years ,months 和 days ,所以Period 是由年,月和日组成的时间量。


Period常用的用法

创建Period对象

 LocalDate first = LocalDate.of(2021, 8, 29);

 LocalDate second = LocalDate.of(2022, 9, 30);

 Period period = Period.between(first, second);

 System.out.println(period);

1

2

3

4



访问Period的时间

 LocalDate first = LocalDate.of(2021, 8, 28);

 LocalDate second = LocalDate.of(2022, 10, 31);

 Period period = Period.between(first, second);

 System.out.println(period);

 System.out.println("年:"+period.getYears());

 System.out.println("月:"+period.getMonths());

 System.out.println("日:"+period.getDays());

1

2

3

4

5

6

7



可以转换整个时间成其他单位,月


LocalDate first = LocalDate.of(2021, 8, 29);

 LocalDate second = LocalDate.of(2022, 9, 30);

 Period period = Period.between(first, second);

 System.out.println(period);

 System.out.println("月:"+period.toTotalMonths());

1

2

3

4

5



由图上可知,getMonths 方法和toTotalMonths 方法不太一样,前者是获取这段时间的月的部分,后者是整个时间转化为以月为单位长度。


toTotalMonths 源码:


public long toTotalMonths() {

       return years * 12L + months;  // no overflow

   }

1

2

3

Duration计算

plusDays()

plusMonths()

plusYears()


minusDays()

minusMonths()

minusYears()

1

2

3

4

5

6

7

以plusMonths 和minusMonths 为例:


 LocalDate first = LocalDate.of(2021, 8, 28);

 LocalDate second = LocalDate.of(2022, 10, 31);

 Period period = Period.between(first, second);

 System.out.println(period);

 Period period1 = period.plusMonths(1);

 System.out.println("plusMonths 后:"+period);

 System.out.println("plusMonths 后新的Period对象:"+period1);


 Period period2 = period.minusMonths(1);

 System.out.println("minusMonths 后:"+period);

 System.out.println("minusMonths 后新的Period对象:"+period2);

1

2

3

4

5

6

7

8

9

10

11



由上面的验证可知,这些计算方法执行后,会返回一个新的Period对象,原先的Period对象不变。


TemporalUnit

TemporalUnit类说明

包路径:java.time.temporal.TemporalUnit


public interface TemporalUnit {

...

}


public enum ChronoUnit implements TemporalUnit {

   private final String name;

   private final Duration duration;

  ...

}

1

2

3

4

5

6

7

8

9

TemporalUnit 主要实现类是枚举类型ChronoUnit


一个ChronoUnit成员会维护一个字符串名字属性name和一个Duration类型的实例。


其中ChronoUnit枚举了标准的日期时间单位集合,就是常用的年、月、日、小时、分钟、秒、毫秒、微秒、纳秒,这些时间单位的时间量到底是多少,代表多长的时间,在该枚举类中都有定义。


public enum ChronoUnit implements TemporalUnit {

   NANOS("Nanos", Duration.ofNanos(1)),

   MICROS("Micros", Duration.ofNanos(1000)),

   MILLIS("Millis", Duration.ofNanos(1000_000)),

   SECONDS("Seconds", Duration.ofSeconds(1)),

   MINUTES("Minutes", Duration.ofSeconds(60)),

   HOURS("Hours", Duration.ofSeconds(3600)),

   HALF_DAYS("HalfDays", Duration.ofSeconds(43200)),

   DAYS("Days", Duration.ofSeconds(86400)),

   WEEKS("Weeks", Duration.ofSeconds(7 * 86400L)),

   MONTHS("Months", Duration.ofSeconds(31556952L / 12)),

   YEARS("Years", Duration.ofSeconds(31556952L)),

   DECADES("Decades", Duration.ofSeconds(31556952L * 10L)),

   CENTURIES("Centuries", Duration.ofSeconds(31556952L * 100L)),

   MILLENNIA("Millennia", Duration.ofSeconds(31556952L * 1000L)),

   ERAS("Eras", Duration.ofSeconds(31556952L * 1000_000_000L)),

   FOREVER("Forever", Duration.ofSeconds(Long.MAX_VALUE, 999_999_999));    

   private final String name;

   private final Duration duration;

   private ChronoUnit(String name, Duration estimatedDuration) {

       this.name = name;

       this.duration = estimatedDuration;

   }

   ···

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

ChronoUnit常用的用法

 LocalDateTime localDateTime = LocalDateTime.of(2021, 8, 30, 23, 14, 20);

 LocalDateTime offset = localDateTime.plus(1, ChronoUnit.DAYS);

 // 非同一对象

 Assert.assertNotSame(localDateTime, offset);

 System.out.println(offset);

1

2

3

4

5



TemporalField

TemporalField类说明

包路径:java.time.temporal.TemporalField


public interface TemporalField {

   ...

}


public enum ChronoField implements TemporalField {

    private final String name;

   private final TemporalUnit baseUnit;

   private final TemporalUnit rangeUnit;

   private final ValueRange range;

   ...

}

1

2

3

4

5

6

7

8

9

10

11

TemporalField 主要实现类是枚举类型ChronoField


一个ChronoField成员会维护一个字符串名字属性name、一个TemporalUnit的基础单位baseUnit、一个TemporalUnit的表示范围的单位rangeUnit和一个ValueRange类型的range用于表示当前属性的范围。


public enum ChronoField implements TemporalField {

   //一秒钟的纳秒数

   NANO_OF_SECOND("NanoOfSecond", NANOS, SECONDS, ValueRange.of(0, 999_999_999))

   //一分钟的秒数

   SECOND_OF_MINUTE("SecondOfMinute", SECONDS, MINUTES, ValueRange.of(0, 59), "second")

   //一个小时的分钟数

   MINUTE_OF_HOUR("MinuteOfHour", MINUTES, HOURS, ValueRange.of(0, 59), "minute")

   //一上午或者一下午有多少个小时

   CLOCK_HOUR_OF_AMPM("ClockHourOfAmPm", HOURS, HALF_DAYS, ValueRange.of(1, 12))

   //一天的小时数

   CLOCK_HOUR_OF_DAY("ClockHourOfDay", HOURS, DAYS, ValueRange.of(1, 24))

   //上午还是下午

   AMPM_OF_DAY("AmPmOfDay", HALF_DAYS, DAYS, ValueRange.of(0, 1), "dayperiod")

   //一周的第几天

   DAY_OF_WEEK("DayOfWeek", DAYS, WEEKS, ValueRange.of(1, 7), "weekday")

   //当前月的天数

   DAY_OF_MONTH("DayOfMonth", DAYS, MONTHS, ValueRange.of(1, 28, 31), "day")

   //当前年的天数

   DAY_OF_YEAR("DayOfYear", DAYS, YEARS, ValueRange.of(1, 365, 366))

   //当前月的周数

   ALIGNED_WEEK_OF_MONTH("AlignedWeekOfMonth", WEEKS, MONTHS, ValueRange.of(1, 4, 5))

   //当前年的周数

   ALIGNED_WEEK_OF_YEAR("AlignedWeekOfYear", WEEKS, YEARS, ValueRange.of(1, 53))

   //以每月的第一天为星期一,然后计算当天是一周的第几天

   ALIGNED_DAY_OF_WEEK_IN_MONTH("AlignedDayOfWeekInMonth", DAYS, WEEKS, ValueRange.of(1, 7))

   //以每月的第一天为星期一,然后计算当天是一周的第几天

   ALIGNED_DAY_OF_WEEK_IN_YEAR("AlignedDayOfWeekInYear", DAYS, WEEKS, ValueRange.of(1, 7))

   //当前年的月数

   MONTH_OF_YEAR("MonthOfYear", MONTHS, YEARS, ValueRange.of(1, 12), "month")

 

   private final TemporalUnit baseUnit;

   private final String name;

   private final TemporalUnit rangeUnit;

   private final ValueRange range;

   private final String displayNameKey;

...

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

ChronoField常用的用法

ALIGNED_WEEK_OF_MONTH 和 ALIGNED_DAY_OF_WEEK_IN_MONTH 使用示例


 //每七天一周,2021-08-31 是周二,对应的值是3

 int num = LocalDate.of(2021, 8, 31).get(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH);

 System.out.println(num);

 //这个月的第5周 2021-08-31

 num = LocalDate.of(2021, 8, 31).get(ChronoField.ALIGNED_WEEK_OF_MONTH);

 System.out.println(num);

1

2

3

4

5

6



ValueRange

ValueRange类说明

ValueRange 表示取值范围。


public final class ValueRange implements Serializable {


   /**

    * The smallest minimum value.最小值

    */

   private final long minSmallest;

   /**

    * The largest minimum value.最大可能最小值

    */

   private final long minLargest;

   /**

    * The smallest maximum value.最小可能最大值

    */

   private final long maxSmallest;

   /**

    * The largest maximum value.最大值

    */

   private final long maxLargest;

...

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

ValueRange常用的用法

ValueRange valueRange = ValueRange.of(1L, 10000L);

 System.out.println(valueRange);

 valueRange = ValueRange.of(1L, 5L, 10000L, 50000L);

 System.out.println(valueRange);

1

2

3

4



 LocalDateTime localDateTime = LocalDateTime.of(2021, 8, 30, 23, 14, 20);

 ValueRange valueRange = localDateTime.range(ChronoField.DAY_OF_MONTH);

 System.out.println(valueRange.getMinimum());

 System.out.println(valueRange.getMaximum());

 System.out.println(valueRange.getLargestMinimum());

 System.out.println(valueRange.getSmallestMaximum());

1

2

3

4

5

6



Chronology 判断是否闰年

判断是否闰年是由年表Chronology 提供的,通常情况下,我们使用ISO下的年表,是IsoChronology 。


看下代码实现


@Override

   public boolean isLeapYear(long prolepticYear) {

       return ((prolepticYear & 3) == 0) && ((prolepticYear % 100) != 0 || (prolepticYear % 400) == 0);

   }

1

2

3

4

好精炼的代码,值得我们研究研究


闰年的基本判定方法:

1、非整百年:能被4整除的为闰年。(如2004年就是闰年,2001年不是闰年)

2、整百年:能被400整除的是闰年。(如2000年是闰年,1900年不是闰年)


((prolepticYear & 3) == 0) && ((prolepticYear % 100) != 0 || (prolepticYear % 400) == 0);

1

这段代码用了两个条件,这两个条件都符合,才是闰年。


(prolepticYear & 3) == 0


(prolepticYear % 100) != 0 || (prolepticYear % 400) == 0


(prolepticYear & 3) == 0 用了与运算符“&”,其使用规律如下:

两个操作数中位都为1,结果才为1,否则结果为0。


3 的二进制是011 ,prolepticYear & 3 目的是保留最后2位二进制数,然后判断是否最后两位二进制数等于0。如果等于0,证明能被4整除。闰年一定要满足是4的倍数的条件;


(prolepticYear % 100) != 0 || (prolepticYear % 400) == 0 这个就比较好理解了,看是不是100的倍数或者是不是400 倍数。


而且小虚竹发现java.time.Year#isLeap() 用的实现代码逻辑是一样的


public static boolean isLeap(long year) {

   return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);

}

1

2

3

即使是巨佬写的代码,也存在代码的复用性问题


上面IsoChronology 是对Chronology接口接口的isLeapYear实现,MinguoChronology等实现类的isLeapYear,互用了IsoChronology的isLeapYear方法。


//MinguoChronology

public boolean isLeapYear(long prolepticYear) {

       return IsoChronology.INSTANCE.isLeapYear(prolepticYear + YEARS_DIFFERENCE);

   }

1

2

3

4

巨佬是有考虑复用的,在MinguoChronology等实现类已经有复用了。


java.time.Year#isLeap() 的优先级高,因为它是静态方法。isoChronology ** 可以引Year.isLeap**

Year ** 不可以引Chronology.isLeapYear** 。


博主发现在IsoChronology ** 的resolveYMD** 中已经存在了对Year.isLeap 的引用。




有的工具类会为了减少外部类依赖,重新写一次底层方法,避免外部类(或是不在一个包底下)的类依赖,这个已经用了,说不过去 。所以代码是存在复用性问题的。


实战

 int year = 2020;

 System.out.println(Year.isLeap(year));

 System.out.println(IsoChronology.INSTANCE.isLeapYear(year));


 LocalDate localDate = LocalDate.of(2021,9,7);

 LocalDateTime localDateTime = LocalDateTime.now();

 System.out.println(localDate.isLeapYear());

 System.out.println(localDateTime.toLocalDate().isLeapYear());

1

2

3

4

5

6

7

8



比较日期时间的先后

基本上都有这四个比较方法::compareTo()、isBefore()、isAfter()、和equals()


比较-LocalDate

 LocalDate localDate1 = LocalDate.of(2021, 8, 14);

 // 比较指定日期和参数日期,返回正数,那么指定日期时间较晚(数字较大):13

 int i = localDate1.compareTo(LocalDate.of(2021, 8, 1));

 System.out.println(i);

 // 比较指定日期是否比参数日期早(true为早):true

 System.out.println(localDate1.isBefore(LocalDate.of(2021,8,31)));

 // 比较指定日期是否比参数日期晚(true为晚):false

 System.out.println(localDate1.isAfter(LocalDate.of(2021,8,31)));

 // 比较两个日期是否相等:true

 System.out.println(localDate1.isEqual(LocalDate.of(2021, 8, 14)));

1

2

3

4

5

6

7

8

9

10



比较-LocalTime

 LocalTime localTime1 = LocalTime.of(23, 26, 30);

 LocalTime localTime2 = LocalTime.of(23, 26, 32);

 // 两个时间进行比较 大返回1,小就返回-1,一样就返回0:-1

 System.out.println(localTime1.compareTo(localTime2));


 // 比较指定时间是否比参数时间早(true为早):true

 System.out.println(localTime1.isBefore(localTime2));

 // 比较指定时间是否比参数时间晚(true为晚):false

 System.out.println(localTime1.isAfter(localTime2));

 // 比较两个时间是否相等:true

 System.out.println(localTime1.equals(LocalTime.of(23, 26, 30)));

1

2

3

4

5

6

7

8

9

10

11



比较-OffsetDateTime

 LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);

 OffsetDateTime offsetDateTime1 = OffsetDateTime.of(localDateTime1, ZoneOffset.ofHours(8));

 OffsetDateTime offsetDateTime3 = OffsetDateTime.of(localDateTime1, ZoneOffset.ofHours(8));


 LocalDateTime localDateTime2 = LocalDateTime.of(2021, 8, 15, 13, 14, 30);

 OffsetDateTime offsetDateTime2 = OffsetDateTime.of(localDateTime2, ZoneOffset.ofHours(8));


 // 两个时间进行比较 大返回1,小就返回-1,一样就返回0:-1

 System.out.println(offsetDateTime1.compareTo(offsetDateTime2));


 // 比较指定时间是否比参数时间早(true为早):true

 System.out.println(offsetDateTime1.isBefore(offsetDateTime2));

 // 比较指定时间是否比参数时间晚(true为晚):false

 System.out.println(offsetDateTime1.isAfter(offsetDateTime2));

 // 比较两个时间是否相等:true

 System.out.println(offsetDateTime1.equals(offsetDateTime3));

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16



比较-OffsetTime

 LocalTime localTime1 = LocalTime.of( 13, 14, 20);

 OffsetTime offsetTime1 = OffsetTime.of(localTime1, ZoneOffset.ofHours(8));

 OffsetTime offsetTime3 = OffsetTime.of(localTime1, ZoneOffset.ofHours(8));


 LocalTime localTime2 = LocalTime.of(13, 14, 30);

 OffsetTime offsetTime2 = OffsetTime.of(localTime2, ZoneOffset.ofHours(8));

 // 两个时间进行比较 大返回1,小就返回-1,一样就返回0:-1

 System.out.println(offsetTime1.compareTo(offsetTime2));


 // 比较指定时间是否比参数时间早(true为早):true

 System.out.println(offsetTime1.isBefore(offsetTime2));

 // 比较指定时间是否比参数时间晚(true为晚):false

 System.out.println(offsetTime1.isAfter(offsetTime2));

 // 比较两个时间是否相等:true

 System.out.println(offsetTime1.equals(offsetTime3));

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15



比较-ZonedDateTime

 LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);

 ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime1, ZoneOffset.ofHours(8));


 ZonedDateTime zonedDateTime3 = ZonedDateTime.of(localDateTime1, ZoneOffset.ofHours(8));


 LocalDateTime localDateTime2 = LocalDateTime.of(2021, 8, 15, 13, 14, 30);

 ZonedDateTime zonedDateTime2 = ZonedDateTime.of(localDateTime2, ZoneOffset.ofHours(8));


 // 两个时间进行比较 大返回1,小就返回-1,一样就返回0:-1

 System.out.println(zonedDateTime1.compareTo(zonedDateTime2));


 // 比较指定时间是否比参数时间早(true为早):true

 System.out.println(zonedDateTime1.isBefore(zonedDateTime2));

 // 比较指定时间是否比参数时间晚(true为晚):false

 System.out.println(zonedDateTime1.isAfter(zonedDateTime2));

 // 比较两个时间是否相等:true

 System.out.println(zonedDateTime1.equals(zonedDateTime3));

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17



计算日期时间的间隔

Duration 和**Period ** 都有 **between ** 方法


这个就不在重复说了,上面Duration 和Period 的常用用法里有介绍到。


TemporalAdjuster 日期校准器

序号 方法 描述

1 dayOfWeekInMonth 返回同一个月中每周的第几天

2 firstDayOfMonth 返回当月的第一天

3 firstDayOfNextMonth 返回下月的第一天

4 firstDayOfNextYear 返回下一年的第一天

5 firstDayOfYear 返回本年的第一天

6 firstInMonth 返回同一个月中第一个星期几

7 lastDayOfMonth 返回当月的最后一天

8 lastDayOfNextMonth 返回下月的最后一天

9 lastDayOfNextYear 返回下一年的最后一天

0 lastDayOfYear 返回本年的最后一天

11 lastInMonth 返回同一个月中最后一个星期几

12 next / previous 返回后一个/前一个给定的星期几

13 nextOrSame / previousOrSame 返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回

LocalDateTime now = LocalDateTime.of(2021,9,8,0,20,13);

 System.out.println("当前时间:" + now + "======>" + now.getDayOfWeek());

 System.out.println("下一个周一:" + now.with(TemporalAdjusters.next(DayOfWeek.MONDAY)));

 System.out.println("上一个周一:" + now.with(TemporalAdjusters.previous(DayOfWeek.MONDAY)));

 System.out.println("下一个周五:" + now.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY)));

 System.out.println("上一个周五:" + now.with(TemporalAdjusters.previousOrSame(DayOfWeek.FRIDAY)));

 System.out.println("本月最后一个周五:" + now.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)));

 System.out.println("本月第一个周五:" + now.with(TemporalAdjusters.firstInMonth(DayOfWeek.FRIDAY)));

 System.out.println("本月第一天:" + now.with(TemporalAdjusters.firstDayOfMonth()));

 System.out.println("本月最后一天:" + now.with(TemporalAdjusters.lastDayOfMonth()));

 System.out.println("下月的第一天:" + now.with(TemporalAdjusters.firstDayOfNextMonth()));

 System.out.println("今年的第一天:" + now.with(TemporalAdjusters.firstDayOfYear()));

 System.out.println("今年的最后一天:" + now.with(TemporalAdjusters.lastDayOfYear()));

 System.out.println("下一年的第一天:" + now.with(TemporalAdjusters.firstDayOfNextYear()));

 System.out.println("本月的第二个周五:" + now.with(TemporalAdjusters.dayOfWeekInMonth(2,DayOfWeek.FRIDAY)));

 System.out.println("两周后:" + now.with(TemporalAdjusters.ofDateAdjuster(date -> date.plusWeeks(2))));

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16



(五)JSR-310:实战+源码分析

使用场景

对JDK8+中的日期时间工具类封装


项目引用

此博文的依据:hutool-5.6.5版本源码


       <dependency>

  <groupId>cn.hutool</groupId>

  <artifactId>hutool-core</artifactId>

  <version>5.6.5</version>

 </dependency>

1

2

3

4

5

方法摘要

方法 描述

cn.hutool.core.date.LocalDateTimeUtil.now()

当前时间,默认时区

cn.hutool.core.date.LocalDateTimeUtil.of(java.time.Instant)

{@link Instant}转{@link LocalDateTime},使用默认时区

cn.hutool.core.date.LocalDateTimeUtil.ofUTC(java.time.Instant)

{@link Instant}转{@link LocalDateTime},使用UTC时区

cn.hutool.core.date.LocalDateTimeUtil.of(java.time.ZonedDateTime)

{@link ZonedDateTime}转{@link LocalDateTime}

cn.hutool.core.date.LocalDateTimeUtil.of(java.time.Instant, java.time.ZoneId)

{@link Instant}转{@link LocalDateTime}

cn.hutool.core.date.LocalDateTimeUtil.of(java.time.Instant, java.util.TimeZone)

{@link Instant}转{@link LocalDateTime}

cn.hutool.core.date.LocalDateTimeUtil.of(long)

毫秒转{@link LocalDateTime},使用默认时区

注意:此方法使用默认时区,如果非UTC,会产生时间偏移


cn.hutool.core.date.LocalDateTimeUtil.ofUTC(long)

毫秒转{@link LocalDateTime},使用UTC时区

cn.hutool.core.date.LocalDateTimeUtil.of(long, java.time.ZoneId)

毫秒转{@link LocalDateTime},根据时区不同,结果会产生时间偏移

cn.hutool.core.date.LocalDateTimeUtil.of(long, java.util.TimeZone)

毫秒转{@link LocalDateTime},结果会产生时间偏移

cn.hutool.core.date.LocalDateTimeUtil.of(java.util.Date)

{@link Date}转{@link LocalDateTime},使用默认时区

cn.hutool.core.date.LocalDateTimeUtil.of(java.time.temporal.TemporalAccessor)

{@link TemporalAccessor}转{@link LocalDateTime},使用默认时区

cn.hutool.core.date.LocalDateTimeUtil.ofDate(java.time.temporal.TemporalAccessor)

{@link TemporalAccessor}转{@link LocalDate},使用默认时区

cn.hutool.core.date.LocalDateTimeUtil.parse(java.lang.CharSequence)

解析日期时间字符串为{@link LocalDateTime},仅支持yyyy-MM-dd’T’HH:mm:ss格式,例如:2007-12-03T10:15:30

cn.hutool.core.date.LocalDateTimeUtil.parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)

解析日期时间字符串为{@link LocalDateTime},格式支持日期时间、日期、时间

cn.hutool.core.date.LocalDateTimeUtil.parse(java.lang.CharSequence, java.lang.String)

解析日期时间字符串为{@link LocalDateTime}

cn.hutool.core.date.LocalDateTimeUtil.parseDate(java.lang.CharSequence)

解析日期时间字符串为{@link LocalDate},仅支持yyyy-MM-dd’T’HH:mm:ss格式,例如:2007-12-03T10:15:30

cn.hutool.core.date.LocalDateTimeUtil.parseDate(java.lang.CharSequence, java.time.format.DateTimeFormatter)

解析日期时间字符串为{@link LocalDate},格式支持日期

cn.hutool.core.date.LocalDateTimeUtil.parseDate(java.lang.CharSequence, java.lang.String)

解析日期字符串为{@link LocalDate}

cn.hutool.core.date.LocalDateTimeUtil.formatNormal(java.time.LocalDateTime)

格式化日期时间为yyyy-MM-dd HH:mm:ss格式

cn.hutool.core.date.LocalDateTimeUtil.format(java.time.LocalDateTime, java.time.format.DateTimeFormatter)

格式化日期时间为指定格式

cn.hutool.core.date.LocalDateTimeUtil.format(java.time.LocalDateTime, java.lang.String)

格式化日期时间为指定格式

cn.hutool.core.date.LocalDateTimeUtil.formatNormal(java.time.LocalDate)

格式化日期时间为yyyy-MM-dd格式

cn.hutool.core.date.LocalDateTimeUtil.format(java.time.LocalDate, java.time.format.DateTimeFormatter)

格式化日期时间为指定格式

cn.hutool.core.date.LocalDateTimeUtil.format(java.time.LocalDate, java.lang.String)

格式化日期时间为指定格式

cn.hutool.core.date.LocalDateTimeUtil.offset(java.time.LocalDateTime, long, java.time.temporal.TemporalUnit)

日期偏移,根据field不同加不同值(偏移会修改传入的对象)

cn.hutool.core.date.LocalDateTimeUtil.between(java.time.LocalDateTime, java.time.LocalDateTime)

获取两个日期的差,如果结束时间早于开始时间,获取结果为负。

返回结果为{@link Duration}对象,通过调用toXXX方法返回相差单位


cn.hutool.core.date.LocalDateTimeUtil.between(java.time.LocalDateTime, java.time.LocalDateTime, java.time.temporal.ChronoUnit)

获取两个日期的差,如果结束时间早于开始时间,获取结果为负。

返回结果为时间差的long值


cn.hutool.core.date.LocalDateTimeUtil.betweenPeriod(java.time.LocalDate, java.time.LocalDate)

获取两个日期的表象时间差,如果结束时间早于开始时间,获取结果为负。

比如2011年2月1日,和2021年8月11日,日相差了10天,月相差6月


cn.hutool.core.date.LocalDateTimeUtil.beginOfDay(java.time.LocalDateTime)

修改为一天的开始时间,例如:2020-02-02 00:00:00,000

cn.hutool.core.date.LocalDateTimeUtil.endOfDay(java.time.LocalDateTime)

修改为一天的结束时间,例如:2020-02-02 23:59:59,999

cn.hutool.core.date.LocalDateTimeUtil.toEpochMilli(java.time.temporal.TemporalAccessor)

{@link TemporalAccessor}转换为 时间戳(从1970-01-01T00:00:00Z开始的毫秒数)

方法明细-now()

方法名称:cn.hutool.core.date.LocalDateTimeUtil.now()

方法描述

当前时间,默认时区


支持版本及以上

参数描述:

参数名 描述

返回值:

{@link LocalDateTime}


参考案例:

 Assert.assertNotNull(LocalDateTimeUtil.now());

 System.out.println(LocalDateTimeUtil.now());

1

2



源码解析:

/**

* 当前时间,默认时区

*

* @return {@link LocalDateTime}

*/

public static LocalDateTime now() {

  return LocalDateTime.now();

}

1

2

3

4

5

6

7

8

LocalDateTime.now() 的源码


public static LocalDateTime now() {

   return now(Clock.systemDefaultZone());

}

1

2

3

Clock.systemDefaultZone()


用的是系统默认的时区ZoneId.systemDefault()


   public static Clock systemDefaultZone() {

       return new SystemClock(ZoneId.systemDefault());

   }

1

2

3



最终调用的也是System.currentTimeMillis()


方法明细-of(java.time.Instant)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.of(java.time.Instant)

方法描述

{@link Instant}转{@link LocalDateTime},使用默认时区


支持版本及以上

参数描述:

参数名 描述

Instant instant

instant {@link Instant}

返回值:

{@link LocalDateTime}


参考案例:

 String dateStr = "2020-01-23 12:23:56";

 final DateTime dt = DateUtil.parse(dateStr);


 LocalDateTime of = LocalDateTimeUtil.of(dt.toInstant());

 System.out.println(of);


1

2

3

4

5

6



源码解析:

public static LocalDateTime of(Instant instant) {

 return of(instant, ZoneId.systemDefault());

}

1

2

3

这里使用了默认时区,所以打印出来的日期时间是带时区的。


public static LocalDateTime of(Instant instant, ZoneId zoneId) {

  if (null == instant) {

     return null;

  }

  return LocalDateTime.ofInstant(instant, ObjectUtil.defaultIfNull(zoneId, ZoneId.systemDefault()));

}

1

2

3

4

5

6

方法明细-ofUTC(java.time.Instant)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.ofUTC(java.time.Instant)

方法描述

{@link Instant}转{@link LocalDateTime},使用UTC时区


支持版本及以上

参数描述:

参数名 描述

Instant instant

instant {@link Instant}

返回值:

{@link LocalDateTime}


参考案例:

 String dateStr = "2020-01-23T12:23:56";

 final DateTime dt = DateUtil.parse(dateStr);


 LocalDateTime of = LocalDateTimeUtil.ofUTC(dt.toInstant());

 Assert.assertEquals(dateStr, of.toString());

 System.out.println(of);

1

2

3

4

5

6



源码解析:

public static LocalDateTime ofUTC(Instant instant) {

 return of(instant, ZoneId.of("UTC"));

}

1

2

3

这里使用了UTC 时区,然后调用下面的LocalDateTime.ofInstant


public static LocalDateTime of(Instant instant, ZoneId zoneId) {

 if (null == instant) {

  return null;

 }


 return LocalDateTime.ofInstant(instant, ObjectUtil.defaultIfNull(zoneId, ZoneId.systemDefault()));

}

1

2

3

4

5

6

7

方法明细-of(java.time.ZonedDateTime)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.of(java.time.ZonedDateTime)

方法描述

{@link ZonedDateTime}转{@link LocalDateTime}


支持版本及以上

参数描述:

参数名 描述

ZonedDateTime zonedDateTime

zonedDateTime {@link ZonedDateTime}

返回值:

{@link LocalDateTime}


参考案例:

 String dateStr = "2021-05-21T11:23:56";

 final DateTime dt = DateUtil.parse(dateStr);

 //使用默认时区

 LocalDateTime localDateTime = LocalDateTimeUtil.of(dt);

 System.out.println(localDateTime);

 ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());

 System.out.println(zonedDateTime);

 zonedDateTime = localDateTime.atZone( ZoneId.of("Asia/Shanghai"));

 System.out.println(zonedDateTime);

 LocalDateTime of = LocalDateTimeUtil.of(zonedDateTime);


 Assert.assertNotNull(of);

 Assert.assertEquals("2021-05-21T11:23:56", of.toString());

1

2

3

4

5

6

7

8

9

10

11

12

13



源码解析:

public static LocalDateTime of(ZonedDateTime zonedDateTime) {

 if (null == zonedDateTime) {

  return null;

 }

 return zonedDateTime.toLocalDateTime();

}

1

2

3

4

5

6

这里先判断了参数 是否空值


然后调用zonedDateTime.toLocalDateTime()


我们知道zonedDateTime 和LocalDateTime 是可以直接转化的


方法明细-of(java.time.Instant, java.time.ZoneId)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.of(java.time.Instant, java.time.ZoneId)

方法描述

{@link Instant}转{@link LocalDateTime}


支持版本及以上

参数描述:

参数名 描述

Instant instant

instant {@link Instant}

ZoneId zoneId

zoneId 时区

返回值:

{@link LocalDateTime}


参考案例:

 String dateStr = "2021-05-21T11:23:56";

 final DateTime dt = DateUtil.parse(dateStr);

 LocalDateTime of = LocalDateTimeUtil.of(dt.getTime(), ZoneId.of("UTC"));

 Assert.assertNotNull(of);

 Assert.assertEquals(dateStr, of.toString());


 of = LocalDateTimeUtil.of(dt.getTime(), ZoneId.of("Asia/Shanghai"));

 Assert.assertNotNull(of);

 Assert.assertEquals("2021-05-21T19:23:56", of.toString());

1

2

3

4

5

6

7

8

9

源码解析:

public static LocalDateTime of(Instant instant, ZoneId zoneId) {

 if (null == instant) {

  return null;

 }


 return LocalDateTime.ofInstant(instant, ObjectUtil.defaultIfNull(zoneId, ZoneId.systemDefault()));

}

1

2

3

4

5

6

7

这里先判断了参数 是否空值


然后执行了LocalDateTime.ofInstant(instant, ObjectUtil.defaultIfNull(zoneId, ZoneId.systemDefault()))


这里可拆分两部分:


1、ObjectUtil.defaultIfNull(zoneId, ZoneId.systemDefault())


2、LocalDateTime.ofInstant(instant, zoneId)


ObjectUtil.defaultIfNull(zoneId, ZoneId.systemDefault()) 的源码



public static <T> T defaultIfNull(final T object, final T defaultValue) {

 return (null != object) ? object : defaultValue;

}

1

2

3

4

这个很好理解,判断值是否为null ,如果是返回默认值,如果不是,原值返回。


**LocalDateTime.ofInstant(instant, zoneId) ** 是jdk8自带的源生方法


方法明细-of(java.time.Instant, java.util.TimeZone)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.of(java.time.Instant, java.util.TimeZone)

方法描述

{@link Instant}转{@link LocalDateTime}


支持版本及以上

参数描述:

参数名 描述

Instant instant

instant {@link Instant}

TimeZone timeZone

timeZone 时区

返回值:

{@link LocalDateTime}


参考案例:

 String dateStr = "2021-05-21T11:23:56";

 // 通过转换获取的Instant为UTC时间

 Instant instant1 = DateUtil.parse(dateStr).toInstant();

 LocalDateTime localDateTime = LocalDateTimeUtil.of(instant1,TimeZone.getTimeZone(ZoneId.of("Asia/Shanghai")));

 Assert.assertEquals("2021-05-21T19:23:56", localDateTime.toString());

 System.out.println(localDateTime);

1

2

3

4

5

6



源码解析:

public static LocalDateTime of(Instant instant, TimeZone timeZone) {

 if (null == instant) {

  return null;

 }


 return of(instant, ObjectUtil.defaultIfNull(timeZone, TimeZone.getDefault()).toZoneId());

}

1

2

3

4

5

6

7

这里先判断了参数 是否空值


然后执行了LocalDateTime.ofInstant(timeZone, zoneId)


这里可拆分两部分:


1、ObjectUtil.defaultIfNull(timeZone, ObjectUtil.defaultIfNull(timeZone, TimeZone.getDefault()).toZoneId())


2、LocalDateTime.ofInstant(timeZone, zoneId)


ObjectUtil.defaultIfNull(timeZone, ObjectUtil.defaultIfNull(timeZone, TimeZone.getDefault()).toZoneId()) 的源码


public static <T> T defaultIfNull(final T object, final T defaultValue) {

 return (null != object) ? object : defaultValue;

}

1

2

3

这个很好理解,判断值是否为null ,如果是返回默认值,如果不是,原值返回。


**LocalDateTime.ofInstant(instant, zoneId) ** 是jdk8自带的源生方法


方法明细-of(long)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.of(long)

方法描述

毫秒转{@link LocalDateTime},使用默认时区


注意:此方法使用默认时区,如果非UTC,会产生时间偏移


支持版本及以上

参数描述:

参数名 描述

long epochMilli

epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数

返回值:

{@link LocalDateTime}


参考案例:

 String dateStr = "2021-05-22 10:23:56";

 Long time = DateUtil.parse(dateStr).getTime();

 // 使用默认时区

 LocalDateTime localDateTime = LocalDateTimeUtil.of(time);

 Assert.assertEquals("2021-05-22T10:23:56", localDateTime.toString());

 System.out.println(localDateTime);

1

2

3

4

5

6



源码解析:

public static LocalDateTime of(long epochMilli) {

 return of(Instant.ofEpochMilli(epochMilli));

}

1

2

3

这是把long转成Instant


public static Instant ofEpochMilli(long epochMilli) {

       long secs = Math.floorDiv(epochMilli, 1000);

       int mos = (int)Math.floorMod(epochMilli, 1000);

       return create(secs, mos * 1000_000);

   }

1

2

3

4

5

然后再调用of(Instant)


public static LocalDateTime of(Instant instant) {

  return of(instant, ZoneId.systemDefault());

}

1

2

3

方法明细-ofUTC(long)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.ofUTC(long)

方法描述

毫秒转{@link LocalDateTime},使用UTC时区


支持版本及以上

参数描述:

参数名 描述

long epochMilli

epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数

返回值:

{@link LocalDateTime}


参考案例:

 String dateStr = "2021-05-22T10:23:56";

 Long time = DateUtil.parse(dateStr).getTime();

 // 使用UTC时区

 LocalDateTime localDateTime = LocalDateTimeUtil.ofUTC(time);

 Assert.assertEquals("2021-05-22T10:23:56", localDateTime.toString());

 System.out.println(localDateTime);

1

2

3

4

5

6



源码解析:

public static LocalDateTime ofUTC(long epochMilli) {

 return ofUTC(Instant.ofEpochMilli(epochMilli));

}

1

2

3

这是把long转成Instant


public static Instant ofEpochMilli(long epochMilli) {

       long secs = Math.floorDiv(epochMilli, 1000);

       int mos = (int)Math.floorMod(epochMilli, 1000);

       return create(secs, mos * 1000_000);

   }

1

2

3

4

5

然后再调用ofUTC(Instant)


public static LocalDateTime ofUTC(Instant instant) {

 return of(instant, ZoneId.of("UTC"));

}

1

2

3

方法明细-of(long, java.time.ZoneId)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.of(long, java.time.ZoneId)

方法描述

毫秒转{@link LocalDateTime},根据时区不同,结果会产生时间偏移


支持版本及以上

参数描述:

参数名 描述

long epochMilli

epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数

ZoneId zoneId

zoneId 时区

返回值:

{@link LocalDateTime}


参考案例:

 String dateStr = "2021-05-22T10:23:56";

 Long time = DateUtil.parse(dateStr).getTime();


 LocalDateTime localDateTime = LocalDateTimeUtil.of(time,ZoneId.of("Asia/Shanghai"));

 Assert.assertEquals("2021-05-22T18:23:56", localDateTime.toString());

1

2

3

4

5

源码解析:

/**

 * 毫秒转{@link LocalDateTime},根据时区不同,结果会产生时间偏移

 *

 * @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数

 * @param zoneId     时区

 * @return {@link LocalDateTime}

 */

public static LocalDateTime of(long epochMilli, ZoneId zoneId) {

 return of(Instant.ofEpochMilli(epochMilli), zoneId);

}

1

2

3

4

5

6

7

8

9

10

这是把long转成Instant


public static Instant ofEpochMilli(long epochMilli) {

       long secs = Math.floorDiv(epochMilli, 1000);

       int mos = (int)Math.floorMod(epochMilli, 1000);

       return create(secs, mos * 1000_000);

   }

1

2

3

4

5

然后再调用of(Instant, zoneId)


上面的方法已经分析多次,就不再重复水字数。


方法明细-of(long, java.util.TimeZone)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.of(long, java.util.TimeZone)

方法描述

毫秒转{@link LocalDateTime},结果会产生时间偏移


支持版本及以上

参数描述:

参数名 描述

long epochMilli

epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数

TimeZone timeZone

timeZone 时区

返回值:

{@link LocalDateTime}


参考案例:

 String dateStr = "2021-05-22T10:23:56";

 Long time = DateUtil.parse(dateStr).getTime();


 LocalDateTime localDateTime = LocalDateTimeUtil.of(time, TimeZone.getTimeZone(ZoneId.of("Asia/Shanghai")));

 Assert.assertEquals("2021-05-22T18:23:56", localDateTime.toString());

1

2

3

4

5

源码解析:

public static LocalDateTime of(long epochMilli, TimeZone timeZone) {

 return of(Instant.ofEpochMilli(epochMilli), timeZone);

}

1

2

3

这是把long转成Instant


public static Instant ofEpochMilli(long epochMilli) {

       long secs = Math.floorDiv(epochMilli, 1000);

       int mos = (int)Math.floorMod(epochMilli, 1000);

       return create(secs, mos * 1000_000);

   }

1

2

3

4

5

然后再调用of(Instant, timeZone)


上面的方法已经分析多次,就不再重复水字数。


方法明细-of(java.util.Date)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.of(java.util.Date)

方法描述

{@link Date}转{@link LocalDateTime},使用默认时区


支持版本及以上

参数描述:

参数名 描述

Date date

date Date对象

返回值:

{@link LocalDateTime}


参考案例:

String dateStr = "2021-05-22 10:23:56";

 DateTime date = DateUtil.parse(dateStr);

 //使用默认时区

 LocalDateTime localDateTime = LocalDateTimeUtil.of(date);

 Assert.assertEquals("2021-05-22T10:23:56", localDateTime.toString());

1

2

3

4

5

源码解析:

public static LocalDateTime of(Date date) {

 if (null == date) {

  return null;

 }


 if (date instanceof DateTime) {

  return of(date.toInstant(), ((DateTime) date).getZoneId());

 }

 return of(date.toInstant());

}

1

2

3

4

5

6

7

8

9

10

此方法是把Date 强转为LocalDateTime


好习惯,先判断参数date是否为空


if (date instanceof DateTime) {

  return of(date.toInstant(), ((DateTime) date).getZoneId());

 }

1

2

3

这个DateTime 是hutool自己封装的对象,继承于Date ,封装了一些常用的方法




如果不是前两者的话,就调用of(date.Instant)


方法明细-of(java.time.temporal.TemporalAccessor)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.of(java.time.temporal.TemporalAccessor)

方法描述

{@link TemporalAccessor}转{@link LocalDateTime},使用默认时区


支持版本及以上

参数描述:

参数名 描述

TemporalAccessor temporalAccessor

temporalAccessor {@link TemporalAccessor}

返回值:

{@link LocalDateTime}


参考案例:

 String dateStr = "2021-05-22T10:23:56";

 //使用默认时区

 TemporalAccessor temporalAccessor = DateTimeFormatter.ISO_DATE_TIME.parse(dateStr);

 LocalDateTime localDateTime = LocalDateTimeUtil.of(temporalAccessor);

 Assert.assertEquals("2021-05-22T10:23:56", localDateTime.toString());

1

2

3

4

5

源码解析:

public static LocalDateTime of(TemporalAccessor temporalAccessor) {

 if (null == temporalAccessor) {

  return null;

 }


 if(temporalAccessor instanceof LocalDate){

  return ((LocalDate)temporalAccessor).atStartOfDay();

 }


 return LocalDateTime.of(

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.YEAR),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.MONTH_OF_YEAR),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.DAY_OF_MONTH),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.HOUR_OF_DAY),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.MINUTE_OF_HOUR),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.SECOND_OF_MINUTE),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.NANO_OF_SECOND)

 );

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

因为入参TemporalAccessor time的实现类常用的有如下几个(java8提供的):


LocalDateTime

LocalDate

LocalTime

好习惯,先判断参数temporalAccessor是否为空


然后判断temporalAccessor是否为LocalDate对象,如果是则调用LocalDate.atStartOfDay(),返回值是localDate+‘00:00’


//LocalDate

public LocalDateTime atStartOfDay() {

       return LocalDateTime.of(this, LocalTime.MIDNIGHT);

   }

1

2

3

4

/**

* The time of midnight at the start of the day, '00:00'.

*/

public static final LocalTime MIDNIGHT;

1

2

3

4

public static LocalDateTime of(LocalDate date, LocalTime time) {

   Objects.requireNonNull(date, "date");

   Objects.requireNonNull(time, "time");

   return new LocalDateTime(date, time);

}

1

2

3

4

5

最后通过LocalDateTime.of 方法获取LocalDateTime对象的值。


但是博主发现一个问题,LocalTime 是没有年月日的,那怎么转化为LocalDateTime ,我们来写个demo看看效果


LocalTime localTime = LocalTime.now();

LocalDateTime localDateTime = LocalDateTimeUtil.of(localTime);

System.out.println(localDateTime);

1

2

3



居然没有报错,这是为什么呢


return LocalDateTime.of(

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.YEAR),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.MONTH_OF_YEAR),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.DAY_OF_MONTH),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.HOUR_OF_DAY),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.MINUTE_OF_HOUR),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.SECOND_OF_MINUTE),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.NANO_OF_SECOND)

 );

1

2

3

4

5

6

7

8

9

这里也是hutool自己封装的方法,都是调用**public static int get(TemporalAccessor temporalAccessor, TemporalField field) ** 源码如下


public static int get(TemporalAccessor temporalAccessor, TemporalField field) {

  if (temporalAccessor.isSupported(field)) {

     return temporalAccessor.get(field);

  }


  return (int)field.range().getMinimum();

}

1

2

3

4

5

6

7

这个代码很好理解,就是取temporalAccessor 对象对应的属性值,如果不存在,则取这个属性值的最小值。


断点看效果:


1、localtime是不存在year属性的




2、取这个字段的最小值。




其他字段获取方式也差不多。


方法明细-ofDate(java.time.temporal.TemporalAccessor)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.ofDate(java.time.temporal.TemporalAccessor)

方法描述

{@link TemporalAccessor}转{@link LocalDate},使用默认时区


支持版本及以上

5.3.10


参数描述:

参数名 描述

TemporalAccessor temporalAccessor

temporalAccessor {@link TemporalAccessor}

返回值:

{@link LocalDate}


参考案例:

 String dateStr = "2021-05-22T10:23:56";

 //使用默认时区

 TemporalAccessor temporalAccessor = DateTimeFormatter.ISO_DATE_TIME.parse(dateStr);

 LocalDate localDate = LocalDateTimeUtil.ofDate(temporalAccessor);

 Assert.assertEquals("2021-05-22", localDate.toString());

1

2

3

4

5

源码解析:

public static LocalDate ofDate(TemporalAccessor temporalAccessor) {

 if (null == temporalAccessor) {

  return null;

 }


 if(temporalAccessor instanceof LocalDateTime){

  return ((LocalDateTime)temporalAccessor).toLocalDate();

 }


 return LocalDate.of(

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.YEAR),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.MONTH_OF_YEAR),

   TemporalAccessorUtil.get(temporalAccessor, ChronoField.DAY_OF_MONTH)

 );

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

因为入参TemporalAccessor time的实现类常用的有如下几个(java8提供的):


LocalDateTime

LocalDate

LocalTime

好习惯,先判断参数temporalAccessor是否为空


然后判断temporalAccessor是否为LocalDateTime对象,如果是则调用LocalDateTime.toLocalDate(),返回值是localDate,因为LocalDateTime=localDate+LocalTime


最后通过LocalDate.of 方法获取LocalDate对象的值。


方法明细-parse(java.lang.CharSequence)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.parse(java.lang.CharSequence)

方法描述

解析日期时间字符串为{@link LocalDateTime},仅支持yyyy-MM-dd’T’HH:mm:ss格式,例如:2007-12-03T10:15:30


支持版本及以上

参数描述:

参数名 描述

CharSequence text

text 日期时间字符串

返回值:

{@link LocalDateTime}


参考案例:

 final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56");

 Assert.assertEquals("2020-01-23T12:23:56", localDateTime.toString());

1

2



源码解析:

/**

 * 解析日期时间字符串为{@link LocalDateTime},仅支持yyyy-MM-dd'T'HH:mm:ss格式,例如:2007-12-03T10:15:30

 *

 * @param text      日期时间字符串

 * @return {@link LocalDateTime}

 */

public static LocalDateTime parse(CharSequence text) {

 return parse(text, (DateTimeFormatter)null);

}

1

2

3

4

5

6

7

8

9

请看下面的源码分析。


方法明细-parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)

方法描述

解析日期时间字符串为{@link LocalDateTime},格式支持日期时间、日期、时间


支持版本及以上

参数描述:

参数名 描述

CharSequence text

text 日期时间字符串 当formatter为null时,字符串要符合格式2020-01-23T12:23:56

DateTimeFormatter formatter

formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter}

返回值:

{@link LocalDateTime}


参考案例:

 final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56", DateTimeFormatter.ISO_DATE_TIME);

 Assert.assertEquals("2020-01-23T12:23:56", localDateTime.toString());

 System.out.println(localDateTime);

1

2

3



源码解析:

public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter) {

 if (null == text) {

  return null;

 }

 if (null == formatter) {

  return LocalDateTime.parse(text);

 }


 return of(formatter.parse(text));

}

1

2

3

4

5

6

7

8

9

10

如果有同学对CharSequence 对象陌生的话,那对String 应该不会陌生,String 是CharSequence 的实现接口


public final class String

   implements java.io.Serializable, Comparable<String>, CharSequence {

   ...

   }

1

2

3

4

DateTimeFormatter 是jdk8提供的日期时间格式化器,用来替换我们的老朋友 simpledateformat 。


好习惯,先判断参数CharSequence是否为空


然后再判断参数DateTimeFormatter 是否为空,如果为空,则直接调用LocalDateTime.parse(text)


来看看源码


public static LocalDateTime parse(CharSequence text) {

   return parse(text, DateTimeFormatter.ISO_LOCAL_DATE_TIME);

}

1

2

3

这里用的日期格式化字符串要像这种格式的:


such as ‘2011-12-03T10:15:30’


那如果传入的CharSequence参数不是这种格式的字符串,会是什么结果


//符合格式2020-01-23T12:23:56

LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56", DateTimeFormatter.ISO_DATE_TIME);

Assert.assertEquals("2020-01-23T12:23:56", localDateTime.toString());

System.out.println(localDateTime);


//不符合格式的

DateTimeFormatter dateTimeFormatter = null;

localDateTime = LocalDateTimeUtil.parse("2020-01-23", dateTimeFormatter);

System.out.println(localDateTime);

1

2

3

4

5

6

7

8

9

执行结果,在预料之中,直接报错,这里是个坑,大家要注意


java.time.format.DateTimeParseException: Text ‘2020-01-23’ could not be parsed at index 10




最后调用of(formatter.parse(text))


formatter.parse(text) 返回结果是TemporalAccessor,不一定是我们想要的LocalDateTime ,所以还要再通过of转一下


formatter.parse(text) 是原生JDK8自带的方法。


方法明细-parse(java.lang.CharSequence, java.lang.String)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.parse(java.lang.CharSequence, java.lang.String)

方法描述

解析日期时间字符串为{@link LocalDateTime}


支持版本及以上

参数描述:

参数名 描述

CharSequence text

text 日期时间字符串

String format

format 日期格式,类似于yyyy-MM-dd HH:mm:ss,SSS

返回值:

{@link LocalDateTime}


参考案例:

 final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23", DatePattern.NORM_DATE_PATTERN);

 Assert.assertEquals("2020-01-23T00:00", localDateTime.toString());

1

2

源码解析:

public static LocalDateTime parse(CharSequence text, String format) {

 if (null == text) {

  return null;

 }


 DateTimeFormatter formatter = null;

 if(StrUtil.isNotBlank(format)){

  // 修复yyyyMMddHHmmssSSS格式不能解析的问题

  // fix issue#1082

  //see https://stackoverflow.com/questions/22588051/is-java-time-failing-to-parse-fraction-of-second

  // jdk8 bug at: https://bugs.openjdk.java.net/browse/JDK-8031085

  if(StrUtil.startWithIgnoreEquals(format, DatePattern.PURE_DATETIME_PATTERN)){

   final String fraction = StrUtil.removePrefix(format, DatePattern.PURE_DATETIME_PATTERN);

   if(ReUtil.isMatch("[S]{1,2}", fraction)){

    //将yyyyMMddHHmmssS、yyyyMMddHHmmssSS的日期统一替换为yyyyMMddHHmmssSSS格式,用0补

    text += StrUtil.repeat('0', 3-fraction.length());

   }

   formatter = new DateTimeFormatterBuilder()

     .appendPattern(DatePattern.PURE_DATETIME_PATTERN)

     .appendValue(ChronoField.MILLI_OF_SECOND, 3)

     .toFormatter();

  } else{

   formatter = DateTimeFormatter.ofPattern(format);

  }

 }


 return parse(text, formatter);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

养成好习惯,先判断参数CharSequence和format是否为空


这边针对jdk8的一个bug进行了兼容处理


1、在正常配置按照标准格式的字符串日期,是能够正常转换的。如果月,日,时,分,秒在不足两位的情况需要补0,否则的话会转换失败,抛出异常。

 DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

 LocalDateTime dt1 = LocalDateTime.parse("2021-7-20 23:46:43.946", DATE_TIME_FORMATTER);

 System.out.println(dt1);

1

2

3

会报错:




java.time.format.DateTimeParseException: Text '2021-7-20 23:46:43.946' could not be parsed at index 5

1

分析原因:是格式字符串与实际的时间不匹配


“yyyy-MM-dd HH:mm:ss.SSS”


“2021-7-20 23:46:43.946”


中间的月份格式是MM,实际时间是7


解决方案:保持格式字符串与实际的时间匹配


DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

 LocalDateTime dt1 = LocalDateTime.parse("2021-07-20 23:46:43.946", DATE_TIME_FORMATTER);

 System.out.println(dt1);

1

2

3



方法明细-parseDate(java.lang.CharSequence)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.parseDate(java.lang.CharSequence)

方法描述

解析日期时间字符串为{@link LocalDate},仅支持yyyy-MM-dd’T’HH:mm:ss格式,例如:2007-12-03T10:15:30


支持版本及以上

5.3.10


参数描述:

参数名 描述

CharSequence text

text 日期时间字符串

返回值:

{@link LocalDate}


参考案例:

 LocalDate localDate = LocalDateTimeUtil.parseDate("2020-01-23");  Assert.assertEquals("2020-01-23", localDate.toString());

1

源码解析:

/**

 * 解析日期时间字符串为{@link LocalDate},仅支持yyyy-MM-dd'T'HH:mm:ss格式,例如:2007-12-03T10:15:30

 *

 * @param text      日期时间字符串

 * @return {@link LocalDate}

 * @since 5.3.10

 */

public static LocalDate parseDate(CharSequence text) {

 return parseDate(text, (DateTimeFormatter)null);

}

1

2

3

4

5

6

7

8

9

10

请看下面的源码分析。


方法明细-parseDate(java.lang.CharSequence, java.time.format.DateTimeFormatter)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.parseDate(java.lang.CharSequence, java.time.format.DateTimeFormatter)

方法描述

解析日期时间字符串为{@link LocalDate},格式支持日期


支持版本及以上

5.3.10


参数描述:

参数名 描述

CharSequence text

text 日期时间字符串

DateTimeFormatter formatter

formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter}

返回值:

{@link LocalDate}


参考案例:

 final LocalDateTime localDateTime = LocalDateTimeUtil.parse("12:23:56", DatePattern.NORM_TIME_PATTERN);

 Assert.assertEquals("12:23:56", localDateTime.toLocalTime().toString());

1

2

源码解析:

public static LocalDate parseDate(CharSequence text, DateTimeFormatter formatter) {

 if (null == text) {

  return null;

 }

 if (null == formatter) {

  return LocalDate.parse(text);

 }


 return ofDate(formatter.parse(text));

}

1

2

3

4

5

6

7

8

9

10

如果有同学对CharSequence 对象陌生的话,那对String 应该不会陌生,String 是CharSequence 的实现接口


public final class String

   implements java.io.Serializable, Comparable<String>, CharSequence {

   ...

   }

1

2

3

4

DateTimeFormatter 是jdk8提供的日期时间格式化器,用来替换我们的老朋友 simpledateformat 。


好习惯,先判断参数CharSequence是否为空


然后再判断参数DateTimeFormatter 是否为空,如果为空,则直接调用LocalDate.parse(text)


来看看源码


public static LocalDate parse(CharSequence text) {

       return parse(text, DateTimeFormatter.ISO_LOCAL_DATE);

   }

1

2

3

这里用的日期格式化字符串要像这种格式的:


such as ‘2011-12-03’


最后调用of(formatter.parse(text))


formatter.parse(text) 返回结果是TemporalAccessor,不一定是我们想要的LocalDate ,所以还要再通过of转一下


formatter.parse(text) 是原生JDK8自带的方法。


方法明细-parseDate(java.lang.CharSequence, java.lang.String)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.parseDate(java.lang.CharSequence, java.lang.String)

方法描述

解析日期字符串为{@link LocalDate}


支持版本及以上

参数描述:

参数名 描述

CharSequence text

text 日期字符串

String format

format 日期格式,类似于yyyy-MM-dd

返回值:

{@link LocalDateTime}


参考案例:

 //第一个参数和第二个参数格式保持一致

 LocalDate localDate = LocalDateTimeUtil.parseDate("2020-01-23 12:23:56","yyyy-MM-dd hh:mm:ss");

 Assert.assertEquals("2020-01-23", localDate.toString());

  localDate = LocalDateTimeUtil.parseDate("2020/01/23 12:23:56","yyyy/MM/dd hh:mm:ss");

 Assert.assertEquals("2020-01-23", localDate.toString());

1

2

3

4

5

源码解析:

public static LocalDate parseDate(CharSequence text, String format) {

 if (null == text) {

  return null;

 }

 return parseDate(text, DateTimeFormatter.ofPattern(format));

}

1

2

3

4

5

6

请看上面的源码分析。


方法明细-formatNormal(java.time.LocalDateTime)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.formatNormal(java.time.LocalDateTime)

方法描述

格式化日期时间为yyyy-MM-dd HH:mm:ss格式


支持版本及以上

5.3.11


参数描述:

参数名 描述

LocalDateTime time

time {@link LocalDateTime}

返回值:

格式化后的字符串


参考案例:

       final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56");

    String format = LocalDateTimeUtil.formatNormal(localDateTime);

 Assert.assertEquals("2020-01-23 12:23:56", format);

1

2

3

源码解析:

/**

 * 格式化日期时间为yyyy-MM-dd HH:mm:ss格式

 *

 * @param time      {@link LocalDateTime}

 * @return 格式化后的字符串

 * @since 5.3.11

 */

public static String formatNormal(LocalDateTime time) {

 return format(time, DatePattern.NORM_DATETIME_FORMATTER);

}


//------------------------------

public static String format(LocalDateTime time, DateTimeFormatter formatter) {

 return TemporalAccessorUtil.format(time, formatter);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

TemporalAccessorUtil 类是hutool封装的工具类,看下面的源码,也是比较好理解的。


//TemporalAccessorUtil

/**

* 格式化日期时间为指定格式

*

* @param time      {@link TemporalAccessor}

* @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter}

* @return 格式化后的字符串

* @since 5.3.10

*/

public static String format(TemporalAccessor time, DateTimeFormatter formatter) {

  if (null == time) {

     return null;

  }


  if(null == formatter){

     formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

  }



  try {

     return formatter.format(time);

  } catch (UnsupportedTemporalTypeException e){

     if(time instanceof LocalDate && e.getMessage().contains("HourOfDay")){

        // 用户传入LocalDate,但是要求格式化带有时间部分,转换为LocalDateTime重试

        return formatter.format(((LocalDate) time).atStartOfDay());

     }else if(time instanceof LocalTime && e.getMessage().contains("YearOfEra")){

        // 用户传入LocalTime,但是要求格式化带有日期部分,转换为LocalDateTime重试

        return formatter.format(((LocalTime) time).atDate(LocalDate.now()));

     }

     throw e;

  }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

养成好习惯,先判断参数TemporalAccessor 和DateTimeFormatter 是否为空


如果DateTimeFormatter 为空,则给默认值DateTimeFormatter.ISO_LOCAL_DATE_TIME


such as ‘2011-12-03T10:15:30’


然后调用格式化方法formatter.format(time) 如果time 不是LocalDateTime 对象,则会报错,然后在catch里做了兼容处理,对LocalDate 和 LocalTime 对象可以进行格式化处理,其他的直接返回异常。


方法明细-format(java.time.LocalDateTime, java.time.format.DateTimeFormatter)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.format(java.time.LocalDateTime, java.time.format.DateTimeFormatter)

方法描述

格式化日期时间为指定格式


支持版本及以上

参数描述:

参数名 描述

LocalDateTime time

time {@link LocalDateTime}

DateTimeFormatter formatter

formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter}

返回值:

格式化后的字符串


参考案例:

 LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56");

 String format = LocalDateTimeUtil.format(localDateTime, DateTimeFormatter.ISO_DATE_TIME);

 Assert.assertEquals("2020-01-23T12:23:56", format);

1

2

3

源码解析:

/**

 * 格式化日期时间为指定格式

 *

 * @param time      {@link LocalDateTime}

 * @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter}

 * @return 格式化后的字符串

 */

public static String format(LocalDateTime time, DateTimeFormatter formatter) {

 return TemporalAccessorUtil.format(time, formatter);

}

1

2

3

4

5

6

7

8

9

10

请看上面的源码分析。


方法明细-format(java.time.LocalDateTime, java.lang.String)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.format(java.time.LocalDateTime, java.lang.String)

方法描述

格式化日期时间为指定格式


支持版本及以上

参数描述:

参数名 描述

LocalDateTime time

time {@link LocalDateTime}

String format

format 日期格式,类似于yyyy-MM-dd HH:mm:ss,SSS

返回值:

格式化后的字符串


参考案例:

 final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56");

 String format = LocalDateTimeUtil.format(localDateTime, DatePattern.NORM_DATETIME_PATTERN);

 Assert.assertEquals("2020-01-23 12:23:56", format);

1

2

3

源码解析:

public static String format(LocalDateTime time, String format) {

 if (null == time) {

  return null;

 }

 return format(time, DateTimeFormatter.ofPattern(format));

}

1

2

3

4

5

6

DateTimeFormatter.ofPattern(format) 执行完会返回DateTimeFormatter


然后会调用 format(time, DateTimeFormatter 方法


请看上面的源码分析。


方法明细-formatNormal(java.time.LocalDate)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.formatNormal(java.time.LocalDate)

方法描述

格式化日期时间为yyyy-MM-dd格式


支持版本及以上

5.3.11


参数描述:

参数名 描述

LocalDate date

date {@link LocalDate}

返回值:

格式化后的字符串


参考案例:

 final LocalDate date = LocalDate.parse("2020-01-23");

 String format = LocalDateTimeUtil.format(date, DatePattern.NORM_DATE_PATTERN);

 Assert.assertEquals("2020-01-23", format);


 format = LocalDateTimeUtil.formatNormal(date);

 Assert.assertEquals("2020-01-23", format);

1

2

3

4

5

6

源码解析:

public static String formatNormal(LocalDate date) {

 return format(date, DatePattern.NORM_DATE_FORMATTER);

}

1

2

3

请看上面的源码分析。


方法明细-format(java.time.LocalDate, java.time.format.DateTimeFormatter)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.format(java.time.LocalDate, java.time.format.DateTimeFormatter)

方法描述

格式化日期时间为指定格式


支持版本及以上

5.3.10


参数描述:

参数名 描述

LocalDate date

date {@link LocalDate}

DateTimeFormatter formatter

formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter}

返回值:

格式化后的字符串


参考案例:

 final LocalDate date = LocalDate.parse("2021-05-22");

 String format = LocalDateTimeUtil.format(date, DateTimeFormatter.ISO_DATE);

 Assert.assertEquals("2021-05-22", format);

1

2

3

源码解析:

public static String format(LocalDate date, DateTimeFormatter formatter) {

 return TemporalAccessorUtil.format(date, formatter);

}

1

2

3

请看上面的源码分析。


方法明细-format(java.time.LocalDate, java.lang.String)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.format(java.time.LocalDate, java.lang.String)

方法描述

格式化日期时间为指定格式


支持版本及以上

5.3.10


参数描述:

参数名 描述

LocalDate date

date {@link LocalDate}

String format

format 日期格式,类似于yyyy-MM-dd

返回值:

格式化后的字符串


参考案例:

 final LocalDate date = LocalDate.parse("2020-01-23");

 String format = LocalDateTimeUtil.format(date, DatePattern.NORM_DATE_PATTERN);

 Assert.assertEquals("2020-01-23", format);


1

2

3

4

源码解析:

public static String format(LocalDate date, String format) {

 if (null == date) {

  return null;

 }

 return format(date, DateTimeFormatter.ofPattern(format));

}

1

2

3

4

5

6

请看上面的源码分析。


方法明细-offset(java.time.LocalDateTime, long, java.time.temporal.TemporalUnit)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.offset(java.time.LocalDateTime, long, java.time.temporal.TemporalUnit)

方法描述

日期偏移,根据field不同加不同值(偏移会修改传入的对象)


支持版本及以上

参数描述:

参数名 描述

LocalDateTime time

time {@link LocalDateTime}

long number

number 偏移量,正数为向后偏移,负数为向前偏移

TemporalUnit field

field 偏移单位,见{@link ChronoUnit},不能为null

返回值:

偏移后的日期时间


参考案例:

 final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56");

 LocalDateTime offset = LocalDateTimeUtil.offset(localDateTime, 1, ChronoUnit.DAYS);

 // 非同一对象

 Assert.assertNotSame(localDateTime, offset);


 Assert.assertEquals("2020-01-24T12:23:56", offset.toString());

1

2

3

4

5

6

源码解析:

/**

 * 日期偏移,根据field不同加不同值(偏移会修改传入的对象)

 *

 * @param time   {@link LocalDateTime}

 * @param number 偏移量,正数为向后偏移,负数为向前偏移

 * @param field  偏移单位,见{@link ChronoUnit},不能为null

 * @return 偏移后的日期时间

 */

public static LocalDateTime offset(LocalDateTime time, long number, TemporalUnit field) {

 if (null == time) {

  return null;

 }


 return time.plus(number, field);

}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

time.plus(number, field) 是jdk8提供的方法


LocalDateTime localDateTime = LocalDateTime.of(2021, 8, 30, 23, 14, 20);

LocalDateTime offset = localDateTime.plus(1, ChronoUnit.DAYS);

// 非同一对象

Assert.assertNotSame(localDateTime, offset);

System.out.println(offset);

1

2

3

4

5



方法明细-between(java.time.LocalDateTime, java.time.LocalDateTime)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.between(java.time.LocalDateTime, java.time.LocalDateTime)

方法描述

获取两个日期的差,如果结束时间早于开始时间,获取结果为负。


返回结果为{@link Duration}对象,通过调用toXXX方法返回相差单位 ### 支持版本及以上


参数描述:

参数名 描述

LocalDateTime startTimeInclude

startTimeInclude 开始时间(包含)

LocalDateTime endTimeExclude

endTimeExclude 结束时间(不包含)

返回值:

时间差 {@link Duration}对象


参考案例:

 final Duration between = LocalDateTimeUtil.between(

   LocalDateTimeUtil.parse("2019-02-02T00:00:00"),

   LocalDateTimeUtil.parse("2020-02-02T00:00:00"));

 Assert.assertEquals(365, between.toDays());

1

2

3

4

源码解析:

public static Duration between(LocalDateTime startTimeInclude, LocalDateTime endTimeExclude) {

 return TemporalUtil.between(startTimeInclude, endTimeExclude);

}

1

2

3

其中TemporalUtil 是hutool封装的工具类,源码如下:


/**

* 获取两个日期的差,如果结束时间早于开始时间,获取结果为负。

* <p>

* 返回结果为{@link Duration}对象,通过调用toXXX方法返回相差单位

*

* @param startTimeInclude 开始时间(包含)

* @param endTimeExclude   结束时间(不包含)

* @return 时间差 {@link Duration}对象

*/

public static Duration between(Temporal startTimeInclude, Temporal endTimeExclude) {

  return Duration.between(startTimeInclude, endTimeExclude);

}

1

2

3

4

5

6

7

8

9

10

11

12

Duration 适合处理较短的时间,需要更高的精确性。我们能使用between()方法比较两个瞬间的差。


Duration.between 是JDK8提供的方法。


方法明细-between(java.time.LocalDateTime, java.time.LocalDateTime, java.time.temporal.ChronoUnit)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.between(java.time.LocalDateTime, java.time.LocalDateTime, java.time.temporal.ChronoUnit)

方法描述

获取两个日期的差,如果结束时间早于开始时间,获取结果为负。


返回结果为时间差的long值


支持版本及以上

5.4.5


参数描述:

参数名 描述

LocalDateTime startTimeInclude

startTimeInclude 开始时间(包括)

LocalDateTime endTimeExclude

endTimeExclude 结束时间(不包括)

ChronoUnit unit

unit 时间差单位

返回值:

时间差


参考案例:

 final long betweenWeek = DateUtil.betweenWeek(

   DateUtil.parse("2020-11-21"),

   DateUtil.parse("2020-11-23"), false);


 final long betweenWeek2 = LocalDateTimeUtil.between(

   LocalDateTimeUtil.parse("2020-11-21", "yyy-MM-dd"),

   LocalDateTimeUtil.parse("2020-11-23", "yyy-MM-dd"),

   ChronoUnit.WEEKS);

 Assert.assertEquals(betweenWeek, betweenWeek2);

1

2

3

4

5

6

7

8

9

源码解析:

public static long between(LocalDateTime startTimeInclude, LocalDateTime endTimeExclude, ChronoUnit unit) {

 return TemporalUtil.between(startTimeInclude, endTimeExclude, unit);

}

1

2

3

多了一个时间单位的选项


public static long between(Temporal startTimeInclude, Temporal endTimeExclude, ChronoUnit unit) {

  return unit.between(startTimeInclude, endTimeExclude);

}

1

2

3

这个返回的数据为0




这个返回的数据为1




这两个demo的数据,反应出了结果是按时间差单位来产生的。


方法明细-betweenPeriod(java.time.LocalDate, java.time.LocalDate)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.betweenPeriod(java.time.LocalDate, java.time.LocalDate)

方法描述

获取两个日期的表象时间差,如果结束时间早于开始时间,获取结果为负。


比如2011年2月1日,和2021年8月11日,日相差了10天,月相差6月


支持版本及以上

5.4.5


参数描述:

参数名 描述

LocalDate startTimeInclude

startTimeInclude 开始时间(包括)

LocalDate endTimeExclude

endTimeExclude 结束时间(不包括)

返回值:

时间差


参考案例:

 final LocalDate localDate1= LocalDate.parse("2021-05-22");

 final LocalDate localDate2= LocalDate.parse("2021-06-23");


 Period period = LocalDateTimeUtil.betweenPeriod(localDate1,localDate2);

 Assert.assertEquals(1, period.getMonths());

 Assert.assertEquals(1, period.getDays());


 period = LocalDateTimeUtil.betweenPeriod(localDate2,localDate1);

 Assert.assertEquals(-1, period.getMonths());

 Assert.assertEquals(-1, period.getDays());

 final LocalDate localDate3= LocalDate.parse("2021-06-22");

 period = LocalDateTimeUtil.betweenPeriod(localDate1,localDate3);

 Assert.assertEquals(1, period.getMonths());

 Assert.assertEquals(0, period.getDays());

1

2

3

4

5

6

7

8

9

10

11

12

13

14

源码解析:

public static Period betweenPeriod(LocalDate startTimeInclude, LocalDate endTimeExclude) {

 return Period.between(startTimeInclude, endTimeExclude);

}

1

2

3

Period.between 是JDK8提供的方法


Period 是ChronoPeriod 的实现类,类里包含两个变量years ,months 和 days ,所以Period 是由年,月和日组成的时间量。


LocalDate first = LocalDate.of(2021, 8, 29);

LocalDate second = LocalDate.of(2022, 9, 30);

Period period = Period.between(first, second);

System.out.println(period);

1

2

3

4



方法明细-beginOfDay(java.time.LocalDateTime)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.beginOfDay(java.time.LocalDateTime)

方法描述

修改为一天的开始时间,例如:2020-02-02 00:00:00,000


支持版本及以上

参数描述:

参数名 描述

LocalDateTime time

time 日期时间

返回值:

一天的开始时间


参考案例:

 final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56");

 final LocalDateTime beginOfDay = LocalDateTimeUtil.beginOfDay(localDateTime);

 Assert.assertEquals("2020-01-23T00:00", beginOfDay.toString());

1

2

3

源码解析:

/**

 * 修改为一天的开始时间,例如:2020-02-02 00:00:00,000

 *

 * @param time 日期时间

 * @return 一天的开始时间

 */

public static LocalDateTime beginOfDay(LocalDateTime time) {

 return time.with(LocalTime.MIN);

}

1

2

3

4

5

6

7

8

9

LocalDateTime.with 是jdk8提供的方法。


日期时间的加减法及修改

LocalDateTime currentTime = LocalDateTime.now(); // 当前日期和时间

System.out.println("------------------时间的加减法及修改-----------------------");

//3.LocalDateTime的加减法包含了LocalDate和LocalTime的所有加减,上面说过,这里就只做简单介绍

System.out.println("3.当前时间:" + currentTime);

System.out.println("3.当前时间加5年:" + currentTime.plusYears(5));

System.out.println("3.当前时间加2个月:" + currentTime.plusMonths(2));

System.out.println("3.当前时间减2天:" + currentTime.minusDays(2));

System.out.println("3.当前时间减5个小时:" + currentTime.minusHours(5));

System.out.println("3.当前时间加5分钟:" + currentTime.plusMinutes(5));

System.out.println("3.当前时间加20秒:" + currentTime.plusSeconds(20));

//还可以灵活运用比如:向后加一年,向前减一天,向后加2个小时,向前减5分钟,可以进行连写

System.out.println("3.同时修改(向后加一年,向前减一天,向后加2个小时,向前减5分钟):" + currentTime.plusYears(1).minusDays(1).plusHours(2).minusMinutes(5));

System.out.println("3.修改年为2025年:" + currentTime.withYear(2025));

System.out.println("3.修改月为12月:" + currentTime.withMonth(12));

System.out.println("3.修改日为27日:" + currentTime.withDayOfMonth(27));

System.out.println("3.修改小时为12:" + currentTime.withHour(12));

System.out.println("3.修改分钟为12:" + currentTime.withMinute(12));

System.out.println("3.修改秒为12:" + currentTime.withSecond(12));

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18



方法明细-endOfDay(java.time.LocalDateTime)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.endOfDay(java.time.LocalDateTime)

方法描述

修改为一天的结束时间,例如:2020-02-02 23:59:59,999


支持版本及以上

参数描述:

参数名 描述

LocalDateTime time

time 日期时间

返回值:

一天的结束时间


参考案例:

 final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56");

 final LocalDateTime endOfDay = LocalDateTimeUtil.endOfDay(localDateTime);

 Assert.assertEquals("2020-01-23T23:59:59.999999999", endOfDay.toString());

1

2

3

源码解析:

/**

 * 修改为一天的结束时间,例如:2020-02-02 23:59:59,999

 *

 * @param time 日期时间

 * @return 一天的结束时间

 */

public static LocalDateTime endOfDay(LocalDateTime time) {

 return time.with(LocalTime.MAX);

}


1

2

3

4

5

6

7

8

9

10

请看上面的源码分析。


方法明细-toEpochMilli(java.time.temporal.TemporalAccessor)

方法名称:cn.hutool.core.date.LocalDateTimeUtil.toEpochMilli(java.time.temporal.TemporalAccessor)

方法描述

{@link TemporalAccessor}转换为 时间戳(从1970-01-01T00:00:00Z开始的毫秒数)


支持版本及以上

5.4.1


参数描述:

参数名 描述

TemporalAccessor temporalAccessor

temporalAccessor Date对象

返回值:

{@link Instant}对象


参考案例:

 String dateStr = "2021-05-22";

 //TemporalAccessor 的实现类包含Instant LocalDateTime ZonedDateTime OffsetDateTime LocalDate LocalTime OffsetTime

 LocalDate localDate = LocalDate.parse(dateStr);

 //Date对象

 long time = LocalDateTimeUtil.toEpochMilli(localDate);

 Assert.assertEquals(DateUtil.parse(dateStr).getTime(), time);

1

2

3

4

5

6

源码解析:

public static long toEpochMilli(TemporalAccessor temporalAccessor) {

 return TemporalAccessorUtil.toEpochMilli(temporalAccessor);

}

1

2

3

TemporalAccessorUtil.toEpochMilli(temporalAccessor) 是hutool封装的方法。


public static long toEpochMilli(TemporalAccessor temporalAccessor) {

  return toInstant(temporalAccessor).toEpochMilli();

}

1

2

3

把temporalAccessor 对象转化为Instant 对象,我们来看下是怎么转化的


public static Instant toInstant(TemporalAccessor temporalAccessor) {

  if (null == temporalAccessor) {

     return null;

  }


  Instant result;

  if (temporalAccessor instanceof Instant) {

     result = (Instant) temporalAccessor;

  } else if (temporalAccessor instanceof LocalDateTime) {

     result = ((LocalDateTime) temporalAccessor).atZone(ZoneId.systemDefault()).toInstant();

  } else if (temporalAccessor instanceof ZonedDateTime) {

     result = ((ZonedDateTime) temporalAccessor).toInstant();

  } else if (temporalAccessor instanceof OffsetDateTime) {

     result = ((OffsetDateTime) temporalAccessor).toInstant();

  } else if (temporalAccessor instanceof LocalDate) {

     result = ((LocalDate) temporalAccessor).atStartOfDay(ZoneId.systemDefault()).toInstant();

  } else if (temporalAccessor instanceof LocalTime) {

     // 指定本地时间转换 为Instant,取当天日期

     result = ((LocalTime) temporalAccessor).atDate(LocalDate.now()).atZone(ZoneId.systemDefault()).toInstant();

  } else if (temporalAccessor instanceof OffsetTime) {

     // 指定本地时间转换 为Instant,取当天日期

     result = ((OffsetTime) temporalAccessor).atDate(LocalDate.now()).toInstant();

  } else {

     result = Instant.from(temporalAccessor);

  }


  return result;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

就是对应的temporalAccessor 实现类进行处理,从这个源码中,我们可以学到:如何把不同的时间类型转化为Instant 对象。


然后把Instant 对象转化为毫秒级的long数据


Instant now = Instant.now();

System.out.println("now:"+now);

System.out.println(now.getEpochSecond()); // 秒

System.out.println(now.toEpochMilli()); // 毫秒

1

2

3

4



参考:

JSR

JCP官网:https://www.jcp.org/en/home/index


2020 年执行委员会 (EC) 选举:https://www.jcp.org/aboutJava/communityprocess/elections/2020.html


2020 执行委员会选举提名人:https://www.jcp.org/aboutJava/communityprocess/elections/2020-nominees.html


JSR-310:新日期时间API(一)

时区:https://baike.baidu.com/item/%E6%97%B6%E5%8C%BA/491122?fr=aladdin


UTC:https://baike.baidu.com/item/%E5%8D%8F%E8%B0%83%E4%B8%96%E7%95%8C%E6%97%B6/787659?fromtitle=UTC&fromid=5899996&fr=aladdin


GMT:https://baike.baidu.com/item/%E4%B8%96%E7%95%8C%E6%97%B6/692237?fromtitle=GMT&fromid=6026868&fr=aladdin


CST:https://baike.baidu.com/item/CST/14822063?fr=aladdin


DST:https://baike.baidu.com/item/%E5%A4%8F%E4%BB%A4%E6%97%B6/1809579?fromtitle=DST&fromid=1203186&fr=aladdin


ISO-8601:https://baike.baidu.com/item/ISO%208601/3910715?fr=aladdin


TZUpdater :https://www.oracle.com/java/technologies/javase/tzupdater-readme.html


IANA时区数据版本:https://data.iana.org/time-zones/releases/


JRE 软件中的时区数据版本:https://www.oracle.com/java/technologies/tzdata-versions.html


JSR-310:常用计算工具(四)

duration:http://tutorials.jenkov.com/java-date-time/duration.html


ChronoUnit:https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/time/temporal/ChronoUnit.html


ValueRange:https://nowjava.com/docs/java-api-11/java.base/java/time/temporal/ValueRange.html#getMinimum()


推荐相关文章

JSR-310系列文章

高级JAVA开发必须掌握技能:java8 新日期时间API((一)JSR-310:ZoneId 时区和偏移量)


高级JAVA开发必须掌握技能:java8 新日期时间API((二)JSR-310:常用的日期时间API)


高级JAVA开发必须掌握技能:java8 新日期时间API((三)JSR-310:格式化和解析)


高级JAVA开发必须掌握技能:java8 新日期时间API((四)JSR-310:常用计算工具)


高级JAVA开发必须掌握技能:java8 新日期时间API((五)JSR-310:实战+源码分析)


高级JAVA开发必须掌握技能:java8 JSR-310判断是否闰年实现,发现原作者的代码可能有问题


hutool日期时间系列文章

1DateUtil(时间工具类)-当前时间和当前时间戳


2DateUtil(时间工具类)-常用的时间类型Date,DateTime,Calendar和TemporalAccessor(LocalDateTime)转换


3DateUtil(时间工具类)-获取日期的各种内容


4DateUtil(时间工具类)-格式化时间


5DateUtil(时间工具类)-解析被格式化的时间


6DateUtil(时间工具类)-时间偏移量获取


7DateUtil(时间工具类)-日期计算


8ChineseDate(农历日期工具类)


9LocalDateTimeUtil(JDK8+中的{@link LocalDateTime} 工具类封装)


10TemporalAccessorUtil{@link TemporalAccessor} 工具类封装


其他

要探索JDK的核心底层源码,那必须掌握native用法


万字博文教你搞懂java源码的日期和时间相关用法


java的SimpleDateFormat线程不安全出问题了,虚竹教你多种解决方案


源码分析:JDK获取默认时区的风险和最佳实践


高级JAVA开发必备技能:时区的规则发生变化时,如何同步JDK的时区规则image.png


image.png


image.png

目录
相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
77 2
|
6天前
|
JSON Java Apache
Java基础-常用API-Object类
继承是面向对象编程的重要特性,允许从已有类派生新类。Java采用单继承机制,默认所有类继承自Object类。Object类提供了多个常用方法,如`clone()`用于复制对象,`equals()`判断对象是否相等,`hashCode()`计算哈希码,`toString()`返回对象的字符串表示,`wait()`、`notify()`和`notifyAll()`用于线程同步,`finalize()`在对象被垃圾回收时调用。掌握这些方法有助于更好地理解和使用Java中的对象行为。
|
20天前
|
算法 Java API
如何使用Java开发获得淘宝商品描述API接口?
本文详细介绍如何使用Java开发调用淘宝商品描述API接口,涵盖从注册淘宝开放平台账号、阅读平台规则、创建应用并申请接口权限,到安装开发工具、配置开发环境、获取访问令牌,以及具体的Java代码实现和注意事项。通过遵循这些步骤,开发者可以高效地获取商品详情、描述及图片等信息,为项目和业务增添价值。
54 10
|
28天前
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
1月前
|
Oracle 安全 Java
深入理解Java生态:JDK与JVM的区分与协作
Java作为一种广泛使用的编程语言,其生态中有两个核心组件:JDK(Java Development Kit)和JVM(Java Virtual Machine)。本文将深入探讨这两个组件的区别、联系以及它们在Java开发和运行中的作用。
82 1
|
1月前
|
IDE Java 编译器
开发 Java 程序一定要安装 JDK 吗
开发Java程序通常需要安装JDK(Java Development Kit),因为它包含了编译、运行和调试Java程序所需的各种工具和环境。不过,某些集成开发环境(IDE)可能内置了JDK,或可使用在线Java编辑器,无需单独安装。
76 1
|
1月前
|
安全 Java API
Java中的Lambda表达式与Stream API的高效结合####
探索Java编程中Lambda表达式与Stream API如何携手并进,提升数据处理效率,实现代码简洁性与功能性的双重飞跃。 ####
26 0
|
1月前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
2月前
|
缓存 Java Maven
java: 警告: 源发行版 11 需要目标发行版 11 无效的目标发行版: 11 jdk版本不符,项目jdk版本为其他版本
如何解决Java项目中因JDK版本不匹配导致的编译错误,包括修改`pom.xml`文件、调整项目结构、设置Maven和JDK版本,以及清理缓存和重启IDEA。
63 1
java: 警告: 源发行版 11 需要目标发行版 11 无效的目标发行版: 11 jdk版本不符,项目jdk版本为其他版本
|
2月前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
38 0
[Java]静态代理与动态代理(基于JDK1.8)