首页 > 编程语言 >时区以及时区对于Java时间类格式化的影响

时区以及时区对于Java时间类格式化的影响

时间:2024-09-02 22:52:07浏览次数:9  
标签:UTC 00 45 格式化 对于 09 2024 Java 时区

时区基本概念

时区(Time Zone)是指地球上的一个地区与格林尼治标准时间(GMT)或协调世界时(UTC)之间的时间差异。由于地球自转的原因,不同的地理位置会有不同的时间。时区的划分使得世界各地能够更合理地安排时间,保持同步。

UTC(协调世界时): UTC 是一种标准时间,它没有受到地球自转速度变化影响,是全世界时间标准的基础。所有的时区都是相对于 UTC 来定义的,例如 UTC+8 表示比 UTC 早 8 个小时的时间。

GMT(格林尼治标准时间): GMT 是一种基于地球自转和太阳位置的时间标准,虽然它和 UTC 时间非常接近,但由于 GMT 是一种天文时间,存在微小的变化。

时区偏移(Offset): 时区通常会以 UTC 偏移的方式表示,例如 UTC+0:00、UTC+05:30、UTC-8 :00等等。UTC+8:00 的时区意味着这个时区比 UTC 早 8 个小时,比如中国的北京时间。

夏令时(Daylight Saving Time, DST): 某些国家和地区会使用夏令时,在夏季时将时间拨快一小时,以充分利用日照时间。夏令时的使用会导致某些地方的时间偏移在一年中有变化。

Java中的时区API

JDK8之前的TimeZone

/**
 * 获取所有可用的zoneId
 */
public static synchronized String[] getAvailableIDs();

/**
 * 获取所在地默认时区,国内便是Asia/Shanghai,东八区GMT+08:00
 */
public static TimeZone getDefault();

/**
 * 根据ID获取时区,注意如果是一个不支持的ID,则会降级到GMT时区,而不是报错,因此有一个坑。
 * 格式:
 * 标准的地区名如Asia/Shanghai
 * UTC、GMT
 * 偏移量: GMT+08:00 (注意不支持UTC+08:00), 然后偏移量只支持到分钟级别
 * 这个API支持单独的UTC,但是不支持UTC开头的偏移量
 * 因此如果你使用了UTC+08:00,则会降级到GMT时区,产生诡异现象。
 */
public static synchronized TimeZone getTimeZone(String ID);

以地区命名的id和偏移量区别

比如: Asia/Shanghai和GMT+08:00,对于大部分情况而言是对等的,然而由于夏令时的存在,使得时钟拨快了一个小时,使用Asia/Shanghai的话,会根据规则判断当前时间是不是在夏令时区间,从而计算出的偏移量是+09:00,而使用GMT+08:00则是固定的。因此使用Asia/Shanghai这个地区命名的不用用户考虑这种繁琐的夏令时规则。

JDK8新增的ZoneId、ZoneOffset

ZoneId

/**
 * 获取所有可用的zoneId
 */
public static Set<String> getAvailableZoneIds();

/**
 * 获取所在地默认时区,国内便是Asia/Shanghai,东八区UTC+08:00
 */
public static ZoneId systemDefault();

/**
 * 根据ID获取时区,这个方法做了改进,不支持的参数会直接报错而不是降级到UTC,非常棒。
 * 格式:
 * 标准的地区名如Asia/Shanghai
 * Z、UTC、GMT。这三个等价,相当于UTC+0
 * 偏移量: UTC+08:00 GMT+08:00
 * 这个API同时支持UTC和GMT前缀
 */
public static ZoneId of(String zoneId);

ZoneOffset

代表UTC时区的偏移量,没有了地区名写法。ZoneOffset继承了ZoneId

/**
 * 覆盖了ZoneId的of方法
 * 格式: 不能写前缀,只能写偏移量
 * +08:00
 * -08:00
 * 特别的这个偏移量可以精确到秒
 * +08:00:30
 */
public static ZoneOffset of(String offsetId);

/**
 * 根据小时的偏移量
 * ofHours(8)相当于+08:00
 * ofHours(-8)相当于-08:00
 */
public static ZoneOffset ofHours(int hours);

ZoneOffset不仅仅提供了ofHours方法,诸如ofHoursMinutes等方法,一眼就懂。

新老时区API互转

TimeZone提供了两个方法

/**
 * 静态方法
 */
public static TimeZone getTimeZone(ZoneId zoneId);

/**
 * 成员方法
 */
public ZoneId toZoneId();

纪元毫秒(epochMillis)

纪元毫秒是从计算机的纪元时间(Epoch time)开始经过的毫秒数。纪元时间通常被定义为 1970年1月1日 00:00:00 UTC。纪元毫秒通常用来表示计算机系统中日期和时间的时间戳。

注意是UTC时区的1970-01-01 00:00:00

给定一个毫秒数,它代表非UTC时区的具体哪个时间,必须要指定时区才有意义。

比如1000这个纪元毫秒数,即1秒

等同于UTC时区的1970-01-01 00:00:01

也等同于UTC+8:00时区的1970-01-01 08:00:01

想反的,给定一个时间格式字符串,必须指定时区,才能转成纪元毫秒数

java.util.Date

Date类本身存储的就是纪元毫秒数,getTime方法就可以获取到这个存储的纪元毫秒数。

但是Date类中的其它方法,诸如getYeargetDategetSeconds都是配合默认时区(中国是Asia/Shanghai)的一个时间来获取的。

这些getXXX方法都已经被标注过时了。

Date类中的toString方法也一样,作为显示用,内部调用了这些getXXX方法。

使用DateFormat格式化成字符时,得到的结果具体是哪个时间,取决于DateFormat中设置的timeZone,若没有指定,则默认值TimeZone.getDefault()

总结: Date类存储的值是纪元毫秒数,与时区无关,不过内部中的一些方法返回值使用了默认时区来计算。

java.sql.Timestamp

java.sql.Timestamp继承了java.util.Date,然后多加了一个nanos属性用来存储纳秒,值为[0 , 999,999,999]中的一个。也就是说增加了精度,其余和java.util.Date一样。

Instant

Instant为JDK8新增的一个类,可以用来代替java.util.Date,内部存储的是纪元纳秒数,精度会更高。可以翻译为时刻。

内部有两个属性

seconds:距离纪元时间的秒数

nanos:纳秒数,[0, 999,999,999],用于提高精度。

创建方法

/**
 * 根据毫秒数创建
 */
public static Instant ofEpochMilli(long epochMilli);

/**
 * 一个参数是秒
 * 第二个参数是纳秒数,可以为正也可以为负,用来调整最终的一个结果
 * 注意这两个参数不等同于Instant中的连个成员变量seconds、nanos
 */
public static Instant ofEpochSecond(long epochSecond, long nanoAdjustment);

/**
 * 当前时间对应的时刻
 */
public static Instant now();

基本使用

@Test
public void testInstant() {
    // 该毫秒数对应UTC时间为2024-09-02 13:45:49
    long millis = 1725284749641L;
    Instant instant = Instant.ofEpochMilli(millis);
    // 2024-09-02T13:45:49.641Z
    System.out.println(instant);
    instant = Instant.parse("2024-09-02T13:45:49.641Z");
    // 1725284749641
    System.out.println(instant.toEpochMilli());
    // 配合时区转成ZonedDateTime, 2024-09-02T21:45:49.641+08:00[Asia/Shanghai],可以看到时间加了8小时
    System.out.println(instant.atZone(ZoneId.of("Asia/Shanghai")));
    // 配合时区转成OffsetDateTime,2024-09-02T21:45:49.641+08:00
    System.out.println(instant.atOffset(ZoneOffset.ofHours(8)));
}

java.util.Date格式化

@Test
public void testFormatDate() {
    // 该毫秒数对应UTC时间为2024-09-02 13:45:49
    long millis = 1725284749641L;
    Date date = new Date(millis);

    String pattern = "yyyy-MM-dd HH:mm:ss";
    /*
     * SimpleDateFormat线程不安全, 方法会修改自身
     * timeZone默认为TimeZone.getDefault(),国内即Asia/Shanghai
     */
    DateFormat dateFormat = new SimpleDateFormat(pattern);

    DateFormat dateFormatGmt9 = new SimpleDateFormat(pattern);
    dateFormatGmt9.setTimeZone(TimeZone.getTimeZone("GMT+09:00"));

    // 东八区: 2024-09-02 21:45:49
    System.out.println(dateFormat.format(date));
    // 东九区: 2024-09-02 22:45:49
    System.out.println(dateFormatGmt9.format(date));
}

JSR310时间类格式化

@Test
public void testFormatJsr310Date() {
    /*
     * 下面4个都是代表东八区的2024-09-02 12:45:49
     * LocalDateTime没有时区概念
     * Instant + 东八区便能得到时间024-09-02 12:45:49
     */
    LocalDateTime localDateTime = LocalDateTime.of(2024,9, 2, 21, 45, 49);
    ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of("Asia/Shanghai"));
    OffsetDateTime offsetDateTime = OffsetDateTime.of(localDateTime, ZoneOffset.ofHours(8));
    Instant instant = Instant.ofEpochMilli(1725284749641L);

    // 2024-09-02T21:45:49
    System.out.println(localDateTime);
    // 2024-09-02T21:45:49+08:00[Asia/Shanghai]
    System.out.println(zonedDateTime);
    // 2024-09-02T21:45:49+08:00
    System.out.println(offsetDateTime);
    /*
     * 2024-09-02T13:45:49.641Z
     * Instant toString方法展示的是UTC时区的时间, 末尾的Z代表UTC
     * 那么对应于东八区的时间就是2024-09-02T21:45:49.64
     */
    System.out.println(instant);

    /*
     * DateTimeFormatter线程安全,和String一样,为不可变对象
     * 与SimpleDateFormat不同,时区字段默认是null,没有默认值
     */
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    // 2024-09-02 21:45:49
    System.out.println(formatter.format(localDateTime));
    // 2024-09-02 21:45:49
    System.out.println(formatter.format(zonedDateTime));
    // 2024-09-02 21:45:49
    System.out.println(formatter.format(offsetDateTime));
    // 会报错, 格式化成年月日这样形式, Formatter必须要指定时区
    Assertions.assertThrowsExactly(UnsupportedTemporalTypeException.class, () -> formatter.format(instant));

    // 指定东九区时区
    DateTimeFormatter utc9Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
            .withZone(ZoneOffset.ofHours(9));

    // 2024-09-02 21:45:49
    System.out.println(utc9Formatter.format(localDateTime));
    // 2024-09-02 22:45:49
    System.out.println(utc9Formatter.format(zonedDateTime));
    // 2024-09-02 22:45:49
    System.out.println(utc9Formatter.format(offsetDateTime));
    // 2024-09-02 22:45:49
    System.out.println(utc9Formatter.format(instant));
}

总结:

DateTimeFormatter是和String一样的不可变对象,线程安全。因此修改时一定记得要返回,否则无效哦。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
formatter = formatter.withZone(ZoneOffset.ofHours(9));

DateTimeFormatter中的时区属性默认值为null,而不是系统所在时区,和SimpleDateFormat不同。

LocalDateTime格式化时不会进行时区转换,因为本身没有时区概念。

ZonedDateTimeOffsetDateTime格式化时,只有当DateTimeFormatter明确指定了时区才会进行转换。ZonedDateTimeOffsetDateTime携带的时区为源,DateTimeFormatter中的时区为目标时区。

Instant格式化时,DateTimeFormatter必须指定时区,否则会报错。毕竟纪元毫秒数(纳秒数)搭配时区才能转成时间。

标签:UTC,00,45,格式化,对于,09,2024,Java,时区
From: https://www.cnblogs.com/wt20/p/18393708

相关文章

  • Java LeetCode练习
        LeetCodeExercise        807.保持城市天际线    题解:贪心        从左侧和右侧看,城市天际线等于矩阵grid的每一行的建筑物高度最大值;从顶部和底部看,城市天际线等于矩阵grid的每一列的建筑物高度最大值。只要不改变每一行和每一列......
  • 「Java跳槽面试必备」2024年09月最新八股文
    【前言】网上各种面试八股文太多太多,但我今年找了好几个都是很久很久以前的老面试题,老文档了,和我出去面试市场上面试官问的问题基本上不一样了,可以说被打了一个措手不及,浪费了好几个机会,回来又找了好一些资料,以及结合自己最近的面试情况总结了一些心得免费分享给大家!虽然只有几本......
  • Java学习案例:控制台实现电影院管理系统
    文章目录@[TOC](文章目录)前言一、实现效果1、登录界面以及注册功能演示2、普通用户登录(1)热映影片(2)即将上映(3)个人信息(4)票夹3、管理员登录(1)电影管理(2)用户管理(3)个人信息二、功能源码1、接收用户的合法输入2、控制台格式化输出3、创建工具类4、多个类之间数据传递三......
  • 《JavaEE进阶》----9.<SpringMVC实践项目:【简易对话留言板(数据存在数据库中)】>
    本篇博客讲解设计的一个网页版简易对话留言板。这个是将数据存在数据库中。我们通过链接本地数据库。在这里面存入的数据。此时数据存在在硬盘中,只要数据不被删除,硬盘不损坏。那么这些数据就会被永久保存引入的依赖:配置数据库:spring:datasource:#数据库连接配置......
  • Java表达式与语句
    文章目录Java表达式和语句1.变量2.运算符与表达式3.语句Java表达式和语句1.变量变量及作用域局部变量在一个方法或由一对{}表示的代码块内定义的变量称为局部变量,有时也称为自动变量、临时变量或堆栈变量。局部变量的作用域是所在的方法或代码块,当程序执行流......
  • Java 面向对象编程的四个基本原则(封装、继承、多态和抽象),并给出一个简单的例子说明如
    面向对象编程(OOP)是一种编程范式,它使用“对象”来设计软件。在Java中,面向对象编程的四个基本原则是封装、继承、多态和抽象。每个原则都有其特定的目标,帮助开发者构建更加模块化、可维护和可扩展的代码。封装封装是指将数据(属性)和行为(方法)捆绑在一起,并隐藏对象的具体实现细......
  • Java 面试题:事务隔离级别以及并行事务会出现什么问题&&怎么解决脏读、不可重复读和幻
    文章目录四种事务隔离级别MySQL中设置事务隔离级别四种事务隔离级别在并行事务中可能会遇到的问题脏读、不可重复读和幻读三者区别事务的隔离级别是怎么解决这三个问题的?ReadView是什么ReadView包含的信息ReadView在MVCC中的工作原理工作流程总结事务的隔......
  • 探索Groovy的Elvis操作符及其在Java中的替代方案
    在编程的世界里,我们经常需要处理变量的默认值问题,尤其是在变量可能为null的情况下。Groovy语言提供了一种优雅的方式来处理这种情况,那就是Elvis操作符。本文将探讨Elvis操作符的用法,并展示如何在Java中实现类似的功能。Elvis操作符简介Elvis操作符(?:)是Groovy语言中的一种......
  • 解耦利器 - Java中的SPI机制
    为什么需要SPI机制SPI和API的区别是什么SPI是一种跟API相对应的反向设计思想:API由实现方确定标准规范和功能,调用方无权做任何干预;而SPI是由调用方确定标准规范,也就是接口,然后调用方依赖此接口,第三方实现此接口,这样做就可以方便的进行扩展,类似于插件机制,这是SPI出现的需求背景。......
  • 解耦利器 - Java中的SPI机制
    为什么需要SPI机制SPI和API的区别是什么SPI是一种跟API相对应的反向设计思想:API由实现方确定标准规范和功能,调用方无权做任何干预;而SPI是由调用方确定标准规范,也就是接口,然后调用方依赖此接口,第三方实现此接口,这样做就可以方便的进行扩展,类似于插件机制,这是SPI出现的需求背景。......