旧API的问题:
在Java 1.0中,对日期和时间的支持只能依赖java.util.Date
类。 存在的问题有:
- 年份的起始选择是1900年
- 月份的起始从0开始
在Java 1.1中, Date类中的很多方法被废弃了,取而代之的是java.util.Calendar
类 ,仍然有很多问题:
- 月份依旧是从0开始计算
- 同时存在Date和Calendar这两个类,增加了程序员的疑惑
- 有的特性只在某一个类有提供,比如DateFormat只在Date类里有
- DateFormat不是线程安全的
- Date和Calendar类都是可变的
所有这些缺陷和不一致导致用户们转投第三方的日期和时间库,比如Joda-Time。为了解决这些问题, Oracle决定在原生的Java API中提供高质量的日期和时间支持。所以,你会看到Java 8在java.time包中整合了很多Joda-Time的特性。
先给出几个新API的示例,可以看到,Java8中对时间、日期的操作是非常优雅和易于理解的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| LocalDate today = LocalDate.now();
LocalDate today = LocalDate.now(); LocalDate someDay = LocalDate.of(2021, Month.MAY, 14); Period period = Period.between(today, someDay);
System.out.printf("interval : %d years, %d months, %d days ", period.getYears(), period.getMonths(), period.getDays());
LocalDate tomorrow = today.plusDays(1);
LocalDate secondDay = today.withDayOfMonth(1);
LocalDate lastDay = today.with(TemporalAdjusters.lastDayOfMonth());
LocalDate firstMonday = today.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
LocalTime now = LocalTime.now();
LocalTime then = now.plusHours(2);
long window = Duration.ofMinutes(45).toMillis();
|
时间、日期、时间戳
LocalDate
该类的实例是一个不可变对象,它只包含日期,并不含时间。另外,它不附带时区信息。
1 2 3 4 5 6 7 8 9 10
| LocalDate date = LocalDate.of(2014, 3, 18); int year = date.getYear(); Month month = date.getMonth(); int day = date.getDayOfMonth(); DayOfWeek dow = date.getDayOfWeek(); int len = date.lengthOfMonth(); boolean leap = date.isLeapYear();
LocalDate today = LocalDate.now();
|
LocalTime
一天中的时间,比如13:45:20,可以使用LocalTime类表示 :
1 2 3 4
| LocalTime time = LocalTime.of(13, 45, 20); int hour = time.getHour(); int minute = time.getMinute(); int second = time.getSecond();
|
LocalDate和LocalTime都可以通过字符串创建:
1 2
| LocalDate date = LocalDate.parse("2014-03-18"); LocalTime time = LocalTime.parse("13:45:20");
|
LocalDateTime
LocalDateTime,是LocalDate和LocalTime的合体。它同时表示了日期和时间,但不带有时区信息,你可以直接创建,也可以通过合并日期和时间对象构造,如下所示。
1 2 3 4 5 6 7 8 9 10 11
|
LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20); LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(13, 45, 20); LocalDateTime dt4 = date.atTime(time); LocalDateTime dt5 = time.atDate(date);
LocalDate date1 = dt1.toLocalDate(); LocalTime time1 = dt1.toLocalTime();
|
时间戳类Instant
Unix元年时间(UTC时区1970年1月1日午夜时分)开始所经历的秒数
1 2 3 4 5 6 7 8 9 10 11
| Instant.now();
Instant.ofEpochSecond(3); Instant.ofEpochSecond(3, 0); Instant.ofEpochSecond(2, 1_000_000_000); Instant.ofEpochSecond(4, -1_000_000_000);
|
可以通过Duration和Period类使用Instant 。
Duration 、Period
Duration
类主要用于以秒和纳秒衡量时间的长短 。
1 2 3 4
| Duration d1 = Duration.between(time1, time2); Duration d1 = Duration.between(dateTime1, dateTime2); Duration d2 = Duration.between(instant1, instant2);
|
- 由于LocalDateTime和Instant是为不同的目的而设计的,一个是为了便于人阅读使用,另一个是为了便于机器处理,所以你不能将二者混用。如果你试图在这两类对象之间创建duration,会触发一个DateTimeException异常。
- Duration类主要用于以秒和纳秒衡量时间的长短,所以不能向between方法传递LocalDate对象做参数。
使用Period
类,得到两个LocalDate之间的时长
1 2
| Period tenDays = Period.between(LocalDate.of(2014, 3, 8), LocalDate.of(2014, 3, 18));
|
最后, Duration和Period类都提供了很多非常方便的工厂类,直接创建对应的实例 。
1 2 3 4 5 6
| Duration threeMinutes = Duration.ofMinutes(3); Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);
Period tenDays = Period.ofDays(10); Period threeWeeks = Period.ofWeeks(3); Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
|
操纵、解析和格式化日期
截至目前,我们介绍的这些日期/时间对象都是不可变的 。
如果你已经有一个LocalDate对象,想要创建它的一个修改版副本,最直接也最简单的方法是使用withAttribute方法。
1 2 3 4
| LocalDate date1 = LocalDate.of(2014, 3, 18); LocalDate date2 = date1.withYear(2011); LocalDate date3 = date2.withDayOfMonth(25); LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);
|
或者以声明的方式操纵LocalDate对象
1 2 3 4
| LocalDate date1 = LocalDate.of(2014, 3, 18); LocalDate date2 = date1.plusWeeks(1); LocalDate date3 = date2.minusYears(3); LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);
|
TemporalAdjuster
有的时候,你需要进行一些更加复杂的操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时,你可以使用重载版本的with方法,向其传递一个提供了更多定制化选择的TemporalAdjuster对象
1 2 3 4
| import static java.time.temporal.TemporalAdjusters.*; LocalDate date1 = LocalDate.of(2014, 3, 18); LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY)); LocalDate date3 = date2.with(lastDayOfMonth());
|
另外,还也已创建自定义的TemporalAdjuster,例如获得当前日期的下一个工作日。见《Java 8 in Action》。
打印输出及解析日期、时间对象
和老的java.util.DateFormat相比较,所有的DateTimeFormatter实例都是线程安全
的。所以,你能够以单例模式创建格式器实例,就像DateTimeFormatter预定义的那些格式器常量,并能在多个线程间共享这些实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| LocalDate date = LocalDate.of(2014, 3, 18); String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
LocalDate date1 = LocalDate.parse("20140318",DateTimeFormatter.BASIC_ISO_DATE); LocalDate date2 = LocalDate.parse("2014-03-18",DateTimeFormatter.ISO_LOCAL_DATE);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); LocalDate date1 = LocalDate.of(2014, 3, 18); String formattedDate = date1.format(formatter); LocalDate date2 = LocalDate.parse(formattedDate, formatter);
DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN); LocalDate date1 = LocalDate.of(2014, 3, 18); String formattedDate = date.format(italianFormatter); LocalDate date2 = LocalDate.parse(formattedDate, italianFormatter);
|
最后,如果你还需要更加细粒度的控制, DateTimeFormatterBuilder类还提供了更复杂
的格式器,你可以选择恰当的方法,一步一步地构造自己的格式器。另外,它还提供了非常强大的解析功能,比如区分大小写的解析、柔性解析(允许解析器使用启发式的机制去解析输入,不精 确 地 匹 配 指 定 的 模 式 )、 填 充 ,以及在格式器中指 定可选节 。比 如 , 你可以通 过DateTimeFormatterBuilder 自己编程实现italianFormatter,代码清单如下。
1 2 3 4 5 6 7 8
| DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder() .appendText(ChronoField.DAY_OF_MONTH) .appendLiteral(". ") .appendText(ChronoField.MONTH_OF_YEAR) .appendLiteral(" ") .appendText(ChronoField.YEAR) .parseCaseInsensitive() .toFormatter(Locale.ITALIAN);
|
时区
日期和时间的类都不包含时区信息。
时区的处理是新版日期和时间API新增加的重要功能,使用新版日期和时间API时区的处理被极大地简化了。新的java.time.ZoneId类是老版java.util.TimeZone的替代品。
可以通过调用ZoneId的getRules()得到指定时区。每个特定的ZoneId对象都由一个地区ID标识,比如:
ZoneId romeZone = ZoneId.of("Europe/Rome);
地区ID都为“{区域}/{城市}”的格式,这些地区集合的设定都由英特网编号分配机构(IANA)的时区数据库提供。
还可以通过Java 8的新方法toZoneId将一个老的时区对象转换为ZoneId:
ZoneId zoneId = TimeZone.getDefault().toZoneId();
一旦得到一个ZoneId对象,你就可以将它与LocalDate、 LocalDateTime或者是Instant对象整合起来,构造为一个ZonedDateTime实例,它代表了指定时区的时间点,代码清单如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| LocalDate date = LocalDate.of(2014, Month.MARCH, 18); ZonedDateTime zdt1 = date.atStartOfDay(romeZone); LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); ZonedDateTime zdt2 = dateTime.atZone(romeZone); Instant instant = Instant.now(); ZonedDateTime zdt3 = instant.atZone(romeZone);
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); Instant instantFromDateTime = dateTime.toInstant(romeZone);
Instant instant = Instant.now(); LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone);
|
Comment