优秀的编程知识分享平台

网站首页 > 技术文章 正文

Java日期时间类中“老三样”的坑你踩了没

nanyue 2024-10-22 13:09:07 技术文章 10 ℃

背景

在 Java8之前,我们处理日期时间需求时,使用 Date、Calender 和 SimpleDateFormat,来声明时间戳、使用日历处理日期和格式化解析日期时间。但是,这些类的 API 的缺点比较明显,比如可读性差、易用性差、使用起来冗余繁琐,还有线程安全问题。

坑点

1、时间格式化的坑,明明是一个 2020 年的日期,怎么使用 SimpleDateFormat 格式化后就提前跨年了?(我昨天的一篇文档也解释过了,也因为这个我的年终奖没了,大家可以翻看下那个YYYY和yyyy的那篇)https://www.toutiao.com/a7047306921803629063/

原因:混淆了 SimpleDateFormat 的各种格式化模式。JDK文档中有说明:小写 y 是年,而大写 Y 是 week year,也就是所在的周属于哪一年。所以务必要看文档,文档是如此的重要,都是细小的知识点。

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解析为月份。

最近发表
标签列表