"Reverse" 错误的解析日期

"Reverse" wrong parsed date

我们 运行 一个使用不同数据的 REST-web 服务,我当前的问题属于一个日期,作为字符串接收并由 java.text.SimpleDateFormat (java 8) 解析:

我们收到了很多 (>50k) 'wrong' 格式的字符串,它们无论如何都被 SimpleDateFormat 解析了。

SimpleDateFormat 使用模式 "yyyy-MM-dd" 配置。 我们以相反的方式收到字符串 "dd-MM-yyyy".

例如,字符串“07-07-1950”被解析为日期“0012-10-31”(从第 7 年的 7 月开始,增加了 1950 天)。

我们修复了实现,因此现在可以按预期解析这些字符串。但是我们系统中有所有损坏的日期。现在最后一个问题是:

有没有办法从日期“0012-10-31”到可能的原始输入(例如“07-07-1950”、“07-06-1980”,也许更多...)得出结论?

此致

我认为您无法找出损坏输入的原始日期,但您应该能够找到所有损坏的日期,或许还能找到重新使用该数据的方法。这是因为每个日期都被未知的天数改变了,并且逆向该过程需要您知道或者天数开始日期,你这里好像没有。

也就是说,缩小任何损坏的日期实际上相当容易。

一个月的最大值应该是 12。这意味着损坏数据的最新 "year" 将是 12 年。如果您的日期 运行 一直到目前,最大的年份(被错误地解析为天)将是 2016 年,这将被转换为大约 5.5 年。因此任何年份低于 18 或 19 的日期都已损坏,您至少应该能够删除它们。

这里唯一的边缘情况是,如果您的日期有效地落在十几岁的早期。如果是这种情况,您将不得不手动完成这些操作。但这似乎不太可能。

您是否尝试过将 SimpleDateFormat Lenient 设置为 false

    package test;           

    import java.text.ParseException;            
    import java.text.SimpleDateFormat;          
    import java.util.Date;          

    public class Test {         

        public static void main(String[] args) throws ParseException {          
            SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyy-MM-dd");          
            SimpleDateFormat dateFormat2 = new SimpleDateFormat("dd-MM-yyyy");          
            dateFormat1.setLenient(false);          
            dateFormat2.setLenient(false);          
            Date d = null;          
            String invalidDate = "07-06-1980";          
        try {           
            d = dateFormat1.parse(invalidDate);         
        } catch (Exception e) {         
            System.out.println("reversed date " + invalidDate);         
            d = dateFormat2.parse(invalidDate);         
        }           

        System.out.println(parsed date " + dateFormat1.format(d));          
    }           
}           

reversed date 07-06-1980

parsed date 1980-06-07

我找到了一种查找可能输入的方法:

我可以使用日历遍历可能的日期,以 "wron"g 方式解析日期,并使用这些信息构建地图。

public static Map<String, Collection<String>> createDateMapping() throws ParseException
{
    final DateFormat targetFormat = new SimpleDateFormat("yyyy-MM-dd");
    final DateFormat wrongFormat = new SimpleDateFormat("dd-MM-yyyy");

    //starting today
    final Calendar cal = Calendar.getInstance();

    final Map<String, Collection<String>> inputMappings = new HashMap<>();

    //rolling down to year zero is quite time consuming, back to year 1899 should be enough...
    while (cal.get(Calendar.YEAR) > 1899)
    {
        //creating the "wrong" date string
        final String formattedDate = wrongFormat.format(cal.getTime());
        final String key = targetFormat.format(targetFormat.parse(formattedDate));

        if (!inputMappings.containsKey(key))
        {
            inputMappings.put(key, new ArrayList<>());
        }

        inputMappings.get(key).add(targetFormat.format(cal.getTime()));

        //roll calendar to previous day
        cal.roll(Calendar.DAY_OF_YEAR, false);

        if (cal.get(Calendar.DAY_OF_YEAR) == 1)
        {
            //roll down the year manually, since it is not rolled down automatically
            cal.roll(Calendar.DAY_OF_YEAR, false);

            //roll down the day again, to start at the last day of the year again
            cal.roll(Calendar.YEAR, false);
        }
    }

    return inputMappings;
}

通过使用这个方法我可以:

final Map<String, Collection<String>> dateMapping = createDateMapping();

System.out.println(dateMapping.get("0012-10-31"));//[2011-05-07, 1980-06-07, 1950-07-07, 1919-08-07]

它不会完全解决问题,但至少是一个很好的起点 - 希望有一些日期有更明确的结果。

基于

首先,我简化了代码。

public static Map<String, Set<LocalDate>> createDateMapping(LocalDate min, LocalDate max) throws ParseException {
    DateFormat targetFormat = new SimpleDateFormat("yyyy-MM-dd");
    DateTimeFormatter wrongFormat = DateTimeFormatter.ofPattern("dd-MM-yyyy");

    final Map<String, Set<LocalDate>> inputMappings = new LinkedHashMap<>();

    for (LocalDate date = min; !date.isAfter(max); date = date.plusDays(1)) {
        final String incorrectlyFormattedDate = date.format(wrongFormat);
        final String key = targetFormat.format(targetFormat.parse(incorrectlyFormattedDate));
        if (!inputMappings.containsKey(key)) {
            inputMappings.put(key, new TreeSet<>());
        }
        inputMappings.get(key).add(date);
    }

    return inputMappings;
}

轻松修复无效日期取决于有效日期的范围。
例如,如果 max=2016-12-31 那么下面的 table 显示 fixable/ambiguous 取决于 min

的唯一日期的数量
min         fixable ambiguous
-----------------------------
1990-01-01  9862    0
1980-01-01  8827    2344
1970-01-01  5331    5918
1960-01-01  1832    9494
1950-01-01  408     10950
1940-01-01  314     11054
1930-01-01  218     11160
1920-01-01  165     11223
1910-01-01  135     11263
1900-01-01  105     11303

无效日期的模糊匹配以大约 30 年的间隔出现,因此如果实际日期落在 30 年的时间段内,那么您很幸运

    LocalDate max = LocalDate.of(2016, Month.DECEMBER, 31);
    LocalDate min = max.minusYears(30);
    Map<String, Set<LocalDate>> invalidDateMapping = createDateMapping(min, max);
    long reversibleCount = invalidDateMapping.entrySet().stream().filter(e -> e.getValue().size() == 1).count(); // 10859
    long ambiguousCount = invalidDateMapping.size() - reversibleCount; // 50