content: /*
   * Copyright (c) 2010 Pentaho Corporation.  All rights reserved. 
   * This software was developed by Pentaho Corporation and is provided under the terms 
   * of the GNU Lesser General Public License, Version 2.1. You may not use 
   * this file except in compliance with the license. If you need a copy of the license, 
   * please go to http://www.gnu.org/licenses/lgpl-2.1.txt. The Original Code is Time Series 
   * Forecasting.  The Initial Developer is Pentaho Corporation.
   *
   * Software distributed under the GNU Lesser Public License is distributed on an "AS IS" 
   * basis, WITHOUT WARRANTY OF ANY KIND, either express or  implied. Please refer to 
   * the license for the specific language governing your rights and limitations.
   */
  
  /*
   *    CustomPeriodicTest.java
   *    Copyright (C) 2011 Pentaho Corporation
   */
  
  package weka.classifiers.timeseries.core;
  
  import java.io.Serializable;
  import java.util.Calendar;
  import java.util.Date;
  import java.util.GregorianCalendar;
  
  /**
   * Class that evaluates a supplied date against user-specified
   * date constant fields. Fields that can be tested against include
   * year, month, week of year, week of month, day of year, day of month,
   * day of week, hour of day, minute of hour and second. Wildcard "*"
   * matches any value for a particular field. Each CustomPeriodicTest
   * is made up of one or two test parts. If the first test part's operator
   * is "=", then no second part is necessary. Otherwise the first test part
   * may use > or >= operators and the second test part < or <= operators.
   * Taken together, the two parts define an interval. An optional label
   * may be associated with the interval.
   * 
   * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
   * @version $Revision: 49983 $
   */
  public class CustomPeriodicTest implements Serializable {
    
    /**
     * For serialization
     */
    private static final long serialVersionUID = -1470297629040276248L;  
    
    /**
     * Enum defining inequality operations
     */
    public static enum Operator {
      NONE("None") {
        boolean evaluate(int first, int second) {
          return false;
        }
      },
      EQUALS("=") {
        boolean evaluate(int first, int second) {
          return (first == second);
        }
      },
      GREATER_THAN_OR_EQUAL_TO(">=") {
        boolean evaluate(int first, int second) {
          return (first >= second);
        }
      },
      GREATER_THAN(">") {
        boolean evaluate(int first, int second) {
          return (first > second);
        }
      },
      LESS_THAN_OR_EQUAL_TO("<=") {
        boolean evaluate(int first, int second) {
          return (first <= second);
        }
      },
      LESS_THAN("<") {
        boolean evaluate(int first, int second) {
          return (first < second);
        }
      };
      
      private final String m_stringVal;
      
      abstract boolean evaluate(int first, int second);
      
      Operator(String name) {
        m_stringVal = name;
      }
      
      public String toString() {
        return m_stringVal;
      }
    }
    
    /**
     * Inner class defining one boundary of an interval
     */
    public class TestPart implements Serializable {
      /**
       * For serialization
       */
      private static final long serialVersionUID = -898852333853148631L;
  
      /** the operator for this bound */
      public Operator m_boundOperator = Operator.NONE;
      
      /** true if this is the upper bound */
      protected boolean m_isUpper = false;
      
      /** date fields */
      public int m_year = -100;
      protected int m_month = -100;
      public int m_week_of_yr = -100;
      public int m_week_of_month = -100;
      public int m_day_of_yr = -100;
      public int m_day_of_month = -100;
      protected int m_day_of_week = -100;
      public int m_hour_of_day = -100;
      public int m_min_of_hour = -100;
      public int m_second = -100;
      
      protected Calendar c = new GregorianCalendar();
      
      /**
       * Set whether this is the upper bound or not.
       * 
       * @param upper true if this is the upper bound.
       */
      public void setIsUpper(boolean upper) {
        m_isUpper = upper;
      }
      
      /**
       * Returns true if this is the upper bound.
       * 
       * @return true if this is the upper bound.
       */
      public boolean isUpper() {
        return m_isUpper;
      }
      
      /**
       * Evaluate the supplied date against this bound. Handles
       * date fields that are cyclic (such as month, day of week etc.)
       * so that intervals such as oct < date < mar evaluate correctly.
       * 
       * @param d the date to test
       * @param other the other bound
       * @return true if the supplied date is within this bound
       */
      public boolean eval(Date d, TestPart other) {
        
        c.setTime(d);
        
        boolean result = true;
        
        if (m_year != -100) {
          result = (result && m_boundOperator.
              evaluate(c.get(Calendar.YEAR), m_year));
        }
        if (m_month != -100) {        
          int monthBound = m_month;
          int val = c.get(Calendar.MONTH);
          
          // are we the upper bound and part of a range?
          if (m_isUpper && other != null 
              && other.m_boundOperator != Operator.EQUALS &&
              other.m_month != -100) {
            
            // translate the upper bound by subtracting lower bound
            monthBound -= other.m_month;
            if (monthBound < 0) {
              monthBound += 12;
            }
            // translate the test value
            val -= other.m_month;
            if (val < 0) {
              val += 12;
            }           
          }
          
          // are we the lower bound and part of a range?
          if (!m_isUpper && m_boundOperator != Operator.EQUALS && other != null &&
              other.m_month != -100) {
            monthBound = 0; // lower bound becomes zero
            val -= m_month; // translate value
            if (val < 0) {
              val += 12;
            }
          }
          
          result = (result && m_boundOperator.
              evaluate(val, monthBound));
        }
        if (m_week_of_yr != -100) {
          int weekBound = m_week_of_yr;        
          int val = c.get(Calendar.WEEK_OF_YEAR);
          
          // are we the upper bound and part of a range?
          if (m_isUpper && other != null 
              && other.m_boundOperator != Operator.EQUALS &&
              other.m_week_of_yr != -100) {
            // translate by subtracting lower bound
            weekBound -= other.m_week_of_yr;
            if (weekBound < 0) {
              weekBound += 52;
            }
            // translate the test value
            val -= other.m_week_of_yr;
            if (val < 0) {
              val += 52;
            }
          }
          
          // are we the lower bound and part of a range?
          if (!m_isUpper && m_boundOperator != Operator.EQUALS && other != null &&
              other.m_week_of_yr != -100) {
            weekBound = 0;
            val -= m_week_of_yr;
            if (val < 0) {
              val += 52;
            }
          }
          
          result = (result && m_boundOperator.
              evaluate(val, weekBound));
        }
        if (m_week_of_month != -100) {
          int weekBound = m_week_of_month;
          int val = c.get(Calendar.WEEK_OF_MONTH);
          
          // are we the upper bound and part of a range?
          if (m_isUpper && other != null 
              && other.m_boundOperator != Operator.EQUALS &&
              other.m_week_of_month != -100) {
            // translate by subtracting lower bound
            weekBound -= other.m_week_of_month;
            if (weekBound < 0) {
              weekBound += 6;
            }
            // translate the test value
            val -= other.m_week_of_month;
            if (val < 0) {
              val += 6;
            }
          }
          
          // are we the lower bound and part of a range?
          if (!m_isUpper && m_boundOperator != Operator.EQUALS && other != null &&
              other.m_week_of_month != -100) {
            weekBound = 0;
            val -= m_week_of_month;
            if (val < 0) {
              val += 6;
            }
          }
          
          result = (result && m_boundOperator.evaluate(val, weekBound));
        }
        if (m_day_of_yr != -100) {
          int dayBound = m_day_of_yr;
          int val = c.get(Calendar.DAY_OF_YEAR);
          
          // are we the upper bound and part of a range?
          if (m_isUpper && other != null 
              && other.m_boundOperator != Operator.EQUALS &&
              other.m_day_of_yr != -100) {
            // translate by subtracting lower bound
            dayBound -= other.m_day_of_yr;
            if (dayBound < 0) {
              dayBound += 365;
            }
            // translate the test value
            val -= other.m_day_of_yr;
            if (val < 0) {
              val += 365;
            }
          }
          
          // are we the lower bound and part of a range?
          if (!m_isUpper && m_boundOperator != Operator.EQUALS && other != null &&
              other.m_day_of_yr != -100) {
            dayBound = 0;
            val -= m_day_of_yr;
            if (val < 0) {
              val += 365;
            }
          }
          
          result = (result && m_boundOperator.
              evaluate(val, dayBound));
        }
        if (m_day_of_month != -100) {
          int dayBound = m_day_of_month;
          int val = c.get(Calendar.DAY_OF_MONTH);
          
          // are we the upper bound and part of a range?
          if (m_isUpper && other != null 
              && other.m_boundOperator != Operator.EQUALS &&
              other.m_day_of_month != -100) {
            // translate by subtracting lower bound
            dayBound -= other.m_day_of_month;
            if (dayBound < 0) {
              dayBound += 31;
            }
            // translate the test value
            val -= other.m_day_of_month;
            if (val < 0) {
              val += 31;
            }
          }
          
          // are we the lower bound and part of a range?
          if (!m_isUpper && m_boundOperator != Operator.EQUALS && other != null &&
              other.m_day_of_month != -100) {
            dayBound = 0;
            val -= m_day_of_month;
            if (val < 0) {
              val += 31;
            }          
          }
          
          result = (result && m_boundOperator.
              evaluate(val, dayBound));
        }
        if (m_day_of_week != -100) {
          int dayBound = m_day_of_week;
          int val = c.get(Calendar.DAY_OF_WEEK);
          
          // are we the upper bound and part of a range?
          if (m_isUpper && other != null 
              && other.m_boundOperator != Operator.EQUALS &&
              other.m_day_of_week != -100) {
            // translate by subtracting lower bound
            dayBound -= other.m_day_of_week;
            if (dayBound < 0) {
              dayBound += 7;
            }
            // translate the test value
            val -= other.m_day_of_week;
            if (val < 0) {
              val += 7;
            }          
          }
          
          // are we the lower bound and part of a range?
          if (!m_isUpper && m_boundOperator != Operator.EQUALS && other != null &&
              other.m_day_of_week != -100) {
            dayBound = 0;
            val -= m_day_of_week;
            if (val < 0) {
              val += 7;
            }
          }
          
          result = (result && m_boundOperator.
              evaluate(val, dayBound));
        }
        if (m_hour_of_day != -100) {
          int hourBound = m_hour_of_day;
          int val = c.get(Calendar.HOUR_OF_DAY);
          
          // are we the upper bound and part of a range?
          if (m_isUpper && other != null 
              && other.m_boundOperator != Operator.EQUALS &&
              other.m_hour_of_day != -100) {
            // translate by subtracting lower bound
            hourBound -= other.m_hour_of_day;
            if (hourBound < 0) {
              hourBound += 24;
            }
            // translate test value
            val -= other.m_hour_of_day;
            if (val < 0) {
              val += 24;
            }
          }
          
          // are we the lower bound and part of a range?
          if (!m_isUpper && m_boundOperator != Operator.EQUALS && other != null &&
              other.m_hour_of_day != -100) {
            hourBound = 0;
            val -= m_hour_of_day;
            if (val < 0) {
              val += 24;
            }          
          }
          
          result = (result && m_boundOperator.
              evaluate(val, hourBound));
        }
        if (m_min_of_hour != -100) {
          int minBound = m_min_of_hour;
          int val = c.get(Calendar.MINUTE);
          
          // are we the upper bound and part of a range?
          if (m_isUpper && other != null 
              && other.m_boundOperator != Operator.EQUALS &&
              other.m_min_of_hour != -100) {
            // translate by subtracting lower bound
            minBound -= other.m_min_of_hour;
            if (minBound < 0) {
              minBound += 60;
            }
            // translate the test value
            val -= other.m_min_of_hour;
            if (val < 0) {
              val += 60;
            }          
          }
          
          // are we the lower bound and part of a range?
          if (!m_isUpper && m_boundOperator != Operator.EQUALS && other != null &&
              other.m_min_of_hour != -100) {
            minBound = 0;
            val -= m_min_of_hour;
            if (val < 0) {
              val += 60;
            }
          }
          
          result = (result && m_boundOperator.
              evaluate(val, minBound));
        }
        if (m_second != -100) {
          int secBound = m_second;
          int val = c.get(Calendar.SECOND);
          
          // are we the upper bound and part of a range?
          if (m_isUpper && other != null 
              && other.m_boundOperator != Operator.EQUALS &&
              other.m_second != -100) {
            // translate by subtracting lower bound
            secBound -= other.m_second;
            if (secBound < 0) {
              secBound += 60;
            }
            // translate the test value
            val -= other.m_second;
            if (val < 0) {
              val += 60;
            }          
          }
          
          // are we the lower bound and part of a range?
          if (!m_isUpper && m_boundOperator != Operator.EQUALS && other != null &&
              other.m_second != -100) {
            secBound = 0;
            val -= m_second;
            if (val < 0) {
              val += 60;
            }
          }
          
          result = (result && m_boundOperator.
              evaluate(val, secBound));
        }
              
        return result;
      }
      
      /**
       * Provides a textual representation of this test bound
       * 
       * @return a textual description of this test bound
       */
      public String toString() {
        StringBuffer result = new StringBuffer();
        result.append(m_boundOperator.toString());
        
        if (m_year != -100) {
          result.append("" + m_year);
        } else {
          result.append("*");
        }
        result.append(":");
        if (m_month != -100) {
          result.append(getMonthString());
        } else {
          result.append("*");
        }
        result.append(":");
        if (m_week_of_yr != -100) {
          result.append("" + m_week_of_yr);
        } else {
          result.append("*");
        }
        result.append(":");
        if (m_week_of_month != -100) {
          result.append("" + m_week_of_month);
        } else {
          result.append("*");
        }
        result.append(":");
        if (m_day_of_yr != -100) {
          result.append("" + m_day_of_yr);
        } else {
          result.append("*");
        }
        result.append(":");
        if (m_day_of_month != -100) {
          result.append("" + m_day_of_month);
        } else {
          result.append("*");
        }
        result.append(":");
        if (m_day_of_week != -100) {
          result.append(getDayString());
        } else {
          result.append("*");
        }
        result.append(":");
        if (m_hour_of_day != -100) {
          result.append("" + m_hour_of_day);
        } else {
          result.append("*");
        }
        result.append(":");
        if (m_min_of_hour != -100) {
          result.append("" + m_min_of_hour);
        } else {
          result.append("*");
        }
        result.append(":");
        if (m_second != -100) {
          result.append("" + m_second);
        } else {
          result.append("*");
        }
        
        return result.toString();
      }
      
      /**
       * Set the operator for this bound
       * 
       * @param operator as a String
       */
      public void setOperator(String operator) {
        for (Operator t : Operator.values()) {
          if (operator.equals(t.toString())) {
            m_boundOperator = t;
            break;
          }
        }
      }
      
      /**
       * Set the year for this bound.
       * 
       * @param year the year or "*" to match any value.
       */
      public void setYear(String year) {
        if (year.equals("*")) {
          m_year = -100;
        } else {
          m_year = Integer.parseInt(year);
        }
      }
      
      /**
       * Set the week of the year for this bound.
       * 
       * @param week the week of the year or "*" to match any value.
       */
      public void setWeekOfYear(String week) {
        if (week.equals("*")) {
          m_week_of_yr = -100;
        } else {
          m_week_of_yr = Integer.parseInt(week);
        }
      }
      
      /**
       * Set the week of the month for this bound.
       * 
       * @param week the week of the month or "*" to match any value.
       */
      public void setWeekOfMonth(String week) {
        if (week.equals("*")) {
          m_week_of_yr = -100;
        } else {
          m_week_of_month = Integer.parseInt(week);
        }
      }
      
      /**
       * Set the day of the year for this bound.
       * 
       * @param dayOfYear the day of the year or "*" to match any value.
       */
      public void setDayOfYear(String dayOfYear) {
        if (dayOfYear.equals("*")) {
          m_day_of_yr = -100;
        } else {
          m_day_of_yr = Integer.parseInt(dayOfYear);
        }
      }
      
      /**
       * Set the day of the month for this bound.
       * 
       * @param dayOfMonth the day of the month or "*" to match any value.
       */
      public void setDayOfMonth(String dayOfMonth) {
        if (dayOfMonth.equals("*")) {
          m_day_of_month = -100;
        } else {
          m_day_of_month = Integer.parseInt(dayOfMonth);
        }
      }
      
      /**
       * Set the month for this bound.
       * 
       * @param month the month or "*" to match any value.
       */
      public void setMonth(String month) {
        m_month = parseMonth(month);
      }
      
      /**
       * Set the day of the week for this bound.
       * 
       * @param dayOfWeek the day of the week or "*" to match any value.
       */
      public void setDayOfWeek(String dayOfWeek) {
        m_day_of_week = parseDay(dayOfWeek);
      }
      
      /**
       * Set the hour of the day for this bound.
       * 
       * @param hourOfDay the hour of the day or "*" to match any value.
       */
      public void setHourOfDay(String hourOfDay) {
        if (hourOfDay.equals("*")) {
          m_hour_of_day = -100;
        } else {
          m_hour_of_day = Integer.parseInt(hourOfDay);
        }
      }
      
      /**
       * Set the minute of the hour for this bound.
       * 
       * @param min the minute of the hour or "*" to match any value.
       */
      public void setMinuteOfHour(String min) {
        if (min.equals("*")) {
          m_min_of_hour = -100;
        } else {
          m_min_of_hour = Integer.parseInt(min);
        }
      }
      
      /**
       * Set the second for this bound.
       * 
       * @param second the second or "*" to match any value.
       */
      public void setSecond(String second) {
        if (second.equals("*")) {
          m_second = -100;
        } else {
          m_second = Integer.parseInt(second);
        }
      }
      
      /**
       * Get the day of the week as a string.
       * 
       * @return the day of the week.
       */
      public String getDayString() {
        
        if (m_day_of_week == Calendar.SUNDAY) {
          return "sun";
        }
        if (m_day_of_week == Calendar.MONDAY) {
          return "mon";
        }
        if (m_day_of_week == Calendar.TUESDAY) {
          return "tue";
        }
        if (m_day_of_week == Calendar.WEDNESDAY) {
          return "wed";
        }
        if (m_day_of_week == Calendar.THURSDAY) {
          return "thu";
        }
        if (m_day_of_week == Calendar.FRIDAY) {
          return "fri";
        }
        if (m_day_of_week == Calendar.SATURDAY) {
          return "sat";
        }
        
        return "*";
      }
      
      /**
       * Get the month as a String.
       * 
       * @return the month.
       */
      public String getMonthString() {
        if (m_month == Calendar.JANUARY) {
          return "jan";
        }
        if (m_month == Calendar.FEBRUARY) {
          return "feb";
        }
        if (m_month == Calendar.MARCH) {
          return "mar";
        }
        if (m_month == Calendar.APRIL) {
          return "apr";
        }
        if (m_month == Calendar.MAY) {
          return "may";
        }
        if (m_month == Calendar.JUNE) {
          return "jun";
        }
        if (m_month == Calendar.JULY) {
          return "jul";
        }
        if (m_month == Calendar.AUGUST) {
          return "aug";
        }
        if (m_month == Calendar.SEPTEMBER) {
          return "sep";
        }
        if (m_month == Calendar.OCTOBER) {
          return "oct";
        }
        if (m_month == Calendar.NOVEMBER) {
          return "nov";
        }
        if (m_month == Calendar.DECEMBER) {
          return "dec";
        }
        
        return "*";
      }
    }
    
    /** the upper bound */
    protected TestPart m_upperTest;
    
    /** the lower bound */
    protected TestPart m_lowerTest;
    
    /** the (optional) label for this interval */
    protected String m_label;
    
    /**
     * Constructor.
     * 
     * @param theTest the test definition as a String
     * @throws IllegalArgumentException if the test can't be parsed or is 
     * ill defined.
     */
    public CustomPeriodicTest(String theTest) throws IllegalArgumentException {
      setTest(theTest);
    }
    
    /**
     * Get the lower bound test
     * 
     * @return the lower bound test
     */
    public TestPart getLowerTest() {
      return m_lowerTest;
    }
    
    /**
     * Get the upper bound test
     * 
     * @return the upper bound test.
     */
    public TestPart getUpperTest() {
      return m_upperTest;
    }
    
    /**
     * Set the test as a String
     * 
     * @param theTest the test
     * @throws IllegalArgumentException if the test can't be parsed or is
     * ill defined.
     */
    public void setTest(String theTest) throws IllegalArgumentException {
      /*// check for brackets first
      if (theTest.indexOf('(') < 0 || theTest.indexOf(')') < 0) {
        throw new IllegalArgumentException("Custom periodic test needs enclosing " +
        		"parenthesis!");
      } */
        
  /*    // split off brackets
      theTest = theTest.substring(1, theTest.indexOf(')') - 1); */
      theTest = theTest.trim();
      
      if (theTest.indexOf(' ') > 0) {
        String[] parts = theTest.split(" ");
        
        m_lowerTest = parseTest(parts[0]);
        m_upperTest = parseTest(parts[1]);
        m_upperTest.setIsUpper(true);
        
        if (m_upperTest.m_boundOperator == m_lowerTest.m_boundOperator) {
          throw new IllegalArgumentException("Doesn't make sense to define " +
          		"an upper and lower test that uses the same operator");
        }      
      } else {
        m_lowerTest = parseTest(theTest);
      }
    }
    
    /**
     * Get the optional label for this interval.
     * 
     * @return the label for this interval
     */
    public String getLabel() {
      return m_label;
    }
    
    /**
     * Set the label for this interval
     * 
     * @param label the label for this interval
     */
    public void setLabel(String label) {
      m_label = label;
    }
    
    /**
     * Evaluate the supplied date with respect to this custom periodic
     * test interval
     * 
     * @param d the date to test
     * @return true if the date lies within the interval.
     */
    public boolean evaluate(Date d) {
      boolean result = m_lowerTest.eval(d, m_upperTest);
      
      // only evaluate the upper test if there is one and the lower operator
      // is not equals
      if (m_upperTest != null && m_lowerTest.m_boundOperator != Operator.EQUALS) {      
        result = (result && m_upperTest.eval(d, m_lowerTest));
      }
      
      return result;
    }
    
    /**
     * Parses the day of the week as either a number or
     * a string
     * 
     * @param day the day of the week
     * @return the integer index of the day of the week
     * @throws IllegalArgumentException if the supplied day can't be parsed
     */
    private int parseDay(String day) throws IllegalArgumentException {
      // try parsing as a number
      try {
        int result = Integer.parseInt(day);
        return result;
      } catch (NumberFormatException e) {}
      
      // try parsing as a word
      day = day.toLowerCase();
      if (day.equals("sunday") || day.equals("sun")) {
        return Calendar.SUNDAY;
      }
      if (day.equals("monday") || day.equals("mon")) {
        return Calendar.MONDAY;
      }
      if (day.equals("tuesday") || day.equals("tue")) {
        return Calendar.TUESDAY;
      }
      if (day.equals("wednesday") || day.equals("wed")) {
        return Calendar.WEDNESDAY;
      }
      if (day.equals("thursday") || day.equals("thu")) {
        return Calendar.THURSDAY;
      }
      if (day.equals("friday") || day.equals("fri")) {
        return Calendar.FRIDAY;
      }
      if (day.equals("saturday") || day.equals("sat")) {
        return Calendar.SATURDAY;
      }
      if (day.equals("*")) {
        return -100;
      }
      throw new IllegalArgumentException("Can't parse month!");
    }
    
    /**
     * Parses the month as either a number or
     * a string
     * 
     * @param month the month of the year
     * @return the integer index of the month
     * @throws IllegalArgumentException if the supplied month can't be parsed
     */
    private int parseMonth(String month) throws IllegalArgumentException {
      // try parsing as a number
      try {
        int result = Integer.parseInt(month);
        return result;
      } catch (NumberFormatException e) {}
      
      // try parsing as a word
      month = month.toLowerCase();
      if (month.equals("january") || month.equals("jan")) {
        return Calendar.JANUARY;
      }
      if (month.equals("february") || month.equals("feb")) {
        return Calendar.FEBRUARY;
      }
      if (month.equals("march") || month.equals("mar")) {
        return Calendar.MARCH;
      }
      if (month.equals("april") || month.equals("apr")) {
        return Calendar.APRIL;
      }
      if (month.equals("may")) {
        return Calendar.MAY;
      }
      if (month.equals("june") || month.equals("jun")) {
        return Calendar.JUNE;
      }
      if (month.equals("july") || month.equals("jul")) {
        return Calendar.JULY;
      }
      if (month.equals("august") || month.equals("aug")) {
        return Calendar.AUGUST;
      }
      if (month.equals("september") || month.equals("sep")) {
        return Calendar.SEPTEMBER;
      }
      if (month.equals("october") || month.equals("oct")) {
        return Calendar.OCTOBER;
      }
      if (month.equals("november") || month.equals("nov")) {
        return Calendar.NOVEMBER;
      }
      if (month.equals("december") || month.equals("dec")) {
        return Calendar.DECEMBER;
      }
      if (month.equals("*")) {
        return -100;
      }
      
      throw new IllegalArgumentException("Can't parse month!");
    }
    
    /**
     * Parses the textual definition of one bound
     * 
     * @param aTest the bound to parse
     * @return a TestPart object encapsulating the bound
     * @throws IllegalArgumentException if the test can't be parsed or
     * is ill defined
     */
    protected TestPart parseTest(String aTest) throws IllegalArgumentException {
      // check for a label
      if (aTest.indexOf('/') > 0) {
        String[] parts = aTest.split("/");
        aTest = parts[0].trim();
        m_label = parts[1].trim();
      }     
      
      aTest = aTest.trim();
      TestPart newTest = new TestPart();
      
      if (aTest.charAt(0) == '=') {
        newTest.m_boundOperator = Operator.EQUALS;
        aTest = aTest.substring(1, aTest.length());
      } else {
        if (aTest.charAt(0) == '>') {
          newTest.m_boundOperator = Operator.GREATER_THAN;
          aTest = aTest.substring(1, aTest.length());
          if (aTest.charAt(0) == '=') {
            newTest.m_boundOperator = Operator.GREATER_THAN_OR_EQUAL_TO;
            aTest = aTest.substring(1, aTest.length());
          }
        } else if (aTest.charAt(0) == '<') {
          newTest.m_boundOperator = Operator.LESS_THAN;
          aTest = aTest.substring(1, aTest.length());
          if (aTest.charAt(0) == '=') {
            newTest.m_boundOperator = Operator.LESS_THAN_OR_EQUAL_TO;
            aTest = aTest.substring(1, aTest.length());
          }
        }
      }
      
      String[] parts = aTest.split(":");
      // do we have the right number of parts?
      if (parts.length != 10) {
  /*      System.err.println(aTest);
        System.err.println("-- Num parts " + parts.length);
        for (int z = 0; z < parts.length; z++) {
          System.err.println(parts[z]);
        }*/
        throw new IllegalArgumentException("Test does not contain 10 parts!");
      }
      if (parts[0].trim().length() > 0 && parts[0].charAt(0) != '*') {
        newTest.m_year = Integer.parseInt(parts[0].trim());
      }
      if (parts[1].trim().length() > 0 && parts[1].charAt(0) != '*') {
        newTest.m_month = parseMonth(parts[1].trim());
      }
      if (parts[2].trim().length() > 0 && parts[2].charAt(0) != '*') {
        newTest.m_week_of_yr = Integer.parseInt(parts[2].trim());
      }
      if (parts[3].trim().length() > 0 && parts[3].charAt(0) != '*') {
        newTest.m_week_of_month = Integer.parseInt(parts[3].trim());
      }
      if (parts[4].trim().length() > 0 && parts[4].charAt(0) != '*') {
        newTest.m_day_of_yr = Integer.parseInt(parts[4].trim());
      }
      if (parts[5].trim().length() > 0 && parts[5].charAt(0) != '*') {
        newTest.m_day_of_month = Integer.parseInt(parts[5].trim());
      }
      if (parts[6].trim().length() > 0 && parts[6].charAt(0) != '*') {
        newTest.m_day_of_week = parseDay(parts[6].trim());
      }
      if (parts[7].trim().length() > 0 && parts[7].charAt(0) != '*') {
        newTest.m_hour_of_day = Integer.parseInt(parts[7].trim());
      }
      if (parts[8].trim().length() > 0 && parts[8].charAt(0) != '*') {
        newTest.m_min_of_hour = Integer.parseInt(parts[8].trim());
      }
      if (parts[9].trim().length() > 0 && parts[9].charAt(0) != '*') {
        newTest.m_second = Integer.parseInt(parts[9].trim());
      }
      
      return newTest;
    }
    
    /**
     * Returns a textual description of this test
     * 
     * @return a textual description of this test
     */
    public String toString() {
      String result = m_lowerTest.toString();
      if (m_upperTest != null && m_lowerTest.m_boundOperator != Operator.EQUALS) {
        result += (" " + m_upperTest.toString());
      }
      if (m_label != null && m_label.length()> 0) {
        result += "/" + m_label;
      }
      return result;
    }
    
    /**
     * Main method for testing this class
     * 
     * @param args command line arguments - first element should
     * contain a textual definition of a test
     */
    public static void main(String[] args) {
      try {
        if (args.length != 1) {
          System.err.println("Usage: CustomPeriodicTest \"TestPart TestPart\"");
          System.exit(1);
        }
        CustomPeriodicTest test = new CustomPeriodicTest(args[0]);
        System.out.println("CustomPeriodicTest: \n\n" + test.toString());
      } catch (Exception ex) {
        ex.printStackTrace();      
      }
    }
  }
  