Joda-Time 令时间和日期值变得易于管理、操作和理解。事实上,易于使用是 Joda 的主要设计目标。其他目标包括可扩展性、完整的特性集以及对多种日历系统的支持。并且 Joda 与 JDK 是百分之百可互操作的,因此您无需替换所有 Java 代码,只需要替换执行日期/时间计算的那部分代码。
本文将介绍并展示如何使用它。我将介绍以下主题:
日期/时间替代库简介
Joda 的关键概念
创建Joda-Time 对象
以Joda 的方式操作时间 style
以Joda 的方式格式化时间
Joda 简介
为什么要使用 Joda?考虑创建一个用时间表示的某个随意的时刻 — 比如,2000 年 1 月 1 日 0 时 0 分。我如何创建一个用时间表示这个瞬间的 JDK 对象?使用 java.util.Date
?事实上这是行不通的,因为自 JDK 1.1 之后的每个 Java 版本的 Javadoc 都声明应当使用java.util.Calendar
。Date
中不赞成使用的构造函数的数量严重限制了您创建此类对象的途径。
然而,Date
确实有一个构造函数,您可以用来创建用时间表示某个瞬间的对象(除 “现在” 以外)。该方法使用距离 1970 年 1 月 1 日子时格林威治标准时间(也称为 epoch)以来的毫秒数作为一个参数,对时区进行校正。考虑到 Y2K 对软件开发企业的重要性,您可能会认为我已经记住了这个值 — 但是我没有。Date
也不过如此。
那么 Calendar
又如何呢?我将使用下面的方式创建必需的实例:
Calendar calendar = Calendar.getInstance();
calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
使用 Joda,代码应该类似如下所示:
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0);
这一行简单代码没有太大的区别。但是现在我将使问题稍微复杂化。假设我希望在这个日期上加上 90 天并输出结果。使用 JDK,我需要使用清单 1 中的代码:
清单 1. 以 JDK 的方式向某一个瞬间加上 90 天并输出结果
Calendar calendar = Calendar.getInstance();
calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
SimpleDateFormat sdf =
new SimpleDateFormat("E MM/dd/yyyy HH:mm:ss.SSS");
calendar.add(Calendar.DAY_OF_MONTH, 90);
System.out.println(sdf.format(calendar.getTime()));
使用 Joda,代码如清单 2 所示:
清单 2. 以 Joda 的方式向某一个瞬间加上 90 天并输出结果
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0);
System.out.println(dateTime.plusDays(90).toString("E MM/dd/yyyy HH:mm:ss.SSS");
两者之间的差距拉大了(Joda 用了两行代码,JDK 则是 5 行代码)。
现在假设我希望输出这样一个日期:距离 Y2K 45 天之后的某天在下一个月的当前周的最后一天的日期。坦白地说,我甚至不想使用 Calendar
处理这个问题。使用 JDK 实在太痛苦了,即使是简单的日期计算,比如上面这个计算。正是多年前的这样一个时刻,我第一次领略到 Joda-Time 的强大。使用 Joda,用于计算的代码如清单 3 所示:
清单 3. 改用 Joda
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0);
System.out.println(dateTime.plusDays(45).plusMonths(1).dayOfWeek()
.withMaximumValue().toString("E MM/dd/yyyy HH:mm:ss.SSS");
清单 3 的输出为:
Sun 03/19/2000 00:00:00.000
如果您正在寻找一种易于使用的方式替代 JDK 日期处理,那么您真的应该考虑 Joda。如果不是这样的话,那么继续痛苦地使用 Calendar
完成所有日期计算吧。当您做到这一点后,您完全可以做到使用几把剪刀修建草坪并使用一把旧牙刷清洗您的汽车。
Joda 和 JDK 互操作性
JDK Calendar
类缺乏可用性,这一点很快就能体会到,而 Joda 弥补了这一不足。Joda 的设计者还做出了一个决定,我认为这是它取得成功的构建:JDK 互操作性。Joda 的类能够生成(但是,正如您将看到的一样,有时会采用一种比较迂回的方式)java.util.Date
的实例(和Calendar
)。这使您能够保留现有的依赖 JDK 的代码,但是又能够使用 Joda 处理复杂的日期/时间计算。
例如,完成 清单 3 中的计算后。我只需要做出如清单 4 所示的更改就可以返回到 JDK 中:
清单 4. 将 Joda 计算结果插入到 JDK 对象中
Calendar calendar = Calendar.getInstance();
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0);
System.out.println(dateTime.plusDays(45).plusMonths(1).dayOfWeek()
.withMaximumValue().toString("E MM/dd/yyyy HH:mm:ss.SSS");
calendar.setTime(dateTime.toDate());
就是这么简单。我完成了计算,但是可以继续在 JDK 对象中处理结果。这是 Joda 的一个非常棒的特性。
Joda 的关键日期/时间概念
Joda 使用以下概念,它们可以应用到任何日期/时间库:
- 不可变性(Immutability)
- 瞬间性(Instant)
- 局部性(Partial)
- 年表(Chronology)
- 时区(Time zone)
我将针对 Joda 依次讨论每一个概念。
不可变性
我在本文讨论的 Joda 类具有不可变性,因此它们的实例无法被修改。(不可变类的一个优点就是它们是线程安全的)。我将向您展示的用于处理日期计算的 API 方法全部返回一个对应 Joda 类的新实例,同时保持原始实例不变。当您通过一个 API 方法操作 Joda 类时,您必须捕捉该方法的返回值,因为您正在处理的实例不能被修改。您可能对这种模式很熟悉;比如,这正是 java.lang.String
的各种操作方法的工作方式。
瞬间性
Instant
表示时间上的某个精确的时刻,使用从 epoch 开始计算的毫秒表示。这一定义与 JDK 相同,这就是为什么任何 Joda Instant
子类都可以与 JDK Date
和 Calendar
类兼容的原因。
更通用一点的定义是:一个瞬间 就是指时间线上只出现一次且唯一的一个时间点,并且这种日期结构只能以一种有意义的方式出现一次。
局部性
一个局部时间,正如我将在本文中将其称为局部时间片段一样,它指的是时间的一部分片段。瞬间性指定了与 epoch 相对的时间上的一个精确时刻,与此相反,局部时间片段指的是在时间上可以来回 “移动” 的一个时刻,这样它便可以应用于多个实例。比如,6 月 2 日 可以应用于任意一年的 6 月份(使用 Gregorian 日历)的第二天的任意瞬间。同样,11:06 p.m. 可以应用于任意一年的任意一天,并且每天只能使用一次。即使它们没有指定一个时间上的精确时刻,局部时间片段仍然是有用的。
我喜欢将局部时间片段看作一个重复周期中的一点,这样的话,如果我正在考虑的日期构建可以以一种有意义的方式出现多次(即重复的),那么它就是一个局部时间。
年表
Joda 本质 — 以及其设计核心 — 的关键就是年表(它的含义由一个同名抽象类捕捉)。从根本上讲,年表是一种日历系统 — 一种计算时间的特殊方式 — 并且是一种在其中执行日历算法的框架。受 Joda 支持的年表的例子包括:
- ISO(默认)
- Coptic
- Julian
- Islamic
Joda-Time 1.6 支持 8 种年表,每一种都可以作为特定日历系统的计算引擎。
时区
时区是值一个相对于英国格林威治的地理位置,用于计算时间。要了解事件发生的精确时间,还必须知道发生此事件的位置。任何严格的时间计算都必须涉及时区(或相对于 GMT),除非在同一个时区内发生了相对时间计算(即时这样时区也很重要,如果事件对于位于另一个时区的各方存在利益关系的话)。
DateTimeZone
是 Joda 库用于封装位置概念的类。许多日期和时间计算都可以在不涉及时区的情况下完成,但是仍然需要了解 DateTimeZone
如何影响 Joda 的操作。默认时间,即从运行代码的机器的系统时钟检索到的时间,在大部分情况下被使用。
介绍的差不多了,接下来直接看代码:
package com.bijian.study;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
public class JodaTest {
public static void main(String[] args) {
// 初始化时间
DateTime dateTime = new DateTime(2012, 12, 13, 18, 23, 55);
// 年,月,日,时,分,秒,毫秒
DateTime dt3 = new DateTime(2011, 2, 13, 10, 30, 50, 333);
// 2010年2月13日10点30分50秒333毫秒
// 下面就是按照一点的格式输出时间
String str2 = dateTime.toString("MM/dd/yyyy hh:mm:ss.SSSa");
String str3 = dateTime.toString("dd-MM-yyyy HH:mm:ss");
String str4 = dateTime.toString("EEEE dd MMMM, yyyy HH:mm:ssa");
String str5 = dateTime.toString("MM/dd/yyyy HH:mm ZZZZ");
String str6 = dateTime.toString("MM/dd/yyyy HH:mm Z");
DateTimeFormatter format = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
// 时间解析
DateTime dateTime2 = DateTime.parse("2012-12-21 23:22:45", format);
// 时间格式化,输出==> 2012/12/21 23:22:45 Fri
String string_u = dateTime2.toString("yyyy/MM/dd HH:mm:ss EE");
System.out.println(string_u);
// 格式化带Locale,输出==> 2012年12月21日 23:22:45 星期五
String string_c = dateTime2.toString("yyyy年MM月dd日 HH:mm:ss EE",
Locale.CHINESE);
System.out.println(string_c);
DateTime dt1 = new DateTime();// 取得当前时间
// 根据指定格式,将时间字符串转换成DateTime对象,这里的格式和上面的输出格式是一样的
DateTime dt2 = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")
.parseDateTime("2012-12-26 03:27:39");
// 计算两个日期间隔的天数
LocalDate start = new LocalDate(2012, 12, 14);
LocalDate end = new LocalDate(2013, 01, 15);
int days = Days.daysBetween(start, end).getDays();
// 计算两个日期间隔的小时数,分钟数,秒数
// 增加日期
DateTime dateTime1 = DateTime.parse("2012-12-03");
dateTime1 = dateTime1.plusDays(30);
dateTime1 = dateTime1.plusHours(3);
dateTime1 = dateTime1.plusMinutes(3);
dateTime1 = dateTime1.plusMonths(2);
dateTime1 = dateTime1.plusSeconds(4);
dateTime1 = dateTime1.plusWeeks(5);
dateTime1 = dateTime1.plusYears(3);
// Joda-time 各种操作.....
dateTime = dateTime.plusDays(1) // 增加天
.plusYears(1)// 增加年
.plusMonths(1)// 增加月
.plusWeeks(1)// 增加星期
.minusMillis(1)// 减分钟
.minusHours(1)// 减小时
.minusSeconds(1);// 减秒数
// 判断是否闰月
DateTime dt4 = new DateTime();
org.joda.time.DateTime.Property month = dt4.monthOfYear();
System.out.println("是否闰月:" + month.isLeap());
// 取得 3秒前的时间
DateTime dt5 = dateTime1.secondOfMinute().addToCopy(-3);
dateTime1.getSecondOfMinute();// 得到整分钟后,过的秒钟数
dateTime1.getSecondOfDay();// 得到整天后,过的秒钟数
dateTime1.secondOfMinute();// 得到分钟对象,例如做闰年判断等使用
// DateTime与java.util.Date对象,当前系统TimeMillis转换
DateTime dt6 = new DateTime(new Date());
Date date = dateTime1.toDate();
DateTime dt7 = new DateTime(System.currentTimeMillis());
dateTime1.getMillis();
Calendar calendar = Calendar.getInstance();
dateTime = new DateTime(calendar);
}
}
运行结果:
2012/12/21 23:22:45 星期五
2012年12月21日 23:22:45 星期五
是否闰月:true
下面是一个小例子用来计算小宝宝从出生到现在总共的天数小时数等,首先用jdk的类去做,不用joda,然后再用joda去做,以做比较。
用jdk做的例子,这里算的从出生到现在的时间间隔是准确的,如果是输入的某天来算的话就不是很准确,多一秒就算一天。可以看到用jdk去做的话,要写的代码还是挺繁琐的。
package com.bijian.study;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Scanner;
public class CalBaby {
private final static String birthday = "2012-3-10 08:20:55";
/**
* @param args
*/
public static void main(String[] args) {
while(true){
String format1 = "yyyy-MM-dd";
String format2 = "yyyy-MM-dd HH:mm:ss";
Scanner s = new Scanner(System.in);
System.out.println("########################################");
cutTwoDateToDay(convertToDate1(birthday,format2),new Date(),false);
System.out.println("请选择操作");
System.out.println("请输入日期(格式例如:2012-11-08)");
System.out.println("########################################");
String endDateStr = s.nextLine();
Date endDate = convertToDate1(endDateStr,format1);
if(endDate == null){
System.out.println("输入格式错误!请重新输入.");
continue;
}
boolean inputFlag = true;
cutTwoDateToDay(convertToDate1(birthday,format2),endDate,inputFlag);
}
}
/**
* 计算两个日期之间的差距天数
*
* @param a
* @param b
* @return
*/
public static void cutTwoDateToDay(Date beginDate, Date endDate,boolean inputFlag) {
Calendar calendar = Calendar.getInstance();
long intervalDays = 0;
calendar.setTime(beginDate);
long begin = calendar.getTimeInMillis();
calendar.setTime(endDate);
long end = calendar.getTimeInMillis();
long totalM = end - begin;
System.out.println((end -begin));
System.out.println(24*60*60*1000);
intervalDays = totalM /(24*60*60*1000);
long intervalHours = (totalM - (intervalDays*24*60*60*1000))/(60*60*1000);
long intervalMin = (totalM - intervalDays * (24*60*60*1000) - intervalHours*60*60*1000)/(60*1000);
if(inputFlag){
if(totalM > 0L && totalM %(24*60*60*1000) > 0L){
intervalDays = intervalDays + 1;
}
System.out.println("宝宝从出生到"+formatDate(endDate,"yyyy-MM-dd")+"已经"+intervalDays+"天了");
}else{
System.out.println("宝宝来到这个世界已经"+intervalDays+"天"+intervalHours+"小时"+intervalMin+"分钟了");
}
}
/**
* 将字符串日期转换为Date yyyy-MM-dd HH:mm:ss yyyy-MM-dd
*
* @param s
* @return
*/
public static Date convertToDate1(String s,String format) {
if (s == null) {
return null;
}
try {
SimpleDateFormat df = new SimpleDateFormat(format);
return df.parse(s);
} catch (Exception e) {
return null;
}
}
public static String formatDate(Date date, String strType) {
if (date == null){
return null;
}
SimpleDateFormat sdf = new SimpleDateFormat(strType);
try {
return sdf.format(date);
}
catch (Exception e){
return null;
}
}
}
运行测试结果:
########################################
124288757170
86400000
宝宝来到这个世界已经1438天12小时39分钟了
请选择操作
请输入日期(格式例如:2012-11-08)
########################################
2012-11-08
20965145000
86400000
宝宝从出生到2012-11-08已经243天了
########################################
124288786437
86400000
宝宝来到这个世界已经1438天12小时39分钟了
请选择操作
请输入日期(格式例如:2012-11-08)
########################################
2014-02-18
61313945000
86400000
宝宝从出生到2014-02-18已经710天了
########################################
124288799223
86400000
宝宝来到这个世界已经1438天12小时39分钟了
请选择操作
请输入日期(格式例如:2012-11-08)
########################################
下面是用joda来做,用这个来做就简单的多了,而且也很准确。
package com.bijian.study;
import java.util.Scanner;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
public class CalBabyJoda {
private final static String birthday = "2012-3-10 08:20:55";
public static void main(String[] args) {
while(true){
Scanner s = new Scanner(System.in);
System.out.println("########################################");
DateTimeFormatter format1 = DateTimeFormat .forPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatter format2 = DateTimeFormat .forPattern("yyyy-MM-dd");
DateTime startDateTime = DateTime.parse(birthday, format1);
System.out.println("宝宝来到这个世界已经");
calDateToDay(startDateTime,new DateTime());
System.out.println("如选择其它日期请输入(格式例如:2012-11-08 14:24:54或着2012-11-08)");
System.out.println("########################################");
String endDate = s.nextLine();
DateTime endDateTime = null;
try{
endDateTime = DateTime.parse(endDate,format1);
}catch(Exception e){
try{
endDateTime = DateTime.parse(endDate,format2);
}catch(Exception e1){
System.out.println("输入格式错误!请重新输入.");
continue;
}
}
System.out.println("宝宝从出生到" + endDateTime.toString("yyyy-MM-dd HH:mm:ss") + "已经");
calDateToDay(startDateTime,endDateTime);
}
}
public static void calDateToDay(DateTime startDateTime,DateTime endDateTime){
LocalDate start=new LocalDate(startDateTime);
LocalDate end=new LocalDate(endDateTime);
Days days = Days.daysBetween(start, end);
int intervalDays = days.getDays();
int intervalHours = endDateTime.getHourOfDay() - startDateTime.getHourOfDay();
int intervalMinutes = endDateTime.getMinuteOfHour() - startDateTime.getMinuteOfHour();
int intervalSeconds = endDateTime.getSecondOfMinute() - startDateTime.getSecondOfMinute();
if(intervalSeconds < 0){
intervalMinutes = intervalMinutes -1;
intervalSeconds = 60 + intervalSeconds;
}
if(intervalMinutes < 0){
intervalHours = intervalHours -1;
intervalMinutes = 60 + intervalMinutes;
}
if(intervalHours < 0){
intervalDays = intervalDays -1;
intervalHours = 24 + intervalHours;
}
System.out.println(intervalDays + "天" + intervalHours +
"小时" + intervalMinutes + "分钟" + intervalSeconds + "秒");
System.out.println("############################");
}
}
运行测试结果:
########################################
宝宝来到这个世界已经
1438天12小时41分钟57秒
############################
如选择其它日期请输入(格式例如:2012-11-08 14:24:54或着2012-11-08)
########################################
2012-11-08
宝宝从出生到2012-11-08 00:00:00已经
242天15小时39分钟5秒
############################
########################################
宝宝来到这个世界已经
1438天12小时42分钟0秒
############################
如选择其它日期请输入(格式例如:2012-11-08 14:24:54或着2012-11-08)
########################################
2014-02-18
宝宝从出生到2014-02-18 00:00:00已经
709天15小时39分钟5秒
############################
########################################
宝宝来到这个世界已经
1438天12小时42分钟7秒
############################
如选择其它日期请输入(格式例如:2012-11-08 14:24:54或着2012-11-08)
########################################
介绍的比较详细:
http://www.ibm.com/developerworks/cn/java/j-jodatime.html
示例写得比较好:
http://bijian1013.iteye.com/blog/2276805
还可以简单看一下:
http://shanruifeng.cc/archives/31#14