最新消息:发现真没时间折腾VPS,最近又换了个空间。呵呵

一个Java日期格式验证的历程

说实话,虽然写了很多年代码,但水平一直很一般。比如这次写一个日期格式验证居然前前后后改了四五次。把经历写下来,只是为了让自己考虑事情更全面一些。

需求:因为系统有很多日期格式,所以日期验证函数的输入是一个日期字符串和一个格式字符串。格式字符串用的是Java定义的格式(参考地址)。

刚开始写时,觉得很简单,直接就写了下面的代码。

Java
public static boolean isDate(String dttm, String format) {
    boolean retValue = false;
    if (dttm != null) {
        SimpleDateFormat formatter = new SimpleDateFormat(format);
        try {
            formatter.parse(dttm);
            retValue = true;
        } catch (ParseException e) {
        }
    }
    return retValue;
}

写好之后,一测试,发现2012/12/43这种日期居然也返回True,于是查资料,设置转化时不进位formatter.setLenient(false);之后好用了,代码为

Java
public static boolean isDate(String dttm, String format) {
    boolean retValue = false;
    if (dttm != null) {
        SimpleDateFormat formatter = new SimpleDateFormat(format);
        formatter.setLenient(false);
        try {
            formatter.parse(dttm);
            retValue = true;
        } catch (ParseException e) {
        }
    }
    return retValue;
}

写到这里,以为万事大吉了。但之后的测试却发现,当日期有分隔符时,只会转换前一部分,比如2012/12/30ABCD也会返回True。想了一下,觉得先转为日期型,再转为字符串,再比较两者是否相等,但马上就否决了这个方案,因为客户要求的是不严格的日期形式,如格式为yyyy/MM/dd,输入2012/1/2也需要验证通过。然后考虑了一下,觉得先用正则表达式做一次验证,然后再验证是否是日期型。代码如下

Java
public static boolean isDate(String dttm, String format) {
    boolean retValue = false;

    if (dttm == null || dttm.isEmpty() || format == null || format.isEmpty()) {
        return retValue;
    }

    String regFormat = format;
    regFormat = regFormat.replaceAll("(^')|('$)", "");
    regFormat = regFormat.replaceAll("'([^'])", "$1");
    regFormat = regFormat.replace("''", "'");
    regFormat = regFormat.replace("\\", "\\\\");
    regFormat = regFormat.replaceAll("[MdHmsS]+", "\\d+");
    regFormat = regFormat.replaceAll("[y]+", "\\d{1,4}");
    if (!dttm.matches("^" + regFormat + "$")) {
        return false;
    }

    SimpleDateFormat formatter = new SimpleDateFormat(format);
    formatter.setLenient(false);
    try {
        formatter.parse(dttm);
        retValue = true;
    } catch (ParseException e) {
    }

    return retValue;
}

上面的代码只对应了yMdHmsS,虽然对当时的系统已经足够了,但还是感觉不太爽,觉得应该有一种通用的方法。于是查Java的API,发现parse方法还有一个带参数的方法。理解了它的使用方法之后,把代码改成下面的样子

Java
private static boolean isDate(String dttm, String format) {
    if (dttm == null || dttm.isEmpty() || format == null || format.isEmpty()) {
        return false;
    }

    DateFormat formatter = new SimpleDateFormat(format);
    formatter.setLenient(false);
    ParsePosition pos = new ParsePosition(0);
    Date date = formatter.parse(dttm, pos);

    if (date == null || pos.getErrorIndex() > 0) {
        return false;
    }

    if (pos.getIndex() != dttm.length()) {
        return false;
    }

    return true;
}

本来以为这样应该万事大吉了,但之后的测试又发现两个Bug。一个是,当输入的日期没有年份(需求是没有输入年份是默认当前年份)时,默认取的是1970年,这样的话,如果当年是闰年的话,2/29号就验证出错了;另一个是Java的日期和Oracle的日期大小不同,Oracle好像最大只支持到9999年,而Java可以有2万多年。所以代码又被改成了下面的样子

Java
private static boolean isDate(String dttm, String format) {
    if (dttm == null || dttm.isEmpty() || format == null || format.isEmpty()) {
        return false;
    }

    if (format.replaceAll("'.+?'", "").indexOf("y") < 0) {
        format += "/yyyy";
        DateFormat formatter = new SimpleDateFormat("/yyyy");
        dttm += formatter.format(new Date());
    }

    DateFormat formatter = new SimpleDateFormat(format);
    formatter.setLenient(false);
    ParsePosition pos = new ParsePosition(0);
    Date date = formatter.parse(dttm, pos);

    if (date == null || pos.getErrorIndex() > 0) {
        return false;
    }
    if (pos.getIndex() != dttm.length()) {
        return false;
    }

    if (formatter.getCalendar().get(Calendar.YEAR) > 9999) {
        return false;
    }

    return true;
}

转载请注明:宇托的狗窝 » 一个Java日期格式验证的历程

发表我的评论
取消评论

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

网友最新评论 (9)

  1. 如果我输入2016 0211,好像也不行
    佩恩2016-12-28 18:46:21回复
    • 输入2016 0211,它的格式应该是"yyyy MMdd",注意"yyyy"和"MMdd"之间有个空格。
      宇托2017-01-05 10:48:40回复
  2. MySQL的datetime起始日期00-00-00 00:00:00无法通过验证
    ok2014-09-03 18:25:41回复
    • 起始是0000-00-00 00:00:00,上面写错了。
      ok2014-09-03 18:40:47回复
      • 那你可以自己添加年份大于0000就可以了
        yutuo2014-09-03 22:25:56回复
        • MySQL 的datetime 0000-00-00 00:00:00 和0001-01-01 00:00:00 自己添加时发现是等效的,就把0000-00-00 转换成了0001-01-01,接口里面验证的时候,前面传过来的最小值是0000-00-00 00:00:00
          ok2014-09-04 10:11:26回复
  3. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Bingo2014-07-16 11:37:39回复
  4. 关于你说的设置不进位之后还是不正确的,我的不是这样的,设置不进位就好了
    Bingo2014-07-16 11:34:59回复
    • 对不起,是我的笔误(原:"2012/12/43ABCD",正:"2012/12/30ABCD")。设置不进位之后,再测试"2012/12/30ABCD"这样的字符串时(调用方法:isDate("2012/12/30ABCD", "yyyy/MM/dd"))时,返回的是true。
      yutuo2014-08-21 16:31:04回复