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
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
282 if (time.equals("now")) {
283
284 if (tokenizer.nextToken().equals("+")) {
285 try {
286
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
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
325 SimpleDateFormat format = new SimpleDateFormat("HH:mm");
326 boolean isAmPm = false;
327
328 if (tokenizer.hasMoreTokens()) {
329
330
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 }