20230629 6. 日期和时间 API
日期和时间 API
为什么处理时间会如此之难呢?问题出在人类自身上
- Java 1.0 有一个
Date
类,事后证明它过于简单了 - 当 Java 1.1 引入
Calendar
类之后,Date
类中的大部分方法就被弃用了。但是,Calendar
API 还不够给力,它的实例是易变的,并且它没有处理诸如闰秒这样的问题。 - 第 3 次升级很吸引人,那就是 Java SE 中引入的
java.time
API ,它修正了过去的缺陷,并且应该会服役相当长的一段时间。
时间线( Instant
、Duration
)
在历史上,基本的时间单位 秒
是从地球的自转中推导出来的。地球自转一周需要 24 个小时,即 24 * 60 * 60 = 86400 秒,因此,看起来这好像只是一个有关如何精确定义 1 秒的天文度量问题。遗憾的是,地球有轻微的颤动,所以需要更加精确的定义。1967 年,人们根
据铯 133 原子内在的特性推导出了与其历史定义相匹配的秒的新的精确定义。自那以后,原子钟网络就一直被当作官方时间。
官方时间的监护者们时常需要将绝对时间与地球自转进行同步。首先,官方的秒需要稍作调整,从 1972 年开始,偶尔需要插入“闰秒” (在理论上,偶尔也需要移除 1 秒,但是这还从来没发生过 )。闰秒是个痛点,许多计算机系统使用“平滑”方式来人为地在紧邻闰秒之前让时间变慢或变快,以保证每天都是 86400 秒。这种做法可以奏效,因为计算机上的本地时间并非那么精确,而计算机也惯于将自身时间与外部的时间服务进行同步
Java 的 Date 和 Time API 规范要求 Java 使用的时间尺度为:
- 每天 86400 秒
- 每天正午与官方时间精确匹配
- 在其他时间点上,以精确定义的方式与官方时间接近匹配
在 Java 中, Instant
表示时间线上的某个点。 被称为“新纪元”的时间线原点被设置为穿过伦敦格林威治皇家天文台的本初子午线所处时区的 1970 年 1 月 1 日的午夜。这与 UNIX/POSIX 时间中使用的惯例相同。从该原点开始,时间按照每天 86400 秒向前或向回度量,精确到纳秒。
Instant
的值向回可追溯 10 亿年 ( Instant.MIN
)。最大的值 Instant.MAX
是公元 1 000 000 000 年的 12 月 31 日
静态方法调用 Instant.now()
会给出当前的时刻。你可以按照常用的方式,用 equals
和 compareTo
方法来比较两个 Instant
对象,因此你可以将 Instant
对象用作时间戳
为了得到两个时刻之间的时间差,可以使用静态方法 Duration.between
Duration
是两个时刻之间的时间量。 你可以通过调用 toNanos
、toMillis
、getSeconds
、toMinutes
、toHours
、toDays
获得 Duration
按照传统单位度量的时间长度
Instant instant1 = Instant.now();
System.out.println(instant1); // 2022-02-08T10:52:40.603Z
Thread.sleep(1234);
Instant instant2 = Instant.now();
Duration duration1 = Duration.between(instant1, instant2);
Duration duration2 = Duration.between(instant2, instant1);
System.out.println(duration1); // PT1.309S
System.out.println(duration2); // PT-1.309S
System.out.println(duration1.toMillis()); // 1309
Duration
对象的内部存储所需的空间超过了一个 long
的值,因此秒数存储在一个 long
中,而纳秒数存储在一个额外的 int
中。如果想要让计算精确到纳秒级,那么实际上你需要整个 Duration
的存储内容。如果不要求这么高的精度,那么你可以用 long
的值来执行计算,然后直接调用 toNanos
大约 300 年时间对应的纳秒数才会溢出 long
的范围
如果想要检查某个算法是否至少比另一个算法快 10 倍:
Duration timeElapsed = Duration.between(start, end);
Duration timeElapsed2 = Duration.between(start2, end2);
boolean overTenTimesFaster =
timeElapsed.multipliedBy(10).minus(timeElapsed2).isNegative();
// boolean overTenTimesFaster = timeElapsed.toNanos() * 10 < timeElapsed2.toNanos();
用于时间的 Instant
和 Duration
的算术运算
方法 | 描述 |
---|---|
plus 、 minus |
在当前的 Instant 或 Duration 上加上或减去一个 Duration |
plusNanos 、 plusMillis 、 plusSeconds 、 minusNanos 、 minusMillis 、 minusSeconds |
在当前的 Instant 或 Duration 上加上或减去给定时间单位 |
plusMinutes 、 plusHours 、 plusDays 、 minusMinutes 、 minusHours 、 minusDays |
在当前 Duration 上加上或减去给定时间单位的数值 |
multipliedBy 、 dividedBy 、 negated |
返回由当前的 Duration 乘以或除以给定 long 或 -1 而得到的 Duration 。注意,可以缩放 Duration ,但是不能缩放 Instant |
isZero 、 isNegative |
检查当前的 Duration 是否是 0 或负值 |
Instant
和 Duration
类都是不可修改的类,所以诸如 multipliedBy
和 minus
这样的方法都会返回一个新的实例
本地日期( LocalDate
、Period
)
让我们从绝对时间转移到人类时间。 Java API 中有两种 人类时间 ,本地日期/时间 和 时区时间 。本地日期/时间包含日期和当天的时间,但是与时区信息没有任何关联。1903 年 6 月 14 日就是一个本地日期的示例( lambda 的发明者 Alonzo Church 在这一天诞生)。因为这个日期既没有当天的时间,也没有时区信息,因此它并不对应精确的时刻。 与之相反的是, 1969 年 7 月 16 日 09:32:00 EDT (阿波罗 11 号发射的时刻)是一个时区日期/时间,表示的是时间线上的一个精确的时刻。
有许多计算并不需要时区,在某些情况下,时区甚至是一种障碍。 假设你安排每周 10:00 开一次会。如果你加 7 天到最后一次会议的时区时间上,那么你可能会碰巧跨越了夏令时的时间调整边界,这次会议可能会早一小时或晚一小时!
API 的设计者们推荐程序员不要使用时区时间,除非确实想要表示绝对时间的实例。生日、假日、 计划时间等通常最好都表示成本地日期和时间。
LocalDate
是带有年、月、 日的日期。为了构建 LocalDate
对象,可以使用 now
或 of
静态方法:
LocalDate today = LocalDate.now();
LocalDate alonzosBirthday = LocalDate.of(1903, 6, 14);
alonzosBirthday = LocalDate.of(1903, Month.JUNE, 14);
与 UNIX 和 java.util.Date
使用的月从 0
开始计算而年从 1900
开始计算的不规则的惯用法不同,你需要提供通常使用的月份的数字。或者,你可以使用 java.time.Month
枚举
最有用的操作 LocalDate
对象的方法:
方法 | 描述 |
---|---|
now 、 of |
这些静态方法会构建一个 LocalDate ,要么从当前时间构建,要么从给定的年、月、日构建 |
plusDays 、 plusWeeks 、 plusMonths 、 plusYears |
在当前的 LocalDate 上加上一定量的天、星期、月或年 |
minusDays 、 minusWeeks 、 minusMonths 、 minusYears |
在当前的 LocalDate 上减去一定量的天、星期、月或年 |
plus 、 minus |
加上或减去一个 Duration 或 Period |
withDayOfMonth 、 withDayOfYear 、 withMonth 、 withYear |
返回一个新的 LocalDate 。其月的日期、年的日期、月或年修改为给定的值 |
getDayOfMonth |
获取月的日期(在 1 到 31 之间) |
getDayOfYear |
获取年的日期(在 1 到 366 之间) |
getDayOfWeek |
获取星期日期,返回 DayOfWeek 枚举值 |
getMonth 、 getMonthValue |
获取月份的 Month 枚举值,或者是 1 - 12 之间的数 |
getYear |
获取年份,在 -999 999 999 到 999 999 999 之间 |
until |
获取 Period ,或者两个日期之间按照给定的 ChronoUnits 计算的数值 |
isBefore 、 isAfter |
将当前的 LocalDate 与另一个 LocalDate 进行比较 |
isLeapYear |
如果当前是闰年,则返回 true |
程序员日是每年的第 256 天,如何很容易地计算出它:
LocalDate programmersDay = LocalDate.of(2022, 1, 1).plusDays(255);
两个 Instant
之间的时长是 Duration
,而用于本地日期的等价物是 Period
,它表示的是流逝的年、月或日的数量。可以调用 birthday.plus(Period.ofYears(1))
来获取下一年的生日。当然,也可以直接调用 birthday.plusYears(1)
util
方法会产生两个本地日期之间的时长
LocalDate birthday = LocalDate.now();
birthday.plus(Period.ofYears(1));
LocalDate nextBirthday = birthday.plusYears(1);
Period period = birthday.until(nextBirthday);
System.out.println(period); // P1Y
System.out.println(period.getYears()); // 1
long days = birthday.until(nextBirthday, ChronoUnit.DAYS);
System.out.println(days); // 365
有些方法可能会创建出并不存在的日期。例如,在 1 月 31 日上加上 1 个月不应该产生 2 月 31 日。这些方法并不会抛出异常,而是会返回该月有效的最后一天
getDayOfWeek
会产生星期日期,即 DayOfWeek
枚举的某个值。 DayOfWeek.MONDAY
的枚举值为 1
,而 DayOfWeek.SUNDAY
的枚举值为 7
DayOfWeek
枚举具有便捷方法 plus
和 minus
,以 7
为模计算星期日期
int value = DayOfWeek.MONDAY.getValue();
System.out.println(value); // 1
DayOfWeek dayOfWeek = DayOfWeek.SATURDAY.plus(3);
System.out.println(dayOfWeek); // TUESDAY
周末实际上在每周的末尾。这与 java.util.Calendar
所差异,在 Calendar
中,星期六的值为 1
,而星期天的值为 7
除了 LocalDate
之外,还有 MonthDay
、 YearMonth
、Year
类可以描述部分日期。例如, 12 月 25 日(没有指定年份)可以表示成一个 MonthDay
对象
MonthDay monthDay = MonthDay.of(Month.DECEMBER, 5);
System.out.println(monthDay); // --12-05
日期调整器( TemporalAdjusters
)
TemporalAdjusters
类提供了大量用于常见调整的静态方法。你可以将调整方法的结果传递给 with
方法。例如,某个月的第一个星期二可以像下面这样计算:
LocalDate firstTuesday = LocalDate.of(year, month, 1).with(
TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY));
with
方法会返回一个新 LocalDate
对象, 不会修改原来的对象
TemporalAdjusters
类中的日期调整器:
方法 | 描述 |
---|---|
next(dayOfWeek) 、 previous(dayOfWeek) |
下一个或上一个给定的星期日期 |
nextOrSame(dayOfWeek) 、 previousOrSame(dayOfWeek) |
从给定的日期开始的下一个或上一个给定的星期日期 |
dayOfWeekInMonth(ordinal, dayOfWeek) |
月份中的第 n 个 weekday |
lastInMonth(DayOfWeek dayOfWeek) |
月份中的最后一个 weekday |
firstDayOfMonth() 、 firstDayOfNextMonth() 、 firstDayOfNextYear() 、 lastDayOfMonth() 、 lastDayOfYear() |
方法名所描述的日期 |
还可以通过实现 TemporalAdjuster
接口来创建自己的调整器。下面是用于计算下一个工作日的调整器:
LocalDate today = LocalDate.now();
TemporalAdjuster NEXT_WORKDAY = w -> {
LocalDate result = (LocalDate) w;
do {
result = result.plusDays(1);
} while (result.getDayOfWeek().getValue() >= 6);
return result;
};
LocalDate backToWork = today.with(NEXT_WORKDAY);
注意, lambda 表达式的参数类型为 Temporal
,它必须被强制转型为 LocalDate
。可以用 ofDateAdjuster
方法来避免这种强制转型,该方法期望得到的参数是类型为 UnaryOperator<LocalDate>
的 lambda 表达式
TemporalAdjuster NEXT_WORKDAY = TemporalAdjusters.ofDateAdjuster(w -> {
LocalDate result = w; // 无需转换
do {
result = result.plusDays(1);
} while (result.getDayOfWeek().getValue() >= 6);
return result;
});
本地时间( LocalTime
)
LocalTime
表示当日时刻,例如 15:30:00 。可以用 now
或 of
方法创建其实例:
LocalTime rightNow = LocalTime.now();
LocalTime bedtime = LocalTime.of(22, 30); // or LocalTime.of(22, 30, 0)
plus
和 minus
操作是按照一天 24 小时循环操作的
LocalTime
自身并不关心 AM/PM 。这种愚蠢的设计将问题抛给格式器去解决
LocalTime
的方法:
方法 | 描述 |
---|---|
now 、of |
这些静态方法会构建一个 LocalTime ,要么从当前时间构建,要么从给定的小时和分钟,以及可选的秒和纳秒构建 |
plusHours 、plusMinutes 、plusSeconds 、plusNanos |
在当前的 LocalTime 上加上一定量的小时、分钟、秒或纳秒 |
minusHours 、minusMinutes 、minusSeconds 、minusNanos |
在当前的 LocalTime 上减去一定量的小时、分钟、秒或纳秒 |
plus 、minus |
加上或减去一个 Duration |
withHour 、withMinute 、withSecond 、withNano |
返回一个新的 LocalTime ,其小时、分钟、秒和纳秒修改为给定的值 |
getHour 、getMinute 、getSecond 、getNano |
获取当前 LocalTime 的小时、分钟、秒或纳秒 |
toSecondOfDay 、toNanoOfDay |
返回午夜到当前 LocalTime 的秒或纳秒的数量 |
isBefore 、isAfter |
将当前的 LocalTime 与另一个 LocalTime 进行比较 |
还有一个表示日期和时间的 LocalDateTime
类。这个类适合存储固定时区的时间点,例如,用于排课或排程。但是,如果你的计算需要跨越夏令时,或者需要处理不同时区的用户,那么就应该使用 ZonedDateTime
时区时间( ZonedDateTime
)
时区,可能是因为完全是人为创造的原因,它甚至比地球不规则的转动引发的复杂性还要麻烦。在理性的世界中,我们都会遵循格林尼泊时间。有些人在 2:00 吃午饭,而有些人却在 22:00 吃午饭。这就是中国的做法,中国横跨了多个时区,但是使用了同一个时间(东八区 UTC+8)。在其他地方,时区显得并不规则,并且还有国际日期变更线,而夏令时使事情变得更糟了
互联网编码分配管理机构( Internet Assigned Numbers Authority, IANA )保存着一个数据库,里面存储着世界上所有已知的时区,它每年会更新数次,而批量更新会处理夏令时的变更规则。Java 使用了 IANA 数据库
每个时区都有一个 ID ,例如 America/New_York
和 Europe/Berlin
。要想找出所有可用的时区,可以调用 ZoneId.getAvailableZoneIds()
属于中国的时区 ID 有 6 个:
Asia/Chongqing
Asia/Shanghai
Asia/Urumqi
Asia/Macao
Asia/Hong_Kong
Asia/Taipei
给定一个时区 ID ,静态方法 ZoneId.of(id)
可以产生一个 ZoneId
对象。可以通过调用 local.atZone(zoneId)
用这个对象将 LocalDateTime
对象转换为 ZonedDateTime
对象,或者可以通过调用静态方法 ZonedDateTime.of(year, month, dayOfMonth, hour, minute, second, nanoOfSecond, zone)
来构造一个 ZonedDateTime
对象
ZonedDateTime apollo11launch = ZonedDateTime.of(1969, 7, 16, 9, 32, 0, 0,
ZoneId.of("America/New_York"));
// 1969-07-16T09:32-04:00[America/New_York]
这是一个具体的时刻,调用 apollo11launch.toInstant
可以获得对应的 Instant
对象。反过来,如果你有一个时刻对象,调用 instant.atZone(ZoneId.of("UTC"))
可以获得格林威治皇家天文台的 ZonedDateTime
对象,或者使用其他 ZoneId
获得地球上其他地方的 ZoneId
时区时间( ZonedDateTime
)对应时刻( Instant
)的相互转换:
ZonedDateTime apollo11launch = ZonedDateTime.of(1969, 7, 16, 9, 32, 0, 0,
ZoneId.of("America/New_York"));
System.out.println("apollo11launch: " + apollo11launch); // apollo11launch: 1969-07-16T09:32-04:00[America/New_York]
Instant instant = apollo11launch.toInstant();
System.out.println("instant: " + instant); // instant: 1969-07-16T13:32:00Z
// 相同时刻
ZonedDateTime sameInstantZonedDateTime = apollo11launch.withZoneSameInstant(ZoneId.of("Asia/Chongqing"));
System.out.println(sameInstantZonedDateTime); // 1969-07-16T21:32+08:00[Asia/Chongqing]
System.out.println(sameInstantZonedDateTime.toInstant()); // 1969-07-16T13:32:00Z
// 相同本地时间
ZonedDateTime sameLocalZonedDateTime = apollo11launch.withZoneSameLocal(ZoneId.of("Asia/Chongqing"));
System.out.println(sameLocalZonedDateTime); // 1969-07-16T09:32+08:00[Asia/Chongqing]
System.out.println(sameLocalZonedDateTime.toInstant()); // 1969-07-16T01:32:00Z
UTC
代表 协调世界时
,这是英文 Coordinated Universal Time 和法文首字母缩写的折中,它与这两种语言中的缩写都不一致。UTC 是不考虑夏令时的格林威治皇家天文台时间
ZonedDateTime
的许多方法都与 LocalDateTime
的方法相同,它们大多数都很直接,但是夏令时带来了一些复杂性
方法 | 描述 |
---|---|
now 、 of 、 ofInstant |
构建一个 ZonedDateTime ,要么从当前时间构建, 要么从一个 LocalDateTime 、一个 LocalDate 、与 ZoneId 一起的年 / 月 / 日 / 分钟 / 秒 / 纳秒,或从一个 Instant 和 ZoneId 中创建。这些都是静态方法 |
plusDays 、 plusWeeks 、 plusMonths 、 plusYears 、 plusHours 、 plusMinutes 、 plusSeconds 、 plusNanos |
在当前的 ZonedDateTime 上加上一定量的时间单位 |
minusDays 、 minusWeeks 、 minuMonths 、 minusYears 、 minusHours 、 minuMinutes 、 minusSeconds 、 minusNanos |
在当前的 ZonedDateTime 上减去一定量的时间单位 |
plus 、 minus |
加上或减去一个 Duration 或 Period |
withDayOfMonth 、 withDayOfYear 、 withMonth 、 withYear 、 withHour 、 withMinute 、 withSecond 、 withNano |
返回一个新的 ZonedDateTime ,其某个时间单位被修改为给定的值 |
withZoneSameInstant 、 withZoneSameLocal |
返回一个给定时区的新的 ZonedDateTime ,要么表示同一个时刻,要么表示同一个本地时间 |
getDayOfMonth |
获取月的日期(在 1 - 31 之间) |
getDayOfYear |
获取年的日期(在 1 - 366 之间) |
getDayOfWeek |
获取星期日期,返回 DayOfWeek 枚举的某个值 |
getMonth 、 getMonthValue |
获取月份的 Month 枚举值, 或者在 1 - 12 之间的数字 |
getYear |
获取年份,在喃999 999 999 - 999 999 |
getHour 、 getMinute 、 getSecond 、 getNano |
获取当前的 ZonedDateTime 的小时、分钟、秒和纳秒 |
getOffset |
获取作为 ZoneOffset 实例的距离 UTC 的偏移量。偏移量在 -12:00 ~+14:00 之间变化。有些时区有小数偏移量。偏移量会随夏令时而发生变化 |
toLocalDate 、 toLocalTime 、 toInstant |
产生本地日期或本地时间 ,或者对应的 Instant 对象 |
isBefore 、 isAfter |
将当前的 ZonedDateTime 与另一个 ZonedDateTime 进行比较 |
当夏令时开始 ,时钟向前拨快一小时。当夏令时结束时,时钟要向回拨慢一小时,这样同一个本地时间就会有出现两次。当你构建位于这个时间段内的时间对象时,就会得到这两个时刻中较早的一个。一个小时后的时间会具有相同的小时和分钟,但是时区的偏移量会发生变化。
在调整跨越夏令时边界的日期时特别注意。例如,如果你将会议设置在下个星期,不要直接加上一个 7
天的 Duration
,而是应该使用 Period
类。
警告:还有一个 OffsetDateTime
类,它表示与 UTC 具有偏移量的时间,但是没有时区规则的束缚。这个类被设计用于专用应用,这些应用特别需要剔除这些规则的约束,例如某些网络协议。对于人类时间,还是应该使用 ZonedDateTime
格式化和解析( DateTimeFormatter
)
DateTimeFormatter
类提供了三种用于打印日期/ 时间值的格式器:
- 预定义的格式器
Locale
相关的格式器- 带有定制模式的格式器
预定义的格式器:
格式器 | 描述 | 示例 |
---|---|---|
BASIC_ISO_DATE |
年、月、日、时区偏移盘,中间没有分隔符 | 19690716-0400 |
ISO_LOCAL_DATE ISO_LOCAL_TIME ISO_LOCAL_DATE_TIME |
分隔符为 - : T |
1969-07-16 09:32:00 1969-07-16T09:32:00 |
ISO_OFFSET_DATE ISO_OFFSET_TIME ISO_OFFSET_DATE_TIME |
类似 ISO_LOCAL_XXX ,但是有时区偏移量 |
1969-07-16-04:00 09:32:00-04:00 1969-07-16T09:32:00-04:00 |
ISO_ZONED_DATE_TIME |
有时区偏移量和时区 ID | 1969-07-16T09:32:00-04:00[America/New_York] |
ISO_INSTANT |
在 UTC 中,用 Z 时区 ID 来表示 | 1969-07-16T13:32:00Z |
ISO_DATE ISO_TIME ISO_DATE_TIME |
类似 ISO_OFFSET_DATE 、ISO_OFFSET_TIME 、ISO_ZONED_DATE_TIME ,但是时区信息是可选的 |
1969-07-16-04:00 09:32:00-04:00 1969-07-16T09:32:00-04:00[America/New_York] |
ISO_ORDINAL_DATE |
LocalDate 的年和年日期 |
1969-197-04:00 |
ISO_WEEK_DATE |
LocalDate 的年、星期和星期日期 |
1969-W29-3-04:00 |
RFC_1123_DATE_TIME |
用于邮件时间戳的标准,编篡于 RFC822 ,并在 RFC1123 中将年份更新到 4 位 | Wed, 16 Jul 1969 09:32:00 -0400 |
使用标准的格式器:
ZonedDateTime apollo11launch = ZonedDateTime.of(1969, 7, 16, 9, 32, 0, 0,
ZoneId.of("America/New_York"));
String format = DateTimeFormatter.BASIC_ISO_DATE.format(apollo11launch);
System.out.println(DateTimeFormatter.BASIC_ISO_DATE);
标准格式器主要是为了机器刻度的时间戳而设计的。为了向人类读者表示日期和时间,可以使用 Locale
相关的格式器。 对于日期和时间而言,有 4 种与 Locale
相关的格式化风格,即 SHORT
、MEDIUM
、LONG
、FULL
,在 Locale.US
的情况下 :
风格 | 日期 | 时间 |
---|---|---|
SHORT |
7/16/69 | 9:32 AM |
MEDIUM |
Jul 16, 1969 | 9:32:00 AM |
LONG |
July 16, 1969 | 9:32:00 AM EDT |
FULL |
Wednesday, July 16, 1969 | 9:32:00 AM EDT |
静态方法 ofLocalizedDate
、 ofLocalizedTime
、 ofLocalizedDateTime
可以创建这种格式器,这些方法使用了默认的 Locale
,为了切换到不同的 Locale
,可以直接使用 withLocale
方法:
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
String formatted = formatter.format(apollo11launch);
System.out.println(formatted); // July 16, 1969 9:32:00 AM EDT
formatted = formatter.withLocale(Locale.FRENCH).format(apollo11launch);
// 16 juillet 1969 09:32:00 EDT
System.out.println(formatted);
DayOfWeek
和 Month
枚举都有 getDisplayName
方法,可以按照不同的 Locale
和格式给出星期日期和月份的名字:
for (DayOfWeek w : DayOfWeek.values()) {
System.out.print(w.getDisplayName(TextStyle.SHORT, Locale.ENGLISH) + " ");
}
// Mon Tue Wed Thu Fri Sat Sun
System.out.println();
for (Month m : Month.values()) {
System.out.print(m.getDisplayName(TextStyle.SHORT, Locale.ENGLISH) + " ");
}
// Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
java.time.format.DateTimeFormatter
类被设计用来替代 java.text.DateFormat
。如果你为了向后兼容性而需要后者的实例,那么可以调用 formatter.toFormat()
最后,可以通过指定模式来定制自己的日期格式:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("E yyyy-MM-dd HH:mm");
String formatted = formatter.format(apollo11launch);
System.out.println(formatted); // 星期三 1969-07-16 09:32
按照显得晦涩且随时间推移不断扩充的规则,每个字母都表示一个不同的时间域,而字母重复的次数对应于所选择的特定格式
常用的日期/时间格式的格式化符号,示例中的结果是在 Locale.US
的情况下 :
时间域或目的 | 示例 |
---|---|
ERA | G : AD GGGG : Anno Domini GGGGG : A |
YEAR_OF_ERA | yy : 69 yyyy : 1969 |
MONTH_OF_YEAR | M : 7 MM : 07 MMM : Jul MMMM : July MMMMM : J |
DAY_OF_MONTH | d : 6 dd : 06 |
DAY_OF_WEEK | e : 3 E : Wed EEEE : Wednesday EEEEE : W |
HOUR_OF_DAY | H : 9 HH : 09 |
CLOCK_HOUR_OF_AM_PM | K : 9 KK : 09 |
AMPM_OF_DAY | a : AM |
MINUTE_OF_HOUR | mm : 02 |
SECOND_OF_MINUTE | ss : 00 |
NANO_OF_SECOND | nnnnnn : 000000 |
时区 ID | VV : America/New_York |
时区名 | z : EDT zzzz : Eastern Daylight Time |
时区偏移盘 | x : -04 xx : -0400 xxx : -04:00 XXX : 与 xxx 相同,但是 Z 表示 0 |
本地化的时区偏移盘 | 0 : GMT-4 0000 : GMT-04:00 |
为了解析字符串中的日期/时间值,可以使用众多的静态 parse
方法之一:
// 使用了标准的 ISO_LOCAL_DATE 格式器
LocalDate churchsBirthday = LocalDate.parse("1903-06-14");
// 使用的是一个定制的格式器
ZonedDateTime apollo11launch = ZonedDateTime.parse("1969-07-16 03:32:00-0400",
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssxx"));
与遗留代码的互操作
Instant
类近似于 java.util.Date
。在 Java SE 中,这个类有两个额外的方法:将 Date
转换为 Instant
的 toInstant
方法,以及反方向转换的静态的 from
方法
ZonedDateTime
近似于 java.util.GregorianCalendar
,在 Java SE 中,这个类有细粒度的转换方法。toZonedDateTime
方法可以将 GregorianCalendar
转换为 ZonedDateTime
,而静态的 from
方法可以执行反方向的转换
另一个可用于日期和时间类的转换集位于 java.sql
包中。你还可以传递一个 DateTimeFormatter
给使用 java.text.Format
的遗留代码
类 | to 遗留类 | from 遗留类 |
---|---|---|
Instant 、java.util.Date |
Date.from(instant) |
date.toInstant() |
ZonedDateTime 、java.util.GregorianCalendar |
GregorianCalendar.from(zonedDateTime) |
cal.toZonedDateTime() |
Instant 、java.sql.Timestamp |
Timestamp.from(instant) |
timestamp.toInstant() |
LocalDateTime 、java.sql.Timestamp |
Timestamp.valueOf(localDateTime) |
timestamp.toLocalDateTime() |
LocalDate 、java.sql.Date |
Date.valueOf(localDate) |
date.toLocalDate() |
LocalTime 、java.sql.Time |
Time.valueOf(localTime) |
time.toLocalTime() |
DateTimeFormatter 、java.text.DateFormat |
formatter.toFormat() |
无 |
java.util.TimeZone 、ZoneId |
Timezone.getTimeZone(id) |
timeZone.toZoneId() |
java.nio.file.attribute.FileTime 、Instant |
FileTime.from(instant) |
fileTime.toInstant() |