1 package net.sf.saxon.value; 2 import net.sf.saxon.Err; 3 import net.sf.saxon.ConversionContext; 4 import net.sf.saxon.expr.XPathContext; 5 import net.sf.saxon.functions.Component; 6 import net.sf.saxon.om.FastStringBuffer; 7 import net.sf.saxon.trans.DynamicError; 8 import net.sf.saxon.trans.XPathException; 9 import net.sf.saxon.type.BuiltInAtomicType; 10 import net.sf.saxon.type.ItemType; 11 import net.sf.saxon.type.Type; 12 import net.sf.saxon.type.ValidationException; 13 14 import java.util.*; 15 16 20 21 public class DateValue extends CalendarValue { 22 23 protected int tzOffset = 0; 25 26 private Date UTCDate = null; 29 32 33 protected DateValue() {} 34 35 39 public DateValue(CharSequence s) throws XPathException { 40 setLexicalValue(s); 41 } 42 43 49 public DateValue(GregorianCalendar calendar, boolean timeZoneSpecified, int timeZoneOffset) { 50 this.calendar = calendar; 51 this.zoneSpecified = timeZoneSpecified; 52 this.tzOffset = timeZoneOffset; 53 } 54 55 61 public void setLexicalValue(CharSequence s) throws XPathException { 62 zoneSpecified = false; 63 StringTokenizer tok = new StringTokenizer(trimWhitespace(s).toString(), "-:+Z", true); 64 try { 65 if (!tok.hasMoreElements()) badDate("Too short", s); 66 String part = (String )tok.nextElement(); 67 int era = +1; 68 if ("+".equals(part)) { 69 part = (String )tok.nextElement(); 70 } else if ("-".equals(part)) { 71 era = -1; 72 part = (String )tok.nextElement(); 73 } 74 int year = Integer.parseInt(part) * era; 75 if (part.length() < 4) badDate("Year is less than four digits", s); 76 if (year==0) badDate("Year zero is not allowed", s); 77 if (!tok.hasMoreElements()) badDate("Too short", s); 78 if (!"-".equals(tok.nextElement())) badDate("Wrong delimiter after year", s); 79 80 if (!tok.hasMoreElements()) badDate("Too short", s); 81 part = (String )tok.nextElement(); 82 int month = Integer.parseInt(part); 83 if (part.length() != 2) badDate("Month must be two digits", s); 84 if (month < 1 || month > 12) badDate("Month is out of range", s); 85 if (!tok.hasMoreElements()) badDate("Too short", s); 86 if (!"-".equals(tok.nextElement())) badDate("Wrong delimiter after month", s); 87 88 if (!tok.hasMoreElements()) badDate("Too short", s); 89 part = (String )tok.nextElement(); 90 int day = Integer.parseInt(part); 91 if (part.length() != 2) badDate("Day must be two digits", s); 92 if (day < 1 || day > 31) badDate("Day is out of range", s); 93 94 if (tok.hasMoreElements()) { 95 96 String delim = (String )tok.nextElement(); 97 98 if ("Z".equals(delim)) { 99 zoneSpecified = true; 100 tzOffset = 0; 101 if (tok.hasMoreElements()) badDate("Continues after 'Z'", s); 102 } else if (!(!"+".equals(delim) && !"-".equals(delim))) { 103 zoneSpecified = true; 104 if (!tok.hasMoreElements()) badDate("Missing timezone", s); 105 part = (String )tok.nextElement(); 106 int tzhour = Integer.parseInt(part); 107 if (part.length() != 2) badDate("Timezone hour must be two digits", s); 108 if (tzhour > 14) badDate("Timezone hour is out of range", s); 109 if (tzhour > 12) badDate("Because of Java limitations, Saxon currently limits the timezone to +/- 12 hours", s); 110 if (!tok.hasMoreElements()) badDate("No minutes in timezone", s); 111 if (!":".equals(tok.nextElement())) badDate("Wrong delimiter after timezone hour", s); 112 113 if (!tok.hasMoreElements()) badDate("No minutes in timezone", s); 114 part = (String )tok.nextElement(); 115 int tzminute = Integer.parseInt(part); 116 if (part.length() != 2) badDate("Timezone minute must be two digits", s); 117 if (tzminute > 59) badDate("Timezone minute is out of range", s); 118 if (tok.hasMoreElements()) badDate("Continues after timezone", s); 119 120 tzOffset = (tzhour*60 + tzminute); 121 if ("-".equals(delim)) tzOffset = -tzOffset; 122 } else { 123 badDate("Timezone format is incorrect", s); 124 } 125 } 126 127 TimeZone zone = new SimpleTimeZone(tzOffset*60000, "LLL"); 128 calendar = new GregorianCalendar(zone); 129 calendar.clear(); 130 calendar.setLenient(false); 131 calendar.set(Math.abs(year), month-1, day); 132 calendar.set(Calendar.ZONE_OFFSET, tzOffset*60000); 133 calendar.set(Calendar.DST_OFFSET, 0); 134 if (year < 0) { 135 calendar.set(Calendar.ERA, GregorianCalendar.BC); 136 } 137 try { 138 calendar.getTime(); 139 } catch (IllegalArgumentException err) { 140 badDate("Non-existent date", s); 141 } 142 143 144 } catch (NumberFormatException err) { 145 badDate("Non-numeric component", s); 146 } 147 } 148 149 private void badDate(String msg, CharSequence value) throws ValidationException { 150 ValidationException err = new ValidationException("Invalid date " + Err.wrap(value, Err.VALUE) + ". " + msg); 151 err.setErrorCode("FORG0001"); 152 throw err; 153 } 154 155 160 161 public Date getUTCDate() { 162 if (UTCDate==null) { 164 UTCDate = calendar.getTime(); 165 } 166 return UTCDate; 167 } 168 169 175 176 public AtomicValue convertPrimitive(BuiltInAtomicType requiredType, boolean validate, ConversionContext conversion) { 177 switch(requiredType.getPrimitiveType()) { 178 case Type.DATE: 179 case Type.ATOMIC: 180 case Type.ITEM: 181 return this; 182 case Type.DATE_TIME: 183 return new DateTimeValue(calendar, zoneSpecified); 184 185 case Type.STRING: 186 return new StringValue(getStringValueCS()); 187 188 case Type.UNTYPED_ATOMIC: 189 return new UntypedAtomicValue(getStringValueCS()); 190 191 case Type.G_YEAR: { 192 GregorianCalendar cal2 = new GregorianCalendar(calendar.getTimeZone()); 193 cal2.clear(); 194 cal2.set(Calendar.ERA, calendar.get(Calendar.ERA)); 195 cal2.set(calendar.get(Calendar.YEAR), 0, 1); 196 cal2.set(Calendar.ZONE_OFFSET, tzOffset*60000); 197 cal2.set(Calendar.DST_OFFSET, 0); 198 calendar.getTime(); 199 return new GYearValue(cal2, zoneSpecified, tzOffset); 200 } 201 case Type.G_YEAR_MONTH: { 202 GregorianCalendar cal2 = new GregorianCalendar(calendar.getTimeZone()); 203 cal2.clear(); 204 cal2.set(Calendar.ERA, calendar.get(Calendar.ERA)); 205 cal2.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), 1); 206 cal2.set(Calendar.ZONE_OFFSET, tzOffset*60000); 207 cal2.set(Calendar.DST_OFFSET, 0); 208 calendar.getTime(); 209 return new GYearMonthValue(cal2, zoneSpecified, tzOffset); 210 } 211 case Type.G_MONTH: { 212 GregorianCalendar cal2 = new GregorianCalendar(calendar.getTimeZone()); 213 cal2.clear(); 214 cal2.set(2000, calendar.get(Calendar.MONTH), 1); 215 cal2.set(Calendar.ZONE_OFFSET, tzOffset*60000); 216 cal2.set(Calendar.DST_OFFSET, 0); 217 calendar.getTime(); 218 return new GMonthValue(cal2, zoneSpecified, tzOffset); 219 } 220 case Type.G_MONTH_DAY: { 221 GregorianCalendar cal2 = new GregorianCalendar(calendar.getTimeZone()); 222 cal2.clear(); 223 cal2.set(2000, calendar.get(Calendar.MONTH), calendar.get(Calendar.DATE)); 224 cal2.set(Calendar.ZONE_OFFSET, tzOffset*60000); 225 cal2.set(Calendar.DST_OFFSET, 0); 226 calendar.getTime(); 227 return new GMonthDayValue(cal2, zoneSpecified, tzOffset); 228 } 229 case Type.G_DAY:{ 230 GregorianCalendar cal2 = new GregorianCalendar(calendar.getTimeZone()); 231 cal2.clear(); 232 cal2.set(2000, 0, calendar.get(Calendar.DATE)); 233 cal2.set(Calendar.ZONE_OFFSET, tzOffset*60000); 234 cal2.set(Calendar.DST_OFFSET, 0); 235 calendar.getTime(); 236 return new GDayValue(cal2, zoneSpecified, tzOffset); 237 } 238 239 default: 240 ValidationException err = new ValidationException("Cannot convert date to " + 241 requiredType.getDisplayName()); 242 err.setErrorCode("FORG0001"); 244 return new ValidationErrorValue(err); 245 } 246 } 247 248 252 253 public String getStringValue() { 254 255 FastStringBuffer sb = new FastStringBuffer(16); 256 int era = calendar.get(GregorianCalendar.ERA); 257 int year = calendar.get(Calendar.YEAR); 258 if (era == GregorianCalendar.BC) { 259 sb.append('-'); 260 } 261 DateTimeValue.appendString(sb, year, (year>9999 ? (calendar.get(Calendar.YEAR)+"").length() : 4)); 262 sb.append('-'); 263 DateTimeValue.appendString(sb, calendar.get(Calendar.MONTH)+1, 2); 264 sb.append('-'); 265 DateTimeValue.appendString(sb, calendar.get(Calendar.DATE), 2); 266 267 if (zoneSpecified) { 268 DateTimeValue.appendTimezone(tzOffset, sb); 269 } 270 271 return sb.toString(); 272 273 } 274 275 279 280 public ItemType getItemType() { 281 return Type.DATE_TYPE; 282 } 283 284 289 290 public CalendarValue removeTimezone() throws XPathException { 291 return (CalendarValue) 292 ((DateTimeValue)convert(Type.DATE_TIME, null)) 293 .removeTimezone() 294 .convert(Type.DATE, null); 295 } 296 297 303 304 public CalendarValue setTimezone(SecondsDurationValue tz) throws XPathException { 305 return (CalendarValue) 306 ((DateTimeValue)convert(Type.DATE_TIME, null)) 307 .setTimezone(tz) 308 .convert(Type.DATE, null); 309 } 310 311 314 315 public Object convertToJava(Class target, XPathContext context) throws XPathException { 316 if (target.isAssignableFrom(Date.class)) { 317 return getUTCDate(); 318 } else if (target.isAssignableFrom(DateTimeValue.class)) { 319 return this; 320 } else if (target==String .class || target==CharSequence .class) { 321 return getStringValue(); 322 } else if (target==Object .class) { 323 return getStringValue(); 324 } else { 325 Object o = super.convertToJava(target, context); 326 if (o == null) { 327 throw new DynamicError("Conversion of date to " + target.getName() + 328 " is not supported"); 329 } 330 return o; 331 } 332 } 333 334 338 339 public AtomicValue getComponent(int component) throws XPathException { 340 switch (component) { 341 case Component.YEAR: 342 return new IntegerValue(calendar.get(Calendar.YEAR)); 343 case Component.MONTH: 344 return new IntegerValue(calendar.get(Calendar.MONTH) + 1); 345 case Component.DAY: 346 return new IntegerValue(calendar.get(Calendar.DATE)); 347 case Component.TIMEZONE: 348 if (zoneSpecified) { 349 return SecondsDurationValue.fromMilliseconds(tzOffset*60000); 350 } else { 351 return null; 352 } 353 default: 354 throw new IllegalArgumentException ("Unknown component for date: " + component); 355 } 356 } 357 358 368 369 public int compareTo(Object other) { 370 if (!(other instanceof DateValue)) { 371 throw new ClassCastException ("Date values are not comparable to " + other.getClass()); 372 } 373 int primaryDiff = getUTCDate().compareTo(((DateValue)other).getUTCDate()); 374 if (primaryDiff==0) { 375 return ((DateValue)other).tzOffset - tzOffset; 377 } else { 378 return primaryDiff; 379 } 380 381 } 382 383 387 388 public int compareTo(CalendarValue other, ConversionContext conversion) { 389 return compareTo(other); 391 } 392 393 public boolean equals(Object other) { 394 return compareTo(other) == 0; 395 } 396 397 public int hashCode() { 398 return getUTCDate().hashCode() + new Integer (tzOffset).hashCode(); 400 } 401 402 409 410 public CalendarValue add(DurationValue duration) throws XPathException { 411 if (duration instanceof SecondsDurationValue) { 412 double seconds = duration.getLengthInSeconds(); 413 int days = (int)Math.floor(seconds / (60*60*24)); 414 GregorianCalendar cal2 = (GregorianCalendar)calendar.clone(); 415 cal2.add(Calendar.DATE, days); 416 return new DateValue(cal2, zoneSpecified, tzOffset); 417 } else if (duration instanceof MonthDurationValue) { 418 int months = ((MonthDurationValue)duration).getLengthInMonths(); 419 GregorianCalendar cal2 = (GregorianCalendar)calendar.clone(); 420 cal2.add(Calendar.MONTH, months); 421 return new DateValue(cal2, zoneSpecified, tzOffset); 422 } else { 423 DynamicError err = new DynamicError( 424 "Date arithmetic is not supported on xs:duration, only on its subtypes"); 425 err.setIsTypeError(true); 426 throw err; 427 } 428 } 429 430 437 438 public SecondsDurationValue subtract(CalendarValue other, ConversionContext context) throws XPathException { 439 if (!(other instanceof DateValue)) { 440 DynamicError err = new DynamicError( 441 "First operand of '-' is a date, but the second is not"); 442 err.setIsTypeError(true); 443 throw err; 444 } 445 return super.subtract(other, context); 446 } 447 448 } 449 450 468 | Popular Tags |