DateFormat方法也有它本身的问题。好比,它不是线程安全的。这意味着两个线程若是尝试使用同一个formatter解析日期,你可能会获得没法预期的结果。java
Java 8提供新的日期和时间API,LocalDate类实例是一个不可变对象,只提供简单的日期而且不含当天时间信息。此外也不附带任何与时区相关的信息。git
经过静态工厂方法of建立一个LocalDate实例。LocalDate实例提供了多种方法来读取经常使用的值,好比年份、月份、星期几等,以下所示。github
LocalDate localDate = LocalDate.of(2014, 3, 18);
int year = localDate.getYear();
Month month = localDate.getMonth();
int day = localDate.getDayOfMonth();
DayOfWeek dow = localDate.getDayOfWeek();
int len = localDate.lengthOfMonth();
boolean leap = localDate.isLeapYear();
// 使用工厂方法从系统时钟中获取当前的日期
LocalDate today = LocalDate.now();
System.out.println(String.format("year:%s\nmonth:%s\nday:%s\ndow:%s\nlen:%s\nleap:%s", year, month, day, dow, len, leap));
System.out.println(today);
结果:
year:2014
month:MARCH
day:18
dow:TUESDAY
len:31
leap:false
2019-03-27
复制代码
Java 8日期-时间类都提供了相似的工厂方法。经过传递TemporalField参数给get方法拿到一样的信息。TemporalField接口定义了如何访问temporal对象某个字段的值。ChronoField枚举实现TemporalField接口,可使用get方法获得枚举元素的值。数据库
int year = localDate.get(ChronoField.YEAR);
int month = localDate.get(ChronoField.MONTH_OF_YEAR);
int day = localDate.get(ChronoField.DAY_OF_MONTH);
复制代码
使用LocalTime类表示时间,可使用of重载的两个工厂方法建立LocalTime的实例。编程
LocalTime类也提供了一些get方法访问这些变量的值,以下所示。安全
LocalTime localTime = LocalTime.of(13, 45, 20);
int hour = localTime.getHour();
int minute = localTime.getMinute();
int second = localTime.getSecond();
System.out.println(String.format("hour:%s\nminute:%s\nsecond:%s", hour, minute, second));
打印结果:
hour:13
minute:45
second:20
复制代码
LocalDate和LocalTime均可以经过解析表明它们的字符串建立。使用静态方法parse能够实现:app
LocalDate date = LocalDate.parse("2019-03-27");
LocalTime time = LocalTime.parse("20:17:08");
复制代码
能够向parse方法传递一个DateTimeFormatter。该类的实例定义了如何格式化一个日期或者时间对象。用来替换老版java.util.DateFormat。 若是传递的字符串参数没法被解析为合法的LocalDate或LocalTime对象,这两个parse方法都会抛出一个继承自RuntimeException的DateTimeParseException异常。函数式编程
复合类LocalDateTime,是LocalDate和LocalTime的合体。它同时表示了日期和时间,不带有时区信息。能够直接建立,也能够经过合并日期和时间对象构造。函数
LocalTime time = LocalTime.of(21, 31, 50);
LocalDate date = LocalDate.of(2019, 03, 27);
LocalDateTime dt1 = LocalDateTime.of(2017, Month.NOVEMBER, 07, 22, 31, 51);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(22, 21, 14);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);
复制代码
建立LocalDateTime对象ui
也可使用toLocalDate或者toLocalTime方法,从LocalDateTime中提取LocalDate或者LocalTime组件:
LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();
复制代码
从计算机的角度来看,"2019年03月27日11:20:03"这样的方式是不容易理解的,计算机更加容易理解建模时间最天然的格式是表示一个持续时间段上某个点的单一大整型数。新的java.time.Instant类对时间建模的方式,基本上它是以Unix元年时间(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的秒数进行计算。
Instant.ofEpochSecond(3);
Instant.ofEpochSecond(3, 0);
// 2 秒以后再加上100万纳秒(1秒)
Instant.ofEpochSecond(2, 1_000_000_000);
// 4秒以前的100万纳秒(1秒)
Instant.ofEpochSecond(4, -1_000_000_000);
复制代码
Instant类也支持静态工厂方法now,它可以获取当前时刻的时间戳。
Instant now = Instant.now();
System.out.println(now);
2019-03-27T03:26:39.451Z
复制代码
Instant的设计初衷是为了便于机器使用,它包含的是由秒及纳秒所构成的数字。所以Instant没法处理那些咱们很是容易理解的时间单位。
int day = Instant.now().get(ChronoField.DAY_OF_MONTH);
它会抛出下面这样的异常:
Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: DayOfMonth
可是你能够经过Duration和Period类使用Instant,接下来咱们会对这部份内容进行介绍。
复制代码
全部类都实现了Temporal接口,该接口定义如何读取和操纵为时间建模的对象的值。若是须要建立两个Temporal对象之间的duration,就须要Duration类的静态工厂方法between。 能够建立两个LocalTimes对象、两个LocalDateTimes对象,或者两个Instant对象之间的duration:
LocalTime time1 = LocalTime.of(21, 50, 10);
LocalTime time2 = LocalTime.of(22, 50, 10);
LocalDateTime dateTime1 = LocalDateTime.of(2019, 03, 27, 21, 20, 40);
LocalDateTime dateTime2 = LocalDateTime.of(2019, 03, 27, 21, 20, 40);
Instant instant1 = Instant.ofEpochSecond(1000 * 60 * 2);
Instant instant2 = Instant.ofEpochSecond(1000 * 60 * 3);
Duration d1 = Duration.between(time1, time2);
Duration d2 = Duration.between(dateTime1, dateTime2);
Duration d3 = Duration.between(instant1, instant2);
// PT1H 相差1小时
System.out.println("d1:" + d1);
// PT2H 相差2小时
System.out.println("d2:" + d2);
// PT16H40M 相差16小时40分钟
System.out.println("d3:" + d3);
复制代码
LocalDateTime是为了便于人阅读使用,Instant是为了便于机器处理,因此不能将两者混用。若是在这两类对象之间建立duration,会触发一个DateTimeException异常。 此外,因为Duration类主要用于以秒和纳秒衡量时间的长短,你不能仅向between方法传递一个LocalDate对象作参数。
使用Period类以年、月或者日的方式对多个时间单位建模。使用该类的工厂方法between,可使用获得两个LocalDate之间的时长。
Period period = Period.between(LocalDate.of(2019, 03, 7), LocalDate.of(2019, 03, 17));
// 相差10天
System.out.println("Period between:" + period);
复制代码
Duration和Period类都提供了不少很是方便的工厂类,直接建立对应的实例。
Duration threeMinutes = Duration.ofMinutes(3);
Duration fourMinutes = Duration.of(4, ChronoUnit.MINUTES);
Period tenDay = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
Duration类和Period类共享了不少类似的方法,有兴趣的能够参考官网的文档。
复制代码
截至目前,咱们介绍的这些日期-时间对象都是不可修改的,这是为了更好地支持函数式编程,确保线程安全,保持领域模式一致性而作出的重大设计决定。 固然,新的日期和时间API也提供了一些便利的方法来建立这些对象的可变版本。好比,你可能但愿在已有的LocalDate实例上增长3天。除此以外,咱们还会介绍如何依据指定的模式, 好比dd/MM/yyyy,建立日期-时间格式器,以及如何使用这种格式器解析和输出日期。
若是已经有一个LocalDate对象,想要建立它的一个修改版,最直接也最简单的方法是使用withAttribute方法。withAttribute方法会建立对象的一个副本,并按照须要修改它的属性。
// 这段代码中全部的方法都返回一个修改了属性的对象。它们都不会修改原来的对象!
LocalDate date1 = LocalDate.of(2017, 12, 15);
LocalDate date2 = date1.withYear(2019);
LocalDate date3 = date2.withDayOfMonth(25);
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);
复制代码
它们都声明于Temporal接口,全部的日期和时间API类都实现这两个方法,它们定义了单点的时间,好比LocalDate、LocalTime、LocalDateTime以及Instant。更确切地说,使用get和with方法,咱们能够将Temporal对象值的读取和修改区分开。若是Temporal对象不支持请求访问的字段,它会抛出一个UnsupportedTemporalTypeException异常,好比试图访问Instant对象的ChronoField.MONTH_OF_YEAR字段,或者LocalDate对象的ChronoField.NANO_OF_SECOND字段时都会抛出这样的异常。
// 以声明的方式操纵LocalDate对象,能够加上或者减去一段时间
LocalDate date1 = LocalDate.of(2014, 10, 19);
LocalDate date2 = date1.plusWeeks(1);
LocalDate date3 = date2.minusYears(3);
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);
复制代码
与咱们刚才介绍的get和with方法相似最后一行使用的plus方法也是通用方法,它和minus方法都声明于Temporal接口中。经过这些方法,对TemporalUnit对象加上或者减去一个数字,咱们能很是方便地将Temporal对象前溯或者回滚至某个时间段,经过ChronoUnit枚举咱们能够很是方便地实现TemporalUnit接口。
有时须要进行一些更加复杂的操做,好比,将日期调整到下个周日、下个工做日,或者是本月的最后一天。可使用重载版本的with方法,向其传递一个提供了更多定制化选择的TemporalAdjuster对象,更加灵活地处理日期。
// 对于最多见的用例,日期和时间API已经提供了大量预约义的TemporalAdjuster。能够经过TemporalAdjuster类的静态工厂方法访问。
LocalDate date1 = LocalDate.of(2013, 12, 11);
LocalDate date2 = date1.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
LocalDate date3 = date2.with(TemporalAdjusters.lastDayOfMonth());
复制代码
使用TemporalAdjuster能够进行更加复杂的日期操做,方法的名称很直观。若是没有找到符合预期的预约义的TemporalAdjuster,能够建立自定义的TemporalAdjuster。TemporalAdjuster接口只声明一个方法(即函数式接口)。实现该接口须要定义如何将一个Temporal对象转换为另外一个Temporal对象,能够把它当作一个UnaryOperator。
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
复制代码
新的java.time.format包就是特别为格式化以及解析日期-时间对象而设计的。其中最重要的类是DateTimeFormatter。建立格式器最简单的方法是经过它的静态工厂方法以及常量。全部的DateTimeFormatter实例都能用于以必定的格式建立表明特定日期或时间的字符串。
LocalDate date = LocalDate.of(2013, 10, 11);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
20131011
2013-10-11
复制代码
经过解析表明日期或时间的字符串从新建立该日期对象,也可使用工厂方法parse从新建立。
LocalDate date2 = LocalDate.parse("20141007", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date3 = LocalDate.parse("2014-10-07", DateTimeFormatter.ISO_LOCAL_DATE);
复制代码
DateTimeFormatter实例是线程安全的,老的java.util.DateFormat线程不安全。单例模式建立格式器实例,在多个线程间共享实例是没有问题的。也能够经过ofPattern静态工厂方法,按照某个特定的模式建立格式器。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String formattedDateStr = date.format(formatter);
LocalDate date1 = LocalDate.parse(formattedDateStr, formatter);
复制代码
ofPattern方法也提供了一个重载的版本,能够传入Locale建立格式器。
DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
LocalDate date = LocalDate.of(2015, 11, 14);
String formattedDate = date.format(italianFormatter);
LocalDate date1 = LocalDate.parse(formattedDate, italianFormatter);
复制代码
DateTimeFormatterBuilder类还提供了更复杂的格式器,以提供更加细粒度的控制。同时也提供很是强大的解析功能,好比区分大小写的解析、柔性解析、填充,以及在格式器中指定可选节等等。
经过DateTimeFormatterBuilder自定义格式器
DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.ITALIAN);
LocalDate now = LocalDate.now();
String s = now.format(italianFormatter);
复制代码
新版日期和时间API新增长的重要功能是时区的处理。新的java.time.ZoneId类替代老版java.util.TimeZone。跟其余日期和时间类同样,ZoneId类也是没法修改的。是按照必定的规则将区域划分红的标准时间相同的区间。在ZoneRules这个类中包含了40个时区实例,能够经过调用ZoneId的getRules()获得指定时区的规则,每一个特定的ZoneId对象都由一个地区ID标识。
ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai");
复制代码
Java 8的新方法toZoneId将一个老的时区对象转换为ZoneId。地区ID都为“{区域}/{城市}”的格式,地区集合的设定都由英特网编号分配机构(IANA)的时区数据库提供。
ZoneId zoneId = TimeZone.getDefault().toZoneId();
复制代码
ZoneId对象能够与LocalDate、LocalDateTime或者是Instant对象整合构造为成ZonedDateTime实例,它表明了相对于指定时区的时间点。
LocalDate date = LocalDate.of(2019, 03, 27);
ZonedDateTime zdt1 = date.atStartOfDay(shanghaiZone);
LocalDateTime dateTime = LocalDateTime.of(2015, 12, 21, 11, 11, 11);
ZonedDateTime zdt2 = dateTime.atZone(shanghaiZone);
Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(shanghaiZone);
经过ZoneId,你还能够将LocalDateTime转换为Instant:
LocalDateTime dateTime = LocalDateTime.of(2016, 10, 14, 15, 35);
Instant instantFromDateTime = dateTime.toInstant(shanghaiZone);
你也能够经过反向的方式获得LocalDateTime对象:
Instant instant = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, shanghaiZone);
复制代码
另外一种比较通用的表达时区的方式是利用当前时区和UTC/格林尼治的固定误差。使用ZoneId的一个子类ZoneOffset,表示的是当前时间和伦敦格林尼治子午线时间的差别:
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
复制代码
本文同步发表在公众号,欢迎你们关注!😁 后续笔记欢迎关注获取第一时间更新!