在 Java 8 之前,我们处理日期时间需求时,使用 Date、Calender 和 SimpleDateFormat,来声明时间戳、使用日历处理日期和格式化解析日期时间。但是,这些类的 API 的缺点比较明显,比如可读性差、易用性差、使用起来冗余繁琐,SimpleDateFormat还有线程安全问题。
比如:年份的起始选择是1900年,月份的起始从0开始。这意味着,如果你想要用Date表示Java 8的发布日期,即2014年3月18日,需要创建下面这样的Date实例:
Date date = new Date(114, 2, 18);
// 它的打印输出效果为:
Tue Mar 18 00:00:00 CET 2014
它的返回值中甚至还包含了JVM的默认时区CET,即中欧时间(Central Europe Time)。但这并不表示Date类在任何方面支持时区。
随着Java 1.0退出历史舞台,Date类的种种问题和限制几乎一扫而光,但很明显,这些历史旧账如果不牺牲前向兼容性是无法解决的。所以,在Java 1.1中,Date类中的很多方法被废弃了,取而代之的是java.util.Calendar类。很不幸,Calendar类也有类似的问题和设计缺陷,导致使用这些方法写出的代码非常容易出错。比如,月份依旧是从0开始计算(不过,至少Calendar类拿掉了由1900年开始计算年份这一设计)。更糟的是,同时存在Date和Calendar这两个类,也增加了程序员的困惑。到底该使用哪一个类呢?此外,有的特性只在某一个类有提供,比如用于以语言无关方式格式化和解析日期或时间的DateFormat方法就只在Date类里有。
因此,Java 8 推出了新的日期时间类。每一个类功能明确清晰、类之间协作简单、API 定义清晰不踩坑,API 功能强大无需借助外部工具类即可完成操作,并且线程安全。
of:静态工厂方法(用类名去调用)。
parse:静态工厂方法,关注于解析(用类名去调用)。
now: 静态工厂方法,用当前时间创建实例(用类名去调用)
get:获取某些东西的值。
is:检查某些东西的是否是true。
with:返回一个部分状态改变了的时间日期对象拷贝(单独一个with方法,参数为TemporalAdjusters类型)。
plus:返回一个时间增加了的、时间日期对象拷贝(如果参数是负数也能够有minus方法的效果)。
minus:返回一个时间减少了的、时间日期对象拷贝。
to:把当前时间日期对象转换成另外一个,可能会损失部分状态。
at:把这个对象与另一个对象组合起来,例如: date.atTime(time)。
format :根据某一个DateTimeFormatter格式化为字符串。
该类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息。
你可以通过静态工厂方法of创建一个LocalDate实例。LocalDate实例提供了多种方法来读取常用的值,比如年份、月份、星期几等。
// 创建一个LocalDate实例
LocalDate date = LocalDate.of(2022, 8, 24);
// LocalDate date = LocalDate.of(2022, Month.AUGUST, 24);
System.out.println(date); // 2022-08-24
// 获取年
int year = date.getYear();
System.out.println(year); // 2022
// 获取月
Month month = date.getMonth();
System.out.println(month); // AUGUST
// 获取日
int day = date.getDayOfMonth();
System.out.println(day); // 24
// 获取星期几
DayOfWeek dow = date.getDayOfWeek();
System.out.println(dow); // WEDNESDAY
// 获取当月天数
int len = date.lengthOfMonth();
System.out.println(len); // 31
// 获取是否是闰年
boolean leap = date.isLeapYear();
System.out.println(leap); // false
还可以使用工厂方法从系统时钟中获取当前的日期:
LocalDate today = LocalDate.now();
还可以通过传递一个TemporalField参数给get方法拿到同样的信息。TemporalField是一个接口,它定义了如何访问temporal对象某个字段的值。ChronoField枚举实现了这一接口,所以你可以很方便地使用get方法得到枚举元素的值。
LocalDate today = LocalDate.now();
int year = date.get(ChronoField.YEAR);
int month = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);
LocalDate date = LocalDate.parse("2022-08-24");
你可以向parse方法传递一个DateTimeFormatter。该类的实例定义了如何格式化一个日期或者时间对象。
它是替换老版java.util.DateFormat的推荐替代品。
我们会在下面介绍怎样使用DateTimeFormatter。
同时,也请注意,一旦传递的字符串参数无法被解析为合法的LocalDate或LocalTime对象,这两个parse方法都会抛出一个继承自RuntimeException的DateTimeParseException异常。
与LocalDate类似地,一天中的时间,比如13:45:20,可以使用LocalTime类表示。
LocalTime time = LocalTime.of(13, 45, 20);
// 获取小时
int hour = time.getHour(); // 13
// 获取分钟数
int minute = time.getMinute(); // 45
// 获取秒数
int second = time.getSecond(); // 20
LocalTime now = LocalTime.now();
// 通过解析代表字符串创建
LocalTime time = LocalTime.parse("13:45:20");
你可以向parse方法传递一个DateTimeFormatter。该类的实例定义了如何格式化一个日期或者时间对象。
它是替换老版java.util.DateFormat的推荐替代品。
我们会在下面介绍怎样使用DateTimeFormatter。
同时,也请注意,一旦传递的字符串参数无法被解析为合法的LocalDate或LocalTime对象,这两个parse方法都会抛出一个继承自RuntimeException的DateTimeParseException异常。
这个复合类名叫LocalDateTime,是LocalDate和LocalTime的合体。
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dt1 = LocalDateTime.of(2022, Month.AUGUST, 24, 13, 45, 20);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(13, 45, 20);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);
注意,通过它们各自的atTime或者atDate方法,向LocalDate传递一个时间对象,或者向LocalTime传递一个日期对象的方式,你可以创建一个LocalDateTime对象。
也可以使用toLocalDate或者toLocalTime方法,从LocalDateTime中提取LocalDate或者LocalTime组件:
LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();
Instant用于表示一个时间戳,它与我们常使用的System.currentTimeMillis()有些类似,不过Instant可以精确到纳秒(Nano-Second),System.currentTimeMillis()方法只精确到毫秒(Milli-Second)。
如果查看Instant源码,发现它的内部使用了两个常量,seconds表示从1970-01-01 00:00:00开始到现在的秒数,nanos表示纳秒部分(nanos的值不会超过999,999,999)。
// 获取当前时间戳
Instant now = Instant.now();
// 获取秒级时间戳
long epochSecond = now.getEpochSecond();
Instant除了使用now()方法创建外,还可以通过ofEpochSecond方法创建
ofEpochSecond()方法的第一个参数为秒,第二个参数为纳秒。
// 从1970-01-01 00:00:00开始后两分钟,再加10万纳秒的时刻
Instant instant = Instant.ofEpochSecond(120, 100000);
// 从1970-01-01 00:00:00开始后120秒
Instant.ofEpochSecond(120);
使用Instant.ofEpochSecond(1661309631);发现,获取的时间少了8小时,这边猜测可能是时区的问题。
解决方案(治标不治本,欢迎大家留言更好的解决方案):
手动加8个小时。。。
Instant instant2 = instant1.plusMillis(TimeUnit.HOURS.toMillis(8));
Instant的设计初衷是为了便于机器使用。它包含的是由秒及纳秒所构成的数字。所以,它无法处理那些我们非常容易理解的时间单位。
int day = Instant.now().get(ChronoField.DAY_OF_MONTH);
// 会抛异常
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: DayOfMonth
但是可以通过Duration和Period类使用Instant。
Duration 表示一个时间段,Duration 包含两部分:seconds 表示秒,nanos 表示纳秒,它们的组合表达了时间长度。
因为 Duration 表示时间段,所以 Duration 类中不包含 now() 静态方法。注意,Duration 不包含毫秒这个属性。
Duration只能处理两个时间类,例如LocalTime, LocalDateTime, ZonedDateTime; 如果传入的是LocalDate,将会抛出异常
//Duration d1 = Duration.between(time1, time2);
//Duration d1 = Duration.between(dateTime1, dateTime2);
//Duration d2 = Duration.between(instant1, instant2);
LocalDateTime start = LocalDateTime.parse("2007-12-03T10:15:30");
LocalDateTime end = LocalDateTime.parse("2007-12-05T10:25:33");
//between的用法是end-start的时间,若start的时间大于end的时间,则所有的值是负的
Duration duration = Duration.between(start, end);
String timeString = duration.toString(); //此持续时间的字符串表示形式,使用基于ISO-8601秒*的表示形式,例如 PT8H6M12.345S
System.out.println("相差的天数="+duration.toDays());
System.out.println("相差的小时="+ duration.toHours());
System.out.println("相差的分钟="+duration.toMinutes());
System.out.println("相差的秒数="+duration.toSeconds());
System.out.println("相差的毫秒="+duration.toMillis());
System.out.println("相差的纳秒="+duration.toNanos());
System.out.println("timeString时间="+timeString);
//isNegative返回Duration实例对象是否为负
System.out.println(Duration.between(start, end).isNegative());//false end-start为正,所以此处返回false
System.out.println(Duration.between(end, start).isNegative());//true start-end为负,所以此处返回true
System.out.println(Duration.between(start, start).isNegative());//false start-start为0,所以此处为false
方法 | 说明 |
---|---|
static Duration between(Temporal startInclusive, Temporal endExclusive) | 计算两个时间的间隔,默认是秒 |
boolean isNegative() | 检查Duration实例是否小于0,若小于0返回true, 若大于等于0返回false |
long toDays() | 将时间转换为以天为单位的long值 |
long toHours() | 将时间转换为以时为单位的long值 |
long toMinutes() | 将时间转换为以分钟为单位的long值 |
long toSeconds() | 将时间转换为以秒为单位的long值 |
long toMillis() | 将时间转换为以毫秒为单位的long值 |
long toNanos() | 将时间转换为以纳秒为单位的long值 |
Period 在概念上和 Duration 类似,区别在于 Period 是以年月日来衡量一个时间段。Duration 用于计算两个时间间隔,Period 用于计算两个日期间隔,所以 between() 方法只能接收 LocalDate 类型的参数。
Period p = Period.of(2021, 12, 3);
System.out.println("年月日:"+p);
p = Period.ofYears(1);
System.out.println("年:"+p);
p = Period.ofWeeks(2);
System.out.println("周的天数"+p);
Period period = Period.of(2021,-1,8);
System.out.println("校验年月日任何一位值是否有负数:{}",period.isNegative());//true
LocalDate start = LocalDate.of(2020,2,28);
LocalDate end = LocalDate.of(2021,12,3);
Period period = Period.between(start,end);
System.out.println("两个时间之间的差值 年:"+period.getYears()+",月:"+period.getMonths()+",日:"+period.getDays());
方法 | 说明 |
---|---|
static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive) | 计算两个日期之间的间隔 |
boolean isNegative() | 检查此时间段的三个单位中是否有一个为负数。这将检查年,月或天的单位是否小于零。如果此期间的任何单位为负,则为true |
int getYears() | 获取年 |
int getMonths() | 获取月 |
int getDays() | 获取日 |
方 法 名 | 是否是静态方法 | 方法描述 |
---|---|---|
between | 是 | 创建两个时间点之间的 interval |
from | 是 | 由一个临时时间点创建 interval |
of | 是 | 由它的组成部分创建 interval 的实例 |
parse | 是 | 由字符串创建 interval 的实例 |
addTo | 否 | 创建该 interval 的副本,并将其叠加到某个指定的 temporal 对象 |
get | 否 | 读取该 interval 的状态 |
isNegative | 否 | 检查该 interval 是否为负值,不包含零 |
isZero | 否 | 检查该 interval 的时长是否为零 |
minus | 否 | 通过减去一定的时间创建该 interval 的副本 |
multipliedBy | 否 | 将 interval 的值乘以某个标量创建该 interval 的副本 |
negated | 否 | 以忽略某个时长的方式创建该 interval 的副本 |
plus | 否 | 以增加某个指定的时长的方式创建该 interval 的副本 |
subtractFrom | 否 | 从指定的 temporal 对象中减去该 interval |
// 创建一个LocalDate
LocalDate date1 = LocalDate.of(2022, 3, 18); // 2022-03-18
// 直接修改年
LocalDate date2 = date1.withYear(2011); // 2011-03-18
// 直接修改月
LocalDate date3 = date2.withDayOfMonth(25); // 2011-03-25
// 直接修改月
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9); // 2011-09-25
LocalDate date1 = LocalDate.of(2014, 3, 18); // 2014-03-18
// 周加1
LocalDate date2 = date1.plusWeeks(1); // 2014-03-25
// 年减3
LocalDate date3 = date2.minusYears(3); // 2011-03-25
// 月加6
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS); // 2011-09-25
注意!每次对日期进行操作,并不会在原日期对象上直接进行操作,而是会返回一个操作之后的对象。
像LocalDate、LocalTime、LocalDateTime以及Instant这样表示时间点的日期时间类提供了大量通用的方法:
方 法 名 | 是否是静态方法 | 描 述 |
---|---|---|
from | 是 | 依据传入的 Temporal 对象创建对象实例 |
now | 是 | 依据系统时钟创建 Temporal 对象 |
of | 是 | 由 Temporal 对象的某个部分创建该对象的实例 |
parse | 是 | 由字符串创建 Temporal 对象的实例 |
atOffset | 否 | 将 Temporal 对象和某个时区偏移相结合 |
atZone | 否 | 将 Temporal 对象和某个时区相结合 |
format | 否 | 使用某个指定的格式器将Temporal对象转换为字符串(Instant类不提供该方法) |
get | 否 | 读取 Temporal 对象的某一部分的值 |
minus | 否 | 创建 Temporal 对象的一个副本,通过将当前 Temporal 对象的值减去一定的时长创建该副本 |
plus | 否 | 创建 Temporal 对象的一个副本,通过将当前 Temporal 对象的值加上一定的时长创建该副本 |
with | 否 | 以该 Temporal 对象为模板,对某些状态进行修改创建该对象的副本 |
TemporalAdjuster是时间调节器,可以执行复杂的日期操作,例如,可以获得下一个星期日的日期、当月的最后一天(再也不用计算当月是28,29还是30天了)、下一年的第一天、下一个工作日等等。
TemporalAdjusters工具类有很多预定义的static方法返回TemporalAdjuster对象,使用不同方式调节Temporal对象而与Temporal实现无关。
方法 | 说明 |
---|---|
static TemporalAdjuster firstDayOfMonth() | 当前月的第一天 |
static TemporalAdjuster firstDayOfNextMonth() | 下一个月的第一天 |
static TemporalAdjuster firstDayOfNextYear() | 下一年的第一天 |
static TemporalAdjuster firstDayOfYear() | 当年的第一天 |
static TemporalAdjuster lastDayOfYear() | 当年的最后一天 |
static TemporalAdjuster lastDayOfMonth() | 当月的最后一天 |
static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek) | 某月的第一个星期几 |
static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek) | 某月的最后一个星期几 |
static TemporalAdjuster dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) | 某月的第几个星期几,例如,三月中第二个星期二 |
static TemporalAdjuster next(DayOfWeek dayOfWeek) | (往后不包括当天)下一个星期几是几月几号。若当前为周三,那么next(DayOfWeek.WEDNESDAY)指下一个周三即下周三;next(DayOfWeek.SUNDAY) 指下一个周日即本周日(此时并不是下周日) |
static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek) | (往后包括当天)最近星期几的日期。如最近星期五的日期,如果今天是星期五,则返回今天日期,如果今天不是星期五,则返回下周五的日期 |
static TemporalAdjuster previous(DayOfWeek dayOfWeek) | (往前不包括当天)上一个星期几是几月几号。若当前为周三,那么previous(DayOfWeek.WEDNESDAY)指上一个周三即上周三;previous(DayOfWeek.TUESDAY) 指上一个周二即昨天(此时并不是上周二) |
static TemporalAdjuster previousOrSame(DayOfWeek dayOfWeek) | (往前包括当天)最近星期几的日期。如最近星期五的日期,如果今天是星期五,则返回今天日期,如果今天不是星期五,则返回上周五的日期 |
LocalDate now = LocalDate.now();
System.out.println("当前时间:"+now); //2021-11-30
//获取当月第一天
System.out.println("当月第一天:"+now.with(TemporalAdjusters.firstDayOfMonth()));// 2021-11-01
//获取本月第2天:
System.out.println("本月第2天:"+now.withDayOfMonth(2)); //2021-11-02
//获取下月第一天
System.out.println("下月第一天:"+now.with(TemporalAdjusters.firstDayOfNextMonth())); //2021-12-01
//获取明年第一天
System.out.println("明年第一天:"+now.with(TemporalAdjusters.firstDayOfNextYear())); //2022-01-01
//获取本年第一天
System.out.println("本年第一天:"+now.with(TemporalAdjusters.firstDayOfYear()));//2021-01-01
//获取当月最后一天,再也不用计算是28,29,30还是31:
System.out.println("当月最后一天:"+now.with(TemporalAdjusters.lastDayOfMonth())); //2021-11-30
//获取本年最后一天
System.out.println("本年最后一天:"+now.with(TemporalAdjusters.lastDayOfYear())); //2021-12-31
//获取当月的第一个星期一
System.out.println("当月的第一个星期一:"+now.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY))); //2021-11-01
//获取当月的最后一个星期一
System.out.println("当月的最后一个星期一:"+now.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY))); //2021-11-29
//获取当月第三周星期五
System.out.println("当月第三周星期五:"+now.with(TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.FRIDAY))); //2021-11-19
//获取本周一
System.out.println("本周一:"+now.with(DayOfWeek.MONDAY)); //2021-11-29
//获取上周二
System.out.println("上周二:"+now.minusWeeks(1).with(ChronoField.DAY_OF_WEEK, 2)); //2021-11-23
//(往前不包括当天)获取当前日期的上一个周一 如果今天是周一,则返回上周一
System.out.println("上一个周一(不包括当天):"+now.with(TemporalAdjusters.previous(DayOfWeek.MONDAY))); //2021-11-29
//(往前包括当天)最近星期五的日期 如果今天是星期五,则返回今天日期
System.out.println("上一个周一(包括当天):"+now.with(TemporalAdjusters.previousOrSame(DayOfWeek.FRIDAY))); //2021-11-26
//获取下周二
System.out.println("下周二:"+now.plusWeeks(1).with(ChronoField.DAY_OF_WEEK, 2)); //2021-12-07
//(往后不包括当天)获取当前日期的下一个周日 如果今天是周日,则返回下周日的时间 如果今天是星期一,则返回本周日的时间
System.out.println("下一个周日(不包括当天):"+now.with(TemporalAdjusters.next(DayOfWeek.SUNDAY))); //2021-12-05
//(往后包括当天)最近星期五的日期 如果今天是星期五,则返回今天日期
System.out.println("下一个周日(包括当天):"+now.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY))); //2021-12-03
我们可以通过两种不同方式自定义TemporalAdjuster 实现。
// 通过Temporal.with()方法获得2022-08-21之后7天的日期
LocalDate localDate = LocalDate.of(2022, 8, 21);
TemporalAdjuster temporalAdjuster = t -> t.plus(Period.ofDays(7));
LocalDate result = localDate.with(temporalAdjuster);
String fourteenDaysAfterDate = "2022-08-26";
System.out.println(fourteenDaysAfterDate.equals(result.toString()));//true
// 获取下一个工作日
LocalDate localDate = LocalDate.of(2022, 8, 21);
TemporalAdjuster NEXT_WORKING_DAY = TemporalAdjusters.ofDateAdjuster(date -> {
DayOfWeek dayOfWeek = date.getDayOfWeek();
if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
return localDate.plusDays(3);
} else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
return localDate.plusDays(2);
} else {
return localDate.plusDays(1);
}
});
System.out.println("下一个工作日:" + localDate.with(NEXT_WORKING_DAY));
// 简写为
LocalDate nextWorkDay = LocalDate.now().with(tempDate -> {
LocalDate localDate = (LocalDate) tempDate;
DayOfWeek dayOfWeek = localDate.getDayOfWeek();
if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
return localDate.plusDays(3);
} else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
return localDate.plusDays(2);
} else {
return localDate.plusDays(1);
}
});
System.out.println("下一个工作日:" + nextWorkDay);
public class CustomTemporalAdjuster implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
DayOfWeek dayOfWeek
= DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
int daysToAdd;
if (dayOfWeek == DayOfWeek.FRIDAY)
daysToAdd = 3;
else if (dayOfWeek == DayOfWeek.SATURDAY)
daysToAdd = 2;
else
daysToAdd = 1;
return temporal.plus(daysToAdd, ChronoUnit.DAYS);
}
}
测试代码如下:
LocalDate localDate = LocalDate.of(2022, 8, 21);
CustomTemporalAdjuster temporalAdjuster = new CustomTemporalAdjuster();
LocalDate nextWorkingDay = localDate.with(temporalAdjuster);
System.out.println("2022-08-22".equals(nextWorkingDay.toString())); //true
JDK 8在新的DateTimeFormatter类中解决了该问题,该类可用于定义日期和时间格式,例如“ yyyy-MM-dd HH:mm:SS”,用于指定格式的语法与我们之前在SimpleDateFormat类中使用的语法相同,但此类既是线程安全的又是不可变的,这意味着您可以在线程之间共享其实例。 理想情况下,可以将DateTimeFormatter的引用存储到静态变量中以使其成为全局变量。
可以使用DateTimeFormatter中定义好的格式化器,有很多。
注意:LocalDate、LocalTime用的格式化器是不同的,注意区分,否则会抛异常。
// 日期转字符串
LocalDate date = LocalDate.now();
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
// 字符串转日期
LocalDate date1 = LocalDate.parse("20220824", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2022-08-24", DateTimeFormatter.ISO_LOCAL_DATE);
// 时间转字符串
LocalTime time = LocalTime.now();
String s1 = time.format(DateTimeFormatter.ISO_TIME);
// 字符串转日期
LocalTime date1 = LocalTime.parse("15:37:42", DateTimeFormatter.ISO_TIME);
ofPattern中写想要格式化的格式:yyyy-MM-dd’T’HH:mm:ss.SSSxxx’[‘VV’]’ 如 2022-08-24T15:06:29.483+08:00[Asia/Shanghai]
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.now();
String formattedDate = date1.format(formatter);
LocalDate date2 = LocalDate.parse(formattedDate, formatter);
如果你还需要更加细粒度的控制,DateTimeFormatterBuilder类还提供了更复杂的格式器,你可以选择恰当的方法,一步一步地构造自己的格式器。另外,它还提供了非常强大的解析功能,比如区分大小写的解析、柔性解析(允许解析器使用启发式的机制去解析输入,不精 确 地 匹 配 指 定 的 模 式 )、 填 充 , 以 及 在 格 式 器 中 指 定 可 选 节 。
DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.ITALIAN);
使用新版日期和时间API时区的处理被极大地简化了。新的java.time.ZoneId类是老版java.util.TimeZone的替代品。它的设计目标就是要让你无需为时区处理的复杂和繁琐而操心,比如处理日光时(Daylight Saving Time,DST)这种问题。跟其他日期和时间类一样,ZoneId类也是无法修改的。
// 获取指定时区的规则,地区ID都为“{区域}/{城市}”的格式,这些地区集合的设定都由英特网编号分配机构(IANA)的时区数据库提供。
ZoneId romeZone = ZoneId.of("Europe/Rome");
// 将一个老的时区对象转换为ZoneId
ZoneId zoneId = TimeZone.getDefault().toZoneId();
一个ZoneId对象,可以将它与LocalDate、LocalDateTime或者是Instant对象整合起来,构造为一个ZonedDateTime实例,它代表了相对于指定时区的时间点。
LocalDate date = LocalDate.now();
ZonedDateTime zdt1 = date.atStartOfDay(romeZone);
LocalDateTime dateTime = LocalDateTime.now();
ZonedDateTime zdt2 = dateTime.atZone(romeZone);
Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(romeZone);
理解LocaleDate、LocalTime、LocalDateTime以及ZoneId之间的差异:
使用ZoneOffset类,它是ZoneId的一个子类,表示的是当前时间和伦敦格林尼治子午线时间的差异:
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
“-05:00”的偏差实际上对应的是美国东部标准时间。注意,使用这种方式定义的ZoneOffset并未考虑任何日光时的影响,所以在大多数情况下,不推荐使用。由于ZoneOffset也是ZoneId,所以你可以像下面代码那样使用它。你甚至还可以创建这样的OffsetDateTime,它使用ISO-8601的历法系统,以相对于UTC/格林尼治时间的偏差方式表示日期时间。
OffsetDateTime dateTimeInNewYork = OffsetDateTime.of(date, newYorkOffset);
ISO-8601日历系统是世界文明日历系统的事实标准。但是,Java 8中另外还提供了4种其他的日历系统。这些日历系统中的每一个都有一个对应的日志类,分别是ThaiBuddhistDate、MinguoDate 、 JapaneseDate 以 及 HijrahDate 。所有这些类以及 LocalDate 都实现了ChronoLocalDate接口,能够对公历的日期进行建模。利用LocalDate对象,你可以创建这些类的实例。更通用地说,使用它们提供的静态工厂方法,你可以创建任何一个Temporal对象的实例,如下所示:
LocalDate date = LocalDate.now();
JapaneseDate japaneseDate = JapaneseDate.from(date);
或者,你还可以为某个Locale显式地创建日历系统,接着创建该Locale对应的日期的实例。
新的日期和时间API中,Chronology接口建模了一个日历系统,使用它的静态工厂方法ofLocale,可以得到它的一个实例,代码如下:
Chronology japaneseChronology = Chronology.ofLocale(Locale.JAPAN);
ChronoLocalDate now = japaneseChronology.dateNow();
日期及时间API的设计者建议我们使用LocalDate,尽量避免使用ChronoLocalDate,原因是开发者在他们的代码中可能会做一些假设,而这些假设在不同的日历系统中,有可能不成立。
比如,有人可能会做这样的假设,即一个月天数不会超过31天,一年包括12个月,或者一年中包含的月份数目是固定的。由于这些原因,我们建议你尽量在你的应用中使用LocalDate,包括存储、操作、业务规则的解读;不过如果你需要将程序的输入或者输出本地化,这时你应该使用ChronoLocalDate类。
在Java 8新添加的几种日历类型中,HijrahDate(伊斯兰教日历)是最复杂一个,因为它会发生各种变化。Hijrah日历系统构建于农历月份继承之上。Java 8提供了多种方法判断一个月份,比如新月,在世界的哪些地方可见,或者说它只能首先可见于沙特阿拉伯。withVariant
方法可以用于选择期望的变化。为了支持HijrahDate这一标准,Java 8中还包括了乌姆库拉(Umm Al-Qura)变量。
下面这段代码作为一个例子说明了如何在ISO日历中计算当前伊斯兰年中斋月的起始和终止日期:
// 取得当前的Hijrah日期,紧接着对其进行修正,得到斋月的第一天,即第9个月
HijrahDate ramadanDate =
HijrahDate.now().with(ChronoField.DAY_OF_MONTH, 1)
.with(ChronoField.MONTH_OF_YEAR, 9);
// IsoChronology.INSTANCE是IsoChronology类的一个静态实例
// 斋月始于2014-06-28,止于2014-07-27
System.out.println("Ramadan starts on " +
IsoChronology.INSTANCE.date(ramadanDate) +
" and ends on " +
IsoChronology.INSTANCE.date(
ramadanDate.with(
TemporalAdjusters.lastDayOfMonth())));
https://blog.csdn.net/Next_Second/article/details/125476220
https://blog.csdn.net/lianghecai52171314/article/details/123795653
https://blog.csdn.net/weixin_49114503/article/details/121681862
《java8实战》