详解Java中的时区类TimeZone的用法
作者:log_cd
一、TimeZone 简介
TimeZone 表示时区偏移量,也可以计算夏令时。
在操作 Date, Calendar等表示日期/时间的对象时,经常会用到TimeZone;因为不同的时区,时间不同。
下面说说TimeZone对象的 2种常用创建方式。
1.获取默认的TimeZone对象
使用方法:
TimeZone tz = TimeZone.getDefault()
2.使用 getTimeZone(String id) 方法获取TimeZone对象
使用方法:
// 获取 “GMT+08:00”对应的时区 TimeZone china = TimeZone.getTimeZone("GMT+:08:00"); // 获取 “中国/重庆”对应的时区 TimeZone chongqing = TimeZone.getTimeZone("Asia/Chongqing");
关于 getTimeZone(String id) 这种方式支持的全部id参数的取值,可以通过以下方式查找:
String[] ids = TimeZone.getAvailableIDs(); for (String id:ids) System.out.printf(id+", ");
输出结果:
Etc/GMT+12, Etc/GMT+11, Pacific/Midway, Pacific/Niue ....等等
TimeZone tz = TimeZone.getTimeZone("Etc/GMT+11"); TimeZone的函数接口 // 构造函数
Object clone() synchronized static String[] getAvailableIDs() synchronized static String[] getAvailableIDs(int offsetMillis) int getDSTSavings() synchronized static TimeZone getDefault() final String getDisplayName(Locale locale) String getDisplayName(boolean daylightTime, int style, Locale locale) final String getDisplayName() final String getDisplayName(boolean daylightTime, int style) String getID() abstract int getOffset(int era, int year, int month, int day, int dayOfWeek, int timeOfDayMillis) int getOffset(long time) abstract int getRawOffset() synchronized static TimeZone getTimeZone(String id) boolean hasSameRules(TimeZone timeZone) abstract boolean inDaylightTime(Date time) synchronized static void setDefault(TimeZone timeZone) void setID(String id) abstract void setRawOffset(int offsetMillis) abstract boolean useDaylightTime()
二、TimeZone示例:
下面通过示例演示在Date中使用TimeZone。
参考代码如下(TimeZoneTest.java):
import java.text.DateFormat; import java.util.Date; import java.util.TimeZone; /** * TimeZone的测试程序 */ public class TimeZoneTest { public static void main(String[] args) { // 测试创建TimeZone对象的3种方法 showUsageOfTimeZones() ; // 测试TimeZone的其它API testOtherAPIs() ; // 打印getTimeZone(String id)支持的所有id //printAllTimeZones() ; } /** * 测试创建TimeZone对象的3种方法 */ public static void showUsageOfTimeZones() { TimeZone tz; // (01) 默认时区 tz = TimeZone.getDefault(); printDateIn(tz) ; // (02) 设置时区为"GMT+08:00" tz = TimeZone.getTimeZone("GMT+08:00"); printDateIn(tz) ; // (03) 设置时区为"" tz = TimeZone.getTimeZone("Asia/Chongqing"); printDateIn(tz) ; } /** * 打印 tz对应的日期/时间 */ private static void printDateIn(TimeZone tz) { // date为2013-09-19 14:22:30 Date date = new Date(113, 8, 19, 14, 22, 30); // 获取默认的DateFormat,用于格式化Date DateFormat df = DateFormat.getInstance(); // 设置时区为tz df.setTimeZone(tz); // 获取格式化后的字符串 String str = df.format(date); System.out.println(tz.getID()+" :"+str); } /** * 测试TimeZone的其它API */ public static void testOtherAPIs() { // 默认时区 TimeZone tz = TimeZone.getDefault(); // 获取“id” String id = tz.getID(); // 获取“显示名称” String name = tz.getDisplayName(); // 获取“时间偏移”。相对于“本初子午线”的偏移,单位是ms。 int offset = tz.getRawOffset(); // 获取“时间偏移” 对应的小时 int gmt = offset/(3600*1000); System.out.printf("id=%s, name=%s, offset=%s(ms), gmt=%s\n", id, name, offset, gmt); } /** * 打印getTimeZone(String id)支持的所有id */ public static void printAllTimeZones() { String[] ids = TimeZone.getAvailableIDs(); for (String id:ids) { //int offset = TimeZone.getTimeZone(avaIds[i]).getRawOffset(); //System.out.println(i+" "+avaIds[i]+" "+offset / (3600 * 1000) + "\t"); System.out.printf(id+", "); } System.out.println(); } }
三、关于TimeZone和时间校准
涉及有关时间区域信息时Java和Solaris很相似。每个时间区域都有一个时间区域ID标识符。在J2SE 1.3 and 1.4中,这个ID是个字符串,是由位于J2SE 安装程序的jre/lib子目录中的tzmappings文件这些ID列表。 J2SE 1.3 仅仅只包含tzmappings文件,但是 J2SE 1.4包含世界不同地区的时间区域数据文件。jre/lib/zi存放着这些文件。在J2SE 1.4里,sun.util.calendar.ZoneInfo从这些文件获取DST规则。在Solaris中, 这些时间区域数据文件是以二进制形式存放的,不是文本文件,因此你不能看它们。 在J2SE 1.4中的时间区域数据文件和在Solaris中是不同的。
java.util.TimeZone类中getDefault方法的源代码显示,它最终是会调用sun.util.calendar.ZoneInfo类的getTimeZone 方法。这个方法为需要的时间区域返回一个作为ID的String参数。这个默认的时间区域ID是从 user.timezone (system)属性那里得到。如果user.timezone没有定义,它就会尝试从user.country和java.home (System)属性来得到ID。 如果它没有成功找到一个时间区域ID,它就会使用一个"fallback" 的GMT值。换句话说, 如果它没有计算出你的时间区域ID,它将使用GMT作为你默认的时间区域。
注意,System属性是在java.lang.System类的initProperties方法中被初始化的。这是一个本地方法。因此源代码是不可用的----除非你深入到J2SE分发包中的本地代码库中去研究。然而,在Windows系统中,System 属性是从Windows注册表中被初始化的,而在Linux/Unix中是由环境变量来进行初始化。initProperties方法的Javadoc声明,某些属性"必须保证被定义" 且列出它们。被java.util.TimeZone类的getDefault方法使用的三个System属性中,只有java.home作为一种“保证的”属性在Javadoc中被列出。
推荐的解决方案 :
因此,你如何确保JAVA能给你正确的时间和日期呢?最好的办法是确认JAVA虚拟机(JVM)的默认TimeZone类是正确的,且是适合你的地理范围(Locale)的。你如何来确保默认TimeZone是正确的且适合的呢?这又是一个新问题了。象大多数处理的问题一样,这个也有许多解决方案。根据java.util.TimeZone.getDefault方法的源代码来看,最好的办法是正确地设置user.timezone属性。在启动JAVA虚拟机时,你能很容易的通过使用 -D 命令 -line 参数的办法来覆盖(override)在java.lang.System.initProperties方法中所设置的值。例如:
java -Duser.timezone=Asia/Shanghai DateTest
这个命令启动DateTest类,并设置 user.timezone属性到Asia/Shanghai。你也能够通过使用java.lang.System 类的setProperty方法来设置user.timezone 属性:
System.setProperty("user.timezone","Asia/Shanghai");
如果没有一个可用的时间区域ID适合你,那么就你可以创建一个自定义TimeZone 使用java.util.TimeZone 类的 setDefault 方法将它设置为默认的时间区域----就象我先前在ItsInitializer 类中所做的操作一样。
记住,在J2SE中,大多数日期和时间相关的类都包含时间区域信息,包括那些格式类,如java.text.DateFormat, 因此它们都会被JVM的默认时间区域所影响。然而,在你创建这些类的实例时,你能为它们确保正确的时间区域信息,使得你可以更容易来设置整个JVM的默认时间区域。并且一旦设置好,就可以确保所有的这些类都将使用同一个默认的时间区域。