首页 » PHP教程 » phpgmt0800技巧_全网最全彻底弄透Java处理GMTUTC日期时间

phpgmt0800技巧_全网最全彻底弄透Java处理GMTUTC日期时间

访客 2024-12-08 0

扫一扫用手机浏览

文章目录 [+]

众所周知,JDK以版本8为界,有两套处理日期/韶光的API:

虽然我一贯鼓励弃用Date而支持在项目中只利用JSR 310日期韶光类型,但是呢,由于Date依旧有弘大的存量用户,以是本文也不落单,对二者的实现均进行阐述。

phpgmt0800技巧_全网最全彻底弄透Java处理GMTUTC日期时间

Date类型实现

java.util.Date在JDK 1.0就已存在,用于表示日期 + 韶光的类型,纵使年代已非常久远,并且此类的具有职责不单一,利用很未便利等诸多毛病,但由于十几二十年的历史缘故原由存在,它的生命力依旧顽强,用户量巨大。

phpgmt0800技巧_全网最全彻底弄透Java处理GMTUTC日期时间
(图片来自网络侵删)

先来认识下Date,看下这个例子的输出:

@Testpublicvoidtest1(){DatecurrDate=newDate();System.out.println(currDate.toString());//已经@DeprecatedSystem.out.println(currDate.toLocaleString());//已经@DeprecatedSystem.out.println(currDate.toGMTString());}

运行程序,输出:

FriJan1510:22:34CST20212021-1-1510:22:3415Jan202102:22:34GMT

第一个:标准的UTC韶光(CST就代表了偏移量 +0800) 第二个:本地韶光,根据本地时区显示的韶光格式 第三个:GTM韶光,也便是格林威治这个时候的韶光,可以看到它是凌晨2点(北京韶光是上午10点哦)

第二个、第三个其实在JDK 1.1就都标记为@Deprecated过期了,基本禁止再利用。
若须要转换为本地韶光 or GTM韶光输出的话,请利用格式化器java.text.DateFormat去处理。

时区/偏移量TimeZone

在JDK8之前,Java对时区和偏移量都是利用java.util.TimeZone来表示的。

一样平常情形下,利用静态方法TimeZone#getDefault()即可获得当前JVM所运行的时区,比如你在中国运行程序,这个方法返回的便是中国时区(也叫北京时区、北京韶光)。

有的时候你须要做带时区的韶光转换,譬如:接口返回值中既要有展示北京韶光,也要展示纽约韶光。
这个时候就要获取到纽约的时区,以北京韶光为基准在其上进行带时区转换一把:

@Testpublicvoidtest2(){StringpatternStr="yyyy-MM-ddHH:mm:ss";//北京韶光(new出来便是默认时区的韶光)DatebjDate=newDate();//得到纽约的时区TimeZonenewYorkTimeZone=TimeZone.getTimeZone("America/New_York");//根据此时区将北京韶光转换为纽约的DateDateFormatnewYorkDateFormat=newSimpleDateFormat(patternStr);newYorkDateFormat.setTimeZone(newYorkTimeZone);System.out.println("这是北京韶光:"+newSimpleDateFormat(patternStr).format(bjDate));System.out.println("这是纽约韶光:"+newYorkDateFormat.format(bjDate));}

运行程序,输出:

这是北京韶光:2021-01-1511:48:16这是纽约韶光:2021-01-1422:48:16

(11 + 24) - 22 = 13,北京比纽约快13个小时没毛病。

把稳:两个韶光表示的该当是同一时候,也便是常说的韶光戳值是相等的

那么问题来了,你怎么知道获取纽约的时区用America/New_York这个zoneId呢?随便写个字符串行弗成?

答案是当然弗成,这是有章可循的。
下面我先容两种查阅zoneId的办法,任你挑选:

办法一:用Java程序把所有可用的zoneId打印出来,然后查阅

@Testpublicvoidtest3(){String[]availableIDs=TimeZone.getAvailableIDs();System.out.println("可用zoneId总数:"+availableIDs.length);for(StringzoneId:availableIDs){System.out.println(zoneId);}}

运行程序,输出(大部分符合规律:/前表示所属州,/表示城市名称):

可用zoneId总数:628Africa/AbidjanAfrica/Accra...Asia/Chongqing//亚洲/重庆Asia/Shanghai//亚洲/上海Asia/Dubai//亚洲/迪拜...America/New_York//美洲/纽约America/Los_Angeles//美洲/洛杉矶...Europe/London//欧洲/伦敦...Etc/GMTEtc/GMT+0Etc/GMT+1...

值得把稳的是并没有 Asia/Beijing 哦。

解释:此结果基于JDK 8版本,不同版本输出的总个数可能存在差异,但主流的ZoneId一样平常不会有变革

办法二: zoneId的列表是jre掩护的一个文本文件,路径是你JDK/JRE的安装路径。
地址在.\jre\lib目录的为未tzmappings的文本文件里。
打开这个文件去ctrl + f找也是可以达到查找的目的地。

这两种屋子可以帮你找到ZoneId的字典方便查阅,但是还有这么一种情形:当前所在的城市呢,在tzmappings文件里根本没有(比如没有收录),那要获取这个地方的韶光去显示怎么破呢?虽然概率很小,但不见得没有嘛,毕竟环球那么多国家那么多城市呢~

Java自然也考虑到了这一点,因此也是有办法的:指定其时区数字表示形式,实在也叫偏移量(不要见告我这个地方的时区都不知道,那就真没救了),如下示例

@Testpublicvoidtest4(){System.out.println(TimeZone.getTimeZone("GMT+08:00").getID());System.out.println(TimeZone.getDefault().getID());//纽约韶光System.out.println(TimeZone.getTimeZone("GMT-05:00").getID());System.out.println(TimeZone.getTimeZone("America/New_York").getID());}

运行程序,输出:

GMT+08:00//效果等同于Asia/ShanghaiAsia/ShanghaiGMT-05:00//效果等同于America/New_YorkAmerica/New_York

值得把稳的是,这里只能用GMT+08:00,而不能用UTC+08:00,缘故原由下文有阐明。

设置默认时区

一样平常来说,JVM在哪里跑,默认时区便是哪。
对付海内程序员来讲,一样平常只会打仗到东八区,也便是北京韶光(本地韶光)。
随着国际互助越来越密切,很多时候须要日期韶光国际化处理,举个很实际的例子:同一份运用在阿里云支配、在AWS(外洋)上也支配一份供外洋用户利用,此时同一份代码支配在不同的时区了,怎么破?

倘若时区不同,那么势必影响到程序的运行结果,很随意马虎带来打算逻辑的缺点,很可能就乱套了。
Java让我们有多种办法可以手动设置/修正默认时区:

API办法: 逼迫将时区设为北京时区TimeZone.setDefault(TimeZone.getDefault().getTimeZone("GMT+8"));JVM参数办法:-Duser.timezone=GMT+8运维设置办法:将操作系统主机时区设置为北京时区,这是推举办法,可以完备对开拓者无感,也方便了运维统一管理

据我理解,很多公司在阿里云、腾讯云、国内外的云主机上支配运用时,全部都是采取运维设置统一时区:中国时区,这种办法来管理的,这样对程序来说就肃清了默认时区不一致的问题,对开拓者友好。

让人恼火的夏令时

你知道吗,中国曾经也利用过夏令时。

什么是夏令时?

离现在最近是1986年至1991年用过夏令时(每年4月中旬的第一个周日2时 - 9月中旬的第一个星期日2时止): 1986年5月4日至9月14日 1987年4月12日至9月13日 1988年4月10日至9月11日 1989年4月16日至9月17日1990年4月15日至9月16日 1991年4月14日至9月15日

夏令时是一个“非常烦人”的东西,大大的 增加了日期韶光处理的繁芜度。
比如这个灵魂拷问:若你的出生日期是1988-09-11 00:00:00(夏令时末了一天)且存进了数据库,想一想,对此日期的格式化有没有可能就会出问题呢,有没有可能被你格式化成1988-09-10 23:00:00呢?

针对此拷问,我仿照了如下代码:

@Testpublicvoidtest5()throwsParseException{StringpatterStr="yyyy-MM-dd";DateFormatdateFormat=newSimpleDateFormat(patterStr);StringbirthdayStr="1988-09-11";//字符串->Date->字符串Datebirthday=dateFormat.parse(birthdayStr);longbirthdayTimestamp=birthday.getTime();System.out.println("老王的生日是:"+birthday);System.out.println("老王的生日的韶光戳是:"+birthdayTimestamp);System.out.println("==============程序经由一番周转,我的同时方法入参传来了生日的韶光戳=============");//字符串->Date->韶光戳->Date->字符串birthday=newDate(birthdayTimestamp);System.out.println("老王的生日是:"+birthday);System.out.println("老王的生日的韶光戳是:"+dateFormat.format(birthday));}

这段代码,在不同的JDK版本下运行,可能涌现不同的结果,有兴趣的可copy过去自行试试。

关于JDK处理夏令时(特指中国的夏令时)确实涌现干涉干与题且造成过bug,当时对应的JDK版本是1.8.0_2xx之前版本格式化那个日期出问题了,在这之后的版本貌似就没问题了。
这里我供应的版本信息仅供参考,若有碰着类似case就升级JDK版本到最新吧,一样平常就不会有问题了。

发生这个情形是在JDK非常小的版本号之间,不太好定位精确版本号界线,以是仅供参考

总的来说,只要你利用的是较新版本的JDK,开拓者是无需关心夏令时问题的,纵然环球仍有很多国家在利用夏令时,咱们只须要面向时区做韶光转换就没问题。

Date时区无关性

类Date表示一个特定的韶光瞬间,精度为毫秒。
既然表示的是瞬间/时候,那它一定和时区是无关的,看下面代码:

@Testpublicvoidtest6(){StringpatterStr="yyyy-MM-ddHH:mm:ss";DatecurrDate=newDate(System.currentTimeMillis());//北京时区DateFormatbjDateFormat=newSimpleDateFormat(patterStr);bjDateFormat.setTimeZone(TimeZone.getDefault());//纽约时区DateFormatnewYorkDateFormat=newSimpleDateFormat(patterStr);newYorkDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));//伦敦时区DateFormatlondonDateFormat=newSimpleDateFormat(patterStr);londonDateFormat.setTimeZone(TimeZone.getTimeZone("Europe/London"));System.out.println("毫秒数:"+currDate.getTime()+",北京本地韶光:"+bjDateFormat.format(currDate));System.out.println("毫秒数:"+currDate.getTime()+",纽约本地韶光:"+newYorkDateFormat.format(currDate));System.out.println("毫秒数:"+currDate.getTime()+",伦敦本地韶光:"+londonDateFormat.format(currDate));}

运行程序,输出:

毫秒数:1610696040244,北京本地韶光:2021-01-1515:34:00毫秒数:1610696040244,纽约本地韶光:2021-01-1502:34:00毫秒数:1610696040244,伦敦本地韶光:2021-01-1507:34:00

也便是说,同一个毫秒值,根据时区/偏移量的不同可以展示多地的韶光,这就证明了Date它的时区无关性。

确切地说:Date工具里存的是自格林威治韶光( GMT)1970年1月1日0点至Date所表示时候所经由的毫秒数,是个数值。

读取字符串为Date类型

这是开拓中极其常见的一种需求:client要求方扔给你一个字符串如"2021-01-15 18:00:00",然后你须要把它转为Date类型,怎么破?

问题来了,光秃秃的扔给我个字符串说是15号晚上6点韶光,我咋知道你指的是北京的晚上6点,还是东京的晚上6点呢?还是纽约的晚上6点呢?

因此,对付字符串形式的日期韶光,只有指定了时区才故意义。
也便是说字符串 + 时区才能精确知道它是什么时候,否则是存在歧义的。

大概你可能会说了,自己平时开拓中前端便是扔个字符串给我,然后我就给格式化为一个Date类型,并没有传入时区参数,运行这么久也没见出什么问题呀。
如下所示:

@Testpublicvoidtest7()throwsParseException{StringpatterStr="yyyy-MM-ddHH:mm:ss";//仿照要求参数的韶光字符串StringdateStrParam="2020-01-1518:00:00";//仿照做事端对此做事换转换为Date类型DateFormatdateFormat=newSimpleDateFormat(patterStr);System.out.println("格式化器用的时区是:"+dateFormat.getTimeZone().getID());Datedate=dateFormat.parse(dateStrParam);System.out.println(date);}

运行程序,输出:

格式化器用的时区是:Asia/ShanghaiWedJan1518:00:00CST2020

看起来结果没问题。
事实上,这是由于默认情形下你们交互双发就达成了左券:双方均利用的是北京韶光(时区),既然是相同时区,以是互通有无不会有任何问题。
不信你把你接口给外洋用户调试试?

对付格式化器来讲,虽然说编程过程中一样平常情形下我们并不须要给DateFormat设置时区(那就用默认时区呗)就可正常转换。
但是作为高手的你必须清清楚楚,明明白白地知道这是由于交互双发默认有个相同时区的左券存在。

SimpleDateFormat格式化

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

DateFormat是一个韶光格式化器抽象类,SimpleDateFormat是其详细实现类,用于以措辞环境敏感的办法格式化和解析日期。
它许可格式化(日期→文本)、解析(文本→日期)和规范化。

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

对付一个格式化器来讲,模式(模版)是其关键成分,理解一下:

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

字母含义匹配类型示例y年Year2020,20M月MonthJuly; Jul; 07d月中的天数(俗称日,最大值31)Number10H小时(0-23)Number0,23m分钟(0-59)Number30,59s秒(0-59)Number30,59---------yyyy-MM-dd HH:mm:ss(分隔符可以是任意字符,乃至汉字)Y当前周所在的年份Year2020(不建议利用,周若跨年有坑)S毫秒数(1-999)Number999aam/pmTextPMz时区通用时区Pacific Standard Time; PST; GMT-08:00Z时区RFC 822时区-0800,+0800X时区ISO 8601时区-08; -0800; -08:00G年代TextAD(公元)、BC(公元前)D年中的天数(1-366)Number360w年中的周数(1-54)Number27W月中的周数(1-5)Number3E星期几名称TextTuesday; Tueu星期几数字(1=Monday...)Number1k小时(1-24)Number不建议利用K/ham/pm小时数字Number一样平常合营a一起利用

这个表格里涌现了一些“分外”的匹配类型,做如下阐明:

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

@Testpublicvoidtest9()throwsParseException{StringpatternStr="GGGGGGGGEEEEEEEEaaaaaaaa";DatecurrDate=newDate();System.out.println("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓中文地区模式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");System.out.println("====================Date->String====================");DateFormatdateFormat=newSimpleDateFormat(patternStr,Locale.CHINA);System.out.println(dateFormat.format(currDate));System.out.println("====================String->Date====================");StringdateStrParam="公元公元公元星期六星期六星期六下午下午下午";System.out.println(dateFormat.parse(dateStrParam));System.out.println("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓英文地区模式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");System.out.println("====================Date->String====================");dateFormat=newSimpleDateFormat(patternStr,Locale.US);System.out.println(dateFormat.format(currDate));System.out.println("====================String->Date====================");dateStrParam="ADadbCSatSatUrdaysunDayPMPMAm";System.out.println(dateFormat.parse(dateStrParam));}

运行程序,输出:

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓中文地区模式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓====================Date->String====================公元公元公元星期六星期六星期六下午下午下午====================String->Date====================SatJan0312:00:00CST1970↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓英文地区模式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓====================Date->String====================ADADADSatSatSaturdayPMPMPM====================String->Date====================SunJan0100:00:00CST1970

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

英文单词,不分区大小写。
如SatUrday sunDay都是没问题,但是不能有拼写缺点若有多个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:00RFC 822时区:遵照RFC 822格式,向下兼容通用时区(名称部分除外)ISO 8601时区:对付格式化,如果与GMT的偏移值为0(也便是格林威治韶光喽),则天生“Z”;如果模式字母的数量为1,则忽略小时的任何分数。
例如,如果模式是“X”,时区是“GMT+05:30”,则天生“+05”。
在进行解析时,“Z”被解析为UTC时区指示符。
一样平常时区不被接管。
如果模式字母的数量是4个或更多,在布局SimpleDateFormat或运用模式时抛出IllegalArgumentException。
这个规则理解起来还是比较费劲的,在开拓中一样平常不太建议利用此种模式。
若要利用请务必本地做好测试

SimpleDateFormat的利用很大略,重点是理解其规则模式。
末了关于SimpleDateFormat的利用再强调这两点哈:

SimpleDateFormat并非线程安全类,利用时请务必把稳并发安全问题若利用SimpleDateFormat去格式化成非本地区域(默认Locale)的话,那就必须在布局的时候就指定好,如Locale.US对付Date类型的任何格式化、解析请统一利用SimpleDateFormatJSR 310类型

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

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

@Testpublicvoidtest10(){longcurrMillis=System.currentTimeMillis();java.util.Datedate=newDate(currMillis);java.sql.DatesqlDate=newjava.sql.Date(currMillis);java.sql.Timetime=newTime(currMillis);java.sql.Timestamptimestamp=newTimestamp(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:SatJan1621:50:36CST2021java.sql.Date:2021-01-16java.sql.Time:21:50:36java.sql.Timestamp:2021-01-1621:50:36.733国际化支持得并不是好,比如跨时区操作、夏令时等等

Java 自己也实在忍不了这么难用的日期韶光API了,于是在2014年随着Java 8的发布引入了全新的JSR 310日期韶光。
JSR-310源于佳构韶光库joda-time打造,办理了上面提到的所有问题,是全体Java 8最大亮点之一。

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

当然喽,本文重点并不在于谈论JSR 310日期/韶光体系,而是看看JSR 310日期韶光类型是如何处理上面Date类型碰着的那些case的。

时区/偏移量ZoneId

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

值得提前强调,时区和偏移量在观点和实际浸染上是有较大差异的,紧张表示在:

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

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

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

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

ZoneId

它代表一个时区的ID,如Europe/Paris。
它规定了一些规则可用于将一个Instant韶光戳转换为本地日期/韶光LocalDateTime。

上面说了时区ZoneId是包含有规则的,实际上描述偏移量何时以及如何变革的实际规则由java.time.zone.ZoneRules定义。
ZoneId则只是一个用于获取底层规则的ID。
之以是采取这种方法,是由于规则是由政府定义的,并且常常变革,而ID是稳定的。

对付API调用者来说只须要利用这个ID(也便是ZoneId)即可,而需无关心更为底层的时区规则ZoneRules,和“政府”同步规则的事是它领域内的事就交给它喽。
如:夏令时这条规则是由各国政府制订的,而且不同国家不同年一样平常都不一样,这个事就交由JDK底层的ZoneRules机制自行sync,利用者无需关心。

ZoneId在系统内是唯一的,它共包含三种类型的ID:

最大略的ID类型:ZoneOffset,它由'Z'和以'+'或'-'开头的id组成。
如:Z、+18:00、-18:00另一种类型的ID是带有某种前缀形式的偏移样式ID,例如'GMT+2'或'UTC+01:00'。
可识别的(合法的)前缀是'UTC', 'GMT'和'UT'第三种类型是基于区域的ID(推举利用)。
基于区域的ID必须包含两个或多个字符,且不能以'UTC'、'GMT'、'UT' '+'或'-'开头。
基于区域的id由配置定义好的,如Europe/Paris

观点说了一大堆,下面给几个代码示例感想熏染下吧。

1、获取系统默认的ZoneId:

@Testpublicvoidtest1(){//JDK1.8之前做法System.out.println(TimeZone.getDefault());//JDK1.8之后做法System.out.println(ZoneId.systemDefault());}输出:Asia/Shanghaisun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null]

二者结果是一样的,都是Asia/Shanghai。
由于ZoneId方法底层便是依赖TimeZone,如图:

2、指定字符串得到一个ZoneId:

@Testpublicvoidtest2(){System.out.println(ZoneId.of("Asia/Shanghai"));//报错:java.time.zone.ZoneRulesException:Unknowntime-zoneID:Asia/xxxSystem.out.println(ZoneId.of("Asia/xxx"));}

很明显,这个字符串也是不能随便写的。
那么问题来了,可写的有哪些呢?同样的ZoneId供应了API供你获取到所有可用的字符串id,有兴趣的同学建议自行考试测验:

@Testpublicvoidtest3(){ZoneId.getAvailableZoneIds();}

3、根据偏移量得到一个ZoneId:

@Testpublicvoidtest4(){ZoneIdzoneId=ZoneId.ofOffset("UTC",ZoneOffset.of("+8"));System.out.println(zoneId);//必须是大写的ZzoneId=ZoneId.ofOffset("UTC",ZoneOffset.of("Z"));System.out.println(zoneId);}输出:UTC+08:00UTC

这里第一个参数传的前缀,可用值为:"GMT", "UTC", or "UT"。
当然还可以传空串,那就直接返回第二个参数ZoneOffset。
若以上都不是就报错

把稳:根据偏移量得到的ZoneId内部并无现成时区规则可用,因此对付有夏令营的国家转换可能出问题,一样平常不建议这么去做。

4、从日期里面得到时区:

@Testpublicvoidtest5(){System.out.println(ZoneId.from(ZonedDateTime.now()));System.out.println(ZoneId.from(ZoneOffset.of("+8")));//报错:java.time.DateTimeException:UnabletoobtainZoneIdfromTemporalAccessor:System.out.println(ZoneId.from(LocalDateTime.now()));System.out.println(ZoneId.from(LocalDate.now()));}

虽然方法入参是TemporalAccessor,但是只接管带时区的类型,LocalXXX是弗成的,利用时稍加把稳。

ZoneOffset

间隔格林威治/UTC的时区偏移量,例如+02:00。
值得把稳的是它继续自ZoneId,以是也可当作一个ZoneId来利用的,当然并不建议你这么去做,请独立利用。

时区偏移量是时区与格林威治/UTC之间的韶光差。
这常日是固定的小时数和分钟数。
天下不同的地区有不同的时区偏移量。
在ZoneId类中捕获关于偏移量如何随一年的地点和韶光而变革的规则(紧张是夏令时规则),以是继续自ZoneId。

1、最小/最大偏移量:由于偏移量传入的是数字,这个是有限定的哦

@Testpublicvoidtest6(){System.out.println("最小偏移量:"+ZoneOffset.MIN);System.out.println("最小偏移量:"+ZoneOffset.MAX);System.out.println("中央偏移量:"+ZoneOffset.UTC);//超出最大范围System.out.println(ZoneOffset.of("+20"));}输出:最小偏移量:-18:00最小偏移量:+18:00中央偏移量:Zjava.time.DateTimeException:Zoneoffsethoursnotinvalidrange:value20isnotintherange-18to18

2、通过时分秒布局偏移量(利用很方便,推举):

@Testpublicvoidtest7(){System.out.println(ZoneOffset.ofHours(8));System.out.println(ZoneOffset.ofHoursMinutes(8,8));System.out.println(ZoneOffset.ofHoursMinutesSeconds(8,8,8));System.out.println(ZoneOffset.ofHours(-5));//指定一个精确的秒数获取实例(有时候也很有用处)System.out.println(ZoneOffset.ofTotalSeconds(86060));}//输出:+08:00+08:08+08:08:08-05:00+08:00

看来,偏移量是能精确到秒的哈,只不过一样平常来说精确到分钟已经到顶了。

设置默认时区

ZoneId并没有供应设置默认时区的方法,但是通过文章可知ZoneId获取默认时区底层依赖的是TimeZone.getDefault()方法,因此设置默认时区办法完备遵照TimeZone的办法即可(共三种办法,还记得吗?)。

让人恼火的夏令时

由于有夏令时规则的存在,让操作日期/韶光的繁芜度大大增加。
但还好JDK只管即便的屏蔽了这些规则对利用者的影响。
因此:推举利用时区(ZoneId)转换日期/韶光,一样平常情形下不建议利用偏移量ZoneOffset去搞,这样就不会有夏令时的烦恼啦。

JSR 310时区干系性

java.util.Date类型它具有时区无关性,带来的弊端便是一旦涉及到国际化韶光转换等需求时,利用Date来处理是很未便利的。

JSR 310办理了Date存在的一系列问题:对日期、韶光进行了分开表示(LocalDate、LocalTime、LocalDateTime),对本地韶光和带时区的韶光进行了分开管理。
LocalXXX表示本地韶光,也便是说是当前JVM所在时区的韶光;ZonedXXX表示是一个带有时区的日期韶光,它们能非常方便的相互完成转换。

@Testpublicvoidtest8(){//本地日期/韶光System.out.println("================本地韶光================");System.out.println(LocalDate.now());System.out.println(LocalTime.now());System.out.println(LocalDateTime.now());//时区韶光System.out.println("================带时区的韶光ZonedDateTime================");System.out.println(ZonedDateTime.now());//利用系统时区System.out.println(ZonedDateTime.now(ZoneId.of("America/New_York")));//自己指定时区System.out.println(ZonedDateTime.now(Clock.systemUTC()));//自己指定时区System.out.println("================带时区的韶光OffsetDateTime================");System.out.println(OffsetDateTime.now());//利用系统时区System.out.println(OffsetDateTime.now(ZoneId.of("America/New_York")));//自己指定时区System.out.println(OffsetDateTime.now(Clock.systemUTC()));//自己指定时区}

运行程序,输出:

================本地韶光================2021-01-1709:18:40.7032021-01-17T09:18:40.703================带时区的韶光ZonedDateTime================2021-01-17T09:18:40.704+08:00[Asia/Shanghai]2021-01-16T20:18:40.706-05:00[America/New_York]2021-01-17T01:18:40.709Z================带时区的韶光OffsetDateTime================2021-01-17T09:18:40.710+08:002021-01-16T20:18:40.710-05:002021-01-17T01:18:40.710Z

本地韶光的输出非常“干净”,可直接用于显示。
带时区的韶光显示了该韶光代表的是哪个时区的韶光,毕竟不指定时区的韶光是没有任何意义的。
LocalXXX由于它具有时区无关性,因此它不能代表一个瞬间/时候。

其余,关于LocalDateTime、OffsetDateTime、ZonedDateTime三者的跨时区转换问题,以及它们的详解,由于内容过多放在了下文专文阐述,保持关注。

读取字符串为JSR 310类型

一个独立的日期韶光类型字符串如2021-05-05T18:00-04:00它是没有任何意义的,由于没有时区无法确定它代表那个瞬间,这是理论当然也适宜JSR 310类型喽。

碰着一个日期韶光格式字符串,要解析它一样平常有这两种情形:

不带时区/偏移量的字符串:要么不理它说转换不了,要么就约定一个时区(一样平常用系统默认时区),利用LocalDateTime来解析

@Testpublicvoidtest11(){StringdateTimeStrParam="2021-05-05T18:00";LocalDateTimelocalDateTime=LocalDateTime.parse(dateTimeStrParam);System.out.println("解析后:"+localDateTime);}输出:解析后:2021-05-05T18:00带时区字/偏移量的符串:

@Testpublicvoidtest12(){//带偏移量利用OffsetDateTimeStringdateTimeStrParam="2021-05-05T18:00-04:00";OffsetDateTimeoffsetDateTime=OffsetDateTime.parse(dateTimeStrParam);System.out.println("带偏移量解析后:"+offsetDateTime);//带时区利用ZonedDateTimedateTimeStrParam="2021-05-05T18:00-05:00[America/New_York]";ZonedDateTimezonedDateTime=ZonedDateTime.parse(dateTimeStrParam);System.out.println("带时区解析后:"+zonedDateTime);}输出:带偏移量解析后:2021-05-05T18:00-04:00带时区解析后:2021-05-05T18:00-04:00[America/New_York]

请把稳带时区解析后这个结果:字符串参数偏移量明明是-05,为毛转换为ZonedDateTime后偏移量成为了-04呢???

这里是我故意造了这么一个case引起你的重视,对此结果我做如下阐明:

如图,在2021.03.14 - 2021.11.07期间,纽约的偏移量是-4,别的时候是-5。
本例的日期是2021-05-05处在夏令时之中,因此偏移量是-4,这就阐明了为何你显示的写了-5终极还是成了-4。

JSR 310格式化

针对JSR 310日期韶光类型的格式化/解析,有个专门的类java.time.format.DateTimeFormatter用于处理。

DateTimeFormatter也是一个不可变的类,所以是线程安全的,比SimpleDateFormat靠谱多了吧。
其余它还内置了非常多的格式化模版实例供以利用,形如:

格式化器示例ofLocalizedDate(dateStyle)'2021-01-03'ofLocalizedTime(timeStyle)'10:15:30'ofLocalizedDateTime(dateTimeStyle)'3 Jun 2021 11:05:30'ISO_LOCAL_DATE'2021-12-03'ISO_LOCAL_TIME'10:15:30'ISO_LOCAL_DATE_TIME'2021-12-03T10:15:30'ISO_OFFSET_DATE_TIME'2021-12-03T10:15:30+01:00'ISO_ZONED_DATE_TIME'2021-12-03T10:15:30+01:00[Europe/Paris]'

@Testpublicvoidtest13(){System.out.println(DateTimeFormatter.ISO_LOCAL_DATE.format(LocalDate.now()));System.out.println(DateTimeFormatter.ISO_LOCAL_TIME.format(LocalTime.now()));System.out.println(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now()));}输出:2021-01-1722:43:21.3982021-01-17T22:43:21.4

若想自定义模式pattern,和Date一样它也可以自己指定任意的pattern 日期/韶光模式。
由于本文在Date部分详细先容了日期/韶光模式,各个字母代表什么意思以及如何利用,这里就不再赘述了哈。

虽然DateTimeFormatter支持的模式比Date略有增加,但大体还保持同等,个人以为这块无需再花精力。
若真有须要再查官网也不迟

@Testpublicvoidtest14(){DateTimeFormatterformatter=DateTimeFormatter.ofPattern("第Q季度yyyy-MM-ddHH:mm:ss",Locale.US);//格式化输出System.out.println(formatter.format(LocalDateTime.now()));//解析StringdateTimeStrParam="第1季度2021-01-1722:51:32";LocalDateTimelocalDateTime=LocalDateTime.parse(dateTimeStrParam,formatter);System.out.println("解析后的结果:"+localDateTime);}

Q/q:季度,如3; 03; Q3; 3rd quarter。

最佳实践弃用Date,拥抱JSR 310

每每说到JSR 310日期/韶光时我都会呼吁,保持老例我这里连续啰嗦一句:放弃Date乃至禁用Date,利用JSR 310日期/韶光吧,它才这天期韶光处理的最佳实践。

其余,在利用期间关于指定时区(默认时区时)依旧有一套我心目中的最佳实践存在,这里分享给你:

永久显式地指定你须要的时区,纵然你要获取的是默认时区

//办法一:普通做法LocalDateTime.now();//办法二:最佳实践LocalDateTime.now(ZoneId.systemDefault());

如上代码二者效果千篇一律。
但是办法二是最佳实践。

情由是:这样做能让代码带有明确的意图,肃清模棱两可的可能性,纵然获取的是默认时区。
拿办法一来说吧,它就存在意图不明确的地方:到底是代码编写者忘却指定时区欠考虑了,还是就想用默认时区呢?这个答案如果不通读高下文是无法确定的,从而造成了不必要的沟通掩护本钱。
因此纵然你是要获取默认时区,也请显示的用ZoneId.systemDefault()写上去。

利用JVM的默认时区需当心,建议时区和当前会话保持绑定

这个最佳实践在分外场景用得到。
这么做的情由是:JVM的默认时区通过静态方法TimeZone#setDefault()可全局设置,因此JVM的任何一个线程都可以随意变动默认时区。
若关于韶光处理的代码对时区非常敏感的话,最佳实践是你把时区信息和当前会话绑定,这样就可以不用再受到其它线程潜在影响了,确保了健壮性。

解释:会话可能只是当前要求,也可能是一个Session,详细case详细剖析

总结

文章的内容较多,信息量均比较大,消化起来须要些韶光。
一方面我建议你先收藏留以当做参考书备用,另一方面建议多实践,代码这东西只有多写写才能有更深体会。

本文思考题

看完了不一定懂,看懂了不一定会。
来,文末3个思考题帮你复盘:

Date类型如何处理夏令时?ZoneId和ZoneOffset有什么差异?平时项目若碰着日期韶光的处理,有哪些最佳实践?
标签:

相关文章

php动态特点技巧_PHP 7 新特点

PHP 7 中的函数的形参类型声明可以是标量了。在 PHP 5 中只能是类名、接口、array 或者 callable (PHP...

PHP教程 2024-12-10 阅读0 评论0

断定结尾php技巧_PHP入门PHP基本语法一

认识PHP代码标识利用不同的四对标记指令分隔符程序注释变量什么是变量如何定义变量变量名的命名规则4.1 PHP标量类型—整型4.2...

PHP教程 2024-12-10 阅读0 评论0