时区
地球自西向东旋转,东边比西边先看到太阳,东边的时间也比西边的早。为了统一世界的时间,1884年的国际经度会议规规定将全球划分为24个时区(东、西各12个时区)。规定英国(格林尼治天文台旧址)为零时区(GMT+00),东1-12区,西1-12区,中国北京处于东8区(GMT+08)。
若英国时间为6点整,则GMT时间为6点整,则北京时间为14点整。
时区ID
ZoneId 为时区ID,比如Europe/Paris,表示欧洲巴黎时区
GMT和UTC
GMT,即格林尼治标准时间,也就是世界时。GMT的正午是指当太阳横穿格林尼治子午线(本初子午线)时的时间。但由于地球自转不均匀不规则,导致GMT不精确,现在已经不再作为世界标准时间使用。
UTC,即协调世界时。UTC是以原子时秒长为基础,在时刻上尽量接近于GMT的一种时间计量系统。为确保UTC与GMT相差不会超过0.9秒,在有需要的情况下会在UTC内加上正或负闰秒。UTC现在作为世界标准时间使用。
所以,UTC与GMT基本上等同,误差不超过0.9秒。
UNIX时间戳
计算机中的UNIX时间戳,是以GMT/UTC时间「1970-01-01T00:00:00」为起点,到具体时间的秒数,不考虑闰秒。这么做当然是为了简化计算机对时间操作的复杂度。
比如我的电脑现在的系统时间为2015年2月27日15点43分0秒,因为我的电脑默认时区为东8区,则0时区的时间为2015年2月27日7点43分0秒,则UNIX时间戳为1425022980秒。
所以要有时区或者偏移量信息的时间,才能和UTC时间比较,计算出时间差,才能算出时间戳。
偏移量 VS 时区 (ZoneOffset VS ZoneId)
偏移量(与UTC的偏移量) offset-from-UTC 仅仅只记录了时分秒而已,除此之外没有任何其他信息。举个例子 ,+08:00的意思时超前于UTC八个小时,而 -05:45 意思是落后于UTC五小时四十五分钟。
而时区对于特定地区的人来说是过去,现在,未来的偏移量的历史集合。像夏令时 这样的异常会导致特定时间段内的偏移量会随时间变化,无论是过去已经发生的还是 未来政客们宣布计划的改变。
时区是偏移的针对特定区域中的历史加上一组规则来处理异常,如日光节约时间(DST) ,导致在时间的偏移量超过特定时间段的变化。
时区=(偏移历史+异常规则)
因此在知道时更好地使用区域。
很多地区的偏移量都会随时间变化。比如,美国的夏令时 会导致约半年左右的时间内都会再原来的基础上偏移一个小时,然后再下半年再调整回这一个小时的偏移量。时区的意义就是记录这些所有的会造成偏移的情况。
因此,没有结合日期计算的偏移量没有意义的。举个例子,法国时间 (Europe/Paris),在一年的一小部分时间内偏移量为+01:00,而由于夏令时的缘故,大部分时间(三月末到十月末)的偏移量都是+02:00。
// 指定区域,构造时间: 法国巴黎时间的当前时间,五月有DST,偏移量+2
LocalDateTime now = LocalDateTime.of(LocalDate.of(2020, 5, 31), LocalTime.now())
ZonedDateTime zonedDateTime = ZonedDateTime.of(now, ZoneId.of("Europe/Paris"));
zonedDateTime.getOffset(); // +02:00
// 手动调整的二月份,此时没有DST,偏移量是+1
ZonedDateTime thirdMonth = zonedDateTime.minusMonths(3);
thirdMonth.getOffset(); // +01:00
获得JVM当前默认时区ID:
ZoneId.systemDefault()
获得JVM当前默认Offset:
ZoneOffset offset = ZonedDateTime.now().getOffset();
ZoneOffset offset = OffsetDateTime.now().getOffset();
●Instant 它代表的是时间戳,比如2016-04-14T14:20:13.592Z,Instant是无时区的
,这可以从java.time.Clock类中获取,像这样:
Instant current = Clock.system(ZoneId.of("Asia/Tokyo")).instant();
Clock:时钟,比如获取目前美国纽约的时间
Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的
,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
●LocalDate 它表示的是不带时间的日期,比如:2021-04-14。
●LocalTime - 它表示的是不带日期的时间,比如:19:23:01.438。
●LocalDateTime - 它包含了时间与日期,不过没有带时区的偏移量,比如:2021-04-27T19:30:42.749。
●ZonedDateTime - 这是一个带时区的完整时间,它根据UTC/格林威治时间来进行时区调整
,比如:2021-04-27T19:31:43.090+08:00[Asia/Shanghai]
由于ZonedDateTime是包含时区信息的,所以要构造或者转换成ZonedDateTime必须指定ZoneId,
比如LocalDateTime,示例如下:
LocalDateTime localDateTime = LocalDateTime.now();
//LocalDateTime转ZonedDateTime需要指定ZoneId
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.systemDefault());
//ZonedDateTime转LocalDateTime,只需要去掉ZonedDateTime中包含的ZoneId即可
LocalDateTime localDateTime2 = zonedDateTime.toLocalDateTime();
Instant也是没有时区信息的,所以从Instant构造ZonedDateTime也要指定ZoneId,示例如下:
ZonedDateTime.ofInstant(Instant.now(),ZoneId.systemDefault())
Clock是有时区信息的,所以从Clock构造ZonedDateTime无需指定ZoneId,示例如下:
ZonedDateTime.now(Clock.systemDefaultZone());
ZonedDateTime 示例:2021-04-27T19:31:43.090+08:00[Asia/Shanghai],不仅包含了LocalDateTime和ZoneId还包含了ZoneOffset.但是上面在构造ZonedDateTime的时候并没有指定ZoneOffset,为什么呢?
在ZoneDateTime中也是可以传入ZoneOffset的,示例如下:
ZoneOffset offset = OffsetDateTime.now().getOffset();//我本地是上海偏移量是(+8)
ZonedDateTime zonedDateTime2 = ZonedDateTime.ofInstant(localDateTime, zoneOffset, ZoneId.systemDefault());//2021-04-27T20:21:35.300+08:00[Asia/Shanghai]
可不可以传别的值呢,比如+9,肯定是可以的,示例如下:
ZoneOffset zoneOffset3 = ZoneOffset.of("+09:00");
ZonedDateTime zonedDateTime3 = ZonedDateTime.ofInstant(localDateTime, zoneOffset3, ZoneId.systemDefault());//2021-04-27T19:21:35.300+08:00[Asia/Shanghai]
可以看到和上面的相比,时间不是20点而是19点。如果是想把LocalDateTime转成ZoneDateTime这是不对的,所以最好还是不要调用这个方法,要调用不带Offset参数的,这个方法什么场景会调用,还不太清楚,如果有大神指教,不胜感激。
所以上面的答案是:ZoneID决定偏移量的变化方式和时间。不能自由设置偏移量,因为区域控制哪些偏移量是有效的。
●OffsetDateTime类实际上包含了LocalDateTime与ZoneOffset。它用来表示一个包含格林威治时间偏移量(+/-小时:分,比如+06:00或者 -08:00)的完整的日期(年月日)及时间(时分秒,纳秒),但不包含时区 ID。比如:2021-04-29T15:37:11.331+08:00
所以没有时区或Offset信息的日期转成OffsetDateTime,需要指定ZoneId或者Offset
//Instantto OffsetDateTime
OffsetDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
//LocalDateTime to OffsetDateTime
LocalDateTime.now().atOffset(OffsetDateTime.now().getOffset())
或者
OffsetDateTime.of(LocalDateTime.now(),OffsetDateTime.now().getOffset())
ZonedDateTime和OffsetDateTime之间的区别
ZonedDateTime:
● 存储所有日期和时间字段,精度为纳秒,时区,区域偏移用于处理模糊的本地日期时间
● 无法自由设置偏移,因为区域控制有效的偏移值
● 完全支持DST并处理夏令时调整
● 在用户特定的时区显示日期时间字段非常方便
OffsetDateTime:
● 存储所有日期和时间字段,精度为纳秒,以及与GMT / UTC的偏移量(无时区信息)
● 应该用于在数据库中存储日期或通过网络进行通信
Clock是有时区信息的,所以从Clock构造OffsetDateTime无需指定ZoneId,示例如下:
OffsetDateTime.now(Clock.systemDefaultZone());
构造OffsetDateTime还可以调用它的from(TemporalAccessor temporal)方法,但是注意一定是要包含时区或者Offset信息的实现类,否则运行时报异常。提示信息类似:“Unable to obtain ZoneOffset from TemporalAccessor”
OffsetDateTime.from(OffsetDateTime.now());//正确
OffsetDateTime.from(ZonedDateTime.now());//正确
OffsetDateTime.from(LocalDateTime.now());//运行报错
OffsetDateTime.from(LocalTime.now());//运行报错
OffsetDateTime.from(LocalDate.now());//运行报错
Duration:计算两个“时间”的间隔
Period:用于计算两个“日期”的间隔
ZoneOffset:时区偏移量,比如:+8:00
ZonedDateTime:可以得到特定时区的日期/时间
总之,1、时区和Offset可以转化,所以有时候可以替代,但是尽量使用时区ZoneId.
2、无时区时间转化为有时区时间,需要指定ZoneId.反过来不必。
Temporal 接口、TemporalAdjuster 接口与TemporalAdjusters工具类
Temporal可以根据实际需求表示为日期、时间或两者组合。其几个实现类包括:
LocalDate – 表示没有时区的日期
LocalDateTime – 表示没有时区的日期和时间
TemporalAdjuster用于时间较正,例如,可以获得当月的最后一天、下一年的第一天。一般调用时间日期的with方法,例如:
localDate.with(TemporalAdjusters.lastDayOfMonth());
TemporalAdjusters工具类有很多预定义的TemporalAdjuster实现。
例如:
dayOfWeekInMonth() – 一周中的某一天,例如,三月中第二个星期二
firstDayOfMonth() – 当前月的第一天
firstDayOfNextMonth() – 下一个月的第一天
firstDayOfNextYear() – 下一年的第一天
firstDayOfYear() – 当年的第一天
lastDayOfMonth() – 当月的最后一天
nextOrSame() – 下一次或当天发生的一周中的某天
自定义TemporalAdjuster 实现,比如获得之后14天的日期。
LocalDate localDate = LocalDate.of(2021, 04, 30);
TemporalAdjuster temporalAdjuster = t -> t.plus(Period.ofDays(14));
LocalDate result = localDate.with(temporalAdjuster);//输出2021-05-14
时间段 Period与Duration
LocalDate birthDay = LocalDate.of(2021, 02, 15);
LocalDate localDate = LocalDate.now();
//两个日期差
Period period = Period.between(birthDay, localDate);
//从Period 获得月份或者天
period.getMonths(), period.getDays());
//加3天
birthDay.plus(Period.ofDays(3))//2021-02-18
// 计算时间差值
Instant instant = Instant.now();
Instant instant1 = instant.plus(Duration.ofMinutes(2));
Duration duration = Duration.between(instant, instant1);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。