JDK8 after时间日期api

参考:https://zq99299.github.io/java-tutorial/datetime/iso/

介绍

简介

为了解决这些问题并在JDK内核中提供更好的支持,针对Java SE 8设计了一个新的没有这些问题的日期和时间API。该项目由Joda-Time(Stephen Colebourne)和Oracle的作者在JSR 310下共同领导,出现在Java SE 8软件包中java.time。

before JDK8 时间日期Api缺点:

1️⃣ 可变性:对于时间与日期而言应该是不可变的

2️⃣ 安全性:线程不安全,且不能处理闰秒

🕥 ​百度百科:

​ 闰秒,是指为保持协调世界时接近于世界时时刻,由国际计量局统一规定在年底或年中(也可能在季末)对协调世界时增加或减少1 的调整。由于地球自转的不均匀性和长期变慢性(主要由潮汐摩擦引起的),会使 世界时(民用时)和原子时之间相差超过到 ±0.9秒时,就把协调世界时向前拨1秒(负闰秒,最后一分钟为59秒)或向后拨1秒(正闰秒,最后一分钟为61秒); 闰秒一般加在 公历年末或公历六月末。

​ 目前,全球已经进行了27次闰秒,均为正闰秒。

​ 最近一次闰秒在北京时间2017年1月1日7时59分59秒(时钟显示****07:59:60)出现。这也是本世纪的第五次闰秒。

3️⃣ 偏移性:月份是从0开始的 — Date 月份从0开始,一月是0,十二月是11

4️⃣ 格式化:格式化只能处理Data,而不能直接处理Calender

after JDK8时间日期API

🔑 不可变性、域驱动(Data与Time严格分离,而不像之前的Data即包含时间又包含日期)、线程安全(每次的修改动作返回的是一个新的对象)


分类:

java.time -> 包含值对象的基础包。

java.time.chrono -> 提供对不同的日历系统的访问。

java.time.format -> 提供格式化和解析时间和日期。

java.time.temporal -> 包括底层框架和扩展特性。

java.time.zone -> 包含时区支持的类。

说明:对于大多数开发者只会用到基础包和format包,也可能用到temporal包。因此尽管有68个新的公开类型,大多数开发者,大概只会用到其中的三分之一。


Zoned - 时区

UTC、GMT、CST、DST、时区、夏令时偏移量:彻底弄懂GMT、UTC、时区和夏令时 - 知乎 (zhihu.com)

全球城市ZoneId和UTC时间偏移量的最全对照表 - YourBatman - 博客园 (cnblogs.com)

JSR310新日期API(一)-时区与时间偏移量 - 云+社区 - 腾讯云 (tencent.com)

ZonedId

介绍:

  • 表示时区 ID,例如Europe/Paris,它是旧API TimeZone 的替代。
    ZoneId用于标识用于在Instant和LocalDateTime之间转换的规则。

  • 有两种不同类型的 ID,对应着两个实现类:

    • 固定偏移量(ZoneOffset) - UTC/格林威治的完全解析偏移量,对所有本地日期时间使用相同的偏移量

      • UTC或者GMT。

      • Z(相当于UTC)。

      • +h或者-h。

      • +hh或者-hh。

      • +hh:mm或者-hh:mm。

      • +hh:mm:ss或者-hh:mm:ss。

      • +hhmmss或者-hhmmss。

    • 地理区域 (ZoneRegion,default权限包内访问)- 应用一组特定规则来查找 UTC/格林威治偏移量的区域

      • Asia/Shanghia
      • 。。。
  • 大多数固定偏移量由ZoneOffset表示。对任何ZoneId调用normalized()将确保固定偏移 ID 将表示为ZoneOffset (UTC)。

  • 描述偏移量何时以及如何变化的实际规则由ZoneRules定义。

  • 这个类只是一个用来获取底层规则的ID。 之所以采用这种方法,是因为规则是由政府定义的并且经常变化,而 ID 是稳定的。


of、from方法

获取ZonedId实例

public void testOf(){
    ZoneId zoneId = ZoneId.of("Asia/Shanghai");

    // 使用别名映射使用其 ID 获取ZoneId的实例,以补充标准区域 ID, 可查看 SHORT_IDS, CTT -> Asia/Shanghai
    ZoneId zoneId1 = ZoneId.of(TimeZone.getTimeZone("CTT").getID(), ZoneId.SHORT_IDS);

    

    LocalDateTime now = LocalDateTime.now(zoneId); //2021-10-01T23:36:14.276298100

    LocalDateTime now1 = LocalDateTime.now(zoneId1); //2021-10-01T23:36:14.277295200
    
    ZoneId from = ZoneId.from(ZoneOffset.of("+02:00"));

    ZoneId from1 = ZoneId.from(MonthDay.now());
   

}


other

getAvailableZoneIds():获取当前可用的zoneId

systemDefault():根据系统获取zoneId

ofOffset(String prefix, ZoneOffset offset):获取一个包含偏移量的ZoneId实例。如果前缀是“GMT”、“UTC”或“UT”,则ZoneId带有前缀和非零偏移量的ZoneId 。 如果前缀为空 ,则返回ZoneOffset 。

normalized():固定偏移 ID 将表示为ZoneOffset

public void test3(){

    Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();

    ZoneId zoneId = ZoneId.systemDefault();


    ZoneId zoneId1 = ZoneId.ofOffset("", ZoneOffset.of("+02:00")); //+02:00

    ZoneId zoneId2 = ZoneId.ofOffset("GMT", ZoneOffset.of("+02:00")); //GMT+02:00

}
ZoneId zoneId = ZoneId.ofOffset("GMT", ZoneOffset.of("+02:00"));//GMT+02:00

ZoneId normalized = zoneId.normalized(); //+02:00

ZoneRules — 时区规则

简介:

ZoneRulesProvider用于加载Zone Rule(时区规则,ZoneRules),自定义实现是可以通过系统变量设置java.time.zone.DefaultZoneRulesProvider=全类名ZoneRulesProvider自定义的提供类,或者通过SPI加载,默认的实现类是TzdbZoneRulesProviderTzdbZoneRulesProvider会加载${JAVA_HONE}/lib/tzdb.dat文件

ZoneOffset — 固定偏移量

简介:

  • ZonedId的实现类,表示格林威治/UTC 的时区偏移量,例如+02:00 。

  • 时区偏移量是时区与格林威治/UTC 不同的时间量。 这通常是固定的小时数和分钟数。

  • 世界不同地区有不同的时区偏移。 ZoneId类中捕获了偏移量如何因地点和一年中的时间而变化的规则。
    例如:

    巴黎在冬天比格林威治/UTC 早一小时,在夏天比格林威治/UTC 早两小时。 巴黎的ZoneId实例将引用两个ZoneOffset实例 - 冬季的+01:00实例和夏季的+02:00实例。

    2008 年,世界各地的时区偏移从 -12:00 扩展到 +14:00。 为防止扩展该范围时出现任何问题,但仍提供验证,偏移范围限制为 -18:00 到 18:00(含)。

示例:

+ xx:00表示东xx区,- xx:00表示西xx区 注意:是在格林威治/UTC 下

pblic void test1(){

    LocalDateTime now = LocalDateTime.now(ZoneOffset.of("+02:00")); //东2区
    System.out.println(now);  //2021-10-01T18:15:25.706604300

    LocalDateTime now1 = LocalDateTime.now(ZoneOffset.of("-04:00")); //西4区
    System.out.println(now1); //2021-10-01T12:15:25.706604300
}


Clock - 时钟

大多基于 temporal 对象都提供了一个无参数的 now() 方法,它提供了使用系统时钟和默认时区的当前日期和时间。 这些基于时间的对象还提供了一个单参数 now (clock) 方法, 允许您传入另一个时钟。

当前日期和时间取决于时区,对于全球化应用程序,clock 是确保日期/时间使用正确时区创建所必需的。 因此,虽然 Clock 类的使用是可选的,但此功能允许您测试您的代码是否适用于其他时区,或者使用时间不变的固定时钟。

Clock 是一个抽象类,所以不能创建它的一个实例,以下工厂方法可用于测试。

  • Clock.offset(Clock,Duration)返回一个被指定持续时间偏移的时钟。
  • Clock.systemUTC()返回表示格林尼治/ UTC 时区的时钟。
  • Clock.fixed(Instant,ZoneId)总是返回相同的 Instant。对于这个时钟,时间停滞不前。


Temporal - 时间

java.time.temporal 提供了一组接口、类和枚举,它们支持日期和时间代码,特别是日期和时间计算


接口:

InterfaceDescription
Temporal框架级接口,定义对临时对象(如日期、时间、偏移量或这些对象的某种组合)的读写访问。
TemporalAccessor框架级接口,定义对临时对象(如日期、时间、偏移量或这些对象的某种组合)的只读访问
TemporalAdjuster调整时间对象的策略
TemporalAmount框架级接口定义时间,如"6 小时"、“8 天"或"2 年零 3 个月”。
TemporalField日期时间的字段,比如:month-of-year或hour-of-minute。
TemporalQuery查询时间对象的策略。
TemporalUnit日期时间的单位,如Days 或 Hours。


类:

ClassDescription
IsoFields特定于ISO-8601日历系统的字段和单位,包括季度和以周为单位的年
JulianFields一组日期字段,提供对Julian Days的访问。
TemporalAdjusters工具类,提供TemporalAdjuster对象.
TemporalQueries工具类,提供TemporalQuery 的通用实现
ValueRange日期-时间字段的有效值范围。
WeekFieldsday-of-week、week-of-month、 week-of-year 字段的本地化定义。


枚举:

EnumDescription
ChronoField一组标准的字段
ChronoUnit一组标准的日期周期单位


关系:

TemporalAccessor 接口提供的只读版本 Temporal 接口。

Temporal and TemporalAccessor 对象是用字段来定义的,如TemporalField 接口, ChronoField 是一个具体的实现 TemporalField 接口的枚举类;并提供了一套丰富的定义的常量,如 DAY_OF_WEEK,MINUTE_OF_HOUR,和MONTH_OF_YEAR。

这些字段的单位由 TemporalUnit接口指定 。 ChronoUnit 枚举实现 TemporalUnit 接口。ChronoField.DAY_OF_WEEK 字段是 ChronoUnit.DAYS和ChronoUnit.WEEKS 的组合;

Temporal 接口中的基于算术的方法需要使用 TemporalAmount值定义的参数 。Period and Duration 类实现了 TemporalAmount 接口

TemporalField - 时间属性

接口,表示日期时间字段,例如月份或分钟。

日期和时间使用字段来表示,这些字段将时间线划分为对人类有意义的内容。 此接口的实现表示这些字段。

最常用的单位在ChronoField中定义。 在IsoFields 、 WeekFields和JulianFields中提供了更多字段。 应用程序代码也可以通过实现这个接口来编写字段。

ChronoField

ChronoFiled为该接口的实现类,提供了一组用于访问日期和时间值的常量,比如:CLOCK_HOUR_OF_DAY,NANO_OF_DAY 和 DAY_OF_YEAR,

But 有些时间、日期类不支持某些属性,比如LocalDate不支持ChronoField.CLOCK_HOUR_OF_DAY,此时可以使用TemporalAccessor.isSupported(TemporalField)来查看是否支持:

// 是否支持 am.pm 上午下午这样的字段
// 由于 LocalDate 不包含时分秒,所以不支持
boolean isSupported = LocalDate.now().isSupported(ChronoField.CLOCK_HOUR_OF_DAY);

IsoField

特定于 ISO- 8601 日历系统,定义了一些特定的字段

    public static final TemporalField DAY_OF_QUARTER; //季度的那一天
    public static final TemporalField QUARTER_OF_YEAR; // 年的那一个季度
    public static final TemporalField WEEK_OF_WEEK_BASED_YEAR; //基于周年的一周
    public static final TemporalField WEEK_BASED_YEAR;  //周年

DateDay-of-weekField values
2008-12-28Sunday(星期天)Week 52 of week-based-year 2008
2008-12-29Monday(星期一)Week 1 of week-based-year 2009
2008-12-31Wednesday( 星期三)Week 1 of week-based-year 2009
2009-01-01Thursday(星期四)Week 1 of week-based-year 2009
2009-01-04Sunday(星期日)Week 1 of week-based-year 2009
2009-01-05Monday( 星期一)Week 2 of week-based-year 2009

Other

  • WeekFields : 提供了在某些周相关的访问

    • 2008-12-31 星期三
    • 2008 年 12 月第 5 周
    • 2008 年 12 月第 5 周
    • 2009 年第 1 周
    • 2008 年第 53 周
    • 。。。。
  • JulianFields

    天文科学界的时间表达

TemporalUnit - 时间单位

接口,表示日期时间单位,例如Days、Hours。
时间的测量建立在单位上,例如years、months、days、hours、minutes and seconds。 此接口的实现代表这些单元。

此接口的实例表示单位本身,而不是单位的数量。 请参阅Period以了解用通用单位表示金额的类。
最常用的单位在ChronoUnit中定义。 在IsoFields中提供了更多单位。 单元也可以通过实现这个接口由应用程序代码编写。
该单元使用双重调度工作。 客户端代码调用像LocalDateTime这样的日期时间的方法来检查单位是否是ChronoUnit 。 如果是,则日期时间必须处理它。 否则,将方法调用重新调度到此接口中的匹配方法。

ChronoUnit

ChronoUnit类时TemporalUnit接口实现类,提供了一组根据日期和时间,从毫秒到几千年标准单元,比如:MONTHS、YEARS等。

BUT 类似于ChronoFiled,有些时间日期类可能也不支持某些单位,比如Instant 不支持DAYS,TemporalAccessor.isSupported(TemporalUnit)方法可用于验证一个类是否支持特定时间单位。

boolean isSupported = instant.isSupported(ChronoUnit.DAYS);

IsoField

IsoFields内部也定义了关于TemporalUnit 的常量

  public static final TemporalUnit WEEK_BASED_YEARS = Unit.WEEK_BASED_YEARS; //周年为概念的单位

  public static final TemporalUnit QUARTER_YEARS = Unit.QUARTER_YEARS; //季度为概念的单位

TemporalAdjuster - 时间调整器

接口,是修改时间对象的关键工具,可以用于任何基于时间/ Temporal 的类型。

它们的存在是为了将调整过程外部化,允许根据策略设计模式使用不同的方法。

TemporalAdjusters类包含一组标准的调整器,可用作静态方法。 这些包括:
查找该月的第一天或最后一天
找到下个月的第一天
找到一年的第一天或最后一天
寻找明年的第一天
查找一个月内的第一天或最后一天,例如“六月的第一个星期三”
查找下一周或上一天,例如“下周四”

实现类:

LocalDate、LocalTime、LocalDateTime、Instant、Year、YearMonth、Month、MonthDay、DayOfMoth等类或枚举

public enum Month implements TemporalAccessor, TemporalAdjuster{
    @Override
    public Temporal adjustInto(Temporal temporal) {
        if (Chronology.from(temporal).equals(IsoChronology.INSTANCE) == false) {
            throw new DateTimeException("Adjustment only supported on ISO date-time");
        }
        return temporal.with(MONTH_OF_YEAR, getValue()); //修改指定参数的值
    }
    ...
}

TemporalAdjusters

TemporalAdjusters为工具类,用于产生一个调整器对象。


示例:

with(TemporalAdjuster adjuster)为时间日期修改方法。

public void testTemporal(){
    LocalDateTime now = LocalDateTime.now(ZoneOffset.systemDefault()); //2021-10-02T14:53:56.975302500
    System.out.println(now);

    LocalDateTime with = now.with(TemporalAdjusters.firstDayOfMonth()); //2021-10-01T14:53:56.975302500
    System.out.println(with);

    LocalDateTime with1 = now.with(with); //2021-10-01T14:53:56.975302500
    System.out.println(with1);


}


自定义转化器:

自定义一个调结器;比如:每月 15 号发工资,但是你是 15 号后入职的,那么就月底最后一天发,如果遇到周 6 周日,则推前到周 5

public void fun27(){
    LocalDate d1 = LocalDate.of(2018, 05, 13);
    LocalDate d2 = LocalDate.of(2018, 05, 16);
    PaydayAdjuster adjuster = new PaydayAdjuster();
    System.out.println(d1.with(adjuster)); //2018-05-15
    System.out.println(d2.with(adjuster)); // 2018-05-31
}

public class PaydayAdjuster implements TemporalAdjuster {

    /**
         * The adjustInto method accepts a Temporal instance
         * and returns an adjusted LocalDate. If the passed in
         * parameter is not a LocalDate, then a DateTimeException is thrown.
         */
    public Temporal adjustInto(Temporal input) {
        LocalDate date = LocalDate.from(input);
        int day;
        if (date.getDayOfMonth() < 15) {
            day = 15;
        } else {
            // 如果大于15号,则当月最后一天
            day = date.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
        }
        date = date.withDayOfMonth(day);
        if (date.getDayOfWeek() == DayOfWeek.SATURDAY ||
            date.getDayOfWeek() == DayOfWeek.SUNDAY) {
            // 如果是周6或周日,则当前时间的前一个星期五
            // 也就是:如果遇到发工资那天是星期周六日的话,则提前到周五
            date = date.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
        }

        return input.with(date);
    }
}

TemporalQuery - 时间查询

接口,表示用于检索来自基于时间的对象的信息。
TemporalQuery 是从时间对象中提取信息的关键工具。

根据策略设计模式,它们的存在是为了将查询过程外部化,允许使用不同的方法。 示例可能是检查日期是否为闰年 2 月 29 日之前的一天的查询,或计算距您下一个生日的天数。

TemporalField接口提供了另一种查询时间对象的机制。 该接口仅限于返回long 。 相比之下,查询可以返回任何类型。

有两种使用TemporalQuery等效方法。

  • 第一种是直接调用这个接口上的方法。

  • 第二种是使用TemporalAccessor.query(TemporalQuery) ,建议使用第二种方法 ,因为它在代码中更清晰。

    temporal = thisQuery.queryFrom(temporal);
    temporal = temporal.query(thisQuery);
    

最常见的实现是方法引用,例如LocalDate::from和ZoneId::from 。

TemporalQueries以静态方法的形式提供了其他常见查询。

TemporalQueries

TemporalQueries 类(注意复数)提供多个预定义的查询,包括当应用程序不能识别基于时间的对象的类型是有用的方法。

public void test2(){

    LocalDate localDate = LocalDate.now();
    LocalTime localTime = LocalTime.now();
    LocalDateTime localDateTime = LocalDateTime.now();

    TemporalQuery<Chronology> chronology = TemporalQueries.chronology();
    //方式一:
    Chronology query = chronology.queryFrom(localDate);
    //方式二:
    Chronology query1 = localDate.query(chronology);

    System.out.println(query); //ISO
    System.out.println(query1); //ISO


    Chronology query2 = localTime.query(chronology);

    System.out.println(query2); //null

    Chronology query3 = localDateTime.query(chronology);
    System.out.println(query3); //ISO

}


自定义查询器

查询一个日子是否是家人重要的日子

@Test
public void fun29() {
    LocalDate date = LocalDate.now();
    // 不使用Lambda表达式查询
    Boolean isFamilyVacation = date.query(new FamilyVacations());

    // 使用Lambda表达式查询
    Boolean isFamilyBirthday = date.query(e ->{return FamilyBirthdays.isFamilyBirthday(e);}); //可优化为方法引用

    if (isFamilyVacation.booleanValue() || isFamilyBirthday.booleanValue())
        System.out.printf("%s 是一个重要的日子!%n", date);
    else
        System.out.printf("%s 不是一个重要的日子.%n", date);
}

// 该日子 去游乐园玩耍的日子
public class FamilyVacations implements TemporalQuery<Boolean> {
    @Override
    public Boolean queryFrom(TemporalAccessor date) {
        int month = date.get(ChronoField.MONTH_OF_YEAR);
        int day = date.get(ChronoField.DAY_OF_MONTH);

        // Disneyland over Spring Break
        // 4月 3号 ~ 4月8号 (包括)
        if ((month == Month.APRIL.getValue()) && ((day >= 3) && (day <= 8)))
            return Boolean.TRUE;

        // Smith family reunion on Lake Saugatuck
        // 8月 8号~14号 (包括)
        if ((month == Month.AUGUST.getValue()) && ((day >= 8) && (day <= 14)))
            return Boolean.TRUE;

        return Boolean.FALSE;
    }
}

// 该日子 检查是否是家人的生日
public static class FamilyBirthdays {
    // 只检查月和日
    public static Boolean isFamilyBirthday(TemporalAccessor date) {
        int month = date.get(ChronoField.MONTH_OF_YEAR);
        int day = date.get(ChronoField.DAY_OF_MONTH);

        // Angie's 的生日是4月3号
        if ((month == Month.APRIL.getValue()) && (day == 3))
            return Boolean.TRUE;

        // Sue's 的生日是6月18号
        if ((month == Month.JUNE.getValue()) && (day == 18))
            return Boolean.TRUE;

        // Joe's 的生日是5月29号
        if ((month == Month.MAY.getValue()) && (day == 29))
            return Boolean.TRUE;

        return Boolean.FALSE;
    }
}


TemporalAmount - 时间时间量

框架级接口,用于定义时间量,表示一段时间,例如“6 小时”、“8 天”或“2 年零 3 个月”。
amount可以被认为是 TemporalUnit 到 long 的映射 ,通过getUnits()和get(TemporalUnit)公开。

一个简单的案例可能只有一个单位值对,例如“6 小时”。 更复杂的情况可能有多个单位值对,例如“7 年 3 个月和 5 天”。

有两种常见的实现方式:

  • Period是基于日期的实现,存储年、月和日。
  • Duration是基于时间的实现,存储秒和纳秒,但使用其他基于持续时间的单位(例如分钟、小时和固定的 24 小时天)提供一些访问。

此接口是框架级接口,不应在应用程序代码中广泛使用。 相反,应用程序应该创建并传递具体类型的实例,例如Period和Duration

Duration

TemporalAmount的实现类,基于时间的值(秒,毫微秒)的时间量

public void test3(){
    Instant now = Instant.now();
    Instant instant = LocalDateTime.now(ZoneOffset.of("+02:00")).toInstant(ZoneOffset.of("-06:00"));
    Duration between = Duration.between(now, instant);
    System.out.println(between.getNano());
}

Period

TemporalAmount的实现类,基于日期的值(年,月,日)的时间量

@Test
public void test4(){
    LocalDate of = LocalDate.of(1998, 12, 17);
    LocalDate now = LocalDate.now(ZoneOffset.of("+08:00"));
    Period p = Period.between(of,now);

    long p2 = ChronoUnit.DAYS.between(of, now);
    System.out.println("You are " + p.getYears() + " years, " + p.getMonths() +
                       " months, and " + p.getDays() +
                       " days old. (" + p2 + " days total)"); //You are 22 years, 9 months, and 15 days old. (8325 days total)
}

Other

ChronoUnit中定义了用于测量时间(秒,毫微秒)的单位。当你想要在一个单位的时间内测量一段时间,比如几天或几秒时, ChronoUnit.between 可以做到。between 方法与所有基于时间的对象一起工作,但是它只返回单个单元的数量

Instant current = Instant.now();
// 10秒前
Instant previous = current.minus(10, ChronoUnit.SECONDS);
if (previous != null) {
    // 计算两个时间之前间隔多少毫秒
    long between = ChronoUnit.MILLIS.between(previous, current);
    System.out.println(between); // 10000
}

Instant - 时间戳

表示时间线上的一个瞬时点, java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间 单位。

Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。

它只是 从格林威治时间开始的秒数,java.time包是基于纳秒来计算的,因此Instant可以精确到纳秒。


换算单位:

(1 ns = 10-9 s) 1秒 = 1000毫秒 =106微秒=109纳秒


方法:

now() :

静态方法,返回默认UTC时区的Instant类的对象。

ofEpochMilli(long epochMilli):

静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒 数之后的Instant类的对象。

atOffset(ZoneOffset offset) :

结合即时的偏移来创建一个 OffsetDateTime。

toEpochMilli() :

返回1970-01-01 00:00:00到当前时间的毫秒数,即为时间戳。

。。。。

示例:

public void test1(){

    LocalDateTime now = LocalDateTime.now(ZoneOffset.of("+02:00")); //东2区
    System.out.println(now);  //2021-10-02T14:55:32.229830400

    LocalDateTime now1 = LocalDateTime.now(ZoneOffset.of("-04:00")); //西4区
    System.out.println(now1); //2021-10-02T08:55:32.229830400


    Instant instant = now.toInstant(ZoneOffset.of("+06:00")); //中国位于东8区,加6时区

    System.out.println(instant); //2021-10-02T08:55:32.229830400Z


    boolean after = instant.isAfter(Instant.now().plus(-1, ChronoUnit.DAYS));
    System.out.println(after); //true
}

LocalTime、LocalDate、LocalDateTime - 时间与日期

LocalTime、LocalDate、LocalDateTime 这三个类是根据ISO - 8601标准来表示日期、时间、日期和时间的类,这三个类都是不可变类,生成的对象是不可变对象,它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。


🕥 ​百度百科:

国际标准化组织的国际标准ISO 8601是日期和时间的表示方法 – 北京时间2004年5月3日下午5点30分8秒,可以写成2004-05-03T17:30:08+08:00或20040503T173008+08。


优势:

线程安全

LocalDate 月份和星期都改成了 enum,月份从1开始

提供了丰富的时间日期推算方法


方法:

now() / * now(ZoneId zone) :

静态方法,根据当前系统时区/指定时区获取对象。

of():

静态方法,根据指定日期/时间创建对象

getDayOfMonth() | getDayOfYear():

获得月份天数(1-31) /获得年份天数(1-366)。

getDayOfWeek():

获得星期几(返回一个 DayOfWeek 枚举值)。

getMonth():

获得月份, 返回一个 Month 枚举值。

getMonthValue() / getYear():

获得月份(1-12) /获得年份。

getHour()/getMinute()/getSecond():

获得当前对象对应的小时、分钟、秒。

withDayOfMonth()/withDayOfYear()/ withMonth()/withYear():

将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象。

plusYears(long yearsToAdd) | plusMonths(long monthsToAdd) | plusWeeks(long weeksToAdd) | plusDays(long daysToAdd):

向当前对象添加几天、几周、几个月、几年。

minusMonths() | minusWeeks()| minusDays() | minusYears() | minusHours():

从当前对象减去几月、几周、几天、几年。

LocalTime

定义:

public final class LocalTime implements Temporal, TemporalAdjuster, Comparable<LocalTime>, Serializable {
    。。。。
}


表示为 ISO-8601 日历系统中没有时区的时间,用于表示一天中的时间。例如10:15:30 。
LocalTime是一个不可变的日期时间对象,表示时间,通常被视为小时-分钟-秒。 时间以纳秒精度表示。 例如,值“13:45.30.123456789”可以被存储在一个LocalTime 。
此类不存储或表示日期或时区。 相反,它是对挂钟上显示的当地时间的描述。 如果没有偏移量或时区等附加信息,它就无法表示时间线上的瞬间。

初始化


public void test1(){
    LocalTime now = LocalTime.now();
    LocalTime now1 = LocalTime.now(Clock.systemUTC());
    LocalTime now2 = LocalTime.now(ZoneId.of("America/New_York"));
    LocalTime now3 = LocalTime.from(now);
    LocalTime now4 = LocalTime.of(21, 15, 45, 434324);
    LocalTime now5 = LocalTime.ofInstant(Instant.now(), ZoneOffset.of("+02:00"));
    LocalTime now6 = LocalTime.ofNanoOfDay(1);
    LocalTime now7 = LocalTime.ofSecondOfDay(1);
    LocalTime parse = LocalTime.parse("13:45:30.123456789");
}

修改、增加、减少

image-20211002213430847

withxx - 修改

返回此时间的副本,并将指定字段设置为新值。

public void testWith(){
    LocalTime now = LocalTime.now(); //21:42:32.517650700
    System.out.println(now);


    LocalTime with1 = now.withHour(3);
    LocalTime with2 = now.withMinute(4);
    LocalTime with3 = now.withSecond(2);
    LocalTime with4 = now.withNano(32121);


    boolean supported = now.isSupported(ChronoField.HOUR_OF_AMPM);
    if (supported) {
        LocalTime with5 = now.with(ChronoField.HOUR_OF_AMPM, 2);
        System.out.println(with5); //14:42:32.517650700
    }

    LocalTime with6 = now.with(with1);
}

plusxx - 增加

返回此时间的副本,并添加指定的数量。

public void testPlus(){
    LocalTime now = LocalTime.now();
    System.out.println(now); //21:50:36.337918700

    LocalTime plus1 = now.plusHours(2);
    LocalTime plus2 = now.plusMinutes(12);
    LocalTime plus3 = now.plusSeconds(23);
    LocalTime plus4 = now.plusNanos(32131);

    if (now.isSupported(ChronoUnit.HOURS)) {
        LocalTime plus5 = now.plus(3, ChronoUnit.HOURS);
        System.out.println(plus5); //00:50:36.337918700
    }

    LocalTime plus6 = now.plus(Duration.between(LocalTime.now(), LocalTime.parse("13:45:30.123456789")));
}







返回此时间减去指定数量的副本

public void testMinus(){
    LocalTime now = LocalTime.now();
    System.out.println(now); //21:55:16.643077900

    LocalTime minus1 = now.minusHours(2);
    LocalTime minus2 = now.minusMinutes(12);
    LocalTime minus3 = now.minusSeconds(12);
    LocalTime minus4 = now.minusNanos(3231);

    if (now.isSupported(ChronoUnit.HOURS)) {
        LocalTime plus5 = now.minus(3, ChronoUnit.HOURS);
        System.out.println(plus5); //18:55:16.643077900
    }

    LocalTime plus6 = now.minus(Duration.between(LocalTime.now(), LocalTime.parse("13:45:30.123456789")));

    System.out.println(plus6); //06:05:03.162699011

}

Other

public void testOther(){
    LocalTime now1 = LocalTime.now();

    LocalTime now2 = LocalTime.now(ZoneOffset.of("+02:00"));


    boolean after = now1.isAfter(now2); //true
    boolean before = now1.isBefore(now2); //false


    LocalDateTime localDateTime = now1.atDate(LocalDate.now());
    OffsetTime offsetTime = now1.atOffset(ZoneOffset.of("+08:00"));
    
    

}

LocalData

定义:

public final class LocalDate implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable{
	。。。
}


ISO-8601 日历系统中没有时区的日期,例如2007-12-03 。
LocalDate是表示日期的不可变日期时间对象,通常被视为年-月-日。 也可以访问其他日期字段,例如年中的某天、一周中的某天和一年中的某周。 例如,value “2nd October 2007”可以存储在LocalDate 。
此类不存储或表示时间或时区。 相反,它是对日期的描述,比如用作生日。 如果没有偏移量或时区等附加信息,它就无法表示时间线上的瞬间。
ISO-8601 日历系统是当今世界大部分地区使用的现代民用日历系统。 它相当于预兆公历系统,其中今天的闰年规则适用于所有时间。 对于当今编写的大多数应用程序,ISO-8601 规则完全适用。 但是,任何使用历史日期并要求它们准确的应用程序都会发现 ISO-8601 方法不合适。

初始化

public void testInit(){
    LocalDate now = LocalDate.now();
    LocalDate now1 = LocalDate.now(Clock.systemDefaultZone());
    LocalDate now2 = LocalDate.now(ZoneId.of("America/New_York"));
    LocalDate of = LocalDate.of(2021, 10, 2);
    LocalDate from = LocalDate.from(now);
    LocalDate parse = LocalDate.parse("2007-12-03");
    LocalDate localDate = LocalDate.ofInstant(Instant.now(), ZoneOffset.of("+08:00"));
}

修改、添加、减少

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Nxk1sTE-1633605433110)(C:/Users/bihai/Desktop/JDK%E6%97%B6%E9%97%B4%E6%97%A5%E6%9C%9FAPI/assets/image-20210930172102096.png)]

withxx - 修改
/**
* 修改LocalData的年、月、日
*/
public void testWith(){
    LocalDate localDate = getLocalDate(); //2021-09-30
    LocalDate localDate1 = localDate.withYear(2022); //2022-09-30
    LocalDate localDate2 = localDate.withMonth(10); //2021-10-30
    LocalDate localDate3 = localDate.withDayOfMonth(23); //2021-09-23
    LocalDate localDate4 = localDate.withDayOfYear(1); //2021-01-01
}

**参数 ---- TemporalAdjuster 调整器 **

当前时间为:2021/9/30

TemporalAdjusters工具类:

LocalDate with = localDate.with(TemporalAdjusters.firstDayOfMonth()); //2021/9/1

TemporalAdjuster接口的实现类:

LocalDate date = localDate.with(Month.JULY); // 2021/7/30


LocalDate date = localDate.with(Year.of(2022)); // 2022/9/30

返回 7 月最后一天的日期:

LocalDate date = localDate.with(Month.JULY).with(TemporalAdjusters.lastDayOfMonth()); //2021-07-31

参数 — TemporalField 时间属性

对指定的枚举属性进行赋值改变,最常用的单位在ChronoField中定义。 在IsoFields 、 WeekFields和JulianFields中提供了更多字段

LocalDate with2 = localDate.with(ChronoField.DAY_OF_WEEK, 7); //2021-10-03 目前星期四

plusxx - 增加

简单示例:

public void testPlus(){
    LocalDate localDate = getLocalDate(); //2021-09-30
    final LocalDate localDate1 = localDate.plusDays(21); //2021-10-21
    LocalDate localDate2 = localDate.plusMonths(2); //2021-11-30
    LocalDate localDate3 = localDate.plusWeeks(2); //2021-10-14
    LocalDate localDate4 = localDate.plusYears(2); //2023-09-30
}


参数 — TemporalAmount

LocalDate plus = localDate.plus(Period.between(LocalDate.of(2021,9,29), LocalDate.of(2022, 10, 30))); //2022-10-31


参数 — TemporalUnit

LocalDate plus = now.plus(1, ChronoUnit.DAYS);

minus - 减少
public void testMinus(){
    LocalDate now = LocalDate.now();

    LocalDate minus = now.minus(Period.between(LocalDate.now(), LocalDate.now(ZoneOffset.of("+08:00"))));

    if(now.isSupported(ChronoUnit.DAYS)){
        LocalDate minus1 = now.minus(1, ChronoUnit.DAYS);
    }

}

LocalDataTime

定义:

public final class LocalDateTime
implements Temporal, TemporalAdjuster, ChronoLocalDateTime<LocalDate>, Serializable {
    
}


表示ISO-8601 日历系统中没有时区的日期时间,例如2007-12-03T10:15:30 。
LocalDateTime是一个不可变的日期时间对象,表示日期时间,通常被视为年-月-日-时-分-秒。 也可以访问其他日期和时间字段,例如一年中的某一天、一周中的某一天和一年中的一周。 时间以纳秒精度表示。 例如,值“2nd October 2007 at 13:45.30.123456789”可以存储在LocalDateTime 。
此类不存储或表示时区。 相反,它是对日期的描述,用于生日,结合挂钟上的当地时间。 如果没有偏移量或时区等附加信息,它就无法表示时间线上的瞬间。


方法:

大部分方法类似于LocalData、LocalTime

@Test
public void test1(){

    LocalDateTime now = LocalDateTime.now(ZoneOffset.of("+02:00")); //东2区
    System.out.println(now);  //2021-10-02T14:55:32.229830400

    LocalDateTime now1 = LocalDateTime.now(ZoneOffset.of("-04:00")); //西4区
    System.out.println(now1); //2021-10-02T08:55:32.229830400


    Instant instant = now.toInstant(ZoneOffset.of("+06:00")); //中国位于东8区,加6

    System.out.println(instant); //2021-10-02T08:55:32.229830400Z


    boolean after = instant.isAfter(Instant.now().plus(-1, ChronoUnit.DAYS));
    System.out.println(after); //true
}


ZonedDateTime、OffsetDateTime、OffsetTime - 时间与日期

区别于上面的,它们包含时区信息

介绍:

  • ZonedDateTime 使用格林威治/ UTC 的时区偏移量处理具有相应时区的日期和时间。
  • OffsetDateTime 使用格林威治/ UTC 的相应时区偏移量处理日期和时间,但不包含时区 ID。
  • OffsetTime 使用格林威治/ UTC 的相应时区偏移量处理时间,但不包含时区 ID。


选择:

如果您正在编写复杂的软件, 该软件根据地理位置对自己的日期和时间计算规则进行建模,或者将时间戳存储在仅跟踪格林威治/ UTC 时间的绝对偏移量的数据库中, 则可能需要使用 OffsetDateTime。另外,XML 和其他网络格式将日期时间传输定义为 OffsetDateTime 或 OffsetTime。

尽管所有三个类都保持了格林威治/ UTC 时间的偏移量,但只有 ZonedDateTime 使用 ZoneRules (java.time.zone 包的一部分)来确定偏移量对于特定时区的变化方式。例如,大多数时区在将时钟向前移动到夏令时时遇到间隙(通常为 1 小时), 并且在将时钟移回标准时间和重复转换前的最后一个小时时,时间重叠。该 ZonedDateTime 类适应这种情况, 而 OffsetDateTime 和 OffsetTime 类,它们不具备访问 ZoneRules

ZonedDateTime

结合了 LocalDateTime 与类 了 zoneid 类。它用于表示具有时区(地区/城市,如欧洲/巴黎)的完整日期(年,月,日)和时间(小时,分钟,秒,纳秒)


示例:

示例中定义了从旧金山到东京的航班的出发时间,作为美国/洛杉矶时区的 ZonedDateTime。 该 withZoneSameInstant 和 plusMinutes 方法用于创建实例 ZonedDateTime 代表在东京的预计到达时间, 650 分钟的飞行后。该 ZoneRules.isDaylightSavings 方法确定它是否是当飞机抵达东京是否是夏令时。

public void test(){
    DateTimeFormatter format = DateTimeFormatter.ofPattern("YYYY-MM-dd  HH:mm:ss");

    // Leaving from San Francisco on July 20, 2013, at 7:30 p.m.
    //  2013-07-20  19:30:00
    LocalDateTime leaving = LocalDateTime.of(2013, Month.JULY, 20, 19, 30);
    ZoneId leavingZone = ZoneId.of("America/Los_Angeles");
    ZonedDateTime departure = ZonedDateTime.of(leaving, leavingZone);

    try {
        String out1 = departure.format(format);
        System.out.printf("LEAVING:  %s (%s)%n", out1, leavingZone);
    } catch (DateTimeException exc) {
        System.out.printf("%s can't be formatted!%n", departure);
        throw exc;
    }

    // Flight is 10 hours and 50 minutes, or 650 minutes
    ZoneId arrivingZone = ZoneId.of("Asia/Tokyo");
    // 使用美国洛杉矶出发的时间,然后换算成东京的时区,返回该时区对应的时间
    ZonedDateTime arrival = departure.withZoneSameInstant(arrivingZone)
        .plusMinutes(650); // 在该时区的基础上加650分钟

    try {
        String out2 = arrival.format(format);
        System.out.printf("ARRIVING: %s (%s)%n", out2, arrivingZone);
    } catch (DateTimeException exc) {
        System.out.printf("%s can't be formatted!%n", arrival);
        throw exc;
    }

    // 夏令时
    if (arrivingZone.getRules().isDaylightSavings(arrival.toInstant()))
        System.out.printf("  (%s daylight saving time will be in effect.)%n",
                          arrivingZone);
    else
        // 标准时间
        System.out.printf("  (%s standard time will be in effect.)%n",
                          arrivingZone);
}

OffsetDateTime

结合了 LocalDateTime 与类 ZoneOffset 类。它用于表示格林威治/ UTC 时间的偏移量 (+/-小时:分钟,例如 +06:00 或-)的整个日期(年,月,日)和时间(小时,分钟,秒,纳秒)08:00)。


示例:

// 2017.07.20 19:30
LocalDateTime localDate = LocalDateTime.of(2013, Month.JULY, 20, 19, 30);
ZoneOffset offset = ZoneOffset.of("-08:00");

OffsetDateTime offsetDate = OffsetDateTime.of(localDate, offset);

// 当前时间月中的最后一个周4
OffsetDateTime lastThursday =
        offsetDate.with(TemporalAdjusters.lastInMonth(DayOfWeek.THURSDAY));
System.out.printf("The last Thursday in July 2013 is the %sth.%n",
                  lastThursday.getDayOfMonth()); //2017.07.25 19:30

OffsetTime

结合 LocalDateTime 与类 ZoneOffset 类。它用于表示格林威治/ UTC 时间偏移 (+/-小时:分钟,例如+06:00或-08:00)的时间(小时,分钟,秒,纳秒)。 OffsetTime 类是在同一场合的使用 OffsetDateTime 类,但跟踪的日期(Date)时不需要。


示例:

  • withZoneSameInstant : 调用了 toEpochSecond 把当前的时间纳秒 结合 指定的偏移量换算成新的纳秒
  • withZoneSameLocal :不会换算时间,只是把时区更改了
@Test
public void testInit(){

    LocalTime now = LocalTime.now();
    System.out.println(now); //23:47:00.679417

    OffsetTime of = OffsetTime.of(now, ZoneOffset.of("+08:00"));
    System.out.println(of); //23:47:00.679417+08:00


    OffsetTime offsetTime = of.withOffsetSameInstant(ZoneOffset.of("+02:00"));
    OffsetTime offsetTime1 = of.withOffsetSameLocal(ZoneOffset.of("+02:00"));

    System.out.println(offsetTime); //17:47:00.679417+02:00
    System.out.println(offsetTime1); //23:47:00.679417+02:00

}


DateTimeFormatter - 时间日期格式化

1️⃣用于时间或日期的格式化(format)与解析(parse),格式化(时间 -> 文本字符串),解析(文本字符串 -> 时间)。

DateTimeFormatter的通用实现:

  • 预定义的标准格式。如: ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
  • 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
  • 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

DateTimeFormatterBuilder提供了更复杂的格式化器。

2️⃣ 主要的日期时间类提供了两个方法——一个用于格式化,format(DateTimeFormatter格式化器),另一个用于解析,parse(CharSequence text, DateTimeFormatter格式化器)。

以LocalDate为例:

public void test1(){
    LocalDate now = LocalDate.now();
    String format = now.format(DateTimeFormatter.ISO_DATE); 
    System.out.println(format); //2021-10-03

    LocalDate parse = LocalDate.parse(format, DateTimeFormatter.ISO_DATE);
    System.out.println(parse); //2021-10-03
}


3️⃣ ​除了格式之外,还可以使用所需的Locale、Chronology、ZoneId和DecimalStyle创建格式化程序。

  • withLocale方法返回一个覆盖区域设置的新格式化程序。 语言环境影响格式化和解析的某些方面。 例如,ofLocalizedDate提供了一个格式化程序,该格式化程序使用地区特定的日期格式。

    /**
     *
     *  FULL和LONG样式通常需要时区。 使用这些样式进行格式化时, ZoneId必须可用,通过使用ZonedDateTime或withZone
     */
    @Test
    public void test2(){
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now); //2021-10-03T14:35:58.417011
    
        DateTimeFormatter locale = DateTimeFormatter
            .ofLocalizedDateTime(FormatStyle.LONG)
            .withZone(ZoneOffset.of("+06:00"))
            .withLocale(Locale.FRANCE);
    
        String format = now.format(locale);
        System.out.println(format); //3 octobre 2021 à 14:35:58 +06:00
    
        DateTimeFormatter locale1 = DateTimeFormatter
            .ofLocalizedDateTime(FormatStyle.LONG)
            .withZone(ZoneOffset.of("+06:00"))
            .withLocale(Locale.CHINA);
    
        String format1 = now.format(locale1);
        System.out.println(format1); //2021年10月3日 +06:00 下午2:35:58
    
    
    }
    
  • withChronology方法返回一个新的格式化程序,该格式化程序将覆盖该年表。 如果覆盖,日期-时间值将format为格式化前的时间。 在parse日期-时间值时,会将其转换为返回之前的年表。

  • withZone方法返回一个覆盖该区域的新格式化程序。 如果重写,则在格式化之前将日期-时间值转换为带有请求的ZoneId的Zonedatetime。 在解析ZoneId时,在返回值之前应用ZoneId。

    public void test3(){
        ZonedDateTime now = ZonedDateTime.now();
    
        DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneOffset.of("+06:00"));
    
        String format = now.format(formatter);
        System.out.println(format); //2021-10-03T13:05:17.0848843+06:00
    }
    
    
  • withDecimalStyle方法返回一个覆盖DecimalStyle的新格式化程序。 DecimalStyle符号用于格式化和解析。

有些应用程序可能需要使用较旧的java.text.Format类进行格式化。 toFormat()方法返回java.text.Format的实现。

4️⃣ 预定义格式

image-20211003151550911

5️⃣ 格式化和解析模式

 符号       含义                    		描述      		示例
  ------  -------                     ------------      ------------------
   G       era (时代)                   text              AD; Anno Domini; A
   u       year                        year              2004; 04
   y       year-of-era                 year              2004; 04
   D       day-of-year                 number            189
   M/L     month-of-year               number/text       7; 07; Jul; July; J
   d       day-of-month                number            10

   Q/q     quarter-of-year(季节)        number/text       3; 03; Q3; 3rd quarter
   Y       week-based-year             year              1996; 96
   w       week-of-week-based-year     number            27
   W       week-of-month               number            4
   E       day-of-week                 text              Tue; Tuesday; T
   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
   F       week-of-month               number            3

   a       am-pm-of-day(上午、下午)      text              PM
   h       clock-hour-of-am-pm (1-12)  number            12
   K       hour-of-am-pm (0-11)        number            0
   k       clock-hour-of-am-pm (1-24)  number            0

   H       hour-of-day (0-23)          number            0
   m       minute-of-hour              number            30
   s       second-of-minute            number            55
   S       fraction-of-second          fraction          978
   A       milli-of-day                number            1234
   n       nano-of-second              number            987654321
   N       nano-of-day                 number            1234000000

   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
   z       time-zone name              zone-name         Pacific Standard Time; PST
   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;

   p       pad next                    pad modifier      1

   '       escape for text             delimiter
   ''      single quote                literal           '
   [       optional section start
   ]       optional section end
   #       reserved for future use
   {       reserved for future use
   }       reserved for future use


模式字母的数量决定了格式:

Text: 文本样式根据使用的pattern letters数量确定。少于4个pattern letters将使用short form。正好4个pattern letters将使用full form。正好5个pattern letters将使用narrow form。 Pattern letters “L”,"c"和"q"指定文本样式的独立形式。

Number: 如果字母数量为1,则该值使用最小位数输出,不进行填充。否则以数字计数用作输出字段的宽度,必要时将值填充为零。 以下模式字母对pattern letters有限制。 “c”和“F” 只能指定一个字母。 ‘d’, ‘H’, ‘H’, ‘K’, ‘K’, ‘m’和’s’ 最多可以指定两个字母。 “D” 最多可以指定三个字母。

Number/Text: 如果pattern letters的数量为 3 或更多,请使用上面的 Text 规则。否则使用上面的Number规则。

Fraction: 以几分之一秒的形式输出纳米秒字段。 纳米秒值有9位数字,因此pattern letters的计数从1到9。 如果小于9,则纳秒值被截断,只输出最有效的数字。

Year: 字母的数量决定了在填充下面使用的最小字段宽度。 如果字母数是2,则使用简化的两位数形式。 对于打印,输出最右边的两位数字。 对于解析,这将使用基值2000进行解析,结果是在2000到2099(包括2000在内)范围内的一年。 如果字母数小于4(而不是2),则按照SignStyle.NORMAL只在负年份输出该符号。 否则,如SignStyle.EXCEEDS_PAD所示,如果超出了填充宽度,则输出符号。

ZoneId: 输出时区ID,例如“Europe/Paris”。 如果字母数为2,则输出时区ID。 任何其他字母计数抛出IllegalArgumentException。

Zone names: 输出时区ID的显示名称。 如果模式字母为’z’,则输出为夏时制感知区域名称。 如果没有足够的信息来确定是否应用夏令时,将使用忽略夏令时的名称。 如果字母数为1、2或3,则输出短名称。 如果字母数为4,则输出全名。 五个或更多的字母抛出IllegalArgumentException。

如果模式字母是’v’,则输出提供忽略夏令时的区域名称。 如果字母数为1,则输出短名称。 如果字母数为4,则输出全名。 两个、三个和五个或更多的字母抛出IllegalArgumentException。

Offset X和X: 根据pattern letters的数量格式化偏移量。 一个字母只输出小时,如’+01’,除非分钟非零,在这种情况下分钟也输出,如’+0130’。 两个字母输出小时和分钟,不带冒号,例如’+0130’。 三个字母输出小时和分钟,带一个冒号,例如’+01:30’。 四个字母输出时、分和可选的秒,没有冒号,如’+013015’。 五个字母输出时、分和可选的秒,用冒号,例如’+01:30:15’。 六个或更多的字母抛出IllegalArgumentException。 当要输出的偏移量为0时,模式字母’X’(大写)将输出’Z’,而模式字母’X’(小写)将输出’+00’、’+0000’或’+00:00’。

Offset O: 根据pattern letters的数量格式化本地化偏移量。 一个字母输出本地化偏移量的简写形式,即本地化偏移量文本,例如’GMT’,带有不带前导零的小时,如果非零则可选两位数的分钟和秒,以及冒号,例如’GMT+8’。 四个字母输出完整的表单,它是本地化的偏移文本,例如’GMT,带有两位数字的小时和分钟字段,如果非零,第二个字段是可选的,以及冒号,例如’GMT+08:00’。 任何其他字母计数抛出IllegalArgumentException

Offset Z: 这是基于pattern letters的数量格式化偏移。 一个、两个或三个字母输出小时和分钟,不带冒号,如’+0130’。 当偏移量为0时,输出将是’+0000’。 四个字母输出本地化offset的完整形式,相当于四个字母offset - o。 如果偏移量为零,则输出将是相应的本地化偏移量文本。 五个字母输出小时,分钟,如果非零,则可选秒,用冒号。 如果偏移量为零,则输出’Z’。 六个或更多的字母抛出IllegalArgumentException。

Optional section: 可选section标记的工作方式与调用DateTimeFormatterBuilder.optionalStart()和DateTimeFormatterBuilder.optionalEnd()完全相同。

Pad modifier: 修改紧接其后的模式以填充空格。 垫的宽度是由模式字母的数量决定的。 这与调用DateTimeFormatterBuilder.padNext(int)相同。 例如,'ppH’输出在左边填充宽度为2的小时数。

任何未识别的字母都是错误的。 任何非字母字符,除了’[’,’]’,’{’,’}’,’#'和单引号将直接输出。 尽管如此,还是建议在希望直接输出的所有字符周围使用单引号,以确保将来的更改不会破坏应用程序。


6️⃣ 解析

解析是作为两阶段操作实现的。 首先,使用格式化程序定义的布局解析文本,生成字段到值的Map、ZoneId和Chronology。 其次,通过验证、组合和简化各种字段,将解析的数据解析为更有用的字段。

这个类提供了五个解析方法。 其中四个执行解析和解析阶段。 第五种方法是parseUnresolved(CharSequence, ParsePosition),它只执行第一个阶段,不处理结果。 因此,它本质上是一个低级别的操作。

解析阶段由在该类上设置的两个参数控制。

ResolverStyle是一个枚举,它提供了三种不同的方法:strict, smart 、 lenient。 明智的选择是默认的。 它可以使用withResolverStyle(ResolverStyle)进行设置。

withResolverFields(TemporalField…)参数允许在解析开始之前过滤要解析的字段集。 例如,如果格式化程序已经解析了年、月、月的日和年的日,那么有两种方法来解析日期:(year + month + day-of-month)和(year + day-of-year)。 解析器字段允许选择这两种方法中的一种。 如果没有设置解析器字段,则两种方法的结果必须是相同的日期。


解析单独的字段以形成完整的日期和时间是一个复杂的过程,行为分布在许多类中。 它遵循以下步骤:

  1. chronology(年表)是已确定的。 结果的年表是已解析的年表,如果没有解析年表,则是该类上设置的年表,如果为空,则是IsoChronology。
  2. 将解析ChronoField日期字段。 这是通过使用年表实现的。 ResolverStyle resolveDate (Map)。 关于字段解析的文档位于Chronology的实现中。
  3. 解析ChronoField时间字段。 这在ChronoField上有记录,对于所有的年表都是一样的。
  4. 任何不是ChronoField的字段都将被处理。 这是通过使用TemporalField实现的。 解决(地图,TemporalAccessor, ResolverStyle)。 关于字段解析的文档位于TemporalField的实现中。
  5. 重新解析ChronoField日期和时间字段。 这允许步骤4中的字段生成ChronoField值,并将其处理为日期和时间。
  6. 如果一天中至少有一个小时可用,则形成LocalTime。 这涉及到提供分钟、秒和秒的默认值。
  7. 任何剩余的未解析字段将与任何已解析的日期和/或时间进行交叉检查。 因此,早期阶段将(年+月+月日)解析为一个日期,该阶段将检查该日期的周天数是否有效。
  8. 如果解析了多余的天数,那么如果日期可用,就将其添加到日期中。
  9. 如果存在基于秒的字段,但是LocalTime没有被解析,那么解析器将确保毫秒、微秒和纳秒值可用,以满足ChronoField的约定。 如果缺失,这些将被设为零。
  10. 如果同时解析了日期和时间,并且存在偏移量或区域,则字段ChronoField。 INSTANT_SECONDS被创建。 如果解析了偏移量,那么该偏移量将与LocalDateTime组合,形成瞬间,忽略任何区域。 如果解析ZoneId时没有偏移量,那么该zone将与LocalDateTime结合,使用ChronoLocalDateTime.atZone(ZoneId)的规则形成即时。


方法:

ofPattern(String pattern):

静态方法, 返 回 一 个 指 定 字 符 串 格 式 的 DateTimeFormatter。

format(TemporalAccessor t):

格式化一个日期、时间,返回字符串。

parse(CharSequence text):

将指定格式的字符序列解析为一个日期、时间。


非ISO的日期转换

1️⃣ 主要日期和时间 API 建立在 ISO 日历系统上,年表在幕后运作以代表日历系统的一般概念。 例如日本、民国、泰国等。

java.time.chrono包中预定义了几个Chronology,包括:

image-20211004160239555


2️⃣ 大多数其他日历系统也以年、月和日的共享概念运作,与地球绕太阳和月球绕地球的周期相关联。 这些共享概念由ChronoField定义,可供任何Chronology实现使用, 有关此含义的完整讨论,请参阅ChronoLocalDate , 一般来说,建议是使用已知的基于 ISO 的LocalDate ,而不是ChronoLocalDate 。
虽然Chronology对象通常使用ChronoField并且基于日期的时代、年代、月份、月份模型,但这不是必需的。 Chronology实例可能代表一种完全不同的日历系统,例如玛雅。

实际上, Chronology实例也充当工厂。 of(String)方法允许通过标识符查找实例,而ofLocale(Locale)方法允许通过语言环境查找。

3️⃣ Chronology实例提供了一组方法来创建ChronoLocalDate实例。 日期类用于操作特定日期。

  • dateNow()
  • dateNow(clock)
  • dateNow(zone)
  • date(yearProleptic, month, day)
  • date(era, yearOfEra, month, day)
  • dateYearDay(yearProleptic, dayOfYear)
  • dateYearDay(era, yearOfEra, dayOfYear)
  • date(TemporalAccessor)


添加新日历
4️⃣ ​可用的年表集可以通过应用程序扩展。 添加新的日历系统需要编写Chronology 、 ChronoLocalDate和Era 。 大多数特定于日历系统的逻辑将在ChronoLocalDate实现中。 Chronology实现充当工厂。
为了允许发现额外的年表,使用了ServiceLoader 。 必须将一个名为“java.time.chrono.Chronology”的文件添加到META-INF/services目录中,其中列出了实现类。 有关服务加载的更多详细信息,请参阅 ServiceLoader。 对于通过 id 或 calendarType 查找,首先找到系统提供的日历,然后是应用程序提供的日历。
每个年表必须定义一个在系统内唯一的年表 ID。 如果年表表示由 CLDR 规范定义的日历系统,则日历类型是 CLDR 类型和 CLDR 变体(如果适用)的串联。
implSpec:
必须小心实现此接口以确保其他类正确运行。 所有可以实例化的实现都必须是最终的、不可变的和线程安全的。 子类应该尽可能可序列化。

ISO日期 - > 非ISO日期

@Test
public void test1(){
    LocalDateTime now = LocalDateTime.now();
    System.out.println("中国 : chronology = " + now.getChronology() + " date = " + now);

    JapaneseDate japaneseDate = JapaneseDate.from(now);
    System.out.println("日本 : chronology = " + japaneseDate.getChronology() + " date = " + japaneseDate);


    ThaiBuddhistDate thaiBuddhistDate = ThaiBuddhistDate.from(now);
    System.out.println("泰国 : chronology = " + thaiBuddhistDate.getChronology() + " date = " + thaiBuddhistDate);


    HijrahDate hijrahDate = HijrahDate.from(now);
    System.out.println("Hijrah : chronology = " + hijrahDate.getChronology() + " date = " + hijrahDate);

    MinguoDate minguoDate = MinguoDate.from(now);
    System.out.println("中华民国 : chronology = " + minguoDate.getChronology() + " date = " + minguoDate);

}


结果:
中国 : chronology = ISO date = 2021-10-04T16:21:16.555555300
日本 : chronology = Japanese date = Japanese Reiwa 3-10-04
泰国 : chronology = ThaiBuddhist date = ThaiBuddhist BE 2564-10-04
Hijrah : chronology = Hijrah-umalqura date = Hijrah-umalqura AH 1443-02-27
中华民国 : chronology = Minguo date = Minguo ROC 110-10-04


下列程序将 LocalDate 转换为 ChronoLocalDate 并返回到 String; 采用指定的日历表格式化成指定的格式;另外采用指定的日历表和格式解析字符串为 date; 注意 DateTimeFormatterBuilder() 的使用:

/*
 * Convert LocalDate -> ChronoLocalDate -> String and back.
 */

public class StringConverter {

    /**
     * 将LocalDate(ISO)值转换为日期日期日期
     * 使用所提供的年表,然后格式化
     * 使用DateTimeFormatter与一个字符串的日期时间
     * 基于年表和当前地区的短模式。
     * @param localDate - ISO日期转换和格式。
     * @param chrono    - 可选的日历年表,如果为空则默认使用IsoChronology
     */
    public static String toString(LocalDate localDate, Chronology chrono) {
        if (localDate != null) {
            // 特定功能获取/设置缺省语言环境。
            // 获取默认的语言环境
            Locale locale = Locale.getDefault(Locale.Category.FORMAT);
            ChronoLocalDate cDate;
            if (chrono == null) {
                chrono = IsoChronology.INSTANCE;
            }
            try {
                cDate = chrono.date(localDate);
            } catch (DateTimeException ex) {
                System.err.println(ex);
                chrono = IsoChronology.INSTANCE;
                cDate = localDate;
            }
            String pattern = "M/d/yyyy GGGGG";
            DateTimeFormatter dateFormatter =
                    DateTimeFormatter.ofPattern(pattern);
            return dateFormatter.format(cDate);
        } else {
            return "";
        }
    }

    /**
     * 使用DateTimeFormatter将字符串解析为计时日期
     * 基于当前语言环境的短模式
     * 提供年表,然后将其转换为LocalDate(ISO)值。
     * @param text   - 已简短的格式输入日期文本
     * @param chrono - 可选的日历年表,如果为空则默认使用IsoChronology
     */
    public static LocalDate fromString(String text, Chronology chrono) {
        if (text != null && !text.isEmpty()) {
            Locale locale = Locale.getDefault(Locale.Category.FORMAT);
            if (chrono == null) {
                chrono = IsoChronology.INSTANCE;
            }
            String pattern = "M/d/yyyy GGGGG";
            DateTimeFormatter df = new DateTimeFormatterBuilder().parseLenient()
                    .appendPattern(pattern)
                    .toFormatter()
                    .withChronology(chrono)
                    .withDecimalStyle(DecimalStyle.of(locale));
            TemporalAccessor temporal = df.parse(text);
            ChronoLocalDate cDate = chrono.date(temporal);
            return LocalDate.from(cDate);
        }
        return null;
    }

    public static void main(String[] args) {
        LocalDate date = LocalDate.of(1996, Month.OCTOBER, 29);
        System.out.printf("%s%n",
                          StringConverter.toString(date, JapaneseChronology.INSTANCE));
        System.out.printf("%s%n",
                          StringConverter.toString(date, MinguoChronology.INSTANCE));
        System.out.printf("%s%n",
                          StringConverter.toString(date, ThaiBuddhistChronology.INSTANCE));
        System.out.printf("%s%n",
                          StringConverter.toString(date, HijrahChronology.INSTANCE));

      // 转换/解析为基于ISO的日期

        System.out.printf("%s%n", StringConverter.fromString("10/29/0008 H",
                                                             JapaneseChronology.INSTANCE));
        System.out.printf("%s%n",
                          StringConverter.fromString("10/29/0085 1",
                                                     MinguoChronology.INSTANCE));
        System.out.printf("%s%n",
                          StringConverter.fromString("10/29/2539 B.E.",
                                                     ThaiBuddhistChronology.INSTANCE));
        System.out.printf("%s%n",
                          StringConverter.fromString("6/16/1417 1",
                                                     HijrahChronology.INSTANCE));
    }
}

非ISO日期 - > ISO日期

使用静态 LocalDate.from 方法将非 ISO 日期转换为 LocalDate 实例,其他基于时间的类也提供此方法,如果无法转换日期,则会引发 DateTimeException。

LocalDate date = LocalDate.from(JapaneseDate.now());


遗留时间日期API转换

在 Java SE 8 发布前,java 提供了日期时间机制的类 java.util.Date, java.util.Calendar 以及 java.util.TimeZone 类,以及它们的子类,如 java.util.GregorianCalendar 中。这些类有几个缺点,包括:

  • Calendar 是不安全的
  • 由于这些类是可变的,因此他们不能用于多线程
  • 应用程序代码中的错误是常见的,原因是不寻常的几个月和缺乏类型安全

与遗留代码的互操作性

也许你使用了 java.util 的日期相关的类,并且想对现有代码进行最小改动的情况下使用 java.time 的功能

JDK8 提供了几个方法允许 java.util 和 java.time 对象之间进行转换:

  • Calendar.toInstant()
  • GregorianCalendar.toZonedDateTime()
  • GregorianCalendar.from(ZonedDateTime)
  • Date.from(Instant)
  • Date.toInstant()
  • TimeZone.toZoneId()
  1. Calendar -》 ZonedDateTime,请注意,必须提供时区才能将 Instant 转换为 ZonedDateTime

    Calendar now = Calendar.getInstance();
    ZonedDateTime zdt = ZonedDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());
    
  2. Date 与 Instant相互转换

    Instant inst = date.toInstant();
    
    Date newDate = Date.from(inst);
    
  3. GregorianCalendar 与 ZonedDateTime相互转换

    GregorianCalendar cal = new GregorianCalendar();
    TimeZone tz = cal.getTimeZone();
    int tzoffset = cal.get(Calendar.ZONE_OFFSET); // 获取偏移量
    
    ZonedDateTime zdt = cal.toZonedDateTime();
    LocalDateTime ldt = zdt.toLocalDateTime();
    LocalDate date = zdt.toLocalDate();
    LocalTime time = zdt.toLocalTime();
    
    
    GregorianCalendar newCal = GregorianCalendar.from(zdt);
    

java.util Date 与 java.time 功能映射

java.util.Date 与 java.time.Instant

这两个类是相似的:

  • 代表时间轴(UTC)上的瞬时点
  • 保存一个与时区无关的时间
  • 表示的是纳秒 epoch-seconds(自 1970-01-01T00:00:00Z 起)

Date.from(Instant)Date.toInstant() 方法互相转换

java.util.GregorianCalendar 和 java.time.ZonedDateTime

ZonedDateTime 类是替代 GregorianCalendar 的 。它提供了以下类似的功能。

人类时间

  • LocalDate: 年,月,日
  • LocalTime: 时,分,秒,纳秒
  • ZoneId: 时区
  • ZoneOffset: 从 GMT 的偏移量

GregorianCalendar.from(ZonedDateTime)GregorianCalendar.to(ZonedDateTime) 相互转换

java.util.TimeZone 和 java.time.ZoneId/ZoneOffset

  • ZoneId 指定时区标识符和访问所使用的每个时区的规则
  • ZoneOffset 指定一个从格林尼治/ UTC 偏移。有关更多信息,请参阅 时区和偏移 类。

GregorianCalendar 和 java.time.LocalTime

在 GregorianCalendar 实例中将日期设置为 1970-01-01 以便使用时间组件的代码可以替换为 LocalTime 实例。 因为 LocalTime 只包含时分秒

GregorianCalendar 和 java.time.LocalDate

在 GregorianCalendar 实例中将时间设置为 00:00 以便使用日期组件的代码可以用 LocalDate 的实例替换。 (这样的方法有缺陷,因为在一些国家,由于过渡到夏令时,每年午夜都不会发生。

  1. Calendar -》 ZonedDateTime,请注意,必须提供时区才能将 Instant 转换为 ZonedDateTime

    Calendar now = Calendar.getInstance();
    ZonedDateTime zdt = ZonedDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());
    
  2. Date 与 Instant相互转换

    Instant inst = date.toInstant();
    
    Date newDate = Date.from(inst);
    
  3. GregorianCalendar 与 ZonedDateTime相互转换

    GregorianCalendar cal = new GregorianCalendar();
    TimeZone tz = cal.getTimeZone();
    int tzoffset = cal.get(Calendar.ZONE_OFFSET); // 获取偏移量
    
    ZonedDateTime zdt = cal.toZonedDateTime();
    LocalDateTime ldt = zdt.toLocalDateTime();
    LocalDate date = zdt.toLocalDate();
    LocalTime time = zdt.toLocalTime();
    
    
    GregorianCalendar newCal = GregorianCalendar.from(zdt);
    

java.util Date 与 java.time 功能映射

java.util.Date 与 java.time.Instant

这两个类是相似的:

  • 代表时间轴(UTC)上的瞬时点
  • 保存一个与时区无关的时间
  • 表示的是纳秒 epoch-seconds(自 1970-01-01T00:00:00Z 起)

Date.from(Instant)Date.toInstant() 方法互相转换

java.util.GregorianCalendar 和 java.time.ZonedDateTime

ZonedDateTime 类是替代 GregorianCalendar 的 。它提供了以下类似的功能。

人类时间

  • LocalDate: 年,月,日
  • LocalTime: 时,分,秒,纳秒
  • ZoneId: 时区
  • ZoneOffset: 从 GMT 的偏移量

GregorianCalendar.from(ZonedDateTime)GregorianCalendar.to(ZonedDateTime) 相互转换

java.util.TimeZone 和 java.time.ZoneId/ZoneOffset

  • ZoneId 指定时区标识符和访问所使用的每个时区的规则
  • ZoneOffset 指定一个从格林尼治/ UTC 偏移。有关更多信息,请参阅 时区和偏移 类。

GregorianCalendar 和 java.time.LocalTime

在 GregorianCalendar 实例中将日期设置为 1970-01-01 以便使用时间组件的代码可以替换为 LocalTime 实例。 因为 LocalTime 只包含时分秒

GregorianCalendar 和 java.time.LocalDate

在 GregorianCalendar 实例中将时间设置为 00:00 以便使用日期组件的代码可以用 LocalDate 的实例替换。 (这样的方法有缺陷,因为在一些国家,由于过渡到夏令时,每年午夜都不会发生。)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值