日期时间API (Date/Time API )
文 | 莫若吻
1.Java8之前java.util.Date和Calendar类的弊端
1)最开始的时候,Date既要承载日期信息,又要做日期之间的转换,还要做不同日期格式的显示,职责较繁杂(不遵守单一职责)。
后来从JDK 1.1 开始,这三项职责分开了:
- 使用Calendar类实现日期和时间字段之间转换;
- 使用DateFormat类来格式化和分析日期字符串;
- Date只用来承载日期和时间信息。
现在原有Date中的相应方法已废弃。无论是Date,还是Calendar,都使用着太不方便,这是API没有设计好的地方。
2)令人无语的year和month(month是从0开始的)
eg:
- Date date = new Date(2016,1,1);
- System.out.println(date);
输出结果:Tue Feb 01 00:00:00 CST 3916
这样得到的结果year为2012+1900,而month明明给定的参数是1,却输出的是二月。设置日期可以用java.util.Calendar
- Calendar calendar = Calendar.getInstance();
- calendar.set(2016, 5, 2);
虽然Calendar年份的传值不需要减去1900,但Calendar的month也是从0开始的,表达5月份应该用4这个数字。
3)java.util.Date与java.util.Calendar中的所有属性都是可变的,且线程不安全。
eg:
- public class Test {
-
- public static void main(String[] args) {
- Calendar birth = Calendar.getInstance();
- birth.set(1975, Calendar.MAY, 26);
- Calendar now = Calendar.getInstance();
- System.out.println(daysBetween(birth, now));
- System.out.println(daysBetween(birth, now));
-
- }
- }
- public static long daysBetween(Calendar begin, Calendar end) {
- long daysBetween = 0;
- while(begin.before(end)) {
- begin.add(Calendar.DAY_OF_MONTH, 1);
- daysBetween++;
- }
- return daysBetween;
- }
-
- }
Note:daysBetween有点问题,如果连续计算两个Date实例的话,第二次会取得0,因为Calendar状态是可变的,考虑到重复计算的场合,最好复制一个新的Calendar。修改代码如下
- public static long daysBetween(Calendar begin, Calendar end) {
- Calendar calendar = (Calendar) begin.clone();
- long daysBetween = 0;
- while(calendar.before(end)) {
- calendar.add(Calendar.DAY_OF_MONTH, 1);
- daysBetween++;
- }
- return daysBetween;
- }
2.简述 新的日期时间API
Java 的日期与时间 API 问题由来已久,Java 8 之前的版本中关于时间、日期及其他时间日期格式化类由于线程安全、重量级、序列化成本高等问题而饱受批评。Java 8 吸收了 Joda-Time 的精华,以一个新的开始为 Java 创建优秀的 API。新的 java.time 中包含了所有关于时钟(Clock),本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了了日期时间和本地化的管理。
目前Java8新增了java.time包定义的类表示日期-时间概念的规则,很方便使用;最重要的一点是值不可变,且线程安全。
下图是java.time包下的一些主要的类的日期时间 值的格式,方便理解使用:
Note:不过尽管有了新的API,但仍有一个严重的问题——大量的旧代码和库仍然在使用老的API。现在,Java 8解决了这个问题,它给Date类增加了一个新的方法toInstant(),可以将Date转化成新的实例。这样就可以切换到新的API。
对于新API:
非常有用的值类型:
Instant ----- 与java.util.Date相似
ZonedDateTime ----- ZoneId -时区很重要的时候使用
OffsetDateTime ----- OffsetTime, ZoneOffset -对UTC的偏移处理
Duration, Period ----- 但如果你想找到两个日期之间的时间量,你可能会寻找ChronoUnit代替(详情见下文)
其他有用的类型:
DateTimeFormatter ----- 将日期类型转换成字符串类型
ChronoUnit ----- 计算出两点之间的时间量,例如ChronoUnit.DAYS.between(t1, t2)
TemporalAdjuster ----- 例如date.with(TemporalAdjuster.firstDayOfMonth())
Note:大多数情况下,新的值类型由JDBC提供支持。有一小部分异常,eg:ZonedDateTime在SQL中没有对应的(类型)。
3.Java 新旧日期API的区别
4.java.time包下的类
4.1 Clock类
Clock类提供了访问当前日期和时间的方法。Clock使用时区来访问当前的instant, date和time。Clock类可以替换 System.currentTimeMillis() 和 TimeZone.getDefault()。
eg:
-
- Clock clock1 = Clock.systemDefaultZone();
- System.out.println( "系统时间日期:"+clock1.instant() );
- System.out.println( "时间毫秒:"+clock1.millis() );
-
- final Clock clock = Clock.systemUTC();
- System.out.println( "时间日期:"+clock.instant() );
- System.out.println( "时间毫秒值:"+clock.millis() );
输出结果:
系统时间日期:2016-05-12T07:42:37.883Z
时间毫秒:1463038957894
时间日期:2016-05-12T07:42:37.894Z
时间毫秒值:1463038957894
某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。
eg:
- Instant instant = clock1.instant();
- Date javadate = Date.from(instant);
- System.out.println( "date:"+javadate);
输出结果:
date:Thu May 12 15:47:00 CST 2016
4.2 ZoneId(时区)
在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of()来获取到。时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。
eg:
-
- System.out.println(ZoneId.getAvailableZoneIds());
-
- ZoneId zone1 = ZoneId.of("Europe/Berlin");
- ZoneId zone2 = ZoneId.of("Brazil/East");
- System.out.println(zone1.getRules());
- System.out.println(zone2.getRules());
-
-
4.3 LocalTime(本地时间)
LocalTime 定义了一个没有时区信息的时间。
eg:
1)获取现在的本地时间
-
- final LocalTime time = LocalTime.now();
- final LocalTime timeFromClock = LocalTime.now( clock );
- System.out.println( time );
- System.out.println( timeFromClock );
输出结果:16:03:23.212
08:03:23.212
2)按时区显示时间
- ZoneId zone1 = ZoneId.of("Europe/Berlin");
- ZoneId zone2 = ZoneId.of("Brazil/East");
-
- LocalTime now1 = LocalTime.now(zone1);
- LocalTime now2 = LocalTime.now(zone2);
- System.out.println("时区:Europe/Berlin---"+now1);
- System.out.println("时区:Brazil/East---"+now2);
输出结果:
时区:Europe/Berlin---10:03:23.217
时区:Brazil/East---05:03:23.217
LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。
eg:
- LocalTime late = LocalTime.of(22, 12, 18);
- System.out.println(late);
-
- DateTimeFormatter germanFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
- .withLocale(Locale.GERMAN);
-
- LocalTime leetTime = LocalTime.parse("15:39", germanFormatter);
- System.out.println(leetTime);
4.4 LocalDate(本地日期)
LocalDate 表示了一个确切的日期(eg: 2014-03-11)。该对象值是不可变的,使用方式和LocalTime基本一致。
eg:
- Clock clock = Clock.systemDefaultZone();
-
-
- final LocalDate date = LocalDate.now();
- final LocalDate dateFromClock = LocalDate.now(clock);
- System.out.println(date);
- System.out.println(dateFromClock);
输出结果:2016-05-12
2016-05-12
从字符串解析一个LocalDate类型和解析LocalTime一样简单.
eg:
- DateTimeFormatter germanFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
- .withLocale(Locale.GERMAN);
-
- LocalDate xmas = LocalDate.parse("25.10.2016", germanFormatter);
- System.out.println(xmas);
输出结果:2016-10-25
4.5 LocalDateTime(本地日期时间)
表示了具体时间和日期。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。
eg:
1)
- Clock clock = Clock.systemDefaultZone();
-
-
- final LocalDateTime datetime = LocalDateTime.now();
- final LocalDateTime datetimeFromClock = LocalDateTime.now(clock);
- System.out.println(datetime);
- System.out.println(datetimeFromClock);
输出结果:2016-05-12T16:33:17.546
2016-05-12T16:33:17.546
2)
- LocalDateTime sylvester = LocalDateTime.of(2016, Month.DECEMBER, 31, 23, 59, 59);
-
- DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
- System.out.println(dayOfWeek);
-
- Month month = sylvester.getMonth();
- System.out.println(month);
-
- long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
- System.out.println(minuteOfDay);
输出结果:
SATURDAY
DECEMBER
1439
只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。
eg:
- LocalDateTime sylvester = LocalDateTime.of(2016, Month.DECEMBER, 31, 23, 59, 59);
- Instant instant = sylvester
- .atZone(ZoneId.systemDefault())
- .toInstant();
-
- Date legacyDate = Date.from(instant);
- System.out.println(legacyDate);
输出结果:
Sat Dec 31 23:59:59 CST 2016
格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我也可以自定义格式。
eg:
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM dd, yyyy - HH:mm");
- LocalDateTime parsed = LocalDateTime.parse("05 03, 2016 - 07:13", formatter);
- String string = formatter.format(parsed);
- System.out.println(string);
输出结果:
05 03, 2016 - 07:13
Note:和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。
4.6 ZonedDateTime(日期时间和时区信息)
使用ZonedDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。
eg:
- Clock clock = Clock.systemDefaultZone();
-
- final ZonedDateTime zonedDatetime = ZonedDateTime.now();
- final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now(clock);
- final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));
- System.out.println(zonedDatetime);
- System.out.println(zonedDatetimeFromClock);
- System.out.println(zonedDatetimeFromZone);
输出结果:
2016-05-12T16:59:55.779+08:00[Asia/Shanghai]
2016-05-12T16:59:55.779+08:00[Asia/Shanghai]
2016-05-12T01:59:55.781-07:00[America/Los_Angeles]
4.7 Duration类
Duration持有的时间精确到纳秒。很容易计算两个日期中间的差异。
eg:求时间差
-
- final LocalDateTime from = LocalDateTime.of(2014, Month.APRIL, 16, 0, 0, 0);
- final LocalDateTime to = LocalDateTime.of(2015, Month.APRIL, 16, 23, 59, 59);
- final Duration duration = Duration.between(from, to);
- System.out.println("Duration in days: " + duration.toDays());
- System.out.println("Duration in hours: " + duration.toHours());</span>
输出结果:
Duration in days: 365
Duration in hours: 8783
还有一种获取时间差值的方式:ChronoUnit
- ZoneId zone1 = ZoneId.of("America/Cuiaba");
- ZoneId zone2 = ZoneId.of("Brazil/East");
- LocalTime now1 = LocalTime.now(zone1);
- LocalTime now2 = LocalTime.now(zone2);
- long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
- long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
- System.out.println(hoursBetween);
- System.out.println(minutesBetween);
5.新日期时间API示例
eg:(注:感兴趣自己运行结果试试各种方法,很easy。)
- import java.time.Clock;
- import java.time.Duration;
- import java.time.Instant;
- import java.time.LocalDateTime;
- import java.time.ZoneId;
- import java.time.ZonedDateTime;
- import java.time.chrono.ChronoLocalDateTime;
- import java.time.chrono.Chronology;
- import java.time.chrono.HijrahChronology;
- import java.time.format.DateTimeFormatter;
- import java.time.temporal.IsoFields;
- import java.util.Date;
-
- public class TimeTest {
-
- public static void main(String[] args) throws InterruptedException {
- testClock();
-
-
-
-
-
-
- }
- public static void testClock() throws InterruptedException {
-
- Clock c1 = Clock.systemUTC();
- System.out.println(c1.millis());
-
-
- Clock c2 = Clock.systemDefaultZone();
-
- Clock c31 = Clock.system(ZoneId.of("Europe/Paris"));
- System.out.println(c31.instant());
- Clock c32 = Clock.system(ZoneId.of("Asia/Shanghai"));
- System.out.println(c32.instant());
-
- Clock c4 = Clock.fixed(Instant.now(), ZoneId.of("Asia/Shanghai"));
- System.out.println(c4.millis());
- Thread.sleep(1000);
- System.out.println(c4.millis());
-
- Clock c5 = Clock.offset(c1, Duration.ofSeconds(2));
- System.out.println(c1.millis());
- System.out.println(c5.millis());
- }
-
- public static void testInstant() {
-
- Instant instant1 = Instant.now();
- System.out.println(instant1.getEpochSecond());
-
- System.out.println(instant1.toEpochMilli());
- Clock clock1 = Clock.systemUTC();
- Instant instant2 = Instant.now(clock1);
- System.out.println(instant2.toEpochMilli());
- Clock clock2 = Clock.fixed(instant1, ZoneId.systemDefault());
- Instant instant3 = Instant.now(clock2);
- System.out.println(instant3.toEpochMilli());
- }
-
- public static void testLocalDateTime() {
-
-
- LocalDateTime now = LocalDateTime.now();
- System.out.println(now);
-
- LocalDateTime now2 = LocalDateTime.now(ZoneId.of("Europe/Paris"));
- System.out.println(now2);
-
- Clock clock = Clock.system(ZoneId.of("Asia/Dhaka"));
- LocalDateTime now3 = LocalDateTime.now(clock);
- System.out.println(now3);
-
-
- LocalDateTime d1 = LocalDateTime.of(2013, 12, 31, 23, 59);
-
- LocalDateTime d2 = LocalDateTime.of(2013, 12, 31, 23, 59, 59, 11);
-
- Instant instant = Instant.now();
- LocalDateTime d3 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
- System.out.println(d3);
-
- LocalDateTime d4 = LocalDateTime.parse("2013-12-31T23:59");
- System.out.println(d4);
- LocalDateTime d5 = LocalDateTime.parse("2013-12-31T23:59:59.999");
-
- System.out.println(d5);
-
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
- LocalDateTime d6 = LocalDateTime.parse("2013/12/31 23:59:59", formatter);
- System.out.println(formatter.format(d6));
-
- System.out.println(d6.getYear());
- System.out.println(d6.getMonth());
- System.out.println(d6.getDayOfYear());
- System.out.println(d6.getDayOfMonth());
- System.out.println(d6.getDayOfWeek());
- System.out.println(d6.getHour());
- System.out.println(d6.getMinute());
- System.out.println(d6.getSecond());
- System.out.println(d6.getNano());
-
- LocalDateTime d7 = d6.minusDays(1);
- LocalDateTime d8 = d7.plus(1, IsoFields.QUARTER_YEARS);
-
-
-
- }
-
- public static void testZonedDateTime() {
-
-
- ZonedDateTime now = ZonedDateTime.now();
- System.out.println(now);
- ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
- System.out.println(now2);
-
- ZonedDateTime z1 = ZonedDateTime.parse("2013-12-31T23:59:59Z[Europe/Paris]");
- System.out.println(z1);
- }
-
- public static void testDuration() {
-
- Duration d1 = Duration.between(Instant.ofEpochMilli(System.currentTimeMillis() - 12323123), Instant.now());
-
- System.out.println(d1.toDays());
- System.out.println(d1.toHours());
- System.out.println(d1.toMinutes());
- System.out.println(d1.toMillis());
- System.out.println(d1.toNanos());
-
- Duration d2 = Duration.ofDays(1);
- System.out.println(d2.toDays());
- }
-
- public static void testChronology() {
-
- Chronology c = HijrahChronology.INSTANCE;
- ChronoLocalDateTime d = c.localDateTime(LocalDateTime.now());
- System.out.println(d);
- }
-
-
-
-
- public static void testNewOldDateConversion() {
- Instant instant = new Date().toInstant();
- Date date = Date.from(instant);
- System.out.println(instant);
- System.out.println(date);
- }
-
-
- }