Introduction to Joda-Time – Joda-Time简介

最后修改: 2018年 6月 23日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

Joda-Time is the most widely used date and time processing library, before the release of Java 8. Its purpose was to offer an intuitive API for processing date and time and also address the design issues that existed in the Java Date/Time API.

Joda-Time是最广泛使用的日期和时间处理库,在Java 8发布之前。它的目的是为处理日期和时间提供一个直观的API,同时解决Java日期/时间API中存在的设计问题。

The central concepts implemented in this library were introduced in the JDK core with the release of the Java 8 version. The new date and time API is found in the java.time package (JSR-310). An overview of these features can be found in this article.

该库中实现的核心概念是随着 Java 8 版本的发布而在 JDK 核心中引入的。新的日期和时间 API 可在 java.time 包(JSR-310)中找到。有关这些功能的概述可在本文章中找到。

After the release of Java 8, authors consider the project to be mostly finished and advise to use the Java 8 API if possible.

在Java 8发布后,作者认为该项目已基本完成,并建议尽可能地使用Java 8 API。

2. Why Use Joda-Time?

2.为什么使用Joda-Time?

The date/time API, before Java 8, presented multiple design problems.

在Java 8之前,日期/时间API呈现出多种设计问题。

Among the problems is the fact that the Date and SimpleDateFormatter classes aren’t thread-safe. To address this issue, Joda-Time uses immutable classes for handling date and time.

其中的问题是,DateSimpleDateFormatter类并不是线程安全的。为了解决这个问题,Joda-Time使用不可变的类来处理日期和时间。

The Date class doesn’t represent an actual date, but instead, it specifies an instant in time, with millisecond precision. The year in a Date starts from 1900, while most of the date operations usually use Epoch time which starts from January 1st, 1970.

Date类并不代表一个实际的日期,相反,它指定了一个时间上的瞬间,精度为毫秒。Date中的年份从1900年开始,而大多数的日期操作通常使用从1970年1月1日开始的Epoch时间。

Also, the day, month and year offset of a Date is counterintuitive. Days start at 0, while month begins from 1. To access any of them, we must use the Calendar class. Joda-Time offers a clean and fluent API for handling dates and time.

另外,Date的日、月、年的偏移量是反直觉的。日从0开始,而月从1开始。要访问其中任何一个,我们必须使用Calendar类。Joda-Time为处理日期和时间提供了一个干净而流畅的API。

Joda-Time also offers support for eight calendar systems, while Java offers just 2: Gregorian – java.util.GregorianCalendar and Japanese – java.util.JapaneseImperialCalendar.

Joda-Time还提供了对8种日历系统的支持,而Java只提供了2种:Gregorian – java.util.GregorianCalendar 和 Japanese – java.util.JapaneseImperialCalendar

 3. Setup

3.设置

To include the functionality of the Joda-Time library, we need to add the following dependency from Maven Central:

为了包含Joda-Time库的功能,我们需要从Maven中心添加以下依赖。

<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.10</version>
</dependency>

 4. Library Overview

4.图书馆概况

Joda-Time models the concept of date and time using the classes in the org.joda.time package.

Joda-Time使用org.joda.time包中的类来模拟日期和时间的概念

Among those classes the most commonly used are:

在这些类别中,最常使用的是。

  • LocalDate – represents a date without time
  • LocalTime – represents the time without the time zone
  • LocalDateTime – represents both the date and time without a time zone
  • Instant – represents an exact point in time in milliseconds from the Java epoch of 1970-01-01T00:00:00Z
  • Duration – represents the duration in milliseconds between 2 points in time
  • Period – similar to Duration, but allowing access to individual components of the date and time object, like years, month, days, etc.
  • Interval – represents the time interval between 2 instants

Other important features are the date parsers and formatters. These can be found in the org.joda.time.format package.

其他重要的功能是日期解析器和格式化器。这些可以在org.joda.time.format包中找到。

The calendar system and time zone specific classes can be found in the org.joda.time.chrono and org.joda.time.tz packages.

日历系统和时区特定的类可以在org.joda.time.chronoorg.joda.time.tz包中找到。

Let’s take a look at some examples in which we use the key features of Joda-Time to handle date and time.

让我们看看一些例子,其中我们使用Joda-Time的主要功能来处理日期和时间。

5. Representing Date and Time

5.表示日期和时间

5.1. Current Date and Time

5.1.当前日期和时间

The current date, without time information, can be obtained by using the now() method from the LocalDate class:

没有时间信息的当前日期,可以通过使用now()方法从LocalDate类获得。

LocalDate currentDate = LocalDate.now();

When we need just the current time, without date information, we can use the LocalTime class:

当我们只需要当前时间,而没有日期信息时,我们可以使用LocalTime类。

LocalTime currentTime = LocalTime.now();

To obtain a representation of the current date and time without considering the time zone, we can use LocalDateTime:

为了获得一个不考虑时区的当前日期和时间的代表,我们可以使用LocalDateTime

LocalDateTime currentDateAndTime = LocalDateTime.now();

Now, using currentDateAndTime, we can convert it to the other types of objects modeling date and time.

现在,使用currentDateAndTime,我们可以将其转换为其他类型的对象建模日期和时间。

We can obtain a DateTime object (which takes into account the time zone) by using the method toDateTime(). When time is not necessary we can convert it to a LocalDate with the method toLocalDate(), and when we only need the time we can use toLocalTime() to obtain a LocalTime object:

我们可以通过使用toDateTime()方法获得一个DateTime对象(它考虑了时区)。当不需要时间时,我们可以通过toLocalDate()方法将其转换为LocalDate,而当我们只需要时间时,我们可以使用toLocalTime()来获得LocalTime对象。

DateTime dateTime = currentDateAndTime.toDateTime();
LocalDate localDate = currentDateAndTime.toLocalDate();
LocalTime localTime = currentDateAndTime.toLocalTime();

All the above methods have an overloaded method which accepts a DateTimeZone object to help us represent the date or time in the specified time zone:

所有上述方法都有一个重载方法,它接受一个DateTimeZone对象来帮助我们表示指定时区的日期或时间。

LocalDate currentDate = LocalDate.now(DateTimeZone.forID("America/Chicago"));

Also, Joda-Time offers excellent integration with the Java Date and Time API. The constructors accept a java.util.Date object and also, we can use the toDate() method to return a java.util.Date object:

此外,Joda-Time还提供了与Java日期和时间API的出色集成。构造函数接受一个java.util.Date对象,同时,我们可以使用toDate()方法来返回一个java.util.Date对象。

LocalDateTime currentDateTimeFromJavaDate = new LocalDateTime(new Date());
Date currentJavaDate = currentDateTimeFromJavaDate.toDate();

5.2. Custom Date and Time

5.2.自定义日期和时间

To represent custom date and time, Joda-Time provides us with several constructors. We can specify the following objects:

为了表示自定义日期和时间,Joda-Time为我们提供了几个构造函数。我们可以指定以下对象。

  • an Instant
  • a Java Date object
  • a String representation of the date and time using the ISO format
  • parts of the date and time: year, month, day, hour, minute, second, millisecond
Date oneMinuteAgoDate = new Date(System.currentTimeMillis() - (60 * 1000));
Instant oneMinutesAgoInstant = new Instant(oneMinuteAgoDate);

DateTime customDateTimeFromInstant = new DateTime(oneMinutesAgoInstant);
DateTime customDateTimeFromJavaDate = new DateTime(oneMinuteAgoDate);
DateTime customDateTimeFromString = new DateTime("2018-05-05T10:11:12.123");
DateTime customDateTimeFromParts = new DateTime(2018, 5, 5, 10, 11, 12, 123);

Another way we can define a custom date and time is by parsing a given String representation of a date and time in the ISO format:

另一种我们可以定义自定义日期和时间的方法是通过解析ISO格式的日期和时间的给定String表示。

DateTime parsedDateTime = DateTime.parse("2018-05-05T10:11:12.123");

We can also parse custom representations of a date and time by defining a custom DateTimeFormatter:

我们还可以通过定义一个自定义的DateTimeFormatter来解析日期和时间的自定义表示。

DateTimeFormatter dateTimeFormatter
  = DateTimeFormat.forPattern("MM/dd/yyyy HH:mm:ss");
DateTime parsedDateTimeUsingFormatter
  = DateTime.parse("05/05/2018 10:11:12", dateTimeFormatter);

6. Working with Date and Time

6.使用日期和时间的工作

6.1. Using Instant

6.1.使用Instant

An Instant represents the number of milliseconds from 1970-01-01T00:00:00Z until a given moment in time. For example, the current moment in time can be obtained using the default constructor or the method now():

一个Instant表示从1970-01-01T00:00:00Z到某个时间点的毫秒数量。例如,可以使用默认的构造函数或方法now()获得当前时间点。

Instant instant = new Instant();
Instant.now();

To create an Instant for a custom moment in time we can use either one of the constructors or use the methods ofEpochMilli() and ofEpochSecond():

要为一个自定义的时间点创建一个Instant,我们可以使用其中一个构造函数,或者使用ofEpochMilli()ofEpochSecond()方法。

Instant instantFromEpochMilli
  = Instant.ofEpochMilli(milliesFromEpochTime);
Instant instantFromEpocSeconds
  = Instant.ofEpochSecond(secondsFromEpochTime);

The constructors accept a String representing a date and time in the ISO format, a Java Date or a long value representing the number of milliseconds from 1970-01-01T00:00:00Z:

构造函数接受一个String,代表ISO格式的日期和时间,一个Java Date或一个long值,代表从1970-01-01T00:00:00Z的毫秒数。

Instant instantFromString
  = new Instant("2018-05-05T10:11:12");
Instant instantFromDate
  = new Instant(oneMinuteAgoDate);
Instant instantFromTimestamp
  = new Instant(System.currentTimeMillis() - (60 * 1000));

When date and time are represented as a String we have the option to parse the String using our desired format:

当日期和时间被表示为String时,我们可以选择使用我们想要的格式来解析String

Instant parsedInstant
  = Instant.parse("05/05/2018 10:11:12", dateTimeFormatter);

Now that we know what Instant represents and how we can create one, let’s see how it can be used.

现在我们知道了Instant代表什么以及如何创建一个,让我们看看如何使用它。

To compare to Instant objects we can use compareTo() because it implements the Comparable interface, but also we can use the Joda-Time API methods provided in the ReadableInstant interface which Instant also implements:

为了与Instant对象进行比较,我们可以使用compareTo(),因为它实现了Comparable接口,但我们也可以使用ReadableInstant接口中提供的Joda-Time API方法,Instant也实现了该接口。

assertTrue(instantNow.compareTo(oneMinuteAgoInstant) > 0);
assertTrue(instantNow.isAfter(oneMinuteAgoInstant));
assertTrue(oneMinuteAgoInstant.isBefore(instantNow));
assertTrue(oneMinuteAgoInstant.isBeforeNow());
assertFalse(oneMinuteAgoInstant.isEqual(instantNow));

Another helpful feature is that Instant can be converted to a DateTime object or event a Java Date:

另一个有用的功能是,Instant可以转换为DateTime对象或事件为Java Date

DateTime dateTimeFromInstant = instant.toDateTime();
Date javaDateFromInstant = instant.toDate();

When we need to access parts of a date and time, like the year, the hour and so on, we can use the get() method and specify a DateTimeField:

当我们需要访问日期和时间的部分内容时,如年份、小时等,我们可以使用get()方法并指定一个DateTimeField

int year = instant.get(DateTimeFieldType.year());
int month = instant.get(DateTimeFieldType.monthOfYear());
int day = instant.get(DateTimeFieldType.dayOfMonth());
int hour = instant.get(DateTimeFieldType.hourOfDay());

Now that we covered the Instant class let’s see some examples of how we can use Duration, Period and Interval.

现在我们已经涵盖了Instant类,让我们看看一些如何使用DurationPeriodInterval的例子。

6.2. Using Duration, Period and Interval

6.2.使用DurationPeriodInterval

A Duration represents the time in milliseconds between two points in time or in this case it could be two Instants. We’ll use this when we need to add or subtract a specific amount of time to or from another Instant without considering chronology and time zones:

Duration表示两个时间点之间的时间(以毫秒为单位),在这里可以是两个Instant当我们需要对另一个Instant添加或减去一个特定的时间量时,我们就会使用它,而不考虑时序和时区

long currentTimestamp = System.currentTimeMillis();
long oneHourAgo = currentTimestamp - 24*60*1000;
Duration duration = new Duration(oneHourAgo, currentTimestamp);
Instant.now().plus(duration);

Also, we can determine how many days, hours, minutes, seconds or milliseconds the duration represents:

此外,我们还可以确定该期限代表多少天、多少小时、多少分钟、多少秒或多少毫秒。

long durationInDays = duration.getStandardDays();
long durationInHours = duration.getStandardHours();
long durationInMinutes = duration.getStandardMinutes();
long durationInSeconds = duration.getStandardSeconds();
long durationInMilli = duration.getMillis();

The main difference between Period and Duration is that Period is defined in terms of its date and time components (years, months, hours, etc.) and doesn’t represent an exact number of milliseconds. When using Period date and time calculations will consider the time zone and daylight saving.

PeriodDuration之间的主要区别是,Period是以其日期和时间成分(年、月、小时等)来定义的,并不代表精确的毫秒数。当使用Period日期和时间计算时,将考虑时区和夏令时

For example, adding a Period of 1 month to the February 1st will result in the date representation of March 1st. By using Period the library will take into account leap years.

例如,在2月1日添加1个月的Period,将导致日期表示为3月1日。通过使用Period,图书馆将考虑到闰年。

If we’re to use a Duration we the result wouldn’t be correct, because the Duration represents a fixed amount of time that doesn’t take into account chronology or time zones:

如果我们使用Duration,结果就不正确了,因为Duration代表一个固定的时间量,不考虑时间顺序或时区。

Period period = new Period().withMonths(1);
LocalDateTime datePlusPeriod = localDateTime.plus(period);

An Interval, as the name states, represents the date and time interval between two fixed points in time represented by two Instant objects:

顾名思义,Interval表示由两个Instant对象代表的两个固定时间点之间的日期和时间间隔。

Interval interval = new Interval(oneMinuteAgoInstant, instantNow);

The class is useful when we need to check whether two intervals overlap or calculate the gap between them. The overlap() method will return the overlapping Interval or null when they don’t overlap:

当我们需要检查两个区间是否重叠或计算它们之间的差距时,这个类很有用。overlap()方法将返回重叠的Intervalnull当它们不重叠时。

Instant startInterval1 = new Instant("2018-05-05T09:00:00.000");
Instant endInterval1 = new Instant("2018-05-05T11:00:00.000");
Interval interval1 = new Interval(startInterval1, endInterval1);
        
Instant startInterval2 = new Instant("2018-05-05T10:00:00.000");
Instant endInterval2 = new Instant("2018-05-05T11:00:00.000");
Interval interval2 = new Interval(startInterval2, endInterval2);

Interval overlappingInterval = interval1.overlap(interval2);

The difference between intervals can be calculated using the gap() method, and when we want to know whether the end of an interval is equal to the start of another interval we can use the abuts() method:

区间之差可以用gap()方法来计算,当我们想知道一个区间的终点是否等于另一个区间的起点时,可以使用abuts()方法。

assertTrue(interval1.abuts(new Interval(
  new Instant("2018-05-05T11:00:00.000"),
  new Instant("2018-05-05T13:00:00.000"))));

6.3. Date and Time Operations

6.3.日期和时间操作

Some of the most common operations are to add, subtract and convert date and time. The library provides specific methods for each of the classes LocalDate, LocalTime, LocalDateTime, and DateTime. It’s important to note that these classes are immutable so that every method invocation will create a new object of its type.

一些最常见的操作是加、减和转换日期和时间。该库为LocalDateLocalTimeLocalDateTimeDateTime中的每一个类提供具体的方法。值得注意的是,这些类是不可变的,所以每个方法的调用都会创建一个新的类型的对象。

Let’s take LocalDateTime for the current moment and try to change its value:

让我们以LocalDateTime为当前时刻,尝试改变它的值。

LocalDateTime currentLocalDateTime = LocalDateTime.now();

To add an extra day to currentLocalDateTime we use the plusDays() method:

为了给currentLocalDateTime添加一个额外的日子,我们使用plusDays()方法。

LocalDateTime nextDayDateTime = currentLocalDateTime.plusDays(1);

We can also use plus() method to add a Period or Duration to our currentLocalDateTime:

我们还可以使用plus()方法为我们的currentLocalDateTime添加一个PeriodDuration

Period oneMonth = new Period().withMonths(1);
LocalDateTime nextMonthDateTime = currentLocalDateTime.plus(oneMonth);

The methods are similar for the other date and time components, for example, plusYears() for adding extra years, plusSeconds() for adding more seconds and so on.

其他日期和时间组件的方法类似,例如,plusYears()用于添加额外的年份,plusSeconds()用于添加更多的秒,等等。

To subtract a day from our currentLocalDateTime we can use the minusDays() method:

要从我们的currentLocalDateTime中减去一天,我们可以使用minusDays()方法。

LocalDateTime previousDayLocalDateTime
  = currentLocalDateTime.minusDays(1);

Besides, doing calculations with date and time, we can also, set individual parts of the date or time. For example, setting the hour to 10 can be achieved using the withHourOfDay() method. Other methods that start with the prefix “with” can be used to set components of that date or time:

除了用日期和时间进行计算,我们还可以设置日期或时间的个别部分。例如,使用withHourOfDay()方法可以实现将小时设置为10。其他以“with”开头的方法也可以用来设置该日期或时间的组成部分。

LocalDateTime currentDateAtHour10 = currentLocalDateTime
  .withHourOfDay(0)
  .withMinuteOfHour(0)
  .withSecondOfMinute(0)
  .withMillisOfSecond(0);

Another important aspect is that we can convert from a date and time class type to another. To do this, we can use specific methods provided by the library:

另一个重要的方面是,我们可以从一个日期和时间类的类型转换到另一个。要做到这一点,我们可以使用库中提供的特定方法。

  • toDateTime() – converts LocalDateTime to a DateTime object
  • toLocalDate() – converts LocalDateTime to a LocalDate object
  • toLocalTime() – converts LocalDateTime to a LocalTime object
  • toDate() – converts LocalDateTime to a Java Date object

7. Working with Time Zones

7.与时区打交道

Joda-Time makes it easy for us to work with different time zones and changed between them. We have the DateTimeZone abstract class which is used to represent all aspects regarding a time zone.

Joda-Time使我们能够轻松地处理不同的时区,并在它们之间进行改变。我们有一个DateTimeZone抽象类,用来表示关于时区的所有方面。

The default time zone used by Joda-Time is selected from the user.timezone Java system property. The library API lets us specify, individually for each class or calculation what timezone should be used. For example, we can create a LocalDateTime object

Joda-Time使用的默认时区是从user.timezone Java系统属性中选择的。库的API让我们为每个类或计算单独指定应使用的时区。例如,我们可以创建一个LocalDateTime对象

When we know that we’ll use a specific time zone across the whole application, we can set the default time zone:

当我们知道我们将在整个应用程序中使用一个特定的时区时,我们可以设置默认时区。

DateTimeZone.setDefault(DateTimeZone.UTC);

From now one all the date and time operations, if not otherwise specified, will be represented in the UTC time zone.

从现在开始,所有的日期和时间操作,如果没有另外指定,将以UTC时区表示。

To see all the available time zones we can use the method getAvailableIDs():

要查看所有可用的时区,我们可以使用getAvailableIDs()方法:

DateTimeZone.getAvailableIDs()

When we need to represent the date or time in a specific time zone we can use any of the classes LocalTime, LocalDate, LocalDateTime, DateTime and specify in the constructor the DateTimeZone object:

当我们需要表示特定时区的日期或时间时,我们可以使用任何一个类LocalTime, LocalDate, LocalDateTime, DateTime并在构造函数中指定DateTimeZone对象。

DateTime dateTimeInChicago
  = new DateTime(DateTimeZone.forID("America/Chicago"));
DateTime dateTimeInBucharest
  = new DateTime(DateTimeZone.forID("Europe/Bucharest"));
LocalDateTime localDateTimeInChicago
  = new LocalDateTime(DateTimeZone.forID("America/Chicago"));

Also, when converting between those classes we can specify the desired time zone. The method toDateTime() accepts a DateTimeZone object and toDate() accepts java.util.TimeZone object:

另外,在这些类之间进行转换时,我们可以指定所需的时区。方法toDateTime()接受一个DateTimeZone对象,toDate()接受java.util.TimeZone对象。

DateTime convertedDateTime
  = localDateTimeInChicago.toDateTime(DateTimeZone.forID("Europe/Bucharest"));
Date convertedDate
  = localDateTimeInChicago.toDate(TimeZone.getTimeZone("Europe/Bucharest"));

8. Conclusion

8.结语

Joda-Time is a fantastic library that started with the main goal to fix the problems in the JDK regarding date and time operations. It soon became the de facto library for date and time handling and recently the main concepts from it were introduced in Java 8.

Joda-Time是一个神奇的库,其主要目标是解决JDK中关于日期和时间操作的问题。它很快成为日期和时间处理的de facto库,最近它的主要概念被引入了Java 8。

It’s important to note that the author considers it “to be a largely finished project” and recommends to migrate the existing code to use the Java 8 implementation.

需要注意的是,作者认为它“是一个基本完成的项目”,并建议迁移现有代码以使用Java 8实现。

The source code for the article is available over on GitHub.

文章的源代码可在GitHub上获得