Source code: com/tripi/asp/AspDate.java
1 /**
2 * ArrowHead ASP Server
3 * This is a source file for the ArrowHead ASP Server - an 100% Java
4 * VBScript interpreter and ASP server.
5 *
6 * For more information, see http://www.tripi.com/arrowhead
7 *
8 * Copyright (C) 2002 Terence Haddock
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 */
25 package com.tripi.asp;
26
27 import java.text.DateFormat;
28 import java.text.DateFormatSymbols;
29 import java.text.SimpleDateFormat;
30 import java.util.Calendar;
31 import java.util.Date;
32 import java.util.GregorianCalendar;
33
34 import jregex.Matcher;
35 import jregex.Pattern;
36 import jregex.RETokenizer;
37
38 import org.apache.log4j.Category;
39
40 /**
41 * The AspDate class contains the special code needed for handling ASP
42 * dates. This class can process most date format, and can be configured
43 * for the default month, day, year ordering.
44 *
45 * @author Terence Haddock
46 * @version 0.9
47 */
48 public class AspDate
49 {
50 /** Debugging class */
51 private static final Category DBG = Category.getInstance(AspDate.class);
52
53 /** Month bit field */
54 static final int MONTH = 0x10;
55
56 /** Day bit field */
57 static final int DAY = 0x8;
58
59 /** Year bit field */
60 static final int YEAR = 0x4;
61
62 /** Time bit field */
63 static final int TIME = 0x2;
64
65 /** AM/PM bit field */
66 static final int AMPM = 0x1;
67
68 /** Field contains the current default order of the month, day, year fields */
69 protected static String order = "MDY";
70
71 /** The current date/time values as a calendar object */
72 protected Calendar cal = new GregorianCalendar();
73
74 /** Does this object hold a time value? */
75 protected boolean hasTime = false;
76
77 /** Does this object hold a date value? */
78 protected boolean hasDate = false;
79
80 /** Long date/time format */
81 static DateFormat longDateTimeFormat =
82 new SimpleDateFormat("M/d/yyyy h:mm:ss aa");
83
84 /** Long date format */
85 static DateFormat longDateFormat =
86 new SimpleDateFormat("EEEE, MMMM d, yyyy");
87
88 /** Short date format */
89 static DateFormat shortDateFormat = new SimpleDateFormat("M/d/yyyy");
90
91 /** Long time format */
92 static DateFormat longTimeFormat = new SimpleDateFormat("h:mm:ss aa");
93
94 /** Short time format */
95 static DateFormat shortTimeFormat = new SimpleDateFormat("H:mm");
96
97 /**
98 * This constructor creates an AspDate object from a generic time
99 * string.
100 * @param datestr date/time string
101 * @throws AspException on error
102 */
103 public AspDate(String datestr) throws AspException
104 {
105 /* This is the tokenizer we will use */
106 final Pattern p = new Pattern("[, \t\\-\\\\/]+");
107 RETokenizer tok = p.tokenizer(datestr);
108 String parts[] = tok.split();
109 /* match will hold the type of data this string can represent in a
110 bit pattern */
111 int match[] = new int[parts.length];
112 for (int i = 0; i < parts.length; i++) {
113 if (DBG.isDebugEnabled())
114 DBG.debug("parts[" + i + "] = " + parts[i]);
115 if (isNumber(parts[i])) {
116 int iVal = Integer.parseInt(parts[i]);
117 if (iVal > 0 && iVal <= 12) {
118 match[i] = MONTH | DAY | YEAR;
119 } else if (iVal > 12 && iVal <= 31) {
120 match[i] = DAY | YEAR;
121 } else {
122 match[i] = YEAR;
123 }
124 } else if (getMonthString(parts[i]) != -1) {
125 match[i] = MONTH;
126 } else if (isTimeString(parts[i])) {
127 match[i] = TIME;
128 } else if (parts[i].equalsIgnoreCase("am") ||
129 parts[i].equalsIgnoreCase("pm")) {
130 match[i] = AMPM;
131 } else {
132 throw new AspCastException("Cannot convert string \"" +
133 datestr + "\" to date");
134 }
135 }
136 if (DBG.isDebugEnabled()) {
137 DBG.debug("Before logical elimination: ");
138 for (int i = 0; i < parts.length; i++) {
139 DBG.debug("match[" + parts[i] + "] = " + match[i]);
140 }
141 }
142 /* From the bit patterns, perform a logical elimination of the
143 match state */
144 int bit = 1;
145 while (bit <= (MONTH|DAY|YEAR|TIME|AMPM)) {
146 for (int i = 0; i < parts.length; i++) {
147 if (match[i] == bit) {
148 if (DBG.isDebugEnabled()) DBG.debug("best for bit " + bit
149 + ": " + i);
150 for (int j = 0 ; j < parts.length; j++)
151 if (j != i) match[j] &= ~bit;
152 break;
153 }
154 }
155 bit*=2;
156 }
157 if (DBG.isDebugEnabled()) {
158 DBG.debug("After logical elimination: ");
159 for (int i = 0; i < parts.length; i++) {
160 DBG.debug("match[" + parts[i] + "] = " + match[i]);
161 }
162 }
163 /* Handle the MDY ordering for the last elimination */
164 for (int k = 0; k < 3; k++) {
165 if (order.charAt(k)=='M') bit = MONTH;
166 else if (order.charAt(k)=='D') bit = DAY;
167 else if (order.charAt(k)=='Y') bit = YEAR;
168 if (DBG.isDebugEnabled()) DBG.debug("Bit: " + bit);
169 for (int i = 0 ; i < parts.length; i++) {
170 if ((match[i] & bit) == bit) {
171 for (int j = 0; j < parts.length; j++)
172 if (j != i) match[j] &= ~bit;
173 match[i] = bit;
174 break;
175 }
176 }
177 }
178 if (DBG.isDebugEnabled()) {
179 DBG.debug("After ordering: ");
180 for (int i = 0; i < parts.length; i++) {
181 DBG.debug("match[" + parts[i] + "] = " + match[i]);
182 }
183 }
184 /* Get the actual data which corresponds to the month,day,year fields */
185 cal.set(Calendar.HOUR_OF_DAY, 0);
186 cal.set(Calendar.MINUTE, 0);
187 cal.set(Calendar.SECOND, 0);
188 cal.set(Calendar.MILLISECOND, 0);
189 for (int i = 0; i < parts.length ; i++)
190 {
191 if (match[i] == MONTH) {
192 int month;
193 if (Character.isLetter(parts[i].charAt(0))) {
194 month = getMonthString(parts[i]);
195 } else {
196 month = Integer.parseInt(parts[i]) - 1;
197 }
198 cal.set(Calendar.MONTH, month);
199 hasDate = true;
200 } else if (match[i] == DAY) {
201 int day = Integer.parseInt(parts[i]);
202 cal.set(Calendar.DAY_OF_MONTH, day);
203 hasDate = true;
204 } else if (match[i] == YEAR) {
205 int year = Integer.parseInt(parts[i]);
206 if (year > 50 && year < 100) {
207 year+=1900;
208 } else if (year < 100) {
209 year+=2000;
210 }
211 cal.set(Calendar.YEAR, year);
212 hasDate = true;
213 } else if (match[i] == TIME) {
214 /* Check if a time is included */
215 if (i < (match.length - 1) && match[i+1] == AMPM) {
216 parseTime(parts[i] + parts[i + 1]);
217 } else {
218 parseTime(parts[i]);
219 }
220 } else if (match[i] == AMPM) {
221 if (i == 0 || match[i - 1] != TIME) {
222 /* AM/PM has to be after a time */
223 throw new AspCastException("Cannot convert string \"" +
224 datestr + "\" to date");
225 }
226 } else {
227 /* A part did not match */
228 throw new AspCastException("Cannot convert string \"" +
229 datestr + "\" to date");
230 }
231 }
232 if (!hasDate)
233 {
234 cal.set(Calendar.MONTH, 11);
235 cal.set(Calendar.DAY_OF_MONTH, 30);
236 cal.set(Calendar.YEAR, 1899);
237 }
238 }
239
240 /**
241 * This constructor creates an AspDate object from a Java Date object.
242 * @param date Java date object
243 */
244 public AspDate(Date date)
245 {
246 this(date, true, true);
247 }
248
249 /**
250 * This constructor creates an AspDate object from a Java Date object,
251 * optionally setting the date and time values.
252 * @param javaDate Java date object
253 * @param hasTime has a time value?
254 * @param hasDate has a date value?
255 */
256 public AspDate(Date javaDate, boolean hasTime, boolean hasDate)
257 {
258 cal.setTime(javaDate);
259 this.hasTime = hasTime;
260 this.hasDate = hasDate;
261 if (!hasTime) {
262 cal.set(Calendar.HOUR_OF_DAY, 0);
263 cal.set(Calendar.MINUTE, 0);
264 cal.set(Calendar.SECOND, 0);
265 cal.set(Calendar.MILLISECOND, 0);
266 }
267 if (!hasDate) {
268 cal.set(Calendar.MONTH, 11);
269 cal.set(Calendar.DAY_OF_MONTH, 30);
270 cal.set(Calendar.YEAR, 1899);
271 }
272 }
273
274 /**
275 * This constructor creates an AspDate object from a Java calendar object.
276 * @param cal Java calendar object
277 */
278 public AspDate(Calendar cal)
279 {
280 this(cal, true, true);
281 }
282
283 /**
284 * This constructor creates an AspDate object from a Java Calendar object,
285 * optionally setting the date and time values.
286 * @param javaCal Java calendar object
287 * @param hasTime has a time value?
288 * @param hasDate has a date value?
289 */
290 public AspDate(Calendar javaCal, boolean hasTime, boolean hasDate)
291 {
292 this(javaCal.getTime(), hasTime, hasDate);
293 }
294
295 /**
296 * Constructor set with specific time settings.
297 * @param month Month setting
298 * @param day Day setting
299 * @param year Year setting
300 * @param hour Hour setting
301 * @param minute Minute setting
302 * @param second Second setting
303 */
304 protected AspDate(int month, int day, int year, int hour, int minute, int second)
305 {
306 cal.set(Calendar.HOUR_OF_DAY, 0);
307 cal.set(Calendar.MINUTE, 0);
308 cal.set(Calendar.SECOND, 0);
309 cal.set(Calendar.MILLISECOND, 0);
310 cal.set(Calendar.MONTH, 11);
311 cal.set(Calendar.DAY_OF_MONTH, 30);
312 cal.set(Calendar.YEAR, 1899);
313 if (month != -1)
314 {
315 cal.set(Calendar.MONTH, month);
316 hasDate = true;
317 }
318 if (day != -1)
319 {
320 cal.set(Calendar.DAY_OF_MONTH, day);
321 hasDate = true;
322 }
323 if (year != -1)
324 {
325 cal.set(Calendar.YEAR, year);
326 hasDate = true;
327 }
328 if (hour != -1)
329 {
330 cal.set(Calendar.HOUR_OF_DAY, hour);
331 hasTime = true;
332 }
333 if (minute != -1)
334 {
335 cal.set(Calendar.MINUTE, minute);
336 hasTime = true;
337 }
338 if (second != -1)
339 {
340 cal.set(Calendar.SECOND, second);
341 hasTime = true;
342 }
343 }
344
345 /**
346 * Constructor set with specific date-only settings.
347 * @param month Month setting
348 * @param day Day setting
349 * @param year Year setting
350 */
351 protected AspDate(int month, int day, int year)
352 {
353 this(month, day, year, -1, -1, -1);
354 }
355
356 /**
357 * This function determines if the specified string contains a number
358 * @param str String to test
359 * @return true if the value is a number, false otherwise
360 */
361 private static boolean isNumber(String str)
362 {
363 char ch[] = str.toCharArray();
364 for (int x = 0; x < ch.length ; x++)
365 {
366 if (!Character.isDigit(ch[x])) return false;
367 }
368 return true;
369 }
370
371 /**
372 * This funtion obtains from a string, the month the string corresponds
373 * to. Uses the build-in DateFormatSymbols class to match strings to
374 * months.
375 * @param str String to convert to month number.
376 * @return number of the month from 0 to 11, or -1 if the string is
377 * not recognized
378 */
379 private static int getMonthString(String str)
380 {
381 DateFormatSymbols format = new DateFormatSymbols();
382 String months[] = format.getShortMonths();
383 String mon = str.toLowerCase();
384 for (int i = 0; i < 12; i++)
385 if (mon.startsWith(months[i].toLowerCase())) return i;
386 return -1;
387 }
388
389 /**
390 * This function attempts to determine if the given string is a time
391 * string.
392 * @param str string to check for time string
393 * @return true if the string is a time string, false otherwise
394 * @throws AspException on error
395 */
396 public static boolean isTimeString(String str) throws AspException
397 {
398 Pattern p = new Pattern("^[0-9]{1,2}:[0-9]{1,2}" +
399 "(:[0-9]{1,2}(\\.[0-9]{1,2})?)?([aApP][mM])?$");
400 Matcher m = p.matcher(str);
401 if (m.matches()) return true;
402 return false;
403 }
404
405 /**
406 * This function parses the given string into hour, minute, second,
407 * and sets the internal variables accordingly.
408 * @param str Time string to parse
409 * @throws AspException on error
410 */
411 protected void parseTime(String str) throws AspException
412 {
413 final Pattern p = new Pattern("^([0-9]{1,2}):([0-9]{1,2})" +
414 "(:([0-9]{1,2})(\\.[0-9]{1,2})?)?([aApP][mM])?$");
415 Matcher m = p.matcher(str);
416
417 if (!m.find()) {
418 throw new AspCastException("Cannot convert string \"" +
419 str + "\" to time");
420 }
421
422 hasTime = true;
423 int hour = Integer.parseInt(m.group(1));
424 int minute = Integer.parseInt(m.group(2));
425 int second = 0;
426 if (m.group(4) != null) {
427 second = Integer.parseInt(m.group(4));
428 } else {
429 second = 0;
430 }
431 if (m.group(6) != null) {
432 String apm = m.group(6).toLowerCase();
433 if (apm.equals("pm")) {
434 hour+=12;
435 }
436 }
437 if (DBG.isDebugEnabled()) {
438 DBG.debug("Setting hour: " + hour);
439 DBG.debug("Setting minute: " + minute);
440 DBG.debug("Setting second: " + second);
441 }
442 cal.set(Calendar.HOUR_OF_DAY, hour);
443 cal.set(Calendar.MINUTE, minute);
444 cal.set(Calendar.SECOND, second);
445 cal.set(Calendar.MILLISECOND, 0);
446 if (DBG.isDebugEnabled()) {
447 DBG.debug("Resulting cal: " + cal);
448 }
449 }
450
451 /**
452 * Converts this AspDate object to a Calendar object
453 * @return calendar object representing this AspDate object
454 */
455 public Calendar toCalendar()
456 {
457 return (Calendar)cal.clone();
458 }
459
460 /**
461 * Converts this AspDate object to a Date object
462 * @return date object representing this AspDate object
463 */
464 public Date toDate()
465 {
466 return cal.getTime();
467 }
468
469 /**
470 * Get the month value of this AspDate
471 * @return month value
472 */
473 public int getMonth()
474 {
475 return cal.get(Calendar.MONTH);
476 }
477
478 /**
479 * Get the day value of this AspDate
480 * @return day value
481 */
482 public int getDay()
483 {
484 return cal.get(Calendar.DAY_OF_MONTH);
485 }
486
487 /**
488 * Get the year value of this AspDate
489 * @return year value
490 */
491 public int getYear()
492 {
493 return cal.get(Calendar.YEAR);
494 }
495
496 /**
497 * Get the hour value of this AspDate
498 * @return hour value
499 */
500 public int getHour()
501 {
502 return cal.get(Calendar.HOUR_OF_DAY);
503 }
504
505 /**
506 * Get the minute value of this AspDate
507 * @return minute value
508 */
509 public int getMinute()
510 {
511 return cal.get(Calendar.MINUTE);
512 }
513
514 /**
515 * Get the second value of this AspDate
516 * @return second value
517 */
518 public int getSecond()
519 {
520 return cal.get(Calendar.SECOND);
521 }
522
523 /**
524 * Does this object have a time value?
525 * @return true if this object has a time value, false otherwise.
526 */
527 public boolean hasTime()
528 {
529 return hasTime;
530 }
531
532 /**
533 * Does this object have a date value?
534 * @return true if this object has a date value, false otherwise.
535 */
536 public boolean hasDate()
537 {
538 return hasDate;
539 }
540
541 /**
542 * Convert this date to a string.
543 * @return String representation of this date.
544 */
545 public String toString()
546 {
547 if (!hasTime)
548 {
549 return shortDateFormat.format(toDate());
550 } else if (!hasDate) {
551 return longTimeFormat.format(toDate());
552 } else {
553 return longDateTimeFormat.format(toDate());
554 }
555 }
556 }