001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools.date; 003 004import java.text.ParseException; 005import java.util.Calendar; 006import java.util.Date; 007import java.util.GregorianCalendar; 008import java.util.TimeZone; 009 010import javax.xml.datatype.DatatypeConfigurationException; 011import 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 */ 020public 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 static 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 static 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 = Integer.parseInt(date.substring(0, 4)); 186 int month = Integer.parseInt(date.substring(5, 7)); 187 int day = Integer.parseInt(date.substring(8, 10)); 188 int hour = Integer.parseInt(date.substring(11, 13)); 189 int minute = Integer.parseInt(date.substring(14, 16)); 190 int second = Integer.parseInt(date.substring(17, 19)); 191 192 calendar.clear(); 193 calendar.set(Calendar.YEAR, year); 194 calendar.set(Calendar.MONTH, month - 1); 195 calendar.set(Calendar.DAY_OF_MONTH, day); 196 calendar.set(Calendar.HOUR_OF_DAY, hour); 197 calendar.set(Calendar.MINUTE, minute); 198 calendar.set(Calendar.SECOND, second); 199 200 return calendar.getTime(); 201 } 202 203 private Date parseLongStandardDate(String date) { 204 int year = Integer.parseInt(date.substring(0, 4)); 205 int month = Integer.parseInt(date.substring(5, 7)); 206 int day = Integer.parseInt(date.substring(8, 10)); 207 int hour = Integer.parseInt(date.substring(11, 13)); 208 int minute = Integer.parseInt(date.substring(14, 16)); 209 int second = Integer.parseInt(date.substring(17, 19)); 210 int millisecond = Integer.parseInt(date.substring(20, 23)); 211 212 calendar.clear(); 213 calendar.set(Calendar.YEAR, year); 214 calendar.set(Calendar.MONTH, month - 1); 215 calendar.set(Calendar.DAY_OF_MONTH, day); 216 calendar.set(Calendar.HOUR_OF_DAY, hour); 217 calendar.set(Calendar.MINUTE, minute); 218 calendar.set(Calendar.SECOND, second); 219 calendar.set(Calendar.MILLISECOND, millisecond); 220 221 return calendar.getTime(); 222 } 223 224 /** 225 * Attempts to parse the specified date. 226 * 227 * @param date 228 * The date to parse. 229 * @return The date. 230 * @throws ParseException 231 * Occurs if the date does not match any of the supported date 232 * formats. 233 */ 234 public Date parse(String date) throws ParseException { 235 try { 236 if (isDateInShortStandardFormat(date)) { 237 return parseShortStandardDate(date); 238 } else if (isDateInLongStandardFormat(date)) { 239 return parseLongStandardDate(date); 240 } else { 241 return datatypeFactory.newXMLGregorianCalendar(date).toGregorianCalendar().getTime(); 242 } 243 244 } catch (IllegalArgumentException e) { 245 return fallbackDateParser.parse(date); 246 } 247 } 248}