本文主要针对Date、Calendar、Instant、LocalDate、LocalTime和LocalDateTime的使用做了介绍并进行了对比,同时对simpleDateFormat和simpleDateFormat进行了对比和介绍。篇幅较长可针对具体模块选择性阅读。
对于Date类型最常用的操作想必就是new Date()了
public static void main(String[] args) {
System.out.println(new Date());
}
这时候我们会得到一个当前时间的一个Date类型的字段输出字样为Tue Jun 09 19:20:37 CST 2020 一个带有年月日时分秒的时间。
其中的CST可视为美国、澳大利亚、古巴或中国的标准时间,在这边就是中国的标准时间了。
当然我们对于这样格式的时间的做法是通过JAVA.text.SimpleDateFormat类来进行格式化之后返回页面展示
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
System.out.println(simpleDateFormat.format(new Date()));
}
输出为2020-06-09 19:43:21这个就是我们最为熟悉的一个时间字段格式化之后的结果了。
当然这里要友情提醒的是SimpleDateFormat这个类并不是线程安全的,在高并发场景下需要谨慎使用。Date类型自带有很多的函数具体如下:
可以发现很多都已经变成过时函数,虽然还可以使用但是并不保证在将来某个更新中被删除的可能性,我们也是不推荐使用这种过时的函数的。
这时候java.util.Calendar是一个不错的类可以帮我们解决很多问题。
相信一开始接触java的小伙伴一定被Calendar的强大征服过,但是用久了慢慢地我们会发现其实这个类并没有想象中那么强大,首先一个问题就是它并不支持时区,其次它也不是线程安全的。
所以考虑到它的种种缺陷,java8使用了新的时间和日期API LocalDateTime
Instant表示的是时间线上的一个点,也就是时刻,可以和Date做一个比较。
比较直接的一个不同就是Instant获取的是UTC的时间,而Date是根据当前服务器所处的环境的默认时区来获取的当前时间。
//Date案例
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
System.out.println("new Date() = "+date);
String s = simpleDateFormat.format(date);
System.out.println("SimpleDateFormat 格式化Date后 = "+s);
//Instant案例Instant instant = Instant.now();System.out.println("Instant = "+instant);
System.out.println("Instant +08:00 = "+instant.atOffset(ZoneOffset.ofHours(8)));
输出
new Date() = Wed Jun 10 15:27:48 CST 2020
SimpleDateFormat 格式化Date后 = 2020-06-10 15:27:48
Instant = 2020-06-10T07:27:48.198Z
Instant +08:00 = 2020-06-10T15:27:48.198+08:00
默认时区是UTC在使用Instant的时候是一个需要注意的点,也是容易忽略的一个点,这里划重点!相同的问题在LocalDate、LocalTime和LocalDateTime是不存在的。
但是Instant的官方描述来看,它是一个不可变的且线程安全的类
* @implSpec
* This class is immutable and thread-safe. * @since 1.8
*/public final class Instant implements Temporal, TemporalAdjuster, Comparable<Instant>, Serializable {}
有效时间范围是从-1000000000-01-01T00:00Z~-1000000000-01-01T00:00Z,可以满足大部分场景下的时刻现实问题。
同时在java8提供了toInsatant()和from()两个方法用于Date和Instant之间的来回转换
System.out.println("toInstant() = "+date.toInstant());
System.out.println("from() = "+Date.from(instant));
输出
toInstant() = 2020-06-10T07:45:42.440Z
from() = Wed Jun 10 15:45:42 CST 2020
可以看到相互转换过程中的时区问题不需要我们考虑,会自动+08:00或者-08:00。
比较头疼的一个事情就是java8没有针对Instant提供一个可供自定义的格式化类,所以这边我的解决方法是转换成LocalDateTime,再使用DateTimeFormatter来完成格式化。
System.out.println("Instant = " + LocalDateTime.ofInstant(instant, ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
输出
Instant = 2020-06-10 16:02:26
这样就比较容易的解决了格式化的问题,当然你也可以自定义一个@config来完成对Instant的格式化,也不失为一种一劳永逸的方案。LocalDateTime和DateTimeFormatter在后面的内容中会做详细的介绍。
在讲解LocalDateTime之前我们先分别介绍一下LocalDate和LocalTime,以便于能更深入地理解LocalDateTime。
LocalDate首先是一个不可变类,默认格式为yyyy-MM-dd,其次它是一个只获取年月日的类,侧重点在日历而不是时间(这里我们需要把日历和时间这两个概念区分开)。
使用LocalDate.now()可以获取当前年月日,也可以使用LocalDate.of(year,month,dayOfMonth)来指定日期
public static void main(String[] args) {
LocalDate today = LocalDate.now(); System.out.println("LocalDate.now() = "+today);
today = LocalDate.of(2020,06,10);
LocalDate laterDate = today.plusDays(30);
System.out.println("LocalDate.of() = "+today.toString());
System.out.println("plusDays = "+laterDate.toString());
System.out.println("new Date() = "+ new Date());
}
输出
LocalDate.now() = 2020-06-09
LocalDate.of() = 2020-06-10
plusDays = 2020-07-10
new Date() = Tue Jun 09 20:31:22 CST 2020
从这个例子可以对比看出LocalDate和Date的不同。
同时作为一个访问器方法,LocalDate每次都是生成一个新的对象,而不是改变原有的对象的值。
可以从today.plusDays(30)中轻易地看到。与之类似的就是更改器方法,在java较早的版本中有一个类java.util.GregorianCalendar
public static void main(String[] args) {
GregorianCalendar someDay = new GregorianCalendar(2020,06,9);
System.out.println("before someDay = "+someDay.getTime());
someDay.add(Calendar.DAY_OF_MONTH,30);
System.out.println("after someDay = "+someDay.getTime());
}
输出
before someDay = Thu Jul 09 00:00:00 CST 2020
after someDay = Sat Aug 08 00:00:00 CST 2020
可以看到someDay的值随着函数add的调用一直在变化着,这与LocalDate大不一样,这是需要注意的一个点。
这边还需要注意一个点是localDate.getDayOfWeek().getValue(),LocalDate对于一周的枚举计数和Calendar有些不一样。
LocalDate一周是从周一开始计数对应的value值为1,周日结束对应的value值为7。
而Calendar一周是从周日开始计数对应的value值为1,周六结束对应的value为7,相比较下个人觉得LocalDate更加合理和好用一些。
LocalDate常用的方法如下:
图片来自java核心技术卷2
LocalTime与LocalDate类似同样是一个不可变类,默认格式是HH:mm:ss.zzz,可以看到它所关注的是当前的时刻。
public static void main(String[] args) {
LocalTime localTime = LocalTime.now(); System.out.println("LocalTime.now() = "+localTime);
localTime = LocalTime.of(8,8,8,888);
System.out.println("LocalTime.of() = "+localTime);
LocalTime laterTime = localTime.plusHours(2);
System.out.println("localTime.plusHours() = "+laterTime);
//根据时区获取当前时刻,同理适用与LocalDate
LocalTime newlocalTime = LocalTime.now(ZoneId.of("America/New_York"));
System.out.println("America/New_York Time = "+newlocalTime);
}
输出
LocalTime.now() = 20:58:54.941
LocalTime.of() = 08:08:08.000000888
localTime.plusHours() = 10:08:08.000000888
America/New_York Time = 08:58:54.944
这里我们会发现,尽管LocalTime默认的格式为HH:mm:ss.zzz,但是纳秒级别的精度它也是能支持的08:08:08.000000888。LocalTime常用的方法如下:
java核心技术卷2
经过对LocalDate和LocalTime的介绍,LocalDateTime相信大家也已经知道如何使用了。
LocalDateTime也是一个不可变类且线程安全,它的默认格式为yyyy-MM-ddTHH:mm:ss.zzz,显然在日常的web开发过程中我们都会对这样的日期格式进行格式化,这就是我这边特别要提的一点了。
之前我们讲过java.text.SimpleDateFormat可以自定义格式化时间格式,但是他并不是线程安全的类,所以java8开始配合LocalDateTime提供了java.time.format.DateTimeFormatter来搞定这个问题。
* @implSpec
* This class is immutable and thread-safe. * * @since 1.8
*/public final class DateTimeFormatter
这是官方对他的介绍,这个类是不可变并且是线程安全的。所以我们可以放心地用了。
但是友情提醒下线程安全+线程安全不一定线程安全,不要误解了,这里就不展开讨论了。下面我们和SimpleDateFormat一起对比着来使用一下。
// LocalDateTime
LocalDateTime localDateTime = LocalDateTime.now();String newLocalDateTime = localDateTime.format(DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm:ss"));
System.out.println("LocalDateTime = "+localDateTime);
System.out.println("DateTimeFormatter 格式化后的时间 = "+newLocalDateTime);
//DateSimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
System.out.println("new Date() = "+date);
String s = simpleDateFormat.format(date);
System.out.println("SimpleDateFormat 格式化Date后 = "+s);
输出
LocalDateTime = 2020-06-10T14:41:02.546
DateTimeFormatter 格式化后的时间 = 2020-06-10 14:41:02
new Date() = Wed Jun 10 14:41:02 CST 2020
SimpleDateFormat 格式化Date后 = 2020-06-10 14:41:02
从代码量上就可以看到DateTimeFormatter的优势了,一行搞定。
相比一下SimpleDateFormat每次都要new一个对象,在极端情况下就会导致创建很多实例短时间无法回收而浪费很多内存空间,当然我们也可以通过使用静态变量通过添加synchronized来达到目的,但是同步块不可避免的问题就是阻塞。
当然你一定会说我可以使用ThreadLocal来创建副本来解决SimpleDateFormat的线程安全问题。这个是比较好的一个解决方案,如下:
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}};
System.out.println("ThreadLocal = "+threadLocal.get().format(date));
输出
ThreadLocal = 2020-06-10 14:52:05
显然结果挺好的,但是使用LocalDateTime.format(DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm:ss"))一行就能解决了,相对而言是比较会更简洁和方便一些,所以推荐使用DateTimeFormatter。
作者|IT老哥|微信公众号