java夏令时问题呈现(中国也存在夏令时)

一,先看看中国的夏令时

 1986年至1991年,中华人民共和国在全国范围实行了六年夏令时,每年从4月中旬的第一个星期日2时整(北京时间)到9月中旬第一个星期日的凌晨2时整;
  十日为旬。 上旬 每月第一日至第十日的十天,为上旬。 中旬 每月十一日到二十日的十天,为中旬,下旬同理

可以通过如下代码找出这6的异常点

     public static void testDayTime(TimeZone timeZone){
         
          SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         
          System.out.println("Time Zone is " + timeZone.getDisplayName() + " " + timeZone.getID());
         
          Calendar start = Calendar.getInstance(timeZone);
          start.setTime(new Date(0));//UTC 1970-01-01
         
          System.out.println("start=" + fmt.format(start.getTime()));
         
          long end = Calendar.getInstance(timeZone).getTimeInMillis();//current time
         
          boolean find = false;
          for(long i = start.getTimeInMillis(); i < end; i= start.getTimeInMillis() ){
               start.add(Calendar.DATE, 1); //add one day
              
               if((start.getTimeInMillis() - i)%(24*3600*1000L) != 0){
                   find = true;
                    System.out.println("from " + fmt.format(new Date(i)) +
                              "to " + fmt.format(start.getTime()) +
                              " has " + (start.getTimeInMillis() - i) + "ms" +
                              "[" + (start.getTimeInMillis() - i)/(3600*1000L) + "hours]");
               }
          }
          if(!find){
              System.out.println("Every day is ok.");
          }
     }
    
     public static void main(String argv[] ) throws Exception{
         
          TimeZone timeZone = TimeZone.getDefault();
          WhatTime.testDayTime(timeZone);
         
          System.out.println("----------------------------------------------------------------");
         
          timeZone = TimeZone.getTimeZone("GMT");
          WhatTime.testDayTime(timeZone);
     }
from 1986-05-03 08:00:00to 1986-05-04 08:00:00 has 82800000ms[23hours]
from 1986-09-13 08:00:00to 1986-09-14 08:00:00 has 90000000ms[25hours]
from 1987-04-11 08:00:00to 1987-04-12 08:00:00 has 82800000ms[23hours]
from 1987-09-12 08:00:00to 1987-09-13 08:00:00 has 90000000ms[25hours]
from 1988-04-09 08:00:00to 1988-04-10 08:00:00 has 82800000ms[23hours]
from 1988-09-10 08:00:00to 1988-09-11 08:00:00 has 90000000ms[25hours]
from 1989-04-15 08:00:00to 1989-04-16 08:00:00 has 82800000ms[23hours]
from 1989-09-16 08:00:00to 1989-09-17 08:00:00 has 90000000ms[25hours]
from 1990-04-14 08:00:00to 1990-04-15 08:00:00 has 82800000ms[23hours]
from 1990-09-15 08:00:00to 1990-09-16 08:00:00 has 90000000ms[25hours]
from 1991-04-13 08:00:00to 1991-04-14 08:00:00 has 82800000ms[23hours]
from 1991-09-14 08:00:00to 1991-09-15 08:00:00 has 90000000ms[25hours]
----------------------------------------------------------------
然后我们再准确的找下是那几个时间点有问题

//     
//     比如1986年的夏令时时间,从代码来看,是从1986-05-04 00:00:00到1986-09-13 22:59:59
//     但是java也不一定对,需要看老人或者看当年的报纸才能知道准确的夏令时时间
  	    @Test
  	   	public void test4() throws Exception {
  	    	SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  	        TimeZone zone = TimeZone.getDefault();
  	    	Date d12=sf.parse("1986-05-03 23:59:00");//
  	    	Date d13=sf.parse("1986-05-04 00:00:00");//
  	    	Date d14=sf.parse("1986-06-04 00:00:00");//
  	       	Date d15=sf.parse("1986-09-13 22:59:59");// 
  	       	Date d16=sf.parse("1986-09-13 23:00:00");// 
  	       	//突变点
  	       	System.out.println("===1986-05-04 00:00:00实际时间====="+sf.parse("1986-05-04 00:00:00").toLocaleString());
  	       	System.out.println("===1986-09-13 22:59:59实际时间====="+sf.parse("1986-09-13 22:59:59").toLocaleString());
  	       	System.out.println("===1986-09-13 23:00:00实际时间====="+sf.parse("1986-09-13 23:00:00").toLocaleString());
  	       	System.out.println("===1986-09-13 23:59:59实际时间====="+sf.parse("1986-09-13 23:59:59").toLocaleString());
  	       	
  	       	
  	        Date d21=sf.parse("1987-04-11 23:59:00");//
	    	Date d22=sf.parse("1987-04-12 00:00:00");//
	    	Date d23=sf.parse("1987-09-12 22:59:59");//
	       	Date d24=sf.parse("1987-09-12 23:00:00");//

	       	
  	        Date d31=sf.parse("1988-04-09 23:59:59");//
	    	Date d32=sf.parse("1988-04-10 00:00:00");//
	    	Date d33=sf.parse("1988-09-10 22:59:59");//
	       	Date d34=sf.parse("1988-09-10 23:00:00");//
	       	
	       	
	     	System.out.println("===============");
  	        Date d41=sf.parse("1989-04-15 23:59:59");//
	    	Date d42=sf.parse("1989-04-16 00:00:00");//
	    	Date d43=sf.parse("1989-09-16 22:59:59");//
	       	Date d44=sf.parse("1989-09-16 23:00:00");//
	       	
	       	
	       	
	     	System.out.println("===============");
  	        Date d51=sf.parse("1990-04-14 23:59:59");//
	    	Date d52=sf.parse("1990-04-15 00:00:00");//
	    	Date d53=sf.parse("1990-09-15 22:59:59");//
	       	Date d54=sf.parse("1990-09-15 23:00:00");//
	       	
	       	System.out.println("===============");
  	        Date d61=sf.parse("1991-04-13 23:59:59");//
	    	Date d62=sf.parse("1991-04-14 00:00:00");//
	    	Date d63=sf.parse("1991-09-14 22:59:59");//
	       	Date d64=sf.parse("1991-09-14 23:00:00");//
	       	
  	     
	        System.out.println("=========1986=======");
  	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d12));
  	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d13));
  	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d14));
  	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d15));
  	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d16));
  	        System.out.println("=========1987=======");
  	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d21));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d22));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d23));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d24));
	        System.out.println("=========1988=======");
  	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d31));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d32));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d33));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d34));
	        
	        System.out.println("=========1989=======");
  	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d41));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d42));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d43));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d44));
	        
	        System.out.println("=========1990=======");
  	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d51));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d52));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d53));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d54));
	        
	        System.out.println("======1991==========");
  	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d61));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d62));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d63));
	        System.out.println("目标时区是否使用了夏令时:"+isDaylight(zone, d64));
	        
	        
	        //6个突变点如下,只在开始实行的时候变化
  	       	System.out.println("===1986-05-04 00:00:00实际时间====="+sf.parse("1986-05-04 00:00:00").toLocaleString());
  	       	System.out.println("===1987-04-12 00:00:00实际时间====="+sf.parse("1987-04-12 00:00:00").toLocaleString());
  	       	System.out.println("===1988-04-10 00:00:00实际时间====="+sf.parse("1988-04-10 00:00:00").toLocaleString());
  	       	System.out.println("===1990-04-15 00:00:00实际时间====="+sf.parse("1990-04-15 00:00:00").toLocaleString());
  	       	System.out.println("===1991-04-14 00:00:00实际时间====="+sf.parse("1991-04-14 00:00:00").toLocaleString());
  	   	}
     
   //判断是否在夏令时
     private boolean isDaylight(TimeZone zone,Date date) {
    	//正常逻辑是:时区使用了夏令时再判断时间,这里因为中国取消了
    	if (zone.getID().equals("Asia/Shanghai")) {
    		return  zone.inDaylightTime(date);
		}
    	return zone.useDaylightTime()&&zone.inDaylightTime(date);
     }

通过上面,找到了6个突变点:

1986-05-04 00:00:00
1987-04-12 00:00:00

1988-04-10 00:00:00

1989-04-16 00:00:00

1990-04-15 00:00:00

1991-04-14 00:00:00


 /**
      * 说明:这6个夏令时日期会导致实际存入数据库的date日期发生变化,比如写的是1986-05-04,实际存入的是1986-05-04 01:00:00
      * 如果之后在数据库里面进行日期匹配的时候会出现问题,你需要trunc(date)来比较
      * 
      * 
      * 这里有2个问题,
      * 1.为什么夏令时结束的时候不会突变呢,比如:1986-09-13 23:00:00这个时候结束了夏令时
      * 按理实际的时间应该是变为:1986-09-13 22:00:00, 结果没有变,那么减少的那一个小时去哪里了呢?怎么体现?(待琢磨。。。)
      * 2.上面得到的夏令时时间范围跟实际规定的真的一样吗?
      * 
      * 下面我们看看美国的夏令时是不是也是这种情况
      */
  //美国的夏令时从三月的第二个周日开始到十一月的第一个周日结束
   		//已知官方2016年:America/New_York的夏令时时间是:  2016-3-13 02:00:00  到  2016-11-06 01:59:59 
   	     @Test
	    public void test5() throws Exception {
		// 转换为0时区时间作为参照点
		SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		// sf.setTimeZone(TimeZone.getTimeZone("GMT+0"));
		TimeZone york = TimeZone.getTimeZone("America/New_York"); // GMT-5
		sf.setTimeZone(york);

		Date d1 = sf.parse("2016-03-13 01:59:59");// false
		Date d2 = sf.parse("2016-03-13 02:00:00");// true

		Date d3 = sf.parse("2016-11-06 00:59:59");// true
		Date d3_1 = sf.parse("2016-11-06 01:00:00");// false
		Date d3_2 = sf.parse("2016-11-06 01:59:59");// false预计是夏令时时间,实际不是
		Date d4 = sf.parse("2016-11-06 02:00:00");// false
              //可以发现,对于夏令时开始的时间判断确实没问题,但是对于夏令时的结束时间判断错误,准确说,提前了一个小时判断了
		System.out.println("目标时区是否使用了夏令时:" + isDaylight(york, d1));
		System.out.println("目标时区是否使用了夏令时:" + isDaylight(york, d2));
		System.out.println("目标时区是否使用了夏令时:" + isDaylight(york, d3));
		System.out.println("目标时区是否使用了夏令时:" + isDaylight(york, d3_1));
		System.out.println("目标时区是否使用了夏令时:" + isDaylight(york, d3_2));
		System.out.println("目标时区是否使用了夏令时:" + isDaylight(york, d4));

	}

 	 //再来看下美国夏令时的突变时间
   	  @Test
  	public void test6() 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,结果还是01: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
      }
   	  /**
   	   *通过以上测试可以发现:无论是中国还是美国的夏令时
   	   *依然存在上面的2个问题:
   	   *1.夏令时在结束的时间点是不会突变的,具体原因待查
   	   *2.通过代码判断的夏令时时间段比 实际宣传的少一个小时
   	   */

     
    
	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);
	}


小结:

虽然存在这2个问题,但是时间并不影响我们代码逻辑,

唯一要注意的是夏令时开始时那6个突变点,是真实的会影响日期的准确性,在数据库进行date比较时,必须要截取日期来比较

 *1.夏令时在结束的时间点是不会突变的,具体原因待查
 *2.通过代码判断的夏令时时间段比 实际宣传的少一个小时
 
 

如果你想解决第一个问题,即在夏令时结束的时候,也让其时间突变,

参考我的另外一篇文章:

http://blog.csdn.net/u011165335/article/details/76636296




猜你喜欢

转载自blog.csdn.net/u011165335/article/details/78924498