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.CalendarDate 中不赞成使用的构造函数的数量严重限制了您创建此类对象的途径。

然而,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

Joda-Time 入门相关推荐

  1. Spring Boot入门到牛X

    Spring Boot入门到牛X 1.Spring Boot 项目下载地址:https://download.csdn.net/download/weixin_39549656/10287664 1. ...

  2. 探花交友_第2章-完善个人信息与MongoDB入门

    探花交友_第2章-完善个人信息与MongoDB入门 文章目录 探花交友_第2章-完善个人信息与MongoDB入门 1.完善个人信息 1.1.图片上传 1.1.1.图片存储解决方案 1.1.2.阿里云O ...

  3. validated 验证数组_Spring Boot 快速入门系列(VIII)—— 数据校验篇之 @Valid @Validated...

    点击上方蓝色字体关注我吧 一起学习,一起进步,做积极的人! 场景描述: 你还在业务代码或是控制层中校验接口传入参数的有效性和合法性吗?还是只做了前端参数校验,后端接口直接放行?这样的话,程序运行的正确 ...

  4. Dropwizard官方教程(一) 入门

    转载:https://www.jianshu.com/p/3bb308c9bbcb 入门 本文将指导您完成一个简单的Dropwizard的Hello World项目.在此过程中,我们将解释各种底层库及 ...

  5. 用Construct 2制作入门小游戏~

    今天在软导课上了解到了Construct 2这个神器,本零基础菜鸟决定尝试做一个简单的小游戏(实际上是入门的教程啊= = 首先呢,肯定是到官网下载软件啊,点击我下载~ 等安装完毕后我便按照新手教程开始 ...

  6. Docker入门六部曲——Swarm

    原文链接:http://www.dubby.cn/detail.html?id=8738 准备工作 安装Docker(版本最低1.13). 安装好Docker Compose,上一篇文章介绍过的. 安 ...

  7. Docker入门六部曲——Stack

    原文链接:http://www.dubby.cn/detail.html?id=8739 准备知识 安装Docker(版本最低1.13). 阅读完Docker入门六部曲--Swarm,并且完成其中介绍 ...

  8. Docker入门六部曲——服务

    原文链接:http://www.dubby.cn/detail.html?id=8735 准备 已经安装好Docker 1.13或者以上的版本. 安装好Docker Compose.如果你是用的是Do ...

  9. 【springboot】入门

    简介: springBoot是spring团队为了整合spring全家桶中的系列框架做研究出来的一个轻量级框架.随着spring4.0推出而推出,springBoot可以説是J2SEE的一站式解决方案 ...

  10. SpringBoot (一) :入门篇 Hello World

    什么是SpringBoot Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不 ...

最新文章

  1. DatagridView内容自动换行和换行符换行
  2. 安装win10和Linux双系统的个人经验
  3. leetcode题解46-全排列
  4. 创建第一个Scrapy项目
  5. rstudio 修改代码间距_如何在RStudio里修改R脚本的编码方式
  6. 三津谈保险系统建设(一): 现状分析和建设目标规划
  7. 视频教程-8086汇编语言讲座-其他
  8. 物联网服务器搭建记录,心得
  9. 关于安卓图片剪切造成软件崩溃问题的解决方法
  10. Win10 Edge浏览器设置默认bing/google为搜索引擎教程
  11. SpringBoot集成MyBatis操作Mysql(极速体验版)
  12. 商业智能BI能做什么
  13. 如何画出美丽漂亮的三维立体图——Mathematica的快速上手
  14. Camera测试-- ITS测试
  15. 模拟机械键盘音效的软件
  16. MYSQL配置初始化
  17. 【Ubuntu】服务器使用
  18. NEO从源码分析看数字资产
  19. 一下搞懂HTTP协议
  20. AI独立开发者:一周涨粉8万赚2W美元;推特#HustleGPT GPT-4创业挑战;即刻#AIHackathon创业者在行动 | ShowMeAI周刊

热门文章

  1. 阿里P8大神给予迷茫的程序员一些中肯建议,不要再虚度光阴了
  2. 自学web前端真的很难找到工作
  3. vertical-align的用法
  4. 平时用来调试的日记打印源码
  5. 计算机毕业设计ssm+vue基本微信小程序的南通农商银行微银行系统
  6. java事务类型_Spring事务类型祥解
  7. 零基础Python完全自学教程15:Python中的列表
  8. 手机QQ,登陆不上去
  9. 【软件工具类】常用科研办公软件工具汇总
  10. 一位女生程序员微自传