日期与时间
其他参考资料:
纪元(epoch):UTC时间1970年1月1日00:00:00。UTC是国际协调时间,与大家熟悉的GMT(格林尼治时间)一样,是一种实用的科学标准时间。
Java 1.0 有一个java.util.Date
类,事后证明它过于简单了,当Java 1.1引入Calendar
类之后,Date
类中的大部分方法就被弃用了。但是,Calendar
的API还不够给力,它的实例是可修改的,并且它没有处理诸如闰秒这样的问题。第3次升级则很吸引人,那就是Java 8引入的java.time
API,它修正了过去的缺陷,并且应该会服役相当长的一段时间。
在Java API中有两种人类时间,本地日期/时间和时区时间。其中本地日期/时间包含日期和当天的时间,但是与时区信息没有任何关联。比如,生日、假日、计划时间等通常最好都表示成本地日期和时间。
Duration
和Period
都实现了TemporalAmount
接口。Instant/LocalDate/LocalTime/LocalDateTime/ZonedDateTime
都实现了Temporal
接口和TemporalAccessor
接口。
1. 时间线
Instant
和Duration
类都是不可修改的类,所以诸如multipliedBy
和minus
这样的方法都会返回一个新的实例。
1.1 Instant
在Java中,Instant
表示时间线上的某个点。被称为“新纪元”的时间线原点被设置为穿过伦敦格林尼治皇家天文台的本初子午线所处时区的1970年1月1日的午夜,这与UNIX/POSIX时间中使用的惯例相同。从该原点开始,时间按照每天86400秒向前或向回度量,精确到纳秒。
Instant
的值往回可追溯10亿年(Instant.MIN
),最大的值Instant.MAX
是公元1000000000年的12月31日。
Instant的方法 | 解释 |
---|---|
static Instant now() | 从最佳的可用系统时钟中获取当前的时刻 |
Instant plus(TemporalAmount amountToAdd) | 产生一个时刻,该时刻与当前时刻距离给定的时间量 |
Instant minus(TemporalAmount amountToSubtract) | 产生一个时刻,该时刻与当前时刻距离给定的时间量 |
Instant (plus\minus)(Nanos\Millis\Seconds)(long number) | 产生一个时刻,该时刻与当前时刻距离给定数量的纳秒、微秒或秒 |
1.2 Duration
为了得到两个时刻之间的时间差,可以使用Duration
的静态方法Duration.between
,如:
Instant start = Instant.now();
runAlgorithm();
Instant end = Instant.now();
Duration timeElapsed = Duration.between(start, end);
long millis = timeElapsed.toMillis();
Duration的方法 | 作用 |
---|---|
static Duration of(Nano\Millis\Seconds\Minutes\Hours\Days)(long number) | 产生一个给定数量的指时间单位的时间间隔 |
static Duration between(Temporal startInclusive, Temporal endExclusive) | 产生一个在给定时间点之间的Duration对象 |
long toNanos() | |
long toMillis() | |
long toSeconds() | |
long toMinutes() | |
long toHours() | |
long toDays() | |
int to(Nanos\Millis\Seconds\Minutes\Hours)Part() | 获取当前时长中给定时间单位的部分 |
long toDaysPart() | |
Instant plus(TemporalAmount amountToAdd) | |
Instant minus(TemporalAmount amountToSubtract) | |
Duration multipliedBy(long multiplicand) | |
Duration dividedBy(long divisor) | |
Duration negated() | |
boolean isZero() | 如果当前Duration对象是0,返回true |
boolean isNegative() | 如果当前Duration对象是负数,返回true |
Duration (plus\minus)(Nanos\Millis\Seconds\Minutes\Hour\Days)(long number) | 产生一个时长,该时长是通过当前时刻加上或减去给定的数量的指定时间单位而得到的 |
2. 本地日期
2.1 LocalDate
LocalDate
是带有年、月、日的日期。
为了构建LocalDate
对象,可以使用now
或of
静态方法:
LocalDate today = LocalDate.now();
LocalDate alonzosBirthday = LocalDate.of(1903, 6, 14);
alozosBirthday = LocalDate.of(1903, Month.JUNE, 14);
与UNIX和
java.util.Date
中使用的月从0开始计算而年从1900开始计算的不规则的惯用法不同,你需要提供通常使用的月份的数字或者使用Month
枚举。
LocalDate
的API中有些方法可能会创建出并不存在的日期,例如,在1月31日上加上1个月不应该产生2月31日,这些方法并不会抛出异常,而是会返回该月有效的最后一天,例如:
LocalDate.of(2016, 1, 31).plusMonths(1); // 产生2016年2月29日
LocalDate.of(2016, 3, 31).minusMonths(1); // 产生2016年2月29日
LocalDate
的API主要如下:
LocalDate的方法 | 说明 |
---|---|
static LocalDate now() | 构造一个表示当前日期的对象 |
static LocalDate of(int year, int month, int dayOfMonth) | 构造一个表示给定日期的对象 |
static LocalDate of(int year, Month month, int dayOfMonth) | 构造一个表示给定日期的对象 |
static LocalDate parse(CharSequence text) | 用默认的格式器产生一个LocalDate |
static LocalDate parse(CharSequence text, DateTimeFormatter formatter) | 用给定的格式器产生一个LocalDate |
LocalDate (plus\minus)(Days\Weeks\Months\Years)(long number) | 产生一个LocalDate,该对象是通过在当前对象上加上/减去给定数量的时间单位获取的 |
LocalDate plus(TemporalAmount amountToAdd) | 产生一个LocalDate,该时刻与当前时刻距离给定的时间量 |
LocalDate minus(TemporalAmount amountToSubtract) | 产生一个LocalDate,该时刻与当前时刻距离给定的时间量 |
LocalDate withDayOfMonth(int dayOfMonth) | 返回一个新的LocalDate,将月份日期修改为给定值 |
LocalDate withDayOfYear(int dayOfYear) | 返回一个新的LocalDate,将年日期修改为给定值 |
LocalDate withMonth(int month) | 返回一个新的LocalDate,将月修改为给定值 |
LocalDate withYear(int year) | 返回一个新的LocalDate,将年修改为给定值 |
int getDayOfMonth() | 得到当前日期的日 |
int getDayOfYear() | 当前当前日期的年 |
DayOfWeek getDayOfWeek() | 得到当前日期是星期几 |
Month getMonth() | |
int getMonthValue() | 得到当前日期的月 |
int getYear() | 获取年份 |
Period until(ChronoLocalDate endDateExclusive) | 获取直到给定终止日期的period。 |
boolean isBefore(ChronoLocalDate other) | |
boolean isAfter(ChronoLocalDate other) | |
boolean isLeapYear() | 判断是否是闰年 |
LocalDate with(TemporalAdjuster adjuster) | 返回该日期通过给定的调整器调整后的结果 |
Stream<LocalDate> datesUntil(LocalDate endExclusive) | |
Stream<LocalDate> datesUntil(LocalDate endExclusive, Period step) | 产生一个时间流,从当前的LocalDate对象直至参数endExclusive指定的日期 |
2.2 Period
两个Instant
之间的时长是Duration
,而用于本地日期的等价物是Period
,它表示的是流逝的年、月或日的数量。
Period的方法 | 作用 |
---|---|
static Period of(years, int months, int days) | 用给定数量的时间单位产生一个Period对象 |
Period of(Days\Weeks\Months\Years)(int number) | 用给定数量的时间单位产生一个Period对象 |
int get(Days\Months\Years)() | 获取当前Period对象的日、月或年 |
Period (plus\minus)(Days\Months\Years)(long number) | |
Period plus(TemporalAmount amountToAd) | |
Period minus(TemporalAmount amountToSubtract) | |
Period with(Days\Months\Years)(int number) | 返回一个新的Period,将日、月、年修改为给定值 |
2.3 TemporalAdjuster
TemporalAdjuster
是日期调整器类。
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
对于日程安排应用来说,经常需要计算诸如“每个月的第一个星期二”这样的日期。TemporalAdjusters
类提供了大量用于常见调整的静态方法。你可以将调整方法的结果传递合with
方法,如,某个月的第一个星期二可以像下面这样计算:
LocalDate firstTuesday = LocalDate.of(year, month, 1)
.with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY));
TemporalAdjusters的方法 | 作用 |
---|---|
static TemporalAdjuster next(DayOfWeek dayOfWeek) | 返回一个调整器,用于将日期调整为给定的星期日期 |
static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek) | 同上 |
static TemporalAdjuster previous(DayOfWeek dayOfWeek) | 同上 |
static TemporalAdjuster previousOrSame(DayOfWeek dayOfWeek) | 同上 |
static TemporalAdjuster dayOfWeekInMonth(int n, DayOfWeek dayOfWeek) | 返回一个调整器,用于将日期调整为月份中第n个或最后一个给定的星期日期 |
static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek) | 同上 |
static TemporalAdjuster firstDayOfMonth() | 返回一个调整器,用于将日期调整为月份或年份中给定的日期 |
static TemporalAdjuster firstDayOfNextMonth() | 同上 |
static TemporalAdjuster firstDayOfYear() | 同上 |
static TemporalAdjuster firstDayOfNextYear() | 同上 |
static TemporalAdjuster lastDayOfMonth() | 同上 |
static TemporalAdjuster lastDayOfYear() | 同上 |
3. LocalTime
LocalTime
表示当日时刻,例如15:30:00。可以用now
或of
方法创建其实例:
LocalTime rightNow = LocalTime.now();
LocalTime bedTime = LocalTime.of(22, 30);
LocalTime
自身并不关心AM/PM,这种愚蠢的设计将问题抛给格式器去解决。
LocalTime的方法 | 作用 |
---|---|
static LocalTime now() | 获取当前的LocalTime |
static LocalTime of(int hour, int minute) | |
static LocalTime of(int hour, int minute, int second) | |
static LocalTime of(int hour, int minute, int second, int nanoOfSecond) | 产生一个LocalTime,它具有给的小时(0-23)、分钟、秒(0-59)和纳秒(0-999999999)之间 |
LocalTime (plus\minus)(Hours\Minutes\Seconds\Nanos)(long number) | 产生一个LocalTime,该对象是通过在当前对象上加上或减去给定数量的时间单位获得的 |
LocalTime plus(TemporalAmount amountToAdd) | 产生一个时刻,该时刻与当前时刻距离给定的时间量 |
LocalTime minus(TemporalAmount amountToSubtract) | 同上 |
LocalTime with(Hour\Minute\Second\Nano)(int value) | 产生一个时刻,将小时/分钟/秒//纳秒修改为给定值 |
int getHour() | 获取小时(0~23) |
int getMinute() | 获取分钟(0~59) |
int getSecond() | 获取秒(0~59) |
int getNano() | 获取纳秒(0~999999999) |
int toSecondOfDay() | 产生自午夜到当前LocalTime的秒数 |
long toNanoOfDay() | 产生自午夜到当前LocalTime的纳秒数 |
boolean isBefore(LocalTime other) | |
boolean isAfter(LocalTime other) |
4. LocalDateTime
LocalDateTime
表示日期和时间,其适合存储固定时区的时间点,例如用于排课或排程。
但是,如果你的计算需要跨越夏令时,或者需要处理不同时区的用户,那么应该使用接下来要讨论的ZonedDateTime
类。
5. ZonedDateTime
时间问题比较复杂。在理性的世界中,我们都会遵循格林尼治时间,有些人在02:00吃午饭,而有些人却在22:00吃午饭。中国横跨了4个时区,但是使用了同一个时间,在其他地方,时区显得并不规则,并且还有国际日期变更线,而夏令时则使事情变得更复杂了。
互联网编码分配管理机构(IANA)保存着一个数据库,里面存储着上所有已知的时区,它每个会更新数次,而批量更新会处理夏令时的变更规则。Java使用了IANA数据库。
每个时区都有一个ID,例如America/New_York和Europe/Berlin。要想找出所有可用的时区,可以调用ZoneId.getAvailableZoneIds
。在本书撰写之时,有将近600个ID。
ZonedDateTime
的许多方法都与LocalDateTime
的方法相同,它们大多数都很直观(尽管夏令时带来了一些复杂性)。
ZonedDateTime的方法 | 作用 |
---|---|
static ZonedDateTime now() | 获取当前的ZonedDateTime |
static ZonedDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZonedId zone) | |
static ZonedDateTime of(LocalDate date, LocalTime, ZoneId zone) | |
static ZonedDateTime of(LocalDateTime localDateTime, ZoneId zone) | |
static ZonedDateTime ofInstant(Instant instant, ZoneId zone) | 用给定的参数和时区产生一个ZonedDateTime |
static ZonedDateTime parse(CharSequence text) | 用默认的格式器产生一个ZonedDateTime |
static ZonedDateTime parse(CharSequence text, DateTimeFormatter formatter) | 用给定的格式器产生一个ZonedDateTime |
ZonedDateTime (plus\minus)(Days\Weeks\Months\Years\Hours\Minutes\Seconds\Nanos)(long number) | |
ZonedDateTime plus(TemporalAmount amountToAdd) | |
ZonedDateTime minus(TemporalAmount amountToSubtract) | |
ZonedDateTime with(DayOfMonth\DayOfYear\Month\Year\Hour\Minute\Second\Nano)(int value) | 返回一个新的ZonedDateTime ,用给定的值替换给定的时间单位 |
ZonedDateTime withZoneSameInstant(ZondId zone) | 返回一个新的ZonedDateTime ,位于给定的时区,它与当前对象表示相同的时刻 |
ZonedDateTime withZoneSameLocal(ZoneId zone) | 返回一个新的ZonedDateTime ,位于给定的时区,它与当前对象表示相同的本地时间 |
int getDayOfMonth() | |
int geDayOfYear() | |
DayOfWeek getDayOfWeek() | |
Month getMonth() | |
int getMonthValue() | |
int getYear() | |
int getHour() | |
int getMinute() | |
int getSecond() | |
int getNano() | |
ZoneOffset getOffset() | 获取与UTC的时间差距。差距可在-12:00~+14:00变化,有些时区还有小数时间差,时间差会随着夏令时变化 |
LocalDate toLocalDate() | |
LocalTime toLocalTime() | |
LocalDateTime toLocalDateTime() | |
Instant toInstant() | |
boolean isBefore(ChronoZonedDateTime other) | |
boolean isAfter(ChronoZonedDateTime other) |
6. 格式化和解析
DateTimeFormatter
类提供了三种用于打印日期/时间值的格式器:
预定义的格式器
image-20220906092156500 要使用标准的格式器,可以直接调用其
format
方法:// 1967-07-16T09:32:00-04:00" String formatted = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(appollo11launch);
locale
相关的格式器标准格式器主要是为了机器可读的时间戳而设计的,为了向人类读者表示日期和时间,可以使用
locale
相关的格式器。对于日期和时间而言,有4种与locale
相关的格式化风格,即SHORT, MEDIUM, LONG, FULL
。image-20220906092704709 静态方法
ofLocalizedDate
,ofLocalizedTime
,ofLocalizedDateTime
可以创建这种格式器,例如:DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG); // July 16, 1969 9:32:00 AM EDT String formatted = formatter.format(apollo11launch);
这些方法使用了默认的
locale
,为了切换到不同的locale
,可以直接使用withLocale
方法:// 16 juillet 1969 09:32:00 EDT formatted = formatter.withLocale(Locale.FRENCH).format(apollo11launch);
带有定制模式的格式器
可以通过指定模式来定制自己的日期格式,如:
// 以下会将日期格式化为 Wed 1969-07-16 09:32 formatter = DateTimeFormatter.ofPattern("E yyyy-MM-dd HH:mm");
image-20220906093312680
DaTimeFormatter
的API:
DateTimeFormatter的方法 | 作用 |
---|---|
String format(TemporalAccessor temporal) | 格式化给定值 |
static DateTimeFormatter ofLocallizedDate(FormatStyle dateStyle) | 产生一个用于给定风格的格式器 |
static DateTimeFormatter ofLocallizedTime(FormatStyle timeStyle) | 同上 |
static DateTimeFormatter ofLocallizedDateTime(FormatStyle dateTimeStyle) | 同上 |
static DateTimeFormatter ofLocallizedDateTime(FormatStyle dateStyle, FormatStyle timeStyle) | 同上 |
DateTimeFormatter withLocale(Locale locale) | 用给定的地点产生一个等价于当前格式器的格式器 |
static DateTimeFormatter ofPattern(String pattern) | 用给定的模式产生一个格式器 |
static DateTimeFormatter ofPattern(String pattern, Locale locale) | 用给定的模式和地点产生一个格式器 |
7. 与遗留代码的交互
作为全新的创造,Java Date和Time API必须能够与已有类之间进行互操作,特别是无处不在的java.uitl.Date
, java.util.GregorianCalendar
, java.sql.Date/Time/Timestamp
。
类 | 转换到遗留类 | 转换自遗留类 |
---|---|---|
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() |