seaairland

           

          JavaAPI的Date,Calendar日期處理相關類分析

          ?

          在計算機程序中精確的處理日期是困難的。不僅有顯而易見的(英語:?January,?法語:?Janvier,?德語:?Januar,?等)國際化需求,?而且得考慮不同的日期系統(并非所有的文化都用基督耶穌的生日作為紀年的開始)。如有高精度或非常大規模的時間需要被處理,?就有額外的方面需要被注意,比如閏秒或時間系統的變化。(公歷(陽歷,?格里高利歷法)在西方被普遍接受是在1582年,但并非所有的國家在同一天接受!)

          盡管有關于閏秒,?時區,?夏令時,?陰歷的問題,?度量時間卻是一個非常簡單的概念:?時間的進行是線性的很容易被忽略。一旦時間軸的區域被定義,?任何時間點被從起點時間的流逝就可以確定。這和地理位置或當地時區是獨立的?–?對任意指定的時間點,?對任意地區,?從起點的過程是相同的(忽略相對論的矯正)。


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

          可當我們試圖根據某些日歷解釋這一時間點的時候困難來了,?比如,?根據月,?日,?或者年來表示它。在這一步上地理信息變得相關:?在時間上的同一個點對應不同的天的某一時間,?依賴于區域?(比如:?時區)?;诮忉屓掌诘男拚洺J潜匾?今天一個月以后是哪一天?)?并且增加了額外的困難:?上溢和下溢(12月15號的后一個月是下一年),?且不明確(1月30號后的一個月是哪一天?).

          在最初的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的簡單封裝.

          根據此種解釋,?類中僅有的沒有過期的(除了那些毫秒數的get和set方法)是那些排序方法.

          這個類依靠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?總是被初始化為當前時間.?獲得一個初始化為任意Date的Calendar?實例是不可能的—API強制程序員通過一系列的在實例上的方法調用,?比如setTime(date)來顯式的設置日期.

          訪問Interpreted?字段和類常量

          Calendar?類遵從一不常用的方式來訪問interpreted?date實例的單個字段.?而不是提供一些dedicated屬性?getters和setters方法(比如getMonth()),?它僅提供了一個,?使用一個標示作為參數來獲取請求的屬性的方法:

          int?get(Calendar.MONTH)?等等.

          注意這個函數總是返回一個int!

          這些字段的標示符被定義為Calendar類的public?static?final變量.?(這些identifiers是raw的整數,?沒有被封裝為一個枚舉抽象.)

          除了對應這些字段標示(鍵值),?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()不影響其它字段.?比如,?當給代表12月15號的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?有一個可讀的,?本地無關的顯示名.?顯示名以兩種風格:?LONG和SHORT呈現.

          星期的開始?

          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?(以冗余增加的順序).對一給定locale和style,?程序員可依靠此類獲取適當的日期格式.

          抽象基類DateFormat?沒有定義靜態方法來完成文本和日期之間的格式化和轉換.?作為替代,?它定義了幾個靜態工廠方法來獲取被初始化為給定locale和選定style的實例.?既然標準的格式化總是包含日期和時間,?附加工廠方法可用來獲取僅處理時間或日期部分的實例.?String?format(Date)和Date?parse(String)?方法然后執行變形.?注意具體的子類可以選擇打破這種習慣.

          在其內部使用,?解釋日期的Calendar對象是可訪問和修改的,?TimeZone和NumberFormat對象也同樣.?然而,?一旦DateFormat?被實例化locale和style就不能再修改.

          亦有可用的(抽象的)用以拼接的字符串解析和格式化的方法,?分別接受額外的ParsePosition或FieldPosition參數.?這些方法的每一個都有兩個版本.?一個接受或返回Date實例另一個接受或返回普通的Object,?來允許在子類中有選擇性的處理Date.?它定義了一些以_FIELD?結尾的public?static變量來標示多種可能和FieldPosition一起使用的變量(cf.?java.util.Format的Javadoc).

          僅有且最常用的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();

          }

          注意需要被catch的ParseException.?當輸入的字符串不能被parse的時候被拋出.


          java.sql.*相關類

          在java.sql.*包中的日期時間處理類都繼承了java.util.Date.?事實上它們三個反映了三種標準SQL92模型的類型需要DATE,?TIME,?and?TIMESTAMP.

          像java.util.Date,?SQL包中的這三個類是表示一個時間點的數字的簡單封裝.?分別地Date和Time類忽略關于一天中的時間或日歷的日期.

          可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.?希望這篇文章對幫助你搞清它們的用法有所幫助.


          posted on 2006-05-02 14:58 chenhui 閱讀(1252) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           

          導航

          統計

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          介紹 IOC

          友情鏈接

          最新隨筆

          搜索

          積分與排名

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 祁门县| 安国市| 宁远县| 宣威市| 卫辉市| 霍山县| 剑阁县| 周至县| 卓资县| 襄汾县| 汕尾市| 浏阳市| 三穗县| 衡南县| 德钦县| 溆浦县| 靖西县| 常州市| 通化市| 砚山县| 恩平市| 襄垣县| 淳化县| 峡江县| 海淀区| 贵南县| 鞍山市| 额敏县| 绥宁县| 琼结县| 东丽区| 铅山县| 兴宁市| 东城区| 揭阳市| 茂名市| 洪雅县| 丰顺县| 廉江市| 揭西县| 民权县|