本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《 阿里云开发者社区用户服务协议 》和 《 阿里云开发者社区知识产权保护指引 》。如果您发现本社区中有涉嫌抄袭的内容,填写 侵权投诉表单 进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

本章包括 20 个涉及日期和时间的问题。这些问题通过Date、Calendar、LocalDate、LocalTime、LocalDateTime、ZoneDateTime、OffsetDateTime、OffsetTime、Instant等涵盖了广泛的主题(转换、格式化、加减、定义时段/持续时间、计算等)。到本章结束时,您将在确定日期和时间方面没有问题,同时符合您的应用的需要。本章介绍的基本问题将非常有助于了解日期-时间 API 的整体情况,并将像拼图中需要拼凑起来的部分一样解决涉及日期和时间的复杂挑战。




问题


使用以下问题来测试您的日期和时间编程能力。我强烈建议您在使用解决方案和下载示例程序之前,先尝试一下每个问题:


将字符串转换为日期和时间:编写一个程序,演示字符串和日期/时间之间的转换。


格式化日期和时间:**解释日期和时间的格式模式。


获取当前日期/时间(不含日期/时间):编写程序,提取当前日期(不含时间或日期)。


从LocalDate和LocalTime到LocalDateTime:编写一个程序,从LocalDate对象和LocalTime构建一个LocalDateTime。它将日期和时间组合在一个LocalDateTime对象中。


通过Instant类获取机器时间:解释并举例说明InstantAPI。


定义使用基于日期的值的时间段(Period)和使用基于时间的值的时间段(Duration):解释并举例说明Period和DurationAPI 的用法。


获取日期和时间单位:编写一个程序,从表示日期时间的对象中提取日期和时间单位(例如,从日期中提取年、月、分钟等)。


对日期时间的加减:编写一个程序,对日期时间对象加减一定的时间(如年、日、分等)(如对日期加 1 小时,对LocalDateTime减 2 天等)。


获取 UTC 和 GMT 的所有时区:编写一个程序,显示 UTC 和 GMT 的所有可用时区。


获取所有可用时区的本地日期时间:编写一个程序,显示所有可用时区的本地时间。68. 显示航班日期时间信息:编写程序,显示 15 小时 30 分钟的航班时刻信息。更确切地说,是从澳大利亚珀斯飞往欧洲布加勒斯特的航班。


将 Unix 时间戳转换为日期时间:编写将 Unix 时间戳转换为java.util.Date和java.time.LocalDateTime的程序。


查找月份的第一天/最后一天:编写一个程序,通过 JDK8,TemporalAdjusters查找月份的第一天/最后一天。


定义/提取区域偏移:编写一个程序,展示定义和提取区域偏移的不同技术。


Date与Temporal之间的转换:编写Date与Instant、LocalDate、LocalDateTime等之间的转换程序。


迭代一系列日期:编写一个程序,逐日(以一天的步长)迭代一系列给定日期。


计算年龄:编写一个计算一个人年龄的程序。


一天的开始和结束:编写一个程序,返回一天的开始和结束时间。


两个日期之间的差异:编写一个程序,计算两个日期之间的时间量(以天为单位)。


实现象棋时钟:编写实现象棋时钟的程序。



以下各节介绍上述问题的解决方案。记住,通常没有一个正确的方法来解决一个特定的问题。另外,请记住,这里显示的解释仅包括解决问题所需的最有趣和最重要的细节。下载示例解决方案以查看更多详细信息,并在 这个页面 中试用程序。



58 将字符串转换为日期和时间

将String转换或解析为日期和时间可以通过一组parse()方法来完成。从日期和时间到String的转换可以通过toString()或format()方法完成。



JDK8 之前

在 JDK8 之前,这个问题的典型解决方案依赖于抽象的DateFormat类的主扩展,名为SimpleDateFormat(这不是线程安全类)。在本书附带的代码中,有几个示例说明了如何使用此类。



从 JDK8 开始


从 JDK8 开始,SimpleDateFormat可以替换为一个新类—DateTimeFormatter。这是一个不可变(因此是线程安全的)类,用于打印和解析日期时间对象。这个类支持从预定义的格式化程序(表示为常量,如 ISO 本地时间2011-12-03,是ISO_LOCAL_DATE)到用户定义的格式化程序(依赖于一组用于编写自定义格式模式的符号)。


此外,除了Date类之外,JDK8 还提供了几个新类,它们专门用于处理日期和时间。其中一些类显示在下面的列表中(这些类也被称为临时类,因为它们实现了Temporal接口):


LocalDate(ISO-8601 日历系统中没有时区的日期)

LocalTime(ISO-8601 日历系统中无时区的时间)

LocalDateTime(ISO-8601 日历系统中无时区的日期时间)

ZonedDateTime(ISO-8601 日历系统中带时区的日期时间),依此类推

OffsetDateTime(在 ISO-8601 日历系统中,有 UTC/GMT 偏移的日期时间)

OffsetTime(在 ISO-8601 日历系统中与 UTC/GMT 有偏移的时间)


为了通过预定义的格式化程序将String转换为LocalDate,它应该遵循DateTimeFormatter.ISO_LOCAL_DATE模式,例如2020-06-01。LocalDate提供了一种parse()方法,可以如下使用:


类似地,在 LocalTime 的情况下,字符串应该遵循 DateTimeFormatter.ISO_LOCAL_TIME 模式;例如, 10:15:30 ,如下面的代码片段所示:


LocalDateTime 的情况下,字符串应该遵循 DateTimeFormatter.ISO_LOCAL_DATE_TIME 模式,例如 2020-06-01T11:20:15 ,如下代码片段所示:

LocalDateTime localDateTime 
  = LocalDateTime.parse("2020-06-01T11:20:15");


ZonedDateTime 的情况下,字符串必须遵循 DateTimeFormatter.ISO_ZONED_DATE_TIME 模式,例如 2020-06-01T10:15:30+09:00[Asia/Tokyo] ,如下代码片段所示:


OffsetDateTime 的情况下,字符串必须遵循 DateTimeFormatter.ISO_OFFSET_DATE_TIME 模式,例如 2007-12-03T10:15:30+01:00 ,如下代码片段所示:


最后,在OffsetTime的情况下,字符串必须遵循DateTimeFormatter.ISO_OFFSET_TIME模式,例如10:15:30+01:00,如下代码片段所示:


如果字符串不符合任何预定义的格式化程序,则是时候通过自定义格式模式使用用户定义的格式化程序了;例如,字符串 01.06.2020 表示需要用户定义格式化程序的日期,如下所示:


但是,像 12|23|44 这样的字符串需要如下用户定义的格式化程序:


01.06.2020, 11:20:15 这样的字符串需要一个用户定义的格式化程序,如下所示:


01.06.2020, 11:20:15+09:00 [Asia/Tokyo] 这样的字符串需要一个用户定义的格式化程序,如下所示:


2007.12.03, 10:15:30, +01:00 这样的字符串需要一个用户定义的格式化程序,如下所示:


最后,像 10 15 30 +01:00 这样的字符串需要一个用户定义的格式化程序,如下所示:



前面示例中的每个ofPattern()方法也支持Locale。


从LocalDate、LocalDateTime或ZonedDateTime到String的转换至少可以通过两种方式完成:

依赖于LocalDate、LocalDateTime或ZonedDateTime.toString()方法(自动或显式)。请注意,依赖于toString()将始终通过相应的预定义格式化程序打印日期:



依靠 DateTimeFormatter.format() 方法。请注意,依赖于 DateTimeFormatter.format() 将始终使用指定的格式化程序打印日期/时间(默认情况下,时区将为 null ),如下所示:


在讨论中添加一个明确的时区可以如下所示:


这次,字符串表示欧洲/巴黎时区中的日期/时间:




59 格式化日期和时间


前面的问题包含一些通过SimpleDateFormat.format()和DateTimeFormatter.format()格式化日期和时间的风格。为了定义格式模式,开发人员必须了解格式模式语法。换句话说,开发人员必须知道 Java 日期时间 API 使用的一组符号,以便识别有效的格式模式。


大多数符号与 SimpleDateFormat (JDK8 之前)和 DateTimeFormatter (从 JDK8 开始)通用。下表列出了 JDK 文档中提供的最常见符号的完整列表:


字母 含义 演示 示例
y 1994; 94
M 数字/文本 7; 07; Jul; July; J
W 每月的一周 数字 4
E 星期几 文本 Tue; Tuesday; T
d 日期 数字 15
H 小时 数字 22
m 分钟 数字 34
s 数字 55
S 秒的分数 数字 345
z 时区名称 时区名称 Pacific Standard Time; PST
Z 时区偏移 时区偏移 -0800
V 时区 ID(JDK8) 时区 ID America/Los_Angeles; Z; -08:30




下表提供了一些格式模式示例:

模式 示例
yyyy-MM-dd 2019-02-24
MM-dd-yyyy 02-24-2019
MMM-dd-yyyy Feb-24-2019
dd-MM-yy 24-02-19
dd.MM.yyyy 24.02.2019
yyyy-MM-dd HH:mm:ss 2019-02-24 11:26:26
yyyy-MM-dd HH:mm:ssSSS 2019-02-24 11:36:32743
yyyy-MM-dd HH:mm:ssZ 2019-02-24 11:40:35+0200
yyyy-MM-dd HH:mm:ss z 2019-02-24 11:45:03 EET
E MMM yyyy HH:mm:ss.SSSZ Sun Feb 2019 11:46:32.393+0200
yyyy-MM-dd HH:MM:ss VV (JDK8) 2019-02-24 11:45:41 Europe/Athens



在 JDK8 之前,可以通过 SimpleDateFormat 应用格式模式:


从 JDK8 开始,可以通过 DateTimeFormatter 应用格式模式:


对于 LocalTime (ISO-8601 日历系统中没有时区的时间):


对于 LocalDateTime (ISO-8601 日历系统中没有时区的日期时间):


对于 ZonedDateTime (ISO-8601 日历系统中带时区的日期时间):


对于 OffsetDateTime (在 ISO-8601 日历系统中,与 UTC/GMT 有偏移的日期时间):


对于 OffsetTime (在 ISO-8601 日历系统中与 UTC/GMT 有偏移的时间):




60 获取没有时间/日期的当前日期/时间


在 JDK8 之前,解决方案必须集中在 java.util.Date 类上。绑定到本书的代码包含此解决方案。


从 JDK8 开始,日期和时间可以通过专用类 LocalDate LocalTime java.time 包中获得: