如何检查和解析不同的日期样式

时间:2017-07-25 21:32:23

标签: java date-parsing java-date

我从Oracle列类型为varchar2而不是date的表中获取出生日期数据,其中主要原因是数据由CV解析公司解析,因为不同的CV具有各种样式出生日期如下:

3-3-1986
11.04.1983
07/24/1969
December, 05, 1986
NOVEMBER 03, 1981
    OCTOBER 06,1973
May 18th, 1984
Jan. 27th, 1967
Nov. 18, 1976
July 3,1989
27/02/1978 Place of birthLisbon, Portugal
June,11,1979

以下是我写的方法:

public int getAge(String dob){
    int age = 0;
        if(dob==null || dob.equals("")){
        age = 0;
        }
        else{
            dob = dob.trim();
            String[] words = dob.split ("-|/");
            String day = words[0];
            String month = words[1];
            String year = words[2];             
            age = CalculateAge.AgeCalculator(day, month, year);
        }   

    return age;
}

但是在这种方法中,我只能处理斜线和破折号。请帮我理清如何从上述日期样本中准确地获得日,月和年。

2 个答案:

答案 0 :(得分:4)

你不能。

以任何可想到的格式解析任何字符串是不可能的。

举一个例子:11.04.1983

是4月11日还是11月4日?根本没有办法知道。

你能做的最好的事情就是当你看到一个四位数的年份时提取年份,并且可能在大于12的时候判断一个月的日期。

顺便说一句,跟踪出生日期并计算求职者年龄这么麻烦似乎很奇怪。年龄通常是工作资格的不良标准。在许多地方这样做是违法的。

答案 1 :(得分:0)

虽然可以使用正则表达式来获取值,但您仍然需要对这些值做出一些决定/验证:

    当你不知道4月11日 th 或11月4日 th 模糊的情况(由@BasilBourque's answer指出)如11.04.1983 >:在这种情况下,你必须选择一个(或者可能尝试猜测)
  1. 验证其他值:如果您在非闰年中第32天 2月29日 th ,或 4月31日 th - 您需要在使用前检查这些值
  2. 还有计算年龄的问题
  3. 对于案例1,嗯,除了猜测之外没什么可做的。如果您能够以任何格式接收日期,则无法真正解决这种歧义(除非您假设某种格式是首选)。

    对于案例2和案例3,Java的API可以帮助您,因为它已经完成了您需要的所有验证。

    如果您使用的是 Java 8 ,请考虑使用new java.time API。它更容易,less bugged and less error-prone than the old APIs

    如果您使用的是 Java< = 7 ,则可以使用ThreeTen Backport,这是Java 8新日期/时间类的绝佳后端。对于 Android ,有ThreeTenABP(更多关于如何使用它here)。

    以下代码适用于两者。 唯一的区别是包名称(在Java 8中为java.time,在ThreeTen Backport(或Android的ThreeTenABP)中为org.threeten.bp),但类和方法名称是相同的

    首先,我创建了一个DateTimeFormatter个对象列表,每个对象都能解析一个(或多个)格式。

    对于某些情况,我使用可选部分(由[]分隔),因为某些模式只有空格或逗号区别,所以保持它们是可选的允许我在两种情况下使用相同的格式化程序。

    其他情况比较棘手,需要使用DateTimeFormatterBuilder更复杂的方法(参见代码中的注释)。

    之后,我删除了一些不必要的东西(比如Place of birth,开头和结尾的空格),然后我尝试用所有格式化程序解析日期,直到它工作(或得到{{1}如果无效的话。)

    然后我使用日期来计算年龄,使用null类。

    ChronoUnit

    当然,此代码不接受任何可能的模式,因为这是不可能的。您必须映射您可以接收的所有可能模式,并在出现后将新的模式添加到列表中。 (即使你使用正则表达式,你可能不得不改变它来处理新案例,但使用格式化程序可以保证它将解析并验证日期 - 并且还可以正确计算年龄)。

    如果您需要更多不同的模式,还可以查看javadoc以获取有关所有现有模式的更多信息。

    如果您真的愿意使用正则表达式并找到提取值的方法,那么您也可以这样做:

    // list of different formatters
    List<DateTimeFormatter> list = new ArrayList<>();
    
    // 3-3-1986 (assuming it's day-month-year)
    list.add(DateTimeFormatter.ofPattern("d-M-yyyy"));
    
    // 11.04.1983 (assuming it's day.month.year)
    list.add(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
    
    // 07/24/1969 (month/day/year)
    list.add(DateTimeFormatter.ofPattern("MM/dd/yyyy"));
    
    // "December, 05, 1986", "NOVEMBER 03, 1981", "July 3,1989" and "June,11,1979"
    // for " OCTOBER 06,1973", I'll remove the spaces before parsing
    list.add(new DateTimeFormatterBuilder()
        // case insensitive for month name
        .parseCaseInsensitive()
        // optional "," after month and optional spaces (after month and before year)
        .appendPattern("MMMM[ ][','][ ]d','[ ]yyyy")
        // use English locale for month name
        .toFormatter(Locale.ENGLISH));
    
    // "May 18th, 1984", "Jan. 27th, 1967" and "Nov. 18, 1976"
    // append suffix for days (st, nd, rd and th)
    // add suffix to days
    Map<Long, String> days = new HashMap<>();
    for (int i = 1; i <= 31; i++) {
        String s;
        switch (i) {
            case 1:
            case 21:
            case 31:
                s = "st";
                break;
            case 2:
            case 22:
                s = "nd";
                break;
            case 3:
            case 23:
                s = "rd";
                break;
            default:
                s = "th";
        }
        days.put((long) i, i + s);
    }
    list.add(new DateTimeFormatterBuilder()
        // month name with optional "."
        .appendPattern("MMM[.] ")
        // optional day with suffix
        .optionalStart().appendText(ChronoField.DAY_OF_MONTH, days).optionalEnd()
        // optional day without suffix
        .optionalStart().appendValue(ChronoField.DAY_OF_MONTH, 1, 2, SignStyle.NEVER).optionalEnd()
        // year
        .appendPattern(", yyyy")
        // use English locale for month name
        .toFormatter(Locale.ENGLISH));
    
    // 27/02/1978 Place of birthLisbon, Portugal ("Place of birth etc" will be removed manually)
    list.add(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
    
    String[] inputs = new String[] { "3-3-1986", "11.04.1983", "07/24/1969", "December, 05, 1986", "NOVEMBER 03, 1981", "    OCTOBER 06,1973",
                    "May 18th, 1984", "Jan. 27th, 1967", "Nov. 18, 1976", "July 3,1989", "27/02/1978 Place of birthLisbon, Portugal", "June,11,1979" };
    LocalDate now = LocalDate.now(); // current date
    for (String s : inputs) {
        LocalDate d = parse(list, s);
        if (d != null) {
            // get age in years
            long years = ChronoUnit.YEARS.between(d, now);
        }
    }
    
    // auxiliary method
    public LocalDate parse(List<DateTimeFormatter> list, String s) {
        // remove the unnecessary stuff
        // you can customize it to remove whatever unnecessary stuff you have in the inputs
        String input = s.replaceAll("Place of birth.*", "").trim();
    
        for (DateTimeFormatter fmt : list) {
            try {
                return LocalDate.parse(input, fmt);
            } catch (Exception e) {
                // can't parse: do nothing and try the next DateTimeFormatter
            }
        }
    
        // can't parse, return null
        return null;
    }
    

    如果值产生无效日期,则会抛出// assuming you've already got year, month, day from the regex LocalDate d = LocalDate.of(year, month, day); 。如果日期有效,您可以使用它来计算年龄,如上所示。

    无论如何,我不认为一个正则表达式是可能的(即使它是,我认为这将是如此复杂,将成为维护的噩梦) - 你可能必须创建许多不同的并循环使用它们。

    不要误会我的意思,正则表达式很酷,但它们并不是解决所有问题的最佳解决方案。