Java 中文官方教程 2022 版(二十八)
方法命名约定
原文:
docs.oracle.com/javase/tutorial/datetime/overview/naming.html
日期时间 API 提供了丰富的方法集合,涵盖了丰富的类。在可能的情况下,方法名称在类之间保持一致。例如,许多类提供了一个now
方法,用于捕获与该类相关的当前时刻的日期或时间值。还有from
方法允许从一个类转换为另一个类。
方法名称前缀也有标准化。由于日期时间 API 中的大多数类都是不可变的,API 不包括set
方法。(创建后,不可变对象的值不能更改。set
方法的不可变等效方法是with
。)以下表列出了常用的前缀:
前缀 | 方法类型 | 用途 |
---|---|---|
of |
静态工厂 | 创建一个实例,其中工厂主要验证输入参数,而不是转换它们。 |
from |
静态工厂 | 将输入参数转换为目标类的实例,这可能涉及从输入中丢失信息。 |
parse |
静态工厂 | 解析输入字符串以生成目标类的实例。 |
format |
实例 | 使用指定的格式化程序格式化时间对象中的值以生成字符串。 |
get |
实例 | 返回目标对象的状态的一部分。 |
is |
实例 | 查询目标对象的状态。 |
with |
实例 | 返回目标对象的副本,其中一个元素已更改;这相当于 JavaBean 上的set 方法的不可变版本。 |
plus |
实例 | 返回目标对象的副本,其中添加了一定数量的时间。 |
minus |
实例 | 返回目标对象的副本,其中减去了一定数量的时间。 |
to |
实例 | 将此对象转换为另一种类型。 |
at |
实例 | 将此对象与另一个对象组合。 |
课程:标准日历
日期时间 API 的核心是java.time包。java.time
中定义的类基于 ISO 日历系统,这是代表日期和时间的世界标准。ISO 日历遵循普罗列普提克格里高利规则。格里高利日历于 1582 年引入;在普罗列普提克格里高利日历中,日期从那时开始向前延伸,以创建一致的、统一的时间线,并简化日期计算。
本课程涵盖以下主题:
概述
这一部分比较了人类时间和机器时间的概念,并提供了java.time
包中主要基于时间的类的表格。
星期几和月份枚举
这一部分讨论了定义星期几(DayOfWeek
)和定义月份(Month
)的枚举。
日期类
这一部分展示了仅处理日期而不涉及时间或时区的基于时间的类。这四个类分别是LocalDate
、YearMonth
、MonthDay
和Year
。
日期和时间类
这一部分介绍了处理时间和日期时间的LocalTime
和LocalDateTime
类,但不涉及时区。
时区和偏移类
这一部分讨论了存储时区(或时区偏移)信息的基于时间的类,ZonedDateTime
、OffsetDateTime
和OffsetTime
。还讨论了支持类ZoneId
、ZoneRules
和ZoneOffset
。
Instant 类
这一部分讨论了Instant
类,它表示时间轴上的瞬时时刻。
解析和格式化
这一部分概述了如何使用预定义的格式化程序来格式化和解析日期和时间值。
时间包
这一部分概述了java.time.temporal
包,支持时间类、字段(TemporalField
和ChronoField
)和单位(TemporalUnit
和ChronoUnit
)。这一部分还解释了如何使用时间调整器来检索调整后的时间值,比如“4 月 11 日后的第一个星期二”,以及如何执行时间查询。
周期和持续时间
这一部分解释了如何计算一段时间,使用了Period
和Duration
类,以及ChronoUnit.between
方法。
时钟
这一部分简要介绍了Clock
类。您可以使用这个类来提供一个替代时钟来替代系统时钟。
非 ISO 日期转换
这一部分解释了如何将 ISO 日历系统中的日期转换为非 ISO 日历系统中的日期,比如JapaneseDate
或ThaiBuddhistDate
。
遗留日期时间代码
本节提供了一些关于如何将旧的java.util.Date
和java.util.Calendar
代码转换为日期时间 API 的技巧。
摘要
本节提供了标准日历课程的摘要。
概述
原文:
docs.oracle.com/javase/tutorial/datetime/iso/overview.html
有两种基本表示时间的方式。一种方式以人类术语表示时间,称为人类时间,例如年、月、日、小时、分钟和秒。另一种方式,机器时间,连续测量时间沿着时间线从一个起点,称为纪元,以纳秒分辨率。日期时间包提供了丰富的类来表示日期和时间。日期时间 API 中的一些类旨在表示机器时间,而另一些更适合表示人类时间。
首先确定您需要日期和时间的哪些方面,然后选择满足这些需求的类或类。在选择基于时间的类时,首先确定您需要表示人类时间还是机器时间。然后确定您需要表示时间的哪些方面。您需要时区吗?日期和时间?仅日期?如果您需要日期,您需要月、日、和年,还是一个子集?
术语: 在日期时间 API 中捕获和处理日期或时间值的类,如Instant
、LocalDateTime
和ZonedDateTime
,在本教程中统称为基于时间的类(或类型)。不包括支持类型,如TemporalAdjuster
接口或DayOfWeek
枚举。
例如,您可能会使用LocalDate
对象来表示出生日期,因为大多数人都在同一天庆祝生日,无论他们是在出生城市还是在国际日期变更线的另一边的地球上。如果您正在跟踪占星时间,那么您可能希望使用LocalDateTime
对象来表示出生日期和时间,或者ZonedDateTime
,它还包括时区。如果您正在创建时间戳,那么您很可能会使用Instant
,它允许您比较时间线上的一个瞬时点和另一个。
以下表格总结了java.time
包中存储日期和/或时间信息的基于时间的类,或者可用于测量时间量的类。列中的勾号表示该类使用该特定类型的数据,toString 输出列显示使用toString
方法打印的实例。讨论位置列链接到教程中相关页面。
类或枚举 | 年 | 月 | 日 | 小时 | 分钟 | 秒* | 时区偏移 | 时区 ID | toString 输出 | 讨论位置 |
---|---|---|---|---|---|---|---|---|---|---|
Instant |
![]() |
2013-08-20T15:16:26.355Z |
Instant 类 | |||||||
LocalDate |
![]() |
![]() |
![]() |
2013-08-20 |
日期类 | |||||
LocalDateTime |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
2013-08-20T08:16:26.937 |
日期和时间类 | ||
ZonedDateTime |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
2013-08-21T00:16:26.941+09:00[亚洲/东京] |
时区和偏移类 |
LocalTime |
![]() |
![]() |
![]() |
08:16:26.943 |
日期和时间类 | |||||
MonthDay |
![]() |
![]() |
--08-20 |
日期类 | ||||||
Year |
![]() |
2013 |
日期类 | |||||||
YearMonth |
![]() |
![]() |
2013-08 |
日期类 | ||||||
Month |
![]() |
八月 |
星期几和月份枚举 | |||||||
OffsetDateTime |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
2013-08-20T08:16:26.954-07:00 |
时区和偏移类 | |
OffsetTime |
![]() |
![]() |
![]() |
![]() |
08:16:26.957-07:00 |
时区和偏移类 | ||||
Duration |
** | ** | ** | ![]() |
PT20H (20 小时) |
周期和持续时间 | ||||
Period |
![]() |
![]() |
![]() |
*** | *** | P10D (10 天) |
周期和持续时间 | |||
* | 秒被捕捉到纳秒精度。 | |||||||||
** | 这个类不存储这些信息,但有方法提供这些单位的时间。 | |||||||||
*** | 当Period 添加到ZonedDateTime 时,会考虑夏令时或其他本地时间差异。 |
DayOfWeek 和 Month 枚举
日期时间 API 提供了用于指定星期几和每年月份的枚举。
DayOfWeek
DayOfWeek
枚举由描述一周中每天的七个常量组成:MONDAY
到SUNDAY
。DayOfWeek
常量的整数值范围从 1(星期一)到 7(星期日)。使用定义的常量(DayOfWeek.FRIDAY
)可以使您的代码更易读。
这个枚举还提供了许多方法,类似于时间为基础的类提供的方法。例如,以下代码将“Monday”加 3 天并打印结果。输出为“THURSDAY”:
System.out.printf("%s%n", DayOfWeek.MONDAY.plus(3));
使用getDisplayName(TextStyle, Locale)
方法,您可以检索一个字符串来标识用户所在地区的星期几。TextStyle
枚举允许您指定要显示的字符串类型:FULL
、NARROW
(通常是一个字母)或SHORT
(缩写)。STANDALONE
的TextStyle
常量在某些语言中使用,当作为日期的一部分时输出不同于单独使用时的输出。以下示例打印了“Monday”三种主要形式的TextStyle
:
DayOfWeek dow = DayOfWeek.MONDAY;
Locale locale = Locale.getDefault();
System.out.println(dow.getDisplayName(TextStyle.FULL, locale));
System.out.println(dow.getDisplayName(TextStyle.NARROW, locale));
System.out.println(dow.getDisplayName(TextStyle.SHORT, locale));
此代码在en
区域设置下的输出如下:
Monday
M
Mon
月
Month
枚举包括了十二个月的常量,从JANUARY
到DECEMBER
。与DayOfWeek
枚举一样,Month
枚举是强类型的,每个常量的整数值对应 ISO 范围从 1(一月)到 12(十二月)。使用定义的常量(Month.SEPTEMBER
)可以使您的代码更易读。
Month
枚举还包括许多方法。以下代码使用maxLength
方法打印二月份最大可能的天数。输出为“29”:
System.out.printf("%d%n", Month.FEBRUARY.maxLength());
Month
枚举还实现了getDisplayName(TextStyle, Locale)
方法,使用指定的TextStyle
检索一个字符串来标识用户所在地区的月份。如果特定的TextStyle
未定义,则返回表示常量数值的字符串。以下代码使用三种主要文本样式打印八月份的月份:
Month month = Month.AUGUST;
Locale locale = Locale.getDefault();
System.out.println(month.getDisplayName(TextStyle.FULL, locale));
System.out.println(month.getDisplayName(TextStyle.NARROW, locale));
System.out.println(month.getDisplayName(TextStyle.SHORT, locale));
此代码在en
区域设置下的输出如下:
August
A
Aug
日期类
日期时间 API 提供了四个专门处理日期信息的类,不考虑时间或时区。这些类的使用建议由类名决定:LocalDate
、YearMonth
、MonthDay
和Year
。
LocalDate
LocalDate
表示 ISO 日历中的年-月-日,用于表示没有时间的日期。您可以使用LocalDate
来跟踪重要事件,如出生日期或结婚日期。以下示例使用of
和with
方法创建LocalDate
实例:
LocalDate date = LocalDate.of(2000, Month.NOVEMBER, 20);
LocalDate nextWed = date.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY));
有关TemporalAdjuster
接口的更多信息,请参见时间调整器。
除了通常的方法外,LocalDate
类还提供了用于获取有关给定日期信息的 getter 方法。getDayOfWeek
方法返回特定日期所在的星期几。例如,以下代码返回"MONDAY":
DayOfWeek dotw = LocalDate.of(2012, Month.JULY, 9).getDayOfWeek();
以下示例使用TemporalAdjuster
来获取特定日期后的第一个星期三。
LocalDate date = LocalDate.of(2000, Month.NOVEMBER, 20);
TemporalAdjuster adj = TemporalAdjusters.next(DayOfWeek.WEDNESDAY);
LocalDate nextWed = date.with(adj);
System.out.printf("For the date of %s, the next Wednesday is %s.%n",
date, nextWed);
运行代码会产生以下结果:
For the date of 2000-11-20, the next Wednesday is 2000-11-22.
Period and Duration 部分还有使用LocalDate
类的示例。
YearMonth
YearMonth
类表示特定年份的月份。以下示例使用YearMonth.lengthOfMonth()
方法来确定几个年份和月份组合的天数。
YearMonth date = YearMonth.now();
System.out.printf("%s: %d%n", date, date.lengthOfMonth());
YearMonth date2 = YearMonth.of(2010, Month.FEBRUARY);
System.out.printf("%s: %d%n", date2, date2.lengthOfMonth());
YearMonth date3 = YearMonth.of(2012, Month.FEBRUARY);
System.out.printf("%s: %d%n", date3, date3.lengthOfMonth());
此代码的输出如下:
2013-06: 30
2010-02: 28
2012-02: 29
MonthDay
MonthDay
类表示特定月份的某一天,例如 1 月 1 日的元旦。
以下示例使用MonthDay.isValidYear
方法来确定 2010 年 2 月 29 日是否有效。调用返回false
,确认 2010 年不是闰年。
MonthDay date = MonthDay.of(Month.FEBRUARY, 29);
boolean validLeapYear = date.isValidYear(2010);
Year
Year
类表示一年。以下示例使用Year.isLeap
方法来确定给定年份是否是闰年。调用返回true
,确认 2012 年是闰年。
boolean validLeapYear = Year.of(2012).isLeap();
日期和时间类
原文:
docs.oracle.com/javase/tutorial/datetime/iso/datetime.html
LocalTime
LocalTime
类类似于其他以Local
为前缀的类,但仅处理时间。这个类非常适用于表示人类时间,比如电影时间,或者当地图书馆的开放和关闭时间。它也可以用来创建数字时钟,就像下面的例子所示:
LocalTime thisSec;
for (;;) {
thisSec = LocalTime.now();
// implementation of display code is left to the reader
display(thisSec.getHour(), thisSec.getMinute(), thisSec.getSecond());
}
LocalTime
类不存储时区或夏令时信息。
LocalDateTime
处理日期和时间,不带时区的类是LocalDateTime
,是日期时间 API 的核心类之一。这个类用于表示日期(月-日-年)和时间(时-分-秒-纳秒),实际上是LocalDate
和LocalTime
的组合。这个类可以用来表示特定事件,比如 2013 年 8 月 17 日下午 1:10 开始的美洲杯挑战者系列赛首场路易威登杯决赛。请注意,这意味着当地时间下午 1:10。要包含时区,必须使用ZonedDateTime
或OffsetDateTime
,如时区和偏移类中所讨论的。
除了每个基于时间的类都提供的now
方法外,LocalDateTime
类还有各种of
方法(或以of
为前缀的方法)来创建LocalDateTime
的实例。还有一个from
方法,用于将另一种时间格式的实例转换为LocalDateTime
实例。还有用于添加或减去小时、分钟、天、周和月份的方法。下面的例子展示了其中一些方法。日期时间表达式用粗体表示:
System.out.printf("now: %s%n", LocalDateTime.now());
System.out.printf("Apr 15, 1994 @ 11:30am: %s%n",
LocalDateTime.of(1994, Month.APRIL, 15, 11, 30));
System.out.printf("now (from Instant): %s%n",
LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()));
System.out.printf("6 months from now: %s%n",
LocalDateTime.now().plusMonths(6));
System.out.printf("6 months ago: %s%n",
LocalDateTime.now().minusMonths(6));
这段代码产生的输出看起来类似于以下内容:
now: 2013-07-24T17:13:59.985
Apr 15, 1994 @ 11:30am: 1994-04-15T11:30
now (from Instant): 2013-07-24T17:14:00.479
6 months from now: 2014-01-24T17:14:00.480
6 months ago: 2013-01-24T17:14:00.481
时区和偏移类
时区是地球上使用相同标准时间的区域。每个时区由一个标识符描述,通常具有region/city(Asia/Tokyo
)的格式和与格林威治/协调世界时的偏移。例如,东京的偏移是+09:00
。
ZoneId 和 ZoneOffset
日期时间 API 提供了两个用于指定时区或偏移的类:
-
ZoneId
指定了一个时区标识符,并提供了在Instant
和LocalDateTime
之间转换的规则。 -
ZoneOffset
指定了与格林威治/协调世界时的时区偏移。
从格林威治/协调世界时的偏移通常以整小时定义,但也有例外情况。以下代码来自TimeZoneId
示例,打印了所有使用非整小时定义的格林威治/协调世界时偏移的时区列表。
Set<String> allZones = ZoneId.getAvailableZoneIds();
LocalDateTime dt = LocalDateTime.now();
// Create a List using the set of zones and sort it.
List<String> zoneList = new ArrayList<String>(allZones);
Collections.sort(zoneList);
...
for (String s : zoneList) {
ZoneId zone = ZoneId.of(s);
ZonedDateTime zdt = dt.atZone(zone);
ZoneOffset offset = zdt.getOffset();
int secondsOfHour = offset.getTotalSeconds() % (60 * 60);
String out = String.format("%35s %10s%n", zone, offset);
// Write only time zones that do not have a whole hour offset
// to standard out.
if (secondsOfHour != 0) {
System.out.printf(out);
}
...
}
此示例将以下列表打印到标准输出:
America/Caracas -04:30
America/St_Johns -02:30
Asia/Calcutta +05:30
Asia/Colombo +05:30
Asia/Kabul +04:30
Asia/Kathmandu +05:45
Asia/Katmandu +05:45
Asia/Kolkata +05:30
Asia/Rangoon +06:30
Asia/Tehran +04:30
Australia/Adelaide +09:30
Australia/Broken_Hill +09:30
Australia/Darwin +09:30
Australia/Eucla +08:45
Australia/LHI +10:30
Australia/Lord_Howe +10:30
Australia/North +09:30
Australia/South +09:30
Australia/Yancowinna +09:30
Canada/Newfoundland -02:30
Indian/Cocos +06:30
Iran +04:30
NZ-CHAT +12:45
Pacific/Chatham +12:45
Pacific/Marquesas -09:30
Pacific/Norfolk +11:30
TimeZoneId
示例还会将所有时区 ID 打印到名为timeZones
的文件中。
日期时间类
日期时间 API 提供了三个基于时间的类,可以与时区一起使用:
-
ZonedDateTime
处理具有与格林威治/协调世界时的相应时区偏移的日期和时间。 -
OffsetDateTime
处理具有与格林威治/协调世界时的相应时区偏移的日期和时间,没有时区 ID。 -
OffsetTime
处理具有与格林威治/协调世界时的相应时区偏移的时间,没有时区 ID。
何时使用OffsetDateTime
而不是ZonedDateTime
?如果您正在编写基于地理位置的自己的日期和时间计算规则的复杂软件,或者如果您正在存储仅跟踪与格林威治/协调世界时的绝对偏移的时间戳的数据库,那么您可能希望使用OffsetDateTime
。此外,XML 和其他网络格式将日期时间传输定义为OffsetDateTime
或OffsetTime
。
尽管所有三个类都保持与格林威治/协调世界时的偏移,但只有ZonedDateTime
使用ZoneRules
,这是java.time.zone
包的一部分,用于确定特定时区的偏移如何变化。例如,大多数时区在将时钟向前调整到夏令时时会经历一个间隙(通常为 1 小时),在将时钟调回标准时间和过渡前最后一个小时重复时会发生时间重叠。ZonedDateTime
类适应了这种情况,而没有访问ZoneRules
的OffsetDateTime
和OffsetTime
类则没有。
ZonedDateTime
ZonedDateTime类实际上将LocalDateTime
类与ZoneId
类结合在一起。它用于表示完整的日期(年、月、日)和时间(时、分、秒、纳秒)以及时区(地区/城市,如Europe/Paris
)。
以下代码来自Flight
示例,定义了从旧金山飞往东京的航班的出发时间为美国/洛杉矶时区的ZonedDateTime
。使用withZoneSameInstant
和plusMinutes
方法创建一个代表东京预计到达时间的ZonedDateTime
实例,经过 650 分钟的飞行。ZoneRules.isDaylightSavings
方法确定航班到达东京时是否为夏令时。
DateTimeFormatter
对象用于格式化ZonedDateTime
实例以进行打印:
DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM d yyyy hh:mm a");
// Leaving from San Francisco on July 20, 2013, at 7:30 p.m.
LocalDateTime leaving = LocalDateTime.of(2013, Month.JULY, 20, 19, 30);
ZoneId leavingZone = ZoneId.of("America/Los_Angeles");
ZonedDateTime departure = ZonedDateTime.of(leaving, leavingZone);
try {
String out1 = departure.format(format);
System.out.printf("LEAVING: %s (%s)%n", out1, leavingZone);
} catch (DateTimeException exc) {
System.out.printf("%s can't be formatted!%n", departure);
throw exc;
}
// Flight is 10 hours and 50 minutes, or 650 minutes
ZoneId arrivingZone = ZoneId.of("Asia/Tokyo");
ZonedDateTime arrival = departure.withZoneSameInstant(arrivingZone)
.plusMinutes(650);
try {
String out2 = arrival.format(format);
System.out.printf("ARRIVING: %s (%s)%n", out2, arrivingZone);
} catch (DateTimeException exc) {
System.out.printf("%s can't be formatted!%n", arrival);
throw exc;
}
if (arrivingZone.getRules().isDaylightSavings(arrival.toInstant()))
System.out.printf(" (%s daylight saving time will be in effect.)%n",
arrivingZone);
else
System.out.printf(" (%s standard time will be in effect.)%n",
arrivingZone);
这将产生以下输出:
LEAVING: Jul 20 2013 07:30 PM (America/Los_Angeles)
ARRIVING: Jul 21 2013 10:20 PM (Asia/Tokyo)
(Asia/Tokyo standard time will be in effect.)
OffsetDateTime
OffsetDateTime类实际上将LocalDateTime
类与ZoneOffset
类结合在一起。它用于表示完整的日期(年、月、日)和时间(时、分、秒、纳秒)以及与格林威治/UTC 时间的偏移(+/-小时:分钟,如+06:00
或-08:00
)。
以下示例使用OffsetDateTime
与TemporalAdjuster.lastDay
方法找到 2013 年 7 月最后一个星期四。
// Find the last Thursday in July 2013.
LocalDateTime localDate = LocalDateTime.of(2013, Month.JULY, 20, 19, 30);
ZoneOffset offset = ZoneOffset.of("-08:00");
OffsetDateTime offsetDate = OffsetDateTime.of(localDate, offset);
OffsetDateTime lastThursday =
offsetDate.with(TemporalAdjusters.lastInMonth(DayOfWeek.THURSDAY));
System.out.printf("The last Thursday in July 2013 is the %sth.%n",
lastThursday.getDayOfMonth());
运行此代码的输出为:
The last Thursday in July 2013 is the 25th.
OffsetTime
OffsetTime类实际上将LocalTime
类与ZoneOffset
类结合在一起。它用于表示时间(时、分、秒、纳秒)以及与格林威治/UTC 时间的偏移(+/-小时:分钟,如+06:00
或-08:00
)。
OffsetTime
类在与OffsetDateTime
类相同的情况下使用,但不需要跟踪日期时使用。
Instant 类
原文:
docs.oracle.com/javase/tutorial/datetime/iso/instant.html
日期时间 API 的核心类之一是Instant
类,它表示时间轴上纳秒的开始。此类对于生成时间戳以表示机器时间很有用。
import java.time.Instant;
Instant timestamp = Instant.now();
从Instant
类返回的值从 1970 年 1 月 1 日的第一秒开始计时(1970-01-01T00:00:00Z
),也称为EPOCH
。在纪元之前发生的瞬间具有负值,在纪元之后发生的瞬间具有正值。
Instant
类提供的其他常量是MIN
,表示最小可能的(遥远的过去)瞬间,以及MAX
,表示最大(遥远的未来)瞬间。
调用Instant
上的toString
会产生以下输出:
2013-05-30T23:38:23.085Z
此格式遵循ISO-8601用于表示日期和时间的标准。
Instant
类提供了各种方法来操作Instant
。有用于添加或减去时间的plus
和minus
方法。以下代码将当前时间加 1 小时:
Instant oneHourLater = Instant.now().plus(1, ChronoUnit.HOURS);
有用于比较瞬间的方法,例如isAfter
和isBefore
。until
方法返回两个Instant
对象之间存在多少时间。以下代码报告自 Java 纪元开始以来经过了多少秒。
long secondsFromEpoch = Instant.ofEpochSecond(0L).until(Instant.now(),
ChronoUnit.SECONDS);
Instant
类不适用于年、月或日等人类时间单位。如果要在这些单位中执行计算,可以通过将Instant
与时区绑定将Instant
转换为另一个类,例如LocalDateTime
或ZonedDateTime
。然后可以访问所需单位的值。以下代码使用ofInstant
方法和默认时区将Instant
转换为LocalDateTime
对象,然后以更可读的形式打印出日期和时间:
Instant timestamp;
...
LocalDateTime ldt = LocalDateTime.ofInstant(timestamp, ZoneId.systemDefault());
System.out.printf("%s %d %d at %d:%d%n", ldt.getMonth(), ldt.getDayOfMonth(),
ldt.getYear(), ldt.getHour(), ldt.getMinute());
输出将类似于以下内容:
MAY 30 2013 at 18:21
ZonedDateTime
或 OffsetTimeZone
对象都可以转换为 Instant
对象,因为每个对象都映射到时间线上的确切时刻。然而,反过来却不成立。要将 Instant
对象转换为 ZonedDateTime
或 OffsetDateTime
对象,需要提供时区或时区偏移信息。
解析和格式化
日期时间 API 中基于时间的类提供了用于解析包含日期和时间信息的字符串的parse
方法。这些类还提供了用于为显示格式化基于时间的对象的format
方法。在这两种情况下,过程是相似的:您提供一个模式给DateTimeFormatter
来创建一个格式化程序对象。然后将此格式化程序传递给parse
或format
方法。
DateTimeFormatter
类提供了许多预定义的格式化程序,或者您可以定义自己的格式化程序。
如果在转换过程中出现问题,parse
和 format
方法会抛出异常。因此,您的解析代码应捕获DateTimeParseException
错误,您的格式化代码应捕获DateTimeException
错误。有关异常处理的更多信息,请参阅捕获和处理异常。
DateTimeFormatter
类既是不可变的又是线程安全的;在适当的情况下,应该将其分配给静态常量。
版本说明: java.time
日期时间对象可以直接与java.util.Formatter
和String.format
一起使用,方法是使用与旧的java.util.Date
和java.util.Calendar
类一起使用的熟悉基于模式的格式化。
解析
LocalDate
类中的单参数parse(CharSequence)
方法使用ISO_LOCAL_DATE
格式化程序。要指定不同的格式化程序,可以使用两个参数的parse(CharSequence, DateTimeFormatter)
方法。以下示例使用预定义的BASIC_ISO_DATE
格式化程序,该格式使用19590709
表示 1959 年 7 月 9 日。
String in = ...;
LocalDate date = LocalDate.parse(in, DateTimeFormatter.BASIC_ISO_DATE);
您还可以使用自己的模式定义格式化程序。以下代码来自Parse
示例,创建了一个应用格式为"MMM d yyyy"的格式化程序。该格式指定三个字符表示月份,一个数字表示日期,四个数字表示年份。使用此模式创建的格式化程序将识别诸如"Jan 3 2003"或"Mar 23 1994"之类的字符串。但是,要将格式指定为"MMM dd yyyy",日期的两个字符,则您必须始终使用两个字符,对于一位数日期,用零填充:"Jun 03 2003"。
String input = ...;
try {
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("MMM d yyyy");
LocalDate date = LocalDate.parse(input, formatter);
System.out.printf("%s%n", date);
}
catch (DateTimeParseException exc) {
System.out.printf("%s is not parsable!%n", input);
throw exc; // Rethrow the exception.
}
// 'date' has been successfully parsed
DateTimeFormatter
类的文档指定了您可以使用的符号的完整列表来指定格式化或解析的模式。
非 ISO 日期转换页面上的StringConverter
示例提供了另一个日期格式化的示例。
格式化
format(DateTimeFormatter)
方法使用指定的格式将基于时间的对象转换为字符串表示。以下代码来自 Flight
示例,使用格式"MMM d yyy hh:mm a"转换了一个ZonedDateTime
实例。日期的定义方式与之前的解析示例相同,但此模式还包括小时、分钟以及上午和下午组件。
ZoneId leavingZone = ...;
ZonedDateTime departure = ...;
try {
DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM d yyyy hh:mm a");
String out = departure.format(format);
System.out.printf("LEAVING: %s (%s)%n", out, leavingZone);
}
catch (DateTimeException exc) {
System.out.printf("%s can't be formatted!%n", departure);
throw exc;
}
这个示例的输出,打印了到达和离开时间,如下所示:
LEAVING: Jul 20 2013 07:30 PM (America/Los_Angeles)
ARRIVING: Jul 21 2013 10:20 PM (Asia/Tokyo)
时间包
原文:
docs.oracle.com/javase/tutorial/datetime/iso/temporal.html
java.time.temporal
包提供了支持日期和时间代码的一组接口、类和枚举,特别是日期和时间计算。
这些接口旨在在最低级别使用。典型的应用代码应该根据具体类型(如LocalDate
或ZonedDateTime
)声明变量和参数,而不是根据Temporal
接口。这与声明类型为String
而不是CharSequence
的变量完全相同。
时间和 TemporalAccessor
Temporal
接口提供了访问基于时间的对象的框架,并由基于时间的类(如Instant
、LocalDateTime
和ZonedDateTime
)实现。该接口提供了添加或减去时间单位的方法,使得基于时间的算术在各种日期和时间类之间变得简单和一致。TemporalAccessor
接口提供了Temporal
接口的只读版本。
Temporal
和TemporalAccessor
对象都是根据字段定义的,如TemporalField
接口中所指定的。ChronoField
枚举是TemporalField
接口的具体实现,并提供了一组定义的常量,如DAY_OF_WEEK
、MINUTE_OF_HOUR
和MONTH_OF_YEAR
。
这些字段的单位由TemporalUnit
接口指定。ChronoUnit
枚举实现了TemporalUnit
接口。字段ChronoField.DAY_OF_WEEK
是ChronoUnit.DAYS
和ChronoUnit.WEEKS
的组合。ChronoField
和ChronoUnit
枚举在以下部分讨论。
Temporal
接口中基于算术的方法需要根据TemporalAmount
值定义的参数。Period
和Duration
类(在 Period and Duration 中讨论)实现了TemporalAmount
接口。
ChronoField 和 IsoFields
实现了TemporalField
接口的ChronoField
枚举提供了一组丰富的常量,用于访问日期和时间值。一些示例包括CLOCK_HOUR_OF_DAY
、NANO_OF_DAY
和DAY_OF_YEAR
。这个枚举可以用来表达时间的概念方面,比如一年中的第三周、一天中的第 11 小时或一个月中的第一个星期一。当你遇到一个未知类型的Temporal
时,可以使用TemporalAccessor.isSupported(TemporalField)
方法来确定Temporal
是否支持特定字段。下面这行代码返回false
,表示LocalDate
不支持ChronoField.CLOCK_HOUR_OF_DAY
:
boolean isSupported = LocalDate.now().isSupported(ChronoField.CLOCK_HOUR_OF_DAY);
针对 ISO-8601 日历系统的特定字段在IsoFields
类中定义。以下示例展示了如何使用ChronoField
和IsoFields
获取字段的值:
time.get(ChronoField.MILLI_OF_SECOND)
int qoy = date.get(IsoFields.QUARTER_OF_YEAR);
另外两个类定义了可能有用的额外字段,WeekFields
和 JulianFields
。
ChronoUnit
实现了TemporalUnit
接口的ChronoUnit
枚举提供了一组基于日期和时间的标准单位,从毫秒到千年。请注意,并非所有的ChronoUnit
对象都被所有类支持。例如,Instant
类不支持ChronoUnit.MONTHS
或ChronoUnit.YEARS
。日期时间 API 中的类包含isSupported(TemporalUnit)
方法,可用于验证一个类是否支持特定的时间单位。下面对isSupported
的调用返回false
,确认Instant
类不支持ChronoUnit.DAYS
。
Instant instant = Instant.now();
boolean isSupported = instant.isSupported(ChronoUnit.DAYS);
时间调整器
原文:
docs.oracle.com/javase/tutorial/datetime/iso/adjusters.html
TemporalAdjuster
接口,位于java.time.temporal
包中,提供了接受Temporal
值并返回调整后值的方法。这些调整器可以与任何基于时间的类型一起使用。
如果将调整器与ZonedDateTime
一起使用,则会计算出一个新日期,保留原始的时间和时区值。
预定义调整器
TemporalAdjusters
类(注意复数形式)提供了一组预定义的调整器,用于查找月份的第一天或最后一天,年份的第一天或最后一天,月份的最后一个星期三,或特定日期后的第一个星期二等等。这些预定义的调整器被定义为静态方法,并设计用于与静态导入语句一起使用。
以下示例结合了几个TemporalAdjusters
方法,与基于时间的类中定义的with
方法一起,根据 2000 年 10 月 15 日的原始日期计算新日期:
LocalDate date = LocalDate.of(2000, Month.OCTOBER, 15);
DayOfWeek dotw = date.getDayOfWeek();
System.out.printf("%s is on a %s%n", date, dotw);
System.out.printf("first day of Month: %s%n",
date.with(TemporalAdjusters.firstDayOfMonth()));
System.out.printf("first Monday of Month: %s%n",
date.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)));
System.out.printf("last day of Month: %s%n",
date.with(TemporalAdjusters.lastDayOfMonth()));
System.out.printf("first day of next Month: %s%n",
date.with(TemporalAdjusters.firstDayOfNextMonth()));
System.out.printf("first day of next Year: %s%n",
date.with(TemporalAdjusters.firstDayOfNextYear()));
System.out.printf("first day of Year: %s%n",
date.with(TemporalAdjusters.firstDayOfYear()));
这将产生以下输出:
2000-10-15 is on a SUNDAY
first day of Month: 2000-10-01
first Monday of Month: 2000-10-02
last day of Month: 2000-10-31
first day of next Month: 2000-11-01
first day of next Year: 2001-01-01
first day of Year: 2000-01-01
自定义调整器
您还可以创建自定义调整器。为此,您需要创建一个实现了TemporalAdjuster
接口并带有adjustInto(Temporal)
方法的类。来自NextPayday
示例的PaydayAdjuster
类是一个自定义调整器。PaydayAdjuster
评估传入的日期并返回下一个发薪日,假设发薪日每月发生两次:在每月的 15 日和最后一天。如果计算出的日期落在周末,则使用前一个星期五。假定当前日历年。
/**
* The adjustInto method accepts a Temporal instance
* and returns an adjusted LocalDate. If the passed in
* parameter is not a LocalDate, then a DateTimeException is thrown.
*/
public Temporal adjustInto(Temporal input) {
LocalDate date = LocalDate.from(input);
int day;
if (date.getDayOfMonth() < 15) {
day = 15;
} else {
day = date.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
}
date = date.withDayOfMonth(day);
if (date.getDayOfWeek() == DayOfWeek.SATURDAY ||
date.getDayOfWeek() == DayOfWeek.SUNDAY) {
date = date.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
}
return input.with(date);
}
调整器的调用方式与预定义调整器相同,使用with
方法。以下代码行来自NextPayday
示例:
LocalDate nextPayday = date.with(new PaydayAdjuster());
在 2013 年,6 月 15 日和 6 月 30 日都落在周末。使用 2013 年 6 月 3 日和 6 月 18 日的相应日期运行NextPayday
示例,得到以下结果:
Given the date: 2013 Jun 3
the next payday: 2013 Jun 14
Given the date: 2013 Jun 18
the next payday: 2013 Jun 28
时间查询
原文:
docs.oracle.com/javase/tutorial/datetime/iso/queries.html
TemporalQuery
可用于从基于时间的对象中检索信息。
预定义查询
TemporalQueries
类(注意是复数形式)提供了几个预定义查询,包括在应用程序无法识别基于时间的对象类型时有用的方法。与调整器一样,预定义查询被定义为静态方法,并设计用于与静态导入语句一起使用。
例如,precision
查询返回特定基于时间的对象可以返回的最小ChronoUnit
。以下示例在几种类型的基于时间的对象上使用了precision
查询:
TemporalQuery<TemporalUnit> query = TemporalQueries.precision();
System.out.printf("LocalDate precision is %s%n",
LocalDate.now().query(query));
System.out.printf("LocalDateTime precision is %s%n",
LocalDateTime.now().query(query));
System.out.printf("Year precision is %s%n",
Year.now().query(query));
System.out.printf("YearMonth precision is %s%n",
YearMonth.now().query(query));
System.out.printf("Instant precision is %s%n",
Instant.now().query(query));
输出如下所示:
LocalDate precision is Days
LocalDateTime precision is Nanos
Year precision is Years
YearMonth precision is Months
Instant precision is Nanos
自定义查询
您还可以创建自定义查询。一种方法是创建一个实现了TemporalQuery
接口的类,并使用queryFrom(TemporalAccessor)
方法。CheckDate
示例实现了两个自定义查询。第一个自定义查询可以在FamilyVacations
类中找到,该类实现了TemporalQuery
接口。queryFrom
方法将传入的日期与计划的假期日期进行比较,并在日期范围内返回TRUE
。
// Returns true if the passed-in date occurs during one of the
// family vacations. Because the query compares the month and day only,
// the check succeeds even if the Temporal types are not the same.
public Boolean queryFrom(TemporalAccessor date) {
int month = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);
// Disneyland over Spring Break
if ((month == Month.APRIL.getValue()) && ((day >= 3) && (day <= 8)))
return Boolean.TRUE;
// Smith family reunion on Lake Saugatuck
if ((month == Month.AUGUST.getValue()) && ((day >= 8) && (day <= 14)))
return Boolean.TRUE;
return Boolean.FALSE;
}
第二个自定义查询在FamilyBirthdays
类中实现。该类提供了一个isFamilyBirthday
方法,用于将传入的日期与几个生日进行比较,如果匹配则返回TRUE
。
// Returns true if the passed-in date is the same as one of the
// family birthdays. Because the query compares the month and day only,
// the check succeeds even if the Temporal types are not the same.
public static Boolean isFamilyBirthday(TemporalAccessor date) {
int month = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);
// Angie's birthday is on April 3.
if ((month == Month.APRIL.getValue()) && (day == 3))
return Boolean.TRUE;
// Sue's birthday is on June 18.
if ((month == Month.JUNE.getValue()) && (day == 18))
return Boolean.TRUE;
// Joe's birthday is on May 29.
if ((month == Month.MAY.getValue()) && (day == 29))
return Boolean.TRUE;
return Boolean.FALSE;
}
FamilyBirthday
类未实现TemporalQuery
接口,可作为 lambda 表达式的一部分使用。来自CheckDate
示例的以下代码展示了如何调用两个自定义查询。
// Invoking the query without using a lambda expression.
Boolean isFamilyVacation = date.query(new FamilyVacations());
// Invoking the query using a lambda expression.
Boolean isFamilyBirthday = date.query(FamilyBirthdays::isFamilyBirthday);
if (isFamilyVacation.booleanValue() || isFamilyBirthday.booleanValue())
System.out.printf("%s is an important date!%n", date);
else
System.out.printf("%s is not an important date.%n", date);
Period 和 Duration
当编写代码来指定一段时间时,请使用最符合您需求的类或方法:Duration
类、Period
类或ChronoUnit.between
方法。Duration
使用基于时间的值(秒、纳秒)来衡量一段时间。Period
使用基于日期的值(年、月、日)。
注意: 一天的Duration
确切地为 24 小时。将一天的Period
添加到ZonedDateTime
中时,根据时区可能会有所变化。例如,如果发生在夏令时的第一天或最后一天。
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。
运行示例程序
要运行示例程序,你需要在命令行上指定语言和国家。本节将向你展示一些示例。
国际化示例程序
国际化程序只需要几个步骤。你会惊讶于它是多么容易。
在国际化之前
假设你已经编写了一个显示三条消息的程序,如下所示:
public class NotI18N {
static public void main(String[] args) {
System.out.println("Hello.");
System.out.println("How are you?");
System.out.println("Goodbye.");
}
}
你已经决定这个程序需要为居住在法国和德国的人显示相同的消息。不幸的是,你的编程人员不懂多种语言,所以你需要帮助将这些消息翻译成法语和德语。由于翻译人员不是程序员,你需要将消息从源代码中移出,放入文本文件供翻译人员编辑。此外,程序必须足够灵活,以便能够显示其他语言的消息,但目前没有人知道那些语言会是什么。
看起来这个程序需要国际化。
国际化后
国际化程序的源代码如下。请注意,消息的文本未包含在软件代码中。
import java.util.*;
public class I18NSample {
static public void main(String[] args) {
String language;
String country;
if (args.length != 2) {
language = new String("en");
country = new String("US");
} else {
language = new String(args[0]);
country = new String(args[1]);
}
Locale currentLocale;
ResourceBundle messages;
currentLocale = new Locale(language, country);
messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
System.out.println(messages.getString("greetings"));
System.out.println(messages.getString("inquiry"));
System.out.println(messages.getString("farewell"));
}
}
要编译和运行此程序,您需要这些源文件:
-
I18NSample.java
-
MessagesBundle.properties
-
MessagesBundle_de_DE.properties
-
MessagesBundle_en_US.properties
-
MessagesBundle_fr_FR.properties
运行示例程序
国际化程序是灵活的;它允许最终用户在命令行上指定语言和国家。在下面的示例中,语言代码是fr
(法语),国家代码是FR
(法国),因此程序会以法语显示消息:
% java I18NSample fr FR
Bonjour.
Comment allez-vous?
Au revoir.
在下一个示例中,语言代码是en
(英语),国家代码是US
(美国),因此程序会以英语显示消息:
% java I18NSample en US
Hello.
How are you?
Goodbye.
国际化示例程序
如果你查看国际化的源代码,你会注意到硬编码的英文消息已经被移除。因为消息不再是硬编码的,而且语言代码在运行时指定,同一个可执行文件可以在全球范围内分发。本地化不需要重新编译。该程序已经国际化。
你可能想知道消息的文本发生了什么变化,或者语言和国家代码的含义是什么。别担心。当你逐步学习国际化示例程序的过程中,你会了解这些概念。
1. 创建属性文件
一个属性文件存储了关于程序或环境特性的信息。属性文件是纯文本格式。你可以用几乎任何文本编辑器创建这个文件。
在示例中,属性文件存储了要显示的可翻译消息的文本。在程序国际化之前,这些文本的英文版本是硬编码在System.out.println
语句中的。默认的属性文件名为MessagesBundle.properties
,包含以下内容:
greetings = Hello
farewell = Goodbye
inquiry = How are you?
现在消息已经在一个属性文件中,它们可以被翻译成各种语言。不需要对源代码进行任何更改。法语翻译者创建了一个名为MessagesBundle_fr_FR.properties
的属性文件,其中包含以下内容:
greetings = Bonjour.
farewell = Au revoir.
inquiry = Comment allez-vous?
注意等号右侧的值已经被翻译,但左侧的键没有改变。这些键不能改变,因为当你的程序获取翻译后的文本时会引用这些键。
属性文件的名称很重要。例如,MessagesBundle_fr_FR.properties
文件的名称包含了fr
语言代码和FR
国家代码。在创建Locale
对象时也会使用这些代码。
2. 定义 Locale
Locale
对象标识特定的语言和国家。以下语句定义了一个Locale
,其中语言为英语,国家为美国:
aLocale = new Locale("en","US");
下一个示例创建了法语语言在加拿大和法国的Locale
对象:
caLocale = new Locale("fr","CA");
frLocale = new Locale("fr","FR");
该程序是灵活的。程序不再使用硬编码的语言和国家代码,而是在运行时从命令行获取它们:
String language = new String(args[0]);
String country = new String(args[1]);
currentLocale = new Locale(language, country);
Locale
对象只是标识符。在定义了一个Locale
之后,你可以将它传递给执行有用任务的其他对象,比如格式化日期和数字。这些对象是区域敏感的,因为它们的行为根据Locale
的不同而变化。ResourceBundle
就是一个区域敏感的对象的例子。
3. 创建 ResourceBundle
ResourceBundle
对象包含特定于区域设置的对象。你可以使用ResourceBundle
对象来隔离与区域设置相关的数据,比如可翻译的文本。在示例程序中,ResourceBundle
由包含我们想要显示的消息文本的属性文件支持。
ResourceBundle
的创建方式如下:
messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
传递给getBundle
方法的参数标识将要访问的属性文件。第一个参数MessagesBundle
指的是这一系列属性文件:
MessagesBundle_en_US.properties
MessagesBundle_fr_FR.properties
MessagesBundle_de_DE.properties
Locale
是getBundle
的第二个参数,指定了选择哪个MessagesBundle
文件。创建Locale
时,语言代码和国家代码被传递给其构造函数。请注意,语言和国家代码在属性文件的名称中跟随MessagesBundle
。
现在你只需要从ResourceBundle
中获取翻译后的消息即可。
4. 从 ResourceBundle 中获取文本
属性文件包含键值对。值包含了程序将显示的翻译文本。在从ResourceBundle
中获取翻译后的消息时,你需要使用getString
方法指定键。例如,要检索由greetings
键标识的消息,你可以这样调用getString
:
String msg1 = messages.getString("greetings");
示例程序使用了键greetings
,因为它反映了消息的内容,但也可以使用另一个String
,比如s1
或msg1
。只需记住,键是硬编码在程序中的,必须存在于属性文件中。如果你的翻译人员意外修改了属性文件中的键,getString
将无法找到消息。
结论
这就是全部。正如你所看到的,国际化一个程序并不太困难。它需要一些规划和一点额外的编码,但好处是巨大的。为了让你了解国际化过程的概况,本课程中的示例程序被故意保持简单。在接下来的课程中,你将了解 Java 编程语言更高级的国际化特性。
检查表
原文:
docs.oracle.com/javase/tutorial/i18n/intro/checklist.html
许多程序在最初编写时并未国际化。这些程序可能最初是原型,或者可能并非用于国际分发。如果你必须国际化现有程序,请执行以下步骤:
识别文化相关数据
文本消息是随文化变化最明显的数据形式。然而,其他类型的数据可能会随地区或语言而变化。以下列表包含了文化相关数据的例子:
-
消息
-
GUI 组件上的标签
-
在线帮助
-
声音
-
颜色
-
图形
-
图标
-
日期
-
时间
-
数字
-
货币
-
测量
-
电话号码
-
敬语和个人头衔
-
邮政地址
-
页面布局
在资源包中隔离可翻译文本
翻译是昂贵的。你可以通过将必须翻译的文本隔离在ResourceBundle
对象中来帮助降低成本。可翻译的文本包括状态消息、错误消息、日志文件条目和 GUI 组件标签。这些文本包含在尚未国际化的程序中。你需要找到所有显示给最终用户的包含文本的出现。例如,你应该清理这样的代码:
String buttonLabel = "OK";
// ...
JButton okButton = new JButton(buttonLabel);
详细信息请参见隔离特定区域数据部分。
处理复合消息
复合消息包含可变数据。在消息"The disk contains 1100 files."中,整数 1100 可能会变化。这个消息很难翻译,因为整数在句子中的位置在所有语言中都不相同。下面的消息是不可翻译的,因为句子元素的顺序是由串联固定的:
Integer fileCount;
// ...
String diskStatus = "The disk contains " + fileCount.toString() + " files";
尽可能避免构建复合消息,因为它们很难翻译。然而,如果你的应用程序需要复合消息,你可以使用消息部分中描述的技术来处理它们。
格式化数字和货币
如果你的应用程序显示数字和货币,你必须以与地区无关的方式格式化它们。以下代码尚未国际化,因为它在所有国家中都不会正确显示数字:
Double amount;
TextField amountField;
// ...
String displayAmount = amount.toString();
amountField.setText(displayAmount);
你应该用一个能正确格式化数字的例程替换前面的代码。Java 编程语言提供了几个格式化数字和货币的类。这些类在数字和货币部分有讨论。
格式化日期和时间
日期和时间格式因地区和语言而异。如果你的代码包含如下语句,你需要进行更改:
Date currentDate = new Date();
TextField dateField;
// ...
String dateString = currentDate.toString();
dateField.setText(dateString);
如果你使用日期格式化类,你的应用程序可以在全球范围内正确显示日期和时间。有关示例和说明,请参见日期和时间部分。
使用 Unicode 字符属性
以下代码尝试验证一个字符是否为字母:
char ch;
// This code is incorrect
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
注意类似这样的代码,因为它在除英语以外的语言中无法正常工作。例如,if
语句在德语单词 Grün 中漏掉了字符ü。
Character
比较方法使用 Unicode 标准来识别字符属性。因此,您应该用以下代码替换先前的代码:
char ch;
// ...
if (Character.isLetter(ch))
有关Character
比较方法的更多信息,请参阅检查字符属性部分。
正确比较字符串
在对文本进行排序时,通常需要比较字符串。如果文本是显示的,您不应该使用String
类的比较方法。一个未国际化的程序可能会这样比较字符串:
String target;
String candidate;
// ...
if (target.equals(candidate)) {
// ...
if (target.compareTo(candidate) < 0) {
// ...
String.equals
和String.compareTo
方法执行二进制比较,在大多数语言中排序时效率低下。相反,您应该使用Collator
类,该类在比较字符串部分有描述。
转换非 Unicode 文本
Java 编程语言中的字符是以 Unicode 编码的。如果您的应用程序处理非 Unicode 文本,您可能需要将其转换为 Unicode。有关更多信息,请参阅转换非 Unicode 文本部分。
课程:设置区域设置
一个国际化的程序可以在世界各地以不同方式显示信息。例如,该程序将在巴黎、东京和纽约显示不同的消息。如果本地化过程已经经过精细调整,该程序将显示不同的消息在纽约和伦敦,以考虑美式英语和英式英语之间的差异。一个国际化的程序如何识别其最终用户的适当语言和地区?简单。它引用一个Locale
对象。
一个Locale
对象是特定语言和地区组合的标识符。如果一个类根据Locale
变化其行为,那么它被称为区域敏感。例如,NumberFormat
类是区域敏感的;它返回的数字格式取决于Locale
。因此,NumberFormat
可能会将数字返回为 902 300(法国)、902.300(德国)或 902,300(美国)。Locale
对象只是标识符。真正的工作,比如格式化和检测单词边界,是由区域敏感类的方法执行的。
以下各节解释了如何使用Locale
对象:
创建一个 Locale
在创建Locale
对象时,通常会指定语言代码和国家代码。第三个参数,变体,是可选的。
BCP 47 扩展
本节向您展示如何向Locale
添加 Unicode 区域扩展或私有使用扩展。
识别可用的 Locale
区域敏感类仅支持特定的Locale
定义。本节将向您展示如何确定支持哪些Locale
定义。
语言标签过滤和查找
本节描述了语言标签、语言标签过滤和语言标签查找的国际化支持。
Locale 的范围
在 Java 平台上,您不需要通过在运行应用程序之前设置环境变量来指定全局Locale
。相反,您要么依赖于默认 Locale,要么为每个区域敏感对象分配一个Locale
。
区域敏感服务 SPI
本节解释了如何启用依赖于区域设置的数据和服务的插件。这些 SPI(服务提供者接口)提供了对当前可用区域设置之外更多区域设置的支持。
创建 Locale
有几种方法可以创建 Locale
对象。无论使用哪种技术,创建可以简单地指定语言代码。但是,您还可以通过设置区域(也称为“国家”)和变体代码来进一步区分区域。如果您使用 JDK 7 发布或更高版本,还可以指定脚本代码和 Unicode 区域扩展。
创建 Locale
对象的四种方法是:
-
Locale.Builder
类 -
Locale
构造函数 -
Locale.forLanguageTag
工厂方法 -
Locale
常量
版本说明: Locale.Builder
类和 forLanguageTag
方法是在 Java SE 7 发布中添加的。
LocaleBuilder
类
Locale.Builder
实用类可用于构造符合 IETF BCP 47 语法的 Locale
对象。例如,要指定法语和加拿大国家,您可以调用 Locale.Builder
构造函数,然后链接设置器方法如下:
Locale aLocale = new Locale.Builder().setLanguage("fr").setRegion("CA").build();
下一个示例创建了英语在美国和英国的 Locale
对象:
Locale bLocale = new Locale.Builder().setLanguage("en").setRegion("US").build();
Locale cLocale = new Locale.Builder().setLanguage("en").setRegion("GB").build();
最后一个示例创建了俄语的 Locale
对象:
Locale dLocale = new Locale.Builder().setLanguage("ru").setScript("Cyrl").build();
Locale
构造函数
Locale
类有三个可用的构造函数用于创建 Locale
对象:
以下示例创建了法语在加拿大,英语在美国和英国,以及俄语的 Locale
对象。
aLocale = new Locale("fr", "CA");
bLocale = new Locale("en", "US");
cLocale = new Locale("en", "GB");
dLocale = new Locale("ru");
在 JDK 7 之前的版本中,无法在 Locale
对象上设置脚本代码。
forLanguageTag
工厂方法
如果您有符合 IETF BCP 47 标准的语言标记字符串,可以使用在 Java SE 7 发布中引入的 forLanguageTag(String)
工厂方法。例如:
Locale aLocale = Locale.forLanguageTag("en-US");
Locale bLocale = Locale.forLanguageTag("ja-JP-u-ca-japanese");
Locale
常量
为了方便起见,Locale
类为一些语言和国家提供了常量。例如:
cLocale = Locale.JAPAN;
dLocale = Locale.CANADA_FRENCH;
当您指定语言常量时,Locale
的区域部分是未定义的。下面的三个语句创建等效的 Locale
对象:
j1Locale = Locale.JAPANESE;
j2Locale = new Locale.Builder().setLanguage("ja").build();
j3Locale = new Locale("ja");
由以下三个语句创建的Locale
对象也是等效的:
j4Locale = Locale.JAPAN;
j5Locale = new Locale.Builder().setLanguage("ja").setRegion("JP").build();
j6Locale = new Locale("ja", "JP");
代码
以下部分讨论语言代码和可选的脚本、地区和变体代码。
语言代码
语言代码是符合 ISO 639 标准的两个或三个小写字母。您可以在www.loc.gov/standards/iso639-2/php/code_list.php
找到 ISO 639 代码的完整列表。
以下表格列出了一些语言代码。
示例语言代码
语言代码 | 描述 |
---|---|
de |
德语 |
en |
英语 |
fr |
法语 |
ru |
俄语 |
ja |
日语 |
jv |
爪哇语 |
ko |
韩语 |
zh |
中文 |
脚本代码
脚本代码以大写字母开头,后跟三个小写字母,并符合 ISO 15924 标准。您可以在unicode.org/iso15924/iso15924-codes.html
找到 ISO 15924 代码的完整列表。
以下表格列出了一些脚本代码。
示例脚本代码
脚本代码 | 描述 |
---|---|
Arab |
阿拉伯语 |
Cyrl |
西里尔字母 |
Kana |
片假名 |
Latn |
拉丁字母 |
有三种方法可以检索Locale
的脚本信息:
-
getScript()
返回Locale
对象的 4 字母脚本代码。如果未为区域设置定义脚本,则返回空字符串。 -
getDisplayScript()
返回适合显示给用户的区域设置脚本的名称。如果可能,名称将针对默认区域设置进行本地化。因此,例如,如果脚本代码是"Latn",则返回的显示脚本名称将在英语语言区域设置下为"Latin"。 -
getDisplayScript(Locale)
返回指定Locale
的显示名称,如果可能的话进行本地化。
地区代码
地区(国家)代码由符合 ISO 3166 标准的两个或三个大写字母组成,或者符合 UN M.49 标准的三个数字。代码的副本可以在www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html
找到。
以下表格包含几个示例国家和地区代码。
示例地区代码
A-2 代码 | A-3 代码 | 数字代码 | 描述 |
---|---|---|---|
AU |
AUS |
036 |
澳大利亚 |
BR |
BRA |
076 |
巴西 |
CA |
CAN |
124 |
加拿大 |
CN |
CHN |
156 |
中国 |
DE |
DEU |
276 |
德国 |
FR |
FRA |
250 |
法国 |
IN |
IND |
356 |
印度 |
RU |
RUS |
643 |
俄罗斯联邦 |
US |
USA |
840 |
美国 |
变体代码
可选的variant
代码可用于进一步区分您的Locale
。例如,变体代码可用于指示区域代码未涵盖的方言差异。
版本说明: 在 Java SE 7 发布之前,变体代码有时用于标识不特定于语言或区域的差异。例如,它可能已用于标识计算平台之间的差异,如 Windows 或 UNIX。根据 IETF BCP 47 标准,不鼓励此用法。
要定义与您的环境相关的非语言特定变体,请使用扩展机制,如 BCP 47 扩展中所解释的那样。
自 Java SE 7 发布以来,符合 IETF BCP 47 标准的变体代码专门用于指示定义语言或其方言的附加变体。IETF BCP 47 标准对变体子标记施加了语法限制。您可以在www.iana.org/assignments/language-subtag-registry
上查看变体代码列表(搜索变体)。
例如,Java SE 使用变体代码支持泰语。按照惯例,th
和 th_TH
区域设置的NumberFormat
对象将使用常见的阿拉伯数字形状或阿拉伯数字来格式化泰国数字。然而,th_TH_TH
区域设置的NumberFormat
将使用泰国数字形状。ThaiDigits.java
中的摘录演示了这一点:
String outputString = new String();
Locale[] thaiLocale = {
new Locale("th"),
new Locale("th", "TH"),
new Locale("th", "TH", "TH")
};
for (Locale locale : thaiLocale) {
NumberFormat nf = NumberFormat.getNumberInstance(locale);
outputString = outputString + locale.toString() + ": ";
outputString = outputString + nf.format(573.34) + "\n";
}
以下是此示例的屏幕截图:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
2023-04-12 NumPy 初学者指南中文第三版:11~14
2023-04-12 NumPy 初学者指南中文第三版:6~10
2023-04-12 NumPy 初学者指南中文第三版:1~5
2023-04-12 NumPy 秘籍中文第二版:11~12
2023-04-12 NumPy 秘籍中文第二版:6~10
2023-04-12 NumPy 秘籍中文第二版:1~5
2023-04-12 NumPy 秘籍中文第二版:十二、使用 NumPy 进行探索性和预测性数据分析