View Javadoc

1   /********************************************************************************
2    * $Id: TimeParser.java 111 2008-03-11 18:06:37Z sjardine $
3    * 
4    * Copyright 2008 Stefan Unterhofer <stefan.unterhofer@enerbility.com>
5    * Copyright 2008 Steven Jardine, MJN Services, Inc. <steve@mjnservices.com>
6    * 
7    * All rights reserved. This program and the accompanying materials are made
8    * available under the terms of the GNU Lesser Public License v2.1 which 
9    * accompanies this distribution, and is available at
10   * 	http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
11   *
12   * For more information on the HylaFAX Fax Server please see
13   * 	HylaFAX  - http://www.hylafax.org or 
14   * 	Hylafax+ - http://hylafax.sourceforge.net
15   * 
16   * Contributors:
17   * 	Stefan Unterhofer - Initial API and implementation
18   * 	Steven Jardine - Misc fixes, Code formatting, rework of license header,
19   * 			javadoc 
20   ******************************************************************************/
21  package gnu.hylafax.job;
22  
23  import java.text.SimpleDateFormat;
24  import java.util.Calendar;
25  import java.util.Date;
26  import java.util.HashMap;
27  import java.util.Locale;
28  import java.util.Map;
29  import java.util.NoSuchElementException;
30  import java.util.StringTokenizer;
31  import java.util.TimeZone;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  
36  /***
37   * This class parses hylafax time Strings as used in hfaxd.conf for example to
38   * Strings in the format awaited by server using the hylafax client protocol.
39   * 
40   * The time is specified in the formats:
41   * 
42   * <b>HH:MM [AM|PM] [ month DD | dayofweek ]</b> or <b>now + N period</b>
43   * 
44   * Valid values for <b>month</b> are Jan(uary), Feb(ruary), Mar(ch), Apr(il),
45   * May, Jun(e), Jul(y), Aug(ust), Sep(tember), Oct(ober), Nov(ember) and
46   * Dec(ember).
47   * 
48   * Valid values for <b>dayofweek</b> are Mon(day), Tue(sday), Wed(nesday),
49   * Thu(rsday), Fri(day), Sat(urday), Sun(day)
50   * 
51   * Valid values for <b>period<b> are second(s), minute(s), hour(s), day(s),
52   * week(s), month(s)
53   * 
54   * Examples: now + 5 minutes 10:15 PM May 5 23:20 Monday
55   * 
56   * @version $Revision: 111 $
57   * @author Stefan Unterhofer <stefan.unterhofer@enerbility.com>
58   * @author Steven Jardine <steve@mjnservices.com>
59   */
60  public class TimeParser {
61  
62      /***
63       * Exception class for parse errors
64       */
65      public class ParseException extends Exception {
66  	ParseException() {
67  	    super();
68  	}
69  
70  	ParseException(String message) {
71  	    super(message);
72  	}
73  
74  	ParseException(String message, Throwable cause) {
75  	    super(message, cause);
76  	}
77  
78  	ParseException(Throwable cause) {
79  	    super(cause);
80  	}
81      }
82  
83      /***
84       * HashMap containing valid dayofweek values linking to the corresponding
85       * Calendar fields
86       */
87      private static Map daysOfWeek = new HashMap();
88  
89      private static final Log logger = LogFactory.getLog(TimeParser.class);
90  
91      /***
92       * HashMap containing valid month values and the corresponding Calendar
93       * fields
94       */
95      private static Map months = new HashMap();
96  
97      /***
98       * HashMap containing valid period values and linking to the corresponding
99       * Calendar fields
100      */
101     private static Map periods = new HashMap();
102 
103     static {
104 	periods.put("second", new Integer(Calendar.SECOND));
105 	periods.put("seconds", new Integer(Calendar.SECOND));
106 	periods.put("minute", new Integer(Calendar.MINUTE));
107 	periods.put("minutes", new Integer(Calendar.MINUTE));
108 	periods.put("hour", new Integer(Calendar.HOUR));
109 	periods.put("hours", new Integer(Calendar.HOUR));
110 	periods.put("day", new Integer(Calendar.DAY_OF_YEAR));
111 	periods.put("days", new Integer(Calendar.DAY_OF_YEAR));
112 	periods.put("week", new Integer(Calendar.WEEK_OF_YEAR));
113 	periods.put("weeks", new Integer(Calendar.WEEK_OF_YEAR));
114 	periods.put("month", new Integer(Calendar.MONTH));
115 	periods.put("months", new Integer(Calendar.MONTH));
116     }
117 
118     static {
119 	months.put("jan", new Integer(Calendar.JANUARY));
120 	months.put("january", new Integer(Calendar.JANUARY));
121 	months.put("feb", new Integer(Calendar.FEBRUARY));
122 	months.put("february", new Integer(Calendar.FEBRUARY));
123 	months.put("mar", new Integer(Calendar.MARCH));
124 	months.put("march", new Integer(Calendar.MARCH));
125 	months.put("apr", new Integer(Calendar.APRIL));
126 	months.put("april", new Integer(Calendar.APRIL));
127 	months.put("may", new Integer(Calendar.MAY));
128 	months.put("jun", new Integer(Calendar.JUNE));
129 	months.put("june", new Integer(Calendar.JUNE));
130 	months.put("jul", new Integer(Calendar.JULY));
131 	months.put("july", new Integer(Calendar.JULY));
132 	months.put("aug", new Integer(Calendar.AUGUST));
133 	months.put("august", new Integer(Calendar.AUGUST));
134 	months.put("sep", new Integer(Calendar.SEPTEMBER));
135 	months.put("september", new Integer(Calendar.SEPTEMBER));
136 	months.put("oct", new Integer(Calendar.OCTOBER));
137 	months.put("october", new Integer(Calendar.OCTOBER));
138 	months.put("nov", new Integer(Calendar.NOVEMBER));
139 	months.put("november", new Integer(Calendar.NOVEMBER));
140 	months.put("dec", new Integer(Calendar.DECEMBER));
141 	months.put("december", new Integer(Calendar.DECEMBER));
142     }
143 
144     static {
145 	daysOfWeek.put("mon", new Integer(Calendar.MONDAY));
146 	daysOfWeek.put("monday", new Integer(Calendar.MONDAY));
147 	daysOfWeek.put("tue", new Integer(Calendar.TUESDAY));
148 	daysOfWeek.put("tuesday", new Integer(Calendar.TUESDAY));
149 	daysOfWeek.put("wed", new Integer(Calendar.WEDNESDAY));
150 	daysOfWeek.put("wednesday", new Integer(Calendar.WEDNESDAY));
151 	daysOfWeek.put("thu", new Integer(Calendar.THURSDAY));
152 	daysOfWeek.put("thursday", new Integer(Calendar.THURSDAY));
153 	daysOfWeek.put("fri", new Integer(Calendar.FRIDAY));
154 	daysOfWeek.put("friday", new Integer(Calendar.FRIDAY));
155 	daysOfWeek.put("sat", new Integer(Calendar.SATURDAY));
156 	daysOfWeek.put("saturday", new Integer(Calendar.SATURDAY));
157 	daysOfWeek.put("sun", new Integer(Calendar.SUNDAY));
158 	daysOfWeek.put("sunday", new Integer(Calendar.SUNDAY));
159     }
160 
161     private Locale locale;
162 
163     private TimeZone timeZone;
164 
165     /***
166      * Initializes the Parser with the current timezone/locale of the program.
167      */
168     public TimeParser() {
169 	this.locale = Locale.getDefault();
170 	this.timeZone = TimeZone.getDefault();
171     }
172 
173     /***
174      * Initializes the parser with a custom timezone and locale
175      * 
176      * @param locale
177      *                Custom locale
178      * @param timeZone
179      *                Custom timezone
180      */
181     public TimeParser(Locale locale, TimeZone timeZone) {
182 	this.locale = locale;
183 	this.timeZone = timeZone;
184     }
185 
186     /***
187      * Gets the given Time in the Format awaited by the hylafax client protocol
188      * for the KILLTIME parameter, which is now + ddhhmm, days, hours and
189      * minutes from now.
190      * 
191      * @param time
192      *                Time String in the hylafax config file format
193      * @return Time String used by the hylafax client protocol
194      * @throws ParseException
195      *                 if the supplied time string isn't valid
196      */
197     public String getKillTime(String time) throws ParseException {
198 	if (time.toLowerCase().trim().equals("now")) {
199 	    return "000000";
200 	}
201 
202 	long killTime = parse(time).getTime() - System.currentTimeMillis();
203 
204 	// convert to seconds since we don't need ms
205 	killTime /= 1000;
206 
207 	long days = killTime / (60 * 60 * 24);
208 	killTime %= 60 * 60 * 24;
209 
210 	long hours = killTime / (60 * 60);
211 	killTime %= 60 * 60;
212 
213 	long minutes = killTime / 60;
214 
215 	String dayS, hourS, minS;
216 	if (days < 10) {
217 	    dayS = "0" + days;
218 	} else {
219 	    dayS = "" + days;
220 	}
221 
222 	if (hours < 10) {
223 	    hourS = "0" + hours;
224 	} else {
225 	    hourS = "" + hours;
226 	}
227 
228 	if (minutes < 10) {
229 	    minS = "0" + minutes;
230 	} else {
231 	    minS = "" + minutes;
232 	}
233 
234 	return dayS + hourS + minS;
235     }
236 
237     /***
238      * Gets the given Time in the Format awaited by the Hylafax client protocol
239      * for the SENDTIME parameter, which is YYYYMMDDhhmmss
240      * 
241      * @param time
242      *                Time String in the hylafax config file format
243      * @return Time String used by the hylafax client protocol
244      * @throws ParseException
245      *                 if the supplied time string isn't valid
246      */
247     public String getSendTime(String time) throws ParseException {
248 	if (time.trim().toLowerCase().equals("now")) {
249 	    return "now";
250 	}
251 
252 	SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmm");
253 	return format.format(parse(time));
254     }
255 
256     /***
257      * This method parses a given datetime String into a java.util.Date using a
258      * Calendar
259      * 
260      * @param string
261      *                Hylafax datetime String (e.g. now + 10 minutes)
262      * @return java.util.Date object containing the parsed date
263      * @throws ParseException
264      *                 if the entered String is not valid
265      */
266     private Date parse(String str) throws ParseException {
267 	try {
268 	    String string = str.toLowerCase().trim();
269 
270 	    Calendar cal = Calendar.getInstance(timeZone, locale);
271 	    cal.setTimeInMillis(System.currentTimeMillis());
272 
273 	    StringTokenizer tokenizer = new StringTokenizer(string);
274 
275 	    if (logger.isDebugEnabled()) {
276 		logger.debug("Parsing String " + string);
277 		logger.debug("Current Time is " + cal.getTime());
278 	    }
279 
280 	    String time = tokenizer.nextToken();
281 	    // String has form now + N period
282 	    if (time.equals("now")) {
283 		// check if + is present
284 		if (tokenizer.nextToken().equals("+")) {
285 		    try {
286 			// parse N
287 			String number = tokenizer.nextToken();
288 			if (logger.isDebugEnabled()) {
289 			    logger.debug("N = " + number);
290 			}
291 			int n = Integer.parseInt(number);
292 
293 			if (n < 0) {
294 			    throw new NumberFormatException();
295 			}
296 
297 			// parse period
298 			String period = tokenizer.nextToken();
299 
300 			if (periods.get(period) != null) {
301 			    cal.setLenient(true);
302 			    cal.add(((Integer) periods.get(period)).intValue(),
303 				    n);
304 			} else {
305 			    throw new ParseException(
306 				    "period has to be minute(s), hour(s), day(s), week(s) or month(s)");
307 			}
308 
309 			if (logger.isDebugEnabled()) {
310 			    logger.debug("Parsing finished, date is "
311 				    + cal.getTime());
312 			}
313 
314 			return cal.getTime();
315 
316 		    } catch (NumberFormatException e) {
317 			throw new ParseException(
318 				"N must be a positive numeric value");
319 		    }
320 		}
321 		throw new ParseException(
322 			"Parse error now must be followed by '+'");
323 	    }
324 	    // string has form HH:MM [AM|PM] [month DD | dayofweek]
325 	    SimpleDateFormat format = new SimpleDateFormat("HH:mm");
326 	    boolean isAmPm = false;
327 
328 	    if (tokenizer.hasMoreTokens()) {
329 
330 		// check if the time is given in AM/PM format or 24 hours
331 		String amPm = tokenizer.nextToken();
332 
333 		if (amPm.equals("am") || amPm.equals("pm")) {
334 		    format = new SimpleDateFormat("KK:mm");
335 		    isAmPm = true;
336 		}
337 
338 		try {
339 		    Calendar d = Calendar.getInstance();
340 		    d.setTime(format.parse(time));
341 		    cal.set(Calendar.HOUR_OF_DAY, d.get(Calendar.HOUR_OF_DAY));
342 		    cal.set(Calendar.MINUTE, d.get(Calendar.MINUTE));
343 		} catch (java.text.ParseException e) {
344 		    throw new ParseException("Time has to be hh:mm [AM|PM]");
345 		}
346 
347 		if (isAmPm) {
348 		    if (amPm.equals("pm")) {
349 			cal.add(Calendar.HOUR, 12);
350 		    }
351 
352 		    if (tokenizer.hasMoreTokens()) {
353 			amPm = tokenizer.nextToken();
354 		    } else {
355 			return cal.getTime();
356 		    }
357 		}
358 
359 		if (daysOfWeek.get(amPm) != null) {
360 		    cal.set(Calendar.DAY_OF_WEEK, ((Integer) daysOfWeek
361 			    .get(amPm)).intValue());
362 		} else if (months.get(amPm) != null) {
363 		    cal.set(Calendar.MONTH, ((Integer) months.get(amPm))
364 			    .intValue());
365 
366 		    try {
367 			cal.setLenient(false);
368 			cal.set(Calendar.DAY_OF_MONTH, Integer
369 				.parseInt(tokenizer.nextToken()));
370 		    } catch (NumberFormatException e) {
371 			throw new ParseException(
372 				"day of month has to be a valid numeric value");
373 		    } catch (ArrayIndexOutOfBoundsException e) {
374 			throw new ParseException(
375 				"day value has to be within mont boundaries");
376 		    }
377 
378 		} else {
379 		    throw new ParseException(
380 			    "Value has to be a valid month or day of week");
381 		}
382 
383 	    } else {
384 		try {
385 		    Calendar d = Calendar.getInstance();
386 		    d.setTime(format.parse(time));
387 		    cal.set(Calendar.HOUR_OF_DAY, d.get(Calendar.HOUR_OF_DAY));
388 		    cal.set(Calendar.MINUTE, d.get(Calendar.MINUTE));
389 		} catch (java.text.ParseException e) {
390 		    throw new ParseException("Time has to be hh:mm [AM|PM]");
391 		}
392 
393 	    }
394 	    if (logger.isDebugEnabled()) {
395 		logger.debug("Returning Date " + cal.getTime());
396 	    }
397 
398 	    return cal.getTime();
399 
400 	} catch (NoSuchElementException e) {
401 	    throw new ParseException("String to parse not complete");
402 	}
403     }
404 
405 }