java日期操作技巧(Java日期时间类中老三样)
java日期操作技巧(Java日期时间类中老三样)public abstract class DateFormat extends Format { protected Calendar calendar; } public class SimpleDateFormat extends DateFormat { @Override public Date parse(String text ParsePosition pos) { CalendarBuilder calb = new CalendarBuilder(); parsedDate = calb.establish(calendar).getTime(); return parsedDate; } } class CalendarBuilder { Calendar establish(Ca
背景在 Java8之前,我们处理日期时间需求时,使用 Date、Calender 和 SimpleDateFormat,来声明时间戳、使用日历处理日期和格式化解析日期时间。但是,这些类的 API 的缺点比较明显,比如可读性差、易用性差、使用起来冗余繁琐,还有线程安全问题。
坑点1、时间格式化的坑,明明是一个 2020 年的日期,怎么使用 SimpleDateFormat 格式化后就提前跨年了?(我昨天的一篇文档也解释过了,也因为这个我的年终奖没了,大家可以翻看下那个YYYY和yyyy的那篇)https://www.toutiao.com/a7047306921803629063/
原因:混淆了 SimpleDateFormat 的各种格式化模式。JDK文档中有说明:小写 y 是年,而大写 Y 是 week year,也就是所在的周属于哪一年。所以务必要看文档,文档是如此的重要,都是细小的知识点。
https://www.toutiao.com/a7047306921803629063/
2、定义的 static 的 SimpleDateFormat 可能会出现线程安全问题
SimpleDateFormat 的作用是定义解析和格式化日期时间的模式。这,看起来这是一次性的工作,应该复用,但它的解析和格式化操作是非线程安全的。
源码分析:
SimpleDateFormat 继承了 DateFormat,DateFormat 有一个字段 Calendar;SimpleDateFormat 的 parse 方法调用 CalendarBuilder 的 establish 方法,来构建 Calendar;establish 方法内部先清空 Calendar 再构建 Calendar,整个操作没有加锁。显然,如果多线程池调用 parse 方法,也就意味着多线程在并发操作一个 Calendar,可能会产生一个线程还没来得及处理 Calendar 就被另一个线程清空了的情况。
public abstract class DateFormat extends Format {
protected Calendar calendar;
}
public class SimpleDateFormat extends DateFormat {
@Override
public Date parse(String text ParsePosition pos)
{
CalendarBuilder calb = new CalendarBuilder();
parsedDate = calb.establish(calendar).getTime();
return parsedDate;
}
}
class CalendarBuilder {
Calendar establish(Calendar cal) {
...
cal.clear();//清空
for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp ) {
for (int index = 0; index <= maxFieldIndex; index ) {
if (field[index] == stamp) {
cal.set(index field[MAX_FIELD index]);//构建
break;
}
}
}
return cal;
}
}
3、当需要解析的字符串和格式不匹配的时候居然还有结果
比如,我们期望使用 yyyyMM 来解析 20201231 字符串,居然输出了 2022 年 1 月 1 日,原因是把 1231 当成了月份:
String dateString = "20201231";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMM");
System.out.println("result:" dateFormat.parse(dateString));
对于 SimpleDateFormat 的这三个坑,我们使用 Java 8 中的 DateTimeFormatter 就可以避过去。
- 坑1的解决方法:
DateTimeFormatterBuilder 来定义格式化字符串,不用去记忆使用大写的 Y 还是小写的 Y,大写的 M 还是小写的 m
- 坑2的解决方法:
DateTimeFormatter 是线程安全的,可以定义为 static 使用
- 坑3的解决方法:
DateTimeFormatter 的解析比较严格,需要解析的字符串和格式不匹配时,会直接报错,而不会把1231解析为月份。