本章包括 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()方法,可以如下使用:
// 06 is the month, 01 is the day LocalDate localDate = LocalDate.parse("2020-06-01");
类似地,在LocalTime
的情况下,字符串应该遵循DateTimeFormatter.ISO_LOCAL_TIME
模式;例如,10:15:30
,如下面的代码片段所示:
LocalTime localTime = LocalTime.parse("12:23:44");
在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]
,如下代码片段所示:
ZonedDateTime zonedDateTime = ZonedDateTime.parse("2020-06-01T10:15:30+09:00[Asia/Tokyo]");
在OffsetDateTime
的情况下,字符串必须遵循DateTimeFormatter.ISO_OFFSET_DATE_TIME
模式,例如2007-12-03T10:15:30+01:00
,如下代码片段所示:
OffsetDateTime offsetDateTime
最后,在OffsetTime的情况下,字符串必须遵循DateTimeFormatter.ISO_OFFSET_TIME模式,例如10:15:30+01:00,如下代码片段所示:
OffsetTime offsetTime = OffsetTime.parse("10:15:30+01:00");
如果字符串不符合任何预定义的格式化程序,则是时候通过自定义格式模式使用用户定义的格式化程序了;例如,字符串01.06.2020
表示需要用户定义格式化程序的日期,如下所示:
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy"); LocalDate localDateFormatted = LocalDate.parse("01.06.2020", dateFormatter);
但是,像12|23|44
这样的字符串需要如下用户定义的格式化程序:
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH|mm|ss"); LocalTime localTimeFormatted = LocalTime.parse("12|23|44", timeFormatter);
像01.06.2020, 11:20:15
这样的字符串需要一个用户定义的格式化程序,如下所示:
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy, HH:mm:ss"); LocalDateTime localDateTimeFormatted = LocalDateTime.parse("01.06.2020, 11:20:15", dateTimeFormatter);
像01.06.2020, 11:20:15+09:00 [Asia/Tokyo]
这样的字符串需要一个用户定义的格式化程序,如下所示:
DateTimeFormatter zonedDateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy, HH:mm:ssXXXXX '['VV']'"); ZonedDateTime zonedDateTimeFormatted = ZonedDateTime.parse("01.06.2020, 11:20:15+09:00 [Asia/Tokyo]", zonedDateTimeFormatter);
像2007.12.03, 10:15:30, +01:00
这样的字符串需要一个用户定义的格式化程序,如下所示:
DateTimeFormatter offsetDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd, HH:mm:ss, XXXXX"); OffsetDateTime offsetDateTimeFormatted = OffsetDateTime.parse("2007.12.03, 10:15:30, +01:00", offsetDateTimeFormatter);
最后,像10 15 30 +01:00
这样的字符串需要一个用户定义的格式化程序,如下所示:
DateTimeFormatter offsetTimeFormatter = DateTimeFormatter.ofPattern("HH mm ss XXXXX"); OffsetTime offsetTimeFormatted = OffsetTime.parse("10 15 30 +01:00", offsetTimeFormatter);
前面示例中的每个ofPattern()方法也支持Locale。
从LocalDate、LocalDateTime或ZonedDateTime到String的转换至少可以通过两种方式完成:
依赖于LocalDate、LocalDateTime或ZonedDateTime.toString()方法(自动或显式)。请注意,依赖于toString()将始终通过相应的预定义格式化程序打印日期:
// 2020-06-01 results in ISO_LOCAL_DATE, 2020-06-01 String localDateAsString = localDate.toString(); // 01.06.2020 results in ISO_LOCAL_DATE, 2020-06-01 String localDateAsString = localDateFormatted.toString(); // 2020-06-01T11:20:15 results // in ISO_LOCAL_DATE_TIME, 2020-06-01T11:20:15 String localDateTimeAsString = localDateTime.toString(); // 01.06.2020, 11:20:15 results in // ISO_LOCAL_DATE_TIME, 2020-06-01T11:20:15 String localDateTimeAsString = localDateTimeFormatted.toString(); // 2020-06-01T10:15:30+09:00[Asia/Tokyo] // results in ISO_ZONED_DATE_TIME, // 2020-06-01T11:20:15+09:00[Asia/Tokyo] String zonedDateTimeAsString = zonedDateTime.toString(); // 01.06.2020, 11:20:15+09:00 [Asia/Tokyo] // results in ISO_ZONED_DATE_TIME, // 2020-06-01T11:20:15+09:00[Asia/Tokyo] String zonedDateTimeAsString = zonedDateTimeFormatted.toString();
依靠DateTimeFormatter.format()
方法。请注意,依赖于DateTimeFormatter.format()
将始终使用指定的格式化程序打印日期/时间(默认情况下,时区将为null
),如下所示:
// 01.06.2020 String localDateAsFormattedString = dateFormatter.format(localDateFormatted); // 01.06.2020, 11:20:15 String localDateTimeAsFormattedString = dateTimeFormatter.format(localDateTimeFormatted); // 01.06.2020, 11:20:15+09:00 [Asia/Tokyo] String zonedDateTimeAsFormattedString = zonedDateTimeFormatted.format(zonedDateTimeFormatter);
在讨论中添加一个明确的时区可以如下所示:
DateTimeFormatter zonedDateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy, HH:mm:ssXXXXX '['VV']'") .withZone(ZoneId.of("Europe/Paris")); ZonedDateTime zonedDateTimeFormatted = ZonedDateTime.parse("01.06.2020, 11:20:15+09:00 [Asia/Tokyo]", zonedDateTimeFormatter);
这次,字符串表示欧洲/巴黎时区中的日期/时间:
// 01.06.2020, 04:20:15+02:00 [Europe/Paris] String zonedDateTimeAsFormattedString = zonedDateTimeFormatted.format(zonedDateTimeFormatter);
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
应用格式模式:
// yyyy-MM-dd Date date = new Date(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); String stringDate = formatter.format(date);
从 JDK8 开始,可以通过DateTimeFormatter
应用格式模式:
- 对于
LocalDate
(ISO-8601 日历系统中没有时区的日期):
// yyyy-MM-dd LocalDate localDate = LocalDate.now(); DateTimeFormatter formatterLocalDate = DateTimeFormatter.ofPattern("yyyy-MM-dd"); String stringLD = formatterLocalDate.format(localDate); // or shortly String stringLD = LocalDate.now() .format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
对于LocalTime
(ISO-8601 日历系统中没有时区的时间):
// HH:mm:ss LocalTime localTime = LocalTime.now(); DateTimeFormatter formatterLocalTime = DateTimeFormatter.ofPattern("HH:mm:ss"); String stringLT = formatterLocalTime.format(localTime); // or shortly String stringLT = LocalTime.now() .format(DateTimeFormatter.ofPattern("HH:mm:ss"));
对于LocalDateTime
(ISO-8601 日历系统中没有时区的日期时间):
// yyyy-MM-dd HH:mm:ss LocalDateTime localDateTime = LocalDateTime.now(); DateTimeFormatter formatterLocalDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String stringLDT = formatterLocalDateTime.format(localDateTime); // or shortly String stringLDT = LocalDateTime.now() .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
对于ZonedDateTime
(ISO-8601 日历系统中带时区的日期时间):
// E MMM yyyy HH:mm:ss.SSSZ ZonedDateTime zonedDateTime = ZonedDateTime.now(); DateTimeFormatter formatterZonedDateTime = DateTimeFormatter.ofPattern("E MMM yyyy HH:mm:ss.SSSZ"); String stringZDT = formatterZonedDateTime.format(zonedDateTime); // or shortly String stringZDT = ZonedDateTime.now() .format(DateTimeFormatter .ofPattern("E MMM yyyy HH:mm:ss.SSSZ"));
对于OffsetDateTime
(在 ISO-8601 日历系统中,与 UTC/GMT 有偏移的日期时间):
// E MMM yyyy HH:mm:ss.SSSZ OffsetDateTime offsetDateTime = OffsetDateTime.now(); DateTimeFormatter formatterOffsetDateTime = DateTimeFormatter.ofPattern("E MMM yyyy HH:mm:ss.SSSZ"); String odt1 = formatterOffsetDateTime.format(offsetDateTime); // or shortly String odt2 = OffsetDateTime.now() .format(DateTimeFormatter .ofPattern("E MMM yyyy HH:mm:ss.SSSZ"));
对于OffsetTime
(在 ISO-8601 日历系统中与 UTC/GMT 有偏移的时间):
// HH:mm:ss,Z OffsetTime offsetTime = OffsetTime.now(); DateTimeFormatter formatterOffsetTime = DateTimeFormatter.ofPattern("HH:mm:ss,Z"); String ot1 = formatterOffsetTime.format(offsetTime); // or shortly String ot2 = OffsetTime.now() .format(DateTimeFormatter.ofPattern("HH:mm:ss,Z"));
60 获取没有时间/日期的当前日期/时间
在 JDK8 之前,解决方案必须集中在java.util.Date
类上。绑定到本书的代码包含此解决方案。
从 JDK8 开始,日期和时间可以通过专用类LocalDate
和LocalTime
从java.time
包中获得:
// 2019-02-24 LocalDate onlyDate = LocalDate.now(); // 12:53:28.812637300 LocalTime onlyTime = LocalTime.now();