Java 中文官方教程 2022 版(二十八)(2)https://developer.aliyun.com/article/1486867
Duration
一个Duration
在衡量机器时间的情况下最为适用,比如使用Instant
对象的代码。Duration
对象以秒或纳秒为单位进行衡量,不使用年、月和日等基于日期的构造,尽管该类提供了将时间转换为天、小时和分钟的方法。Duration
可以具有负值,如果创建时结束点早于起始点。
以下代码计算两个瞬间之间的持续时间(以纳秒为单位):
Instant t1, t2; ... long ns = Duration.between(t1, t2).toNanos();
以下代码将 10 秒添加到一个Instant
中:
Instant start; ... Duration gap = Duration.ofSeconds(10); Instant later = start.plus(gap);
一个Duration
与时间线无关,即它不跟踪时区或夏令时。将相当于 1 天的Duration
添加到ZonedDateTime
中,结果将确切地添加 24 小时,而不考虑夏令时或其他可能导致的时间差异。
ChronoUnit
讨论 Temporal Package 中的ChronoUnit
枚举定义了用于衡量时间的单位。当您想要以单个时间单位(如天或秒)衡量一段时间时,ChronoUnit.between
方法非常有用。between
方法适用于所有基于时间的对象,但仅以单个单位返回时间量。以下代码计算两个时间戳之间的间隔,以毫秒为单位:
import java.time.Instant; import java.time.temporal.Temporal; import java.time.temporal.ChronoUnit; Instant previous, current, gap; ... current = Instant.now(); if (previous != null) { gap = ChronoUnit.MILLIS.between(previous,current); } ...
Period
要使用基于日期的值(年、月、日)来定义一段时间,请使用Period
类。Period
类提供了各种get
方法,比如getMonths
、getDays
和getYears
,以便您可以从期间中提取时间量。
所有三个单位(月、日和年)一起表示的总时间段。要以单个时间单位(如天)表示测量的时间量,可以使用ChronoUnit.between
方法。
以下代码报告了你的年龄,假设你出生于 1960 年 1 月 1 日。使用Period
类来确定年、月和日的时间。使用ChronoUnit.between
方法确定相同的时间段,以总天数显示在括号中:
LocalDate today = LocalDate.now(); LocalDate birthday = LocalDate.of(1960, Month.JANUARY, 1); Period p = Period.between(birthday, today); long p2 = ChronoUnit.DAYS.between(birthday, today); System.out.println("You are " + p.getYears() + " years, " + p.getMonths() + " months, and " + p.getDays() + " days old. (" + p2 + " days total)");
该代码生成类似以下的输出:
You are 53 years, 4 months, and 29 days old. (19508 days total)
要计算距离你下一个生日还有多长时间,可以使用来自Birthday
示例的以下代码。使用Period
类来确定月和日的值。ChronoUnit.between
方法返回总天数的值,并显示在括号中。
LocalDate birthday = LocalDate.of(1960, Month.JANUARY, 1); LocalDate nextBDay = birthday.withYear(today.getYear()); //If your birthday has occurred this year already, add 1 to the year. if (nextBDay.isBefore(today) || nextBDay.isEqual(today)) { nextBDay = nextBDay.plusYears(1); } Period p = Period.between(today, nextBDay); long p2 = ChronoUnit.DAYS.between(today, nextBDay); System.out.println("There are " + p.getMonths() + " months, and " + p.getDays() + " days until your next birthday. (" + p2 + " total)");
该代码生成类似以下的输出:
There are 7 months, and 2 days until your next birthday. (216 total)
这些计算没有考虑时区差异。例如,如果你出生在澳大利亚,但目前居住在班加罗尔,这会稍微影响你确切年龄的计算。在这种情况下,可以结合使用Period
和ZonedDateTime
类。当你将Period
添加到ZonedDateTime
时,会考虑时差。
时钟
大多数基于时间的对象提供一个无参数的now()
方法,使用系统时钟和默认时区提供当前日期和时间。这些基于时间的对象还提供一个带有一个参数的now(Clock)
方法,允许您传入一个替代的Clock
。
当前日期和时间取决于时区,对于全球化应用程序,需要一个Clock
来确保日期/时间是使用正确的时区创建的。因此,虽然使用Clock
类是可选的,但这个特性允许您测试您的代码是否适用于其他时区,或者通过使用一个固定的时钟,时间不会改变。
Clock
类是抽象的,因此您不能创建它的实例。以下工厂方法对于测试很有用。
Clock.offset(Clock, Duration)
返回一个按指定Duration
偏移的时钟。Clock.systemUTC()
返回代表格林威治/UTC 时区的时钟。Clock.fixed(Instant, ZoneId)
总是返回相同的Instant
。对于这个时钟,时间停滞不前。
非 ISO 日期转换
本教程不会详细讨论java.time.chrono
包。但是,了解到该包提供了几个不基于 ISO 的预定义年表,如日本、伊斯兰、民国和泰国佛教。您还可以使用此包创建自己的年表。
本节将向您展示如何在 ISO 日期和其他预定义年表日期之间进行转换。
转换为非 ISO 日期
您可以使用from(TemporalAccessor)
方法将 ISO 日期转换为其他年表的日期,例如JapaneseDate.from(TemporalAccessor)
。如果无法将日期转换为有效实例,则此方法会抛出DateTimeException
。以下代码将LocalDateTime
实例转换为几个预定义的非 ISO 日历日期:
LocalDateTime date = LocalDateTime.of(2013, Month.JULY, 20, 19, 30); JapaneseDate jdate = JapaneseDate.from(date); HijrahDate hdate = HijrahDate.from(date); MinguoDate mdate = MinguoDate.from(date); ThaiBuddhistDate tdate = ThaiBuddhistDate.from(date);
StringConverter
示例将从LocalDate
转换为ChronoLocalDate
再转换为String
,然后再转回去。toString
方法接受LocalDate
实例和Chronology
,并通过提供的Chronology
返回转换后的字符串。DateTimeFormatterBuilder
用于构建可用于打印日期的字符串:
/** * Converts a LocalDate (ISO) value to a ChronoLocalDate date * using the provided Chronology, and then formats the * ChronoLocalDate to a String using a DateTimeFormatter with a * SHORT pattern based on the Chronology and the current Locale. * * @param localDate - the ISO date to convert and format. * @param chrono - an optional Chronology. If null, then IsoChronology is used. */ public static String toString(LocalDate localDate, Chronology chrono) { if (localDate != null) { Locale locale = Locale.getDefault(Locale.Category.FORMAT); ChronoLocalDate cDate; if (chrono == null) { chrono = IsoChronology.INSTANCE; } try { cDate = chrono.date(localDate); } catch (DateTimeException ex) { System.err.println(ex); chrono = IsoChronology.INSTANCE; cDate = localDate; } DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT) .withLocale(locale) .withChronology(chrono) .withDecimalStyle(DecimalStyle.of(locale)); String pattern = "M/d/yyyy GGGGG"; return dateFormatter.format(cDate); } else { return ""; } }
当使用以下日期调用预定义年表的方法时:
LocalDate date = LocalDate.of(1996, Month.OCTOBER, 29); System.out.printf("%s%n", StringConverter.toString(date, JapaneseChronology.INSTANCE)); System.out.printf("%s%n", StringConverter.toString(date, MinguoChronology.INSTANCE)); System.out.printf("%s%n", StringConverter.toString(date, ThaiBuddhistChronology.INSTANCE)); System.out.printf("%s%n", StringConverter.toString(date, HijrahChronology.INSTANCE));
输出如下:
10/29/0008 H 10/29/0085 1 10/29/2539 B.E. 6/16/1417 1
转换为基于 ISO 的日期
您可以使用静态LocalDate.from
方法将非 ISO 日期转换为LocalDate
实例,如下例所示:
LocalDate date = LocalDate.from(JapaneseDate.now());
其他基于时间的类也提供此方法,如果日期无法转换,则会抛出DateTimeException
。
来自StringConverter
示例的fromString
方法解析包含非 ISO 日期的String
并返回LocalDate
实例。
/** * Parses a String to a ChronoLocalDate using a DateTimeFormatter * with a short pattern based on the current Locale and the * provided Chronology, then converts this to a LocalDate (ISO) * value. * * @param text - the input date text in the SHORT format expected * for the Chronology and the current Locale. * * @param chrono - an optional Chronology. If null, then IsoChronology * is used. */ public static LocalDate fromString(String text, Chronology chrono) { if (text != null && !text.isEmpty()) { Locale locale = Locale.getDefault(Locale.Category.FORMAT); if (chrono == null) { chrono = IsoChronology.INSTANCE; } String pattern = "M/d/yyyy GGGGG"; DateTimeFormatter df = new DateTimeFormatterBuilder().parseLenient() .appendPattern(pattern) .toFormatter() .withChronology(chrono) .withDecimalStyle(DecimalStyle.of(locale)); TemporalAccessor temporal = df.parse(text); ChronoLocalDate cDate = chrono.date(temporal); return LocalDate.from(cDate); } return null; }
当使用以下字符串调用方法时:
System.out.printf("%s%n", StringConverter.fromString("10/29/0008 H", JapaneseChronology.INSTANCE)); System.out.printf("%s%n", StringConverter.fromString("10/29/0085 1", MinguoChronology.INSTANCE)); System.out.printf("%s%n", StringConverter.fromString("10/29/2539 B.E.", ThaiBuddhistChronology.INSTANCE)); System.out.printf("%s%n", StringConverter.fromString("6/16/1417 1", HijrahChronology.INSTANCE));
打印的字符串应该都能转换回 1996 年 10 月 29 日:
1996-10-29 1996-10-29 1996-10-29 1996-10-29
传统日期时间代码
在 Java SE 8 发布之前,Java 日期和时间机制由 java.util.Date
、java.util.Calendar
和 java.util.TimeZone
类以及它们的子类,如 java.util.GregorianCalendar
提供。这些类有几个缺点,包括:
Calendar
类不是类型安全的。- 由于这些类是可变的,它们不能在多线程应用程序中使用。
- 应用程序代码中常见的错误是由于月份编号不寻常和缺乏类型安全性。
与传统代码的互操作性
也许您有使用 java.util
日期和时间类的传统代码,并且希望在最小更改代码的情况下利用 java.time
功能。
JDK 8 发布中添加了几种允许在 java.util
和 java.time
对象之间进行转换的方法:
Calendar.toInstant()
将Calendar
对象转换为Instant
。GregorianCalendar.toZonedDateTime()
将GregorianCalendar
实例转换为ZonedDateTime
。GregorianCalendar.from(ZonedDateTime)
使用默认区域设置从ZonedDateTime
实例创建一个GregorianCalendar
对象。Date.from(Instant)
从Instant
创建一个Date
对象。Date.toInstant()
将Date
对象转换为Instant
。TimeZone.toZoneId()
将一个TimeZone
对象转换为ZoneId
。
以下示例将 Calendar
实例转换为 ZonedDateTime
实例。请注意,必须提供一个时区来从 Instant
转换为 ZonedDateTime
:
Calendar now = Calendar.getInstance(); ZonedDateTime zdt = ZonedDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault()));
以下示例展示了 Date
和 Instant
之间的转换:
Instant inst = date.toInstant(); Date newDate = Date.from(inst);
以下示例将从 GregorianCalendar
转换为 ZonedDateTime
,然后从 ZonedDateTime
转换为 GregorianCalendar
。其他基于时间的类是使用 ZonedDateTime
实例创建的:
GregorianCalendar cal = ...; TimeZone tz = cal.getTimeZone(); int tzoffset = cal.get(Calendar.ZONE_OFFSET); ZonedDateTime zdt = cal.toZonedDateTime(); GregorianCalendar newCal = GregorianCalendar.from(zdt); LocalDateTime ldt = zdt.toLocalDateTime(); LocalDate date = zdt.toLocalDate(); LocalTime time = zdt.toLocalTime();
将 java.util 日期和时间功能映射到 java.time
由于在 Java SE 8 发布中完全重新设计了日期和时间的 Java 实现,您不能将一个方法替换为另一个方法。如果您想使用 java.time
包提供的丰富功能,最简单的解决方案是使用前一节中列出的 toInstant
或 toZonedDateTime
方法。但是,如果您不想使用该方法或该方法不满足您的需求,则必须重写您的日期时间代码。
在 概述 页面介绍的表格是评估哪些 java.time
类满足您需求的好地方。
两个 API 之间没有一对一的映射对应关系,但以下表格给出了 java.util
日期和时间类中的哪些功能映射到 java.time
API 的一般想法。
java.util 功能 | java.time 功能 | 注释 |
| java.util.Date
| java.time.Instant
| Instant
和 Date
类相似。每个类:- 表示时间线上的瞬时时间点(UTC)
- 持有独立于时区的时间
- 表示为自 1970-01-01T00:00:00Z 以来的纪元秒数加上纳秒
Date.from(Instant)
和 Date.toInstant()
方法允许在这些类之间进行转换。 |
| java.util.GregorianCalendar
| java.time.ZonedDateTime
| ZonedDateTime
类是 GregorianCalendar
的替代品。它提供以下类似功能。人类时间表示如下:
LocalDate
:年,月,日
LocalTime
:小时,分钟,秒,纳秒
ZoneId
:时区
ZoneOffset
:与 GMT 的当前偏移量
GregorianCalendar.from(ZonedDateTime)
和 GregorianCalendar.to(ZonedDateTime)
方法促进这些类之间的转换。
java.util.TimeZone |
java.time.ZoneId 或 java.time.ZoneOffset |
ZoneId 类指定时区标识符,并访问每个时区使用的规则。ZoneOffset 类仅指定与格林威治/UTC 的偏移量。更多信息,请参阅时区和偏移类。 |
将日期设置为 1970-01-01 的 GregorianCalendar |
java.time.LocalTime |
将日期设置为 1970-01-01 的 GregorianCalendar 实例中的代码以使用时间组件可以替换为 LocalTime 实例。 |
GregorianCalendar 时间设置为 00:00. |
java.time.LocalDate |
将时间设置为 GregorianCalendar 实例中的 00:00 以使用日期组件的代码可以替换为 LocalDate 实例。(这种 GregorianCalendar 方法存在缺陷,因为由于转换到夏令时,有些国家一年中不会发生午夜。) |
日期和时间格式化
尽管java.time.format.DateTimeFormatter
提供了一个强大的机制来格式化日期和时间值,但你也可以直接使用java.time
基于时间的类与java.util.Formatter
和String.format
一起使用,使用与java.util
日期和时间类相同的基于模式的格式化。
摘要
原文:
docs.oracle.com/javase/tutorial/datetime/iso/summary.html
java.time
包含许多类,您的程序可以使用这些类来表示时间和日期。这是一个非常丰富的 API。基于 ISO 的日期的关键入口点如下:
Instant
类提供了时间线的机器视图。LocalDate
、LocalTime
和LocalDateTime
类提供了日期和时间的人类视图,没有任何关于时区的参考。ZoneId
、ZoneRules
和ZoneOffset
类描述时区、时区偏移和时区规则。ZonedDateTime
类表示带有时区的日期和时间。OffsetDateTime
和OffsetTime
类分别表示日期和时间,或仅时间。这些类考虑了时区偏移。Duration
类以秒和纳秒来衡量时间量。Period
类使用年、月和日来衡量时间量。
其他非 ISO 日历系统可以使用 java.time.chrono
包来表示。尽管本教程不涵盖此包,但 非 ISO 日期转换 页面提供了关于将基于 ISO 的日期转换为其他日历系统的信息。
日期时间 API 是作为 Java 社区流程的一部分开发的,其编号为 JSR 310。更多信息,请参阅 JSR 310: 日期和时间 API。
问题和练习:日期时间 API
原文:
docs.oracle.com/javase/tutorial/datetime/iso/QandE/questions.html
问题
1. 你会使用哪个类来存储你的生日,包括年、月、日、秒和纳秒?
2. 给定一个随机日期,如何找到前一个星期四的日期?
3. ZoneId
和ZoneOffset
之间有什么区别?
4. 如何将Instant
转换为ZonedDateTime
?如何将ZonedDateTime
转换为Instant
?
练习
1. 为给定年份编写一个示例,报告该年份内每个月的长度。
2. 为当前年份的给定月份编写一个示例,列出该月份内所有的星期一。
3. 编写一个示例,测试给定日期是否是一个星期五的 13 日。
检查你的答案。
Trail: 国际化
本教程中的课程将教您如何国际化 Java 应用程序。国际化的应用程序易于根据全球用户的习俗和语言进行定制。
注意: 本教程涵盖了核心国际化功能,这是桌面、企业和移动应用程序提供的附加功能所需的基础。有关更多信息,请参阅Java 国际化主页。
定义了国际化这一术语,提供了一个快速的示例程序,并提供了一个检查表,您可以用来国际化现有程序。
解释了如何创建和使用Locale
对象。
展示了如何动态访问随Locale
变化的对象。
解释了如何根据Locale
格式化数字、日期和文本消息,以及如何使用模式创建自定义格式。
提供了在与区域设置无关的方式下操作文本的技术。
解释了如何为 IDN 提供国际化支持。
解释了如何启用依赖于区域设置的数据和服务的插件。
教训:介绍
国际化 是设计应用程序的过程,使其能够在不进行工程更改的情况下适应各种语言和地区。有时国际化一词被缩写为 i18n,因为在第一个 “i” 和最后一个 “n” 之间有 18 个字母。
一个国际化的程序具有以下特点:
- 通过添加本地化数据,同一个可执行文件可以在全球范围内运行。
- 文本元素,比如状态消息和 GUI 组件标签,不是硬编码在程序中的。而是存储在源代码之外,并动态检索。
- 支持新语言不需要重新编译。
- 依赖文化的数据,比如日期和货币,以符合最终用户的地区和语言的格式出现。
- 它可以快速本地化。
本地化 是通过添加特定区域或语言的区域特定组件和翻译文本来使软件适应特定区域或语言的过程。本地化一词通常缩写为 l10n,因为在 “l” 和 “n” 之间有 10 个字母。
本地化的主要任务是翻译用户界面元素和文档。本地化不仅涉及改变语言交互,还涉及其他相关变化,比如数字、日期、货币的显示等。如果音频和图像等其他类型的数据在文化上敏感,可能需要本地化。应用程序国际化得越好,为特定语言和字符编码方案本地化就越容易。
一开始国际化可能看起来有点令人生畏。阅读以下章节将有助于您逐渐了解这个主题。
一个快速示例
本节向您展示如何逐步国际化一个简单的程序。
清单
所以你继承了一个需要国际化的程序,或者你正在计划确定新开发软件的需求。你可能不知道从哪里开始?查看这个清单。它总结了必要的国际化任务,并提供了本章相关课程的链接。
一个快速示例
如果你是国际化软件的新手,这节课适合你。这节课使用一个简单的示例来演示如何国际化程序,以便以适当的语言显示文本消息。你将学习如何Locale
和ResourceBundle
对象一起工作,以及如何使用属性文件。
国际化前
源代码的第一个版本包含了硬编码的英文消息版本,这不是编写国际化软件的正确方式。
国际化后
这是我们的源代码在国际化后的 sneak preview。
运行示例程序
要运行示例程序,你需要在命令行上指定语言和国家。本节将向你展示一些示例。
国际化示例程序
国际化程序只需要几个步骤。你会惊讶于它是多么容易。
Java 中文官方教程 2022 版(二十八)(4)https://developer.aliyun.com/article/1486877