使用Java中的Date和Calendar类

 

JavaAPIDate,Calendar日期处理相关类分析    [ 日期:2006-01-14 ]   [ 来自:pthinker ]

在计算机程序中精确的处理日期是困难的。不仅有显而易见的(英语: January, 法语: Janvier, 德语: Januar, )国际化需求而且得考虑不同的日期系统(并非所有的文化都用基督耶稣的生日作为纪年的开始)。如有高精度或非常大规模的时间需要被处理就有额外的方面需要被注意,比如闰秒或时间系统的变化。(公历(阳历格里高利历法)在西方被普遍接受是在1582,但并非所有的国家在同一天接受!)

尽管有关于闰秒时区夏令时阴历的问题度量时间却是一个非常简单的概念时间的进行是线性的很容易被忽略。一旦时间轴的区域被定义任何时间点被从起点时间的流逝就可以确定。这和地理位置或当地时区是独立的 – 对任意指定的时间点对任意地区从起点的过程是相同的(忽略相对论的矫正)


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

可当我们试图根据某些日历解释这一时间点的时候困难来了比如根据月或者年来表示它。在这一步上地理信息变得相关在时间上的同一个点对应不同的天的某一时间依赖于区域 (比如时区)。基于解释日期的修正经常是必要的(今天一个月以后是哪一天?) 并且增加了额外的困难上溢和下溢(1215号的后一个月是下一年), 且不明确(130号后的一个月是哪一天?).

在最初的JDK 1.0, 一个时间点通过把它解释为java.util.Date它被计算在一起来表示虽然相对容易处理但它并不支持国际化JDK 1.1.4 JDK 1.1.5, 多样的负责处理日期的职责被分配到以下类中:

java.util.Date
代表一个时间点.

abstract java.util.Calendar
java.util.GregorianCalendar extends java.util.Calendar
解释和处理Date.

abstract java.util.TimeZone
java.util.SimpleTimeZone extends java.util.TimeZone
代表一个任意的从格林威治的偏移量也包含了适用于夏令时(daylight savings rules)的信息.

abstract java.text.DateFormat extends java.text.Format
java.text.SimpleDateFormat extends java.text.DateFormat
变形到格式良好的可打印的String, 反之亦然.

java.text.DateFormatSymbols
月份星期等的翻译作为从Locale取得信息的一种替代选择.

java.sql.Date extends java.util.Date
java.sql.Time extends java.util.Date
java.sql.Timestamp extends java.util.Date
代表时间点同时为了在sql语句中使用也包含适当的格式.


注意: DateFormat 和相关的类在java.text.*所有的java.sql.*包中日期处理相关类继承了java.util.Date所有的其它类在java.util.*包中.

这些""类来自三个分离的继承层次其顶层类(Calendar, TimeZone, and DateFormat)是抽象的针对每一个抽象类, Java标准类库提供了一个具体的实现.

java.util.Date

java.util.Date 代表一个时间点在许多应用中此种抽象被称为"TimeStamp." 在标准的Java类库实现中这个时间点代表Unix纪元January 1, 1970, 00:00:00 GMT开始的毫秒数因而概念上来说这个类是long的简单封装.

根据此种解释类中仅有的没有过期的(除了那些毫秒数的getset方法)是那些排序方法.

这个类依靠System.currentTimeMillis() 来取得当前的时间点因此它的准确度和精度由System的实现和它所调用底层(本质是操作系统)决定.

The java.util.Date API

在最初的 Date类使用中名字和约定引起了无尽的混淆然而用0-11计算月1900计算年的决定模仿了C标准类库的习惯调用函数 getTime()返回起始于Unix纪元的毫秒数和 getDate()返回星期的决定显然是Java类设计者自己的.


java.util.Calendar

语义

Calendar
代表一个时间点(一个"Date"), 用以在特定的区域和时区适当的解释器每一个Calendar 实例有一个包含了自纪元开始的代表时间点的long变量.

这意味着Calendar 不是一个(无状态变换者或解释器也不是一个修改dates的工厂它不支持如下方式:

Month Interpreter.getMonth(inputDate) or

Date Factory.addMonth(inputDate)

Instead, Calendar
实例必须被初始化到特定的Date. Calendar实例可以被修改或查询interpreted属性.

奇怪的是此类的instances 总是被初始化为当前时间获得一个初始化为任意DateCalendar 实例是不可能的—API强制程序员通过一系列的在实例上的方法调用比如setTime(date)来显式的设置日期.

访问Interpreted 字段和类常量

Calendar 
类遵从一不常用的方式来访问interpreted date实例的单个字段而不是提供一些dedicated属性 getterssetters方法(比如getMonth()), 它仅提供了一个使用一个标示作为参数来获取请求的属性的方法:

int get(Calendar.MONTH) 
等等.

注意这个函数总是返回一个int!

这些字段的标示符被定义为Calendar类的public static final变量. (这些identifiersraw的整数没有被封装为一个枚举抽象.)

除了对应这些字段标示(键值), Calendar 类定义了许多附加的public static final 变量来保存这些字段的值因此为测试某一特定date (Calendar 的实例calendar表示是否在一年的第一个月会有像如下的代码:

if (calendar.get(Calendar.MONTH) == Calendar.JANUARY) {...}

注意月份被叫做 JANUARY, FEBRUARY, 等等不管location(相对更中性的名字比如: MONTH_1, MONTH_2, 等等). 也有一个字段UNDECIMBER, 被一些(非公历(阳历格里高利历法))日历使用代表一年的第十三个月.

不幸的是键值和值既没有通过名字也没分组成嵌套的inerface来区分.

处理

Calendar 
提供了三种办法来修改当前实例代表的日期: set(), add(), roll(). set()方法简单的设置特定的字段为期望的值. add()  roll() 的不同在于它们处理over- and underflows: add() 传递变更到"较小""较大"的字段roll()不影响其它字段比如当给代表1215号的Calendar实例增加一个月add()使用年会增加但使用roll()不会发生任何变化为每一种case对应一个函数的决定的动机是它们可能在GUI中不同的使用情形.

由于 Calendar的实现的方式它包含冗余的数据所有的字段都可以从给定的时区和纪元开始的毫秒数计算出来,反之亦然这个类为这些操作分别定义了抽象方法computeFields()computeTime(), 又定义了complete()方法执行完全的来回旅程因为有两套冗余的数据这两套数据可能不同步根据类的JavaDoc文档当发生变更的时候依赖的数据以lazily 的方式重新计算当重新计算需要的时候子类必须维护一套脏数据标志作为符号.


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

实现的Leakage

对于一个的日期相关处理类不得不说实现的细节在某种程度上被泄漏到了API在这点上这是它们有意用作基类的自定义开发的反映但它也偶然看出是不充分清晰设计一个公共接口的结果.Calendar 抽象是否维护两个冗余数据集合完全是一个实现的细节因而应当对客户类隐藏这也包括打算通过继承来重用此类.


附加的功能

Calendar
基类提供的附加功能分成三类几个静态的工厂方法来获得用任意时区和locales初始化的实例如前面提到的所有以这种方式获得实例已经被初始化为当前时间没有工厂方法被提供来获得初始化为任意时间点的实例.

第二组包含before(Object)after(Object)方法它们接受Object类型的参数因而允许这些方法被子类以任意类型的参数覆盖掉.

最后有许多附加的方法来获得设置附加的属性比如当前的时区当中有几个用以查询特定字段在当前Calendar实现下的可能和实际的最大、最小值.


java.util.GregorianCalendar


GregorianCalendar 
是仅有可用的Calendar的子类它提供了基础Calendar抽象适合于根据在西方的习惯解释日期的实现它增加了许多public的构造函数也有针对于Gregorian Calendars的方法比如isLeapYear().


java.util.TimeZone 
 java.util.SimpleTimeZone


TimeZone 
类和其子类是辅助类Calendar用以根据选择的时区来解释日期按字面意思来说一个时区表示加到GMT上后到当前时区的一定的偏移显然这个偏移在夏令时有效的时候会发生变化因而为了计算对于给定日期和时间的本地时间, TimeZone抽象不仅需要明白当DST有效时的额外偏移而且还需明白什么时候DST有效的规则.


抽象基类 TimeZone 提供了基本的处理"raw"(没有考虑夏令时)实际偏移(用毫秒数!)的方法但任何关于DST规则的功能实现被留给了子类比如SimpleTimeZone. 后者提供了许多方法来指定控制DST开始和结束的规则比如在一个月中明确的某一天或某一天随后的周几每一个TimeZone 有一个可读的本地无关的显示名显示名以两种风格: LONGSHORT呈现.

星期的开始?

Calendar 
的文档投入了相当的文字来正确的计算月或年中的weeks. weekday 被认为是一周的开始在因国家的不同而不同在美国一周通常被认为从周日开始在部分欧洲国家一周从周一开始结束于周日.这将影响到哪一周被认为是在一年(或月)第一个完整的周和计算一年的周数.


时区由一标示字符串明确的决定基类提供静态方法String[] getAvailableIDs()来获得所有已知安装(JDK内带有)的标准时区. (在我的安装内有557, JDK1.4.1) 假如需要, JavaDoc 定义了严格的建立自定义时区标示符的语法也提供了静态工厂方法用以获取 — 指定ID或缺省的当前时区的TimeZone 实例. SimpleTimeZone提供了一些公有的构造函数奇怪的是对于一个抽象类TimeZone. (JavaDoc 写到 "子类构造函数调用." 显然应该声明为protected.)

java.text.DateFormat

尽管Calendar和相关类处理locale-specific日期的解释,仍有DateFormat 类辅助日期和(人类)可阅读字符串之间的变换表示一个时间点时会出现附加的本地化问题不仅仅在语言而且日期格式是地区独立的(美国: Month/Day/Year,德国: Day.Month.Year, 等等). DateFormat 尽力地为程序员管理这些不同.

抽象基类DateFormat不需要(且不允许任意的程序员定义的日期格式作为替代它定义了四种格式化风格: SHORT, MEDIUM, LONG, FULL (以冗余增加的顺序).对一给定localestyle, 程序员可依靠此类获取适当的日期格式.

抽象基类DateFormat 没有定义静态方法来完成文本和日期之间的格式化和转换作为替代它定义了几个静态工厂方法来获取被初始化为给定locale和选定style的实例既然标准的格式化总是包含日期和时间附加工厂方法可用来获取仅处理时间或日期部分的实例. String format(Date)Date parse(String) 方法然后执行变形注意具体的子类可以选择打破这种习惯.

在其内部使用解释日期的Calendar对象是可访问和修改的, TimeZoneNumberFormat对象也同样然而一旦DateFormat 被实例化localestyle就不能再修改.

亦有可用的(抽象的)用以拼接的字符串解析和格式化的方法分别接受额外的ParsePositionFieldPosition参数这些方法的每一个都有两个版本一个接受或返回Date实例另一个接受或返回普通的Object, 来允许在子类中有选择性的处理Date. 它定义了一些以_FIELD 结尾的public static变量来标示多种可能和FieldPosition一起使用的变量(cf. java.util.FormatJavadoc).

仅有且最常用的DateFormat类的具体实现是SimpleDateFormat. 它提供了所有上述的功能且允许定义任意的时间格式有一套丰富语法来指定格式化模式; JavaDoc提供了所有细节模式可以被指定为构造函数的参数或显式的设置.

Printing a Timestamp: A Cut-and-Paste Example

想象你要用用户定义的格式打印当前的时间比如log文件以下就是做这些的:

// 
创建以下格式的模式: Hour(0-23):Minute:Second

SimpleDateFormat formatter = new SimpleDateFormat( "HH:mm:ss" );

Date now = new Date();

String logEntry = formatter.format(now);


// 
从后端读入

try {

Date sometime = formatter.parse(logEntry);

} catch ( ParseException exc ) {

exc.printStackTrace();

}

注意需要被catchParseException. 当输入的字符串不能被parse的时候被抛出.


java.sql.*
相关类

java.sql.*包中的日期时间处理类都继承了java.util.Date. 事实上它们三个反映了三种标准SQL92模型的类型需要DATE, TIME, and TIMESTAMP.

java.util.Date, SQL包中的这三个类是表示一个时间点的数字的简单封装分别地DateTime类忽略关于一天中的时间或日历的日期.

Timestamp不仅包含到毫秒精度通常的时间和日期而且允许存储附加的精确到纳秒精度的时间点的数据. (纳秒是一秒的十亿分之一)

除了影射对应的SQL数据类型这些类处理与SQL一致的字符串表示的转换在这一点这三个类中的每一个覆盖了toString()方法此外每个类提供了静态的工厂方法, valueOf(String), 返回被初始化为传递参数字符串表示的时间的当前调用类的实例这三个方法的字符串表示的格式已被SQL标准选定且不能被程序员改变.

存储纳秒需要的额外数据没有很好的与在Timestamp中其它通常的时间和日期信息的表示一致比如Timestamp实例上调用 getTime() 将返回自Unix纪元开始的毫秒数,忽略了纳秒数据简单地根据JavaDoc文档, hashCode() 方法在子类中没有被覆盖因而也忽略了纳秒数据.

java.sql.Timestamp
JavaDoc指出"inheritance relationship (...) 实际表示实现的继承而不是类型继承(这违反了继承的初衷). 但即使这句话是错误的既然Java没有私有继承的概念(也即继承实现). 所有java.sql.*包中的类应该被设计为封装一个java.util.Date对象而不是继承它仅暴露需要的方法 — 最起码方法比如hashCode() 应该被适当的覆盖.

最后一个评论是关于数据库引擎的时区的处理java.sql.*包中的类不允许显式的设置时区数据库服务器(或驱动可自由的依据服务器server的当地时区解释这些信息且其可能被影响而变化(比如因为夏令时).

总结

通过前面的讨论很清楚, Java的日期处理相关类并非很复杂但是没有被很好设计封装被疏漏, APIs结构复杂且没有被很好的组织且非常见的思路经常被无缘由的使用实现更有其它的莫名奇妙(提议看看Calendar.getInstance(Locale)对于所有可用locale实际返回对象的类型!) 另一方面, the classes manage to treat all of the difficulties inherent in internationalized date handling and, in any case, are here to stay. 希望这篇文章对帮助你搞清它们的用法有所帮助.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值