Java原本提供了Date和Calendar用于处理日期、时间的类,包括创建日期、时间对象,获取系统当前日期、时间等操作。但Date不仅无法实现国际化,而且它对不同属性也使用了前后矛盾的偏移量,比如月份与小时都是从0开始的,月份中的天数则是从1开始的,年又是从1900开始的,而java.util.Calendar则显得过于复杂,从下面介绍中会看到传统Java对日期、时间处理的不足。Java 8吸取了Joda-Time库(一个被广泛使用的日期、时间库)的经验,提供了一套全新的日期时间库。
一、Date类
Java提供了Date类来处理日期、时间(此处的Date是指java.util包下的Date类,而不是java.sql包下的Date类),Date对象既包含日期,也包含时间。Date类从JDK1.0起就开始存在了,但正因为它历史悠久,所以它的大部分构造器、方法都已经过时,不再推荐使用了。
Date类提供了6个构造器,其中4个已经Deprecated(Java不再推荐使用,使用不再推荐的构造器时编译器会提出警告信息,并导致程序性能、安全性等方面的问题),剩下的两个构造器如下。
- Date():生成一个代表当前日期时间的Date对象。该构造器在底层调用System.currentTimeMillis()获得long整数作为日期参数。
- Date(long date):根据指定的long型整数来生成一个Date对象。该构造器的参数表示创建的Date对象和GMT 1970年1月1日 00:00:00之间的时间差,以毫秒作为计时单位。
与Date构造器相同的是,Date对象的大部分方法也Deprecated了,剩下为数不多的几个方法。
- boolean after(Date when):测试该日期是否在指定日期when之后。
- boolean before(Date when):测试该日期是否在指定日期when之前。
- long getTime():返回该时间对应的long型整数,即从GMT 1970年1月1日 00:00:00到该Date对象之间的时间差,以毫秒作为计时单位。
- void setTime(long time):设置该Date对象的时间。
总体来说,Date是一个设计相当糟糕的类,因此Java官方推荐尽量少用Date的构造器和方法。如果需要对日期、时间进行加减运算,或获取指定时间的年、月、日、时、分、秒信息,可使用Calendar工具类
二、Calendar类
因为Date类在设计上存在一些缺陷,所以Java提供了Calendar类来更好地处理日期和时间。Calendar是一个抽象类,它用于表示日历。
历史上有着许多种纪年方法,它们的差异实在太大了。为了统一计时,全世界通常选择最普及、最通用的日历:Gregorian Calendar,也就是日常介绍年份时常用的“公元几几年”。
Calendar类本身是一个抽象类,它是所有日历类的模板,并提供了一些所有日历通用的方法;但它本身不能直接实例化,程序只能创建Calendar子类的实例,Java本身提供了一个GregorianCalendar类,一个代表格里高利日历的子类,它代表了通常所说的公历。
Calendar类是一个抽象类,所以不能使用构造器来创建Calendar对象。但它提供了几个静态genInstance()方法来获取Calendar对象,这些方法根据TimeZone,Locale类来获取特定的Calendar,如果不指定TimeZone,Locale,则使用默认的TimeZone,Locale来创建Calendar。
Calendar与Date都是表示日期的工具类,他们可以直接自由转换,如下代码所示。
// 创建一个默认的Calendar对象
Calendar calendar = Calendar.getInstance();
// 从Calendar对象中取出Date对象
Date date = calendar.getTime();
// 通过Date对象获得对应的Calendar对象
// 因为Calendar/GregorianCalendar没有构造器函数可以接收Date对象
// 所以必须先获得一个Calendar实例,然后调用其setTime()方法
Calendar calendar2 = Calendar.getInstance();
calendar2.setTime(date);
Calendar类提供了大量访问、修改日期时间的方法,常用方法如下。
- void add(int field, int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量。
- int get(int field):返回指定日历字段的值。
- int getActualMaximum(int field):返回指定日历字段可能拥有的最大值。例如月,最大值为11。
- int getActualMinimum(int field):返回指定日历字段可能拥有的最大值。例如月,最小值为0。
- void roll(int field,int amount):与add()方法类似,区别在于加上amount后超过了该字段所能表示的最大范围时,也不会向上一个字段进位。
- void set(int field,int value):将给定的日历字段设置为给定值。
- void set(int year, int month, int date):设置Calendar对象的年、月、日三个字段的值。
- void set(int year, int month, int date, int hourOfDay, int minute, int second):设置Calendar对象的年、月、日、时、分、秒6个字段的值。
**注意:**上面很多方法都需要一个int类型的field参数,field是Calendar类的类变量,如Calendar.YEAR、Calendar.MONTH等分别代表了年、月、日、小时、分钟、秒等时间字段。需要指出的是,Calendar.MONTH字段代表月份,月份的起始值不是1,而是0,所以要设置8月时,用7而不是8。
Calendar类还有如下几个注意点:
1.add与roll的区别
add(int field, int amount)的功能非常强大,add主要用于改变Calendar的特定字段的值。如果需要增加某字段的值,则让amount为整数;如果需要减少某字段的值,则让amount为负数即可。
add(int field, int amount)有如下两条规则。
- 当被修改的字段超出它允许的范围时,会发生进位,即上一级字段也会增大。例如:
Calendar cal1 = Calendar.getInstance();
cal1.set(2003, 7, 23, 0, 0, 0); // 2003-8-23
cal1.add(MONTH, 6); // 2003-8-23 => 2004-2-23
- 如果下一级字段也需要改变,那么该字段会修正到变化最小的值。例如:
Calendar cal2 = Calendar.getInstance();
cal2.set(2003, 7, 31, 0, 0, 0); // 2003-8-31
// 因为进位后月份改为2月,2月没有31日,自动变成29日
cal2.add(MONTH, 6); // 2003-8-31 => 2004-2-29
roll()的规则与add()的处理规则不同:当被修改的字段超出它允许的范围时,上一级字段不会增大。
Calendar cal3 = Calendar.getInstance();
cal3.set(2003, 7, 23, 0, 0, 0); // 2003-8-23
cal3.roll(MONTH, 6); // 2003-8-23 => 2003-2-23
下一级字段的处理规则与add()方法相似:
Calendar cal4 = Calendar.getInstance();
cal4.set(2003, 7, 31, 0, 0, 0); // 2003-8-31
// MONTH字段“进位”后变成2, 2月没有31日
// YEAR字段不会改变,2003年2月只有28天
cal4.add(MONTH, 6); // 2003-8-31 => 2003-2-28
2.设置Calendar的容错性
Calendar提供了一个setLenient()用于设置它的容错性,Calendar默认支持较好的容错性,通过setLenient(false)可以关闭Calendar的容错性,让它进行严格的参数检查。
3.set()方法延迟修改
set(f, value)方法将日历字段f更改为value,此外它还设置了一个内部成员变量,以知识日历字段f已经被更改。**尽管日历字段f是立即更改的,但该Calendar所代表的时间却不会立即修改,直到下次调用get()、getTime()、getTimeInMillis()、add()或roll()时才会重新计算日历的时间。**这被称为set()方法的延迟修改,采用延迟修改的优势是多次调用set()不会触发多次不必要的计算(需要计算出一个代表实际时间的long型整数)。
##三、Java 8新增的日期、时间包
Java 8专门新增了一个java.time包,该包下包含了如下常用的类。
- Clock:该类用于获取指定时区的当前日期、时间。该类可取代System类的currentTimeMillis()方法,而且提供了更多方法来获取当前日期、时间。该类提供了大量静态方法来获取Clock对象。
- **Duration:**该类代表持续时间。该类可以非常方便地获取一段时间。
- **Instant:**代表一个具体的时刻,可以精确到纳秒。该类提供了静态的now()方法来获取当前时刻,也提供了静态的now(Clock clock)方法来获取clock对应的时刻。除此之外,它还提供了一系列minusXxx()方法在当前时刻基础上减去一段时间,也提供了plusXxx()方法在当前时刻基础上加上一段时间。
- **LocalDate:**该类代表不带时区的日期,例如2007-12-03。该类提供了静态的now()方法来获取当前日期,也提供了静态的now(Clock clock)方法来获取clock对应的日期。除此之外,它还提供了一系列minusXxx()方法在当前年份基础上减去几年、几月、几周或几日等,也提供了plusXxx()方法在当前年份基础上加上几年、几月、几周或几日等。
- **LocalTime:**该类代表不带时区的日期,例如10:15:30。该类提供了静态的now()方法来获取当前时间,也提供了静态的now(Clock clock)方法来获取clock对应的时间。除此之外,它还提供了一系列minusXxx()方法在当前年份基础上减去几小时、几分、几秒等,也提供了plusXxx()方法在当前年份基础上加上几小时、几分、几秒等。
- **LocalDateTime:**该类代表不带时区的日期、时间,例如2007-12-03T10:15:30。该类提供了静态的now()方法来获取当前日期、时间,也提供了静态的now(Clock clock)方法来获取clock对应的日期、时间。除此之外,它还提供了一系列minusXxx()方法在当前年份基础上减去几年、几月、几日、几小时、几分、几秒等,也提供了plusXxx()方法在当前年份基础上加上几年、几月、几日、几小时、几分、几秒等。
- **MonthDay:**该类仅代表月日,例如–04-12。该类提供了静态的now()方法来获取当前月日,也提供了静态的now(Clock clock)方法来获取clock对应的月日。
- **Year:**该类仅代表年,例如2014。该类提供了静态的now()方法来获取当前年份,也提供了静态的now(Clock clock)方法来获取clock对应的年份。除此之外,它还提供了一系列minusXxx()方法在当前年份基础上减去几年,也提供了plusXxx()方法在当前年份基础上加上几年等。
- **YearMonth:**该类仅代表年月,例如2014-04。该类提供了静态的now()方法来获取当前年月,也提供了静态的now(Clock clock)方法来获取clock对应的年月。除此之外,它还提供了一系列minusXxx()方法在当前年份基础上减去几年、几月,也提供了plusXxx()方法在当前年份基础上加上几年、几月。
- **ZonedDateTime:**该类代表一个时区的日期、时间。
- **ZoneId:**该类代表一个时区
- **DayOfWeek:**这是一个枚举类,定义了周日到周六的枚举值。
- **Month:**这也是一个枚举类,定义了一月到十二月的枚举值。