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