0001: /*
0002: * ClockCmd.java --
0003: *
0004: * Implements the built-in "clock" Tcl command.
0005: *
0006: * Copyright (c) 1998-2000 Christian Krone.
0007: * Copyright (c) 1997 Cornell University.
0008: * Copyright (c) 1995-1997 Sun Microsystems, Inc.
0009: * Copyright (c) 1992-1995 Karl Lehenbauer and Mark Diekhans.
0010: *
0011: * See the file "license.terms" for information on usage and
0012: * redistribution of this file, and for a DISCLAIMER OF ALL
0013: * WARRANTIES.
0014: *
0015: * RCS: @(#) $Id: ClockCmd.java,v 1.7 2006/01/26 19:49:18 mdejong Exp $
0016: *
0017: */
0018:
0019: package tcl.lang;
0020:
0021: import java.util.*;
0022: import java.text.*;
0023:
0024: /**
0025: * This class implements the built-in "clock" command in Tcl.
0026: */
0027:
0028: class ClockCmd implements Command {
0029:
0030: static final private String[] validCmds = { "clicks", "format",
0031: "scan", "seconds" };
0032:
0033: static final private int CMD_CLICKS = 0;
0034: static final private int CMD_FORMAT = 1;
0035: static final private int CMD_SCAN = 2;
0036: static final private int CMD_SECONDS = 3;
0037:
0038: static final private String clicksOpts[] = { "-milliseconds" };
0039:
0040: static final private int OPT_CLICKS_MILLISECONDS = 0;
0041:
0042: static final private String[] formatOpts = { "-format", "-gmt" };
0043:
0044: static final private int OPT_FORMAT_FORMAT = 0;
0045: static final private int OPT_FORMAT_GMT = 1;
0046:
0047: static final private String[] scanOpts = { "-base", "-gmt" };
0048:
0049: static final private int OPT_SCAN_BASE = 0;
0050: static final private int OPT_SCAN_GMT = 1;
0051:
0052: static final int EPOCH_YEAR = 1970;
0053: static final int MILLIS_PER_HOUR = 60 * 60 * 1000;
0054:
0055: /**
0056: *----------------------------------------------------------------------
0057: *
0058: * cmdProc --
0059: *
0060: * This procedure is invoked as part of the Command interface to
0061: * process the "clock" Tcl command. See the user documentation
0062: * for details on what it does.
0063: *
0064: * Results:
0065: * None.
0066: *
0067: * Side effects:
0068: * See the user documentation.
0069: *
0070: *----------------------------------------------------------------------
0071: */
0072:
0073: public void cmdProc(Interp interp, // Current interpreter.
0074: TclObject[] objv) // Argument list.
0075: throws TclException // A standard Tcl exception.
0076: {
0077: int clockVal; // Time value as seconds of epoch.
0078: String dateString; // Time value as string.
0079: int argIx; // Counter over arguments.
0080: String format = null; // User specified format string.
0081: boolean useGmt = false; // User specified flag to use gmt.
0082: TclObject baseObj = null; // User specified raw value of baseClock.
0083: Date baseClock; // User specified time value.
0084: Date date; // Parsed date value.
0085:
0086: if (objv.length < 2) {
0087: throw new TclNumArgsException(interp, 1, objv,
0088: "option ?arg ...?");
0089: }
0090: int cmd = TclIndex.get(interp, objv[1], validCmds, "option", 0);
0091:
0092: switch (cmd) {
0093: case CMD_CLICKS: {
0094: if (objv.length > 3) {
0095: throw new TclNumArgsException(interp, 2, objv,
0096: "?-milliseconds?");
0097: }
0098: if (objv.length == 3) {
0099: // We can safely ignore the -milliseconds options, since
0100: // we measure the clicks in milliseconds anyway...
0101: int clicksOpt = TclIndex.get(interp, objv[2],
0102: clicksOpts, "switch", 0);
0103: }
0104: long millis = System.currentTimeMillis();
0105: int clicks = (int) (millis % Integer.MAX_VALUE);
0106: interp.setResult(clicks);
0107: break;
0108: }
0109:
0110: case CMD_FORMAT: {
0111: if ((objv.length < 3) || (objv.length > 7)) {
0112: throw new TclNumArgsException(interp, 2, objv,
0113: "clockval ?-format string? ?-gmt boolean?");
0114: }
0115: clockVal = TclInteger.get(interp, objv[2]);
0116:
0117: for (argIx = 3; argIx + 1 < objv.length; argIx += 2) {
0118: int formatOpt = TclIndex.get(interp, objv[argIx],
0119: formatOpts, "switch", 0);
0120: switch (formatOpt) {
0121: case OPT_FORMAT_FORMAT: {
0122: format = objv[argIx + 1].toString();
0123: break;
0124: }
0125: case OPT_FORMAT_GMT: {
0126: useGmt = TclBoolean.get(interp, objv[argIx + 1]);
0127: break;
0128: }
0129: }
0130: }
0131: if (argIx < objv.length) {
0132: throw new TclNumArgsException(interp, 2, objv,
0133: "clockval ?-format string? ?-gmt boolean?");
0134: }
0135: FormatClock(interp, clockVal, useGmt, format);
0136: break;
0137: }
0138:
0139: case CMD_SCAN: {
0140: if ((objv.length < 3) || (objv.length > 7)) {
0141: throw new TclNumArgsException(interp, 2, objv,
0142: "dateString ?-base clockValue? ?-gmt boolean?");
0143: }
0144: dateString = objv[2].toString();
0145:
0146: for (argIx = 3; argIx + 1 < objv.length; argIx += 2) {
0147: int scanOpt = TclIndex.get(interp, objv[argIx],
0148: scanOpts, "switch", 0);
0149: switch (scanOpt) {
0150: case OPT_SCAN_BASE: {
0151: baseObj = objv[argIx + 1];
0152: break;
0153: }
0154: case OPT_SCAN_GMT: {
0155: useGmt = TclBoolean.get(interp, objv[argIx + 1]);
0156: break;
0157: }
0158: }
0159: }
0160: if (argIx < objv.length) {
0161: throw new TclNumArgsException(interp, 2, objv,
0162: "clockval ?-format string? ?-gmt boolean?");
0163: }
0164: if (baseObj != null) {
0165: long seconds = TclInteger.get(interp, baseObj);
0166: baseClock = new Date(seconds * 1000);
0167: } else {
0168: baseClock = new Date();
0169: }
0170:
0171: date = GetDate(dateString, baseClock, useGmt);
0172: if (date == null) {
0173: throw new TclException(interp,
0174: "unable to convert date-time string \""
0175: + dateString + "\"");
0176: }
0177:
0178: int seconds = (int) (date.getTime() / 1000);
0179: interp.setResult(seconds);
0180: break;
0181: }
0182:
0183: case CMD_SECONDS: {
0184: if (objv.length != 2) {
0185: throw new TclNumArgsException(interp, 2, objv, null);
0186: }
0187: long millis = System.currentTimeMillis();
0188: int seconds = (int) (millis / 1000);
0189: interp.setResult(seconds);
0190: break;
0191: }
0192: }
0193: }
0194:
0195: /**
0196: *-----------------------------------------------------------------------------
0197: *
0198: * FormatClock --
0199: *
0200: * Formats a time value based on seconds into a human readable
0201: * string.
0202: *
0203: * Results:
0204: * None.
0205: *
0206: * Side effects:
0207: * The interpreter will contain the formatted string as result.
0208: *
0209: *-----------------------------------------------------------------------------
0210: */
0211:
0212: private void FormatClock(Interp interp, // Current interpreter.
0213: int clockVal, // Time in seconds.
0214: boolean useGMT, // Boolean
0215: String format) // Format string
0216: throws TclException // A standard Tcl exception.
0217: {
0218: Date date = new Date((long) clockVal * 1000);
0219: GregorianCalendar calendar = new GregorianCalendar();
0220: SimpleDateFormat fmt, locFmt;
0221: FieldPosition fp = new FieldPosition(0);
0222: StringBuffer result = new StringBuffer();
0223:
0224: if (format == null) {
0225: format = new String("%a %b %d %H:%M:%S %Z %Y");
0226: }
0227:
0228: if (useGMT) {
0229: calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
0230: }
0231: calendar.setTime(date);
0232: fmt = new SimpleDateFormat("mm.dd.yy", Locale.US);
0233: fmt.setCalendar(calendar);
0234:
0235: if (format.equals("%Q")) { // Enterprise Stardate.
0236: int trekYear = calendar.get(Calendar.YEAR) + 377 - 2323;
0237: int trekDay = (calendar.get(Calendar.DAY_OF_YEAR) * 1000)
0238: / (calendar.isLeapYear(calendar.get(Calendar.YEAR)) ? 366
0239: : 365);
0240: int trekHour = (calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar
0241: .get(Calendar.MINUTE)) / 144;
0242:
0243: interp.setResult("Stardate " + (trekYear < 10 ? "0" : "")
0244: + (trekYear * 1000 + trekDay) + '.' + trekHour);
0245: return;
0246: }
0247:
0248: for (int ix = 0; ix < format.length(); ix++) {
0249: if (format.charAt(ix) == '%' && ix + 1 < format.length()) {
0250: switch (format.charAt(++ix)) {
0251: case '%': // Insert a %.
0252: result.append('%');
0253: break;
0254: case 'a': // Abbreviated weekday name (Mon, Tue, etc.).
0255: fmt.applyPattern("EEE");
0256: fmt.format(date, result, fp);
0257: break;
0258: case 'A': // Full weekday name (Monday, Tuesday, etc.).
0259: fmt.applyPattern("EEEE");
0260: fmt.format(date, result, fp);
0261: break;
0262: case 'b':
0263: case 'h': // Abbreviated month name (Jan,Feb,etc.).
0264: fmt.applyPattern("MMM");
0265: fmt.format(date, result, fp);
0266: break;
0267: case 'B': // Full month name.
0268: fmt.applyPattern("MMMM");
0269: fmt.format(date, result, fp);
0270: break;
0271: case 'c': // Locale specific date and time.
0272: locFmt = (SimpleDateFormat) DateFormat
0273: .getDateTimeInstance(DateFormat.SHORT,
0274: DateFormat.SHORT);
0275: locFmt.setCalendar(calendar);
0276: locFmt.format(date, result, fp);
0277: break;
0278: case 'C': // Century (00 - 99).
0279: int century = calendar.get(Calendar.YEAR) / 100;
0280: result.append((century < 10 ? "0" : "") + century);
0281: break;
0282: case 'd': // Day of month (01 - 31).
0283: fmt.applyPattern("dd");
0284: fmt.format(date, result, fp);
0285: break;
0286: case 'D': // Date as %m/%d/%y.
0287: fmt.applyPattern("MM/dd/yy");
0288: fmt.format(date, result, fp);
0289: break;
0290: case 'e': // Day of month (1 - 31), no leading zeros.
0291: fmt.applyPattern("d");
0292: String day = fmt.format(date);
0293: result.append((day.length() < 2 ? " " : "") + day);
0294: break;
0295: case 'H': // Hour in 24-hour format (00 - 23).
0296: fmt.applyPattern("HH");
0297: fmt.format(date, result, fp);
0298: break;
0299: case 'I': // Hour in 12-hour format (01 - 12).
0300: fmt.applyPattern("hh");
0301: fmt.format(date, result, fp);
0302: break;
0303: case 'j': // Day of year (001 - 366).
0304: fmt.applyPattern("DDD");
0305: fmt.format(date, result, fp);
0306: break;
0307: case 'k': // Hour in 24-hour format (0 - 23), no leading zeros.
0308: fmt.applyPattern("H");
0309: String h24 = fmt.format(date);
0310: result.append((h24.length() < 2 ? " " : "") + h24);
0311: break;
0312: case 'l': // Hour in 12-hour format (1 - 12), no leading zeros.
0313: fmt.applyPattern("h");
0314: String h12 = fmt.format(date);
0315: result.append((h12.length() < 2 ? " " : "") + h12);
0316: break;
0317: case 'm': // Month number (01 - 12).
0318: fmt.applyPattern("MM");
0319: fmt.format(date, result, fp);
0320: break;
0321: case 'M': // Minute (00 - 59).
0322: fmt.applyPattern("mm");
0323: fmt.format(date, result, fp);
0324: break;
0325: case 'n': // Insert a newline.
0326: result.append('\n');
0327: break;
0328: case 'p': // AM/PM indicator.
0329: fmt.applyPattern("aa");
0330: fmt.format(date, result, fp);
0331: break;
0332: case 'r': // Time as %I:%M:%S %p.
0333: fmt.applyPattern("KK:mm:ss aaaa");
0334: fmt.format(date, result, fp);
0335: break;
0336: case 'R': // Time as %H:%M.
0337: fmt.applyPattern("hh:mm");
0338: fmt.format(date, result, fp);
0339: break;
0340: case 's': // seconds since epoch.
0341: long millis = calendar.getTime().getTime();
0342: if (useGMT) {
0343: Calendar localCalendar = Calendar.getInstance();
0344: localCalendar.setTime(calendar.getTime());
0345: millis -= localCalendar
0346: .get(Calendar.ZONE_OFFSET)
0347: + localCalendar
0348: .get(Calendar.DST_OFFSET);
0349: }
0350: result.append((int) (millis / 1000));
0351: break;
0352: case 'S': // Seconds (00 - 59).
0353: fmt.applyPattern("ss");
0354: fmt.format(date, result, fp);
0355: break;
0356: case 't': // Insert a tab.
0357: result.append('\t');
0358: break;
0359: case 'T': // Time as %H:%M:%S.
0360: fmt.applyPattern("hh:mm:ss");
0361: fmt.format(date, result, fp);
0362: break;
0363: case 'u': // Weekday number (1 - 7) Sunday = 7.
0364: int dayOfWeek17 = calendar
0365: .get(Calendar.DAY_OF_WEEK);
0366: if (dayOfWeek17 == calendar.SUNDAY) {
0367: result.append(7);
0368: } else {
0369: result.append(dayOfWeek17 - Calendar.SUNDAY);
0370: }
0371: break;
0372: case 'U': // Week of year (01-52), Sunday is first day.
0373: int weekS = GetWeek(calendar, Calendar.SUNDAY,
0374: false);
0375: result.append((weekS < 10 ? "0" : "") + weekS);
0376: break;
0377: case 'V': // ISO 8601 Week Of Year (01 - 53).
0378: int isoWeek = GetWeek(calendar, Calendar.MONDAY,
0379: true);
0380: result.append((isoWeek < 10 ? "0" : "") + isoWeek);
0381: break;
0382: case 'w': // Weekday number (0 - 6) Sunday = 0.
0383: int dayOfWeek06 = calendar
0384: .get(Calendar.DAY_OF_WEEK);
0385: result.append(dayOfWeek06 - calendar.SUNDAY);
0386: break;
0387: case 'W': // Week of year (01-52), Monday is first day.
0388: int weekM = GetWeek(calendar, Calendar.MONDAY,
0389: false);
0390: result.append((weekM < 10 ? "0" : "") + weekM);
0391: break;
0392: case 'x': // Locale specific date format.
0393: locFmt = (SimpleDateFormat) DateFormat
0394: .getDateInstance(DateFormat.SHORT);
0395: locFmt.setCalendar(calendar);
0396: locFmt.format(date, result, fp);
0397: break;
0398: case 'X': // Locale specific time format.
0399: locFmt = (SimpleDateFormat) DateFormat
0400: .getTimeInstance(DateFormat.SHORT);
0401: locFmt.setCalendar(calendar);
0402: locFmt.format(date, result, fp);
0403: break;
0404: case 'y': // Year without century (00 - 99).
0405: fmt.applyPattern("yy");
0406: fmt.format(date, result, fp);
0407: break;
0408: case 'Y': // Year with century (e.g. 1990)
0409: fmt.applyPattern("yyyy");
0410: fmt.format(date, result, fp);
0411: break;
0412: case 'Z': // Time zone name.
0413: fmt.applyPattern("zzz");
0414: fmt.format(date, result, fp);
0415: break;
0416: default:
0417: result.append(format.charAt(ix));
0418: break;
0419: }
0420: } else {
0421: result.append(format.charAt(ix));
0422: }
0423: }
0424: interp.setResult(result.toString());
0425: }
0426:
0427: /**
0428: *-----------------------------------------------------------------------------
0429: *
0430: * GetWeek --
0431: *
0432: * Returns the week_of_year of the given date.
0433: * The weekday considered as start of the week is given as argument.
0434: * Specify iso as true to get the week_of_year accourding to ISO.
0435: *
0436: * Results:
0437: * Day of the week .
0438: *
0439: * Side effects:
0440: * The interpreter will contain the formatted string as result.
0441: *
0442: *-----------------------------------------------------------------------------
0443: */
0444:
0445: private int GetWeek(Calendar calendar, // Calendar containing Date.
0446: int firstDayOfWeek, // this day starts a week (MONDAY/SUNDAY).
0447: boolean iso) // evaluate according to ISO?
0448: {
0449: if (iso) {
0450: firstDayOfWeek = Calendar.MONDAY;
0451: }
0452:
0453: // After changing the firstDayOfWeek, we have to set the time value anew,
0454: // so that the fields of the calendar are recalculated.
0455:
0456: calendar.setFirstDayOfWeek(firstDayOfWeek);
0457: calendar.setMinimalDaysInFirstWeek(iso ? 4 : 7);
0458: calendar.setTime(calendar.getTime());
0459: int week = calendar.get(Calendar.WEEK_OF_YEAR);
0460:
0461: if (!iso) {
0462: // The week for the first days of the year may be 52 or 53.
0463: // But here we have to return 0, if we don't compute ISO week.
0464: // So any bigger than 50th week in January will become 00.
0465:
0466: if (calendar.get(Calendar.MONTH) == Calendar.JANUARY
0467: && week > 50) {
0468: week = 0;
0469: }
0470: }
0471:
0472: return week;
0473: }
0474:
0475: /**
0476: *-----------------------------------------------------------------------------
0477: *
0478: * SetWeekday --
0479: *
0480: * The date of the given calendar will be incremented, so that it will
0481: * match the weekday in the diff object. If dayOrdinal is bigger than 1,
0482: * additional weeks will be added.
0483: *
0484: * Results:
0485: * None.
0486: *
0487: * Side effects:
0488: * Modifies the given calendar.
0489: *
0490: *-----------------------------------------------------------------------------
0491: */
0492:
0493: private void SetWeekday(Calendar calendar, // Calendar containing Date.
0494: ClockRelTimespan diff) // time difference to evaluate
0495: {
0496: int weekday = diff.getWeekday();
0497: int dayOrdinal = diff.getDayOrdinal();
0498:
0499: while (calendar.get(Calendar.DAY_OF_WEEK) != weekday) {
0500: calendar.add(Calendar.DATE, 1);
0501: }
0502: if (dayOrdinal > 1) {
0503: calendar.add(Calendar.DATE, 7 * (dayOrdinal - 1));
0504: }
0505: }
0506:
0507: /**
0508: *-----------------------------------------------------------------------------
0509: *
0510: * SetOrdMonth --
0511: *
0512: * The date of the given calendar will be incremented, so that it will
0513: * match the ordinal month in the diff object.
0514: *
0515: * Results:
0516: * None.
0517: *
0518: * Side effects:
0519: * Modifies the given calendar.
0520: *
0521: *-----------------------------------------------------------------------------
0522: */
0523:
0524: private void SetOrdMonth(Calendar calendar, // Calendar containing Date.
0525: ClockRelTimespan diff) // time difference to evaluate
0526: {
0527: int month = diff.getMonths();
0528: int ordMonth = diff.getOrdMonth();
0529:
0530: calendar.add(Calendar.MONTH, 1); /* we want to get the next month... */
0531: while (calendar.get(Calendar.MONTH) != month) {
0532: calendar.add(Calendar.MONTH, 1);
0533: }
0534: if (ordMonth > 1) {
0535: calendar.add(Calendar.YEAR, ordMonth - 1);
0536: }
0537: calendar.set(Calendar.DAY_OF_MONTH, 1);
0538: calendar.clear(Calendar.HOUR_OF_DAY);
0539: calendar.clear(Calendar.MINUTE);
0540: calendar.clear(Calendar.SECOND);
0541: }
0542:
0543: /**
0544: *-----------------------------------------------------------------------------
0545: *
0546: * GetDate --
0547: *
0548: * Scan a human readable date string and construct a Date.
0549: *
0550: * Results:
0551: * The scanned date (or null, if an error occured).
0552: *
0553: * Side effects:
0554: * None.
0555: *
0556: *-----------------------------------------------------------------------------
0557: */
0558:
0559: private Date GetDate(String dateString, // Date string to scan
0560: Date baseDate, // Date to use as base
0561: boolean useGMT) // Boolean
0562: {
0563: GregorianCalendar calendar = new GregorianCalendar();
0564: Calendar now = Calendar.getInstance();
0565: now.setTime(baseDate);
0566: calendar.set(now.get(Calendar.YEAR), now.get(Calendar.MONTH),
0567: now.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
0568: if (useGMT) {
0569: calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
0570: }
0571:
0572: ClockToken[] dt = GetTokens(dateString, false);
0573:
0574: ParsePosition parsePos = new ParsePosition(0);
0575: ClockRelTimespan diff = new ClockRelTimespan();
0576: int hasTime = 0;
0577: int hasZone = 0;
0578: int hasDate = 0;
0579: int hasDay = 0;
0580: int hasOrdMonth = 0;
0581: int hasRel = 0;
0582:
0583: while (parsePos.getIndex() < dt.length) {
0584: if (ParseTime(dt, parsePos, calendar)) {
0585: hasTime++;
0586: } else if (ParseZone(dt, parsePos, calendar)) {
0587: hasZone++;
0588: } else if (ParseIso(dt, parsePos, calendar)) {
0589: hasDate++;
0590: } else if (ParseDate(dt, parsePos, calendar)) {
0591: hasDate++;
0592: } else if (ParseDay(dt, parsePos, diff)) {
0593: hasDay++;
0594: } else if (ParseOrdMonth(dt, parsePos, diff)) {
0595: hasOrdMonth++;
0596: } else if (ParseRelSpec(dt, parsePos, diff)) {
0597: hasRel++;
0598: } else if (ParseNumber(dt, parsePos, calendar, hasDate > 0
0599: && hasTime > 0 && hasRel == 0)) {
0600: if (hasDate == 0 || hasTime == 0 || hasRel > 0) {
0601: hasTime++;
0602: }
0603: } else if (ParseTrek(dt, parsePos, calendar)) {
0604: hasDate++;
0605: hasTime++;
0606: } else {
0607: return null;
0608: }
0609: }
0610:
0611: if (hasTime > 1 || hasZone > 1 || hasDate > 1 || hasDay > 1
0612: || hasOrdMonth > 1) {
0613: return null;
0614: }
0615:
0616: // The following line handles years that are specified using
0617: // only two digits. The line of code below implements a policy
0618: // defined by the X/Open workgroup on the millinium rollover.
0619: // Note: some of those dates may not actually be valid on some
0620: // platforms. The POSIX standard startes that the dates 70-99
0621: // shall refer to 1970-1999 and 00-38 shall refer to 2000-2038.
0622: // This later definition should work on all platforms.
0623:
0624: int this Year = calendar.get(Calendar.YEAR);
0625: if (this Year < 100) {
0626: if (this Year >= 69) {
0627: calendar.set(Calendar.YEAR, this Year + 1900);
0628: } else {
0629: calendar.set(Calendar.YEAR, this Year + 2000);
0630: }
0631: }
0632:
0633: if (hasRel > 0) {
0634: if (hasTime == 0 && hasDate == 0 && hasDay == 0) {
0635: calendar.setTime(baseDate);
0636: }
0637: // Certain JDK implementations are buggy WRT DST.
0638: // Work around this issue by adding a day instead
0639: // of a days worth of seconds.
0640: final int seconds_in_day = (60 * 60 * 24);
0641: int seconds = diff.getSeconds();
0642: boolean negative_seconds = (seconds < 0);
0643: int days = 0;
0644: if (negative_seconds)
0645: seconds *= -1;
0646: while (seconds >= seconds_in_day) {
0647: seconds -= seconds_in_day;
0648: days++;
0649: }
0650: if (negative_seconds) {
0651: seconds *= -1;
0652: days *= -1;
0653: }
0654: if (days != 0) {
0655: calendar.add(Calendar.DATE, days);
0656: }
0657: if (seconds != 0) {
0658: calendar.add(Calendar.SECOND, seconds);
0659: }
0660: calendar.add(Calendar.MONTH, diff.getMonths());
0661: }
0662:
0663: if (hasDay > 0 && hasDate == 0) {
0664: SetWeekday(calendar, diff);
0665: }
0666:
0667: if (hasOrdMonth > 0) {
0668: SetOrdMonth(calendar, diff);
0669: }
0670:
0671: return calendar.getTime();
0672: }
0673:
0674: /**
0675: *-----------------------------------------------------------------------------
0676: *
0677: * ParseTime --
0678: *
0679: * Parse a time string and sets the Calendar.
0680: * A time string is valid, if it confirms to the following yacc rule:
0681: * time : tUNUMBER tMERIDIAN
0682: * | tUNUMBER ':' tUNUMBER o_merid
0683: * | tUNUMBER ':' tUNUMBER '-' tUNUMBER
0684: * | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid
0685: * | tUNUMBER ':' tUNUMBER ':' tUNUMBER '-' tUNUMBER
0686: * ;
0687: *
0688: * Results:
0689: * True, if a time was read (parsePos was incremented and calendar
0690: * was set according to the read time); false otherwise.
0691: *
0692: * Side effects:
0693: * None.
0694: *
0695: *-----------------------------------------------------------------------------
0696: */
0697:
0698: private boolean ParseTime(ClockToken[] dt, // Input as scanned array of tokens
0699: ParsePosition parsePos, // Current position in input
0700: Calendar calendar) // calendar object to set
0701: {
0702: int pos = parsePos.getIndex();
0703:
0704: if (pos + 6 < dt.length && dt[pos].isUNumber()
0705: && dt[pos + 1].is(':') && dt[pos + 2].isUNumber()
0706: && dt[pos + 3].is(':') && dt[pos + 4].isUNumber()
0707: && dt[pos + 5].is('-') && dt[pos + 6].isUNumber()) {
0708: ClockToken zone = GetTimeZoneFromRawOffset(-dt[pos + 6]
0709: .getInt() / 100);
0710: if (zone != null) {
0711: calendar.set(Calendar.HOUR_OF_DAY, dt[pos].getInt());
0712: calendar.set(Calendar.MINUTE, dt[pos + 2].getInt());
0713: calendar.set(Calendar.SECOND, dt[pos + 4].getInt());
0714: calendar.setTimeZone(zone.getZone());
0715: parsePos.setIndex(pos + 7);
0716: return true;
0717: }
0718: }
0719: if (pos + 4 < dt.length && dt[pos].isUNumber()
0720: && dt[pos + 1].is(':') && dt[pos + 2].isUNumber()
0721: && dt[pos + 3].is(':') && dt[pos + 4].isUNumber()) {
0722: parsePos.setIndex(pos + 5);
0723: ParseMeridianAndSetHour(dt, parsePos, calendar, dt[pos]
0724: .getInt());
0725: calendar.set(Calendar.MINUTE, dt[pos + 2].getInt());
0726: calendar.set(Calendar.SECOND, dt[pos + 4].getInt());
0727: return true;
0728: }
0729: if (pos + 4 < dt.length && dt[pos].isUNumber()
0730: && dt[pos + 1].is(':') && dt[pos + 2].isUNumber()
0731: && dt[pos + 3].is('-') && dt[pos + 4].isUNumber()) {
0732: ClockToken zone = GetTimeZoneFromRawOffset(-dt[pos + 4]
0733: .getInt() / 100);
0734: if (zone != null) {
0735: calendar.set(Calendar.HOUR_OF_DAY, dt[pos].getInt());
0736: calendar.set(Calendar.MINUTE, dt[pos + 2].getInt());
0737: calendar.setTimeZone(zone.getZone());
0738: parsePos.setIndex(pos + 5);
0739: return true;
0740: }
0741: }
0742: if (pos + 2 < dt.length && dt[pos].isUNumber()
0743: && dt[pos + 1].is(':') && dt[pos + 2].isUNumber()) {
0744: parsePos.setIndex(pos + 3);
0745: ParseMeridianAndSetHour(dt, parsePos, calendar, dt[pos]
0746: .getInt());
0747: calendar.set(Calendar.MINUTE, dt[pos + 2].getInt());
0748: return true;
0749: }
0750: if (pos + 1 < dt.length && dt[pos].isUNumber()
0751: && dt[pos + 1].is(ClockToken.MERIDIAN)) {
0752: parsePos.setIndex(pos + 1);
0753: ParseMeridianAndSetHour(dt, parsePos, calendar, dt[pos]
0754: .getInt());
0755: return true;
0756: }
0757: return false;
0758: }
0759:
0760: /**
0761: *-----------------------------------------------------------------------------
0762: *
0763: * ParseZone --
0764: *
0765: * Parse a timezone string and sets the Calendar.
0766: * A timezone string is valid, if it confirms to the following yacc rule:
0767: * zone : tZONE tDST
0768: * | tZONE
0769: * | tDAYZONE
0770: * ;
0771: *
0772: * Results:
0773: * True, if a timezone was read (parsePos was incremented and calendar
0774: * was set according to the read timezone); false otherwise.
0775: *
0776: * Side effects:
0777: * None.
0778: *
0779: *-----------------------------------------------------------------------------
0780: */
0781:
0782: private boolean ParseZone(ClockToken[] dt, // Input as scanned array of tokens
0783: ParsePosition parsePos, // Current position in input
0784: Calendar calendar) // calendar object to set
0785: {
0786: int pos = parsePos.getIndex();
0787:
0788: if (pos + 1 < dt.length && dt[pos].is(ClockToken.ZONE)
0789: && dt[pos + 1].is(ClockToken.DST)) {
0790: calendar.setTimeZone(dt[pos].getZone());
0791: parsePos.setIndex(pos + 2);
0792: return true;
0793: }
0794: if (pos < dt.length && dt[pos].is(ClockToken.ZONE)) {
0795: calendar.setTimeZone(dt[pos].getZone());
0796: parsePos.setIndex(pos + 1);
0797: return true;
0798: }
0799: if (pos < dt.length && dt[pos].is(ClockToken.DAYZONE)) {
0800: calendar.setTimeZone(dt[pos].getZone());
0801: parsePos.setIndex(pos + 1);
0802: return true;
0803: }
0804: return false;
0805: }
0806:
0807: /**
0808: *-----------------------------------------------------------------------------
0809: *
0810: * ParseDay --
0811: *
0812: * Parse a day string and sets the Calendar.
0813: * A day string is valid, if it confirms to the following yacc rule:
0814: * day : tDAY
0815: * | tDAY ','
0816: * | tUNUMBER tDAY
0817: * | '+' tUNUMBER tDAY
0818: * | '-' tUNUMBER tDAY
0819: * | tNEXT tDAY
0820: * ;
0821: *
0822: * Results:
0823: * True, if a day was read (parsePos was incremented and the time
0824: * difference was set according to the read day); false otherwise.
0825: *
0826: * Side effects:
0827: * None.
0828: *
0829: *-----------------------------------------------------------------------------
0830: */
0831:
0832: private boolean ParseDay(ClockToken[] dt, // Input as scanned array of tokens
0833: ParsePosition parsePos, // Current position in input
0834: ClockRelTimespan diff) // time difference to evaluate
0835: {
0836: int pos = parsePos.getIndex();
0837:
0838: if (pos + 2 < dt.length && dt[pos].is('+')
0839: && dt[pos + 1].isUNumber()
0840: && dt[pos + 2].is(ClockToken.DAY)) {
0841: diff.setWeekday(dt[pos + 2].getInt(), dt[pos + 1].getInt());
0842: parsePos.setIndex(pos + 3);
0843: return true;
0844: }
0845: if (pos + 2 < dt.length && dt[pos].is('-')
0846: && dt[pos + 1].isUNumber()
0847: && dt[pos + 2].is(ClockToken.DAY)) {
0848: diff
0849: .setWeekday(dt[pos + 2].getInt(), -dt[pos + 1]
0850: .getInt());
0851: parsePos.setIndex(pos + 3);
0852: return true;
0853: }
0854: if (pos + 1 < dt.length && dt[pos].is(ClockToken.NEXT)
0855: && dt[pos + 1].is(ClockToken.DAY)) {
0856: diff.setWeekday(dt[pos + 1].getInt(), 2);
0857: parsePos.setIndex(pos + 2);
0858: return true;
0859: }
0860: if (pos + 1 < dt.length && dt[pos].is(ClockToken.DAY)
0861: && dt[pos + 1].is(',')) {
0862: diff.setWeekday(dt[pos].getInt());
0863: parsePos.setIndex(pos + 2);
0864: return true;
0865: }
0866: if (pos + 1 < dt.length && dt[pos].isUNumber()
0867: && dt[pos + 1].is(ClockToken.DAY)) {
0868: diff.setWeekday(dt[pos + 1].getInt(), dt[pos].getInt());
0869: parsePos.setIndex(pos + 2);
0870: return true;
0871: }
0872: if (pos < dt.length && dt[pos].is(ClockToken.DAY)) {
0873: diff.setWeekday(dt[pos].getInt());
0874: parsePos.setIndex(pos + 1);
0875: return true;
0876: }
0877: return false;
0878: }
0879:
0880: /**
0881: *-----------------------------------------------------------------------------
0882: *
0883: * ParseDate --
0884: *
0885: * Parse a date string and sets the Calendar.
0886: * A date string is valid, if it confirms to the following yacc rule:
0887: * date : tUNUMBER '/' tUNUMBER
0888: * | tUNUMBER '/' tUNUMBER '/' tUNUMBER
0889: * | tISOBASE
0890: * | tUNUMBER '-' tMONTH '-' tUNUMBER
0891: * | tUNUMBER '-' tUNUMBER '-' tUNUMBER
0892: * | tMONTH tUNUMBER
0893: * | tMONTH tUNUMBER ',' tUNUMBER
0894: * | tUNUMBER tMONTH
0895: * | tEPOCH
0896: * | tUNUMBER tMONTH tUNUMBER
0897: * ;
0898: *
0899: * Results:
0900: * True, if a date was read (parsePos was incremented and calendar
0901: * was set according to the read day); false otherwise.
0902: *
0903: * Side effects:
0904: * None.
0905: *
0906: *-----------------------------------------------------------------------------
0907: */
0908:
0909: private boolean ParseDate(ClockToken[] dt, // Input as scanned array of tokens
0910: ParsePosition parsePos, // Current position in input
0911: Calendar calendar) // calendar object to set
0912: {
0913: int pos = parsePos.getIndex();
0914:
0915: if (pos + 4 < dt.length && dt[pos].isUNumber()
0916: && dt[pos + 1].is('/') && dt[pos + 2].isUNumber()
0917: && dt[pos + 3].is('/') && dt[pos + 4].isUNumber()) {
0918: calendar.set(Calendar.DAY_OF_MONTH, dt[pos + 2].getInt());
0919: calendar.set(Calendar.MONTH, dt[pos].getInt() - 1);
0920: calendar.set(Calendar.YEAR, dt[pos + 4].getInt());
0921: parsePos.setIndex(pos + 5);
0922: return true;
0923: }
0924: if (pos + 4 < dt.length && dt[pos].isUNumber()
0925: && dt[pos + 1].is('-')
0926: && dt[pos + 2].is(ClockToken.MONTH)
0927: && dt[pos + 3].is('-') && dt[pos + 4].isUNumber()) {
0928: calendar.set(Calendar.YEAR, dt[pos + 4].getInt());
0929: calendar.set(Calendar.MONTH, dt[pos + 2].getInt());
0930: calendar.set(Calendar.DAY_OF_MONTH, dt[pos].getInt());
0931: parsePos.setIndex(pos + 5);
0932: return true;
0933: }
0934: if (pos + 4 < dt.length && dt[pos].isUNumber()
0935: && dt[pos + 1].is('-') && dt[pos + 2].isUNumber()
0936: && dt[pos + 3].is('-') && dt[pos + 4].isUNumber()) {
0937: calendar.set(Calendar.YEAR, dt[pos].getInt());
0938: calendar.set(Calendar.MONTH, dt[pos + 2].getInt() - 1);
0939: calendar.set(Calendar.DAY_OF_MONTH, dt[pos + 4].getInt());
0940: parsePos.setIndex(pos + 5);
0941: return true;
0942: }
0943: if (pos + 3 < dt.length && dt[pos].is(ClockToken.MONTH)
0944: && dt[pos + 1].isUNumber() && dt[pos + 2].is(',')
0945: && dt[pos + 3].isUNumber()) {
0946: calendar.set(Calendar.DAY_OF_MONTH, dt[pos + 1].getInt());
0947: calendar.set(Calendar.MONTH, dt[pos].getInt());
0948: calendar.set(Calendar.YEAR, dt[pos + 3].getInt());
0949: parsePos.setIndex(pos + 4);
0950: return true;
0951: }
0952: if (pos + 2 < dt.length && dt[pos].isUNumber()
0953: && dt[pos + 1].is('/') && dt[pos + 2].isUNumber()) {
0954: calendar.set(Calendar.DAY_OF_MONTH, dt[pos + 2].getInt());
0955: calendar.set(Calendar.MONTH, dt[pos].getInt() - 1);
0956: parsePos.setIndex(pos + 3);
0957: return true;
0958: }
0959: if (pos + 2 < dt.length && dt[pos].isUNumber()
0960: && dt[pos + 1].is(ClockToken.MONTH)
0961: && dt[pos + 2].isUNumber()) {
0962: calendar.set(Calendar.DAY_OF_MONTH, dt[pos].getInt());
0963: calendar.set(Calendar.MONTH, dt[pos + 1].getInt());
0964: calendar.set(Calendar.YEAR, dt[pos + 2].getInt());
0965: parsePos.setIndex(pos + 3);
0966: return true;
0967: }
0968: if (pos + 1 < dt.length && dt[pos].is(ClockToken.MONTH)
0969: && dt[pos + 1].isUNumber()) {
0970: calendar.set(Calendar.DAY_OF_MONTH, dt[pos + 1].getInt());
0971: calendar.set(Calendar.MONTH, dt[pos].getInt());
0972: parsePos.setIndex(pos + 2);
0973: return true;
0974: }
0975: if (pos + 1 < dt.length && dt[pos].isUNumber()
0976: && dt[pos + 1].is(ClockToken.MONTH)) {
0977: calendar.set(Calendar.DAY_OF_MONTH, dt[pos].getInt());
0978: calendar.set(Calendar.MONTH, dt[pos + 1].getInt());
0979: parsePos.setIndex(pos + 2);
0980: return true;
0981: }
0982: if (pos < dt.length && dt[pos].isIsoBase()) {
0983: calendar.set(Calendar.DAY_OF_MONTH, dt[pos].getInt() % 100);
0984: calendar.set(Calendar.MONTH,
0985: (dt[pos].getInt() % 10000) / 100 - 1);
0986: calendar.set(Calendar.YEAR, dt[pos].getInt() / 10000);
0987: parsePos.setIndex(pos + 1);
0988: return true;
0989: }
0990: if (pos < dt.length && dt[pos].is(ClockToken.EPOCH)) {
0991: calendar.set(Calendar.DAY_OF_MONTH, 1);
0992: calendar.set(Calendar.MONTH, 0);
0993: calendar.set(Calendar.YEAR, EPOCH_YEAR);
0994: parsePos.setIndex(pos + 1);
0995: return true;
0996: }
0997: return false;
0998: }
0999:
1000: /**
1001: *-----------------------------------------------------------------------------
1002: *
1003: * ParseNumber --
1004: *
1005: * Parse a number and sets the Calendar.
1006: * If argument mayBeYear is true, this number is conidered as year,
1007: * otherwise it is date and time in the form HHMM.
1008: *
1009: * Results:
1010: * True, if a number was read (parsePos was incremented and calendar
1011: * was set according to the read day); false otherwise.
1012: *
1013: * Side effects:
1014: * None.
1015: *
1016: *-----------------------------------------------------------------------------
1017: */
1018:
1019: private boolean ParseNumber(ClockToken[] dt, // Input as scanned array of tokens
1020: ParsePosition parsePos, // Current position in input
1021: Calendar calendar, // calendar object to set
1022: boolean mayBeYear) // number is considered to be year?
1023: {
1024: int pos = parsePos.getIndex();
1025:
1026: if (pos < dt.length && dt[pos].isUNumber()) {
1027: parsePos.setIndex(pos + 1);
1028: if (mayBeYear) {
1029: calendar.set(Calendar.YEAR, dt[pos].getInt());
1030: } else {
1031: calendar.set(Calendar.HOUR_OF_DAY,
1032: dt[pos].getInt() / 100);
1033: calendar.set(Calendar.MINUTE, dt[pos].getInt() % 100);
1034: calendar.set(Calendar.SECOND, 0);
1035: }
1036: return true;
1037: }
1038: return false;
1039: }
1040:
1041: /**
1042: *-----------------------------------------------------------------------------
1043: *
1044: * ParseRelSpec --
1045: *
1046: * Parse a relative time specification and sets the time difference.
1047: * A relative time specification is valid, if it confirms to the
1048: * following yacc rule:
1049: * relspec : relunits tAGO
1050: * | relunits
1051: * ;
1052: *
1053: * Results:
1054: * True, if a relative time specification was read (parsePos was
1055: * incremented and the time difference was set according to the read
1056: * relative time specification); false otherwise.
1057: *
1058: * Side effects:
1059: * None.
1060: *
1061: *-----------------------------------------------------------------------------
1062: */
1063:
1064: private boolean ParseRelSpec(ClockToken[] dt, // Input as scanned array of tokens
1065: ParsePosition parsePos, // Current position in input
1066: ClockRelTimespan diff) // time difference to evaluate
1067: {
1068: if (!ParseRelUnits(dt, parsePos, diff)) {
1069: return false;
1070: }
1071:
1072: int pos = parsePos.getIndex();
1073: if (pos < dt.length && dt[pos].is(ClockToken.AGO)) {
1074: diff.negate();
1075: parsePos.setIndex(pos + 1);
1076: }
1077: return true;
1078: }
1079:
1080: /**
1081: *-----------------------------------------------------------------------------
1082: *
1083: * ParseRelUnits --
1084: *
1085: * Parse a relative time unit and sets the time difference.
1086: * A relative time unit is valid, if it confirms to the
1087: * following yacc rule:
1088: * relspec : '+' tUNUMBER unit
1089: * | '-' tUNUMBER unit
1090: * | tUNUMBER unit
1091: * | tNEXT unit
1092: * | tNEXT tUNUMBER unit
1093: * | unit
1094: * ;
1095: *
1096: * Results:
1097: * True, if a relative time specification was read (parsePos was
1098: * incremented and the time difference was set according to the read
1099: * relative time specification); false otherwise.
1100: *
1101: * Side effects:
1102: * None.
1103: *
1104: *-----------------------------------------------------------------------------
1105: */
1106:
1107: private boolean ParseRelUnits(ClockToken[] dt, // Input as scanned array of tokens
1108: ParsePosition parsePos, // Current position in input
1109: ClockRelTimespan diff) // time difference to evaluate
1110: {
1111: int pos = parsePos.getIndex();
1112:
1113: if (pos + 2 < dt.length && dt[pos].is('+')
1114: && dt[pos + 1].isUNumber() && dt[pos + 2].isUnit()) {
1115: diff.addUnit(dt[pos + 2], dt[pos + 1].getInt());
1116: parsePos.setIndex(pos + 3);
1117: return true;
1118: }
1119: if (pos + 2 < dt.length && dt[pos].is('-')
1120: && dt[pos + 1].isUNumber() && dt[pos + 2].isUnit()) {
1121: diff.addUnit(dt[pos + 2], -dt[pos + 1].getInt());
1122: parsePos.setIndex(pos + 3);
1123: return true;
1124: }
1125: if (pos + 1 < dt.length && dt[pos].isUNumber()
1126: && dt[pos + 1].isUnit()) {
1127: diff.addUnit(dt[pos + 1], dt[pos].getInt());
1128: parsePos.setIndex(pos + 2);
1129: return true;
1130: } else if (pos + 2 < dt.length && dt[pos].is(ClockToken.NEXT)
1131: && dt[pos + 1].isUNumber() && dt[pos + 2].isUnit()) {
1132: diff.addUnit(dt[pos + 2], dt[pos + 1].getInt());
1133: parsePos.setIndex(pos + 3);
1134: return true;
1135: }
1136: if (pos + 1 < dt.length && dt[pos].is(ClockToken.NEXT)
1137: && dt[pos + 1].isUnit()) {
1138: diff.addUnit(dt[pos + 1]);
1139: parsePos.setIndex(pos + 2);
1140: return true;
1141: }
1142: if (pos < dt.length && dt[pos].isUnit()) {
1143: diff.addUnit(dt[pos]);
1144: parsePos.setIndex(pos + 1);
1145: return true;
1146: }
1147: return false;
1148: }
1149:
1150: /**
1151: *-----------------------------------------------------------------------------
1152: *
1153: * ParseOrdMonth --
1154: *
1155: * Parse a relative month and sets the date difference.
1156: * A relative month is valid, if it confirms to the
1157: * following yacc rule:
1158: * ordMonth: tNEXT tMONTH
1159: * | tNEXT tUNUMBER tMONTH
1160: * ;
1161: *
1162: * Results:
1163: * True, if a relative month was read (parsePos was incremented and
1164: * the time difference was set according to the read relative time unit);
1165: * false otherwise.
1166: *
1167: * Side effects:
1168: * None.
1169: *
1170: *-----------------------------------------------------------------------------
1171: */
1172:
1173: private boolean ParseOrdMonth(ClockToken[] dt, // Input as scanned array of tokens
1174: ParsePosition parsePos, // Current position in input
1175: ClockRelTimespan diff) // time difference to evaluate
1176: {
1177: int pos = parsePos.getIndex();
1178:
1179: if (pos + 2 < dt.length && dt[pos].is(ClockToken.NEXT)
1180: && dt[pos + 1].isUNumber()
1181: && dt[pos + 2].is(ClockToken.MONTH)) {
1182: diff
1183: .addOrdMonth(dt[pos + 2].getInt(), dt[pos + 1]
1184: .getInt());
1185: parsePos.setIndex(pos + 3);
1186: return true;
1187: }
1188: if (pos + 1 < dt.length && dt[pos].is(ClockToken.NEXT)
1189: && dt[pos + 1].is(ClockToken.MONTH)) {
1190: diff.addOrdMonth(dt[pos + 1].getInt(), 1);
1191: parsePos.setIndex(pos + 2);
1192: return true;
1193: }
1194: return false;
1195: }
1196:
1197: /**
1198: *-----------------------------------------------------------------------------
1199: *
1200: * ParseIso --
1201: *
1202: * Parse an ISO 8601 point in time and sets the Calendar.
1203: * An ISO 8601 point in time is valid, if it confirms to the
1204: * following yacc rule:
1205: * iso : tISOBASE tZONE tISOBASE
1206: * | tISOBASE tZONE tUNUMBER ':' tUNUMBER ':' tUNUMBER
1207: * | tISOBASE tISOBASE
1208: * ;
1209: *
1210: * Results:
1211: * True, if an ISO 8601 point in time was read (parsePos was incremented
1212: * and calendar was set according to the read point); false otherwise.
1213: *
1214: * Side effects:
1215: * None.
1216: *
1217: *-----------------------------------------------------------------------------
1218: */
1219:
1220: private boolean ParseIso(ClockToken[] dt, // Input as scanned array of tokens
1221: ParsePosition parsePos, // Current position in input
1222: Calendar calendar) // calendar object to set
1223: {
1224: int pos = parsePos.getIndex();
1225:
1226: if (pos + 6 < dt.length && dt[pos].isIsoBase()
1227: && dt[pos + 1].is(ClockToken.ZONE)
1228: && dt[pos + 2].isUNumber() && dt[pos + 3].is(':')
1229: && dt[pos + 4].isUNumber() && dt[pos + 5].is(':')
1230: && dt[pos + 6].isUNumber()) {
1231: calendar.set(Calendar.DAY_OF_MONTH, dt[pos].getInt() % 100);
1232: calendar.set(Calendar.MONTH,
1233: (dt[pos].getInt() % 10000) / 100 - 1);
1234: calendar.set(Calendar.YEAR, dt[pos].getInt() / 10000);
1235: calendar.set(Calendar.HOUR_OF_DAY, dt[pos + 2].getInt());
1236: calendar.set(Calendar.MINUTE, dt[pos + 4].getInt());
1237: calendar.set(Calendar.SECOND, dt[pos + 6].getInt());
1238: parsePos.setIndex(pos + 7);
1239: return true;
1240: }
1241: if (pos + 2 < dt.length
1242: && dt[pos].isIsoBase()
1243: && dt[pos + 1].is(ClockToken.ZONE)
1244: && dt[pos + 1].getZone().getRawOffset() == -7
1245: * MILLIS_PER_HOUR && dt[pos + 2].isIsoBase()) {
1246: calendar.set(Calendar.DAY_OF_MONTH, dt[pos].getInt() % 100);
1247: calendar.set(Calendar.MONTH,
1248: (dt[pos].getInt() % 10000) / 100 - 1);
1249: calendar.set(Calendar.YEAR, dt[pos].getInt() / 10000);
1250: calendar.set(Calendar.HOUR_OF_DAY,
1251: dt[pos + 2].getInt() / 10000);
1252: calendar.set(Calendar.MINUTE,
1253: (dt[pos + 2].getInt() % 10000) / 100);
1254: calendar.set(Calendar.SECOND, dt[pos + 2].getInt() % 100);
1255: parsePos.setIndex(pos + 3);
1256: return true;
1257: }
1258: if (pos + 1 < dt.length && dt[pos].isIsoBase()
1259: && dt[pos + 1].isIsoBase()) {
1260: calendar.set(Calendar.DAY_OF_MONTH, dt[pos].getInt() % 100);
1261: calendar.set(Calendar.MONTH,
1262: (dt[pos].getInt() % 10000) / 100 - 1);
1263: calendar.set(Calendar.YEAR, dt[pos].getInt() / 10000);
1264: calendar.set(Calendar.HOUR_OF_DAY,
1265: dt[pos + 1].getInt() / 10000);
1266: calendar.set(Calendar.MINUTE,
1267: (dt[pos + 1].getInt() % 10000) / 100);
1268: calendar.set(Calendar.SECOND, dt[pos + 1].getInt() % 100);
1269: parsePos.setIndex(pos + 2);
1270: return true;
1271: }
1272: return false;
1273: }
1274:
1275: /**
1276: *-----------------------------------------------------------------------------
1277: *
1278: * ParseTrek --
1279: *
1280: * Parse a Stardate and sets the Calendar.
1281: * A Stardate is valid, if it confirms to the following yacc rule:
1282: * iso : tSTARDATE tUNUMBER '.' tUNUMBER
1283: * ;
1284: *
1285: * Results:
1286: * True, if a Stardate was read (parsePos was incremented
1287: * and calendar was set according to the read point); false otherwise.
1288: *
1289: * Side effects:
1290: * None.
1291: *
1292: *-----------------------------------------------------------------------------
1293: */
1294:
1295: private boolean ParseTrek(ClockToken[] dt, // Input as scanned array of tokens
1296: ParsePosition parsePos, // Current position in input
1297: GregorianCalendar calendar) // calendar object to set
1298: {
1299: int pos = parsePos.getIndex();
1300:
1301: if (pos + 3 < dt.length && dt[pos].is(ClockToken.STARDATE)
1302: && dt[pos + 1].isUNumber() && dt[pos + 2].is('.')
1303: && dt[pos + 3].isUNumber()) {
1304: int trekYear = dt[pos + 1].getInt() / 1000 + 2323 - 377;
1305: int trekDay = 1 + ((dt[pos + 1].getInt() % 1000) * (calendar
1306: .isLeapYear(trekYear) ? 366 : 365)) / 1000;
1307: int trekSeconds = dt[pos + 3].getInt() * 144 * 60;
1308: calendar.set(Calendar.YEAR, trekYear);
1309: calendar.set(Calendar.DAY_OF_YEAR, trekDay);
1310: calendar.set(Calendar.SECOND, trekSeconds);
1311: parsePos.setIndex(pos + 4);
1312: return true;
1313: }
1314: return false;
1315: }
1316:
1317: /**
1318: *-----------------------------------------------------------------------------
1319: *
1320: * ParseMeridianAndSetHour --
1321: *
1322: * Parse a meridian and sets the hour field of the calendar.
1323: * A meridian is valid, if it confirms to the following yacc rule:
1324: * o_merid : // NULL
1325: * | tMERIDIAN
1326: * ;
1327: *
1328: * Results:
1329: * None; parsePos was incremented and the claendar was set according
1330: * to the read meridian.
1331: *
1332: * Side effects:
1333: * None.
1334: *
1335: *-----------------------------------------------------------------------------
1336: */
1337:
1338: private void ParseMeridianAndSetHour(ClockToken[] dt, // Input as scanned array of tokens
1339: ParsePosition parsePos, // Current position in input
1340: Calendar calendar, // calendar object to set
1341: int hour) // hour value (1-12 or 0-23) to set.
1342: {
1343: int pos = parsePos.getIndex();
1344: int hourField;
1345:
1346: if (pos < dt.length && dt[pos].is(ClockToken.MERIDIAN)) {
1347: calendar.set(Calendar.AM_PM, dt[pos].getInt());
1348: parsePos.setIndex(pos + 1);
1349: hourField = Calendar.HOUR;
1350: } else {
1351: hourField = Calendar.HOUR_OF_DAY;
1352: }
1353:
1354: if (hourField == Calendar.HOUR && hour == 12) {
1355: hour = 0;
1356: }
1357: calendar.set(hourField, hour);
1358: }
1359:
1360: /**
1361: *-----------------------------------------------------------------------------
1362: *
1363: * GetTokens --
1364: *
1365: * Lexical analysis of the input string.
1366: *
1367: * Results:
1368: * An array of ClockToken, representing the input string.
1369: *
1370: * Side effects:
1371: * None.
1372: *
1373: *-----------------------------------------------------------------------------
1374: */
1375:
1376: private ClockToken[] GetTokens(String in, // String to parse
1377: boolean debug) // Send the generated token list to stderr?
1378: {
1379: ParsePosition parsePos = new ParsePosition(0);
1380: ClockToken dt;
1381: ArrayList tokens = new ArrayList(in.length());
1382:
1383: while ((dt = GetNextToken(in, parsePos)) != null) {
1384: tokens.add(dt);
1385: }
1386:
1387: ClockToken[] tokenArray = { (ClockToken) null };
1388: tokenArray = (ClockToken[]) tokens.toArray(tokenArray);
1389:
1390: if (debug) {
1391: for (int ix = 0; ix < tokenArray.length; ix++) {
1392: if (ix != 0) {
1393: System.err.print(",");
1394: }
1395: System.err.print(tokenArray[ix].toString());
1396: }
1397: System.err.println("");
1398: }
1399:
1400: return tokenArray;
1401: }
1402:
1403: /**
1404: *-----------------------------------------------------------------------------
1405: *
1406: * GetNextToken --
1407: *
1408: * Lexical analysis of the next token of input string.
1409: *
1410: * Results:
1411: * A ClockToken representing the next token of the input string,
1412: * (parsePos was incremented accordingly), if one was found.
1413: * null otherwise (e.g. at end of input).
1414: *
1415: * Side effects:
1416: * None.
1417: *
1418: *-----------------------------------------------------------------------------
1419: */
1420:
1421: private ClockToken GetNextToken(String in, // String to parse
1422: ParsePosition parsePos) // Current position in input
1423: {
1424: int pos = parsePos.getIndex();
1425: int sign;
1426:
1427: while (true) {
1428: while (pos < in.length()
1429: && Character.isSpaceChar(in.charAt(pos))) {
1430: pos++;
1431: }
1432: if (pos >= in.length()) {
1433: break;
1434: }
1435:
1436: char c = in.charAt(pos);
1437: if (Character.isDigit(c)) {
1438: int number = 0;
1439: int count = 0;
1440: while (pos < in.length()
1441: && Character.isDigit(c = in.charAt(pos))) {
1442: number = 10 * number + c - '0';
1443: pos++;
1444: count++;
1445: }
1446: parsePos.setIndex(pos);
1447: return new ClockToken(number, count >= 6);
1448: }
1449: if (Character.isLetter(c)) {
1450: int beginPos = pos;
1451: while (++pos < in.length()) {
1452: c = in.charAt(pos);
1453: if (!Character.isLetter(c) && c != '.') {
1454: break;
1455: }
1456: }
1457: parsePos.setIndex(pos);
1458: return LookupWord(in.substring(beginPos, pos));
1459: }
1460: parsePos.setIndex(pos + 1);
1461: return new ClockToken(in.charAt(pos));
1462: }
1463: parsePos.setIndex(pos + 1);
1464: return null;
1465: }
1466:
1467: /**
1468: *-----------------------------------------------------------------------------
1469: *
1470: * LookupWord --
1471: *
1472: * Construct a ClockToken for the given word.
1473: *
1474: * Results:
1475: * A ClockToken representing the given word.
1476: *
1477: * Side effects:
1478: * None.
1479: *
1480: *-----------------------------------------------------------------------------
1481: */
1482:
1483: private ClockToken LookupWord(String word) // word to lookup
1484: {
1485: int ix;
1486: String[] names;
1487: String[][] zones;
1488:
1489: if (word.equalsIgnoreCase("am")
1490: || word.equalsIgnoreCase("a.m.")) {
1491: return new ClockToken(ClockToken.MERIDIAN, Calendar.AM);
1492: }
1493: if (word.equalsIgnoreCase("pm")
1494: || word.equalsIgnoreCase("p.m.")) {
1495: return new ClockToken(ClockToken.MERIDIAN, Calendar.PM);
1496: }
1497:
1498: // See if we have an abbreviation for a day or month.
1499:
1500: boolean abbrev;
1501: if (word.length() == 3) {
1502: abbrev = true;
1503: } else if (word.length() == 4 && word.charAt(3) == '.') {
1504: abbrev = true;
1505: word = word.substring(0, 3);
1506: } else {
1507: abbrev = false;
1508: }
1509:
1510: DateFormatSymbols symbols = new DateFormatSymbols(Locale.US);
1511: if (abbrev) {
1512: names = symbols.getShortMonths();
1513: } else {
1514: names = symbols.getMonths();
1515: }
1516: for (ix = 0; ix < names.length; ix++) {
1517: if (word.equalsIgnoreCase(names[ix])) {
1518: return new ClockToken(ClockToken.MONTH, ix);
1519: }
1520: }
1521: if (abbrev) {
1522: names = symbols.getShortWeekdays();
1523: } else {
1524: names = symbols.getWeekdays();
1525: }
1526: for (ix = 0; ix < names.length; ix++) {
1527: if (word.equalsIgnoreCase(names[ix])) {
1528: return new ClockToken(ClockToken.DAY, ix);
1529: }
1530: }
1531:
1532: // Drop out any periods and try the timezone table.
1533:
1534: StringBuffer withoutDotsBuf = new StringBuffer(word.length());
1535: for (ix = 0; ix < word.length(); ix++) {
1536: if (word.charAt(ix) != '.') {
1537: withoutDotsBuf.append(word.charAt(ix));
1538: }
1539: }
1540:
1541: String withoutDots = new String(withoutDotsBuf);
1542: zones = symbols.getZoneStrings();
1543:
1544: for (ix = 0; ix < zones.length; ix++) {
1545: if (withoutDots.equalsIgnoreCase(zones[ix][2])
1546: || withoutDots.equalsIgnoreCase(zones[ix][4])) {
1547: TimeZone zone = TimeZone.getTimeZone(zones[ix][0]);
1548: return new ClockToken(ClockToken.ZONE, zone);
1549: }
1550: }
1551: if (withoutDots.equalsIgnoreCase("dst")) {
1552: return new ClockToken(ClockToken.DST, null);
1553: }
1554:
1555: // Strip off any plural and try the units.
1556:
1557: String singular;
1558: if (word.endsWith("s")) {
1559: singular = word.substring(0, word.length() - 1);
1560: } else {
1561: singular = word;
1562: }
1563: if (singular.equalsIgnoreCase("year")) {
1564: return new ClockToken(ClockToken.MONTH_UNIT, 12);
1565: } else if (singular.equalsIgnoreCase("month")) {
1566: return new ClockToken(ClockToken.MONTH_UNIT, 1);
1567: } else if (singular.equalsIgnoreCase("fortnight")) {
1568: return new ClockToken(ClockToken.MINUTE_UNIT, 14 * 24 * 60);
1569: } else if (singular.equalsIgnoreCase("week")) {
1570: return new ClockToken(ClockToken.MINUTE_UNIT, 7 * 24 * 60);
1571: } else if (singular.equalsIgnoreCase("day")) {
1572: return new ClockToken(ClockToken.MINUTE_UNIT, 24 * 60);
1573: } else if (singular.equalsIgnoreCase("hour")) {
1574: return new ClockToken(ClockToken.MINUTE_UNIT, 60);
1575: } else if (singular.equalsIgnoreCase("minute")) {
1576: return new ClockToken(ClockToken.MINUTE_UNIT, 1);
1577: } else if (singular.equalsIgnoreCase("min")) {
1578: return new ClockToken(ClockToken.MINUTE_UNIT, 1);
1579: } else if (singular.equalsIgnoreCase("second")) {
1580: return new ClockToken(ClockToken.SEC_UNIT, 1);
1581: } else if (singular.equalsIgnoreCase("sec")) {
1582: return new ClockToken(ClockToken.SEC_UNIT, 1);
1583: }
1584:
1585: if (singular.equalsIgnoreCase("tomorrow")) {
1586: return new ClockToken(ClockToken.MINUTE_UNIT, 1 * 24 * 60);
1587: } else if (singular.equalsIgnoreCase("yesterday")) {
1588: return new ClockToken(ClockToken.MINUTE_UNIT, -1 * 24 * 60);
1589: } else if (singular.equalsIgnoreCase("today")) {
1590: return new ClockToken(ClockToken.MINUTE_UNIT, 0);
1591: } else if (singular.equalsIgnoreCase("now")) {
1592: return new ClockToken(ClockToken.MINUTE_UNIT, 0);
1593: } else if (singular.equalsIgnoreCase("last")) {
1594: return new ClockToken(-1, false);
1595: } else if (singular.equalsIgnoreCase("this")) {
1596: return new ClockToken(ClockToken.MINUTE_UNIT, 0);
1597: } else if (singular.equalsIgnoreCase("next")) {
1598: return new ClockToken(ClockToken.NEXT, 1);
1599: } else if (singular.equalsIgnoreCase("ago")) {
1600: return new ClockToken(ClockToken.AGO, 1);
1601: } else if (singular.equalsIgnoreCase("epoch")) {
1602: return new ClockToken(ClockToken.EPOCH, 0);
1603: } else if (singular.equalsIgnoreCase("stardate")) {
1604: return new ClockToken(ClockToken.STARDATE, 0);
1605: }
1606:
1607: // Since a military timezone (T) is used in the clock test of 8.3,
1608: // we can't ignore these timezones any longer...
1609:
1610: if (withoutDots.length() == 1) {
1611: int rawOffset = 0;
1612: boolean found = true;
1613: char milTz = Character.toLowerCase(withoutDots.charAt(0));
1614:
1615: if (milTz >= 'a' && milTz <= 'm') {
1616: rawOffset = milTz - 'a' + 1;
1617: } else if (milTz >= 'n' && milTz < 'z') {
1618: rawOffset = 'n' - milTz - 1;
1619: } else if (milTz != 'z') {
1620: found = false;
1621: }
1622: if (found) {
1623: ClockToken zone = GetTimeZoneFromRawOffset(rawOffset);
1624: if (zone != null) {
1625: return zone;
1626: }
1627: }
1628: }
1629:
1630: return new ClockToken(word);
1631: }
1632:
1633: /**
1634: *-----------------------------------------------------------------------------
1635: *
1636: * GetTimeZoneFromRawOffset --
1637: *
1638: * Look for a timezone with the given offset (in hours) from gmt.
1639: *
1640: * Results:
1641: * A ClockToken representing the specified timezone.
1642: *
1643: * Side effects:
1644: * None.
1645: *
1646: *-----------------------------------------------------------------------------
1647: */
1648:
1649: private ClockToken GetTimeZoneFromRawOffset(int rawOffset // an offset to GMT (in hours).
1650: ) {
1651: String tzNames[] = TimeZone.getAvailableIDs(rawOffset
1652: * MILLIS_PER_HOUR);
1653:
1654: if (tzNames.length > 0) {
1655: TimeZone zone = TimeZone.getTimeZone(tzNames[0]);
1656: return new ClockToken(ClockToken.ZONE, zone);
1657: }
1658: return null;
1659: }
1660:
1661: } // end ClockCmd
1662:
1663: /**
1664: *-----------------------------------------------------------------------------
1665: *
1666: * CLASS ClockToken --
1667: *
1668: * An object of this class represents a lexical unit of the human
1669: * readable date string. It can be one of the following variants:
1670: *
1671: * - unsigned number,
1672: * = occurence can be asked by isUNumber(),
1673: * = value can be retrieved by means of getInt();
1674: * - iso base date,
1675: * = occurence can be asked by isIsoBase(),
1676: * = value can be retrieved by means of getInt();
1677: * - a single character (delimiters like ':' or '/'),
1678: * = occurence can be asked by is(), e.g. is('/');
1679: * - a word (like "January" or "DST")
1680: * = occurence can be asked by is(), e.g. is(ClockToken.AGO);
1681: * = value can be retrieved by means of getInt() or getZone().
1682: *
1683: *-----------------------------------------------------------------------------
1684: */
1685:
1686: class ClockToken {
1687: final static int ISOBASE = 1;
1688: final static int UNUMBER = 2;
1689: final static int WORD = 3;
1690: final static int CHAR = 4;
1691: final static int MONTH = 5;
1692: final static int DAY = 6;
1693: final static int MONTH_UNIT = 7;
1694: final static int MINUTE_UNIT = 8;
1695: final static int SEC_UNIT = 9;
1696: final static int AGO = 10;
1697: final static int EPOCH = 11;
1698: final static int ZONE = 12;
1699: final static int DAYZONE = 13;
1700: final static int DST = 14;
1701: final static int MERIDIAN = 15;
1702: final static int NEXT = 16;
1703: final static int STARDATE = 17;
1704:
1705: ClockToken(int number, boolean isIsoBase) {
1706: this .kind = isIsoBase ? ISOBASE : UNUMBER;
1707: this .number = number;
1708: }
1709:
1710: ClockToken(int kind, int number) {
1711: this .kind = kind;
1712: this .number = number;
1713: }
1714:
1715: ClockToken(int kind, TimeZone zone) {
1716: this .kind = kind;
1717: this .zone = zone;
1718: }
1719:
1720: ClockToken(String word) {
1721: this .kind = WORD;
1722: this .word = word;
1723: }
1724:
1725: ClockToken(char c) {
1726: this .kind = CHAR;
1727: this .c = c;
1728: }
1729:
1730: public boolean isUNumber() {
1731: return kind == UNUMBER;
1732: }
1733:
1734: public boolean isIsoBase() {
1735: return kind == ISOBASE;
1736: }
1737:
1738: public boolean is(char c) {
1739: return this .kind == CHAR && this .c == c;
1740: }
1741:
1742: public boolean is(int kind) {
1743: return this .kind == kind;
1744: }
1745:
1746: public boolean isUnit() {
1747: return kind == MINUTE_UNIT || kind == MONTH_UNIT
1748: || kind == SEC_UNIT;
1749: }
1750:
1751: int getInt() {
1752: return number;
1753: }
1754:
1755: TimeZone getZone() {
1756: return zone;
1757: }
1758:
1759: public String toString() {
1760: if (isUNumber()) {
1761: return "U" + Integer.toString(getInt());
1762: } else if (isIsoBase()) {
1763: return "I" + Integer.toString(getInt());
1764: } else if (kind == WORD) {
1765: return word;
1766: } else if (kind == CHAR) {
1767: return new Character(c).toString();
1768: } else if (kind == ZONE || kind == DAYZONE) {
1769: return zone.getID();
1770: } else {
1771: return "(" + kind + "," + getInt() + ")";
1772: }
1773: }
1774:
1775: private int kind;
1776: private int number;
1777: private String word;
1778: private char c;
1779: private TimeZone zone;
1780: } // end ClockToken
1781:
1782: /**
1783: *-----------------------------------------------------------------------------
1784: *
1785: * CLASS ClockRelTimespan --
1786: *
1787: * An object of this class can be used to track the time difference during
1788: * the analysis of a relative time specification.
1789: *
1790: * It has four read only properties seconds, months, weekday and dayOrdinal,
1791: * which are set to 0 during initialization and which can be modified by
1792: * means of the addSeconds(), addMonths(), setWeekday() and negate() methods.
1793: *
1794: *-----------------------------------------------------------------------------
1795: */
1796:
1797: class ClockRelTimespan {
1798: ClockRelTimespan() {
1799: seconds = 0;
1800: months = 0;
1801: ordMonth = 0;
1802: weekday = 0;
1803: dayOrdinal = 0;
1804: }
1805:
1806: void addSeconds(int s) {
1807: seconds += s;
1808: }
1809:
1810: void addMonths(int m) {
1811: months += m;
1812: }
1813:
1814: void addOrdMonth(int m, int c) {
1815: months = m;
1816: ordMonth += c;
1817: }
1818:
1819: void addUnit(ClockToken unit, int amount) {
1820: if (unit.is(ClockToken.SEC_UNIT)) {
1821: addSeconds(unit.getInt() * amount);
1822: } else if (unit.is(ClockToken.MINUTE_UNIT)) {
1823: addSeconds(unit.getInt() * 60 * amount);
1824: } else if (unit.is(ClockToken.MONTH_UNIT)) {
1825: addMonths(unit.getInt() * amount);
1826: }
1827: }
1828:
1829: void addUnit(ClockToken unit) {
1830: addUnit(unit, 1);
1831: }
1832:
1833: void setWeekday(int w, int ord) {
1834: weekday = w;
1835: dayOrdinal = ord;
1836: }
1837:
1838: void setWeekday(int w) {
1839: setWeekday(w, 1);
1840: }
1841:
1842: void negate() {
1843: seconds = -seconds;
1844: months = -months;
1845: }
1846:
1847: int getSeconds() {
1848: return seconds;
1849: }
1850:
1851: int getMonths() {
1852: return months;
1853: }
1854:
1855: int getOrdMonth() {
1856: return ordMonth;
1857: }
1858:
1859: int getWeekday() {
1860: return weekday;
1861: }
1862:
1863: int getDayOrdinal() {
1864: return dayOrdinal;
1865: }
1866:
1867: private int seconds;
1868: private int months;
1869: private int ordMonth;
1870: private int weekday;
1871: private int dayOrdinal;
1872: }
|