java.time包模块解析
前言
为什么要写这个文章呢, 因为最近开发了一个jdk时间相关的bug, 发现自己对java中的time模块了解很薄弱, 所以打算再深入学习一下这块内容
遇到的bug详情: LocalDate.ofEpochDay(days)踩坑记录
背景
在1.8以前, 我们通常使用 Date / Calendar 等util类来进行时间的描述与操作, 到目前为止Date还是被广泛使用中, 但是Date类有几点不足:
- 粒度不够细: 最小维度为毫秒, 不支持纳秒级别
- 无时区: Date固定采用TimeZone中给定的默认时区
- 不够便捷: 必须通过Calendar类进行时间操作
- 变量歧义: Calendar中, 描述月份不是从1~12而是从0~11; 描述星期时第1天是周日而不是周一;
- 不够抽象: 一个Date为特定的时刻, 无法单纯的描述某一年或某一天
- ...
因此, jdk1.8版本在java.time包下新增了一系列的类, 试图解决以上不足
解析
我们直接来看java.time的包结构, 可以看到它由主包加4个子包构成, 接下来我就逐个对这些包以及包内的类进行解析
java.time(主包)
主包下都是一些十分常用的类, 比如 Clock / LocalDateTime / LocalDate / LocalTime / Duration 等等
Clock
时钟类, 可以把它理解为一个虚拟的钟表, 我们可以通过时钟随时获取当前时刻(时间戳), 也可以获取当前时钟所在的时区
Clock只是一个抽象类, 内部提供了4个它的实现
SystemClock: 这是一个会跟随时间流逝走动的时钟, 这一秒是12:13:45, 下一秒就是12:13:46, 哈哈是不是很神奇... 事实上它只是在我们每次获取时间戳时返回了System.currentTimeMillis(), 所以给人一种会走动的错觉, 我们可以这样创建一个与系统时间同步的虚拟时钟
Clock clock = Clock.systemDefaultZone()
FixedClock: 它就像是一个没有电的时钟, 停留在某一个时刻, 那它跟Instant还有啥区别呢? 区别还是有的, 虽然它不会动, 但是至少它知道自己在哪个时区...而Instant是没有时区概念的. 我们可以这样创建一个停留在创建时间的时钟
Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
OffsetClock: 这是一个有时差的时钟, 它总是比给定的对照时钟快了或慢了固定的时间, 我们可以这样创建一个时钟比机器时间慢1小时的时钟
Clock clock = Clock.offset(Clock.systemDefaultZone(), Duration.ofHours(-1));
TickClock: 这是一个可以自定义步长的时钟, 比如我们可以设置1s为一个步长, 那么不足1s的部分(毫秒/纳秒)将被抹去, 如果设置步长为1分钟, 那么不足1分钟的部分将被抹去, 如果设置12s为一个步长, 那么不足12s的部分将被抹去
Clock tickClock = Clock.tick(Clock.systemDefaultZone(), Duration.ofSeconds(1));
// 以下逻辑会将 2022-02-07T02:23:54.136Z 四舍五入为 2022-02-07T02:23:48Z , 因为步长为12s, 54处在48与60之间, 所以将超出48的部分抹去了
Clock tickClock = Clock.tick(Clock.systemDefaultZone(), Duration.ofSeconds(12));
Instant
时刻类, 专门用于描述某一个瞬间, 可以将它理解为一个时间戳, 与System.currentTimeMillis()类似, 它们都没有时区的概念, 都以1970/1/1 UTC开始计算, 只是精度不同, Instant可以精确到纳秒级别
如上图, Instant使用两个变量来描述一个瞬间, seconds 表示自1970/1/1至目标时刻所经历的秒数, 超出当前秒的时间则使用nanos来补充, nanos用来描述那不足一秒的时间, 所以nanos的值永远不会达到1000000000(10亿纳秒 = 1s)
常用的静态方法
now(): 获取当前时刻
now(Clock): 根据给定的时钟获取当前时刻
ofEpochSecond(long, [long]):
from(TemporalAccessor):
ofEpochMilli(long):
parse(CharSequence):
常用的实例方法
isBefore(Instant): 是否比给定的时刻早
isAfter(Instant): 是否比给定的时刻晚
toEpochMilli(): 返回当前时刻的毫秒级时间戳 (如果Instant是当前时刻的话就与System.currentTimeMillis()相同了)
plus(*): 在当前基础上, 在增加指定的时间, 比如: instant.plus(3, ChronoUnit.SECONDS) 表示增加3秒
minus(*): 与plus()相反, minus(1); = plus(-1);
with(*): 修改时间戳并生成一个新的时间戳, 支持4个维度修改, 毫秒/微妙/纳秒/秒, 除了秒以外, 其他三个维度都只会更改nanos字段值, seconds字段值保持不变, 如: instant = instant.with(ChronoField.MILLI_OF_SECOND, 500); 表示把当前时间戳的nanos字段值更改为500000000
LocalDate
本地日期类, 它用来描述历史上的某一天, 包含 年月日 三个基本信息, 它是一个不可变的类, 只能描述一个固定的日期, 它的数据结构如下:
常用的静态方法
now(): 获取当前日期
now(Clock): 根据给定的时钟获取当前日期
of(int, int/Month, int): 根据给定的年月日信息构造一个日期对象
parse(CharSequence, [DateTimeFormatter]): 根据给定的字符串, 解析为一个日期
常用的实例方法
format(DateTimeFormatter): 将日期根据指定的格式序列化为字符串
atStartOfDay(): 以当前日期的0点为时刻构造一个LocalDateTime
atTime(LocalTime): 以当前日期与给定的时间构造一个LocalDateTime
atTime(int, int, int): 以当前日期与给定的 时/分/秒 构造一个LocalDateTime
getDayOfMonth(): 当前日期是当前月的第几天, 2月7日则为7
getDayOfWeek(): 当前日期为星期几
getDayOfYear(): 当前日期为当前年份的第几天
getMonth(): 当前日期的月份
getMonthValue(): 当前日期的月份值
getYear(): 当前日期的年份
isLeapYear(): 当期日期的年份是否闰年
lengthOfMonth(): 当前月一共有几天
minus(long, TemporalUnit): 在当前日期下减去指定时间, 并返回一个新的日期
plus(long, TemporalUnit): 在当前日期下加上指定时间, 并返回一个新的日期
with(TemporalField, long): 重置当前日期中的 年份或月份或日, 并返回一个新的日期
LocalTime
本地时间类, 他用来描述一天中的某一个时间点, 与LocalDate不同的是, LocalDate仅描述年月日, 而LocalTime则仅描述时分秒以及纳秒, 它的数据结构如下:
常用的静态方法
now(): 获取当前时间
now(Clock): 根据给定的时钟获取当前时间
of(int, int, int): 根据给定的时分秒信息构造一个时间对象
parse(CharSequence, [DateTimeFormatter]): 根据给定的字符串, 解析为一个时间
常用的实例方法
format(DateTimeFormatter): 将时间根据指定的格式序列化为字符串
atDate(LocalDate): 以当前时间与给定的日期构造一个LocalDateTime
getHour()/getMinute()/getSecond()/getNano(): 获取当前时间的 时分秒/纳秒
isAfter(LocalTime): 是否比指定时间晚
isBefore(LocalTime): 是否比指定时间早
minus(long, TemporalUnit): 在当前时间下减去指定时间, 并返回一个新的时间
plus(long, TemporalUnit): 在当前时间下加上指定时间, 并返回一个新的时间
with(TemporalField, long): 重置当前时间中的 时分秒/纳秒, 并返回一个新的时间
LocalDateTime
本地时间类, 它用来描述历史上的某一个时间点, 包括 年/月/日/时/分/秒/纳秒, 它完整的包含了LocalDate与LocalTime的信息, 事实上, 它底层就是这两者的组合, 数据结构如下:
常用的静态方法
now(): 获取当前时间
now(Clock): 根据给定的时钟获取当前时间
of(LocalDate, LocalTime): 根据给定的日期与时间构造一个LocalDateTime
of(int, int, int...): 根据给定的年月日时分秒等信息构造一个LocalDateTime
parse(CharSequence, [DateTimeFormatter]): 根据给定的字符串, 解析为一个LocalDateTime
常用的实例方法
toLocalTime(): 返回LocalTime部分
toLocalDate(): 返回LocalDate部分
isAfter(LocalDateTime): 是否比指定时间晚
isBefore(LocalDateTime): 是否比指定时间早
format(DateTimeFormatter): 将时间根据指定的格式序列化为字符串
getDayOfMonth(): 当前日期是当前月的第几天, 2月7日则为7
getDayOfWeek(): 当前日期为星期几
getDayOfYear(): 当前日期为当前年份的第几天
getMonth(): 当前日期的月份
getMonthValue(): 当前日期的月份值
getYear(): 当前日期的年份
getHour()/getMinute()/getSecond()/getNano(): 获取当前时间的 时分秒/纳秒
minus(long, TemporalUnit): 在当前时间下减去指定时间, 并返回一个新的时间
plus(long, TemporalUnit): 在当前时间下加上指定时间, 并返回一个新的时间
with(TemporalField, long): 重置当前时间中的 时分秒/纳秒, 并返回一个新的时间
format(DateTimeFormatter): 将日期根据指定的格式序列化为字符串
YearMonth
年月类, 专门用来描述某一年的某一个月, 它的数据结构如下:
MonthDay
月日类, 专门用来描述某一个月的某一天, 常见的使用场景为生日, 它的数据结构如下:
Year
年分类, 专门用来描述某一年, 如 2022 年, 它的数据结构如下:
DayOfWeek / Month
这是两个枚举类, 顾名思义
DayOfWeek描述了周一到周日一共7个枚举值, 并且它们所映射的数值分别是1~7, 可以通过 DayOfWeek.of(3); 来获得一个周三的枚举, 是不是很好理解
Month描述了一年中的十二个月, 所以它有12个枚举值, 并且映射的数值分别是1~12, 与 DayOfWeek一样, 它也可以通过 Month.of(1); 来获取1月的枚举
Duration
持续时间, 它用来描述一段时间, 比如 10秒, 30秒, 3个小时等等, 它底层也是使用了两个变量来记录持续时间, seconds记录超过1s的那部分, nanos记录不足1s的部分, 数据结构如下:
常用的静态方法
ofNanos(long): 以纳秒为入参单位构造持续时间对象
ofMillis(long): 以毫秒为入参单位构造持续时间对象
ofSeconds(long, [long])): 以秒为入参单位构造持续时间对象
ofMinutes(long): 以分钟为入参单位构造持续时间对象
ofHours(long): 以小时为入参单位构造持续时间对象
ofDays(long): 以天为入参单位构造持续时间对象
of(long, TemporalUnit): 以指定时间单位与数值构造持续时间对象
parse(CharSequence): 根据给定的字符串解析为一个Duration, 如: PT3S
常用的实例方法
toNanos(): 将单位转化为纳秒, 并返回对应的数值
toMillis(): 将单位转化为毫秒, 并返回对应的数值
toMinutes(): 将单位转化为分钟, 并返回对应的数值
toHours(): 将单位转化为小时, 并返回对应的数值
toDays(): 将单位转化为天, 并返回对应的数值
minus(Duration): 减去指定的持续时间, 返回一个新的持续时间
minus(long, TemporalUnit): 减去指定的持续时间, 返回一个新的持续时间
minusXXX(long): 以特定的时间单位减去持续时间, 返回新的持续时间
plus(long, TemporalUnit): 加上指定的持续时间, 返回一个新的持续时间
plusXXX(long): 以特定的时间单位加上持续时间, 返回新的持续时间
negated(): 反转持续时间, 如3秒反转为-3秒
isNegative(): 是否负数
isZero(): 是否为0
OffsetTime
时区时间, 它用来描述某一个时区的本地时间, 为什么不直接使用 LocalTime, 因为LocalTime对象中并没有保存时区信息, 我们拿到一个LocalTime对象无法反推UTC时间或者其他时区的时间, OffsetTime则可以, 因为它除了保存了LocalTime以外, 还保存了ZoneOffset(时区偏移量), 数据结构如下:
常用的静态方法
now(): 以当前时区的本地时间构造OffsetTime
now(Clock): 以给定的时钟构造OffsetTime
of(LocalTime, ZoneOffset): 以给定的LocalTime与ZoneOffset构造OffsetTime
of(int, int, int, int, ZoneOffset): 以给定的时分秒/纳秒与ZoneOffset构造OffsetTime
ofInstant(Instant, ZoneId): 以给定的时间戳与时区id构造OffsetTime
parse(CharSequence, [DateTimeFotmatter]): 根据给定的字符串与格式解析为一个OffsetTime, 默认的格式为: 14:40:35.024+08:00
常用的实例方法
toLocalTime(): 转化为本地时间LocalTime
getSecond(): 获取时间中的秒值
getMinute(): 获取时间中的分钟值
getHour(): 获取时间中的小时值
getNano(): 获取时间中的纳秒值(不足1s的部分)
compareTo(OffsetTime): 与另一个OffsetTime比较大小
isAfter(LocalDateTime): 是否比指定时间晚
isBefore(LocalDateTime): 是否比指定时间早
minus(long, TemporalUnit): 减去指定的时间, 返回一个新的时间
minusXXX(long): 以特定的时间单位减去时间, 返回新的时间
plus(long, TemporalUnit): 加上指定的时间, 返回一个新的时间
plusXXX(long): 以特定的时间单位加上时间, 返回新的时间
with(TemporalField, long): 将特定单位的值替换成新值, 比如: 12:30:45, 把秒替换成15
withXXX(long): 将特定单位的值替换成新值
format(DateTimeFormatter): 将日期根据指定的格式序列化为字符串
OffsetDateTime
时区时间, 它用来描述某一个时区的本地时间, 与OffsetTime类似, 为什么不直接使用 LocalDateTime, 因为LocalDateTime对象中并没有保存时区信息, 我们拿到一个LocalDateTime对象无法反推UTC时间或者其他时区的时间, OffsetDateTime则可以, 因为它除了保存了LocalDateTime以外, 还保存了ZoneOffset(时区偏移量), 数据结构如下:
常用的静态方法
now(): 以当前时区的本地时间构造OffsetDateTime
now(Clock): 以给定的时钟构造OffsetDateTime
of(LocalDateTime, ZoneOffset): 以给定的LocalDateTime与ZoneOffset构造OffsetDateTime
of(LocalDate, LocalTime, ZoneOffset): 以给定的LocalDate/LocalTime与ZoneOffset构造OffsetDateTime
of(int, int..., ZoneOffset): 以给定的年月日时分秒/纳秒与ZoneOffset构造OffsetDateTime
ofInstant(Instant, ZoneId): 以给定的时间戳与时区id构造OffsetDateTime
parse(CharSequence, [DateTimeFotmatter]): 根据给定的字符串与格式解析为一个OffsetDateTime, 默认的格式为: 2022-02-07T14:40:35.024+08:00
常用的实例方法
toLocalTime(): 转化为本地时间LocalTime
toLocalDate(): 转化为本地时间LocalDate
toLocalDateTime(): 转化为本地时间LocalDateTime
toInstant(): 转化为时间戳
toOffsetTime(): 转化为OffsetTime
getDayOfMonth(): 当前日期是当前月的第几天, 2月7日则为7
getDayOfWeek(): 当前日期为星期几
getDayOfYear(): 当前日期为当前年份的第几天
getMonth(): 当前日期的月份
getMonthValue(): 当前日期的月份值
getYear(): 当前日期的年份
getSecond(): 获取时间中的秒值
getMinute(): 获取时间中的分钟值
getHour(): 获取时间中的小时值
getNano(): 获取时间中的纳秒值(不足1s的部分)
compareTo(OffsetTime): 与另一个OffsetTime比较大小
isAfter(LocalDateTime): 是否比指定时间晚
isBefore(LocalDateTime): 是否比指定时间早
minus(long, TemporalUnit): 减去指定的时间, 返回一个新的时间
minusXXX(long): 以特定的时间单位减去时间, 返回新的时间
plus(long, TemporalUnit): 加上指定的时间, 返回一个新的时间
plusXXX(long): 以特定的时间单位加上时间, 返回新的时间
with(TemporalField, long): 将特定单位的值替换成新值, 比如: 12:30:45, 把秒替换成15
withXXX(long): 将特定单位的值替换成新值
format(DateTimeFormatter): 将日期根据指定的格式序列化为字符串
Period
周期类, 它也是用来描述一个持续时间的, 与Duration雷同, 他们之间的关系可以类比 LocalDate 与 LocalTime 的关系, Period的最细粒度为天, 它的数据结构如下:
常用的静态方法
ofDays(int): 以天为入参单位构造持续时间对象
ofWeeks(int): 以周为入参单位构造持续时间对象
ofMonths(int): 以月为入参单位构造持续时间对象
ofYears(int): 以年为入参单位构造持续时间对象
of(int, int, int): 指定x年x月x天为一个周期构造持续时间对象
between(LocalDate, LocalDate): 以两个LocalDate的时间差构造持续时间对象
parse(CharSequence): 根据给定的字符串解析为一个Period, 如: P1Y1M1D 表示 1年零1个月零1天
常用的实例方法
normalized(): 整理年份和月份, 将超出12月的部分转化为年份, 并返回新的Period
getDays(): 返回days字段, 注意它不会将整个Period单位转为天再返回, 而是仅仅返回days这个字段的值
getMonths(): 返回months字段, 注意它不会将整个Period单位转为月再返回, 而是仅仅返回days这个字段的值
getYears(): 返回years字段, 注意它不会将整个Period单位转为年再返回, 而是仅仅返回days这个字段的值
isNegative(): 是否负数
isZero(): 是否为0
negated(): 反转持续时间, 如3天反转为-3天
minusXXX(long): 以特定的时间单位减去持续时间, 返回新的持续时间
plusXXX(long): 以特定的时间单位加上持续时间, 返回新的持续时间
withXXX(int): 将特定单位上的值替换为新值, 比如1年零3月, 将3月替换为5月
ZoneDateTime
区域时间类, 它用来精准的描述某一个Zone区域的时间,
ZoneId
时区ID类, 它是一个抽象类, 常用子类有 ZoneOffset / ZoneRegion
ZoneId用于标识用于在Instant和LocalDateTime之间转换的规则。有两种不同类型的ID:
固定偏移量(ZoneOffset) - 从UTC/Greenwich完全解析的偏移量,对所有本地日期时间使用相同的偏移量
地理区域(ZoneRegion) - 应用一组特定规则查找UTC/Greenwich的偏移量的区域
ZoneOffset
时区偏移量, 用于描述某一个时区相对UTC时间的偏移量, 比如东八区的id为: +8
它的数据结构如下:
ZoneRegion
地理区域类, 每个地区的时间规则都可能不一样, 即时这两个地区处于同一时区内, 所以就产生了地理区域的概念, 它配合一个特定的ZoneRulesProvider类, 来获取该地区的时间规则(ZoneRules), 它的数据结构如下:
java.time.chrono
这个包中定义了年表日历(Chronology)/纪元(Era)等信息, 并且提供了多种年表日历的具体实现, 比如国际标准日历(IsoChronology), 也有部分国家地方化的日历, 如 日本历(JapaneseChronology) / 民国历(MinguoChronology) 等等
Era / IosEra / MinguoEra ...
时代/纪元类, 大多数历法系统都有一个纪元,将时间线划分为两个纪元。然而,有些日历系统有多个时代,例如每个领导人的统治时期都是一个时代。在所有情况下,时代在概念上是时间线的最大分界线。每个年表定义了已知的时代和一个年表时代,以获得有效的时代。
例如,泰国佛教日历系统将时间分为两个时代,在一个日期之前和之后。相比之下,日本的历法每个天皇的统治只有一个纪元。
Chronology / AbstractChronology / IsoChronology ...
日历类, 一种日历系统,用来组织和标识日期。
主要的日期和时间API建立在ISO日历系统上。其他日历系统实现还有Japanese/Minguo/Thai等等..
ChronoLocaDate / ChronoLocaDateImpl
ChronoLocalDate是日期的抽象表示,其中Chronology年表或日历系统是可插拔的。日期是根据由TemporalField表示的字段定义的,其中大多数常见的实现是在ChronoField中定义的。年表定义了日历系统的操作方式和标准字段的含义。
大多数应用程序应该将方法签名、字段和变量声明为LocalDate,而不是这个接口。 API的设计鼓励使用LocalDate而不是这个接口,即使在应用程序需要处理多个日历系统的情况下也是如此。
ChronoLocaDateTime / ChronoLocaDateTimeImpl / MinguoDate ...
ChronoLocalDateTime是本地日期时间的抽象表示,其中的Chronology年表或日历系统是可插拔的。日期-时间是由TemporalField表示的字段定义的,其中大多数常见的实现是在ChronoField中定义的。年表定义了日历系统的操作方式和标准字段的含义。
大多数应用程序应该将方法签名、字段和变量声明为LocalDateTime,而不是这个接口。 API的设计鼓励使用LocalDateTime而不是这个接口,即使在应用程序需要处理多个日历系统的情况下也是如此。
ChronoPeriod / ChronoPeriodImpl
基于日期的时间量,如任意年表中的“3年、4个月和5天”
该接口为日历系统中基于日期的时间量定义。虽然大多数日历系统使用年、月和日,但也有一些不使用。因此,该接口仅根据由年表定义的一组受支持的单位来操作。支持单位的集合对于给定的年表是固定的。支持单位的值可以设置为零。
周期被定义为一个有向的时间量,这意味着周期的各个部分可能是负的。
此接口有一个较为通用的实现类: Period
ChronoZonedDateTime / ChronoZonedDateTimeImpl
ChronoZonedDateTime是偏移日期-时间的抽象表示,其中Chronology年表或日历系统是可插拔的。日期-时间是由TemporalField表示的字段定义的,其中大多数常见的实现是在ChronoField中定义的。年表定义了日历系统的操作方式和标准字段的含义。
大多数应用程序应该将方法签名、字段和变量声明为ZonedDateTime,而不是这个接口。 API的设计鼓励使用ZonedDateTime而不是这个接口,即使在应用程序需要处理多个日历系统的情况下也是如此。
java.time.format
顾名思义, 该包下提供了用于对时间对象进行格式化以及解析的规范与工具类, 核心工具类: DateTimeFormatter
DateTimeFormatter / DateTimeFormatterBuilder
用于打印和解析日期时间对象的格式化程序。
这个类提供了用于打印和解析的主应用程序入口点,并提供了DateTimeFormatter的常用实现
常用姿势
LocalDateTime dateTime = LocalDateTime.now();
// 将LocalDateTime格式化为字符串, 格式: yyyy-MM-ddTHH:mm:ss.SSS
String dateTimeStr1 = dateTime.format(DateTimeFormatter.ISO_DATE_TIME);
// 将LocalDateTime格式化为字符串, 格式: yyyy-MM-dd HH:mm:ss
String dateTimeStr2 = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
// 将字符串解析为LocalDateTime
LocalDate parsedDateTime = LocalDate.parse(dateTimeStr1, DateTimeFormatter.ISO_DATE_TIME);
DateTimeParseContext
非public类. 在日期和时间解析期间使用的上下文对象。
该类表示解析的当前状态。它具有存储和检索解析值以及管理可选段的能力。它还为解析方法提供关键信息, 一旦解析完成,使用 toUnresolved()来获取解析的结果数据。toResolved()用于获取已解析的结果。
DateTimePrintContext
非public类. 在日期和时间格式化期间使用的上下文对象。
该类为格式中使用的字段提供单个包装器。
DateTimeTextProvider
非public类. 获取日期-时间字段文本形式的提供程序。
DecimalStyle
日期和时间格式中使用的数字样式, 包括正符号/负符号/零/小数点的样式, 默认的样式如下:
该类的数据结构如下:
FormatStyle
枚举类, 描述以何种模式来格式化日期或时间, 侧重于字符串结果的长短, 有四个枚举值, 如下:
Parsed
非public类. 用来存储已解析的数据。
该类在解析期间用于收集数据。解析过程的一部分涉及到处理可选的块和创建的多个数据副本,以支持必要的回溯。
一旦解析完成,这个类就可以用作生成的TemporalAccessor。在大多数情况下,它只在字段被解析后才被公开。
ResolveStyle
枚举类, 定义在处理日期/时间时所采用的校验模式, 一共三个可取值, 如下:
STRICT: 严格模式, 比如月份的入参必须是1~12, 月日的入参必须是1~31之间
SMART: 智能模式, 在合适的情况下, 即时月份入参为13, 也会自动进1位解析为1年零1月
LENIENT: 宽松模式, 不会去校验入参的合理性, 就算月份为45, 也不会去处理
SignStyle
枚举类, 定义处理正/负号的方法
格式化引擎允许使用此枚举来控制数字的正负符号
TextStyle
枚举类, 定义文本格式和解析样式
文本样式为格式化后的文本定义了三种尺寸: full、short、narrow。这三种尺寸都有“标准”和“独立”两种变体。
在大多数语言中,这三种大小之间的差异是明显的。例如,在英语中,“full”月份是“January”,“short”月份是“Jan”,“narrow”月份是“J”。注意,窄尺寸通常不是唯一的。例如,“January”、“June”和“July”都有“窄”文本“J”。
“标准格式”和“独立格式”之间的区别很难描述,因为在英语中没有区别。然而,在其他语言中,当文本单独使用时,与在完整日期中使用的单词是不同的。例如,在日期选择器中单独使用用于月的单词与在日期中与日和年关联使用的单词是不同的。
java.time.temporal
该包下定义了java.time日期/时间相关类的底层接口, 这个包里面的类十分抽象, 单独拿出来是难以理解的, 通常我们不会直接使用这里的类, 而是使用它们的子类
TemporalAccessor
定义对时间对象(如日期、时间、偏移量或这些对象的组合)的只读访问。
这是date, time和offset对象的基本接口类型。它由那些可以提供字段或查询信息的类实现。
接口API
range(TemporalField): 获取特定字段的值范围, 比如秒的范围为: 0~59
get(TemporalField): 获取特定字段的值
query(TemporalQuery): 根据自定义的TemporalQuery函数获取数据
Temporal
继承TemporalAccessor接口, 定义对时间对象(如日期、时间、偏移量或这些对象的组合)的读写访问。
这是date、time和offset对象的基本接口类型,这些对象足够完整,可以使用加减操作。它是由那些能够提供和操作字段或查询等信息的类实现的。该接口的只读版本请参见TemporalAccessor。
接口API
isSupported(TemporalUnit): 用于判断该对象是否支持特定单位的读写
with(TemporalAdjuster): 基于调节器改更改对象中的字段
with(TemporalField, long): 用于覆盖对象中特定字段的值
plus(TemporalAmount): 加法, 给当前时间对象增加特定的持续时间
plus(long, TemporalUnit): 加法, 给当前时间对象增加特定的持续时间, 指定入参单位
minus(TemporalAmount): 减法, 给当前时间对象减去特定的持续时间
minus(long, TemporalUnit): 减法, 给当前时间对象减去特定的持续时间, 指定入参单位
until(Temporal, TemporalUnit): 计算当前对象时间与给定的对象时间在给定单位上相差多少
TemporalAdjuster / TemporalAdjusters
TemporalAdjuster为调整器, 用于定义调整时间对象的策略的函数。它是修改时间对象的关键工具
TemporalAdjusters是一个扩展的工具类, 它帮我们定义了许多开箱即用的调整器函数, 比如 firstDayOfMonth() 函数表示将日期改到月份的第一天
接口API
adjustInto(Temporal): 对给定的Temporal对象进行调整, 并返回调整后的Temporal对象
TemporalAdjusters定义的函数
firstDayOfMonth(): 用于将给定日期对象改为该月的第一天
lastDayOfMonth(): 用于将给定日期对象改为该月的最后一天
firstDayOfNextMonth(): 用于将给定日期对象改为下个月的第一天
firstDayOfYear(): 用于将给定日期对象改为该年的第一天
lastDayOfYear(): 用于将给定日期对象改为该年的最后一天
firstDayOfNextYear(): 用于将给定日期对象改为下一年的第一天
firstInMonth(DayOfWeek): 用于将给定日期对象改为该月的第一个周x
lastInMonth(DayOfWeek): 用于将给定日期对象改为该月的最后一个周x
dayOfWeekInMonth(int, DayOfWeek): 用于将给定日期对象改为该月的第x个周x
next(DayOfWeek): 用于将给定日期对象改为下一个周x
nextOrSame(DayOfWeek): 用于将给定日期对象改为下一个周x, 如果当前就是周x, 则不变
previous(DayOfWeek): 用于将给定日期对象改为上一个周x
previousOrSame(DayOfWeek): 用于将给定日期对象改为上一个周x, 如果当前就是周x, 则不变
TemporalAmount
用于定义时间的数量,如“6小时”、“8天”或“2年3个月”。
这个对象可以被认为是一个从TemporalUnit到long的映射, 例如“6小时”。更复杂的情况可能有多个单位值对,如“7年、3个月、5天”。
有两种常见的实现。Period是基于日期的实现,存储年、月和日。Duration是基于时间的实现,存储秒和纳秒,但提供一些使用其他基于持续时间的单位(如分钟、小时和固定的24小时天数)的访问。
接口API
get(TemporalUnit): 获取某个时间单位上的值
getUnits(): 这个时间数量包含的时间单位集合
addTo(Temporal): 将当前时间数量添加到指定的时间对象上
subtractFrom(Temporal): 将当前时间数量从指定的时间对象上减去
TemporalField / ChronoField
日期-时间字段,例如月-年或小时-分钟。
最常用的单位在ChronoField中定义。IsoFields、WeekFields和JulianFields提供了更多字段。通过实现这个接口,应用程序代码也可以编写字段。
ChronoField定义的字段枚举
NANO_OF_SECOND("NanoOfSecond", NANOS, SECONDS, ValueRange.of(0, 999_999_999)),
NANO_OF_DAY("NanoOfDay", NANOS, DAYS, ValueRange.of(0, 86400L * 1000_000_000L - 1)),
MICRO_OF_SECOND("MicroOfSecond", MICROS, SECONDS, ValueRange.of(0, 999_999)),
MICRO_OF_DAY("MicroOfDay", MICROS, DAYS, ValueRange.of(0, 86400L * 1000_000L - 1)),
MILLI_OF_SECOND("MilliOfSecond", MILLIS, SECONDS, ValueRange.of(0, 999)),
MILLI_OF_DAY("MilliOfDay", MILLIS, DAYS, ValueRange.of(0, 86400L * 1000L - 1)),
SECOND_OF_MINUTE("SecondOfMinute", SECONDS, MINUTES, ValueRange.of(0, 59), "second"),
SECOND_OF_DAY("SecondOfDay", SECONDS, DAYS, ValueRange.of(0, 86400L - 1)),
MINUTE_OF_HOUR("MinuteOfHour", MINUTES, HOURS, ValueRange.of(0, 59), "minute"),
MINUTE_OF_DAY("MinuteOfDay", MINUTES, DAYS, ValueRange.of(0, (24 * 60) - 1)),
HOUR_OF_AMPM("HourOfAmPm", HOURS, HALF_DAYS, ValueRange.of(0, 11)),
CLOCK_HOUR_OF_AMPM("ClockHourOfAmPm", HOURS, HALF_DAYS, ValueRange.of(1, 12)),
HOUR_OF_DAY("HourOfDay", HOURS, DAYS, ValueRange.of(0, 23), "hour"),
CLOCK_HOUR_OF_DAY("ClockHourOfDay", HOURS, DAYS, ValueRange.of(1, 24)),
AMPM_OF_DAY("AmPmOfDay", HALF_DAYS, DAYS, ValueRange.of(0, 1), "dayperiod"),
DAY_OF_WEEK("DayOfWeek", DAYS, WEEKS, ValueRange.of(1, 7), "weekday"),
ALIGNED_DAY_OF_WEEK_IN_MONTH("AlignedDayOfWeekInMonth", DAYS, WEEKS, ValueRange.of(1, 7)),
ALIGNED_DAY_OF_WEEK_IN_YEAR("AlignedDayOfWeekInYear", DAYS, WEEKS, ValueRange.of(1, 7)),
DAY_OF_MONTH("DayOfMonth", DAYS, MONTHS, ValueRange.of(1, 28, 31), "day"),
DAY_OF_YEAR("DayOfYear", DAYS, YEARS, ValueRange.of(1, 365, 366)),
EPOCH_DAY("EpochDay", DAYS, FOREVER, ValueRange.of((long) (Year.MIN_VALUE * 365.25), (long) (Year.MAX_VALUE * 365.25))),
ALIGNED_WEEK_OF_MONTH("AlignedWeekOfMonth", WEEKS, MONTHS, ValueRange.of(1, 4, 5)),
ALIGNED_WEEK_OF_YEAR("AlignedWeekOfYear", WEEKS, YEARS, ValueRange.of(1, 53)),
MONTH_OF_YEAR("MonthOfYear", MONTHS, YEARS, ValueRange.of(1, 12), "month"),
PROLEPTIC_MONTH("ProlepticMonth", MONTHS, FOREVER, ValueRange.of(Year.MIN_VALUE * 12L, Year.MAX_VALUE * 12L + 11)),
YEAR_OF_ERA("YearOfEra", YEARS, FOREVER, ValueRange.of(1, Year.MAX_VALUE, Year.MAX_VALUE + 1)),
YEAR("Year", YEARS, FOREVER, ValueRange.of(Year.MIN_VALUE, Year.MAX_VALUE), "year"),
ERA("Era", ERAS, FOREVER, ValueRange.of(0, 1), "era"),
INSTANT_SECONDS("InstantSeconds", SECONDS, FOREVER, ValueRange.of(Long.MIN_VALUE, Long.MAX_VALUE)),
OFFSET_SECONDS("OffsetSeconds", SECONDS, FOREVER, ValueRange.of(-18 * 3600, 18 * 3600));
TemporalQuery / TemporalQueries
TemporalQuery是一个函数接口, 定义时间对象(TemporalAccessor)的查询策略, TemporalQueries是一个工具类, 它帮我们定义了一些开箱即用的查询策略函数
接口API
queryFrom(TemporalAccessor): 根据当前策略从给定的时间对象中查询数据
TemporalQueries定义的策略函数
zoneId(): 仅支持对ZonedDateTime与ChronoZonedDateTime对象进行查询, 返回当前时区id, 否则返回null
chronology(): 查询时间对象所处的是日历系统
precision(): 查询时间对象所支持的最小精度, 返回时间单位
zone(): 查询时区信息, 相较zoneId()适配性更加好, 它优先返回zoneId, 如果没有则返回offset
offset(): 查询时区偏移量, 返回ZoneOffset
localDate(): 查询当前时间对象中的日期, 返回LocalDate
localTime(): 查询当前时间对象中的时间, 返回LocalTime
TemporalUnit / ChronoUnit
日期-时间单位,例如年、月、日、小时、分和秒, 这个接口的一个实例代表单位本身,而不是数量。
ChronoUnit中定义了最常用的单位。IsoFields提供进一步的单位。通过实现这个接口,应用程序代码也可以编写单元。
ChronoUnit中定义的单位
NANOS("Nanos", Duration.ofNanos(1)),
MICROS("Micros", Duration.ofNanos(1000)),
MILLIS("Millis", Duration.ofNanos(1000_000)),
SECONDS("Seconds", Duration.ofSeconds(1)),
MINUTES("Minutes", Duration.ofSeconds(60)),
HOURS("Hours", Duration.ofSeconds(3600)),
HALF_DAYS("HalfDays", Duration.ofSeconds(43200)),
DAYS("Days", Duration.ofSeconds(86400)),
WEEKS("Weeks", Duration.ofSeconds(7 * 86400L)),
MONTHS("Months", Duration.ofSeconds(31556952L / 12)),
YEARS("Years", Duration.ofSeconds(31556952L)),
DECADES("Decades", Duration.ofSeconds(31556952L * 10L)),
CENTURIES("Centuries", Duration.ofSeconds(31556952L * 100L)),
MILLENNIA("Millennia", Duration.ofSeconds(31556952L * 1000L)),
ERAS("Eras", Duration.ofSeconds(31556952L * 1000_000_000L)),
FOREVER("Forever", Duration.ofSeconds(Long.MAX_VALUE, 999_999_999));
ValueRange
日期-时间字段的有效值范围, 所有时间字段都有一个有效的值范围。例如,ISO的月份有效值范围为1到12之间, 它的数据结构如下:
可以看到它并非只有一个最小值和最大值, 而是四个值, 我们可以通过月份天数来理解这个范围, 有些月份比如10月有31天, 但2月却可能只有28天, 所以描述一个月份的日期范围不能仅使用 1-31 来描述, 而是描述为 1 - 28/31 , 这里 minSmallest与minLargest都为1, maxSmallest为28, maxLargest为31.
java.time.zone
此包是对时区相关功能的扩展, 核心的类 ZoneId/ZoneOffset/ZoneRegion 都在java.time主包中
ZoneOffsetTransition / ZoneOffsetTransitionRule
ZoneOffsetTransition为两个偏移量之间的转换建模。
当存在根本不存在的本地日期时间时,就会出现时间间隔。例如,偏移量从+03:00变化到+04:00。这可以被描述为“今晚凌晨1点,时钟将向前拨一小时”, 重叠发生在本地日期时间存在两次的地方。例如,偏移量从+04:00变化到+03:00。这可以被描述为“今晚凌晨2点时钟将向后拨一小时”。
ZoneOffsetTransition的数据结构如下:
ZoneOffsetTransitionRule可以帮助我们使用特定的规则来创建一个ZoneOffsetTransition, 比如 二月的最后一个星期天 进行时区转换
ZoneRules
定义单个时区的区域偏移如何变化的规则, 它包含了一系列的ZoneOffsetTransition转换规则
它的数据结构如下:
ZoneRulesProvider / TzdbZoneRulesProvider
提供系统的时区规则。
该类管理时区规则的配置。静态方法提供可用于管理提供程序的公共API。抽象方法提供允许提供规则的SPI。
ZoneRulesProvider可以作为扩展类安装在Java平台的实例中,也就是说,jar文件被放置在任何通常的扩展目录中。已安装的提供程序使用ServiceLoader类定义的服务提供程序加载工具加载。一个ZoneRulesProvider使用资源目录META-INF/services中的一个名为java.time.zone.ZoneRulesProvider的提供者配置文件来标识自己。该文件应该包含一行指定完全限定的具体zonerule -provider类名。提供程序也可以通过将它们添加到类路径或通过registerProvider方法注册自己来提供。
Java虚拟机有一个默认的提供程序,它为IANA Time zone Database (TZDB)定义的时区提供区域规则。如果定义了系统属性java.time.zone.DefaultZoneRulesProvider,那么它被认为是一个具体的ZoneRulesProvider类的完全限定名,使用系统类装入器将其加载为默认的提供程序。如果未定义此系统属性,则将加载系统默认提供程序作为默认提供程序。
规则主要通过zone ID查找,就像ZoneId使用的那样。只能使用区域区域id,区域偏移id在这里不使用。
时区规则是政治性的,因此数据可以随时更改。每个提供者将为每个区域ID提供最新的规则,但它们也可能提供规则更改的历史记录。