[CalendarServer-changes] [2445] CalendarServer/branches/users/cdaboo/timezone-service-2436
source_changes at macosforge.org
source_changes at macosforge.org
Thu May 22 17:59:11 PDT 2008
Revision: 2445
http://trac.macosforge.org/projects/calendarserver/changeset/2445
Author: cdaboo at apple.com
Date: 2008-05-22 17:59:10 -0700 (Thu, 22 May 2008)
Log Message:
-----------
Timezone service implementation to support listing, getting and expanding timezones on the server,
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/timezone-service-2436/conf/caldavd-test.plist
CalendarServer/branches/users/cdaboo/timezone-service-2436/conf/caldavd.plist
CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/config.py
CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/customxml.py
CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/ical.py
CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/static.py
CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/tap.py
CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/test/test_timezones.py
CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/timezones.py
Added Paths:
-----------
CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/timezoneservice.py
Modified: CalendarServer/branches/users/cdaboo/timezone-service-2436/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/timezone-service-2436/conf/caldavd-test.plist 2008-05-23 00:07:16 UTC (rev 2444)
+++ CalendarServer/branches/users/cdaboo/timezone-service-2436/conf/caldavd-test.plist 2008-05-23 00:59:10 UTC (rev 2445)
@@ -344,7 +344,11 @@
<key>EnablePrivateEvents</key>
<true/>
+ <!-- Timezone Service -->
+ <key>EnableTimezoneService</key>
+ <true/>
+
<!--
Twisted
-->
Modified: CalendarServer/branches/users/cdaboo/timezone-service-2436/conf/caldavd.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/timezone-service-2436/conf/caldavd.plist 2008-05-23 00:07:16 UTC (rev 2444)
+++ CalendarServer/branches/users/cdaboo/timezone-service-2436/conf/caldavd.plist 2008-05-23 00:59:10 UTC (rev 2445)
@@ -265,6 +265,10 @@
<key>EnablePrivateEvents</key>
<true/>
+ <!-- Timezone Service -->
+ <key>EnableTimezoneService</key>
+ <true/>
+
</dict>
</plist>
Modified: CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/config.py 2008-05-23 00:07:16 UTC (rev 2444)
+++ CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/config.py 2008-05-23 00:59:10 UTC (rev 2445)
@@ -154,8 +154,9 @@
#
# Non-standard CalDAV extensions
#
- "EnableDropBox" : False, # Calendar Drop Box
- "EnablePrivateEvents": False, # Private Events
+ "EnableDropBox" : False, # Calendar Drop Box
+ "EnablePrivateEvents" : False, # Private Events
+ "EnableTimezoneService" : False, # Timezone service
#
# Implementation details
Modified: CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/customxml.py 2008-05-23 00:07:16 UTC (rev 2444)
+++ CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/customxml.py 2008-05-23 00:59:10 UTC (rev 2445)
@@ -188,6 +188,62 @@
return found
+class Timezones (davxml.WebDAVEmptyElement):
+ """
+ Denotes a timezone service resource.
+ (Apple Extension to CalDAV)
+ """
+ namespace = calendarserver_namespace
+ name = "timezones"
+
+class TZIDs (davxml.WebDAVElement):
+ """
+ Wraps a list of timezone ids.
+ """
+ namespace = calendarserver_namespace
+ name = "tzids"
+ allowed_children = { (calendarserver_namespace, "tzid" ): (0, None) }
+
+class TZID (davxml.WebDAVTextElement):
+ """
+ A timezone id.
+ """
+ namespace = calendarserver_namespace
+ name = "tzid"
+
+class TZData (davxml.WebDAVElement):
+ """
+ Wraps a list of timezone observances.
+ """
+ namespace = calendarserver_namespace
+ name = "tzdata"
+ allowed_children = { (calendarserver_namespace, "observance" ): (0, None) }
+
+class Observance (davxml.WebDAVElement):
+ """
+ A timezone observance.
+ """
+ namespace = calendarserver_namespace
+ name = "observance"
+ allowed_children = {
+ (calendarserver_namespace, "onset" ) : (1, 1),
+ (calendarserver_namespace, "utc-offset" ): (1, 1),
+ }
+
+class Onset (davxml.WebDAVTextElement):
+ """
+ The onset date-time for a DST transition.
+ """
+ namespace = calendarserver_namespace
+ name = "onset"
+
+class UTCOffset (davxml.WebDAVTextElement):
+ """
+ A UTC offset value for a timezone observance.
+ """
+ namespace = calendarserver_namespace
+ name = "utc-offset"
+
##
# Extensions to davxml.ResourceType
##
@@ -196,3 +252,4 @@
davxml.ResourceType.dropbox = davxml.ResourceType(davxml.Collection(), DropBox())
davxml.ResourceType.calendarproxyread = davxml.ResourceType(davxml.Principal(), davxml.Collection(), CalendarProxyRead())
davxml.ResourceType.calendarproxywrite = davxml.ResourceType(davxml.Principal(), davxml.Collection(), CalendarProxyWrite())
+davxml.ResourceType.timezones = davxml.ResourceType(Timezones())
Modified: CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/ical.py 2008-05-23 00:07:16 UTC (rev 2444)
+++ CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/ical.py 2008-05-23 00:59:10 UTC (rev 2445)
@@ -37,6 +37,9 @@
from vobject.base import ContentLine as vContentLine
from vobject.base import ParseError as vParseError
from vobject.icalendar import TimezoneComponent
+from vobject.icalendar import dateTimeToString
+from vobject.icalendar import deltaToOffset
+from vobject.icalendar import getTransition
from vobject.icalendar import stringToDate, stringToDateTime, stringToDurations
from vobject.icalendar import utc
@@ -1191,6 +1194,84 @@
_regex_duration = None
##
+# Timezones
+##
+
+def tzexpand(tzdata, start, end):
+ """
+ Expand a timezone to get onset/utc-offset observance tuples withinthe specified
+ time range.
+
+ @param tzdata: the iCalendar data containing a VTIMEZONE.
+ @type tzdata: C{str}
+ @param start: date for the start of the expansion.
+ @type start: C{date}
+ @param end: date for the end of the expansion.
+ @type end: C{date}
+
+ @return: a C{list} of tuples of (C{datetime}, C{str})
+ """
+
+ start = datetime.datetime.fromordinal(start.toordinal())
+ end = datetime.datetime.fromordinal(end.toordinal())
+ icalobj = Component.fromString(tzdata)
+ tzcomp = None
+ for comp in icalobj.subcomponents():
+ if comp.name() == "VTIMEZONE":
+ tzcomp = comp
+ break
+ else:
+ raise ValueError("No VTIMEZONE component in %s" % (tzdata,))
+
+ tzinfo = tzcomp.gettzinfo()
+
+ results = []
+
+ # Get the start utc-offset - that is our first value
+ results.append((dateTimeToString(start), deltaToOffset(tzinfo.utcoffset(start)),))
+ last_dt = start
+
+ while last_dt < end:
+ # Get the transitions for the current year
+ standard = getTransition("standard", last_dt.year, tzinfo)
+ daylight = getTransition("daylight", last_dt.year, tzinfo)
+
+ # Order the transitions
+ if standard and daylight:
+ if standard < daylight:
+ first = standard
+ second = daylight
+ else:
+ first = daylight
+ second = standard
+ elif standard:
+ first = standard
+ second = None
+ else:
+ first = daylight
+ second = None
+
+ for transition in (first, second):
+ # Terminate if the next transition is outside the time range
+ if transition and transition > end:
+ break
+
+ # If the next transition is after the last one, then add its info if
+ # the utc-offset actually changed.
+ if transition and transition > last_dt:
+ utcoffset = deltaToOffset(tzinfo.utcoffset(transition + datetime.timedelta(days=1)))
+ if utcoffset != results[-1][1]:
+ results.append((dateTimeToString(transition), utcoffset,))
+ last_dt = transition
+
+ # Bump last transition up to the start of the next year
+ last_dt = datetime.datetime(last_dt.year + 1, 1, 1, 0, 0, 0)
+ if last_dt >= end:
+ break
+
+ return results
+
+##
# Utilities
##
Modified: CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/static.py 2008-05-23 00:07:16 UTC (rev 2444)
+++ CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/static.py 2008-05-23 00:59:10 UTC (rev 2445)
@@ -30,6 +30,7 @@
"DropBoxHomeFile",
"DropBoxCollectionFile",
"DropBoxChildFile",
+ "TimezoneServiceFile",
]
import datetime
@@ -46,7 +47,7 @@
from twisted.web2.dav.idav import IDAVResource
from twisted.web2.dav.resource import AccessDeniedError
from twisted.web2.dav.resource import davPrivilegeSet
-from twisted.web2.dav.util import parentForURL, bindMethods, allDataFromStream
+from twisted.web2.dav.util import parentForURL, bindMethods
from twistedcaldav import caldavxml
from twistedcaldav import customxml
@@ -66,6 +67,7 @@
from twistedcaldav.directory.calendar import DirectoryCalendarHomeResource
from twistedcaldav.directory.resource import AutoProvisioningResourceMixIn
from twistedcaldav.log import Logger
+from twistedcaldav.timezoneservice import TimezoneServiceResource
from twistedcaldav.cache import CacheChangeNotifier, PropfindCacheMixin
@@ -707,6 +709,31 @@
else:
return responsecode.NOT_FOUND
+class TimezoneServiceFile (TimezoneServiceResource, CalDAVFile):
+ def __init__(self, path, parent):
+ CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
+ TimezoneServiceResource.__init__(self, parent)
+
+ assert self.fp.isfile() or not self.fp.exists()
+
+ def createSimilarFile(self, path):
+ if path == self.fp.path:
+ return self
+ else:
+ return responsecode.NOT_FOUND
+
+ def http_PUT (self, request): return responsecode.FORBIDDEN
+ def http_COPY (self, request): return responsecode.FORBIDDEN
+ def http_MOVE (self, request): return responsecode.FORBIDDEN
+ def http_DELETE (self, request): return responsecode.FORBIDDEN
+ def http_MKCOL (self, request): return responsecode.FORBIDDEN
+
+ def http_MKCALENDAR(self, request):
+ return ErrorResponse(
+ responsecode.FORBIDDEN,
+ (caldav_namespace, "calendar-collection-location-ok")
+ )
+
##
# Utilities
##
Modified: CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/tap.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/tap.py 2008-05-23 00:07:16 UTC (rev 2444)
+++ CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/tap.py 2008-05-23 00:59:10 UTC (rev 2445)
@@ -53,6 +53,7 @@
from twistedcaldav.directory.aggregate import AggregateDirectoryService
from twistedcaldav.directory.sudo import SudoDirectoryService
from twistedcaldav.static import CalendarHomeProvisioningFile
+from twistedcaldav.static import TimezoneServiceFile
from twistedcaldav.timezones import TimezoneCache
from twistedcaldav import pdmonster
@@ -450,9 +451,10 @@
# default resource classes
#
- rootResourceClass = RootResource
- principalResourceClass = DirectoryPrincipalProvisioningResource
- calendarResourceClass = CalendarHomeProvisioningFile
+ rootResourceClass = RootResource
+ principalResourceClass = DirectoryPrincipalProvisioningResource
+ calendarResourceClass = CalendarHomeProvisioningFile
+ timezoneServiceResourceClass = TimezoneServiceFile
def makeService_Slave(self, options):
#
@@ -526,6 +528,14 @@
root.putChild('principals', principalCollection)
root.putChild('calendars', calendarCollection)
+ # Timezone service is optional
+ if config.EnableTimezoneService:
+ timezoneService = self.timezoneServiceResourceClass(
+ os.path.join(config.DocumentRoot, "timezones"),
+ root
+ )
+ root.putChild('timezones', timezoneService)
+
# Configure default ACLs on the root resource
log.info("Setting up default ACEs on root resource")
Modified: CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/test/test_timezones.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/test/test_timezones.py 2008-05-23 00:07:16 UTC (rev 2444)
+++ CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/test/test_timezones.py 2008-05-23 00:59:10 UTC (rev 2445)
@@ -20,7 +20,8 @@
from twistedcaldav.ical import Component
from vobject.icalendar import utc
from vobject.icalendar import registerTzid
-from twistedcaldav.timezones import TimezoneCache
+from twistedcaldav.timezones import TimezoneCache, TimezoneException
+from twistedcaldav.timezones import readTZ, listTZs
import datetime
import os
@@ -176,3 +177,36 @@
self.assertEqual(end, datetime.datetime(2007, 12, 25, 06, 0, 0, tzinfo=utc))
break;
tzcache.unregister()
+
+class TimezonePackageTest (twistedcaldav.test.util.TestCase):
+ """
+ Timezone support tests
+ """
+
+ def test_ReadTZ(self):
+
+ self.assertTrue(readTZ("America/New_York").find("TZID:America/New_York") != -1)
+ self.assertRaises(TimezoneException, readTZ, "America/Pittsburgh")
+
+ def test_ReadTZCached(self):
+
+ self.assertTrue(readTZ("America/New_York").find("TZID:America/New_York") != -1)
+ self.assertTrue(readTZ("America/New_York").find("TZID:America/New_York") != -1)
+ self.assertRaises(TimezoneException, readTZ, "America/Pittsburgh")
+ self.assertRaises(TimezoneException, readTZ, "America/Pittsburgh")
+
+ def test_ListTZs(self):
+
+ results = listTZs()
+ self.assertTrue("America/New_York" in results)
+ self.assertTrue("Europe/London" in results)
+ self.assertTrue("GB" in results)
+
+ def test_ListTZsCached(self):
+
+ results = listTZs()
+ results = listTZs()
+ self.assertTrue("America/New_York" in results)
+ self.assertTrue("Europe/London" in results)
+ self.assertTrue("GB" in results)
+
Modified: CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/timezones.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/timezones.py 2008-05-23 00:07:16 UTC (rev 2444)
+++ CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/timezones.py 2008-05-23 00:59:10 UTC (rev 2445)
@@ -73,18 +73,15 @@
if getTzid(tzid) != None:
return False
- tzStream = openTZ(tzid)
- try:
- calendar = Component.fromStream(tzStream)
+ tzData = readTZ(tzid)
+ calendar = Component.fromString(tzData)
- if calendar.name() != "VCALENDAR":
- raise TimezoneException("%s does not contain valid iCalendar data." % (tzStream.name,))
+ if calendar.name() != "VCALENDAR":
+ raise TimezoneException("%s does not contain valid iCalendar data." % (tzStream.name,))
- # Check that we now have it cached
- if getTzid(tzid) == None:
- raise TimezoneException("Could not read timezone %s from %s." % (tzid, tzStream.name))
- finally:
- tzStream.close()
+ # Check that we now have it cached
+ if getTzid(tzid) == None:
+ raise TimezoneException("Could not read timezone %s from %s." % (tzid, tzStream.name))
return True
@@ -102,23 +99,65 @@
self.vobjectRegisterTzid(tzid, tzinfo)
try:
+ # zoneinfo never changes in a running instance so cache all this data as we use it
+ cachedTZs = {}
+ cachedTZIDs = []
+
import pkg_resources
except ImportError:
#
# We don't have pkg_resources, so assume file paths work, since that's all we have
#
+
dirname = os.path.join(os.path.dirname(__file__), "zoneinfo")
- def openTZ(tzid):
- tzpath = os.path.join(*tzid.split("/")) # Don't assume "/" from tzid is a path separator
- tzpath = os.path.join(dirname, tzpath + ".ics")
- try:
- return file(tzpath)
- except IOError:
- raise TimezoneException("Unknown time zone: %s" % (tzid,))
+ def readTZ(tzid):
+
+ if tzid not in cachedTZs:
+ tzpath = os.path.join(*tzid.split("/")) # Don't assume "/" from tzid is a path separator
+ tzpath = os.path.join(dirname, tzpath + ".ics")
+ try:
+ cachedTZs[tzid] = file(tzpath).read()
+ except IOError:
+ raise TimezoneException("Unknown time zone: %s" % (tzid,))
+
+ return cachedTZs[tzid]
+
+ def listTZs(path=""):
+ if not path and cachedTZIDs:
+ return cachedTZIDs
+
+ result = []
+ for item in os.listdir(os.path.join(dirname, path)):
+ if item.find('.') == -1:
+ result.extend(listTZs(os.path.join(path, item)))
+ elif item.endswith(".ics"):
+ result.append(os.path.join(path, item[:-4]))
+
+ if not path:
+ cachedTZIDs.extend(result)
+ return result
else:
- def openTZ(tzid):
- # Here, "/" is always the path separator
- try:
- return pkg_resources.resource_stream("twistedcaldav", "zoneinfo/%s.ics" % (tzid,))
- except IOError:
- raise TimezoneException("Unknown time zone: %s" % (tzid,))
+ def readTZ(tzid):
+ if tzid not in cachedTZs:
+ # Here, "/" is always the path separator
+ try:
+ cachedTZs[tzid] = pkg_resources.resource_stream("twistedcaldav", "zoneinfo/%s.ics" % (tzid,)).read()
+ except IOError:
+ raise TimezoneException("Unknown time zone: %s" % (tzid,))
+
+ return cachedTZs[tzid]
+
+ def listTZs(path=""):
+ if not path and cachedTZIDs:
+ return cachedTZIDs
+
+ result = []
+ for item in pkg_resources.resource_listdir("twistedcaldav", os.path.join("zoneinfo", path)):
+ if item.find('.') == -1:
+ result.extend(listTZs(os.path.join(path, item)))
+ elif item.endswith(".ics"):
+ result.append(os.path.join(path, item[:-4]))
+
+ if not path:
+ cachedTZIDs.extend(result)
+ return result
Added: CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/timezoneservice.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/timezoneservice.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezone-service-2436/twistedcaldav/timezoneservice.py 2008-05-23 00:59:10 UTC (rev 2445)
@@ -0,0 +1,212 @@
+##
+# Copyright (c) 2008 Apple Inc. 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 twistedcaldav.ical import tzexpand
+
+"""
+Timezone service resource and operations.
+"""
+
+__all__ = [
+ "TimezoneServiceResource",
+]
+
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twisted.web2 import responsecode
+from twisted.web2.dav import davxml
+from twisted.web2.dav.http import ErrorResponse
+from twisted.web2.http import HTTPError
+from twisted.web2.http import Response
+from twisted.web2.http_headers import MimeType
+from twisted.web2.stream import MemoryStream
+
+from twistedcaldav import customxml
+from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.extensions import XMLResponse
+from twistedcaldav.ical import parse_date_or_datetime
+from twistedcaldav.resource import CalDAVResource
+from twistedcaldav.timezones import TimezoneException
+from twistedcaldav.timezones import listTZs
+from twistedcaldav.timezones import readTZ
+
+class TimezoneServiceResource (CalDAVResource):
+ """
+ Timezone Service resource.
+
+ Extends L{DAVResource} to provide timezone service functionality.
+ """
+
+ def __init__(self, parent):
+ """
+ @param parent: the parent resource of this one.
+ """
+ assert parent is not None
+
+ CalDAVResource.__init__(self, principalCollections=parent.principalCollections())
+
+ self.parent = parent
+ self.cache = {}
+
+ def defaultAccessControlList(self):
+ return davxml.ACL(
+ # DAV:Read for all principals (includes anonymous)
+ davxml.ACE(
+ davxml.Principal(davxml.All()),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ ),
+ davxml.Protected(),
+ ),
+ )
+
+ def resourceType(self):
+ return davxml.ResourceType.timezones
+
+ def isCollection(self):
+ return False
+
+ def isCalendarCollection(self):
+ return False
+
+ def isPseudoCalendarCollection(self):
+ return False
+
+ def render(self, request):
+ output = """<html>
+<head>
+<title>Timezone Service Resource</title>
+</head>
+<body>
+<h1>Timezone Service Resource.</h1>
+</body
+</html>"""
+
+ response = Response(200, {}, output)
+ response.headers.setHeader("content-type", MimeType("text", "html"))
+ return response
+
+ def http_GET(self, request):
+ """
+ The timezone service POST method.
+ """
+
+ # GET and POST do the same thing
+ return self.http_POST(request)
+
+ @deferredGenerator
+ def http_POST(self, request):
+ """
+ The timezone service POST method.
+ """
+
+ # Check authentication and access controls
+ x = waitForDeferred(self.authorize(request, (davxml.Read(),)))
+ yield x
+ x.getResult()
+
+ if not request.args:
+ # Do normal GET behavior
+ yield self.render(request)
+ return
+
+ method = request.args.get("method", ("",))
+ if len(method) != 1:
+ raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-method")))
+ method = method[0]
+
+ action = {
+ "list" : self.doPOSTList,
+ "get" : self.doPOSTGet,
+ "expand" : self.doPOSTExpand,
+ }.get(method, None)
+
+ if action is None:
+ raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "supported-method")))
+
+ yield action(request)
+
+ def doPOSTList(self, request):
+ """
+ Return a list of all timezones known to the server.
+ """
+
+ tzids = listTZs()
+ tzids.sort()
+ result = customxml.TZIDs(*[customxml.TZID(tzid) for tzid in tzids])
+ return XMLResponse(responsecode.OK, result)
+
+ def doPOSTGet(self, request):
+ """
+ Return the specified timezone data.
+ """
+
+ tzid = request.args.get("tzid", ())
+ if len(tzid) != 1:
+ raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-timezone")))
+ tzid = tzid[0]
+
+ try:
+ tzdata = readTZ(tzid)
+ except TimezoneException:
+ raise HTTPError(ErrorResponse(responsecode.NOT_FOUND, (calendarserver_namespace, "timezone-available")))
+
+ response = Response()
+ response.stream = MemoryStream(tzdata)
+ response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))
+ return response
+
+ def doPOSTExpand(self, request):
+ """
+ Expand a timezone within specified start/end dates.
+ """
+
+ tzid = request.args.get("tzid", ())
+ if len(tzid) != 1:
+ raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-timezone")))
+ tzid = tzid[0]
+ try:
+ tzdata = readTZ(tzid)
+ except TimezoneException:
+ raise HTTPError(ErrorResponse(responsecode.NOT_FOUND, (calendarserver_namespace, "timezone-available")))
+
+ try:
+ start = request.args.get("start", ())
+ if len(start) != 1:
+ raise ValueError()
+ start = parse_date_or_datetime(start[0])
+ except ValueError:
+ raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-start-date")))
+
+ try:
+ end = request.args.get("end", ())
+ if len(end) != 1:
+ raise ValueError()
+ end = parse_date_or_datetime(end[0])
+ if end <= start:
+ raise ValueError()
+ except ValueError:
+ raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-end-date")))
+
+ # Now do the expansion (but use a cache to avoid re-calculating TZs)
+ observances = self.cache.get((tzid, start, end), None)
+ if observances is None:
+ observances = tzexpand(tzdata, start, end)
+ self.cache[(tzid, start, end)] = observances
+
+ # Turn into XML
+ result = customxml.TZData(
+ *[customxml.Observance(customxml.Onset(onset), customxml.UTCOffset(utc_offset)) for onset, utc_offset in observances]
+ )
+ return XMLResponse(responsecode.OK, result)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080522/a7a7ae01/attachment-0001.htm
More information about the calendarserver-changes
mailing list