001/* A token that contains a date. 002 003 @Copyright (c) 2008-2016 The Regents of the University of California. 004 All rights reserved. 005 006 Permission is hereby granted, without written agreement and without 007 license or royalty fees, to use, copy, modify, and distribute this 008 software and its documentation for any purpose, provided that the 009 above copyright notice and the following two paragraphs appear in all 010 copies of this software. 011 012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 016 SUCH DAMAGE. 017 018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 023 ENHANCEMENTS, OR MODIFICATIONS. 024 025 PT_COPYRIGHT_VERSION_2 026 COPYRIGHTENDKEY 027 028 029 */ 030 031package ptolemy.data; 032 033import java.text.ParseException; 034import java.text.SimpleDateFormat; 035import java.util.Calendar; 036import java.util.GregorianCalendar; 037import java.util.TimeZone; 038 039import ptolemy.data.type.BaseType; 040import ptolemy.data.type.Type; 041import ptolemy.data.type.TypeLattice; 042import ptolemy.graph.CPO; 043import ptolemy.kernel.util.IllegalActionException; 044 045/** A token that contains a date. 046 * 047 * <p>This class tries to support both sub-millisecond precision 048 * and string parsing of dates. This means that there are two 049 * fields, a value, which is a Java long, and a calendar, which is 050 * a java.util.Calendar. The Calendar class only support milliseconds, 051 * it does not support microseconds and nanoseconds. When 052 * operations are performed, it is essential that 053 * {@link #setTimeInMilliseconds(long)} be called so that 054 * both fields are updated.</p> 055 * 056 * <p>Note: Java 8 provides a much improved implementation of dates and times. 057 * This implementation should be upgraded eventually.</p> 058 * 059 * @author Patricia Derler, Christopher based on DateToken in Kepler by Daniel Crawl and Christopher Brooks 060 * @version $Id$ 061 * @since Ptolemy II 10.0 062 * @Pt.ProposedRating Red (cxh) 063 * @Pt.AcceptedRating Red (cxh) 064 */ 065public class DateToken extends AbstractConvertibleToken 066 implements PartiallyOrderedToken { 067 068 /** Construct a date token. The current time is used for the date, 069 * the default precision is milliseconds and the default time zone 070 * is the local time zone. 071 */ 072 public DateToken() { 073 this(Calendar.getInstance().getTimeInMillis(), PRECISION_MILLISECOND, 074 TimeZone.getDefault()); 075 } 076 077 /** Construct a DateToken that represents the time since January 1, 1970. 078 * The time zone defaults to the local time zone. 079 * @param value The time since January 1, 1970 in the default precision 080 * of milliseconds. 081 */ 082 public DateToken(long value) { 083 this(value, PRECISION_MILLISECOND, TimeZone.getDefault()); 084 } 085 086 /** Construct a DateToken that represents the time since January 1, 1970. 087 * The time zone defaults to the local time zone. 088 * @param value The time since January 1, 1970 in the given precision. 089 * @param precision The precision. 090 */ 091 public DateToken(long value, int precision) { 092 this(value, precision, TimeZone.getDefault()); 093 } 094 095 /** Construct a DateToken that represents the time since January 1, 1970. 096 * @param value The time since January 1, 1970 in the given precision. 097 * @param precision The precision. 098 * @param timeZone The string representation of the time zone ID. 099 */ 100 public DateToken(long value, int precision, String timeZone) { 101 _isNil = false; 102 _precision = precision; 103 _timeZone = TimeZone.getTimeZone(timeZone); 104 _value = value; 105 } 106 107 /** Construct a DateToken that represents the time since January 1, 1970. 108 * @param value The time since January 1, 1970 in the given precision. 109 * @param precision The precision. 110 * @param timeZone The time zone. 111 */ 112 public DateToken(long value, int precision, TimeZone timeZone) { 113 _isNil = false; 114 _precision = precision; 115 _timeZone = timeZone; 116 _value = value; 117 } 118 119 /** Construct a DateToken that represents the time specified as a 120 * string. The string is first parsed by the default 121 * java.text.DateFormat parser. Because we have up to nanosecond 122 * precision, we might have to 123 * pre-process the string and take out the digits representing 124 * nanoseconds and microseconds. Then any leading 125 * and trailing double quotes are removed and a 126 * java.text.SimpleDateFormat with a parser with the value of 127 * {@link #_SIMPLE_DATE_FORMAT} is used. 128 * 129 * @param value The date specified in a format acceptable 130 * to java.text.DateFormat. 131 * @exception IllegalActionException If the date is not 132 * parseable by java.text.DateFormat. 133 */ 134 public DateToken(String value) throws IllegalActionException { 135 _precision = PRECISION_MILLISECOND; 136 if (value == null) { 137 _isNil = false; 138 _value = 0l; 139 return; 140 } 141 142 if (value.equals(_NIL)) { 143 _isNil = true; 144 _value = 0l; 145 return; 146 } 147 String dateString = value; 148 149 // Simple date format is not thread safe - intermediate parsing results are 150 // stored in instance fields. 151 synchronized (_SIMPLE_DATE_FORMAT) { 152 try { 153 // See https://stackoverflow.com/questions/4713825/how-to-parse-output-of-new-date-tostring 154 // FIXME: this is probably Locale.US-specific 155 156 // Remove leading and trailing double quotes. 157 if (value.startsWith("\"") && value.endsWith("\"")) { 158 value = value.substring(1, value.length() - 1); 159 } 160 Calendar calendar = Calendar.getInstance(); 161 // Parse dates in varying precision 162 if (value.length() == _SIMPLE_DATE_FORMAT.length()) { 163 calendar.setTime(_simpleDateFormat.parse(value)); 164 _value = calendar.getTimeInMillis(); 165 _precision = PRECISION_MILLISECOND; 166 } else if (value.length() == _SIMPLE_DATE_FORMAT.length() + 3) { 167 String micros = value.substring(value.indexOf(".") + 4, 168 value.indexOf(".") + 7); 169 value = value.substring(0, value.indexOf(".") + 4) 170 + value.substring(value.indexOf(".") + 7); 171 calendar.setTime(_simpleDateFormat.parse(value)); 172 _value = calendar.getTimeInMillis(); 173 _value = _value * 1000 + Integer.parseInt(micros); 174 _precision = PRECISION_MICROSECOND; 175 } else if (value.length() == _SIMPLE_DATE_FORMAT.length() + 6) { 176 String micros = value.substring(value.indexOf(".") + 4, 177 value.indexOf(".") + 7); 178 String nanos = value.substring(value.indexOf(".") + 7, 179 value.indexOf(".") + 10); 180 value = value.substring(0, value.indexOf(".") + 4) 181 + value.substring(value.indexOf(".") + 10); 182 calendar.setTime(_simpleDateFormat.parse(value)); 183 _value = calendar.getTimeInMillis(); 184 _value = (_value * 1000 + Integer.parseInt(micros)) * 1000 185 + Integer.parseInt(nanos); 186 _precision = PRECISION_NANOSECOND; 187 } else { 188 throw new IllegalActionException(null, 189 "Unexpected date" + "format: " + dateString 190 + " is not formatted as " 191 + _SIMPLE_DATE_FORMAT); 192 } 193 194 String timeZoneOffset = value.substring(24, 29); 195 _timeZone = TimeZone.getTimeZone("GMT" + timeZoneOffset); 196 calendar.setTimeZone(_timeZone); 197 _calendar = calendar; 198 } catch (ParseException ex) { 199 throw new IllegalActionException(null, ex, "The date value \"" 200 + value + "\" could not be parsed to a Date." 201 + "Also tried parsing with the \"" 202 + _simpleDateFormat.toPattern() 203 + "\" pattern, the exception was: " + ex.getMessage()); 204 } 205 } 206 _isNil = false; 207 208 } 209 210 /////////////////////////////////////////////////////////////////// 211 //// public methods //// 212 213 /** Add nanoseconds to time. If the precision is less than nanoseconds, 214 * do nothing. 215 * @param nanoseconds The nanoseconds to add. 216 */ 217 public void addNanoseconds(int nanoseconds) { 218 // FIXME: This method should probably return a new token like DoubleToken._add(). 219 if (_precision >= PRECISION_NANOSECOND) { 220 _value += nanoseconds; 221 if (nanoseconds >= 1000000) { 222 _calendar.setTimeInMillis(getTimeInMilliseconds()); 223 } 224 } 225 } 226 227 /** Add microseconds to time. If the precision is less than microseconds, 228 * do nothing. 229 * @param microseconds The microseconds to add. 230 */ 231 public void addMicroseconds(int microseconds) { 232 // FIXME: This method should probably return a new token like DoubleToken._add(). 233 if (_precision == PRECISION_MICROSECOND) { 234 _value += microseconds; 235 } else if (_precision == PRECISION_NANOSECOND) { 236 _value += microseconds * 1000; 237 } 238 if (_precision >= PRECISION_MICROSECOND && microseconds >= 1000) { 239 _calendar.setTimeInMillis(getTimeInMilliseconds()); 240 } 241 } 242 243 /** Convert the specified token into an instance of DateToken. 244 * This method does lossless conversion. 245 * If the argument is already an instance of DateToken, 246 * it is returned without any change. If the argument is 247 * a nil token, then a new nil Token is returned, see {@link 248 * #NIL}. Otherwise, if the argument is below DateToken in the 249 * type hierarchy, it is converted to an instance of DateToken or 250 * one of the subclasses of DateToken and returned. If none of 251 * the above condition is met, an exception is thrown. 252 * 253 * @param token The token to be converted to a DateToken. 254 * @return A DateToken. 255 * @exception IllegalActionException If the conversion 256 * cannot be carried out. 257 */ 258 public static DateToken convert(Token token) throws IllegalActionException { 259 if (token instanceof DateToken) { 260 return (DateToken) token; 261 } 262 263 if (token.isNil()) { 264 return DateToken.NIL; 265 } 266 267 int compare = TypeLattice.compare(BaseType.DATE, token); 268 269 if (compare == CPO.LOWER || compare == CPO.INCOMPARABLE) { 270 // We could try to create a DateToken from a String here, 271 // but not all Strings are convertible to Dates. Marten wrote: 272 // "This seems wrong to me. It is not generally possible to 273 // convert a String into a Date. Also, the type lattice 274 // doesn't permit that conversion. Type inference is 275 // supposed to yield a typing of which the automatic type 276 // conversions that it imposes during run time work 277 // without exception. We should not misuse the conversion 278 // method to build a customized parser." 279 throw new IllegalActionException( 280 notSupportedIncomparableConversionMessage(token, "date")); 281 } 282 283 compare = TypeLattice.compare(BaseType.STRING, token); 284 285 if (compare == CPO.SAME || compare == CPO.HIGHER) { 286 StringToken stringToken = StringToken.convert(token); 287 DateToken result = new DateToken(stringToken.stringValue()); 288 return result; 289 } 290 291 throw new IllegalActionException( 292 notSupportedConversionMessage(token, "date")); 293 } 294 295 /** Get the calendar instance representing this date. 296 * @return The calendar instance. 297 */ 298 public Calendar getCalendarInstance() { 299 if (_calendar == null) { 300 _calendar = new GregorianCalendar(_timeZone); 301 _calendar.setTimeInMillis(getTimeInMilliseconds()); 302 _calendar.setTimeZone(_timeZone); 303 } 304 return _calendar; 305 } 306 307 /** Create a DateToken with a value. 308 * @param value The date specified in a format acceptable 309 * to java.text.DateFormat. 310 * @return a DateToken. 311 * @exception IllegalActionException If thrown while creating 312 * the DateToken. 313 */ 314 public static DateToken date(String value) throws IllegalActionException { 315 return new DateToken(value); 316 } 317 318 /** Get the date of the month part of this date. 319 * @return The date of the month. 320 */ 321 public int getDay() { 322 Calendar calendar = getCalendarInstance(); 323 return calendar.get(Calendar.DAY_OF_MONTH); 324 } 325 326 /** Get the day of week. 327 * @return The day of week. 328 */ 329 public int getDayOfWeek() { 330 Calendar calendar = getCalendarInstance(); 331 return calendar.get(Calendar.DAY_OF_WEEK); 332 } 333 334 /** Get the hour part of this date. 335 * @return The hour. 336 */ 337 public int getHour() { 338 Calendar calendar = getCalendarInstance(); 339 return calendar.get(Calendar.HOUR_OF_DAY); 340 } 341 342 /** Get the minute part of this date. 343 * @return The minute. 344 */ 345 public int getMinute() { 346 Calendar calendar = getCalendarInstance(); 347 return calendar.get(Calendar.MINUTE); 348 } 349 350 /** Get the microsecond part of this date. 351 * @return The microsecond. 352 */ 353 public int getMicrosecond() { 354 if (_precision < PRECISION_MICROSECOND) { 355 return 0; 356 } else if (_precision == PRECISION_MICROSECOND) { 357 return (int) (_value % 1000); 358 } else if (_precision == PRECISION_NANOSECOND) { 359 return (int) ((_value % 1000000) / 1000); 360 } 361 return 0; 362 } 363 364 /** Get the millisecond part of this date. 365 * @return The millisecond. 366 */ 367 public int getMillisecond() { 368 Calendar calendar = getCalendarInstance(); 369 return calendar.get(Calendar.MILLISECOND); 370 } 371 372 /** Get the month part of this date. 373 * @return The month. 374 */ 375 public int getMonth() { 376 Calendar calendar = getCalendarInstance(); 377 return calendar.get(Calendar.MONTH); 378 } 379 380 /** Get the nanosecond part of this date. 381 * @return The nanosecond. 382 */ 383 public int getNanosecond() { 384 if (_precision < PRECISION_NANOSECOND) { 385 return 0; 386 } else if (_precision == PRECISION_NANOSECOND) { 387 return (int) (_value % 1000); 388 } 389 return 0; 390 } 391 392 /** Get the precision of this date. 393 * @return The precision. 394 */ 395 public int getPrecision() { 396 return _precision; 397 } 398 399 /** Get the second part of this date. 400 * @return The second. 401 */ 402 public int getSecond() { 403 Calendar calendar = getCalendarInstance(); 404 return calendar.get(Calendar.SECOND); 405 } 406 407 /** Get the time zone of this date. 408 * @return The time zone. 409 */ 410 public TimeZone getTimeZone() { 411 return _timeZone; 412 } 413 414 /** Get the time zone id of this date. 415 * @return The time zone. 416 */ 417 public String getTimezoneID() { 418 Calendar c = getCalendarInstance(); 419 return c.getTimeZone().getDisplayName(); 420 } 421 422 /** Get the time since January 1, 1970 in the given precision. 423 * @return The time since Januarly 1, 1970. 424 */ 425 public long getValue() { 426 return _value; 427 } 428 429 /** Get time in milliseconds since January 1, 1970. 430 * @return The time as a long value. 431 * @see #setTimeInMilliseconds(long) 432 */ 433 public long getTimeInMilliseconds() { 434 if (_precision == PRECISION_NANOSECOND) { 435 return _value / 1000000; 436 } else if (_precision == PRECISION_MICROSECOND) { 437 return _value / 1000; 438 } else if (_precision == PRECISION_MILLISECOND) { 439 return _value; 440 } else if (_precision == PRECISION_SECOND) { 441 return _value * 1000; 442 } 443 return 0l; 444 } 445 446 /** Return the type of this token. 447 * @return {@link ptolemy.data.type.BaseType#DATE}, 448 * the least upper bound of all the date types. 449 */ 450 @Override 451 public Type getType() { 452 return BaseType.DATE; 453 } 454 455 /** Get the year of this date. 456 * @return The year. 457 */ 458 public int getYear() { 459 Calendar calendar = getCalendarInstance(); 460 return calendar.get(Calendar.YEAR); 461 } 462 463 /** Check whether the value of this token is strictly greater than 464 * that of the argument token. The argument and this token are 465 * converted to equivalent types, and then compared. Generally, 466 * this is the higher of the type of this token and the argument 467 * type. This method defers to the _isLessThan() method to perform 468 * a type-specific equality check. Derived classes should 469 * implement that method to provide type specific actions for 470 * equality testing. 471 * 472 * @param rightArgument The token to compare against. 473 * @return A boolean token with value true if this token has the 474 * same units as the argument, and is strictly greater than the 475 * argument. 476 * @exception IllegalActionException If the argument token and 477 * this token are of incomparable types, or have different units, 478 * or the operation does not make sense for the given types. 479 */ 480 public final BooleanToken isGreaterThan(PartiallyOrderedToken rightArgument) 481 throws IllegalActionException { 482 // Similar to the same method in ScalarToken. 483 int typeInfo = TypeLattice.compare(getType(), (Token) rightArgument); 484 485 if (typeInfo == CPO.SAME) { 486 return ((DateToken) rightArgument)._doIsLessThan(this); 487 } else if (typeInfo == CPO.HIGHER) { 488 // This line is different from ScalarToken and causes problems with StringTokens. 489 PartiallyOrderedToken convertedArgument = (PartiallyOrderedToken) getType() 490 .convert((Token) rightArgument); 491 try { 492 return convertedArgument.isLessThan(this); 493 } catch (IllegalActionException ex) { 494 // If the type-specific operation fails, then create a 495 // better error message that has the types of the 496 // arguments that were passed in. 497 throw new IllegalActionException(null, ex, notSupportedMessage( 498 "isGreaterThan", (Token) this, (Token) rightArgument)); 499 } 500 } else if (typeInfo == CPO.LOWER) { 501 return rightArgument.isLessThan(this); 502 } else { 503 throw new IllegalActionException(notSupportedIncomparableMessage( 504 "isGreaterThan", (Token) this, (Token) rightArgument)); 505 } 506 } 507 508 /** Check whether the value of this token is strictly less than that of the 509 * argument token. 510 * 511 * Only a partial order is assumed, so !(a < b) need not imply (a ≥ b). 512 * 513 * @param rightArgument The token on greater than side of the inequality. 514 * @return BooleanToken.TRUE, if this token is less than the 515 * argument token. BooleanToken.FALSE, otherwise. 516 * @exception IllegalActionException If the tokens are incomparable. 517 */ 518 @Override 519 public BooleanToken isLessThan(PartiallyOrderedToken rightArgument) 520 throws IllegalActionException { 521 DateToken rightDateToken = null; 522 try { 523 rightDateToken = convert((Token) rightArgument); 524 } catch (IllegalActionException ex) { 525 //// FIXME: Since PartiallyOrderedToken is an interface, we cannot do: 526 //throw new IllegalActionException(null, ex, notSupportedMessage( 527 // "isLessThan", this, rightArgument)) 528 //// and must do this instead: 529 throw new IllegalActionException( 530 "Cannot compare ScalarToken with " + rightArgument); 531 } 532 return isLessThan(rightDateToken); 533 } 534 535 /** Check whether the value of this token is strictly less than that of the 536 * argument token. 537 * 538 * @param rightArgument The token to compare against. 539 * @return A boolean token with value true if this token is strictly 540 * less than the argument. 541 * @exception IllegalActionException If the argument token and 542 * this token are of incomparable types, or have different units, 543 * or the operation does not make sense for the given types. 544 */ 545 public BooleanToken isLessThan(DateToken rightArgument) 546 throws IllegalActionException { 547 // FIXME: Copied from ScalarToken, but one line is different 548 int typeInfo = TypeLattice.compare(getType(), rightArgument); 549 550 if (typeInfo == CPO.SAME) { 551 return _doIsLessThan(rightArgument); 552 } else if (typeInfo == CPO.HIGHER) { 553 DateToken convertedArgument = (DateToken) getType() 554 .convert(rightArgument); 555 try { 556 return _doIsLessThan(convertedArgument); 557 } catch (IllegalActionException ex) { 558 // If the type-specific operation fails, then create a 559 // better error message that has the types of the 560 // arguments that were passed in. 561 throw new IllegalActionException(null, ex, 562 notSupportedMessage("isLessThan", this, rightArgument)); 563 } 564 } else if (typeInfo == CPO.LOWER) { 565 return rightArgument.isGreaterThan(this); 566 } else { 567 throw new IllegalActionException(notSupportedIncomparableMessage( 568 "isLessThan", this, rightArgument)); 569 } 570 } 571 572 /** Return true if the token is nil, (aka null or missing). 573 * Nil or missing tokens occur when a data source is sparsely populated. 574 * To create a nil DateToken, call new DateToken("nil"); 575 * @return True if the token is the {@link #NIL} token. 576 */ 577 @Override 578 public boolean isNil() { 579 return _isNil; 580 } 581 582 /** Set the time in milliseconds since January 1, 1970. 583 * @param newValue The time as a long value. 584 * @see #getTimeInMilliseconds() 585 */ 586 public void setTimeInMilliseconds(long newValue) { 587 // FIXME: This is a poor design because we are exposing 588 // _value with a setter. 589 if (_precision == PRECISION_NANOSECOND) { 590 _value = newValue * 1000000; 591 } else if (_precision == PRECISION_MICROSECOND) { 592 _value = newValue * 1000; 593 } else if (_precision == PRECISION_MILLISECOND) { 594 _value = newValue; 595 } else if (_precision == PRECISION_SECOND) { 596 _value = newValue / 1000; 597 } 598 _calendar.setTimeInMillis(newValue); 599 } 600 601 /** Return the value of the token as a String. 602 * @return The string value, which is the same as 603 * the value returned by {@link #toString()}, except 604 * toString() wraps the string value in double quotes. 605 */ 606 public String stringValue() { 607 if (isNil()) { 608 return _NIL; 609 } 610 Calendar c = getCalendarInstance(); 611 _simpleDateFormat.setTimeZone(_timeZone); 612 String timeString = _simpleDateFormat.format(c.getTime()); 613 614 String beforeTimeZone = timeString.substring(0, 615 timeString.lastIndexOf(" ")); 616 beforeTimeZone = beforeTimeZone.substring(0, 617 beforeTimeZone.lastIndexOf(" ")); 618 619 String remainder = timeString.substring(beforeTimeZone.length()); 620 621 return beforeTimeZone + String.format("%03d", getMicrosecond()) 622 + String.format("%03d", getNanosecond()) + remainder; 623 } 624 625 /** 626 * Return a String representation of the DateToken. The string is surrounded 627 * by double-quotes; without them, the Ptolemy expression parser fails to 628 * parse it. 629 * 630 * <p>Unfortunately, the Java Date class has a fatal flaw in that 631 * Date.toString() does not return the value of the number of ms., so 632 * we use a format that includes the number of ms.</p> 633 * 634 * @return A String representation of the DateToken. 635 */ 636 @Override 637 public String toString() { 638 return "date(\"" + stringValue() + "\")"; 639 } 640 641 /** A token that represents a missing value. 642 * Null or missing tokens are common in analytical systems 643 * like R and SAS where they are used to handle sparsely populated data 644 * sources. In database parlance, missing tokens are sometimes called 645 * null tokens. Since null is a Java keyword, we use the term "nil". 646 * The toString() method on a nil token returns the string "nil". 647 */ 648 public static final DateToken NIL; 649 650 // FIXME: the precision should be an enum. 651 652 /** The flag indicating that the the precision is seconds. */ 653 public static final int PRECISION_SECOND = 1; 654 655 /** The flag indicating that the the precision is milliseconds. */ 656 public static final int PRECISION_MILLISECOND = 2; 657 658 /** The flag indicating that the the precision is microseconds. */ 659 public static final int PRECISION_MICROSECOND = 3; 660 661 /** The flag indicating that the the precision is nanoseconds. */ 662 public static final int PRECISION_NANOSECOND = 4; 663 664 /////////////////////////////////////////////////////////////////// 665 //// protected methods //// 666 667 /** Subtract is not supported for Dates. 668 * @param rightArgument The token to subtract from this token. 669 * @return A new token containing the result. 670 * @exception IllegalActionException Always thrown because 671 * multiplying a Date does not make sense. 672 */ 673 @Override 674 protected Token _add(Token rightArgument) throws IllegalActionException { 675 throw new IllegalActionException(null, 676 notSupportedMessage("add", this, rightArgument)); 677 } 678 679 /** Subtract is not supported for Dates. 680 * @param rightArgument The token to subtract from this token. 681 * @return A new token containing the result. 682 * @exception IllegalActionException Always thrown because 683 * dividing a Date does not make sense. 684 */ 685 @Override 686 protected Token _divide(Token rightArgument) throws IllegalActionException { 687 throw new IllegalActionException(null, 688 notSupportedMessage("divide", this, rightArgument)); 689 } 690 691 /** The isCloseTo() method brings both tokens to the same precision. 692 * Then compute difference between time value in given lower precision. 693 * If difference is less than epsilon (casted to an int), return true. 694 * @param token The token to compare to this token 695 * @param epsilon the epsilon 696 * @return A new token containing the result. 697 * @exception IllegalActionException Always thrown because 698 * isCloseTo() on a Date does not make sense. 699 */ 700 @Override 701 protected BooleanToken _isCloseTo(Token token, double epsilon) 702 throws IllegalActionException { 703 // Christopher Brooks: If we convert the two tokens to longs and the 704 // epsilon to a long, then this might make sense? 705 // However, double is not losslessly convertible to long? 706 // Probably throw an IllegalActionException here. 707 708 // Patricia Derler - first version of an implementation of isCloseTo below. 709 // First get both tokens to the same precision. Then compare the difference. 710 // If difference is less than epsilon (casted to an int), return true. 711 712 DateToken dateToken = null; 713 if (token instanceof StringToken) { 714 dateToken = new DateToken(((StringToken) token).stringValue()); 715 } else if (token instanceof DateToken) { 716 dateToken = (DateToken) token; 717 } else { 718 throw new IllegalActionException(null, 719 "Cannot compute _isCloseTo for DateToken and " 720 + token.getType()); 721 } 722 long dateValue = dateToken._value; 723 if (dateToken.getPrecision() > getPrecision()) { 724 int precisionDifference = dateToken.getPrecision() - getPrecision(); 725 dateValue = dateValue / (long) Math.pow(1000, precisionDifference); 726 } 727 if (Math.abs(dateValue - _value) < epsilon) { 728 return BooleanToken.TRUE; 729 } else { 730 return BooleanToken.FALSE; 731 } 732 } 733 734 /** Return true of the the value of this token is equal 735 * to the value of the argument according to java.util.Date. 736 * Two DateTokens are considered equal if the their values 737 * are non-null and the java.util.Date.equals() method returns 738 * true. 739 * It is assumed that the type of the argument is the 740 * same as the type of this class. 741 * @param rightArgument The token with which to test equality. 742 * @return true if the right argument is equal to this token. 743 * @exception IllegalActionException Not thrown in this baseclass 744 */ 745 @Override 746 protected BooleanToken _isEqualTo(Token rightArgument) 747 throws IllegalActionException { 748 749 // The caller of this method should convert 750 // the rightArgument to a DateToken, but we check anyway. 751 if (!(rightArgument instanceof DateToken)) { 752 return BooleanToken.FALSE; 753 } 754 DateToken rightArgumentDateToken = (DateToken) rightArgument; 755 756 if (isNil() || rightArgument.isNil()) { 757 return BooleanToken.FALSE; 758 } 759 760 Calendar left = getCalendarInstance(); 761 Calendar right = rightArgumentDateToken.getCalendarInstance(); 762 763 return BooleanToken.getInstance(left.compareTo(right) == 0 764 && _getMicroAndNanoSeconds() == rightArgumentDateToken 765 ._getMicroAndNanoSeconds()); 766 } 767 768 /** Test for ordering of the values of this Token and the argument 769 * Token. 770 * @param rightArgument The token to compare to this token. 771 * @exception IllegalActionException If this method is not 772 * supported by the derived class. 773 * @return A new Token containing the result. 774 */ 775 protected BooleanToken _isLessThan(DateToken rightArgument) 776 throws IllegalActionException { 777 778 if (isNil() || rightArgument.isNil()) { 779 return BooleanToken.FALSE; 780 } 781 782 Calendar left = getCalendarInstance(); 783 Calendar right = rightArgument.getCalendarInstance(); 784 785 return BooleanToken.getInstance( 786 left.compareTo(right) < 0 || (left.compareTo(right) == 0 787 && _getMicroAndNanoSeconds() < rightArgument 788 ._getMicroAndNanoSeconds())); 789 } 790 791 /** Modulo is not supported for Dates. 792 * @param rightArgument The token to divide into this token. 793 * @return A new token containing the result. 794 * @exception IllegalActionException Always thrown because 795 * modulo of a Date does not make sense. 796 */ 797 @Override 798 protected Token _modulo(Token rightArgument) throws IllegalActionException { 799 throw new IllegalActionException(null, 800 notSupportedMessage("modulo", this, rightArgument)); 801 } 802 803 /** Multiply is not supported for Dates. 804 * @param rightArgument The token to multiply this token by. 805 * @return A new token containing the result. 806 * @exception IllegalActionException Always thrown because 807 * multiplying a Date does not make sense. 808 */ 809 @Override 810 protected Token _multiply(Token rightArgument) 811 throws IllegalActionException { 812 throw new IllegalActionException(null, 813 notSupportedMessage("multiply", this, rightArgument)); 814 } 815 816 /** Subtract is not supported for Dates. 817 * @param rightArgument The token to subtract from this token. 818 * @return A new token containing the result. 819 * @exception IllegalActionException Always thrown because 820 * subtracting a Date does not make sense. 821 */ 822 @Override 823 protected Token _subtract(Token rightArgument) 824 throws IllegalActionException { 825 throw new IllegalActionException(null, 826 notSupportedMessage("subtract", this, rightArgument)); 827 } 828 829 /////////////////////////////////////////////////////////////////// 830 //// protected variables //// 831 832 // This is protected so that the DateToken(String) javadoc can refer 833 // to it. 834 /** The format in which dates are reported. Milliseconds are included 835 * so that the toString() method returns a string that can be parsed 836 * to the same Date. 837 */ 838 protected static final String _SIMPLE_DATE_FORMAT = "EEE MMM dd HH:mm:ss.SSS ZZZZZ yyyy"; 839 840 /////////////////////////////////////////////////////////////////// 841 //// private variables //// 842 843 /** Test for ordering of the values of this Token and the argument 844 * Token. It is guaranteed by the caller that the type and 845 * units of the argument is the same as the type of this class. 846 * This method may defer to the _isLessThan() method that takes a 847 * ScalarToken. Derived classes should implement that method 848 * instead to provide type-specific operation. 849 * @param rightArgument The token with which to test ordering. 850 * @return A BooleanToken which contains the result of the test. 851 * @exception IllegalActionException If the units of the argument 852 * are not the same as the units of this token, or the method is 853 * not supported by the derived class or if either this token or 854 * the argument token is a nil token. 855 */ 856 private BooleanToken _doIsLessThan(PartiallyOrderedToken rightArgument) 857 throws IllegalActionException { 858 if (isNil() || ((Token) rightArgument).isNil()) { 859 throw new IllegalActionException(notSupportedMessage("isLessThan", 860 this, (Token) rightArgument) 861 + " because one or the other is nil"); 862 } 863 864 DateToken convertedArgument = (DateToken) rightArgument; 865 866 return _isLessThan(convertedArgument); 867 } 868 869 private long _getMicroAndNanoSeconds() { 870 if (_precision == PRECISION_NANOSECOND) { 871 return _value % 1000000; 872 } else if (_precision == PRECISION_MICROSECOND) { 873 return (_value % 1000) * 1000; 874 } 875 return 0l; 876 } 877 878 /** True if the value of this Date is missing. */ 879 private boolean _isNil = false; 880 881 /** The String value of a nil token. */ 882 private static final String _NIL = "nil"; 883 884 /** The format used to read and write dates. */ 885 private SimpleDateFormat _simpleDateFormat = new SimpleDateFormat( 886 _SIMPLE_DATE_FORMAT); 887 888 /** The time in a given precision */ 889 private long _value; 890 891 private int _precision; 892 893 private Calendar _calendar; 894 895 /** The time zone. 896 */ 897 private TimeZone _timeZone; 898 899 static { 900 try { 901 NIL = new DateToken(_NIL); 902 } catch (IllegalActionException ex) { 903 throw new ExceptionInInitializerError(ex); 904 } 905 } 906}