参考: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加载,默认的实现类是TzdbZoneRulesProvider
,TzdbZoneRulesProvider
会加载${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 提供了一组接口、类和枚举,它们支持日期和时间代码,特别是日期和时间计算
接口:
Interface | Description |
---|---|
Temporal | 框架级接口,定义对临时对象(如日期、时间、偏移量或这些对象的某种组合)的读写访问。 |
TemporalAccessor | 框架级接口,定义对临时对象(如日期、时间、偏移量或这些对象的某种组合)的只读访问 |
TemporalAdjuster | 调整时间对象的策略 |
TemporalAmount | 框架级接口定义时间,如"6 小时"、“8 天"或"2 年零 3 个月”。 |
TemporalField | 日期时间的字段,比如:month-of-year或hour-of-minute。 |
TemporalQuery | 查询时间对象的策略。 |
TemporalUnit | 日期时间的单位,如Days 或 Hours。 |
类:
Class | Description |
---|---|
IsoFields | 特定于ISO-8601日历系统的字段和单位,包括季度和以周为单位的年 |
JulianFields | 一组日期字段,提供对Julian Days的访问。 |
TemporalAdjusters | 工具类,提供TemporalAdjuster对象. |
TemporalQueries | 工具类,提供TemporalQuery 的通用实现 |
ValueRange | 日期-时间字段的有效值范围。 |
WeekFields | day-of-week、week-of-month、 week-of-year 字段的本地化定义。 |
枚举:
Enum | Description |
---|---|
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; //周年
Date | Day-of-week | Field values |
---|---|---|
2008-12-28 | Sunday(星期天) | Week 52 of week-based-year 2008 |
2008-12-29 | Monday(星期一) | Week 1 of week-based-year 2009 |
2008-12-31 | Wednesday( 星期三) | Week 1 of week-based-year 2009 |
2009-01-01 | Thursday(星期四) | Week 1 of week-based-year 2009 |
2009-01-04 | Sunday(星期日) | Week 1 of week-based-year 2009 |
2009-01-05 | Monday( 星期一) | 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");
}
修改、增加、减少
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️⃣ 预定义格式
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)。 解析器字段允许选择这两种方法中的一种。 如果没有设置解析器字段,则两种方法的结果必须是相同的日期。
解析单独的字段以形成完整的日期和时间是一个复杂的过程,行为分布在许多类中。 它遵循以下步骤:
- chronology(年表)是已确定的。 结果的年表是已解析的年表,如果没有解析年表,则是该类上设置的年表,如果为空,则是IsoChronology。
- 将解析ChronoField日期字段。 这是通过使用年表实现的。 ResolverStyle resolveDate (Map)。 关于字段解析的文档位于Chronology的实现中。
- 解析ChronoField时间字段。 这在ChronoField上有记录,对于所有的年表都是一样的。
- 任何不是ChronoField的字段都将被处理。 这是通过使用TemporalField实现的。 解决(地图,TemporalAccessor, ResolverStyle)。 关于字段解析的文档位于TemporalField的实现中。
- 重新解析ChronoField日期和时间字段。 这允许步骤4中的字段生成ChronoField值,并将其处理为日期和时间。
- 如果一天中至少有一个小时可用,则形成LocalTime。 这涉及到提供分钟、秒和秒的默认值。
- 任何剩余的未解析字段将与任何已解析的日期和/或时间进行交叉检查。 因此,早期阶段将(年+月+月日)解析为一个日期,该阶段将检查该日期的周天数是否有效。
- 如果解析了多余的天数,那么如果日期可用,就将其添加到日期中。
- 如果存在基于秒的字段,但是LocalTime没有被解析,那么解析器将确保毫秒、微秒和纳秒值可用,以满足ChronoField的约定。 如果缺失,这些将被设为零。
- 如果同时解析了日期和时间,并且存在偏移量或区域,则字段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,包括:
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()
-
Calendar -》 ZonedDateTime,请注意,必须提供时区才能将 Instant 转换为 ZonedDateTime
Calendar now = Calendar.getInstance(); ZonedDateTime zdt = ZonedDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());
-
Date 与 Instant相互转换
Instant inst = date.toInstant(); Date newDate = Date.from(inst);
-
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 的实例替换。 (这样的方法有缺陷,因为在一些国家,由于过渡到夏令时,每年午夜都不会发生。
-
Calendar -》 ZonedDateTime,请注意,必须提供时区才能将 Instant 转换为 ZonedDateTime
Calendar now = Calendar.getInstance(); ZonedDateTime zdt = ZonedDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());
-
Date 与 Instant相互转换
Instant inst = date.toInstant(); Date newDate = Date.from(inst);
-
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 的实例替换。 (这样的方法有缺陷,因为在一些国家,由于过渡到夏令时,每年午夜都不会发生。)