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 setUp(self): super(TestTimezoneDBCache, self).setUp() # Use temp dbpath tmpdir = tempfile.mkdtemp() TimezoneDatabase.createTimezoneDatabase(tmpdir) # Save standard components to temp directory for vtz in StandardTZs: cal = Calendar() tz = cal.parseComponent(StringIO(vtz)) tzid_parts = tz.getID().split("/") if not os.path.exists(os.path.join(tmpdir, tzid_parts[0])): os.makedirs(os.path.join(tmpdir, tzid_parts[0])) with open(os.path.join(tmpdir, "{}.ics".format(tz.getID())), "w") as f: f.write(vtz)
class TimezoneDatabase(object): """ On demand timezone database cache. This scans a TZdb directory for .ics files matching a TZID and caches the component data in a calendar from whence the actual component is returned. """ sTimezoneDatabase = None @staticmethod def createTimezoneDatabase(dbpath): TimezoneDatabase.sTimezoneDatabase = TimezoneDatabase() TimezoneDatabase.sTimezoneDatabase.setPath(dbpath) @staticmethod def clearTimezoneDatabase(): if TimezoneDatabase.sTimezoneDatabase is not None: TimezoneDatabase.sTimezoneDatabase.clear() def __init__(self): from pycalendar.icalendar.calendar import Calendar self.dbpath = None self.calendar = Calendar() self.tzcache = {} self.stdtzcache = set() def setPath(self, dbpath): self.dbpath = dbpath def clear(self): from pycalendar.icalendar.calendar import Calendar self.calendar = Calendar() self.tzcache.clear() self.stdtzcache.clear() @staticmethod def getTimezoneDatabase(): if TimezoneDatabase.sTimezoneDatabase is None: TimezoneDatabase.sTimezoneDatabase = TimezoneDatabase() return TimezoneDatabase.sTimezoneDatabase @staticmethod def getTimezone(tzid): return TimezoneDatabase.getTimezoneDatabase()._getTimezone(tzid) @staticmethod def getTimezoneInCalendar(tzid): """ Return a VTIMEZONE inside a valid VCALENDAR """ tz = TimezoneDatabase.getTimezone(tzid) if tz is not None: from pycalendar.icalendar.calendar import Calendar cal = Calendar() cal.addComponent(tz.duplicate(cal)) return cal else: return None @staticmethod def getTimezoneOffsetSeconds(tzid, dt, relative_to_utc=False): # Cache it first tz = TimezoneDatabase.getTimezone(tzid) if tz is not None: return tz.getTimezoneOffsetSeconds(dt, relative_to_utc) else: return 0 @staticmethod def getTimezoneDescriptor(tzid, dt): # Cache it first tz = TimezoneDatabase.getTimezone(tzid) if tz is not None: return tz.getTimezoneDescriptor(dt) else: return "" @staticmethod def isStandardTimezone(tzid): return TimezoneDatabase.getTimezoneDatabase()._isStandardTimezone(tzid) def cacheTimezone(self, tzid): """ 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. @param tzid: the timezone identifier to load @type tzid: L{str} """ if self.dbpath is None: return tzpath = os.path.join(self.dbpath, "%s.ics" % (tzid,)) tzpath = os.path.normpath(tzpath) if tzpath.startswith(self.dbpath) and os.path.isfile(tzpath): try: with open(tzpath) as f: self.calendar.parseComponent(f) except (IOError, InvalidData): raise NoTimezoneInDatabase(self.dbpath, tzid) else: raise NoTimezoneInDatabase(self.dbpath, tzid) def addTimezone(self, tz): """ 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} """ copy = tz.duplicate(self.calendar) self.calendar.addComponent(copy) self.tzcache[copy.getID()] = copy def _addStandardTimezone(self, tz): """ 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} """ if tz.getID() not in self.tzcache: self.addTimezone(tz) self.stdtzcache.add(tz.getID()) def _isStandardTimezone(self, tzid): """ 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} """ return tzid in self.stdtzcache def _getTimezone(self, tzid): """ 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} """ 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] @staticmethod def mergeTimezones(cal, tzs): """ Merge each timezone from other calendar. """ tzdb = TimezoneDatabase.getTimezoneDatabase() # Not if our own calendar if cal is tzdb.calendar: return # Merge each timezone from other calendar for tz in tzs: tzdb.mergeTimezone(tz) def mergeTimezone(self, tz): """ If the supplied VTIMEZONE is not in our cache then store it in memory. """ if self._getTimezone(tz.getID()) is None: self.addTimezone(tz)
class TimezoneDatabase(object): """ On demand timezone database cache. This scans a TZdb directory for .ics files matching a TZID and caches the component data in a calendar from whence the actual component is returned. """ sTimezoneDatabase = None @staticmethod def createTimezoneDatabase(dbpath): TimezoneDatabase.sTimezoneDatabase = TimezoneDatabase() TimezoneDatabase.sTimezoneDatabase.setPath(dbpath) @staticmethod def clearTimezoneDatabase(): if TimezoneDatabase.sTimezoneDatabase is not None: TimezoneDatabase.sTimezoneDatabase.clear() def __init__(self): from pycalendar.icalendar.calendar import Calendar self.dbpath = None self.calendar = Calendar() self.tzcache = {} self.stdtzcache = set() def setPath(self, dbpath): self.dbpath = dbpath def clear(self): from pycalendar.icalendar.calendar import Calendar self.calendar = Calendar() self.tzcache.clear() self.stdtzcache.clear() @staticmethod def getTimezoneDatabase(): if TimezoneDatabase.sTimezoneDatabase is None: TimezoneDatabase.sTimezoneDatabase = TimezoneDatabase() return TimezoneDatabase.sTimezoneDatabase @staticmethod def getTimezone(tzid): return TimezoneDatabase.getTimezoneDatabase()._getTimezone(tzid) @staticmethod def getTimezoneInCalendar(tzid): """ Return a VTIMEZONE inside a valid VCALENDAR """ tz = TimezoneDatabase.getTimezone(tzid) if tz is not None: from pycalendar.icalendar.calendar import Calendar cal = Calendar() cal.addComponent(tz.duplicate(cal)) return cal else: return None @staticmethod def getTimezoneOffsetSeconds(tzid, dt, relative_to_utc=False): # Cache it first tz = TimezoneDatabase.getTimezone(tzid) if tz is not None: return tz.getTimezoneOffsetSeconds(dt, relative_to_utc) else: return 0 @staticmethod def getTimezoneDescriptor(tzid, dt): # Cache it first tz = TimezoneDatabase.getTimezone(tzid) if tz is not None: return tz.getTimezoneDescriptor(dt) else: return "" @staticmethod def isStandardTimezone(tzid): return TimezoneDatabase.getTimezoneDatabase()._isStandardTimezone(tzid) def cacheTimezone(self, tzid): """ 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. @param tzid: the timezone identifier to load @type tzid: L{str} """ if self.dbpath is None: return tzpath = os.path.join(self.dbpath, "%s.ics" % (tzid, )) tzpath = os.path.normpath(tzpath) if tzpath.startswith(self.dbpath) and os.path.isfile(tzpath): try: with open(tzpath) as f: self.calendar.parseComponent(f) except (IOError, InvalidData): raise NoTimezoneInDatabase(self.dbpath, tzid) else: raise NoTimezoneInDatabase(self.dbpath, tzid) def addTimezone(self, tz): """ 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} """ copy = tz.duplicate(self.calendar) self.calendar.addComponent(copy) self.tzcache[copy.getID()] = copy def _addStandardTimezone(self, tz): """ 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} """ if tz.getID() not in self.tzcache: self.addTimezone(tz) self.stdtzcache.add(tz.getID()) def _isStandardTimezone(self, tzid): """ 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} """ return tzid in self.stdtzcache def _getTimezone(self, tzid): """ 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} """ 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] @staticmethod def mergeTimezones(cal, tzs): """ Merge each timezone from other calendar. """ tzdb = TimezoneDatabase.getTimezoneDatabase() # Not if our own calendar if cal is tzdb.calendar: return # Merge each timezone from other calendar for tz in tzs: tzdb.mergeTimezone(tz) def mergeTimezone(self, tz): """ If the supplied VTIMEZONE is not in our cache then store it in memory. """ if self._getTimezone(tz.getID()) is None: self.addTimezone(tz)
def testParseComponent(self): data1 = """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n") data2 = """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//Example Inc.//Example Calendar//EN BEGIN:VTIMEZONE TZID:America/Montreal LAST-MODIFIED:20040110T032845Z BEGIN:DAYLIGHT DTSTART:20000404T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20001026T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE END:VCALENDAR """.replace("\n", "\r\n") result = """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTIMEZONE TZID:America/Montreal LAST-MODIFIED:20040110T032845Z BEGIN:DAYLIGHT DTSTART:20000404T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20001026T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n") cal = Calendar() cal.parse(StringIO.StringIO(data1)) cal.parseComponent(StringIO.StringIO(data2)) self.assertEqual(str(cal), result)