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 }