Joda-Time简介
一、简介
Joda-Time是在Java 8发布之前使用最广泛的日期和时间处理库。它的目的是提供一个直观的API来处理日期和时间,并解决Java Date/Time API中存在的设计问题。这个库中实现的核心概念在Java 8版本发布时引入到了JDK中。
-
Joda-Time的由来
Java 8之前的日期/时间API存在多个设计问题:
-
Date和SimpleDateFormatter类不是线程安全的;为了解决这个问题,Joda-Time使用不可变类来处理日期和时间。
-
Date类并不表示实际的日期,而是指定一个瞬间;日期中的年份是从1900年开始的,而大多数日期操作通常使用从1970年1月1日开始的时间。
-
日期的日、月和年偏移量是违反直觉的:天从0开始,月从1开始,而且要访问它们中的任何一个,必须使用Calendar类。
-
为此,Joda-Time提供了一个干净流畅的API来处理日期和时间;Joda-Time还支持八种日历系统,而Java只支持两种:GregorianCalendar和JapaneseImperialCalendar。
- 安装
添加依赖:
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.12.5</version>
</dependency>
- 结构
Joda-Time使用org.joda.time
包中的类对日期和时间的概念进行建模:用org.joda.time.format
包中的类解析和格式化日期;用org.joda.time.chrono
和org.joda.time.tz
包中的类表示日历系统和特定于时区。
二、API
1、日期和时间
- LocalDate:日期(不带时间)
import org.joda.time.LocalDate;
LocalDate currentDate = LocalDate.now();
System.out.println(currentDate);
输出:
2023-09-10
- LocalTime:时间(不带时区)
import org.joda.time.LocalTime;
LocalTime currentTime = LocalTime.now();
System.out.println(currentTime);
输出:
14:49:02.141
- LocalDateTime:日期和时间(不带时区)
import org.joda.time.LocalDateTime;
LocalDateTime currentDateAndTime = LocalDateTime.now();
System.out.println(currentDateAndTime);
输出:
2023-09-10T14:49:42.957
LocalDateTime对象可以通过toDateTime()
方法获取DateTime对象(带时区):
DateTime dateTime = currentDateAndTime.toDateTime();
LocalDateTime对象可以通过toLocalDate()
方法获取LocalDate对象:
LocalDate localDate = currentDateAndTime.toLocalDate();
LocalDateTime对象可以通过toLocalTime()
方法获取LocalTime对象:
LocalTime localTime = currentDateAndTime.toLocalTime();
LocalDateTime对象可以通过toDate()
方法获取Java Date对象:
Date date = currentDateAndTime.toDate();
LocalDate、LocalTime、LocalDateTime可以接受一个DateTimeZone对象来表示指定时区的日期或时间:
DateTimeZone timeZone = DateTimeZone.forID("UTC");
LocalDate date = LocalDate.now(timeZone);
System.out.println(date);
LocalTime time = LocalTime.now(timeZone);
System.out.println(time);
LocalDateTime dateTime = LocalDateTime.now(timeZone);
System.out.println(dateTime);
输出:
2023-09-10
06:50:58.905
2023-09-10T06:50:58.907
Joda-Time还提供了与Java日期和时间对象的转换:
import java.util.Date;
import org.joda.time.LocalDateTime;
LocalDateTime dateTime = new LocalDateTime(new Date());
System.out.println(dateTime);
Date date = dateTime.toDate();
System.out.println(date);
输出:
2023-09-10T14:51:33.939
Sun Sep 10 14:51:33 CST 2023
2、自定义日期时间
Joda-Time提供了自定义日期和时间的方式:
- 构造函数
import java.util.Date;
import org.joda.time.DateTime;
import org.joda.time.Instant;
Date oneMinuteAgoDate = new Date(System.currentTimeMillis() - (60 * 1000));
Instant oneMinuteAgoInstant = new Instant(oneMinuteAgoDate);
DateTime dateTimeFromInstant = new DateTime(oneMinuteAgoInstant);
DateTime dateTimeFromJavaDate = new DateTime(oneMinuteAgoDate);
DateTime dateTimeFromString = new DateTime("2022-10-01T08:00:00.100");
DateTime dateTimeFromParts = new DateTime(2022, 10, 1, 8, 0, 0, 100);
System.out.println(oneMinuteAgoDate);
System.out.println(oneMinuteAgoInstant);
System.out.println(dateTimeFromInstant);
System.out.println(dateTimeFromJavaDate);
System.out.println(dateTimeFromString);
System.out.println(dateTimeFromParts);
输出:
Sun Sep 10 17:16:15 CST 2023
2023-09-10T09:16:15.340Z
2023-09-10T09:16:15.340Z
2023-09-10T17:16:15.340+08:00
2022-10-01T08:00:00.100+08:00
2022-10-01T08:00:00.100+08:00
- 解析ISO格式的日期和时间字符串
DateTime parsedDateTime = DateTime.parse("2022-10-01T08:00:00.100");
System.out.println(parsedDateTime);
输出:
2022-10-01T08:00:00.100+08:00
- 使用自定义DateTimeFormatter
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
DateTime parsedDateTime = DateTime.parse("2023-05-01 06:08:00", dateTimeFormatter);
System.out.println(parsedDateTime);
输出:
2023-05-01T06:08:00.000+08:00
3、时刻
Instant:表示从Java纪元(epoch)1970-01-01T00:00:00Z
到给定时刻的毫秒数
- 当前时刻
可以使用默认构造函数或now()
方法来获取当前时刻
import org.joda.time.Instant;
Instant instant = new Instant();
System.out.println(instant);
Instant now = Instant.now();
System.out.println(now);
输出:
2023-09-10T07:12:19.184Z
2023-09-10T07:12:19.244Z
- 自定义时刻
如果要创建自定义时刻,可以使用构造函数传入ISO格式日期和时间的字符串、Java Date对象或从1970-01-01T00:00:00Z
开始的毫秒数:
Instant instantFromString = new Instant("2022-05-05T22:10:01");
System.out.println(instantFromString);
Long milliSeconds = System.currentTimeMillis() - 60 * 1000;
Instant instantFromDate = new Instant(new Date(milliSeconds));
System.out.println(instantFromDate);
Instant instantFromTimestamp = new Instant(milliSeconds);
System.out.println(instantFromTimestamp);
输出:
2022-05-05T22:10:01.000Z
2023-09-10T07:12:14.291Z
2023-09-10T07:12:14.291Z
也可以使用ofEpochMilli()
指定毫秒数或使用ofEpochSecond()
指定秒:
Long oneMinuteAgoMilliSeconds = System.currentTimeMillis() - 60 * 1000;
Instant instantOfEpochMilli = Instant.ofEpochMilli(oneMinuteAgoMilliSeconds);
System.out.println(instantOfEpochMilli);
Instant instantOfEpochSecond = Instant.ofEpochSecond(oneMinuteAgoMilliSeconds / 1000);
System.out.println(instantOfEpochSecond);
输出:
2023-09-10T07:15:29.591Z
2023-09-10T07:15:29.000Z
- 转换
Instant可以转换为DateTime对象或Java Date对象:
Instant instant = Instant.now();
DateTime dateTimeFromInstant = instant.toDateTime();
Date dateFromInstant = instant.toDate();
System.out.println(dateTimeFromInstant);
System.out.println(dateFromInstant);
输出:
2023-09-10T15:23:17.997+08:00
Sun Sep 10 15:23:17 CST 2023
- 比较大小
Instant实现了Comparable接口,可以使用compareTo()
方法比较两个对象的大小;Instant也实现了Joda-Time的ReadableInstant接口,也可以用isAfter()
、isBefore()
等方法比较:
Instant instantNow = Instant.now();
Instant oneMinuteAgoInstant = Instant.ofEpochMilli(System.currentTimeMillis() - 60 * 1000);
System.out.println(instantNow.toDate());
System.out.println(oneMinuteAgoInstant.toDate());
System.out.println(instantNow.compareTo(oneMinuteAgoInstant));
System.out.println(instantNow.isAfter(oneMinuteAgoInstant));
System.out.println(oneMinuteAgoInstant.isBefore(instantNow));
System.out.println(oneMinuteAgoInstant.isBeforeNow());
System.out.println(instantNow.isEqual(oneMinuteAgoInstant));
输出:
Sun Sep 10 16:07:19 CST 2023
Sun Sep 10 16:06:19 CST 2023
1
true
true
true
false
- 获取日期时间的一部分
可以通过Instant的get(DateTimeFieldType type)
方法获取日期和时间的一部分,例如:年份、小时等:
Instant instant = Instant.now();
int year = instant.get(DateTimeFieldType.year());
int month = instant.get(DateTimeFieldType.monthOfYear());
int day = instant.get(DateTimeFieldType.dayOfMonth());
int hour = instant.get(DateTimeFieldType.hourOfDay());
int minute = instant.get(DateTimeFieldType.minuteOfHour());
int second = instant.get(DateTimeFieldType.secondOfMinute());
System.out.println(String.format("%s-%s-%s %s:%s:%s", year, month, day, hour, minute, second));
输出:
2023-9-10 7:28:18
4、持续时间、期间和间隔
- Duration:表示两个时间点之间的持续时间,以毫秒为单位
关于持续时间格式的介绍可以参考:ISO 8601日期格式与持续时间格式
import org.joda.time.Duration;
long currentTimestamp = System.currentTimeMillis();
long oneHourAgo = currentTimestamp - 60 * 60 * 1000;
Duration duration = new Duration(oneHourAgo, currentTimestamp);
System.out.println(duration);
Instant afterAnHour = Instant.now().plus(duration);
System.out.println(afterAnHour);
输出:
PT3600S
2023-09-10T08:34:40.057Z
通过Duration对象,可以获取持续时间对应的天、小时、分钟、秒和毫秒:
long currentTimestamp = System.currentTimeMillis();
long oneDayAgo = currentTimestamp - 24 * 60 * 60 * 1000;
Duration duration = new Duration(oneDayAgo, currentTimestamp);
System.out.println(duration.getStandardDays());
System.out.println(duration.getStandardHours());
System.out.println(duration.getStandardMinutes());
System.out.println(duration.getStandardSeconds());
System.out.println(duration.getMillis());
输出:
1
24
1440
86400
86400000
- Period:与Duration类似,但允许访问日期和时间对象的一部分,如年、月、日等。
Period和Duration之间的主要区别在于Period是根据其日期和时间(年、月、小时等)定义的,并不表示精确的毫秒数。当使用Period日期和时间作计算时,会考虑时区和夏令时(Daylight Saving Time)。
例如:当前是2月1日,计算1个月之后的时间,使用Period可以正确计算,而使用Duration只能指定一个固定的时间量:
import org.joda.time.Period;
//1个月
Period period = new Period().withMonths(1);
LocalDateTime datePlusPeriod = new LocalDateTime(2023, 2, 1, 8, 0).plus(period);
System.out.println(datePlusPeriod);
//30天
Duration duration = new Duration(30 * 24 * 60 * 60 * 1000);
LocalDateTime datePlusDuration = new LocalDateTime(2023, 2, 1, 8, 0).plus(duration);
System.out.println(datePlusDuration);
输出:
2023-03-01T08:00:00.000
2023-01-12T14:57:12.704
- Interval:表示两个固定时间点之间的日期和时间间隔
一分钟的间隔:
import org.joda.time.Interval;
Instant instantNow = Instant.now();
Instant oneMinuteAgoInstant = Instant.ofEpochMilli(System.currentTimeMillis() - 60 * 1000);
Interval interval = new Interval(oneMinuteAgoInstant, instantNow);
System.out.println(interval);
输出:
2023-09-10T08:07:23.652Z/2023-09-10T08:08:23.652Z
可以用overlap()
方法来检查两个间隔是否重叠或者计算它们之间的差值,此方法返回重叠的Interval,如果不重叠则返回null:
Instant startInterval1 = new Instant("2022-10-01T08:00:00");
Instant endInterval1 = new Instant("2022-10-01T10:00:00");
Interval interval1 = new Interval(startInterval1, endInterval1);
Instant startInterval2 = new Instant("2022-10-01T09:00:00");
Instant endInterval2 = new Instant("2022-10-01T11:00:00.000");
Interval interval2 = new Interval(startInterval2, endInterval2);
Interval overlappingInterval = interval1.overlap(interval2);
System.out.println(overlappingInterval);
输出:
2022-10-01T09:00:00.000Z/2022-10-01T10:00:00.000Z
两个间隔之前的差异可以使用gap()
方法计算:
Instant startInterval1 = new Instant("2022-10-01T08:00:00");
Instant endInterval1 = new Instant("2022-10-01T09:00:00");
Interval interval1 = new Interval(startInterval1, endInterval1);
Instant startInterval2 = new Instant("2022-10-01T11:00:00");
Instant endInterval2 = new Instant("2022-10-01T12:00:00.000");
Interval interval2 = new Interval(startInterval2, endInterval2);
Interval gap = interval1.gap(interval2);
System.out.println(gap);
输出:
2022-10-01T09:00:00.000Z/2022-10-01T11:00:00.000Z
如果想知道一个间隔的结束是否等于另一个间隔的开始时,可以使用abuts()
方法:
Instant startInterval1 = new Instant("2022-10-01T08:00:00");
Instant endInterval1 = new Instant("2022-10-01T10:00:00");
Interval interval1 = new Interval(startInterval1, endInterval1);
Instant startInterval2 = new Instant("2022-10-01T10:00:00");
Instant endInterval2 = new Instant("2022-10-01T12:00:00.000");
Interval interval2 = new Interval(startInterval2, endInterval2);
System.out.println(interval1.abuts(interval2));//true
5、日期和时间操作
日期和时间常见操作是加、减和转换,Joda-time为LocalDate、LocalTime、LocalDateTime和DateTime的每个类提供特定的方法;这些类是不可变的,每次方法调用都会创建一个其类型的新对象。
LocalDateTime currentDate = LocalDateTime.now();
System.out.println(currentDate);
//添加一天
LocalDateTime nextDay = currentDate.plusDays(1);
System.out.println(nextDay);
//添加一个周期
Period oneMonthPeriod = new Period().withMonths(1);
LocalDateTime nextMonth = currentDate.plus(oneMonthPeriod);
System.out.println(nextMonth);
//减去一年
LocalDateTime lastYear = currentDate.minusYears(1);
System.out.println(lastYear);
//设置时、分、秒
LocalDateTime currentDateAtHour6 = currentDate.withHourOfDay(6).withMinuteOfHour(0).withSecondOfMinute(0);
System.out.println(currentDateAtHour6);
输出:
2023-09-10T16:40:58.707
2023-09-11T16:40:58.707
2023-10-10T16:40:58.707
2022-09-10T16:40:58.707
2023-09-10T06:00:00.707
6、时区
Joda-Time中的抽象类DateTimeZone用来表示关于时区的所有方面,Joda-Time使用的默认时区是从Java系统属性user.timezone
中选择的;Joda-Time允许对每个类或计算单独指定应该使用的时区。
设置默认时区:
DateTimeZone.setDefault(DateTimeZone.UTC);
上面设置完默认时区后,所有的日期和时间的操作,如果没有另外指定将在UTC时区表示。
可以使用getAvailableIDs()
方法查看所有可用的时区:
Set<String> availableIDs = DateTimeZone.getAvailableIDs();
如果需要表示特定时区的日期或时间,需要在LocalTime、LocalDate、LocalDateTime、DateTime的构造函数中指定DateTimeZone;另外,
import java.util.Date;
import java.util.TimeZone;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;
DateTime dateTimeInShanghai = new DateTime(DateTimeZone.forID("Asia/Shanghai"));
LocalDateTime localDateTimeInShanghai = new LocalDateTime(DateTimeZone.forID("Asia/Shanghai"));
System.out.println(dateTimeInShanghai);
System.out.println(localDateTimeInShanghai);
DateTime dateTimeInNewYork = localDateTimeInShanghai.toDateTime(DateTimeZone.forID("America/New_York"));
Date dateInNewYork = localDateTimeInShanghai.toDate(TimeZone.getTimeZone("America/New_York"));
System.out.println(dateTimeInNewYork);
System.out.println(dateInNewYork);
输出:
2023-09-10T17:03:00.549+08:00
2023-09-10T17:03:00.578
2023-09-10T17:03:00.578-04:00
Mon Sep 11 05:03:00 CST 2023