Java中的时间、时区和夏令时

相关概念


时区

时区是地球上的区域使用同一个时间定义。以前,人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。1863年,首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。世界各个国家位于地球不同位置上,因此不同国家的日出、日落时间必定有所偏差。这些偏差就是所谓的时差。


Java中的时间、时区和夏令时_第1张图片 

理论时区

理论时区以被15整除的子午线为中心,向东西两侧延伸7.5度,即每15°划分一个时区,这是理论时区。理论时区的时间采用其中央经线(或标准经线)的地方时。所以每差一个时区,区时相差一个小时,相差多少个时区,就相差多少个小时。东边的时区时间比西边的时区时间来得早。为了避免日期的紊乱,提出国际日期变更线的概念。

实际时区

         但是,为了避开国界线,有的时区的形状并不规则,而且比较大的国家以国家内部行政分界线为时区界线,这是实际时区,即法定时区。

本初子午线

英语:Prime meridian,即0度经线,亦称格林威治子午线、格林尼治子午线或本初经线,是经过英国格林尼治天文台的一条经线(亦称子午线)。本初子午线的东西两边分别定为东经和西经,于180度相遇。

国际日期变更线

         英语:InternationalDate Line,又名国际日界线、国际换日线或国际日期线,这条子午线由于穿越陆地,而在陆地变更日期既不方便也不可行,故实际使用的国际换日线是一条基本上只经过海洋表面的折线(见附图)。

为了解决日期紊乱问题,大体以180度经线为日界线;由于照顾行政区域的统一,日界线并不完全沿180°的子午线划分,而是绕过一些岛屿和海峡:由北往南通过白令海峡和阿留申、萨摩亚、斐济、汤加等群岛到达新西兰的东边。

         须注意的是,是由东向西越过此线,(从0hr到24hr)日期需加一天;由西向东越过此线,(从24hr到0hr)日期需减一天;如:于2011年4月8日15:45向东航行跨过此线,时间应变为2011年4月7日15:45。原理是从零度经线所在时区向东每跨 1 个区间时钟就拨快 1 小时, 而向西每跨 1 个区间时钟就拨慢 1 小时, 如此一来, 到了另一端经线 180 度附近, 就会有 24 小时的落差。为了平衡此一误差, 人们因而订定了国际换日线。

UTC

协调世界时,又称世界标准时间或世界协调时间,简称UTC(从英文“Coordinated Universal Time”/法文“Temps UniverselCoordonné”而来),是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林尼治标准时间。

GMT

世界时UT即格林尼治时间,格林尼治所在地的标准时间。

Unix Time

Unix时间戳(英文为Unix epoch, Unix time, POSIX time 或 Unix timestamp)

是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。

 

夏令时

夏时制,夏时令(DaylightSaving Time:DST),又称“日光节约时制”和“夏令时间”,是一种为节约能源而人为规定地方时间的制度,在这一制度实行期间所采用的统一时间称为“夏令时间”。一般在天亮早的夏季人为将时间提前一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。各个采纳夏时制的国家具体规定不同。目前全世界有近110个国家每年要实行夏令时。

    夏令时为一个时间段,一般在进入时刻将时钟调快一小时(例如2:00调为3:00),在退出时刻将时钟调慢一小时(例如3:00调为2:00)。

常用Java对时间的处理

本地时间

本地时间是一个相对概念,不同时区的8:00并不是同一个时刻。

从编码的角度去理解为UTC的1970年1月1日00:00:00这一时刻,加上经过的时间差(两个时刻间的时间偏移量),再换算为当前时区时间。

与Unix Time(UTC的1970年1月1日00:00:00)的时间差,是时间偏移的绝对值,这个值本身没有时区属性,是进行时间转换的基准(重要)。

java.util.Date

Date本质上就是相对UnixTime的毫秒数,这一点从其构造函数上可以看出来:

public Date() {

        this(System.currentTimeMillis());

}

 

public Date(longdate) {

        fastTime =date;

}

 

那么为什么Date().toString()会有时区属性呢(例如:“TueFeb 02 09:59:25 CST 2016”),可以通过阅读其toString方法得到答案。

待补充

System.currentTimeMillis()

当前时刻与Unix Time的偏移毫秒数绝对值,与时区无关。

Java中的时间、时区和夏令时_第2张图片

不同时区对同一时间的展示

Java中的时间、时区和夏令时_第3张图片

java.text.SimpleDateFormat

Parse

为了避免理解混乱,我们在下面代码中明确设置两个时区相关属性,以下代码打印出来的结果是什么?

public static void main(String[] args) throws Exception

{

    // 系统默认的时区,影响date.toString()打印的展示结果

    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));

 

    String strTime = "2016-02-01 18:41:30";

    dateParseTest("UTC", strTime);

    dateParseTest("Asia/Shanghai", strTime);

}

 

private static void dateParseTest(String timeZoneId, String strTime) throws Exception

{

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

 

    // 指定传入的yyyyMMdd是哪个时区的时间

    sdf.setTimeZone(TimeZone.getTimeZone(timeZoneId));

 

    Date time = sdf.parse(strTime);

    System.out.println(time);

}

 

打印结果:

Tue Feb 02 02:41:30 CST 2016

Mon Feb 01 18:41:30 CST 2016

 

 

将系统默认时区改为UTC,结果如何呢

public static void main(String[] args) throws Exception

{

    // 系统默认的时区,影响date.toString()打印的展示结果

    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

   

    String strTime = "2016-02-01 18:41:30";

    dateParseTest("UTC", strTime);

    dateParseTest("Asia/Shanghai", strTime);

}

 

private static void dateParseTest(String timeZoneId, String strTime) throws Exception

{

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

 

    // 指定传入的yyyyMMdd是哪个时区的时间

    sdf.setTimeZone(TimeZone.getTimeZone(timeZoneId));

 

    Date time = sdf.parse(strTime);

    System.out.println(time);

}

 

打印结果:

Mon Feb 01 18:41:30 UTC 2016

Mon Feb 01 10:41:30 UTC 2016

 

上面两个例子可以得出结论:

1.      sdf.setTimeZone(TimeZone.getTimeZone(timeZoneId));指定待解析的时间是哪个时区的时间。

2.      TimeZone.setDefault(TimeZone.getTimeZone("timeZoneId"));指定系统默认时区,影响Date.toString的打印。

 

Format

public static void main(String[] args) throws Exception

{  

    long now = 1454381552647L;

    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));

    System.out.println(new Date(now));

       

    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

    System.out.println(new Date(now));

       

    dateFormatTest("UTC", now);

    dateFormatTest("Asia/Shanghai", now);

}

   

private static void dateFormatTest(String timeZoneId, long timeMillis) throws Exception

{

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

    

         // 指定解析出来的时间所属时区

    sdf.setTimeZone(TimeZone.getTimeZone(timeZoneId));

 

         String strTime = sdf.format(new Date(timeMillis));

    System.out.println(strTime);

}

 

打印结果:

Tue Feb 02 10:52:32 CST 2016

Tue Feb 02 02:52:32 UTC 2016

2016-02-02 02:52:32.647

2016-02-02 10:52:32.647

 

结论:

1.      sdf.setTimeZone(TimeZone.getTimeZone(timeZoneId));指定格式化出来的时间是哪个时区的。

 

java.util.Calendar

Calendar本质上与Date类似,是相对于UnixTime的毫秒数,这一点从其setTime()方法可以看出来:

public final void setTime(Date date) {

    setTimeInMillis(date.getTime());

}

 

cal.setTimeZone()作用是指定Calendar以什么时区的形式展示日历。

public static void main(String[] args) throws Exception

{  

    Date time = new Date(1454381552647L);

       

    // 系统默认时区为Asia/Shanghai,就不举其他时区的例子了

    System.out.println(time);

       

    Calendar cal = Calendar.getInstance();

    cal.setTime(time);

       

    cal.setTimeZone(TimeZone.getTimeZone("UTC"));

    System.out.println(cal.get(Calendar.HOUR));

       

    cal.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));

    System.out.println(cal.get(Calendar.HOUR));

}

 

结果:

Tue Feb 02 10:52:32 CST 2016

2

10

 

Java对夏令时的展示

Java的jdk在Date的toString中已经包含夏令时的计算,以下代码可以印证:

public static void main(String[] args) throws Exception

{

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 

    String sTime = "1986-09-13 22:00:00";

    sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));

    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));

       

    Date time = sdf.parse(sTime);

    System.out.println(time.getTime());

    System.out.println(time);

       

    Calendar cd = Calendar.getInstance();

    cd.setTime(time);

       

    // 2小时以后是几点?

    cd.add(Calendar.HOUR, 2); 

    time = cd.getTime();

       

    System.out.println("------------------------------");

    System.out.println(time.getTime());

    System.out.println(time);

}

 

打印结果:

527000400000

Sat Sep 13 22:00:00 CDT 1986

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

527007600000

Sat Sep 13 23:00:00 CST 1986

 

中华人民共和国在1986年~1991年实行了夏令时制度,每年夏令时实行时间如下:

1986年5月4日至9月14日(1986年因是实行夏令时的第一年,从5月4日开始到9月14日结束)

1987年4月12日至9月13日,

1988年4月10日至9月11日,

1989年4月16日至9月17日,

1990年4月15日至9月16日,

1991年4月14日至9月15日。

 

上面代码中1986-09-1322:00:00加上2小时,应该变为1986-09-13 24:00:00(或者1986-09-14 00:00:00),但由于在9月14日零点退出夏令时,时钟向后调整1小时,实际变为1986-09-13 23:00:00。

注意:从9月14日零点退出夏令时,java的Date.toString打印的时区也从CDT恢复为CST( ChinaStandard Time UT+8:00)。

 

如何得到供展示的时间格式?

通过上面种种分析,我们得到结论,各种接口、模块、系统间,最不容易出现误解的针对时间的参数传递方式,就是传递相对Unix Time的毫秒数。

那么我们如何从毫秒数格式化为需要的展示形式呢?(明显直接Date.toString不能满足各种格式需要)

假设需求为:展示时间格式为“5:23AM,February 2 2016”并且在夏令时的情况下,展示为“5:23AM, February 2 2016 DST”,代码如何实现呢。

以下为样例代码:

public static void main(String[] args) throws Exception

{

    // 该时间为Sat Sep 13 22:00:00 CDT 1986(在中国的夏令时中)

    Date time = new Date(527000400000L);

       

    // 明确当前系统时区,Date.toString会打印为该时区时间

    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));

 

    System.out.println(time);

       

    // 将这个时间格式化为Asia/Shanghai时区的展示形式(在夏令时中)

    formatDst(time, "Asia/Shanghai");

       

    // 将这个时间格式化为GMT+8时区的展示形式(无夏令时)

    formatDst(time, "GMT+8");

       

    // 将这个时间格式化为UTC时区的展示形式(无夏令时)

    formatDst(time, "UTC");

}

 

private static void formatDst(Date time, String timeZoneId)

{

    SimpleDateFormat sdf = new SimpleDateFormat("H:ma, MMMM d yyyy",

            Locale.US);

 

    TimeZone tz = TimeZone.getTimeZone(timeZoneId);

    sdf.setTimeZone(tz);

    String strTime = sdf.format(time);

 

    // 判断该时间在tz时区中是否处于夏令时中

    if (tz.inDaylightTime(time))

    {

        strTime = strTime + " DST";

    }

       

    System.out.println(strTime);

}

 

打印结果:

Sat Sep 13 22:00:00 CDT 1986

22:0PM, September 13 1986 DST

21:0PM, September 13 1986

13:0PM, September 13 1986

 

附件


时区分布图

Java中的时间、时区和夏令时_第4张图片

Java时间格式化常用缩写

http://blog.csdn.net/beauty9235/article/details/2033133

你可能感兴趣的:(源码分析,杂项)