“反转”错误的解析日期

Posted

技术标签:

【中文标题】“反转”错误的解析日期【英文标题】:"Reverse" wrong parsed date 【发布时间】:2016-07-19 09:37:27 【问题描述】:

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

我们收到了很多 (>50k) 的“错误”格式的字符串,它们无论如何都被 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”等等......)得出结论?

最好的问候

【问题讨论】:

顺便说一下,麻烦的旧日期时间类,如 java.util.Datejava.util.Calendarjava.text.SimpleDateFormat 现在是 legacy,被 Java 8 中内置的 java.time 类所取代& Java 9。见Tutorial by Oracle。 【参考方案1】:

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

我可以使用 Calendar 遍历可能的日期,以“错误”的方式解析日期,并使用这些信息构建地图。

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]

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

【讨论】:

【参考方案2】:

以Martin Ackermann's answer 为基础:

首先,我稍微简化了代码。

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 则下表显示了可修复/不明确的唯一日期数,具体取决于 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

【讨论】:

【参考方案3】:

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

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

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

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

【讨论】:

【参考方案4】:

您是否尝试过将 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));          
               
           

颠倒日期 07-06-1980

解析日期 1980-06-07

【讨论】:

问题不在于如何正确解析日期,或如何避免错误解析——而是关于从已经错误解析的日期到原始输入的结论

以上是关于“反转”错误的解析日期的主要内容,如果未能解决你的问题,请参考以下文章

解析日期在云函数中执行的创建作业中引发错误

错误代码:38 DB::Exception:无法解析日期:值太短:无法从字符串解析日期:执行“FUNCTION toDate()”时

JSON 意外语法错误 - 日期解析/编码/解码

Restkit JSON 错误解析 1969/12/07 到 1970/01/25 GMT 范围内的日期

Java Android Studio - 使用 Android 10 的物理设备上出现无法解析的日期错误

java 字符串转换成日期