001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.tools;
003    
004    import java.text.ParseException;
005    import java.util.Calendar;
006    import java.util.Date;
007    import java.util.GregorianCalendar;
008    import java.util.TimeZone;
009    
010    import javax.xml.datatype.DatatypeConfigurationException;
011    import javax.xml.datatype.DatatypeFactory;
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     */
020    public class PrimaryDateParser {
021        private DatatypeFactory datatypeFactory;
022        private FallbackDateParser fallbackDateParser;
023        private Calendar calendar;
024    
025        /**
026         * Creates a new instance.
027         */
028        public PrimaryDateParser() {
029            // Build an xml data type factory.
030            try {
031                datatypeFactory = DatatypeFactory.newInstance();
032    
033            } catch (DatatypeConfigurationException e) {
034                throw new RuntimeException("Unable to instantiate xml datatype factory.", e);
035            }
036    
037            fallbackDateParser = new FallbackDateParser();
038    
039            calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
040        }
041    
042        private boolean isDateInShortStandardFormat(String date) {
043            char[] dateChars;
044            // We can only parse the date if it is in a very specific format.
045            // eg. 2007-09-23T08:25:43Z
046    
047            if (date.length() != 20) {
048                return false;
049            }
050    
051            dateChars = date.toCharArray();
052    
053            // Make sure any fixed characters are in the correct place.
054            if (dateChars[4] != '-') {
055                return false;
056            }
057            if (dateChars[7] != '-') {
058                return false;
059            }
060            if (dateChars[10] != 'T') {
061                return false;
062            }
063            if (dateChars[13] != ':') {
064                return false;
065            }
066            if (dateChars[16] != ':') {
067                return false;
068            }
069            if (dateChars[19] != 'Z') {
070                return false;
071            }
072    
073            // Ensure all remaining characters are numbers.
074            for (int i = 0; i < 4; i++) {
075                if (dateChars[i] < '0' || dateChars[i] > '9') {
076                    return false;
077                }
078            }
079            for (int i = 5; i < 7; i++) {
080                if (dateChars[i] < '0' || dateChars[i] > '9') {
081                    return false;
082                }
083            }
084            for (int i = 8; i < 10; i++) {
085                if (dateChars[i] < '0' || dateChars[i] > '9') {
086                    return false;
087                }
088            }
089            for (int i = 11; i < 13; i++) {
090                if (dateChars[i] < '0' || dateChars[i] > '9') {
091                    return false;
092                }
093            }
094            for (int i = 14; i < 16; i++) {
095                if (dateChars[i] < '0' || dateChars[i] > '9') {
096                    return false;
097                }
098            }
099            for (int i = 17; i < 19; i++) {
100                if (dateChars[i] < '0' || dateChars[i] > '9') {
101                    return false;
102                }
103            }
104    
105            // No problems found so it is in the special case format.
106            return true;
107        }
108    
109        private boolean isDateInLongStandardFormat(String date) {
110            char[] dateChars;
111            // We can only parse the date if it is in a very specific format.
112            // eg. 2007-09-23T08:25:43.000Z
113    
114            if (date.length() != 24) {
115                return false;
116            }
117    
118            dateChars = date.toCharArray();
119    
120            // Make sure any fixed characters are in the correct place.
121            if (dateChars[4] != '-') {
122                return false;
123            }
124            if (dateChars[7] != '-') {
125                return false;
126            }
127            if (dateChars[10] != 'T') {
128                return false;
129            }
130            if (dateChars[13] != ':') {
131                return false;
132            }
133            if (dateChars[16] != ':') {
134                return false;
135            }
136            if (dateChars[19] != '.') {
137                return false;
138            }
139            if (dateChars[23] != 'Z') {
140                return false;
141            }
142    
143            // Ensure all remaining characters are numbers.
144            for (int i = 0; i < 4; i++) {
145                if (dateChars[i] < '0' || dateChars[i] > '9') {
146                    return false;
147                }
148            }
149            for (int i = 5; i < 7; i++) {
150                if (dateChars[i] < '0' || dateChars[i] > '9') {
151                    return false;
152                }
153            }
154            for (int i = 8; i < 10; i++) {
155                if (dateChars[i] < '0' || dateChars[i] > '9') {
156                    return false;
157                }
158            }
159            for (int i = 11; i < 13; i++) {
160                if (dateChars[i] < '0' || dateChars[i] > '9') {
161                    return false;
162                }
163            }
164            for (int i = 14; i < 16; i++) {
165                if (dateChars[i] < '0' || dateChars[i] > '9') {
166                    return false;
167                }
168            }
169            for (int i = 17; i < 19; i++) {
170                if (dateChars[i] < '0' || dateChars[i] > '9') {
171                    return false;
172                }
173            }
174            for (int i = 20; i < 23; i++) {
175                if (dateChars[i] < '0' || dateChars[i] > '9') {
176                    return false;
177                }
178            }
179    
180            // No problems found so it is in the special case format.
181            return true;
182        }
183    
184        private Date parseShortStandardDate(String date) {
185            int year;
186            int month;
187            int day;
188            int hour;
189            int minute;
190            int second;
191    
192            year = Integer.parseInt(date.substring(0, 4));
193            month = Integer.parseInt(date.substring(5, 7));
194            day = Integer.parseInt(date.substring(8, 10));
195            hour = Integer.parseInt(date.substring(11, 13));
196            minute = Integer.parseInt(date.substring(14, 16));
197            second = Integer.parseInt(date.substring(17, 19));
198    
199            calendar.clear();
200            calendar.set(Calendar.YEAR, year);
201            calendar.set(Calendar.MONTH, month - 1);
202            calendar.set(Calendar.DAY_OF_MONTH, day);
203            calendar.set(Calendar.HOUR_OF_DAY, hour);
204            calendar.set(Calendar.MINUTE, minute);
205            calendar.set(Calendar.SECOND, second);
206    
207            return calendar.getTime();
208        }
209    
210        private Date parseLongStandardDate(String date) {
211            int year;
212            int month;
213            int day;
214            int hour;
215            int minute;
216            int second;
217            int millisecond;
218    
219            year = Integer.parseInt(date.substring(0, 4));
220            month = Integer.parseInt(date.substring(5, 7));
221            day = Integer.parseInt(date.substring(8, 10));
222            hour = Integer.parseInt(date.substring(11, 13));
223            minute = Integer.parseInt(date.substring(14, 16));
224            second = Integer.parseInt(date.substring(17, 19));
225            millisecond = Integer.parseInt(date.substring(20, 23));
226    
227            calendar.clear();
228            calendar.set(Calendar.YEAR, year);
229            calendar.set(Calendar.MONTH, month - 1);
230            calendar.set(Calendar.DAY_OF_MONTH, day);
231            calendar.set(Calendar.HOUR_OF_DAY, hour);
232            calendar.set(Calendar.MINUTE, minute);
233            calendar.set(Calendar.SECOND, second);
234            calendar.set(Calendar.MILLISECOND, millisecond);
235    
236            return calendar.getTime();
237        }
238    
239        /**
240         * Attempts to parse the specified date.
241         *
242         * @param date
243         *            The date to parse.
244         * @return The date.
245         * @throws ParseException
246         *             Occurs if the date does not match any of the supported date
247         *             formats.
248         */
249        public Date parse(String date) throws ParseException {
250            try {
251                if (isDateInShortStandardFormat(date)) {
252                    return parseShortStandardDate(date);
253                } else if (isDateInLongStandardFormat(date)) {
254                    return parseLongStandardDate(date);
255                } else {
256                    return datatypeFactory.newXMLGregorianCalendar(date).toGregorianCalendar().getTime();
257                }
258    
259            } catch (IllegalArgumentException e) {
260                return fallbackDateParser.parse(date);
261            }
262        }
263    }