时区是地球上的区域使用同一个时间定义。以前,人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。1863年,首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。世界各个国家位于地球不同位置上,因此不同国家的日出、日落时间必定有所偏差。这些偏差就是所谓的时差。
理论时区以被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(从英文“Coordinated Universal Time”/法文“Temps UniverselCoordonné”而来),是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林尼治标准时间。
世界时UT即格林尼治时间,格林尼治所在地的标准时间。
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)。
本地时间是一个相对概念,不同时区的8:00并不是同一个时刻。
从编码的角度去理解为UTC的1970年1月1日00:00:00这一时刻,加上经过的时间差(两个时刻间的时间偏移量),再换算为当前时区时间。
与Unix Time(UTC的1970年1月1日00:00:00)的时间差,是时间偏移的绝对值,这个值本身没有时区属性,是进行时间转换的基准(重要)。
Date本质上就是相对UnixTime的毫秒数,这一点从其构造函数上可以看出来:
public Date() { this(System.currentTimeMillis()); }
public Date(longdate) { fastTime =date; } |
那么为什么Date().toString()会有时区属性呢(例如:“TueFeb 02 09:59:25 CST 2016”),可以通过阅读其toString方法得到答案。
待补充
当前时刻与Unix Time的偏移毫秒数绝对值,与时区无关。
不同时区对同一时间的展示
为了避免理解混乱,我们在下面代码中明确设置两个时区相关属性,以下代码打印出来的结果是什么?
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的打印。
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));指定格式化出来的时间是哪个时区的。
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的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 |
http://blog.csdn.net/beauty9235/article/details/2033133