六、日期和时间API
6.1 时间线
-
Java 的Date 和TimeAPI 规范要求Java 使用的时间尺度为:
- 每天86 400 秒
- 每天正午与官方时间精确匹配
- 在其他时间点上,以精确定义的方式与官方时间接近匹配
-
在Java 中, Instant 表示时间线上的某个点。Instant 的值向回可追溯10 亿年( Instant.MIN ) 。最大的值 Instant . MAX 是公元I 000 000 000 年的12 月31 日。被称为“新纪元”的时间线原点被设置为穿过伦敦格林威治皇家天文台的本初子午线所处时区的1970 年1 月1 日的午夜。这与UNIX/POSIX 时间中使用的惯例相同。从该原点开始, 时间按照每天 86400 秒向前或向回度批,精确到纳秒。
-
两个时刻之间的时间差,可以使用静态方法 Duration.between 。Duration 是两个时刻之间的时间量。你可以通过调用toNanos 、toMillis 、get Seconds 、toMinutes 、toHours 和toDays 来获得Duration 按照传统单位度量的时间长度。Duration 对象的内部存储所需的空间超过了一个long 的值,因此秒数存储在一个long 中,而纳秒数存储在一个额外的int 中。
-
大约300 年时间对应的纳秒数才会溢出long 的范围。
-
Instant 和Duration 类都是不可修改的类,所以诸如multi pl iedBy 和minus这样的方法都会返回一个新的实例。
Duration timeElapsed2 = Duration.between(start2, end2); boolean overTenTimesFaster = timeElapsed.mu1tip1iedBy(10).minus(timeElapsed2).isNegative();
-
6.2 本地时间
-
Local Date 是带有年、月、日的日期。为了构建Local Date 对象,可以使用now 或of静态方法。与UNIX 和java . util.Date 中使用的月从0 开始计算而年从1900 开始计算的不规则的惯用法不同,你需要提供通常使用的月份的数字。或者,你可以使用Month 枚举。
LocalDate today = Local Date.now(); // Today's date LocalDate alonzosBirthday = LocalDate.of(1903, 6, 14); alonzosBirthday = LocalDate.of(1903, Month.JUNE, 14);
-
两个Instant 之间的时长是Duration,而用于本地日期的等价物是Period,它表示的是流逝的年、月或日的数量。
- 可以调用birthday.plus(Period.ofYears(1))来获取下一年的生日。
- util 方法会产生两个本地日期之间的时长。
- 有些方法可能会创建出并不存在的日期。如,在1 月31 日上加上1 个月不应该产生2 月31 日。这些方法并不会抛出异常,而是会返回该月有效的最后一天。
- getDayOfWeek 会产生星期日期,即DayOfWeek 枚举的某个值。
-
除了Local Date 之外,还有MonthDay 、YearMonth 和Year 类可以描述部分日期。如, 12 月25 日( 没有指定年份)可以表示成一个MonthDay 对象。
6.3 日期调整器
-
TemporalAdjusters 类提供了大量用于常见调整的静态方法。你可以将调整方法的结果传递给with 方法。with 方法会返回一个新的Local Date 对象,而不会修改原来的对象。
// 计算某个月的第一个星期二 LocalDate firstTuesday = LocalDate.of(year, mouth, 1).with( TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY) );
-
通过实现TemporalAdjuster 接口来创建自己的调整器。
TemporalAdjuster NEXT_WORKDAY = temporal -> { LocalDate result = (LocalDate) temporal; do { result = result.plusDays(1); } while (result.getDayOfWeek().getValue() >= 6); return result; }; LocalDate backToWork = today.with(NEXT_WORKDAY);
以下方法可以避免强制转化
TemporalAdjuster NEXT_WORKDAY = TemporalAdjusters.ofDateAdjuster(temporal -> { LocalDate result = temporal; do { result = result.plusDays(1); } while (result.getDayOfWeek().getValue() >= 6); return result; });
6.4 本地肘间
-
LocalTime 表示当日时刻。可以用now 或of 方法创建其实例
LocalTime rightNow = LocalTime.now(); LocalTime bedtime = LocalTime.of(22, 30); // or LocalTime.of (22, 30, 0)
plus 和minus 操作是按照一天24 小时循环操作的。常见的对本地时间的操作如下
-
日期和时间的LocalDateTime 类,这个类适合存储固定时区的时间点。如果你的计算需要跨越夏令时,或者需要处理不同时区的用户,那么就应该使用接下来要讨论的ZonedDateTi me 类。
6.5 时区时间
-
每个时区都有一个ID , 例如America/New_York 和Europe/Berlin,调用
ZoneId.getAvailableZonelds
列出所有可用的时区。 -
给定一个时区ID , 静态方法
Zoneld.of(id)
可以产生一个Zoneld 对象。可以通过调用local.atZone(zoneld)
用这个对象将LocalDateTime 对象转换为ZonedDateTime 对象,或者可以通过调用静态方法ZonedDateTime.of(year, month, day, hour, minute, second, nano, zoneld)
来构造一个ZonedDateTime 对象。ZonedDateTime apollolllaunch = ZonedDateTime.of(1969, 7, 16, 9, 32, 0, 0, ZoneId.of(" America/New_ York")); // 获取Instant对象 apollolllaunch.toInstant() // 从Instant获得格林威治皇家天文台的 ZonedDateTime 对象 instant.atZone(Zoneld.of("UTC”))
UTC 是不考虑夏令时的格林威治皇家天文台时间。
-
ZonedDateTime 的许多方法都与LoealDateTime 的方法相,它们大多数都很直接,但是夏令时带来了一些复杂性。
-
夏令时
-
当夏令时开始时,时钟要向前拨快一小时。
ZonedDateTime skipped = ZonedDateTime.of( LocalDate.of(2013, 3, 31), LocalTime.of(2, 30), ZoneId.of("Europe/Berlin")); // Constructs March 31 3: 30
-
当夏令时结束时,时钟要向回拨慢一小时,这样同一个本地时间就会有出现两次。当你构建位千这个时间段内的时间对象时,就会得到这两个时刻中较早的一个
ZonedDateTime ambiguous = ZonedDateTime.of( LocalDate.of(2013, 10, 27), // End of daylight savings ti me LocalTime.of(2, 30), ZoneId.of("Europe/Berlin")); // 2013-10-27T02: 30+02: 00 [Europe/Berlin] ZonedDateTime anHourlater = ambiguous.plusHours(1); // 2013-10-27T02: 30+01: 00 [Europe/Berlin]
一个小时后的时间会具有相同的小时和分钟,但是时区的偏移量会发生变化。
-
你还需要在调整跨越夏令时边界的日期时特别注意。
// 如果你将会议设置在下个星期,不要直接加上一个7 天的Duration ZonedDateTime nextMeeting = meeting.plus(Duration.ofDays(7)); // X // Caution! Won't work with daylight savings ti // 而是应该使用Period 类。 ZonedDateTime nextMeeting = meeting.plus(Period.ofDays(7));
-
6.6 格式化和解析
-
DateTimeFormatter 类提供了三种用千打印日期/ 时间值的格式器
-
预定义的格式器
-
Locale 相关的格式器
-
带有定制模式的格式器
-
-
使用标准的格式器
String formatted = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(apollolllaunch);
标准格式器主要是为了机器刻度的时间戳而设计的。
-
为了向人类读者表示日期和时间,可以使用Locale 相关的格式器。对于日期和时间而言,有4 种与Locale 相关的格式化风格,即SHORT 、MEDIUM 、LONG 和FULL
-
静态方法 ofLocalizedDate 、ofLocalizedTime 和ofLocalizedDateTime 可以创建这种格式器。
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG); String formatted = formatter.format(apollolllaunch); // July 16, 1969 9:32:00 AM EDT
-
上述静态方法使用了默认的Locale。为了切换到不同的Locale, 可以直接使用withlocale 方法。
formatter.withLocale(Locale.FRENCH).format(apollolllaunch);
-
DayOfWeek 和Month 枚举都有getDisplayName 方法,可以按照不同的Local e 和格式给出星期日期和月份的名字。
for (DayOfWeek w : DayOfWeek.values()) System.out.print(w.getDisplayName(TextStyle.SHORT, Locale.ENGLISH) + " "); // Prints Mon Tue Wed Thu Fri Sat Sun
-
通过指定模式来定制自己的日期格式。按照显得晦涩且随时间推移不断扩充的规则, 每个字母都表示一个不同的时间域,而字母重复的次数对应千所选择的特定格式。
formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
最有用的模式元素如下所示。
-
6.7 与遗留代码的互操作
-
java.time 类与遗留类之间的转换
评论区