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 }