Java 编程问题:三、使用日期和时间

简介: Java 编程问题:三、使用日期和时间

本章包括 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 开始,日期和时间可以通过专用类LocalDateLocalTimejava.time包中获得:

// 2019-02-24
LocalDate onlyDate = LocalDate.now();
// 12:53:28.812637300
LocalTime onlyTime = LocalTime.now();





相关文章
|
20天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
26天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
102 3
|
5天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
19天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
18天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
21天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
27天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
24天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
26天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
47 2
|
12天前
|
Java API 数据库
Java 反射机制:动态编程的 “魔法钥匙”
Java反射机制是允许程序在运行时访问类、方法和字段信息的强大工具,被誉为动态编程的“魔法钥匙”。通过反射,开发者可以创建更加灵活、可扩展的应用程序。
31 0