全网最全!彻底弄透Java处理GMT/UTC日期时间(中)

简介: 全网最全!彻底弄透Java处理GMT/UTC日期时间(中)

SimpleDateFormat格式化


Java中对Date类型的输入输出/格式化,推荐使用DateFormat而非用其toString()方法。


DateFormat是一个时间格式化器抽象类,SimpleDateFormat是其具体实现类,用于以语言环境敏感的方式格式化和解析日期。它允许格式化(日期→文本)、解析(文本→日期)和规范化。


划重点:对语言环境敏感,也就是说对环境Locale、时区TimeZone都是敏感的。既然敏感,那就是可定制的


对于一个格式化器来讲,模式(模版)是其关键因素,了解一下:


日期/时间模式:


格式化的模式由指定的字符串组成,未加引号的大写/小写字母(A-Z a-z)代表特定模式,用来表示模式含义,若想原样输出可以用单引号’'包起来,除了英文字母其它均不解释原样输出/匹配。下面是它规定的模式字母(其它字母原样输出):


image.png


image.png


这个表格里出现了一些“特殊”的匹配类型,做如下解释:


  • Text:格式化(Date -> String),如果模式字母的数目是4个或更多,则使用完整形式;否则,如果可能的话,使用简短或缩写形式。对于解析(String -> Date),这两种形式都一样,与模式字母的数量无关


@Test
public void test9() throws ParseException {
    String patternStr = "G GG GGGGG E EE EEEEE a aa aaaaa";
    Date currDate = new Date();
    System.out.println("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓中文地区模式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    System.out.println("====================Date->String====================");
    DateFormat dateFormat = new SimpleDateFormat(patternStr, Locale.CHINA);
    System.out.println(dateFormat.format(currDate));
    System.out.println("====================String->Date====================");
    String dateStrParam = "公元 公元 公元 星期六 星期六 星期六 下午 下午 下午";
    System.out.println(dateFormat.parse(dateStrParam));
    System.out.println("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓英文地区模式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    System.out.println("====================Date->String====================");
    dateFormat = new SimpleDateFormat(patternStr, Locale.US);
    System.out.println(dateFormat.format(currDate));
    System.out.println("====================String->Date====================");
    dateStrParam = "AD ad bC Sat SatUrday sunDay PM PM Am";
    System.out.println(dateFormat.parse(dateStrParam));
}


运行程序,输出:


↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓中文地区模式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
====================Date->String====================
公元 公元 公元 星期六 星期六 星期六 下午 下午 下午
====================String->Date====================
Sat Jan 03 12:00:00 CST 1970
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓英文地区模式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
====================Date->String====================
AD AD AD Sat Sat Saturday PM PM PM
====================String->Date====================
Sun Jan 01 00:00:00 CST 1970


观察打印结果,除了符合模式规则外,还能在String -> Date解析时总结出两点结论:


  1. 英文单词,不分区大小写。如SatUrday sunDay都是没问题,但是不能有拼写错误
  2. 若有多个part表示一个意思,那么last win。如Sat SatUrday sunDay最后一个生效


对于Locale地域参数,因为中文不存在格式、缩写方面的特性,因此这些规则只对英文地域(如Locale.US生效)


  • Number:格式化(Date -> String),模式字母的数量是数字的【最小】数量,较短的数字被零填充到这个数量。对于解析(String -> Date),模式字母的数量将被忽略,除非需要分隔两个相邻的字段
  • Year:对于格式化和解析,如果模式字母的数量是4个或更多,则使用特定于日历的长格式。否则,使用日历特定的简短或缩写形式
  • Month:如果模式字母的数量是3个或更多,则被解释为文本;否则,它将被解释为一个数字。
  • 通用时区:如果该时区有名称,如Pacific Standard Time、PST、CST等那就用名称,否则就用GMT规则的字符串,如:GMT-08:00
  • RFC 822时区:遵循RFC 822格式,向下兼容通用时区(名称部分除外)
  • ISO 8601时区:对于格式化,如果与GMT的偏移值为0(也就是格林威治时间喽),则生成“Z”;如果模式字母的数量为1,则忽略小时的任何分数。例如,如果模式是“X”,时区是“GMT+05:30”,则生成“+05”。在进行解析时,“Z”被解析为UTC时区指示符。一般时区不被接受。如果模式字母的数量是4个或更多,在构造SimpleDateFormat或应用模式时抛出IllegalArgumentException。
  • 这个规则理解起来还是比较费劲的,在开发中一般不太建议使用此种模式。若要使用请务必本地做好测试


SimpleDateFormat的使用很简单,重点是了解其规则模式。最后关于SimpleDateFormat的使用再强调这两点哈:


  1. SimpleDateFormat并非线程安全类,使用时请务必注意并发安全问题
  2. 若使用SimpleDateFormat去格式化成非本地区域(默认Locale)的话,那就必须在构造的时候就指定好,如Locale.US
  3. 对于Date类型的任何格式化、解析请统一使用SimpleDateFormat


JSR 310类型


曾经有个人做了个很有意思的投票,统计对Java API的不满意程度。最终Java Date/Calendar API斩获第二烂(第一烂是Java XML/DOM),体现出它烂的点较多,这里给你例举几项:


  1. 定义并不一致,在java.util和java.sql包中竟然都有Date类,而且呢对它进行格式化/解析类竟然又跑到java.text去了,精神分裂啊
  2. java.util.Date等类在建模日期的设计上行为不一致,缺陷明显。包括易变性、糟糕的偏移值、默认值、命名等等
  3. java.util.Date同时包含日期和时间,而其子类java.sql.Date却仅包含日期,这是什么神继承?


image.png


@Test
public void test10() {
    long currMillis = System.currentTimeMillis();
    java.util.Date date = new Date(currMillis);
    java.sql.Date sqlDate = new java.sql.Date(currMillis);
    java.sql.Time time = new Time(currMillis);
    java.sql.Timestamp timestamp = new Timestamp(currMillis);
    System.out.println("java.util.Date:" + date);
    System.out.println("java.sql.Date:" + sqlDate);
    System.out.println("java.sql.Time:" + time);
    System.out.println("java.sql.Timestamp:" + timestamp);
}


运行程序,输出

java.util.Date:Sat Jan 16 21:50:36 CST 2021
java.sql.Date:2021-01-16
java.sql.Time:21:50:36
java.sql.Timestamp:2021-01-16 21:50:36.733
  • 国际化支持得并不是好,比如跨时区操作、夏令时等等

Java 自己也实在忍不了这么难用的日期时间API了,于是在2014年随着Java 8的发布引入了全新的JSR 310日期时间。JSR-310源于精品时间库joda-time打造,解决了上面提到的所有问题,是整个Java 8最大亮点之一。


JSR 310日期/时间 所有的 API都在java.time这个包内,没有例外。


image.png


当然喽,本文重点并不在于讨论JSR 310日期/时间体系,而是看看JSR 310日期时间类型是如何处理上面Date类型遇到的那些case的。


时区/偏移量ZoneId


在JDK 8之前,Java使用java.util.TimeZone来表示时区。而在JDK 8里分别使用了ZoneId表示时区,ZoneOffset表示UTC的偏移量。


值得提前强调,时区和偏移量在概念和实际作用上是有较大区别的,主要体现在:


  1. UTC偏移量仅仅记录了偏移的小时分钟而已,除此之外无任何其它信息。举个例子:+08:00的意思是比UTC时间早8小时,没有地理/时区含义,相应的-03:30代表的意思仅仅是比UTC时间晚3个半小时
  2. 时区是特定于地区而言的,它和地理上的地区(包括规则)强绑定在一起。比如整个中国都叫东八区,纽约在西五区等等


中国没有夏令时,所有东八区对应的偏移量永远是+8;纽约有夏令时,因此它的偏移量可能是-4也可能是-5哦


综合来看,时区更好用。令人恼火的夏令时问题,若你使用UTC偏移量去表示那么就很麻烦,因为它可变:一年内的某些时期在原来基础上偏移量 +1,某些时期 -1;但若你使用ZoneId时区去表示就很方便喽,比如纽约是西五区,你在任何时候获取其当地时间都是能得到正确答案的,因为它内置了对夏令时规则的处理,也就是说啥时候+1啥时候-1时区自己门清,不需要API调用者关心。


UTC偏移量更像是一种写死偏移量数值的做法,这在天朝这种没有时区规则(没有夏令时)的国家不会存在问题,东八区和UTC+08:00效果永远一样。但在一些夏令时国家(如美国、法国等等),就只能根据时区去获取当地时间喽。所以当你不了解当地规则时,最好是使用时区而非偏移量。

相关文章
|
4小时前
|
Java 关系型数据库 MySQL
37、一篇文章学习 Java 中的日期相关类(Date 和 Calendar),非常常用
37、一篇文章学习 Java 中的日期相关类(Date 和 Calendar),非常常用
29 0
|
4小时前
|
Java API
Java 8 时间和日期 API
Java 8 时间和日期 API
39 1
|
4小时前
|
存储 Java 程序员
Java 日期时间
4月更文挑战第17天
|
4小时前
|
Java
java中日期处理的一些工具方法
java中日期处理的一些工具方法
20 1
|
4小时前
|
Java API
Java一分钟之-Java日期与时间API:LocalDate, LocalDateTime
【5月更文挑战第13天】Java 8引入`java.time`包,改进日期时间API。`LocalDate`代表日期,`LocalDateTime`包含日期和时间。本文概述两者的基本用法、常见问题及解决策略。创建日期时间使用`of()`和`parse()`,操作日期时间有`plusDays()`、`minusMonths()`等。注意点包括:设置正确的`DateTimeFormatter`,考虑闰年影响,以及在需要时区信息时使用`ZonedDateTime`。正确使用这些类能提升代码质量。
10 3
|
4小时前
|
Java
Java String类型转换成Date日期类型
Java String类型转换成Date日期类型
|
4小时前
|
人工智能 安全 Java
Java8 - LocalDateTime时间日期类使用详解
Java8 - LocalDateTime时间日期类使用详解
|
4小时前
|
前端开发 Java API
JavaSE&Java8 时间日期API + 使用心得
JavaSE&Java8 时间日期API + 使用心得
16 0
|
4小时前
|
安全 Java API
Java日期与时间处理详解
Java日期与时间处理详解
17 1
|
4小时前
|
安全 Java API
Java日期与时间
Java日期与时间
38 1