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 &lt; b) need not imply (a &ge; 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}