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 final FallbackDateParser fallbackDateParser; 023 private final 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 // We can only parse the date if it is in a very specific format. 044 // eg. 2007-09-23T08:25:43Z 045 046 if (date.length() != 20) { 047 return false; 048 } 049 050 char[] dateChars = date.toCharArray(); 051 052 // Make sure any fixed characters are in the correct place. 053 if (dateChars[4] != '-') { 054 return false; 055 } 056 if (dateChars[7] != '-') { 057 return false; 058 } 059 if (dateChars[10] != 'T') { 060 return false; 061 } 062 if (dateChars[13] != ':') { 063 return false; 064 } 065 if (dateChars[16] != ':') { 066 return false; 067 } 068 if (dateChars[19] != 'Z') { 069 return false; 070 } 071 072 // Ensure all remaining characters are numbers. 073 for (int i = 0; i < 4; i++) { 074 if (dateChars[i] < '0' || dateChars[i] > '9') { 075 return false; 076 } 077 } 078 for (int i = 5; i < 7; i++) { 079 if (dateChars[i] < '0' || dateChars[i] > '9') { 080 return false; 081 } 082 } 083 for (int i = 8; i < 10; i++) { 084 if (dateChars[i] < '0' || dateChars[i] > '9') { 085 return false; 086 } 087 } 088 for (int i = 11; i < 13; i++) { 089 if (dateChars[i] < '0' || dateChars[i] > '9') { 090 return false; 091 } 092 } 093 for (int i = 14; i < 16; i++) { 094 if (dateChars[i] < '0' || dateChars[i] > '9') { 095 return false; 096 } 097 } 098 for (int i = 17; i < 19; i++) { 099 if (dateChars[i] < '0' || dateChars[i] > '9') { 100 return false; 101 } 102 } 103 104 // No problems found so it is in the special case format. 105 return true; 106 } 107 108 private static boolean isDateInLongStandardFormat(String date) { 109 // We can only parse the date if it is in a very specific format. 110 // eg. 2007-09-23T08:25:43.000Z 111 112 if (date.length() != 24) { 113 return false; 114 } 115 116 char[] dateChars = date.toCharArray(); 117 118 // Make sure any fixed characters are in the correct place. 119 if (dateChars[4] != '-') { 120 return false; 121 } 122 if (dateChars[7] != '-') { 123 return false; 124 } 125 if (dateChars[10] != 'T') { 126 return false; 127 } 128 if (dateChars[13] != ':') { 129 return false; 130 } 131 if (dateChars[16] != ':') { 132 return false; 133 } 134 if (dateChars[19] != '.') { 135 return false; 136 } 137 if (dateChars[23] != 'Z') { 138 return false; 139 } 140 141 // Ensure all remaining characters are numbers. 142 for (int i = 0; i < 4; i++) { 143 if (dateChars[i] < '0' || dateChars[i] > '9') { 144 return false; 145 } 146 } 147 for (int i = 5; i < 7; i++) { 148 if (dateChars[i] < '0' || dateChars[i] > '9') { 149 return false; 150 } 151 } 152 for (int i = 8; i < 10; i++) { 153 if (dateChars[i] < '0' || dateChars[i] > '9') { 154 return false; 155 } 156 } 157 for (int i = 11; i < 13; i++) { 158 if (dateChars[i] < '0' || dateChars[i] > '9') { 159 return false; 160 } 161 } 162 for (int i = 14; i < 16; i++) { 163 if (dateChars[i] < '0' || dateChars[i] > '9') { 164 return false; 165 } 166 } 167 for (int i = 17; i < 19; i++) { 168 if (dateChars[i] < '0' || dateChars[i] > '9') { 169 return false; 170 } 171 } 172 for (int i = 20; i < 23; i++) { 173 if (dateChars[i] < '0' || dateChars[i] > '9') { 174 return false; 175 } 176 } 177 178 // No problems found so it is in the special case format. 179 return true; 180 } 181 182 private Date parseShortStandardDate(String date) { 183 int year = Integer.parseInt(date.substring(0, 4)); 184 int month = Integer.parseInt(date.substring(5, 7)); 185 int day = Integer.parseInt(date.substring(8, 10)); 186 int hour = Integer.parseInt(date.substring(11, 13)); 187 int minute = Integer.parseInt(date.substring(14, 16)); 188 int second = Integer.parseInt(date.substring(17, 19)); 189 190 calendar.clear(); 191 calendar.set(Calendar.YEAR, year); 192 calendar.set(Calendar.MONTH, month - 1); 193 calendar.set(Calendar.DAY_OF_MONTH, day); 194 calendar.set(Calendar.HOUR_OF_DAY, hour); 195 calendar.set(Calendar.MINUTE, minute); 196 calendar.set(Calendar.SECOND, second); 197 198 return calendar.getTime(); 199 } 200 201 private Date parseLongStandardDate(String date) { 202 int year = Integer.parseInt(date.substring(0, 4)); 203 int month = Integer.parseInt(date.substring(5, 7)); 204 int day = Integer.parseInt(date.substring(8, 10)); 205 int hour = Integer.parseInt(date.substring(11, 13)); 206 int minute = Integer.parseInt(date.substring(14, 16)); 207 int second = Integer.parseInt(date.substring(17, 19)); 208 int millisecond = Integer.parseInt(date.substring(20, 23)); 209 210 calendar.clear(); 211 calendar.set(Calendar.YEAR, year); 212 calendar.set(Calendar.MONTH, month - 1); 213 calendar.set(Calendar.DAY_OF_MONTH, day); 214 calendar.set(Calendar.HOUR_OF_DAY, hour); 215 calendar.set(Calendar.MINUTE, minute); 216 calendar.set(Calendar.SECOND, second); 217 calendar.set(Calendar.MILLISECOND, millisecond); 218 219 return calendar.getTime(); 220 } 221 222 /** 223 * Attempts to parse the specified date. 224 * 225 * @param date 226 * The date to parse. 227 * @return The date. 228 * @throws ParseException 229 * Occurs if the date does not match any of the supported date 230 * formats. 231 */ 232 public Date parse(String date) throws ParseException { 233 try { 234 if (isDateInShortStandardFormat(date)) { 235 return parseShortStandardDate(date); 236 } else if (isDateInLongStandardFormat(date)) { 237 return parseLongStandardDate(date); 238 } else { 239 return datatypeFactory.newXMLGregorianCalendar(date).toGregorianCalendar().getTime(); 240 } 241 242 } catch (IllegalArgumentException e) { 243 return fallbackDateParser.parse(date); 244 } 245 } 246}