<!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>[14027] CalendarServer/trunk</title>
</head>
<body>

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

<h3>Log Message</h3>
<pre>Update to latest tzdist draft.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunkrequirementsstabletxt">CalendarServer/trunk/requirements-stable.txt</a></li>
<li><a href="#CalendarServertrunktwistedcaldavicalpy">CalendarServer/trunk/twistedcaldav/ical.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavtimezonestdservicepy">CalendarServer/trunk/twistedcaldav/timezonestdservice.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunkrequirementsstabletxt"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/requirements-stable.txt (14026 => 14027)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/requirements-stable.txt        2014-09-30 11:58:01 UTC (rev 14026)
+++ CalendarServer/trunk/requirements-stable.txt        2014-09-30 11:58:08 UTC (rev 14027)
</span><span class="lines">@@ -7,7 +7,7 @@
</span><span class="cx"> -e .
</span><span class="cx"> -e svn+http://svn.calendarserver.org/repository/calendarserver/twext/trunk@14020#egg=twextpy
</span><span class="cx"> -e svn+http://svn.calendarserver.org/repository/calendarserver/PyKerberos/trunk@13420#egg=kerberos
</span><del>--e svn+http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk@13802#egg=pycalendar
</del><ins>+-e svn+http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk@14025#egg=pycalendar
</ins><span class="cx"> 
</span><span class="cx"> # Specify specific versions of our dependencies so that we're all testing the same config.
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavicalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/ical.py (14026 => 14027)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/ical.py        2014-09-30 11:58:01 UTC (rev 14026)
+++ CalendarServer/trunk/twistedcaldav/ical.py        2014-09-30 11:58:08 UTC (rev 14027)
</span><span class="lines">@@ -3733,7 +3733,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def tzexpandlocal(tzdata, start, end):
</del><ins>+def tzexpandlocal(tzdata, start, end, utc_onset=False):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Expand a timezone to get onset(local)/utc-offset-from/utc-offset-to/name observance tuples within the specified
</span><span class="cx">     time range.
</span><span class="lines">@@ -3744,6 +3744,8 @@
</span><span class="cx">     @type start: C{date}
</span><span class="cx">     @param end: date for the end of the expansion.
</span><span class="cx">     @type end: C{date}
</span><ins>+    @param utc_onset: whether or not onset values are in UTC.
+    @type utc_onset: C{bool}
</ins><span class="cx"> 
</span><span class="cx">     @return: a C{list} of tuples
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -3778,9 +3780,9 @@
</span><span class="cx">             tzcomp._pycalendar.getTimezoneOffsetSeconds(start),
</span><span class="cx">             tzcomp._pycalendar.getTimezoneDescriptor(start),
</span><span class="cx">         ))
</span><del>-    for tzstart, _ignore_utctzstart, tzoffsetfrom, tzoffsetto, name in tzexpanded:
</del><ins>+    for tzstart, utctzstart, tzoffsetfrom, tzoffsetto, name in tzexpanded:
</ins><span class="cx">         results.append((
</span><del>-            tzstart,
</del><ins>+            utctzstart if utc_onset else tzstart,
</ins><span class="cx">             tzoffsetfrom,
</span><span class="cx">             tzoffsetto,
</span><span class="cx">             name,
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavtimezonestdservicepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/timezonestdservice.py (14026 => 14027)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/timezonestdservice.py        2014-09-30 11:58:01 UTC (rev 14026)
+++ CalendarServer/trunk/twistedcaldav/timezonestdservice.py        2014-09-30 11:58:08 UTC (rev 14027)
</span><span class="lines">@@ -17,7 +17,7 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Timezone service resource and operations.
</span><span class="cx"> 
</span><del>-This is based on http://tools.ietf.org/html/draft-douglass-timezone-service which is the CalConnect
</del><ins>+This is based on http://tools.ietf.org/html/draft-ietf-tzdist-service which is the IETF
</ins><span class="cx"> proposal for a standard timezone service.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="lines">@@ -29,7 +29,7 @@
</span><span class="cx"> from txweb2 import responsecode
</span><span class="cx"> from txweb2.dav.method.propfind import http_PROPFIND
</span><span class="cx"> from txweb2.dav.noneprops import NonePropertyStore
</span><del>-from txweb2.http import HTTPError, JSONResponse
</del><ins>+from txweb2.http import HTTPError, JSONResponse, StatusResponse
</ins><span class="cx"> from txweb2.http import Response
</span><span class="cx"> from txweb2.http_headers import MimeType
</span><span class="cx"> from txweb2.stream import MemoryStream
</span><span class="lines">@@ -196,18 +196,13 @@
</span><span class="cx"> 
</span><span class="cx">     http_PROPFIND = http_PROPFIND
</span><span class="cx"> 
</span><del>-    def http_GET(self, request):
-        &quot;&quot;&quot;
-        The timezone service POST method.
-        &quot;&quot;&quot;
</del><ins>+    def http_POST(self, request):
+        raise HTTPError(StatusResponse(responsecode.NOT_ALLOWED, &quot;Method not allowed&quot;))
</ins><span class="cx"> 
</span><del>-        # GET and POST do the same thing
-        return self.http_POST(request)
</del><span class="cx"> 
</span><del>-
-    def http_POST(self, request):
</del><ins>+    def http_GET(self, request):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        The timezone service POST method.
</del><ins>+        The timezone service GET method.
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         # Check authentication and access controls
</span><span class="lines">@@ -234,6 +229,7 @@
</span><span class="cx">                 &quot;list&quot;          : self.doList,
</span><span class="cx">                 &quot;get&quot;           : self.doGet,
</span><span class="cx">                 &quot;expand&quot;        : self.doExpand,
</span><ins>+                &quot;find&quot;          : self.doFind,
</ins><span class="cx">             }.get(action, None)
</span><span class="cx"> 
</span><span class="cx">             if action is None:
</span><span class="lines">@@ -290,6 +286,12 @@
</span><span class="cx">                         {&quot;name&quot;: &quot;end&quot;, &quot;required&quot;: False, &quot;multi&quot;: False, },
</span><span class="cx">                     ],
</span><span class="cx">                 },
</span><ins>+                {
+                    &quot;name&quot;: &quot;find&quot;,
+                    &quot;parameters&quot;: [
+                        {&quot;name&quot;: &quot;pattern&quot;, &quot;required&quot;: True, &quot;multi&quot;: False, },
+                    ],
+                },
</ins><span class="cx">             ]
</span><span class="cx">         }
</span><span class="cx">         return JSONResponse(responsecode.OK, result, pretty=config.TimezoneService.PrettyPrintJSON)
</span><span class="lines">@@ -416,11 +418,14 @@
</span><span class="cx">             if len(start) &gt; 1:
</span><span class="cx">                 raise ValueError()
</span><span class="cx">             elif len(start) == 1:
</span><del>-                start = DateTime.parseText(&quot;{}0101&quot;.format(int(start[0])))
</del><ins>+                if len(start[0]) != 20:
+                    raise ValueError()
+                start = DateTime.parseText(start[0], fullISO=True)
</ins><span class="cx">             else:
</span><del>-                start = DateTime.getToday()
</del><ins>+                start = DateTime.getNowUTC()
</ins><span class="cx">                 start.setDay(1)
</span><span class="cx">                 start.setMonth(1)
</span><ins>+                start.setHHMMSS(0, 0, 0)
</ins><span class="cx">         except ValueError:
</span><span class="cx">             raise HTTPError(JSONResponse(
</span><span class="cx">                 responsecode.BAD_REQUEST,
</span><span class="lines">@@ -436,11 +441,14 @@
</span><span class="cx">             if len(end) &gt; 1:
</span><span class="cx">                 raise ValueError()
</span><span class="cx">             elif len(end) == 1:
</span><del>-                end = DateTime.parseText(&quot;{}0101&quot;.format(int(end[0])))
</del><ins>+                if len(end[0]) != 20:
+                    raise ValueError()
+                end = DateTime.parseText(end[0], fullISO=True)
</ins><span class="cx">             else:
</span><del>-                end = DateTime.getToday()
</del><ins>+                end = DateTime.getNowUTC()
</ins><span class="cx">                 end.setDay(1)
</span><span class="cx">                 end.setMonth(1)
</span><ins>+                start.setHHMMSS(0, 0, 0)
</ins><span class="cx">                 end.offsetYear(10)
</span><span class="cx">             if end &lt;= start:
</span><span class="cx">                 raise ValueError()
</span><span class="lines">@@ -469,12 +477,13 @@
</span><span class="cx">         # Now do the expansion (but use a cache to avoid re-calculating TZs)
</span><span class="cx">         observances = self.expandcache.get((tzid, start, end), None)
</span><span class="cx">         if observances is None:
</span><del>-            observances = tzexpandlocal(tzdata, start, end)
</del><ins>+            observances = tzexpandlocal(tzdata, start, end, utc_onset=True)
</ins><span class="cx">             self.expandcache[(tzid, start, end)] = observances
</span><span class="cx"> 
</span><span class="cx">         # Turn into JSON
</span><span class="cx">         result = {
</span><span class="cx">             &quot;dtstamp&quot;: self.timezones.dtstamp,
</span><ins>+            &quot;tzid&quot;: tzid,
</ins><span class="cx">             &quot;observances&quot;: [
</span><span class="cx">                 {
</span><span class="cx">                     &quot;name&quot;: name,
</span><span class="lines">@@ -487,7 +496,81 @@
</span><span class="cx">         return JSONResponse(responsecode.OK, result, pretty=config.TimezoneService.PrettyPrintJSON)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def doFind(self, request):
+        &quot;&quot;&quot;
+        Return a list of all timezones matching a pattern.
+        &quot;&quot;&quot;
</ins><span class="cx"> 
</span><ins>+        pattern = request.args.get(&quot;pattern&quot;, ())
+        if len(pattern) != 1:
+            raise HTTPError(JSONResponse(
+                responsecode.BAD_REQUEST,
+                {
+                    &quot;error&quot;: &quot;invalid-pattern&quot;,
+                    &quot;description&quot;: &quot;Invalid pattern query parameter&quot;,
+                },
+                pretty=config.TimezoneService.PrettyPrintJSON,
+            ))
+        pattern = pattern[0]
+
+        def _comp_is(pattern, s):
+            return pattern == s
+        def _comp_startswith(pattern, s):
+            return s.startswith(pattern)
+        def _comp_endswith(pattern, s):
+            return s.endswith(pattern)
+        def _comp_contains(pattern, s):
+            return pattern in s
+
+        def _normalize(s):
+            return s.replace(&quot;_&quot;, &quot; &quot;).lower()
+
+        if pattern.startswith(&quot;*&quot;) and pattern.endswith(&quot;*&quot;):
+            pattern = pattern[1:-1]
+            comparator = _comp_contains
+        elif pattern.endswith(&quot;*&quot;):
+            pattern = pattern[:-1]
+            comparator = _comp_startswith
+        elif pattern.startswith(&quot;*&quot;):
+            pattern = pattern[1:]
+            comparator = _comp_endswith
+        else:
+            comparator = _comp_is
+        pattern = _normalize(pattern)
+
+        if not pattern:
+            raise HTTPError(JSONResponse(
+                responsecode.BAD_REQUEST,
+                {
+                    &quot;error&quot;: &quot;invalid-pattern&quot;,
+                    &quot;description&quot;: &quot;Invalid pattern query parameter&quot;,
+                },
+                pretty=config.TimezoneService.PrettyPrintJSON,
+            ))
+
+        timezones = []
+        for tz in self.timezones.listTimezones(None):
+            matched = comparator(pattern, _normalize(tz.tzid))
+            if not matched:
+                for alias in tz.aliases:
+                    if comparator(pattern, _normalize(alias)):
+                        matched = True
+                        break
+            if matched:
+                timezones.append({
+                    &quot;tzid&quot;: tz.tzid,
+                    &quot;last-modified&quot;: tz.dtstamp,
+                    &quot;aliases&quot;: tz.aliases,
+                })
+
+        result = {
+            &quot;dtstamp&quot;: self.timezones.dtstamp,
+            &quot;timezones&quot;: timezones,
+        }
+        return JSONResponse(responsecode.OK, result, pretty=config.TimezoneService.PrettyPrintJSON)
+
+
+
</ins><span class="cx"> class TimezoneInfo(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Maintains information from an on-disk store of timezone files.
</span></span></pre>
</div>
</div>

</body>
</html>