[CalendarServer-changes] [11172] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Mon May 13 12:19:53 PDT 2013


Revision: 11172
          http://trac.calendarserver.org//changeset/11172
Author:   cdaboo at apple.com
Date:     2013-05-13 12:19:53 -0700 (Mon, 13 May 2013)
Log Message:
-----------
Allow admins to update timezones via manage_timezones command line tool. This includes having timezone data stored in the
<config.DataRoot> directory.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
    CalendarServer/trunk/calendarserver/tap/util.py
    CalendarServer/trunk/calendarserver/tools/managetimezones.py
    CalendarServer/trunk/calendarserver/tools/test/deprovision/caldavd.plist
    CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist
    CalendarServer/trunk/calendarserver/tools/test/principals/caldavd.plist
    CalendarServer/trunk/conf/caldavd-test.plist
    CalendarServer/trunk/setup.py
    CalendarServer/trunk/support/Apple.make
    CalendarServer/trunk/twistedcaldav/stdconfig.py
    CalendarServer/trunk/twistedcaldav/test/test_extensions.py
    CalendarServer/trunk/twistedcaldav/test/test_timezones.py
    CalendarServer/trunk/twistedcaldav/test/util.py
    CalendarServer/trunk/twistedcaldav/timezones.py
    CalendarServer/trunk/twistedcaldav/timezonestdservice.py
    CalendarServer/trunk/twistedcaldav/zoneinfo/version.txt

Added Paths:
-----------
    CalendarServer/trunk/doc/calendarserver_manage_timezones.8

Modified: CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/test/test_caldav.py	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/calendarserver/tap/test/test_caldav.py	2013-05-13 19:19:53 UTC (rev 11172)
@@ -349,6 +349,7 @@
         self.config.Memcached.Pools.Default.ClientEnabled = False
         self.config.Memcached.Pools.Default.ServerEnabled = False
         self.config.DirectoryAddressBook.Enabled = False
+        self.config.UsePackageTimezones = True
 
         if self.configOptions:
             self.config.update(self.configOptions)

Modified: CalendarServer/trunk/calendarserver/tap/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/util.py	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/calendarserver/tap/util.py	2013-05-13 19:19:53 UTC (rev 11172)
@@ -588,6 +588,10 @@
         resource = FileResource(info["path"])
         root.putChild(name, resource)
 
+    # Need timezone cache before setting up any timezone service
+    log.info("Setting up Timezone Cache")
+    TimezoneCache.create()
+
     # Timezone service is optional
     if config.EnableTimezoneService:
         log.info("Setting up time zone service resource: %r"
@@ -678,9 +682,6 @@
     #
     # Configure ancillary data
     #
-    log.info("Setting up Timezone Cache")
-    TimezoneCache.create()
-
     log.info("Configuring authentication wrapper")
 
     overrides = {}

Modified: CalendarServer/trunk/calendarserver/tools/managetimezones.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/managetimezones.py	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/calendarserver/tools/managetimezones.py	2013-05-13 19:19:53 UTC (rev 11172)
@@ -25,6 +25,9 @@
 from twisted.internet.defer import inlineCallbacks
 from twisted.python.filepath import FilePath
 
+from calendarserver.tools.util import loadConfig
+from twistedcaldav.config import ConfigurationError
+from twistedcaldav.timezones import TimezoneCache
 from twistedcaldav.timezonestdservice import PrimaryTimezoneDatabase, \
     SecondaryTimezoneDatabase
 
@@ -36,6 +39,7 @@
 import tarfile
 import tempfile
 import urllib
+from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
 
 
 def _doPrimaryActions(action, tzpath, xmlfile, changed, tzvers):
@@ -68,10 +72,9 @@
 
     print("Downloading latest data from IANA")
     if tzvers:
-        path = "http://www.iana.org/time-zones/repository/releases/tzdata%s.tar.gz" % (tzvers,)
+        path = "https://www.iana.org/time-zones/repository/releases/tzdata%s.tar.gz" % (tzvers,)
     else:
-        path = "http://www.iana.org/time-zones/repository/tzdata-latest.tar.gz"
-        tzvers = "Latest (%s)" % (PyCalendarDateTime.getToday().getText(),)
+        path = "https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz"
     data = urllib.urlretrieve(path)
     print("Extract data at: %s" % (data[0]))
     rootdir = tempfile.mkdtemp()
@@ -79,7 +82,21 @@
     os.mkdir(zonedir)
     with tarfile.open(data[0], "r:gz") as t:
         t.extractall(zonedir)
-    print("Converting data at: %s" % (zonedir,))
+
+    # Get the version from the Makefile
+    try:
+        makefile = open(os.path.join(zonedir, "Makefile")).read()
+        lines = makefile.splitlines()
+        for line in lines:
+            if line.startswith("VERSION="):
+                tzvers = line[8:].strip()
+                break
+    except IOError:
+        pass
+
+    if not tzvers:
+        tzvers = PyCalendarDateTime.getToday().getText()
+    print("Converting data (version: %s) at: %s" % (tzvers, zonedir,))
     startYear = 1800
     endYear = PyCalendarDateTime.getToday().getYear() + 10
     PyCalendar.sProdID = "-//calendarserver.org//Zonal//EN"
@@ -106,7 +123,7 @@
     versfile = os.path.join(os.path.dirname(xmlfile), "version.txt")
     print("Updating version file at: %s" % (versfile,))
     with open(versfile, "w") as f:
-        f.write("Olson data source: %s\n" % (tzvers,))
+        f.write(TimezoneCache.IANA_VERSION_PREFIX + tzvers)
 
 
 
@@ -212,26 +229,29 @@
     print("""Usage: managetimezones [options]
 Options:
     -h            Print this help and exit
-    -f            XML file path
+    -f            config file path [REQUIRED]
+    -x            XML file path
     -z            zoneinfo file path
+    --tzvers      year/release letter of IANA data to refresh
+                  default: use the latest release
+    --url         URL or domain of secondary service
 
     # Primary service
     --refresh     refresh data from IANA
+    --refreshpkg  refresh package data from IANA
     --create      create new XML file
     --update      update XML file
     --list        list timezones in XML file
     --changed     changed since timestamp
 
-    --tzvers      year/release letter of IANA data to refresh
-                  default: use the latest release
 
     # Secondary service
-    --url         URL or domain of service
     --cache       Cache data from service
 
 Description:
-    This utility will create, update or list an XML timezone database
-    summary file, or refresh iCalendar timezone from IANA (Olson).
+    This utility will create, update, or list an XML timezone database
+    summary file, or refresh iCalendar timezone from IANA (Olson). It can
+    also be used to update the server's own zoneinfo database from IANA.
 
 """)
 
@@ -243,6 +263,7 @@
 
 
 def main():
+    configFileName = DEFAULT_CONFIG_FILE
     primary = False
     secondary = False
     action = None
@@ -251,13 +272,15 @@
     changed = None
     url = None
     tzvers = None
+    updatepkg = False
 
     # Get options
     options, _ignore_args = getopt.getopt(
         sys.argv[1:],
-        "hf:z:",
+        "f:hx:z:",
         [
             "refresh",
+            "refreshpkg",
             "create",
             "update",
             "list",
@@ -272,12 +295,18 @@
         if option == "-h":
             usage()
         elif option == "-f":
+            configFileName = value
+        elif option == "-x":
             xmlfile = value
         elif option == "-z":
             tzpath = value
         elif option == "--refresh":
             action = "refresh"
             primary = True
+        elif option == "--refreshpkg":
+            action = "refresh"
+            primary = True
+            updatepkg = True
         elif option == "--create":
             action = "create"
             primary = True
@@ -302,16 +331,29 @@
         else:
             usage("Unrecognized option: %s" % (option,))
 
+    if configFileName is None:
+        usage("A configuration file must be specified")
+    try:
+        loadConfig(configFileName)
+    except ConfigurationError, e:
+        sys.stdout.write("%s\n" % (e,))
+        sys.exit(1)
+
     if action is None:
         action = "list"
         primary = True
     if tzpath is None:
-        try:
-            import pkg_resources
-        except ImportError:
-            tzpath = os.path.join(os.path.dirname(__file__), "zoneinfo")
+        if updatepkg:
+            try:
+                import pkg_resources
+            except ImportError:
+                tzpath = os.path.join(os.path.dirname(__file__), "zoneinfo")
+            else:
+                tzpath = pkg_resources.resource_filename("twistedcaldav", "zoneinfo") #@UndefinedVariable
         else:
-            tzpath = pkg_resources.resource_filename("twistedcaldav", "zoneinfo") #@UndefinedVariable
+            # Setup the correct zoneinfo path based on the config
+            tzpath = TimezoneCache.getDBPath()
+            TimezoneCache.validatePath()
     if xmlfile is None:
         xmlfile = os.path.join(tzpath, "timezones.xml")
 

Modified: CalendarServer/trunk/calendarserver/tools/test/deprovision/caldavd.plist
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/deprovision/caldavd.plist	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/calendarserver/tools/test/deprovision/caldavd.plist	2013-05-13 19:19:53 UTC (rev 11172)
@@ -688,7 +688,10 @@
     <key>EnableTimezoneService</key>
     <true/>
 
+	<key>UsePackageTimezones</key>
+	<true/>
 
+
     <!--
         Miscellaneous items
       -->

Modified: CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist	2013-05-13 19:19:53 UTC (rev 11172)
@@ -666,6 +666,8 @@
     <key>EnableTimezoneService</key>
     <true/>
 
+	<key>UsePackageTimezones</key>
+	<true/>
 
     <!--
         Miscellaneous items

Modified: CalendarServer/trunk/calendarserver/tools/test/principals/caldavd.plist
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/principals/caldavd.plist	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/calendarserver/tools/test/principals/caldavd.plist	2013-05-13 19:19:53 UTC (rev 11172)
@@ -692,7 +692,10 @@
     <key>EnableTimezoneService</key>
     <true/>
 
+	<key>UsePackageTimezones</key>
+	<true/>
 
+
     <!--
         Miscellaneous items
       -->

Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/conf/caldavd-test.plist	2013-05-13 19:19:53 UTC (rev 11172)
@@ -850,6 +850,9 @@
     	</dict>
     </dict>
 
+	<key>UsePackageTimezones</key>
+	<true/>
+
     <!-- Batch Upload via POST -->
     <key>EnableBatchUpload</key>
     <true/>

Added: CalendarServer/trunk/doc/calendarserver_manage_timezones.8
===================================================================
--- CalendarServer/trunk/doc/calendarserver_manage_timezones.8	                        (rev 0)
+++ CalendarServer/trunk/doc/calendarserver_manage_timezones.8	2013-05-13 19:19:53 UTC (rev 11172)
@@ -0,0 +1,107 @@
+.\"
+.\" Copyright (c) 2006-2013 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.
+.\"
+.\" The following requests are required for all man pages.
+.Dd May 11, 2013
+.Dt CALENDARSERVER_MANAGE_TIMEZONES 8
+.Os
+.Sh NAME
+.Nm calendarserver_manage_timezones
+.Nd Calendar Server timezone database management utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar file
+.Op Fl h
+.Op Fl x Ar file
+.Op Fl z Ar directory
+.Op Fl -tzvers Ar tzdb-version
+.Op Fl -url Ar url
+.Op Fl -refresh
+.Op Fl -refreshpkg
+.Op Fl -create
+.Op Fl -update
+.Op Fl -list
+.Op Fl -changed Ar date
+.Op Fl -cache
+.Sh DESCRIPTION
+.Nm
+This utility will create, update, or list an XML timezone database
+summary XML file, or refresh timezone data from IANA, or refresh
+timezone data from another timezone server.
+.Pp
+It can also be used to update the server's own zoneinfo database
+from IANA.
+.Pp
+It should be run as a user with the same privileges as the Calendar
+Server itself, as it needs to read and write data that belongs to the
+server. If using the --refreshpkg option it will need to write to
+the actual python package data so will need to be run as root.
+.Pp
+Actions to perform are specified via the options below.  Only one
+action is allowed.
+.Sh OPTIONS
+.Bl -tag -width flag
+.It Fl h
+Displays usage information
+.It Fl f Ar FILE
+Use the Calendar Server configuration specified in the given file.
+Defaults to /etc/caldavd/caldavd.plist.
+.It Fl x Ar FILE
+Update the timezone database XML file at the specified location.
+Defaults to timezones.xml in the zoneinfo directory.
+.It Fl z Ar DIRECTORY
+Path to a zoneinfo directory where timezone data is stored.
+Defaults to the configuration file's Data/zoneinfo directory.
+.It Fl -tzvers Ar version
+Name of IANA timezone data version to use (e.g., '2013a').
+.It Fl -url
+If the server is configured as a secondary timezone zone, use this URL
+as the URL of the secondary server to pull timezone data from.
+.El
+.Sh ACTIONS
+.Bl -tag -width flag
+.It Fl -refresh
+Update the zoneinfo data from the IANA registry.
+.It Fl -refreshpkg
+Update the server's zoneinfo package data from the IANA registry.
+This updates twistedcaldav.zoneinfo and should only be used by
+server developers wishing to update the server repository.
+.It Fl -create
+Create a new timezone database XML file based on the timezone data
+currently in the zoneinfo directory.
+.It Fl -update
+Update the timezone database XML file based on the timezone data
+currently in the zoneinfo directory.
+.It Fl -list
+List the timezones specified in the timezone database XML file.
+.It Fl -changed Ar date
+List the timezones in the timezone database XML file that have changed
+since the specified date value (YYYYMMHH).
+.It Fl -cache
+Update the server's timezone database by pulling data from a primary
+timezone server.
+.El
+.Sh EXAMPLES
+Update the server's timezone data from the latest IANA data:
+.Pp
+.Dl "calendarserver_manage_timezones --refesh"
+.Pp
+.Sh FILES
+.Bl -tag -width flag
+.It /etc/caldavd/caldavd.plist
+The Calendar Server configuration file.
+.El
+.Sh SEE ALSO
+.Xr caldavd 8

Modified: CalendarServer/trunk/setup.py
===================================================================
--- CalendarServer/trunk/setup.py	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/setup.py	2013-05-13 19:19:53 UTC (rev 11172)
@@ -144,7 +144,7 @@
                             #"bin/calendarserver_manage_postgres",
                              "bin/calendarserver_manage_principals",
                              "bin/calendarserver_manage_push",
-                            #"bin/calendarserver_manage_timezones",
+                             "bin/calendarserver_manage_timezones",
                              "bin/calendarserver_migrate_resources",
                             #"bin/calendarserver_monitor_amp_notifications",
                             #"bin/calendarserver_monitor_notifications",

Modified: CalendarServer/trunk/support/Apple.make
===================================================================
--- CalendarServer/trunk/support/Apple.make	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/support/Apple.make	2013-05-13 19:19:53 UTC (rev 11172)
@@ -102,6 +102,7 @@
 	$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_purge_principals.8"      "$(DSTROOT)$(SIPP)$(MANDIR)/man8"
 	$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_manage_push.8"           "$(DSTROOT)$(SIPP)$(MANDIR)/man8"
 	$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_shell.8"                 "$(DSTROOT)$(SIPP)$(MANDIR)/man8"
+	$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_manage_timezones.8"      "$(DSTROOT)$(SIPP)$(MANDIR)/man8"
 	$(_v) gzip -9 -f "$(DSTROOT)$(SIPP)$(MANDIR)/man8/"*.[0-9]
 	@echo "Installing launchd config..."
 	$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(NSLOCALDIR)/$(NSLIBRARYSUBDIR)/Server/Calendar and Contacts"

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2013-05-13 19:19:53 UTC (rev 11172)
@@ -548,7 +548,7 @@
     "TimezoneService"         : {    # New standard timezone service
         "Enabled"       : False, # Overall on/off switch
         "Mode"          : "primary", # Can be "primary" or "secondary"
-        "BasePath"      : "", # Path to zoneinfo - if None use default package path
+        "BasePath"      : "", # Path to directory containing a zoneinfo - if None use default package path
                                      # secondary service MUST define its own writable path
         "XMLInfoPath"   : "", # Path to db cache info - if None use default package path
                                      # secondary service MUST define its own writable path if
@@ -564,6 +564,7 @@
     },
 
     "EnableTimezonesByReference" : False, # Strip out VTIMEZONES that are known
+    "UsePackageTimezones" : False, # Use timezone data from twistedcaldav.zoneinfo - don't copy to Data directory
 
     "EnableBatchUpload"       : True, # POST batch uploads
     "MaxResourcesBatchUpload" : 100, # Maximum number of resources in a batch POST

Modified: CalendarServer/trunk/twistedcaldav/test/test_extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_extensions.py	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/twistedcaldav/test/test_extensions.py	2013-05-13 19:19:53 UTC (rev 11172)
@@ -16,17 +16,18 @@
 ##
 
 from twext.python.filepath import CachingFilePath as FilePath
-from txdav.xml.element import WebDAVElement, ResourceType
-from txdav.xml.parser import WebDAVDocument
 from twext.web2.http_headers import MimeType
 from twext.web2.static import MetaDataMixin
 
 from twisted.internet.defer import inlineCallbacks, Deferred, succeed
-from twisted.trial.unittest import TestCase
 from twisted.web.microdom import parseString
 
 from twistedcaldav.extensions import DAVFile, DAVResourceWithChildrenMixin, extractCalendarServerPrincipalSearchData
+from twistedcaldav.test.util import TestCase
 
+from txdav.xml.element import WebDAVElement, ResourceType
+from txdav.xml.parser import WebDAVDocument
+
 from xml.etree.cElementTree import XML
 
 class UnicodeProperty(WebDAVElement):
@@ -39,6 +40,7 @@
     allowed_children = {}
 
 
+
 class StrProperty(WebDAVElement):
     """
     An element with a unicode name.
@@ -74,7 +76,7 @@
 def browserHTML2ETree(htmlString):
     """
     Loosely interpret an HTML string as XML and return an ElementTree object for it.
-    
+
     We're not promising strict XML (in fact, we're specifically saying HTML) in
     the content-type of certain responses, but it's much easier to work with
     the ElementTree data structures present in Python 2.5+ for testing; so
@@ -222,6 +224,7 @@
         self.assertEquals(result[1], ['burger'])
 
 
+
 class CalendarServerPrincipalSearchTests(TestCase):
     def test_extractCalendarServerPrincipalSearchData(self):
         """
@@ -237,13 +240,12 @@
 </B:calendarserver-principal-search>
 """
         doc = WebDAVDocument.fromString(data)
-        tokens, context, applyTo, clientLimit, propElement =  extractCalendarServerPrincipalSearchData(doc.root_element)
+        tokens, context, applyTo, clientLimit, _ignore_propElement = extractCalendarServerPrincipalSearchData(doc.root_element)
         self.assertEquals(tokens, ["morgen"])
         self.assertEquals(context, "attendee")
         self.assertFalse(applyTo)
         self.assertEquals(clientLimit, None)
 
-
         data = """<B:calendarserver-principal-search xmlns:A="DAV:" xmlns:B="http://calendarserver.org/ns/">
   <B:search-token>morgen</B:search-token>
   <B:search-token>sagen</B:search-token>
@@ -258,7 +260,7 @@
 </B:calendarserver-principal-search>
 """
         doc = WebDAVDocument.fromString(data)
-        tokens, context, applyTo, clientLimit, propElement =  extractCalendarServerPrincipalSearchData(doc.root_element)
+        tokens, context, applyTo, clientLimit, _ignore_propElement = extractCalendarServerPrincipalSearchData(doc.root_element)
         self.assertEquals(tokens, ["morgen", "sagen"])
         self.assertEquals(context, None)
         self.assertTrue(applyTo)

Modified: CalendarServer/trunk/twistedcaldav/test/test_timezones.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_timezones.py	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/twistedcaldav/test/test_timezones.py	2013-05-13 19:19:53 UTC (rev 11172)
@@ -15,6 +15,7 @@
 ##
 
 import twistedcaldav.test.util
+from twistedcaldav.config import config
 from twistedcaldav.ical import Component
 from twistedcaldav.timezones import TimezoneCache, TimezoneException
 from twistedcaldav.timezones import readTZ, listTZs
@@ -33,16 +34,18 @@
     def tearDown(self):
         TimezoneCache.clear()
         TimezoneCache.create()
-        
+
+
     def doTest(self, filename, dtstart, dtend, testEqual=True):
-        
+
         if testEqual:
             testMethod = self.assertEqual
         else:
             testMethod = self.assertNotEqual
 
         calendar = Component.fromStream(file(os.path.join(self.data_dir, filename)))
-        if calendar.name() != "VCALENDAR": self.fail("Calendar is not a VCALENDAR")
+        if calendar.name() != "VCALENDAR":
+            self.fail("Calendar is not a VCALENDAR")
 
         instances = calendar.expandTimeRanges(PyCalendarDateTime(2100, 1, 1))
         for key in instances:
@@ -51,14 +54,15 @@
             end = instance.end
             testMethod(start, dtstart)
             testMethod(end, dtend)
-            break;
+            break
 
+
     def test_truncatedApr(self):
         """
-        Properties in components
+        Custom VTZ with truncated standard time - 2006. Daylight 2007 OK.
         """
-        
-        TimezoneCache.create("")
+
+        TimezoneCache.create(empty=True)
         TimezoneCache.clear()
 
         self.doTest(
@@ -67,11 +71,13 @@
             PyCalendarDateTime(2007, 04, 01, 17, 0, 0, PyCalendarTimezone(utc=True))
         )
 
+
     def test_truncatedDec(self):
         """
-        Properties in components
+        Custom VTZ valid from 2007. Daylight 2007 OK.
         """
-        TimezoneCache.create("")
+
+        TimezoneCache.create(empty=True)
         TimezoneCache.clear()
 
         self.doTest(
@@ -80,12 +86,13 @@
             PyCalendarDateTime(2007, 12, 10, 18, 0, 0, PyCalendarTimezone(utc=True))
         )
 
+
     def test_truncatedAprThenDecFail(self):
         """
-        Properties in components
+        Custom VTZ with truncated standard time - 2006 loaded first. Daylight 2007 OK, standard 2007 wrong.
         """
 
-        TimezoneCache.create("")
+        TimezoneCache.create(empty=True)
         TimezoneCache.clear()
 
         self.doTest(
@@ -100,10 +107,12 @@
             testEqual=False
         )
 
+
     def test_truncatedAprThenDecOK(self):
         """
-        Properties in components
+        VTZ loaded from std timezone DB. 2007 OK.
         """
+
         TimezoneCache.create()
 
         self.doTest(
@@ -117,11 +126,13 @@
             PyCalendarDateTime(2007, 12, 10, 18, 0, 0, PyCalendarTimezone(utc=True)),
         )
 
+
     def test_truncatedDecThenApr(self):
         """
-        Properties in components
+        Custom VTZ valid from 2007 loaded first. Daylight 2007 OK.
         """
-        TimezoneCache.create("")
+
+        TimezoneCache.create(empty=True)
         TimezoneCache.clear()
 
         self.doTest(
@@ -135,6 +146,8 @@
             PyCalendarDateTime(2007, 04, 01, 17, 0, 0, PyCalendarTimezone(utc=True))
         )
 
+
+
 class TimezoneCacheTest (twistedcaldav.test.util.TestCase):
     """
     Timezone support tests
@@ -143,13 +156,14 @@
     data_dir = os.path.join(os.path.dirname(__file__), "data")
 
     def test_basic(self):
-        
+
         TimezoneCache.create()
         self.assertTrue(readTZ("America/New_York"))
         self.assertTrue(readTZ("US/Eastern"))
 
+
     def test_not_in_cache(self):
-        
+
         TimezoneCache.create()
 
         data = """BEGIN:VCALENDAR
@@ -183,7 +197,8 @@
 """
 
         calendar = Component.fromString(data)
-        if calendar.name() != "VCALENDAR": self.fail("Calendar is not a VCALENDAR")
+        if calendar.name() != "VCALENDAR":
+            self.fail("Calendar is not a VCALENDAR")
         instances = calendar.expandTimeRanges(PyCalendarDateTime(2100, 1, 1))
         for key in instances:
             instance = instances[key]
@@ -191,8 +206,10 @@
             end = instance.end
             self.assertEqual(start, PyCalendarDateTime(2007, 12, 25, 05, 0, 0, PyCalendarTimezone(utc=True)))
             self.assertEqual(end, PyCalendarDateTime(2007, 12, 25, 06, 0, 0, PyCalendarTimezone(utc=True)))
-            break;
+            break
 
+
+
 class TimezonePackageTest (twistedcaldav.test.util.TestCase):
     """
     Timezone support tests
@@ -201,30 +218,59 @@
     def setUp(self):
         TimezoneCache.clear()
         TimezoneCache.create()
-        
+
+
     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()
         self.assertTrue("America/New_York" in results)
         self.assertTrue("Europe/London" in results)
         self.assertTrue("GB" in results)
-        
+
+
+    def test_copyPackage(self):
+        """
+        Test that copying of the tz package works.
+        """
+
+        self.patch(config, "UsePackageTimezones", True)
+        TimezoneCache.clear()
+        TimezoneCache.create()
+
+        self.assertFalse(os.path.exists(os.path.join(config.DataRoot, "zoneinfo")))
+        self.assertFalse(os.path.exists(os.path.join(config.DataRoot, "zoneinfo", "America", "New_York.ics")))
+
+        pkg_tz = readTZ("America/New_York")
+
+        self.patch(config, "UsePackageTimezones", False)
+        TimezoneCache.clear()
+        TimezoneCache.create()
+
+        self.assertTrue(os.path.exists(os.path.join(config.DataRoot, "zoneinfo")))
+        self.assertTrue(os.path.exists(os.path.join(config.DataRoot, "zoneinfo", "America", "New_York.ics")))
+
+        copy_tz = readTZ("America/New_York")
+
+        self.assertEqual(str(pkg_tz), str(copy_tz))

Modified: CalendarServer/trunk/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/util.py	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/twistedcaldav/test/util.py	2013-05-13 19:19:53 UTC (rev 11172)
@@ -252,6 +252,7 @@
         memcacher.Memcacher.allowTestCache = True
         memcacher.Memcacher.memoryCacheInstance = None
         config.DirectoryAddressBook.Enabled = False
+        config.UsePackageTimezones = True
 
         accounts = FilePath(config.DataRoot).child("accounts.xml")
         accounts.setContent(xmlFile.getContent())
@@ -338,6 +339,7 @@
         memcacher.Memcacher.allowTestCache = True
         memcacher.Memcacher.memoryCacheInstance = None
         config.DirectoryAddressBook.Enabled = False
+        config.UsePackageTimezones = True
 
 
     @property

Modified: CalendarServer/trunk/twistedcaldav/timezones.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/timezones.py	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/twistedcaldav/timezones.py	2013-05-13 19:19:53 UTC (rev 11172)
@@ -18,6 +18,8 @@
 
 from twext.python.log import Logger
 
+from twisted.python.filepath import FilePath
+
 from twistedcaldav.config import config
 
 from pycalendar.timezonedb import PyCalendarTimezoneDatabase
@@ -47,9 +49,13 @@
 class TimezoneException(Exception):
     pass
 
+
+
 class TimezoneCache(object):
-    
+
     dirName = None
+    version = "Unknown"
+    IANA_VERSION_PREFIX = "IANA Timezone Registry: "
 
     @staticmethod
     def _getPackageDBPath():
@@ -60,22 +66,86 @@
         else:
             return pkg_resources.resource_filename("twistedcaldav", "zoneinfo") #@UndefinedVariable
 
+
     @staticmethod
     def getDBPath():
         if TimezoneCache.dirName is None:
             if config.TimezoneService.Enabled and config.TimezoneService.BasePath:
                 TimezoneCache.dirName = config.TimezoneService.BasePath
+            elif config.UsePackageTimezones:
+                TimezoneCache.dirName = TimezoneCache._getPackageDBPath()
             else:
-                TimezoneCache.dirName = TimezoneCache._getPackageDBPath()
-        
+                TimezoneCache.dirName = os.path.join(config.DataRoot, "zoneinfo")
+
         return TimezoneCache.dirName
 
+
     @staticmethod
-    def create(dbpath=None):
-        TimezoneCache.dirName = dbpath
+    def create(empty=False):
+        """
+        Create a timezone database from the files pointed to by the config. The empty option is used
+        only for testing and will point the DB at an empty directory - i.e., no standard timezones are used.
+
+        @param empty: set to C{True} when doing certain tests that do not want the standard timezone database used.
+        @type empty: C{bool}
+        """
+        if empty:
+            TimezoneCache.dirName = ""
+        else:
+            TimezoneCache.dirName = None
+            TimezoneCache.validatePath()
+        TimezoneCache.version = TimezoneCache.getTZVersion(TimezoneCache.getDBPath())
         PyCalendarTimezoneDatabase.createTimezoneDatabase(TimezoneCache.getDBPath())
-    
+
+
     @staticmethod
+    def getTZVersion(dbpath):
+        try:
+            return open(os.path.join(dbpath, "version.txt")).read().strip()
+        except IOError:
+            return TimezoneCache.IANA_VERSION_PREFIX + "Unknown"
+
+
+    class FilteredFilePath(FilePath):
+        """
+        A FilePath that does a directory copy ignoring dot-prefixed files or directories.
+        """
+
+        def copyFilteredDirectoryTo(self, destination):
+            if self.isdir():
+                if not destination.exists():
+                    destination.createDirectory()
+                for child in self.children():
+                    if child.basename()[0] != ".":
+                        destChild = destination.child(child.basename())
+                        child = TimezoneCache.FilteredFilePath(child.path)
+                        child.copyFilteredDirectoryTo(destChild)
+            elif self.isfile():
+                self.copyTo(destination)
+
+
+    @staticmethod
+    def validatePath():
+        dbpath = FilePath(TimezoneCache.getDBPath())
+        if not dbpath.exists():
+            # Move package data to the path
+            pkgpath = TimezoneCache.FilteredFilePath(TimezoneCache._getPackageDBPath())
+            log.info("Copying timezones from %s to %s" % (pkgpath.path, dbpath.path,))
+            pkgpath.copyFilteredDirectoryTo(dbpath)
+        else:
+            # Check if pkg is more recent and copy over
+            pkgversion = TimezoneCache.getTZVersion(TimezoneCache._getPackageDBPath())
+            dbversion = TimezoneCache.getTZVersion(dbpath.path)
+            if pkgversion > dbversion:
+                dbpath.remove()
+                pkgpath = TimezoneCache.FilteredFilePath(TimezoneCache._getPackageDBPath())
+                log.info("Updating timezones at %s with %s" % (dbpath.path, pkgpath.path,))
+                pkgpath.copyFilteredDirectoryTo(dbpath)
+            else:
+                log.info("Valid timezones at %s" % (dbpath.path,))
+
+
+    @staticmethod
     def clear():
         PyCalendarTimezoneDatabase.clearTimezoneDatabase()
 
@@ -94,6 +164,8 @@
         readVTZ(tzid)
     return True
 
+
+
 def addVTZ(tzid, tzcal):
     """
     Add a VTIMEZONE component to the cache.
@@ -101,37 +173,43 @@
     if tzid not in cachedVTZs:
         cachedVTZs[tzid] = tzcal
         cachedTZs[tzid] = str(tzcal)
-    
+
+
+
 def readVTZ(tzid):
     """
     Try to load the specified TZID as a calendar object from the database. Raise if not found.
     """
 
     if tzid not in cachedVTZs:
-        
+
         tzcal = PyCalendarTimezoneDatabase.getTimezoneInCalendar(tzid)
         if tzcal:
             cachedVTZs[tzid] = tzcal
         else:
             raise TimezoneException("Unknown time zone: %s" % (tzid,))
-        
+
     return cachedVTZs[tzid]
 
+
+
 def readTZ(tzid):
     """
     Try to load the specified TZID as text from the database. Raise if not found.
     """
 
     if tzid not in cachedTZs:
-        
+
         tzcal = readVTZ(tzid)
         if tzcal:
             cachedTZs[tzid] = str(tzcal)
         else:
             raise TimezoneException("Unknown time zone: %s" % (tzid,))
-        
+
     return cachedTZs[tzid]
 
+
+
 def listTZs(path=""):
     """
     List all timezones in the database.
@@ -146,7 +224,7 @@
             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

Modified: CalendarServer/trunk/twistedcaldav/timezonestdservice.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/timezonestdservice.py	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/twistedcaldav/timezonestdservice.py	2013-05-13 19:19:53 UTC (rev 11172)
@@ -98,7 +98,7 @@
             self.timezones.createNewDatabase()
         else:
             self.timezones.readDatabase()
-        self.info_source = "Primary"
+        self.info_source = TimezoneCache.version
 
 
     def _initSecondaryService(self):
@@ -250,6 +250,7 @@
 
         result = {
             "info" : {
+                "version": "1",
                 "primary-source" if self.primary else "secondary_source": self.info_source,
                 "contacts" : [],
             },

Modified: CalendarServer/trunk/twistedcaldav/zoneinfo/version.txt
===================================================================
--- CalendarServer/trunk/twistedcaldav/zoneinfo/version.txt	2013-05-13 19:02:29 UTC (rev 11171)
+++ CalendarServer/trunk/twistedcaldav/zoneinfo/version.txt	2013-05-13 19:19:53 UTC (rev 11172)
@@ -1 +1 @@
-Olson data source: Latest (20130508)
+IANA Timezone Registry: 2013c
\ No newline at end of file
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130513/cf40aefa/attachment-0001.html>


More information about the calendarserver-changes mailing list