1 package net.sf.saxon.value; 2 3 import net.sf.saxon.expr.XPathContext; 4 import net.sf.saxon.functions.Component; 5 import net.sf.saxon.om.FastStringBuffer; 6 import net.sf.saxon.trans.DynamicError; 7 import net.sf.saxon.trans.XPathException; 8 import net.sf.saxon.type.BuiltInAtomicType; 9 import net.sf.saxon.type.ItemType; 10 import net.sf.saxon.type.Type; 11 import net.sf.saxon.type.ValidationException; 12 import net.sf.saxon.ConversionContext; 13 14 import java.util.*; 15 16 19 20 public final class TimeValue extends CalendarValue { 21 22 23 28 29 public TimeValue(GregorianCalendar calendar, boolean tzSpecified) { 30 this.calendar = calendar; 31 zoneSpecified = tzSpecified; 32 } 33 34 38 39 public TimeValue(CharSequence s) throws XPathException { 40 42 zoneSpecified = false; 43 StringTokenizer tok = new StringTokenizer(trimWhitespace(s).toString(), "-:.+Z", true); 44 try { 45 if (!tok.hasMoreElements()) badTime("too short"); 46 String part = (String ) tok.nextElement(); 47 48 int hour = Integer.parseInt(part); 49 if (part.length() != 2) badTime("hour must be two digits"); 50 if (hour > 24) badTime("hour is out of range"); 51 if (!tok.hasMoreElements()) badTime("too short"); 52 if (!":".equals(tok.nextElement())) badTime("wrong delimiter after hour"); 53 54 if (!tok.hasMoreElements()) badTime("too short"); 55 part = (String ) tok.nextElement(); 56 int minute = Integer.parseInt(part); 57 if (part.length() != 2) badTime("minute must be two digits"); 58 if (minute > 59) badTime("minute is out of range"); 59 if (hour == 24 && minute != 0) badTime("If hour is 24, minute must be 00"); 60 if (!tok.hasMoreElements()) badTime("too short"); 61 if (!":".equals(tok.nextElement())) badTime("wrong delimiter after minute"); 62 63 if (!tok.hasMoreElements()) badTime("too short"); 64 part = (String ) tok.nextElement(); 65 int second = Integer.parseInt(part); 66 if (part.length() != 2) badTime("second must be two digits"); 67 if (second > 61) badTime("second is out of range"); 68 if (hour == 24 && second != 0) badTime("If hour is 24, second must be 00"); 69 70 int millisecond = 0; 71 int tz = 0; 72 73 int state = 0; 74 while (tok.hasMoreElements()) { 75 if (state == 9) { 76 badTime("characters after the end"); 77 } 78 String delim = (String ) tok.nextElement(); 79 if (".".equals(delim)) { 80 if (state != 0) { 81 badTime("decimal separator occurs twice"); 82 } 83 part = (String ) tok.nextElement(); 84 double fractionalSeconds = Double.parseDouble('.' + part); 85 millisecond = (int) (Math.round(fractionalSeconds * 1000)); 86 if (hour == 24 && millisecond != 0) { 87 badTime("If hour is 24, milliseconds must be 0"); 88 } 89 state = 1; 90 } else if ("Z".equals(delim)) { 91 if (state > 1) { 92 badTime("Z cannot occur here"); 93 } 94 zoneSpecified = true; 95 tz = 0; 96 state = 9; } else if ("+".equals(delim) || "-".equals(delim)) { 98 if (state > 1) { 99 badTime(delim + " cannot occur here"); 100 } 101 state = 2; 102 zoneSpecified = true; 103 if (!tok.hasMoreElements()) badTime("missing timezone"); 104 part = (String ) tok.nextElement(); 105 if (part.length() != 2) badTime("timezone hour must be two digits"); 106 tz = Integer.parseInt(part) * 60; 107 if (tz > 14 * 60) badTime("timezone hour is out of range"); 108 if (tz > 12 * 60) badTime("Because of Java limitations, Saxon currently limits the timezone to +/- 12 hours"); 109 if ("-".equals(delim)) tz = -tz; 110 111 } else if (":".equals(delim)) { 112 if (state != 2) { 113 badTime("colon cannot occur here"); 114 } 115 state = 9; 116 part = (String ) tok.nextElement(); 117 int tzminute = Integer.parseInt(part); 118 if (part.length() != 2) badTime("timezone minute must be two digits"); 119 if (tzminute > 59) badTime("timezone minute is out of range"); 120 if (tz < 0) tzminute = -tzminute; 121 tz += tzminute; 122 } else { 123 badTime("timezone format is incorrect"); 124 } 125 } 126 127 if (state == 2 || state == 3) { 128 badTime("timezone incomplete"); 129 } 130 131 TimeZone zone = new SimpleTimeZone(tz * 60000, "LLL"); 133 calendar = new GregorianCalendar(zone); 134 calendar.setLenient(false); 135 136 int year = 1972; 138 int month = 11; 139 int day = 31; 140 if (hour == 24) { 141 hour = 0; 142 } 143 144 calendar.set(year, month, day, hour, minute, second); 145 calendar.set(Calendar.MILLISECOND, millisecond); 146 calendar.set(Calendar.ZONE_OFFSET, tz * 60000); 147 calendar.set(Calendar.DST_OFFSET, 0); 148 149 try { 150 calendar.getTime(); 151 } catch (IllegalArgumentException err) { 152 badTime("time components out of range"); 153 } 154 155 156 } catch (NumberFormatException err) { 157 badTime("non-numeric component"); 158 } 159 } 160 161 private void badTime(String msg) throws XPathException { 162 throw new DynamicError("Invalid time value (" + msg + ')'); 163 } 164 165 171 172 public AtomicValue convertPrimitive(BuiltInAtomicType requiredType, boolean validate, ConversionContext conversion) { 173 switch (requiredType.getPrimitiveType()) { 174 case Type.TIME: 175 case Type.ATOMIC: 176 case Type.ITEM: 177 return this; 178 case Type.STRING: 179 return new StringValue(getStringValue()); 180 case Type.UNTYPED_ATOMIC: 181 return new UntypedAtomicValue(getStringValue()); 182 default: 185 ValidationException err = new ValidationException("Cannot convert time to " + 186 requiredType.getDisplayName()); 187 err.setErrorCode("FORG0001"); 189 return new ValidationErrorValue(err); 190 } 191 } 192 193 198 199 public String getStringValue() { 200 201 FastStringBuffer sb = new FastStringBuffer(16); 202 203 DateTimeValue.appendString(sb, calendar.get(Calendar.HOUR_OF_DAY), 2); 204 sb.append(':'); 205 DateTimeValue.appendString(sb, calendar.get(Calendar.MINUTE), 2); 206 sb.append(':'); 207 DateTimeValue.appendSeconds(calendar, sb); 208 209 if (zoneSpecified) { 210 DateTimeValue.appendTimezone(calendar, sb); 211 } 212 213 return sb.toString(); 214 215 } 216 217 220 221 public DateTimeValue toDateTime() { 222 return new DateTimeValue(calendar, zoneSpecified); 223 } 224 225 229 230 public ItemType getItemType() { 231 return Type.TIME_TYPE; 232 } 233 234 239 240 public CalendarValue removeTimezone() throws XPathException { 241 return (CalendarValue) 242 new DateTimeValue(calendar, zoneSpecified) 243 .removeTimezone() 244 .convert(Type.TIME, null); 245 } 246 247 253 254 public CalendarValue setTimezone(SecondsDurationValue tz) throws XPathException { 255 return (CalendarValue) 256 new DateTimeValue(calendar, zoneSpecified) 257 .setTimezone(tz) 258 .convert(Type.TIME, null); 259 } 260 261 264 265 public Object convertToJava(Class target, XPathContext context) throws XPathException { 266 if (target.isAssignableFrom(TimeValue.class)) { 267 return this; 268 } else if (target == String .class) { 269 return getStringValue(); 270 } else if (target == Object .class) { 271 return getStringValue(); 272 } else { 273 Object o = super.convertToJava(target, context); 274 if (o == null) { 275 throw new DynamicError("Conversion of time to " + target.getName() + 276 " is not supported"); 277 } 278 return o; 279 } 280 } 281 282 286 287 public AtomicValue getComponent(int component) throws XPathException { 288 switch (component) { 289 case Component.HOURS: 290 return new IntegerValue(calendar.get(Calendar.HOUR_OF_DAY)); 291 case Component.MINUTES: 292 return new IntegerValue(calendar.get(Calendar.MINUTE)); 293 case Component.SECONDS: 294 FastStringBuffer sb = new FastStringBuffer(10); 295 DateTimeValue.appendSeconds(calendar, sb); 296 return DecimalValue.makeDecimalValue(sb, false); 297 case Component.TIMEZONE: 298 if (zoneSpecified) { 299 int tzsecs = (calendar.get(Calendar.ZONE_OFFSET) + 300 calendar.get(Calendar.DST_OFFSET)); 301 return SecondsDurationValue.fromMilliseconds(tzsecs); 302 } else { 303 return null; 304 } 305 default: 306 throw new IllegalArgumentException ("Unknown component for time: " + component); 307 } 308 } 309 310 320 321 public int compareTo(Object other) { 322 if (!(other instanceof TimeValue)) { 323 throw new ClassCastException ("Time values are not comparable to " + other.getClass()); 324 } 325 TimeValue otherTime = (TimeValue)other; 326 if (zoneSpecified == otherTime.zoneSpecified) { 327 GregorianCalendar cal2 = otherTime.calendar; 328 return calendar.getTime().compareTo(cal2.getTime()); 329 } else { 330 return new DateTimeValue(calendar, zoneSpecified).compareTo( 331 new DateTimeValue(otherTime.calendar, otherTime.zoneSpecified)); 332 } 333 } 334 335 345 346 public int compareTo(CalendarValue other, ConversionContext conversion) { 347 if (!(other instanceof TimeValue)) { 348 throw new ClassCastException ("Time values are not comparable to " + other.getClass()); 349 } 350 TimeValue otherTime = (TimeValue)other; 351 if (zoneSpecified == otherTime.zoneSpecified) { 352 GregorianCalendar cal2 = otherTime.calendar; 353 return calendar.getTime().compareTo(cal2.getTime()); 354 } else { 355 return new DateTimeValue(calendar, zoneSpecified).compareTo( 356 new DateTimeValue(otherTime.calendar, otherTime.zoneSpecified), 357 conversion); 358 } 359 } 360 361 362 public boolean equals(Object other) { 363 return compareTo(other) == 0; 364 } 365 366 public int hashCode() { 367 return new DateTimeValue(calendar, zoneSpecified).hashCode(); 368 } 369 370 377 378 public CalendarValue add(DurationValue duration) throws XPathException { 379 if (duration instanceof SecondsDurationValue) { 380 double seconds = duration.getLengthInSeconds(); 381 GregorianCalendar cal2 = (GregorianCalendar) calendar.clone(); 382 cal2.add(Calendar.SECOND, (int) seconds); 383 cal2.add(Calendar.MILLISECOND, (int) ((seconds % 1) * 1000)); 384 return new TimeValue(cal2, zoneSpecified); 385 } else { 386 DynamicError err = new DynamicError( 387 "Time+Duration arithmetic is supported only for xdt:dayTimeDuration"); 388 err.setIsTypeError(true); 389 throw err; 390 } 391 } 392 393 400 401 public SecondsDurationValue subtract(CalendarValue other, ConversionContext context) throws XPathException { 402 if (!(other instanceof TimeValue)) { 403 DynamicError err = new DynamicError( 404 "First operand of '-' is a time, but the second is not"); 405 err.setIsTypeError(true); 406 throw err; 407 } 408 return super.subtract(other, context); 409 } 410 411 412 413 } 414 415 433 | Popular Tags |