“反转”错误的解析日期
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.Date
、java.util.Calendar
和 java.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()”时
Restkit JSON 错误解析 1969/12/07 到 1970/01/25 GMT 范围内的日期