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

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

Date-Instant


为了从Date转换到Instant,可采用Date.toInstant()方法求解。可通过Date.from(Instant instant)方法实现反转:


  • DateInstant可以这样完成:


Date date = new Date();
// e.g., 2019-02-27T12:02:49.369Z, UTC
Instant instantFromDate = date.toInstant();


   Instant到Date可以这样完成:

Instant instant = Instant.now();
// Wed Feb 27 14:02:49 EET 2019, default system time zone
Date dateFromInstant = Date.from(instant);



请记住,Date不是时区感知的,但它显示在系统默认时区中(例如,通过toString())。Instant是 UTC 时区。

让我们快速地将这些代码片段包装在两个工具方法中,它们在一个工具类DateConverters中定义:

public static Instant dateToInstant(Date date) {
  return date.toInstant();
}
public static Date instantToDate(Instant instant) {
  return Date.from(instant);
}


此外,让我们使用以下屏幕截图中的方法来丰富此类:


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VC4DDDjb-1657077517352)(img/ae07c212-3c31-4d8f-91b1-b494d5b9d393.png)]


屏幕截图中的常量DEFAULT_TIME_ZONE是系统默认时区:

public static final ZoneId DEFAULT_TIME_ZONE = ZoneId.systemDefault();



DateLocalDate


Date对象可以通过Instant对象转换为LocalDate。一旦我们从给定的Date对象中获得Instant对象,解决方案就可以应用于它系统默认时区,并调用toLocaleDate()方法:

// e.g., 2019-03-01
public static LocalDate dateToLocalDate(Date date) {
  return dateToInstant(date).atZone(DEFAULT_TIME_ZONE).toLocalDate();
}


LocalDateDate的转换应该考虑到LocalDate不包含Date这样的时间成分,所以解决方案必须提供一个时间成分作为一天的开始(关于这个问题的更多细节可以在“一天的开始和结束”问题中找到):

// e.g., Fri Mar 01 00:00:00 EET 2019
public static Date localDateToDate(LocalDate localDate) {
  return Date.from(localDate.atStartOfDay(
    DEFAULT_TIME_ZONE).toInstant());
}



DateLocalDateTime

DateDateLocalTime的转换与从DateLocalDate的转换是一样的,只是溶液应该调用toLocalDateTime()方法如下:

// e.g., 2019-03-01T07:25:25.624
public static LocalDateTime dateToLocalDateTime(Date date) {
  return dateToInstant(date).atZone(
    DEFAULT_TIME_ZONE).toLocalDateTime();
}


LocalDateTimeDate的转换非常简单。只需应用系统默认时区并调用toInstant()

// e.g., Fri Mar 01 07:25:25 EET 2019
public static Date localDateTimeToDate(LocalDateTime localDateTime) {
  return Date.from(localDateTime.atZone(
    DEFAULT_TIME_ZONE).toInstant());
}




DateZonedDateTime

DateZonedDateTime的转换可以通过从给定Date对象获取Instant对象和系统默认时区来完成:

// e.g., 2019-03-01T07:25:25.624+02:00[Europe/Athens]
public static ZonedDateTime dateToZonedDateTime(Date date) {
  return dateToInstant(date).atZone(DEFAULT_TIME_ZONE);
}


将ZonedDateTime转换为Date就是将ZonedDateTime转换为Instant:

// e.g., Fri Mar 01 07:25:25 EET 2019
public static Date zonedDateTimeToDate(ZonedDateTime zonedDateTime) {
  return Date.from(zonedDateTime.toInstant());
}



DateOffsetDateTime

DateOffsetDateTime的转换依赖于toOffsetDateTime()方法:

// e.g., 2019-03-01T07:25:25.624+02:00
public static OffsetDateTime dateToOffsetDateTime(Date date) {
  return dateToInstant(date).atZone(
    DEFAULT_TIME_ZONE).toOffsetDateTime();
}


OffsetDateTimeDate的转换方法需要两个步骤。首先将OffsetDateTime转换为LocalDateTime;其次将LocalDateTime转换为Instant,对应偏移量:

// e.g., Fri Mar 01 07:55:49 EET 2019
public static Date offsetDateTimeToDate(
    OffsetDateTime offsetDateTime) {
  return Date.from(offsetDateTime.toLocalDateTime()
    .toInstant(ZoneOffset.of(offsetDateTime.getOffset().getId())));
}



DateLocalTime


Date转换为LocalTime可以依赖LocalTime.toInstant()方法,如下所示:


// e.g., 08:03:20.336
public static LocalTime dateToLocalTime(Date date) {
  return LocalTime.ofInstant(dateToInstant(date), DEFAULT_TIME_ZONE);
}


LocalTime转换为Date应该考虑到LocalTime没有日期组件。这意味着解决方案应将日期设置为 1970 年 1 月 1 日,即纪元:

// e.g., Thu Jan 01 08:03:20 EET 1970
public static Date localTimeToDate(LocalTime localTime) {
  return Date.from(localTime.atDate(LocalDate.EPOCH)
    .toInstant(DEFAULT_TIME_ZONE.getRules()
      .getOffset(Instant.now())));
}



Date-OffsetTime

Date转换为OffsetTime可以依赖OffsetTime.toInstant()方法,如下所示:

// e.g., 08:03:20.336+02:00
public static OffsetTime dateToOffsetTime(Date date) {
  return OffsetTime.ofInstant(dateToInstant(date), DEFAULT_TIME_ZONE);
}

OffsetTime转换为Date应该考虑到OffsetTime没有日期组件。这意味着解决方案应将日期设置为 1970 年 1 月 1 日,即纪元:

// e.g., Thu Jan 01 08:03:20 EET 1970
public static Date offsetTimeToDate(OffsetTime offsetTime) {
  return Date.from(offsetTime.atDate(LocalDate.EPOCH).toInstant());
}




73 迭代一系列日期


假设范围是由开始日期 2019 年 2 月 1 日和结束日期 2019 年 2 月 21 日界定的。这个问题的解决方案应该循环【2019 年 2 月 1 日,2019 年 2 月 21 日】间隔一天,并在屏幕上打印每个日期。基本上要解决两个主要问题:


  • 一旦开始日期和结束日期相等,就停止循环。
  • 每天增加开始日期直到结束日期。




JDK8 之前

在 JDK8 之前,解决方案可以依赖于Calendar工具类。绑定到本书的代码包含此解决方案。



从 JDK8 开始

首先,从 JDK8 开始,可以很容易地将日期定义为LocalDate,而不需要Calendar的帮助:


LocalDate startLocalDate = LocalDate.of(2019, 2, 1);
LocalDate endLocalDate = LocalDate.of(2019, 2, 21);


一旦开始日期和结束日期相等,我们就通过LocalDate.isBefore(ChronoLocalDate other)方法停止循环。此标志方法检查此日期是否早于给定日期。

使用LocalDate.plusDays(long daysToAdd)方法逐日增加开始日期直到结束日期。在for循环中使用这两种方法会产生以下代码:


for (LocalDate date = startLocalDate; 
       date.isBefore(endLocalDate); date = date.plusDays(1)) {
  // do something with this day
  System.out.println(date);
}


输出的快照应如下所示:

2019-02-01
2019-02-02
2019-02-03
...
2019-02-20



从 JDK9 开始


JDK9 可以用一行代码解决这个问题。由于新的LocalDate.datesUntil(LocalDate endExclusive)方法,这是可能的。此方法返回Stream<LocalDate>,增量步长为一天:

startLocalDate.datesUntil(endLocalDate).forEach(System.out::println);


如果增量步骤应以天、周、月或年表示,则依赖于LocalDate.datesUntil(LocalDate endExclusive, Period step)。例如,1 周的增量步骤可以指定如下:

startLocalDate.datesUntil(endLocalDate, Period.ofWeeks(1)).forEach(System.out::println);


输出应为(第 1-8 周,第 8-15 周),如下所示:

2019-02-01
2019-02-08
2019-02-15



74 计算年龄

可能最常用的两个日期之间的差异是关于计算一个人的年龄。通常,一个人的年龄以年表示,但有时应提供月,甚至天。



JDK8 之前

在 JDK8 之前,试图提供一个好的解决方案可以依赖于Calendar和/或SimpleDateFormat。绑定到本书的代码包含这样一个解决方案。



从 JDK8 开始

更好的方法是升级到 JDK8,并依赖以下简单的代码片段:

LocalDate startLocalDate = LocalDate.of(1977, 11, 2);
LocalDate endLocalDate = LocalDate.now();
long years = ChronoUnit.YEARS.between(startLocalDate, endLocalDate);


由于Period类的原因,将月和日添加到结果中也很容易实现:

Period periodBetween = Period.between(startLocalDate, endLocalDate);


现在,可以通过periodBetween.getYears()、periodBetween.getMonths()、periodBetween.getDays()获得以年、月、日为单位的年龄。

例如,在当前日期 2019 年 2 月 28 日和 1977 年 11 月 2 日之间,我们有 41 年 3 个月 26 天。



75 一天的开始和结束


在 JDK8 中,可以通过几种方法来找到一天的开始/结束。

让我们考虑一下通过LocalDate表达的一天:

LocalDate localDate = LocalDate.of(2019, 2, 28);


找到 2019 年 2 月 28 日一天的开始的解决方案依赖于一个名为atStartOfDay()的方法。此方法从该日期午夜 00:00 返回LocalDateTime:

// 2019-02-28T00:00
LocalDateTime ldDayStart = localDate.atStartOfDay();


或者,该溶液可以使用of(LocalDate date, LocalTime time)方法。该方法将给定的日期和时间组合成LocalDateTime。因此,如果经过的时间是LocalTime.MIN(一天开始时的午夜时间),则结果如下:

// 2019-02-28T00:00
LocalDateTime ldDayStart = LocalDateTime.of(localDate, LocalTime.MIN);



一个LocalDate物体的一天结束时间至少可以用两种方法得到。一种解决方案是依靠LocalDate.atTime(LocalTime time)。得到的LocalDateTime可以表示该日期与一天结束时的组合,如果解决方案作为参数传递,LocalTime.MAX(一天结束时午夜前的时间):

// 2019-02-28T23:59:59.999999999
LocalDateTime ldDayEnd = localDate.atTime(LocalTime.MAX);


或者,该解决方案可以通过atDate(LocalDate date)方法将LocalTime.MAX与给定日期结合:

// 2019-02-28T23:59:59.999999999
LocalDateTime ldDayEnd = LocalTime.MAX.atDate(localDate);


由于LocalDate没有时区的概念,前面的例子容易出现由不同的角落情况引起的问题,例如夏令时。有些夏令时会在午夜(00:00 变为 01:00 AM)更改时间,这意味着一天的开始时间是 01:00:00,而不是 00:00:00。为了缓解这些问题,请考虑以下示例,这些示例将前面的示例扩展为使用夏令时感知的ZonedDateTime:


// 2019-02-28T00:00+08:00[Australia/Perth]
ZonedDateTime ldDayStartZone 
  = localDate.atStartOfDay(ZoneId.of("Australia/Perth"));
// 2019-02-28T00:00+08:00[Australia/Perth]
ZonedDateTime ldDayStartZone = LocalDateTime
  .of(localDate, LocalTime.MIN).atZone(ZoneId.of("Australia/Perth"));
// 2019-02-28T23:59:59.999999999+08:00[Australia/Perth]
ZonedDateTime ldDayEndZone = localDate.atTime(LocalTime.MAX)
  .atZone(ZoneId.of("Australia/Perth"));
// 2019-02-28T23:59:59.999999999+08:00[Australia/Perth]
ZonedDateTime ldDayEndZone = LocalTime.MAX.atDate(localDate)
  .atZone(ZoneId.of("Australia/Perth"));


现在,我们来考虑一下-LocalDateTime,2019 年 2 月 28 日,18:00:00:

LocalDateTime localDateTime = LocalDateTime.of(2019, 2, 28, 18, 0, 0);


显而易见的解决方案是从LocalDateTime中提取LocalDate,并应用前面的方法。另一个解决方案依赖于这样一个事实,Temporal接口的每个实现(包括LocalDate)都可以利用with(TemporalField field, long newValue)方法。主要是,with()方法返回这个日期的一个副本,其中指定的字段ChronoField设置为newValue。因此,如果解决方案将ChronoField.NANO_OF_DAY(一天的纳秒)设置为LocalTime.MIN,那么结果将是一天的开始。这里的技巧是通过toNanoOfDay()将LocalTime.MIN转换为纳秒,如下所示:

// 2019-02-28T00:00
LocalDateTime ldtDayStart = localDateTime
  .with(ChronoField.NANO_OF_DAY, LocalTime.MIN.toNanoOfDay());


这相当于:

LocalDateTime ldtDayStart 
   = localDateTime.with(ChronoField.HOUR_OF_DAY, 0);


一天的结束是非常相似的。只需通过LocalTime.MAX而不是MIN:

// 2019-02-28T23:59:59.999999999
LocalDateTime ldtDayEnd = localDateTime
  .with(ChronoField.NANO_OF_DAY, LocalTime.MAX.toNanoOfDay());


这相当于:

LocalDateTime ldtDayEnd = localDateTime.with(
  ChronoField.NANO_OF_DAY, 86399999999999L);


LocalDate一样,LocalDateTime对象不知道时区。在这种情况下,ZonedDateTime可以帮助:

// 2019-02-28T00:00+08:00[Australia/Perth]
ZonedDateTime ldtDayStartZone = localDateTime
  .with(ChronoField.NANO_OF_DAY, LocalTime.MIN.toNanoOfDay())
  .atZone(ZoneId.of("Australia/Perth"));
// 2019-02-28T23:59:59.999999999+08:00[Australia/Perth]
ZonedDateTime ldtDayEndZone = localDateTime
  .with(ChronoField.NANO_OF_DAY, LocalTime.MAX.toNanoOfDay())
  .atZone(ZoneId.of("Australia/Perth"));

作为奖励,让我们看看 UTC 一天的开始/结束。除了依赖于with()方法的解决方案外,另一个解决方案可以依赖于toLocalDate(),如下所示:

// e.g., 2019-02-28T09:23:10.603572Z
ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
// 2019-02-28T00:00Z
ZonedDateTime dayStartZdt 
  = zdt.toLocalDate().atStartOfDay(zdt.getZone());
// 2019-02-28T23:59:59.999999999Z
ZonedDateTime dayEndZdt = zdt.toLocalDate()
  .atTime(LocalTime.MAX).atZone(zdt.getZone());

由于java.util.DateCalendar存在许多问题,因此建议避免尝试用它们实现此问题的解决方案。



76 两个日期之间的差异

计算两个日期之间的差值是一项非常常见的任务(例如,请参阅“计算年龄”部分)。让我们看看其他方法的集合,这些方法可以用来获得以毫秒、秒、小时等为单位的两个日期之间的差异。



JDK8 之前

建议通过java.util.Date和Calendar类来表示日期时间信息。最容易计算的差异用毫秒表示。绑定到本书的代码包含这样一个解决方案。


从 JDK8 开始

从 JDK8 开始,建议通过Temporal(例如,DateTimeDateLocalTimeZonedDateTime等)来表示日期时间信息。


假设两个LocalDate对象,2018 年 1 月 1 日和 2019 年 3 月 1 日:

LocalDate ld1 = LocalDate.of(2018, 1, 1);
LocalDate ld2 = LocalDate.of(2019, 3, 1);

计算这两个Temporal对象之间差异的最简单方法是通过ChronoUnit类。除了表示一组标准的日期周期单位外,ChronoUnit还提供了几种简便的方法,包括between(Temporal t1Inclusive, Temporal t2Exclusive)。顾名思义,between()方法计算两个Temporal对象之间的时间量。让我们看看计算ld1和ld2之间的差值的工作原理,以天、月和年为单位:


// 424
long betweenInDays = Math.abs(ChronoUnit.DAYS.between(ld1, ld2));
// 14
long betweenInMonths = Math.abs(ChronoUnit.MONTHS.between(ld1, ld2));
// 1
long betweenInYears = Math.abs(ChronoUnit.YEARS.between(ld1, ld2));

或者,每个Temporal公开一个名为until()的方法。实际上,LocalDate有两个,一个返回Period作为两个日期之间的差,另一个返回long作为指定时间单位中两个日期之间的差。使用返回Period的方法如下:

Period period = ld1.until(ld2);
// Difference as Period: 1y2m0d
System.out.println("Difference as Period: " + period.getYears() + "y" 
  + period.getMonths() + "m" + period.getDays() + "d");

使用允许我们指定时间单位的方法如下:

// 424
long untilInDays = Math.abs(ld1.until(ld2, ChronoUnit.DAYS));
// 14
long untilInMonths = Math.abs(ld1.until(ld2, ChronoUnit.MONTHS));
// 1
long untilInYears = Math.abs(ld1.until(ld2, ChronoUnit.YEARS));

ChronoUnit.convert()方法也适用于LocalDateTime的情况。让我们考虑以下两个LocalDateTime对象:2018 年 1 月 1 日 22:15:15 和 2019 年 3 月 1 日 23:15:15:

LocalDateTime ldt1 = LocalDateTime.of(2018, 1, 1, 22, 15, 15);
LocalDateTime ldt2 = LocalDateTime.of(2018, 1, 1, 23, 15, 15);


现在,让我们看看ldt1ldt2之间的区别,用分钟表示:

// 60
long betweenInMinutesWithoutZone 
  = Math.abs(ChronoUnit.MINUTES.between(ldt1, ldt2));


并且,通过LocalDateTime.until()方法以小时表示的差异:

// 1
long untilInMinutesWithoutZone 
  = Math.abs(ldt1.until(ldt2, ChronoUnit.HOURS));


但是,ChronoUnit.between()until()有一个非常棒的地方,那就是它们与ZonedDateTime一起工作。例如,让我们考虑欧洲/布加勒斯特时区和澳大利亚/珀斯时区的ldt1,加上一小时:

ZonedDateTime zdt1 = ldt1.atZone(ZoneId.of("Europe/Bucharest"));
ZonedDateTime zdt2 = zdt1.withZoneSameInstant(
  ZoneId.of("Australia/Perth")).plusHours(1);

现在,我们用ChronoUnit.between()来表示zdt1zdt2之间的差分,用ZonedDateTime.until()来表示zdt1zdt2之间的差分,用小时表示:

// 60
long betweenInMinutesWithZone 
  = Math.abs(ChronoUnit.MINUTES.between(zdt1, zdt2));
// 1
long untilInHoursWithZone 
  = Math.abs(zdt1.until(zdt2, ChronoUnit.HOURS));

最后,让我们重复这个技巧,但是对于两个独立的ZonedDateTime对象:一个为ldt1获得,一个为ldt2获得:

ZonedDateTime zdt1 = ldt1.atZone(ZoneId.of("Europe/Bucharest"));
ZonedDateTime zdt2 = ldt2.atZone(ZoneId.of("Australia/Perth"));
// 300
long betweenInMinutesWithZone 
  = Math.abs(ChronoUnit.MINUTES.between(zdt1, zdt2));
// 5
long untilInHoursWithZone 
  = Math.abs(zdt1.until(zdt2, ChronoUnit.HOURS));



77 实现象棋时钟

从 JDK8 开始,java.time包有一个名为Clock的抽象类。这个类的主要目的是允许我们在需要时插入不同的时钟(例如,出于测试目的)。默认情况下,Java 有四种实现:SystemClock、OffsetClock、TickClock和FixedClock。对于每个实现,Clock类中都有static方法。例如,下面的代码创建了FixedClock(一个总是返回相同Instant的时钟):


Clock fixedClock = Clock.fixed(Instant.now(), ZoneOffset.UTC);


还有一个TickClock,它返回给定时区整秒的当前Instant滴答声:

Clock tickClock = Clock.tickSeconds(ZoneId.of("Europe/Bucharest"));


还有一种方法可以用来在整分钟内打勾tickMinutes(),还有一种通用方法tick(),它允许我们指定Duration

Clock类也可以支持时区和偏移量,但是Clock类最重要的方法是instant()。此方法返回Clock的瞬间:

// 2019-03-01T13:29:34Z
System.out.println(tickClock.instant());



还有一个millis()方法,它以毫秒为单位返回时钟的当前时刻。


假设我们要实现一个时钟,它充当象棋时钟:


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QaOdoWVZ-1657077517353)(img/ad6496ac-4425-407c-9a5f-c922283d6bcb.png)]


为了实现一个Clock类,需要遵循以下几个步骤:


   扩展Clock类。

   执行Serializable。

   至少覆盖从Clock继承的抽象方法。


Clock类的框架如下:

public class ChessClock extends Clock implements Serializable {
  @Override
  public ZoneId getZone() {
    ...
  }
  @Override
  public Clock withZone(ZoneId zone) {
    ...
  }
  @Override
  public Instant instant() {
    ...
  }
}


我们的ChessClock将只与 UTC 一起工作;不支持其他时区。这意味着getZone()withZone()方法可以实现如下(当然,将来可以修改):

@Override
public ZoneId getZone() {
  return ZoneOffset.UTC;
}
@Override
public Clock withZone(ZoneId zone) {
  throw new UnsupportedOperationException(
    "The ChessClock works only in UTC time zone");
}


我们实现的高潮是instant()方法。难度在于管理两个Instant,一个是左边的玩家(instantLeft),一个是右边的玩家(instantRight)。我们可以将instant()方法的每一次调用与当前玩家已经执行了一个移动的事实相关联,现在轮到另一个玩家了。所以,基本上,这个逻辑是说同一个玩家不能调用instant()两次。实现这个逻辑,instant()方法如下:


public class ChessClock extends Clock implements Serializable {
  public enum Player {
    LEFT,
    RIGHT
  }
  private static final long serialVersionUID = 1L;
  private Instant instantStart;
  private Instant instantLeft;
  private Instant instantRight;
  private long timeLeft;
  private long timeRight;
  private Player player;
  public ChessClock(Player player) {
    this.player = player;
  }
  public Instant gameStart() {
    if (this.instantStart == null) {
      this.timeLeft = 0;
      this.timeRight = 0;
      this.instantStart = Instant.now();
      this.instantLeft = instantStart;
      this.instantRight = instantStart;
      return instantStart;
    }
    throw new IllegalStateException(
      "Game already started. Stop it and try again.");
  }
  public Instant gameEnd() {
    if (this.instantStart != null) {
      instantStart = null;
      return Instant.now();
    }
    throw new IllegalStateException("Game was not started.");
  }
  @Override
  public ZoneId getZone() {
    return ZoneOffset.UTC;
  }
  @Override
  public Clock withZone(ZoneId zone) {
    throw new UnsupportedOperationException(
      "The ChessClock works only in UTC time zone");
  }
  @Override
  public Instant instant() {
    if (this.instantStart != null) {
      if (player == Player.LEFT) {
        player = Player.RIGHT;
        long secondsLeft = Instant.now().getEpochSecond() 
          - instantRight.getEpochSecond();
        instantLeft = instantLeft.plusSeconds(
          secondsLeft - timeLeft);
        timeLeft = secondsLeft;
        return instantLeft;
      } else {
        player = Player.LEFT;
        long secondsRight = Instant.now().getEpochSecond() 
          - instantLeft.getEpochSecond();
        instantRight = instantRight.plusSeconds(
          secondsRight - timeRight);
        timeRight = secondsRight;
        return instantRight;
      }
    }
    throw new IllegalStateException("Game was not started.");
  }
}


因此,根据哪个玩家调用了instant()方法,代码计算出该玩家在执行移动之前思考所需的秒数。此外,代码会切换播放器,因此下一次调用instant()将处理另一个播放器。

让我们考虑一个从2019-03-01T14:02:46.309459Z开始的国际象棋游戏:

ChessClock chessClock = new ChessClock(Player.LEFT);
// 2019-03-01T14:02:46.309459Z
Instant start = chessClock.gameStart();


此外,玩家执行以下一系列动作,直到右边的玩家赢得游戏:

Left moved first after 2 seconds: 2019-03-01T14:02:48.309459Z
Right moved after 5 seconds: 2019-03-01T14:02:51.309459Z
Left moved after 6 seconds: 2019-03-01T14:02:54.309459Z
Right moved after 1 second: 2019-03-01T14:02:52.309459Z
Left moved after 2 second: 2019-03-01T14:02:56.309459Z
Right moved after 3 seconds: 2019-03-01T14:02:55.309459Z
Left moved after 10 seconds: 2019-03-01T14:03:06.309459Z
Right moved after 11 seconds and win: 2019-03-01T14:03:06.309459Z


看来时钟正确地记录了运动员的动作。

最后,比赛在 40 秒后结束:

Game ended:2019-03-01T14:03:26.350749300Z
Instant end = chessClock.gameEnd();
Game duration: 40 seconds
// Duration.between(start, end).getSeconds();




总结


任务完成了!本章提供了使用日期和时间信息的全面概述。广泛的应用必须处理这类信息。因此,将这些问题的解决方案放在你的工具带下不是可选的。从Date、Calendar到LocalDate、LocalTime、LocalDateTime、ZoneDateTime、OffsetDateTime、OffsetTime、Instant——它们在涉及日期和时间的日常任务中都是非常重要和有用的。



相关文章
|
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详解