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

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

<h3>Log Message</h3>
<pre>Add caching and standard timezone indicator. Support different variants of automatically including timezones in text representations.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#PyCalendartrunksrcpycalendaricalendarcalendarpy">PyCalendar/trunk/src/pycalendar/icalendar/calendar.py</a></li>
<li><a href="#PyCalendartrunksrcpycalendaricalendarteststest_validationpy">PyCalendar/trunk/src/pycalendar/icalendar/tests/test_validation.py</a></li>
<li><a href="#PyCalendartrunksrcpycalendaricalendarvavailabilitypy">PyCalendar/trunk/src/pycalendar/icalendar/vavailability.py</a></li>
<li><a href="#PyCalendartrunksrcpycalendartimezonedbpy">PyCalendar/trunk/src/pycalendar/timezonedb.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#PyCalendartrunksrcpycalendarteststest_timezonedbpy">PyCalendar/trunk/src/pycalendar/tests/test_timezonedb.py</a></li>
<li><a href="#PyCalendartrunksrcpycalendartestsutilspy">PyCalendar/trunk/src/pycalendar/tests/utils.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="PyCalendartrunksrcpycalendaricalendarcalendarpy"></a>
<div class="modfile"><h4>Modified: PyCalendar/trunk/src/pycalendar/icalendar/calendar.py (13706 => 13707)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/icalendar/calendar.py        2014-06-30 14:57:51 UTC (rev 13706)
+++ PyCalendar/trunk/src/pycalendar/icalendar/calendar.py        2014-07-01 16:36:05 UTC (rev 13707)
</span><span class="lines">@@ -42,6 +42,11 @@
</span><span class="cx">     FIND_EXACT = 0
</span><span class="cx">     FIND_MASTER = 1
</span><span class="cx"> 
</span><ins>+    # Enums for includeTimezone parameter
+    ALL_TIMEZONES = 0       # Always include referenced timezones
+    NONSTD_TIMEZONES = 1    # Only include non-standard referenced timezones
+    NO_TIMEZONES = 2        # Never include timezones other than those already present
+
</ins><span class="cx">     sContainerDescriptor = &quot;iCalendar&quot;
</span><span class="cx">     sComponentType = Component
</span><span class="cx">     sPropertyType = Property
</span><span class="lines">@@ -70,6 +75,14 @@
</span><span class="cx">         self.mOverriddenComponentsByUID = collections.defaultdict(list)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def __str__(self):
+        &quot;&quot;&quot;
+        Override this to generate text without adding timezones - i.e., this will not change the
+        underlying object in any way.
+        &quot;&quot;&quot;
+        return self.getText(includeTimezones=Calendar.NO_TIMEZONES)
+
+
</ins><span class="cx">     def duplicate(self):
</span><span class="cx">         other = super(Calendar, self).duplicate()
</span><span class="cx">         other.mName = self.mName
</span><span class="lines">@@ -338,32 +351,30 @@
</span><span class="cx">                 del self.mMasterComponentsByTypeAndUID[component.getType()][uid]
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def getText(self, includeTimezones=False, format=None):
</del><ins>+    def getText(self, includeTimezones=None, format=None):
</ins><span class="cx"> 
</span><span class="cx">         if format is None or format == self.sFormatText:
</span><span class="cx">             s = StringIO()
</span><span class="cx">             self.generate(s, includeTimezones=includeTimezones)
</span><span class="cx">             return s.getvalue()
</span><span class="cx">         elif format == self.sFormatJSON:
</span><del>-            return self.getTextJSON(includeTimezones)
</del><ins>+            return self.getTextJSON(includeTimezones=includeTimezones)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def generate(self, os, includeTimezones=False):
</del><ins>+    def generate(self, os, includeTimezones=None):
</ins><span class="cx">         # Make sure all required timezones are in this object
</span><del>-        if includeTimezones:
-            self.includeTimezones()
</del><ins>+        self.includeMissingTimezones(includeTimezones=includeTimezones)
</ins><span class="cx">         super(Calendar, self).generate(os)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def getTextXML(self, includeTimezones=False):
</del><ins>+    def getTextXML(self, includeTimezones=None):
</ins><span class="cx">         node = self.writeXML(includeTimezones)
</span><span class="cx">         return xmlutils.toString(node)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def writeXML(self, includeTimezones=False):
</del><ins>+    def writeXML(self, includeTimezones=None):
</ins><span class="cx">         # Make sure all required timezones are in this object
</span><del>-        if includeTimezones:
-            self.includeTimezones()
</del><ins>+        self.includeMissingTimezones(includeTimezones=includeTimezones)
</ins><span class="cx"> 
</span><span class="cx">         # Root node structure
</span><span class="cx">         root = XML.Element(xmlutils.makeTag(xmldefinitions.iCalendar20_namespace, xmldefinitions.icalendar))
</span><span class="lines">@@ -371,16 +382,15 @@
</span><span class="cx">         return root
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def getTextJSON(self, includeTimezones=False):
</del><ins>+    def getTextJSON(self, includeTimezones=None):
</ins><span class="cx">         jobject = []
</span><span class="cx">         self.writeJSON(jobject, includeTimezones)
</span><span class="cx">         return json.dumps(jobject[0], indent=2, separators=(',', ':'))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def writeJSON(self, jobject, includeTimezones=False):
</del><ins>+    def writeJSON(self, jobject, includeTimezones=None):
</ins><span class="cx">         # Make sure all required timezones are in this object
</span><del>-        if includeTimezones:
-            self.includeTimezones()
</del><ins>+        self.includeMissingTimezones(includeTimezones=includeTimezones)
</ins><span class="cx"> 
</span><span class="cx">         # Root node structure
</span><span class="cx">         super(Calendar, self).writeJSON(jobject)
</span><span class="lines">@@ -588,7 +598,23 @@
</span><span class="cx">         return True
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def includeTimezones(self):
</del><ins>+    def includeMissingTimezones(self, includeTimezones=None):
+        &quot;&quot;&quot;
+        For each timezone referenced in this L{Calendar}, if the corresponding VTIMEZONE component
+        is not present, then add the matching component from the timezone database. If
+        L{includeTimezones} is L{False}, then only add VTIMEZONEs that are not part of the standard
+        timezone database.
+        
+        @param includeTimezones: indicated whether all or only non-standard timezones are included
+        @type includeTimezones: L{bool}
+        &quot;&quot;&quot;
+
+        # Don't add anything in this case
+        if includeTimezones == Calendar.NO_TIMEZONES:
+            return
+        if includeTimezones is None:
+            includeTimezones = Calendar.NONSTD_TIMEZONES
+
</ins><span class="cx">         # Get timezone names from each component
</span><span class="cx">         tzids = set()
</span><span class="cx">         for component in self.mComponents:
</span><span class="lines">@@ -598,6 +624,9 @@
</span><span class="cx">         # Make sure each timezone is in current calendar
</span><span class="cx">         from pycalendar.timezonedb import TimezoneDatabase
</span><span class="cx">         for tzid in tzids:
</span><ins>+            # Skip standard timezones if requested
+            if includeTimezones == Calendar.NONSTD_TIMEZONES and TimezoneDatabase.isStandardTimezone(tzid):
+                continue
</ins><span class="cx">             tz = self.getTimezone(tzid)
</span><span class="cx">             if tz is None:
</span><span class="cx">                 # Find it in the static object
</span><span class="lines">@@ -605,3 +634,22 @@
</span><span class="cx">                 if tz is not None:
</span><span class="cx">                     dup = tz.duplicate()
</span><span class="cx">                     self.addComponent(dup)
</span><ins>+
+
+    def stripStandardTimezones(self):
+        &quot;&quot;&quot;
+        Remove VTIMEZONE components from this L{Calendar} if the corresponding TZIDs are
+        in the timezone database.
+        
+        @return: L{True} if changes were made, L{False} otherwise
+        @rtype: L{bool}
+        &quot;&quot;&quot;
+        from pycalendar.timezonedb import TimezoneDatabase
+        changed = False
+        for component in self.getComponents(definitions.cICalComponent_VTIMEZONE):
+            tz = TimezoneDatabase.getTimezone(component.getID())
+            if tz is not None and TimezoneDatabase.isStandardTimezone(component.getID()):
+                self.removeComponent(component)
+                changed = True
+
+        return changed
</ins></span></pre></div>
<a id="PyCalendartrunksrcpycalendaricalendarteststest_validationpy"></a>
<div class="modfile"><h4>Modified: PyCalendar/trunk/src/pycalendar/icalendar/tests/test_validation.py (13706 => 13707)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/icalendar/tests/test_validation.py        2014-06-30 14:57:51 UTC (rev 13706)
+++ PyCalendar/trunk/src/pycalendar/icalendar/tests/test_validation.py        2014-07-01 16:36:05 UTC (rev 13707)
</span><span class="lines">@@ -16,9 +16,9 @@
</span><span class="cx"> 
</span><span class="cx"> from pycalendar.exceptions import ValidationError
</span><span class="cx"> from pycalendar.icalendar.calendar import Calendar
</span><del>-import unittest
</del><ins>+from pycalendar.tests.utils import TestPyCalendar
</ins><span class="cx"> 
</span><del>-class TestValidation(unittest.TestCase):
</del><ins>+class TestValidation(TestPyCalendar):
</ins><span class="cx"> 
</span><span class="cx">     def test_basic(self):
</span><span class="cx"> 
</span><span class="lines">@@ -1682,7 +1682,7 @@
</span><span class="cx">         for title, test_old, test_new, test_fixed, test_unfixed in data:
</span><span class="cx">             cal = Calendar.parseText(test_old)
</span><span class="cx">             fixed, unfixed = cal.validate(doFix=True)
</span><del>-            self.assertEqual(str(cal), test_new, msg=&quot;Failed test: %s&quot; % (title,))
</del><ins>+            self.assertEqual(str(cal), test_new, msg=&quot;Failed test: %s&quot; % (str(cal),))
</ins><span class="cx">             self.assertEqual(set(fixed), test_fixed, msg=&quot;Failed test: %s&quot; % (title,))
</span><span class="cx">             self.assertEqual(set(unfixed), test_unfixed, msg=&quot;Failed test: %s&quot; % (title,))
</span><span class="cx"> 
</span></span></pre></div>
<a id="PyCalendartrunksrcpycalendaricalendarvavailabilitypy"></a>
<div class="modfile"><h4>Modified: PyCalendar/trunk/src/pycalendar/icalendar/vavailability.py (13706 => 13707)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/icalendar/vavailability.py        2014-06-30 14:57:51 UTC (rev 13706)
+++ PyCalendar/trunk/src/pycalendar/icalendar/vavailability.py        2014-07-01 16:36:05 UTC (rev 13707)
</span><span class="lines">@@ -106,4 +106,18 @@
</span><span class="cx">             definitions.cICalProperty_DTEND,
</span><span class="cx">         )
</span><span class="cx"> 
</span><ins>+
+    def getTimezones(self, tzids):
+        &quot;&quot;&quot;
+        In addition to looking in the VAVAILABILITY component, we must also return any TZIDs used
+        in AVAILABLE child components.
+
+        @param tzids: result to report back
+        @type tzids: L{set}
+        &quot;&quot;&quot;
+
+        super(VAvailability, self).getTimezones(tzids)
+        for available in self.getComponents(definitions.cICalComponent_AVAILABLE):
+            available.getTimezones(tzids)
+
</ins><span class="cx"> Component.registerComponent(definitions.cICalComponent_VAVAILABILITY, VAvailability)
</span></span></pre></div>
<a id="PyCalendartrunksrcpycalendarteststest_timezonedbpy"></a>
<div class="addfile"><h4>Added: PyCalendar/trunk/src/pycalendar/tests/test_timezonedb.py (0 => 13707)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/tests/test_timezonedb.py                                (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/test_timezonedb.py        2014-07-01 16:36:05 UTC (rev 13707)
</span><span class="lines">@@ -0,0 +1,405 @@
</span><ins>+##
+#    Copyright (c) 2012-2013 Cyrus Daboo. All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+##
+
+from cStringIO import StringIO
+from pycalendar.icalendar.calendar import Calendar
+from pycalendar.tests.utils import TestPyCalendar
+from pycalendar.timezonedb import TimezoneDatabase
+
+StandardTZs = (
+    &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//calendarserver.org//Zonal//EN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+X-LIC-LOCATION:America/New_York
+BEGIN:STANDARD
+DTSTART:18831118T120358
+RDATE:18831118T120358
+TZNAME:EST
+TZOFFSETFROM:-045602
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19180331T020000
+RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19181027T020000
+RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:STANDARD
+DTSTART:19200101T000000
+RDATE:19200101T000000
+RDATE:19420101T000000
+RDATE:19460101T000000
+RDATE:19670101T000000
+TZNAME:EST
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19200328T020000
+RDATE:19200328T020000
+RDATE:19740106T020000
+RDATE:19750223T020000
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19201031T020000
+RDATE:19201031T020000
+RDATE:19450930T020000
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19210424T020000
+RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19210925T020000
+RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19420209T020000
+RDATE:19420209T020000
+TZNAME:EWT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19450814T190000
+RDATE:19450814T190000
+TZNAME:EPT
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19460428T020000
+RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19460929T020000
+RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:STANDARD
+DTSTART:19551030T020000
+RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19670430T020000
+RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19671029T020000
+RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19760425T020000
+RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19870405T020000
+RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
+&quot;&quot;&quot;,
+    &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//calendarserver.org//Zonal//EN
+BEGIN:VTIMEZONE
+TZID:America/Los_Angeles
+X-LIC-LOCATION:America/Los_Angeles
+BEGIN:STANDARD
+DTSTART:18831118T120702
+RDATE:18831118T120702
+TZNAME:PST
+TZOFFSETFROM:-075258
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19180331T020000
+RRULE:FREQ=YEARLY;UNTIL=19190330T100000Z;BYDAY=-1SU;BYMONTH=3
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19181027T020000
+RRULE:FREQ=YEARLY;UNTIL=19191026T090000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19420209T020000
+RDATE:19420209T020000
+TZNAME:PWT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19450814T160000
+RDATE:19450814T160000
+TZNAME:PPT
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19450930T020000
+RDATE:19450930T020000
+RDATE:19490101T020000
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:STANDARD
+DTSTART:19460101T000000
+RDATE:19460101T000000
+RDATE:19670101T000000
+TZNAME:PST
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19480314T020000
+RDATE:19480314T020000
+RDATE:19740106T020000
+RDATE:19750223T020000
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19500430T020000
+RRULE:FREQ=YEARLY;UNTIL=19660424T100000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19500924T020000
+RRULE:FREQ=YEARLY;UNTIL=19610924T090000Z;BYDAY=-1SU;BYMONTH=9
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:STANDARD
+DTSTART:19621028T020000
+RRULE:FREQ=YEARLY;UNTIL=19661030T090000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19670430T020000
+RRULE:FREQ=YEARLY;UNTIL=19730429T100000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19671029T020000
+RRULE:FREQ=YEARLY;UNTIL=20061029T090000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19760425T020000
+RRULE:FREQ=YEARLY;UNTIL=19860427T100000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19870405T020000
+RRULE:FREQ=YEARLY;UNTIL=20060402T100000Z;BYDAY=1SU;BYMONTH=4
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
+&quot;&quot;&quot;,
+)
+
+NonStandardTZs = (
+    &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//calendarserver.org//Zonal//EN
+BEGIN:VTIMEZONE
+TZID:America/Cupertino
+X-LIC-LOCATION:America/Cupertino
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
+&quot;&quot;&quot;,
+)
+
+
+class TestTimezoneDB(TestPyCalendar):
+
+    def setUp(self):
+        super(TestTimezoneDB, self).setUp()
+
+        # Standard components explicitly added
+        for vtz in StandardTZs:
+            cal = Calendar()
+            TimezoneDatabase.getTimezoneDatabase()._addStandardTimezone(cal.parseComponent(StringIO(vtz)))
+
+        # Just parsing will add as non-standard
+        for vtz in NonStandardTZs:
+            Calendar.parseData(vtz)
+
+
+    def test_getTimezone(self):
+        &quot;&quot;&quot;
+        L{TimezoneDatabase.getTimezone} returns correct result.
+        &quot;&quot;&quot;
+
+        data = (
+            (&quot;America/New_York&quot;, True),
+            (&quot;America/Los_Angeles&quot;, True),
+            (&quot;America/Cupertino&quot;, True),
+            (&quot;America/FooBar&quot;, False),
+        )
+
+        for tzid, result in data:
+            tz = TimezoneDatabase.getTimezone(tzid)
+            if result:
+                self.assertTrue(tz is not None)
+                self.assertEqual(tz.getID(), tzid)
+            else:
+                self.assertTrue(tz is None)
+
+
+    def test_getTimezoneInCalendar(self):
+        &quot;&quot;&quot;
+        L{TimezoneDatabase.getTimezoneInCalendar} returns correct result.
+        &quot;&quot;&quot;
+
+        data = (
+            (&quot;America/New_York&quot;, True),
+            (&quot;America/Los_Angeles&quot;, True),
+            (&quot;America/Cupertino&quot;, True),
+            (&quot;America/FooBar&quot;, False),
+        )
+
+        for tzid, result in data:
+            cal = TimezoneDatabase.getTimezoneInCalendar(tzid)
+            if result:
+                self.assertTrue(cal is not None)
+                self.assertEqual(cal.getComponents()[0].getID(), tzid)
+            else:
+                self.assertTrue(cal is None)
+
+
+    def test_isStandardTimezone(self):
+        &quot;&quot;&quot;
+        L{TimezoneDatabase.isStandardTimezone} returns correct result.
+        &quot;&quot;&quot;
+
+        data = (
+            (&quot;America/New_York&quot;, True),
+            (&quot;America/Los_Angeles&quot;, True),
+            (&quot;America/Cupertino&quot;, False),
+            (&quot;America/FooBar&quot;, False),
+        )
+
+        for tzid, result in data:
+            self.assertEqual(TimezoneDatabase.isStandardTimezone(tzid), result, &quot;Failed {}&quot;.format(tzid))
</ins></span></pre></div>
<a id="PyCalendartrunksrcpycalendartestsutilspy"></a>
<div class="addfile"><h4>Added: PyCalendar/trunk/src/pycalendar/tests/utils.py (0 => 13707)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/tests/utils.py                                (rev 0)
+++ PyCalendar/trunk/src/pycalendar/tests/utils.py        2014-07-01 16:36:05 UTC (rev 13707)
</span><span class="lines">@@ -0,0 +1,29 @@
</span><ins>+##
+#    Copyright (c) 2012-2013 Cyrus Daboo. All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+##
+
+from pycalendar.timezonedb import TimezoneDatabase
+import unittest
+
+class TestPyCalendar(unittest.TestCase):
+    &quot;&quot;&quot;
+    Base class for all tests. This will do common setup/clean-up.
+    &quot;&quot;&quot;
+
+    def setUp(self):
+        TimezoneDatabase.createTimezoneDatabase(None)
+
+    def tearDown(self):
+        TimezoneDatabase.clearTimezoneDatabase()
</ins></span></pre></div>
<a id="PyCalendartrunksrcpycalendartimezonedbpy"></a>
<div class="modfile"><h4>Modified: PyCalendar/trunk/src/pycalendar/timezonedb.py (13706 => 13707)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/trunk/src/pycalendar/timezonedb.py        2014-06-30 14:57:51 UTC (rev 13706)
+++ PyCalendar/trunk/src/pycalendar/timezonedb.py        2014-07-01 16:36:05 UTC (rev 13707)
</span><span class="lines">@@ -43,6 +43,8 @@
</span><span class="cx">         from pycalendar.icalendar.calendar import Calendar
</span><span class="cx">         self.dbpath = None
</span><span class="cx">         self.calendar = Calendar()
</span><ins>+        self.tzcache = {}
+        self.stdtzcache = set()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def setPath(self, dbpath):
</span><span class="lines">@@ -52,6 +54,8 @@
</span><span class="cx">     def clear(self):
</span><span class="cx">         from pycalendar.icalendar.calendar import Calendar
</span><span class="cx">         self.calendar = Calendar()
</span><ins>+        self.tzcache.clear()
+        self.stdtzcache.clear()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @staticmethod
</span><span class="lines">@@ -63,20 +67,9 @@
</span><span class="cx"> 
</span><span class="cx">     @staticmethod
</span><span class="cx">     def getTimezone(tzid):
</span><ins>+        return TimezoneDatabase.getTimezoneDatabase()._getTimezone(tzid)
</ins><span class="cx"> 
</span><del>-        # Check whether current cached
-        tzdb = TimezoneDatabase.getTimezoneDatabase()
-        tz = tzdb.calendar.getTimezone(tzid)
-        if tz is None:
-            try:
-                tzdb.cacheTimezone(tzid)
-            except NoTimezoneInDatabase:
-                pass
-            tz = tzdb.calendar.getTimezone(tzid)
</del><span class="cx"> 
</span><del>-        return tz
-
-
</del><span class="cx">     @staticmethod
</span><span class="cx">     def getTimezoneInCalendar(tzid):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -113,8 +106,20 @@
</span><span class="cx">             return &quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @staticmethod
+    def isStandardTimezone(tzid):
+        return TimezoneDatabase.getTimezoneDatabase()._isStandardTimezone(tzid)
+
+
</ins><span class="cx">     def cacheTimezone(self, tzid):
</span><ins>+        &quot;&quot;&quot;
+        Load the specified timezone identifier's timezone data from a file and parse it
+        into the L{Calendar} used to store timezones used by this object.
</ins><span class="cx"> 
</span><ins>+        @param tzid: the timezone identifier to load
+        @type tzid: L{str}
+        &quot;&quot;&quot;
+
</ins><span class="cx">         if self.dbpath is None:
</span><span class="cx">             return
</span><span class="cx"> 
</span><span class="lines">@@ -130,10 +135,67 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def addTimezone(self, tz):
</span><ins>+        &quot;&quot;&quot;
+        Add the specified VTIMEZONE component to this object's L{Calendar} cache. This component
+        is assumed to be a non-standard timezone - i.e., not loaded from the timezone database.
+
+        @param tz: the VTIMEZONE component to add
+        @type tz: L{Component}
+        &quot;&quot;&quot;
</ins><span class="cx">         copy = tz.duplicate(self.calendar)
</span><span class="cx">         self.calendar.addComponent(copy)
</span><ins>+        self.tzcache[copy.getID()] = copy
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def _addStandardTimezone(self, tz):
+        &quot;&quot;&quot;
+        Same as L{addTimezone} except that the timezone is marked as a standard timezone. This
+        is only meant to be used for testing which happens int he absence of a real standard
+        timezone database.
+
+        @param tz: the VTIMEZONE component to add
+        @type tz: L{Component}
+        &quot;&quot;&quot;
+        if tz.getID() not in self.tzcache:
+            self.addTimezone(tz)
+        self.stdtzcache.add(tz.getID())
+
+
+    def _isStandardTimezone(self, tzid):
+        &quot;&quot;&quot;
+        Add the specified VTIMEZONE component to this object's L{Calendar} cache. This component
+        is assumed to be a non-standard timezone - i.e., not loaded from the timezone database.
+
+        @param tzid: the timezone identifier to lookup
+        @type tzid: L{str}
+        &quot;&quot;&quot;
+        return tzid in self.stdtzcache
+
+
+    def _getTimezone(self, tzid):
+        &quot;&quot;&quot;
+        Get a timezone matching the specified timezone identifier. Use this object's
+        cache - if not in the cache try to load it from a tz database file and store in
+        this object's calendar.
+
+        @param tzid: the timezone identifier to lookup
+        @type tzid: L{str}
+        &quot;&quot;&quot;
+        if tzid not in self.tzcache:
+            tz = self.calendar.getTimezone(tzid)
+            if tz is None:
+                try:
+                    self.cacheTimezone(tzid)
+                except NoTimezoneInDatabase:
+                    pass
+                tz = self.calendar.getTimezone(tzid)
+            self.tzcache[tzid] = tz
+            if tz is not None and tzid is not None:
+                self.stdtzcache.add(tzid)
+
+        return self.tzcache[tzid]
+
+
</ins><span class="cx">     @staticmethod
</span><span class="cx">     def mergeTimezones(cal, tzs):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -156,5 +218,5 @@
</span><span class="cx">         If the supplied VTIMEZONE is not in our cache then store it in memory.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        if self.getTimezone(tz.getID()) is None:
</del><ins>+        if self._getTimezone(tz.getID()) is None:
</ins><span class="cx">             self.addTimezone(tz)
</span></span></pre>
</div>
</div>

</body>
</html>