java 8 新特性 日期和时间的使用姿势

背景

在java1.0的时候,引入了java.util.Date类,但是糟糕的设计被人吐槽,随即在java1.1的时候引入了java.util.Calendar类,但是很不幸,Calendar类设计也有类似的问题,而且由于java版本需要兼容之前的版本,致使java.util.Date和java.util.Calendar类同时存在,导致很多人非常困惑。基于以上原因java在JSR 310提供了新的日期和时间API ,提供了非常丰富的时间和日期。

名词解释:JSR

Java Specification Requests(Java规范请求),由JCP成员向委员会提交的Java发展议案,经过一系列流程后,如果通过最终会体现在未来的Java中。

最终的JSR提供了一个参考实现,它是源代码形式的技术的免费实现,以及用于验证API规范的技术兼容性工具包

java.util.Date、java.util.Calendar API缺陷

  • 名称让人误解:java.util.Date并不表示日期,他表示时间的一个瞬间,并且精度为秒。

  • 不是final类:可以被继承,并且java.sql.Date继承了java.util.Date,虽然全限定类名不一致,但是类的名称一致,容易用错。

  • 可读性差:

    Date date = new Date(1945, 7, 2, 12, 30, 0);
    System.out.println(date.toString());
    
    // Sat Aug 02 12:30:00 CEST 3845
    
  • 可变和线程不安全:日期/时间类型是自然值,可以通过不可变类型建模,但是实际上Date是可变的(通过setTime()),而且Date和Calendar都是线程不安全的。

    不可变类由于其状态无法更改而具有固有的线程安全性。但是,Date和Calendar都是可变的,这要求程序员明确考虑克隆和线程化。此外,DateTimeFormat中缺乏线程安全性尚未广为人知,并且已成为导致许多难以追踪线程问题的原因。

  • 建模类型少:除了Java SE在日期时间上存在的类问题之外,它还没有用于建模其他概念的类。非时区日期或时间,持续时间,期间和间隔在Java SE中没有类表示形式。结果,开发人员经常使用int表示持续时间,而javadoc指定了单位。

    缺乏全面的日期和时间模型也导致许多常见的操作比应有的棘手。例如,计算两个日期之间的天数是当前特别困难的问题。

  • 不支持国际化。

综上,其实java.util.Date和java.util.Calendar API还有很多缺陷,这里就不一一的列举了。

这里扩展下:其中java.util.Calendar一个典型的bug,从而导致某些用户无法输入正确的出生日期。这是由Calendar类引起的,夏季仅允许夏令时增加一小时,而从历史上看,第二次世界大战时的夏令时增加了两小时。尽管此错误已得到修复,但如果将来某个国家选择在夏季引入夏时制,并增加三个小时,那么Calendar类将再次被打破。JDK-4945385 : java.util.GregorianCalendar.getMaximum(ZONE_OFFSET) works wrong

java 8 日期和时间API

简介

由于原有的日期和时间(Date and Time)API不能满足开发者的要求且存在问题,所以java 8 中提供了新的日期和时间API。并且设计的类有线程安全、不可变、实用性强等特性,弥补之前设计时间和日期的缺陷。

新的API由三个核心思想驱动:

  • 不可变值类。java.util.SimpleDateFormatJava中 现有格式器(例如)的严重缺陷之一是它们不是线程安全的。
  • 域驱动的设计。 新的API使用紧密表示日期和时间的不同用例的类对其字段进行了非常精确的建模。这种对域驱动设计的重视在清晰度和易理解性方面提供了长期利益,但是当从以前的API移植到Java SE 8时,您可能需要考虑应用程序的域日期模型。
  • 按时间顺序分隔。 新的API使人们可以使用不同的“非ISO-8601”日历系统,例如日本或泰国使用的日历系统。

Java 8 中的时间和日期API大部分借鉴了Joda-Time项目。并且提供了新的包:java.time.*,新的API提供了日期(dates)、时间(times)、瞬时时间(instants)、持续时间(durations)等。

java 8 日期和时间类解释

Clock: 时钟,获取当地时区日期、时间等
Duration: 基于时间线的时间,比如:34.5秒
Instant: 时间戳,代表时间线上的一个瞬时点,精确到纳秒级
LocalDate: 表示日期,比如:2007-12-03
LocalDateTime: 表示日期-时间,比如:2007-12-03T10:15:30
LocalTime: 表示时间,比如:10:15:30
MonthDay: 月-日,比如: --12-03
OffsetDateTime: 带有偏移量的日期-时间,比如:2007-12-03T10:15:30+01:00
OffsetTime: 带有偏移量的时间,比如:10:15:30+01:00
Period: 基于日期的时间段,比如:2年,3月和4天
Year: 年,比如:2007
YearMonth: 年-月,比如:2007-12
ZonedDateTime: 带有时区的日期-时间,比如:2007-12-03T10:15:30+01:00 Europe/Paris
ZoneId: 时区id,比如:Europe/Paris
ZoneOffset: 从Greenwich/UTC时区的偏移量,比如: +02:00
DayOfWeek: 枚举,day-of-week,比如:Tuesday
Month: month-of-year,比如:July

#java 8 日期和时间使用

说明:java 8 日期和时间的api,里边所涉及的类的方法,大家可以在这个网址方法使用的例子

输入方法签名,就可以找到
image-20191127073819425.png
例子很详细
image-20191127073924542.png

一张图来说明LocalDate、LocalTime、LocalDateTime、ZoneId、ZonedDatetTime之间的关系
模块功能 (1).png

Instant

Instant:从人的角度来看,我们习惯于星期几、几号、几点等这样的方式来理解日期和时间,但是对于机器来说不是以这样方式,从计算机的角度来看,建模时间最自然的格式是表示一个持续时间段上某个点的单一大整数型。这也是新的java.time.Instant类对时间建模的方式,基于1970-01-01T00:00:00Z(UTC时区1970年1月1日午夜时分),Instant是不可变类和线程安全,相比于System.currentTimeMillis()方法只精确到毫秒,而Instant是精确到纳秒级别。

// 获取当前时间戳 单位秒 
long nowSecond = Instant.now().getEpochSecond();
// 获取当前时间戳 单位毫秒 代替System.currentTimeMillis()
long epochMilli = Instant.now().toEpochMilli();
// 判断一个时间戳是否在另一个之前 值为false;当然还有isBefor()方法。
boolean isAfter = Instant.now().minusSeconds(10).isAfter(Instant.now());
// 构建时间戳 基于1970-01-01T00:00:00Z,可以就行纳秒级别的调整
// 向前调整了一秒,纳秒分片在0~999_999_999之间
Instant.of(3,-1000_000_000);
//还有很多方法,具体见api

LocalDate

LocalDate:LocalDate是一个不变的日期-时间对象代表了一个日期,通常被视为year-month-day。其他日期字段,如day-of-year,day-of-week,week-of-year也可以访问。例如“2nd October 2007”可以存储在一个LocalDate。这个类不存储或代表一个时间和时区。相反,它是一个描述的日期,如用于生日。它不能代表一个即时的时间线上没有额外的信息,比如一个偏移量或时区。

// 获取当前日期  ==> 2019-11-25
LocalDate today = LocalDate.now();
// 根据年月日构建日期  12就是12月份,不像之前的日期类型,11代表12月份  ==> 2019-11-29 
LocalDate ofDate = LocalDate.of(2019, 11, 29);
// LocalDate.of(int year,Month month,int dayOfMonth) 重载方法 ==> 2019-11-29
LocalDate localDate = LocalDate.of(2019, Month.NOVEMBER, 29);
// 还有其他方法 根据dayOfYear 可以推断出月份和月份中的那一天
// 比如 LocalDate.ofYearDay(2019,12) ==> 2019-01-12
LocalDate.ofYearDay(int year,int dayOfYear);
// 相对1970-01-01偏移量 ==> 1970-01-02
LocalDate.ofEpochDay(1);
// 根据字符串获取时间,严格按照DateTimeFormatter.ISO_LOCAL_DATE格式获取 == > '2011-12-03'
// 当然也可以指定格式,所以提供了一个重载方法 ==> 2012-12-12
LocalDate.parse("2011-12-03");
LocalDate.parse("2012:12:12",DateTimeFormatter.ofPattern("yyyy:MM:dd"));
// 对日期的操作 
// 对日期加操作,减操(minus)作和加操作相同
LocalDate plus = LocalDate.from(ZonedDateTime.now())
            .plusDays(1)
            .plusWeeks(1)
            .plusMonths(1)
            .plusYears(1)
            .plus(Period.ofWeeks(1))
            .plus(1, ChronoUnit.DAYS);
// 获取本月的第一天(还提供了最后一天的方法) ==> 2019-11-01
LocalDate.parse("2019-11-26").with(TemporalAdjusters.firstDayOfMonth());
// 获取2019-11-26所在星期5的日期 ==> 2019-11-29
LocalDate.of(2019, 11, 26).with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY));
// 获取本月的第6天 ==> 2019-11-06
LocalDate.parse("2019:11:26", DateTimeFormatter.ofPattern("yyyy:MM:dd")).withDayOfMonth(6);
// 转换成LocalDateTime ==> 2019-04-12T00:00
LocalDate.of(2019, Month.APRIL, 12).atStartOfDay();
// 是否在一个日期之前 ==> false
 LocalDate.now().isBefore(LocalDate.now().minusDays(1));
//还有很多方法,具体见api

TemporalAdjuster

TemporalAdjuster:官方解释一个时间的策略调节对象,调节器是修改时间对象的工具。他们存在外部化的过程调整,允许不同的方法,按照策略设计模式。我们在获取复杂的时间的时候需要用到,比如将日期设置为每月的最后一天,获取日期所在周五的日期等。

当然提供了一个工具类 TemporalAdjusters,它包含了很多封装好的静态方法,当然你可以自己根据需求写一个调节日期的策略,以下是TemporalAdjusters提供的方法,基本够我们用了。

  • finding the first or last day of the month
  • finding the first day of next month
  • finding the first or last day of the year
  • finding the first day of next year
  • finding the first or last day-of-week within a month, such as “first Wednesday in June”
  • finding the next or previous day-of-week, such as “next Thursday”
// 获取下一年的第一天 ==> 2020-01-01
 LocalDate.now().with(TemporalAdjusters.firstDayOfNextYear());
// 获取2019-11-26时间在下个星期三的日期 ==>2019-11-27
LocalDate.parse("2019-11-26").with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY));
// 获取2019-11-27时间在下个星期三的日期或者是如果该日期是周三则返回改日期 ==>2019-11-27
LocalDate.parse("2019-11-27").with(TemporalAdjusters.nextOrSame(DayOfWeek.WEDNESDAY));
// 2019-11-27
LocalDate.parse("2019-11-26").with(TemporalAdjusters.nextOrSame(DayOfWeek.WEDNESDAY));
// 更多方法,具体见api

LocalTime

LocalTime:LocalTime也提供了很多和LocalDate相同的方法,只是LocalTime专注于时间的处理,它提供小时,分钟,秒,毫微秒的各种处理。

// 获取当前日期 ==> 08:56:07.768
LocalTime.now();
// 调节时间 ==> 10:10:10
LocalTime.of(1, 1, 1)
            .withHour(10)
            .withMinute(10)
            .withSecond(10);
// 当前时间是否在当前时间减去一个小时之前,==> true
 LocalTime.from(LocalDateTime.now()).isAfter(LocalTime.now().minusHours(1));
// 日期格式化 ==> 10:10:10
LocalTime.parse("10-10-10", DateTimeFormatter.ofPattern("HH-mm-ss"));
// 日期格式化 默认格式是 DateTimeFormatter.ISO_LOCAL_TIME 
// 严格按照这个格式否则会DateTimeParseException ==> 11:11:11
LocalTime.parse("11:11:11");
// 更多方法,具体见api

LocalDateTime

LocalDateTime:LocalDateTime是LocalDate(日期)和LocalTime(时间)的结合体,即表示日期加时间,它提供的方法和LocalDate和LocalTime类似。

// 构建日期时间方法很多这里介绍几个 ==> 2019-11-11T10:10:10
LocalDateTime.of(LocalDate.of(2019, 11, 11), LocalTime.parse("10:10:10"));
// ==> 2019-11-21T10:10:20.000000011
LocalDateTime.of(2019, 11, 21, 10, 10, 20, 11); 
// 当前日期时间
LocalDateTime.now();
//用Instant构建  ==> 2019-11-27T15:58:42.743
LocalDateTime.ofInstant(Instant.now(), ZoneOffset.systemDefault());
// 从日期时间中获取月份==> 12月
Month month = LocalDateTime.parse("2019-11-27 15:58:11", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
            .plusMonths(1)
            .getMonth();
// 获取今年第一天和对改时间日期修改 ==> 2019-01-02T10:01:01.403
LocalDateTime.now()
            .with(TemporalAdjusters.firstDayOfYear())
            .withHour(10)
            .withMinute(1)
            .withSecond(1)
            .plusDays(1);
// 更多方法,具体见api

Duration

Duration:用于时、分、秒、毫秒的时间差值,即基于两个时间的差值。

LocalDateTime localDateTime = LocalDateTime.now();
Duration between = Duration.between(localDateTime, localDateTime
            .plusDays(1)
            .plusHours(1)
            .plusMinutes(1)
            .plusSeconds(1)
            .plus(1, ChronoUnit.MILLIS)
            .plusNanos(1)
        );
// 这段时间的总天数、小时数、分钟数、秒数、毫秒数,纳秒数
between.toDays();
between.toHours();
between.toMinutes();
between.getSeconds();
between.toMillis();
between.toNanos();
// 更多方法,具体见api

Period

Period:用于年月日之间的差值,即两个日期的差值。

 // Period和Duration方法类似
// of构建
Period.of(20, 1, 1);

LocalDate localDate = LocalDate.parse("2019-12-12");
Period period = Period.between(localDate, localDate.minusYears(1).minusMonths(1).minusDays(1));       
// 获取年月日总数
period.getYears();
period.getMonths();
period.getDays();
// 更多方法,具体见api

ZonedDateTime

ZonedDateTime:ZonedDateTime可以简单的理解成LocalDateTime加上ZoneId(时区),LocalDateTime有一个系统默认的时区,这样的话,ZonedDateTime和LocalDateTime的方法使用类似,只是多了一个可以改变的时区的概念。官方解释:ZonedDateTime是带有时区的日期时间的不可变表示形式。此类存储所有日期和时间字段(精度为纳秒)和时区,其中时区偏移量用于处理模棱两可的本地日期时间。例如,值“Europe/Paris时区的2007年10月2日13:45.30.123456789 +02:00”可以存储在ZonedDateTime中。什么意思呢,你可以用ZoneId表示时区,当然可以用相对于UTC/Greenwich的偏移量来表示,比如相对于UTC/Greenwich 偏移了 2个小时,可以用ZoneId.ofOffset(“UTC”, ZoneOffset.ofHours(2))构建。

// 美国时区(西七区,美国分了好几个时区)下的日期时间 ==> 2019-11-27T05:34:23.421-08:00[America/Los_Angeles]
ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("America/Los_Angeles"));
// 系统默认时区下的日期时间 和美国时区下的时间是不一样的 ==> 2019-11-27T21:38:16.670+08:00[Asia/Shanghai] 
ZonedDateTime.now()
// 相对于UTC/Greenwich的偏移量来表示ZonedDateTime 相对于UTC/Greenwich 2个小时
ZonedDateTime.of(LocalDate.parse("2019-12-12"), LocalTime.of(1, 1),
                 ZoneId.ofOffset("UTC", ZoneOffset.ofHours(2)));
// 更多方法,具体见api

Instant、LocalDate、LocalTime、LocalDateTime、Date相互转换

// 1.Date <==> Instant相互转换
Instant instant = new Date().toInstant();
Date date = Date.from(instant);
// 2.Date <==> LocalDate相互转换 Date转LocalDate比较麻烦,需要先转成LocalDateTime
// Date ==> LocalDate 现将Date转成LocalDateTime,然后转成LocalDate
LocalDate localDate = LocalDateTime.ofInstant(new Date().toInstant(), ZoneId.systemDefault()).toLocalDate();
// Date <== LocalDate LocalDate里没时间信息,需要给定,我们给定为当天的开始
// 先转成这天00:00:00的LocalDateTime,然后在转成Instant,最后转成Date
Instant instant1 = localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
        LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
Date date1 = Date.from(instant1);
// 3.Date <==> LocalTime 相互转换 ,LocalDate、LocalTime和Date转换类似,注意由于LocalTime只有时间信息,
// 所以转Date的时候需要给出日期信息,即LocalDate信息
LocalTime localTime= LocalDateTime.ofInstant(new Date().toInstant(), ZoneId.systemDefault()).toLocalTime();
Instant instant2 = localTime.atDate(LocalDate.now()).atZone(ZoneId.systemDefault()).toInstant();
Date date2 = Date.from(instant2)
// 4.Date <==> LocalDateTime相互转换
LocalDateTime localDateTime = localDate.atStartOfDay();
Instant instant3 = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
Date date3 = Date.from(instant3);
// 6.Instant <==>  LocalDateTime相互转换
LocalDateTime localDateTime1 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
Instant instant4 = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
// 7.LocalDateTime <==>  LocalDate相互转换
LocalDate localDate2 = localDateTime1.toLocalDate();
LocalDateTime localDateTime2 = localDate2.atStartOfDay();
// 8.LocalDateTime <==> LocalTime
LocalTime localTime2 = localDateTime2.toLocalTime();
LocaDateTime locaDateTime3 = locaTime.atDate(LocalDate.parse("2019-12-12"));
// 9.Instant <==> LocalDate和LocalTime转,参考6,7,8
// 更多方法,具体见api


综上,java 8 日期和时间的类还有很多,大家可以查阅官方文档https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值