一.準備知識
1.America/New_York的夏令時時間如下: 包左不包右
2016-3-13, 02:00:00到2016-11-6, 02:00:00
2017-3-12, 02:00:00到2017-11-5, 02:00:00
2.三字母時區ID
為了與JDK 1.1.x 兼容,一些三字母時區ID(比如"PST"、"CTT"、"AST")也受支持。
但是,它們的使用被廢棄,這是因為相同的縮寫經常用於多個時區
例如CST:有4個意思,美國,澳大利亞,中國,古巴時間
3.標準
GMT:Green Mean Time格林威治標準時間,1960年前被作為標準時間參考GMT+12-->GMT-12
java8的範圍為GMT+18-->GMT-18
UTC:Coordinated Universal Time 時間協調世界時間,比GMT精確,在1972年1月1日成為新的標準;UTC,UTC+1,UTC+2...UTC+12,UTC-12...UTC-1
java8的範圍UTC-18-->UTC+18
DST:Daylight Saving Time 夏令時間,指在夏天的時候,將時鐘撥快一個小時,以提早日光的使用,在英國稱為夏令時間;
目前有110多個國家採用夏令時;
在中國,從1986-1992只實行了6年,之後就取消了;原因如下:
1.中國東西方向跨度很大,而且採用的是統一的東八區,採用夏令時無法兼容東西部;
2.高緯度地區,冬夏晝夜時間變化大;意義不大;
4.表示東八區可以用: GMT+8或者Etc/GMT-8(剛好相反,為什麼呢,因為php開發者認為,東八區比標準時間快8小時,應該減去8小時,於是表示成了這樣。參考的對像不同導致了不同的表示方法;)
5. 中國時區的表示方式
GMT+8
UTC+8
Asia/Harbin 哈爾濱//中國標準時間
Asia/Chongqing 重慶//中國標準時間
Asia/Chungking 重慶//中國標準時間
Asia/Urumqi 烏魯木齊//中國標準時間
Asia/Shanghai 上海(東8區)//中國標準時間
PRC
Asia/Macao 澳門//中國標準時間
Hongkong 香港//香港時間跟中國標準時間一致
Asia/Hong_Kong 香港
Asia/Taipei 台北(台灣的) //中國標準時間新加坡跟中國的時間一樣;
Asia/Singapore
Singapore
6. 標準時區的表示
UTC
UTC+0
UTC-0
GMT 格林尼治標準時間
GMT0 格林尼治標準時間
Etc/GMT 格林尼治標準時間
Etc/GMT+0 格林尼治標準時間
Etc/GMT-0 格林尼治標準時間
Etc/GMT0 格林尼治標準時間注意:GMT+xx(-xx)有很大的包容性,還可以自動的識別各種時間的表示
二. 時區轉換
環境:java8之前
1.將當前時間轉換為指定時區顯示
@Test public void test() throws Exception { Date a=new Date(); SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sf.setTimeZone(TimeZone.getTimeZone("America/New_York")); //把中國時區轉為了美國紐約時區System.out.println(sf.format(a)); }2.指定時間轉為指定時區顯示
真能正確轉換嗎?好像有個坑,看了看網上的實現
關於夏令時,感覺有點問題
//實現方式1 沒有考慮夏令時@Test public void test2() throws Exception { / Date dateTime=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-11-6 14:00:00"); Date dateTime=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-10-6 14:00:00"); TimeZone zhong = TimeZone.getTimeZone("GMT+8:00");//中國TimeZone york = TimeZone.getTimeZone("America/New_York"); //GMT-5 //這裡的時區偏移量是固定的,沒有夏令時,錯long chineseMills = dateTime.getTime() + york.getRawOffset()-zhong.getRawOffset(); Date date = new Date(chineseMills); System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)); } //實現方式2 你可能回想,用Calendar類的Calendar.DST_OFFSET //還是不對Calendar.DST_OFFSET這個是死的@Testpublic void test3() throws Exception {//Date time =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-11-6 14:00:00");//Date time =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-11-6 1:00:00");//Date time =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-11-6 0:59:00");//Date time =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-11-6 1:59:59");Date time =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-11-6 3:00:00");// 1、取得本地時間:Calendar cal = Calendar.getInstance();cal.setTime(time);cal.setTimeZone(TimeZone.getTimeZone("America/New_York"));// 2、取得時間偏移量:這個是固定的int zoneOffset = cal.get(Calendar.ZONE_OFFSET)/(1000*60*60);// 3、取得夏令時差:這個是固定的,不是根據時間動態判斷,只要時區存在夏令時,就是1int dstOffset = cal.get(Calendar.DST_OFFSET)/(1000*60*60);System.out.println(zoneOffset);System.out.println(dstOffset);// 4、從本地時間裡扣除這些差量,即可以取得UTC時間://cal.add(Calendar.MILLISECOND, -(zoneOffset + dstOffset));cal.add(Calendar.HOUR, -(zoneOffset + dstOffset));Date time2 = cal.getTime();System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time2));} //實現方式3 準備工作// 不是說java會自動替我們處理夏令時嗎//先來個簡單的測試,看夏令時判斷方法是否是正確,就可以推斷,java的自動處理是否正確//已知2016年:America/New_York的夏令時時間是: 2016-3-13 02:00:00 到2016-11-06 01:59:59 @Test public void test4() throws Exception { //轉換為0時區時間作為參照點SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sf.setTimeZone(TimeZone.getTimeZone("GMT+0"));// Date dateTime=sf.parse("2016-11-6 5:59:59"); Date d1=sf.parse("2016-03-13 6:59:59");//false Date d2=sf.parse("2016-03-13 7:00:00");//true Date d3=sf.parse("2016-11-6 6:59:59");//false Date d4=sf.parse("2016-11-6 7:00:00");//false //現在發現了,對於夏令時開始的時間判斷確實沒問題,但是對於夏令時的結束時間判斷錯誤,準確說,提前了一個小時判斷了//看下面驗證就知道了,那麼為什麼提前了一個小時,不知道,怎麼解決? 目前想到的只能用java8 d3=sf.parse("2016-11-6 5:59:59");//true d4=sf.parse("2016-11-6 6:00:00");//false TimeZone york = TimeZone.getTimeZone("America/New_York"); //GMT-5 System.out.println("目標時區是否使用了夏令時:"+isDaylight(york, d1)); System.out.println("目標時區是否使用了夏令時:"+isDaylight(york, d2)); System.out.println("目標時區是否使用了夏令時:"+isDaylight(york, d3)); System.out.println("目標時區是否使用了夏令時:"+isDaylight(york, d4)); } //判斷是否在夏令時private boolean isDaylight(TimeZone zone,Date date) {return zone.useDaylightTime()&&zone.inDaylightTime(date); } //實現方式3 //通過上面的驗證我們知道了系統的判斷是有問題的,通過設置時區,程序自動處理夏令時也不是那麼正確,起碼我現在無法理解為什麼@Testpublic void test5() throws Exception { //中間相隔13個小時中國+8 紐約-5 ChangeZone("2016-3-13 14:59:59", "PRC","America/New_York", "yyyy-MM-dd HH:mm:ss");//2016-03-13 01:59:59 ChangeZone("2016-3-13 15:00:00", "PRC","America/New_York", "yyyy-MM-dd HH:mm:ss");//2016-03-13 03:00:00 ChangeZone("2016-11-6 13:59:59", "PRC","America/New_York", "yyyy-MM-dd HH:mm:ss");//2016-11-06 01:59:59 //這個結果是不對的,應該02:00:00 ChangeZone("2016-11-6 14:00:00", "PRC","America/New_York", "yyyy-MM-dd HH:mm:ss");//2016-11-06 01:00:00 } //具體的實現如下: //思路是沒問題的public static void ChangeZone(String time, String srcID, String destID, String pattern) throws ParseException { //設置默認時區TimeZone zone = TimeZone.getTimeZone(srcID); TimeZone.setDefault(zone); Date date = new SimpleDateFormat(pattern).parse(time); //設置目標時區TimeZone destzone = TimeZone.getTimeZone(destID); SimpleDateFormat sdf = new SimpleDateFormat(pattern); //設置要格式化的時區sdf.setTimeZone(destzone); String changTime = sdf.format(date); // 獲取目標時區System.out.println("修改時區後" + destzone.getID() + "的時間:" + changTime); }小結:以上的三種實現方式得到的結果對夏令時都有點問題
三,java8的實現
1.先看看java8的支持時區變化
//jdk8的可用時區@Testpublic void testName1() throws Exception {//jdk8的所有時區Set<String> ids = ZoneId.getAvailableZoneIds();String[] id1 = ids.toArray(new String[ids.size()]);String idss = Arrays.toString(id1).replace("]", ",]");System.out.println(ids.size());//少了28個595//jdk8之前的所有時區String[] id2 = TimeZone.getAvailableIDs();System.out.println(id2.length); //623//找出沒jdk8中沒有的for (String id : id2) {if (!idss.contains(id+",")) {System.out.print(id+",");}}//結論:jdk8裡面的所有時區,在之前全部都有//jdk8刪除的時區如下://都是一些容易引起歧義的時區表示方法// EST, HST, MST, ACT, AET, AGT, ART, AST, BET, BST, CAT, CNT, CST, CTT, EAT, ECT, IET, IST, JST, MIT, NET, NST, PLT, PNT, PRT, PST, SST, VST,// 但是這些短名稱的其實還是可以使用的,只是支持列表裡面沒有了而已LocalDateTime date = LocalDateTime.ofInstant(Instant.now(),ZoneId.of(ZoneId.SHORT_IDS.get("PST")));System.out.println("/nDate = " + date);}2.如何添加時區信息呢,或者說轉換
//LocalDate,LocalDateTime,Instant添加時區信息@Testpublic void testName3() {LocalDate date=LocalDate.now();//LocalDate添加時區信息//方式1ZonedDateTime zone = date.atStartOfDay(ZoneId.of("GMT+08:00"));System.out.println(zone);zone = date.atStartOfDay(ZoneId.systemDefault()); System.out.println(zone);//方式2 LocalDate date2 = LocalDate.now(ZoneId.of("GMT+0"));System.out.println(date2);System.out.println("------------------------------------");//LocalDateTime添加時區信息LocalDateTime time = LocalDateTime.now();//方式1ZonedDateTime zonetime = time.atZone(ZoneId.of("GMT+0"));//方式2ZonedDateTime zonetime2=ZonedDateTime.now(ZoneId.of("GMT+0"));//方式3LocalDateTime zonetime3 = LocalDateTime.now(Clock.system(ZoneId.of("GMT+0")));//方式4ZonedDateTime zonetime4 = ZonedDateTime.of(time, ZoneId.of("GMT+0"));System.out.println(zonetime); //不變System.out.println(zonetime2);//變System.out.println(zonetime3);//變System.out.println(zonetime4);//不變System.out.println("------------------------------------");//Instant時區信息ZonedDateTime atZone = Instant.now().atZone(ZoneId.of("GMT+0"));System.out.println(atZone);//變}3.如何獲取當前時間的指定時間(測試略)
//獲取當前時間的指定時區時間,參照點:0時區public LocalDateTime getCurrentZoneTime(ZoneId dest) { Objects.requireNonNull(dest); LocalDateTime time2 = LocalDateTime.now(Clock.system(dest)); String zoneDesc = getZoneDesc(TimeZone.getTimeZone(dest)); System.out.println(dest.getId()+"對應得標準時區:"+zoneDesc); System.out.println("目標時區"+dest+"的時間"+time2.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); return time2;} //獲取標準時區,方式1//在jdk8之前的方法,利用TimeZone private static String getZoneDesc(TimeZone destzone) { Objects.requireNonNull(destzone); int Offset = destzone.getRawOffset() / (1000 * 60 * 60); if (Offset <= 0) {return "GMT"+String.valueOf(Offset); } else {return "GMT+" + String.valueOf(Offset); } } //java8的方法,方式2,利用ZoneRules //得到時區的標準偏移量,ZoneRules.getStandardOffset //得到時區的實際偏移量(得到的偏移量會根據夏令時改變) // 方式1:ZonedDateTime.getOffset // 方式2:ZoneRules.getOffset private String getZoneDesc2(ZoneId dest) { Objects.requireNonNull(dest); ZoneRules rule=dest.getRules(); //獲取時區的標準偏移量String standardOffset = rule.getStandardOffset(ZonedDateTime.now(dest).toInstant()).getId(); String s = standardOffset.split(":")[0]; int Offset = Integer.parseInt(s); //返回方式1:帶小時分鐘// return "GMT"+standardOffset; //返回方式2:只帶小時數if (Offset>0) { return "GMT+"+Offset;}else{return "GMT"+Offset;} }4.如何獲取指定時區的指定時間
開始實現上面留下的時區問題啦
同理先看下使用java8的夏令時判斷方法是否正確
//先手寫個判斷美國的時間是否在夏令時public boolean isDaylightTime(LocalDateTime a) { Objects.requireNonNull(a); LocalDateTime startDate = a.withMonth(3).toLocalDate().atTime(2, 0);LocalDateTime startlightDay = startDate.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.SUNDAY));//更新為11月LocalDateTime endDate = a.withMonth(11).toLocalDate().atTime(1, 59,59);LocalDateTime endlightDay = endDate.with(TemporalAdjusters.dayOfWeekInMonth(1, DayOfWeek.SUNDAY));if (a.isBefore(startlightDay) || a.isAfter(endlightDay)) {System.out.println("不在夏令時"+a);return false;}System.out.println("在夏令時"+a);return true;} //其實java8 已經有現成的方法啦,比我的好用//傳入指定時間和時區public boolean isDaylightTime(LocalDateTime a,ZoneId dest) { ZonedDateTime z1 = a.atZone(dest); //或者這樣轉// ZonedDateTime z2 = ZonedDateTime.of(a, dest); System.out.println(z1.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); ZoneRules rules = dest.getRules(); boolean flag= rules.isDaylightSavings(z1.toInstant()); System.out.println(flag);return flag; }//測試一下,發現java8的夏令時方法判斷完全正確噢
//已知2016年:America/New_York的夏令時時間是: 2016-3-13 02:00:00 到2016-11-06 01:59:59 // (每年3月的第二個星期日,11月的第一個星期日) @Testpublic void testName() throws Exception {//LocalDateTime a1=LocalDateTime.now(); LocalDateTime a2=LocalDateTime.of(2016, 3, 13, 1, 59,59);LocalDateTime a3=LocalDateTime.of(2016, 3, 13, 2, 00);LocalDateTime a4=LocalDateTime.of(2016, 11, 6, 1, 59,59);LocalDateTime a5=LocalDateTime.of(2016, 11, 6, 2, 0,0);//isDaylightTime(a2);//isDaylightTime(a3);//isDaylightTime(a4);//isDaylightTime(a5);System.out.println("=================");isDaylightTime(a2,ZoneId.of("America/New_York"));//falseisDaylightTime(a3,ZoneId.of("America/New_York"));//trueisDaylightTime(a4,ZoneId.of("America/New_York"));//trueisDaylightTime(a5,ZoneId.of("America/New_York"));//fasle}開始實現:
版本1:
//獲取指定時間的指定時區時間參照點:默認時區public LocalDateTime getZongTime(LocalDateTime time,ZoneId dest) { Objects.requireNonNull(dest); return getZongTime(time, null, dest); } //不能用2個時區的ZonedDateTime相減,因為這裡一旦指定時區,那個時間就是這個時區了public LocalDateTime getZongTime(LocalDateTime time,ZoneId src,ZoneId dest) { //難點就是如何求偏移量//這裡使用默認時區,在中國的就是中國,在美國的就是美國,這樣估計更合適Objects.requireNonNull(dest); ZonedDateTime z1=null; if (src==null) { z1 = time.atZone(ZoneId.systemDefault()); }else{ z1 = time.atZone(src); } // 時區及時響應變化ZonedDateTime z2 = z1.withZoneSameInstant(dest); System.out.println(dest.getId()+"對應得標準時區:"+getZoneDesc( TimeZone.getTimeZone(dest))); System.out.println("目標時區"+dest+"的時間"+z2.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); System.out.println("-------------"); return time; }測試如下:
@Test public void test6() throws Exception { //預計不在夏令時2016-03-13 01:59:59 LocalDateTime time4= LocalDateTime.of(2016, 3, 13, 14, 59, 59); getZongTime(time4,ZoneId.of("America/New_York")); //預計在夏令時2016-03-13 03:00:00 LocalDateTime time1= LocalDateTime.of(2016, 3, 13, 15, 00, 00); getZongTime(time1,ZoneId.of("America/New_York")); //預計在夏令時結果呢:2016-11-06 01:59:59 //感覺又失敗了,應該是2016-11-06 02:59:59 //也就是說,此時java8對夏令時的結束處理之前的方式3 一模一樣,提前了一小時判斷//即把夏令時結束時間當成了2016-11-6 00:59:59,但是java8的判斷方法是正確的呀,是不是有點奇怪LocalDateTime time2= LocalDateTime.of(2016, 11, 6, 14, 59, 59); getZongTime(time2,ZoneId.of("America/New_York")); //預計不在夏令時2016-11-06 02:00:00 LocalDateTime time3= LocalDateTime.of(2016, 11, 6, 15, 00, 00); getZongTime(time3,ZoneId.of("America/New_York")); }所以我現在懷疑這種結果到底是不是系統計算問題呢,還是我不了解紐約的習俗呢?
但是還是可以得到我想要的結果的,運用2個方法:
withEarlierOffsetAtOverlap(), withLaterOffsetAtOverlap()
版本2:
//獲取指定時間的指定時區時間參照點:默認時區public LocalDateTime getZongTime2(LocalDateTime time,ZoneId dest) {Objects.requireNonNull(dest);return getZongTime2(time, null, dest);}//版本2 public LocalDateTime getZongTime2(LocalDateTime time,ZoneId src,ZoneId dest) { //難點就是如何求偏移量//這裡使用默認時區,在中國的就是中國,在美國的就是美國,這樣估計更合適Objects.requireNonNull(dest); ZonedDateTime z1=null; if (src==null) { z1 = time.atZone(ZoneId.systemDefault()); }else{ z1 = time.atZone(src); }// ZonedDateTime z2 = z1.withZoneSameInstant(dest); //處理重疊問題long hours = Duration.between(z2.withEarlierOffsetAtOverlap(), z2.withLaterOffsetAtOverlap()).toHours(); z2= z2.plusHours(hours); System.out.println(dest.getId()+"對應得標準時區:"+getZoneDesc( TimeZone.getTimeZone(dest))); System.out.println("目標時區"+dest+"的時間"+z2.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); System.out.println("-------------"); return time;}測試:OK了
@Test public void test4() throws Exception { //預計不在夏令時2016-03-13 01:59:59 LocalDateTime time4= LocalDateTime.of(2016, 3, 13, 14, 59, 59); getZongTime2(time4,ZoneId.of("America/New_York")); //預計在夏令時2016-03-13 03:00:00 LocalDateTime time1= LocalDateTime.of(2016, 3, 13, 15, 00, 00); getZongTime2(time1,ZoneId.of("America/New_York")); //預計在夏令時2016-11-06 02:59:59 LocalDateTime time2= LocalDateTime.of(2016, 11, 6, 14, 59, 59); getZongTime2(time2,ZoneId.of("America/New_York")); //預計不在夏令時2016-11-06 02:00:00 LocalDateTime time3= LocalDateTime.of(2016, 11, 6, 15, 00, 00); getZongTime2(time3,ZoneId.of("America/New_York")); }結果:
America/New_York對應得標準時區:GMT-5
目標時區America/New_York的時間2016-03-13 01:59:59
-------------
America/New_York對應得標準時區:GMT-5
目標時區America/New_York的時間2016-03-13 03:00:00
-------------
America/New_York對應得標準時區:GMT-5
目標時區America/New_York的時間2016-11-06 02:59:59
-------------
America/New_York對應得標準時區:GMT-5
目標時區America/New_York的時間2016-11-06 02:00:00
-------------
以上這篇基於java時區轉換夏令時的問題及解決方法就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持武林網。