zoukankan      html  css  js  c++  java
  • Java 8 (11) 新的日期和时间API

    在Java 1.0中,对日期和时间的支持只能依赖java.util.Date类。这个类只能以毫秒的精度表示时间。这个类还有很多糟糕的问题,比如年份的起始选择是1900年,月份的起始从0开始。这意味着你要想表示2018年8月22日,就必须创建下面这样的Date实例:

    Date date = new Date (118,7,22);

    Wed Aug 22 00:00:00 CST 2018

    甚至Date类的toString方法返回的字符串也容易误人。现在这个返回值甚至还包含了JVM的默认时区CST,但这不表示Date类在任何方面支持时区。

    Java 1.1中,Date类中的很多方法被废弃了,取而代之的是java.util.Calendar类。但是Calendar也有类似的问题和设计缺陷,导致使用这些方法写出的代码非常容易出错。比如月份依旧是从0开始计算,不过去掉了1900年开始计算年份这一设计。更糟的是同时存在Date和Calendar这两个类,也增加了程序员的困惑。到底该使用哪一个类呢?此外,有的特性只有在某一个类有提供,比如用于以语言无关方式格式化和解析日期或时间的DateFormat方法就只有在Date类里有。

    DateFormat也有它自己的问题,首先他不是县城安全的。这意味着两个县城如果尝试使用同一个formatter解析日期,你可能无法得到预期的结果。

    最后,Date和Calendar类都是可变的。

    Java 8 在java.time包中整合了很多Joda-Time的特性,java.time包中提供了很多新的类可以帮助你解决问题:LocalDate、LocalTime、Instant、Duration和Period。

    LocalDate

    首先该类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间。另外,它也不附带任何时区相关的信息。 你可以通过静态工厂of创建一个LocalDate实例,LocalDate提供了很多方法来读取常用的值,比如年月日星期几等:

            LocalDate date = LocalDate.of(2018,8,22);
            int year = date.getYear(); //2018
            Month month = date.getMonth(); //AUGUST(7)
            int day = date.getDayOfMonth(); //22
            DayOfWeek dow = date.getDayOfWeek(); //WEDNESDAY
            int len = date.lengthOfMonth(); //31
            boolean leap = date.isLeapYear(); //false
    
            LocalDate today = LocalDate.now(); //2018-08-22

    传递TemporalField参数给 date的get方法也可以拿到同样的信息,TemporalField是一个借口,它定义了如何访问temporal对象某个字段的值。ChronoField枚举实现了这一借口,所以你可以很方便的使用get方法得到枚举元素的值:

            int yearofGet = date.get(ChronoField.YEAR); //2018
            int monthofGet = date.get(ChronoField.MONTH_OF_YEAR); //8
            int dayofGet = date.get(ChronoField.DAY_OF_MONTH); //22

    LocalTime

    与LocalDate类似,LocalTime表示时间,你可以使用of重载的两个工厂方法创建LocalTime实例,第一个重载是小时和分钟,第二个重载还接受秒。也提供了getter方法访问这些变量的值:

            LocalTime time = LocalTime.of(17,55,55);
            int hour = time.getHour();//17
            int minute = time.getMinute();//55
            int second = time.getSecond();//55

    LocalDate和LocalTime都支持从字符串创建,使用静态方法parse:

            LocalDate dateofString = LocalDate.parse("2018-08-22");
            LocalTime timeofString = LocalTime.parse("15:53:52");

    如果格式不正确,无法被解析成合法的LocalDate或LocalTime对象。parse方法会抛出一个继承自RuntimeException的DateTimeParseException异常。

    LocalDateTime

    这个复合类是LocalDate何LocalTime的合体,同时表示了日期和时间。但不带有时区信息,你可以直接创建,也可以通过合并日期和时间对象构造。

            LocalDateTime dt1 = dateofString.atTime(timeofString);
            LocalDateTime dt2 = time.atDate(date);
            LocalDateTime dt3 = LocalDateTime.of(date,time);
            LocalDateTime dt4 = LocalDateTime.of(2018,8,22,17,55,55);
            //2018-08-22T17:55:55

    也可以将LocalDateTime拆分为独立的LocalDate 和LocalTime:

            LocalDate localDate = dt1.toLocalDate();
            LocalTime localTime = dt1.toLocalTime();

    Instant

    作为人,我们习惯于星期几、几号、几点、几分这样的方式理解日期和时间,但是对于计算机而言并不容易理解。java.time.Instant类对时间的建模方式是 以Unix元年时间(UTC时区1970年1月1日午夜时分)开始所经历的描述进行计算。

    可以使用静态工厂方法ofEpochSecond传递一个代笔哦啊秒数的值创建一个该类的实例。这个方法还有一个重载版本,它接受第二个以纳秒为单位的参数值,对传入作为秒数的参数进行调整。重载的版本会调整纳秒参数,确保保存的纳秒分片在0到999999999之间,这意味着下面这些对ofEpochSecond工厂方法的调用返回几乎同样的Instant对象:

            Instant instant = Instant.ofEpochSecond(3);
            Instant instant2 = Instant.ofEpochSecond(3, 0);
            Instant instant3 = Instant.ofEpochSecond(2, 1_000_000_000);
            Instant instant4 = Instant.ofEpochSecond(4, -1_000_000_000);

    1970-01-01T00:00:03Z

    Instant类也支持静态工厂方法now,它能够帮你获取当前时刻的时间戳。Instant的设计初衷是为了便于机器使用,它所包含的是由秒及纳秒所构成的数字。所以,它无法处理那些我们非常容易理解的时间单位。

    定义Duration或Period

    计算日期时间差使用这两个类

            Duration d1 = Duration.between(time1, time2);
            Duration d2 = Duration.between(datetime1, datetime2);
            Duration d3 = Duration.between(instant,instant2);

    由于LocalDateTime和Instant是为不同的目的而设计的,一个是为了人阅读的,一个是为了机器处理的,所以不能将二者混用。此外,由于Duration类主要用于以秒和纳秒衡量时间的长短,所以不能向between方法传递LocalDate。

    Duration和Period类都提供了很多非常方便的工厂类,直接创建对应的实例。

            Duration threeMinutes = Duration.ofMinutes(3);
            Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);
    
            Period tenDays = Period.ofDays(10);
            Period threeWeeks  = Period.ofWeeks(3);
            Period twoYearsSixMothsOneDay = Period.of(2,6,1);

    Period计算时间差

            LocalDate today = LocalDate.now();
            System.out.println("Today : " + today);
            LocalDate birthDate = LocalDate.of(1991, 1, 11);
            System.out.println("BirthDate : " + birthDate);
    
            Period p = Period.between(birthDate, today);
            System.out.printf("年龄 : %d 年 %d 月 %d 日", p.getYears(), p.getMonths(), p.getDays());

    Today : 2018-08-22
    BirthDate : 1991-01-11
    年龄 : 27 年 7 月 11 日

    Duration计算相差秒数

            Instant inst1 = Instant.now();
            System.out.println("Inst1 : " + inst1);
            Instant inst2 = inst1.plus(Duration.ofSeconds(10));
            System.out.println("Inst2 : " + inst2);
    
            System.out.println("Difference in milliseconds : " + Duration.between(inst1, inst2).toMillis());
    
            System.out.println("Difference in seconds : " + Duration.between(inst1, inst2).getSeconds());

    Difference in milliseconds : 10000
    Difference in seconds : 10

    到目前为止,这些日期和时间都是不可修改的,这是为了更好的支持函数式编程,确保线程安全。如果你想在LocalDate实例上增加3天,或者将日期解析和输入 dd/MM/yyyy这种格式。 将在下一节讲解。

    操作、解析和格式化日期

    对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对象,他们不会影响原来的对象。

            LocalDate  dd = LocalDate.of(2018,8,23); //2018-08-23
            LocalDate dd1 = dd.withYear(2017); //2017-08-23
            LocalDate dd2 = dd.withDayOfMonth(22); //2018-08-22
            LocalDate dd4 = dd.withMonth(10); //2018-10-23
            LocalDate dd3 = dd.with(ChronoField.MONTH_OF_YEAR,9); //2018-09-23

    除了withAttribute详细的年月日,也可以采用通用的with方法,第一个参数是TemporalField对象,第二个参数是修改的值。它也可以操纵LocalDate对象:

            LocalDate dd5 = dd.plusWeeks(1); //加一周
            LocalDate dd6 = dd.minusYears(3); //减去三年
            LocalDate dd7 = dd.plus(6,ChronoUnit.MONTHS); //加6月

    plus和minus方法和上面的with类似,他们都声明于Temporal接口中。像LocalDate、LocalTime、LocalDateTime以及Instant这些表示日期和时间的类提供了大量的通用方法:

    1、from 静态方法 依据传入的Temporal对象创建是实例

    2、now 静态方法 依据系统时钟创建Temporal对象

    3、of 静态方法 由Temporal对象的某个部分创建对象的实例

    4、 parse 静态方法  由字符串创建Temporal对象的实例

    5、atOffset 将Temporal对象和某个时区偏移相结合

    6、atZone 将Temporal 对象和某个时区相结合

    7、format 使用某个指定的格式将Temporal对象转换为字符串(Instant类不提供此方法)

    8、get 读取Temporal对象的某一部分的值

    9、minus 创建对象的一个副本,然后将当前的Temporal对象的值减去一定的时长创建该副本

    10、plus 创建对象的一个副本,然后将当前Temporal对象的值加上一定的时长创建该副本

    11、with 以该对象为模板,对某些状态进行修改创建该对象的副本。

    TemporalAdjuster

    操纵更复杂的日期,比如将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时可以使用with的重载版本,向其传递一个提供了更多定制化选择的TemporalAdjuster对象,更加灵活的处理日期。

    import static java.time.temporal.TemporalAdjusters.*;
    
            LocalDate dd = LocalDate.of(2018,8,23);
    
            LocalDate dd1 = dd.with(dayOfWeekInMonth(2,DayOfWeek.FRIDAY)); //同一个月中,第二个星期五 2018-08-10
    
            LocalDate dd2 = dd.with(firstDayOfMonth()); //当月的第一天 2018-08-01
            LocalDate dd3 = dd.with(firstDayOfNextMonth()); //下月的第一天 2018-09-01
            LocalDate dd4 = dd.with(firstDayOfNextYear()); //明年的第一天 2019-01-01
            LocalDate dd5 = dd.with(firstDayOfYear()); //当年的第一天 2018-01-01
            LocalDate dd6 = dd.with(firstInMonth(DayOfWeek.MONDAY)); //当月第一个星期一 2018-08-06
    
            LocalDate dd7 = dd.with(lastDayOfMonth()); //当月的最后一天 2018-08-31
            LocalDate dd8 = dd.with(lastDayOfYear()); //当年的最后一天 2018-12-31
            LocalDate dd9 = dd.with(lastInMonth(DayOfWeek.SUNDAY)); //当月最后一个星期日 2018-08-26
    
            LocalDate dd10 = dd.with(previous(DayOfWeek.MONDAY)); //将日期向前调整到第一个符合星期一 2018-08-20
            LocalDate dd11 = dd.with(next(DayOfWeek.MONDAY)); //将日期向后调整到第一个符合星期一 2018-08-27
    
            LocalDate dd12 = dd.with(previousOrSame(DayOfWeek.FRIDAY)); //将日期向前调整第一个符合星期五,如果该日期已经符合,直接返回该对象 2018-08-17
            LocalDate dd13 = dd.with(nextOrSame(DayOfWeek.FRIDAY)); //将日期向后调整第一个符合星期五,如果该日期已经符合,直接返回该对象 2018-08-24

    TemporalAdjuster可以进行复杂的日期操作,如果没有找到符合的预定义方法,可以自己创建一个,TemporalAdjuster接口只声明了一个方法所以他说一个函数式接口:

    @FunctionalInterface
    public interface TemporalAdjuster {
        Temporal adjustInto(Temporal temporal);
    }

    这意味着TemporalAdjuster接口的实现需要定义如何将一个Temporal对象转换为另一个Temporal对象。比如设计一个NextWorkingDay类,实现计算下一个工作日,过滤掉周六和周日节假日。

    public class NextWorkingDay implements TemporalAdjuster {
        @Override
        public Temporal adjustInto(Temporal temporal) {
            DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
            int dayToAdd = 1;
            //如果当前日期 星期一到星期五之间返回下一天
            if(dow == DayOfWeek.FRIDAY) dayToAdd = 3;
            //如果当前日期 周六或周日 返回下周一
            else if(dow == DayOfWeek.SATURDAY) dayToAdd = 2;
            return temporal.plus(dayToAdd, ChronoUnit.DAYS);
        }
    }

    由于TemporalAdjuster是函数式接口,所以你只能以Lambda表达式的方式向Adjuster接口传递行为:

    date.with(t->{

      //上面一坨

    })

    为了可以重用,TemporalAdjuster对象推荐使用TemporalAdjusters类的静态工厂方法ofDateAdjuster:

            LocalDate dd = LocalDate.of(2018, 8, 23);
    
            TemporalAdjuster nextWorkingDay = TemporalAdjusters.ofDateAdjuster(
                    temporal -> {
                        DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
                        int dayToAdd = 1;
                        //如果当前日期 星期一到星期五之间返回下一天
                        if (dow == DayOfWeek.FRIDAY) dayToAdd = 3;
                            //如果当前日期 周六或周日 返回下周一
                        else if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;
                        return temporal.plus(dayToAdd, ChronoUnit.DAYS);
                    }
            );
            LocalDate next = dd.with(nextWorkingDay);

    打印输出及解析日期

    处理日期和时间对象时,格式化以及解析日期-时间对象是另一个非常重要的功能。新的java.time.format包就是为了这个目的而设计的。这个包中最重要的类是DateTimeFormatter,创建格式器最简单的方式是通过他的静态工厂方法及常量。

            String s1 = next.format(DateTimeFormatter.BASIC_ISO_DATE); //20180824
            String s2 = next.format(DateTimeFormatter.ISO_LOCAL_DATE); //2018-08-24

    除了解析为字符串外,还可以通过解析代表日期或时间的字符串重新创建该日期对象。

            LocalDate date1 = LocalDate.parse("20180901",DateTimeFormatter.BASIC_ISO_DATE);
            LocalDate date2 = LocalDate.parse("2018-09-02",DateTimeFormatter.ISO_LOCAL_DATE);

    与老的java.util.DateFormat想比较,所有的DateTimeFormatter实例都是线程安全的。DateTimeFormatter类还支持一个静态工厂方法,它按照某个特定的模式创建格式器。

            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
            LocalDate now = LocalDate.now();
            String formatterDate = now.format(formatter);
            LocalDate nowparse = LocalDate.parse(formatterDate,formatter);

    ofPattern可以按照指定的格式进行解析成字符串,然后又调用了parse方法的重载 将该格式的字符串转换成了 LocalDate对象。

    ofPattern也提供了重载版本,使用它可以创建某个Locale的格式器:

            DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy年MMMMd号", Locale.CHINA);
            LocalDate chinaDate = LocalDate.parse("2018-08-21");
            String formatterDate2 = chinaDate.format(formatter2); //2018年八月21号
            LocalDate chinaDate2 = LocalDate.parse(formatterDate2,formatter2);

    DateFormatterBuilder类还提供了更复杂的格式器和更强大的解析功能:

            DateTimeFormatter chinaFormatter = new DateTimeFormatterBuilder().appendText(ChronoField.YEAR)
                    .appendLiteral("年")
                    .appendText(ChronoField.MONTH_OF_YEAR)
                    .appendText(ChronoField.DAY_OF_MONTH)
                    .appendLiteral("号")
                    .parseCaseInsensitive().toFormatter(Locale.CHINA);

    处理不同的时区和历法

    之前所看到的日期和时间种类都不包含时区信息。时区的处理是新版日期和时间API新增加的重要功能,新的 java.time.ZoneId 类是老版 java.util.TimeZone 的替代品。

    时区是按照一定的规则将区域划分成的标准时间相同的区间。在ZoneRules这个类中包含了40个这样的实例。你可以使用ZoneId的getRules()得到指定时区的规则。每个特定的ZoneId对象都由一个地区标识:

    ZoneId romeZone = ZoneId.of("Europe/Rome"); //格式 欧洲/罗马

    地区ID都为 “{区域}/{城市}”的格式,这些地区集合的设定都由英特网编号分配机构(IANA)的时区数据库提供。你可以通过java 8的新方法toZoneId将一个老的时区对象转换为ZoneId

    ZoneId zoneId = TimeZone.getDefault().toZoneId();

    一旦得到一个ZoneId对象,就可以将它与LocalDate、LocalDateTIme或者是Instant对象整合起来,构造为一个ZonedDateTime实例,它代表了相对于指定时区的时间点,

            LocalDate date = LocalDate.of(2018,8,22);
            ZonedDateTime zdt1 = date.atStartOfDay(romeZone);
    
            LocalDateTime dateTime = LocalDateTime.of(2018,8,23,13,48,00);
            ZonedDateTime zdt2 = dateTime.atZone(romeZone);
    
            Instant instant = Instant.now();
            ZonedDateTime zdt3 = instant.atZone(romeZone);

    ZonedDateTime = LocalDateTime(LocalDate + LocalTime) + ZoneId

    通过ZoneId,你还可以将LocalDateTime转换为Instant

            LocalDateTime dateTime = LocalDateTime.of(2018,8,23,13,48,00);
            Instant instantFromDateTime = dateTime.toInstant(romeZone);
    
            Instant instant1 = Instant.now();
            LocalDateTime timeFromInstant = LocalDateTime.ofInstant(romeZone);

    利用和 UTC/格林尼治时间的固定偏差计算时区

    另一种比较常用的表达时区的方式就是利用当前时区和 UTC/格林尼治 的固定偏差,比如,纽约落后伦敦5小时。这种情况下,你可以使用ZoneOffset类,它是ZoneId的一个子类,表示的是当前时间和伦敦格林尼治子午时间的差异:

    ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");

    这种方式不推荐使用,因为 -05:00 的偏差实际上是对应的美国东部标准时间,并未考虑任何日光时的影响。

            LocalDateTime dateTime1 = LocalDateTime.now();
            OffsetDateTime dateTimeInNewYork1 = OffsetDateTime.of(dateTime1,newYorkOffset);

    它使用ISO-8601的历法系统,以相对于UTC时间的偏差方式表示日期时间。

    使用别的日历系统

    ISO-8601日历系统是世界文明日历系统的事实标准。但是,java 8 中另外提供了4种其他的日历系统。这些日历系统中的每一个都有一个对应的日志类,分别是ThaiBuddhistDate、MinguoDate、JapaneseDate以及HijrahDate。所有这些类以及LocalDate都实现了ChronoLocalDate接口,能够对公历的日期进行建模。利用LocalDate对象,可以创建这些类的实例。

    小结:

      1、Java 8之前的java.util.Date类以及其他用于建模日期时间的雷有很多不一致及设计上的缺陷,包括易变性以及糟糕的偏移值、默认值和命名。

      2、新版的日期和时间API中,日期-时间对象是不可变的。

      3、新的API提供了两种不同的时间表示方式,有效地区分了运行时人喝机器的不同需求。

      4、操纵的日期不会影响老值,而是新生成一个实例。

      5、TemporalAdjuster可以更精确的操纵日期,还可以自定义日期转换器。

      6、他们都是线程安全的

  • 相关阅读:
    UVA
    HDU
    manacher求最长回文子串算法
    next数组求最小循环节
    HUST
    廖雪峰Java1-4数组操作-2数组排序
    廖雪峰Java1-4数组操作-1遍历数组
    廖雪峰Java1-3流程控制-9break、continue
    廖雪峰Java-3流程控制-7for循环
    廖雪峰Java1-3流程控制-6 do-while循环
  • 原文地址:https://www.cnblogs.com/baidawei/p/9519747.html
Copyright © 2011-2022 走看看