Java 8 Date-Time API 详解
从Java版本1.0开始就支持日期和时间,主要通过java.util.Date
类。 但是,Date
类设计不佳。 例如,Date
中的月份从1开始,但从日期却从0开始。在JDK 1.1中使用它的许多方法已经废弃,同时java.util.Calendar
被引入来接管Date
中的一些功能。 这两个是处理日期和时间的主要类,直到JDK 1.7,尽管他们被认为是不足够并且不容易处理,导致许多人诉诸第三方替代品,例如Joda Time(http://joda.org)。 JDK 1.8中的新日期和时间API解决了旧API中的许多问题,并且与Joda Time API类似。
这里介绍JDK 1.8中的日期 - 时间的API。
JDK 8Overview
新的日期和时间API使得使用日期和时间非常容易。java.time
包中包含API中的核心类。 另外,还有其他四个包,其成员使用较少:java.time.chrono
,java.time.format
,java.time.temporal
和java.time.zone
。
在java.time
包中,Instant
类表示时间线上的一个点,通常用于对时间进行操作。 LocalDate
类为没有时间和时区部分的日期建模,例如,用于表示生日。
如果你需要日期和时间,那么LocalDateTime
就是为你准备的。 例如,订单发货日期可能需要一个日期以外的时间来使订单更容易跟踪。 如果你需要一段时间但不关心日期,那么可以使用LocalTime
。
如果时区很重要,日期和时间API提供ZonedDateTime
类。 顾名思义,这个类表示带有时区日期时间。 例如,你可以使用此类来计算位于不同时区的两个机场之间的飞行时间。
然后有两个类来测量时间总计,即Duration
类和Period
类。 这两个类是相似的,除了Duration
是基于时间,但而Period
是基于日期的。 Duration
提供了纳秒精度的时间量。 例如,可以模拟飞行时间,因为它通常以小时数和分钟数表示。 另一方面,如果只关心天数,月数或年数,例如计算一个人的年龄,则Period
更为适用。
java.time
包也带有两个枚举DayOfWeek
和Month
。 DayOfWeek
表示从一周的一天,从周一开始到周日。 Month
枚举代表这一年的十二个月,从1月到12月。
处理日期和时间通常涉及解析和格式。 日期和时间API通过在所有主要类中提供parse
和format
方法来解决这两个问题。 另外,java.time.format
包含一个用于格式化日期和时间的DateTimeFormatter
类。
Instant类
Instant
实例表示时间线上的一个点。 参考点是标准的Java纪元(epoch),即1970-01-01T00:00:00Z(1970年1月1日00:00 GMT)。 Instant类的
EPOCH属性返回表示Java纪元的
Instant`实例。 在纪元之后的时间是正值,而在此之前的时间即是负值。
Instant
的静态now
方法返回一个表示当前时间的Instant
对象:
Instant now = Instant.now();
getEpochSecond
方法返回自纪元以来经过的秒数。 getNano
方法返回自上一秒开始以来的纳秒数。
Instant
类的一个常用用途是用来操作时间,如以下代码所示。
import java.time.Duration;
import java.time.Instant;
public class InstantDemo1 {
public static void main(String[] args) {
Instant start = Instant.now();
// do something here
Instant end = Instant.now();
System.out.println(Duration.between(start, end).toMillis());
}
}
如上面代码所示,Duration
类用于返回两个Instant
之间时间数量的差异。
LocalDate类
LocalDate
类只包括日期没有时间的部分。 它也没有时区。 下表显示了LocalDate
中一些重要的方法。
方法 | 描述 |
---|---|
now | 静态方法,返回今天的日期 |
of | 从指定年份,月份和日期创建LocalDate的静态方法 |
getDayOfMonth, getMonthValue, getYear | 以int形式返回此LocalDate的日,月或年 |
getMonth | 以Month枚举常量返回此LocalDate的月份 |
plusDays, minusDays | 给LocalDate添加或减去指定的天数 |
plusWeeks, minusWeeks | 给LocalDate添加或减去指定的星期数 |
plusMonths, minusMonths | 给LocalDate添加或减去指定的月份数 |
plusYears, minusYears | 给LocalDate添加或减去指定的年数 |
isLeapYear | 检查LocalDate指定的年份是否为闰年 |
isAfter, isBefore | 检查此LocalDate是在给定日期之后还是之前 |
lengthOfMonth | 返回此LocalDate中月份的天数 |
withDayOfMonth | 返回此LocalDate的拷贝,将月份中的某天设置为给定值 |
withMonth | 返回此LocalDate的拷贝,其月份设置为给定值 |
withYear | 返回此LocalDate的拷贝,并将年份设置为给定值 |
LocalDate
提供了各种创建日期的方法。 例如,要创建代表今天日期的LocalDate
,使用静态now
方法。
LocalDate today = LocalDate.now();
要创建代表特定年,月和日的LocalDate
,使用of
方法,该方法也是静态的。 例如,以下代码创建了一个代表2018年3月7日的LocalDate
实例。
LocalDate date = LocalDate.of(2018, 3, 7);
还有一个接受java.time.Month
枚举的常量作为第二个参数的of
方法。 例如,下面是使用第二种方法重载构造相同日期的代码。
LocalDate date = LocalDate.of(2018, Month.MARCH, 7);
还有获取LocalDate
的日,月或年的方法,例如getDayOfMonth
,getMonth
,getMonthValue
和getYear
。 他们都没有任何参数,并返回一个int或Month
的枚举常量。 另外,还有一个get
方法,它接受一个TemporalField
并返回这个LocalDate
的一部分。 例如,传递ChronoField.YEAR以
获取LocalDate
的年份部分。
int year = localDate.get(ChronoField.YEAR);
ChronoField
是一个实现TemporalField
接口的枚举,因此可以传递一个ChronoField
常量来获取。 TemporalField
和ChronoField
都是java.time.temporal
包的一部分。 但是,并非ChronoField
中的所有常量都可以get
获取,因为并非所有常量都受支持。 例如,传递ChronoField.SECOND_OF_DAY
以引发异常。 因此,取而代之,最好使用getMonth
,getYear
或类似方法来获取LocalDate
的组件。
此外,还有拷贝LocalDate
的方法,例如plusDays
,plusYears
,minusMonths
等等。 例如,要获取表示明天的LocalDate
,可以创建一个代表今天的LocalDat
e,然后调用其plusDays
方法。
LocalDate tomorrow = LocalDate.now().plusDays(1);
要获取昨天表示的LocalDate
,可以使用minusDays
方法。
LocalDate yesterday = LocalDate.now().minusDays(1);
另外,还有plus
和minus
方法以更通用的方式获得LocalDate
的拷贝。 两者都接受一个int参数和一个TemporalUnit
参数。 这些方法的签名如下。
public LocalDate plus(long amountToAdd,
java.time.temporal.TemporalUnit unit)
public LocalDate minus(long amountToSubtract,
java.time.temporal.TemporalUnit unit)
例如,获得一个从今天开始前20年的LocalDate
,可以使用这段代码。
LocalDate pastDate = LocalDate.now().minus(2, ChronoUnit.DECADES);
ChronoUnit
是一个实现TemporalUnit
的枚举,因此可以将ChronoUnit
常量传递给plus
和minus
方法。
LocalDate
是不可变的,因此无法更改。 任何返回LocalDate
的方法都返回LocalDate
的新实例。
以下是使用LocalDate
的例子。
import java.time.LocalDate;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
public class LocalDateDemo1 {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);
LocalDate oneDecadeAgo = today.minus(1, ChronoUnit.DECADES);
System.out.println("Day of month: " + today.getDayOfMonth());
System.out.println("Today is " + today);
System.out.println("Tomorrow is " + tomorrow);
System.out.println("A decade ago was " + oneDecadeAgo);
System.out.println("Year : " + today.get(ChronoField.YEAR));
System.out.println("Day of year:" + today.getDayOfYear());
}
}
Period类
Period
类基于日期的时间数量构建,例如五天,一周或三年。 下面列出了一些重要的方法。
方法 | 描述 |
---|---|
between | 在两个LocalDates之间创建一个Period示例 |
ofDays, ofWeeks, ofMonths, ofYears | 创建代表给定天数/周/月/年的Period实例 |
of | 根据给定的年数,月数和天数创建一个Period实例 |
getDays, getMonths, getYears | 以int形式返回此Period的天数/月/年 |
isNegative | 如果此Period的三个部分中的任何一个为负数,则返回true。 否则返回false |
isZero | 如果此Period的所有三个部分均为零,则返回true。 否则,返回false |
plusDays, minusDays | 在此Period上添加或减去给定的天数 |
plusMonths, minusMonths | 在此Period上增加或减去给定的月数 |
plusYears, minusYears | 在此Period增加或减去给定的年数 |
withDays | 以指定的天数返回此Period的拷贝 |
withMonths | 以指定的月数返回此Period的拷贝 |
withYears | 以指定的年数返回此Period的拷贝 |
创建一个Period
很简单,这要感谢between
,of
,ofDays
/ ofWeeks
/ ofMonths
/ ofYears
等静态工厂方法。 例如,以下是如何创建代表两周的Period
实例。
Period twoWeeks = Period.ofWeeks(2);
要创建代表一年两个月三天的Period
实例,请使用of
方法。
Period p = Period.of(1, 2, 3);
要获取某个期间的年/月/日组件,调用其getYears
/ getMonths
/ getDays
方法。 例如,以下代码中的howManyDays
变量的值是14。
Period twoWeeks = Period.ofWeeks(2);
int howManyDays = twoWeeks.getDays();
最后,可以使用plusXXX
或minusXXX
方法以及withXXX
方法来创建Period
的拷贝。 Period
是不可变的,所以这些方法返回新的Period
实例。
例如,下面的代码显示了一个计算个人年龄的年龄计算器。 它从两个LocalDate
创建一个Period
并调用它的getDays
,getMonths
和getYears
方法。
import java.time.LocalDate;
import java.time.Period;
public class PeriodDemo1 {
public static void main(String[] args) {
LocalDate dateA = LocalDate.of(1978, 8, 26);
LocalDate dateB = LocalDate.of(1988, 9, 28);
Period period = Period.between(dateA, dateB);
System.out.printf("Between %s and %s"
+ " there are %d years, %d months"
+ " and %d days%n", dateA, dateB,
period.getYears(),
period.getMonths(),
period.getDays());
}
}
运行PeriodDemo1
类打印下面字符串。
Between 1978-08-26 and 1988-09-28 there are 10 years, 1 months and 2 days
LocalDateTime类
LocalDateTime
类是一个没有时区的日期时间的构建。 下表显示了LocalDateTime
中一些重要的方法。 这些方法类似于LocalDate
的方法,以及用于修改时间部分的一些其他方法,例如在LocalDate
中不可用的plusHours
,plusMinutes
和plusSeconds
。
方法 | 描述 |
---|---|
now | 返回当前日期和时间的静态方法。 |
of | 从指定年份,月份,日期,小时,分钟,秒和毫秒创建LocalDateTime的静态方法。 |
getYear, getMonthValue, getDayOfMonth, getHour, getMinute, getSecond | 以int形式返回此LocalDateTime的年,月,日,小时,分钟或秒部分。 |
plusDays, minusDays | 给当前LocalDateTime添加或减去指定的天数。 |
plusWeeks, minusWeeks | 给当前LocalDateTime添加或减去指定的周数。 |
plusMonths, minusMonths | 给当前LocalDateTime添加或减去指定的月数。 |
plusYears, minusYears | 给当前LocalDateTime添加或减去指定的年数。 |
plusHours, minusHours | 给当前LocalDateTime添加或减去指定的小时数 |
plusMinutes, minusMinutes | 给当前LocalDateTime添加或减去指定的分钟数 |
plusSeconds, minusSeconds | 给当前LocalDateTime添加或减去指定的秒数 |
IsAfter, isBefore | 检查此LocalDateTime是否在指定的日期时间之后或之前 |
withDayOfMonth | 返回此LocalDateTime的拷贝,并将月份中的某天设置为指定值 |
withMonth, withYear | 返回此LocalDateTime的拷贝,其月或年设置为指定值 |
withHour, withMinute, withSecond | 返回此LocalDateTime的拷贝,其小时/分钟/秒设置为指定值 |
LocalDateTime
提供了各种静态方法来创建日期时间。 该方法现在带有三个重载方法返回当前的日期时间。 无参的方法是最容易使用的:
LocalDateTime now = LocalDateTime.now();
要创建具有特定日期和时间的LocalDateTime
,请使用of
方法。 此方法有多个重载,并允许传递日期时间或LocalDate
和LocalTime
的单个部分。 以下是一些方法的签名。
public static LocalDateTime of(int year, int month, int dayOfMonth,
int hour, int minute)
public static LocalDateTime of(int year, int month, int dayOfMonth,
int hour, int minute)
public static LocalDateTime of(int year, Month month,
int dayOfMonth, int hour, int minute)
public static LocalDateTime of(int year, Month month,
int dayOfMonth, int hour, int minute)
public static LocalDateTime of(LocalDate date, LocalTime time)
例如,下面的代码段创建一个LocalDateTime
,代表2015年12月31日早上八点。
LocalDateTime endOfYear = LocalDateTime.of(2015, 12, 31, 8, 0);
可以使用plusXXX
或minusXXX
方法创建LocalDateTime
的拷贝。 例如,此代码创建一个LocalDateTime
,它表示明天的同一时间。
LocalDateTime now = LocalDateTime.now();
LocalDateTime sameTimeTomorrow = now.plusHours(24);
Time Zones
互联网数字分配机构(IANA)维护一个可从此网页下载的时区数据库:
[http://www.iana.org/time-zones](http://www.iana.org/time-zones)
但为了便于查看,可以访问此Wikipedia页面:
http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
Java日期和时间API也适用于时区。 抽象类ZoneId
(在java.time
包中)表示一个区域标识符。 它有一个名为getAvailableZoneIds
的静态方法,它返回所有区域标识符。 下面展示了如何使用这种方法打印所有时区的排序列表。
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class TimeZoneDemo1 {
public static void main(String[] args) {
Set<String> allZoneIds = ZoneId.getAvailableZoneIds();
List<String> zoneList = new ArrayList<>(allZoneIds);
Collections.sort(zoneList);
for (String zoneId : zoneList) {
System.out.println(zoneId);
}
// alternatively, you can use this line of code to
// print a sorted list of zone ids
// ZoneId.getAvailableZoneIds().stream().sorted().
// forEach(System.out::println);
}
}
getAvailableZoneIds
返回字符串的Set
集合。 可以使用Collections.sort()
或更优雅地通过调用它的stream
方法对Set
进行排序。 可以编写此代码对区域标识符进行排序。
ZoneId.getAvailableZoneIds().stream().sorted()
.forEach(System.out::println);
getAvailableZoneIds
返回586个区域标识符的Set
集合。 以下是上述代码中的一部分区域标识符。
Africa/Cairo
Africa/Johannesburg
America/Chicago
America/Los_Angeles
America/Mexico_City
America/New_York
America/Toronto
Antarctica/South_Pole
Asia/Hong_Kong
Asia/Shanghai
Asia/Tokyo
Australia/Melbourne
Australia/Sydney
Canada/Atlantic
Europe/Amsterdam
Europe/London
Europe/Paris
US/Central
US/Eastern
US/Pacific
ZonedDateTime
ZonedDateTime
类以一个时区为日期时间的构建。例如,以下是一个时区的日期时间:
2015-12-31T10:59:59+01:00 Europe/Paris
ZonedDateTime
始终是不可变的,时间分量的存储精度为纳秒。
ZonedDateTIme
中一些重要方法的使用与LocalDateTime
类似,只是多了一个时区的概念。可自行查阅API。
像LocalDateTime
一样,ZonedDateTime
类现在提供静态now
和of
方法,并构造一个ZonedDateTime
实例。 now
方法创建一个ZonedDateTime
代表执行的日期和时间。 无参now
方法会使用计算机的默认时区创建ZonedDateTime
。
ZonedDateTime now = ZonedDateTime.now();
now
的另一个重载方法允许传递区域标识符:
ZonedDateTime parisTime =
ZonedDateTime.now(ZoneId.of("Europe/Paris"));
of
方法也有好几个重载的方法。在所有情况下,都需要传递区域标识符。 第一个重载方法允许传递时区日期时间的每个部分,从年份到纳秒。
public static ZonedDateTime of(int year, int month, int dayOfMonth,
int hour, int minute, int second, int nanosecond,
ZoneId zone)
of
方法的第二个重载方法需要LocalDate
,LocalTime
和ZoneId
参数:
public static ZonedDateTime of(LocalDate date, LocalTime time,
ZoneId zone)
of
方法的最后一个重载方法需要LocalDateTime
和ZoneId
参数。
public static ZonedDateTime of(LocalDateTime datetime, ZoneId zone)
像LocalDate
和LocalDateTime
一样,ZonedDateTime
提供了使用plusXXX
,minusXXX
和withXXX
方法创建实例拷贝的方法。
例如,下面代码行创建一个带默认时区的ZonedDateTime
,并调用它的minusDays
方法以在三天前创建相同的ZonedDateTime
。
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime threeDaysEarlier = now.minusDays(3);
Duration
Duration
类是基于时间的持续时间的构建。 它与Period
类似,不同之处在于Duration
的时间分量为纳秒精度,并考虑了ZonedDateTime
实例之间的时区。 下表显示了Duration
中重要的方法。
方法 | 描述 |
---|---|
between | 在两个时差的对象之间创建一个Duration实例,例如在两个LocalDateTime或两个ZonedDateTime之间。 |
ofYears, ofMonths, ofWeeks, ofDays, ofHours, ofMinutes, ofSeconds, ofNano | 创建给定年数/月/周/天/小时/分钟/秒/纳秒的Duration实例 |
of | 根据指定数量的时间单位创建Duration实例 |
toDays, toHours, toMinutes | 以int形式返回此Duration的天数/小时/分钟数 |
isNegative | 如果此Duration为负,则返回true。 否则返回false。 |
isZero | 如果此Duration长度为零,则返回true。 否则,返回false |
plusDays, minusDays | 在此Duration内添加或减去指定的天数。 |
plusMonths, minusMonths | 在此Duration内添加或减去指定的月数。 |
plusYears, minusYears | 在Duration内添加或减去指定的年数 |
withSeconds | 以指定的秒数返回此Duration的拷贝。 |
可以通过调用静态方法between
或of
来创建Duration
。 下面的代码会在2015年1月26日11:10至2015年1月26日12:40之间创建两个LocalDateTime
的Duration
。
import java.time.Duration;
import java.time.LocalDateTime;
public class DurationDemo1 {
public static void main(String[] args) {
LocalDateTime dateTimeA = LocalDateTime
.of(2015, 1, 26, 8, 10, 0, 0);
LocalDateTime dateTimeB = LocalDateTime
.of(2015, 1, 26, 11, 40, 0, 0);
Duration duration = Duration.between(
dateTimeA, dateTimeB);
System.out.printf("There are %d hours and %d minutes.%n",
duration.toHours(),
duration.toMinutes() % 60);
}
}
运行DurationDemo1
类的结果是这样的。
There are 3 hours and 30 minutes.
下面的代码在两个ZoneDateTime
之间创建一个Duration
,具有相同的日期和时间,但时区不同。
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class DurationDemo2 {
public static void main(String[] args) {
ZonedDateTime zdt1 = ZonedDateTime.of(
LocalDateTime.of(2015, Month.JANUARY, 1,
8, 0),
ZoneId.of("America/Denver"));
ZonedDateTime zdt2 = ZonedDateTime.of(
LocalDateTime.of(2015, Month.JANUARY, 1,
8, 0),
ZoneId.of("America/Toronto"));
Duration duration = Duration.between(zdt1, zdt2);
System.out.printf("There are %d hours and %d minutes.%n",
duration.toHours(),
duration.toMinutes() % 60);
}
}
运行DurationDemo2
类在控制台上打印如下结果。
There are -2 hours and 0 minutes.
这是预料之中的,因为时区America/Denver
和America/Toronto
之间有两个小时的差异。
作为一个更复杂的例子,下面的代码显示了一个公交车旅行时间计算器。 它有一个方法calculateTravelTime
,它需要一个离开的ZonedDateTime
实例和一个到达的ZonedDateTime
实例。 该代码调用calculateTravelTime
方法两次。 这两次公交车都在丹佛早上8点从科罗拉多州丹佛出发,并于多伦多时间第二天早上8点抵达多伦多。 公交车首次于2014年3月8日启程,第二次于2014年3月18日启程。
两种情况下的旅行时间是多少?
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class TravelTimeCalculator {
public Duration calculateTravelTime(
ZonedDateTime departure, ZonedDateTime arrival) {
return Duration.between(departure, arrival);
}
public static void main(String[] args) {
TravelTimeCalculator calculator =
new TravelTimeCalculator();
ZonedDateTime departure1 = ZonedDateTime.of(
LocalDateTime.of(2014, Month.MARCH, 8,
8, 0),
ZoneId.of("America/Denver"));
ZonedDateTime arrival1 = ZonedDateTime.of(
LocalDateTime.of(2014, Month.MARCH, 9,
8, 0),
ZoneId.of("America/Toronto"));
Duration travelTime1 = calculator
.calculateTravelTime(departure1, arrival1);
System.out.println("Travel time 1: "
+ travelTime1.toHours() + " hours");
ZonedDateTime departure2 = ZonedDateTime.of(
LocalDateTime.of(2014, Month.MARCH, 18,
8, 0),
ZoneId.of("America/Denver"));
ZonedDateTime arrival2 = ZonedDateTime.of(
LocalDateTime.of(2014, Month.MARCH, 19,
8, 0),
ZoneId.of("America/Toronto"));
Duration travelTime2 = calculator
.calculateTravelTime(departure2, arrival2);
System.out.println("Travel time 2: "
+ travelTime2.toHours() + " hours");
}
}
运行结果为:
Travel time 1: 21 hours
Travel time 2: 22 hours
为什么有这个区别? 因为2014年的夏令时从3月9日星期日凌晨2点开始。 因此,在2014年3月8日至2014年3月9日之间“失去”了一小时。
Formatting A Date-Time
可以使用java.time.format.DateTimeFormatter
格式化本地或时区日期时间。LocalDate
,LocalDateTime
,LocalTime
和ZoneDateTime
类提供具有以下签名的格式方法。
public java.lang.String format(java.time.format.DateTimeFormatter
formatter)
很明显,要格式化日期或时间,必须首先创建DateTimeFormatter
实例。
下面的代码使用两个格式化实例格式化当前日期。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
public class DateTimeFormatterDemo1 {
public static void main(String[] args) {
DateTimeFormatter formatter1 = DateTimeFormatter
.ofLocalizedDateTime(FormatStyle.MEDIUM);
LocalDateTime example = LocalDateTime.of(
2000, 3, 19, 10, 56, 59);
System.out.println("Format 1: " + example
.format(formatter1));
DateTimeFormatter formatter2 = DateTimeFormatter
.ofPattern("MMMM dd, yyyy HH:mm:ss");
System.out.println("Format 2: " +
example.format(formatter2));
}
}
运行结果如下:(第一个结果取决于你的区域设置)。
Format 1: 19-Mar-2000 10:56:59 AM
Format 2: March 19, 2000 10:56:59
Parsing A Date-Time
在Java Date和Time API的许多类中有两种parse
方法。第一个需要格式化实例,第二个则不需要。后一个方法会根据默认模式解析日期时间。要使用自己的格式化模式,请使用DateTimeFormatter
。如果传递的字符串不能被解析,那么解析方法将抛出一个DateTimeParseException
。
import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Scanner;
public class AgeCalculator {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-M-d");
public Period calculateAge(LocalDate birthday) {
LocalDate today = LocalDate.now();
return Period.between(birthday, today);
}
public LocalDate getBirthday() {
Scanner scanner = new Scanner(System.in);
LocalDate birthday;
while (true) {
System.out.println("Please enter your birthday "
+ "in yyyy-MM-dd format (e.g. 1980-9-28): ");
String input = scanner.nextLine();
try {
birthday = LocalDate.parse(input, formatter);
return birthday;
} catch(DateTimeParseException e) {
System.out.println("Error! Please try again");
}
}
}
public static void main(String[] args) {
AgeCalculator ageCalculator = new AgeCalculator();
LocalDate birthday = ageCalculator.getBirthday();
Period age = ageCalculator.calculateAge(birthday);
System.out.printf("Today you are %d years, %d months"
+ " and %d days old%n",
age.getYears(), age.getMonths(), age.getDays());
}
}
AgeCalculator
类有两个方法,getBirthday
和calculateAge
。 getBirthday
方法使用Scanner
类来读取用户输入,并使用DateTimeFormatter
类将输入解析到LocalDate
中。 getBirthday
方法一直请求一个日期,直到用户输入正确格式的日期,在这种情况下,方法返回。 calculateAge
方法需要一个生日,并在生日和今天的日期之间创建一个Period
实例。
如果运行这个例子,会在控制台上看到这个。
Please enter your birthday in yyyy-MM-dd format (e.g. 1980-9-28):
如果以正确的格式输入日期,则程序将打印计算的年龄,如下所示。
Today you are 79 years, 0 months and 15 days old
Summary
Java 8带来了全新的Date-Time API来替代以java.util.Date
类为中心的旧的API。 通过本篇文章,学习如何使用新API中的核心类,如Instant
,LocalDate
,LocalDateTime
,ZonedDateTime
,Period
和Duration
,以及学习如何格式化和解析日期时间。