[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