<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[14191] PyCalendar/trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/14191">14191</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2014-12-01 07:15:13 -0800 (Mon, 01 Dec 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Initial RSCALE support using cffi to interface with the ICU library.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#PyCalendartrunkproject">PyCalendar/trunk/.project</a></li>
<li><a href="#PyCalendartrunksrcpycalendardatetimepy">PyCalendar/trunk/src/pycalendar/datetime.py</a></li>
<li><a href="#PyCalendartrunksrcpycalendaricalendardefinitionspy">PyCalendar/trunk/src/pycalendar/icalendar/definitions.py</a></li>
<li><a href="#PyCalendartrunksrcpycalendaricalendarrecurrencepy">PyCalendar/trunk/src/pycalendar/icalendar/recurrence.py</a></li>
<li><a href="#PyCalendartrunksrcpycalendaricalendarteststest_recurrencepy">PyCalendar/trunk/src/pycalendar/icalendar/tests/test_recurrence.py</a></li>
<li><a href="#PyCalendartrunksrcpycalendaricalendarxmldefinitionspy">PyCalendar/trunk/src/pycalendar/icalendar/xmldefinitions.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#PyCalendartrunksrcpycalendaricalendaricudatetimepy">PyCalendar/trunk/src/pycalendar/icalendar/icudatetime.py</a></li>
<li><a href="#PyCalendartrunksrcpycalendaricalendarrecuriterpy">PyCalendar/trunk/src/pycalendar/icalendar/recuriter.py</a></li>
<li><a href="#PyCalendartrunksrcpycalendaricalendarteststest_icudatetimepy">PyCalendar/trunk/src/pycalendar/icalendar/tests/test_icudatetime.py</a></li>
<li><a href="#PyCalendartrunksrcpycalendaricalendarteststest_recuriterpy">PyCalendar/trunk/src/pycalendar/icalendar/tests/test_recuriter.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="PyCalendartrunkproject"></a>
<div class="modfile"><h4>Modified: PyCalendar/trunk/.project (14190 => 14191)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/.project        2014-12-01 15:13:17 UTC (rev 14190)
+++ PyCalendar/trunk/.project        2014-12-01 15:15:13 UTC (rev 14191)
</span><span class="lines">@@ -3,6 +3,7 @@
</span><span class="cx">         &lt;name&gt;pycalendar&lt;/name&gt;
</span><span class="cx">         &lt;comment&gt;&lt;/comment&gt;
</span><span class="cx">         &lt;projects&gt;
</span><ins>+                &lt;project&gt;cffi&lt;/project&gt;
</ins><span class="cx">         &lt;/projects&gt;
</span><span class="cx">         &lt;buildSpec&gt;
</span><span class="cx">                 &lt;buildCommand&gt;
</span></span></pre></div>
<a id="PyCalendartrunksrcpycalendardatetimepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/trunk/src/pycalendar/datetime.py (14190 => 14191)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/datetime.py        2014-12-01 15:13:17 UTC (rev 14190)
+++ PyCalendar/trunk/src/pycalendar/datetime.py        2014-12-01 15:15:13 UTC (rev 14191)
</span><span class="lines">@@ -301,7 +301,7 @@
</span><span class="cx">         self.changed()
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def setYYMMDD(self, year, month, days):
</del><ins>+    def setYYMMDD(self, year, month, days, isleapmonth=False):
</ins><span class="cx">         if (self.mYear != year) or (self.mMonth != month) or (self.mDay != days):
</span><span class="cx">             self.mYear = year
</span><span class="cx">             self.mMonth = month
</span><span class="lines">@@ -321,14 +321,18 @@
</span><span class="cx"> 
</span><span class="cx">     def offsetYear(self, diff_year):
</span><span class="cx">         self.mYear += diff_year
</span><del>-        self.normalise()
</del><span class="cx"> 
</span><ins>+        # Do special normalization for this case to do a skip backwards if
+        # the new date is invalid
+        if self.mDay &gt; utils.daysInMonth(self.mMonth, self.mYear):
+            self.mDay = utils.daysInMonth(self.mMonth, self.mYear)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def getMonth(self):
</span><span class="cx">         return self.mMonth
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def setMonth(self, month):
</del><ins>+    def setMonth(self, month, isleapmonth=False):
</ins><span class="cx">         if self.mMonth != month:
</span><span class="cx">             self.mMonth = month
</span><span class="cx">             self.changed()
</span><span class="lines">@@ -336,9 +340,26 @@
</span><span class="cx"> 
</span><span class="cx">     def offsetMonth(self, diff_month):
</span><span class="cx">         self.mMonth += diff_month
</span><del>-        self.normalise()
</del><span class="cx"> 
</span><ins>+        # Normalise month
+        normalised_month = ((self.mMonth - 1) % 12) + 1
+        adjustment_year = (self.mMonth - 1) / 12
+        if (normalised_month - 1) &lt; 0:
+            normalised_month += 12
+            adjustment_year -= 1
+        self.mMonth = normalised_month
+        self.mYear += adjustment_year
</ins><span class="cx"> 
</span><ins>+        # Do special normalization for this case to do a skip backwards if
+        # the new date is invalid
+        if self.mDay &gt; utils.daysInMonth(self.mMonth, self.mYear):
+            self.mDay = utils.daysInMonth(self.mMonth, self.mYear)
+
+
+    def getLeapMonth(self):
+        return False
+
+
</ins><span class="cx">     def getDay(self):
</span><span class="cx">         return self.mDay
</span><span class="cx"> 
</span><span class="lines">@@ -444,16 +465,22 @@
</span><span class="cx">         # What day does the current year start on, and diff that with the current day
</span><span class="cx">         temp = DateTime(year=self.mYear, month=1, day=1)
</span><span class="cx">         first_day = temp.getDayOfWeek()
</span><ins>+        if first_day == 0:
+            first_day = 7
</ins><span class="cx">         current_day = self.getDayOfWeek()
</span><ins>+        if current_day == 0:
+            current_day = 7
</ins><span class="cx"> 
</span><span class="cx">         # Calculate and set yearday for start of week. The first week is the one that contains at least
</span><span class="cx">         # four days (with week start defaulting to MONDAY), so that means the 1st of January would fall
</span><span class="cx">         # on MO, TU, WE, TH.
</span><span class="cx">         if first_day in (DateTime.MONDAY, DateTime.TUESDAY, DateTime.WEDNESDAY, DateTime.THURSDAY):
</span><del>-            year_day = (weekno - 1) * 7 + current_day - first_day
</del><ins>+            offset = 0
</ins><span class="cx">         else:
</span><del>-            year_day = weekno * 7 + current_day - first_day
</del><ins>+            offset = 1
</ins><span class="cx"> 
</span><ins>+        year_day = (weekno - 1 + offset) * 7 + current_day - first_day
+
</ins><span class="cx">         # It is possible we have a negative offset which means go back to the prior year as part of
</span><span class="cx">         # week #1 exists at the end of that year.
</span><span class="cx">         if year_day &lt; 0:
</span><span class="lines">@@ -1143,6 +1170,16 @@
</span><span class="cx">         jobject.append(self.getJSONText())
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    # When doing recurrence iteration we sometimes need to preserve an invalid value for
+    # either day or month (though month is never invalid for Gregorian calendars it can
+    # be for non-Gregorian). For this class we simply set the stored attributes to their
+    # invalid values.
+    def setInvalid(self, year, month, day, isleapmonth=False):
+        self.mYear = year
+        self.mMonth = month
+        self.mDay = day
+
+
</ins><span class="cx">     def invalid(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Are any of the current fields invalid.
</span><span class="lines">@@ -1157,6 +1194,32 @@
</span><span class="cx">         return False
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def invalidSkip(self, skip):
+        &quot;&quot;&quot;
+        If this is an invalid value skip backward or forward or not at all.
+
+        @param skip: the skip mode (yes, backward, forward)
+        @type skip: L{int}
+        &quot;&quot;&quot;
+
+        if self.invalid():
+            if skip == definitions.eRecurrence_SKIP_YES:
+                # Leave it as invalid
+                pass
+            elif skip == definitions.eRecurrence_SKIP_BACKWARD:
+                if self.mDay &lt;= 0:
+                    self.mDay = 1
+                    self.offsetDay(-1)
+                else:
+                    self.mDay = utils.daysInMonth(self.mMonth, self.mYear)
+            elif skip == definitions.eRecurrence_SKIP_FORWARD:
+                if self.mDay &lt;= 0:
+                    self.mDay = 1
+                else:
+                    self.mDay = utils.daysInMonth(self.mMonth, self.mYear)
+                    self.offsetDay(1)
+
+
</ins><span class="cx">     def normalise(self):
</span><span class="cx">         # Normalise seconds
</span><span class="cx">         normalised_secs = self.mSeconds % 60
</span></span></pre></div>
<a id="PyCalendartrunksrcpycalendaricalendardefinitionspy"></a>
<div class="modfile"><h4>Modified: PyCalendar/trunk/src/pycalendar/icalendar/definitions.py (14190 => 14191)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/icalendar/definitions.py        2014-12-01 15:13:17 UTC (rev 14190)
+++ PyCalendar/trunk/src/pycalendar/icalendar/definitions.py        2014-12-01 15:15:13 UTC (rev 14191)
</span><span class="lines">@@ -230,6 +230,8 @@
</span><span class="cx"> eRecurrence_BYMONTH = 11
</span><span class="cx"> eRecurrence_BYSETPOS = 12
</span><span class="cx"> eRecurrence_WKST = 13
</span><ins>+eRecurrence_RSCALE = 14
+eRecurrence_SKIP = 15
</ins><span class="cx"> 
</span><span class="cx"> cICalValue_RECUR_FREQ = &quot;FREQ&quot;
</span><span class="cx"> cICalValue_RECUR_FREQ_LEN = 5
</span><span class="lines">@@ -256,6 +258,8 @@
</span><span class="cx"> cICalValue_RECUR_BYMONTH = &quot;BYMONTH&quot;
</span><span class="cx"> cICalValue_RECUR_BYSETPOS = &quot;BYSETPOS&quot;
</span><span class="cx"> cICalValue_RECUR_WKST = &quot;WKST&quot;
</span><ins>+cICalValue_RECUR_RSCALE = &quot;RSCALE&quot;
+cICalValue_RECUR_SKIP = &quot;SKIP&quot;
</ins><span class="cx"> 
</span><span class="cx"> eRecurrence_WEEKDAY_SU = 0
</span><span class="cx"> eRecurrence_WEEKDAY_MO = 1
</span><span class="lines">@@ -273,6 +277,14 @@
</span><span class="cx"> cICalValue_RECUR_WEEKDAY_FR = &quot;FR&quot;
</span><span class="cx"> cICalValue_RECUR_WEEKDAY_SA = &quot;SA&quot;
</span><span class="cx"> 
</span><ins>+eRecurrence_SKIP_YES = 0
+eRecurrence_SKIP_BACKWARD = 1
+eRecurrence_SKIP_FORWARD = 2
+
+cICalValue_RECUR_SKIP_YES = &quot;YES&quot;
+cICalValue_RECUR_SKIP_BACKWARD = &quot;BACKWARD&quot;
+cICalValue_RECUR_SKIP_FORWARD = &quot;FORWARD&quot;
+
</ins><span class="cx"> #     5545 Section 3.8.1.11
</span><span class="cx"> eStatus_VEvent_None = 0
</span><span class="cx"> eStatus_VEvent_Confirmed = 1
</span></span></pre></div>
<a id="PyCalendartrunksrcpycalendaricalendaricudatetimepy"></a>
<div class="addfile"><h4>Added: PyCalendar/trunk/src/pycalendar/icalendar/icudatetime.py (0 => 14191)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/icalendar/icudatetime.py                                (rev 0)
+++ PyCalendar/trunk/src/pycalendar/icalendar/icudatetime.py        2014-12-01 15:15:13 UTC (rev 14191)
</span><span class="lines">@@ -0,0 +1,1005 @@
</span><ins>+##
+#    Copyright (c) 2014 Cyrus Daboo. All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+##
+
+from cffi import FFI
+from __builtin__ import classmethod
+from pycalendar.datetime import DateTime
+from pycalendar.icalendar import definitions
+
+# Use cffi to get access to libicucore functions and constants
+ffi = FFI()
+hdr = &quot;&quot;&quot;
+    //#define U_FAILURE(x) ((x)&gt;U_ZERO_ERROR)
+
+    typedef double UDate;
+    UDate     ucal_getNow (void);
+
+    typedef void *        UCalendar;
+    typedef uint16_t    UChar;
+    enum      UCalendarType { UCAL_TRADITIONAL=0, UCAL_DEFAULT=0, UCAL_GREGORIAN, ... };
+    typedef enum UCalendarType UCalendarType;
+    enum UErrorCode {
+        U_ZERO_ERROR = 0
+    };
+    typedef enum UErrorCode UErrorCode;
+
+    enum UCalendarDaysOfWeek {
+      /** Sunday */
+      UCAL_SUNDAY = 1,
+      /** Monday */
+      UCAL_MONDAY,
+      /** Tuesday */
+      UCAL_TUESDAY,
+      /** Wednesday */
+      UCAL_WEDNESDAY,
+      /** Thursday */
+      UCAL_THURSDAY,
+      /** Friday */
+      UCAL_FRIDAY,
+      /** Saturday */
+      UCAL_SATURDAY
+    };
+
+    typedef enum UCalendarDaysOfWeek UCalendarDaysOfWeek;
+
+    enum UCalendarDateFields {
+      UCAL_ERA,
+      UCAL_YEAR,
+      UCAL_MONTH,
+      UCAL_WEEK_OF_YEAR,
+      UCAL_WEEK_OF_MONTH,
+      UCAL_DATE,
+      UCAL_DAY_OF_YEAR,
+      UCAL_DAY_OF_WEEK,
+      UCAL_DAY_OF_WEEK_IN_MONTH,
+      UCAL_AM_PM,
+      UCAL_HOUR,
+      UCAL_HOUR_OF_DAY,
+      UCAL_MINUTE,
+      UCAL_SECOND,
+      UCAL_MILLISECOND,
+      UCAL_ZONE_OFFSET,
+      UCAL_DST_OFFSET,
+      UCAL_YEAR_WOY,
+      UCAL_DOW_LOCAL,
+      UCAL_EXTENDED_YEAR,
+      UCAL_JULIAN_DAY,
+      UCAL_MILLISECONDS_IN_DAY,
+      UCAL_IS_LEAP_MONTH,
+      UCAL_FIELD_COUNT,
+      UCAL_DAY_OF_MONTH=5
+      };
+
+    typedef enum UCalendarDateFields UCalendarDateFields;
+
+    enum UCalendarMonths {
+      /** January */
+      UCAL_JANUARY,
+      /** February */
+      UCAL_FEBRUARY,
+      /** March */
+      UCAL_MARCH,
+      /** April */
+      UCAL_APRIL,
+      /** May */
+      UCAL_MAY,
+      /** June */
+      UCAL_JUNE,
+      /** July */
+      UCAL_JULY,
+      /** August */
+      UCAL_AUGUST,
+      /** September */
+      UCAL_SEPTEMBER,
+      /** October */
+      UCAL_OCTOBER,
+      /** November */
+      UCAL_NOVEMBER,
+      /** December */
+      UCAL_DECEMBER,
+      /** Value of the &lt;code&gt;UCAL_MONTH&lt;/code&gt; field indicating the
+        * thirteenth month of the year. Although the Gregorian calendar
+        * does not use this value, lunar calendars do.
+        */
+      UCAL_UNDECIMBER
+    };
+    typedef enum UCalendarMonths UCalendarMonths;
+
+    UCalendar * ucal_open(const UChar *zoneID, int32_t len, const char *locale, UCalendarType type, UErrorCode *status);
+    void        ucal_close(UCalendar *cal);
+    UCalendar * ucal_clone(const UCalendar* cal, UErrorCode* status);
+    void        ucal_setDate(UCalendar *cal, int32_t year, int32_t month, int32_t date, UErrorCode *status);
+
+    const char* ucal_getTZDataVersion (UErrorCode *status);
+
+    UDate ucal_getMillis(const UCalendar* cal,
+               UErrorCode* status);
+    void  ucal_setMillis(UCalendar* cal,
+               UDate        dateTime,
+               UErrorCode*  status);
+
+    int32_t ucal_get(const UCalendar* cal,
+         UCalendarDateFields  field,
+         UErrorCode*          status);
+    void ucal_set(UCalendar*  cal,
+         UCalendarDateFields  field,
+         int32_t              value);
+    void ucal_add(UCalendar*  cal,
+         UCalendarDateFields  field,
+         int32_t              amount,
+         UErrorCode*          status);
+
+    enum UCalendarLimitType {
+      /** Minimum value */
+      UCAL_MINIMUM,
+      /** Maximum value */
+      UCAL_MAXIMUM,
+      /** Greatest minimum value */
+      UCAL_GREATEST_MINIMUM,
+      /** Leaest maximum value */
+      UCAL_LEAST_MAXIMUM,
+      /** Actual minimum value */
+      UCAL_ACTUAL_MINIMUM,
+      /** Actual maximum value */
+      UCAL_ACTUAL_MAXIMUM
+    };
+
+    typedef enum UCalendarLimitType UCalendarLimitType;
+
+    int32_t ucal_getLimit(const UCalendar*     cal,
+                  UCalendarDateFields  field,
+                  UCalendarLimitType   type,
+                  UErrorCode*          status);
+&quot;&quot;&quot;
+
+ffi.cdef(hdr)
+ffi.verify(hdr.replace(&quot;, ...&quot;, &quot;&quot;))
+
+ICU = ffi.dlopen(&quot;libicucore&quot;)
+
+class ICUDateTime(object):
+    &quot;&quot;&quot;
+    An ICU-based L{DateTime} like class that supports non-Gregorian date-time values and arithmetic.
+    &quot;&quot;&quot;
+
+    RSCALE_GREGORIAN = &quot;gregorian&quot;
+    RSCALE_HEBREW = &quot;hebrew&quot;
+
+    RSCALE_CALCODE = {
+        &quot;gregorian&quot;: &quot;&quot;,
+        &quot;chinese&quot;: &quot;C&quot;,
+        &quot;islamic-civil&quot;: &quot;I&quot;,
+        &quot;hebrew&quot;: &quot;H&quot;,
+        &quot;ethiopic&quot;: &quot;E&quot;,
+    }
+
+    def __init__(self, rscale, ucal):
+        &quot;&quot;&quot;
+        Initialize using an ICU C{ucal} object and the name of the calendar scale.
+
+        @param rscale: calendar scale being used
+        @type rscale: L{str}
+        @param ucal: ICU ucal object
+        @type ucal: L{ICU.UCalendar*}
+        &quot;&quot;&quot;
+        self.rscale = rscale
+        self.ucal = ucal
+
+        self.mHours = 0
+        self.mMinutes = 0
+        self.mSeconds = 0
+
+        self.mDateOnly = True
+
+        self.mTZUTC = False
+        self.mTZID = None
+        self.mTZOffset = None
+
+        self.mInvalid = None
+
+
+    def __del__(self):
+        &quot;&quot;&quot;
+        Always close the ICU C{ucal} object.
+        &quot;&quot;&quot;
+        ICU.ucal_close(self.ucal)
+        self.ucal = None
+
+
+    def duplicate(self):
+        &quot;&quot;&quot;
+        Duplicate this object.
+        &quot;&quot;&quot;
+
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        clone = ICU.ucal_clone(self.ucal, error)
+        dup = ICUDateTime(self.rscale, clone)
+        dup._transferHHMMSS(self, dup)
+        dup.mInvalid = self.mInvalid
+
+        return dup
+
+
+    def __repr__(self):
+        return &quot;ICUDateTime: %s&quot; % (self.getText(),)
+
+
+    def __hash__(self):
+        return hash(self.getPosixTime())
+
+
+    @classmethod
+    def fromDateTime(cls, dt, rscale):
+        &quot;&quot;&quot;
+        Convert from a regular L{DateTime} to the specified calendar scale.
+
+        @param dt: the regular value to convert from
+        @type dt: L{DateTime}
+        @param rscale: the calendar scale to convert to
+        @type rscale: L{str}
+
+        @return: the new ICU object
+        @rtyope: L{ICUDateTime}
+        &quot;&quot;&quot;
+
+        # Try to create the ICU object that represents this date
+        gregorian = cls.fromDateComponents(cls.RSCALE_GREGORIAN, dt.getYear(), dt.getMonth(), dt.getDay())
+        cls._transferHHMMSS(dt, gregorian)
+        return gregorian.convertTo(rscale)
+
+
+    def toDateTime(self):
+        &quot;&quot;&quot;
+        Convert to a regular L{DateTime}.
+
+        @return: the converted object
+        @rtype: L{DateTime}
+        &quot;&quot;&quot;
+
+        # Try to create the ICU object that represents this date
+        gregorian = self if self.rscale.lower() == self.RSCALE_GREGORIAN else self.convertTo(self.RSCALE_GREGORIAN)
+        dt = DateTime(gregorian.getYear(), gregorian.getMonth(), gregorian.getDay())
+        self._transferHHMMSS(self, dt)
+        return dt
+
+
+    @classmethod
+    def _newUcal(cls, rscale):
+        &quot;&quot;&quot;
+        Create an ICU C{ucal} object for the specified calendar scale.
+
+        @param rscale: calendar scale to use
+        @type rscale: L{str}
+
+        @return: the ICU ucal object
+        @rtype: L{ICU.UCalendar*}
+        &quot;&quot;&quot;
+        calsystem = &quot;*@calendar={}&quot;.format(rscale)
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        ucal = ICU.ucal_open(ffi.NULL, -1, ffi.new(&quot;char[]&quot;, calsystem), ICU.UCAL_DEFAULT, error)
+        if error[0] != ICU.U_ZERO_ERROR:
+            raise ValueError(&quot;Unable to create ICU calendar for rscale '{}', code: {}&quot;.format(rscale, error))
+        return ucal
+
+
+    @classmethod
+    def fromDateComponents(cls, rscale, year, month, day, isleapmonth=False):
+        &quot;&quot;&quot;
+        Create ICU calendar for the specified calendar scale with the specified components.
+
+        @param dt: the regular value to convert from
+        @type dt: L{DateTime}
+        @param rscale: the calendar scale to convert to
+        @type rscale: L{str}
+        @param year: the year component
+        @type year: L{int}
+        @param month: the month component
+        @type month: L{int}
+        @param day: the day component
+        @type day: L{int}
+        @param isleapmonth: the leap month component
+        @type isleapmonth: L{bool}
+
+        @return: the new object
+        @rtype: L{ICUDateTime}
+        &quot;&quot;&quot;
+
+        # Try to create the ICU object that represents this date
+        ucal = cls._newUcal(rscale)
+
+        month, isleapmonth = cls._adjustToICULeapMonth(rscale, month, isleapmonth)
+
+        ICU.ucal_set(ucal, ICU.UCAL_EXTENDED_YEAR, year)
+        ICU.ucal_set(ucal, ICU.UCAL_MONTH, cls._numericMonthToICU(month))
+        ICU.ucal_set(ucal, ICU.UCAL_DAY_OF_MONTH, day)
+        ICU.ucal_set(ucal, ICU.UCAL_IS_LEAP_MONTH, isleapmonth)
+
+        return ICUDateTime(rscale, ucal)
+
+
+    @classmethod
+    def _numericMonthToICU(cls, month):
+        &quot;&quot;&quot;
+        Map our month numbers (1..13) to ICU constants.
+
+        @param month: the month to map
+        @type month: L{int}
+
+        @return: the ICU constant
+        @rtype: L{ICU.UCalendarMonths}
+        &quot;&quot;&quot;
+        return {
+            1: ICU.UCAL_JANUARY,
+            2: ICU.UCAL_FEBRUARY,
+            3: ICU.UCAL_MARCH,
+            4: ICU.UCAL_APRIL,
+            5: ICU.UCAL_MAY,
+            6: ICU.UCAL_JUNE,
+            7: ICU.UCAL_JULY,
+            8: ICU.UCAL_AUGUST,
+            9: ICU.UCAL_SEPTEMBER,
+            10: ICU.UCAL_OCTOBER,
+            11: ICU.UCAL_NOVEMBER,
+            12: ICU.UCAL_DECEMBER,
+            13: ICU.UCAL_UNDECIMBER,
+        }[month]
+
+
+    @classmethod
+    def _icuToNumericMonth(cls, month):
+        &quot;&quot;&quot;
+        Map ICU constants to our month numbers (1..13).
+
+        @param month: the ICU constant to map
+        @type month: L{ICU.UCalendarMonths}
+
+        @return: the month
+        @rtype: L{int}
+        &quot;&quot;&quot;
+        return {
+            ICU.UCAL_JANUARY: 1,
+            ICU.UCAL_FEBRUARY: 2,
+            ICU.UCAL_MARCH: 3,
+            ICU.UCAL_APRIL: 4,
+            ICU.UCAL_MAY: 5,
+            ICU.UCAL_JUNE: 6,
+            ICU.UCAL_JULY: 7,
+            ICU.UCAL_AUGUST: 8,
+            ICU.UCAL_SEPTEMBER: 9,
+            ICU.UCAL_OCTOBER: 10,
+            ICU.UCAL_NOVEMBER: 11,
+            ICU.UCAL_DECEMBER: 12,
+            ICU.UCAL_UNDECIMBER: 13,
+        }[month]
+
+
+    @classmethod
+    def _adjustToICULeapMonth(cls, rscale, month, isleapmonth):
+        &quot;&quot;&quot;
+        For the Hebrew calendar, ICU uses a count of 13 months rather than 12 months
+        plus an &quot;isleapmonth&quot; indicator. So when converting to/from ICU we need to make
+        that adjustment as we always use 12 months + isleapmonth. This method converts
+        from our internal representation to what ICU uses.
+
+        @param rscale: calendar scale to convert to
+        @type rscale: L{str}
+        @param month: month number (12 month cycle)
+        @type month: L{int}
+        @param isleapmonth: is leap month indicator
+        @type isleapmonth: L{bool} of L{None}
+
+        @return: a tuple of the ICU-mapped month number and isleapmonth indicator
+        @rtype: L{tuple} of (L{int}, L{bool}
+        &quot;&quot;&quot;
+
+        if rscale.lower() == cls.RSCALE_HEBREW:
+            if month == 5 and isleapmonth:
+                month = 6
+                isleapmonth = None
+            elif month &gt;= 6:
+                month += 1
+        return (month, isleapmonth,)
+
+
+    @classmethod
+    def _adjustFromICULeapMonth(cls, rscale, month, isleapmonth):
+        &quot;&quot;&quot;
+        For the Hebrew calendar, ISU uses a count of 13 months rather than 12 months
+        plus an &quot;isleapmonth&quot; indicator. So when converting to/from ICU we need to make
+        that adjustment as we always use 12 months + isleapmonth. This method converts
+        to our internal representation from what ICU uses.
+
+        @param rscale: calendar scale to convert from
+        @type rscale: L{str}
+        @param month: month number (13 month cycle)
+        @type month: L{int}
+        @param isleapmonth: is leap month indicator
+        @type isleapmonth: L{bool} of L{None}
+
+        @return: a tuple of the month number and isleapmonth indicator
+        @rtype: L{tuple} of (L{int}, L{bool}
+        &quot;&quot;&quot;
+
+        if rscale.lower() == cls.RSCALE_HEBREW:
+            isleapmonth = False
+            if month == 6:
+                isleapmonth = True
+            elif month &gt;= 6:
+                month -= 1
+        return (month, isleapmonth,)
+
+
+    @classmethod
+    def _transferHHMMSS(cls, from_dt, to_dt):
+        &quot;&quot;&quot;
+        Transfer the time and timezone components from one L{ICUDateTime} to another.
+
+        @param from_dt: object to copy from
+        @type from_dt: L{ICUDateTime}
+        @param to_dt: object to copy to
+        @type to_dt: L{ICUDateTime}
+        &quot;&quot;&quot;
+        if not from_dt.isDateOnly():
+            to_dt.setDateOnly(False)
+            to_dt.setHHMMSS(from_dt.getHours(), from_dt.getMinutes(), from_dt.getSeconds())
+            to_dt.setTimezoneID(from_dt.getTimezoneID())
+            to_dt.setTimezoneUTC(from_dt.getTimezoneUTC())
+
+
+    def convertTo(self, rscale):
+        &quot;&quot;&quot;
+        Convert this L{ICUDateTime} into another one in the specified calendar scale.
+
+        @param rscale: calendar scale to convert to
+        @type rscale: L{str}
+
+        @return: the converted date
+        @rtype: L{ICUDateTime}
+        &quot;&quot;&quot;
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        converted = self._newUcal(rscale)
+        millis = ICU.ucal_getMillis(self.ucal, error)
+        ICU.ucal_setMillis(converted, millis, error)
+        dt = ICUDateTime(rscale, converted)
+        self._transferHHMMSS(self, dt)
+
+        # For some reason this is needed to properly setup all the fields. Without this, I have
+        # noticed that ucal_getLimit does not return the correct day of month limit for a Chinese
+        # calendar.
+        dt.getDateComponents()
+
+        return dt
+
+
+    def getDateComponents(self):
+        &quot;&quot;&quot;
+        Get the year, month, day, isleapmonth components in our internal format from
+        this ICU date.
+
+        @return: the date components
+        @rtype: L{tuple} of (L{int}, L{int}, L{int}, L{bool})
+        &quot;&quot;&quot;
+        year = self.getYear()
+        month = self.getMonth()
+        day = self.getDay()
+        isleapmonth = self.getLeapMonth()
+
+        month, isleapmonth = self._adjustFromICULeapMonth(self.rscale, month, isleapmonth)
+
+        return (year, month, day, isleapmonth,)
+
+
+    def getPosixTime(self):
+        &quot;&quot;&quot;
+        Return an integer representing a standard offset in seconds from a specific
+        epoch. This is used for sorting similar object.
+        &quot;&quot;&quot;
+
+        # Use the ICU &quot;millis&quot; for this.
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        return ICU.ucal_getMillis(self.ucal, error)
+
+
+    def isDateOnly(self):
+        return self.mDateOnly
+
+
+    def setDateOnly(self, date_only):
+        self.mDateOnly = date_only
+
+
+    def setYYMMDD(self, year, month, day, isleapmonth=False):
+        self.setYear(year)
+        self.setMonth(month, isleapmonth)
+        self.setDay(day)
+
+        self.testInvalid(year, month, day, isleapmonth)
+
+
+    def getYear(self):
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        return ICU.ucal_get(self.ucal, ICU.UCAL_EXTENDED_YEAR, error)
+
+
+    def setYear(self, year):
+        _ignore_old_year, old_month, old_day, old_isleapmonth = self.getDateComponents()
+        ICU.ucal_set(self.ucal, ICU.UCAL_EXTENDED_YEAR, year)
+        self.testInvalid(year, old_month, old_day, old_isleapmonth)
+
+
+    def offsetYear(self, diff_year):
+        &quot;&quot;&quot;
+        Offset the ICU date year component by the specified amount.
+
+        @param diff_year: amount to offset
+        @type diff_year: L{int}
+        &quot;&quot;&quot;
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        ICU.ucal_add(self.ucal, ICU.UCAL_EXTENDED_YEAR, diff_year, error)
+
+
+    def getMonth(self):
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        return self._icuToNumericMonth(ICU.ucal_get(self.ucal, ICU.UCAL_MONTH, error))
+
+
+    def setMonth(self, month, isleapmonth=False):
+        old_year, _ignore_old_month, old_day, _ignore_old_isleapmonth = self.getDateComponents()
+        ICU.ucal_set(self.ucal, ICU.UCAL_MONTH, self._numericMonthToICU(month))
+        ICU.ucal_set(self.ucal, ICU.UCAL_IS_LEAP_MONTH, isleapmonth)
+        self.testInvalid(old_year, month, old_day, isleapmonth)
+
+
+    def offsetMonth(self, diff_month):
+        &quot;&quot;&quot;
+        Offset the ICU date month component by the specified amount.
+
+        @param diff_year: amount to offset
+        @type diff_year: L{int}
+        &quot;&quot;&quot;
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        ICU.ucal_add(self.ucal, ICU.UCAL_MONTH, diff_month, error)
+
+
+    def getLeapMonth(self):
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        return ICU.ucal_get(self.ucal, ICU.UCAL_IS_LEAP_MONTH, error) != 0
+
+
+    def getDay(self):
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        return ICU.ucal_get(self.ucal, ICU.UCAL_DAY_OF_MONTH, error)
+
+
+    def setDay(self, day):
+        old_year, old_month, _ignore_old_day, old_isleapmonth = self.getDateComponents()
+        ICU.ucal_set(self.ucal, ICU.UCAL_DAY_OF_MONTH, day)
+        self.testInvalid(old_year, old_month, day, old_isleapmonth)
+
+
+    def offsetDay(self, diff_day):
+        &quot;&quot;&quot;
+        Offset the ICU date month component by the specified amount.
+
+        @param diff_year: amount to offset
+        @type diff_year: L{int}
+        &quot;&quot;&quot;
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        ICU.ucal_add(self.ucal, ICU.UCAL_DAY_OF_MONTH, diff_day, error)
+
+
+    def setYearDay(self, day, allow_invalid=False):
+
+        # Find the limit for the current year
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        limit = ICU.ucal_getLimit(self.ucal, ICU.UCAL_DAY_OF_YEAR, ICU.UCAL_ACTUAL_MAXIMUM, error)
+
+        if day &gt; 0:
+            ICU.ucal_set(self.ucal, ICU.UCAL_DAY_OF_YEAR, min(day, limit))
+            if day &gt; limit and allow_invalid:
+                self.setInvalid(self.getYear(), 1, day)
+            else:
+                self.clearInvalid()
+        elif day &lt; 0:
+            offset = limit + day + 1
+            ICU.ucal_set(self.ucal, ICU.UCAL_DAY_OF_YEAR, max(offset, 1))
+            if offset &lt;= 0 and allow_invalid:
+                self.setInvalid(self.getYear(), 1, day)
+            else:
+                self.clearInvalid()
+
+
+    def getYearDay(self):
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        return ICU.ucal_get(self.ucal, ICU.UCAL_DAY_OF_YEAR, error)
+
+
+    def setMonthDay(self, day, allow_invalid=False):
+
+        # Find the limit for the current year
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        limit = ICU.ucal_getLimit(self.ucal, ICU.UCAL_DAY_OF_MONTH, ICU.UCAL_ACTUAL_MAXIMUM, error)
+
+        if day &gt; 0:
+            ICU.ucal_set(self.ucal, ICU.UCAL_DAY_OF_MONTH, min(day, limit))
+            if day &gt; limit and allow_invalid:
+                y, m, _ignore_d, l = self.getDateComponents()
+                self.setInvalid(y, m, day, l)
+            else:
+                self.clearInvalid()
+
+        elif day &lt; 0:
+            offset = limit + day + 1
+            ICU.ucal_set(self.ucal, ICU.UCAL_DAY_OF_MONTH, max(offset, 1))
+            if offset &lt;= 0 and allow_invalid:
+                y, m, _ignore_d, l = self.getDateComponents()
+                self.setInvalid(y, m, day, l)
+            else:
+                self.clearInvalid()
+
+
+    def isMonthDay(self, day):
+        if day &gt; 0:
+            return self.getDay() == day
+        elif day &lt; 0:
+            error = ffi.new(&quot;UErrorCode *&quot;, 0)
+            limit = ICU.ucal_getLimit(self.ucal, ICU.UCAL_DAY_OF_MONTH, ICU.UCAL_ACTUAL_MAXIMUM, error)
+            return self.getDay() - 1 - limit == day
+        else:
+            return False
+
+
+    def setWeekNo(self, weekno):
+        &quot;&quot;&quot;
+        Set the current date to one with the same day of the week in the current year with the
+        specified week number. Note this might cause the year to shift backwards or forwards
+        if the date is at the boundary between two years.
+
+        @param weekno: the week number to set (currently must be positive)
+        @type weekno: C{int}
+        &quot;&quot;&quot;
+
+        # Only supported for Gregorian calendars
+        if self.rscale.lower() != self.RSCALE_GREGORIAN:
+            raise ValueError(&quot;Week numbers only supported for Gregorian calendars&quot;)
+        dt = self.toDateTime()
+        dt.setWeekNo(weekno)
+        self.setYYMMDD(dt.getYear(), dt.getMonth(), dt.getDay())
+
+
+    def getWeekNo(self):
+        &quot;&quot;&quot;
+        Return the ISO week number for the current date.
+        &quot;&quot;&quot;
+
+        # Only supported for Gregorian calendars
+        if self.rscale.lower() != self.RSCALE_GREGORIAN:
+            raise ValueError(&quot;Week numbers only supported for Gregorian calendars&quot;)
+        dt = self.toDateTime()
+        return dt.getWeekNo()
+
+
+    def isWeekNo(self, weekno):
+        # This is the iso 8601 week number definition
+
+        if weekno &gt; 0:
+            return self.getWeekNo() == weekno
+        else:
+            # This needs to calculate the negative offset from the last week in
+            # the current year
+            return False
+
+
+    def setDayOfWeekInYear(self, offset, day):
+        # Set to first day in year
+        self.setYYMMDD(self.getYear(), 1, 1, False)
+
+        # Determine first weekday in year
+        first_day = self.getDayOfWeek()
+
+        if offset &gt; 0:
+            cycle = (offset - 1) * 7 + day
+            cycle -= first_day
+            if first_day &gt; day:
+                cycle += 7
+            self.offsetDay(cycle)
+        elif offset &lt; 0:
+            # Find the limit for the current year
+            error = ffi.new(&quot;UErrorCode *&quot;, 0)
+            limit = ICU.ucal_getLimit(self.ucal, ICU.UCAL_DAY_OF_YEAR, ICU.UCAL_ACTUAL_MAXIMUM, error)
+
+            first_day += limit - 1
+            first_day %= 7
+
+            cycle = (-offset - 1) * 7 - day
+            cycle += first_day
+            if day &gt; first_day:
+                cycle += 7
+            self.offsetDay(limit - cycle - 1)
+
+        self.clearInvalid()
+
+
+    def setDayOfWeekInMonth(self, offset, day, allow_invalid=False):
+        # Set to first day in month
+        y, m, d, l = self.getDateComponents()
+        self.setYYMMDD(y, m, 1, l)
+
+        # Determine first weekday in month
+        first_day = self.getDayOfWeek()
+
+        if offset &gt; 0:
+            cycle = (offset - 1) * 7 + day
+            cycle -= first_day
+            if first_day &gt; day:
+                cycle += 7
+            mday = cycle + 1
+            self.offsetDay(cycle)
+        elif offset &lt; 0:
+            # Find the limit for the current year
+            error = ffi.new(&quot;UErrorCode *&quot;, 0)
+            days_in_month = ICU.ucal_getLimit(self.ucal, ICU.UCAL_DAY_OF_MONTH, ICU.UCAL_ACTUAL_MAXIMUM, error)
+
+            first_day += days_in_month - 1
+            first_day %= 7
+
+            cycle = (-offset - 1) * 7 - day
+            cycle += first_day
+            if day &gt; first_day:
+                cycle += 7
+            mday = days_in_month - cycle
+            self.offsetDay(days_in_month - cycle - 1)
+
+        if self.getDay() != mday and allow_invalid:
+            self.setInvalid(y, m, d, l)
+        else:
+            self.clearInvalid()
+
+
+    def isDayOfWeekInMonth(self, offset, day):
+        # First of the actual day must match
+        if self.getDayOfWeek() != day:
+            return False
+
+        # If there is no count the we match any of this day in the month
+        if offset == 0:
+            return True
+
+        # Create temp date-time with the appropriate parameters and then
+        # compare
+        temp = self.duplicate()
+        temp.setDayOfWeekInMonth(offset, day)
+
+        # Now compare dates
+        return self.getDateComponents() == temp.getDateComponents()
+
+
+    def getDayOfWeek(self):
+        error = ffi.new(&quot;UErrorCode *&quot;, 0)
+        return ICU.ucal_get(self.ucal, ICU.UCAL_DAY_OF_WEEK, error) - 1
+
+
+    def setHHMMSS(self, hours, minutes, seconds):
+        if (self.mHours != hours) or (self.mMinutes != minutes) or (self.mSeconds != seconds):
+            self.mHours = hours
+            self.mMinutes = minutes
+            self.mSeconds = seconds
+
+
+    def getHours(self):
+        return self.mHours
+
+
+    def setHours(self, hours):
+        if self.mHours != hours:
+            self.mHours = hours
+
+
+    def offsetHours(self, diff_hour):
+        self.mHours += diff_hour
+        self.normalise()
+
+
+    def getMinutes(self):
+        return self.mMinutes
+
+
+    def setMinutes(self, minutes):
+        if self.mMinutes != minutes:
+            self.mMinutes = minutes
+
+
+    def offsetMinutes(self, diff_minutes):
+        self.mMinutes += diff_minutes
+        self.normalise()
+
+
+    def getSeconds(self):
+        return self.mSeconds
+
+
+    def setSeconds(self, seconds):
+        if self.mSeconds != seconds:
+            self.mSeconds = seconds
+
+
+    def offsetSeconds(self, diff_seconds):
+        self.mSeconds += diff_seconds
+        self.normalise()
+
+
+    def getTimezoneUTC(self):
+        return self.mTZUTC
+
+
+    def setTimezoneUTC(self, utc):
+        if self.mTZUTC != utc:
+            self.mTZUTC = utc
+
+
+    def getTimezoneID(self):
+        return self.mTZID
+
+
+    def setTimezoneID(self, tzid):
+        self.mTZUTC = False
+        self.mTZID = tzid
+
+
+    # When doing recurrence iteration we sometimes need to preserve an invalid value for
+    # either day or month (though month is never invalid for Gregorian calendars it can
+    # be for non-Gregorian). For this class we simply set the stored attributes to their
+    # invalid values.
+    def setInvalid(self, year, month, day, isleapmonth=False):
+        self.mInvalid = (year, month, day, isleapmonth,)
+
+
+    def testInvalid(self, year, month, day, isleapmonth=False):
+        &quot;&quot;&quot;
+        If the requested set of YYMMDDLL does not match the current set of YYMMDDLL then the requested
+        set was invalid.
+        &quot;&quot;&quot;
+        components = self.getDateComponents()
+        if components != (year, month, day, isleapmonth,):
+            self.setInvalid(year, month, day, isleapmonth)
+        else:
+            self.clearInvalid()
+
+
+    def clearInvalid(self):
+        self.mInvalid = None
+
+
+    def invalid(self):
+        &quot;&quot;&quot;
+        Are any of the current fields invalid.
+        &quot;&quot;&quot;
+
+        # Right now we only care about invalid days of the month (e.g. February 30th). In the
+        # future we may also want to look for invalid times during a DST transition.
+
+        return self.mInvalid is not None
+
+
+    def invalidSkip(self, skip):
+        &quot;&quot;&quot;
+        If this is an invalid value skip backward or forward or not at all.
+
+        @param skip: the skip mode (yes, backward, forward)
+        @type skip: L{int}
+        &quot;&quot;&quot;
+
+        if self.mInvalid:
+            if skip == definitions.eRecurrence_SKIP_YES:
+                # Leave it as invalid
+                pass
+            else:
+                # Need to determine which component (day or month/leap) is invalid,
+                # and react accordingly
+                _ignore_y, m, d, l = self.getDateComponents()
+                if (m, l) != (self.mInvalid[1], self.mInvalid[3]):
+                    # Month/leap is invalid
+                    if skip == definitions.eRecurrence_SKIP_BACKWARD:
+                        # Defaults to skip backward
+                        pass
+                    elif skip == definitions.eRecurrence_SKIP_FORWARD:
+                        self.offsetDay(1)
+
+                elif d != self.mInvalid[2]:
+                    if skip == definitions.eRecurrence_SKIP_BACKWARD:
+                        if self.mInvalid[2] &lt; 1:
+                            self.offsetDay(-1)
+                    elif skip == definitions.eRecurrence_SKIP_FORWARD:
+                        if self.mInvalid[2] &gt; 0:
+                            self.offsetDay(1)
+
+                self.clearInvalid()
+
+
+    def normalise(self):
+        # Normalise seconds
+        normalised_secs = self.mSeconds % 60
+        adjustment_mins = self.mSeconds / 60
+        if normalised_secs &lt; 0:
+            normalised_secs += 60
+            adjustment_mins -= 1
+        self.mSeconds = normalised_secs
+        self.mMinutes += adjustment_mins
+
+        # Normalise minutes
+        normalised_mins = self.mMinutes % 60
+        adjustment_hours = self.mMinutes / 60
+        if normalised_mins &lt; 0:
+            normalised_mins += 60
+            adjustment_hours -= 1
+        self.mMinutes = normalised_mins
+        self.mHours += adjustment_hours
+
+        # Normalise hours
+        normalised_hours = self.mHours % 24
+        adjustment_days = self.mHours / 24
+        if normalised_hours &lt; 0:
+            normalised_hours += 24
+            adjustment_days -= 1
+        self.mHours = normalised_hours
+
+        self.offsetDay(adjustment_days)
+
+        # Wipe the time if date only
+        if self.mDateOnly:
+            self.mSeconds = self.mMinutes = self.mHours = 0
+
+
+    def getText(self):
+        &quot;&quot;&quot;
+        Generate an ISO-8601 string representation of this ICU date. Use a code
+        prefix for the calendar scale.
+
+        @return: the ISO-8601 text
+        @rtype L{str}
+        &quot;&quot;&quot;
+        calcode = self.RSCALE_CALCODE.get(self.rscale.lower(), &quot;{}:&quot;.format(self.rscale))
+        if calcode:
+            calcode = &quot;{{{}}}&quot;.format(calcode)
+        year, month, day, isleapmonth = self.getDateComponents()
+        date = &quot;{}{:04d}{:02d}{}{:02d}&quot;.format(calcode, year, month, &quot;L&quot; if isleapmonth else &quot;&quot;, day)
+        if not self.isDateOnly():
+            date += &quot;T{:02d}{:02d}{:02d}{}&quot;.format(self.mHours, self.mMinutes, self.mSeconds, &quot;Z&quot; if self.mTZUTC else &quot;&quot;)
+        return date
+
+
+if __name__ == '__main__':
+    newyear = ICUDateTime.fromDateComponents(&quot;chinese&quot;, 4651, 1, 1, False)
+    print(&quot;From: {} to {}&quot;.format(
+        newyear.getText(),
+        newyear.convertTo(&quot;gregorian&quot;).getText(),
+    ))
+
+    for i in range(0):
+        newyear.offsetDay(1)
+        print(&quot;From: {} to {}&quot;.format(
+            newyear.getText(),
+            newyear.convertTo(&quot;gregorian&quot;).getText(),
+        ))
+
+    offset = 1
+    greg = ICUDateTime.fromDateComponents(&quot;gregorian&quot;, 2014, 1, 31, False)
+    greg.offsetMonth(offset)
+    print(greg.getText())
+
+    greg = DateTime(2014, 1, 31)
+    greg.offsetMonth(offset)
+    print(greg.getText())
</ins></span></pre></div>
<a id="PyCalendartrunksrcpycalendaricalendarrecuriterpy"></a>
<div class="addfile"><h4>Added: PyCalendar/trunk/src/pycalendar/icalendar/recuriter.py (0 => 14191)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/icalendar/recuriter.py                                (rev 0)
+++ PyCalendar/trunk/src/pycalendar/icalendar/recuriter.py        2014-12-01 15:15:13 UTC (rev 14191)
</span><span class="lines">@@ -0,0 +1,151 @@
</span><ins>+##
+#    Copyright (c) 2014 Cyrus Daboo. All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+##
+
+from pycalendar.icalendar import definitions
+from pycalendar.icalendar.icudatetime import ICUDateTime
+import collections
+
+class RecurrenceIterator(collections.Iterator):
+    &quot;&quot;&quot;
+    An iterator that iterates a simple recurrence pattern.
+    &quot;&quot;&quot;
+
+    def __init__(self, start, freq, interval, rscale=None, skip=definitions.eRecurrence_SKIP_YES, allow_invalid=False):
+        &quot;&quot;&quot;
+        @param start: the start date-time
+        @type start: L{DateTime} or L{ICUDateTime}
+        @param freq: the frequency of iteration
+        @type freq: L{int}
+        @param interval: the interval for each iteration
+        @type interval: L{int}
+        @param rscale: calendar scale to apply recurrence pattern to
+        @type rscale: L{str}
+        @param skip: skipping behavior for invalid dates
+        @type skip: L{int}
+        @param allow_invalid: whether or not invalid values are allowed
+        @type allow_invalid: L{InvalidDateTime}
+        &quot;&quot;&quot;
+        self.start = start
+        self.freq = freq
+        self.interval = interval
+        self.rscale = rscale
+        self.skip = skip
+        self.allow_invalid = allow_invalid
+
+        self.step = 0
+
+        # If an RSCALE is set, the C{self.start} value is a normal L{DateTime} object but we want
+        # to have the recurrence apply to the non-Gregorian calendar. So convert the C{self.start}
+        # value into the corresponding L{ICUDateTime} object.
+        if self.rscale:
+            self.start = ICUDateTime.fromDateTime(self.start, self.rscale)
+
+
+    def __iter__(self):
+        return self
+
+
+    def next(self):
+        &quot;&quot;&quot;
+        Iterate one step of the recurrence. Always return an L{DateTime} for an rscale based
+        recurrence.
+
+        @return: the resulting date-time - this object is not re-used by the iterator so can be used
+            directly by the caller without any need to copy it
+        @rtype L{DateTime}
+        &quot;&quot;&quot;
+
+        dt = self.nextraw()
+
+        # Always return the L{DateTime} equivalent when using C{self.rscale}
+        return dt.toDateTime() if self.rscale else dt
+
+
+    def nextraw(self):
+        &quot;&quot;&quot;
+        Iterate one step of the recurrence using the native date-time calendar scale, and return
+        the native value.
+
+        @return: the resulting date-time - this object is not re-used by the iterator so can be used
+            directly by the caller without any need to copy it
+        @rtype L{DateTime} or L{ICUDateTime}
+        &quot;&quot;&quot;
+
+        dt = self.start.duplicate()
+
+        # Add appropriate interval
+        if self.freq == definitions.eRecurrence_SECONDLY:
+            dt.offsetSeconds(self.step)
+        elif self.freq == definitions.eRecurrence_MINUTELY:
+            dt.offsetMinutes(self.step)
+        elif self.freq == definitions.eRecurrence_HOURLY:
+            dt.offsetHours(self.step)
+        elif self.freq == definitions.eRecurrence_DAILY:
+            dt.offsetDay(self.step)
+        elif self.freq == definitions.eRecurrence_WEEKLY:
+            dt.offsetDay(7 * self.step)
+        elif self.freq == definitions.eRecurrence_MONTHLY:
+            dt.offsetMonth(self.step)
+
+            # Check whether the day matches the start - if not we stepped
+            # to an invalid date so apply skip behavior
+            if dt.getDay() != self.start.getDay():
+                if self.allow_invalid:
+                    dt.setInvalid(dt.getYear(), dt.getMonth(), self.start.getDay(), dt.getLeapMonth())
+                elif self.skip == definitions.eRecurrence_SKIP_YES:
+                    # Iterate until we have a valid month
+                    while dt.getDay() != self.start.getDay():
+                        self.step += self.interval
+                        dt = self.start.duplicate()
+                        dt.offsetMonth(self.step)
+                elif self.skip == definitions.eRecurrence_SKIP_BACKWARD:
+                    # Both ICU and PyCalendar skip back by default
+                    pass
+                elif self.skip == definitions.eRecurrence_SKIP_FORWARD:
+                    # Go one day forward
+                    dt.offsetDay(1)
+
+        elif self.freq == definitions.eRecurrence_YEARLY:
+            dt.offsetYear(self.step)
+
+            # Check whether the month/day matches the start - if not we stepped
+            # to an invalid date so apply skip behavior
+            if dt.getDay() != self.start.getDay() or dt.getMonth() != self.start.getMonth() or dt.getLeapMonth() != self.start.getLeapMonth():
+                if self.allow_invalid:
+                    dt.setInvalid(dt.getYear(), self.start.getMonth(), self.start.getDay(), self.start.getLeapMonth())
+                elif self.skip == definitions.eRecurrence_SKIP_YES:
+                    # Iterate until we have a valid date-time
+                    while dt.getDay() != self.start.getDay() or dt.getMonth() != self.start.getMonth() or dt.getLeapMonth() != self.start.getLeapMonth():
+                        self.step += self.interval
+                        dt = self.start.duplicate()
+                        dt.offsetYear(self.step)
+                elif self.skip == definitions.eRecurrence_SKIP_BACKWARD:
+                    # Both ICU and PyCalendar skip back by default
+                    pass
+                elif self.skip == definitions.eRecurrence_SKIP_FORWARD:
+                    # Go one day forward
+                    dt.offsetDay(1)
+
+        self.step += self.interval
+
+        return dt
+
+
+if __name__ == '__main__':
+    icudt = ICUDateTime.fromDateComponents(&quot;gregorian&quot;, 2014, 1, 31)
+    iter = RecurrenceIterator(icudt, definitions.eRecurrence_MONTHLY, 1, definitions.eRecurrence_SKIP_BACKWARD)
+    for i in range(12):
+        print(iter.next().getText())
</ins></span></pre></div>
<a id="PyCalendartrunksrcpycalendaricalendarrecurrencepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/trunk/src/pycalendar/icalendar/recurrence.py (14190 => 14191)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/icalendar/recurrence.py        2014-12-01 15:13:17 UTC (rev 14190)
+++ PyCalendar/trunk/src/pycalendar/icalendar/recurrence.py        2014-12-01 15:15:13 UTC (rev 14191)
</span><span class="lines">@@ -21,6 +21,7 @@
</span><span class="cx"> from pycalendar.valueutils import ValueMixin
</span><span class="cx"> import cStringIO as StringIO
</span><span class="cx"> import xml.etree.cElementTree as XML
</span><ins>+from pycalendar.icalendar.recuriter import RecurrenceIterator
</ins><span class="cx"> 
</span><span class="cx"> def WeekDayNumCompare_compare(w1, w2):
</span><span class="cx"> 
</span><span class="lines">@@ -55,6 +56,8 @@
</span><span class="cx">         definitions.cICalValue_RECUR_YEARLY   : definitions.eRecurrence_YEARLY,
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    cFreqInverseMap = dict([(v, k) for k, v in cFreqMap.items()])
+
</ins><span class="cx">     cFreqToXMLMap = {
</span><span class="cx">         definitions.eRecurrence_SECONDLY: xmldefinitions.recur_freq_secondly,
</span><span class="cx">         definitions.eRecurrence_MINUTELY: xmldefinitions.recur_freq_minutely,
</span><span class="lines">@@ -80,6 +83,8 @@
</span><span class="cx">         definitions.cICalValue_RECUR_BYMONTH    : definitions.eRecurrence_BYMONTH,
</span><span class="cx">         definitions.cICalValue_RECUR_BYSETPOS   : definitions.eRecurrence_BYSETPOS,
</span><span class="cx">         definitions.cICalValue_RECUR_WKST       : definitions.eRecurrence_WKST,
</span><ins>+        definitions.cICalValue_RECUR_RSCALE     : definitions.eRecurrence_RSCALE,
+        definitions.cICalValue_RECUR_SKIP       : definitions.eRecurrence_SKIP,
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     cWeekdayMap = {
</span><span class="lines">@@ -94,6 +99,20 @@
</span><span class="cx"> 
</span><span class="cx">     cWeekdayRecurMap = dict([(v, k) for k, v in cWeekdayMap.items()])
</span><span class="cx"> 
</span><ins>+    cSkipMap = {
+        definitions.cICalValue_RECUR_SKIP_YES       : definitions.eRecurrence_SKIP_YES,
+        definitions.cICalValue_RECUR_SKIP_BACKWARD  : definitions.eRecurrence_SKIP_BACKWARD,
+        definitions.cICalValue_RECUR_SKIP_FORWARD   : definitions.eRecurrence_SKIP_FORWARD,
+    }
+
+    cSkipInverseMap = dict([(v, k) for k, v in cSkipMap.items()])
+
+    cSkipToXMLMap = {
+        definitions.eRecurrence_SKIP_YES: xmldefinitions.recur_skip_yes,
+        definitions.eRecurrence_SKIP_BACKWARD: xmldefinitions.recur_skip_backward,
+        definitions.eRecurrence_SKIP_FORWARD: xmldefinitions.recur_skip_forward,
+    }
+
</ins><span class="cx">     cUnknownIndex = -1
</span><span class="cx"> 
</span><span class="cx">     def __init__(self):
</span><span class="lines">@@ -103,6 +122,7 @@
</span><span class="cx">     def duplicate(self):
</span><span class="cx">         other = Recurrence()
</span><span class="cx"> 
</span><ins>+        other.mRscale = self.mRscale
</ins><span class="cx">         other.mFreq = self.mFreq
</span><span class="cx"> 
</span><span class="cx">         other.mUseCount = self.mUseCount
</span><span class="lines">@@ -112,6 +132,9 @@
</span><span class="cx">             other.mUntil = self.mUntil.duplicate()
</span><span class="cx"> 
</span><span class="cx">         other.mInterval = self.mInterval
</span><ins>+
+        other.mSkip = self.mSkip
+
</ins><span class="cx">         if self.mBySeconds is not None:
</span><span class="cx">             other.mBySeconds = self.mBySeconds[:]
</span><span class="cx">         if self.mByMinutes is not None:
</span><span class="lines">@@ -145,6 +168,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def init_Recurrence(self):
</span><ins>+
+        self.mRscale = None
</ins><span class="cx">         self.mFreq = definitions.eRecurrence_YEARLY
</span><span class="cx"> 
</span><span class="cx">         self.mUseCount = False
</span><span class="lines">@@ -154,6 +179,9 @@
</span><span class="cx">         self.mUntil = None
</span><span class="cx"> 
</span><span class="cx">         self.mInterval = 1
</span><ins>+
+        self.mSkip = None
+
</ins><span class="cx">         self.mBySeconds = None
</span><span class="cx">         self.mByMinutes = None
</span><span class="cx">         self.mByHours = None
</span><span class="lines">@@ -174,12 +202,14 @@
</span><span class="cx"> 
</span><span class="cx">     def __hash__(self):
</span><span class="cx">         return hash((
</span><ins>+            self.mRscale,
</ins><span class="cx">             self.mFreq,
</span><span class="cx">             self.mUseCount,
</span><span class="cx">             self.mCount,
</span><span class="cx">             self.mUseUntil,
</span><span class="cx">             self.mUntil,
</span><span class="cx">             self.mInterval,
</span><ins>+            self.mSkip,
</ins><span class="cx">             tuple(self.mBySeconds) if self.mBySeconds else None,
</span><span class="cx">             tuple(self.mByMinutes) if self.mByMinutes else None,
</span><span class="cx">             tuple(self.mByHours) if self.mByHours else None,
</span><span class="lines">@@ -205,10 +235,12 @@
</span><span class="cx"> 
</span><span class="cx">     def equals(self, comp):
</span><span class="cx">         return (
</span><del>-            (self.mFreq == comp.mFreq)
</del><ins>+            (self.mRscale == comp.mRscale)
+            and (self.mFreq == comp.mFreq)
</ins><span class="cx">             and (self.mUseCount == comp.mUseCount) and (self.mCount == comp.mCount)
</span><span class="cx">             and (self.mUseUntil == comp.mUseUntil) and (self.mUntil == comp.mUntil)
</span><span class="cx">             and (self.mInterval == comp.mInterval)
</span><ins>+            and (self.mSkip == comp.mSkip)
</ins><span class="cx">             and self.equalsNum(self.mBySeconds, comp.mBySeconds)
</span><span class="cx">             and self.equalsNum(self.mByMinutes, comp.mByMinutes)
</span><span class="cx">             and self.equalsNum(self.mByHours, comp.mByHours)
</span><span class="lines">@@ -274,6 +306,14 @@
</span><span class="cx">             setattr(self, attr, value)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getRscale(self):
+        return self.mRscale
+
+
+    def setRscale(self, rscale):
+        self._setAndclearIfChanged(&quot;mRscale&quot;, rscale)
+
+
</ins><span class="cx">     def getFreq(self):
</span><span class="cx">         return self.mFreq
</span><span class="cx"> 
</span><span class="lines">@@ -322,6 +362,24 @@
</span><span class="cx">         self._setAndclearIfChanged(&quot;mInterval&quot;, interval)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getSkip(self):
+        return self.mSkip
+
+
+    def effectiveSkip(self):
+        &quot;&quot;&quot;
+        The default skip value depends on whether RSCALE is used or not
+        &quot;&quot;&quot;
+        if self.mSkip is None:
+            return definitions.eRecurrence_SKIP_YES if self.mRscale is None else definitions.eRecurrence_SKIP_BACKWARD
+        else:
+            return self.mSkip
+
+
+    def setSkip(self, skip):
+        self._setAndclearIfChanged(&quot;mSkip&quot;, skip)
+
+
</ins><span class="cx">     def getByMonth(self):
</span><span class="cx">         return self.mByMonth
</span><span class="cx"> 
</span><span class="lines">@@ -473,7 +531,7 @@
</span><span class="cx">                 if self.mByMonth is not None:
</span><span class="cx">                     raise ValueError(&quot;Recurrence: Only one BYMONTH allowed&quot;)
</span><span class="cx">                 self.mByMonth = []
</span><del>-                self.parseList(tvalue, self.mByMonth, 1, 12, errmsg=&quot;Recurrence: Invalid BYMONTH value&quot;)
</del><ins>+                self.parseMonthNumList(tvalue, self.mByMonth, 1, 12, errmsg=&quot;Recurrence: Invalid BYMONTH value&quot;)
</ins><span class="cx"> 
</span><span class="cx">             elif index == definitions.eRecurrence_BYSETPOS:
</span><span class="cx">                 if self.mBySetPos is not None:
</span><span class="lines">@@ -487,7 +545,21 @@
</span><span class="cx">                     raise ValueError(&quot;Recurrence: Invalid WKST value&quot;)
</span><span class="cx">                 self.mWeekstart = index
</span><span class="cx"> 
</span><ins>+            elif index == definitions.eRecurrence_RSCALE:
+                self.mRscale = tvalue.upper()
</ins><span class="cx"> 
</span><ins>+            elif index == definitions.eRecurrence_SKIP:
+                # Get the SKIP value
+                index = Recurrence.cSkipMap.get(tvalue, Recurrence.cUnknownIndex)
+                if index == Recurrence.cUnknownIndex:
+                    raise ValueError(&quot;Recurrence: Invalid SKIP value&quot;)
+                self.mSkip = index
+
+        # Final validity checks
+        if self.mRscale is None and self.mSkip is not None:
+            raise ValueError(&quot;Recurrence: SKIP only allowed with RSCALE&quot;)
+
+
</ins><span class="cx">     def parseList(self, txt, list, min=None, max=None, allowNegative=False, errmsg=&quot;&quot;):
</span><span class="cx"> 
</span><span class="cx">         if &quot;,&quot; in txt:
</span><span class="lines">@@ -507,6 +579,33 @@
</span><span class="cx">             list.append(value)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def parseMonthNumList(self, txt, list, min=None, max=None, allowNegative=False, errmsg=&quot;&quot;):
+        &quot;&quot;&quot;
+        Month numbers can include &quot;L&quot; leap month suffix.
+        &quot;&quot;&quot;
+
+        if &quot;,&quot; in txt:
+            tokens = txt.split(&quot;,&quot;)
+        else:
+            tokens = (txt,)
+
+        for token in tokens:
+            if token.endswith(&quot;L&quot;):
+                suffix = True
+                token = token[:-1]
+            else:
+                suffix = False
+            value = int(token)
+            if not allowNegative and value &lt; 0:
+                raise ValueError(errmsg)
+            avalue = abs(value)
+            if min is not None and avalue &lt; min:
+                raise ValueError(errmsg)
+            if max is not None and avalue &gt; max:
+                raise ValueError(errmsg)
+            list.append((value, suffix,))
+
+
</ins><span class="cx">     def parseListDW(self, txt, list, errmsg=&quot;&quot;):
</span><span class="cx"> 
</span><span class="cx">         if &quot;,&quot; in txt:
</span><span class="lines">@@ -542,30 +641,16 @@
</span><span class="cx"> 
</span><span class="cx">     def generate(self, os):
</span><span class="cx">         try:
</span><ins>+            if self.mRscale:
+                os.write(definitions.cICalValue_RECUR_RSCALE)
+                os.write(&quot;=&quot;)
+                os.write(self.mRscale.upper())
+                os.write(&quot;;&quot;)
+
</ins><span class="cx">             os.write(definitions.cICalValue_RECUR_FREQ)
</span><span class="cx">             os.write(&quot;=&quot;)
</span><ins>+            os.write(self.cFreqInverseMap[self.mFreq])
</ins><span class="cx"> 
</span><del>-            if self.mFreq == definitions.eRecurrence_SECONDLY:
-                os.write(definitions.cICalValue_RECUR_SECONDLY)
-
-            elif self.mFreq == definitions.eRecurrence_MINUTELY:
-                os.write(definitions.cICalValue_RECUR_MINUTELY)
-
-            elif self.mFreq == definitions.eRecurrence_HOURLY:
-                os.write(definitions.cICalValue_RECUR_HOURLY)
-
-            elif self.mFreq == definitions.eRecurrence_DAILY:
-                os.write(definitions.cICalValue_RECUR_DAILY)
-
-            elif self.mFreq == definitions.eRecurrence_WEEKLY:
-                os.write(definitions.cICalValue_RECUR_WEEKLY)
-
-            elif self.mFreq == definitions.eRecurrence_MONTHLY:
-                os.write(definitions.cICalValue_RECUR_MONTHLY)
-
-            elif self.mFreq == definitions.eRecurrence_YEARLY:
-                os.write(definitions.cICalValue_RECUR_YEARLY)
-
</del><span class="cx">             if self.mUseCount:
</span><span class="cx">                 os.write(&quot;;&quot;)
</span><span class="cx">                 os.write(definitions.cICalValue_RECUR_COUNT)
</span><span class="lines">@@ -583,6 +668,12 @@
</span><span class="cx">                 os.write(&quot;=&quot;)
</span><span class="cx">                 os.write(str(self.mInterval))
</span><span class="cx"> 
</span><ins>+            if self.mSkip is not None:
+                os.write(&quot;;&quot;)
+                os.write(definitions.cICalValue_RECUR_SKIP)
+                os.write(&quot;=&quot;)
+                os.write(self.cSkipInverseMap[self.mSkip])
+
</ins><span class="cx">             self.generateList(os, definitions.cICalValue_RECUR_BYSECOND, self.mBySeconds)
</span><span class="cx">             self.generateList(os, definitions.cICalValue_RECUR_BYMINUTE, self.mByMinutes)
</span><span class="cx">             self.generateList(os, definitions.cICalValue_RECUR_BYHOUR, self.mByHours)
</span><span class="lines">@@ -624,7 +715,7 @@
</span><span class="cx">             self.generateList(os, definitions.cICalValue_RECUR_BYMONTHDAY, self.mByMonthDay)
</span><span class="cx">             self.generateList(os, definitions.cICalValue_RECUR_BYYEARDAY, self.mByYearDay)
</span><span class="cx">             self.generateList(os, definitions.cICalValue_RECUR_BYWEEKNO, self.mByWeekNo)
</span><del>-            self.generateList(os, definitions.cICalValue_RECUR_BYMONTH, self.mByMonth)
</del><ins>+            self.generateMonthNumList(os, definitions.cICalValue_RECUR_BYMONTH, self.mByMonth)
</ins><span class="cx">             self.generateList(os, definitions.cICalValue_RECUR_BYSETPOS, self.mBySetPos)
</span><span class="cx"> 
</span><span class="cx">             # MO is the default so we do not need it
</span><span class="lines">@@ -672,10 +763,31 @@
</span><span class="cx">                 os.write(str(e))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def generateMonthNumList(self, os, title, items):
+        &quot;&quot;&quot;
+        Month numbers can include &quot;L&quot; leap month suffix.
+        &quot;&quot;&quot;
+
+        if (items is not None) and (len(items) != 0):
+            os.write(&quot;;&quot;)
+            os.write(title)
+            os.write(&quot;=&quot;)
+            comma = False
+            for item in items:
+                if comma:
+                    os.write(&quot;,&quot;)
+                comma = True
+                os.write(str(item[0]) + (&quot;L&quot; if item[1] else &quot;&quot;))
+
+
</ins><span class="cx">     def writeXML(self, node, namespace):
</span><span class="cx"> 
</span><span class="cx">         recur = XML.SubElement(node, xmlutils.makeTag(namespace, xmldefinitions.value_recur))
</span><span class="cx"> 
</span><ins>+        if self.mRscale:
+            freq = XML.SubElement(recur, xmlutils.makeTag(namespace, xmldefinitions.recur_rscale))
+            freq.text = self.mRscale
+
</ins><span class="cx">         freq = XML.SubElement(recur, xmlutils.makeTag(namespace, xmldefinitions.recur_freq))
</span><span class="cx">         freq.text = self.cFreqToXMLMap[self.mFreq]
</span><span class="cx"> 
</span><span class="lines">@@ -690,6 +802,10 @@
</span><span class="cx">             interval = XML.SubElement(recur, xmlutils.makeTag(namespace, xmldefinitions.recur_interval))
</span><span class="cx">             interval.text = str(self.mInterval)
</span><span class="cx"> 
</span><ins>+        if self.mSkip is not None:
+            skip = XML.SubElement(recur, xmlutils.makeTag(namespace, xmldefinitions.recur_skip))
+            skip.text = self.cSkipToXMLMap[self.mSkip]
+
</ins><span class="cx">         self.writeXMLList(recur, namespace, xmldefinitions.recur_bysecond, self.mBySeconds)
</span><span class="cx">         self.writeXMLList(recur, namespace, xmldefinitions.recur_byminute, self.mByMinutes)
</span><span class="cx">         self.writeXMLList(recur, namespace, xmldefinitions.recur_byhour, self.mByHours)
</span><span class="lines">@@ -706,7 +822,7 @@
</span><span class="cx">         self.writeXMLList(recur, namespace, xmldefinitions.recur_bymonthday, self.mByMonthDay)
</span><span class="cx">         self.writeXMLList(recur, namespace, xmldefinitions.recur_byyearday, self.mByYearDay)
</span><span class="cx">         self.writeXMLList(recur, namespace, xmldefinitions.recur_byweekno, self.mByWeekNo)
</span><del>-        self.writeXMLList(recur, namespace, xmldefinitions.recur_bymonth, self.mByMonth)
</del><ins>+        self.writeXMLMonthNumList(recur, namespace, xmldefinitions.recur_bymonth, self.mByMonth)
</ins><span class="cx">         self.writeXMLList(recur, namespace, xmldefinitions.recur_bysetpos, self.mBySetPos)
</span><span class="cx"> 
</span><span class="cx">         # MO is the default so we do not need it
</span><span class="lines">@@ -722,6 +838,17 @@
</span><span class="cx">                 child.text = str(item)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def writeXMLMonthNumList(self, node, namespace, name, items):
+        &quot;&quot;&quot;
+        Month numbers can include &quot;L&quot; leap month suffix.
+        &quot;&quot;&quot;
+
+        if items is not None and len(items) != 0:
+            for item in items:
+                child = XML.SubElement(node, xmlutils.makeTag(namespace, name))
+                child.text = str(item[0]) + (&quot;L&quot; if item[1] else &quot;&quot;)
+
+
</ins><span class="cx">     def parseJSON(self, jobject):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         jCal splits the value into components. We need to convert that back to the
</span><span class="lines">@@ -750,6 +877,9 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         jdict = {}
</span><span class="cx"> 
</span><ins>+        if self.mRscale:
+            jdict[xmldefinitions.recur_rscale] = self.mRscale
+
</ins><span class="cx">         jdict[xmldefinitions.recur_freq] = self.cFreqToXMLMap[self.mFreq]
</span><span class="cx"> 
</span><span class="cx">         if self.mUseCount:
</span><span class="lines">@@ -760,6 +890,9 @@
</span><span class="cx">         if self.mInterval &gt; 1:
</span><span class="cx">             jdict[xmldefinitions.recur_interval] = self.mInterval
</span><span class="cx"> 
</span><ins>+        if self.mSkip is not None:
+            jdict[xmldefinitions.recur_skip] = self.cSkipToXMLMap[self.mSkip]
+
</ins><span class="cx">         if self.mBySeconds:
</span><span class="cx">             jdict[xmldefinitions.recur_bysecond] = self.mBySeconds
</span><span class="cx">         if self.mByMinutes:
</span><span class="lines">@@ -784,7 +917,7 @@
</span><span class="cx">         if self.mByWeekNo:
</span><span class="cx">             jdict[xmldefinitions.recur_byweekno] = self.mByWeekNo
</span><span class="cx">         if self.mByMonth:
</span><del>-            jdict[xmldefinitions.recur_bymonth] = self.mByMonth
</del><ins>+            jdict[xmldefinitions.recur_bymonth] = [(str(item[0]) + &quot;L&quot;) if item[1] else item[0] for item in self.mByMonth]
</ins><span class="cx">         if self.mBySetPos:
</span><span class="cx">             jdict[xmldefinitions.recur_bysetpos] = self.mBySetPos
</span><span class="cx"> 
</span><span class="lines">@@ -925,9 +1058,7 @@
</span><span class="cx">         return limited
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def simpleExpand(self, start, range, items, float_offset):
-        start_iter = start.duplicate()
-        ctr = 0
</del><ins>+    def simpleExpand(self, start, range, results, float_offset):
</ins><span class="cx"> 
</span><span class="cx">         if self.mUseUntil:
</span><span class="cx">             float_until = self.mUntil.duplicate()
</span><span class="lines">@@ -935,35 +1066,30 @@
</span><span class="cx">                 float_until.setTimezoneID(0)
</span><span class="cx">                 float_until.offsetSeconds(float_offset)
</span><span class="cx"> 
</span><ins>+        riter = RecurrenceIterator(start, self.mFreq, self.mInterval, self.mRscale, self.effectiveSkip())
</ins><span class="cx">         while True:
</span><ins>+            start_iter = riter.next()
+
</ins><span class="cx">             # Exit if after period we want
</span><span class="cx">             if range.isDateAfterPeriod(start_iter):
</span><span class="cx">                 return False
</span><ins>+            elif self.mUseUntil:
+                # Exit if next item is after until (it is OK if it is the same as
+                # UNTIL as UNTIL is inclusive)
+                if start_iter &gt; float_until:
+                    return True
</ins><span class="cx"> 
</span><span class="cx">             # Add current one to list
</span><del>-            items.append(start_iter.duplicate())
</del><ins>+            results.append(start_iter)
</ins><span class="cx"> 
</span><del>-            # Get next item
-            start_iter.recur(self.mFreq, self.mInterval, allow_invalid=True)
-            while start_iter.invalid():
-                start_iter.recur(self.mFreq, self.mInterval, allow_invalid=True)
-
</del><span class="cx">             # Check limits
</span><span class="cx">             if self.mUseCount:
</span><del>-                # Bump counter and exit if over
-                ctr += 1
-                if ctr &gt;= self.mCount:
</del><ins>+                # Exit if max count reached
+                if len(results) &gt;= self.mCount:
</ins><span class="cx">                     return True
</span><del>-            elif self.mUseUntil:
-                # Exit if next item is after until (its OK if its the same as
-                # UNTIL as UNTIL is inclusive)
-                if start_iter &gt; float_until:
-                    return True
</del><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def complexExpand(self, start, range, items, float_offset):
-        start_iter = start.duplicate()
-        ctr = 0
</del><ins>+    def complexExpand(self, start, range, results, float_offset):
</ins><span class="cx"> 
</span><span class="cx">         if self.mUseUntil:
</span><span class="cx">             float_until = self.mUntil.duplicate()
</span><span class="lines">@@ -971,16 +1097,15 @@
</span><span class="cx">                 float_until.setTimezoneID(None)
</span><span class="cx">                 float_until.offsetSeconds(float_offset)
</span><span class="cx"> 
</span><del>-        # Always add the initial instance DTSTART
-        if self.mUseCount:
-            # Bump counter and exit if over
-            ctr += 1
-            if ctr &gt;= self.mCount:
-                return True
</del><ins>+        # Allow invalid values during the complex iteration as those may end up being coerced to a valid value
+        # when a BYxxx rule expands
+        riter = RecurrenceIterator(start, self.mFreq, self.mInterval, self.mRscale, self.effectiveSkip(), allow_invalid=True)
+        while True:
+            # Keep the iterated date-time value in its native calendar scale
+            start_iter = riter.nextraw()
</ins><span class="cx"> 
</span><del>-        # Need to re-initialise start based on BYxxx rules
-        while True:
-            # Behaviour is based on frequency
</del><ins>+            # Each recurrence cycle may generate multiple items based on the frequency and other rule parts,
+            # but we need to limit based on until and range and count
</ins><span class="cx">             set_items = []
</span><span class="cx"> 
</span><span class="cx">             if self.mFreq == definitions.eRecurrence_SECONDLY:
</span><span class="lines">@@ -1005,12 +1130,23 @@
</span><span class="cx">                 self.generateYearlySet(start_iter, set_items)
</span><span class="cx"> 
</span><span class="cx">             # Ignore if it is invalid
</span><ins>+            def _invalidMap(dt):
+                dt.invalidSkip(self.effectiveSkip())
+                return dt
+            set_items = map(lambda x: _invalidMap(x), set_items)
</ins><span class="cx">             set_items = filter(lambda x: not x.invalid(), set_items)
</span><span class="cx"> 
</span><span class="cx">             # Always sort the set as BYxxx rules may not be sorted
</span><span class="cx">             # set_items.sort(cmp=DateTime.sort)
</span><span class="cx">             set_items.sort(key=lambda x: x.getPosixTime())
</span><span class="cx"> 
</span><ins>+            if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0):
+                set_items[:] = self.bySetPosLimit(set_items)
+
+            # Remaining behavior requires L{DateTime} objects
+            if self.mRscale:
+                set_items[:] = map(lambda date: date.toDateTime(), set_items)
+
</ins><span class="cx">             # Process each one in the generated set
</span><span class="cx">             for iter in set_items:
</span><span class="cx"> 
</span><span class="lines">@@ -1027,34 +1163,22 @@
</span><span class="cx">                     return False
</span><span class="cx"> 
</span><span class="cx">                 # Exit if beyond the UNTIL limit
</span><del>-                if self.mUseUntil:
</del><ins>+                elif self.mUseUntil:
</ins><span class="cx">                     # Exit if next item is after until (its OK if its the same
</span><span class="cx">                     # as UNTIL as UNTIL is inclusive)
</span><span class="cx">                     if iter &gt; float_until:
</span><span class="cx">                         return True
</span><span class="cx"> 
</span><del>-                # Special for start instance
-                if (ctr == 1) and (start == iter):
-                    continue
-
</del><span class="cx">                 # Add current one to list
</span><del>-                items.append(iter)
</del><ins>+                results.append(iter)
</ins><span class="cx"> 
</span><span class="cx">                 # Check limits
</span><span class="cx">                 if self.mUseCount:
</span><del>-                    # Bump counter and exit if over
-                    ctr += 1
-                    if ctr &gt;= self.mCount:
</del><ins>+                    # Exit if max count reached
+                    if len(results) &gt;= self.mCount:
</ins><span class="cx">                         return True
</span><span class="cx"> 
</span><del>-            # Exit if after period we want
-            if range.isDateAfterPeriod(start_iter):
-                return False
</del><span class="cx"> 
</span><del>-            # Get next item
-            start_iter.recur(self.mFreq, self.mInterval, allow_invalid=True)
-
-
</del><span class="cx">     def clear(self):
</span><span class="cx">         self.mCached = False
</span><span class="cx">         self.mFullyCached = False
</span><span class="lines">@@ -1065,8 +1189,7 @@
</span><span class="cx">     # IMPORTANT ExcludeFutureRecurrence assumes mCacheStart is setup with the
</span><span class="cx">     # owning VEVENT's DTSTART
</span><span class="cx">     # Currently this method is only called when a recurrence is being removed
</span><del>-    # so
-    # the recurrence data should be cached
</del><ins>+    # so the recurrence data should be cached
</ins><span class="cx"> 
</span><span class="cx">     # Exclude dates on or after the chosen one
</span><span class="cx">     def excludeFutureRecurrence(self, exclude):
</span><span class="lines">@@ -1098,7 +1221,7 @@
</span><span class="cx">         # All possible BYxxx are valid, though some combinations are not
</span><span class="cx"> 
</span><span class="cx">         # Start with initial date-time
</span><del>-        items.append(start.duplicate())
</del><ins>+        items.append(start)
</ins><span class="cx"> 
</span><span class="cx">         if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
</span><span class="cx">             items[:] = self.byMonthExpand(items)
</span><span class="lines">@@ -1138,15 +1261,12 @@
</span><span class="cx">         if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0):
</span><span class="cx">             items[:] = self.bySecondExpand(items)
</span><span class="cx"> 
</span><del>-        if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0):
-            items[:] = self.bySetPosLimit(items)
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def generateMonthlySet(self, start, items):
</span><span class="cx">         # Cannot have BYYEARDAY and BYWEEKNO
</span><span class="cx"> 
</span><span class="cx">         # Start with initial date-time
</span><del>-        items.append(start.duplicate())
</del><ins>+        items.append(start)
</ins><span class="cx"> 
</span><span class="cx">         if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
</span><span class="cx">             # BYMONTH limits the range of possible values
</span><span class="lines">@@ -1183,15 +1303,12 @@
</span><span class="cx">         if ((self.mBySeconds is not None) and (len(self.mBySeconds) != 0)):
</span><span class="cx">             items[:] = self.bySecondExpand(items)
</span><span class="cx"> 
</span><del>-        if ((self.mBySetPos is not None) and (len(self.mBySetPos) != 0)):
-            items[:] = self.bySetPosLimit(items)
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def generateWeeklySet(self, start, items):
</span><span class="cx">         # Cannot have BYYEARDAY and BYMONTHDAY
</span><span class="cx"> 
</span><span class="cx">         # Start with initial date-time
</span><del>-        items.append(start.duplicate())
</del><ins>+        items.append(start)
</ins><span class="cx"> 
</span><span class="cx">         if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
</span><span class="cx">             # BYMONTH limits the range of possible values
</span><span class="lines">@@ -1220,15 +1337,12 @@
</span><span class="cx">         if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0):
</span><span class="cx">             items[:] = self.bySecondExpand(items)
</span><span class="cx"> 
</span><del>-        if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0):
-            items[:] = self.bySetPosLimit(items)
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def generateDailySet(self, start, items):
</span><span class="cx">         # Cannot have BYYEARDAY
</span><span class="cx"> 
</span><span class="cx">         # Start with initial date-time
</span><del>-        items.append(start.duplicate())
</del><ins>+        items.append(start)
</ins><span class="cx"> 
</span><span class="cx">         if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
</span><span class="cx">             # BYMONTH limits the range of possible values
</span><span class="lines">@@ -1262,15 +1376,12 @@
</span><span class="cx">         if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0):
</span><span class="cx">             items[:] = self.bySecondExpand(items)
</span><span class="cx"> 
</span><del>-        if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0):
-            items[:] = self.bySetPosLimit(items)
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def generateHourlySet(self, start, items):
</span><span class="cx">         # Cannot have BYYEARDAY
</span><span class="cx"> 
</span><span class="cx">         # Start with initial date-time
</span><del>-        items.append(start.duplicate())
</del><ins>+        items.append(start)
</ins><span class="cx"> 
</span><span class="cx">         if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
</span><span class="cx">             # BYMONTH limits the range of possible values
</span><span class="lines">@@ -1306,15 +1417,12 @@
</span><span class="cx">         if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0):
</span><span class="cx">             items[:] = self.bySecondExpand(items)
</span><span class="cx"> 
</span><del>-        if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0):
-            items[:] = self.bySetPosLimit(items)
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def generateMinutelySet(self, start, items):
</span><span class="cx">         # Cannot have BYYEARDAY
</span><span class="cx"> 
</span><span class="cx">         # Start with initial date-time
</span><del>-        items.append(start.duplicate())
</del><ins>+        items.append(start)
</ins><span class="cx"> 
</span><span class="cx">         if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
</span><span class="cx">             # BYMONTH limits the range of possible values
</span><span class="lines">@@ -1352,15 +1460,12 @@
</span><span class="cx">         if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0):
</span><span class="cx">             items[:] = self.bySecondExpand(items)
</span><span class="cx"> 
</span><del>-        if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0):
-            items[:] = self.bySetPosLimit(items)
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def generateSecondlySet(self, start, items):
</span><span class="cx">         # Cannot have BYYEARDAY
</span><span class="cx"> 
</span><span class="cx">         # Start with initial date-time
</span><del>-        items.append(start.duplicate())
</del><ins>+        items.append(start)
</ins><span class="cx"> 
</span><span class="cx">         if (self.mByMonth is not None) and (len(self.mByMonth) != 0):
</span><span class="cx">             # BYMONTH limits the range of possible values
</span><span class="lines">@@ -1400,10 +1505,7 @@
</span><span class="cx">             if (len(items) == 0):
</span><span class="cx">                 return
</span><span class="cx"> 
</span><del>-        if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0):
-            items[:] = self.bySetPosLimit(items)
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def byMonthExpand(self, dates):
</span><span class="cx">         # Loop over all input items
</span><span class="cx">         output = []
</span><span class="lines">@@ -1412,7 +1514,7 @@
</span><span class="cx">             # insert into output
</span><span class="cx">             for iter2 in self.mByMonth:
</span><span class="cx">                 temp = iter1.duplicate()
</span><del>-                temp.setMonth(iter2)
</del><ins>+                temp.setMonth(*iter2)
</ins><span class="cx">                 output.append(temp)
</span><span class="cx"> 
</span><span class="cx">         return output
</span><span class="lines">@@ -1581,125 +1683,40 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def byMonthLimit(self, dates):
</span><del>-        # Loop over all input items
-        output = []
-        for iter1 in dates:
-            # Loop over each BYMONTH and indicate keep if input month matches
-            keep = False
-            for iter2 in self.mByMonth:
-                keep = (iter1.getMonth() == iter2)
-                if keep:
-                    break
</del><ins>+        # Keep each date that matches a BYMONTH
+        return filter(lambda date: date.getMonth() in self.mByMonth, dates)
</ins><span class="cx"> 
</span><del>-            if keep:
-                output.append(iter1)
</del><span class="cx"> 
</span><del>-        return output
-
-
</del><span class="cx">     def byWeekNoLimit(self, dates):
</span><del>-        # Loop over all input items
-        output = []
-        for iter1 in dates:
-            # Loop over each BYWEEKNO and indicate keep if input month matches
-            keep = False
-            for iter2 in self.mByWeekNo:
-                keep = iter1.isWeekNo(iter2)
-                if keep:
-                    break
</del><ins>+        # Keep each date that matches a BYWEEKNO
+        return filter(lambda date: any([date.isWeekNo(iter) for iter in self.mByWeekNo]), dates)
</ins><span class="cx"> 
</span><del>-            if keep:
-                output.append(iter1)
</del><span class="cx"> 
</span><del>-        return output
-
-
</del><span class="cx">     def byMonthDayLimit(self, dates):
</span><del>-        # Loop over all input items
-        output = []
-        for iter1 in dates:
-            # Loop over each BYMONTHDAY and indicate keep if input month
-            # matches
-            keep = False
-            for iter2 in self.mByMonthDay:
-                keep = iter1.isMonthDay(iter2)
-                if keep:
-                    break
</del><ins>+        # Keep each date that matches a BYMONTHDAY
+        return filter(lambda date: any([date.isMonthDay(iter) for iter in self.mByMonthDay]), dates)
</ins><span class="cx"> 
</span><del>-            if keep:
-                output.append(iter1)
</del><span class="cx"> 
</span><del>-        return output
-
-
</del><span class="cx">     def byDayLimit(self, dates):
</span><del>-        # Loop over all input items
-        output = []
-        for iter1 in dates:
-            # Loop over each BYDAY and indicate keep if input month matches
-            keep = False
-            for iter2 in self.mByDay:
-                keep = iter1.isDayOfWeekInMonth(iter2[0], iter2[1])
-                if keep:
-                    break
</del><ins>+        # Keep each date that matches a BYDAY
+        return filter(lambda date: any([date.isDayOfWeekInMonth(iter[0], iter[1]) for iter in self.mByDay]), dates)
</ins><span class="cx"> 
</span><del>-            if keep:
-                output.append(iter1)
</del><span class="cx"> 
</span><del>-        return output
-
-
</del><span class="cx">     def byHourLimit(self, dates):
</span><del>-        # Loop over all input items
-        output = []
-        for iter1 in dates:
-            # Loop over each BYHOUR and indicate keep if input hour matches
-            keep = False
-            for iter2 in self.mByHours:
-                keep = (iter1.getHours() == iter2)
-                if keep:
-                    break
</del><ins>+        # Keep each date that matches a BYHOUR
+        return filter(lambda date: date.getHours() in self.mByHours, dates)
</ins><span class="cx"> 
</span><del>-            if keep:
-                output.append(iter1)
</del><span class="cx"> 
</span><del>-        return output
-
-
</del><span class="cx">     def byMinuteLimit(self, dates):
</span><del>-        # Loop over all input items
-        output = []
-        for iter1 in dates:
-            # Loop over each BYMINUTE and indicate keep if input minute matches
-            keep = False
-            for iter2 in self.mByMinutes:
-                keep = (iter1.getMinutes() == iter2)
-                if keep:
-                    break
</del><ins>+        # Keep each date that matches a BYMINUTE
+        return filter(lambda date: date.getMinutes() in self.mByMinutes, dates)
</ins><span class="cx"> 
</span><del>-            if keep:
-                output.append(iter1)
</del><span class="cx"> 
</span><del>-        return output
-
-
</del><span class="cx">     def bySecondLimit(self, dates):
</span><del>-        # Loop over all input items
-        output = []
-        for iter1 in dates:
-            # Loop over each BYSECOND and indicate keep if input second matches
-            keep = False
-            for iter2 in self.mBySeconds:
-                keep = (iter1.getSeconds() == iter2)
-                if keep:
-                    break
</del><ins>+        # Keep each date that matches a BYSECOND
+        return filter(lambda date: date.getSeconds() in self.mBySeconds, dates)
</ins><span class="cx"> 
</span><del>-            if keep:
-                output.append(iter1)
</del><span class="cx"> 
</span><del>-        return output
-
-
</del><span class="cx">     def bySetPosLimit(self, dates):
</span><span class="cx">         # The input dates MUST be sorted in order for this to work properly
</span><span class="cx">         # dates.sort(cmp=DateTime.sort)
</span></span></pre></div>
<a id="PyCalendartrunksrcpycalendaricalendarteststest_icudatetimepy"></a>
<div class="addfile"><h4>Added: PyCalendar/trunk/src/pycalendar/icalendar/tests/test_icudatetime.py (0 => 14191)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/icalendar/tests/test_icudatetime.py                                (rev 0)
+++ PyCalendar/trunk/src/pycalendar/icalendar/tests/test_icudatetime.py        2014-12-01 15:15:13 UTC (rev 14191)
</span><span class="lines">@@ -0,0 +1,225 @@
</span><ins>+# coding: utf-8
+##
+#    Copyright (c) 2007-2013 Cyrus Daboo. All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+##
+
+import unittest
+from pycalendar.datetime import DateTime
+from pycalendar.icalendar.icudatetime import ICUDateTime
+
+class TestICUDateTime(unittest.TestCase):
+    &quot;&quot;&quot;
+    Test L{ICUDateTime}
+    &quot;&quot;&quot;
+
+    def testRoundtripDateText(self):
+
+        data_date = (
+            (&quot;gregorian&quot;, 2011, 1, 2, False, &quot;20110102&quot;,),
+            (&quot;gregorian&quot;, 1, 1, 2, False, &quot;00010102&quot;,),
+            (&quot;chinese&quot;, 4651, 1, 1, False, &quot;{C}46510101&quot;,),
+            (&quot;chinese&quot;, 4651, 9, 1, True, &quot;{C}465109L01&quot;,),
+        )
+
+        for rscale, y, m, d, l, result in data_date:
+            dt = ICUDateTime.fromDateComponents(rscale, y, m, d, l)
+            self.assertEqual(dt.getText(), result, &quot;Failed on: %s&quot; % (result,))
+
+
+    def testRoundtripDateTime(self):
+
+        data = (
+            (&quot;20110102&quot;, &quot;gregorian&quot;, &quot;20110102&quot;),
+            (&quot;20110102T010203Z&quot;, &quot;gregorian&quot;, &quot;20110102T010203Z&quot;),
+            (&quot;00010102&quot;, &quot;gregorian&quot;, &quot;00010102&quot;),
+            (&quot;20140102&quot;, &quot;chinese&quot;, &quot;{C}46501202&quot;),
+            (&quot;20140102T010203Z&quot;, &quot;chinese&quot;, &quot;{C}46501202T010203Z&quot;),
+            (&quot;20141025&quot;, &quot;chinese&quot;, &quot;{C}465109L02&quot;),
+            (&quot;20141025T010203&quot;, &quot;chinese&quot;, &quot;{C}465109L02T010203&quot;),
+        )
+
+        for item, rscale, uitem in data:
+            dt = DateTime.parseText(item, False)
+            udt = ICUDateTime.fromDateTime(dt, rscale)
+            self.assertEqual(udt.getText(), uitem, &quot;Failed on icu: %s&quot; % (uitem,))
+            result = udt.toDateTime()
+            self.assertEqual(result.getText(), item, &quot;Failed on dt: %s&quot; % (item,))
+
+
+    def testSetYear(self):
+
+        data_date = (
+            (&quot;gregorian&quot;, 2012, 1, 2, False, 2013, &quot;20130102&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 2, 29, False, 2013, &quot;20130301&quot;, True,),
+            (&quot;gregorian&quot;, 2012, 2, 29, False, 2016, &quot;20160229&quot;, False,),
+        )
+
+        for rscale, y, m, d, l, year, result, result_invalid in data_date:
+            dt = ICUDateTime.fromDateComponents(rscale, y, m, d, l)
+            dt.setYear(year)
+            self.assertEqual(dt.getText(), result, &quot;Failed on: {} {} {}&quot;.format(year, result, result_invalid,))
+            self.assertEqual(dt.invalid(), result_invalid, &quot;Failed invalid on: {} {} {}&quot;.format(year, result, result_invalid,))
+
+
+    def testOffsetYear(self):
+
+        data_date = (
+            (&quot;gregorian&quot;, 2012, 1, 2, False, 1, &quot;20130102&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 2, 29, False, 1, &quot;20130228&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 2, 29, False, 4, &quot;20160229&quot;, False,),
+        )
+
+        for rscale, y, m, d, l, year, result, result_invalid in data_date:
+            dt = ICUDateTime.fromDateComponents(rscale, y, m, d, l)
+            dt.offsetYear(year)
+            self.assertEqual(dt.getText(), result, &quot;Failed on: {} {} {}&quot;.format(year, result, result_invalid,))
+            self.assertEqual(dt.invalid(), result_invalid, &quot;Failed invalid on: {} {} {}&quot;.format(year, result, result_invalid,))
+
+
+    def testSetMonth(self):
+
+        data_date = (
+            (&quot;gregorian&quot;, 2012, 1, 2, False, 2, &quot;20120202&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 1, 29, False, 2, &quot;20120229&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 1, 31, False, 2, &quot;20120302&quot;, True,),
+            (&quot;gregorian&quot;, 2012, 2, 29, False, 3, &quot;20120329&quot;, False,),
+        )
+
+        for rscale, y, m, d, l, month, result, result_invalid in data_date:
+            dt = ICUDateTime.fromDateComponents(rscale, y, m, d, l)
+            dt.setMonth(month)
+            self.assertEqual(dt.getText(), result, &quot;Failed on: {} {} {}&quot;.format(month, result, result_invalid,))
+            self.assertEqual(dt.invalid(), result_invalid, &quot;Failed invalid on: {} {} {}&quot;.format(month, result, result_invalid,))
+
+
+    def testOffsetMonth(self):
+
+        data_date = (
+            (&quot;gregorian&quot;, 2012, 1, 2, False, 1, &quot;20120202&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 1, 29, False, 1, &quot;20120229&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 1, 31, False, 1, &quot;20120229&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 2, 29, False, 1, &quot;20120329&quot;, False,),
+        )
+
+        for rscale, y, m, d, l, month, result, result_invalid in data_date:
+            dt = ICUDateTime.fromDateComponents(rscale, y, m, d, l)
+            dt.offsetMonth(month)
+            self.assertEqual(dt.getText(), result, &quot;Failed on: {} {} {}&quot;.format(month, result, result_invalid,))
+            self.assertEqual(dt.invalid(), result_invalid, &quot;Failed invalid on: {} {} {}&quot;.format(month, result, result_invalid,))
+
+
+    def testSetDay(self):
+
+        data_date = (
+            (&quot;gregorian&quot;, 2012, 2, 1, False, 2, &quot;20120202&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 2, 1, False, 29, &quot;20120229&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 2, 1, False, 31, &quot;20120302&quot;, True,),
+        )
+
+        for rscale, y, m, d, l, day, result, result_invalid in data_date:
+            dt = ICUDateTime.fromDateComponents(rscale, y, m, d, l)
+            dt.setDay(day)
+            self.assertEqual(dt.getText(), result, &quot;Failed on: {} {} {}&quot;.format(day, result, result_invalid,))
+            self.assertEqual(dt.invalid(), result_invalid, &quot;Failed invalid on: {} {} {}&quot;.format(day, result, result_invalid,))
+
+
+    def testOffsetDay(self):
+
+        data_date = (
+            (&quot;gregorian&quot;, 2012, 2, 1, False, 1, &quot;20120202&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 2, 1, False, 28, &quot;20120229&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 2, 1, False, 30, &quot;20120302&quot;, False,),
+        )
+
+        for rscale, y, m, d, l, day, result, result_invalid in data_date:
+            dt = ICUDateTime.fromDateComponents(rscale, y, m, d, l)
+            dt.offsetDay(day)
+            self.assertEqual(dt.getText(), result, &quot;Failed on: {} {} {}&quot;.format(day, result, result_invalid,))
+            self.assertEqual(dt.invalid(), result_invalid, &quot;Failed invalid on: {} {} {}&quot;.format(day, result, result_invalid,))
+
+
+    def testYearDay(self):
+
+        data_date = (
+            (&quot;gregorian&quot;, 2012, 1, 2, False, 1, False, &quot;20120101&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 1, 2, False, 60, False, &quot;20120229&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 1, 2, False, 366, False, &quot;20121231&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 1, 2, False, 367, False, &quot;20121231&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 1, 2, False, 367, True, &quot;20121231&quot;, True,),
+            (&quot;gregorian&quot;, 2012, 1, 2, False, -1, False, &quot;20121231&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 1, 2, False, -307, False, &quot;20120229&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 1, 2, False, -366, False, &quot;20120101&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 1, 2, False, -367, False, &quot;20120101&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 1, 2, False, -367, True, &quot;20120101&quot;, True,),
+        )
+
+        for rscale, y, m, d, l, yearday, allow_invalid, result, result_invalid in data_date:
+            dt = ICUDateTime.fromDateComponents(rscale, y, m, d, l)
+            dt.setYearDay(yearday, allow_invalid=allow_invalid)
+            self.assertEqual(dt.getText(), result, &quot;Failed on: {} {} {}&quot;.format(yearday, result, result_invalid,))
+            self.assertEqual(dt.invalid(), result_invalid, &quot;Failed invalid on: {} {} {}&quot;.format(yearday, result, result_invalid,))
+
+
+    def testMonthDay(self):
+
+        data_date = (
+            (&quot;gregorian&quot;, 2012, 1, 2, False, 1, False, &quot;20120101&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 1, 31, False, 1, False, &quot;20120101&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 2, 1, False, 31, False, &quot;20120229&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 2, 1, False, 31, True, &quot;20120229&quot;, True,),
+            (&quot;gregorian&quot;, 2012, 1, 2, False, -1, False, &quot;20120131&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 2, 29, False, -29, False, &quot;20120201&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 2, 29, False, -30, False, &quot;20120201&quot;, False,),
+            (&quot;gregorian&quot;, 2012, 2, 29, False, -30, True, &quot;20120201&quot;, True,),
+        )
+
+        for rscale, y, m, d, l, monthday, allow_invalid, result, result_invalid in data_date:
+            dt = ICUDateTime.fromDateComponents(rscale, y, m, d, l)
+            dt.setMonthDay(monthday, allow_invalid=allow_invalid)
+            self.assertEqual(dt.getText(), result, &quot;Failed on: {} {} {}&quot;.format(monthday, result, result_invalid,))
+            self.assertEqual(dt.invalid(), result_invalid, &quot;Failed invalid on: {} {} {}&quot;.format(monthday, result, result_invalid,))
+
+
+    def testWeekNum(self):
+
+        data_date = (
+            (&quot;gregorian&quot;, 2012, 1, 3, False, 1, 2, &quot;20120110&quot;,),
+            (&quot;gregorian&quot;, 2014, 1, 3, False, 1, 2, &quot;20140110&quot;,),
+            (&quot;gregorian&quot;, 2012, 1, 1, False, 52, 2, &quot;20120115&quot;,),
+        )
+
+        for rscale, y, m, d, l, start_weekno, weekno, result in data_date:
+            dt = ICUDateTime.fromDateComponents(rscale, y, m, d, l)
+            self.assertEqual(dt.getWeekNo(), start_weekno)
+            dt.setWeekNo(weekno)
+            self.assertTrue(dt.isWeekNo(weekno))
+            self.assertEqual(dt.getText(), result, &quot;Failed on: {} {} vs {}&quot;.format(weekno, result, dt.getText(),))
+
+
+    def testDayOfWeekInYear(self):
+
+        data_date = (
+            (&quot;gregorian&quot;, 2012, 1, 3, False, 1, DateTime.SUNDAY, &quot;20120101&quot;,),
+            (&quot;gregorian&quot;, 2012, 1, 3, False, 1, DateTime.MONDAY, &quot;20120102&quot;,),
+            (&quot;gregorian&quot;, 2012, 1, 3, False, 2, DateTime.SUNDAY, &quot;20120108&quot;,),
+            (&quot;gregorian&quot;, 2012, 1, 3, False, 10, DateTime.SUNDAY, &quot;20120304&quot;,),
+            (&quot;gregorian&quot;, 2012, 1, 3, False, -1, DateTime.SUNDAY, &quot;20121230&quot;,),
+            (&quot;gregorian&quot;, 2012, 1, 3, False, -45, DateTime.SUNDAY, &quot;20120226&quot;,),
+        )
+
+        for rscale, y, m, d, l, offset, day, result in data_date:
+            dt = ICUDateTime.fromDateComponents(rscale, y, m, d, l)
+            dt.setDayOfWeekInYear(offset, day)
+            self.assertEqual(dt.getText(), result, &quot;Failed on: {} vs {}&quot;.format(result, dt.getText(),))
</ins></span></pre></div>
<a id="PyCalendartrunksrcpycalendaricalendarteststest_recuriterpy"></a>
<div class="addfile"><h4>Added: PyCalendar/trunk/src/pycalendar/icalendar/tests/test_recuriter.py (0 => 14191)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/icalendar/tests/test_recuriter.py                                (rev 0)
+++ PyCalendar/trunk/src/pycalendar/icalendar/tests/test_recuriter.py        2014-12-01 15:15:13 UTC (rev 14191)
</span><span class="lines">@@ -0,0 +1,559 @@
</span><ins>+# coding: utf-8
+##
+#    Copyright (c) 2007-2013 Cyrus Daboo. All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+##
+
+from pycalendar.datetime import DateTime
+from pycalendar.icalendar import definitions
+from pycalendar.icalendar.icudatetime import ICUDateTime
+from pycalendar.icalendar.recuriter import RecurrenceIterator
+import unittest
+
+class MonthlySkips(object):
+
+    def testMonthlySkipYes(self):
+
+        riter = RecurrenceIterator(self.dt, definitions.eRecurrence_MONTHLY, 1, rscale=self.rscale, skip=definitions.eRecurrence_SKIP_YES)
+        results = [riter.next().getText() for _ in range(12)]
+        self.assertEqual(
+            results,
+            [
+                &quot;20140131&quot;,
+                &quot;20140331&quot;,
+                &quot;20140531&quot;,
+                &quot;20140731&quot;,
+                &quot;20140831&quot;,
+                &quot;20141031&quot;,
+                &quot;20141231&quot;,
+                &quot;20150131&quot;,
+                &quot;20150331&quot;,
+                &quot;20150531&quot;,
+                &quot;20150731&quot;,
+                &quot;20150831&quot;,
+            ]
+        )
+
+
+    def testMonthlySkipForward(self):
+
+        riter = RecurrenceIterator(self.dt, definitions.eRecurrence_MONTHLY, 1, rscale=self.rscale, skip=definitions.eRecurrence_SKIP_FORWARD)
+        results = [riter.next().getText() for _ in range(12)]
+        self.assertEqual(
+            results,
+            [
+                &quot;20140131&quot;,
+                &quot;20140301&quot;,
+                &quot;20140331&quot;,
+                &quot;20140501&quot;,
+                &quot;20140531&quot;,
+                &quot;20140701&quot;,
+                &quot;20140731&quot;,
+                &quot;20140831&quot;,
+                &quot;20141001&quot;,
+                &quot;20141031&quot;,
+                &quot;20141201&quot;,
+                &quot;20141231&quot;,
+            ]
+        )
+
+
+    def testMonthlySkipBackward(self):
+
+        riter = RecurrenceIterator(self.dt, definitions.eRecurrence_MONTHLY, 1, rscale=self.rscale, skip=definitions.eRecurrence_SKIP_BACKWARD)
+        results = [riter.next().getText() for _ in range(12)]
+        self.assertEqual(
+            results,
+            [
+                &quot;20140131&quot;,
+                &quot;20140228&quot;,
+                &quot;20140331&quot;,
+                &quot;20140430&quot;,
+                &quot;20140531&quot;,
+                &quot;20140630&quot;,
+                &quot;20140731&quot;,
+                &quot;20140831&quot;,
+                &quot;20140930&quot;,
+                &quot;20141031&quot;,
+                &quot;20141130&quot;,
+                &quot;20141231&quot;,
+            ]
+        )
+
+
+    def testMonthlySkipBackwardLeap(self):
+
+        riter = RecurrenceIterator(self.dtleap, definitions.eRecurrence_MONTHLY, 1, rscale=self.rscale, skip=definitions.eRecurrence_SKIP_BACKWARD)
+        results = [riter.next().getText() for _ in range(12)]
+        self.assertEqual(
+            results,
+            [
+                &quot;20160131&quot;,
+                &quot;20160229&quot;,
+                &quot;20160331&quot;,
+                &quot;20160430&quot;,
+                &quot;20160531&quot;,
+                &quot;20160630&quot;,
+                &quot;20160731&quot;,
+                &quot;20160831&quot;,
+                &quot;20160930&quot;,
+                &quot;20161031&quot;,
+                &quot;20161130&quot;,
+                &quot;20161231&quot;,
+            ]
+        )
+
+
+
+class TestMonthlyDateTime(unittest.TestCase, MonthlySkips):
+
+    def setUp(self):
+        self.dt = DateTime(2014, 1, 31)
+        self.dtleap = DateTime(2016, 1, 31)
+        self.rscale = None
+
+
+
+class TestMonthlyGregorianICU(unittest.TestCase, MonthlySkips):
+
+    def setUp(self):
+        self.dt = ICUDateTime.fromDateComponents(&quot;gregorian&quot;, 2014, 1, 31)
+        self.dtleap = ICUDateTime.fromDateComponents(&quot;gregorian&quot;, 2016, 1, 31)
+        self.rscale = None
+
+
+
+class YearlySkipsOnLeapDay(object):
+
+    def testYearlySkipYes(self):
+
+        riter = RecurrenceIterator(self.dt, definitions.eRecurrence_YEARLY, 1, rscale=self.rscale, skip=definitions.eRecurrence_SKIP_YES)
+        results = [riter.next().getText() for _ in range(5)]
+        self.assertEqual(
+            results,
+            [
+                &quot;20160229&quot;,
+                &quot;20200229&quot;,
+                &quot;20240229&quot;,
+                &quot;20280229&quot;,
+                &quot;20320229&quot;,
+            ]
+        )
+
+
+    def testYearlySkipForward(self):
+
+        riter = RecurrenceIterator(self.dt, definitions.eRecurrence_YEARLY, 1, rscale=self.rscale, skip=definitions.eRecurrence_SKIP_FORWARD)
+        results = [riter.next().getText() for _ in range(5)]
+        self.assertEqual(
+            results,
+            [
+                &quot;20160229&quot;,
+                &quot;20170301&quot;,
+                &quot;20180301&quot;,
+                &quot;20190301&quot;,
+                &quot;20200229&quot;,
+            ]
+        )
+
+
+    def testYearlySkipBackward(self):
+
+        riter = RecurrenceIterator(self.dt, definitions.eRecurrence_YEARLY, 1, rscale=self.rscale, skip=definitions.eRecurrence_SKIP_BACKWARD)
+        results = [riter.next().getText() for _ in range(5)]
+        self.assertEqual(
+            results,
+            [
+                &quot;20160229&quot;,
+                &quot;20170228&quot;,
+                &quot;20180228&quot;,
+                &quot;20190228&quot;,
+                &quot;20200229&quot;,
+            ]
+        )
+
+
+
+class TestYearlyDateTime(unittest.TestCase, YearlySkipsOnLeapDay):
+
+    def setUp(self):
+        self.dt = DateTime(2016, 2, 29)
+        self.rscale = None
+
+
+
+class TestYearlyGregorianICU(unittest.TestCase, YearlySkipsOnLeapDay):
+
+    def setUp(self):
+        self.dt = ICUDateTime.fromDateComponents(&quot;gregorian&quot;, 2016, 2, 29)
+        self.rscale = None
+
+
+
+class TestMonthlyChineseICU(unittest.TestCase):
+
+    def testMonthlyStartInLeapYearSkipYes(self):
+        dt = ICUDateTime.fromDateComponents(&quot;chinese&quot;, 4650, 12, 30)
+
+        riter = RecurrenceIterator(dt, definitions.eRecurrence_MONTHLY, 1, rscale=None, skip=definitions.eRecurrence_SKIP_YES)
+        results = []
+        while True:
+            result = riter.next()
+            if result.getYear() &gt;= 4655:
+                break
+            results.append(result.getText())
+        self.assertEqual(
+            results,
+            [
+                &quot;{C}46501230&quot;,
+                &quot;{C}46510230&quot;,
+                &quot;{C}46510430&quot;,
+                &quot;{C}46510630&quot;,
+                &quot;{C}46510830&quot;,
+                &quot;{C}46510930&quot;,
+                &quot;{C}46511030&quot;,
+                &quot;{C}46511230&quot;,
+                &quot;{C}46520230&quot;,
+                &quot;{C}46520530&quot;,
+                &quot;{C}46520730&quot;,
+                &quot;{C}46520830&quot;,
+                &quot;{C}46520930&quot;,
+                &quot;{C}46521130&quot;,
+                &quot;{C}46530130&quot;,
+                &quot;{C}46530330&quot;,
+                &quot;{C}46530630&quot;,
+                &quot;{C}46530830&quot;,
+                &quot;{C}46530930&quot;,
+                &quot;{C}46531130&quot;,
+                &quot;{C}46531230&quot;,
+                &quot;{C}46540230&quot;,
+                &quot;{C}46540430&quot;,
+                &quot;{C}465406L30&quot;,
+                &quot;{C}46540830&quot;,
+                &quot;{C}46541030&quot;,
+                &quot;{C}46541130&quot;,
+                &quot;{C}46541230&quot;,
+            ]
+        )
+
+
+    def testMonthlyStartInLeapYearSkipForward(self):
+        dt = ICUDateTime.fromDateComponents(&quot;chinese&quot;, 4650, 12, 30)
+
+        riter = RecurrenceIterator(dt, definitions.eRecurrence_MONTHLY, 1, rscale=None, skip=definitions.eRecurrence_SKIP_FORWARD)
+        results = []
+        while True:
+            result = riter.next()
+            if result.getYear() &gt;= 4655:
+                break
+            results.append(result.getText())
+        self.assertEqual(
+            results,
+            [
+                &quot;{C}46501230&quot;,
+                &quot;{C}46510201&quot;,
+                &quot;{C}46510230&quot;,
+                &quot;{C}46510401&quot;,
+                &quot;{C}46510430&quot;,
+                &quot;{C}46510601&quot;,
+                &quot;{C}46510630&quot;,
+                &quot;{C}46510801&quot;,
+                &quot;{C}46510830&quot;,
+                &quot;{C}46510930&quot;,
+                &quot;{C}46511001&quot;,
+                &quot;{C}46511030&quot;,
+                &quot;{C}46511201&quot;,
+                &quot;{C}46511230&quot;,
+                &quot;{C}46520201&quot;,
+                &quot;{C}46520230&quot;,
+                &quot;{C}46520401&quot;,
+                &quot;{C}46520501&quot;,
+                &quot;{C}46520530&quot;,
+                &quot;{C}46520701&quot;,
+                &quot;{C}46520730&quot;,
+                &quot;{C}46520830&quot;,
+                &quot;{C}46520930&quot;,
+                &quot;{C}46521101&quot;,
+                &quot;{C}46521130&quot;,
+                &quot;{C}46530101&quot;,
+                &quot;{C}46530130&quot;,
+                &quot;{C}46530301&quot;,
+                &quot;{C}46530330&quot;,
+                &quot;{C}46530501&quot;,
+                &quot;{C}46530601&quot;,
+                &quot;{C}46530630&quot;,
+                &quot;{C}46530801&quot;,
+                &quot;{C}46530830&quot;,
+                &quot;{C}46530930&quot;,
+                &quot;{C}46531101&quot;,
+                &quot;{C}46531130&quot;,
+                &quot;{C}46531230&quot;,
+                &quot;{C}46540201&quot;,
+                &quot;{C}46540230&quot;,
+                &quot;{C}46540401&quot;,
+                &quot;{C}46540430&quot;,
+                &quot;{C}46540601&quot;,
+                &quot;{C}465406L01&quot;,
+                &quot;{C}465406L30&quot;,
+                &quot;{C}46540801&quot;,
+                &quot;{C}46540830&quot;,
+                &quot;{C}46541001&quot;,
+                &quot;{C}46541030&quot;,
+                &quot;{C}46541130&quot;,
+                &quot;{C}46541230&quot;,
+            ]
+        )
+
+
+    def testMonthlyStartInLeapYearSkipBackward(self):
+        dt = ICUDateTime.fromDateComponents(&quot;chinese&quot;, 4650, 12, 30)
+
+        riter = RecurrenceIterator(dt, definitions.eRecurrence_MONTHLY, 1, rscale=None, skip=definitions.eRecurrence_SKIP_BACKWARD)
+        results = []
+        while True:
+            result = riter.next()
+            if result.getYear() &gt;= 4655:
+                break
+            results.append(result.getText())
+        self.assertEqual(
+            results,
+            [
+                &quot;{C}46501230&quot;,
+                &quot;{C}46510129&quot;,
+                &quot;{C}46510230&quot;,
+                &quot;{C}46510329&quot;,
+                &quot;{C}46510430&quot;,
+                &quot;{C}46510529&quot;,
+                &quot;{C}46510630&quot;,
+                &quot;{C}46510729&quot;,
+                &quot;{C}46510830&quot;,
+                &quot;{C}46510930&quot;,
+                &quot;{C}465109L29&quot;,
+                &quot;{C}46511030&quot;,
+                &quot;{C}46511129&quot;,
+                &quot;{C}46511230&quot;,
+                &quot;{C}46520129&quot;,
+                &quot;{C}46520230&quot;,
+                &quot;{C}46520329&quot;,
+                &quot;{C}46520429&quot;,
+                &quot;{C}46520530&quot;,
+                &quot;{C}46520629&quot;,
+                &quot;{C}46520730&quot;,
+                &quot;{C}46520830&quot;,
+                &quot;{C}46520930&quot;,
+                &quot;{C}46521029&quot;,
+                &quot;{C}46521130&quot;,
+                &quot;{C}46521229&quot;,
+                &quot;{C}46530130&quot;,
+                &quot;{C}46530229&quot;,
+                &quot;{C}46530330&quot;,
+                &quot;{C}46530429&quot;,
+                &quot;{C}46530529&quot;,
+                &quot;{C}46530630&quot;,
+                &quot;{C}46530729&quot;,
+                &quot;{C}46530830&quot;,
+                &quot;{C}46530930&quot;,
+                &quot;{C}46531029&quot;,
+                &quot;{C}46531130&quot;,
+                &quot;{C}46531230&quot;,
+                &quot;{C}46540129&quot;,
+                &quot;{C}46540230&quot;,
+                &quot;{C}46540329&quot;,
+                &quot;{C}46540430&quot;,
+                &quot;{C}46540529&quot;,
+                &quot;{C}46540629&quot;,
+                &quot;{C}465406L30&quot;,
+                &quot;{C}46540729&quot;,
+                &quot;{C}46540830&quot;,
+                &quot;{C}46540929&quot;,
+                &quot;{C}46541030&quot;,
+                &quot;{C}46541130&quot;,
+                &quot;{C}46541230&quot;,
+            ]
+        )
+
+
+    def testMonthlyRscaleStartInLeapYearSkipYes(self):
+        dt = ICUDateTime.fromDateComponents(&quot;chinese&quot;, 4650, 12, 30).toDateTime()
+
+        riter = RecurrenceIterator(dt, definitions.eRecurrence_MONTHLY, 1, rscale=&quot;chinese&quot;, skip=definitions.eRecurrence_SKIP_YES)
+        results = []
+        while True:
+            result = riter.next()
+            if result.getYear() &gt;= 2018:
+                break
+            results.append(result.getText())
+        self.assertEqual(
+            results,
+            [
+                &quot;20140130&quot;,
+                &quot;20140330&quot;,
+                &quot;20140528&quot;,
+                &quot;20140726&quot;,
+                &quot;20140923&quot;,
+                &quot;20141023&quot;,
+                &quot;20141221&quot;,
+                &quot;20150218&quot;,
+                &quot;20150418&quot;,
+                &quot;20150715&quot;,
+                &quot;20150912&quot;,
+                &quot;20151012&quot;,
+                &quot;20151111&quot;,
+                &quot;20160109&quot;,
+                &quot;20160308&quot;,
+                &quot;20160506&quot;,
+                &quot;20160802&quot;,
+                &quot;20160930&quot;,
+                &quot;20161030&quot;,
+                &quot;20161228&quot;,
+                &quot;20170127&quot;,
+                &quot;20170327&quot;,
+                &quot;20170525&quot;,
+                &quot;20170821&quot;,
+                &quot;20171019&quot;,
+                &quot;20171217&quot;,
+            ]
+        )
+
+
+    def testMonthlyRscaleStartInLeapYearSkipForward(self):
+        dt = ICUDateTime.fromDateComponents(&quot;chinese&quot;, 4650, 12, 30).toDateTime()
+
+        riter = RecurrenceIterator(dt, definitions.eRecurrence_MONTHLY, 1, rscale=&quot;chinese&quot;, skip=definitions.eRecurrence_SKIP_FORWARD)
+        results = []
+        while True:
+            result = riter.next()
+            if result.getYear() &gt;= 2018:
+                break
+            results.append(result.getText())
+        self.assertEqual(
+            results,
+            [
+                &quot;20140130&quot;,
+                &quot;20140301&quot;,
+                &quot;20140330&quot;,
+                &quot;20140429&quot;,
+                &quot;20140528&quot;,
+                &quot;20140627&quot;,
+                &quot;20140726&quot;,
+                &quot;20140825&quot;,
+                &quot;20140923&quot;,
+                &quot;20141023&quot;,
+                &quot;20141122&quot;,
+                &quot;20141221&quot;,
+                &quot;20150120&quot;,
+                &quot;20150218&quot;,
+                &quot;20150320&quot;,
+                &quot;20150418&quot;,
+                &quot;20150518&quot;,
+                &quot;20150616&quot;,
+                &quot;20150715&quot;,
+                &quot;20150814&quot;,
+                &quot;20150912&quot;,
+                &quot;20151012&quot;,
+                &quot;20151111&quot;,
+                &quot;20151211&quot;,
+                &quot;20160109&quot;,
+                &quot;20160208&quot;,
+                &quot;20160308&quot;,
+                &quot;20160407&quot;,
+                &quot;20160506&quot;,
+                &quot;20160605&quot;,
+                &quot;20160704&quot;,
+                &quot;20160802&quot;,
+                &quot;20160901&quot;,
+                &quot;20160930&quot;,
+                &quot;20161030&quot;,
+                &quot;20161129&quot;,
+                &quot;20161228&quot;,
+                &quot;20170127&quot;,
+                &quot;20170226&quot;,
+                &quot;20170327&quot;,
+                &quot;20170426&quot;,
+                &quot;20170525&quot;,
+                &quot;20170624&quot;,
+                &quot;20170723&quot;,
+                &quot;20170821&quot;,
+                &quot;20170920&quot;,
+                &quot;20171019&quot;,
+                &quot;20171118&quot;,
+                &quot;20171217&quot;,
+            ]
+        )
+
+
+    def testMonthlyRscaleStartInLeapYearSkipBackward(self):
+        dt = ICUDateTime.fromDateComponents(&quot;chinese&quot;, 4650, 12, 30).toDateTime()
+
+        riter = RecurrenceIterator(dt, definitions.eRecurrence_MONTHLY, 1, rscale=&quot;chinese&quot;, skip=definitions.eRecurrence_SKIP_BACKWARD)
+        results = []
+        while True:
+            result = riter.next()
+            if result.getYear() &gt;= 2018:
+                break
+            results.append(result.getText())
+        self.assertEqual(
+            results,
+            [
+                &quot;20140130&quot;,
+                &quot;20140228&quot;,
+                &quot;20140330&quot;,
+                &quot;20140428&quot;,
+                &quot;20140528&quot;,
+                &quot;20140626&quot;,
+                &quot;20140726&quot;,
+                &quot;20140824&quot;,
+                &quot;20140923&quot;,
+                &quot;20141023&quot;,
+                &quot;20141121&quot;,
+                &quot;20141221&quot;,
+                &quot;20150119&quot;,
+                &quot;20150218&quot;,
+                &quot;20150319&quot;,
+                &quot;20150418&quot;,
+                &quot;20150517&quot;,
+                &quot;20150615&quot;,
+                &quot;20150715&quot;,
+                &quot;20150813&quot;,
+                &quot;20150912&quot;,
+                &quot;20151012&quot;,
+                &quot;20151111&quot;,
+                &quot;20151210&quot;,
+                &quot;20160109&quot;,
+                &quot;20160207&quot;,
+                &quot;20160308&quot;,
+                &quot;20160406&quot;,
+                &quot;20160506&quot;,
+                &quot;20160604&quot;,
+                &quot;20160703&quot;,
+                &quot;20160802&quot;,
+                &quot;20160831&quot;,
+                &quot;20160930&quot;,
+                &quot;20161030&quot;,
+                &quot;20161128&quot;,
+                &quot;20161228&quot;,
+                &quot;20170127&quot;,
+                &quot;20170225&quot;,
+                &quot;20170327&quot;,
+                &quot;20170425&quot;,
+                &quot;20170525&quot;,
+                &quot;20170623&quot;,
+                &quot;20170722&quot;,
+                &quot;20170821&quot;,
+                &quot;20170919&quot;,
+                &quot;20171019&quot;,
+                &quot;20171117&quot;,
+                &quot;20171217&quot;,
+            ]
+        )
</ins></span></pre></div>
<a id="PyCalendartrunksrcpycalendaricalendarteststest_recurrencepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/trunk/src/pycalendar/icalendar/tests/test_recurrence.py (14190 => 14191)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/icalendar/tests/test_recurrence.py        2014-12-01 15:13:17 UTC (rev 14190)
+++ PyCalendar/trunk/src/pycalendar/icalendar/tests/test_recurrence.py        2014-12-01 15:15:13 UTC (rev 14191)
</span><span class="lines">@@ -51,6 +51,14 @@
</span><span class="cx">         &quot;FREQ=YEARLY;BYDAY=MO;BYWEEKNO=20&quot;,
</span><span class="cx">         &quot;FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3&quot;,
</span><span class="cx">         &quot;FREQ=DAILY;BYMINUTE=0,20,40;BYHOUR=9,10,11,12,13,14,15,16&quot;,
</span><ins>+
+        # RSCALE
+        &quot;RSCALE=CHINESE;FREQ=DAILY&quot;,
+        &quot;RSCALE=GREGORIAN;FREQ=YEARLY;COUNT=400;SKIP=YES&quot;,
+        &quot;RSCALE=GREGORIAN;FREQ=YEARLY;COUNT=400;SKIP=BACKWARD&quot;,
+        &quot;RSCALE=GREGORIAN;FREQ=YEARLY;COUNT=400;SKIP=FORWARD&quot;,
+        &quot;RSCALE=CHINESE;FREQ=YEARLY;BYMONTH=5,6,6L,7&quot;,
+
</ins><span class="cx">     )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -59,7 +67,7 @@
</span><span class="cx">         for item in TestRecurrence.items:
</span><span class="cx">             recur = Recurrence()
</span><span class="cx">             recur.parse(item)
</span><del>-            self.assertEqual(recur.getText(), item, &quot;Failed to parse and re-generate '%s'&quot; % (item,))
</del><ins>+            self.assertEqual(recur.getText(), item, &quot;Failed to parse and re-generate '%s' '%s'&quot; % (item, recur.getText()))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def testParseInvalid(self):
</span><span class="lines">@@ -78,6 +86,8 @@
</span><span class="cx">             &quot;FREQ=MONTHLY;BYDAY=+1,3MO&quot;,
</span><span class="cx">             &quot;FREQ=MONTHLY;BYHOUR=A&quot;,
</span><span class="cx">             &quot;FREQ=MONTHLY;BYHOUR=54&quot;,
</span><ins>+            &quot;FREQ=MONTHLY;SKIP=YES&quot;,
+            &quot;RSCALE=CHINESE;FREQ=MONTHLY;SKIP=NO&quot;,
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         for item in items:
</span><span class="lines">@@ -118,153 +128,618 @@
</span><span class="cx"> 
</span><span class="cx">     def testByWeekNoExpand(self):
</span><span class="cx"> 
</span><ins>+        rule = &quot;FREQ=YEARLY;BYWEEKNO=1,2&quot;
+        for rrule in (
+            rule,
+            &quot;RSCALE=GREGORIAN;{};SKIP=YES&quot;.format(rule)
+        ):
+            recur = Recurrence()
+            recur.parse(rrule)
+            start = DateTime(2013, 1, 1, 0, 0, 0)
+            end = DateTime(2017, 1, 1, 0, 0, 0)
+            items = []
+            range = Period(start, end)
+            recur.expand(start, range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2013, 1, 1, 0, 0, 0),
+                    DateTime(2013, 1, 8, 0, 0, 0),
+                    DateTime(2014, 1, 1, 0, 0, 0),
+                    DateTime(2014, 1, 8, 0, 0, 0),
+                    DateTime(2015, 1, 1, 0, 0, 0),
+                    DateTime(2015, 1, 8, 0, 0, 0),
+                    DateTime(2016, 1, 8, 0, 0, 0),
+                    DateTime(2016, 1, 15, 0, 0, 0),
+                ],
+                msg=&quot;Failed: {}&quot;.format(rrule),
+            )
+
+
+    def testMonthlyInvalidStart(self):
+
+        rule = &quot;FREQ=MONTHLY&quot;
+        for rrule in (
+            rule,
+            &quot;RSCALE=GREGORIAN;{};SKIP=YES&quot;.format(rule)
+        ):
+            recur = Recurrence()
+            recur.parse(rrule)
+            start = DateTime(2014, 1, 40, 12, 0, 0)
+            end = DateTime(2015, 1, 1, 0, 0, 0)
+            items = []
+            range = Period(start, end)
+            recur.expand(start, range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2014, 2, 9, 12, 0, 0),
+                    DateTime(2014, 3, 9, 12, 0, 0),
+                    DateTime(2014, 4, 9, 12, 0, 0),
+                    DateTime(2014, 5, 9, 12, 0, 0),
+                    DateTime(2014, 6, 9, 12, 0, 0),
+                    DateTime(2014, 7, 9, 12, 0, 0),
+                    DateTime(2014, 8, 9, 12, 0, 0),
+                    DateTime(2014, 9, 9, 12, 0, 0),
+                    DateTime(2014, 10, 9, 12, 0, 0),
+                    DateTime(2014, 11, 9, 12, 0, 0),
+                    DateTime(2014, 12, 9, 12, 0, 0),
+                ],
+                msg=&quot;Failed: {}&quot;.format(rrule),
+            )
+
+
+    def testWeeklyTwice(self):
+
+        rule = &quot;FREQ=WEEKLY&quot;
+        for rrule in (
+            rule,
+            &quot;RSCALE=GREGORIAN;{};SKIP=YES&quot;.format(rule)
+        ):
+            recur = Recurrence()
+            recur.parse(rrule)
+            start = DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(utc=True))
+            end = DateTime(2014, 2, 1, 0, 0, 0, tzid=Timezone(utc=True))
+            items = []
+            range = Period(start, end)
+            recur.expand(DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)), range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 1, 8, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 1, 15, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 1, 22, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 1, 29, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+
+                ],
+                msg=&quot;Failed: {}&quot;.format(rrule),
+            )
+
+            start = DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(utc=True))
+            end = DateTime(2014, 3, 1, 0, 0, 0, tzid=Timezone(utc=True))
+            items = []
+            range = Period(start, end)
+            recur.expand(DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)), range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 1, 8, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 1, 15, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 1, 22, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 1, 29, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 2, 5, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 2, 12, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 2, 19, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 2, 26, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                ],
+                msg=&quot;Failed: {}&quot;.format(rrule),
+            )
+
+
+    def testMonthlyInUTC(self):
+
+        rule = &quot;FREQ=MONTHLY&quot;
+        for rrule in (
+            rule,
+            &quot;RSCALE=GREGORIAN;{};SKIP=YES&quot;.format(rule)
+        ):
+            recur = Recurrence()
+            recur.parse(rrule)
+            start = DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(utc=True))
+            end = DateTime(2015, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
+            items = []
+            range = Period(start, end)
+            recur.expand(DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)), range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 2, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 3, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 4, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 5, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 6, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 7, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 8, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 9, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
+                    DateTime(2014, 10, 1, 12, 0, 0),
+                    DateTime(2014, 11, 1, 12, 0, 0),
+                    DateTime(2014, 12, 1, 12, 0, 0),
+                ],
+                msg=&quot;Failed: {}&quot;.format(rrule),
+            )
+
+
+    def testMonthlyStart31st(self):
+
+        rule = &quot;FREQ=MONTHLY&quot;
+        for rrule in (
+            rule,
+            &quot;RSCALE=GREGORIAN;{};SKIP=YES&quot;.format(rule)
+        ):
+            recur = Recurrence()
+            recur.parse(rrule)
+            start = DateTime(2014, 1, 31, 12, 0, 0)
+            end = DateTime(2015, 1, 1, 0, 0, 0)
+            items = []
+            range = Period(start, end)
+            recur.expand(start, range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2014, 1, 31, 12, 0, 0),
+                    DateTime(2014, 3, 31, 12, 0, 0),
+                    DateTime(2014, 5, 31, 12, 0, 0),
+                    DateTime(2014, 7, 31, 12, 0, 0),
+                    DateTime(2014, 8, 31, 12, 0, 0),
+                    DateTime(2014, 10, 31, 12, 0, 0),
+                    DateTime(2014, 12, 31, 12, 0, 0),
+                ],
+                msg=&quot;Failed: {}&quot;.format(rrule),
+            )
+
+
+    def testMonthlyByMonthDay31(self):
+
+        rule = &quot;FREQ=MONTHLY;BYMONTHDAY=31&quot;
+        for rrule in (
+            rule,
+            &quot;RSCALE=GREGORIAN;{};SKIP=YES&quot;.format(rule)
+        ):
+            recur = Recurrence()
+            recur.parse(rrule)
+            start = DateTime(2014, 1, 31, 12, 0, 0)
+            end = DateTime(2015, 1, 1, 0, 0, 0)
+            items = []
+            range = Period(start, end)
+            recur.expand(start, range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2014, 1, 31, 12, 0, 0),
+                    DateTime(2014, 3, 31, 12, 0, 0),
+                    DateTime(2014, 5, 31, 12, 0, 0),
+                    DateTime(2014, 7, 31, 12, 0, 0),
+                    DateTime(2014, 8, 31, 12, 0, 0),
+                    DateTime(2014, 10, 31, 12, 0, 0),
+                    DateTime(2014, 12, 31, 12, 0, 0),
+                ],
+                msg=&quot;Failed: {}&quot;.format(rrule),
+            )
+
+
+    def testMonthlyByMonthDayMinus31(self):
+
+        rule = &quot;FREQ=MONTHLY;BYMONTHDAY=-31&quot;
+        for rrule in (
+            rule,
+            &quot;RSCALE=GREGORIAN;{};SKIP=YES&quot;.format(rule)
+        ):
+            recur = Recurrence()
+            recur.parse(rrule)
+            start = DateTime(2014, 1, 1, 12, 0, 0)
+            end = DateTime(2015, 1, 1, 0, 0, 0)
+            items = []
+            range = Period(start, end)
+            recur.expand(start, range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2014, 1, 1, 12, 0, 0),
+                    DateTime(2014, 3, 1, 12, 0, 0),
+                    DateTime(2014, 5, 1, 12, 0, 0),
+                    DateTime(2014, 7, 1, 12, 0, 0),
+                    DateTime(2014, 8, 1, 12, 0, 0),
+                    DateTime(2014, 10, 1, 12, 0, 0),
+                    DateTime(2014, 12, 1, 12, 0, 0),
+                ],
+                msg=&quot;Failed: {}&quot;.format(rrule),
+            )
+
+
+    def testMonthlyByLastFridayExpand(self):
+
+        rule = &quot;FREQ=MONTHLY;BYDAY=-1FR&quot;
+        for rrule in (
+            rule,
+            &quot;RSCALE=GREGORIAN;{};SKIP=YES&quot;.format(rule)
+        ):
+            recur = Recurrence()
+            recur.parse(rrule)
+            start = DateTime(2014, 1, 31, 12, 0, 0)
+            end = DateTime(2015, 1, 1, 0, 0, 0)
+            items = []
+            range = Period(start, end)
+            recur.expand(start, range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2014, 1, 31, 12, 0, 0),
+                    DateTime(2014, 2, 28, 12, 0, 0),
+                    DateTime(2014, 3, 28, 12, 0, 0),
+                    DateTime(2014, 4, 25, 12, 0, 0),
+                    DateTime(2014, 5, 30, 12, 0, 0),
+                    DateTime(2014, 6, 27, 12, 0, 0),
+                    DateTime(2014, 7, 25, 12, 0, 0),
+                    DateTime(2014, 8, 29, 12, 0, 0),
+                    DateTime(2014, 9, 26, 12, 0, 0),
+                    DateTime(2014, 10, 31, 12, 0, 0),
+                    DateTime(2014, 11, 28, 12, 0, 0),
+                    DateTime(2014, 12, 26, 12, 0, 0),
+                ],
+                msg=&quot;Failed: {}&quot;.format(rrule),
+            )
+
+
+    def testMonthlyByFifthFridayExpand(self):
+
+        rule = &quot;FREQ=MONTHLY;BYDAY=5FR&quot;
+        for rrule in (
+            rule,
+            &quot;RSCALE=GREGORIAN;{};SKIP=YES&quot;.format(rule)
+        ):
+            recur = Recurrence()
+            recur.parse(rrule)
+            start = DateTime(2014, 1, 31, 12, 0, 0)
+            end = DateTime(2015, 1, 1, 0, 0, 0)
+            items = []
+            range = Period(start, end)
+            recur.expand(start, range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2014, 1, 31, 12, 0, 0),
+                    DateTime(2014, 5, 30, 12, 0, 0),
+                    DateTime(2014, 8, 29, 12, 0, 0),
+                    DateTime(2014, 10, 31, 12, 0, 0),
+                ],
+                msg=&quot;Failed: {}&quot;.format(rrule),
+            )
+
+
+    def testYearlyLeapDay(self):
+
+        rule = &quot;FREQ=YEARLY&quot;
+        for rrule in (
+            rule,
+            &quot;RSCALE=GREGORIAN;{};SKIP=YES&quot;.format(rule)
+        ):
+            recur = Recurrence()
+            recur.parse(rrule)
+            start = DateTime(2012, 2, 29, 12, 0, 0)
+            end = DateTime(2020, 1, 1, 0, 0, 0)
+            items = []
+            range = Period(start, end)
+            recur.expand(start, range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2012, 2, 29, 12, 0, 0),
+                    DateTime(2016, 2, 29, 12, 0, 0),
+                ],
+                msg=&quot;Failed: {}&quot;.format(rrule),
+            )
+
+
+    def testYearlyYearDay(self):
+
+        rule = &quot;FREQ=YEARLY;BYYEARDAY=366&quot;
+        for rrule in (
+            rule,
+            &quot;RSCALE=GREGORIAN;{};SKIP=YES&quot;.format(rule)
+        ):
+            recur = Recurrence()
+            recur.parse(rrule)
+            start = DateTime(2012, 12, 31, 12, 0, 0)
+            end = DateTime(2020, 1, 1, 0, 0, 0)
+            items = []
+            range = Period(start, end)
+            recur.expand(start, range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2012, 12, 31, 12, 0, 0),
+                    DateTime(2016, 12, 31, 12, 0, 0),
+                ],
+                msg=&quot;Failed: {}&quot;.format(rrule),
+            )
+
+
+    def testClearOnChange(self):
+
</ins><span class="cx">         recur = Recurrence()
</span><del>-        recur.parse(&quot;FREQ=YEARLY;BYWEEKNO=1,2&quot;)
</del><ins>+        recur.parse(&quot;FREQ=DAILY&quot;)
+
</ins><span class="cx">         start = DateTime(2013, 1, 1, 0, 0, 0)
</span><span class="cx">         end = DateTime(2017, 1, 1, 0, 0, 0)
</span><ins>+        range = Period(start, end)
</ins><span class="cx">         items = []
</span><del>-        range = Period(start, end)
</del><span class="cx">         recur.expand(start, range, items)
</span><del>-        self.assertEqual(
-            items,
-            [
-                DateTime(2013, 1, 1, 0, 0, 0),
-                DateTime(2013, 1, 8, 0, 0, 0),
-                DateTime(2014, 1, 1, 0, 0, 0),
-                DateTime(2014, 1, 8, 0, 0, 0),
-                DateTime(2015, 1, 1, 0, 0, 0),
-                DateTime(2015, 1, 8, 0, 0, 0),
-                DateTime(2016, 1, 8, 0, 0, 0),
-                DateTime(2016, 1, 15, 0, 0, 0),
-            ],
-        )
</del><ins>+        self.assertTrue(recur.mCached)
+        self.assertTrue(len(items) &gt; 100)
</ins><span class="cx"> 
</span><ins>+        recur.setUseCount(True)
+        recur.setCount(10)
+        self.assertFalse(recur.mCached)
+        items = []
+        recur.expand(start, range, items)
+        self.assertEqual(len(items), 10)
</ins><span class="cx"> 
</span><del>-    def testMonthlyInvalidStart(self):
</del><span class="cx"> 
</span><ins>+
+class TestRecurrenceRscale(unittest.TestCase):
+
+    def testMonthlyRscaleStartInLeapYearSkipYes(self):
+
</ins><span class="cx">         recur = Recurrence()
</span><del>-        recur.parse(&quot;FREQ=MONTHLY&quot;)
-        start = DateTime(2014, 1, 40, 12, 0, 0)
-        end = DateTime(2015, 1, 1, 0, 0, 0)
</del><ins>+        recur.parse(&quot;RSCALE=CHINESE;FREQ=MONTHLY;SKIP=YES&quot;)
+        start = DateTime(2014, 1, 30) # {C}46501230
+        end = DateTime(2018, 1, 1)
</ins><span class="cx">         items = []
</span><span class="cx">         range = Period(start, end)
</span><span class="cx">         recur.expand(start, range, items)
</span><span class="cx">         self.assertEqual(
</span><span class="cx">             items,
</span><span class="cx">             [
</span><del>-                DateTime(2014, 2, 9, 12, 0, 0),
-                DateTime(2014, 3, 9, 12, 0, 0),
-                DateTime(2014, 4, 9, 12, 0, 0),
-                DateTime(2014, 5, 9, 12, 0, 0),
-                DateTime(2014, 6, 9, 12, 0, 0),
-                DateTime(2014, 7, 9, 12, 0, 0),
-                DateTime(2014, 8, 9, 12, 0, 0),
-                DateTime(2014, 9, 9, 12, 0, 0),
-                DateTime(2014, 10, 9, 12, 0, 0),
-                DateTime(2014, 11, 9, 12, 0, 0),
-                DateTime(2014, 12, 9, 12, 0, 0),
</del><ins>+                DateTime(2014, 1, 30),
+                DateTime(2014, 3, 30),
+                DateTime(2014, 5, 28),
+                DateTime(2014, 7, 26),
+                DateTime(2014, 9, 23),
+                DateTime(2014, 10, 23),
+                DateTime(2014, 12, 21),
+                DateTime(2015, 2, 18),
+                DateTime(2015, 4, 18),
+                DateTime(2015, 7, 15),
+                DateTime(2015, 9, 12),
+                DateTime(2015, 10, 12),
+                DateTime(2015, 11, 11),
+                DateTime(2016, 1, 9),
+                DateTime(2016, 3, 8),
+                DateTime(2016, 5, 6),
+                DateTime(2016, 8, 2),
+                DateTime(2016, 9, 30),
+                DateTime(2016, 10, 30),
+                DateTime(2016, 12, 28),
+                DateTime(2017, 1, 27),
+                DateTime(2017, 3, 27),
+                DateTime(2017, 5, 25),
+                DateTime(2017, 8, 21),
+                DateTime(2017, 10, 19),
+                DateTime(2017, 12, 17),
</ins><span class="cx">             ],
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def testWeeklyTwice(self):
</del><ins>+    def testMonthlyRscaleStartInLeapYearSkipForward(self):
</ins><span class="cx"> 
</span><span class="cx">         recur = Recurrence()
</span><del>-        recur.parse(&quot;FREQ=WEEKLY&quot;)
-        start = DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(utc=True))
-        end = DateTime(2014, 2, 1, 0, 0, 0, tzid=Timezone(utc=True))
</del><ins>+        recur.parse(&quot;RSCALE=CHINESE;FREQ=MONTHLY;SKIP=FORWARD&quot;)
+        start = DateTime(2014, 1, 30) # {C}46501230
+        end = DateTime(2018, 1, 1)
</ins><span class="cx">         items = []
</span><span class="cx">         range = Period(start, end)
</span><del>-        recur.expand(DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)), range, items)
</del><ins>+        recur.expand(start, range, items)
</ins><span class="cx">         self.assertEqual(
</span><span class="cx">             items,
</span><span class="cx">             [
</span><del>-                DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 1, 8, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 1, 15, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 1, 22, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 1, 29, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-
</del><ins>+                DateTime(2014, 1, 30),
+                DateTime(2014, 3, 1),
+                DateTime(2014, 3, 30),
+                DateTime(2014, 4, 29),
+                DateTime(2014, 5, 28),
+                DateTime(2014, 6, 27),
+                DateTime(2014, 7, 26),
+                DateTime(2014, 8, 25),
+                DateTime(2014, 9, 23),
+                DateTime(2014, 10, 23),
+                DateTime(2014, 11, 22),
+                DateTime(2014, 12, 21),
+                DateTime(2015, 1, 20),
+                DateTime(2015, 2, 18),
+                DateTime(2015, 3, 20),
+                DateTime(2015, 4, 18),
+                DateTime(2015, 5, 18),
+                DateTime(2015, 6, 16),
+                DateTime(2015, 7, 15),
+                DateTime(2015, 8, 14),
+                DateTime(2015, 9, 12),
+                DateTime(2015, 10, 12),
+                DateTime(2015, 11, 11),
+                DateTime(2015, 12, 11),
+                DateTime(2016, 1, 9),
+                DateTime(2016, 2, 8),
+                DateTime(2016, 3, 8),
+                DateTime(2016, 4, 7),
+                DateTime(2016, 5, 6),
+                DateTime(2016, 6, 5),
+                DateTime(2016, 7, 4),
+                DateTime(2016, 8, 2),
+                DateTime(2016, 9, 1),
+                DateTime(2016, 9, 30),
+                DateTime(2016, 10, 30),
+                DateTime(2016, 11, 29),
+                DateTime(2016, 12, 28),
+                DateTime(2017, 1, 27),
+                DateTime(2017, 2, 26),
+                DateTime(2017, 3, 27),
+                DateTime(2017, 4, 26),
+                DateTime(2017, 5, 25),
+                DateTime(2017, 6, 24),
+                DateTime(2017, 7, 23),
+                DateTime(2017, 8, 21),
+                DateTime(2017, 9, 20),
+                DateTime(2017, 10, 19),
+                DateTime(2017, 11, 18),
+                DateTime(2017, 12, 17),
</ins><span class="cx">             ],
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        start = DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(utc=True))
-        end = DateTime(2014, 3, 1, 0, 0, 0, tzid=Timezone(utc=True))
-        items = []
-        range = Period(start, end)
-        recur.expand(DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)), range, items)
-        self.assertEqual(
-            items,
-            [
-                DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 1, 8, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 1, 15, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 1, 22, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 1, 29, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 2, 5, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 2, 12, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 2, 19, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 2, 26, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-            ],
-        )
</del><span class="cx"> 
</span><ins>+    def testMonthlyRscaleStartInLeapYearSkipBackwardDefault(self):
</ins><span class="cx"> 
</span><del>-    def testMonthlyInUTC(self):
</del><ins>+        for rrule in (
+            &quot;RSCALE=CHINESE;FREQ=MONTHLY;SKIP=BACKWARD&quot;,
+            &quot;RSCALE=CHINESE;FREQ=MONTHLY&quot;
+        ):
+            recur = Recurrence()
+            recur.parse(rrule)
+            start = DateTime(2014, 1, 30) # {C}46501230
+            end = DateTime(2018, 1, 1)
+            items = []
+            range = Period(start, end)
+            recur.expand(start, range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2014, 1, 30),
+                    DateTime(2014, 2, 28),
+                    DateTime(2014, 3, 30),
+                    DateTime(2014, 4, 28),
+                    DateTime(2014, 5, 28),
+                    DateTime(2014, 6, 26),
+                    DateTime(2014, 7, 26),
+                    DateTime(2014, 8, 24),
+                    DateTime(2014, 9, 23),
+                    DateTime(2014, 10, 23),
+                    DateTime(2014, 11, 21),
+                    DateTime(2014, 12, 21),
+                    DateTime(2015, 1, 19),
+                    DateTime(2015, 2, 18),
+                    DateTime(2015, 3, 19),
+                    DateTime(2015, 4, 18),
+                    DateTime(2015, 5, 17),
+                    DateTime(2015, 6, 15),
+                    DateTime(2015, 7, 15),
+                    DateTime(2015, 8, 13),
+                    DateTime(2015, 9, 12),
+                    DateTime(2015, 10, 12),
+                    DateTime(2015, 11, 11),
+                    DateTime(2015, 12, 10),
+                    DateTime(2016, 1, 9),
+                    DateTime(2016, 2, 7),
+                    DateTime(2016, 3, 8),
+                    DateTime(2016, 4, 6),
+                    DateTime(2016, 5, 6),
+                    DateTime(2016, 6, 4),
+                    DateTime(2016, 7, 3),
+                    DateTime(2016, 8, 2),
+                    DateTime(2016, 8, 31),
+                    DateTime(2016, 9, 30),
+                    DateTime(2016, 10, 30),
+                    DateTime(2016, 11, 28),
+                    DateTime(2016, 12, 28),
+                    DateTime(2017, 1, 27),
+                    DateTime(2017, 2, 25),
+                    DateTime(2017, 3, 27),
+                    DateTime(2017, 4, 25),
+                    DateTime(2017, 5, 25),
+                    DateTime(2017, 6, 23),
+                    DateTime(2017, 7, 22),
+                    DateTime(2017, 8, 21),
+                    DateTime(2017, 9, 19),
+                    DateTime(2017, 10, 19),
+                    DateTime(2017, 11, 17),
+                    DateTime(2017, 12, 17),
+                ],
+            )
</ins><span class="cx"> 
</span><ins>+
+    def testYearlyLeapDaySkipYes(self):
+
</ins><span class="cx">         recur = Recurrence()
</span><del>-        recur.parse(&quot;FREQ=MONTHLY&quot;)
-        start = DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(utc=True))
-        end = DateTime(2015, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</del><ins>+        recur.parse(&quot;RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=YES;COUNT=5&quot;)
+        start = DateTime(2016, 2, 29)
+        end = DateTime(2100, 1, 1)
</ins><span class="cx">         items = []
</span><span class="cx">         range = Period(start, end)
</span><del>-        recur.expand(DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)), range, items)
</del><ins>+        recur.expand(start, range, items)
</ins><span class="cx">         self.assertEqual(
</span><span class="cx">             items,
</span><span class="cx">             [
</span><del>-                DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 2, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 3, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 4, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 5, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 6, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 7, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 8, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 9, 1, 12, 0, 0, tzid=Timezone(tzid=&quot;America/New_York&quot;)),
-                DateTime(2014, 10, 1, 12, 0, 0),
-                DateTime(2014, 11, 1, 12, 0, 0),
-                DateTime(2014, 12, 1, 12, 0, 0),
-            ],
</del><ins>+                DateTime(2016, 2, 29),
+                DateTime(2020, 2, 29),
+                DateTime(2024, 2, 29),
+                DateTime(2028, 2, 29),
+                DateTime(2032, 2, 29),
+            ]
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def testMonthlyStart31st(self):
</del><ins>+    def testYearlyLeapDaySkipForward(self):
</ins><span class="cx"> 
</span><span class="cx">         recur = Recurrence()
</span><del>-        recur.parse(&quot;FREQ=MONTHLY&quot;)
-        start = DateTime(2014, 1, 31, 12, 0, 0)
-        end = DateTime(2015, 1, 1, 0, 0, 0)
</del><ins>+        recur.parse(&quot;RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD;COUNT=5&quot;)
+        start = DateTime(2016, 2, 29)
+        end = DateTime(2100, 1, 1)
</ins><span class="cx">         items = []
</span><span class="cx">         range = Period(start, end)
</span><span class="cx">         recur.expand(start, range, items)
</span><span class="cx">         self.assertEqual(
</span><span class="cx">             items,
</span><span class="cx">             [
</span><del>-                DateTime(2014, 1, 31, 12, 0, 0),
-                DateTime(2014, 3, 31, 12, 0, 0),
-                DateTime(2014, 5, 31, 12, 0, 0),
-                DateTime(2014, 7, 31, 12, 0, 0),
-                DateTime(2014, 8, 31, 12, 0, 0),
-                DateTime(2014, 10, 31, 12, 0, 0),
-                DateTime(2014, 12, 31, 12, 0, 0),
-            ],
</del><ins>+                DateTime(2016, 2, 29),
+                DateTime(2017, 3, 1),
+                DateTime(2018, 3, 1),
+                DateTime(2019, 3, 1),
+                DateTime(2020, 2, 29),
+            ]
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def testMonthlyByMonthDay31(self):
</del><ins>+    def testYearlyLeapDaySkipBackwardDefault(self):
</ins><span class="cx"> 
</span><ins>+        for rrule in (
+            &quot;RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=BACKWARD;COUNT=5&quot;,
+            &quot;RSCALE=GREGORIAN;FREQ=YEARLY;COUNT=5&quot;,
+        ):
+            recur = Recurrence()
+            recur.parse(rrule)
+            start = DateTime(2016, 2, 29)
+            end = DateTime(2100, 1, 1)
+            items = []
+            range = Period(start, end)
+            recur.expand(start, range, items)
+            self.assertEqual(
+                items,
+                [
+                    DateTime(2016, 2, 29),
+                    DateTime(2017, 2, 28),
+                    DateTime(2018, 2, 28),
+                    DateTime(2019, 2, 28),
+                    DateTime(2020, 2, 29),
+                ]
+            )
+
+
+    def testChineseMonthlyByMonthDay30SkipYes(self):
+
+        rrule = &quot;RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=30;SKIP=YES&quot;
</ins><span class="cx">         recur = Recurrence()
</span><del>-        recur.parse(&quot;FREQ=MONTHLY;BYMONTHDAY=31&quot;)
-        start = DateTime(2014, 1, 31, 12, 0, 0)
</del><ins>+        recur.parse(rrule)
+        start = DateTime(2014, 1, 30, 12, 0, 0)
</ins><span class="cx">         end = DateTime(2015, 1, 1, 0, 0, 0)
</span><span class="cx">         items = []
</span><span class="cx">         range = Period(start, end)
</span><span class="lines">@@ -272,22 +747,24 @@
</span><span class="cx">         self.assertEqual(
</span><span class="cx">             items,
</span><span class="cx">             [
</span><del>-                DateTime(2014, 1, 31, 12, 0, 0),
-                DateTime(2014, 3, 31, 12, 0, 0),
-                DateTime(2014, 5, 31, 12, 0, 0),
-                DateTime(2014, 7, 31, 12, 0, 0),
-                DateTime(2014, 8, 31, 12, 0, 0),
-                DateTime(2014, 10, 31, 12, 0, 0),
-                DateTime(2014, 12, 31, 12, 0, 0),
</del><ins>+                DateTime(2014, 1, 30, 12, 0, 0),
+                DateTime(2014, 3, 30, 12, 0, 0),
+                DateTime(2014, 5, 28, 12, 0, 0),
+                DateTime(2014, 7, 26, 12, 0, 0),
+                DateTime(2014, 9, 23, 12, 0, 0),
+                DateTime(2014, 10, 23, 12, 0, 0),
+                DateTime(2014, 12, 21, 12, 0, 0),
</ins><span class="cx">             ],
</span><ins>+            msg=&quot;Failed: {} {}&quot;.format(rrule, items,),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def testMonthlyByMonthDayMinus31(self):
</del><ins>+    def testChineseMonthlyByMonthDay30SkipBackward(self):
</ins><span class="cx"> 
</span><ins>+        rrule = &quot;RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=30;SKIP=BACKWARD&quot;
</ins><span class="cx">         recur = Recurrence()
</span><del>-        recur.parse(&quot;FREQ=MONTHLY;BYMONTHDAY=-31&quot;)
-        start = DateTime(2014, 1, 1, 12, 0, 0)
</del><ins>+        recur.parse(rrule)
+        start = DateTime(2014, 1, 30, 12, 0, 0)
</ins><span class="cx">         end = DateTime(2015, 1, 1, 0, 0, 0)
</span><span class="cx">         items = []
</span><span class="cx">         range = Period(start, end)
</span><span class="lines">@@ -295,22 +772,29 @@
</span><span class="cx">         self.assertEqual(
</span><span class="cx">             items,
</span><span class="cx">             [
</span><del>-                DateTime(2014, 1, 1, 12, 0, 0),
-                DateTime(2014, 3, 1, 12, 0, 0),
-                DateTime(2014, 5, 1, 12, 0, 0),
-                DateTime(2014, 7, 1, 12, 0, 0),
-                DateTime(2014, 8, 1, 12, 0, 0),
-                DateTime(2014, 10, 1, 12, 0, 0),
-                DateTime(2014, 12, 1, 12, 0, 0),
</del><ins>+                DateTime(2014, 1, 30, 12, 0, 0),
+                DateTime(2014, 2, 28, 12, 0, 0),
+                DateTime(2014, 3, 30, 12, 0, 0),
+                DateTime(2014, 4, 28, 12, 0, 0),
+                DateTime(2014, 5, 28, 12, 0, 0),
+                DateTime(2014, 6, 26, 12, 0, 0),
+                DateTime(2014, 7, 26, 12, 0, 0),
+                DateTime(2014, 8, 24, 12, 0, 0),
+                DateTime(2014, 9, 23, 12, 0, 0),
+                DateTime(2014, 10, 23, 12, 0, 0),
+                DateTime(2014, 11, 21, 12, 0, 0),
+                DateTime(2014, 12, 21, 12, 0, 0),
</ins><span class="cx">             ],
</span><ins>+            msg=&quot;Failed: {} {}&quot;.format(rrule, items,),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def testMonthlyByLastFridayExpand(self):
</del><ins>+    def testChineseMonthlyByMonthDay30SkipForward(self):
</ins><span class="cx"> 
</span><ins>+        rrule = &quot;RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=30;SKIP=FORWARD&quot;
</ins><span class="cx">         recur = Recurrence()
</span><del>-        recur.parse(&quot;FREQ=MONTHLY;BYDAY=-1FR&quot;)
-        start = DateTime(2014, 1, 31, 12, 0, 0)
</del><ins>+        recur.parse(rrule)
+        start = DateTime(2014, 1, 30, 12, 0, 0)
</ins><span class="cx">         end = DateTime(2015, 1, 1, 0, 0, 0)
</span><span class="cx">         items = []
</span><span class="cx">         range = Period(start, end)
</span><span class="lines">@@ -318,27 +802,29 @@
</span><span class="cx">         self.assertEqual(
</span><span class="cx">             items,
</span><span class="cx">             [
</span><del>-                DateTime(2014, 1, 31, 12, 0, 0),
-                DateTime(2014, 2, 28, 12, 0, 0),
-                DateTime(2014, 3, 28, 12, 0, 0),
-                DateTime(2014, 4, 25, 12, 0, 0),
-                DateTime(2014, 5, 30, 12, 0, 0),
</del><ins>+                DateTime(2014, 1, 30, 12, 0, 0),
+                DateTime(2014, 3, 1, 12, 0, 0),
+                DateTime(2014, 3, 30, 12, 0, 0),
+                DateTime(2014, 4, 29, 12, 0, 0),
+                DateTime(2014, 5, 28, 12, 0, 0),
</ins><span class="cx">                 DateTime(2014, 6, 27, 12, 0, 0),
</span><del>-                DateTime(2014, 7, 25, 12, 0, 0),
-                DateTime(2014, 8, 29, 12, 0, 0),
-                DateTime(2014, 9, 26, 12, 0, 0),
-                DateTime(2014, 10, 31, 12, 0, 0),
-                DateTime(2014, 11, 28, 12, 0, 0),
-                DateTime(2014, 12, 26, 12, 0, 0),
</del><ins>+                DateTime(2014, 7, 26, 12, 0, 0),
+                DateTime(2014, 8, 25, 12, 0, 0),
+                DateTime(2014, 9, 23, 12, 0, 0),
+                DateTime(2014, 10, 23, 12, 0, 0),
+                DateTime(2014, 11, 22, 12, 0, 0),
+                DateTime(2014, 12, 21, 12, 0, 0),
</ins><span class="cx">             ],
</span><ins>+            msg=&quot;Failed: {} {}&quot;.format(rrule, items,),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def testMonthlyByFifthFridayExpand(self):
</del><ins>+    def testChineseMonthlyByMonthDayMinus30SkipYes(self):
</ins><span class="cx"> 
</span><ins>+        rrule = &quot;RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=-30;SKIP=YES&quot;
</ins><span class="cx">         recur = Recurrence()
</span><del>-        recur.parse(&quot;FREQ=MONTHLY;BYDAY=5FR&quot;)
-        start = DateTime(2014, 1, 31, 12, 0, 0)
</del><ins>+        recur.parse(rrule)
+        start = DateTime(2014, 1, 30, 12, 0, 0)
</ins><span class="cx">         end = DateTime(2015, 1, 1, 0, 0, 0)
</span><span class="cx">         items = []
</span><span class="cx">         range = Period(start, end)
</span><span class="lines">@@ -346,66 +832,72 @@
</span><span class="cx">         self.assertEqual(
</span><span class="cx">             items,
</span><span class="cx">             [
</span><del>-                DateTime(2014, 1, 31, 12, 0, 0),
-                DateTime(2014, 5, 30, 12, 0, 0),
-                DateTime(2014, 8, 29, 12, 0, 0),
-                DateTime(2014, 10, 31, 12, 0, 0),
</del><ins>+                DateTime(2014, 3, 1, 12, 0, 0),
+                DateTime(2014, 4, 29, 12, 0, 0),
+                DateTime(2014, 6, 27, 12, 0, 0),
+                DateTime(2014, 8, 25, 12, 0, 0),
+                DateTime(2014, 9, 24, 12, 0, 0),
+                DateTime(2014, 11, 22, 12, 0, 0),
</ins><span class="cx">             ],
</span><ins>+            msg=&quot;Failed: {} {}&quot;.format(rrule, items,),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def testYearlyLeapDay(self):
</del><ins>+    def testChineseMonthlyByMonthDayMinus30SkipBackward(self):
</ins><span class="cx"> 
</span><ins>+        rrule = &quot;RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=-30;SKIP=BACKWARD&quot;
</ins><span class="cx">         recur = Recurrence()
</span><del>-        recur.parse(&quot;FREQ=YEARLY&quot;)
-        start = DateTime(2012, 2, 29, 12, 0, 0)
-        end = DateTime(2020, 1, 1, 0, 0, 0)
</del><ins>+        recur.parse(rrule)
+        start = DateTime(2014, 1, 30, 12, 0, 0)
+        end = DateTime(2015, 1, 1, 0, 0, 0)
</ins><span class="cx">         items = []
</span><span class="cx">         range = Period(start, end)
</span><span class="cx">         recur.expand(start, range, items)
</span><span class="cx">         self.assertEqual(
</span><span class="cx">             items,
</span><span class="cx">             [
</span><del>-                DateTime(2012, 2, 29, 12, 0, 0),
-                DateTime(2016, 2, 29, 12, 0, 0),
</del><ins>+                DateTime(2014, 1, 30, 12, 0, 0),
+                DateTime(2014, 3, 1, 12, 0, 0),
+                DateTime(2014, 3, 30, 12, 0, 0),
+                DateTime(2014, 4, 29, 12, 0, 0),
+                DateTime(2014, 5, 28, 12, 0, 0),
+                DateTime(2014, 6, 27, 12, 0, 0),
+                DateTime(2014, 7, 26, 12, 0, 0),
+                DateTime(2014, 8, 25, 12, 0, 0),
+                DateTime(2014, 9, 24, 12, 0, 0),
+                DateTime(2014, 10, 23, 12, 0, 0),
+                DateTime(2014, 11, 22, 12, 0, 0),
+                DateTime(2014, 12, 21, 12, 0, 0),
</ins><span class="cx">             ],
</span><ins>+            msg=&quot;Failed: {} {}&quot;.format(rrule, items,),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def testYearlyYearDay(self):
</del><ins>+    def testChineseMonthlyByMonthDayMinus30SkipForward(self):
</ins><span class="cx"> 
</span><ins>+        rrule = &quot;RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=-30;SKIP=FORWARD&quot;
</ins><span class="cx">         recur = Recurrence()
</span><del>-        recur.parse(&quot;FREQ=YEARLY;BYYEARDAY=366&quot;)
-        start = DateTime(2012, 12, 31, 12, 0, 0)
-        end = DateTime(2020, 1, 1, 0, 0, 0)
</del><ins>+        recur.parse(rrule)
+        start = DateTime(2014, 1, 30, 12, 0, 0)
+        end = DateTime(2015, 1, 1, 0, 0, 0)
</ins><span class="cx">         items = []
</span><span class="cx">         range = Period(start, end)
</span><span class="cx">         recur.expand(start, range, items)
</span><span class="cx">         self.assertEqual(
</span><span class="cx">             items,
</span><span class="cx">             [
</span><del>-                DateTime(2012, 12, 31, 12, 0, 0),
-                DateTime(2016, 12, 31, 12, 0, 0),
</del><ins>+                DateTime(2014, 1, 31, 12, 0, 0),
+                DateTime(2014, 3, 1, 12, 0, 0),
+                DateTime(2014, 3, 31, 12, 0, 0),
+                DateTime(2014, 4, 29, 12, 0, 0),
+                DateTime(2014, 5, 29, 12, 0, 0),
+                DateTime(2014, 6, 27, 12, 0, 0),
+                DateTime(2014, 7, 27, 12, 0, 0),
+                DateTime(2014, 8, 25, 12, 0, 0),
+                DateTime(2014, 9, 24, 12, 0, 0),
+                DateTime(2014, 10, 24, 12, 0, 0),
+                DateTime(2014, 11, 22, 12, 0, 0),
+                DateTime(2014, 12, 22, 12, 0, 0),
</ins><span class="cx">             ],
</span><ins>+            msg=&quot;Failed: {} {}&quot;.format(rrule, items,),
</ins><span class="cx">         )
</span><del>-
-
-    def testClearOnChange(self):
-
-        recur = Recurrence()
-        recur.parse(&quot;FREQ=DAILY&quot;)
-
-        start = DateTime(2013, 1, 1, 0, 0, 0)
-        end = DateTime(2017, 1, 1, 0, 0, 0)
-        range = Period(start, end)
-        items = []
-        recur.expand(start, range, items)
-        self.assertTrue(recur.mCached)
-        self.assertTrue(len(items) &gt; 100)
-
-        recur.setUseCount(True)
-        recur.setCount(10)
-        self.assertFalse(recur.mCached)
-        items = []
-        recur.expand(start, range, items)
-        self.assertEqual(len(items), 10)
</del></span></pre></div>
<a id="PyCalendartrunksrcpycalendaricalendarxmldefinitionspy"></a>
<div class="modfile"><h4>Modified: PyCalendar/trunk/src/pycalendar/icalendar/xmldefinitions.py (14190 => 14191)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/icalendar/xmldefinitions.py        2014-12-01 15:13:17 UTC (rev 14190)
+++ PyCalendar/trunk/src/pycalendar/icalendar/xmldefinitions.py        2014-12-01 15:15:13 UTC (rev 14191)
</span><span class="lines">@@ -22,6 +22,8 @@
</span><span class="cx"> 
</span><span class="cx"> value_recur = &quot;recur&quot;
</span><span class="cx"> 
</span><ins>+recur_rscale = &quot;rscale&quot;
+
</ins><span class="cx"> recur_freq = &quot;freq&quot;
</span><span class="cx"> recur_freq_secondly = &quot;SECONDLY&quot;
</span><span class="cx"> recur_freq_minutely = &quot;MINUTELY&quot;
</span><span class="lines">@@ -35,6 +37,11 @@
</span><span class="cx"> recur_until = &quot;until&quot;
</span><span class="cx"> recur_interval = &quot;interval&quot;
</span><span class="cx"> 
</span><ins>+recur_skip = &quot;skip&quot;
+recur_skip_yes = &quot;yes&quot;
+recur_skip_backward = &quot;backward&quot;
+recur_skip_forward = &quot;forward&quot;
+
</ins><span class="cx"> recur_bysecond = &quot;bysecond&quot;
</span><span class="cx"> recur_byminute = &quot;byminute&quot;
</span><span class="cx"> recur_byhour = &quot;byhour&quot;
</span></span></pre>
</div>
</div>

</body>
</html>