美文网首页Java
大厂都是怎么用Java8代替SimpleDateFormat?

大厂都是怎么用Java8代替SimpleDateFormat?

作者: Java斗帝之路 | 来源:发表于2020-11-25 15:51 被阅读0次

    1 SimpleDateFormat 之坑

    1.1 格式化

    1.1.1 案例

    • 初始化一个Calendar,设置日期2020年12月29日


    • 日志


    这是由于混淆SimpleDateFormat的各种格式化模式:

    • 小写y是年

    • 大写Y是week year,即所在的周属于哪一年

    一年第一周的判断方式

    getFirstDayOfWeek()开始,完整的7天,并且包含那一年至少getMinimalDaysInFirstWeek()天。
    该计算方式和区域相关,对zh_CN区域,2020年第一周条件:从周日开始的完整7天,2020年包含1天即 可。显然,2019年12月27日周日到2020年1月2日周六是2020年第一周,得出的week year就是2021年。

    若把区域改为法国

    Locale.setDefault(Locale.FRANCE);
    
    

    则week yeay就还是2020年,因为一周的第一天从周一开始算,2020年的第一周是2019年12月28日周一开始,27日还是属于去年:


    小结

    无特殊需求,针对年份的日期格式化,应该一律使用 “y” 而非 “Y”。

    线程安全问题

    使用一个100线程的线程池,循环20次把时间格式化任务提交到线程池处理,每个任务中又循环10次解析2020-01-01 11:12:13这样一个时间表示:

    • 运行程序后大量报错,即使没有报错的输出结果也不正常,比如2020年解析成57728年


      image image.gif

    SimpleDateFormat 用于定义解析和格式化日期时间的模式。看起来是一次性工作,应该复用,但它的解析和格式化操作都非线程安全。

    分析源码

    SimpleDateFormat继承自DateFormat,DateFormat有字段Calendar;

    • SimpleDateFormat#parse调用CalendarBuilder#establish构建Calendar

    establish方法内部先清空Calendar再构建Calendar,整个操作没有加锁。


    显然,若使用线程池调用parse,即多线程并发操作一个Calendar,就可能会产生一个线程还没来得及处理Calendar就被另一个线程清空。format方法同理,不再赘述。因此只能在同一个线程复用SimpleDateFormat,

    解决方案

    通过ThreadLocal来存放SimpleDateFormat:

    • 日志输出全部正确 image image.gif

    1.2 当需要解析的字符串和格式不匹配,SimpleDateFormat还是能得到结果

    案例

    使用yyyyMM解析20160901字符串:

    • 居然输出2112年,这是因为把 1111当成月份

    对于SimpleDateFormat的这些坑,使用Java 8中的DateTimeFormatter即可避免。

    2 Java 8中的DateTimeFormatter

    2.1 格式化字符串

    首先,使用DateTimeFormatterBuilder定义格式化字符串,无需死记大写Y还是小写y,大写M还是小写m:


    2.2 线程安全

    可定义为static使用

    2.3 待解析字符串和格式不匹配时就报错

    • 日志
    2020/11/11 11:11:11.789
    Exception in thread "main" java.time.format.DateTimeParseException: Text '20201111' could not be parsed at index 0
        at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
        at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1777)
        at org.javaedge.time.commonmistakes.datetime.dateformat.CommonMistakesApplication.better(CommonMistakesApplication.java:96)
        at org.javaedge.time.commonmistakes.datetime.dateformat.CommonMistakesApplication.main(CommonMistakesApplication.java:47)
    
    
    image.gif

    3 Java8计算日期时间

    • 有人喜欢使用时间戳进行计算,比如希望得到当前时间后30天:把new Date().getTime得到的时间戳加30天对应毫秒数
    • 得到的日期居然比当前日期还要早,根本不是后30天


    因为int发生了溢出!。

    • 应将30改为30L,使其为long:


    • 正确输出


    • Java 8前代码,建议使用Calendar:


    • 使用Java 8的日期时间类型,可以直接进行各种计算,更加简洁和方便:


    对日期时间做计算操作,日期时间API会比Calendar功能强大很多。

    3.1 minus/plus直接对日期加减

    3.2 with快捷时间调节

    • TemporalAdjusters.firstDayOfMonth得到当前月的第一天

    • TemporalAdjusters.firstDayOfYear()得到当前年的第一天

    • TemporalAdjusters.previous(DayOfWeek.SATURDAY)得到上一个周六

    • TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)得到本月最后一个周五

    3.3 使用lambda自定义的时间调整

    • 为当前时间增加100天以内的随机天数:


    判断日期是否符合某个条件

    query查询是否匹配条件

    使用Java 8操作和计算日期时间虽然方便,但计算两个日期差时可能会踩坑:Java 8中有一个专门的类Period定义了日期间隔,通过Period.between得到了两个LocalDate的差,返回的是两个日期差几年零几月零几天。
    如果希望得知两个日期之间差几天,直接调用Period的getDays()方法得到的只是最后的“零几天”,而不是算总的间隔天数。

    比如,计算2020年12月12日和2020年10月1日的日期间隔,很明显日期差是2个月零11天,但获取getDays方法得到的结果只是11天,而不是72天:


    可使用ChronoUnit.DAYS.between解决这个问题:


    4 总结

    也许你认为java.util.Date类似于新API中的LocalDateTime。其实不是,虽然它们都没时区概念

    • java.util.Date类是因为使用UTC表示,所以没有时区概念,其本质是时间戳

    • LocalDateTime,严格上可以认为是一个日期时间的表示,而不是一个时间点

    因此,在把Date转换为LocalDateTime的时候,需要通过Date的toInstant方法得到一个UTC时间戳进行转换,并需要提供当前的时区,这样才能把UTC时间转换为本地日期时间(的表示)。反过来,把LocalDateTime的时间表示转换为Date时,也需要提供时区,用于指定是哪个时区的时间表示,也就是先通过atZone方法把LocalDateTime转换为ZonedDateTime,然后才能获得UTC时间戳:

    Date in = new Date();
    LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
    Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
    
    

    有人说新API很麻烦,还需要考虑时区,真麻烦。但并非因为API强行设计繁琐,而是UTC时间要变为当地时间,必须考虑时区!


    为什么阿里巴巴的程序员成长速度这么快?

    霸榜GitHub的Offer来了原理篇+框架篇,开放分享;

    50W年薪程序员需要的技术栈分析

    看完三件事❤️

    如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

    点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。

    关注公众号 『 Java斗帝 』,不定期分享原创知识。

    同时可以期待后续文章ing🚀

    相关文章

      网友评论

        本文标题:大厂都是怎么用Java8代替SimpleDateFormat?

        本文链接:https://www.haomeiwen.com/subject/adsniktx.html