专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

JDK8中LocalDate的源码改动

LocalDate、 LocalTime、 LocalDateTime是Java 8开始提供的时间日期API,主要用来优化Java 8以前对于时间日期的处理操作,确实很方便。笔者在使用的过程中,由于引用LocalDate产生了一个有趣的问题,觉得有必要记录一下。

问题描述

我们系统需要利用原有的核心一个接口报文,向外围提供接口服务。里面有表示日期的字段,原始字段类型定义为String。服务中拿LocalDate.parse来获得LocalDate对象,类似于下面:

LocalDate d = LocalDate.parse("2017/2/21", DateTimeFormatter.ofPattern("yyyy/M/d"));

非常快的开发完了,测试的结果也非常满意。

但是某一天,外围调用我们接口的人反应了一个情况:有一次他们手工做报文,日期写错了,为”2017/2/29″,按照道理我们的服务应该校验日期,然后给调用者返回一个错误,但是实际上什么也没有,正常业务执行了。

我找到这一条的日志,发现后台记录的日期是“2017-02-28”。按照先入为主的概念,2017不是闰年,2月份只有28天,所以应该是校验出错误的。查看代码,发现除了上面的转换,都没有其他对于这个字段的操作,所以一下子就僵住了。

问题查找

由于确实没有其他地方来操作这个字段,百度了一圈,没有任何这方面的提示。实在没有办法,只好追踪源码了。时间日期API在rt.jar中,幸好我们有强大的idea, 直接点开就行:

记一次JDK8中关于LocalDate的一点源码改动

在LocalDate里面发现,parse调用的是DateTimeFormatter.parse,真正调用的是parseResolved0,有异常就抛出DateTimeParseException:

public <T> parse(CharSequence text, TemporalQuery<T> query) {
        Objects.requireNonNull(text, "text");
        Objects.requireNonNull(query, "query");
        try {
            return parseResolved0(text, null).query(query);
        } catch (DateTimeParseException ex) {
            throw ex;
        } catch (RuntimeException ex) {
            throw createError(text, ex);
        }
    }

经过一连串的debug, 最终定位在java.time.chrono.IsoChronology.resolveYMD方法中,代码为:

@Override  // override for performance
    LocalDate resolveYMD(Map <TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
        int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR));
        if (resolverStyle == ResolverStyle.LENIENT) {
            long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1);
            long days = Math.subtractExact(fieldValues.remove(DAY_OF_MONTH), 1);
            return LocalDate.of(y, 11).plusMonths(months).plusDays(days);
        }
        int moy = MONTH_OF_YEAR.checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR));
        int dom = DAY_OF_MONTH.checkValidIntValue(fieldValues.remove(DAY_OF_MONTH));
        if (resolverStyle == ResolverStyle.SMART) {  // previous valid
            if (moy == 4 || moy == 6 || moy == 9 || moy == 11) {
                dom = Math.min(dom, 30);
            } else if (moy == 2) {
                dom = Math.min(dom, Month.FEBRUARY.length(Year.isLeap(y)));

            }
        }
        return LocalDate.of(y, moy, dom);
    }

可以看到,源码中,对于4、6、9、11月份,她会获取传入的日期天数和30之间的最小值,而对于2月来说,则判断传入的日期天数和28(闰年是29)之间的最小值,这样就能合理的解释了上面为什么”2017/2/29″校验没有错误,直接变成了”2017-2-28″。我又试了一下,把日期改为”2017/2/31″, 也不会有问题,改为“2017/2/32”,就会报异常:

记一次JDK8中关于LocalDate的一点源码改动

根据上面的情况,推断出:

  • 首先会判断天数是不是大于31,如果大于31,抛出异常,不管是哪个月份
  • 不大于31, 则根据不同的月份,返回月份的实际天数

其实这种处理说不上好坏,反正我觉得没有什么大问题,只不过和我们业务的要求不太相符。我们保险业务对应日期是要严格校验的,前后一天的日期的变化直接关系到是否能够承保,所以不能容忍这样的“智能”的操作,而是应该抛出异常

解决

问题已经出来了,怎么解决呢?当然有很多解决办法,比如直接甩锅给调用方,理直气壮不带犹豫的,不过这都不是我的风格。还是从源码上改一下吧:理想情况就是如果天数不符合要求,就抛出异常:

LocalDate resolveYMD(Map <TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
        int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR));
        if (resolverStyle == ResolverStyle.LENIENT) {
            long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1);
            long days = Math.subtractExact(fieldValues.remove(DAY_OF_MONTH), 1);
            return LocalDate.of(y, 11).plusMonths(months).plusDays(days);
        }
        int moy = MONTH_OF_YEAR.checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR));
        int dom = DAY_OF_MONTH.checkValidIntValue(fieldValues.remove(DAY_OF_MONTH));
        // previous valid
        if (resolverStyle == ResolverStyle.SMART) {
            if (moy == 4 || moy == 6 || moy == 9 || moy == 11) {
                //原来的:dom = Math.min(dom, 30);
                //改为:
                if (dom > 30) {
                    throw new DateTimeException("The max days of " + moy + " month is 30.");
                }

            } else if (moy == 2) {
                //原来的:dom = Math.min(dom, Month.FEBRUARY.length(Year.isLeap(y)));
                //改为:
                if (Year.isLeap(y)) {
                    if (dom > 29) {
                        throw new DateTimeException("The max of days of " + moy + " month is 29.");
                    }
                } else {
                    if (dom > 28) {
                        throw new DateTimeException("The max days of " + moy + " month is 28.");
                    }
                }


            }
        }
        return LocalDate.of(y, moy, dom);
    }

原本在idea里面新建了java.time.chrono.IsoChronology类,把原来的代码拷贝过来,改一下上面的方法就好了,谁知道竟然没有任何变化,这是为什么呢?

原来,java里面有些jar里面的类最优先加载的,你没有办法加载你自己项目中写的同样pagekage和classname的类,怎么办,只好釜底抽薪,先把下面中自己写的类编译,然后在rt.jar中,把IsoChronology.class更改掉,再试成功了:

记一次JDK8中关于LocalDate的一点源码改动

注意抛出的就是我定义的异常信息。

下面的图是没有替换IsoChronolocy.class的时候返回的,没有异常,只有最后被“智能”返回的信息:

记一次JDK8中关于LocalDate的一点源码改动

后记

第一次修改jdk源码竟然是从这儿开始的,惭愧的无以复加。

未经允许不得转载:搜云库技术团队 » JDK8中LocalDate的源码改动

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们