在使用java8以前的时间日期类的时候,总觉得这一块有点蹩脚:
- java.util.Date和java.sql.Date,不知道为啥定义两个同名的类,而且后者还没有默认的构造方法;
- 日期类格式化类SimpleDateFormat居然是在java.text包下;
- 推荐实用Calendar获取Date对象的年月日时分秒,不过从名字上很难感觉到两者之间有什么关联。
还有一些其他的问题我不再一一说明了,下面是从网上找到的关于时间日期类的问题总结:
- 非线程安全:java.util.Date、java.util.Calendar、java.util.GregoiranCalendar和 java.text.SimpleDateFormat四大类都不是线程安全的;
- 设计不佳 :一方面日期和日期格式化分布在多个包中;另一方面,java.util.Date 的默认日期为1970年1月1日,没有统一性。而且Date 类也缺少直接操作日期的相关方法。
- 时区处理困难:因为设计不佳,开发人员不得不编写大量代码来处理时区问题。
- 还有其它一些问题,如Calendar类月份从零开始计算等。
java8对时间日期类进行了重新设计,将这些类都放在了 java.time包和其子包下,并做出了一下改进:
- 新的日期时间 API 是线程安全的。不仅没有 setter 方法,而且任何对实例的变更都会返回一个新的实例,保证原来的实例不变。
- 新的日期时间 API 提供了大量的方法,用于修改日期时间的各个部分,并返回一个新的实例。
- 借鉴了第三方日期时间库joda很多的优点。
- 在时区方面,新的日期时间 API 引入了 域 ( domain ) 这个概念。
- Java 8 还针对原来复杂的 API 进行重新组合和拆分,分成了好多个类。
下表是java8中主要的日期时间类:
类 | 作用 |
---|---|
LocalDate | 本地日期,没有时区信息 |
LocalTime | 本地时间,没有时区信息 |
LocalDateTime | 本地日期时间,是上面两个类的集合,没有时区信息 |
TemporalAdjuster | 调整日期工具类,比如获取下一日 |
Duration | 计算两个时间的时间间隔 |
Period | 计算两个日期的时间间隔 |
Instant | 记录瞬时时间,没有时区信息 |
Clock | 时钟类,包含有时区信息 |
ZonedDate | 日期类,包含有时区信息 |
ZonedTime | 时间类,包含有时区信息 |
ZonedDateTime | 日期时间类,包含有时区信息 |
ZoneId | 时区类 |
DateTimeFormatter | 日期时间格式化类,功能类似于SimpleDateFormat |
接下来按照上表的顺序一一介绍每个类。
一、LocalDate 、LocalTime、LocalDateTime
这三个类没有时区信息,表示的是本地时间或者日期。
这三个类将构造方法都设为private,因此都不能通过new关键字创建对象。为了获取对象,它们都提供了相似的静态方法。
- now():不带参数的now()可以获取当前系统时间,内部是借助
Clock.systemDefaultZone()
完成的,也可以指定参数来获取指定时区或者指定时间日期的对象; - of():通过分别设置年月日时分秒来获得对象,LocalDateTime还提供了入参为LocalDate 和LocalTime的of()方法
- parse():可以根据字符串和指定的格式获得时间日期对象,如果没有传入格式,默认使用ISO格式:yyyy-MM-ddThh:mm:ss;
- ofXXX():这三个类还提供了一些以of开头的方法来获取对象,有的方法默认起点时间是1970-01-01 00:00:00,大家使用时注意一下,各个类提供的方法不太一致,这里不再一一介绍了;
- from():入参为时间日期类对象(TemporalAccessor对象),可以将入参对象转换为LocalDate 、LocalTime或者LocalDateTime。
注意:这三个类都是不可变对象,修改时间日期的任何一个部分都会新建对象。
除了静态方法之外,还有以下实例方法:
- with()/withMonth()/withDay()/…:以当前日期时间为基准,修改其中某一部分的值获得一个新对象;
- plus()/plusYears()/plusMonths()/…/minus()/minusMonths()/…:在当前时间上加上/减去某个值获得一个新对象;
- format():获得一个格式化后的时间日期字符串;
- isAfter()/isBefore()/isEqual():比较两个对象;
- get()/getXXX():可以获得日期时间的某一个部分值,比如获取年、月。
下面以LocalDateTime为例,介绍这些方法的使用:
public static void main(String argv[]){
//获取当前时间
LocalDateTime now= LocalDateTime.now();
System.out.println(now);
LocalDateTime parseDate= LocalDateTime.parse("2021-01-01T00:00:01");
System.out.println(parseDate);
//获取2021-01-01T00:01:01
LocalDateTime ofDate= LocalDateTime.of(2021,1,1,0,1,1);
System.out.println(ofDate);
LocalDateTime ofDate1= LocalDateTime.of(LocalDate.now(), LocalTime.now());
System.out.println(ofDate1);
LocalDateTime firstDay=now.with(TemporalAdjusters.firstDayOfMonth());
System.out.println(firstDay==now);
LocalDateTime addOneDay=now.plusDays(1);
System.out.println(addOneDay);
System.out.println(now.format(DateTimeFormatter.ofPattern("yyyy_MM_dd hh:mm:ss")));
}
运行结果为:
2021-01-13T21:15:37.980
2021-01-01T00:00:01
2021-01-01T00:01:01
2021-01-13T21:15:38.091
false
2021-01-14T21:15:37.980
2021_01_13 09:15:37
二、TemporalAdjuster
该类用于调整时间日期,可以在当前对象的基础上调整到本月的第一个天或者下个星期。
该类可以让我们更加灵活方便的调整时间日期。注意该类是一个接口,不过java为我们提供了TemporalAdjusters工具类可以直接获取TemporalAdjuster对象。
public static void main(String argv[]){
//获取当前时间
LocalDateTime now= LocalDateTime.now();
System.out.println(now);
//调整为本月的第一天
System.out.println(now.with(TemporalAdjusters.firstDayOfMonth()));
//调整为下个月的第一天
System.out.println(now.with(TemporalAdjusters.firstDayOfNextMonth()));
//调整到下个星期五
System.out.println(now.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)));
//如果TemporalAdjusters不能满足功能,可以创建TemporalAdjuster对象
//下面实现一个对象,用于调整当前时间到5个小时之后
System.out.println(now.with(new TemporalAdjuster(){
//TemporalAdjuster接口只有这一个方法
public Temporal adjustInto(Temporal temporal){
if(temporal instanceof LocalDate) {
return temporal;//日期对象无法调整
}
else if(temporal instanceof LocalTime) {
LocalTime time=(LocalTime)temporal;
return time.plusHours(5);
}else if(temporal instanceof LocalDateTime){
LocalDateTime time=(LocalDateTime)temporal;
return time.plusHours(5);
}
return temporal;
}
}));
}
运行结果为:
2021-01-13T21:37:00.536
2021-01-01T21:37:00.536
2021-02-01T21:37:00.536
2021-01-15T21:37:00.536
2021-01-14T02:37:00.536
三、Duration
该类表示一个时间间隔,时间间隔会转换为秒和纳秒存储在Duration对象中。
常用方法有:
- ofDays()/ofHours()/ofXXX():设置一个时间间隔,单位为天/小时/XXX;
- parse():入参为字符串,根据字符串解析出时间间隔,字符串的格式为+/-PnDTnHnMn.nS,n表示数字,D表示天,H表示小时,M表示分钟,S表示秒,这是 ISO-8601的表示格式;
- between():计算两个日期时间之间的间隔,该方法要求两个入参必须支持时间,比如LocalDate只支持日期,使用该方法会报错。
以上三个方法是静态方法,下面的方法是实例方法;
- withSeconds()/withNanos():调整秒或者纳秒;
- plus()/minus():在当前间隔的基础上增加或者减去一个时间;
- toHours()/toMillis()/toDays()/…:将当前间隔转换为要求的时间日期单位。
public static void main(String argv[]){
Duration dayDuration=Duration.ofDays(1);
System.out.println(dayDuration);
Duration minutesDuration=Duration.ofMinutes(1);
System.out.println(minutesDuration);
//字符串的格式为PnDTnHnMn.nS
Duration parseDuration=Duration.parse("P1DT1H1M1.1S");
System.out.println(parseDuration);
//获取当前时间
LocalDateTime now= LocalDateTime.now();
System.out.println(now);
//一个小时后的时间
LocalDateTime oneHour= now.plusHours(1);
System.out.println(oneHour);
Duration betweenDuration=Duration.between(now,oneHour);
System.out.println(betweenDuration);
//一分钟加上13秒
System.out.println(minutesDuration.plusSeconds(13));
//一分钟加上13秒转换成毫秒
System.out.println(minutesDuration.plusSeconds(13).toMillis());
}
运行结果为:
PT24H
PT1M
PT25H1M1.1S
2021-01-13T22:09:02.874
2021-01-13T23:09:02.874
PT1H
PT1M13S
73000
如果将Duration.between()方法的入参对象换成LocalDate:
public static void main(String argv[]){
//获取当前时间
LocalDate now= LocalDate.now();
System.out.println(now);
//一个小时后的时间
LocalDate oneDay= now.plusDays(1);
System.out.println(oneDay);
Duration betweenDuration=Duration.between(now,oneDay);
}
执行上面的代码会报如下错误:
Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
at java.time.LocalDate.until(LocalDate.java:1614)
at java.time.Duration.between(Duration.java:475)
at Main.main(Main.java:30)
四、Period
Period与Duration功能相似,都是表示一个时间间隔,只不过Period的最小间隔单位为天,Duration的最小间隔单位为纳秒。
Period使用years、months、days三个int类型记录时间间隔。
Period提供了和Duration类似的方法,可以参考Duration。
Period.between()方法只能计算两个LocalDate对象之间的时间间隔,这一点与Duration不同。
下面是Period的例子:
public static void main(String argv[]){
Period dayPeriod=Period.ofDays(1);
System.out.println(dayPeriod);
Period monthsDuration=Period.ofMonths(1);
System.out.println(monthsDuration);
//字符串的格式为PnYnMnD
Period parsePeriod=Period.parse("P1Y1M2D");
System.out.println(parsePeriod);
//获取当前时间
LocalDate now= LocalDate.now();
System.out.println(now);
//一个小时后的时间
LocalDate tenDays= now.plusDays(10);
System.out.println(tenDays);
Period betweenPeriod=Period.between(now,tenDays);
System.out.println(betweenPeriod);
//一个月加上两天
System.out.println(monthsDuration.plusDays(2));
//一个月加上15天,最后转换为月表示
System.out.println(monthsDuration.plusDays(15).toTotalMonths());
}
运行结果为:
P1D
P1M
P1Y1M2D
2021-01-14
2021-01-24
P10D
P1M2D
1
五、Instant
Instant表示某个时间点或者说某个时刻,最小精确到纳秒,内部使用long型的seconds和int型的nanos记录该时间点,其中seconds记录了从1970-01-01T00:00:00以来的秒数。
Instant提供了如下常用方法:
- now():返回当前时刻,与LocalTime不同的是,这里返回的UTC时间,与北京时间相差八个小时,
- ofEpochSecond()/ofEpochSecond()/ofEpochMilli():可以根据秒数/纳秒数/毫秒数设置当前时刻,当以秒或者毫秒设置时,时间基准是1970-01-01T00:00:00;
- parse():入参为字符串,根据字符串解析出当前时刻,字符串的格式为yyyy-MM-DDThh:mm:ss.SSZ,比如2007-12-03T10:15:30.00Z,这个字符串表示时间也是UTC时间;
- getEpochSecond()/getNano():前者返回从1970-01-01T00:00:00以来的秒数,后者返回当前时刻的纳秒数;
- with():在当前时刻的基础上调整时间,只支持以秒/纳秒/毫秒/微秒调整;
- plus()/minus():在当前时刻基础上加上/减去时间得到一个新的时刻;
- compareTo()/isAfter()/isBefore():比较大小
- atOffset():入参为基于UTC时间的偏移量,可以指定偏移几个小时或者几分钟,返回值是一个OffsetDateTime,该值是基于UTC时间的偏移时间。
Instant不包含时区信息,该类记录的时间都是UTC时间,不是某个时区的时间。
public static void main(String argv[]){
Instant now=Instant.now();
System.out.println(now);
Instant oneSec=Instant.ofEpochSecond(1);
System.out.println(oneSec);
Instant parseInstant=Instant.parse("2017-02-03T10:37:30.00Z");
System.out.println(parseInstant);
System.out.println(parseInstant.getEpochSecond());
System.out.println(now.atOffset(ZoneOffset.ofHours(8)));
}
运行结果为:
2021-01-14T13:35:32.586Z
1970-01-01T00:00:01Z
2017-02-03T10:37:30Z
1486118250
2021-01-14T21:35:32.586+08:00
六、Clock
该类用于获取某个时刻,包含了日期和时间,带有时区信息。
上面介绍的类里面的now()方法就是通过该类获得的当前时间,
该类是一抽象类,其提供了如下实现类:
- SystemClock:当前系统时间,内部调用System.currentTimeMillis()实现,使用属性zone记录当前时区;
- OffsetClock:基于一个Clock对象,添加一个Duration类型的偏移量;
- TickClock:在一个Clock对象的基础上,偏移一定量的纳秒时间;
- FixedClock:表示一个固定时间。
Clock提供了一些静态方法可以获得上面这些类的对象。
下面举几个例子:
public static void main(String argv[]){
//系统当前时间
Clock now=Clock.systemDefaultZone();
System.out.println(now);
System.out.println(now.instant());
Clock utc=Clock.systemUTC();
System.out.println(utc);
System.out.println(utc.instant());
//在当前时间的基础上增加1个小时
Clock oneHour=Clock.offset(now,Duration.ofHours(1));
System.out.println(oneHour.instant());
//返回当前时间的毫秒数,与System.currentTimeMillis()运行结果相同
System.out.println(now.millis());
}
运行结果为:
SystemClock[Asia/Shanghai]
2021-01-14T14:08:00.675Z
SystemClock[Z]
2021-01-14T14:08:00.719Z
2021-01-14T15:08:00.719Z
1610633280719
七、ZonedDate 、ZonedTime、ZonedDateTime
功能与LocalDate 、LocalTime、LocalDateTime类似,也提供类似的方法,区别在于这三个类记录了时区信息,内部有一个ZoneId的属性记录时区信息。
使用方法可以参考LocalDate 、LocalTime、LocalDateTime。
八、ZoneId
ZoneId是时区对象。
public static void main(String argv[]){
//获取当前系统默认的时区
ZoneId systemZone=ZoneId.systemDefault();
System.out.println(systemZone);
System.out.println("======================");
//获取当前系统支持的所有时区
Set<String> zoneSet=ZoneId.getAvailableZoneIds();
zoneSet.stream().forEach(System.out::println);
System.out.println("======================");
//设置一个指定时区
ZoneId certainZone=ZoneId.of("Asia/Shanghai");
System.out.println(certainZone);
}
运行结果:
Asia/Shanghai
======================
Asia/Aden
America/Cuiaba
Etc/GMT+9
Etc/GMT+8
Africa/Nairobi
。。。。。。时区省略
Pacific/Majuro
America/Argentina/Buenos_Aires
Europe/Nicosia
Pacific/Guadalcanal
Europe/Athens
US/Pacific
Europe/Monaco
======================
Asia/Shanghai
九、DateTimeFormatter
DateTimeFormatter从功能上来说与SimpleDateFormat类似,都是解析时间字符串或者格式化时间日期对象,两者最大的区别是DateTimeFormatter是线程安全的,SimpleDateFormat是线程不安全的。如果在多线程环境中使用SimpleDateFormat,要么加锁,要么使用ThreadLocal对象做隔离。
DateTimeFormatter常用方法:
- ofPattern():静态方法,入参为指定格式的字符串,可以通过本方法获得一个DateTimeFormatter对象;
- format():实例方法,可以将日期时间对象格式化为字符串。
public static void main(String argv[]){
DateTimeFormatter formatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
TemporalAccessor data=formatter.parse("2021-01-01 14:14:15");
System.out.println(data);
//转换为LocalDateTime对象
LocalDateTime dateTime=LocalDateTime.from(data);
System.out.println(dateTime);
//转换为LocalDate对象
LocalDate date=LocalDate.from(data);
System.out.println(date);
LocalDateTime time=LocalDateTime.now();
System.out.println(formatter.format(time));
}
运行结果为:
{},ISO resolved to 2021-01-01T14:14:15
2021-01-01T14:14:15
2021-01-01
2021-01-15 22:29:33
DateTimeFormatter提供一些常用的格式化常量,举例如下:
public static final DateTimeFormatter ISO_INSTANT;
public static final DateTimeFormatter ISO_WEEK_DATE;
public static final DateTimeFormatter ISO_ORDINAL_DATE;
public static final DateTimeFormatter ISO_DATE_TIME;
可以直接使用上面这些常量格式化时间日期对象或者格式化字符串。
除了使用DateTimeFormatter之外,一些时间对象也提供了parse()和format()方法,这两个方法效果与使用DateTimeFormatter一样。
https://blog.csdn.net/ThinkWon/article/details/111087199