001/*
002 * Copyright (c) 2009-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2015-07-15 00:38:07 +0000 (Wed, 15 Jul 2015) $' 
007 * '$Revision: 33547 $'
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.text.ParseException;
033import java.text.SimpleDateFormat;
034import java.util.Arrays;
035import java.util.Date;
036import java.util.TimeZone;
037
038import ptolemy.actor.lib.LimitedFiringSource;
039import ptolemy.actor.parameters.PortParameter;
040import ptolemy.data.DateToken;
041import ptolemy.data.StringToken;
042import ptolemy.data.expr.StringParameter;
043import ptolemy.data.type.BaseType;
044import ptolemy.kernel.CompositeEntity;
045import ptolemy.kernel.util.Attribute;
046import ptolemy.kernel.util.IllegalActionException;
047import ptolemy.kernel.util.NameDuplicationException;
048import ptolemy.kernel.util.Workspace;
049
050/**
051 * Create a date token. If a date string is not given on <i>input</i>, the
052 * current date and time is used.
053 * 
054 * @author Daniel Crawl
055 * @version $Id: CreateDate.java 33547 2015-07-15 00:38:07Z crawl $
056 */
057
058public class CreateDate extends LimitedFiringSource {
059
060        /**
061         * Construct a CreateDate with the given container and name.
062         * 
063         * @param name
064         *            The name of this actor.
065         * @exception IllegalActionException
066         *                If the entity cannot be contained by the proposed
067         *                container.
068         * @exception NameDuplicationException
069         *                If the container already has an actor with this name.
070         */
071        public CreateDate(CompositeEntity container, String name)
072                        throws NameDuplicationException, IllegalActionException {
073                super(container, name);
074
075                inputFormat = new PortParameter(this, "format");
076                inputFormat.setStringMode(true);
077                inputFormat.getPort().setTypeEquals(BaseType.STRING);
078                new Attribute(inputFormat.getPort(), "_showName");
079
080                for (int i = 0; i < dateFormats.length; i++) {
081                        inputFormat.addChoice(dateFormats[i]);
082                }
083
084                input = new PortParameter(this, "input");
085                input.setStringMode(true);
086                input.getPort().setTypeEquals(BaseType.STRING);
087                new Attribute(input.getPort(), "_showName");
088
089                output.setTypeEquals(BaseType.DATE);
090                output.setMultiport(true);
091                output.setDefaultWidth(1);
092
093                defaultTimezone = new StringParameter(this, "defaultTimezone");
094                String[] timezones = TimeZone.getAvailableIDs();
095                Arrays.sort(timezones);
096                for(String timeZoneID : timezones) {
097                    defaultTimezone.addChoice(timeZoneID);
098                }
099                // set the default
100                defaultTimezone.setExpression(TimeZone.getDefault().getID());
101                
102                _attachText("_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" "
103                                + "width=\"60\" height=\"20\" " + "style=\"fill:white\"/>\n"
104                                + "</svg>\n");
105        }
106
107        // /////////////////////////////////////////////////////////////////
108        // // ports and parameters ////
109
110        /**
111         * A string date and time. If not used, the current date and time is used.
112         */
113        public PortParameter input;
114
115        /**
116         * The format of the input date and time. See java.text.SimpleDateFormat for
117         * the syntax.
118         */
119        public PortParameter inputFormat;
120        
121        /** The time zone to use when <i>inputFormat</i> is specified, but no time
122         *  zone is found in the format (specified with 'z' or 'Z'). This value is
123         *  not used when <i>inputFormat</i> is the milliseconds or seconds since
124         *  the epoch.
125         */
126        public StringParameter defaultTimezone;
127
128        // /////////////////////////////////////////////////////////////////
129        // // public methods ////
130
131        /** React to an attribute change. */
132        @Override
133        public void attributeChanged(Attribute attribute) throws IllegalActionException {
134        
135            if(attribute == defaultTimezone) {
136                String str = defaultTimezone.stringValue();
137                if(str.trim().isEmpty()) {
138                    _defaultTimeZone = TimeZone.getDefault();
139                } else {
140                    // make sure it's valid
141                    boolean found = false;
142                    final String[] timezones = TimeZone.getAvailableIDs();
143                    for(String timezone : timezones) {
144                        if(timezone.equals(str)) {
145                            found = true;
146                            break;
147                        }
148                    }
149                    if(!found) {
150                        throw new IllegalActionException(this, "Unknown time zone: " + str);
151                    }
152                    _defaultTimeZone = TimeZone.getTimeZone(str);
153                }
154            } else {
155                super.attributeChanged(attribute);
156            }
157            
158        }
159        
160        /** Clone the actor into the specified workspace. */
161        @Override
162        public Object clone(Workspace workspace) throws CloneNotSupportedException {
163            CreateDate newObject = (CreateDate) super.clone(workspace);
164            newObject._defaultTimeZone = TimeZone.getDefault();
165            return newObject;
166        }
167        
168        /** Create a date token. */
169        @Override
170        public void fire() throws IllegalActionException {
171                super.fire();
172
173                inputFormat.update();
174                input.update();
175
176                Date outDate;
177
178                // see if there's input
179                String dateStr = ((StringToken) input.getToken()).stringValue();
180                if (dateStr.equals("")) {
181                        // output the current date/time.
182                        outDate = new Date();
183                } else {
184                        // see if there's a format
185                        String formatStr = ((StringToken) inputFormat.getToken())
186                                        .stringValue();
187                        if (formatStr.equals("")) {
188                            // no format specified, so use default
189                            SimpleDateFormat sdf = new SimpleDateFormat();
190                            sdf.setTimeZone(_defaultTimeZone);
191                                try {
192                    outDate = sdf.parse(dateStr);
193                } catch (ParseException e) {
194                    throw new IllegalActionException(this, e, "Error parsing date string.");
195                }
196                        } else if (formatStr.equals(MS_SINCE_EPOCH)) {
197                                // first parse as a double since the parser
198                                // for longs cannot handle scientific notation.
199                                long millisec = new Double(dateStr).longValue();
200                                outDate = new Date(millisec);
201                        } else if (formatStr.equals(S_SINCE_EPOCH)) {
202                            long sec = new Double(dateStr).longValue();
203                            outDate = new Date(sec * 1000);
204                        } else {
205                                try {
206                                        SimpleDateFormat sdf = new SimpleDateFormat(formatStr);
207                                        
208                                        // see if the format contains the time zone
209                                        if(!formatStr.contains("z") && !formatStr.contains("Z")) {
210                                            sdf.setTimeZone(_defaultTimeZone);
211                                        }
212
213                                        outDate = sdf.parse(dateStr);
214
215                                } catch (IllegalArgumentException e) {
216                                        throw new IllegalActionException(this, e,
217                                                        "Error in input format.");
218                                } catch (ParseException e) {
219                                        throw new IllegalActionException(this, e,
220                                                        "Error parsing input date.");
221                                }
222                        }
223                }
224                
225                output.broadcast(new DateToken(outDate.getTime()));
226        }
227
228        /** Constant string for special case of milliseconds since epoch.
229         *  There does not appear to be formatting symbols for this.
230         */
231        public final static String MS_SINCE_EPOCH = "milliseconds since epoch";
232        
233        /** Constant string for seconds since epoch. */
234        public final static String S_SINCE_EPOCH = "seconds since epoch";
235
236        /** Common date formats.
237         *  @see java.text.SimpleDateFormat
238         */
239        final static String[] dateFormats = { "MM-dd-yyyy", "MM-dd-yy",
240                        "yyyyMMdd", "yyyy.MM.dd G 'at' HH:mm:ss z", "EEE, MMM d, ''yy",
241                        "h:mm a", "hh 'o''clock' a, zzzz", "K:mm a, z",
242                        "yyyyy.MMMMM.dd GGG hh:mm aaa", "EEE, d MMM yyyy HH:mm:ss Z",
243                        "yyMMddHHmmssZ", "yyyy-MM-dd'T'HH:mm:ss.SSSZ", MS_SINCE_EPOCH,
244                        S_SINCE_EPOCH };
245        
246        /** The time zone to use when a format is specified without a timezone. TODO */
247        private TimeZone _defaultTimeZone = TimeZone.getDefault();
248}