You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by jo...@apache.org on 2008/05/20 07:48:51 UTC

svn commit: r658108 - in /ofbiz/trunk: applications/ecommerce/data/ applications/order/src/org/ofbiz/order/shoppingcart/product/ applications/product/data/ framework/service/src/org/ofbiz/service/calendar/

Author: jonesde
Date: Mon May 19 22:48:51 2008
New Revision: 658108

URL: http://svn.apache.org/viewvc?rev=658108&view=rev
Log:
Applied patch from Bilgin Ibryam in Jira #OFBIZ-1756 to extend the support for by hours and by days in recurrence info and use them as part of conditions for promotions

Modified:
    ofbiz/trunk/applications/ecommerce/data/DemoProduct.xml
    ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/product/ProductPromoWorker.java
    ofbiz/trunk/applications/product/data/ProductTypeData.xml
    ofbiz/trunk/framework/service/src/org/ofbiz/service/calendar/RecurrenceInfo.java
    ofbiz/trunk/framework/service/src/org/ofbiz/service/calendar/RecurrenceRule.java

Modified: ofbiz/trunk/applications/ecommerce/data/DemoProduct.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/ecommerce/data/DemoProduct.xml?rev=658108&r1=658107&r2=658108&view=diff
==============================================================================
--- ofbiz/trunk/applications/ecommerce/data/DemoProduct.xml (original)
+++ ofbiz/trunk/applications/ecommerce/data/DemoProduct.xml Mon May 19 22:48:51 2008
@@ -565,6 +565,7 @@
     <ProductPromo productPromoId="9019" promoName="Test Percent off when Customer Orders over $1000 in 12 months" userEntered="Y" useLimitPerOrder="1" createdDate="2001-05-13 12:00:00.0">
         <promoText><![CDATA[Spend more than $1000 in any 12 months on our fabulous Widgets and Gizmos and get a 5% discount.]]></promoText>
     </ProductPromo>
+    <ProductPromo productPromoId="9020" promoName="Test Promotion Recurrence - Happy hour" promoText="Get 1 Free [WG-1111], between 15:00 - 18:00 at weekdays" userEntered="Y" showToCustomer="Y" requireCode="N" useLimitPerOrder="1" createdDate="2008-01-01 12:00:00.0" createdByUserLogin="admin" lastModifiedDate="2008-01-01 12:00:00.0" lastModifiedByUserLogin="admin"/>    
     <ProductPromoRule productPromoId="9000" productPromoRuleId="01" ruleName="Rule 1: Free Widget"/>
     <ProductPromoRule productPromoId="9010" productPromoRuleId="01" ruleName="Test Percent off rule"/>
     <ProductPromoRule productPromoId="9011" productPromoRuleId="01" ruleName="Test percent off order"/>
@@ -576,6 +577,7 @@
     <ProductPromoRule productPromoId="9017" productPromoRuleId="01" ruleName="GWP for Gizmos Only"/>
     <ProductPromoRule productPromoId="9018" productPromoRuleId="01" ruleName="GWP for Gizmos Only Not Counted"/>
     <ProductPromoRule productPromoId="9019" productPromoRuleId="01" ruleName="Percent off for Ordered Sub-Total History"/>
+    <ProductPromoRule productPromoId="9020" productPromoRuleId="01" ruleName="Test Promotion Recurrence"/>    
     <ProductPromoAction productPromoId="9000" productPromoRuleId="01" productPromoActionSeqId="01" productPromoActionEnumId="PROMO_GWP" orderAdjustmentTypeId="PROMOTION_ADJUSTMENT" quantity="1.0" productId="WG-1111"/>
     <ProductPromoAction productPromoId="9010" productPromoRuleId="01" productPromoActionSeqId="01" productPromoActionEnumId="PROMO_PROD_DISC" orderAdjustmentTypeId="PROMOTION_ADJUSTMENT" quantity="1.0" amount="20.0"/>
     <ProductPromoAction productPromoId="9011" productPromoRuleId="01" productPromoActionSeqId="01" productPromoActionEnumId="PROMO_ORDER_PERCENT" orderAdjustmentTypeId="PROMOTION_ADJUSTMENT" amount="10.0"/>
@@ -587,6 +589,7 @@
     <ProductPromoAction productPromoId="9017" productPromoRuleId="01" productPromoActionSeqId="01" productPromoActionEnumId="PROMO_GWP" orderAdjustmentTypeId="PROMOTION_ADJUSTMENT" quantity="1.0" productId="GZ-1006"/>
     <ProductPromoAction productPromoId="9018" productPromoRuleId="01" productPromoActionSeqId="01" productPromoActionEnumId="PROMO_GWP" orderAdjustmentTypeId="PROMOTION_ADJUSTMENT" quantity="1.0" productId="GZ-1006"/>
     <ProductPromoAction productPromoId="9019" productPromoRuleId="01" productPromoActionSeqId="01" productPromoActionEnumId="PROMO_ORDER_PERCENT" orderAdjustmentTypeId="PROMOTION_ADJUSTMENT" amount="5.0"/>
+    <ProductPromoAction productPromoId="9020" productPromoRuleId="01" productPromoActionSeqId="01" productPromoActionEnumId="PROMO_GWP" orderAdjustmentTypeId="PROMOTION_ADJUSTMENT" quantity="1.0" productId="WG-1111"/>
     <ProductPromoCond productPromoId="9000" productPromoRuleId="01" productPromoCondSeqId="01" inputParamEnumId="PPIP_ORDER_TOTAL" operatorEnumId="PPC_GT" condValue="100"/>
     <ProductPromoCond productPromoId="9012" productPromoRuleId="01" productPromoCondSeqId="01" inputParamEnumId="PPIP_ORDER_TOTAL" operatorEnumId="PPC_GTE" condValue="50"/>
     <ProductPromoCond productPromoId="9013" productPromoRuleId="01" productPromoCondSeqId="01" inputParamEnumId="PPIP_PRODUCT_QUANT" operatorEnumId="PPC_EQ" condValue="3"/>
@@ -594,6 +597,7 @@
     <ProductPromoCond productPromoId="9017" productPromoRuleId="01" productPromoCondSeqId="01" inputParamEnumId="PPIP_PRODUCT_AMOUNT" operatorEnumId="PPC_EQ" condValue="50"/>
     <ProductPromoCond productPromoId="9018" productPromoRuleId="01" productPromoCondSeqId="01" inputParamEnumId="PPIP_PRODUCT_TOTAL" operatorEnumId="PPC_GTE" condValue="150"/>
     <ProductPromoCond productPromoId="9019" productPromoRuleId="01" productPromoCondSeqId="01" inputParamEnumId="PPIP_ORST_HIST" operatorEnumId="PPC_GTE" condValue="1000" otherValue="12"/>
+    <ProductPromoCond productPromoId="9020" productPromoRuleId="01" productPromoCondSeqId="01" inputParamEnumId="PPIP_RECURRENCE" operatorEnumId="PPC_EQ" condValue="205"/>
     <ProductPromoCategory productPromoId="9013" productPromoRuleId="_NA_" productPromoActionSeqId="_NA_" productPromoCondSeqId="_NA_" productCategoryId="200" andGroupId="_NA_" productPromoApplEnumId="PPPA_INCLUDE" includeSubCategories="Y"/>
     <ProductPromoCategory productPromoId="9013" productPromoRuleId="_NA_" productPromoActionSeqId="_NA_" productPromoCondSeqId="_NA_" productCategoryId="201" andGroupId="_NA_" productPromoApplEnumId="PPPA_EXCLUDE" includeSubCategories="Y"/>
     <ProductPromoCategory productPromoId="9013" productPromoRuleId="_NA_" productPromoActionSeqId="_NA_" productPromoCondSeqId="_NA_" productCategoryId="20111" andGroupId="_NA_" productPromoApplEnumId="PPPA_ALWAYS" includeSubCategories="N"/>
@@ -622,6 +626,7 @@
     <ProductStorePromoAppl productStoreId="9000" productPromoId="9017" fromDate="2001-05-13 12:00:00.0" sequenceNum="1"/>
     <ProductStorePromoAppl productStoreId="9000" productPromoId="9018" fromDate="2001-05-13 12:00:00.0" sequenceNum="1"/>
     <ProductStorePromoAppl productStoreId="9000" productPromoId="9019" fromDate="2001-05-13 12:00:00.0" sequenceNum="1"/>
+    <ProductStorePromoAppl productStoreId="9000" productPromoId="9020" fromDate="2001-05-13 12:00:00.0" sequenceNum="1"/>
 
     <ProductPromo productPromoId="9021" promoName="Test Products for Special Promo Price" promoText="With special code [9021] get products in the Featured Products category for their special promotion price." userEntered="Y" showToCustomer="Y" requireCode="Y" createdDate="2001-05-13 12:00:00.0" createdByUserLogin="admin" lastModifiedDate="2001-05-13 12:00:00.0" lastModifiedByUserLogin="admin"/>
     <ProductPromoCode productPromoCodeId="9021" productPromoId="9021" userEntered="Y" requireEmailOrParty="N" createdDate="2001-05-13 12:00:00.0" createdByUserLogin="admin" lastModifiedDate="2001-05-13 12:00:00.0" lastModifiedByUserLogin="admin"/>
@@ -629,6 +634,10 @@
     <ProductPromoAction productPromoId="9021" productPromoRuleId="01" productPromoActionSeqId="01" productPromoActionEnumId="PROMO_PROD_SPPRC" orderAdjustmentTypeId="PROMOTION_ADJUSTMENT"/>
     <ProductPromoCategory productPromoId="9021" productPromoRuleId="01" productPromoActionSeqId="01" productPromoCondSeqId="_NA_" productCategoryId="PROMOTIONS" andGroupId="_NA_" productPromoApplEnumId="PPPA_INCLUDE" includeSubCategories="N"/>
     <ProductStorePromoAppl productStoreId="9000" productPromoId="9021" fromDate="2001-05-13 12:00:00.0" sequenceNum="1"/>
+
+    <!-- a promotion recurrence example for happy hour-->
+    <RecurrenceRule recurrenceRuleId="205" frequency="DAILY" intervalNumber="1" countNumber="-1" byHourList="15,16,17" byDayList="MO,TU,WE,TH,FR"/>
+    <RecurrenceInfo recurrenceInfoId="205" startDateTime="2008-01-01 00:00:00.000" recurrenceRuleId="205" recurrenceCount="0"/>    
     
     <!-- a default pricing rule to play with; take 20% off all products in the PROMOTIONS category -->
     <ProductPriceRule productPriceRuleId="9000" ruleName="Test Rule 1" isSale="Y"/>

Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/product/ProductPromoWorker.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/product/ProductPromoWorker.java?rev=658108&r1=658107&r2=658108&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/product/ProductPromoWorker.java (original)
+++ ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/product/ProductPromoWorker.java Mon May 19 22:48:51 2008
@@ -59,6 +59,8 @@
 import org.ofbiz.service.LocalDispatcher;
 import org.ofbiz.service.ModelService;
 import org.ofbiz.service.ServiceUtil;
+import org.ofbiz.service.calendar.RecurrenceInfo;
+import org.ofbiz.service.calendar.RecurrenceInfoException;
 
 /**
  * ProductPromoWorker - Worker class for catalog/product promotion related functionality
@@ -1035,6 +1037,24 @@
             } else {
                 return false;
             }
+        } else if ("PPIP_RECURRENCE".equals(inputParamEnumId)) {
+            compareBase = new Integer(1);
+            GenericValue recurrenceInfo = delegator.findByPrimaryKeyCache("RecurrenceInfo", UtilMisc.toMap("recurrenceInfoId", condValue));
+            if (recurrenceInfo != null) {
+                RecurrenceInfo recurrence = null;
+                try {
+                    recurrence = new RecurrenceInfo(recurrenceInfo);
+                } catch (RecurrenceInfoException e) {
+                    Debug.logError(e, module);
+                }   
+                
+                // check the current recurrence
+                if (recurrence != null) {
+                    if (recurrence.isValidCurrent()) {
+                        compareBase = new Integer(0);
+                    }
+                }
+            }
         } else {
             Debug.logWarning(UtilProperties.getMessage(resource_error,"OrderAnUnSupportedProductPromoCondInputParameterLhs", UtilMisc.toMap("inputParamEnumId",productPromoCond.getString("inputParamEnumId")), cart.getLocale()), module);
             return false;

Modified: ofbiz/trunk/applications/product/data/ProductTypeData.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/data/ProductTypeData.xml?rev=658108&r1=658107&r2=658108&view=diff
==============================================================================
--- ofbiz/trunk/applications/product/data/ProductTypeData.xml (original)
+++ ofbiz/trunk/applications/product/data/ProductTypeData.xml Mon May 19 22:48:51 2008
@@ -348,6 +348,7 @@
     <Enumeration description="Party Classification" enumCode="PARTY_CLASS" enumId="PPIP_PARTY_CLASS" sequenceId="08" enumTypeId="PROD_PROMO_IN_PARAM"/>
     <Enumeration description="Role Type" enumCode="ROLE_TYPE" enumId="PPIP_ROLE_TYPE" sequenceId="09" enumTypeId="PROD_PROMO_IN_PARAM"/>
     <Enumeration description="Order sub-total X in last Y Months" enumCode="ORST_HIST" enumId="PPIP_ORST_HIST" sequenceId="10" enumTypeId="PROD_PROMO_IN_PARAM"/>
+    <Enumeration description="Promotion Recurrence" enumCode="PROMO_RECURRENCE" enumId="PPIP_RECURRENCE" sequenceId="11" enumTypeId="PROD_PROMO_IN_PARAM"/>
 
     <EnumerationType description="Product Promotion Condition" enumTypeId="PROD_PROMO_COND" hasTable="N" parentTypeId="PROD_PROMO"/>
     <!-- old style very technical ...

Modified: ofbiz/trunk/framework/service/src/org/ofbiz/service/calendar/RecurrenceInfo.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/service/src/org/ofbiz/service/calendar/RecurrenceInfo.java?rev=658108&r1=658107&r2=658108&view=diff
==============================================================================
--- ofbiz/trunk/framework/service/src/org/ofbiz/service/calendar/RecurrenceInfo.java (original)
+++ ofbiz/trunk/framework/service/src/org/ofbiz/service/calendar/RecurrenceInfo.java Mon May 19 22:48:51 2008
@@ -19,7 +19,6 @@
 package org.ofbiz.service.calendar;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Iterator;
@@ -27,7 +26,6 @@
 
 import org.ofbiz.base.util.Debug;
 import org.ofbiz.base.util.StringUtil;
-import org.ofbiz.base.util.UtilMisc;
 import org.ofbiz.entity.GenericDelegator;
 import org.ofbiz.entity.GenericEntityException;
 import org.ofbiz.entity.GenericValue;
@@ -44,7 +42,7 @@
     protected List<RecurrenceRule> rRulesList;
     protected List<RecurrenceRule> eRulesList;
     protected List<Date> rDateList;
-    protected List<Date> eDateList;;
+    protected List<Date> eDateList;
 
     /** Creates new RecurrenceInfo */
     public RecurrenceInfo(GenericValue info) throws RecurrenceInfoException {
@@ -231,6 +229,33 @@
         }
         return nextRuleTime;
     }
+    
+    /** Checks the current recurrence validity at the moment. */
+    public boolean isValidCurrent() {
+    	return isValidCurrent(RecurrenceUtil.now());
+    }
+    
+    /** Checks the current recurrence validity for checkTime. */
+    public boolean isValidCurrent(long checkTime) {
+        if (checkTime == 0 || (rDateList == null && rRulesList == null)) {
+            return false;
+        }
+        
+        boolean found = false;
+        Iterator<RecurrenceRule> rulesIterator = getRecurrenceRuleIterator();
+        while (rulesIterator.hasNext()) {
+            RecurrenceRule rule = rulesIterator.next();
+            long currentTime = rule.validCurrent(getStartTime(), checkTime, getCurrentCount());
+            currentTime = checkDateList(rDateList, currentTime, checkTime);
+            if ((currentTime > 0) && isValid(checkTime)) {
+                found = true;
+            } else {
+                return false;
+            }
+        }
+        
+        return found;
+    }
 
     private long getNextTime(RecurrenceRule rule, long fromTime) {
         long nextTime = rule.next(getStartTime(), fromTime, getCurrentCount());

Modified: ofbiz/trunk/framework/service/src/org/ofbiz/service/calendar/RecurrenceRule.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/service/src/org/ofbiz/service/calendar/RecurrenceRule.java?rev=658108&r1=658107&r2=658108&view=diff
==============================================================================
--- ofbiz/trunk/framework/service/src/org/ofbiz/service/calendar/RecurrenceRule.java (original)
+++ ofbiz/trunk/framework/service/src/org/ofbiz/service/calendar/RecurrenceRule.java Mon May 19 22:48:51 2008
@@ -26,7 +26,6 @@
 
 import org.ofbiz.base.util.Debug;
 import org.ofbiz.base.util.StringUtil;
-import org.ofbiz.base.util.UtilMisc;
 import org.ofbiz.entity.GenericDelegator;
 import org.ofbiz.entity.GenericEntityException;
 import org.ofbiz.entity.GenericValue;
@@ -285,6 +284,116 @@
     }
 
     /** 
+     * Gets the current recurrence (current for the checkTime) of this rule and returns it if it is valid. 
+     * If the current recurrence is not valid, doesn't try to find a valid one, instead returns 0. 
+     *@param startTime The time this recurrence first began.
+     *@param checkTime The time to base the current recurrence on.
+     *@param currentCount The total number of times the recurrence has run.
+     *@return long The current recurrence as long if valid. If next recurrence is not valid, returns 0.
+     */
+    public long validCurrent(long startTime, long checkTime, long currentCount) {
+        if (startTime == 0) {
+            startTime = RecurrenceUtil.now();
+        }
+        if (checkTime == 0) {
+            checkTime = startTime;
+        }
+                      
+        // Test the end time of the recurrence.
+        if (getEndTime() != 0 && getEndTime() <= RecurrenceUtil.now()) {
+            return 0;
+        }
+        
+        // Test the recurrence limit.
+        if (getCount() != -1 && currentCount >= getCount()) {
+            return 0;
+        }
+ 
+        // Get the next frequency from checkTime
+        Date nextRun = getNextFreq(startTime, checkTime);
+        Calendar cal = Calendar.getInstance();
+        Calendar checkTimeCal = Calendar.getInstance();
+        cal.setTime(nextRun);
+        checkTimeCal.setTime(new Date(checkTime));
+        
+        // Get previous frequency and update its values from checkTime
+        switch (getFrequency()) {        
+        case YEARLY:
+            cal.add(Calendar.YEAR, -getIntervalInt());
+            if (cal.get(Calendar.YEAR) != checkTimeCal.get(Calendar.YEAR)) {
+                return 0;
+            }
+        	
+        case MONTHLY:
+        	if (MONTHLY == getFrequency()) {
+                cal.add(Calendar.MONTH, -getIntervalInt());
+                if (cal.get(Calendar.MONTH) != checkTimeCal.get(Calendar.MONTH)) {
+                    return 0;
+                }
+        	} else {
+                cal.set(Calendar.MONTH, checkTimeCal.get(Calendar.MONTH));
+            }
+        	
+        case WEEKLY:
+        	if (WEEKLY == getFrequency()) {
+                cal.add(Calendar.WEEK_OF_YEAR, -getIntervalInt());
+                if (cal.get(Calendar.WEEK_OF_YEAR) != checkTimeCal.get(Calendar.WEEK_OF_YEAR)) {
+                    return 0;
+                }
+        	} else {
+                cal.set(Calendar.WEEK_OF_YEAR, checkTimeCal.get(Calendar.WEEK_OF_YEAR));
+            }
+
+        case DAILY:
+        	if (DAILY == getFrequency()) {
+                cal.add(Calendar.DAY_OF_MONTH, -getIntervalInt());
+                if (cal.get(Calendar.DAY_OF_MONTH) != checkTimeCal.get(Calendar.DAY_OF_MONTH)) {
+                    return 0;
+                }
+        	} else {
+                cal.set(Calendar.DAY_OF_MONTH, checkTimeCal.get(Calendar.DAY_OF_MONTH));
+            }
+
+        case HOURLY:
+        	if (HOURLY == getFrequency()) {
+                cal.add(Calendar.HOUR_OF_DAY, -getIntervalInt());
+                if (cal.get(Calendar.HOUR_OF_DAY) != checkTimeCal.get(Calendar.HOUR_OF_DAY)) {
+                    return 0;
+                }
+        	} else {
+                cal.set(Calendar.HOUR_OF_DAY, checkTimeCal.get(Calendar.HOUR_OF_DAY));
+            }
+
+        case MINUTELY:
+        	if (MINUTELY == getFrequency()) {
+                cal.add(Calendar.MINUTE, -getIntervalInt());
+                if (cal.get(Calendar.MINUTE) != checkTimeCal.get(Calendar.MINUTE)) {
+                    return 0;
+                }
+        	} else {
+                cal.set(Calendar.MINUTE, checkTimeCal.get(Calendar.MINUTE));
+            }
+
+        case SECONDLY:
+        	if (SECONDLY == getFrequency()) {
+                cal.add(Calendar.SECOND, -getIntervalInt());
+                if (cal.get(Calendar.SECOND) != checkTimeCal.get(Calendar.SECOND)) {
+                    return 0;
+                }
+        	} else {
+                cal.set(Calendar.SECOND, checkTimeCal.get(Calendar.SECOND));
+            }
+        }
+
+        // Check for validity of the current frequency.
+        if (validByRule(cal.getTime())) {
+        	 return cal.getTime().getTime();
+        }
+        
+        return 0;
+    }
+    
+    /** 
      * Tests the date to see if it falls within the rules
      *@param startDate date object to test
      *@return True if the date is within the rules
@@ -344,7 +453,7 @@
                 break;
 
             case HOURLY:
-                cal.add(Calendar.HOUR, getIntervalInt());
+                cal.add(Calendar.HOUR_OF_DAY, getIntervalInt());
                 break;
 
             case DAILY:
@@ -380,15 +489,15 @@
 
         // Test each byXXX rule.
         if (bySecondList != null && bySecondList.size() > 0) {
-            if (!bySecondList.contains(cal.get(Calendar.SECOND)))
+            if (!bySecondList.contains(String.valueOf(cal.get(Calendar.SECOND))))
                 return false;
         }
         if (byMinuteList != null && byMinuteList.size() > 0) {
-            if (!byMinuteList.contains(cal.get(Calendar.MINUTE)))
+            if (!byMinuteList.contains(String.valueOf(cal.get(Calendar.MINUTE))))
                 return false;
         }
         if (byHourList != null && byHourList.size() > 0) {
-            if (!byHourList.contains(cal.get(Calendar.HOUR)))
+            if (!byHourList.contains(String.valueOf(cal.get(Calendar.HOUR_OF_DAY))))
                 return false;
         }
         if (byDayList != null && byDayList.size() > 0) {
@@ -399,7 +508,7 @@
                 String dayRule = (String) iter.next();
                 String dayString = getDailyString(dayRule);
 
-                if (Calendar.DAY_OF_WEEK == getCalendarDay(dayString)) {
+                if (cal.get(Calendar.DAY_OF_WEEK) == getCalendarDay(dayString)) {
                     if ((hasNumber(dayRule)) && (getFrequency() == MONTHLY || getFrequency() == YEARLY)) {
                         int modifier = getDailyNumber(dayRule);
 
@@ -563,7 +672,7 @@
     private boolean hasNumber(String str) {
         String list[] = {"+", "-", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"};
         List numberList = Arrays.asList(list);
-        String firstChar = str.substring(0, 0);
+        String firstChar = str.substring(0, 1);
 
         if (numberList.contains(firstChar))
             return true;
@@ -599,7 +708,7 @@
         StringBuilder sBuf = new StringBuilder();
 
         for (int i = 0; i < str.length(); i++) {
-            String thisChar = str.substring(i, i);
+            String thisChar = str.substring(i, i+1);
 
             if (!hasNumber(thisChar)) {
                 sBuf.append(thisChar);
@@ -610,6 +719,7 @@
 
     // Returns the Calendar day of the rule day string
     private int getCalendarDay(String day) {
+        if (day != null) day = day.trim();
         if (day.equalsIgnoreCase("MO"))
             return Calendar.MONDAY;
         if (day.equalsIgnoreCase("TU"))