Java 编程问题:三、使用日期和时间2

简介: Java 编程问题:三、使用日期和时间

61 LocalDateLocalTime中的LocalDateTime


LocalDateTime类公开了一系列of()方法,这些方法可用于获取LocalDateTime的不同类型的实例。例如,从年、月、日、时、分、秒或纳秒获得的LocalDateTime类如下所示:

LocalDateTime ldt = LocalDateTime.of​(2020, 4, 1, 12, 33, 21, 675);


因此,前面的代码将日期和时间组合为of()方法的参数。为了将日期和时间组合为对象,解决方案可以利用以下of()方法:

public static LocalDateTime of(LocalDate date, LocalTime time)


这导致LocalDateLocalTime,如下所示:

LocalDate localDate = LocalDate.now(); // 2019-Feb-24
LocalTime localTime = LocalTime.now(); // 02:08:10 PM


它们可以组合在一个对象LocalDateTime中,如下所示:

LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);


格式化LocalDateTime显示日期和时间如下:

// 2019-Feb-24 02:08:10 PM
String localDateTimeAsString = localDateTime
  .format(DateTimeFormatter.ofPattern("yyyy-MMM-dd hh:mm:ss a"));




62 通过Instant类的机器时间


JDK8 附带了一个新类,名为java.time.Instant。主要地,Instant类表示时间线上的一个瞬时点,从 1970 年 1 月 1 日(纪元)的第一秒开始,在 UTC 时区,分辨率为纳秒。


Java8Instant类在概念上类似于java.util.Date。两者都代表 UTC 时间线上的一个时刻。当Instant的分辨率高达纳秒时,java.util.Date的分辨率为毫秒。


这个类对于生成机器时间的时间戳非常方便。为了获得这样的时间戳,只需调用如下的now()方法:

// 2019-02-24T15:05:21.781049600Z
Instant timestamp = Instant.now();


使用以下代码段可以获得类似的输出:

OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);


或者,使用以下代码段:

Clock clock = Clock.systemUTC();

调用Instant.toString()产生一个输出,该输出遵循 ISO-8601 标准来表示日期和时间。



将字符串转换为Instant


遵循 ISO-8601 标准表示日期和时间的字符串可以通过Instant.parse()方法轻松转换为Instant,如下例所示:

// 2019-02-24T14:31:33.197021300Z
Instant timestampFromString =
  Instant.parse("2019-02-24T14:31:33.197021300Z");



Instant添加/减去时间


对于添加时间,Instant有一套方法。例如,向当前时间戳添加 2 小时可以如下完成:

Instant twoHourLater = Instant.now().plus(2, ChronoUnit.HOURS);


在减去时间方面,例如 10 分钟,请使用以下代码段:

Instant tenMinutesEarlier = Instant.now()
  .minus(10, ChronoUnit.MINUTES);

除plus()方法外,Instant还包含plusNanos()、plusMillis()、plusSeconds()。此外,除了minus()方法外,Instant还包含minusNanos()、minusMillis()、minusSeconds()。



比较Instant对象


比较两个Instant对象可以通过Instant.isAfter()和Instant.isBefore()方法来完成。例如,让我们看看以下两个Instant对象:

Instant timestamp1 = Instant.now();
Instant timestamp2 = timestamp1.plusSeconds(10);


检查timestamp1是否在timestamp2之后:

boolean isAfter = timestamp1.isAfter(timestamp2); // false


检查timestamp1是否在timestamp2之前:

boolean isBefore = timestamp1.isBefore(timestamp2); // true


两个Instant对象之间的时差可以通过Instant.until()方法计算:

// 10 seconds
long difference = timestamp1.until(timestamp2, ChronoUnit.SECONDS);




InstantLocalDateTimeZonedDateTimeOffsetDateTime之间转换


这些常见的转换可以在以下示例中完成:


InstantLocalDateTime之间转换-因为LocalDateTime不知道时区,所以使用零偏移 UTC+0:

// 2019-02-24T15:27:13.990103700
LocalDateTime ldt = LocalDateTime.ofInstant(
  Instant.now(), ZoneOffset.UTC);
// 2019-02-24T17:27:14.013105Z
Instant instantLDT = LocalDateTime.now().toInstant(ZoneOffset.UTC);


InstantZonedDateTime之间转换—将InstantUTC+0 转换为巴黎ZonedDateTimeUTC+1:

// 2019-02-24T16:34:36.138393100+01:00[Europe/Paris]
ZonedDateTime zdt = Instant.now().atZone(ZoneId.of("Europe/Paris"));
// 2019-02-24T16:34:36.150393800Z
Instant instantZDT = LocalDateTime.now()
  .atZone(ZoneId.of("Europe/Paris")).toInstant();


InstantOffsetDateTime之间转换-指定 2 小时的偏移量:

// 2019-02-24T17:34:36.151393900+02:00
OffsetDateTime odt = Instant.now().atOffset(ZoneOffset.of("+02:00"));
// 2019-02-24T15:34:36.153394Z
Instant instantODT = LocalDateTime.now()
  .atOffset(ZoneOffset.of("+02:00")).toInstant();



63 使用基于日期的值定义时段,使用基于时间的值定义持续时间


JDK8 附带了两个新类,分别命名为java.time.Periodjava.time.Duration。让我们在下一节中详细了解它们。



使用基于日期的值的时间段


Period类意味着使用基于日期的值(年、月、周和天)来表示时间量。这段时间可以用不同的方法获得。例如,120 天的周期可以如下获得:


Period fromDays = Period.ofDays(120); // P120D



在ofDays()方法旁边,Period类还有ofMonths()、ofWeeks()和ofYears()。

或者,通过of()方法可以得到 2000 年 11 个月 24 天的期限,如下所示:


Period periodFromUnits = Period.of(2000, 11, 24); // P2000Y11M24D



Period也可以从LocalDate中得到:

LocalDate localDate = LocalDate.now();
Period periodFromLocalDate = Period.of(localDate.getYear(),
  localDate.getMonthValue(), localDate.getDayOfMonth());


最后,可以从遵循 ISO-8601 周期格式PnYnMnD和PnW的String对象获得Period。例如,P2019Y2M25D字符串表示 2019 年、2 个月和 25 天:


Period periodFromString = Period.parse("P2019Y2M25D");



调用Period.toString()将返回时间段,同时也遵循 ISO-8601 时间段格式,PnYnMnD和PnW(例如P120D、P2000Y11M24D)。

但是,当Period被用来表示两个日期之间的一段时间(例如LocalDate时,Period的真实力量就显现出来了。2018 年 3 月 12 日至 2019 年 7 月 20 日期间可表示为:


LocalDate startLocalDate = LocalDate.of(2018, 3, 12);
LocalDate endLocalDate = LocalDate.of(2019, 7, 20);
Period periodBetween = Period.between(startLocalDate, endLocalDate);


年、月、日的时间量可以通过Period.getYears()Period.getMonths()Period.getDays()获得。例如,以下辅助方法使用这些方法将时间量输出为字符串:

public static String periodToYMD(Period period) {
  StringBuilder sb = new StringBuilder();
  sb.append(period.getYears())
   .append("y:")
   .append(period.getMonths())
   .append("m:")
   .append(period.getDays())
   .append("d");
 return sb.toString();
}


我们将此方法称为periodBetween(差值为 1 年 4 个月 8 天):

periodToYMD(periodBetween); // 1y:4m:8d



当确定某个日期是否早于另一个日期时,Period类也很有用。有一个标志方法,名为isNegative()。有一个A周期和一个B周期,如果B在A之前,应用Period.between(A, B)的结果可以是负的,如果A在B之前,应用isNegative()的结果可以是正的,如果B在A之前,false在A之前,则isNegative()返回true B,如我们的例子所示(基本上,如果年、月或日为负数,此方法返回false):

// returns false, since 12 March 2018 is earlier than 20 July 2019
periodBetween.isNegative();



最后,Period可以通过加上或减去一段时间来修改。方法有plusYears()、plusMonths()、plusDays()、minusYears()、minusMonths()、minusDays()等。例如,在periodBetween上加 1 年可以如下操作:


Period periodBetweenPlus1Year = periodBetween.plusYears(1L);


添加两个Period类可以通过Period.plus()方法完成,如下所示:


Period p1 = Period.ofDays(5);
Period p2 = Period.ofDays(20);
Period p1p2 = p1.plus(p2); // P25D



使用基于时间的值的持续时间

Duration类意味着使用基于时间的值(小时、分钟、秒或纳秒)来表示时间量。这种持续时间可以通过不同的方式获得。例如,可以如下获得 10 小时的持续时间:

Duration fromHours = Duration.ofHours(10); // PT10H



ofHours()方法旁边,Duration类还有ofDays()ofMillis()ofMinutes()ofSeconds()ofNanos()

或者,可以通过of()方法获得 3 分钟的持续时间,如下所示:

Duration fromMinutes = Duration.of(3, ChronoUnit.MINUTES); // PT3M


Duration也可以从LocalDateTime中得到:

LocalDateTime localDateTime 
  = LocalDateTime.of(2018, 3, 12, 4, 14, 20, 670);
// PT14M
Duration fromLocalDateTime 
  = Duration.ofMinutes(localDateTime.getMinute());


也可从LocalTime中获得:

LocalTime localTime = LocalTime.of(4, 14, 20, 670);
// PT0.00000067S
Duration fromLocalTime = Duration.ofNanos(localTime.getNano());


最后,可以从遵循 ISO-8601 持续时间格式PnDTnHnMn.nS的String对象获得Duration,其中天被认为正好是 24 小时。例如,P2DT3H4M字符串有 2 天 3 小时 4 分钟:


Duration durationFromString = Duration.parse("P2DT3H4M");


调用Duration.toString()将返回符合 ISO-8601 持续时间格式的持续时间PnDTnHnMn.nS(例如,PT10H、PT3M或PT51H4M)。


但是,与Period的情况一样,当Duration用于表示两次之间的时间段(例如,Instant时,揭示了它的真实功率。从 2015 年 11 月 3 日 12:11:30 到 2016 年 12 月 6 日 15:17:10 之间的持续时间可以表示为两个Instant类之间的差异,如下所示:

Instant startInstant = Instant.parse("2015-11-03T12:11:30.00Z");
Instant endInstant = Instant.parse("2016-12-06T15:17:10.00Z");
// PT10059H5M40S
Duration durationBetweenInstant 
  = Duration.between(startInstant, endInstant);

以秒为单位,可通过Duration.getSeconds()方法获得该差值:

durationBetweenInstant.getSeconds(); // 36212740 seconds


或者,从 2018 年 3 月 12 日 04:14:20.000000670 到 2019 年 7 月 20 日 06:10:10.000000720 之间的持续时间可以表示为两个LocalDateTime对象之间的差异,如下所示:

LocalDateTime startLocalDateTime 
  = LocalDateTime.of(2018, 3, 12, 4, 14, 20, 670);
LocalDateTime endLocalDateTime 
  = LocalDateTime.of(2019, 7, 20, 6, 10, 10, 720);
// PT11881H55M50.00000005S, or 42774950 seconds
Duration durationBetweenLDT 
  = Duration.between(startLocalDateTime, endLocalDateTime);


最后,04:14:20.000000670 和 06:10:10.000000720 之间的持续时间可以表示为两个LocalTime对象之间的差异,如下所示:

LocalTime startLocalTime = LocalTime.of(4, 14, 20, 670);
LocalTime endLocalTime = LocalTime.of(6, 10, 10, 720);
// PT1H55M50.00000005S, or 6950 seconds
Duration durationBetweenLT 
  = Duration.between(startLocalTime, endLocalTime);


在前面的例子中,Duration通过Duration.getSeconds()方法以秒表示,这是Duration类中的秒数。然而,Duration类包含一组方法,这些方法专用于通过toDays()以天为单位、通过toHours()以小时为单位、通过toMinutes()以分钟为单位、通过toMillis()以毫秒为单位、通过toNanos()以纳秒为单位来表达Duration。


从一个时间单位转换到另一个时间单位可能会产生残余。例如,从秒转换为分钟可能导致秒的剩余(例如,65 秒是 1 分钟,5 秒是剩余)。残差可以通过以下一组方法获得:天残差通过toDaysPart(),小时残差通过toHoursPart(),分钟残差通过toMinutesPart()等等。


假设差异应该显示为天:小时:分:秒:纳秒(例如,9d:2h:15m:20s:230n)。将toFoo()和toFooPart()方法的力结合在一个辅助方法中将产生以下代码:

public static String durationToDHMSN(Duration duration) {
  StringBuilder sb = new StringBuilder();
  sb.append(duration.toDays())
    .append("d:")
    .append(duration.toHoursPart())
    .append("h:")
    .append(duration.toMinutesPart())
    .append("m:")
    .append(duration.toSecondsPart())
    .append("s:")
    .append(duration.toNanosPart())
    .append("n");
  return sb.toString();
}


让我们调用这个方法durationBetweenLDT(差别是 495 天 1 小时 55 分 50 秒 50 纳秒):

// 495d:1h:55m:50s:50n
durationToDHMSN(durationBetweenLDT);


与Period类相同,Duration类有一个名为isNegative()的标志方法。当确定某个特定时间是否早于另一个时间时,此方法很有用。有持续时间A和持续时间B,如果B在A之前,应用Duration.between(A, B)的结果可以是负的,如果A在B之前,应用Duration.between(A, B)的结果可以是正的,进一步逻辑,isNegative()如果B在A之前,则返回true,如果A在B之前,则返回false,如以下情况:

durationBetweenLT.isNegative(); // false


最后,Duration可以通过增加或减少持续时间来修改。有plusDays()、plusHours()、plusMinutes()、plusMillis()、plusNanos()、minusDays()、minusHours()、minusMinutes()、minusMillis()和minusNanos()等方法来执行此操作。例如,向durationBetweenLT添加 5 小时可以如下所示:

Duration durationBetweenPlus5Hours = durationBetweenLT.plusHours(5);



添加两个Duration类可以通过Duration.plus()方法完成,如下所示:

Duration d1 = Duration.ofMinutes(20);
Duration d2 = Duration.ofHours(2);
Duration d1d2 = d1.plus(d2);
System.out.println(d1 + "+" + d2 + "=" + d1d2); // PT2H20M



64 获取日期和时间单位


对于Date对象,解决方案可能依赖于Calendar实例。绑定到本书的代码包含此解决方案。


对于 JDK8 类,Java 提供了专用的getFoo()方法和get(TemporalField field)方法。例如,假设下面的LocalDateTime对象:

LocalDateTime ldt = LocalDateTime.now();


依靠getFoo()方法,我们得到如下代码:

int year = ldt.getYear();
int month = ldt.getMonthValue();
int day = ldt.getDayOfMonth();
int hour = ldt.getHour();
int minute = ldt.getMinute();
int second = ldt.getSecond();
int nano = ldt.getNano();

或者,依赖于get(TemporalField field)结果如下:

int yearLDT = ldt.get(ChronoField.YEAR);
int monthLDT = ldt.get(ChronoField.MONTH_OF_YEAR);
int dayLDT = ldt.get(ChronoField.DAY_OF_MONTH);
int hourLDT = ldt.get(ChronoField.HOUR_OF_DAY);
int minuteLDT = ldt.get(ChronoField.MINUTE_OF_HOUR);
int secondLDT = ldt.get(ChronoField.SECOND_OF_MINUTE);
int nanoLDT = ldt.get(ChronoField.NANO_OF_SECOND);


请注意,月份是从 1 开始计算的,即 1 月。

例如,2019-02-25T12:58:13.109389100的LocalDateTime对象可以被切割成日期时间单位,结果如下:

Year: 2019 Month: 2 Day: 25 Hour: 12 Minute: 58 Second: 13 Nano: 109389100


通过一点直觉和文档,很容易将此示例改编为LocalDateLocalTimeZonedDateTime和其他示例。



65 日期时间的加减


这个问题的解决方案依赖于专用于处理日期和时间的 Java API。让我们在下一节中看看它们。




使用Date

对于Date对象,解决方案可能依赖于Calendar实例。绑定到本书的代码包含此解决方案。




使用LocalDateTime


跳转到 JDK8,重点是LocalDate、LocalTime、LocalDateTime、Instant等等。新的 Java 日期时间 API 提供了专门用于加减时间量的方法。LocalDate、LocalTime、LocalDateTime、ZonedDateTime、OffsetDateTime、Instant、Period、Duration以及许多其他方法,如plusFoo()和minusFoo(),其中Foo可以用单位替换时间(例如,plusYears()、plusMinutes()、minusHours()、minusSeconds()等等)。


假设如下LocalDateTime

// 2019-02-25T14:55:06.651155500
LocalDateTime ldt = LocalDateTime.now();


加 10 分钟和调用LocalDateTime.plusMinutes(long minutes)一样简单,减 10 分钟和调用LocalDateTime.minusMinutes(long minutes)一样简单:

LocalDateTime ldtAfterAddingMinutes = ldt.plusMinutes(10);
LocalDateTime ldtAfterSubtractingMinutes = ldt.minusMinutes(10);


输出将显示以下日期:

After adding 10 minutes: 2019-02-25T15:05:06.651155500
After subtracting 10 minutes: 2019-02-25T14:45:06.651155500



除了每个时间单位专用的方法外,这些类还支持plus/minus(TemporalAmount amountToAdd)和plus/minus(long amountToAdd, TemporalUnit unit)。


现在,让我们关注Instant类。除了plus/minusSeconds()、plus/minusMillis()、plus/minusNanos()之外,Instant类还提供了plus/minus(TemporalAmount amountToAdd)方法。


为了举例说明这个方法,我们假设如下Instant:

// 2019-02-25T12:55:06.654155700Z
Instant timestamp = Instant.now();


现在,让我们加减 5 个小时:

Instant timestampAfterAddingHours 
  = timestamp.plus(5, ChronoUnit.HOURS);
Instant timestampAfterSubtractingHours 
  = timestamp.minus(5, ChronoUnit.HOURS);

输出将显示以下Instant

After adding 5 hours: 2019-02-25T17:55:06.654155700Z
After subtracting 5 hours: 2019-02-25T07:55:06.654155700Z
相关文章
|
3天前
|
SQL Java 关系型数据库
Java中的JDBC编程详解
Java中的JDBC编程详解
|
3天前
|
安全 Java API
Java中的Servlet编程详解
Java中的Servlet编程详解
|
3天前
|
Java
Java中的NIO编程详解
Java中的NIO编程详解
|
3天前
|
Java API 开发者
Java中的Socket编程与应用
Java中的Socket编程与应用
|
3天前
|
安全 Java API
Java基础之新日期和时间
“【7月更文挑战第5天】”Java 8 引入了`java.time`包,改进了日期和时间处理。新API包括线程安全的`LocalDate`、`LocalTime`、`LocalDateTime`、`ZonedDateTime`、`Instant`,以及`DateTimeFormatter`、`Period`和`Duration`。
12 0
|
3天前
|
安全 Java C++
Java中的AOP编程详解
Java中的AOP编程详解
|
3天前
|
安全 Java Unix
Java中的时间日期处理与时区管理
Java中的时间日期处理与时区管理
|
3天前
|
存储 安全 算法
深入探讨Java中的泛型编程
深入探讨Java中的泛型编程
|
3天前
|
存储 安全 Java
Java中的泛型编程详解
Java中的泛型编程详解
|
3天前
|
Java API
Java中的日期和时间API详解
Java中的日期和时间API详解