【Java笔记】一起neng清楚Java8的时间吗?

一起neng清楚Java8的时间吗?


如果觉得对你有帮助,能否点个赞或关个注,以示鼓励笔者呢?!博客目录 | 先点这里

  • 前提概念
    • 需要注意的时间概念
    • 时间戳与时间
    • 冬夏令时
  • UTC GMT CST 傻傻分不清?
    • 时间格式有哪些?
    • UT, UTC, GMT, CST的区别
  • Java的时间类
    • LocalDateTime, Instant, OffsetDateTime, ZoneDateTime
    • ZoneId 和 ZoneOffset
  • 时间类API实践
    • 获取Java所支持的所有时区
    • 根据时区获得偏移量
    • 获取当前时间
    • 获取当前时间戳
    • 不同时区的时间进行转换
  • 时间类转换实践
    • LocalDateTime与时间戳的互转
    • Date与时间戳的互转
    • LocalDateTime与Date的互转
  • 相关问题
    • LocalDateTime转换为ZoneDateTime
    • 为什么LocalDateTime转换成Instant需要偏移量信息?
    • 为什么时间戳是从1970年1月1日开始的?

前提概念


需要注意的时间概念

  • 时区是存在冬令时,夏令时的概念,某些地区根据当前所处的时间段的不同,其时区偏移量也会有所不同。
  • 每个国家的时区可能不一样,一些国家可能同时存在多个时区的
  • 要区别时间戳和时间的概念,以及时间戳是从何时开始算起的

时间戳与时间

  • UNIX时间戳
    UNIX时间戳(UNIX Time Stamp)为世界协调时间(Coordinated Universal Time,即UTC)。是1970年01月01日00时00分00秒到现在的总,秒数,与时区无关。
  • Date, LocalDateTime
    JDK8以前的时间标准是Date类,之后的标准是LocalDateTime一系列。无论是Date,还是LocalDateTime都是无时区概念的,它们就只是一个瞬时时间。

冬夏令时

什么是冬夏令时?

  • 又称 “日光节约时制”“夏令时间” ,是一种为节约能源而人为规定地方时间的制度,在这一制度实行期间所采用的统一时间称为 `“夏令时间” 。

  • 一般在天亮早的夏季人为将时间提前一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。各个采纳夏时制的国家具体规定不同。目前全世界有近110个国家每年要实行夏令时。

  • 所以要注意的问题是,一些国家或地区,根据当前所处的时间的不同以及当地采用夏令时政策与否,会导致当地的时间距离UTC 0时区的偏移量发生变化。


UTC GMT CST 傻傻分不清?


时间格式有哪些?

  • UT(Universal Time 世界时)
  • UTC(协调世界时)又称世界统一时间、世界标准时间、国际协调时间
  • GMT(格林尼治标准时间)一般指世界时
  • CST(中央标准时间)可视为美国、澳大利亚、古巴或中国的标准时间。

UT, UTC, GMT, CST的区别

通常而言,如果对时间没有严苛精确(相差1s)的要求,其实不我们是不需要关注这些时间格式的, 比如北京时间 = UTC+8 = GMT+8 = UT+8

  • 什么是UT时间格式?
    UT 称之为世界时,是基于天体观察计算出来的时间。UT本身也是一个广泛的概念,其包括UT0, UT1, UT2等子概念。其中UT0是完全按照天体运行计算出来的时间,而UT1则是在UT0的基础上做了一些调整,UT2则是在UT0,UT1的基础上做了调整。UT0是最原始的观测结果计算值,UT1是修正了地球在长时间尺度下会产生的自转轴漂移的影响,UT2则是为了研究需要,比UT1多修正了季节性的影响

  • 什么是UTC时间格式?
    UTC称之为世界标准时间或世界协调时间,是当今最主要的世界时间标准,以原子时秒长为基础。国际原子时的误差为每日数纳秒,世界时的误差为每日数毫秒,所以为了保证UTC和UT的时间误差不超过0.9秒,在有需要的情况下,会在世界协调时间加上正负闰秒。

  • 什么是GMT时间格式?
    GMT通常是指位於伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。 GMT的测量方式本质也是基于观察天体运动, 但由于 GMT的测量方式是比较传统的测量方式,实际地球每天的自转是有些不规则的,而且正在减缓,所以格林尼治时间已经不再作为时间标准时间。

注意:
但因为GMT过去长期作为世界时间的标准,所以GMT也成为世界标准时间的代名词。所以通常我们所说的GMT时间可以等价UTC世界协调时间。起码在Java的时间类中,是这么一回事。

CST的解释未完待续…


Java的时间类


LocalDateTime, Instant, OffsetDateTime, ZoneDateTime

话说,LocalDateTime, Instant ,OffsetDateTime, ZoneDateTime在java8中都可以表示时间,但是他们具体有什么区别呢?

private static void jdk8TimeTest() {
    System.out.println(LocalDateTime.now());
    System.out.println(Instant.now());
    System.out.println(OffsetDateTime.now());
    System.out.println(ZonedDateTime.now());
 }
2019-11-18T15:01:59.835255
2019-11-18T07:01:59.835660Z
2019-11-18T15:01:59.840892+08:00
2019-11-18T15:01:59.841414+08:00[Asia/Shanghai]
  • LocalDateTime是不带时区信息的,它就是一个瞬时时间,而Instant, OffsetDateTime, ZoneDateTime则是带有时区信息的。
  • 准确的说,Instant, ZoneDateTime是带有时区信息的,OffsetDateTime是没有时区信息,但是带有偏移量信息。为什么要这样区分呢,说白了就是冬夏令时的概念让相同时区在不同时间段会有不同的时间偏移量。因为ZoneDateTime是与时区相关的,因为冬夏令时,时间偏移量会有所不同,但是OffsetDateTime是没有时区概念的,直接与时间偏移量相关,具有固定的时间偏移量
  • 而Instant又是什么呢?与其他三种时间格式最大的区别就是Instant并不是给人类看的,而是用来给机器看的时间。说白了,Instant就是时间戳的载体。对人类而言,给你一串时间戳和一串yyyy-MM-dd, 你肯定更乐意看到yyyy-MM-dd格式的时间。但是机器而言,它并不能识别出yyyy-MM-dd是什么,你还需要将他转义成时间戳,机器才能看的懂。

在这里插入图片描述上图是新老时间类的对应关系


ZoneId 和 ZoneOffset 两兄弟

  • ZoneId代表的是新时间类的地区信息 ,比如Asia/Shanghai
  • ZoneOffset代表的是新时间类的时间偏移量, 比如+08:00

我们知道世界上有很多个国家,每个国家因为所处地球位置的不同,所采取的时区也有所不同。ZoneId就是Java中可以描述不同地区的类。而ZoneOffset就代表是距离0时区的时间偏移量。通常我们根据唯一的地区信息,就可以获取时区偏移量。但是因为有的地区存在冬夏令时的区别,所以根据地区和所处的时间,该地区所采取的时间偏移量可能会有些许偏差。

所以通常我们通过ZoneId去获取ZoneOffset时,需要传入一个时刻信息,以正确获得当前时刻该地区的时间偏移量


时间类API实践


获取Java所支持的所有时区

 private static void getAvailableRegion() {
    Set<String> regions = ZoneId.getAvailableZoneIds();
    System.out.println(regions.size());
    System.out.println(regions);
  }
600
[Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi, America/Marigot, Asia/Aqtau, Pacific/Kwajalein, America/El_Salvador, Asia/Pontianak, Africa/Cairo, Pacific/Pago_Pago, Africa/Mbabane, Asia/Kuching, Pacific/Honolulu, Pacific/Rarotonga, America/Guatemala, Australia/Hobart, Europe/London, America/Belize, America/Panama, Asia/Chungking, America/Managua, America/Indiana/Petersburg, Asia/Yerevan, Europe/Brussels, GMT, Europe/Warsaw, America/Chicago, Asia/Kashgar, Chile/Continental, Pacific/Yap, CET, Etc/GMT-1, Etc/GMT-0, Europe/Jersey, America/Tegucigalpa, Etc/GMT-5, Europe/Istanbul, America/Eirunepe, Etc/GMT-4, America/Miquelon, Etc/GMT-3, Europe/Luxembourg, Etc/GMT-2, Etc/GMT-9, America/Argentina/Catamarca, Etc/GMT-8, Etc/GMT-7, Etc/GMT-6, Europe/Zaporozhye, Canada/Yukon, Canada/Atlantic, Atlantic/St_Helena... ]

根据时区获得偏移量

举个墨西哥的例子 ,Mexico时区时间
在这里插入图片描述

/**
 * 根据zoneId和0时区当前时间获得此时zoneId地区的时间偏移量
 */
public static void main(String[] args) {
	// 0时区
    Long time2019_11_18 = 1574086401759L;
    Long time2020_4_6 = 1586140180000L;
    String mexico = "America/Mexico_City";

    System.out.println(ZoneId.of(mexico).getRules().getOffset(Instant.ofEpochMilli(time2019_11_18)));
    System.out.println(ZoneId.of(mexico).getRules().getOffset(Instant.ofEpochMilli(time2020_4_6)));
 }
  • 因为时间不仅仅有时区的坑,还有冬夏令时的坑。所以某些地区的时区偏移量根据不同的时间是会发生变化的。
  • 比如墨西哥首都所用的时区America/Mexico_City , 在UTC时间2019-11-18日采用时区偏移量是"-06:00"; 在UTC时间2020-04-06日采用时区偏移量是"-05:00" 。所以有时候我们需要根据地区和当前时间去动态获取时区偏移量

获取当前时间

  • 获取默认时区的当前时间
 private static void getCurTime() {
    System.out.println(LocalDateTime.now());
    System.out.println(LocalDateTime.now(Clock.systemDefaultZone()))
    System.out.println(LocalDate.now());
    System.out.println(LocalTime.now());
 }
2019-11-18T11:33:18.838352
2019-11-18T11:33:18.838894
2019-11-18
11:33:18.839033
  • 获取UTC时区的当前时间
private static void getUTCCurTime() {
    System.out.println(LocalDateTime.now(ZoneId.of("UTC")));
    System.out.println(LocalDateTime.now(Clock.systemUTC()));
  }
2019-11-18T03:33:48.602664
2019-11-18T03:33:48.603065
  • 获取某个时区的当前时间
private static void getRegionCurTime() {
	// 获取墨西哥时区的时间
    System.out.println(LocalDateTime.now(ZoneId.of("America/Mexico_City")));
    // 获得上海时区的时间
    System.out.println(LocalDateTime.now(ZoneId.of("Asia/Shanghai")));
    // 获得UTC+08:00时区的时间
    System.out.println(LocalDateTime.now(ZoneOffset.ofHours(8)));
    // 获得UTC-08:00时区的时间
    System.out.println(LocalDateTime.now(ZoneOffset.ofHours(-8)));
  }
2019-11-17T21:16:02.704395
2019-11-18T11:16:02.707218
2019-11-18T11:16:02.707353
2019-11-17T19:16:02.707463

获取当前时间戳

注意一个问题

  • 时间戳是没有时区概念的,它默认就是UTC时区,从1970年01月01日00时00分00秒到现在的秒数/毫秒数
  • 时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数/毫秒数。

获得当前时间的时间戳

/**
 * 时间戳没有时区的概念,Clock带有时区的概念是为转为Date做准备的
 */
private static void getCurTimestamp() {
   // 通过系统函数获取时间戳
   System.out.println(System.currentTimeMillis());
   
   // 通过Clock获取时间戳
   System.out.println(Clock.systemDefaultZone().millis());
   System.out.println(Clock.systemUTC().millis());
   
   // 通过Instant获取时间戳
   System.out.println(Instant.now().toEpochMilli());
   System.out.println(Instant.now(Clock.systemUTC()).toEpochMilli());
}
1574048584043
1574048584064
1574048584064
1574048584064
1574048584064
  • Instant, Clock的本质就是通过Clock获取时间戳, 感觉还是直接currentTimeMillis来的舒服

不同时区的时间进行转换

  • LocalDateTime, ZoneDateTime, OffsetDateTime之间的时区转换
private static void changeRegion() {
        /**
         * +8时区时间
         */
        System.out.println(LocalDateTime.now(ZoneId.of("Asia/Shanghai")));
        /**
         * -6时区时间
         */
        System.out.println(LocalDateTime.now(ZoneId.of("America/Mexico_City")));

        /**
         * zoneDataTime从+8区转换到-6区的时间
         */
        System.out.println(ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).withZoneSameInstant(ZoneId.of("America/Mexico_City")));

        /**
         * LocalDateTime从+8区转换到-6区的时间
         */
        System.out.println(LocalDateTime.now(ZoneId.of("Asia/Shanghai")).atZone(ZoneId.of("Asia/Shanghai")).withZoneSameInstant(ZoneId.of("America/Mexico_City")).toLocalDateTime());

        /**
         * OffsetDateTime从+8区转换到-6区的时间
         */
        System.out.println(OffsetDateTime.now(ZoneId.of("Asia/Shanghai")).withOffsetSameInstant(ZoneId.of("America/Mexico_City").getRules().getOffset(LocalDateTime.now())));
    }
2019-11-18T20:04:27.177675
2019-11-18T06:04:27.178720
2019-11-18T06:04:27.180764-06:00[America/Mexico_City]
2019-11-18T06:04:27.180896
2019-11-18T06:04:27.181374-06:00

时间类转换实践


LocalDateTime与时间戳的互转

(一) LocalDateTime转换为时间戳

private static void jdk8TimeToLong() {

        System.currentTimeMillis();

        /**
         * 将默认时区的当前时间,转换为时间戳
         */
        System.out.println(LocalDateTime.now().atZone(Clock.systemDefaultZone().getZone()).toInstant().toEpochMilli());
        System.out.println(LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
        System.out.println(ZonedDateTime.now().toInstant().toEpochMilli());
        System.out.println(ZonedDateTime.now(Clock.systemDefaultZone()).toInstant().toEpochMilli());
        System.out.println(OffsetDateTime.now().toInstant().toEpochMilli());
        System.out.println(OffsetDateTime.now(Clock.systemDefaultZone()).toInstant().toEpochMilli());

        /**
         * 将UTC时区的当前时间,转换成时间戳
         */
        System.out.println(LocalDateTime.now(Clock.systemUTC()).toInstant(ZoneOffset.UTC).toEpochMilli());
        System.out.println(ZonedDateTime.now(Clock.systemUTC()).toInstant().toEpochMilli());
        System.out.println(OffsetDateTime.now(Clock.systemUTC()).toInstant().toEpochMilli());

        /**
         * 将指定时区(墨西哥-6)的当前时间,转换成时间戳
         */
        System.out.println(LocalDateTime.now(ZoneId.of("America/Mexico_City")).atZone(ZoneId.of("America/Mexico_City")).toInstant().toEpochMilli());
        System.out.println(ZonedDateTime.now(ZoneId.of("America/Mexico_City")).toInstant().toEpochMilli());
        System.out.println(OffsetDateTime.now(ZoneId.of("America/Mexico_City")).toInstant().toEpochMilli());
        
}
  • LocalDateTime是一个瞬时时间,这个类是并没有时区的概念。
    但是我们LocalDateTime.now()获取的时间实际上却是某个时区的当前时间。理解这点很重要,虽然很绕,所以我们可以简单的理解成,我们根据时区信息获得了该时区的当前时间LocalDateTime, 但是它就类似一个写死的字符串,仅仅表示一个瞬时时刻。

  • 那么我们如何将一个没有时区概念的瞬时时间字符串,转换成时间戳呢?
    因为时间戳也是没有时区的概念,它的含义等同于UTC时区下,1970年到当前时刻的毫秒数。所以如果我们要将某个时区的当前时间转换成时间戳。我们就需要经历两个步骤
    (1) 将某时区的时间转换为UTC 0时区时间
    (2) 然后计算1970年到该0时区时间的毫秒值

  • 为什么需要转换成0时区的时间?
    (1) 因为时间戳的定义就是0时区下,1970年01月01日00时00分00秒到当前时刻(0)是毫秒数。或者说北京时间(+8)下,1970年01月01日08时00分00秒到当前时刻(+8)是毫秒数。
    (2) 比如我们的LocalDateTime是北京时区下获得的时间。我们直接直接求1970年01月01日00时00分00秒到该北京时间时刻,就会发现求的的时间戳多了8个小时的毫秒数
    时间戳 - @百度百科

  • 所以我们可以看到LocalDateTime的toInstant方法是需要一个偏移量
    该ZoneOfffset偏移量是指某时区到0时区的时间偏移量,比如相差多少秒,多少小时。当我们传入偏移量后,toInstant方法就会根据偏移量自动处理时区带来的时间戳偏差,并求得正确的时间戳

(一) 时间戳转换为LocalDateTime

private static void longToJDK8Time() {

        /**
         * 0时区-2019-11-18 02:29:40
         * +8时区-2019-11-18 10:29:40
         * -6时区-2019-11-17 20:29:40
         */
        Long time = 1574044180000L;

        /**
         * 将时间戳转换成默认时区的当前时间
         */
        System.out.println(LocalDateTime.ofInstant(Instant.ofEpochMilli(time),ZoneId.systemDefault()));
        System.out.println(Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()));
        System.out.println(Instant.ofEpochMilli(time).atOffset(ZoneOffset.ofHours(8)));

        /**
         * 将时间戳转换成UTC 0时区的当前时间
         */
        System.out.println(LocalDateTime.ofInstant(Instant.ofEpochMilli(time),ZoneId.of("UTC")));
        System.out.println(Instant.ofEpochMilli(time).atZone(ZoneId.of("UTC")));
        System.out.println(Instant.ofEpochMilli(time).atOffset(ZoneOffset.UTC));

        /**
         * 将时间戳转换成指定时区的当前时间
         */
        System.out.println(LocalDateTime.ofInstant(Instant.ofEpochMilli(time),ZoneId.of("America/Mexico_City")));
        System.out.println(Instant.ofEpochMilli(time).atZone(ZoneId.of("America/Mexico_City")));
        System.out.println(Instant.ofEpochMilli(time).atOffset(ZoneOffset.of("-6")));

    }
2019-11-18T10:29:40
2019-11-18T10:29:40+08:00[Asia/Shanghai]
2019-11-18T10:29:40+08:00
2019-11-18T02:29:40
2019-11-18T02:29:40Z[UTC]
2019-11-18T02:29:40Z
2019-11-17T20:29:40
2019-11-17T20:29:40-06:00[America/Mexico_City]
2019-11-17T20:29:40-06:00
  • Instant.ofEpochMilli(timestamp)可以获得0时区的Instant时刻。 然后将Instant转换成我们所需要的LocalDateTime, OffsetDateTime, ZoneDateTime;

Date与时间戳的互转

Date now = new Date(System.currentTimeMillis());
Long nowTime = now.getTime();
  • 是不是有一种过于简单,过于支持,过于舒服的赶脚

LocalDateTime和Date的互转

public void convert() { 
    // LocalDateTime to Date
    LocalDateTime localDateTime1 = LocalDateTime.now();
    Long nowTime = localDateTime1.toInstant(ZoneOffset.ofHours(+0)).toEpochMilli();
    Date date1 = new Date(nowTime);
    
    // Date to LocalDateTime
    Date date = new Date();
    Instant instant = Instant.ofEpochMilli(date.getTime());
    LocalDateTime localDateTime2 = LocalDateTime.ofInstant(instant, ZoneId.of("UTC"));
}    
  • LocalDateTime和Date之间的互转,其中间类就是Instant , 再精确点,其实就是时间戳

相关问题


LocalDateTime转换为ZoneDateTime

LocalDateTime是没有时区概念的,同时通过atZone方法转换为ZoneDateTime也是不会将时间转换成对应时区的时间的。仅仅是在原LocalDateTime瞬时时间的基础上加上时区的信息

public static void main(String[] args) {
    System.out.println(LocalDateTime.now());
    System.out.println(LocalDateTime.now().atZone(ZoneId.of("UTC")));
}
// 仅仅是增加了时区信息,时间并没有发生改变
2019-11-18T16:12:23.020021
2019-11-18T16:12:23.020445Z[UTC]

为什么LocalDateTime转换成Instant需要偏移量信息?

  • LocalDateTime转换成Instant需要时区或时间偏移量信息
// 我们获取utc+8的当前时间
LocalDateTime date = LocalDateTime.now(ZoneOffset.ofHours(8));
// 然后将该时间转换成Instant, 并告诉Instant这个date是utc+8的时间
Instant instant = date.toInstant(ZoneOffset.ofHours(8));
  • ZoneDateTime或OffsetDateTime转换成Instant则不需要时区或时间偏移量信息
Instant instant1 = ZonedDateTime.now(ZoneOffset.ofHours(8)).toInstant();
Instant instant2 = OffsetDateTime.now(ZoneOffset.ofHours(8)).toInstant();

因为LocalDateTime就是一个时间字符串,它没有时区的概念。而Instant是具有时区概念的。当我们把一个无时区概念的纯粹时间字符串转换成Instant时,我们就需要告诉Instant, 这个时间字符串距离UTC 0时区的时间偏移量是多少,这样我们的Instant才能正确的表示LocalDateTime时刻的时间是正确时区的时刻。

说白了就是 , 计算机并不能知道LocalDateTime是什么时区的时间,所以我们需要人为的告诉计算机,这个LocalDateTime是哪个时区的时间,这样我们才能知道LocalDateTime属于哪个时区的时刻,而不属于其他时间的时刻,才能得到一个带时区概念,且正确的Instant.

同理,当我们将ZoneDateTime或OffsetDateTime转换成Instant时就会发现,他们并不需要传入ZoneOffset参数。这是因为ZoneDateTime和OffsetDateTime本身是带有时区或时间偏移量信息的。所以并不需要额外再告诉Instant时区信息


为什么时间戳是从1970年1月1日开始的?

Unix时间戳的定义

  • Unix时间戳的定义是从格林尼治时间 1970年01日01月 00时00分00毫秒起至现在的总秒数,不考虑闰秒

为什么要从1970年开始?

  • 最初计算机操作系统是32位,而时间也是用32位表示。32位能表示的最大值是2147483647。另外1年365天的总秒数是31536000,2147483647/31536000 = 68.1,也就是说32位能表示的最长时间是68年,从1970年开始算起,只能表示到68年2038年01月19日03时14分07秒,便会到达最大时间,过了这个时间点,所有32位操作系统时间便会变为10000000 00000000 00000000 00000000 ,这样便会出现时间回归的现象,造成时间异常

  • 说白了就是因为早期32位操作系统是哪个时间点发明的,后来随着64位操作系统的出现,虽然已经可以解决时间回归的问题,但是为了兼容以前32位操作系统的设计,把1970年作为时间戳的开始时间的设计也就保留至今


服务端统一时间方案

  • 数据库统一时区设置为UTC
  • 服务端应用的时区统一设置为UTC

时间偏移量前缀UTC+8 还是GMT+8 ?


参考资料


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章