java8日期和时间API全解——更完善的日期和时间API

文章目录

  • 写在前面
    • 方法前缀的含义,统一了API
  • 一、LocalDate
    • LocalDate基本使用
    • LocalDate获取当前日期
    • 使用TemporalField读取LocalDate的值
    • 使用解析字符串创建LocalDate
  • 二、LocalTime
    • LocalTime基本使用
    • LocalTime获取当前时间
    • 使用解析字符串创建LocalTime
  • 三、合并日期和时间
    • 日期合并
    • 日期拆解
  • 四、处理时间戳
    • Instant获取当前时间戳
    • 创建Instant
    • Instant格式化时间戳少8小时的问题
    • 注意
  • 五、使用 Duration 或 Period 计算时间间隔
    • Duration - 计算两个“时间”间隔的类
    • Duration常用API
    • Period - 计算两个“日期”间隔的类
    • Period常用API
    • 日期-时间类中表示时间间隔的通用方法
  • 六、日期的修改
  • 七、TemporalAdjuster时间调节器
    • TemporalAdjusters类中预定义实现
    • 自定义TemporalAdjuster 实现
      • 1.使用lambda表达式
      • 2.实现TemporalAdjuster 接口
  • 八、日期时间格式化
    • 格式化日期
    • 按照自定义模式格式化日期
    • 自定义DateTimeFormatter
  • 九、处理时区
    • 为时间点添加时区信息
    • 时区的计算
  • 十、使用别的日历系统
    • 伊斯兰教日历
  • 参考资料

写在前面

在 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 功能强大无需借助外部工具类即可完成操作,并且线程安全。

方法前缀的含义,统一了API

of:静态工厂方法(用类名去调用)。
parse:静态工厂方法,关注于解析(用类名去调用)。
now: 静态工厂方法,用当前时间创建实例(用类名去调用)
get:获取某些东西的值。
is:检查某些东西的是否是truewith:返回一个部分状态改变了的时间日期对象拷贝(单独一个with方法,参数为TemporalAdjusters类型)。
plus:返回一个时间增加了的、时间日期对象拷贝(如果参数是负数也能够有minus方法的效果)。
minus:返回一个时间减少了的、时间日期对象拷贝。
to:把当前时间日期对象转换成另外一个,可能会损失部分状态。
at:把这个对象与另一个对象组合起来,例如: date.atTime(time)。
format :根据某一个DateTimeFormatter格式化为字符串。

一、LocalDate

该类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息。

你可以通过静态工厂方法of创建一个LocalDate实例。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获取当前日期

还可以使用工厂方法从系统时钟中获取当前的日期:

LocalDate today = LocalDate.now();

使用TemporalField读取LocalDate的值

还可以通过传递一个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

LocalDate date = LocalDate.parse("2022-08-24");

你可以向parse方法传递一个DateTimeFormatter。该类的实例定义了如何格式化一个日期或者时间对象。
它是替换老版java.util.DateFormat的推荐替代品。
我们会在下面介绍怎样使用DateTimeFormatter。
同时,也请注意,一旦传递的字符串参数无法被解析为合法的LocalDate或LocalTime对象,这两个parse方法都会抛出一个继承自RuntimeException的DateTimeParseException异常。

二、LocalTime

与LocalDate类似地,一天中的时间,比如13:45:20,可以使用LocalTime类表示。

LocalTime基本使用

LocalTime time = LocalTime.of(13, 45, 20);
// 获取小时
int hour = time.getHour(); // 13
// 获取分钟数
int minute = time.getMinute(); // 45
// 获取秒数
int second = time.getSecond(); // 20

LocalTime获取当前时间

LocalTime now = LocalTime.now();

使用解析字符串创建LocalTime

// 通过解析代表字符串创建
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获取当前时间戳

// 获取当前时间戳
Instant now = Instant.now();
// 获取秒级时间戳
long epochSecond = now.getEpochSecond();

创建Instant

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格式化时间戳少8小时的问题

使用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 或 Period 计算时间间隔

Duration 表示一个时间段,Duration 包含两部分:seconds 表示秒,nanos 表示纳秒,它们的组合表达了时间长度。

因为 Duration 表示时间段,所以 Duration 类中不包含 now() 静态方法。注意,Duration 不包含毫秒这个属性。

Duration只能处理两个时间类,例如LocalTime, LocalDateTime, ZonedDateTime; 如果传入的是LocalDate,将会抛出异常

Duration - 计算两个“时间”间隔的类

//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

Duration常用API

方法 说明
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 - 计算两个“日期”间隔的类

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());

Period常用API

方法 说明
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时间调节器

TemporalAdjuster是时间调节器,可以执行复杂的日期操作,例如,可以获得下一个星期日的日期、当月的最后一天(再也不用计算当月是28,29还是30天了)、下一年的第一天、下一个工作日等等。

TemporalAdjusters类中预定义实现

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 实现

我们可以通过两种不同方式自定义TemporalAdjuster 实现。

1.使用lambda表达式

// 通过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);

2.实现TemporalAdjuster 接口

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用的格式化器是不同的,注意区分,否则会抛异常。
java8日期和时间API全解——更完善的日期和时间API_第1张图片

// 日期转字符串
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);

自定义DateTimeFormatter

如果你还需要更加细粒度的控制,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之间的差异:
java8日期和时间API全解——更完善的日期和时间API_第2张图片

时区的计算

使用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实战》

你可能感兴趣的:(java,java)