[CalendarServer-changes] [15127] PyCalendar/branches/rscale
source_changes at macosforge.org
source_changes at macosforge.org
Mon Sep 14 09:49:28 PDT 2015
Revision: 15127
http://trac.calendarserver.org//changeset/15127
Author: cdaboo at apple.com
Date: 2015-09-14 09:49:28 -0700 (Mon, 14 Sep 2015)
Log Message:
-----------
Merge from trunk.
Modified Paths:
--------------
PyCalendar/branches/rscale/README
PyCalendar/branches/rscale/setup.py
PyCalendar/branches/rscale/src/pycalendar/__init__.py
PyCalendar/branches/rscale/src/pycalendar/datetime.py
PyCalendar/branches/rscale/src/pycalendar/geovalue.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/calendar.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/definitions.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/property.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/recurrence.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/recurrenceset.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/requeststatusvalue.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_calendar.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_json.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_property.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_recurrence.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_vpoll.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/vpoll.py
PyCalendar/branches/rscale/src/pycalendar/parser.py
PyCalendar/branches/rscale/src/pycalendar/period.py
PyCalendar/branches/rscale/src/pycalendar/plaintextvalue.py
PyCalendar/branches/rscale/src/pycalendar/property.py
PyCalendar/branches/rscale/src/pycalendar/timezone.py
PyCalendar/branches/rscale/src/pycalendar/timezonedb.py
PyCalendar/branches/rscale/src/pycalendar/validator.py
PyCalendar/branches/rscale/src/pycalendar/vcard/adr.py
PyCalendar/branches/rscale/src/pycalendar/vcard/n.py
PyCalendar/branches/rscale/src/pycalendar/vcard/orgvalue.py
PyCalendar/branches/rscale/src/zonal/tzconvert.py
PyCalendar/branches/rscale/src/zonal/tzdump.py
PyCalendar/branches/rscale/src/zonal/tzverify.py
Added Paths:
-----------
PyCalendar/branches/rscale/src/pycalendar/icalendar/exceptions.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/rrule_examples.json
PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/rscale_examples.json
PyCalendar/branches/rscale/src/pycalendar/icalendar/vote.py
PyCalendar/branches/rscale/src/pycalendar/icalendar/vvoter.py
PyCalendar/branches/rscale/src/pycalendar/tests/test_geovalue.py
PyCalendar/branches/rscale/src/pycalendar/tests/test_period.py
Property Changed:
----------------
PyCalendar/branches/rscale/
Property changes on: PyCalendar/branches/rscale
___________________________________________________________________
Modified: svn:mergeinfo
- /PyCalendar/branches/duplicate-items:10464-10465
/PyCalendar/branches/json-2:11324-11912
/PyCalendar/branches/revised-api-126:10457-10463
/PyCalendar/branches/server:10466-10553
/PyCalendar/trunk:14191
+ /PyCalendar/branches/duplicate-items:10464-10465
/PyCalendar/branches/json-2:11324-11912
/PyCalendar/branches/revised-api-126:10457-10463
/PyCalendar/branches/server:10466-10553
/PyCalendar/trunk:14191,14218-15020
Modified: PyCalendar/branches/rscale/README
===================================================================
--- PyCalendar/branches/rscale/README 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/README 2015-09-14 16:49:28 UTC (rev 15127)
@@ -7,7 +7,7 @@
Copyright and License
=====================
-Copyright (c) 2005-2013 Apple Inc. All rights reserved.
+Copyright (c) 2005-2015 Apple Inc. All rights reserved.
This software is licensed under the Apache License, Version 2.0. The
Apache License is a well-established open source license, enabling
Modified: PyCalendar/branches/rscale/setup.py
===================================================================
--- PyCalendar/branches/rscale/setup.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/setup.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -27,5 +27,6 @@
'pycalendar',
'pycalendar.icalendar',
'pycalendar.vcard',
+ 'zonal',
]
)
Modified: PyCalendar/branches/rscale/src/pycalendar/__init__.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/__init__.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/__init__.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2007-2013 Cyrus Daboo. All rights reserved.
+# Copyright (c) 2007-2015 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.
@@ -44,8 +44,10 @@
import icalendar.vfreebusy
import icalendar.vjournal
import icalendar.vpoll
+import icalendar.vote
import icalendar.vtimezone
import icalendar.vtimezonedaylight
import icalendar.vtimezonestandard
import icalendar.vtodo
import icalendar.vunknown
+import icalendar.vvoter
Modified: PyCalendar/branches/rscale/src/pycalendar/datetime.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/datetime.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/datetime.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -50,38 +50,50 @@
def __init__(self, year=None, month=None, day=None, hours=None, minutes=None, seconds=None, tzid=None, utcoffset=None):
- self.mYear = 1970
- self.mMonth = 1
- self.mDay = 1
-
- self.mHours = 0
- self.mMinutes = 0
- self.mSeconds = 0
-
- self.mDateOnly = False
-
- self.mTZUTC = False
- self.mTZID = None
- self.mTZOffset = None
-
- self.mPosixTimeCached = False
- self.mPosixTime = 0
-
if (year is not None) and (month is not None) and (day is not None):
+
self.mYear = year
self.mMonth = month
self.mDay = day
+
if (hours is not None) and (minutes is not None) and (seconds is not None):
self.mHours = hours
self.mMinutes = minutes
self.mSeconds = seconds
+ self.mDateOnly = False
else:
+ self.mHours = 0
+ self.mMinutes = 0
+ self.mSeconds = 0
self.mDateOnly = True
+
if tzid:
self.mTZUTC = tzid.getUTC()
self.mTZID = tzid.getTimezoneID()
+ else:
+ self.mTZUTC = False
+ self.mTZID = None
+ self.mTZOffset = None
+ else:
+ self.mYear = 1970
+ self.mMonth = 1
+ self.mDay = 1
+ self.mHours = 0
+ self.mMinutes = 0
+ self.mSeconds = 0
+
+ self.mDateOnly = False
+
+ self.mTZUTC = False
+ self.mTZID = None
+ self.mTZOffset = None
+
+ self.mPosixTimeCached = False
+ self.mPosixTime = 0
+
+
def duplicate(self):
other = DateTime(self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds)
@@ -194,76 +206,31 @@
return 1
# If either are date only, then just do date compare
if self.mDateOnly or comp.mDateOnly:
- if self.mYear == comp.mYear:
- if self.mMonth == comp.mMonth:
- if self.mDay == comp.mDay:
- return 0
- else:
- if self.mDay < comp.mDay:
- return -1
- else:
- return 1
- else:
- if self.mMonth < comp.mMonth:
- return -1
- else:
- return 1
- else:
- if self.mYear < comp.mYear:
- return -1
- else:
- return 1
+ c = cmp(self.mYear, comp.mYear)
+ if c == 0:
+ c = cmp(self.mMonth, comp.mMonth)
+ if c == 0:
+ c = cmp(self.mDay, comp.mDay)
+ return c
# If they have the same timezone do simple compare - no posix calc
# needed
elif (Timezone.same(self.mTZUTC, self.mTZID, comp.mTZUTC, comp.mTZID)):
- if self.mYear == comp.mYear:
- if self.mMonth == comp.mMonth:
- if self.mDay == comp.mDay:
- if self.mHours == comp.mHours:
- if self.mMinutes == comp.mMinutes:
- if self.mSeconds == comp.mSeconds:
- return 0
- else:
- if self.mSeconds < comp.mSeconds:
- return -1
- else:
- return 1
- else:
- if self.mMinutes < comp.mMinutes:
- return -1
- else:
- return 1
- else:
- if self.mHours < comp.mHours:
- return -1
- else:
- return 1
- else:
- if self.mDay < comp.mDay:
- return -1
- else:
- return 1
- else:
- if self.mMonth < comp.mMonth:
- return -1
- else:
- return 1
- else:
- if self.mYear < comp.mYear:
- return -1
- else:
- return 1
+ c = cmp(self.mYear, comp.mYear)
+ if c == 0:
+ c = cmp(self.mMonth, comp.mMonth)
+ if c == 0:
+ c = cmp(self.mDay, comp.mDay)
+ if c == 0:
+ c = cmp(self.mHours, comp.mHours)
+ if c == 0:
+ c = cmp(self.mMinutes, comp.mMinutes)
+ if c == 0:
+ c = cmp(self.mSeconds, comp.mSeconds)
+ return c
+
else:
- posix1 = self.getPosixTime()
- posix2 = comp.getPosixTime()
- if posix1 == posix2:
- return 0
- else:
- if posix1 < posix2:
- return -1
- else:
- return 1
+ return cmp(self.getPosixTime(), comp.getPosixTime())
def compareDate(self, comp):
@@ -872,11 +839,11 @@
self.changed()
- def getLocaleDate(self, locale):
+ def getLocaleDate(self, dateTimeFormat):
buf = StringIO.StringIO()
- if locale == DateTime.FULLDATE:
+ if dateTimeFormat == DateTime.FULLDATE:
buf.write(locale.getDay(self.getDayOfWeek(), locale.LONG))
buf.write(", ")
buf.write(locale.getMonth(self.mMonth, locale.LONG))
@@ -884,7 +851,7 @@
buf.write(str(self.mDay))
buf.write(", ")
buf.write(str(self.mYear))
- elif locale == DateTime.ABBREVDATE:
+ elif dateTimeFormat == DateTime.ABBREVDATE:
buf.write(locale.getDay(self.getDayOfWeek(), locale.SHORT))
buf.write(", ")
buf.write(locale.getMonth(self.mMonth, locale.SHORT))
@@ -892,25 +859,25 @@
buf.write(str(self.mDay))
buf.write(", ")
buf.write(str(self.mYear))
- elif locale == DateTime.NUMERICDATE:
+ elif dateTimeFormat == DateTime.NUMERICDATE:
buf.write(str(self.mMonth))
buf.write("/")
buf.write(str(self.mDay))
buf.write("/")
buf.write(str(self.mYear))
- elif locale == DateTime.FULLDATENOYEAR:
+ elif dateTimeFormat == DateTime.FULLDATENOYEAR:
buf.write(locale.getDay(self.getDayOfWeek(), locale.LONG))
buf.write(", ")
buf.write(locale.getMonth(self.mMonth, locale.LONG))
buf.write(" ")
buf.write(str(self.mDay))
- elif locale == DateTime.ABBREVDATENOYEAR:
+ elif dateTimeFormat == DateTime.ABBREVDATENOYEAR:
buf.write(locale.getDay(self. getDayOfWeek(), locale.SHORT))
buf.write(", ")
buf.write(locale.getMonth(self.mMonth, locale.SHORT))
buf.write(" ")
buf.write(str(self.mDay))
- elif locale == DateTime.NUMERICDATENOYEAR:
+ elif dateTimeFormat == DateTime.NUMERICDATENOYEAR:
buf.write(str(self.mMonth))
buf.write("/")
buf.write(str(self.mDay))
Modified: PyCalendar/branches/rscale/src/pycalendar/geovalue.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/geovalue.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/geovalue.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -52,7 +52,13 @@
try:
self.mValue = [float(splits[0]), float(splits[1])]
except ValueError:
- raise InvalidData("GEO value incorrect", data)
+ if splits[0][-1] == '\\':
+ try:
+ self.mValue = [float(splits[0][:-1]), float(splits[1])]
+ except ValueError:
+ raise InvalidData("GEO value incorrect", data)
+ else:
+ raise InvalidData("GEO value incorrect", data)
# os - StringIO object
Modified: PyCalendar/branches/rscale/src/pycalendar/icalendar/calendar.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/calendar.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/calendar.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -351,6 +351,80 @@
del self.mMasterComponentsByTypeAndUID[component.getType()][uid]
+ def deriveComponent(self, recurrenceID):
+ """
+ Derive an overridden component for the associated RECURRENCE-ID. This assumes
+ that the R-ID is valid for the actual recurrence being used.
+
+ @param recurrenceID: the recurrence instance
+ @type recurrenceID: L{DateTime}
+
+ @return: the derived component
+ @rtype: L{ComponentRecur} or L{None}
+ """
+ master = self.masterComponent()
+ if master is None:
+ return None
+
+ # Create the derived instance
+ newcomp = master.duplicate()
+
+ # Strip out unwanted recurrence properties
+ for propname in (
+ definitions.cICalProperty_RRULE,
+ definitions.cICalProperty_RDATE,
+ definitions.cICalProperty_EXRULE,
+ definitions.cICalProperty_EXDATE,
+ definitions.cICalProperty_RECURRENCE_ID,
+ ):
+ newcomp.removeProperties(propname)
+
+ # New DTSTART is the RECURRENCE-ID we are deriving but adjusted to the
+ # original DTSTART's localtime
+ dtstart = newcomp.getStart()
+ dtend = newcomp.getEnd()
+ oldduration = dtend - dtstart
+
+ newdtstartValue = recurrenceID.duplicate()
+ if not dtstart.isDateOnly():
+ if dtstart.local():
+ newdtstartValue.adjustTimezone(dtstart.getTimezone())
+ else:
+ newdtstartValue.setDateOnly(True)
+
+ newcomp.removeProperties(definitions.cICalProperty_DTSTART)
+ newcomp.removeProperties(definitions.cICalProperty_DTEND)
+ prop = Property(definitions.cICalProperty_DTSTART, newdtstartValue)
+ newcomp.addProperty(prop)
+ if not newcomp.useDuration():
+ prop = Property(definitions.cICalProperty_DTEND, newdtstartValue + oldduration)
+ newcomp.addProperty(prop)
+
+ newcomp.addProperty(Property("RECURRENCE-ID", newdtstartValue))
+
+ # After creating/changing a component we need to do this to keep PyCalendar happy
+ newcomp.finalise()
+
+ return newcomp
+
+
+ def masterComponent(self):
+ """
+ Return the first sub-component of a recurring type that represents the master
+ instance.
+
+ @return: the master component
+ @rtype: L{ComponentRecur} or L{None}
+ """
+ for component in self.getComponents():
+ if isinstance(component, ComponentRecur):
+ rid = component.getRecurrenceID()
+ if rid is None:
+ return component
+ else:
+ return None
+
+
def getText(self, includeTimezones=None, format=None):
if format is None or format == self.sFormatText:
@@ -382,10 +456,10 @@
return root
- def getTextJSON(self, includeTimezones=None):
+ def getTextJSON(self, includeTimezones=None, sort_keys=False):
jobject = []
self.writeJSON(jobject, includeTimezones)
- return json.dumps(jobject[0], indent=2, separators=(',', ':'))
+ return json.dumps(jobject[0], indent=2, separators=(',', ':'), sort_keys=sort_keys)
def writeJSON(self, jobject, includeTimezones=None):
Modified: PyCalendar/branches/rscale/src/pycalendar/icalendar/definitions.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/definitions.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/definitions.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2007-2013 Cyrus Daboo. All rights reserved.
+# Copyright (c) 2007-2015 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.
@@ -346,24 +346,27 @@
cICalProperty_ACTION_NONE = "NONE"
-# Extensions: draft-york-vpoll-00.txt
+# Extensions: draft-york-vpoll-03.txt
-# Section 4.1
-cICalParamater_PUBLIC_COMMENT = "PUBLIC-COMMENT"
-cICalParamater_RESPONSE = "RESPONSE"
+# Section 4.3
+cICalParamater_REQUIRED = "REQUIRED"
cICalParamater_STAY_INFORMED = "STAY-INFORMED"
-# Section 4.2
+# Section 4.4
cICalProperty_ACCEPT_RESPONSE = "ACCEPT-RESPONSE"
+cICalProperty_POLL_COMPLETION = "POLL-COMPLETION"
cICalProperty_POLL_ITEM_ID = "POLL-ITEM-ID"
-cICalProperty_POLL_WINNER = "POLL-WINNER"
cICalProperty_POLL_MODE = "POLL-MODE"
cICalProperty_POLL_MODE_BASIC = "BASIC"
cICalProperty_POLL_PROPERTIES = "POLL-PROPERTIES"
+cICalProperty_POLL_WINNER = "POLL-WINNER"
+cICalProperty_RESPONSE = "RESPONSE"
cICalProperty_VOTER = "VOTER"
-# Section 4.3
+# Section 4.5
cICalComponent_VPOLL = "VPOLL"
+cICalComponent_VVOTER = "VVOTER"
+cICalComponent_VOTE = "VOTE"
# Mulberry extensions
Copied: PyCalendar/branches/rscale/src/pycalendar/icalendar/exceptions.py (from rev 15020, PyCalendar/trunk/src/pycalendar/icalendar/exceptions.py)
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/exceptions.py (rev 0)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/exceptions.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -0,0 +1,20 @@
+##
+# Copyright (c) 2015 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.exceptions import ErrorBase
+
+class TooManyInstancesError(ErrorBase):
+ pass
Modified: PyCalendar/branches/rscale/src/pycalendar/icalendar/property.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/property.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/property.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2007-2013 Cyrus Daboo. All rights reserved.
+# Copyright (c) 2007-2015 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.
@@ -105,10 +105,12 @@
# Extensions: draft-york-vpoll-00.txt
# Section 4.2
definitions.cICalProperty_ACCEPT_RESPONSE : Value.VALUETYPE_TEXT,
- definitions.cICalProperty_POLL_ITEM_ID : Value.VALUETYPE_TEXT,
- definitions.cICalProperty_POLL_WINNER : Value.VALUETYPE_TEXT,
+ definitions.cICalProperty_POLL_COMPLETION : Value.VALUETYPE_TEXT,
+ definitions.cICalProperty_POLL_ITEM_ID : Value.VALUETYPE_INTEGER,
definitions.cICalProperty_POLL_MODE : Value.VALUETYPE_TEXT,
definitions.cICalProperty_POLL_PROPERTIES : Value.VALUETYPE_TEXT,
+ definitions.cICalProperty_POLL_WINNER : Value.VALUETYPE_INTEGER,
+ definitions.cICalProperty_RESPONSE : Value.VALUETYPE_INTEGER,
definitions.cICalProperty_VOTER : Value.VALUETYPE_CALADDRESS,
# Apple Extensions
@@ -193,6 +195,10 @@
elif isinstance(value, str):
self._init_attr_value_text(value, valuetype if valuetype else self.sDefaultValueTypeMap.get(self.mName.upper(), Value.VALUETYPE_UNKNOWN))
+ elif isinstance(value, unicode):
+ value = value.encode("utf-8")
+ self._init_attr_value_text(value, valuetype if valuetype else self.sDefaultValueTypeMap.get(self.mName.upper(), Value.VALUETYPE_UNKNOWN))
+
elif isinstance(value, DateTime):
self._init_attr_value_datetime(value)
Modified: PyCalendar/branches/rscale/src/pycalendar/icalendar/recurrence.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/recurrence.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/recurrence.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -17,6 +17,7 @@
from pycalendar import xmlutils
from pycalendar.datetime import DateTime
from pycalendar.icalendar import definitions, xmldefinitions
+from pycalendar.icalendar.exceptions import TooManyInstancesError
from pycalendar.period import Period
from pycalendar.valueutils import ValueMixin
import cStringIO as StringIO
@@ -385,7 +386,10 @@
def setByMonth(self, by):
- self._setAndclearIfChanged("mByMonth", by[:])
+ # Convert int values in the list to (int, False) L{tuple}'s to match the new API
+ # that includes the leap month indicator
+ items = [(item, False) if isinstance(item, int) else item for item in by]
+ self._setAndclearIfChanged("mByMonth", items)
def getByMonthDay(self):
@@ -1013,7 +1017,7 @@
return result
- def expand(self, start, range, items, float_offset=0):
+ def expand(self, start, range, items, float_offset=0, maxInstances=None):
# Have to normalize this to be very sure we are starting with a valid date, as otherwise
# we could end up looping forever when doing recurrence.
@@ -1039,9 +1043,9 @@
# Simple expansion is one where there is no BYXXX rule part
if not self.hasBy():
- self.mFullyCached = self.simpleExpand(start, range, self.mRecurrences, float_offset)
+ self.mFullyCached = self.simpleExpand(start, range, self.mRecurrences, float_offset, maxInstances=maxInstances)
else:
- self.mFullyCached = self.complexExpand(start, range, self.mRecurrences, float_offset)
+ self.mFullyCached = self.complexExpand(start, range, self.mRecurrences, float_offset, maxInstances=maxInstances)
# Set cache values
self.mCached = True
@@ -1058,7 +1062,7 @@
return limited
- def simpleExpand(self, start, range, results, float_offset):
+ def simpleExpand(self, start, range, results, float_offset, maxInstances=None):
if self.mUseUntil:
float_until = self.mUntil.duplicate()
@@ -1081,6 +1085,8 @@
# Add current one to list
results.append(start_iter)
+ if maxInstances and len(results) > maxInstances:
+ raise TooManyInstancesError("Too many instances")
# Check limits
if self.mUseCount:
@@ -1089,7 +1095,7 @@
return True
- def complexExpand(self, start, range, results, float_offset):
+ def complexExpand(self, start, range, results, float_offset, maxInstances=None):
if self.mUseUntil:
float_until = self.mUntil.duplicate()
@@ -1129,7 +1135,7 @@
elif self.mFreq == definitions.eRecurrence_YEARLY:
self.generateYearlySet(start_iter, set_items)
- # Ignore if it is invalid
+ # Remove invalid items before BYSETPOS
def _invalidMap(dt):
dt.invalidSkip(self.effectiveSkip())
return dt
@@ -1171,6 +1177,8 @@
# Add current one to list
results.append(iter)
+ if maxInstances and len(results) > maxInstances:
+ raise TooManyInstancesError("Too many instances")
# Check limits
if self.mUseCount:
Modified: PyCalendar/branches/rscale/src/pycalendar/icalendar/recurrenceset.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/recurrenceset.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/recurrenceset.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -14,6 +14,7 @@
# limitations under the License.
##
+from pycalendar.icalendar.exceptions import TooManyInstancesError
from pycalendar.utils import set_difference
class RecurrenceSet(object):
@@ -182,7 +183,7 @@
return self.mExperiods
- def expand(self, start, range, items, float_offset=0):
+ def expand(self, start, range, items, float_offset=0, maxInstances=None):
# Need to return whether the limit was applied or not
limited = False
@@ -197,18 +198,22 @@
# RRULES
for iter in self.mRrules:
- if iter.expand(start, range, include, float_offset=float_offset):
+ if iter.expand(start, range, include, float_offset=float_offset, maxInstances=maxInstances):
limited = True
# RDATES
for iter in self.mRdates:
if range.isDateWithinPeriod(iter):
include.append(iter)
+ if maxInstances and len(include) > maxInstances:
+ raise TooManyInstancesError("Too many instances")
else:
limited = True
for iter in self.mRperiods:
if range.isPeriodOverlap(iter):
include.append(iter.getStart())
+ if maxInstances and len(include) > maxInstances:
+ raise TooManyInstancesError("Too many instances")
else:
limited = True
Modified: PyCalendar/branches/rscale/src/pycalendar/icalendar/requeststatusvalue.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/requeststatusvalue.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/requeststatusvalue.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -97,7 +97,7 @@
def parseJSONValue(self, jobject):
- self.mValue = jobject
+ self.mValue = map(lambda x: x.encode("utf-8"), jobject)
def writeJSONValue(self, jobject):
Copied: PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/rrule_examples.json (from rev 15020, PyCalendar/trunk/src/pycalendar/icalendar/tests/rrule_examples.json)
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/rrule_examples.json (rev 0)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/rrule_examples.json 2015-09-14 16:49:28 UTC (rev 15127)
@@ -0,0 +1,231 @@
+[
+ {
+ "rule": "FREQ=YEARLY;BYWEEKNO=1,2",
+ "start": "20130101T000000",
+ "end": "20170101T000000",
+ "results": [
+ "20130101T000000",
+ "20130108T000000",
+ "20140101T000000",
+ "20140108T000000",
+ "20150101T000000",
+ "20150108T000000",
+ "20160108T000000",
+ "20160115T000000"
+ ]
+ },
+ {
+ "rule": "FREQ=MONTHLY",
+ "start": "20140140T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140209T120000",
+ "20140309T120000",
+ "20140409T120000",
+ "20140509T120000",
+ "20140609T120000",
+ "20140709T120000",
+ "20140809T120000",
+ "20140909T120000",
+ "20141009T120000",
+ "20141109T120000",
+ "20141209T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=MONTHLY",
+ "start": "20140131T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140131T120000",
+ "20140331T120000",
+ "20140531T120000",
+ "20140731T120000",
+ "20140831T120000",
+ "20141031T120000",
+ "20141231T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=MONTHLY;BYMONTHDAY=31",
+ "start": "20140131T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140131T120000",
+ "20140331T120000",
+ "20140531T120000",
+ "20140731T120000",
+ "20140831T120000",
+ "20141031T120000",
+ "20141231T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=MONTHLY;BYMONTHDAY=-31",
+ "start": "20140101T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140101T120000",
+ "20140301T120000",
+ "20140501T120000",
+ "20140701T120000",
+ "20140801T120000",
+ "20141001T120000",
+ "20141201T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=MONTHLY;BYDAY=-1FR",
+ "start": "20140131T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140131T120000",
+ "20140228T120000",
+ "20140328T120000",
+ "20140425T120000",
+ "20140530T120000",
+ "20140627T120000",
+ "20140725T120000",
+ "20140829T120000",
+ "20140926T120000",
+ "20141031T120000",
+ "20141128T120000",
+ "20141226T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=MONTHLY;BYDAY=5FR",
+ "start": "20140131T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140131T120000",
+ "20140530T120000",
+ "20140829T120000",
+ "20141031T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1",
+ "start": "20140131T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140131T120000",
+ "20140228T120000",
+ "20140331T120000",
+ "20140430T120000",
+ "20140530T120000",
+ "20140630T120000",
+ "20140731T120000",
+ "20140829T120000",
+ "20140930T120000",
+ "20141031T120000",
+ "20141128T120000",
+ "20141231T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1",
+ "start": "20140127T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140131T120000",
+ "20140228T120000",
+ "20140331T120000",
+ "20140430T120000",
+ "20140530T120000",
+ "20140630T120000",
+ "20140731T120000",
+ "20140829T120000",
+ "20140930T120000",
+ "20141031T120000",
+ "20141128T120000",
+ "20141231T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=MONTHLY;INTERVAL=2;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1",
+ "start": "20140127T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140131T120000",
+ "20140331T120000",
+ "20140530T120000",
+ "20140731T120000",
+ "20140930T120000",
+ "20141128T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=MONTHLY;BYDAY=-1MO,-1TU,-1WE,-1TH,-1FR;BYSETPOS=-1",
+ "start": "20140130T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140131T120000",
+ "20140228T120000",
+ "20140331T120000",
+ "20140430T120000",
+ "20140530T120000",
+ "20140630T120000",
+ "20140731T120000",
+ "20140829T120000",
+ "20140930T120000",
+ "20141031T120000",
+ "20141128T120000",
+ "20141231T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=YEARLY",
+ "start": "20120229T120000",
+ "end": "20200101T000000",
+ "results": [
+ "20120229T120000",
+ "20160229T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=YEARLY;BYYEARDAY=366",
+ "start": "20121231T120000",
+ "end": "20200101T000000",
+ "results": [
+ "20121231T120000",
+ "20161231T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=YEARLY;BYDAY=-1FR;BYMONTH=10",
+ "start": "20101029T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20101029T120000",
+ "20111028T120000",
+ "20121026T120000",
+ "20131025T120000",
+ "20141031T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=YEARLY;BYDAY=1FR;BYMONTH=4",
+ "start": "20100402T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20100402T120000",
+ "20110401T120000",
+ "20120406T120000",
+ "20130405T120000",
+ "20140404T120000"
+ ]
+ },
+ {
+ "rule": "FREQ=YEARLY;BYDAY=FR;BYMONTHDAY=21,22,23,24,25,26,27;BYMONTH=10",
+ "start": "20101022T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20101022T120000",
+ "20111021T120000",
+ "20121026T120000",
+ "20131025T120000",
+ "20141024T120000"
+ ]
+ }
+]
Added: PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/rscale_examples.json
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/rscale_examples.json (rev 0)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/rscale_examples.json 2015-09-14 16:49:28 UTC (rev 15127)
@@ -0,0 +1,304 @@
+[
+ {
+ "title": "MonthlyRscaleStartInLeapYearSkipYes - start: {C}46501230",
+ "rule": ["RSCALE=CHINESE;FREQ=MONTHLY;SKIP=YES"],
+ "start": "20140130",
+ "end": "20180101",
+ "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"
+ ]
+ },
+ {
+ "title": "MonthlyRscaleStartInLeapYearSkipForward - start: {C}46501230",
+ "rule": ["RSCALE=CHINESE;FREQ=MONTHLY;SKIP=FORWARD"],
+ "start": "20140130",
+ "end": "20180101",
+ "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"
+ ]
+ },
+ {
+ "title": "MonthlyRscaleStartInLeapYearSkipBackwardDefault - start: {C}46501230",
+ "rule": [
+ "RSCALE=CHINESE;FREQ=MONTHLY;SKIP=BACKWARD",
+ "RSCALE=CHINESE;FREQ=MONTHLY"
+ ],
+ "start": "20140130",
+ "end": "20180101",
+ "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"
+ ]
+ },
+ {
+ "title": "YearlyLeapDaySkipYes",
+ "rule": ["RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=YES;COUNT=5"],
+ "start": "20160229",
+ "end": "21000101",
+ "results": [
+ "20160229",
+ "20200229",
+ "20240229",
+ "20280229",
+ "20320229"
+ ]
+ },
+ {
+ "title": "YearlyLeapDaySkipForward",
+ "rule": ["RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD;COUNT=5"],
+ "start": "20160229",
+ "end": "21000101",
+ "results": [
+ "20160229",
+ "20170301",
+ "20180301",
+ "20190301",
+ "20200229"
+ ]
+ },
+ {
+ "title": "YearlyLeapDaySkipBackwardDefault",
+ "rule": [
+ "RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=BACKWARD;COUNT=5",
+ "RSCALE=GREGORIAN;FREQ=YEARLY;COUNT=5"
+ ],
+ "start": "20160229",
+ "end": "21000101",
+ "results": [
+ "20160229",
+ "20170228",
+ "20180228",
+ "20190228",
+ "20200229"
+ ]
+ },
+ {
+ "title": "ChineseMonthlyByMonthDay30SkipYes",
+ "rule": ["RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=30;SKIP=YES"],
+ "start": "20140130T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140130T120000",
+ "20140330T120000",
+ "20140528T120000",
+ "20140726T120000",
+ "20140923T120000",
+ "20141023T120000",
+ "20141221T120000"
+ ]
+ },
+ {
+ "title": "ChineseMonthlyByMonthDay30SkipBackward",
+ "rule": ["RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=30;SKIP=BACKWARD"],
+ "start": "20140130T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140130T120000",
+ "20140228T120000",
+ "20140330T120000",
+ "20140428T120000",
+ "20140528T120000",
+ "20140626T120000",
+ "20140726T120000",
+ "20140824T120000",
+ "20140923T120000",
+ "20141023T120000",
+ "20141121T120000",
+ "20141221T120000"
+ ]
+ },
+ {
+ "title": "ChineseMonthlyByMonthDay30SkipForward",
+ "rule": ["RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=30;SKIP=FORWARD"],
+ "start": "20140130T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140130T120000",
+ "20140301T120000",
+ "20140330T120000",
+ "20140429T120000",
+ "20140528T120000",
+ "20140627T120000",
+ "20140726T120000",
+ "20140825T120000",
+ "20140923T120000",
+ "20141023T120000",
+ "20141122T120000",
+ "20141221T120000"
+ ]
+ },
+ {
+ "title": "ChineseMonthlyByMonthDayMinus30SkipYes",
+ "rule": ["RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=-30;SKIP=YES"],
+ "start": "20140130T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140301T120000",
+ "20140429T120000",
+ "20140627T120000",
+ "20140825T120000",
+ "20140924T120000",
+ "20141122T120000"
+ ]
+ },
+ {
+ "title": "ChineseMonthlyByMonthDayMinus30SkipBackward",
+ "rule": ["RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=-30;SKIP=BACKWARD"],
+ "start": "20140130T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140130T120000",
+ "20140301T120000",
+ "20140330T120000",
+ "20140429T120000",
+ "20140528T120000",
+ "20140627T120000",
+ "20140726T120000",
+ "20140825T120000",
+ "20140924T120000",
+ "20141023T120000",
+ "20141122T120000",
+ "20141221T120000"
+ ]
+ },
+ {
+ "title": "ChineseMonthlyByMonthDayMinus30SkipForward",
+ "rule": ["RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=-30;SKIP=FORWARD"],
+ "start": "20140130T120000",
+ "end": "20150101T000000",
+ "results": [
+ "20140131T120000",
+ "20140301T120000",
+ "20140331T120000",
+ "20140429T120000",
+ "20140529T120000",
+ "20140627T120000",
+ "20140727T120000",
+ "20140825T120000",
+ "20140924T120000",
+ "20141024T120000",
+ "20141122T120000",
+ "20141222T120000"
+ ]
+ }
+]
Modified: PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_calendar.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_calendar.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_calendar.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -20,6 +20,7 @@
from pycalendar.icalendar.property import Property
from pycalendar.parser import ParserContext
from pycalendar.period import Period
+from pycalendar.timezone import Timezone
import cStringIO as StringIO
import difflib
import unittest
@@ -852,3 +853,390 @@
)
instances = tuple([instance.getInstanceStart() for instance in instances])
self.assertEqual(instances, result, "Failed in %s: got %s, expected %s" % (title, instances, result))
+
+
+ def testMasterComponent(self):
+
+ data = (
+ (
+ "1.1 Non-recurring no VTIMEZONE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20110601
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20110601
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+""".replace("\n", "\r\n"),
+ ),
+ (
+ "1.2 Non-recurring with VTIMEZONE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTIMEZONE
+TZID:Etc/GMT+1
+X-LIC-LOCATION:Etc/GMT+1
+BEGIN:STANDARD
+DTSTART:18000101T000000
+RDATE:18000101T000000
+TZNAME:GMT+1
+TZOFFSETFROM:-0100
+TZOFFSETTO:-0100
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;TZID=Etc/GMT+1:20110601T000000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;TZID=Etc/GMT+1:20110601T000000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+""".replace("\n", "\r\n"),
+ ),
+ (
+ "2.1 Recurring no VTIMEZONE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20110601
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=DAILY
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20110602
+DTSTART;VALUE=DATE:20110602
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20110601
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=DAILY
+SUMMARY:New Year's Day
+END:VEVENT
+""".replace("\n", "\r\n"),
+ ),
+ (
+ "2.2 Recurring with VTIMEZONE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTIMEZONE
+TZID:Etc/GMT+1
+X-LIC-LOCATION:Etc/GMT+1
+BEGIN:STANDARD
+DTSTART:18000101T000000
+RDATE:18000101T000000
+TZNAME:GMT+1
+TZOFFSETFROM:-0100
+TZOFFSETTO:-0100
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;TZID=Etc/GMT+1:20110601T000000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;TZID=Etc/GMT+1:20110602T000000
+DTSTART;TZID=Etc/GMT+1:20110602T000000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ """BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;TZID=Etc/GMT+1:20110601T000000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=DAILY
+SUMMARY:New Year's Day
+END:VEVENT
+""".replace("\n", "\r\n"),
+ ),
+ (
+ "3.1 Recurring no master, no VTIMEZONE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20110602
+DTSTART;VALUE=DATE:20110602
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ "",
+ ),
+ (
+ "3.2 Recurring no master, with VTIMEZONE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTIMEZONE
+TZID:Etc/GMT+1
+X-LIC-LOCATION:Etc/GMT+1
+BEGIN:STANDARD
+DTSTART:18000101T000000
+RDATE:18000101T000000
+TZNAME:GMT+1
+TZOFFSETFROM:-0100
+TZOFFSETTO:-0100
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;TZID=Etc/GMT+1:20110602T000000
+DTSTART;TZID=Etc/GMT+1:20110602T000000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ "",
+ ),
+ )
+
+ for title, caldata, result in data:
+ calendar = Calendar.parseText(caldata)
+ master = calendar.masterComponent()
+ if master is None:
+ master = ""
+ self.assertEqual(str(master), result, "Failed in %s: got %s, expected %s" % (title, master, result))
+
+
+ def testDeriveComponent(self):
+
+ data = (
+ (
+ "1.1 Recurring no VTIMEZONE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20110601
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=DAILY
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20110602
+DTSTART;VALUE=DATE:20110602
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ DateTime(2011, 6, 3),
+ """BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20110603
+DTSTART;VALUE=DATE:20110603
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+""".replace("\n", "\r\n"),
+ ),
+ (
+ "2.2 Recurring with VTIMEZONE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTIMEZONE
+TZID:Etc/GMT+1
+X-LIC-LOCATION:Etc/GMT+1
+BEGIN:STANDARD
+DTSTART:18000101T000000
+RDATE:18000101T000000
+TZNAME:GMT+1
+TZOFFSETFROM:-0100
+TZOFFSETTO:-0100
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;TZID=Etc/GMT+1:20110601T000000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;TZID=Etc/GMT+1:20110602T000000
+DTSTART;TZID=Etc/GMT+1:20110602T000000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ DateTime(2011, 6, 3, 1, 0, 0, Timezone(utc=True)),
+ """BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;TZID=Etc/GMT+1:20110603T000000
+DTSTART;TZID=Etc/GMT+1:20110603T000000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+""".replace("\n", "\r\n"),
+ ),
+ (
+ "2.3 Recurring with VTIMEZONE, DTEND",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTIMEZONE
+TZID:Etc/GMT+1
+X-LIC-LOCATION:Etc/GMT+1
+BEGIN:STANDARD
+DTSTART:18000101T000000
+RDATE:18000101T000000
+TZNAME:GMT+1
+TZOFFSETFROM:-0100
+TZOFFSETTO:-0100
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;TZID=Etc/GMT+1:20110601T000000
+DTEND;TZID=Etc/GMT+1:20110601T020000
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;TZID=Etc/GMT+1:20110602T000000
+DTSTART;TZID=Etc/GMT+1:20110602T000000
+DTEND;TZID=Etc/GMT+1:20110602T020000
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ DateTime(2011, 6, 3, 1, 0, 0, Timezone(utc=True)),
+ """BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;TZID=Etc/GMT+1:20110603T000000
+DTSTART;TZID=Etc/GMT+1:20110603T000000
+DTEND;TZID=Etc/GMT+1:20110603T020000
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+""".replace("\n", "\r\n"),
+ ),
+ (
+ "2.1 Recurring no master, no VTIMEZONE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20110602
+DTSTART;VALUE=DATE:20110602
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ DateTime(2011, 6, 3),
+ "",
+ ),
+ (
+ "2.2 Recurring no master, with VTIMEZONE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VTIMEZONE
+TZID:Etc/GMT+1
+X-LIC-LOCATION:Etc/GMT+1
+BEGIN:STANDARD
+DTSTART:18000101T000000
+RDATE:18000101T000000
+TZNAME:GMT+1
+TZOFFSETFROM:-0100
+TZOFFSETTO:-0100
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;TZID=Etc/GMT+1:20110602T000000
+DTSTART;TZID=Etc/GMT+1:20110602T000000
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+ DateTime(2011, 6, 3, 1, 0, 0, Timezone(utc=True)),
+ "",
+ ),
+ )
+
+ for title, caldata, rid, result in data:
+ calendar = Calendar.parseText(caldata)
+ master = calendar.deriveComponent(rid)
+ if master is None:
+ master = ""
+ self.assertEqual(str(master), result, "Failed in %s: got %s, expected %s" % (title, master, result))
Modified: PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_json.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_json.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_json.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
##
# Copyright (c) 2007-2013 Cyrus Daboo. All rights reserved.
#
@@ -14,10 +15,12 @@
# limitations under the License.
##
+
from pycalendar.icalendar.calendar import Calendar
from pycalendar.icalendar.property import Property
import difflib
import unittest
+import json
class TestJSON(unittest.TestCase):
@@ -387,14 +390,380 @@
),
)
+ i18ndata = (
+ (
+ """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20080205T191224Z
+DTSTART;VALUE=DATE:20081006
+SUMMARY:Planning meeting å
+UID:4088E990AD89CB3DBB484909
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+ """[
+ "vcalendar",
+ [
+ [
+ "version",
+ {},
+ "text",
+ "2.0"
+ ],
+ [
+ "calscale",
+ {},
+ "text",
+ "GREGORIAN"
+ ],
+ [
+ "prodid",
+ {},
+ "text",
+ "-//Example Inc.//Example Calendar//EN"
+ ]
+ ],
+ [
+ [
+ "vevent",
+ [
+ [
+ "uid",
+ {},
+ "text",
+ "4088E990AD89CB3DBB484909"
+ ],
+ [
+ "dtstart",
+ {},
+ "date",
+ "2008-10-06"
+ ],
+ [
+ "dtstamp",
+ {},
+ "date-time",
+ "2008-02-05T19:12:24Z"
+ ],
+ [
+ "summary",
+ {},
+ "text",
+ "Planning meeting å"
+ ]
+ ],
+ []
+ ]
+ ]
+]""",
+ ),
+ (
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Example Corp.//Example Client//EN
+BEGIN:VTIMEZONE
+LAST-MODIFIED:20040110T032845Z
+TZID:US/Eastern
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20060206T001121Z
+DTSTART;TZID=US/Eastern:20060102T120000
+DURATION:PT1H
+GEO:-123.45;67.89
+RRULE:FREQ=DAILY;COUNT=5
+RDATE;TZID=US/Eastern;VALUE=PERIOD:20060102T150000/PT2H
+SUMMARY:Event #2 å
+DESCRIPTION:We are having a meeting all this week at 12 pm fo
+ r one hour\\, with an additional meeting on the first day 2 h
+ ours long.\\nPlease bring your own lunch for the 12 pm meetin
+ gs.
+UID:00959BC664CA650E933C892C at example.com
+END:VEVENT
+BEGIN:VEVENT
+DTSTAMP:20060206T001121Z
+DTSTART;TZID=US/Eastern:20060104T140000
+DURATION:PT1H
+RECURRENCE-ID;TZID=US/Eastern:20060104T120000
+SUMMARY:Event #2 bis å
+UID:00959BC664CA650E933C892C at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+ """[
+ "vcalendar",
+ [
+ [
+ "version",
+ {},
+ "text",
+ "2.0"
+ ],
+ [
+ "prodid",
+ {},
+ "text",
+ "-//Example Corp.//Example Client//EN"
+ ]
+ ],
+ [
+ [
+ "vtimezone",
+ [
+ [
+ "tzid",
+ {},
+ "text",
+ "US/Eastern"
+ ],
+ [
+ "last-modified",
+ {},
+ "date-time",
+ "2004-01-10T03:28:45Z"
+ ]
+ ],
+ [
+ [
+ "daylight",
+ [
+ [
+ "dtstart",
+ {},
+ "date-time",
+ "2000-04-04T02:00:00"
+ ],
+ [
+ "rrule",
+ {},
+ "recur",
+ {
+ "bymonth":[
+ 4
+ ],
+ "freq":"YEARLY",
+ "byday":[
+ "1SU"
+ ]
+ }
+ ],
+ [
+ "tzname",
+ {},
+ "text",
+ "EDT"
+ ],
+ [
+ "tzoffsetfrom",
+ {},
+ "utc-offset",
+ "-05:00"
+ ],
+ [
+ "tzoffsetto",
+ {},
+ "utc-offset",
+ "-04:00"
+ ]
+ ],
+ []
+ ],
+ [
+ "standard",
+ [
+ [
+ "dtstart",
+ {},
+ "date-time",
+ "2000-10-26T02:00:00"
+ ],
+ [
+ "rrule",
+ {},
+ "recur",
+ {
+ "bymonth":[
+ 10
+ ],
+ "freq":"YEARLY",
+ "byday":[
+ "-1SU"
+ ]
+ }
+ ],
+ [
+ "tzname",
+ {},
+ "text",
+ "EST"
+ ],
+ [
+ "tzoffsetfrom",
+ {},
+ "utc-offset",
+ "-04:00"
+ ],
+ [
+ "tzoffsetto",
+ {},
+ "utc-offset",
+ "-05:00"
+ ]
+ ],
+ []
+ ]
+ ]
+ ],
+ [
+ "vevent",
+ [
+ [
+ "uid",
+ {},
+ "text",
+ "00959BC664CA650E933C892C at example.com"
+ ],
+ [
+ "dtstart",
+ {
+ "tzid":"US/Eastern"
+ },
+ "date-time",
+ "2006-01-02T12:00:00"
+ ],
+ [
+ "duration",
+ {},
+ "duration",
+ "PT1H"
+ ],
+ [
+ "description",
+ {},
+ "text",
+ "We are having a meeting all this week at 12 pm for one hour, with an additional meeting on the first day 2 hours long.\\nPlease bring your own lunch for the 12 pm meetings."
+ ],
+ [
+ "dtstamp",
+ {},
+ "date-time",
+ "2006-02-06T00:11:21Z"
+ ],
+ [
+ "geo",
+ {},
+ "float",
+ [
+ -123.45,
+ 67.89
+ ]
+ ],
+ [
+ "rdate",
+ {
+ "tzid":"US/Eastern"
+ },
+ "period",
+ [
+ "2006-01-02T15:00:00",
+ "PT2H"
+ ]
+ ],
+ [
+ "rrule",
+ {},
+ "recur",
+ {
+ "count":5,
+ "freq":"DAILY"
+ }
+ ],
+ [
+ "summary",
+ {},
+ "text",
+ "Event #2 å"
+ ]
+ ],
+ []
+ ],
+ [
+ "vevent",
+ [
+ [
+ "uid",
+ {},
+ "text",
+ "00959BC664CA650E933C892C at example.com"
+ ],
+ [
+ "recurrence-id",
+ {
+ "tzid":"US/Eastern"
+ },
+ "date-time",
+ "2006-01-04T12:00:00"
+ ],
+ [
+ "dtstart",
+ {
+ "tzid":"US/Eastern"
+ },
+ "date-time",
+ "2006-01-04T14:00:00"
+ ],
+ [
+ "duration",
+ {},
+ "duration",
+ "PT1H"
+ ],
+ [
+ "dtstamp",
+ {},
+ "date-time",
+ "2006-02-06T00:11:21Z"
+ ],
+ [
+ "summary",
+ {},
+ "text",
+ "Event #2 bis å"
+ ]
+ ],
+ []
+ ]
+ ]
+]""",
+ ),
+ )
+
def testGenerateJSON(self):
def _doRoundtrip(caldata, resultdata):
- test1 = resultdata
+ test1 = json.dumps(json.loads(resultdata), indent=2, separators=(',', ':'), sort_keys=True)
cal = Calendar.parseText(caldata)
- test2 = cal.getTextJSON()
+ test2 = cal.getTextJSON(sort_keys=True)
self.assertEqual(
test1,
test2,
@@ -424,6 +793,25 @@
_doRoundtrip(item1, item2)
+ def testParseJSONi18n(self):
+
+ def _doRoundtrip(caldata, jcaldata):
+ cal1 = Calendar.parseText(caldata)
+ test1 = cal1.getText()
+
+ cal2 = Calendar.parseJSONData(jcaldata)
+ test2 = cal2.getText()
+
+ self.assertEqual(
+ test1,
+ test2,
+ "\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines()))
+ )
+
+ for item1, item2 in self.i18ndata:
+ _doRoundtrip(item1, item2)
+
+
def testjCalExample1(self):
jcaldata = """["vcalendar",
Modified: PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_property.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_property.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_property.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -115,6 +115,7 @@
test_data = (
("ATTENDEE", "mailto:attendee at example.com", "ATTENDEE:mailto:attendee at example.com\r\n"),
+ ("ATTENDEE", u"mailto:attendee at example.com", "ATTENDEE:mailto:attendee at example.com\r\n"),
("attendee", "mailto:attendee at example.com", "attendee:mailto:attendee at example.com\r\n"),
("ORGANIZER", "mailto:organizer at example.com", "ORGANIZER:mailto:organizer at example.com\r\n"),
("ORGANizer", "mailto:organizer at example.com", "ORGANizer:mailto:organizer at example.com\r\n"),
Modified: PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_recurrence.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_recurrence.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_recurrence.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -19,6 +19,8 @@
from pycalendar.icalendar.recurrence import Recurrence
import unittest
from pycalendar.timezone import Timezone
+import os
+import json
class TestRecurrence(unittest.TestCase):
@@ -58,7 +60,6 @@
"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",
-
)
@@ -67,7 +68,7 @@
for item in TestRecurrence.items:
recur = Recurrence()
recur.parse(item)
- self.assertEqual(recur.getText(), item, "Failed to parse and re-generate '%s' '%s'" % (item, recur.getText()))
+ self.assertEqual(recur.getText(), item, "Failed to parse and re-generate '%s' '%s'" % (item, recur.getText(),))
def testParseInvalid(self):
@@ -126,778 +127,149 @@
self.assertNotEqual(hashes[i - 1], hashes[i])
- def testByWeekNoExpand(self):
-
- 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):
-
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)
+ 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))
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)
-
-
-
-class TestRecurrenceRscale(unittest.TestCase):
-
- def testMonthlyRscaleStartInLeapYearSkipYes(self):
-
- recur = Recurrence()
- recur.parse("RSCALE=CHINESE;FREQ=MONTHLY;SKIP=YES")
- start = DateTime(2014, 1, 30) # {C}46501230
- end = DateTime(2018, 1, 1)
- items = []
range = Period(start, end)
- recur.expand(start, range, items)
+ recur.expand(DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")), range, items)
self.assertEqual(
items,
[
- 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),
+ 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")),
+
],
)
-
- def testMonthlyRscaleStartInLeapYearSkipForward(self):
-
- recur = Recurrence()
- recur.parse("RSCALE=CHINESE;FREQ=MONTHLY;SKIP=FORWARD")
- start = DateTime(2014, 1, 30) # {C}46501230
- end = DateTime(2018, 1, 1)
+ 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(start, range, items)
+ recur.expand(DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")), range, items)
self.assertEqual(
items,
[
- 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),
+ 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")),
],
)
- def testMonthlyRscaleStartInLeapYearSkipBackwardDefault(self):
+ def testMonthlyInUTC(self):
- 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),
- ],
- )
-
-
- def testYearlyLeapDaySkipYes(self):
-
recur = Recurrence()
- recur.parse("RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=YES;COUNT=5")
- start = DateTime(2016, 2, 29)
- end = DateTime(2100, 1, 1)
+ 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))
items = []
range = Period(start, end)
- recur.expand(start, range, items)
+ recur.expand(DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")), range, items)
self.assertEqual(
items,
[
- DateTime(2016, 2, 29),
- DateTime(2020, 2, 29),
- DateTime(2024, 2, 29),
- DateTime(2028, 2, 29),
- DateTime(2032, 2, 29),
- ]
+ 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, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 11, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ DateTime(2014, 12, 1, 12, 0, 0, tzid=Timezone(tzid="America/New_York")),
+ ],
)
- def testYearlyLeapDaySkipForward(self):
+ def testExampleRules(self):
- recur = Recurrence()
- recur.parse("RSCALE=GREGORIAN;FREQ=YEARLY;SKIP=FORWARD;COUNT=5")
- 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, 3, 1),
- DateTime(2018, 3, 1),
- DateTime(2019, 3, 1),
- DateTime(2020, 2, 29),
- ]
- )
+ examples = os.path.join(os.path.dirname(__file__), "rrule_examples.json")
+ with open(examples) as f:
+ examples = json.loads(f.read())
+ for ctr, i in enumerate(examples):
- def testYearlyLeapDaySkipBackwardDefault(self):
+ rules = [i["rule"]]
+ if "RSCALE" not in rules[0]:
+ rules.append("RSCALE=GREGORIAN;{};SKIP=YES".format(rules[0]))
+ for rule in rules:
+ recur = Recurrence()
+ recur.parse(rule)
+ start = DateTime.parseText(i["start"])
+ end = DateTime.parseText(i["end"])
+ results = map(DateTime.parseText, i["results"])
- 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),
- ]
- )
+ items = []
+ range = Period(start, end)
+ recur.expand(start, range, items)
+ self.assertEqual(
+ items,
+ results,
+ msg="Failed rule: #{} {}".format(ctr + 1, rule)
+ )
- def testChineseMonthlyByMonthDay30SkipYes(self):
+ def testClearOnChange(self):
- rrule = "RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=30;SKIP=YES"
recur = Recurrence()
- recur.parse(rrule)
- start = DateTime(2014, 1, 30, 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, 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),
- ],
- msg="Failed: {} {}".format(rrule, items,),
- )
+ recur.parse("FREQ=DAILY")
-
- def testChineseMonthlyByMonthDay30SkipBackward(self):
-
- rrule = "RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=30;SKIP=BACKWARD"
- recur = Recurrence()
- recur.parse(rrule)
- start = DateTime(2014, 1, 30, 12, 0, 0)
- end = DateTime(2015, 1, 1, 0, 0, 0)
- items = []
+ 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.assertEqual(
- items,
- [
- 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),
- ],
- msg="Failed: {} {}".format(rrule, items,),
- )
+ self.assertTrue(recur.mCached)
+ self.assertTrue(len(items) > 100)
-
- def testChineseMonthlyByMonthDay30SkipForward(self):
-
- rrule = "RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=30;SKIP=FORWARD"
- recur = Recurrence()
- recur.parse(rrule)
- start = DateTime(2014, 1, 30, 12, 0, 0)
- end = DateTime(2015, 1, 1, 0, 0, 0)
+ recur.setUseCount(True)
+ recur.setCount(10)
+ self.assertFalse(recur.mCached)
items = []
- range = Period(start, end)
recur.expand(start, range, items)
- self.assertEqual(
- items,
- [
- 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, 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),
- ],
- msg="Failed: {} {}".format(rrule, items,),
- )
+ self.assertEqual(len(items), 10)
- def testChineseMonthlyByMonthDayMinus30SkipYes(self):
- rrule = "RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=-30;SKIP=YES"
- recur = Recurrence()
- recur.parse(rrule)
- start = DateTime(2014, 1, 30, 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, 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),
- ],
- msg="Failed: {} {}".format(rrule, items,),
- )
+class TestRecurrenceRscale(unittest.TestCase):
- def testChineseMonthlyByMonthDayMinus30SkipBackward(self):
+ def testExampleRules(self):
- rrule = "RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=-30;SKIP=BACKWARD"
- recur = Recurrence()
- recur.parse(rrule)
- start = DateTime(2014, 1, 30, 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, 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),
- ],
- msg="Failed: {} {}".format(rrule, items,),
- )
+ examples = os.path.join(os.path.dirname(__file__), "rscale_examples.json")
+ with open(examples) as f:
+ examples = json.loads(f.read())
+ for ctr, i in enumerate(examples):
- def testChineseMonthlyByMonthDayMinus30SkipForward(self):
+ for rule in i["rule"]:
+ recur = Recurrence()
+ recur.parse(rule)
+ start = DateTime.parseText(i["start"])
+ end = DateTime.parseText(i["end"])
+ results = map(DateTime.parseText, i["results"])
- rrule = "RSCALE=CHINESE;FREQ=MONTHLY;BYMONTHDAY=-30;SKIP=FORWARD"
- recur = Recurrence()
- recur.parse(rrule)
- start = DateTime(2014, 1, 30, 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, 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),
- ],
- msg="Failed: {} {}".format(rrule, items,),
- )
+ items = []
+ range = Period(start, end)
+ recur.expand(start, range, items)
+ self.assertEqual(
+ items,
+ results,
+ msg="Failed rule: #{} {}".format(ctr + 1, rule)
+ )
Modified: PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_vpoll.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_vpoll.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/tests/test_vpoll.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2007-2013 Cyrus Daboo. All rights reserved.
+# Copyright (c) 2007-2015 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.
@@ -32,8 +32,16 @@
ORGANIZER:mailto:user01 at example.com
POLL-MODE:BASIC
POLL-PROPERTIES:DTSTART,DTEND
+BEGIN:VVOTER
VOTER;CN=User 02:mailto:user02 at example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+END:VVOTER
+BEGIN:VVOTER
VOTER;CN=User 03:mailto:user03 at example.com
+END:VVOTER
BEGIN:VEVENT
UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
DTSTART;VALUE=DATE:20130101
@@ -63,11 +71,21 @@
UID:A979D282-2CDB-484F-BD63-3972094DFFC0
DTSTAMP:20020101T000000Z
ORGANIZER:mailto:user01 at example.com
-POLL-ITEM-ID;PUBLIC-COMMENT=Not ideal;RESPONSE=50:1
-POLL-ITEM-ID;PUBLIC-COMMENT=Perfect;RESPONSE=100:2
POLL-MODE:BASIC
POLL-PROPERTIES:DTSTART,DTEND
+BEGIN:VVOTER
VOTER;CN=User 02:mailto:user02 at example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:50
+COMMENT:Not ideal
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+RESPONSE:100
+COMMENT:Perfect
+END:VOTE
+END:VVOTER
END:VPOLL
END:VCALENDAR
""",
Copied: PyCalendar/branches/rscale/src/pycalendar/icalendar/vote.py (from rev 15020, PyCalendar/trunk/src/pycalendar/icalendar/vote.py)
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/vote.py (rev 0)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/vote.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -0,0 +1,51 @@
+##
+# Copyright (c) 2011-2015 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.component import Component
+from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS
+
+class Vote(Component):
+
+ propertyCardinality_1 = (
+ )
+
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_POLL_ITEM_ID,
+ definitions.cICalProperty_RESPONSE,
+ )
+
+ propertyValueChecks = ICALENDAR_VALUE_CHECKS
+
+ def __init__(self, parent=None):
+ super(Vote, self).__init__(parent=parent)
+
+
+ def duplicate(self, parent=None):
+ return super(Vote, self).duplicate(parent=parent)
+
+
+ def getType(self):
+ return definitions.cICalComponent_VOTE
+
+
+ def sortedPropertyKeyOrder(self):
+ return (
+ definitions.cICalProperty_POLL_ITEM_ID,
+ definitions.cICalProperty_RESPONSE,
+ )
+
+Component.registerComponent(definitions.cICalComponent_VOTE, Vote)
Modified: PyCalendar/branches/rscale/src/pycalendar/icalendar/vpoll.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/vpoll.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/vpoll.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2007-2013 Cyrus Daboo. All rights reserved.
+# Copyright (c) 2007-2015 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.
@@ -39,6 +39,7 @@
definitions.cICalProperty_LAST_MODIFIED,
definitions.cICalProperty_POLL_MODE,
definitions.cICalProperty_POLL_PROPERTIES,
+ definitions.cICalProperty_POLL_WINNER,
definitions.cICalProperty_PRIORITY,
definitions.cICalProperty_SEQUENCE,
definitions.cICalProperty_STATUS,
@@ -67,13 +68,18 @@
def sortedComponents(self):
"""
- Also take POLL-ID into account
+ Also take VVOTER and POLL-ID into account
"""
components = self.mComponents[:]
- # Write out the remainder sorted by name, sortKey
- return sorted(components, key=lambda x: (x.getType().upper(), x.loadValueString(definitions.cICalProperty_POLL_ITEM_ID),))
+ # VVOTER sorts above components with POLL-ITEM-ID
+ def _sortKey(subcomponent):
+ if subcomponent.getType().upper() == definitions.cICalComponent_VVOTER:
+ return ("0", subcomponent.loadValueString(definitions.cICalProperty_VOTER),)
+ else:
+ return (subcomponent.getType().upper(), subcomponent.loadValueInteger(definitions.cICalProperty_POLL_ITEM_ID),)
+ return sorted(components, key=_sortKey)
Component.registerComponent(definitions.cICalComponent_VPOLL, VPoll)
Copied: PyCalendar/branches/rscale/src/pycalendar/icalendar/vvoter.py (from rev 15020, PyCalendar/trunk/src/pycalendar/icalendar/vvoter.py)
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/icalendar/vvoter.py (rev 0)
+++ PyCalendar/branches/rscale/src/pycalendar/icalendar/vvoter.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -0,0 +1,71 @@
+##
+# Copyright (c) 2011-2015 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.component import Component
+from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS
+
+class VVoter(Component):
+
+ propertyCardinality_1 = (
+ definitions.cICalProperty_VOTER,
+ )
+
+ propertyCardinality_0_1 = (
+ definitions.cICalProperty_CREATED,
+ definitions.cICalProperty_DESCRIPTION,
+ definitions.cICalProperty_LAST_MODIFIED,
+ definitions.cICalProperty_SUMMARY,
+ definitions.cICalProperty_URL,
+ )
+
+ propertyValueChecks = ICALENDAR_VALUE_CHECKS
+
+ def __init__(self, parent=None):
+ super(VVoter, self).__init__(parent=parent)
+
+
+ def duplicate(self, parent=None):
+ return super(VVoter, self).duplicate(parent=parent)
+
+
+ def getType(self):
+ return definitions.cICalComponent_VVOTER
+
+
+ def addComponent(self, comp):
+ # We can embed the available components only
+ if comp.getType() == definitions.cICalComponent_VOTE:
+ super(VVoter, self).addComponent(comp)
+ else:
+ raise ValueError
+
+
+ def sortedPropertyKeyOrder(self):
+ return (
+ definitions.cICalProperty_VOTER,
+ )
+
+
+ def sortedComponents(self):
+ """
+ Also take POLL-ITEM-ID into account
+ """
+
+ components = self.mComponents[:]
+ return sorted(components, key=lambda x: x.loadValueInteger(definitions.cICalProperty_POLL_ITEM_ID))
+
+Component.registerComponent(definitions.cICalComponent_VVOTER, VVoter)
Modified: PyCalendar/branches/rscale/src/pycalendar/parser.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/parser.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/parser.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -61,6 +61,9 @@
# Remove \-escaping in URI values when parsing - only PARSER_FIX or PARSER_ALLOW
BACKSLASH_IN_URI_VALUE = PARSER_FIX
+ # Remove \-escaping in GEO values when parsing - only PARSER_FIX
+ BACKSLASH_IN_GEO_VALUE = PARSER_FIX
+
@staticmethod
def allRaise():
"""
@@ -75,4 +78,5 @@
ParserContext.INVALID_ADR_N_VALUES = ParserContext.PARSER_RAISE
ParserContext.INVALID_REQUEST_STATUS_VALUE = ParserContext.PARSER_RAISE
ParserContext.BACKSLASH_IN_URI_VALUE = ParserContext.PARSER_RAISE
+ ParserContext.BACKSLASH_IN_GEO_VALUE = ParserContext.PARSER_RAISE
ParserContext.INVALID_REQUEST_STATUS = ParserContext.PARSER_RAISE
Modified: PyCalendar/branches/rscale/src/pycalendar/period.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/period.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/period.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -28,26 +28,28 @@
if end is not None:
self.mEnd = end
- self.mDuration = self.mEnd - self.mStart
+ self.mDuration = None
self.mUseDuration = False
elif duration is not None:
self.mDuration = duration
- self.mEnd = self.mStart + self.mDuration
+ self.mEnd = None
self.mUseDuration = True
else:
self.mEnd = self.mStart.duplicate()
- self.mDuration = Duration()
+ self.mDuration = None
self.mUseDuration = False
def duplicate(self):
- other = Period(start=self.mStart.duplicate(), end=self.mEnd.duplicate())
- other.mUseDuration = self.mUseDuration
+ if self.mUseDuration:
+ other = Period(start=self.mStart.duplicate(), duration=self.mDuration.duplicate())
+ else:
+ other = Period(start=self.mStart.duplicate(), end=self.mEnd.duplicate())
return other
def __hash__(self):
- return hash((self.mStart, self.mEnd,))
+ return hash((self.mStart, self.getEnd(),))
def __repr__(self):
@@ -59,7 +61,7 @@
def __eq__(self, comp):
- return self.mStart == comp.mStart and self.mEnd == comp.mEnd
+ return self.mStart == comp.mStart and self.getEnd() == comp.getEnd()
def __gt__(self, comp):
@@ -68,7 +70,7 @@
def __lt__(self, comp):
return self.mStart < comp.mStart \
- or (self.mStart == comp.mStart) and self.mEnd < comp.mEnd
+ or (self.mStart == comp.mStart) and self.getEnd() < comp.getEnd()
@classmethod
@@ -79,21 +81,24 @@
def parse(self, data, fullISO=False):
- splits = data.split('/', 1)
- if len(splits) == 2:
- start = splits[0]
- end = splits[1]
+ try:
+ splits = data.split('/', 1)
+ if len(splits) == 2:
+ start = splits[0]
+ end = splits[1]
- self.mStart.parse(start, fullISO)
- if end[0] == 'P':
- self.mDuration.parse(end)
- self.mUseDuration = True
- self.mEnd = self.mStart + self.mDuration
+ self.mStart.parse(start, fullISO)
+ if end[0] == 'P':
+ self.mDuration = Duration.parseText(end)
+ self.mUseDuration = True
+ self.mEnd = None
+ else:
+ self.mEnd.parse(end, fullISO)
+ self.mUseDuration = False
+ self.mDuration = None
else:
- self.mEnd.parse(end, fullISO)
- self.mUseDuration = False
- self.mDuration = self.mEnd - self.mStart
- else:
+ raise ValueError
+ except IndexError:
raise ValueError
@@ -146,10 +151,14 @@
def getEnd(self):
+ if self.mEnd is None:
+ self.mEnd = self.mStart + self.mDuration
return self.mEnd
def getDuration(self):
+ if self.mDuration is None:
+ self.mDuration = self.mEnd - self.mStart
return self.mDuration
@@ -159,11 +168,15 @@
def setUseDuration(self, use):
self.mUseDuration = use
+ if self.mUseDuration and self.mDuration is None:
+ self.getDuration()
+ elif not self.mUseDuration and self.mEnd is None:
+ self.getEnd()
def isDateWithinPeriod(self, dt):
# Inclusive start, exclusive end
- return dt >= self.mStart and dt < self.mEnd
+ return dt >= self.mStart and dt < self.getEnd()
def isDateBeforePeriod(self, dt):
@@ -173,17 +186,17 @@
def isDateAfterPeriod(self, dt):
# Exclusive end
- return dt >= self.mEnd
+ return dt >= self.getEnd()
def isPeriodOverlap(self, p):
# Inclusive start, exclusive end
- return not (self.mStart >= p.mEnd or self.mEnd <= p.mStart)
+ return not (self.mStart >= p.getEnd() or self.getEnd() <= p.mStart)
def adjustToUTC(self):
self.mStart.adjustToUTC()
- self.mEnd.adjustToUTC()
+ self.getEnd().adjustToUTC()
def describeDuration(self):
Modified: PyCalendar/branches/rscale/src/pycalendar/plaintextvalue.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/plaintextvalue.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/plaintextvalue.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -48,7 +48,7 @@
def parseJSONValue(self, jobject):
- self.mValue = str(jobject)
+ self.mValue = jobject.encode("utf-8")
def writeJSONValue(self, jobject):
Modified: PyCalendar/branches/rscale/src/pycalendar/property.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/property.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/property.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -472,21 +472,21 @@
prop = cls()
# Get the name
- prop.mName = jobject[0].upper()
+ prop.mName = jobject[0].encode("utf-8").upper()
# Get the parameters
if jobject[1]:
for name, value in jobject[1].items():
# Now add parameter value
name = name.upper()
- attrvalue = Parameter(name=name, value=value)
+ attrvalue = Parameter(name=name.encode("utf-8"), value=value.encode("utf-8"))
prop.mParameters.setdefault(name, []).append(attrvalue)
# Get default value type from property name and insert a VALUE parameter if current value type is not default
value_type = cls.sValueTypeMap.get(jobject[2].upper(), Value.VALUETYPE_UNKNOWN)
default_type = cls.sDefaultValueTypeMap.get(prop.mName.upper(), Value.VALUETYPE_UNKNOWN)
if default_type != value_type:
- attrvalue = Parameter(name=cls.sValue, value=jobject[2].upper())
+ attrvalue = Parameter(name=cls.sValue, value=jobject[2].encode("utf-8").upper())
prop.mParameters.setdefault(cls.sValue, []).append(attrvalue)
# Get value type from property name
Copied: PyCalendar/branches/rscale/src/pycalendar/tests/test_geovalue.py (from rev 15020, PyCalendar/trunk/src/pycalendar/tests/test_geovalue.py)
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/tests/test_geovalue.py (rev 0)
+++ PyCalendar/branches/rscale/src/pycalendar/tests/test_geovalue.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -0,0 +1,47 @@
+##
+# Copyright (c) 2012-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.geovalue import GeoValue
+from pycalendar.icalendar.property import Property
+import unittest
+
+class TestURIValue(unittest.TestCase):
+
+ def testParseValue(self):
+
+ items = (
+ ("-12.345;67.890", "-12.345;67.89"),
+ ("-12.345\\;67.890", "-12.345;67.89"),
+ )
+
+ for item, result in items:
+ req = GeoValue()
+ req.parse(item, "icalendar")
+ test = req.getText()
+ self.assertEqual(test, result, "Failed to parse and re-generate '%s'" % (item,))
+
+
+ def testParseProperty(self):
+
+ items = (
+ ("GEO:-12.345;67.890", "GEO:-12.345;67.89"),
+ ("GEO:-12.345\\;67.890", "GEO:-12.345;67.89"),
+ )
+
+ for item, result in items:
+ prop = Property.parseText(item)
+ test = prop.getText()
+ self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,))
Copied: PyCalendar/branches/rscale/src/pycalendar/tests/test_period.py (from rev 15020, PyCalendar/trunk/src/pycalendar/tests/test_period.py)
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/tests/test_period.py (rev 0)
+++ PyCalendar/branches/rscale/src/pycalendar/tests/test_period.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -0,0 +1,64 @@
+##
+# 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.duration import Duration
+from pycalendar.period import Period
+import unittest
+
+class TestPeriod(unittest.TestCase):
+
+ test_data = (
+ "20000101T000000Z/20000101T010000Z",
+ "20000101T000000Z/PT1H",
+ )
+
+ def testParseGenerate(self):
+
+ for result in TestPeriod.test_data:
+ period = Period.parseText(result)
+ self.assertEqual(period.getText(), result)
+
+
+ def testParseBad(self):
+
+ test_bad_data = (
+ "",
+ "ABC",
+ "20000101T000000Z",
+ "20000101T000000Z/",
+ "20000101T000000Z/P",
+ "20000101T000000Z/2000",
+ )
+ for data in test_bad_data:
+ self.assertRaises(ValueError, Period.parseText, data)
+
+
+ def testSetUseDuration(self):
+
+ p1 = Period(
+ start=DateTime(2000, 1, 1, 0, 0, 0),
+ end=DateTime(2000, 1, 1, 1, 0, 0),
+ )
+ p1.setUseDuration(True)
+ self.assertTrue(p1.getText(), "20000101T000000/PT1H")
+
+ p2 = Period(
+ start=DateTime(2000, 1, 1, 0, 0, 0),
+ duration=Duration(hours=1),
+ )
+ p2.setUseDuration(False)
+ self.assertTrue(p2.getText(), "20000101T000000/20000101T010000")
Modified: PyCalendar/branches/rscale/src/pycalendar/timezone.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/timezone.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/timezone.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -27,6 +27,7 @@
"""
sDefaultTimezone = None
+ UTCTimezone = None
def __init__(self, utc=None, tzid=None):
@@ -127,3 +128,4 @@
return TimezoneDatabase.getTimezoneDescriptor(self.mTimezone, dt)
Timezone.sDefaultTimezone = Timezone()
+Timezone.UTCTimezone = Timezone(utc=True)
Modified: PyCalendar/branches/rscale/src/pycalendar/timezonedb.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/timezonedb.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/timezonedb.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -127,7 +127,8 @@
tzpath = os.path.normpath(tzpath)
if tzpath.startswith(self.dbpath) and os.path.isfile(tzpath):
try:
- self.calendar.parseComponent(open(tzpath))
+ with open(tzpath) as f:
+ self.calendar.parseComponent(f)
except (IOError, InvalidData):
raise NoTimezoneInDatabase(self.dbpath, tzid)
else:
Modified: PyCalendar/branches/rscale/src/pycalendar/validator.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/validator.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/validator.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -27,7 +27,8 @@
Check whether the contents of the specified file is valid iCalendar or vCard data.
"""
- data = open(fname).read()
+ with open(fname) as f:
+ data = f.read()
ParserContext.allRaise()
Modified: PyCalendar/branches/rscale/src/pycalendar/vcard/adr.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/vcard/adr.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/vcard/adr.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -120,7 +120,7 @@
def parseJSON(self, jobject):
- self.mValue = tuple(jobject)
+ self.mValue = tuple(map(lambda x: x.encode("utf-8"), jobject))
def writeJSON(self, jobject):
Modified: PyCalendar/branches/rscale/src/pycalendar/vcard/n.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/vcard/n.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/vcard/n.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -117,7 +117,7 @@
def parseJSON(self, jobject):
- self.mValue = tuple(jobject)
+ self.mValue = tuple(map(lambda x: x.encode("utf-8"), jobject))
def writeJSON(self, jobject):
Modified: PyCalendar/branches/rscale/src/pycalendar/vcard/orgvalue.py
===================================================================
--- PyCalendar/branches/rscale/src/pycalendar/vcard/orgvalue.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/pycalendar/vcard/orgvalue.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -45,7 +45,7 @@
def parseJSONValue(self, jobject):
- self.mValue = tuple(jobject)
+ self.mValue = tuple(map(lambda x: x.encode("utf-8"), jobject))
def writeJSONValue(self, jobject):
Modified: PyCalendar/branches/rscale/src/zonal/tzconvert.py
===================================================================
--- PyCalendar/branches/rscale/src/zonal/tzconvert.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/zonal/tzconvert.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -53,28 +53,28 @@
def parse(self, file):
try:
- f = open(file, "r")
- ctr = 0
- for line in f:
- ctr += 1
- line = line[:-1]
- while True:
- if line.startswith("#") or len(line) == 0:
- break
- elif line.startswith("Rule"):
- self.parseRule(line)
- break
- elif line.startswith("Zone"):
- line = self.parseZone(line, f)
- if line is None:
+ with open(file, "r") as f:
+ ctr = 0
+ for line in f:
+ ctr += 1
+ line = line[:-1]
+ while True:
+ if line.startswith("#") or len(line) == 0:
break
- elif line.startswith("Link"):
- self.parseLink(line)
- break
- elif len(line.strip()) != 0:
- assert False, "Could not parse line %d from tzconvert file: '%s'" % (ctr, line,)
- else:
- break
+ elif line.startswith("Rule"):
+ self.parseRule(line)
+ break
+ elif line.startswith("Zone"):
+ line = self.parseZone(line, f)
+ if line is None:
+ break
+ elif line.startswith("Link"):
+ self.parseLink(line)
+ break
+ elif len(line.strip()) != 0:
+ assert False, "Could not parse line %d from tzconvert file: '%s'" % (ctr, line,)
+ else:
+ break
except:
print("Failed to parse file %s" % (file,))
raise
@@ -119,8 +119,8 @@
def parseWindowsAliases(self, aliases):
try:
- xmlfile = open(aliases)
- xmlroot = XML.ElementTree(file=xmlfile).getroot()
+ with open(aliases) as xmlfile:
+ xmlroot = XML.ElementTree(file=xmlfile).getroot()
except (IOError, XMLParseError):
raise ValueError("Unable to open or read windows alias file: {}".format(aliases))
@@ -217,7 +217,8 @@
# Generate link mapping file
linkPath = os.path.join(outputdir, "links.txt")
- open(linkPath, "w").write("\n".join(link_list))
+ with open(linkPath, "w") as f:
+ f.write("\n".join(link_list))
Modified: PyCalendar/branches/rscale/src/zonal/tzdump.py
===================================================================
--- PyCalendar/branches/rscale/src/zonal/tzdump.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/zonal/tzdump.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -27,12 +27,12 @@
cal = Calendar()
if verbose:
print "Parsing calendar data: %s" % (file,)
- fin = open(file, "r")
- try:
- cal.parse(fin)
- except InvalidData, e:
- print "Failed to parse bad data: %s" % (e.mData,)
- raise
+ with open(file, "r") as fin:
+ try:
+ cal.parse(fin)
+ except InvalidData, e:
+ print "Failed to parse bad data: %s" % (e.mData,)
+ raise
return cal
Modified: PyCalendar/branches/rscale/src/zonal/tzverify.py
===================================================================
--- PyCalendar/branches/rscale/src/zonal/tzverify.py 2015-09-11 17:35:39 UTC (rev 15126)
+++ PyCalendar/branches/rscale/src/zonal/tzverify.py 2015-09-14 16:49:28 UTC (rev 15127)
@@ -63,12 +63,12 @@
for file in files:
if verbose:
print "Parsing calendar data: %s" % (file,)
- fin = open(file, "r")
- try:
- cal.parse(fin)
- except InvalidData, e:
- print "Failed to parse bad data: %s" % (e.mData,)
- raise
+ with open(file, "r") as fin:
+ try:
+ cal.parse(fin)
+ except InvalidData, e:
+ print "Failed to parse bad data: %s" % (e.mData,)
+ raise
return CalendarZonesWrapper(calendar=cal)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20150914/1cba5b63/attachment-0001.html>
More information about the calendarserver-changes
mailing list