java时间类_JAVA日期和时间类彻底解决

你是否在苦苦挣扎在JAVA语言中的日期和时间中?当你在计算机上显示日期和时间时,, 是否要快一个小时?或者可能要早一个小时?, 或者两个小时, 或者更严重? 当你试图用JAVA写日期和时间到一个文件中,或者到你的数据库中(通过Java Database Connectivity (JDBC))— 错误的时间被保存了吗?

我曾经被这个问题困饶过很长时间。我不能解决为什么JAVA改变了我给的时间戳(timestamps)。我从数据库中检索时间戳数据 并显示在我的图形用户接口(GUI)中时, 总是会显示一个不同的时间—和我期望的值要相差1,2或3个小时。我重新检查了数据库中的值,它是正常的。 那么到底应该怎么办呢?

调查

最终我决定对这种情况来调查一番。首先,我写了一个简单的JAVA类:

import java.util.*;

public class DateTest {

public static void main(String[] args) {

System.out.println("Date = " + new Date());

System.out.println("Calendar = " + Calendar.getInstance());

}

}

在Windows 98 的 Java 2 Platform, Standard Edition (J2SE) 1.3.1_01,,我得到:

Date = Tue May 06 08:13:17 IDT 2003

Calendar = java.util.GregorianCalendar[time=1052197997184,areFieldsSet=true,areAllFieldsSet

=true,lenient=false,zone=java.util.SimpleTimeZone[id=Asia/Jerusalem,offset=7200000,

dstSavings=3600000,useDaylight=true,startYear=0,startMode=1,startMonth=3,startDay=9,

startDayOfWeek=0,startTime=3600000,startTimeMode=0,endMode=1,endMonth=8,endDay=24,

endDayOfWeek=0,endTime=3600000,endTimeMode=0],firstDayOfWeek=1,minimalDaysInFirstWeek=1,

ERA=1,YEAR=2003,MONTH=4,WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=6,DAY_OF_YEAR=126,

DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=8,HOUR_OF_DAY=8,MINUTE=13,SECOND=17,

MILLISECOND=184,ZONE_OFFSET=7200000,DST_OFFSET=3600000]

在Sun Solaris 7 with J2SE 1.3.1_02,我得到:

Date = Tue May 06 08:13:17 IDT 2003

Calendar = java.util.GregorianCalendar[time=1052197997184,areFieldsSet=true,areAllFieldsSet

=true,lenient=false,zone=java.util.SimpleTimeZone[id=Asia/Jerusalem,offset=7200000,

dstSavings=3600000,useDaylight=true,startYear=0,startMode=1,startMonth=3,startDay=9,

startDayOfWeek=0,startTime=3600000,startTimeMode=0,endMode=1,endMonth=8,endDay=24,

endDayOfWeek=0,endTime=3600000,endTimeMode=0],firstDayOfWeek=1,minimalDaysInFirstWeek=1,

ERA=1,YEAR=2003,MONTH=4,WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=6,DAY_OF_YEAR=126,

DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=8,HOUR_OF_DAY=8,MINUTE=13,SECOND=17,

MILLISECOND=184,ZONE_OFFSET=7200000,DST_OFFSET=3600000]

在 Linux Mandrake 7.2 with J2SE 1.3.0, 我得到:

Date = Mon May 05 21:04:32 GMT+00:00 2003

Calendar = java.util.GregorianCalendar[time=1052168673155,areFieldsSet=true,areAllFieldsSet

=true,lenient=true,zone=java.util.SimpleTimeZone[id=Custom,offset=0,dstSavings=3600000,

useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,

startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,

endTimeMode=0],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2003,MONTH=4,

WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=5,DAY_OF_YEAR=125,DAY_OF_WEEK=2,

DAY_OF_WEEK_IN_MONTH=1,AM_PM=1,HOUR=9,HOUR_OF_DAY=21,MINUTE=4,SECOND=33,MILLISECOND=155,

ZONE_OFFSET=0,DST_OFFSET=0]

你看到了,Calendar类似乎有一个类成员是java.util.SimpleTimeZone的一个实例,并且我也能通过一些办法确定:

使用javap实用程序,它是J2SE的一部分:

javap -private java.util.Calendar

检查J2SE中src.jar文件中可利用的源代码

实用JAVA的反射机制

在任何一种情况下,你都会发现在java.util.Calendar类中有一个私有实例成员,名字为zone,它是java.util.TimeZone实例。javap结果的部分输出显示为:

private java.util.TimeZone zone

当我对java.util.Date类采用了同样的办法时,你也能发现它有以下成员:

private transient java.util.Calendar cal;

这个就间接的表明,Date类也是一个TimeZone成员。

然而,Java文档告诉我们TimeZone是一个抽象类,但是SimpleTimeZone是一个聚合子类。因此,尽管定义为成员,Calendar中的zone成员实际上是SimpleTimeZone实例,(在J2SE 1.3中)。 这个可以通过使用上面的方法来调查TimeZone很容易得到证实。真正地,Calendar中的zone成员是一个SimpleTimeZone实例。检查DateTest 类的输出,它涉及到TimeZone的夏令时(DST)属性,并为它们命名为以下属性:

dstSavings

useDaylight

startYear

startMode

startMonth

startDay

startDayOfWeek

startTime

startTimeMode

endMode

endMonth

endDay

endDayOfWeek

endTime

endTimeMode

因此你能看到,Date和Calendar类在Daylight Saving Time上有一个概念。 档我开始调查这个的时候,是summer (last year), 为了调整我们服务器所有的夏令时(DST),我将系统时间向前挪移了一个小时。因此,我认为JAVA将不会对DST进行调整。

首先尝试的解决方案

基于Java文档信息和DateTest类的输出结果,我打了一个最好的赌:JAVA虚拟机在第一次被运行时就自动设置了一个默认的时间区域(time zone)。为了验证,我创建了一个ItsInitializer类,使得我的应用程序在被加载(launched)时便能够运行它。以下是我的第一次尝试:

import java.util.TimeZone;

import java.util.SimpleTimeZone;

public class ItsInitializer {

private static boolean s_initialized = false;

private ItsInitializer() {

}

public static synchronized void initialize() {

if (!s_initialized) {

// Modifies the default time zone, disables the Daylight Saving Time.

SimpleTimeZone dtz = (SimpleTimeZone) TimeZone.getDefault();

dtz.setStartRule(0,0,0,0);

dtz.setEndRule(0,0,0,0);

TimeZone.setDefault(dtz);

s_initialized = true;

}

}

}

换句话说,我改变了JVM的默认TimeZone,因此它没有夏令时(DST)规则,所以你也不用担心去调整它。

后来J2SE 1.4发布了,于是我也升级了。但是意外的是:它的ItsInitializer类不能运行了。于是我立即再去调查。 发现了一个DateTest类的输出(for J2SE 1.4.1_01 on Windows 98):

Date = Tue May 06 05:31:03 IDT 2003

Calendar = java.util.GregorianCalendar[time=1052188263870,areFieldsSet=true,areAllFieldsSet

=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Jerusalem",offset=7200000,

dstSavings=3600000,useDaylight=true,transitions=143,lastRule=java.util.SimpleTimeZone

[id=Asia/Jerusalem,offset=7200000,dstSavings=3600000,useDaylight=true,startYear=0,

startMode=1,startMonth=3,startDay=1,startDayOfWeek=0,startTime=3600000,startTimeMode=0,

endMode=1,endMonth=9,endDay=1,endDayOfWeek=0,endTime=3600000,endTimeMode=0]],

firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2003,MONTH=4,WEEK_OF_YEAR=19,

WEEK_OF_MONTH=2,DAY_OF_MONTH=6,DAY_OF_YEAR=126,DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,

HOUR=5,HOUR_OF_DAY=5,MINUTE=31,SECOND=3,MILLISECOND=870,ZONE_OFFSET=7200000,

DST_OFFSET=3600000]

你也看到了吧,J2SE 1.4中的TimeZone类的zone成员已经不在是SimpleTimeZone的一个实例了。取而代之的是sun.util.calendar.ZoneInfo类。因此,我需要改变它的Initializer来兼容J2SE 1.4平台:

import java.util.TimeZone;

import java.util.SimpleTimeZone;

public class ItsInitializer {

private static boolean s_initialized = false;

private ItsInitializer() {

}

public static synchronized void initialize() {

if (!s_initialized) {

// Modifies default time zone, disables Daylight Saving Time.

TimeZone l_defaultTimeZone = TimeZone.getDefault();

int l_rawOffset = l_defaultTimeZone.getRawOffset();

String l_id = l_defaultTimeZone.getID();

SimpleTimeZone l_simpleTimeZone = new SimpleTimeZone(l_rawOffset,

l_id,

0,

0,

0,

0,

0,

0,

0,

0);

TimeZone.setDefault(l_simpleTimeZone);

s_initialized = true;

}

}

}

我创建了一个没有DST规则的SimpleTimeZone实例,并将它指派为JVM的默认TimeZone. 第二个执行形式比第一个更好,因为它没有为Calendar类的zone成员考虑一个实际的类。注意,这第二个版本是向下兼容的------它能在J2SE 1.3上很好的工作。没有什么能比通过亲身经历来学习更有收获,我想获得更多的经验,然后能完全解决这个问题。

我认为,在夏令时(DTS)期间,我们总是会物理地去调整一下计算机的时间,因此我们从不必去调整DST了,这样就可以使得JVM总是设置一个默认的没有夏令时规则的time zone 。结果问题得以解决。

于是我们高兴地通过以上策略来开发我们的应用程序,所有事情都很顺利了。没有更多的日期和时间上的矛盾和差异。 当冬天来临,我们将计算机时间重新调整回来。在程序中我们也没有遇到任何日期和时间上的问题,这还要感谢ItsInitializer这个类。

但是,后来发现,我犯了另一个错,使我不得不又回到当初那种烦躁。

一个无法预料的故障

冬天过了,夏天又一次来临,自然的又进入了夏令时阶段。我原本以为没问题的,于是使用了上面的策略来解决。

On the very day we moved to DST, I got timestamp discrepancies. What happened?

今年,我没有物理上去调整计算机的时间。我已经配置了我们的SUN服务器来使我们不需要物理地调整系统时钟。date命令是一个Unix/Linux命令,用来显示当前系统的日期和时间。date的Solaris版本使用timezone配置文件帮助自动调整DST的显示。和Java的Calendar类的工作机制类似。因此,如果timezone配置文件被安装了,你就不用物理地去调整系统时钟了。我相信LINUX也可以这样做。

我们的Windows上运行的是Windows XP,我们也没有物理地去调整系统时间。我们能在Windows XP配置时间区域。问题来了,对于DST,有时候会自动进行调整而有时候又不会调整,下面两张图显示了这个:

12405008_1.gif

Figure 1. Time zone with automatic DST adjustment

12405008_2.gif

Figure 2. Time zone without automatic DST adjustment

时间区域和JAVA

涉及有关时间区域信息时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/Jerusalem DateTest

这个命令启动DateTest类(在这个文章开头被列出的),并设置 user.timezone属性到Asia/Jerusalem。你也能够通过使用java.lang.System 类的setProperty方法来设置user.timezone 属性:

System.setProperty("user.timezone","Asia/Jerusalem");

如果没有一个可用的时间区域ID适合你,那么就你可以创建一个自定义TimeZone 使用java.util.TimeZone 类的 setDefault 方法将它设置为默认的时间区域----就象我先前在ItsInitializer 类中所做的操作一样。

记住,在J2SE中,大多数日期和时间相关的类都包含时间区域信息,包括那些格式类,如java.text.DateFormat, 因此它们都会被JVM的默认时间区域所影响。然而,在你创建这些类的实例时,你能为它们确保正确的时间区域信息,使得你可以更容易来设置整个JVM的默认时间区域。并且一旦设置好,就可以确保所有的这些类都将使用同一个默认的时间区域。但是,古语云,“事事难料”。

所以,努力接触并驯服这些JAVA日期/时间类吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值