基于java时区转换夏令时的问题及解决方法
一.准备知识
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
为了与JDK1.1.x兼容,一些三字母时区ID(比如"PST"、"CTT"、"AST")也受支持。
但是,它们的使用被废弃,这是因为相同的缩写经常用于多个时区
例如CST:有4个意思,美国,澳大利亚,中国,古巴时间
3.标准
GMT:GreenMeanTime格林威治标准时间,1960年前被作为标准时间参考GMT+12-->GMT-12
java8的范围为GMT+18-->GMT-18
UTC:CoordinatedUniversalTime时间协调世界时间,比GMT精确,在1972年1月1日成为新的标准;UTC,UTC+1,UTC+2...UTC+12,UTC-12...UTC-1
java8的范围UTC-18-->UTC+18
DST:DaylightSavingTime夏令时间,指在夏天的时候,将时钟拨快一个小时,以提早日光的使用,在英国称为夏令时间;
目前有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 publicvoidtest()throwsException{ Datea=newDate(); SimpleDateFormatsf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss"); sf.setTimeZone(TimeZone.getTimeZone("America/New_York")); //把中国时区转为了美国纽约时区 System.out.println(sf.format(a)); }
2.指定时间转为指定时区显示
真能正确转换吗?好像有个坑,看了看网上的实现
关于夏令时,感觉有点问题
//实现方式1没有考虑夏令时 @Test publicvoidtest2()throwsException{ /DatedateTime=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss").parse("2016-11-614:00:00"); DatedateTime=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss").parse("2016-10-614:00:00"); TimeZonezhong=TimeZone.getTimeZone("GMT+8:00");//中国 TimeZoneyork=TimeZone.getTimeZone("America/New_York");//GMT-5 //这里的时区偏移量是固定的,没有夏令时,错 longchineseMills=dateTime.getTime()+york.getRawOffset()-zhong.getRawOffset(); Datedate=newDate(chineseMills); System.out.println(newSimpleDateFormat("yyyy-MM-ddHH:mm:ss").format(date)); }
//实现方式2你可能回想,用Calendar类的Calendar.DST_OFFSET //还是不对Calendar.DST_OFFSET这个是死的 @Test publicvoidtest3()throwsException{ // Datetime=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss").parse("2016-11-614:00:00"); // Datetime=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss").parse("2016-11-61:00:00"); // Datetime=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss").parse("2016-11-60:59:00"); // Datetime=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss").parse("2016-11-61:59:59"); Datetime=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss").parse("2016-11-63:00:00"); //1、取得本地时间: Calendarcal=Calendar.getInstance(); cal.setTime(time); cal.setTimeZone(TimeZone.getTimeZone("America/New_York")); //2、取得时间偏移量:这个是固定的 intzoneOffset=cal.get(Calendar.ZONE_OFFSET)/(1000*60*60); //3、取得夏令时差:这个是固定的,不是根据时间动态判断,只要时区存在夏令时,就是1 intdstOffset=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)); Datetime2=cal.getTime(); System.out.println(newSimpleDateFormat("yyyy-MM-ddHH:mm:ss").format(time2)); }
//实现方式3 准备工作 //不是说java会自动替我们处理夏令时吗 //先来个简单的测试,看夏令时判断方法是否是正确,就可以推断,java的自动处理是否正确 //已知2016年:America/New_York的夏令时时间是:2016-3-1302:00:00到2016-11-0601:59:59 @Test publicvoidtest4()throwsException{ //转换为0时区时间作为参照点 SimpleDateFormatsf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss"); sf.setTimeZone(TimeZone.getTimeZone("GMT+0")); // DatedateTime=sf.parse("2016-11-65:59:59"); Dated1=sf.parse("2016-03-136:59:59");//false Dated2=sf.parse("2016-03-137:00:00");//true Dated3=sf.parse("2016-11-66:59:59");//false Dated4=sf.parse("2016-11-67:00:00");//false //现在发现了,对于夏令时开始的时间判断确实没问题,但是对于夏令时的结束时间判断错误,准确说,提前了一个小时判断了 //看下面验证就知道了,那么为什么提前了一个小时,不知道,怎么解决?目前想到的只能用java8 d3=sf.parse("2016-11-65:59:59");//true d4=sf.parse("2016-11-66:00:00");//false TimeZoneyork=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)); } //判断是否在夏令时 privatebooleanisDaylight(TimeZonezone,Datedate){ returnzone.useDaylightTime()&&zone.inDaylightTime(date); }
//实现方式3 //通过上面的验证我们知道了系统的判断是有问题的,通过设置时区,程序自动处理夏令时也不是那么正确,起码我现在无法理解为什么 @Test publicvoidtest5()throwsException{ //中间相隔13个小时中国+8纽约-5 ChangeZone("2016-3-1314:59:59","PRC","America/New_York","yyyy-MM-ddHH:mm:ss");//2016-03-1301:59:59 ChangeZone("2016-3-1315:00:00","PRC","America/New_York","yyyy-MM-ddHH:mm:ss");//2016-03-1303:00:00 ChangeZone("2016-11-613:59:59","PRC","America/New_York","yyyy-MM-ddHH:mm:ss");//2016-11-0601:59:59 //这个结果是不对的,应该02:00:00 ChangeZone("2016-11-614:00:00","PRC","America/New_York","yyyy-MM-ddHH:mm:ss");//2016-11-0601:00:00 } //具体的实现如下: //思路是没问题的 publicstaticvoidChangeZone(Stringtime,StringsrcID,StringdestID, Stringpattern)throwsParseException{ //设置默认时区 TimeZonezone=TimeZone.getTimeZone(srcID); TimeZone.setDefault(zone); Datedate=newSimpleDateFormat(pattern).parse(time); //设置目标时区 TimeZonedestzone=TimeZone.getTimeZone(destID); SimpleDateFormatsdf=newSimpleDateFormat(pattern); //设置要格式化的时区 sdf.setTimeZone(destzone); StringchangTime=sdf.format(date); //获取目标时区 System.out.println("修改时区后"+destzone.getID()+"的时间:"+changTime); }
小结:以上的三种实现方式得到的结果对夏令时都有点问题
三,java8的实现
1.先看看java8的支持时区变化
//jdk8的可用时区 @Test publicvoidtestName1()throwsException{ //jdk8的所有时区 Setids=ZoneId.getAvailableZoneIds(); String[]id1=ids.toArray(newString[ids.size()]); Stringidss=Arrays.toString(id1).replace("]",",]"); System.out.println(ids.size());//少了28个595 //jdk8之前的所有时区 String[]id2=TimeZone.getAvailableIDs(); System.out.println(id2.length);//623 //找出没jdk8中没有的 for(Stringid: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, //但是这些短名称的其实还是可以使用的,只是支持列表里面没有了而已 LocalDateTimedate=LocalDateTime.ofInstant(Instant.now(), ZoneId.of(ZoneId.SHORT_IDS.get("PST"))); System.out.println("\nDate="+date); }
2.如何添加时区信息呢,或者说转换
//LocalDate,LocalDateTime,Instant添加时区信息 @Test publicvoidtestName3(){ LocalDatedate=LocalDate.now(); //LocalDate添加时区信息 //方式1 ZonedDateTimezone=date.atStartOfDay(ZoneId.of("GMT+08:00")); System.out.println(zone); zone=date.atStartOfDay(ZoneId.systemDefault()); System.out.println(zone); //方式2 LocalDatedate2=LocalDate.now(ZoneId.of("GMT+0")); System.out.println(date2); System.out.println("------------------------------------"); //LocalDateTime添加时区信息 LocalDateTimetime=LocalDateTime.now(); //方式1 ZonedDateTimezonetime=time.atZone(ZoneId.of("GMT+0")); //方式2 ZonedDateTimezonetime2=ZonedDateTime.now(ZoneId.of("GMT+0")); //方式3 LocalDateTimezonetime3=LocalDateTime.now(Clock.system(ZoneId.of("GMT+0"))); //方式4 ZonedDateTimezonetime4=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时区信息 ZonedDateTimeatZone=Instant.now().atZone(ZoneId.of("GMT+0")); System.out.println(atZone);//变 }
3.如何获取当前时间的指定时间(测试略)
//获取当前时间的指定时区时间,参照点:0时区 publicLocalDateTimegetCurrentZoneTime(ZoneIddest){ Objects.requireNonNull(dest); LocalDateTimetime2=LocalDateTime.now(Clock.system(dest)); StringzoneDesc=getZoneDesc(TimeZone.getTimeZone(dest)); System.out.println(dest.getId()+"对应得标准时区:"+zoneDesc); System.out.println("目标时区"+dest+"的时间"+time2.format(DateTimeFormatter.ofPattern("yyyy-MM-ddHH:mm:ss"))); returntime2; }
//获取标准时区,方式1 //在jdk8之前的方法,利用TimeZone privatestaticStringgetZoneDesc(TimeZonedestzone){ Objects.requireNonNull(destzone); intOffset=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 privateStringgetZoneDesc2(ZoneIddest){ Objects.requireNonNull(dest); ZoneRulesrule=dest.getRules(); //获取时区的标准偏移量 StringstandardOffset=rule.getStandardOffset(ZonedDateTime.now(dest).toInstant()).getId(); Strings=standardOffset.split(":")[0]; intOffset=Integer.parseInt(s); //返回方式1:带小时分钟 // return"GMT"+standardOffset; //返回方式2:只带小时数 if(Offset>0){ return"GMT+"+Offset; }else{ return"GMT"+Offset; } }
4.如何获取指定时区的指定时间
开始实现上面留下的时区问题啦
同理先看下使用java8的夏令时判断方法是否正确
//先手写个判断美国的时间是否在夏令时 publicbooleanisDaylightTime(LocalDateTimea){ Objects.requireNonNull(a); LocalDateTimestartDate=a.withMonth(3).toLocalDate().atTime(2,0); LocalDateTimestartlightDay=startDate.with(TemporalAdjusters.dayOfWeekInMonth(2,DayOfWeek.SUNDAY)); //更新为11月 LocalDateTimeendDate=a.withMonth(11).toLocalDate().atTime(1,59,59); LocalDateTimeendlightDay=endDate.with(TemporalAdjusters.dayOfWeekInMonth(1,DayOfWeek.SUNDAY)); if(a.isBefore(startlightDay)||a.isAfter(endlightDay)){ System.out.println("不在夏令时"+a); returnfalse; } System.out.println("在夏令时"+a); returntrue; } //其实java8已经有现成的方法啦,比我的好用 //传入指定时间和时区 publicbooleanisDaylightTime(LocalDateTimea,ZoneIddest){ ZonedDateTimez1=a.atZone(dest); //或者这样转 // ZonedDateTimez2=ZonedDateTime.of(a,dest); System.out.println(z1.format(DateTimeFormatter.ofPattern("yyyy-MM-ddHH:mm:ss"))); ZoneRulesrules=dest.getRules(); booleanflag=rules.isDaylightSavings(z1.toInstant()); System.out.println(flag); returnflag; }
//测试一下,发现java8的夏令时方法判断完全正确噢
//已知2016年:America/New_York的夏令时时间是:2016-3-1302:00:00到2016-11-0601:59:59 // (每年3月的第二个星期日,11月的第一个星期日) @Test publicvoidtestName()throwsException{ // LocalDateTimea1=LocalDateTime.now(); LocalDateTimea2=LocalDateTime.of(2016,3,13,1,59,59); LocalDateTimea3=LocalDateTime.of(2016,3,13,2,00); LocalDateTimea4=LocalDateTime.of(2016,11,6,1,59,59); LocalDateTimea5=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"));//false isDaylightTime(a3,ZoneId.of("America/New_York"));//true isDaylightTime(a4,ZoneId.of("America/New_York"));//true isDaylightTime(a5,ZoneId.of("America/New_York"));//fasle }
开始实现:
版本1:
//获取指定时间的指定时区时间参照点:默认时区 publicLocalDateTimegetZongTime(LocalDateTimetime,ZoneIddest){ Objects.requireNonNull(dest); returngetZongTime(time,null,dest); }
//不能用2个时区的ZonedDateTime相减,因为这里一旦指定时区,那个时间就是这个时区了 publicLocalDateTimegetZongTime(LocalDateTimetime,ZoneIdsrc,ZoneIddest){ //难点就是如何求偏移量 //这里使用默认时区,在中国的就是中国,在美国的就是美国,这样估计更合适 Objects.requireNonNull(dest); ZonedDateTimez1=null; if(src==null){ z1=time.atZone(ZoneId.systemDefault()); }else{ z1=time.atZone(src); } //时区及时响应变化 ZonedDateTimez2=z1.withZoneSameInstant(dest); System.out.println(dest.getId()+"对应得标准时区:"+getZoneDesc(TimeZone.getTimeZone(dest))); System.out.println("目标时区"+dest+"的时间"+z2.format(DateTimeFormatter.ofPattern("yyyy-MM-ddHH:mm:ss"))); System.out.println("-------------"); returntime; }
测试如下:
@Test publicvoidtest6()throwsException{ //预计不在夏令时2016-03-1301:59:59 LocalDateTimetime4=LocalDateTime.of(2016,3,13,14,59,59); getZongTime(time4,ZoneId.of("America/New_York")); //预计在夏令时2016-03-1303:00:00 LocalDateTimetime1=LocalDateTime.of(2016,3,13,15,00,00); getZongTime(time1,ZoneId.of("America/New_York")); //预计在夏令时结果呢:2016-11-0601:59:59 //感觉又失败了,应该是2016-11-0602:59:59 //也就是说,此时java8对夏令时的结束处理之前的方式3一模一样,提前了一小时判断 //即把夏令时结束时间当成了2016-11-600:59:59,但是java8的判断方法是正确的呀,是不是有点奇怪 LocalDateTimetime2=LocalDateTime.of(2016,11,6,14,59,59); getZongTime(time2,ZoneId.of("America/New_York")); //预计不在夏令时2016-11-0602:00:00 LocalDateTimetime3=LocalDateTime.of(2016,11,6,15,00,00); getZongTime(time3,ZoneId.of("America/New_York")); }
所以我现在怀疑这种结果到底是不是系统计算问题呢,还是我不了解纽约的习俗呢?
但是还是可以得到我想要的结果的,运用2个方法:
withEarlierOffsetAtOverlap(),withLaterOffsetAtOverlap()
版本2:
//获取指定时间的指定时区时间参照点:默认时区 publicLocalDateTimegetZongTime2(LocalDateTimetime,ZoneIddest){ Objects.requireNonNull(dest); returngetZongTime2(time,null,dest); } //版本2 publicLocalDateTimegetZongTime2(LocalDateTimetime,ZoneIdsrc,ZoneIddest){ //难点就是如何求偏移量 //这里使用默认时区,在中国的就是中国,在美国的就是美国,这样估计更合适 Objects.requireNonNull(dest); ZonedDateTimez1=null; if(src==null){ z1=time.atZone(ZoneId.systemDefault()); }else{ z1=time.atZone(src); } // ZonedDateTimez2=z1.withZoneSameInstant(dest); //处理重叠问题 longhours=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-ddHH:mm:ss"))); System.out.println("-------------"); returntime; }
测试:OK了
@Test publicvoidtest4()throwsException{ //预计不在夏令时2016-03-1301:59:59 LocalDateTimetime4=LocalDateTime.of(2016,3,13,14,59,59); getZongTime2(time4,ZoneId.of("America/New_York")); //预计在夏令时2016-03-1303:00:00 LocalDateTimetime1=LocalDateTime.of(2016,3,13,15,00,00); getZongTime2(time1,ZoneId.of("America/New_York")); //预计在夏令时2016-11-0602:59:59 LocalDateTimetime2=LocalDateTime.of(2016,11,6,14,59,59); getZongTime2(time2,ZoneId.of("America/New_York")); //预计不在夏令时2016-11-0602:00:00 LocalDateTimetime3=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-1301:59:59
-------------
America/New_York对应得标准时区:GMT-5
目标时区America/New_York的时间2016-03-1303:00:00
-------------
America/New_York对应得标准时区:GMT-5
目标时区America/New_York的时间2016-11-0602:59:59
-------------
America/New_York对应得标准时区:GMT-5
目标时区America/New_York的时间2016-11-0602:00:00
-------------
以上这篇基于java时区转换夏令时的问题及解决方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。