Java中关于时区的哪些事

PS:最近开发遇到时区的一些问题,简记如下:


时区基本概念

  •  为了克服时间上的混乱,全球划分为24个时区(东、西各12个时区)。规定英国(格林尼治天文台旧址)为中时区(零时区)、东1-12区,西1-12区。每个时区横跨经度15度,时间正好是1小时。最后的东、西第12区各跨经度7.5度,以东、西经180度为界。并且规定英国(格林尼治天文台旧址)为本初子午线,即零时(24时)经线。

  • 每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时,相邻两个时区的时间相差1小时。例如,中国东8区的时间总比泰国东7区的时间早1小时,而比日本东9区的时间迟1小时。因此,出国旅行的人,必须随时调整自己的手表,才能和当地时间相一致。凡向西走,每过一个时区,就要把表拨慢1小时(比如2点拨到1点);凡向东走,每过一个时区,就要把表拨快1小时(比如1点拨到2点)。

  •  时间纪元epoch: 所谓的"时间纪元"就是1970-01-01 00:00:00(GMT),指的是开始的时间。

  •  夏令时,又称“日光节约时制”或“夏时制”,是一种为节约能源而人为规定地方时间的制度,在这一制度实行期间所采用的统一时间称为“夏令时间”。夏令时会导致某一天多出一个小时,或者少出一个小时。冬时令时间(非夏时令时间)也称标准时间。

  •  1986年至1991年,中华人民共和国在全国范围实行了六年夏令时,每年从4月中旬的第一个星期日2时整(北京时间)到9月中旬第一个星期日的凌晨2时 整(北京夏令时)。除1986年因是实行夏令时的第一年,从5月4日开始到9月14日结束外,其它年份均按规定的时段施行。1992年4月5日后不再实 行。



Date

  • 主要记录当前时刻相对于epoch的毫秒数。通过getTime()方法获得,Date类的其他方法已经不建议使用。

  • toString()方法会将日期对应的TimeInMills值转换为默认时区(一般是操作系统设置的时区)的时间,例如:Mon Feb 01 15:29:19 CST 2016。 同样,toGMTString()方法会将日期对应的TimeInMills值转换为GMT零时区的时间。注意:不建议这样将日期转换为字符串,应该用SimpleDateFormate。



TimeZone

  • TimeZone represents a time zone offset, and also figures out daylight savings. 描述了一个时区对应的一些信息(如相对零时区的偏移数rawoffset,有没有使用夏时令)

  • TimeInMills是实现世界当前时刻相对January 1, 1970, 00:00:00 GMT(Greenwich Mean Time)的毫秒数(可正可负)。

  • 同一个TimeInMills值在不同的时区取值也不一样,比如TimeInTills=0表示世界当前时刻相对1970-01-01 00:00:00(GMT)的毫秒数,在零时区UTC(0)为1970-01-01 00:00:00,在东八区UTC+8为1970-01-01 08:00:00

  • SimpleTimeZone is a concrete subclass of TimeZone that represents a time zone for use with a Gregorian calendar.




SimpleDateFormate

  • format:日期Date 转换为 字符串。会转换为SmipleDateFormate对象设置对应时区的字符串。

  • parse:字符串转日期函数。字符串本身没有时区概念,会转换为SmipleDateFormate对象设置时区对应的日期Date。

  • 我的操作系统是"Asia/Shanghai",即GMT+8的北京时间,那么执行日期转字符串的format方法时,由于日期生成时默认是操作系统时区,因此2013-1-31 22:17:14是北京时间,那么推算到GMT时区,自然是要减8个小时的

  • 而执行字符串转日期的parse方法时,由于字符串本身没有时区的概念,因此2013-1-31 22:17:14就是指GMT(UTC)时间【ps:所有字符串都看做是GMT时间】,那么当转化为日期时要加上默认时区,即"Asia/Shanghai",因此要加上8个小时。


Calendar: 

Calendar不涉及到日期与字符串的转化,因此不像SimpleDateFormat那么复杂,与日期转字符串的思路类似。但是需要注意的是,设置完时区后,我们不能用calendar.getTime()来直接获取Date日期,因此此时的日期与一开始setTime时是相同值,要想获取某时区的时间,正确的做法是用calendar.get(field)方法



Java实践注意事项:

  • Java中不是每天都是标准的24个小时,可能是23,也可能是25。23小时和25小时就是夏令时、冬令时引起的。

  • 日期的计算,使用Calendar提供的API,是不会出差错的,简单的new Date(long milliseconds)并不靠谱

  • 来自多方协作的项目,最好使用统一的时间标准,例如系统时间,或是统一时区(如GMT)

  • CST缩写表示下面四个时区,尽量不要用基于这样缩写的时间解析(Central Standard Time (USA) UT-6:00  , Central Standard Time (Australia) UT+9:30 , 

    China Standard Time UT+8:00 , Cuba Standard Time UT-4:00  )

  • 涉及到夏时令的算法会复杂很多,使用时要注意。


参考资源:


实战代码:


package edu.ustc.java.basic.date;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.SimpleTimeZone;
import java.util.TimeZone;

/**
 * 类TimeZoneTest.java的实现描述:TODO 类实现描述
 * 
 * @author Ace Jan 25, 2016 2:06:04 PM
 */
public class TimeZoneTest {

	/** 日期格式 */
	public static final String FORMAT_DATE = "yyyy-MM-dd";
	public static final String FORMAT_TIME = "HH:mm:ss";
	public static final String FORMAT_DATETIME = "yyyy-MM-dd HH:mm:ss";

	/**
	 * 测试主函数
	 * @param args
	 */
	public static void main(String[] args) {
		testDate();
		testSimpleDateFormate();
		testTimeZone();
		testCalendar();
		testDaylightSavingTime();
		testUseDST(TimeZone.getDefault());
	}

	public static void testDate() {
		System.out.println("====> testDate()");
		Date now = new Date();
		System.out.println("now -date: year=" + now.getYear() + ", month=" + now.getMonth() + ", day=" + now.getDay());
		System.out.println(
				"now -time: hour=" + now.getHours() + ",minute=" + now.getMinutes() + ", sec=" + now.getSeconds());
		System.out.println("the number of milliseconds=" + now.getTime());
		System.out.println(" Default dateTime String = " + now.toString());
		System.out.println(" GMT dateTime String  = " + now.toGMTString());
		System.out.println(" Local dateTime String  = " + now.toLocaleString());
	}

	/**
	 * SimpleDateFormate类测试
	 */
	public static void testSimpleDateFormate() {
		System.out.println("====> testSimpleDateFormate()");
		SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_DATETIME);
		formatter.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
		Date now = new Date();
		System.out.println("        now  :" + formatter.format(now));
		Date halfYearAgo = new Date(now.getTime() - 1000L * 3600 * 24 * 180);
		System.out.println("half year ago:" + formatter.format(halfYearAgo));
		SimpleDateFormat sdf = new SimpleDateFormat(FORMAT_DATETIME);
		sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
		SimpleDateFormat sdf2 = new SimpleDateFormat(FORMAT_DATETIME);
		sdf2.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
		try {
			String dateStr = "1970-01-01 00:00:00";
			System.out.println("Local America/New_York = " + sdf.parse(dateStr));
			System.out.println("                    TS = " + sdf.parse(dateStr).getTime());
			System.out.println("Local Asia/Shanghai    = " + sdf2.parse(dateStr));
			System.out.println("                    TS = " + sdf2.parse(dateStr).getTime());
			System.out.println(sdf2.format(now));
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			System.out.println("====> 同一时间经过转换,结果不同");
			Date date = new Date(1359641834000L);// 2013-1-31 22:17:14
			String dateStr = "2013-1-31 22:17:14";
			SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT_DATETIME);
			dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
			// 字符串转日期
			System.out.println("字符串转日期,转换为本地时区显示");
			Date dateTmp = dateFormat.parse(dateStr);
			System.out.println("本地时区显示 parse result = " + dateTmp.toString());
			System.out.println("        // TimeInMills = " + dateTmp.getTime() + " ");
			System.out.println(
					"        // rawOffset   = " + (dateTmp.getTime() - date.getTime()) / 3600000 + " h (单位毫秒转换为小时)");
			System.out.println("GMT时区显示 parse result = " + dateFormat.format(dateTmp));
			System.out.println("        // TimeInMills = " + dateTmp.getTime());
			System.out.println(
					"        // rawOffset   = " + (dateTmp.getTime() - date.getTime()) / 3600000 + " h (单位毫秒转换为小时)");

			// 日期转字符串
			System.out.println("日期转字符串,转换为设置的时区显示");
			String dateStrTmp = dateFormat.format(date);
			System.out.println("format result = " + dateStrTmp);
		} catch (ParseException e) {
			e.printStackTrace();
		}
	}

	/**
	 * TimeZone类使用测试
	 */
	public static void testTimeZone() {
		System.out.println("====> testTimeZone()");
		// 输出TimeZone支持的时区ID
		System.out.println(Arrays.asList(TimeZone.getAvailableIDs()));

		System.out.println("==== 测试0 - 基本使用测试");
		// Gets the default TimeZone for this host.
		System.out.println("Default TimeZone    ID : " + TimeZone.getDefault().getID());
		System.out.println("Default TimeZone ZoneId: " + TimeZone.getDefault().toZoneId());
		System.out.println("Default TimeZone Display Name: " + TimeZone.getDefault().getDisplayName());
		System.out.println("Default TimeZone getDSTSavings: " + TimeZone.getDefault().getDSTSavings());
		System.out.println("Default raw offset: " + TimeZone.getDefault().getRawOffset());

		// Create TimeZone by the time zone ID
		System.out.println("getId    : " + TimeZone.getTimeZone("Asia/Shanghai").getID());
		System.out.println("toZoneId : " + TimeZone.getTimeZone("Asia/Shanghai").toZoneId());
		System.out.println("toString : " + TimeZone.getTimeZone("America/New_York").toString());

		// Create TimeZone by the specified custom time zone ID
		System.out.println("getId    : " + TimeZone.getTimeZone("GMT+0800").getID());
		System.out.println("toZoneId : " + TimeZone.getTimeZone("GMT+0800").toZoneId());
		System.out.println("toString : " + TimeZone.getTimeZone("GMT+0800").toString());
		System.out.println("raw offset: " + TimeZone.getTimeZone("GMT+0800").getRawOffset());

		System.out.println("==== 测试1 - 夏时令问题");
		System.out.println("====> testSimpleTimeZone()");
		int timeZoneOffset = +2;
		TimeZone timeZone = new SimpleTimeZone(timeZoneOffset * 60 * 60 * 1000, "Asia/Shanghai");
		System.out.println(timeZone.getID());
		System.out.println(
				"SimpleTimeZone is a concrete subclass of TimeZone that represents a time zone for use with a Gregorian calendar. ");
	}

	/**
	 * Calendar类使用测试
	 */
	public static void testCalendar() {
		System.out.println("====> testCalendar()");

		System.out.println("==== 测试0 - TimeInMills值的意义");
		long begin = 0;
		Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("Europe/Pairs"));
		calendar.setTimeInMillis(begin);
		System.out.println("calendar  TimeInMillis= " + calendar.getTimeInMillis());
		System.out.println("          Local Time= " + calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH)
				+ "-" + calendar.get(Calendar.DAY_OF_MONTH) + " " + calendar.get(Calendar.HOUR_OF_DAY) + ":"
				+ calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND));
		System.out.println("            get Time= " + calendar.getTime()); // 设置完时区后,我们不能用calendar.getTime()来直接获取Date日期

		calendar = Calendar.getInstance(TimeZone.getTimeZone("Asia/Shanghai"));
		calendar.setTimeInMillis(begin);
		System.out.println("calendar  TimeInMillis= " + calendar.getTimeInMillis());
		System.out.println("          Local Time= " + calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH)
				+ "-" + calendar.get(Calendar.DAY_OF_MONTH) + " " + calendar.get(Calendar.HOUR_OF_DAY) + ":"
				+ calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND));
		System.out.println("            get Time= " + calendar.getTime());// 设置完时区后,我们不能用calendar.getTime()来直接获取Date日期
		System.out.println(
				"Conclusion 0: TimeInMills是世界当前时刻相对January 1, 1970, 00:00:00 GMT(Greenwich Mean Time)的毫秒数(可正可负)。");
		System.out.println("-----------------------------");

		System.out.println("==== 测试1 - TimeInMills值相同,但在各个时区的本地时间不一样 ");
		Date date = new Date();

		System.out.println("long : ms = " + date.getTime());
		begin = date.getTime();
		calendar = Calendar.getInstance(TimeZone.getTimeZone("Europe/Pairs"));
		calendar.setTimeInMillis(begin); // 这个函数很耗时
		System.out.println("calendar  TimeInMillis= " + calendar.getTimeInMillis());
		System.out.println("          Local Time= " + calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH)
				+ "-" + calendar.get(Calendar.DAY_OF_MONTH) + " " + calendar.get(Calendar.HOUR_OF_DAY) + ":"
				+ calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND));
		System.out.println("            get Time= " + calendar.getTime());

		calendar = Calendar.getInstance(TimeZone.getTimeZone("Asia/Shanghai"));
		calendar.setTimeInMillis(begin);
		System.out.println("calendar  TimeInMillis= " + calendar.getTimeInMillis());
		System.out.println("          Local Time= " + calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH)
				+ "-" + calendar.get(Calendar.DAY_OF_MONTH) + " " + calendar.get(Calendar.HOUR_OF_DAY) + ":"
				+ calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND));
		System.out.println("            get Time= " + calendar.getTime());

		// 转换
		calendar.setTimeZone(TimeZone.getTimeZone("Europe/Pairs"));
		System.out.println("change calendar TimeZone=" + calendar.getTimeZone().getID());
		System.out.println("calendar  TimeInMillis= " + calendar.getTimeInMillis());
		System.out.println("          Local Time= " + calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH)
				+ "-" + calendar.get(Calendar.DAY_OF_MONTH) + " " + calendar.get(Calendar.HOUR_OF_DAY) + ":"
				+ calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND));
		System.out.println("Conclusion 1: 同一个calendar对象,TimeInMillis一样,但set不同的TimeZone,输出的本地时间不一样。");
		System.out.println("-----------------------------");

		System.out.println("==== 测试2 - calendar对象对日期的灵活操作 ");
		calendar.add(Calendar.DAY_OF_MONTH, 2);
		System.out.println("          Local Time= " + calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH)
				+ "-" + calendar.get(Calendar.DAY_OF_MONTH) + " " + calendar.get(Calendar.HOUR_OF_DAY) + ":"
				+ calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND));
		calendar.add(Calendar.MONTH, 2);
		System.out.println("          Local Time= " + calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH)
				+ "-" + calendar.get(Calendar.DAY_OF_MONTH) + " " + calendar.get(Calendar.HOUR_OF_DAY) + ":"
				+ calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND));
	}

	/**
	 * 夏时令测试
	 */
	public static void testDaylightSavingTime() {
		try {
			System.out.println("====> 夏时令 (Daylight Saving Time)");
			SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT_DATETIME);
			// dateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));

			String dateStr = "1986-05-06 22:17:14";
			Date tmpDate = dateFormat.parse(dateStr);
			System.out.println("tmpDate = " + tmpDate);
			// 夏令时会导致某一天多出一个小时,或者少出一个小时。
			System.out.println("==== 测试1 - 1986年5月4号0点不见了");
			Date d = dateFormat.parse("1986-5-4 0:0:0");
			System.out.println(d + " //中国在当天还在使用夏令时,时间被拨快了1个小时(1986年5月4号0点不见了)");

			System.out.println("==== 测试2 - 预期的0点也没有了");
			String sTime = "1991-04-07 00:00:00";
			Date time = dateFormat.parse(sTime);
			Calendar cd = Calendar.getInstance();
			cd.setTime(time);
			cd.add(Calendar.DATE, 7);
			time = cd.getTime();
			System.out.println(dateFormat.format(time));
		} catch (ParseException e) {
			e.printStackTrace();
		}
	}

	/*
	 * 判断一个时区从1970年开始是否使用过夏时令
	 */
	public static void testUseDST(TimeZone timeZone) {
		System.out.println("====> testUseDST 判断一个时区ID是否使用过夏时令");
		System.out.println("Time Zone is : " + timeZone.getDisplayName() + " : " + timeZone.getID());
		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		Calendar start = Calendar.getInstance(timeZone);
		start.setTime(new Date(0)); // UTC 1970-01-01
		System.out.println("start=" + df.format(start.getTime())); // will
																	// print:
																	// start=1970-01-01
																	// 08:00:00
		Calendar end = Calendar.getInstance(timeZone);
		// end.add(Calendar.YEAR, 1);
		System.out.println("end=" + df.format(end.getTime()));
		boolean find = false;
		for (long i = start.getTimeInMillis(); i < end.getTimeInMillis(); i = start.getTimeInMillis()) {
			start.add(Calendar.DATE, 1); // add one day
			if ((start.getTimeInMillis() - i) % (24 * 3600 * 1000L) != 0) { // 是否能被24整除
				find = true;
				System.out.println("from " + df.format(new Date(i)) + " to " + df.format(start.getTime()) + " has "
						+ (start.getTimeInMillis() - i) + "ms" + "[" + (start.getTimeInMillis() - i) / (3600 * 1000L)
						+ "hours]");
			}

		}
		if (!find) {
			System.out.println("Every day is ok.");
		}
	}
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值