系列中的上一篇
当前教程
Temporal 包
系列中的下一篇

系列中的上一篇: 解析和格式化

系列中的下一篇: Period 和 Duration

Temporal 包

The java.time.temporal 包提供了一组接口、类和枚举,用于支持日期和时间代码,特别是日期和时间计算。

这些接口旨在用于最低级别。典型的应用程序代码应以具体类型声明变量和参数,例如 LocalDateZonedDateTime,而不是以 Temporal 接口声明。这与声明类型为 String 而不是类型为 CharSequence 的变量完全相同。

 

Temporal 和 TemporalAccessor

The Temporal 接口提供了一个框架来访问基于时间的对象,并由基于时间的类实现,例如 InstantLocalDateTimeZonedDateTime。此接口提供了添加或减去时间单位的方法,使基于时间的算术在各种日期和时间类中变得容易且一致。The TemporalAccessor 接口提供了 Temporal 接口的只读版本。

Both TemporalTemporalAccessor 对象都是根据字段定义的,如 TemporalField 接口中指定的那样。The ChronoField 枚举是 TemporalField 接口的具体实现,并提供了一组丰富的定义常量,例如 DAY_OF_WEEKMINUTE_OF_HOURMONTH_OF_YEAR.

这些字段的单位由 TemporalUnit 接口指定。The ChronoUnit 枚举实现了 TemporalUnit 接口。字段 ChronoField.DAY_OF_WEEKChronoUnit.DAYSChronoUnit.WEEKS 的组合。The ChronoFieldChronoUnit 枚举将在以下部分中讨论。

The Temporal 接口中的基于算术的方法需要以 TemporalAmount 值定义的参数。The PeriodDuration 类(在 Period 和 Duration 中讨论)实现了 TemporalAmount 接口。

 

ChronoField 和 IsoFields

The ChronoField 枚举,它实现了 TemporalField 接口,提供了一组丰富的常量用于访问日期和时间值。一些示例是 CLOCK_HOUR_OF_DAYNANO_OF_DAYDAY_OF_YEAR。此枚举可用于表达时间的概念方面,例如一年中的第三周、一天中的第 11 个小时或一个月中的第一个星期一。当您遇到类型未知的 Temporal 时,可以使用 TemporalAccessor.isSupported(TemporalField) 方法来确定 Temporal 是否支持特定字段。以下代码行返回 false,表明 LocalDate 不支持 ChronoField.CLOCK_HOUR_OF_DAY

boolean isSupported = LocalDate.now().isSupported(ChronoField.CLOCK_HOUR_OF_DAY);

IsoFields 类中定义了特定于 ISO-8601 日历系统的其他字段。以下示例展示了如何使用 ChronoFieldIsoFields 获取字段的值

time.get(ChronoField.MILLI_OF_SECOND)
int qoy = date.get(IsoFields.QUARTER_OF_YEAR);

另外两个类定义了可能很有用的其他字段,WeekFieldsJulianFields.

 

ChronoUnit

ChronoUnit 枚举实现了 TemporalUnit 接口,并提供了一组基于日期和时间的标准单位,从毫秒到千年。请注意,并非所有 ChronoUnit 对象都受所有类的支持。例如,Instant 类不支持 ChronoUnit.MONTHSChronoUnit.YEARS。日期时间 API 中的类包含 isSupported(TemporalUnit) 方法,可用于验证类是否支持特定时间单位。以下对 isSupported() 的调用返回 false,确认 Instant 类不支持 ChronoUnit.DAYS

Instant instant = Instant.now();
boolean isSupported = instant.isSupported(ChronoUnit.DAYS);

 

时间调整器

java.time.temporal 包中的 TemporalAdjuster 接口提供了一些方法,这些方法接受一个 Temporal 值并返回一个调整后的值。调整器可与任何基于时间类型的对象一起使用。

如果调整器与 ZonedDateTime 一起使用,则会计算一个新的日期,该日期保留原始时间和时区值。

预定义调整器

TemporalAdjusters 类(注意复数)提供了一组预定义的调整器,用于查找月份的第一天或最后一天、年份的第一天或最后一天、月份的最后一个星期三或特定日期后的第一个星期二,仅举几例。预定义的调整器定义为静态方法,旨在与静态导入语句一起使用。

以下示例使用几个 TemporalAdjusters 方法,结合基于时间的类中定义的 with 方法,根据 2000 年 10 月 15 日的原始日期计算新的日期

LocalDate date = LocalDate.of(2000, Month.OCTOBER, 15);
DayOfWeek dotw = date.getDayOfWeek();
System.out.printf("%s is on a %s%n", date, dotw);

System.out.printf("first day of Month: %s%n",
                  date.with(TemporalAdjusters.firstDayOfMonth()));
System.out.printf("first Monday of Month: %s%n",
                  date.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)));
System.out.printf("last day of Month: %s%n",
                  date.with(TemporalAdjusters.lastDayOfMonth()));
System.out.printf("first day of next Month: %s%n",
                  date.with(TemporalAdjusters.firstDayOfNextMonth()));
System.out.printf("first day of next Year: %s%n",
                  date.with(TemporalAdjusters.firstDayOfNextYear()));
System.out.printf("first day of Year: %s%n",
                  date.with(TemporalAdjusters.firstDayOfYear()));

这将产生以下输出

2000-10-15 is on a SUNDAY
first day of Month: 2000-10-01
first Monday of Month: 2000-10-02
last day of Month: 2000-10-31
first day of next Month: 2000-11-01
first day of next Year: 2001-01-01
first day of Year: 2000-01-01

自定义调整器

您还可以创建自己的自定义调整器。为此,您创建一个实现 TemporalAdjuster 接口的类,该接口带有一个 adjustInto(Temporal) 方法。下一个示例是 PaydayAdjuster 自定义调整器。它评估传入的日期并返回下一个发薪日,假设发薪日每月两次:15 日和月末。如果计算出的日期是周末,则使用前一个星期五。假设当前日历年。

public class PaydayAdjuster implements TemporalAdjuster {
    /**
     * The adjustInto method accepts a Temporal instance 
     * and returns an adjusted LocalDate. If the passed in
     * parameter is not a LocalDate, then a DateTimeException is thrown.
     */
    public Temporal adjustInto(Temporal input) {
        LocalDate date = LocalDate.from(input);
        int day;
        if (date.getDayOfMonth() < 15) {
            day = 15;
        } else {
            day = date.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
        }
        date = date.withDayOfMonth(day);
        if (date.getDayOfWeek() == DayOfWeek.SATURDAY ||
                date.getDayOfWeek() == DayOfWeek.SUNDAY) {
            date = date.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
        }

        return input.with(date);
    }
}

调整器以与预定义调整器相同的方式调用,使用 with() 方法。让我们考虑以下示例

LocalDate nextPayday = date.with(new PaydayAdjuster());

在 2013 年,6 月 15 日和 6 月 30 日都在周末。使用 6 月 3 日和 6 月 18 日(2013 年)的相应日期运行前面的示例,得到以下结果

Given the date:  2013 Jun 3
the next payday: 2013 Jun 14

Given the date:  2013 Jun 18
the next payday: 2013 Jun 28

 

时间查询

TemporalQuery 可用于从基于时间的对象中检索信息。

预定义查询

TemporalQueries 类(注意复数)提供了几个预定义的查询,包括在应用程序无法识别基于时间的对象类型时有用的方法。与调整器一样,预定义的查询定义为静态方法,旨在与静态导入语句一起使用。

例如,精度查询返回特定基于时间的对象可以返回的最小 ChronoUnit。以下示例在几种类型的基于时间的对象上使用精度查询

TemporalQuery<TemporalUnit> query = TemporalQueries.precision();
System.out.printf("LocalDate precision is %s%n",
                  LocalDate.now().query(query));
System.out.printf("LocalDateTime precision is %s%n",
                  LocalDateTime.now().query(query));
System.out.printf("Year precision is %s%n",
                  Year.now().query(query));
System.out.printf("YearMonth precision is %s%n",
                  YearMonth.now().query(query));
System.out.printf("Instant precision is %s%n",
                  Instant.now().query(query));

输出如下所示

LocalDate precision is Days
LocalDateTime precision is Nanos
Year precision is Years
YearMonth precision is Months
Instant precision is Nanos

自定义查询

您还可以创建自己的自定义查询。一种方法是创建一个实现 TemporalQuery 接口的类,该接口带有一个 queryFrom(TemporalAccessor) 方法。这是一个在 FamilyVacations 类中实现的第一个自定义查询,该类实现了 TemporalQuery 接口。queryFrom() 方法将传入的日期与预定的假期日期进行比较,如果它落在这些日期范围内,则返回 true

public class FamilyVacations implements TemporalQuery<Boolean> {
    // Returns true if the passed-in date occurs during one of the
    // family vacations. Because the query compares the month and day only,
    // the check succeeds even if the Temporal types are not the same.
    public Boolean queryFrom(TemporalAccessor date) {
        int month = date.get(ChronoField.MONTH_OF_YEAR);
        int day = date.get(ChronoField.DAY_OF_MONTH);

        // Disneyland over Spring Break
        if ((month == Month.APRIL.getValue()) && ((day >= 3) && (day <= 8)))
            return Boolean.TRUE;

        // Smith family reunion on Lake Saugatuck
        if ((month == Month.AUGUST.getValue()) && ((day >= 8) && (day <= 14)))
            return Boolean.TRUE;

        return Boolean.FALSE;
    }
}

您可以使用以下模式使用此 TemporalQuery

// define a year, a month and a day
LocalDate = date = LocalDate.of(year, month, day);
boolean isFamilyVacation = date.query(new FamilyVacations());

第二个自定义查询在 FamilyBirthdays 类中实现。此类提供了一个 isFamilyBirthday() 方法,该方法将传入的日期与几个生日进行比较,如果匹配则返回 TRUE。

public class FamilyBirthdays {
    // Returns true if the passed-in date is the same as one of the
    // family birthdays. Because the query compares the month and day only,
    // the check succeeds even if the Temporal types are not the same.
    public static Boolean isFamilyBirthday(TemporalAccessor date) {
        int month = date.get(ChronoField.MONTH_OF_YEAR);
        int day = date.get(ChronoField.DAY_OF_MONTH);

        // Angie's birthday is on April 3.
        if ((month == Month.APRIL.getValue()) && (day == 3))
            return Boolean.TRUE;

        // Sue's birthday is on June 18.
        if ((month == Month.JUNE.getValue()) && (day == 18))
            return Boolean.TRUE;

        // Joe's birthday is on May 29.
        if ((month == Month.MAY.getValue()) && (day == 29))
            return Boolean.TRUE;

        return Boolean.FALSE;
    }
}

FamilyBirthday 类没有实现 TemporalQuery 接口,可以作为 lambda 表达式的一部分使用。以下代码展示了如何调用这两个自定义查询。

// Invoking the query without using a lambda expression.
Boolean isFamilyVacation = date.query(new FamilyVacations());

// Invoking the query using a lambda expression.
Boolean isFamilyBirthday = date.query(FamilyBirthdays::isFamilyBirthday);

if (isFamilyVacation.booleanValue() || isFamilyBirthday.booleanValue())
    System.out.printf("%s is an important date!%n", date);
else
    System.out.printf("%s is not an important date.%n", date);

上次更新: 2022 年 1 月 27 日


系列中的上一篇
当前教程
Temporal 包
系列中的下一篇

系列中的上一篇: 解析和格式化

系列中的下一篇: Period 和 Duration