001/*
002 * Copyright (c) 2009-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2014-07-14 22:26:04 +0000 (Mon, 14 Jul 2014) $' 
007 * '$Revision: 32837 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.date;
031
032import java.util.Calendar;
033import java.util.Date;
034import java.util.GregorianCalendar;
035import java.util.HashMap;
036
037import ptolemy.actor.TypedAtomicActor;
038import ptolemy.actor.TypedIOPort;
039import ptolemy.actor.parameters.PortParameter;
040import ptolemy.data.DateToken;
041import ptolemy.data.LongToken;
042import ptolemy.data.StringToken;
043import ptolemy.data.type.BaseType;
044import ptolemy.kernel.CompositeEntity;
045import ptolemy.kernel.util.Attribute;
046import ptolemy.kernel.util.IllegalActionException;
047import ptolemy.kernel.util.NameDuplicationException;
048
049/**
050 * Calculate the (floor of the) difference between two dates in minutes, hours,
051 * days, months, etc.
052 * <p>
053 * <b>NOTE:</b> this actor may take a long time to execute if the granularity is
054 * small, e.g., milliseconds or seconds, and the amount of time is large.
055 * </p>
056 * 
057 * @author Daniel Crawl
058 * @version $Id: DateDifference.java 32837 2014-07-14 22:26:04Z crawl $
059 */
060
061public class DateDifference extends TypedAtomicActor {
062
063        /**
064         * Construct a DateDifference with the given container and name.
065         * 
066         * @param name
067         *            The name of this actor.
068         * @exception IllegalActionException
069         *                If the entity cannot be contained by the proposed
070         *                container.
071         * @exception NameDuplicationException
072         *                If the container already has an actor with this name.
073         */
074        public DateDifference(CompositeEntity container, String name)
075                        throws NameDuplicationException, IllegalActionException {
076                super(container, name);
077
078                date1 = new TypedIOPort(this, "date1", true, false);
079                date1.setTypeEquals(BaseType.DATE);
080                new Attribute(date1, "_showName");
081
082                date2 = new TypedIOPort(this, "date2", true, false);
083                date2.setTypeEquals(BaseType.DATE);
084                new Attribute(date2, "_showName");
085
086                granularity = new PortParameter(this, "granularity");
087                granularity.setStringMode(true);
088                granularity.getPort().setTypeEquals(BaseType.STRING);
089                new Attribute(granularity.getPort(), "_showName");
090                for (String str : _granularityMap.keySet()) {
091                        granularity.addChoice(str);
092                }
093
094                output = new TypedIOPort(this, "output", false, true);
095                output.setTypeEquals(BaseType.LONG);
096                output.setMultiport(true);
097            output.setDefaultWidth(1);
098
099                _attachText("_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" "
100                                + "width=\"60\" height=\"20\" " + "style=\"fill:white\"/>\n"
101                                + "</svg>\n");
102        }
103
104        // /////////////////////////////////////////////////////////////////
105        // // ports and parameters ////
106
107        /** Input date. */
108        public TypedIOPort date1;
109
110        /** Input date. */
111        public TypedIOPort date2;
112
113        /**
114         * Specifies the granularity of difference between dates, e.g., hours, days,
115         * months, etc.
116         */
117        public PortParameter granularity;
118
119        /** The difference between the dates. */
120        public TypedIOPort output;
121
122        // /////////////////////////////////////////////////////////////////
123        // // public methods ////
124
125        /**
126         * Send the token in the value parameter to the output.
127         * 
128         * @exception IllegalActionException
129         *                If it is thrown by the send() method sending out the
130         *                token.
131         */
132        @Override
133    public void fire() throws IllegalActionException {
134                super.fire();
135
136                // read the two input dates
137                Date d1 = new Date(((DateToken) date1.get(0)).getValue());
138                Date d2 = new Date(((DateToken) date2.get(0)).getValue());
139
140                // determine the type of difference
141                granularity.update();
142                String str = ((StringToken) granularity.getToken()).stringValue();
143                Integer granularityType = _granularityMap.get(str);
144                if (str == null) {
145                        throw new IllegalActionException(this, "Unknown time granularity: "
146                                        + str);
147                }
148
149                // calculate and output the difference
150                long diff = _calculateDiff(d1, d2, granularityType);
151                output.broadcast(new LongToken(diff));
152        }
153
154        // /////////////////////////////////////////////////////////////////
155        // // private methods ////
156
157        /** Calculate the difference between two dates for a specific format. */
158        private long _calculateDiff(Date d1, Date d2, Integer granularityType) {
159                long retval = 0;
160                GregorianCalendar cal1 = new GregorianCalendar();
161                GregorianCalendar cal2 = new GregorianCalendar();
162
163                cal1.setTime(d1);
164                cal2.setTime(d2);
165
166                GregorianCalendar lower, upper;
167
168                // determine which date is earlier
169                if (cal1.before(cal2)) {
170                        lower = cal1;
171                        upper = cal2;
172                } else {
173                        lower = cal2;
174                        upper = cal1;
175                }
176
177                if (_debugging) {
178                        _debug("lower date: " + lower.getTime());
179                        _debug("upper date: " + upper.getTime());
180                }
181
182                // increment the lower calendar date until it's after the upper
183                // calendar date.
184                // NOTE: this could take many iterations if the granularity
185                // of time is small, e.g., seconds or milliseconds, and the
186                // difference between the dates is large.
187                int field = granularityType.intValue();
188                while (lower.before(upper)) {
189                        retval++;
190                        lower.add(field, 1);
191                }
192
193                // we want the floor of the difference, so if lower is now after
194                // upper, subtract one unit.
195                if (lower.after(upper)) {
196                        retval--;
197                }
198
199                return retval;
200        }
201
202        // /////////////////////////////////////////////////////////////////
203        // // private variables ////
204
205        /**
206         * An mapping of strings to java.util.Calendar fields for different
207         * granularities of time.
208         */
209        private static HashMap<String, Integer> _granularityMap;
210
211        static {
212                // initialize the granularity map
213                _granularityMap = new HashMap<String, Integer>();
214                _granularityMap.put("Milliseconds", Calendar.MILLISECOND);
215                _granularityMap.put("Seconds", Calendar.SECOND);
216                _granularityMap.put("Minutes", Calendar.MINUTE);
217                _granularityMap.put("Hours", Calendar.HOUR_OF_DAY);
218                _granularityMap.put("Days", Calendar.DAY_OF_YEAR);
219                _granularityMap.put("Months", Calendar.MONTH);
220                _granularityMap.put("Years", Calendar.YEAR);
221        }
222}