<!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">         <name>pycalendar</name>
</span><span class="cx">         <comment></comment>
</span><span class="cx">         <projects>
</span><ins>+                <project>cffi</project>
</ins><span class="cx">         </projects>
</span><span class="cx">         <buildSpec>
</span><span class="cx">                 <buildCommand>
</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 > 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) < 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 > 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 < 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"> """
</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):
+ """
+ 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}
+ """
+
+ if self.invalid():
+ if skip == definitions.eRecurrence_SKIP_YES:
+ # Leave it as invalid
+ pass
+ elif skip == definitions.eRecurrence_SKIP_BACKWARD:
+ if self.mDay <= 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 <= 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 = "FREQ"
</span><span class="cx"> cICalValue_RECUR_FREQ_LEN = 5
</span><span class="lines">@@ -256,6 +258,8 @@
</span><span class="cx"> cICalValue_RECUR_BYMONTH = "BYMONTH"
</span><span class="cx"> cICalValue_RECUR_BYSETPOS = "BYSETPOS"
</span><span class="cx"> cICalValue_RECUR_WKST = "WKST"
</span><ins>+cICalValue_RECUR_RSCALE = "RSCALE"
+cICalValue_RECUR_SKIP = "SKIP"
</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 = "FR"
</span><span class="cx"> cICalValue_RECUR_WEEKDAY_SA = "SA"
</span><span class="cx">
</span><ins>+eRecurrence_SKIP_YES = 0
+eRecurrence_SKIP_BACKWARD = 1
+eRecurrence_SKIP_FORWARD = 2
+
+cICalValue_RECUR_SKIP_YES = "YES"
+cICalValue_RECUR_SKIP_BACKWARD = "BACKWARD"
+cICalValue_RECUR_SKIP_FORWARD = "FORWARD"
+
</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 "License");
+# 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 "AS IS" 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 = """
+ //#define U_FAILURE(x) ((x)>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 <code>UCAL_MONTH</code> 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);
+"""
+
+ffi.cdef(hdr)
+ffi.verify(hdr.replace(", ...", ""))
+
+ICU = ffi.dlopen("libicucore")
+
+class ICUDateTime(object):
+ """
+ An ICU-based L{DateTime} like class that supports non-Gregorian date-time values and arithmetic.
+ """
+
+ RSCALE_GREGORIAN = "gregorian"
+ RSCALE_HEBREW = "hebrew"
+
+ RSCALE_CALCODE = {
+ "gregorian": "",
+ "chinese": "C",
+ "islamic-civil": "I",
+ "hebrew": "H",
+ "ethiopic": "E",
+ }
+
+ def __init__(self, rscale, ucal):
+ """
+ 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*}
+ """
+ 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):
+ """
+ Always close the ICU C{ucal} object.
+ """
+ ICU.ucal_close(self.ucal)
+ self.ucal = None
+
+
+ def duplicate(self):
+ """
+ Duplicate this object.
+ """
+
+ error = ffi.new("UErrorCode *", 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 "ICUDateTime: %s" % (self.getText(),)
+
+
+ def __hash__(self):
+ return hash(self.getPosixTime())
+
+
+ @classmethod
+ def fromDateTime(cls, dt, rscale):
+ """
+ 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}
+ """
+
+ # 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):
+ """
+ Convert to a regular L{DateTime}.
+
+ @return: the converted object
+ @rtype: L{DateTime}
+ """
+
+ # 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):
+ """
+ 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*}
+ """
+ calsystem = "*@calendar={}".format(rscale)
+ error = ffi.new("UErrorCode *", 0)
+ ucal = ICU.ucal_open(ffi.NULL, -1, ffi.new("char[]", calsystem), ICU.UCAL_DEFAULT, error)
+ if error[0] != ICU.U_ZERO_ERROR:
+ raise ValueError("Unable to create ICU calendar for rscale '{}', code: {}".format(rscale, error))
+ return ucal
+
+
+ @classmethod
+ def fromDateComponents(cls, rscale, year, month, day, isleapmonth=False):
+ """
+ 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}
+ """
+
+ # 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):
+ """
+ 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}
+ """
+ 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):
+ """
+ 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}
+ """
+ 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):
+ """
+ For the Hebrew calendar, ICU uses a count of 13 months rather than 12 months
+ plus an "isleapmonth" 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}
+ """
+
+ if rscale.lower() == cls.RSCALE_HEBREW:
+ if month == 5 and isleapmonth:
+ month = 6
+ isleapmonth = None
+ elif month >= 6:
+ month += 1
+ return (month, isleapmonth,)
+
+
+ @classmethod
+ def _adjustFromICULeapMonth(cls, rscale, month, isleapmonth):
+ """
+ For the Hebrew calendar, ISU uses a count of 13 months rather than 12 months
+ plus an "isleapmonth" 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}
+ """
+
+ if rscale.lower() == cls.RSCALE_HEBREW:
+ isleapmonth = False
+ if month == 6:
+ isleapmonth = True
+ elif month >= 6:
+ month -= 1
+ return (month, isleapmonth,)
+
+
+ @classmethod
+ def _transferHHMMSS(cls, from_dt, to_dt):
+ """
+ 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}
+ """
+ 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):
+ """
+ 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}
+ """
+ error = ffi.new("UErrorCode *", 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):
+ """
+ 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})
+ """
+ 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):
+ """
+ Return an integer representing a standard offset in seconds from a specific
+ epoch. This is used for sorting similar object.
+ """
+
+ # Use the ICU "millis" for this.
+ error = ffi.new("UErrorCode *", 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("UErrorCode *", 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):
+ """
+ Offset the ICU date year component by the specified amount.
+
+ @param diff_year: amount to offset
+ @type diff_year: L{int}
+ """
+ error = ffi.new("UErrorCode *", 0)
+ ICU.ucal_add(self.ucal, ICU.UCAL_EXTENDED_YEAR, diff_year, error)
+
+
+ def getMonth(self):
+ error = ffi.new("UErrorCode *", 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):
+ """
+ Offset the ICU date month component by the specified amount.
+
+ @param diff_year: amount to offset
+ @type diff_year: L{int}
+ """
+ error = ffi.new("UErrorCode *", 0)
+ ICU.ucal_add(self.ucal, ICU.UCAL_MONTH, diff_month, error)
+
+
+ def getLeapMonth(self):
+ error = ffi.new("UErrorCode *", 0)
+ return ICU.ucal_get(self.ucal, ICU.UCAL_IS_LEAP_MONTH, error) != 0
+
+
+ def getDay(self):
+ error = ffi.new("UErrorCode *", 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):
+ """
+ Offset the ICU date month component by the specified amount.
+
+ @param diff_year: amount to offset
+ @type diff_year: L{int}
+ """
+ error = ffi.new("UErrorCode *", 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("UErrorCode *", 0)
+ limit = ICU.ucal_getLimit(self.ucal, ICU.UCAL_DAY_OF_YEAR, ICU.UCAL_ACTUAL_MAXIMUM, error)
+
+ if day > 0:
+ ICU.ucal_set(self.ucal, ICU.UCAL_DAY_OF_YEAR, min(day, limit))
+ if day > limit and allow_invalid:
+ self.setInvalid(self.getYear(), 1, day)
+ else:
+ self.clearInvalid()
+ elif day < 0:
+ offset = limit + day + 1
+ ICU.ucal_set(self.ucal, ICU.UCAL_DAY_OF_YEAR, max(offset, 1))
+ if offset <= 0 and allow_invalid:
+ self.setInvalid(self.getYear(), 1, day)
+ else:
+ self.clearInvalid()
+
+
+ def getYearDay(self):
+ error = ffi.new("UErrorCode *", 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("UErrorCode *", 0)
+ limit = ICU.ucal_getLimit(self.ucal, ICU.UCAL_DAY_OF_MONTH, ICU.UCAL_ACTUAL_MAXIMUM, error)
+
+ if day > 0:
+ ICU.ucal_set(self.ucal, ICU.UCAL_DAY_OF_MONTH, min(day, limit))
+ if day > limit and allow_invalid:
+ y, m, _ignore_d, l = self.getDateComponents()
+ self.setInvalid(y, m, day, l)
+ else:
+ self.clearInvalid()
+
+ elif day < 0:
+ offset = limit + day + 1
+ ICU.ucal_set(self.ucal, ICU.UCAL_DAY_OF_MONTH, max(offset, 1))
+ if offset <= 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 > 0:
+ return self.getDay() == day
+ elif day < 0:
+ error = ffi.new("UErrorCode *", 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):
+ """
+ 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}
+ """
+
+ # Only supported for Gregorian calendars
+ if self.rscale.lower() != self.RSCALE_GREGORIAN:
+ raise ValueError("Week numbers only supported for Gregorian calendars")
+ dt = self.toDateTime()
+ dt.setWeekNo(weekno)
+ self.setYYMMDD(dt.getYear(), dt.getMonth(), dt.getDay())
+
+
+ def getWeekNo(self):
+ """
+ Return the ISO week number for the current date.
+ """
+
+ # Only supported for Gregorian calendars
+ if self.rscale.lower() != self.RSCALE_GREGORIAN:
+ raise ValueError("Week numbers only supported for Gregorian calendars")
+ dt = self.toDateTime()
+ return dt.getWeekNo()
+
+
+ def isWeekNo(self, weekno):
+ # This is the iso 8601 week number definition
+
+ if weekno > 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 > 0:
+ cycle = (offset - 1) * 7 + day
+ cycle -= first_day
+ if first_day > day:
+ cycle += 7
+ self.offsetDay(cycle)
+ elif offset < 0:
+ # Find the limit for the current year
+ error = ffi.new("UErrorCode *", 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 > 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 > 0:
+ cycle = (offset - 1) * 7 + day
+ cycle -= first_day
+ if first_day > day:
+ cycle += 7
+ mday = cycle + 1
+ self.offsetDay(cycle)
+ elif offset < 0:
+ # Find the limit for the current year
+ error = ffi.new("UErrorCode *", 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 > 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("UErrorCode *", 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):
+ """
+ If the requested set of YYMMDDLL does not match the current set of YYMMDDLL then the requested
+ set was invalid.
+ """
+ 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):
+ """
+ Are any of the current fields invalid.
+ """
+
+ # 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):
+ """
+ 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}
+ """
+
+ 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] < 1:
+ self.offsetDay(-1)
+ elif skip == definitions.eRecurrence_SKIP_FORWARD:
+ if self.mInvalid[2] > 0:
+ self.offsetDay(1)
+
+ self.clearInvalid()
+
+
+ def normalise(self):
+ # Normalise seconds
+ normalised_secs = self.mSeconds % 60
+ adjustment_mins = self.mSeconds / 60
+ if normalised_secs < 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 < 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 < 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):
+ """
+ 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}
+ """
+ calcode = self.RSCALE_CALCODE.get(self.rscale.lower(), "{}:".format(self.rscale))
+ if calcode:
+ calcode = "{{{}}}".format(calcode)
+ year, month, day, isleapmonth = self.getDateComponents()
+ date = "{}{:04d}{:02d}{}{:02d}".format(calcode, year, month, "L" if isleapmonth else "", day)
+ if not self.isDateOnly():
+ date += "T{:02d}{:02d}{:02d}{}".format(self.mHours, self.mMinutes, self.mSeconds, "Z" if self.mTZUTC else "")
+ return date
+
+
+if __name__ == '__main__':
+ newyear = ICUDateTime.fromDateComponents("chinese", 4651, 1, 1, False)
+ print("From: {} to {}".format(
+ newyear.getText(),
+ newyear.convertTo("gregorian").getText(),
+ ))
+
+ for i in range(0):
+ newyear.offsetDay(1)
+ print("From: {} to {}".format(
+ newyear.getText(),
+ newyear.convertTo("gregorian").getText(),
+ ))
+
+ offset = 1
+ greg = ICUDateTime.fromDateComponents("gregorian", 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 "License");
+# 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 "AS IS" 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):
+ """
+ An iterator that iterates a simple recurrence pattern.
+ """
+
+ def __init__(self, start, freq, interval, rscale=None, skip=definitions.eRecurrence_SKIP_YES, allow_invalid=False):
+ """
+ @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}
+ """
+ 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):
+ """
+ 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}
+ """
+
+ 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):
+ """
+ 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}
+ """
+
+ 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("gregorian", 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("mRscale", 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("mInterval", interval)
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def getSkip(self):
+ return self.mSkip
+
+
+ def effectiveSkip(self):
+ """
+ The default skip value depends on whether RSCALE is used or not
+ """
+ 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("mSkip", 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("Recurrence: Only one BYMONTH allowed")
</span><span class="cx"> self.mByMonth = []
</span><del>- self.parseList(tvalue, self.mByMonth, 1, 12, errmsg="Recurrence: Invalid BYMONTH value")
</del><ins>+ self.parseMonthNumList(tvalue, self.mByMonth, 1, 12, errmsg="Recurrence: Invalid BYMONTH value")
</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("Recurrence: Invalid WKST value")
</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("Recurrence: Invalid SKIP value")
+ self.mSkip = index
+
+ # Final validity checks
+ if self.mRscale is None and self.mSkip is not None:
+ raise ValueError("Recurrence: SKIP only allowed with RSCALE")
+
+
</ins><span class="cx"> def parseList(self, txt, list, min=None, max=None, allowNegative=False, errmsg=""):
</span><span class="cx">
</span><span class="cx"> if "," 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=""):
+ """
+ Month numbers can include "L" leap month suffix.
+ """
+
+ if "," in txt:
+ tokens = txt.split(",")
+ else:
+ tokens = (txt,)
+
+ for token in tokens:
+ if token.endswith("L"):
+ suffix = True
+ token = token[:-1]
+ else:
+ suffix = False
+ value = int(token)
+ if not allowNegative and value < 0:
+ raise ValueError(errmsg)
+ avalue = abs(value)
+ if min is not None and avalue < min:
+ raise ValueError(errmsg)
+ if max is not None and avalue > max:
+ raise ValueError(errmsg)
+ list.append((value, suffix,))
+
+
</ins><span class="cx"> def parseListDW(self, txt, list, errmsg=""):
</span><span class="cx">
</span><span class="cx"> if "," 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("=")
+ os.write(self.mRscale.upper())
+ os.write(";")
+
</ins><span class="cx"> os.write(definitions.cICalValue_RECUR_FREQ)
</span><span class="cx"> os.write("=")
</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(";")
</span><span class="cx"> os.write(definitions.cICalValue_RECUR_COUNT)
</span><span class="lines">@@ -583,6 +668,12 @@
</span><span class="cx"> os.write("=")
</span><span class="cx"> os.write(str(self.mInterval))
</span><span class="cx">
</span><ins>+ if self.mSkip is not None:
+ os.write(";")
+ os.write(definitions.cICalValue_RECUR_SKIP)
+ os.write("=")
+ 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):
+ """
+ Month numbers can include "L" leap month suffix.
+ """
+
+ if (items is not None) and (len(items) != 0):
+ os.write(";")
+ os.write(title)
+ os.write("=")
+ comma = False
+ for item in items:
+ if comma:
+ os.write(",")
+ comma = True
+ os.write(str(item[0]) + ("L" if item[1] else ""))
+
+
</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):
+ """
+ Month numbers can include "L" leap month suffix.
+ """
+
+ 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]) + ("L" if item[1] else "")
+
+
</ins><span class="cx"> def parseJSON(self, jobject):
</span><span class="cx"> """
</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"> """
</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 > 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]) + "L") 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 > 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 >= self.mCount:
</del><ins>+ # Exit if max count reached
+ if len(results) >= 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 > 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 >= 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 > 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 >= self.mCount:
</del><ins>+ # Exit if max count reached
+ if len(results) >= 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 "License");
+# 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 "AS IS" 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):
+ """
+ Test L{ICUDateTime}
+ """
+
+ def testRoundtripDateText(self):
+
+ data_date = (
+ ("gregorian", 2011, 1, 2, False, "20110102",),
+ ("gregorian", 1, 1, 2, False, "00010102",),
+ ("chinese", 4651, 1, 1, False, "{C}46510101",),
+ ("chinese", 4651, 9, 1, True, "{C}465109L01",),
+ )
+
+ for rscale, y, m, d, l, result in data_date:
+ dt = ICUDateTime.fromDateComponents(rscale, y, m, d, l)
+ self.assertEqual(dt.getText(), result, "Failed on: %s" % (result,))
+
+
+ def testRoundtripDateTime(self):
+
+ data = (
+ ("20110102", "gregorian", "20110102"),
+ ("20110102T010203Z", "gregorian", "20110102T010203Z"),
+ ("00010102", "gregorian", "00010102"),
+ ("20140102", "chinese", "{C}46501202"),
+ ("20140102T010203Z", "chinese", "{C}46501202T010203Z"),
+ ("20141025", "chinese", "{C}465109L02"),
+ ("20141025T010203", "chinese", "{C}465109L02T010203"),
+ )
+
+ for item, rscale, uitem in data:
+ dt = DateTime.parseText(item, False)
+ udt = ICUDateTime.fromDateTime(dt, rscale)
+ self.assertEqual(udt.getText(), uitem, "Failed on icu: %s" % (uitem,))
+ result = udt.toDateTime()
+ self.assertEqual(result.getText(), item, "Failed on dt: %s" % (item,))
+
+
+ def testSetYear(self):
+
+ data_date = (
+ ("gregorian", 2012, 1, 2, False, 2013, "20130102", False,),
+ ("gregorian", 2012, 2, 29, False, 2013, "20130301", True,),
+ ("gregorian", 2012, 2, 29, False, 2016, "20160229", 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, "Failed on: {} {} {}".format(year, result, result_invalid,))
+ self.assertEqual(dt.invalid(), result_invalid, "Failed invalid on: {} {} {}".format(year, result, result_invalid,))
+
+
+ def testOffsetYear(self):
+
+ data_date = (
+ ("gregorian", 2012, 1, 2, False, 1, "20130102", False,),
+ ("gregorian", 2012, 2, 29, False, 1, "20130228", False,),
+ ("gregorian", 2012, 2, 29, False, 4, "20160229", 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, "Failed on: {} {} {}".format(year, result, result_invalid,))
+ self.assertEqual(dt.invalid(), result_invalid, "Failed invalid on: {} {} {}".format(year, result, result_invalid,))
+
+
+ def testSetMonth(self):
+
+ data_date = (
+ ("gregorian", 2012, 1, 2, False, 2, "20120202", False,),
+ ("gregorian", 2012, 1, 29, False, 2, "20120229", False,),
+ ("gregorian", 2012, 1, 31, False, 2, "20120302", True,),
+ ("gregorian", 2012, 2, 29, False, 3, "20120329", 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, "Failed on: {} {} {}".format(month, result, result_invalid,))
+ self.assertEqual(dt.invalid(), result_invalid, "Failed invalid on: {} {} {}".format(month, result, result_invalid,))
+
+
+ def testOffsetMonth(self):
+
+ data_date = (
+ ("gregorian", 2012, 1, 2, False, 1, "20120202", False,),
+ ("gregorian", 2012, 1, 29, False, 1, "20120229", False,),
+ ("gregorian", 2012, 1, 31, False, 1, "20120229", False,),
+ ("gregorian", 2012, 2, 29, False, 1, "20120329", 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, "Failed on: {} {} {}".format(month, result, result_invalid,))
+ self.assertEqual(dt.invalid(), result_invalid, "Failed invalid on: {} {} {}".format(month, result, result_invalid,))
+
+
+ def testSetDay(self):
+
+ data_date = (
+ ("gregorian", 2012, 2, 1, False, 2, "20120202", False,),
+ ("gregorian", 2012, 2, 1, False, 29, "20120229", False,),
+ ("gregorian", 2012, 2, 1, False, 31, "20120302", 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, "Failed on: {} {} {}".format(day, result, result_invalid,))
+ self.assertEqual(dt.invalid(), result_invalid, "Failed invalid on: {} {} {}".format(day, result, result_invalid,))
+
+
+ def testOffsetDay(self):
+
+ data_date = (
+ ("gregorian", 2012, 2, 1, False, 1, "20120202", False,),
+ ("gregorian", 2012, 2, 1, False, 28, "20120229", False,),
+ ("gregorian", 2012, 2, 1, False, 30, "20120302", 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, "Failed on: {} {} {}".format(day, result, result_invalid,))
+ self.assertEqual(dt.invalid(), result_invalid, "Failed invalid on: {} {} {}".format(day, result, result_invalid,))
+
+
+ def testYearDay(self):
+
+ data_date = (
+ ("gregorian", 2012, 1, 2, False, 1, False, "20120101", False,),
+ ("gregorian", 2012, 1, 2, False, 60, False, "20120229", False,),
+ ("gregorian", 2012, 1, 2, False, 366, False, "20121231", False,),
+ ("gregorian", 2012, 1, 2, False, 367, False, "20121231", False,),
+ ("gregorian", 2012, 1, 2, False, 367, True, "20121231", True,),
+ ("gregorian", 2012, 1, 2, False, -1, False, "20121231", False,),
+ ("gregorian", 2012, 1, 2, False, -307, False, "20120229", False,),
+ ("gregorian", 2012, 1, 2, False, -366, False, "20120101", False,),
+ ("gregorian", 2012, 1, 2, False, -367, False, "20120101", False,),
+ ("gregorian", 2012, 1, 2, False, -367, True, "20120101", 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, "Failed on: {} {} {}".format(yearday, result, result_invalid,))
+ self.assertEqual(dt.invalid(), result_invalid, "Failed invalid on: {} {} {}".format(yearday, result, result_invalid,))
+
+
+ def testMonthDay(self):
+
+ data_date = (
+ ("gregorian", 2012, 1, 2, False, 1, False, "20120101", False,),
+ ("gregorian", 2012, 1, 31, False, 1, False, "20120101", False,),
+ ("gregorian", 2012, 2, 1, False, 31, False, "20120229", False,),
+ ("gregorian", 2012, 2, 1, False, 31, True, "20120229", True,),
+ ("gregorian", 2012, 1, 2, False, -1, False, "20120131", False,),
+ ("gregorian", 2012, 2, 29, False, -29, False, "20120201", False,),
+ ("gregorian", 2012, 2, 29, False, -30, False, "20120201", False,),
+ ("gregorian", 2012, 2, 29, False, -30, True, "20120201", 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, "Failed on: {} {} {}".format(monthday, result, result_invalid,))
+ self.assertEqual(dt.invalid(), result_invalid, "Failed invalid on: {} {} {}".format(monthday, result, result_invalid,))
+
+
+ def testWeekNum(self):
+
+ data_date = (
+ ("gregorian", 2012, 1, 3, False, 1, 2, "20120110",),
+ ("gregorian", 2014, 1, 3, False, 1, 2, "20140110",),
+ ("gregorian", 2012, 1, 1, False, 52, 2, "20120115",),
+ )
+
+ 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, "Failed on: {} {} vs {}".format(weekno, result, dt.getText(),))
+
+
+ def testDayOfWeekInYear(self):
+
+ data_date = (
+ ("gregorian", 2012, 1, 3, False, 1, DateTime.SUNDAY, "20120101",),
+ ("gregorian", 2012, 1, 3, False, 1, DateTime.MONDAY, "20120102",),
+ ("gregorian", 2012, 1, 3, False, 2, DateTime.SUNDAY, "20120108",),
+ ("gregorian", 2012, 1, 3, False, 10, DateTime.SUNDAY, "20120304",),
+ ("gregorian", 2012, 1, 3, False, -1, DateTime.SUNDAY, "20121230",),
+ ("gregorian", 2012, 1, 3, False, -45, DateTime.SUNDAY, "20120226",),
+ )
+
+ 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, "Failed on: {} vs {}".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 "License");
+# 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 "AS IS" 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,
+ [
+ "20140131",
+ "20140331",
+ "20140531",
+ "20140731",
+ "20140831",
+ "20141031",
+ "20141231",
+ "20150131",
+ "20150331",
+ "20150531",
+ "20150731",
+ "20150831",
+ ]
+ )
+
+
+ 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,
+ [
+ "20140131",
+ "20140301",
+ "20140331",
+ "20140501",
+ "20140531",
+ "20140701",
+ "20140731",
+ "20140831",
+ "20141001",
+ "20141031",
+ "20141201",
+ "20141231",
+ ]
+ )
+
+
+ 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,
+ [
+ "20140131",
+ "20140228",
+ "20140331",
+ "20140430",
+ "20140531",
+ "20140630",
+ "20140731",
+ "20140831",
+ "20140930",
+ "20141031",
+ "20141130",
+ "20141231",
+ ]
+ )
+
+
+ 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,
+ [
+ "20160131",
+ "20160229",
+ "20160331",
+ "20160430",
+ "20160531",
+ "20160630",
+ "20160731",
+ "20160831",
+ "20160930",
+ "20161031",
+ "20161130",
+ "20161231",
+ ]
+ )
+
+
+
+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("gregorian", 2014, 1, 31)
+ self.dtleap = ICUDateTime.fromDateComponents("gregorian", 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,
+ [
+ "20160229",
+ "20200229",
+ "20240229",
+ "20280229",
+ "20320229",
+ ]
+ )
+
+
+ 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,
+ [
+ "20160229",
+ "20170301",
+ "20180301",
+ "20190301",
+ "20200229",
+ ]
+ )
+
+
+ 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,
+ [
+ "20160229",
+ "20170228",
+ "20180228",
+ "20190228",
+ "20200229",
+ ]
+ )
+
+
+
+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("gregorian", 2016, 2, 29)
+ self.rscale = None
+
+
+
+class TestMonthlyChineseICU(unittest.TestCase):
+
+ def testMonthlyStartInLeapYearSkipYes(self):
+ dt = ICUDateTime.fromDateComponents("chinese", 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() >= 4655:
+ break
+ results.append(result.getText())
+ self.assertEqual(
+ results,
+ [
+ "{C}46501230",
+ "{C}46510230",
+ "{C}46510430",
+ "{C}46510630",
+ "{C}46510830",
+ "{C}46510930",
+ "{C}46511030",
+ "{C}46511230",
+ "{C}46520230",
+ "{C}46520530",
+ "{C}46520730",
+ "{C}46520830",
+ "{C}46520930",
+ "{C}46521130",
+ "{C}46530130",
+ "{C}46530330",
+ "{C}46530630",
+ "{C}46530830",
+ "{C}46530930",
+ "{C}46531130",
+ "{C}46531230",
+ "{C}46540230",
+ "{C}46540430",
+ "{C}465406L30",
+ "{C}46540830",
+ "{C}46541030",
+ "{C}46541130",
+ "{C}46541230",
+ ]
+ )
+
+
+ def testMonthlyStartInLeapYearSkipForward(self):
+ dt = ICUDateTime.fromDateComponents("chinese", 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() >= 4655:
+ break
+ results.append(result.getText())
+ self.assertEqual(
+ results,
+ [
+ "{C}46501230",
+ "{C}46510201",
+ "{C}46510230",
+ "{C}46510401",
+ "{C}46510430",
+ "{C}46510601",
+ "{C}46510630",
+ "{C}46510801",
+ "{C}46510830",
+ "{C}46510930",
+ "{C}46511001",
+ "{C}46511030",
+ "{C}46511201",
+ "{C}46511230",
+ "{C}46520201",
+ "{C}46520230",
+ "{C}46520401",
+ "{C}46520501",
+ "{C}46520530",
+ "{C}46520701",
+ "{C}46520730",
+ "{C}46520830",
+ "{C}46520930",
+ "{C}46521101",
+ "{C}46521130",
+ "{C}46530101",
+ "{C}46530130",
+ "{C}46530301",
+ "{C}46530330",
+ "{C}46530501",
+ "{C}46530601",
+ "{C}46530630",
+ "{C}46530801",
+ "{C}46530830",
+ "{C}46530930",
+ "{C}46531101",
+ "{C}46531130",
+ "{C}46531230",
+ "{C}46540201",
+ "{C}46540230",
+ "{C}46540401",
+ "{C}46540430",
+ "{C}46540601",
+ "{C}465406L01",
+ "{C}465406L30",
+ "{C}46540801",
+ "{C}46540830",
+ "{C}46541001",
+ "{C}46541030",
+ "{C}46541130",
+ "{C}46541230",
+ ]
+ )
+
+
+ def testMonthlyStartInLeapYearSkipBackward(self):
+ dt = ICUDateTime.fromDateComponents("chinese", 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() >= 4655:
+ break
+ results.append(result.getText())
+ self.assertEqual(
+ results,
+ [
+ "{C}46501230",
+ "{C}46510129",
+ "{C}46510230",
+ "{C}46510329",
+ "{C}46510430",
+ "{C}46510529",
+ "{C}46510630",
+ "{C}46510729",
+ "{C}46510830",
+ "{C}46510930",
+ "{C}465109L29",
+ "{C}46511030",
+ "{C}46511129",
+ "{C}46511230",
+ "{C}46520129",
+ "{C}46520230",
+ "{C}46520329",
+ "{C}46520429",
+ "{C}46520530",
+ "{C}46520629",
+ "{C}46520730",
+ "{C}46520830",
+ "{C}46520930",
+ "{C}46521029",
+ "{C}46521130",
+ "{C}46521229",
+ "{C}46530130",
+ "{C}46530229",
+ "{C}46530330",
+ "{C}46530429",
+ "{C}46530529",
+ "{C}46530630",
+ "{C}46530729",
+ "{C}46530830",
+ "{C}46530930",
+ "{C}46531029",
+ "{C}46531130",
+ "{C}46531230",
+ "{C}46540129",
+ "{C}46540230",
+ "{C}46540329",
+ "{C}46540430",
+ "{C}46540529",
+ "{C}46540629",
+ "{C}465406L30",
+ "{C}46540729",
+ "{C}46540830",
+ "{C}46540929",
+ "{C}46541030",
+ "{C}46541130",
+ "{C}46541230",
+ ]
+ )
+
+
+ def testMonthlyRscaleStartInLeapYearSkipYes(self):
+ dt = ICUDateTime.fromDateComponents("chinese", 4650, 12, 30).toDateTime()
+
+ riter = RecurrenceIterator(dt, definitions.eRecurrence_MONTHLY, 1, rscale="chinese", skip=definitions.eRecurrence_SKIP_YES)
+ results = []
+ while True:
+ result = riter.next()
+ if result.getYear() >= 2018:
+ break
+ results.append(result.getText())
+ self.assertEqual(
+ results,
+ [
+ "20140130",
+ "20140330",
+ "20140528",
+ "20140726",
+ "20140923",
+ "20141023",
+ "20141221",
+ "20150218",
+ "20150418",
+ "20150715",
+ "20150912",
+ "20151012",
+ "20151111",
+ "20160109",
+ "20160308",
+ "20160506",
+ "20160802",
+ "20160930",
+ "20161030",
+ "20161228",
+ "20170127",
+ "20170327",
+ "20170525",
+ "20170821",
+ "20171019",
+ "20171217",
+ ]
+ )
+
+
+ def testMonthlyRscaleStartInLeapYearSkipForward(self):
+ dt = ICUDateTime.fromDateComponents("chinese", 4650, 12, 30).toDateTime()
+
+ riter = RecurrenceIterator(dt, definitions.eRecurrence_MONTHLY, 1, rscale="chinese", skip=definitions.eRecurrence_SKIP_FORWARD)
+ results = []
+ while True:
+ result = riter.next()
+ if result.getYear() >= 2018:
+ break
+ results.append(result.getText())
+ self.assertEqual(
+ results,
+ [
+ "20140130",
+ "20140301",
+ "20140330",
+ "20140429",
+ "20140528",
+ "20140627",
+ "20140726",
+ "20140825",
+ "20140923",
+ "20141023",
+ "20141122",
+ "20141221",
+ "20150120",
+ "20150218",
+ "20150320",
+ "20150418",
+ "20150518",
+ "20150616",
+ "20150715",
+ "20150814",
+ "20150912",
+ "20151012",
+ "20151111",
+ "20151211",
+ "20160109",
+ "20160208",
+ "20160308",
+ "20160407",
+ "20160506",
+ "20160605",
+ "20160704",
+ "20160802",
+ "20160901",
+ "20160930",
+ "20161030",
+ "20161129",
+ "20161228",
+ "20170127",
+ "20170226",
+ "20170327",
+ "20170426",
+ "20170525",
+ "20170624",
+ "20170723",
+ "20170821",
+ "20170920",
+ "20171019",
+ "20171118",
+ "20171217",
+ ]
+ )
+
+
+ def testMonthlyRscaleStartInLeapYearSkipBackward(self):
+ dt = ICUDateTime.fromDateComponents("chinese", 4650, 12, 30).toDateTime()
+
+ riter = RecurrenceIterator(dt, definitions.eRecurrence_MONTHLY, 1, rscale="chinese", skip=definitions.eRecurrence_SKIP_BACKWARD)
+ results = []
+ while True:
+ result = riter.next()
+ if result.getYear() >= 2018:
+ break
+ results.append(result.getText())
+ self.assertEqual(
+ results,
+ [
+ "20140130",
+ "20140228",
+ "20140330",
+ "20140428",
+ "20140528",
+ "20140626",
+ "20140726",
+ "20140824",
+ "20140923",
+ "20141023",
+ "20141121",
+ "20141221",
+ "20150119",
+ "20150218",
+ "20150319",
+ "20150418",
+ "20150517",
+ "20150615",
+ "20150715",
+ "20150813",
+ "20150912",
+ "20151012",
+ "20151111",
+ "20151210",
+ "20160109",
+ "20160207",
+ "20160308",
+ "20160406",
+ "20160506",
+ "20160604",
+ "20160703",
+ "20160802",
+ "20160831",
+ "20160930",
+ "20161030",
+ "20161128",
+ "20161228",
+ "20170127",
+ "20170225",
+ "20170327",
+ "20170425",
+ "20170525",
+ "20170623",
+ "20170722",
+ "20170821",
+ "20170919",
+ "20171019",
+ "20171117",
+ "20171217",
+ ]
+ )
</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"> "FREQ=YEARLY;BYDAY=MO;BYWEEKNO=20",
</span><span class="cx"> "FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3",
</span><span class="cx"> "FREQ=DAILY;BYMINUTE=0,20,40;BYHOUR=9,10,11,12,13,14,15,16",
</span><ins>+
+ # RSCALE
+ "RSCALE=CHINESE;FREQ=DAILY",
+ "RSCALE=GREGORIAN;FREQ=YEARLY;COUNT=400;SKIP=YES",
+ "RSCALE=GREGORIAN;FREQ=YEARLY;COUNT=400;SKIP=BACKWARD",
+ "RSCALE=GREGORIAN;FREQ=YEARLY;COUNT=400;SKIP=FORWARD",
+ "RSCALE=CHINESE;FREQ=YEARLY;BYMONTH=5,6,6L,7",
+
</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, "Failed to parse and re-generate '%s'" % (item,))
</del><ins>+ self.assertEqual(recur.getText(), item, "Failed to parse and re-generate '%s' '%s'" % (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"> "FREQ=MONTHLY;BYDAY=+1,3MO",
</span><span class="cx"> "FREQ=MONTHLY;BYHOUR=A",
</span><span class="cx"> "FREQ=MONTHLY;BYHOUR=54",
</span><ins>+ "FREQ=MONTHLY;SKIP=YES",
+ "RSCALE=CHINESE;FREQ=MONTHLY;SKIP=NO",
</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 = "FREQ=YEARLY;BYWEEKNO=1,2"
+ for rrule in (
+ rule,
+ "RSCALE=GREGORIAN;{};SKIP=YES".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="Failed: {}".format(rrule),
+ )
+
+
+ def testMonthlyInvalidStart(self):
+
+ rule = "FREQ=MONTHLY"
+ for rrule in (
+ rule,
+ "RSCALE=GREGORIAN;{};SKIP=YES".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="Failed: {}".format(rrule),
+ )
+
+
+ def testWeeklyTwice(self):
+
+ rule = "FREQ=WEEKLY"
+ for rrule in (
+ rule,
+ "RSCALE=GREGORIAN;{};SKIP=YES".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="America/New_York")), range, items)
+ self.assertEqual(
+ items,
+ [
+ DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 1, 8, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 1, 15, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 1, 22, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 1, 29, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+
+ ],
+ msg="Failed: {}".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="America/New_York")), range, items)
+ self.assertEqual(
+ items,
+ [
+ DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 1, 8, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 1, 15, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 1, 22, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 1, 29, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 2, 5, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 2, 12, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 2, 19, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 2, 26, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ ],
+ msg="Failed: {}".format(rrule),
+ )
+
+
+ def testMonthlyInUTC(self):
+
+ rule = "FREQ=MONTHLY"
+ for rrule in (
+ rule,
+ "RSCALE=GREGORIAN;{};SKIP=YES".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="America/New_York")), range, items)
+ self.assertEqual(
+ items,
+ [
+ DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 2, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 3, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 4, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 5, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 6, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 7, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 8, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 9, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 10, 1, 12, 0, 0),
+ DateTime(2014, 11, 1, 12, 0, 0),
+ DateTime(2014, 12, 1, 12, 0, 0),
+ ],
+ msg="Failed: {}".format(rrule),
+ )
+
+
+ def testMonthlyStart31st(self):
+
+ rule = "FREQ=MONTHLY"
+ for rrule in (
+ rule,
+ "RSCALE=GREGORIAN;{};SKIP=YES".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="Failed: {}".format(rrule),
+ )
+
+
+ def testMonthlyByMonthDay31(self):
+
+ rule = "FREQ=MONTHLY;BYMONTHDAY=31"
+ for rrule in (
+ rule,
+ "RSCALE=GREGORIAN;{};SKIP=YES".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="Failed: {}".format(rrule),
+ )
+
+
+ def testMonthlyByMonthDayMinus31(self):
+
+ rule = "FREQ=MONTHLY;BYMONTHDAY=-31"
+ for rrule in (
+ rule,
+ "RSCALE=GREGORIAN;{};SKIP=YES".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="Failed: {}".format(rrule),
+ )
+
+
+ def testMonthlyByLastFridayExpand(self):
+
+ rule = "FREQ=MONTHLY;BYDAY=-1FR"
+ for rrule in (
+ rule,
+ "RSCALE=GREGORIAN;{};SKIP=YES".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="Failed: {}".format(rrule),
+ )
+
+
+ def testMonthlyByFifthFridayExpand(self):
+
+ rule = "FREQ=MONTHLY;BYDAY=5FR"
+ for rrule in (
+ rule,
+ "RSCALE=GREGORIAN;{};SKIP=YES".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="Failed: {}".format(rrule),
+ )
+
+
+ def testYearlyLeapDay(self):
+
+ rule = "FREQ=YEARLY"
+ for rrule in (
+ rule,
+ "RSCALE=GREGORIAN;{};SKIP=YES".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="Failed: {}".format(rrule),
+ )
+
+
+ def testYearlyYearDay(self):
+
+ rule = "FREQ=YEARLY;BYYEARDAY=366"
+ for rrule in (
+ rule,
+ "RSCALE=GREGORIAN;{};SKIP=YES".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="Failed: {}".format(rrule),
+ )
+
+
+ def testClearOnChange(self):
+
</ins><span class="cx"> recur = Recurrence()
</span><del>- recur.parse("FREQ=YEARLY;BYWEEKNO=1,2")
</del><ins>+ recur.parse("FREQ=DAILY")
+
</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) > 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("FREQ=MONTHLY")
- start = DateTime(2014, 1, 40, 12, 0, 0)
- end = DateTime(2015, 1, 1, 0, 0, 0)
</del><ins>+ recur.parse("RSCALE=CHINESE;FREQ=MONTHLY;SKIP=YES")
+ 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("FREQ=WEEKLY")
- 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("RSCALE=CHINESE;FREQ=MONTHLY;SKIP=FORWARD")
+ 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="America/New_York")), 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="America/New_York")),
- DateTime(2014, 1, 8, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 1, 15, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 1, 22, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 1, 29, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
-
</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="America/New_York")), range, items)
- self.assertEqual(
- items,
- [
- DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 1, 8, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 1, 15, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 1, 22, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 1, 29, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 2, 5, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 2, 12, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 2, 19, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 2, 26, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- ],
- )
</del><span class="cx">
</span><ins>+ def testMonthlyRscaleStartInLeapYearSkipBackwardDefault(self):
</ins><span class="cx">
</span><del>- def testMonthlyInUTC(self):
</del><ins>+ for rrule in (
+ "RSCALE=CHINESE;FREQ=MONTHLY;SKIP=BACKWARD",
+ "RSCALE=CHINESE;FREQ=MONTHLY"
+ ):
+ 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("FREQ=MONTHLY")
- 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("RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=YES;COUNT=5")
+ 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="America/New_York")), 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="America/New_York")),
- DateTime(2014, 2, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 3, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 4, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 5, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 6, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 7, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 8, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- DateTime(2014, 9, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
- 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("FREQ=MONTHLY")
- start = DateTime(2014, 1, 31, 12, 0, 0)
- end = DateTime(2015, 1, 1, 0, 0, 0)
</del><ins>+ recur.parse("RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD;COUNT=5")
+ 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 (
+ "RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=BACKWARD;COUNT=5",
+ "RSCALE=GREGORIAN;FREQ=YEARLY;COUNT=5",
+ ):
+ 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 = "RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=30;SKIP=YES"
</ins><span class="cx"> recur = Recurrence()
</span><del>- recur.parse("FREQ=MONTHLY;BYMONTHDAY=31")
- 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="Failed: {} {}".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 = "RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=30;SKIP=BACKWARD"
</ins><span class="cx"> recur = Recurrence()
</span><del>- recur.parse("FREQ=MONTHLY;BYMONTHDAY=-31")
- 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="Failed: {} {}".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 = "RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=30;SKIP=FORWARD"
</ins><span class="cx"> recur = Recurrence()
</span><del>- recur.parse("FREQ=MONTHLY;BYDAY=-1FR")
- 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="Failed: {} {}".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 = "RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=-30;SKIP=YES"
</ins><span class="cx"> recur = Recurrence()
</span><del>- recur.parse("FREQ=MONTHLY;BYDAY=5FR")
- 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="Failed: {} {}".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 = "RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=-30;SKIP=BACKWARD"
</ins><span class="cx"> recur = Recurrence()
</span><del>- recur.parse("FREQ=YEARLY")
- 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="Failed: {} {}".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 = "RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=-30;SKIP=FORWARD"
</ins><span class="cx"> recur = Recurrence()
</span><del>- recur.parse("FREQ=YEARLY;BYYEARDAY=366")
- 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="Failed: {} {}".format(rrule, items,),
</ins><span class="cx"> )
</span><del>-
-
- def testClearOnChange(self):
-
- recur = Recurrence()
- recur.parse("FREQ=DAILY")
-
- 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) > 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 = "recur"
</span><span class="cx">
</span><ins>+recur_rscale = "rscale"
+
</ins><span class="cx"> recur_freq = "freq"
</span><span class="cx"> recur_freq_secondly = "SECONDLY"
</span><span class="cx"> recur_freq_minutely = "MINUTELY"
</span><span class="lines">@@ -35,6 +37,11 @@
</span><span class="cx"> recur_until = "until"
</span><span class="cx"> recur_interval = "interval"
</span><span class="cx">
</span><ins>+recur_skip = "skip"
+recur_skip_yes = "yes"
+recur_skip_backward = "backward"
+recur_skip_forward = "forward"
+
</ins><span class="cx"> recur_bysecond = "bysecond"
</span><span class="cx"> recur_byminute = "byminute"
</span><span class="cx"> recur_byhour = "byhour"
</span></span></pre>
</div>
</div>
</body>
</html>