20230629 6. 日期和时间 API

日期和时间 API

为什么处理时间会如此之难呢?问题出在人类自身上

  • Java 1.0 有一个 Date 类,事后证明它过于简单了
  • 当 Java 1.1 引入 Calendar 类之后,Date 类中的大部分方法就被弃用了。但是, Calendar API 还不够给力,它的实例是易变的,并且它没有处理诸如闰秒这样的问题。
  • 第 3 次升级很吸引人,那就是 Java SE 中引入的 java.time API ,它修正了过去的缺陷,并且应该会服役相当长的一段时间。

时间线( InstantDuration

在历史上,基本的时间单位 是从地球的自转中推导出来的。地球自转一周需要 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() 会给出当前的时刻。你可以按照常用的方式,用 equalscompareTo 方法来比较两个 Instant 对象,因此你可以将 Instant 对象用作时间戳

为了得到两个时刻之间的时间差,可以使用静态方法 Duration.between

Duration 是两个时刻之间的时间量。 你可以通过调用 toNanostoMillisgetSecondstoMinutestoHourstoDays 获得 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();

用于时间的 InstantDuration 的算术运算

方法 描述
plusminus 在当前的 InstantDuration 上加上或减去一个 Duration
plusNanosplusMillisplusSecondsminusNanosminusMillisminusSeconds 在当前的 InstantDuration 上加上或减去给定时间单位
plusMinutesplusHoursplusDaysminusMinutesminusHoursminusDays 在当前 Duration 上加上或减去给定时间单位的数值
multipliedBydividedBynegated 返回由当前的 Duration 乘以或除以给定 long-1 而得到的 Duration注意,可以缩放 Duration ,但是不能缩放 Instant
isZeroisNegative 检查当前的 Duration 是否是 0 或负值

InstantDuration 类都是不可修改的类,所以诸如 multipliedByminus 这样的方法都会返回一个新的实例

本地日期( LocalDatePeriod

让我们从绝对时间转移到人类时间。 Java API 中有两种 人类时间本地日期/时间时区时间 。本地日期/时间包含日期和当天的时间,但是与时区信息没有任何关联。1903 年 6 月 14 日就是一个本地日期的示例( lambda 的发明者 Alonzo Church 在这一天诞生)。因为这个日期既没有当天的时间,也没有时区信息,因此它并不对应精确的时刻。 与之相反的是, 1969 年 7 月 16 日 09:32:00 EDT (阿波罗 11 号发射的时刻)是一个时区日期/时间,表示的是时间线上的一个精确的时刻。

有许多计算并不需要时区,在某些情况下,时区甚至是一种障碍。 假设你安排每周 10:00 开一次会。如果你加 7 天到最后一次会议的时区时间上,那么你可能会碰巧跨越了夏令时的时间调整边界,这次会议可能会早一小时或晚一小时!

API 的设计者们推荐程序员不要使用时区时间,除非确实想要表示绝对时间的实例。生日、假日、 计划时间等通常最好都表示成本地日期和时间。

LocalDate 是带有年、月、 日的日期。为了构建 LocalDate 对象,可以使用 nowof 静态方法:

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 对象的方法:

方法 描述
nowof 这些静态方法会构建一个 LocalDate ,要么从当前时间构建,要么从给定的年、月、日构建
plusDaysplusWeeksplusMonthsplusYears 在当前的 LocalDate 上加上一定量的天、星期、月或年
minusDaysminusWeeksminusMonthsminusYears 在当前的 LocalDate 上减去一定量的天、星期、月或年
plusminus 加上或减去一个 DurationPeriod
withDayOfMonthwithDayOfYearwithMonthwithYear 返回一个新的 LocalDate 。其月的日期、年的日期、月或年修改为给定的值
getDayOfMonth 获取月的日期(在 131 之间)
getDayOfYear 获取年的日期(在 1366 之间)
getDayOfWeek 获取星期日期,返回 DayOfWeek 枚举值
getMonthgetMonthValue 获取月份的 Month 枚举值,或者是 1 - 12 之间的数
getYear 获取年份,在 -999 999 999 到 999 999 999 之间
until 获取 Period ,或者两个日期之间按照给定的 ChronoUnits 计算的数值
isBeforeisAfter 将当前的 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 枚举具有便捷方法 plusminus ,以 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 之外,还有 MonthDayYearMonthYear 类可以描述部分日期。例如, 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 。可以用 nowof 方法创建其实例:

LocalTime rightNow = LocalTime.now();
LocalTime bedtime = LocalTime.of(22, 30); // or LocalTime.of(22, 30, 0)

plusminus 操作是按照一天 24 小时循环操作的

LocalTime 自身并不关心 AM/PM 。这种愚蠢的设计将问题抛给格式器去解决

LocalTime 的方法:

方法 描述
nowof 这些静态方法会构建一个 LocalTime ,要么从当前时间构建,要么从给定的小时和分钟,以及可选的秒和纳秒构建
plusHoursplusMinutesplusSecondsplusNanos 在当前的 LocalTime 上加上一定量的小时、分钟、秒或纳秒
minusHoursminusMinutesminusSecondsminusNanos 在当前的 LocalTime 上减去一定量的小时、分钟、秒或纳秒
plusminus 加上或减去一个 Duration
withHourwithMinutewithSecondwithNano 返回一个新的 LocalTime ,其小时、分钟、秒和纳秒修改为给定的值
getHourgetMinutegetSecondgetNano 获取当前 LocalTime 的小时、分钟、秒或纳秒
toSecondOfDaytoNanoOfDay 返回午夜到当前 LocalTime 的秒或纳秒的数量
isBeforeisAfter 将当前的 LocalTime 与另一个 LocalTime 进行比较

还有一个表示日期和时间的 LocalDateTime 类。这个类适合存储固定时区的时间点,例如,用于排课或排程。但是,如果你的计算需要跨越夏令时,或者需要处理不同时区的用户,那么就应该使用 ZonedDateTime

时区时间( ZonedDateTime

时区,可能是因为完全是人为创造的原因,它甚至比地球不规则的转动引发的复杂性还要麻烦。在理性的世界中,我们都会遵循格林尼泊时间。有些人在 2:00 吃午饭,而有些人却在 22:00 吃午饭。这就是中国的做法,中国横跨了多个时区,但是使用了同一个时间(东八区 UTC+8)。在其他地方,时区显得并不规则,并且还有国际日期变更线,而夏令时使事情变得更糟了

互联网编码分配管理机构( Internet Assigned Numbers Authority, IANA )保存着一个数据库,里面存储着世界上所有已知的时区,它每年会更新数次,而批量更新会处理夏令时的变更规则。Java 使用了 IANA 数据库

每个时区都有一个 ID ,例如 America/New_YorkEurope/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 的方法相同,它们大多数都很直接,但是夏令时带来了一些复杂性

方法 描述
nowofofInstant 构建一个 ZonedDateTime ,要么从当前时间构建, 要么从一个 LocalDateTime 、一个 LocalDate 、与 ZoneId 一起的年 / 月 / 日 / 分钟 / 秒 / 纳秒,或从一个 InstantZoneId 中创建。这些都是静态方法
plusDaysplusWeeksplusMonthsplusYearsplusHoursplusMinutes 、 plusSeconds 、 plusNanos 在当前的 ZonedDateTime 上加上一定量的时间单位
minusDaysminusWeeksminuMonthsminusYearsminusHoursminuMinutesminusSecondsminusNanos 在当前的 ZonedDateTime 上减去一定量的时间单位
plusminus 加上或减去一个 DurationPeriod
withDayOfMonthwithDayOfYearwithMonthwithYearwithHourwithMinutewithSecondwithNano 返回一个新的 ZonedDateTime ,其某个时间单位被修改为给定的值
withZoneSameInstantwithZoneSameLocal 返回一个给定时区的新的 ZonedDateTime ,要么表示同一个时刻,要么表示同一个本地时间
getDayOfMonth 获取月的日期(在 1 - 31 之间)
getDayOfYear 获取年的日期(在 1 - 366 之间)
getDayOfWeek 获取星期日期,返回 DayOfWeek 枚举的某个值
getMonthgetMonthValue 获取月份的 Month 枚举值, 或者在 1 - 12 之间的数字
getYear 获取年份,在喃999 999 999 - 999 999
getHourgetMinutegetSecondgetNano 获取当前的 ZonedDateTime 的小时、分钟、秒和纳秒
getOffset 获取作为 ZoneOffset 实例的距离 UTC 的偏移量。偏移量在 -12:00 ~+14:00 之间变化。有些时区有小数偏移量。偏移量会随夏令时而发生变化
toLocalDatetoLocalTimetoInstant 产生本地日期或本地时间 ,或者对应的 Instant 对象
isBeforeisAfter 将当前的 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_DATEISO_OFFSET_TIMEISO_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 相关的格式化风格,即 SHORTMEDIUMLONGFULL ,在 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

静态方法 ofLocalizedDateofLocalizedTimeofLocalizedDateTime 可以创建这种格式器,这些方法使用了默认的 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);

DayOfWeekMonth 枚举都有 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 转换为 InstanttoInstant 方法,以及反方向转换的静态的 from 方法

ZonedDateTime 近似于 java.util.GregorianCalendar ,在 Java SE 中,这个类有细粒度的转换方法。toZonedDateTime 方法可以将 GregorianCalendar 转换为 ZonedDateTime ,而静态的 from 方法可以执行反方向的转换

另一个可用于日期和时间类的转换集位于 java.sql 包中。你还可以传递一个 DateTimeFormatter 给使用 java.text.Format 的遗留代码

to 遗留类 from 遗留类
Instantjava.util.Date Date.from(instant) date.toInstant()
ZonedDateTimejava.util.GregorianCalendar GregorianCalendar.from(zonedDateTime) cal.toZonedDateTime()
Instantjava.sql.Timestamp Timestamp.from(instant) timestamp.toInstant()
LocalDateTimejava.sql.Timestamp Timestamp.valueOf(localDateTime) timestamp.toLocalDateTime()
LocalDatejava.sql.Date Date.valueOf(localDate) date.toLocalDate()
LocalTimejava.sql.Time Time.valueOf(localTime) time.toLocalTime()
DateTimeFormatterjava.text.DateFormat formatter.toFormat()
java.util.TimeZoneZoneId Timezone.getTimeZone(id) timeZone.toZoneId()
java.nio.file.attribute.FileTimeInstant FileTime.from(instant) fileTime.toInstant()
posted @ 2023-09-05 09:26  流星<。)#)))≦  阅读(35)  评论(0编辑  收藏  举报