基于java时区转换夏令时的问题及解决方法

网友投稿 1742 2023-03-13


基于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

为了与 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这个是死的

@Test

public 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、取得夏令时差:这个是固定的,不是根据时间动态判断,只要时区存在夏令时,就是1

int 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

//通过上面的验证我们知道了系统的判断是有问题的,通过设置时区,程序自动处理夏令时也不是那么正确,起码我现在无法理解为什么

@Test

public 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的可用时区

@Test

public void testName1() throws Exception {

//jdk8的所有时区

Set 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添加时区信息

@Test

public void testName3() {

LocalDate date=LocalDate.now();

//LocalDate添加时区信息

//方式1

ZonedDateTime 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();

//方式1

ZonedDateTime zonetime = time.atZone(ZoneId.of("GMT+0"));

//方式2

ZonedDateTime zonetime2=ZonedDateTime.now(ZoneId.of("GMT+0"));

//方式3

LocalDateTime zonetime3 = LocalDateTime.now(Clock.system(ZoneId.of("GMT+0")));

//方式4

ZonedDateTime 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;

}

//测试一下,vmPXFTF发现java8的夏令时方法判断完全正确噢

//已知2016年:America/New_York的夏令时时间是: 2016-3-13 02:00:00 到 2016-11-06 01:59:59 

// (每年3月的第二个星期日,11月的第一个星期日)

@Test

public 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"));//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:

//获取指定时间的 指定时区时间 参照点:默认时区

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(vmPXFTF"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

-------------


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:API接口文档什么写(api接口的简单编写方式)
下一篇:满仓接口测试用例编写要点(什么是满仓操作)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~