总结
时间点用Instant(如1ms , 1ns),
时间段用Duration(如2个Instant对象所代表时间差),
本地日期用LocalDate(如2020年12月1日),
本地时间用LocalTime(如13时30分20秒),
本地日期+时间用LocalDateTime = LocalDate + LocalTime(如2020年12月1日13时30分20秒),
时区时间用ZonedDateTime(本地北京时间转化到纽约时间),
输出时间不同格式用DateTimeFormatter,
简单环境应用可以使用SimpleDateFormat(线程不安全),
不同时区时间格式化输出用Locale切换。
概要
Java 1.0 Date
简单
Java 1.1 Calendar
引入后,Date类大部分方法弃用。但实例易变,并且没有处理诸如闰秒这样的问题。
Java 3.0 java.time
修正了过去的缺陷
时间线
秒的来源
最初,根据地球自转推导而来。地球自转一周24h = 24x60x60 = 86400s。缺陷:地球有轻微颤动,1秒定义不精确。
1967年以来,原子钟网络被当作官方时间:根据铯133原子内在特性推导而来。
绝对时间与地球同步自转进行同步,官方时间的秒需要稍作调整,从1972年开始,偶尔需要插入“闰秒”。
Java的Date和Time API规范要求Java使用的时间尺度为:
- 每天86400秒
- 每天正午与官方时间精确匹配
- 在其他时间点上,以精确定义的方式与官方时间接近匹配
Instant和Duration类
Instant类
Instant表示时间线上的某个点。
“新纪元”时间线原点被设置为 穿过伦敦本初子午线所处时区的1970年1月1日的午夜,与UNIX/POSIX时间中使用的惯例相同。
从“新纪元”时间线原点开始,时间按照每天86400秒向前或向回度量,精确到纳秒。Instant值可向回追溯10亿年(Instant.MIN) << 宇宙年龄(约135亿年),不过对所有实际应用来说,应该足够。最大值Instant.MAX是公元1 000 000 000年的12月31日。
当前时间: Instant.now()
Instant对象含义:时间戳
比较2个Instant对象:equals和compareTo
如何度量算法运行时间?
Instant start = Instant.now();
runAlgorithm();
Instant end = Instant.now();
Duration timeElapsed = Durtion.between(start, end);
long millis = timeElapsed.toMillis();
Duration类
Duration是2个时刻之间的时间量。
Duration按传统单位度量时间长度的常用方法
toNanos // 转化纳秒
toMillis // 转化为毫秒
getSeconds // 转化为秒
toMinutes // 转化为分钟
toHours // 转化为小时
toDays // 转化为天
例子,如果想要检查某个算法是否比另外一个算法快10倍,可以这样做
Duration timeElapse2 = Duration.between(start2, end2);
boolean overTenTimeFaster = timeElapse.multipliedBy(10).minus(timeElapsed2).isNegative();
// 等价于下面的语句
overTenTimeFaster = timeElapsed.toNanos() * 10 < timeElapsed2.toNanos();
注意: Instant和Duration都是不可修改的类,诸如mltipliedBy和minus都会返回一个新实例
本地时间LocalDate
Java中人类时间分为2种:本地时间,时区时间。
本地时间包含日期和当天的时间,与时区没有任何关联。
某些特殊情况下,时区可能回成为障碍。不推荐使用时区,除非先表达绝对时间。
LocalDate类
LocalDate是带有年、月、日的本地日期,可以用now或of方法构建LocalDate对象。
java.util.Date使用从1900年0月开始计算(与Unix一致)
- LocalDate对象创建方法
LocalDate today = LocalDate.now(); // 今天的日期
LocalDate alonzosBirthday = LocalDate.of(1903.6.14); // 以1903年6月14日构建LocalDate对象
alnozosBirthday = LocalDay.of(1903, Mont.JUNE, 14); // 1903年6月14日
- LocalDate常用方法
方法 | 描述 |
---|---|
now, of | 静态方法构建LocalDate对象,要么从当前时间构建,那么从给定年月日构建 |
plusDays, plusWeeks, plusMonths, plusYears | 在当前的LocalDate上加上一定量的天、星期、月或年 |
minusDays, minusWeeks, minusMonths, minusYears | 在当前的LocalDate上减去一定量的天、星期、月或年 |
plus, minus | 加上或减去一个Duration或Period |
withDayOfMont, 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,或者两个日期之间按照给定的ChrounoUnits计算的数值 |
isBefore, isAfter | 将当前LocalDate与另一个LocalDate进行比较 |
isLeapYear | 如果是闰年,返回true;否则返回false |
注意:闰年指年份能被4整除,但是不能被100整除;或者能被400整除。
例子,程序员日是每年256天。计算方法:
LocalDate programmersDay = LocalDate.of(2020, 1, 1).plusDays(255);
日期间隔Period类
2个时间点Instant间隔是Duration,2个本地日期LocalDate直接的间隔是什么呢?
是Period,表示流逝的年、月或日的数量。
利用Period计算日期,和LocalDate计算日期差异:例如,计算下一年生日
LocalDate birthday = LocalDate.now();
// 根据当前生日日期,计算明年生日日期3种方式
birthday.plusYears(1); // 正确
birthday.plusDays(365); // 错误,会有闰年问题
birthday.plus(Period.ofYears(1)); // LocalDate日期对象 + Period所代表的日期间隔(时长)
如何计算2个本地日期之间时长?
LocalDate day1 = LocalDate.of(2015.1.1);
LocalDate day2 = LocalDate.now();
day1.until(day2); // 相隔5年11个月0天
day1.until(day2, ChronoUnit.DAYS);// 计算具体相隔多少天
异常情况:
有些方法可能创建出并不存在的日期,如1月31+1个月,不会产生2月31日,也不会抛出异常,而是返回该月最后一天。
例如,LocalDate.of(2016, 1, 31).plusMonths(1)
和LocalDate.of(2016, 3, 31).minusMonths(1)
都将产生2016年2月29日
日期调整器
略
本地时刻LocalTime
LocalTime表示当日时刻,如15:30:00。LocalTime默认24小时制,不关心AM/PM,交由格式器解决。
LocalTime创建
LocalTime rightNow = LocalTime.now();
LocalTime bedtime = LocalTime.of(22, 30); // 或者LocalTime.of(22, 30, 0) 表示时间24小时制 22时30分00秒
LocalTime常用方法
方法 | 描述 |
---|---|
now, of | 类方法,构建LocalTime对象。of方法从时分构建LocalTime对象,时分必选,秒、纳秒可选 |
plusHour, plusMinutes, plusSeconds, plusNanos | 在当前LocalTime + 一定量小时、分钟、秒、纳秒 |
minusHour, minusMinutes, minusSeconds, minusNanos | 在当前LocalTime - 一定量小时、分钟、秒、纳秒 |
plus, minus | 加上或减去一个Duration |
withHour, withMinute, withSecond, withNano | 返回一个新LocalTime, 其小时、分钟、秒、纳秒修改为给定值 |
getHour, getMinute, getSecond, getNano | 获取当前LocalDate的小时、分钟、秒、纳秒 |
toSecondOfDay, toNanoOfDay | 返回午夜到当前秒或纳秒的数量 |
isBefore, isAfter | 将当前LocalTime与另外一个LocalTime进行比较 |
LocalDateTime类
适合存储固定时区的时间点,如排课或排程。如果需要跨不同时区,或者跨越夏令时,应该使用ZonedDateTime类。
时区时间
时区时间有什么用?
在北京是17点44分,然而在纽约是4点44分,而在伦敦却是9点44分。在不同时区的人,如何约定好同一个时间举办一个会议? 这就需要用到时区时间ZonedDateTime类。
互联网编码分配管理机构(IANAN)保存着一个数据库,里面存储着世界上所有已知的时区 IANAN官网,每年更新几次,批量更新会处理夏令时的变更规则。Java使用了IANAN数据库。
时区ZonedDateTime类
时区ID
每个时区都有一个时区ID,例如Anmerica/New_York和Europe/Berlin。
目前,全世界有进600个ID。
- 找出所有可用时区
ZoneId.getAvailableZoneIds()
- 给定时区ID,构建ZondId对象
ZoneId.of(id);
- 将LocalDateTime对象转换为ZonedDateTime对象
两种方法
// 方法一
LocalDate local = local.now();
local.atZone(id);
// 方法二
ZonedDateTime.of(year, month, day, hour, minute, second, nano, zoneId);
// e.g.
ZonedDateTime apollolllaunch = ZonedDateTime.of(1969, 7, 16, 9, 32, 0, 0, ZoneId.of("America/New_York")); // 1969-07-16T09:32-04:00[America/New_York]
// 获得Instant, ZonedDateTime -> Instant
Instant instant = apollolllaunch.toInstant();
// Instant -> ZonedDateTime
instant.atZone(ZoneId.of("UTC")); // 获得格林威治皇家天文台的ZonedDateTime对象
ZonedDateTime类
ZonedDateTime许多方法与LocalDateTime类的相同,不过夏令时带来一些复杂性。
夏令时
夏令时,Daylight Saving Time,是一种节约能源而人为规定地方时间的制度,这一制度实行期间所采用的统一时间称为“夏令时”。一般在夏季人为将时间提前1小时,冬季又调回1小时,每个国家具体规定不同。
夏令时开始时,时钟要向前拨快一小时。夏令时结束时,时钟要拨慢一小时。
// e.g. 2013年, 中欧地区3月31日 2:00 切换到夏令时. 如果试图构建的时间是不存在的3月31日2:30,那么实际上得到的是3:30
ZonedDateTime skipped = ZonedDateTime.of(
LocalDate.of(2013, 3, 31),
LocalTime.of(2, 30),
ZoneId.of("Europe/Berlin")
); // 2013年3月31日 3:30
// 夏令时结束时, 时钟要回拨慢一小时, 这样同一个本地时间就会有出现2次. 当构建位于这个时间段内的时间对象时, 会得到这2个时刻中较早的一个
ZonedDateTime ambiguous = ZonedDateTime.of(
LocalDate.of(2013, 10, 27),
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]
// e.g 跨越夏令时边界事需要特别注意. 例如, 如果将会议设置在下个星期, 不要直接+7天Duration, 而应该使用Period类
ZonedDateTime nextMeeting = meeting.plus(Duration.ofDays(7)); // 错误做法
ZonedDateTime nextMeeting = meeting.plus(Period.ofDays(7)); // 正确做法
注意:offsetDateTime类,UTSC具有偏移量的时间,没有时区规则限制,用于专用应用,如某些网络协议。人类时区时间应该使用ZonedDateTime。
格式化和解析
格式器DateTimeFormatter
DateTimeFormatter类提供3种打印日期/时间值的格式器:
- 预定义的格式器
- Local相关的格式器
- 带有定制模式的格式器
注意:java.time.format.DateTimeFormatter类被设计用来替代java.util.DateFormat。如果为向后兼容需要使用后者,可以调用formatter.toFormat()。
示例:使用标准的格式器,直接调用DateTimeFormatter.format方法
ZonedDateTime apollolllaunch = ZonedDateTime.of(1969, 7, 16, 9, 32, 0, 0, ZoneId.of("America/New_York")); // 1969-07-16T09:32-04:00[America/New_York]
String formatted = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(apollolllaunch ); // 1969-07-16T09:32:00-04:00
System.out.println(formatted);
标准格式器主要是为了机器刻度的时间戳而设计的,向人类表示日期和时间,可以使用Locale相关格式器。
有4种与Locale相关的格式化风格:SHORT, MEDIUM, LONG, FULL.
Locale格式化风格
- 创建Locale相关格式化风格
// 创建DateTimeFormatter
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
// 使用默认Locale
String formatted = formatter.format(apollolllaunch ); // 1969年7月16日 上午09时32分00秒
System.out.println(formatted);
// 使用withLocale, 切换到不同Locale
formatted = formatter.withLocale(Locale.FRENCH).format(apollolllaunch ); // 16 juillet 1969 09:32:00 EDT
// 按不同Locale和格式给出星期日期和月份名字
for (DayOfWeek w: DayOfWeek.values()) {
// 打印星期几的英文简称: Mon Tue Wed Thu Fri Sat Sun
System.out.print(w.getDisplayName(TextStyle.SHORT, Locale.ENGLISH) + " ");
}