java calendar SimpleDateFormat Date 類

舟之橋發表於2013-01-22

(在文章的最後,將會介紹Date類,如果有興趣,可以直接翻到最後去閱讀)

究竟什麼是一個 Calendar 呢?中文的翻譯就是日曆,那我們立刻可以想到我們生活中有陽(公)歷、陰(農)歷之分。它們的區別在哪呢?

比如有:
月份的定義 - 陽`(公)歷 一年12 個月,每個月的天數各不同;陰(農)歷,每個月固定28天
每週的第一天 - 陽(公)歷星期日是第一天;陰(農)歷,星期一是第一天

實際上,在歷史上有著許多種紀元的方法。它們的差異實在太大了,比如說一個人的生日是"八月八日" 那麼一種可能是陽(公)歷的八月八日,但也可以是陰(農)歷的日期。所以為了計時的統一,必需指定一個日曆的選擇。那現在最為普及和通用的日曆就是 "Gregorian Calendar"。也就是我們在講述年份時常用 "公元几几年"。Calendar 抽象類定義了足夠的方法,讓我們能夠表述日曆的規則。Java 本身提供了對 "Gregorian Calendar" 規則的實現。我們從 Calendar.getInstance() 中所獲得的例項就是一個 "GreogrianCalendar" 物件(與您通過 new GregorianCalendar() 獲得的結果一致)。

下面的程式碼可以證明這一點:

import java.io.*;
import java.util.*;

public class WhatIsCalendar
{
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
if (calendar instanceof GregorianCalendar)
System.out.println("It is an instance of GregorianCalendar"t;
}
}

 

 

Calendar 在 Java 中是一個抽象類(Abstract Class),GregorianCalendar 是它的一個具體實現。

我們也可以自己的 Calendar 實現類,然後將它作為 Calendar 物件返回(物件導向的特性)。在 IBM alphaWorks 上,IBM 的開發人員實現了多種日曆(http://www.alphaworks.ibm.com/tech/calendars)。同樣在 Internet 上,也有對中國農曆的實現。本文對如何擴充套件 Calendar 不作討論,大家可以通過察看上述 Calendar 的原始碼來學習。

Calendar 與 Date 的轉換非常簡單:

Calendar calendar = Calendar.getInstance();
// 從一個 Calendar 物件中獲取 Date 物件
Date date = calendar.getTime();
// 將 Date 物件反應到一個 Calendar 物件中,
// Calendar/GregorianCalendar 沒有建構函式可以接受 Date 物件
// 所以我們必需先獲得一個例項,然後設定 Date 物件
calendar.setTime(date);

 

 


Calendar 物件在使用時,有一些值得注意的事項:

1. Calendar 的 set() 方法

set(int field, int value) - 是用來設定"年/月/日/小時/分鐘/秒/微秒"等值

field 的定義在 Calendar 中

set(int year, int month, int day, int hour, int minute, int second) 但沒有

set(int year, int month, int day, int hour, int minute, int second, int millisecond) 前面 set(int,int,int,int,int,int) 方法不會自動將 MilliSecond 清為 0。

另外,月份的起始值為0而不是1,所以要設定八月時,我們用7而不是8。

calendar.set(Calendar.MONTH, 7);

我們通常需要在程式邏輯中將它清為 0,否則可能會出現下面的情況:

import java.io.*;
import java.util.*;

public class WhatIsCalendarWrite
{
public static void main(String[] args) throws Exception{
ObjectOutputStream out =
new ObjectOutputStream(
new FileOutputStream("calendar.out"t);
Calendar cal1 = Calendar.getInstance();
cal1.set(2000, 7, 1, 0, 0, 0);
out.writeObject(cal1);
Calendar cal2 = Calendar.getInstance();
cal2.set(2000, 7, 1, 0, 0, 0);
cal2.set(Calendar.MILLISECOND, 0);
out.writeObject(cal2);
out.close();
}
}

 

 

我們將 Calendar 儲存到檔案中

import java.io.*;
import java.util.*;

public class WhatIsCalendarRead
{
public static void main(String[] args) throws Exception{
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream("calendar.out"t);
Calendar cal2 = (Calendar)in.readObject();
Calendar cal1 = Calendar.getInstance();
cal1.set(2000, 7, 1, 0, 0, 0);
if (cal1.equals(cal2))
System.out.println("Equals"t;
else
System.out.println("NotEqual"t;
System.out.println("Old calendar "+cal2.getTime().getTime());
System.out.println("New calendar "+cal1.getTime().getTime());
cal1.set(Calendar.MILLISECOND, 0);
cal2 = (Calendar)in.readObject();
if (cal1.equals(cal2))
System.out.println("Equals"t;
else
System.out.println("NotEqual"t;
System.out.println("Processed Old calendar "+cal2.getTime().getTime());
System.out.println("Processed New calendar "+cal1.getTime().getTime());
}
}

 

 

然後再另外一個程式中取回來(模擬對資料庫的儲存),但是執行的結果是:

NotEqual
Old calendar 965113200422 <------------ 最後三位的MilliSecond與當前時間有關
New calendar 965113200059 <-----------/
Equals
Processed Old calendar 965113200000
Processed New calendar 965113200000

 

 


另外我們要注意的一點是,Calendar 為了效能原因對 set() 方法採取延緩計算的方法。在 JavaDoc 中有下面的例子來說明這個問題:

Calendar cal1 = Calendar.getInstance();
cal1.set(2000, 7, 31, 0, 0 , 0); //2000-8-31
cal1.set(Calendar.MONTH, Calendar.SEPTEMBER); //應該是 2000-9-31,也就是 2000-10-1
cal1.set(Calendar.DAY_OF_MONTH, 30); //如果 Calendar 轉化到 2000-10-1,那麼現在的結果就該是 2000-10-30
System.out.println(cal1.getTime()); //輸出的是2000-9-30,說明 Calendar 不是馬上就重新整理其內部的記錄

 

 

在 Calendar 的方法中,get() 和 add() 會讓 Calendar 立刻重新整理。Set() 的這個特性會給我們的開發帶來一些意想不到的結果。我們後面會看到這個問題。

2. Calendar 物件的容錯性,Lenient 設定
我們知道特定的月份有不同的日期,當一個使用者給出錯誤的日期時,Calendar 如何處理的呢?

import java.io.*;
import java.util.*;

public class WhatIsCalendar
{
public static void main(String[] args) throws Exception{
Calendar cal1 = Calendar.getInstance();
cal1.set(2000, 1, 32, 0, 0, 0);
System.out.println(cal1.getTime());
cal1.setLenient(false);
cal1.set(2000, 1, 32, 0, 0, 0);
System.out.println(cal1.getTime());
}
}

 

 

它的執行結果是:

Tue Feb 01 00:00:00 PST 2000
Exception in thread "main" java.lang.IllegalArgumentException
at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:1368)
at java.util.Calendar.updateTime(Calendar.java:1508)
at java.util.Calendar.getTimeInMillis(Calendar.java:890)
at java.util.Calendar.getTime(Calendar.java:871)
at WhatIsCalendar.main(WhatIsCalendar.java:12)
當我們設定該 Calendar 為 Lenient false 時,它會依據特定的月份檢查出錯誤的賦值。

3. 不穩定的 Calendar

我們知道 Calendar 是可以被 serialize 的,但是我們要注意下面的問題

import java.io.*;
import java.util.*;

public class UnstableCalendar implements Serializable
{

public static void main(String[] args) throws Exception{
Calendar cal1 = Calendar.getInstance();
cal1.set(2000, 7, 1, 0, 0 , 0);
cal1.set(Calendar.MILLISECOND, 0);
ObjectOutputStream out =
new ObjectOutputStream(
new FileOutputStream("newCalendar.out"t);
out.writeObject(cal1);
out.close();
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream("newCalendar.out"t);
Calendar cal2 = (Calendar)in.readObject();
cal2.set(Calendar.MILLISECOND, 0);
System.out.println(cal2.getTime());
}
}

 

 

執行的結果竟然是: Thu Jan 01 00:00:00 PST 1970

它被複原到 EPOC 的起始點,我們稱該 Calendar 是處於不穩定狀態。這個問題的根本原因是 Java 在 serialize GregorianCalendar 時沒有儲存所有的資訊,所以當它被恢復到記憶體中,又缺少足夠的資訊時,Calendar 會被恢復到 EPOCH 的起始值。Calendar 物件由兩部分構成:欄位和相對於 EPOC 的微秒時間差。欄位資訊是由微秒時間差計算出的,而 set() 方法不會強制 Calendar 重新計算欄位。這樣欄位值就不對了。

下面的程式碼可以解決這個問題:

import java.io.*;
import java.util.*;

public class StableCalendar implements Serializable
{

public static void main(String[] args) throws Exception{
Calendar cal1 = Calendar.getInstance();
cal1.set(2000, 7, 1, 0, 0 , 0);
cal1.set(Calendar.MILLISECOND, 0);
ObjectOutputStream out =
new ObjectOutputStream(
new FileOutputStream("newCalendar.out"t);
out.writeObject(cal1);
out.close();
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream("newCalendar.out"t);
Calendar cal2 = (Calendar)in.readObject();
cal2.get(Calendar.MILLISECOND); //先呼叫 get(),強制 Calendar 重新整理
cal2.set(Calendar.MILLISECOND, 0);//再設值
System.out.println(cal2.getTime());
}
}

 

 

執行的結果是: Tue Aug 01 00:00:00 PDT 2000

這個問題主要會影響到在 EJB 程式設計中,引數物件中包含 Calendar 時。經過 Serialize/Deserialize 後,直接操作 Calendar 會產生不穩定的情況。

4. add() 與 roll() 的區別

add() 的功能非常強大,add 可以對 Calendar 的欄位進行計算。如果需要減去值,那麼使用負數值就可以了,如 add(field, -value)。

add() 有兩條規則:

當被修改的欄位超出它可以的範圍時,那麼比它大的欄位會自動修正。如:
Calendar cal1 = Calendar.getInstance();
cal1.set(2000, 7, 31, 0, 0 , 0); //2000-8-31
cal1.add(Calendar.MONTH, 1); //2000-9-31 => 2000-10-1,對嗎?
System.out.println(cal1.getTime()); //結果是 2000-9-30

另一個規則是,如果比它小的欄位是不可變的(由 Calendar 的實現類決定),那麼該小欄位會修正到變化最小的值。

以上面的例子,9-31 就會變成 9-30,因為變化最小。

Roll() 的規則只有一條:
當被修改的欄位超出它可以的範圍時,那麼比它大的欄位不會被修正。如:

Calendar cal1 = Calendar.getInstance();
cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 週日
cal1.roll(Calendar.WEEK_OF_MONTH, -1); //1999-6-1, 週二
cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 週日
cal1.add(Calendar.WEEK_OF_MONTH, -1); //1999-5-30, 週日
WEEK_OF_MONTH 比 MONTH 欄位小,所以 roll 不能修正 MONTH 欄位。

Date類介紹

Data和Calendar類:
一、建立一個日期物件r

讓我們看一個使用系統的當前日期和時間建立一個日期物件並返回一個長整數的簡
單例子. 這個時間通常被稱為Java 虛擬機器(JVM)主機環境的系統時間.
import java.util.Date;

public class DateExample1 {
public static void main(String[] args) {
// Get the system date/time
Date date = new Date();

System.out.println(date.getTime());
}
}

在星期六, 2001年9月29日, 下午大約是6:50的樣子, 上面的例子在系統輸出裝置上
顯示的結果是 1001803809710. 在這個例子中,值得注意的是我們使用了Date 構造
函式建立一個日期物件, 這個建構函式沒有接受任何引數. 而這個建構函式在內部
使用了System.currentTimeMillis() 方法來從系統獲取日期.如果用

System.out.println(new Date());

則輸出形式為:Tue Nov 08 14:28:07 CST 2005

那麼, 現在我們已經知道了如何獲取從1970年1月1日開始經歷的毫秒數了. 我們如
何才能以一種使用者明白的格式來顯示這個日期呢? 在這裡類java.text.
SimpleDateFormat 和它的抽象基類 java.text.DateFormat 就派得上用場了.

二、日期資料的定製格式

假如我們希望定製日期資料的格式, 比方星期六-9月-29日-2001年. 下面的例子展
示瞭如何完成這個工作:

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateExample2 {

public static void main(String[] args) {

SimpleDateFormat bartDateFormat =
new SimpleDateFormat("EEEE-MMMM-dd-yyyy");

Date date = new Date();

System.out.println(bartDateFormat.format(date));
}
}

只要通過向SimpleDateFormat 的建構函式傳遞格式字串"EEE-MMMM-dd-yyyy", 
我們就能夠指明自己想要的格式. 你應該可以看見, 格式字串中的ASCII 字元
告訴格式化函式下面顯示日期資料的哪一個部分. EEEE是星期, MMMM是月, dd是日
, yyyy是年. 字元的個數決定了日期是如何格式化的.傳遞"EE-MM-dd-yy"會顯示 
Sat-09-29-01. 請察看Sun 公司的Web 站點獲取日期格式化選項的完整的指示.

三、將文字資料解析成日期物件r

假設我們有一個文字字串包含了一個格式化了的日期物件, 而我們希望解析這個
字串並從文字日期資料建立一個日期物件. 我們將再次以格式化字串
"MM-dd-yyyy" 呼叫SimpleDateFormat類, 但是這一次, 我們使用格式化解析而不
是生成一個文字日期資料. 我們的例子, 顯示在下面, 將解析文字字串
"9-29-2001"並建立一個值為001736000000 的日期物件.

例子程式:

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateExample3 {

public static void main(String[] args) {
// Create a date formatter that can parse dates of
// the form MM-dd-yyyy.
SimpleDateFormat bartDateFormat =
new SimpleDateFormat("MM-dd-yyyy");

// Create a string containing a text date to be parsed.
String dateStringToParse = "9-29-2001";

try {
// Parse the text version of the date.
// We have to perform the parse method in a
// try-catch construct in case dateStringToParse
// does not contain a date in the format we are expecting.
Date date = bartDateFormat.parse(dateStringToParse);

// Now send the parsed date as a long value
// to the system output.
System.out.println(date.getTime());
}
catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
}

 

五、使用標準的日期格式化過程

既然我們已經可以生成和解析定製的日期格式了, 讓我們來看一看如何使用內建的
格式化過程. 方法 DateFormat.getDateTimeInstance() 讓我們得以用幾種不同的
方法獲得標準的日期格式化過程. 在下面的例子中, 我們獲取了四個內建的日期格
式化過程. 它們包括一個短的, 中等的, 長的, 和完整的日期格式.

import java.text.DateFormat;
import java.util.Date;

public class DateExample4 {

public static void main(String[] args) {
Date date = new Date();

DateFormat shortDateFormat =
DateFormat.getDateTimeInstance(
DateFormat.SHORT,
DateFormat.SHORT);

DateFormat mediumDateFormat =
DateFormat.getDateTimeInstance(
DateFormat.MEDIUM,
DateFormat.MEDIUM);

DateFormat longDateFormat =
DateFormat.getDateTimeInstance(
DateFormat.LONG,
DateFormat.LONG);

DateFormat fullDateFormat =
DateFormat.getDateTimeInstance(
DateFormat.FULL,
DateFormat.FULL);

System.out.println(shortDateFormat.format(date));
System.out.println(mediumDateFormat.format(date));
System.out.println(longDateFormat.format(date));
System.out.println(fullDateFormat.format(date));
}
}

 

注意我們在對 getDateTimeInstance的每次呼叫中都傳遞了兩個值. 第一個引數
是日期風格, 而第二個引數是時間風格. 它們都是基本資料型別int(整型). 考慮
到可讀性, 我們使用了DateFormat 類提供的常量: SHORT, MEDIUM, LONG, 和 
FULL. 要知道獲取時間和日期格式化過程的更多的方法和選項, 請看Sun 公司Web
站點上的解釋.

執行我們的例子程式的時候, 它將向標準輸出裝置輸出下面的內容:
9/29/01 8:44 PM
Sep 29, 2001 8:44:45 PM
September 29, 2001 8:44:45 PM EDT
Saturday, September 29, 2001 8:44:45 PM EDT 

相關文章