JAVA Calendar具體解釋

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

到底什麼是一個 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 的源代碼來學習。orm

Calendar 與 Date 的轉換很easy:對象

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類:
1、建立一個日期對象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 就派得上用場了.

2、日期數據的定製格式

假如咱們但願定製日期數據的格式, 例如星期六-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 網站獲取日期格式化選項的完整的指示.

3、將文本數據解析成日期對象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());
}
}
}

 

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

既然咱們已經可以生成和解析定製的日期格式了, 讓咱們來看一看怎樣使用內建的
格式化過程. 方法 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 PMSep 29, 2001 8:44:45 PMSeptember 29, 2001 8:44:45 PM EDTSaturday, September 29, 2001 8:44:45 PM EDT 

 
相關文章
相關標籤/搜索