首先请记住 Calendar 只是一个抽象类, 也就是说你无法直接获得它的一个实例,换而言之你可以提供一个自己开发的 Calendar 对象。
那究竟什么是一个 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");
- }
- }
Calendar 在 Java 中是一个抽象类(Abstract Class),GregorianCalendar 是它的一个具体实现。
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"));
- 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"));
- 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");
- else
- System.out.println("NotEqual");
- 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");
- else
- System.out.println("NotEqual");
- 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"));
out.writeObject(cal1);
out.close();
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream("newCalendar.out"));
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"));
out.writeObject(cal1);
out.close();
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream("newCalendar.out"));
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 字段。
我们现在已经能够格式化并创建一个日期对象了, 但是我们如何才能设置和获取日期数据的特定部分呢, 比如说小时, 日, 或者分钟? 我们又如何在日期的这些部分加上或者减去值呢? 答案是使用Calendar 类. 就如我们前面提到的那样, Calendar 类中的方法替代了Date 类中被人唾骂的方法.
假设你想要设置, 获取, 和操纵一个日期对象的各个部分, 比方一个月的一天或者是一个星期的一天. 为了演示这个过程, 我们将使用具体的子类 java.util.GregorianCalendar. 考虑下面的例子, 它计算得到下面的第十个星期五是13号.
import java.util.GregorianCalendar;
import java.util.Date;
import java.text.DateFormat;
public class DateExample5 {
public static void main(String[] args) {
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.FULL);
// Create our Gregorian Calendar.
GregorianCalendar cal = new GregorianCalendar();
// Set the date and time of our calendar
// to the system&s date and time
cal.setTime(new Date());
System.out.println("System Date: " + dateFormat.format(cal.getTime())); // Set the day of week to FRIDAY
cal.set(GregorianCalendar.DAY_OF_WEEK, GregorianCalendar.FRIDAY); System.out.println("After Setting Day of Week to Friday: " + dateFormat.format(cal.getTime()));
int friday13Counter = 0;
while (friday13Counter <= 10) {
// Go to the next Friday by adding 7 days. cal.add(GregorianCalendar.DAY_OF_MONTH, 7);
// If the day of month is 13 we have
// another Friday the 13th.
if (cal.get(GregorianCalendar.DAY_OF_MONTH) == 13) {
friday13Counter++; System.out.println(dateFormat.format(cal.getTime()));
}
}
}
}
在这个例子中我们作了有趣的函数调用:
cal.set(GregorianCalendar.DAY_OF_WEEK, GregorianCalendar.FRIDAY);
和:cal.add(GregorianCalendar.DAY_OF_MONTH, 7);
set 方法能够让我们通过简单的设置星期中的哪一天这个域来将我们的时间调整为星期五. 注意到这里我们使用了常量 DAY_OF_WEEK 和 FRIDAY来增强代码的可读性. add 方法让我们能够在日期上加上数值. 润年的所有复杂的计算都由这个方法自动处理.
我们这个例子的输出结果是:
System Date: Saturday, September 29, 2001
当我们将它设置成星期五以后就成了: Friday, September 28, 2001
Friday, September 13, 2002
Friday, December 13, 2002
Friday, June 13, 2003
Friday, February 13, 2004
Friday, August 13, 2004
Friday, May 13, 2005
Friday, January 13, 2006
Friday, October 13, 2006
Friday, April 13, 2007
Friday, July 13, 2007
Friday, June 13, 2008
Calendar类的基础即有变量域的观念。每个类元素都是域,并且这些域在Calendar类中表现为静态变量。这些变量域,可以通过get/set类方法来获得或者设置域值。
// 获得默认的Calendar实例,给它设置时间
Calendarcal = Calendar.getInstance();
intyear = cal.get(Calendar.YEAR);
cal.set(Calendar.MONTH,Calendar.NOVEMBER);
Calendar类的add和roll方法提供在日期之间转换的能力。每个方法都由一个参数变量和一个参数值来修改,通过这个可为正数或负数的参数值来修改它。仅仅不同的是,add方法可以向高阶的变量域溢出。例如,如果从九月三号向后倒退三天,将得到:
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE,-3);
// 值为: 星期六八月 31 23:43:19 EDT 2002
然而使用roll方法向后回滚三天得出:
Calendar cal = Calendar.getInstance();
cal.roll(Calendar.DATE,-3);
// 值为: 星期一九月 30 23:43:47 EDT 2002
这就是为什么通常主要使用add方法的原因。
还有一个隐藏在最通用的Calendar的子类中的功能性方法--isLeapYear(判断是否为闰年)方法。
Calendar cal = Calendar.getInstance();
booleanleapYear = ( (GregorianCalendar)cal ).isLeapYear(2002);
// 这个值是false
尽管它是一个实例方法,isLeapYear方法的行为表现像静态方法,需要提供年份的参数传值给日历。
其实求几天几月几年前/后的方法,应该用Calendar类比较好的(比Date)。
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.MONTH,1);
cal.add(Calendar.YEAR,2000);
date = cal.getTime();
通过接管日期修改的功能,java.util.Calendar类看上去更像是Data类的复杂版本。但是它还提供额外的功能,更不用说它的国际化支持,使得它值得拥有学习的难度曲线。
3. 使用GregorianCalendar类
创建一个代表任意日期的一个途径使用GregorianCalendar类的构造函数,它包含在java.util包中:
GregorianCalendar(int year, int month, int date)
注意月份的表示,一月是0,二月是1,以此类推,是12月是11。因为大多数人习惯于使用单词而不是使用数字来表示月份,这样程序也许更易读,父类Calendar使用常量来表示月份:JANUARY, FEBRUARY,等等。所以,创建Wilbur 和 Orville制造第一架动力飞机的日期(December 17, 1903),你可以使用:
GregorianCalendar firstFlight = new GregorianCalendar(1903, Calendar.DECEMBER, 17);
出于清楚的考虑,你应该使用前面的形式。但是,你也应该学习怎样阅读下面的短格式。下面的例子同样表示December 17,1903(记住,在短格式中,11表示December)
GregorianCalendar firstFlight = new GregorianCalendar(1903, 11, 17); 在上一节中,你学习了转换Date对象到字符串。这里,你可以做同样的事情;但是首先,你需要将GregorianCalendar对象转换到Date。要做到这一点,你可以使用getTime()方法,从它得父类Calendar继承而来。GetTime()方法返回GregorianCalendar相应的Date对象。你能够创建GregorianCalendar对象,转换到Date对象,得到和输出相应的字符串这样一个过程。下面是例子:
import java.util.*;
import java.text.*;
public class Flight {
public static void main(String[] args) {
GregorianCalendar firstFlight = new GregorianCalendar(1903, Calendar.DECEMBER, 17);
Date d = firstFlight.getTime();
DateFormat df = DateFormat.getDateInstance();
String s = df.format(d);
System.out.println("First flight was " + s);
}
有时候创建一个代表当前时刻的GregorianCalendar类的实例是很有用的。你可以简单的使用没有参数的GregorianCalendar构造函数,象这样:
GregorianCalendar thisday = new GregorianCalendar();
一个输出今天日期的例子程序,使用GregorianCalendar对象:
import java.util.*;
import java.text.*;
class Today {
public static void main(String[] args) {
GregorianCalendar thisday = new GregorianCalendar();
Date d = thisday.getTime();
DateFormat df = DateFormat.getDateInstance();
String s = df.format(d);
System.out.println("Today is " + s);
}
}
注意到,Date()构造函数和GregorianCalendar()构造函数很类似:都创建一个对象,条件简单,代表今天。
GregorianCalendar类提供处理日期的方法。一个有用的方法是add().使用add()方法,你能够增加象年,月数,天数到日期对象中。要使用add()方法,你必须提供要增加的字段,要增加的数量。一些有用的字段是DATE, MONTH, YEAR, 和 WEEK_OF_YEAR。下面的程序使用add()方法计算未来80天的一个日期。在Jules的<环球80天>是一个重要的数字,使用这个程序可以计算Phileas Fogg从出发的那一天1872年10月2日后80天的日期:
import java.util.*;
import java.text.*;
public class World {
public static void main(String[] args) {
GregorianCalendar worldTour = new GregorianCalendar(1872, Calendar.OCTOBER, 2);
worldTour.add(GregorianCalendar.DATE, 80);
Date d = worldTour.getTime();
DateFormat df = DateFormat.getDateInstance();
String s = df.format(d);
System.out.println("80 day trip will end " + s);
}
}
add()一个重要的副作用是它改变了原来的日期。有时候,拥有原始日期和修改后的日期很重要。不幸的是,你不能简单的创建一个GregorianCalendar对象,设置它和原来的相等(equal)。原因是两个变量指向同一个Date()对象地址。如果Date对象改变,两个变量就指向改变后的日期对象。代替这种做法,应该创建一个新对象。下面的程序示范了这种做法:import java.util.*;
import java.text.*;
public class ThreeDates {
public static void main(String[] args) {
GregorianCalendar gc1 = new GregorianCalendar(2000, Calendar.JANUARY, 1);
GregorianCalendar gc2 = gc1;
GregorianCalendar gc3 = new GregorianCalendar(2000, Calendar.JANUARY, 1);
//Three dates all equal to January 1, 2000
gc1.add(Calendar.YEAR, 1);
//gc1 and gc2 are changed
DateFormat df = DateFormat.getDateInstance();
Date d1 = gc1.getTime();
Date d2 = gc2.getTime();
Date d3 = gc3.getTime();
String s1 = df.format(d1);
String s2 = df.format(d2);
String s3 = df.format(d3);
System.out.println("gc1 is " + s1);
System.out.println("gc2 is " + s2);
System.out.println("gc3 is " + s3);
}
}
程序运行后,gc1和gc2被变成2001年(因为两个对象指向同一个Date,而Date已经被改变了)。对象gc3指向一个单独的Date,它没有被改变。
package com.minght.sys.util;
/**
* <p>Title: 开源,开放</p>
* <p>Description: opeansource</p>
* <p>Copyright: Copyright (c) 2004</p>
* <p>Company: ?海棠</p>
* @author HaiTang Ming
* @version 1.0
*/
import java.util.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.text.*;
public class timeUtil {
/**
* 将Date类型日期转化成String类型"任意"格式
* java.sql.Date,java.sql.Timestamp类型是java.util.Date类型的子类
* @param date Date
* @param format String
* "2003-01-01"格式
* "yyyy年M月d日"
* "yyyy-MM-dd HH:mm:ss"格式
* @return String
*/
public static String dateToString(java.util.Date date,String format) {
if (date==null || format==null) {
return null;
}
SimpleDateFormat sdf = new SimpleDateFormat(format);
String str = sdf.format(date);
return str;
}
/**
* 将String类型日期转化成java.utl.Date类型"2003-01-01"格式
* @param str String 要格式化的字符串
* @param format String
* @return Date
*/
public static java.util.Date stringToUtilDate(String str,String format) {
if (str==null||format==null) {
return null;
}
SimpleDateFormat sdf = new SimpleDateFormat(format);
java.util.Date date = null;
try
{
date = sdf.parse(str);
}
catch(Exception e)
{
}
return date;
}
/**
* 将String类型日期转化成java.sql.Date类型"2003-01-01"格式
* @param str String
* @param format String
* @return Date
*/
public static java.sql.Date stringToSqlDate(String str,String format) {
if (str==null||format==null) {
return null;
}
SimpleDateFormat sdf = new SimpleDateFormat(format);
java.util.Date date = null;
try
{
date = sdf.parse(str);
}
catch(Exception e)
{
return null;
}
return new java.sql.Date(date.getTime());
}
/**
* 将String类型日期转化成java.sql.Date类型"2003-01-01"格式
* @param str String
* @param format String
* @return Timestamp
*/
public static java.sql.Timestamp stringToTimestamp(String str,String format) {
if (str==null||format==null) {
return null;
}
SimpleDateFormat sdf = new SimpleDateFormat(format);
java.util.Date date = null;
try
{
date = sdf.parse(str);
}
catch(Exception e)
{
return null;
}
return new java.sql.Timestamp(date.getTime());
}
/**
* 将java.util.Date日期转化成java.sql.Date类型
* @param Date
* @return 格式化后的java.sql.Date
*/
public static java.sql.Date toSqlDate(Date date) {
if (date==null) {
return null;
}
return new java.sql.Date(date.getTime());
}
/**
* 将字符串转化为时间格式 string to string
* @param str String
* @param format String
* @return String
*/
public static String toDateString(String str,String oldformat,String newformat){
return dateToString(stringToUtilDate(str,oldformat),newformat);
}
/**
* 将日历转化为日期
* @param calendar Calendar
* @return Date
*/
public static java.util.Date converToDate(java.util.Calendar calendar){
return Calendar.getInstance().getTime();
}
/**
* 将日期转化为日历
* @param date Date
* @return Calendar
*/
public static java.util.Calendar converToCalendar(java.util.Date date){
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar;
}
/**
* 求得从某天开始,过了几年几月几日几时几分几秒后,日期是多少
* 几年几月几日几时几分几秒可以为负数
* @param date Date
* @param year int
* @param month int
* @param day int
* @param hour int
* @param min int
* @param sec int
* @return Date
*/
public static java.util.Date modifyDate(java.util.Date date,int year ,int month,int day,int hour,int min,int sec){
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.YEAR,year);
cal.add(Calendar.MONTH,month);
cal.add(Calendar.DATE,day);
cal.add(Calendar.HOUR,hour);
cal.add(Calendar.MINUTE,min);
cal.add(Calendar.SECOND,sec);
return cal.getTime();
}
/**
* 取得当前日期时间
* 1:year
* 2:month
* 3:day
*/
public static int getCurTime(int i) {
if (i == 1) {
return java.util.Calendar.getInstance().get(Calendar.YEAR);
}
else if (i == 2) {
return java.util.Calendar.getInstance().get(Calendar.MONTH) + 1;
}
else if (i == 3) {
return java.util.Calendar.getInstance().get(Calendar.DATE);
}
return 0;
}
public static void main(String[] args){
System.out.println(dateToString(modifyDate(Calendar.getInstance().getTime(),-1,-1,-1,-1,-1,-1),"yyyy-MM-dd HH:mm:ss"));
}
}