Java 时区问题解析

Java 时区问题解析_第1张图片
—-Java每个时间区域都有一个时间区域ID标识符,这个ID是个字符串,是由位于J2SE 安装程序的jre/lib子目录中的tzmappings文件保存这些ID列表。 J2SE 1.3 仅仅只包含tzmappings文件,但是 J2SE 1.4包含世界不同地区的时间区域数据文件。jre/lib/zi存放着这些文件。
—-TimeZone取默认值,先取该对象已经设定好的默认值,如果没有则取system.property中的”user.timezone”,再没有的话才根据“java.home”和“user.country”来获取。。。。(从TimeZone.getDefault()源码可知);
–System.property中user.timezone会在TimeZone执行由“java.home”和“user.country”获取timezone的方法后获得初值;

SimpleDateFormat使用

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            // 前台传进来的时间,默认是本地时区的时间,所以日期转换的时候,不会有变化
            System.out.println("set default:" + TimeZone.getDefault());
            Date dat = sdf.parse("2017-11-15");
            System.out.println(dat);

            //修改SimpleDateFormat的时区,这种方法修改时区不会更改系统的默认 --------------------时区,只是修改了转换类的使用时区
            //前台传进来的时间,经转换类转换成指定时区的时间
            sdf.setTimeZone(TimeZone.getTimeZone("GMT")); 
            System.out.println("default:" + TimeZone.getDefault());
            System.out.println("user set default:" + TimeZone.getTimeZone("GMT"));
            Date dat1 = sdf.parse("2017-11-15");
            System.out.println(dat1);
        }

结果:

set default:sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null]
②Wed Nov 15 00:00:00 CST 2017default:sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null]
④user set default:sun.util.calendar.ZoneInfo[id="GMT",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
⑤Wed Nov 15 08:00:00 CST 2017

TimeZone/Calendar/Date/DateFormat

①TimeZone:
 TimeZone对象给我们的是原始的偏移量,也就是与GMT相差的微秒数,即TimeZone表示时区偏移量,本质上以毫秒数保存与GMT的差值。获取TimeZone可以通过时区ID,如"America/New_York",也可以通过GMT+/-hh:mm来设定。例如北京时间可以表示为GMT+8:00。TimeZone.getRawOffset()方法可以用来得到当前时区的标准时间到GMT的偏移量。上段提到的"America/New_York""GMT+8:00"两个时区的偏移量分别为-1800000028800000。
②Calendar
    Calendar 类是一个抽象类,不能new,Calendar的getInstance()获取当前日历对象。
    Calendar calendar = Calendar.getInstance();//获取当前日历对象
    long unixTime = calendar.getTimeInMillis();//获取当前时区下日期时间对应的时间戳,和date.getTime()一样;
    cal.getTime() 就是当前日期即相当于new date();
    Calendar的getInstance()方法有参数为TimeZone和Locale的重载,可以使用指定时区和语言环境获得一个日历。无参则使用默认时区和语言环境获得日历。
③Date
   计算机内部记录的时间(Date date = new Date()),  即  java.util.Date代表一个时间点,其值为距公元19701100:00:00的毫秒数。所以可以认为是没有时区和Locale概念的。但是通常我们把date的时区一定是当前操作系统的时区,String类型的时间可以通过转换类指定时区,但是最终转为date的时候,都需要将指定时区的时间转为系统所在时区的时间。
   date.getTimeInMillis();//获取当前时区下日期时间对应的时间戳

     Date date11 = new Date(1391174450000L); // 2014-1-31 21:20:50    
    String dateStr11 = "2014-1-31 21:20:50 ";    
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");    
    dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));    
    try {    
        Date dateTmp = dateFormat.parse(dateStr11);    
        System.out.println(dateTmp);    
     } catch (ParseException e) {    
        e.printStackTrace();    
    }    
    String dateStrTmp = dateFormat.format(date11);    
    System.out.println(dateStrTmp);
    结果:
    Sat Feb 01 05:20:50 CST 2014
    2014-01-31 13:20:50  

④DateFormat:
    日期格式化类DateFormat, 对于不同地区的配置一般有两个点, 一个是Locale , 一个是TimeZone。
    前者(Locale)使DateFormat按所配置的地区特性来输出文字(例如中国,美国,法国不同地区对日期的表示格式不一样,中国可能是2001105日)
    后者(TimeZone)让DateFormat知道怎么去转换,去调整时间偏移度,从而得到符合配置的时区的时间.
    GMT与UTC的时区是一样的,都是以伦敦时间为基准. 而GMT+8时区就是北京时间所在时区.同一时刻的时间比GMT快8小时。
注意事项:
    Date.toString()和Calendar以及TimeZone依赖于TimeZone中defaultTimezone,即Calendar取得是TimeZone中设置的时区.
    TimeZone中设置的时区由jre这个jar下的TimeZone的源码可知,先取用户自己设置的,没有的话再取system中的。

代码分析

package com.yu;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/**
 * 关于时区的问题:
 *  TimeZone中设置的时区由jre这个jar下的TimeZone的源码可知,先取用户自己设置的,没有的话再取system中的。
 * 最好不要用TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"))等设置timezone的时区,因为这会改变Jvm的时区,影响其他业务
 * SimpleDateFormat 默认取得本地系统的时区,可以修改转换时候使用的时区
 * @author yu
* @version 1.0
* @taskId
* @CreateDate Nov 13, 2017
* @since V80
* @see com.yu
*/
public class TimeZoneUtil { public TimeZoneUtil() { } public static void main (String[] args) { notSetDefaultTimeZone(); setDefaultTimeZone(); testTime(); } /** * * Description: 用户不设置TimeZone
* * @author yu.xiaoyan1
* @taskId

*/
private static void notSetDefaultTimeZone() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); try { // 前台传进来的时间,先根据转换类的设置的时区转换日期(东八区),再转换为系统所在时区的日期。 System.out.println("set default:" + TimeZone.getDefault()); Date dat = sdf.parse("2017-11-15"); System.out.println(dat); //修改SimpleDateFormat的时区,这种方法修改时区不会更改系统的默认时区,只是修改了转换类的使用时区 //前台传进来的时间,先根据转换类的设置的时区转换日期(GMT标准时区),再转换为系统所在时区的日期 sdf.setTimeZone(TimeZone.getTimeZone("GMT")); System.out.println("default:" + TimeZone.getDefault()); System.out.println("user set default:" + TimeZone.getTimeZone("GMT")); Date dat1 = sdf.parse("2017-11-15"); System.out.println(dat1); } catch (ParseException e) { e.printStackTrace(); } Date date = new Date(); System.out.println("date.toString():" + date.toString()); System.out.println("date.getTime()" + date.getTime()); Calendar cal = Calendar.getInstance(); System.out.println("Calendar.getInstance().getTime():" + cal.getTime()); System.out.println("Calendar.getInstance().getTimeInMillis():" + cal.getTimeInMillis()); System.out.println("Calendar.getInstance().getTimeZone():" + cal.getTimeZone()); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); Date date1 = new Date(); String dateStr = format.format(date1); System.out.println(dateStr); System.out.println("user set default:" + TimeZone.getDefault()); System.out.println("system user.timezone:" + System.getProperty("user.timezone")); System.out.println("system user.country:" + System.getProperty("user.country")); System.out.println("system java.home:" + System.getProperty("java.home")); System.out.println("默认时区:" + TimeZone.getDefault().getID()); System.out.println("--------------------------------------"); } /** * * Description: 取用户设置的time zone
* * @author yu.xiaoyan1
* @taskId

*/
private static void setDefaultTimeZone() { System.out.println("After setTimeZone0:"); //设置TimeZone 默认的时区 TimeZone.setDefault(TimeZone.getTimeZone("GMT+2")); Date date = new Date(); System.out.println("date.toString():" + date); System.out.println("date.getTime()" + date.getTime()); Calendar cal = Calendar.getInstance(); System.out.println("Calendar.getInstance().getTime():" + cal.getTime()); System.out.println("Calendar.getInstance().getTimeInMillis():" + cal.getTimeInMillis()); System.out.println("Calendar.getInstance().getTimeZone():" + cal.getTimeZone()); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); String dateStr = format.format(cal.getTime()); System.out.println(dateStr); System.out.println("user set default:" + TimeZone.getDefault()); System.out.println("system user.timezone:" + System.getProperty("user.timezone")); System.out.println("system user.country:" + System.getProperty("user.country")); System.out.println("system java.home:" + System.getProperty("java.home")); System.out.println("默认时区:" + TimeZone.getDefault().getID()); System.out.println("--------------------------------------"); } /** * * Description:TimeZone和系统user.timezone不一致的时候 ,取TimeZone设置的值
* * @author yu.xiaoyan1
* @taskId

*/
private static void testTime() { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); System.setProperty("user.timezone", "GMT+2"); Date date = new Date(); System.out.println("date.toString():" + date); System.out.println("date.getTime()" + date.getTime()); Calendar cal = Calendar.getInstance(); System.out.println("Calendar.getInstance().getTime():" + cal.getTime()); System.out.println("Calendar.getInstance().getTimeInMillis():" + cal.getTimeInMillis()); System.out.println("Calendar.getInstance().getTimeZone():" + cal.getTimeZone()); System.out.println("user set default:" + TimeZone.getDefault()); System.out.println("system user.timezone:" + System.getProperty("user.timezone")); System.out.println("system user.country:" + System.getProperty("user.country")); System.out.println("system java.home:" + System.getProperty("java.home")); System.out.println("默认时区:" + TimeZone.getDefault().getID()); //.SimpleDateFormat.setTimeZone()并不会改变TimeZone的值 //既SimpleDateFormat.setTimeZone()又TimeZone.setDefault()时,SimpleDateFormat取SimpleDateFormat.setTimeZone()设定的值 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); date = new Date(); format.setTimeZone(TimeZone.getTimeZone("GMT+6")); String dateStr = format.format(date); System.out.println(dateStr); System.out.println("user set default:" + TimeZone.getDefault()); System.out.println("system user.timezone:" + System.getProperty("user.timezone")); System.out.println("system user.country:" + System.getProperty("user.country")); System.out.println("system java.home:" + System.getProperty("java.home")); System.out.println("默认时区:" + TimeZone.getDefault().getID()); System.out.println("--------------------------------------"); } }

运行结果:

date.toString():Tue Nov 14 10:46:32 CST 2017
date.getTime()1510627592734
Calendar.getInstance().getTime():Tue Nov 14 10:46:32 CST 2017
Calendar.getInstance().getTimeInMillis():1510627592767
Calendar.getInstance().getTimeZone():sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null]
2017-11-14 10:46:32 +0800
user set default:sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null]
system user.timezone:Asia/Shanghai
system user.country:CN
system java.home:D:\path\1work_software\jdk1.7
默认时区:Asia/Shanghai
--------------------------------------
After setTimeZone0:
date.toString():Tue Nov 14 04:46:32 GMT+02:00 2017
date.getTime()1510627592776
Calendar.getInstance().getTime():Tue Nov 14 04:46:32 GMT+02:00 2017
Calendar.getInstance().getTimeInMillis():1510627592777
Calendar.getInstance().getTimeZone():sun.util.calendar.ZoneInfo[id="GMT+02:00",offset=7200000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
2017-11-14 04:46:32 +0200
user set default:sun.util.calendar.ZoneInfo[id="GMT+02:00",offset=7200000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
system user.timezone:Asia/Shanghai
system user.country:CN
system java.home:D:\path\1work_software\jdk1.7
默认时区:GMT+02:00
--------------------------------------
date.toString():Tue Nov 14 10:46:32 CST 2017
date.getTime()1510627592778
Calendar.getInstance().getTime():Tue Nov 14 10:46:32 CST 2017
Calendar.getInstance().getTimeInMillis():1510627592778
Calendar.getInstance().getTimeZone():sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null]
user set default:sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null]
system user.timezone:GMT+2
system user.country:CN
system java.home:D:\path\1work_software\jdk1.7
默认时区:Asia/Shanghai
2017-11-14 08:46:32 +0600
user set default:sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null]
system user.timezone:GMT+2
system user.country:CN
system java.home:D:\path\1work_software\jdk1.7
默认时区:Asia/Shanghai
--------------------------------------

参考文献:

jvm 时区

时区

时区

时区

SImpleDateFormat的构造方法
Java 时区问题解析_第2张图片

夏令时:

 将一个以字符串形式输入的北京时间转换成美国东部时间
String inputDate = "2011-05-14 23:30:00";
TimeZone timeZoneSH = TimeZone.getTimeZone("Asia/Shanghai");
TimeZone timeZoneNY = TimeZone.getTimeZone("America/New_York");
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
inputFormat.setTimeZone(timeZoneSH);
Date date = null;
try 
{
    date = inputFormat.parse(inputDate);
} 
catch (ParseException e) 
{
}

SimpleDateFormat outputFormat = new SimpleDateFormat("EEE MMM d HH:mm:ss Z yyyy", Locale.US);
outputFormat.setTimeZone(timeZoneSH);
System.out.println("Asia/Shanghai:" + outputFormat.format(date));
outputFormat.setTimeZone(timeZoneNY);
System.out.println("America/New_York:" + outputFormat.format(date));

那么,夏令时(DST)的问题怎么解决呢?令人高兴的是,JDK(or JRE)已自动为我们进行了夏令时处理。可以做个试验,来验证以上第2段代码能适用于夏令时转换。美国在2011年开始和结束夏令时的时间是:3.13 2AM和11.6 2AM。

1. 将输入时间inputDate设置为"2011-03-13 14:59:59",输出: 
Asia/Shanghai:Sun Mar 13 14:59:59 +0800 2011 
America/New_York:Sun Mar 13 01:59:59 -0500 2011 
此时,美国东部时间还差1秒进入夏令时,与北京时间相差13小时。

2. 将输入时间inputDate设置为"2011-03-13 15:00:00",输出: 
Asia/Shanghai:Sun Mar 13 15:00:00 +0800 2011 
America/New_York:Sun Mar 13 03:00:00 -0400 2011 
此时,美国东部时间刚好进入夏令时,与北京时间相差12小时,同时,所使用的时区也发生了变化。

结束夏令时的试验就不再赘述了。

JDK(or JRE)之所以能自动的进行DST处理,是因为其已内置了各个国家的夏令时政策,并提供Timezone Updater Tool来保持低版本JDK(or JRE)的TimeZone更新,但SUN官方推荐使用JDK(or JRE)的更新来更新TimeZone信息。

综上,我们应尽量在系统中使用如上描述的long类型变量来记录时间,借助相应的方法,可方便的格式化为不同时区的时间进行显示。

你可能感兴趣的:(java)