1 package com.thaiopensource.datatype.xsd; 2 3 import org.relaxng.datatype.ValidationContext; 4 5 import java.util.Calendar ; 6 import java.util.Date ; 7 import java.util.GregorianCalendar ; 8 9 class DateTimeDatatype extends RegexDatatype implements OrderRelation { 10 static private final String YEAR_PATTERN = "-?([1-9][0-9]*)?[0-9]{4}"; 11 static private final String MONTH_PATTERN = "[0-9]{2}"; 12 static private final String DAY_OF_MONTH_PATTERN = "[0-9]{2}"; 13 static private final String TIME_PATTERN = "[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]*)?"; 14 static private final String TZ_PATTERN = "(Z|[+\\-][0-9][0-9]:[0-5][0-9])?"; 15 16 private final String template; 17 18 27 DateTimeDatatype(String template) { 28 super(makePattern(template)); 29 this.template = template; 30 } 31 32 static private String makePattern(String template) { 33 StringBuffer pattern = new StringBuffer (); 34 for (int i = 0, len = template.length(); i < len; i++) { 35 char c = template.charAt(i); 36 switch (c) { 37 case 'Y': 38 pattern.append(YEAR_PATTERN); 39 break; 40 case 'M': 41 pattern.append(MONTH_PATTERN); 42 break; 43 case 'D': 44 pattern.append(DAY_OF_MONTH_PATTERN); 45 break; 46 case 't': 47 pattern.append(TIME_PATTERN); 48 break; 49 default: 50 pattern.append(c); 51 break; 52 } 53 } 54 pattern.append(TZ_PATTERN); 55 return pattern.toString(); 56 } 57 58 boolean allowsValue(String str, ValidationContext vc) { 59 return getValue(str, vc) != null; 60 } 61 62 static private class DateTime { 63 private final Date date; 64 private final int leapMilliseconds; 65 private final boolean hasTimeZone; 66 67 DateTime(Date date, int leapMilliseconds, boolean hasTimeZone) { 68 this.date = date; 69 this.leapMilliseconds = leapMilliseconds; 70 this.hasTimeZone = hasTimeZone; 71 } 72 73 public boolean equals(Object obj) { 74 if (!(obj instanceof DateTime)) 75 return false; 76 DateTime other = (DateTime)obj; 77 return (this.date.equals(other.date) 78 && this.leapMilliseconds == other.leapMilliseconds 79 && this.hasTimeZone == other.hasTimeZone); 80 } 81 82 public int hashCode() { 83 return date.hashCode(); 84 } 85 86 Date getDate() { 87 return date; 88 } 89 90 int getLeapMilliseconds() { 91 return leapMilliseconds; 92 } 93 94 boolean getHasTimeZone() { 95 return hasTimeZone; 96 } 97 } 98 99 Object getValue(String str, ValidationContext vc) { 102 boolean negative = false; 103 int year = 2000; int month = 1; 105 int day = 1; 106 int hours = 0; 107 int minutes = 0; 108 int seconds = 0; 109 int milliseconds = 0; 110 int pos = 0; 111 int len = str.length(); 112 for (int templateIndex = 0, templateLength = template.length(); 113 templateIndex < templateLength; 114 templateIndex++) { 115 char templateChar = template.charAt(templateIndex); 116 switch (templateChar) { 117 case 'Y': 118 negative = str.charAt(pos) == '-'; 119 int yearStartIndex = negative ? pos + 1 : pos; 120 pos = skipDigits(str, yearStartIndex); 121 try { 122 year = Integer.parseInt(str.substring(yearStartIndex, pos)); 123 } 124 catch (NumberFormatException e) { 125 return null; 126 } 127 break; 128 case 'M': 129 month = parse2Digits(str, pos); 130 pos += 2; 131 break; 132 case 'D': 133 day = parse2Digits(str, pos); 134 pos += 2; 135 break; 136 case 't': 137 hours = parse2Digits(str, pos); 138 pos += 3; 139 minutes = parse2Digits(str, pos); 140 pos += 3; 141 seconds = parse2Digits(str, pos); 142 pos += 2; 143 if (pos < len && str.charAt(pos) == '.') { 144 int end = skipDigits(str, ++pos); 145 for (int j = 0; j < 3; j++) { 146 milliseconds *= 10; 147 if (pos < end) 148 milliseconds += str.charAt(pos++) - '0'; 149 } 150 pos = end; 151 } 152 break; 153 default: 154 pos++; 155 break; 156 } 157 } 158 boolean hasTimeZone = pos < len; 159 int tzOffset; 160 if (hasTimeZone && str.charAt(pos) != 'Z') 161 tzOffset = parseTimeZone(str, pos); 162 else 163 tzOffset = 0; 164 int leapMilliseconds; 165 if (seconds == 60) { 166 leapMilliseconds = milliseconds + 1; 167 milliseconds = 999; 168 seconds = 59; 169 } 170 else 171 leapMilliseconds = 0; 172 try { 173 GregorianCalendar cal = CalendarFactory.getCalendar(); 174 Date date; 175 if (cal == CalendarFactory.cal) { 176 synchronized (cal) { 177 date = createDate(cal, tzOffset, negative, year, month, day, hours, minutes, seconds, milliseconds); 178 } 179 } 180 else 181 date = createDate(cal, tzOffset, negative, year, month, day, hours, minutes, seconds, milliseconds); 182 return new DateTime(date, leapMilliseconds, hasTimeZone); 183 } 184 catch (IllegalArgumentException e) { 185 return null; 186 } 187 } 188 189 196 static class CalendarFactory { 197 static private final int UNKNOWN = -1; 198 static private final int SLOW = 0; 199 static private final int FAST = 1; 200 static private final int LIMIT = 10; 201 static private int speed = UNKNOWN; 202 static GregorianCalendar cal = new GregorianCalendar (); 203 204 static GregorianCalendar getCalendar() { 205 switch (speed) { 207 case SLOW: 208 return cal; 209 case FAST: 210 return new GregorianCalendar (); 211 } 212 long start = System.currentTimeMillis(); 216 GregorianCalendar tem = new GregorianCalendar (); 217 long time = System.currentTimeMillis() - start; 218 speed = time > LIMIT ? SLOW : FAST; 219 return tem; 220 } 221 } 222 223 private static Date createDate(GregorianCalendar cal, int tzOffset, boolean negative, 224 int year, int month, int day, 225 int hours, int minutes, int seconds, int milliseconds) { 226 cal.setLenient(false); 227 cal.setGregorianChange(new Date (Long.MIN_VALUE)); 228 cal.clear(); 229 cal.set(Calendar.ZONE_OFFSET, tzOffset); 231 cal.set(Calendar.DST_OFFSET, 0); 232 cal.set(Calendar.ERA, negative ? GregorianCalendar.BC : GregorianCalendar.AD); 233 month -= 1; 235 cal.set(year, month, day, hours, minutes, seconds); 236 cal.set(Calendar.MILLISECOND, milliseconds); 237 checkDate(cal.isLeapYear(year), month, day); return cal.getTime(); 239 } 240 241 static private void checkDate(boolean isLeapYear, int month, int day) { 242 if (month < 0 || month > 11 || day < 1) 243 throw new IllegalArgumentException (); 244 int dayMax; 245 switch (month) { 246 case Calendar.SEPTEMBER: 248 case Calendar.APRIL: 249 case Calendar.JUNE: 250 case Calendar.NOVEMBER: 251 dayMax = 30; 252 break; 253 case Calendar.FEBRUARY: 254 dayMax = isLeapYear ? 29 : 28; 255 break; 256 default: 257 dayMax = 31; 258 break; 259 } 260 if (day > dayMax) 261 throw new IllegalArgumentException (); 262 } 263 264 static private int parseTimeZone(String str, int i) { 265 int sign = str.charAt(i) == '-' ? -1 : 1; 266 return (Integer.parseInt(str.substring(i + 1, i + 3))*60 + Integer.parseInt(str.substring(i + 4)))*60*1000*sign; 267 } 268 269 static private int parse2Digits(String str, int i) { 270 return (str.charAt(i) - '0')*10 + (str.charAt(i + 1) - '0'); 271 } 272 273 static private int skipDigits(String str, int i) { 274 for (int len = str.length(); i < len; i++) { 275 if ("0123456789".indexOf(str.charAt(i)) < 0) 276 break; 277 } 278 return i; 279 } 280 281 OrderRelation getOrderRelation() { 282 return this; 283 } 284 285 static private final int TIME_ZONE_MAX = 14*60*60*1000; 286 287 public boolean isLessThan(Object obj1, Object obj2) { 288 DateTime dt1 = (DateTime)obj1; 289 DateTime dt2 = (DateTime)obj2; 290 long t1 = dt1.getDate().getTime(); 291 long t2 = dt2.getDate().getTime(); 292 if (dt1.getHasTimeZone() == dt2.getHasTimeZone()) 293 return isLessThan(t1, 294 dt1.getLeapMilliseconds(), 295 t2, 296 dt2.getLeapMilliseconds()); 297 else if (!dt2.getHasTimeZone()) 298 return isLessThan(t1, dt1.getLeapMilliseconds(), t2 - TIME_ZONE_MAX, dt2.getLeapMilliseconds()); 299 else 300 return isLessThan(t1 + TIME_ZONE_MAX, dt1.getLeapMilliseconds(), t2, dt2.getLeapMilliseconds()); 301 } 302 303 static private boolean isLessThan(long t1, int leapMillis1, long t2, int leapMillis2) { 304 if (t1 < t2) 305 return true; 306 if (t1 > t2) 307 return false; 308 if (leapMillis1 < leapMillis2) 309 return true; 310 return false; 311 } 312 } 313 | Popular Tags |