001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools.date; 003 004import java.text.DateFormat; 005import java.text.ParseException; 006import java.text.SimpleDateFormat; 007import java.util.ArrayList; 008import java.util.Date; 009import java.util.List; 010 011import org.openstreetmap.josm.Main; 012 013/** 014 * Handles a number of different date formats encountered in OSM. This is built 015 * based on similar code in JOSM. This class is not threadsafe, a separate 016 * instance must be created per thread. 017 * 018 * @author Brett Henderson 019 */ 020class FallbackDateParser { 021 022 private static final String[] formats = { 023 "yyyy-MM-dd'T'HH:mm:ss'Z'", 024 "yyyy-MM-dd'T'HH:mm:ssZ", 025 "yyyy-MM-dd'T'HH:mm:ss", 026 "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", 027 "yyyy-MM-dd'T'HH:mm:ss.SSSZ", 028 "yyyy-MM-dd HH:mm:ss", 029 "MM/dd/yyyy HH:mm:ss", 030 "MM/dd/yyyy'T'HH:mm:ss.SSS'Z'", 031 "MM/dd/yyyy'T'HH:mm:ss.SSSZ", 032 "MM/dd/yyyy'T'HH:mm:ss.SSS", 033 "MM/dd/yyyy'T'HH:mm:ssZ", 034 "MM/dd/yyyy'T'HH:mm:ss", 035 "yyyy:MM:dd HH:mm:ss" 036 }; 037 038 private final List<DateFormat> dateParsers; 039 private int activeDateParser; 040 041 /** 042 * Creates a new instance. 043 */ 044 FallbackDateParser() { 045 // Build a list of candidate date parsers. 046 dateParsers = new ArrayList<>(formats.length); 047 for (String format : formats) { 048 dateParsers.add(new SimpleDateFormat(format)); 049 } 050 051 // We haven't selected a date parser yet. 052 activeDateParser = -1; 053 } 054 055 /** 056 * Attempts to parse the specified date. 057 * 058 * @param date 059 * The date to parse. 060 * @return The date. 061 * @throws ParseException 062 * Occurs if the date does not match any of the supported date 063 * formats. 064 */ 065 public Date parse(String date) throws ParseException { 066 String correctedDate; 067 068 // Try to fix ruby's broken xmlschema - format 069 // Replace this: 070 // 2007-02-12T18:43:01+00:00 071 // With this: 072 // 2007-02-12T18:43:01+0000 073 if (date.length() == 25 && date.charAt(22) == ':') { 074 correctedDate = date.substring(0, 22) + date.substring(23, 25); 075 } else { 076 correctedDate = date; 077 } 078 079 // If we have previously successfully used a date parser, we'll try it 080 // first. 081 if (activeDateParser >= 0) { 082 try { 083 return dateParsers.get(activeDateParser).parse(correctedDate); 084 } catch (ParseException e) { 085 // The currently active parser didn't work, so we must clear it 086 // and find a new appropriate parser. 087 activeDateParser = -1; 088 } 089 } 090 091 // Try the date parsers one by one until a suitable format is found. 092 for (int i = 0; i < dateParsers.size(); i++) { 093 try { 094 Date result; 095 096 // Attempt to parse with the current parser, if successful we 097 // store its index for next time. 098 result = dateParsers.get(i).parse(correctedDate); 099 activeDateParser = i; 100 101 return result; 102 103 } catch (ParseException pe) { 104 // Ignore parsing errors and try the next pattern. 105 if (Main.isTraceEnabled()) { 106 Main.trace(pe.getMessage()); 107 } 108 } 109 } 110 111 throw new ParseException("The date string (" + date + ") could not be parsed.", 0); 112 } 113}