pycalendar-2.0~svn13177/0000755000175000017500000000000012322631216014033 5ustar rahulrahulpycalendar-2.0~svn13177/LICENSE0000644000175000017500000002613610613006211015040 0ustar rahulrahul Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. pycalendar-2.0~svn13177/setup.py0000644000175000017500000000173412101017573015551 0ustar rahulrahul## # Copyright (c) 2007-2011 Cyrus Daboo. 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 distutils.core import setup, Extension setup ( name = "pycalendar", version = "2.0", description = "iCalendar/vCard Library", license = "Apache 2.0", platforms = ["any"], package_dir={'': 'src'}, packages = [ 'pycalendar', 'pycalendar.icalendar', 'pycalendar.vcard', ] ) pycalendar-2.0~svn13177/.project0000644000175000017500000000075210777427303015522 0ustar rahulrahul PyCalendar org.python.pydev.PyDevBuilder com.ibm.etools.validation.validationbuilder org.python.pydev.pythonNature pycalendar-2.0~svn13177/src/0000755000175000017500000000000012322631216014622 5ustar rahulrahulpycalendar-2.0~svn13177/src/zonal/0000755000175000017500000000000012322631216015745 5ustar rahulrahulpycalendar-2.0~svn13177/src/zonal/zone.py0000644000175000017500000004462712101017573017306 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.datetime import PyCalendarDateTime from pycalendar.vtimezone import PyCalendarVTimezone from pycalendar.property import PyCalendarProperty from pycalendar import definitions from pycalendar.vtimezonestandard import PyCalendarVTimezoneStandard from pycalendar.utcoffsetvalue import PyCalendarUTCOffsetValue import utils import rule """ Class that maintains a TZ data Zone. """ __all__ = ( "Zone", "ZoneRule", ) class Zone(object): """ A tzdata Zone object containing a set of ZoneRules """ def __init__(self): self.name = "" self.rules = [] def __str__(self): return self.generate() def __eq__(self, other): return other and ( self.name == other.name and self.rules == other.rules ) def __ne__(self, other): return not self.__eq__(other) def parse(self, lines): """ Parse the Zone lines from tzdata. @param lines: the lines to parse. @type lines: C{str} """ # Parse one line at a time splitlines = lines.split("\n") # First line is special line = splitlines[0] splits = [x for x in line.expandtabs(1).split(" ") if len(x) > 0] self.name = splits[1] rule = ZoneRule(self) rule.parse(line, 0) self.rules.append(rule) for line in splitlines[1:]: if len(line) == 0: continue rule = ZoneRule(self) rule.parse(line, 2) if rule.gmtoff != "#": self.rules.append(rule) def generate(self): """ Generate a partial Zone line. @return: a C{str} with the Rule. """ lines = [] for count, rule in enumerate(self.rules): if count == 0: items = ( "Zone " + self.name, rule.generate(), ) else: items = ( "", "", "", rule.generate(), ) lines.append("\t".join(items)) return "\n".join(lines) def expand(self, rules, minYear, maxYear): """ Expand this zone into a set of transitions. @param rules: parsed Rules for the tzdb @type rules: C{dict} @param minYear: starting year @type minYear: C{int} @param maxYear: ending year @type maxYear: C{int} @return: C{list} of C{tuple} for ( transition date-time, offset to, offset from, associated rule, ) """ # Start at 1/1/1800 with the offset from the initial zone rule start = PyCalendarDateTime(year=1800, month=1, day=1, hours=0, minutes=0, seconds=0) start_offset = self.rules[0].getUTCOffset() start_stdoffset = self.rules[0].getUTCOffset() startdt = start.duplicate() # Now add each zone rules dates transitions = [] lastUntilDateUTC = start.duplicate() last_offset = start_offset last_stdoffset = start_stdoffset first = True for zonerule in self.rules: last_offset, last_stdoffset = zonerule.expand(rules, transitions, lastUntilDateUTC, last_offset, last_stdoffset, maxYear) lastUntilDate = zonerule.getUntilDate() lastUntilDateUTC = lastUntilDate.getUTC(last_offset, last_stdoffset) # We typically don't care about the initial one if first and len(self.rules) > 1: transitions = [] first = False # Sort the results by date transitions.sort(cmp=lambda x, y: x[0].compareDateTime(y[0])) # Now scan transitions looking for real changes and note those results = [] last_transition = (startdt, start_offset, start_offset) for transition in transitions: dtutc, to_offset, zonerule, rule = transition dt = dtutc.duplicate() dt.offsetSeconds(last_transition[1]) if dtutc.getYear() >= minYear: if dt > last_transition[0]: results.append((dt, last_transition[1], to_offset, zonerule, rule)) elif dt <= last_transition[0]: if len(results): results[-1] = ((results[-1][0], results[-1][1], to_offset, zonerule, None)) else: results.append((last_transition[0], last_transition[1], last_transition[2], zonerule, None)) last_transition = (dt, to_offset, last_transition[2], rule) return results def vtimezone(self, calendar, rules, minYear, maxYear): """ Generate a VTIMEZONE for this Zone. @param calendar: the L{PyCalendar} object for the VCALENDAR in which the VTIMEZONE will be created. @param rules: the C{dict} containing the set of Rules currently defined. @param startYear: a C{int} containing the first year that should be present in the VTIMEZONE. @return: C{vtimezone} component. """ # Get a VTIMEZONE component vtz = PyCalendarVTimezone(parent=calendar) # Add TZID property vtz.addProperty(PyCalendarProperty(definitions.cICalProperty_TZID, self.name)) vtz.addProperty(PyCalendarProperty("X-LIC-LOCATION", self.name)) transitions = self.expand(rules, minYear, maxYear) # Group rules lastZoneRule = None ruleorder = [] rulemap = {} def _generateRuleData(): # Generate VTIMEZONE component for last set of rules for rule in ruleorder: if rule: # Accumulate rule portions with the same offset pairs lastOffsetPair = (rulemap[rule][0][1], rulemap[rule][0][2],) startIndex = 0 for index in xrange(len(rulemap[rule])): offsetPair = (rulemap[rule][index][1], rulemap[rule][index][2],) if offsetPair != lastOffsetPair: rule.vtimezone( vtz, lastZoneRule, rulemap[rule][startIndex][0], rulemap[rule][index - 1][0], rulemap[rule][startIndex][1], rulemap[rule][startIndex][2], index - startIndex, ) lastOffsetPair = (rulemap[rule][index][1], rulemap[rule][index][2],) startIndex = index rule.vtimezone( vtz, lastZoneRule, rulemap[rule][startIndex][0], rulemap[rule][index][0], rulemap[rule][startIndex][1], rulemap[rule][startIndex][2], len(rulemap[rule]), ) else: lastZoneRule.vtimezone( vtz, lastZoneRule, rulemap[rule][0][0], rulemap[rule][-1][0], rulemap[rule][0][1], rulemap[rule][0][2], ) del ruleorder[:] rulemap.clear() for dt, offsetfrom, offsetto, zonerule, rule in transitions: # Check for change of rule - we ignore LMT's if zonerule.format != "LMT": if lastZoneRule and lastZoneRule != zonerule: _generateRuleData() if rule not in ruleorder: ruleorder.append(rule) rulemap.setdefault(rule, []).append((dt, offsetfrom, offsetto,)) lastZoneRule = zonerule # Do left overs _generateRuleData() self._compressRDateComponents(vtz) vtz.finalise() return vtz def _compressRDateComponents(self, vtz): """ Compress sub-components with RDATEs into a single component with multiple RDATEs assuming all other properties are the same. @param vtz: the VTIMEZONE object to compress @type vtz: L{PyCalendarVTimezone} """ # Map the similar sub-components together similarMap = {} for item in vtz.mComponents: item.finalise() key = ( item.getType(), item.getTZName(), item.getUTCOffset(), item.getUTCOffsetFrom(), ) if item.hasProperty(definitions.cICalProperty_RDATE): similarMap.setdefault(key, []).append(item) # Merge similar for values in similarMap.itervalues(): if len(values) > 1: mergeTo = values[0] for mergeFrom in values[1:]: # Copy RDATE from to and remove from actual timezone prop = mergeFrom.getProperties()[definitions.cICalProperty_RDATE][0] mergeTo.addProperty(prop) vtz.mComponents.remove(mergeFrom) class ZoneRule(object): """ A specific rule for a portion of a Zone """ def __init__(self, zone): self.zone = zone self.gmtoff = 0 self.rule = "" self.format = "" self.until = None def __str__(self): return self.generate() def __eq__(self, other): return other and ( self.gmtoff == other.gmtoff and self.rule == other.rule and self.format == other.format and self.until == other.until ) def __ne__(self, other): return not self.__eq__(other) def parse(self, line, offset): """ Parse the Zone line from tzdata. @param line: a C{str} containing the line to parse. """ splits = [x for x in line.expandtabs(1).split(" ") if len(x) > 0] assert len(splits) + offset >= 5, "Help: %s" % (line,) self.gmtoff = splits[2 - offset] self.rule = splits[3 - offset] self.format = splits[4 - offset] if len(splits) >= 6 - offset: self.until = " ".join(splits[5 - offset:]) def generate(self): """ Generate a partial Zone line. @return: a C{str} with the Rule. """ items = ( self.gmtoff, self.rule, self.format, ) if self.until: items = items + (self.until,) return "\t".join(items) def getUntilDate(self): if hasattr(self, "_cached_until"): return self._cached_until year = 9999 month = 12 day = 1 hours = 0 minutes = 0 seconds = 0 mode = None if self.until and not self.until.startswith("#"): splits = self.until.split(" ") year = int(splits[0]) month = 1 day = 1 hours = 0 minutes = 0 seconds = 0 mode = None if len(splits) > 1 and not splits[1].startswith("#"): month = int(rule.Rule.MONTH_NAME_TO_POS[splits[1]]) if len(splits) > 2 and not splits[2].startswith("#"): if splits[2] == "lastSun": dt = PyCalendarDateTime(year=year, month=month, day=1) dt.setDayOfWeekInMonth(-1, PyCalendarDateTime.SUNDAY) splits[2] = dt.getDay() elif splits[2] == "lastSat": dt = PyCalendarDateTime(year=year, month=month, day=1) dt.setDayOfWeekInMonth(-1, PyCalendarDateTime.SATURDAY) splits[2] = dt.getDay() elif splits[2] == "Sun>=1": dt = PyCalendarDateTime(year=year, month=month, day=1) dt.setDayOfWeekInMonth(1, PyCalendarDateTime.SUNDAY) splits[2] = dt.getDay() day = int(splits[2]) if len(splits) > 3 and not splits[3].startswith("#"): splits = splits[3].split(":") hours = int(splits[0]) minutes = int(splits[1][:2]) if len(splits[1]) > 2: mode = splits[1][2:] else: mode = None if len(splits) > 2: seconds = int(splits[2]) dt = PyCalendarDateTime(year=year, month=month, day=day, hours=hours, minutes=minutes, seconds=seconds) self._cached_until = utils.DateTime(dt, mode) return self._cached_until def getUTCOffset(self): if hasattr(self, "_cached_utc_offset"): return self._cached_uutc_offset splits = self.gmtoff.split(":") hours = int(splits[0] if splits[0][0] != "-" else splits[0][1:]) minutes = int(splits[1]) if len(splits) > 1 else 0 seconds = int(splits[2]) if len(splits) > 2 else 0 negative = splits[0][0] == "-" self._cached_uutc_offset = ((hours * 60) + minutes) * 60 + seconds if negative: self._cached_uutc_offset = -self._cached_uutc_offset return self._cached_uutc_offset def expand(self, rules, results, lastUntilUTC, lastOffset, lastStdOffset, maxYear): # Expand the rule assert self.rule == "-" or self.rule[0].isdigit() or self.rule in rules, "No rule '%s' found in cache. %s for %s" % (self.rule, self, self.zone,) if self.rule == "-" or self.rule[0].isdigit(): return self.expand_norule(results, lastUntilUTC, maxYear) else: tempresults = [] ruleset = rules[self.rule] ruleset.expand(tempresults, self, maxYear) # Sort the results by date tempresults.sort(cmp=lambda x, y: x[0].compareDateTime(y[0])) found_one = False found_start = False last_offset = lastOffset last_stdoffset = lastStdOffset finalUntil = self.getUntilDate() for dt, to_offset, rule in tempresults: dtutc = dt.getUTC(last_offset, last_stdoffset) if dtutc >= lastUntilUTC: if not found_start and dtutc != lastUntilUTC: # Insert a start item if not found_one: last_offset = self.getUTCOffset() last_stdoffset = self.getUTCOffset() dtutc = dt.getUTC(last_offset, last_stdoffset) results.append((lastUntilUTC, last_offset, self, None)) found_start = True if dtutc >= finalUntil.getUTC(last_offset, last_stdoffset): break results.append((dtutc, to_offset, self, rule)) last_offset = to_offset last_stdoffset = self.getUTCOffset() found_one = True if found_start == 0: results.append((lastUntilUTC, last_offset, self, None)) return last_offset, last_stdoffset def expand_norule(self, results, lastUntil, maxYear): to_offset = 0 if self.rule[0].isdigit(): splits = self.rule.split(":") to_offset = 60 * 60 * int(splits[0]) if len(splits) > 1: to_offset += 60 * int(splits[1]) # Always add a transition for the start of this rule results.append((lastUntil, self.getUTCOffset() + to_offset, self, None)) return (self.getUTCOffset() + to_offset, self.getUTCOffset()) def vtimezone(self, vtz, zonerule, start, end, offsetfrom, offsetto): # Determine type of component based on offset comp = PyCalendarVTimezoneStandard(parent=vtz) # Do offsets tzoffsetfrom = PyCalendarUTCOffsetValue(offsetfrom) tzoffsetto = PyCalendarUTCOffsetValue(offsetto) comp.addProperty(PyCalendarProperty(definitions.cICalProperty_TZOFFSETFROM, tzoffsetfrom)) comp.addProperty(PyCalendarProperty(definitions.cICalProperty_TZOFFSETTO, tzoffsetto)) # Do TZNAME if self.format.find("%") != -1: tzname = self.format % ("S",) else: tzname = self.format comp.addProperty(PyCalendarProperty(definitions.cICalProperty_TZNAME, tzname)) # Do DTSTART comp.addProperty(PyCalendarProperty(definitions.cICalProperty_DTSTART, start)) # Recurrence comp.addProperty(PyCalendarProperty(definitions.cICalProperty_RDATE, start)) comp.finalise() vtz.addComponent(comp) if __name__ == '__main__': rulesdef = """Rule\tUS\t1918\t1919\t-\tMar\tlastSun\t2:00\t1:00\tD Rule\tUS\t1918\t1919\t-\tOct\tlastSun\t2:00\t0\tS Rule\tUS\t1942\tonly\t-\tFeb\t9\t2:00\t1:00\tW # War Rule\tUS\t1945\tonly\t-\tAug\t14\t23:00u\t1:00\tP # Peace Rule\tUS\t1945\tonly\t-\tSep\t30\t2:00\t0\tS Rule\tUS\t1967\t2006\t-\tOct\tlastSun\t2:00\t0\tS Rule\tUS\t1967\t1973\t-\tApr\tlastSun\t2:00\t1:00\tD Rule\tUS\t1974\tonly\t-\tJan\t6\t2:00\t1:00\tD Rule\tUS\t1975\tonly\t-\tFeb\t23\t2:00\t1:00\tD Rule\tUS\t1976\t1986\t-\tApr\tlastSun\t2:00\t1:00\tD Rule\tUS\t1987\t2006\t-\tApr\tSun>=1\t2:00\t1:00\tD Rule\tUS\t2007\tmax\t-\tMar\tSun>=8\t2:00\t1:00\tD Rule\tUS\t2007\tmax\t-\tNov\tSun>=1\t2:00\t0\tS""" rules = {} import rule ruleset = rule.RuleSet() ruleset.parse(rulesdef) rules[ruleset.name] = ruleset zonedef = """Zone America/New_York -4:56:02\t-\tLMT\t1883 Nov 18 12:03:58 \t\t\t-5:00\tUS\tE%sT\t1920 \t\t\t-5:00\tNYC\tE%sT\t1942 \t\t\t-5:00\tUS\tE%sT\t1946 \t\t\t-5:00\tNYC\tE%sT\t1967 \t\t\t-5:00\tUS\tE%sT""" zone = Zone() zone.parse(zonedef) pycalendar-2.0~svn13177/src/zonal/tzconvert.py0000755000175000017500000002200512101017573020356 0ustar rahulrahul#!/usr/bin/env python ## # Copyright (c) 2007-2012 Cyrus Daboo. 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 __future__ import with_statement from difflib import unified_diff from pycalendar.calendar import PyCalendar import cStringIO as StringIO import getopt import os import rule import sys import zone """ Classes to parse a tzdata files and generate VTIMEZONE data. """ __all__ = ( "tzconvert", ) class tzconvert(object): def __init__(self, verbose=False): self.rules = {} self.zones = {} self.links = {} self.verbose = verbose def getZoneNames(self): return set(self.zones.keys()) def parse(self, file): try: f = open(file, "r") ctr = 0 for line in f: ctr += 1 line = line[:-1] while True: if line.startswith("#") or len(line) == 0: break elif line.startswith("Rule"): self.parseRule(line) break elif line.startswith("Zone"): line = self.parseZone(line, f) if line is None: break elif line.startswith("Link"): self.parseLink(line) break elif len(line.strip()) != 0: assert False, "Could not parse line %d from tzconvert file: '%s'" % (ctr, line,) else: break except: print "Failed to parse file %s" % (file,) raise def parseRule(self, line): ruleitem = rule.Rule() ruleitem.parse(line) self.rules.setdefault(ruleitem.name, rule.RuleSet()).rules.append(ruleitem) def parseZone(self, line, f): os = StringIO.StringIO() os.write(line) last_line = None for nextline in f: nextline = nextline[:-1] if nextline.startswith("\t"): os.write("\n") os.write(nextline) elif nextline.startswith("#") or len(nextline) == 0: continue else: last_line = nextline break zoneitem = zone.Zone() zoneitem.parse(os.getvalue()) self.zones[zoneitem.name] = zoneitem return last_line def parseLink(self, line): splits = line.split() linkFrom = splits[1] linkTo = splits[2] self.links[linkTo] = linkFrom def expandZone(self, zonename, minYear, maxYear=2018): """ Expand a zones transition dates up to the specified year. """ zone = self.zones[zonename] expanded = zone.expand(self.rules, minYear, maxYear) return [(item[0], item[1], item[2],) for item in expanded] def vtimezones(self, minYear, maxYear=2018, filterzones=None): """ Generate iCalendar data for all VTIMEZONEs or just those specified """ cal = PyCalendar() for zone in self.zones.itervalues(): if filterzones and zone.name not in filterzones: continue vtz = zone.vtimezone(cal, self.rules, minYear, maxYear) cal.addComponent(vtz) return cal.getText() def generateZoneinfoFiles(self, outputdir, minYear, maxYear=2018, links=True, filterzones=None): # Empty current directory try: for root, dirs, files in os.walk(outputdir, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) except OSError: pass for zone in self.zones.itervalues(): if filterzones and zone.name not in filterzones: continue cal = PyCalendar() vtz = zone.vtimezone(cal, self.rules, minYear, maxYear) cal.addComponent(vtz) icsdata = cal.getText() fpath = os.path.join(outputdir, zone.name + ".ics") if not os.path.exists(os.path.dirname(fpath)): os.makedirs(os.path.dirname(fpath)) with open(fpath, "w") as f: f.write(icsdata) if self.verbose: print "Write path: %s" % (fpath,) if links: link_list = [] for linkTo, linkFrom in self.links.iteritems(): # Check for existing output file fromPath = os.path.join(outputdir, linkFrom + ".ics") if not os.path.exists(fromPath): print "Missing link from: %s to %s" % (linkFrom, linkTo,) continue with open(fromPath) as f: icsdata = f.read() icsdata = icsdata.replace(linkFrom, linkTo) toPath = os.path.join(outputdir, linkTo + ".ics") if not os.path.exists(os.path.dirname(toPath)): os.makedirs(os.path.dirname(toPath)) with open(toPath, "w") as f: f.write(icsdata) if self.verbose: print "Write link: %s" % (linkTo,) link_list.append("%s\t%s" % (linkTo, linkFrom,)) # Generate link mapping file linkPath = os.path.join(outputdir, "links.txt") open(linkPath, "w").write("\n".join(link_list)) def usage(error_msg=None): if error_msg: print error_msg print """Usage: tzconvert [options] [DIR] Options: -h Print this help and exit --prodid PROD-ID string to use --start Start year --end End year Arguments: DIR Directory containing an Olson tzdata directory to read, also where zoneinfo data will be written Description: This utility convert Olson-style timezone data in iCalendar. VTIMEZONE objects, one .ics file per-timezone. """ if error_msg: raise ValueError(error_msg) else: sys.exit(0) if __name__ == '__main__': # Set the PRODID value used in generated iCalendar data prodid = "-//mulberrymail.com//Zonal//EN" rootdir = "../../stuff/temp" startYear = 1800 endYear = 2018 options, args = getopt.getopt(sys.argv[1:], "h", ["prodid=", "root=", "start=", "end=", ]) for option, value in options: if option == "-h": usage() elif option == "--prodid": prodid = value elif option == "--root": rootdir = value elif option == "--start": startYear = int(value) elif option == "--end": endYear = int(value) else: usage("Unrecognized option: %s" % (option,)) # Process arguments if len(args) > 1: usage("Must have only one argument") if len(args) == 1: rootdir = os.path.expanduser(args[0]) PyCalendar.sProdID = prodid zonedir = os.path.join(rootdir, "tzdata") zonefiles = ( "northamerica", "southamerica", "europe", "africa", "asia", "australasia", "antarctica", "etcetera", "backward", ) parser = tzconvert(verbose=True) for file in zonefiles: parser.parse(os.path.join(zonedir, file)) if 1: parser.generateZoneinfoFiles(os.path.join(rootdir, "zoneinfo"), startYear, endYear, filterzones=( #"America/Montevideo", #"Europe/Paris", #"Africa/Cairo", )) if 0: checkName = "EST" parsed = parser.vtimezones(1800, 2018, filterzones=( checkName, )) icsdir = "../2008i/zoneinfo" cal = PyCalendar() for file in (checkName,): fin = open(os.path.join(icsdir, file + ".ics"), "r") cal.parse(fin) for vtz in cal.getVTimezoneDB(): #from pycalendar.vtimezoneelement import PyCalendarVTimezoneElement #vtz.mEmbedded.sort(PyCalendarVTimezoneElement.sort_dtstart) for embedded in vtz.mEmbedded: embedded.finalise() vtz.finalise() os = StringIO.StringIO() cal.generate(os, False) actual = os.getvalue() print "-- ACTUAL --" print actual print print "-- PARSED --" print parsed print print "-- DIFF --" print "\n".join([line for line in unified_diff(actual.split("\n"), parsed.split("\n"))]) pycalendar-2.0~svn13177/src/zonal/tzverify.py0000755000175000017500000001737012101017573020213 0ustar rahulrahul#!/usr/bin/env python ## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.calendar import PyCalendar from pycalendar.datetime import PyCalendarDateTime from tzconvert import tzconvert import os import sys import getopt from pycalendar.exceptions import PyCalendarInvalidData def loadCalendarFromZoneinfo(zoneinfopath, skips=(), verbose=False, quiet=False): if not quiet: print "Scanning for calendar data in: %s" % (zoneinfopath,) paths = [] def scanForICS(dirpath): for fname in os.listdir(dirpath): fpath = os.path.join(dirpath, fname) if os.path.isdir(fpath): scanForICS(fpath) elif fname.endswith(".ics"): for skip in skips: if skip in fpath: break else: if verbose: print "Found calendar data: %s" % (fpath,) paths.append(fpath) scanForICS(zoneinfopath) if not quiet: print "Parsing calendar data in: %s" % (zoneinfopath,) return loadCalendar(paths, verbose) def loadCalendar(files, verbose): cal = PyCalendar() for file in files: if verbose: print "Parsing calendar data: %s" % (file,) fin = open(file, "r") try: cal.parse(fin) except PyCalendarInvalidData, e: print "Failed to parse bad data: %s" % (e.mData,) raise return CalendarZonesWrapper(calendar=cal) def parseTZData(zonedir, zonefiles): parser = tzconvert() for file in zonefiles: zonefile = os.path.join(zonedir, file) if not os.path.exists(zonefile): print "Zone file '%s' does not exist." % (zonefile,) parser.parse(zonefile) return CalendarZonesWrapper(zones=parser) class CalendarZonesWrapper(object): def __init__(self, calendar=None, zones=None): self.calendar = calendar self.zones = zones assert self.calendar is not None or self.zones is not None def getTZIDs(self): if self.calendar: return getTZIDs(self.calendar) elif self.zones: return self.zones.getZoneNames() def expandTransitions(self, tzid, start, end): if self.calendar: return getExpandedDates(self.calendar, tzid, start, end) elif self.zones: return self.zones.expandZone(tzid, start.getYear(), end.getYear()) def compareCalendars(calendar1, calendar2, start, end, filterTzids=(), verbose=False, quiet=False): # Get all TZIDs from the calendar tzids1 = calendar1.getTZIDs() tzids2 = calendar2.getTZIDs() # Find TZIDs that do not have a corresponding zone missing = tzids1.difference(tzids2) if missing: print """TZIDs in calendar 1 not in calendar 2 files: %s These cannot be checked.""" % (", ".join(missing),) for tzid in tzids1.intersection(tzids2): if filterTzids and tzid not in filterTzids: continue if not quiet: print "\nChecking TZID: %s" % (tzid,) calendardates1 = calendar1.expandTransitions(tzid, start, end) calendardates2 = calendar2.expandTransitions(tzid, start, end) if verbose: print "Calendar 1 dates: %s" % (formattedExpandedDates(calendardates1),) print "Calendar 2 dates: %s" % (formattedExpandedDates(calendardates2),) set1 = set(calendardates1) set2 = set(calendardates2) d1 = set1.difference(set2) for i in set(d1): if i[0] == start: d1.discard(i) break if i[1] == i[2]: d1.discard(i) d2 = set2.difference(set1) for i in set(d2): if i[1] == i[2]: d2.discard(i) if d1: print "In calendar 1 but not in calendar 2 tzid=%s: %s" % (tzid, formattedExpandedDates(d1),) if d2: print "In calendar 2 but not in calendar 1 tzid=%s: %s" % (tzid, formattedExpandedDates(d2),) if not d1 and not d2 and not quiet: print "Matched: %s" % (tzid,) def getTZIDs(cal): results = set() db = cal.getVTimezoneDB() for vtz in db: tzid = vtz.getID() results.add(tzid) return results def getExpandedDates(cal, tzid, start, end): db = cal.getVTimezoneDB() return db[tzid].expandAll(start, end) def sortedList(setdata): l = list(setdata) l.sort(cmp=lambda x, y: PyCalendarDateTime.sort(x[0], y[0])) return l def formattedExpandedDates(expanded): items = sortedList([(item[0], secondsToTime(item[1]), secondsToTime(item[2]),) for item in expanded]) return ", ".join(["(%s, %s, %s)" % item for item in items]) def secondsToTime(seconds): if seconds < 0: seconds = -seconds negative = "-" else: negative = "" secs = divmod(seconds, 60)[1] mins = divmod(seconds / 60, 60)[1] hours = divmod(seconds / (60 * 60), 60)[1] if secs: return "%s%02d:%02d:%02d" % (negative, hours, mins, secs,) else: return "%s%02d:%02d" % (negative, hours, mins,) def usage(error_msg=None): if error_msg: print error_msg print """Usage: tzverify [options] DIR1 DIR2 Options: -h Print this help and exit -v Be verbose -q Be quiet --start Start year --end End year Arguments: DIR1 Directories containing two sets of zoneinfo data DIR2 to be compared Description: This utility will compare iCalendar zoneinfo hierarchies by expanding timezone transitions and comparing. """ if error_msg: raise ValueError(error_msg) else: sys.exit(0) if __name__ == '__main__': verbose = False quiet = False startYear = 1933 endYear = 2018 zonedir1 = None zonedir2 = None options, args = getopt.getopt(sys.argv[1:], "hvq", ["start=", "end=", ]) for option, value in options: if option == "-h": usage() elif option == "-v": verbose = True elif option == "-q": quiet = True elif option == "--start": startYear = int(value) elif option == "--end": endYear = int(value) else: usage("Unrecognized option: %s" % (option,)) # Process arguments if len(args) != 2: usage("Must have two arguments") zonedir1 = os.path.expanduser(args[0]) zonedir2 = os.path.expanduser(args[1]) start = PyCalendarDateTime(year=startYear, month=1, day=1) end = PyCalendarDateTime(year=endYear, month=1, day=1) zonefiles = ( "northamerica", "southamerica", "europe", "africa", "asia", "australasia", "antarctica", ) skips = ( #"Europe/Sofia", #"Africa/Cairo", ) checkcalendar1 = loadCalendarFromZoneinfo(zonedir1, skips, verbose, quiet) checkcalendar2 = loadCalendarFromZoneinfo(zonedir2, skips, verbose, quiet) compareCalendars( checkcalendar1, checkcalendar2, start, end, filterTzids=( #"America/Goose_Bay", ), verbose=verbose, quiet=quiet, ) pycalendar-2.0~svn13177/src/zonal/tests/0000755000175000017500000000000012322631216017107 5ustar rahulrahulpycalendar-2.0~svn13177/src/zonal/tests/test_zone.py0000644000175000017500000000547112101017573021501 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## import unittest from zonal.zone import Zone from zonal.rule import RuleSet from pycalendar.calendar import PyCalendar class TestZone(unittest.TestCase): def test_parse(self): zonedef = """Zone America/New_York\t-4:56:02\t-\tLMT\t1883 Nov 18 12:03:58 \t\t\t-5:00\tUS\tE%sT\t1920 \t\t\t-5:00\tNYC\tE%sT\t1942 \t\t\t-5:00\tUS\tE%sT\t1946 \t\t\t-5:00\tNYC\tE%sT\t1967 \t\t\t-5:00\tUS\tE%sT""" zone = Zone() zone.parse(zonedef) self.assertEqual(str(zone), zonedef) def test_vtimezone(self): zonedef = """Zone America/New_York\t-4:56:02\t-\tLMT\t1883 Nov 18 12:03:58 \t\t\t-5:00\tUS\tE%sT""" rules = """Rule\tUS\t1918\t1919\t-\tMar\tlastSun\t2:00\t1:00\tD Rule\tUS\t1918\t1919\t-\tOct\tlastSun\t2:00\t0\tS Rule\tUS\t1942\tonly\t-\tFeb\t9\t2:00\t1:00\tW Rule\tUS\t1945\tonly\t-\tAug\t14\t23:00u\t1:00\tP Rule\tUS\t1945\tonly\t-\tSep\t30\t2:00\t0\tS Rule\tUS\t1967\t2006\t-\tOct\tlastSun\t2:00\t0\tS Rule\tUS\t1967\t1973\t-\tApr\tlastSun\t2:00\t1:00\tD Rule\tUS\t1974\tonly\t-\tJan\t6\t2:00\t1:00\tD Rule\tUS\t1975\tonly\t-\tFeb\t23\t2:00\t1:00\tD Rule\tUS\t1976\t1986\t-\tApr\tlastSun\t2:00\t1:00\tD Rule\tUS\t1987\t2006\t-\tApr\tSun>=1\t2:00\t1:00\tD Rule\tUS\t2007\tmax\t-\tMar\tSun>=8\t2:00\t1:00\tD Rule\tUS\t2007\tmax\t-\tNov\tSun>=1\t2:00\t0\tS""" result = """BEGIN:VTIMEZONE TZID:America/New_York X-LIC-LOCATION:America/New_York BEGIN:DAYLIGHT DTSTART:20060402T020000 RDATE:20060402T020000 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20061029T020000 RDATE:20061029T020000 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD 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 """.replace("\n", "\r\n") zone = Zone() zone.parse(zonedef) ruleset = RuleSet() ruleset.parse(rules) rules = {ruleset.name: ruleset} cal = PyCalendar() vtz = zone.vtimezone(cal, rules, 2006, 2011) self.assertEqual(str(vtz), result) pycalendar-2.0~svn13177/src/zonal/tests/test_rule.py0000644000175000017500000000613412101017573021472 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## import unittest from zonal.rule import Rule, RuleSet from pycalendar.datetime import PyCalendarDateTime class TestRule(unittest.TestCase): def test_parse(self): data = ( "Rule\tGuat\t2006\tonly\t-\tOct\t1\t0:00\t0\tS", "Rule\tAlgeria\t1916\t1919\t-\tOct\tSun>=1\t23:00s\t0\t-", "Rule\tEgypt\t1945\tonly\t-\tApr\t16\t0:00\t1:00\tS", "Rule\tGhana\t1936\t1942\t-\tSep\t1\t0:00\t0:20\tGHST", ) for ruletext in data: ruleitem = Rule() ruleitem.parse(ruletext) self.assertEqual(str(ruleitem), ruletext) def test_datetimeforyear(self): data = ( ("Rule\tGuat\t2006\tonly\t-\tOct\t1\t0:00\t0\tS", 2006, PyCalendarDateTime(2006, 10, 1, 0, 0, 0), ""), ("Rule\tAlgeria\t1916\t1919\t-\tOct\tSun>=1\t23:00s\t0\t-", 1916, PyCalendarDateTime(1916, 10, 1, 23, 0, 0), "s"), ("Rule\tGhana\t1936\t1942\t-\tSep\t1\t0:00\t0:20\tGHST", 1937, PyCalendarDateTime(1937, 9, 1, 0, 0, 0), ""), ) for ruletext, year, dt, special in data: ruleitem = Rule() ruleitem.parse(ruletext) self.assertEqual(ruleitem.datetimeForYear(year), (dt, special)) def test_getoffset(self): data = ( ("Rule\tGuat\t2006\tonly\t-\tOct\t1\t0:00\t0\tS", 0), ("Rule\tEgypt\t1945\tonly\t-\tApr\t16\t0:00\t1:00\tS", 60 * 60), ("Rule\tGhana\t1936\t1942\t-\tSep\t1\t0:00\t0:20\tGHST", 20 * 60), ) for ruletext, offset in data: ruleitem = Rule() ruleitem.parse(ruletext) self.assertEqual(ruleitem.getOffset(), offset) class TestRuleSet(unittest.TestCase): def test_parse(self): data = """Rule\tUS\t1918\t1919\t-\tMar\tlastSun\t2:00\t1:00\tD Rule\tUS\t1918\t1919\t-\tOct\tlastSun\t2:00\t0\tS Rule\tUS\t1942\tonly\t-\tFeb\t9\t2:00\t1:00\tW Rule\tUS\t1945\tonly\t-\tAug\t14\t23:00u\t1:00\tP Rule\tUS\t1945\tonly\t-\tSep\t30\t2:00\t0\tS Rule\tUS\t1967\t2006\t-\tOct\tlastSun\t2:00\t0\tS Rule\tUS\t1967\t1973\t-\tApr\tlastSun\t2:00\t1:00\tD Rule\tUS\t1974\tonly\t-\tJan\t6\t2:00\t1:00\tD Rule\tUS\t1975\tonly\t-\tFeb\t23\t2:00\t1:00\tD Rule\tUS\t1976\t1986\t-\tApr\tlastSun\t2:00\t1:00\tD Rule\tUS\t1987\t2006\t-\tApr\tSun>=1\t2:00\t1:00\tD Rule\tUS\t2007\tmax\t-\tMar\tSun>=8\t2:00\t1:00\tD Rule\tUS\t2007\tmax\t-\tNov\tSun>=1\t2:00\t0\tS""" ruleset = RuleSet() ruleset.parse(data) self.assertEqual(str(ruleset), data) pycalendar-2.0~svn13177/src/zonal/tests/__init__.py0000644000175000017500000000120212101017573021212 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## pycalendar-2.0~svn13177/src/zonal/utils.py0000644000175000017500000000313312101017573017456 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## class DateTime(object): """ A date-time object that wraps the tzdb wall-clock/utc style date-time information and that can generate appropriate localtime or UTC offsets based on Zone/Rule offsets. """ def __init__(self, dt, mode): self.dt = dt self.mode = mode def __repr__(self): return str(self.dt) def compareDateTime(self, other): return self.dt.compareDateTime(other.dt) def getLocaltime(self, offset, stdoffset): new_dt = self.dt.duplicate() if self.mode == "u": new_dt.offsetSeconds(offset) elif self.mode == "s": new_dt.offsetSeconds(-stdoffset + offset) return new_dt def getUTC(self, offset, stdoffset): new_dt = self.dt.duplicate() if self.mode == "u": pass elif self.mode == "s": new_dt.offsetSeconds(-stdoffset) else: new_dt.offsetSeconds(-offset) return new_dt pycalendar-2.0~svn13177/src/zonal/tzdump.py0000755000175000017500000000671312101017573017653 0ustar rahulrahul#!/usr/bin/env python ## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.calendar import PyCalendar from pycalendar.datetime import PyCalendarDateTime import os import sys import getopt from pycalendar.exceptions import PyCalendarInvalidData def loadCalendar(file, verbose): cal = PyCalendar() if verbose: print "Parsing calendar data: %s" % (file,) fin = open(file, "r") try: cal.parse(fin) except PyCalendarInvalidData, e: print "Failed to parse bad data: %s" % (e.mData,) raise return cal def getExpandedDates(cal, start, end): vtz = cal.getComponents()[0] expanded = vtz.expandAll(start, end) expanded.sort(cmp=lambda x, y: PyCalendarDateTime.sort(x[0], y[0])) return expanded def formattedExpandedDates(expanded): items = [] for item in expanded: utc = item[0].duplicate() utc.setTimezoneUTC(True) utc.offsetSeconds(-item[1]) items.append((item[0], utc, secondsToTime(item[1]), secondsToTime(item[2]),)) return "\n".join(["(%s, %s, %s, %s)" % item for item in items]) def secondsToTime(seconds): if seconds < 0: seconds = -seconds negative = "-" else: negative = "" secs = divmod(seconds, 60)[1] mins = divmod(seconds / 60, 60)[1] hours = divmod(seconds / (60 * 60), 60)[1] if secs: return "%s%02d:%02d:%02d" % (negative, hours, mins, secs,) else: return "%s%02d:%02d" % (negative, hours, mins,) def usage(error_msg=None): if error_msg: print error_msg print """Usage: tzdump [options] FILE Options: -h Print this help and exit -v Be verbose --start Start year --end End year Arguments: FILE iCalendar file containing a single VTIMEZONE Description: This utility will dump the transitions in a VTIMEZONE over the request time range. """ if error_msg: raise ValueError(error_msg) else: sys.exit(0) if __name__ == '__main__': verbose = False startYear = 1918 endYear = 2018 fpath = None options, args = getopt.getopt(sys.argv[1:], "hv", ["start=", "end=", ]) for option, value in options: if option == "-h": usage() elif option == "-v": verbose = True elif option == "--start": startYear = int(value) elif option == "--end": endYear = int(value) else: usage("Unrecognized option: %s" % (option,)) # Process arguments if len(args) != 1: usage("Must have one argument") fpath = os.path.expanduser(args[0]) start = PyCalendarDateTime(year=startYear, month=1, day=1) end = PyCalendarDateTime(year=endYear, month=1, day=1) cal = loadCalendar(fpath, verbose) dates = getExpandedDates(cal, start, end) print formattedExpandedDates(dates) pycalendar-2.0~svn13177/src/zonal/__init__.py0000644000175000017500000000120212101017573020050 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## pycalendar-2.0~svn13177/src/zonal/rule.py0000644000175000017500000004624712101017573017302 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar.datetime import PyCalendarDateTime from pycalendar.utils import daysInMonth from pycalendar.vtimezonestandard import PyCalendarVTimezoneStandard from pycalendar.utcoffsetvalue import PyCalendarUTCOffsetValue from pycalendar.vtimezonedaylight import PyCalendarVTimezoneDaylight from pycalendar.property import PyCalendarProperty from pycalendar.recurrence import PyCalendarRecurrence import utils """ Class that maintains a TZ data Rule. """ __all__ = ( "Rule", "RuleSet", ) class RuleSet(object): """ A set of tzdata rules tied to a specific Rule name """ def __init__(self): self.name = "" self.rules = [] def __str__(self): return self.generate() def __eq__(self, other): return other and ( self.name == other.name and self.rules == other.rules ) def __ne__(self, other): return not self.__eq__(other) def parse(self, lines): """ Parse the set of Rule lines from tzdata. @param lines: the lines to parse. @type lines: C{str} """ splitlines = lines.split("\n") for line in splitlines: splits = line.expandtabs(1).split(" ") name = splits[1] assert name, "Must have a zone name: '%s'" % (lines,) if not self.name: self.name = name assert self.name == name, "Different zone names %s and %s: %s" % (self.name, name,) rule = Rule() rule.parse(line) self.rules.append(rule) def generate(self): """ Generate a Rule line. @return: a C{str} with the Rule. """ items = [rule.generate() for rule in self.rules] return "\n".join(items) def expand(self, results, zoneinfo, maxYear): """ Expand the set of rules into transition/offset pairs for the entire RuleSet starting at the beginning and going up to maxYear at most. @param results: list to append results to @type results: C{list} @param zoneinfo: the Zone in which this RuleSet is being used @type zoneinfo: L{ZoneRule} @param maxYear: the maximum year to expand out to @type maxYear: C{int} """ for rule in self.rules: rule.expand(results, zoneinfo, maxYear) class Rule(object): """ A tzdata Rule """ # Some useful mapping tables LASTDAY_NAME_TO_DAY = { "lastSun": PyCalendarDateTime.SUNDAY, "lastMon": PyCalendarDateTime.MONDAY, "lastTue": PyCalendarDateTime.TUESDAY, "lastWed": PyCalendarDateTime.WEDNESDAY, "lastThu": PyCalendarDateTime.THURSDAY, "lastFri": PyCalendarDateTime.FRIDAY, "lastSat": PyCalendarDateTime.SATURDAY, } DAY_NAME_TO_DAY = { "Sun": PyCalendarDateTime.SUNDAY, "Mon": PyCalendarDateTime.MONDAY, "Tue": PyCalendarDateTime.TUESDAY, "Wed": PyCalendarDateTime.WEDNESDAY, "Thu": PyCalendarDateTime.THURSDAY, "Fri": PyCalendarDateTime.FRIDAY, "Sat": PyCalendarDateTime.SATURDAY, } LASTDAY_NAME_TO_RDAY = { "lastSun": definitions.eRecurrence_WEEKDAY_SU, "lastMon": definitions.eRecurrence_WEEKDAY_MO, "lastTue": definitions.eRecurrence_WEEKDAY_TU, "lastWed": definitions.eRecurrence_WEEKDAY_WE, "lastThu": definitions.eRecurrence_WEEKDAY_TH, "lastFri": definitions.eRecurrence_WEEKDAY_FR, "lastSat": definitions.eRecurrence_WEEKDAY_SA, } DAY_NAME_TO_RDAY = { PyCalendarDateTime.SUNDAY: definitions.eRecurrence_WEEKDAY_SU, PyCalendarDateTime.MONDAY: definitions.eRecurrence_WEEKDAY_MO, PyCalendarDateTime.TUESDAY: definitions.eRecurrence_WEEKDAY_TU, PyCalendarDateTime.WEDNESDAY: definitions.eRecurrence_WEEKDAY_WE, PyCalendarDateTime.THURSDAY: definitions.eRecurrence_WEEKDAY_TH, PyCalendarDateTime.FRIDAY: definitions.eRecurrence_WEEKDAY_FR, PyCalendarDateTime.SATURDAY: definitions.eRecurrence_WEEKDAY_SA, } MONTH_NAME_TO_POS = { "Jan": 1, "Feb": 2, "Mar": 3, "Apr": 4, "May": 5, "Jun": 6, "Jul": 7, "Aug": 8, "Sep": 9, "Oct": 10, "Nov": 11, "Dec": 12, } MONTH_POS_TO_NAME = ("", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec") def __init__(self): self.name = "" self.fromYear = "" self.toYear = "" self.type = "-" self.inMonth = 0 self.onDay = "" self.atTime = 0 self.saveTime = 0 self.letter = "" def __str__(self): return self.generate() def __eq__(self, other): return other and ( self.name == other.name and self.fromYear == other.fromYear and self.toYear == other.toYear and self.type == other.type and self.inMonth == other.inMonth and self.onDay == other.onDay and self.atTime == other.atTime and self.saveTime == other.saveTime and self.letter == other.letter ) def __ne__(self, other): return not self.__eq__(other) def parse(self, line): """ Parse the Rule line from tzdata. @param line: the line to parse. @type line: C{str} """ # Simply split the bits up and store them in various properties splits = [x for x in line.expandtabs(1).split(" ") if len(x) > 0] assert len(splits) >= 10, "Wrong number of fields in Rule: '%s'" % (line,) self.name = splits[1] self.fromYear = splits[2] self.toYear = splits[3] self.type = splits[4] self.inMonth = splits[5] self.onDay = splits[6] self.atTime = splits[7] self.saveTime = splits[8] self.letter = splits[9] def generate(self): """ Generate a Rule line. @return: a C{str} with the Rule. """ items = ( "Rule", self.name, self.fromYear, self.toYear, self.type, self.inMonth, self.onDay, self.atTime, self.saveTime, self.letter, ) return "\t".join(items) def getOffset(self): """ Return the specified rule offset in seconds. @return: C{int} """ splits = self.saveTime.split(":") hours = int(splits[0]) if len(splits) == 2: minutes = int(splits[1]) else: minutes = 0 negative = hours < 0 if negative: return -((-hours * 60) + minutes) * 60 else: return ((hours * 60) + minutes) * 60 def startYear(self): return int(self.fromYear) def endYear(self): if self.toYear == "only": return self.startYear() elif self.toYear == "max": return 9999 else: return int(self.toYear) def datetimeForYear(self, year): """ Given a specific year, determine the actual date/time of the transition @param year: the year to determine the transition for @type year: C{int} @return: C{tuple} of L{PyCalendarDateTime} and C{str} (which is the special tzdata mode character """ # Create a floating date-time dt = PyCalendarDateTime() # Setup base year/month/day dt.setYear(year) dt.setMonth(Rule.MONTH_NAME_TO_POS[self.inMonth]) dt.setDay(1) # Setup base hours/minutes splits = self.atTime.split(":") if len(splits) == 1: splits.append("0") assert len(splits) == 2, "atTime format is wrong: %s, %s" % (self.atTime, self,) hours = int(splits[0]) if len(splits[1]) > 2: minutes = int(splits[1][:2]) special = splits[1][2:] else: minutes = int(splits[1]) special = "" # Special case for 24:00 if hours == 24 and minutes == 0: dt.setHours(23) dt.setMinutes(59) dt.setSeconds(59) else: dt.setHours(hours) dt.setMinutes(minutes) # Now determine the actual start day if self.onDay in Rule.LASTDAY_NAME_TO_DAY: dt.setDayOfWeekInMonth(-1, Rule.LASTDAY_NAME_TO_DAY[self.onDay]) elif self.onDay.find(">=") != -1: splits = self.onDay.split(">=") dt.setNextDayOfWeek(int(splits[1]), Rule.DAY_NAME_TO_DAY[splits[0]]) else: try: day = int(self.onDay) dt.setDay(day) except: assert False, "onDay value is not recognized: %s" % (self.onDay,) return dt, special def getOnDayDetails(self, start, indicatedDay, indicatedOffset): """ Get RRULE BYxxx part items from the Rule data. @param start: start date-time for the recurrence set @type start: L{PyCalendarDateTime} @param indicatedDay: the day that the Rule indicates for recurrence @type indicatedDay: C{int} @param indicatedOffset: the offset that the Rule indicates for recurrence @type indicatedOffset: C{int} """ month = start.getMonth() year = start.getYear() dayOfWeek = start.getDayOfWeek() # Need to check whether day has changed due to time shifting # e.g. if "u" mode is specified, the actual localtime may be # shifted to the previous day if the offset is negative if indicatedDay != dayOfWeek: difference = dayOfWeek - indicatedDay if difference in (1, -6,): indicatedOffset += 1 # Adjust the month down too if needed if start.getDay() == 1: month -= 1 if month < 1: month = 12 elif difference in (-1, 6,): assert indicatedOffset != 1, "Bad RRULE adjustment" indicatedOffset -= 1 else: assert False, "Unknown RRULE adjustment" try: # Form the appropriate RRULE bits day = Rule.DAY_NAME_TO_RDAY[dayOfWeek] offset = indicatedOffset bymday = None if offset == 1: offset = 1 elif offset == 8: offset = 2 elif offset == 15: offset = 3 elif offset == 22: offset = 4 else: days_in_month = daysInMonth(month, year) if days_in_month - offset == 6: offset = -1 elif days_in_month - offset == 13: offset = -2 elif days_in_month - offset == 20: offset = -3 else: bymday = [offset + i for i in range(7) if (offset + i) <= days_in_month] offset = 0 except: assert False, "onDay value is not recognized: %s" % (self.onDay,) return offset, day, bymday def expand(self, results, zoneinfo, maxYear): """ Expand the Rule into a set of transition date/offset pairs @param results: list to append results to @type results: C{list} @param zoneinfo: the Zone in which this RuleSet is being used @type zoneinfo: L{ZoneRule} @param maxYear: the maximum year to expand out to @type maxYear: C{int} """ if self.startYear() >= maxYear: return self.fullExpand(maxYear) zoneoffset = zoneinfo.getUTCOffset() offset = self.getOffset() for dt in self.dt_cache: results.append((dt, zoneoffset + offset, self)) def fullExpand(self, maxYear): """ Do a full recurrence expansion for each year in the Rule's range, upto a specified maximum. @param maxYear: maximum year to expand to @type maxYear: C{int} """ if hasattr(self, "dt_cache"): return self.dt_cache start = self.startYear() end = self.endYear() if end > maxYear: end = maxYear - 1 self.dt_cache = [] for year in xrange(start, end + 1): dt = utils.DateTime(*self.datetimeForYear(year)) self.dt_cache.append(dt) def vtimezone(self, vtz, zonerule, start, end, offsetfrom, offsetto, instanceCount): """ Generate a VTIMEZONE sub-component for this Rule. @param vtz: VTIMEZONE to add to @type vtz: L{PyCalendarVTimezone} @param zonerule: the Zone rule line being used @type zonerule: L{ZoneRule} @param start: the start time for the first instance @type start: L{PyCalendarDateTime} @param end: the start time for the last instance @type end: L{PyCalendarDateTime} @param offsetfrom: the UTC offset-from @type offsetfrom: C{int} @param offsetto: the UTC offset-to @type offsetto: C{int} @param instanceCount: the number of instances in the set @type instanceCount: C{int} """ # Determine type of component based on offset dstoffset = self.getOffset() if dstoffset == 0: comp = PyCalendarVTimezoneStandard(parent=vtz) else: comp = PyCalendarVTimezoneDaylight(parent=vtz) # Do offsets tzoffsetfrom = PyCalendarUTCOffsetValue(offsetfrom) tzoffsetto = PyCalendarUTCOffsetValue(offsetto) comp.addProperty(PyCalendarProperty(definitions.cICalProperty_TZOFFSETFROM, tzoffsetfrom)) comp.addProperty(PyCalendarProperty(definitions.cICalProperty_TZOFFSETTO, tzoffsetto)) # Do TZNAME if zonerule.format.find("%") != -1: tzname = zonerule.format % (self.letter if self.letter != "-" else "",) else: tzname = zonerule.format comp.addProperty(PyCalendarProperty(definitions.cICalProperty_TZNAME, tzname)) # Do DTSTART comp.addProperty(PyCalendarProperty(definitions.cICalProperty_DTSTART, start)) # Now determine the recurrences (use RDATE if only one year or # number of instances is one) if self.toYear != "only" and instanceCount != 1: rrule = PyCalendarRecurrence() rrule.setFreq(definitions.eRecurrence_YEARLY) rrule.setByMonth((Rule.MONTH_NAME_TO_POS[self.inMonth],)) if self.onDay in Rule.LASTDAY_NAME_TO_RDAY: # Need to check whether day has changed due to time shifting dayOfWeek = start.getDayOfWeek() indicatedDay = Rule.LASTDAY_NAME_TO_DAY[self.onDay] if dayOfWeek == indicatedDay: rrule.setByDay(((-1, Rule.LASTDAY_NAME_TO_RDAY[self.onDay]),)) elif dayOfWeek < indicatedDay or dayOfWeek == 6 and indicatedDay == 0: # This is OK as we have moved back a day and thus no month transition # could have occurred fakeOffset = daysInMonth(start.getMonth(), start.getYear()) - 6 offset, rday, bymday = self.getOnDayDetails(start, indicatedDay, fakeOffset) if bymday: rrule.setByMonthDay(bymday) rrule.setByDay(((offset, rday),)) else: # This is bad news as we have moved forward a day possibly into the next month # What we do is switch to using a BYYEARDAY rule with offset from the end of the year rrule.setByMonth(()) daysBackStartOfMonth = ( 365, 334, 306, 275, 245, 214, 184, 153, 122, 92, 61, 31, 0 # Does not account for leap year ) rrule.setByYearDay([-(daysBackStartOfMonth[Rule.MONTH_NAME_TO_POS[self.inMonth]] + i) for i in range(7)]) rrule.setByDay( ((0, divmod(Rule.LASTDAY_NAME_TO_DAY[self.onDay] + 1, 7)[1]),), ) elif self.onDay.find(">=") != -1: indicatedDay, dayoffset = self.onDay.split(">=") # Need to check whether day has changed due to time shifting dayOfWeek = start.getDayOfWeek() indicatedDay = Rule.DAY_NAME_TO_DAY[indicatedDay] if dayOfWeek == indicatedDay: offset, rday, bymday = self.getOnDayDetails(start, indicatedDay, int(dayoffset)) if bymday: rrule.setByMonthDay(bymday) rrule.setByDay(((offset, rday),)) elif dayoffset == 1 and divmod(dayoffset - indicatedDay, 7)[1] == 6: # This is bad news as we have moved backward a day possibly into the next month # What we do is switch to using a BYYEARDAY rule with offset from the end of the year rrule.setByMonth(()) daysBackStartOfMonth = ( 365, 334, 306, 275, 245, 214, 184, 153, 122, 92, 61, 31, 0 # Does not account for leap year ) rrule.setByYearDay([-(daysBackStartOfMonth[Rule.MONTH_NAME_TO_POS[self.inMonth]] + i) for i in range(7)]) rrule.setByDay( ((0, divmod(indicatedDay + 1, 7)[1]),), ) else: # This is OK as we have moved forward a day and thus no month transition # could have occurred offset, rday, bymday = self.getOnDayDetails(start, indicatedDay, int(dayoffset)) if bymday: rrule.setByMonthDay(bymday) rrule.setByDay(((offset, rday),)) else: try: _ignore_day = int(self.onDay) except: assert False, "onDay value is not recognized: %s" % (self.onDay,) # Add any UNTIL if zonerule.getUntilDate().dt.getYear() < 9999 or self.endYear() < 9999: until = end.duplicate() until.offsetSeconds(-offsetfrom) until.setTimezoneUTC(True) rrule.setUseUntil(True) rrule.setUntil(until) comp.addProperty(PyCalendarProperty(definitions.cICalProperty_RRULE, rrule)) else: comp.addProperty(PyCalendarProperty(definitions.cICalProperty_RDATE, start)) comp.finalise() vtz.addComponent(comp) pycalendar-2.0~svn13177/src/pycalendar/0000755000175000017500000000000012322631216016744 5ustar rahulrahulpycalendar-2.0~svn13177/src/pycalendar/datetime.py0000644000175000017500000011047012320545600021114 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar import locale from pycalendar import utils from pycalendar import xmldefs from pycalendar.duration import PyCalendarDuration from pycalendar.parser import ParserContext from pycalendar.timezone import PyCalendarTimezone from pycalendar.valueutils import ValueMixin import cStringIO as StringIO import time import xml.etree.cElementTree as XML class PyCalendarDateTime(ValueMixin): SUNDAY = 0 MONDAY = 1 TUESDAY = 2 WEDNESDAY = 3 THURSDAY = 4 FRIDAY = 5 SATURDAY = 6 FULLDATE = 0 ABBREVDATE = 1 NUMERICDATE = 2 FULLDATENOYEAR = 3 ABBREVDATENOYEAR = 4 NUMERICDATENOYEAR = 5 @staticmethod def sort(e1, e2): return e1.compareDateTime(e2) def __init__(self, year=None, month=None, day=None, hours=None, minutes=None, seconds=None, tzid=None, utcoffset=None): self.mYear = 1970 self.mMonth = 1 self.mDay = 1 self.mHours = 0 self.mMinutes = 0 self.mSeconds = 0 self.mDateOnly = False self.mTZUTC = False self.mTZID = None self.mTZOffset = None self.mPosixTimeCached = False self.mPosixTime = 0 if (year is not None) and (month is not None) and (day is not None): self.mYear = year self.mMonth = month self.mDay = day if (hours is not None) and (minutes is not None) and (seconds is not None): self.mHours = hours self.mMinutes = minutes self.mSeconds = seconds else: self.mDateOnly = True if tzid: self.mTZUTC = tzid.getUTC() self.mTZID = tzid.getTimezoneID() def duplicate(self): other = PyCalendarDateTime(self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds) other.mDateOnly = self.mDateOnly other.mTZUTC = self.mTZUTC other.mTZID = self.mTZID other.mTZOffset = self.mTZOffset other.mPosixTimeCached = self.mPosixTimeCached other.mPosixTime = self.mPosixTime return other def duplicateAsUTC(self): other = self.duplicate() other.adjustToUTC() return other def __repr__(self): return "PyCalendarDateTime: %s" % (self.getText(),) def __hash__(self): return hash(self.getPosixTime()) # Operators def __add__(self, duration): # Add duration seconds to temp object and normalise it result = self.duplicate() result.mSeconds += duration.getTotalSeconds() result.normalise() return result def __sub__(self, dateorduration): if isinstance(dateorduration, PyCalendarDateTime): date = dateorduration # Look for floating if self.floating() or date.floating(): # Adjust the floating ones to fixed copy1 = self.duplicate() copy2 = date.duplicate() if copy1.floating() and copy2.floating(): # Set both to UTC and do comparison copy1.setTimezoneUTC(True) copy2.setTimezoneUTC(True) return copy1 - copy2 elif copy1.floating(): # Set to be the same copy1.setTimezoneID(copy2.getTimezoneID()) copy1.setTimezoneUTC(copy2.getTimezoneUTC()) return copy1 - copy2 else: # Set to be the same copy2.setTimezoneID(copy1.getTimezoneID()) copy2.setTimezoneUTC(copy1.getTimezoneUTC()) return copy1 - copy2 else: # Do diff of date-time in seconds diff = self.getPosixTime() - date.getPosixTime() return PyCalendarDuration(duration=diff) elif isinstance(dateorduration, PyCalendarDuration): duration = dateorduration result = self.duplicate() result.mSeconds -= duration.getTotalSeconds() result.normalise() return result raise ValueError # Comparators def __eq__(self, comp): return self.compareDateTime(comp) == 0 def __ne__(self, comp): return self.compareDateTime(comp) != 0 def __ge__(self, comp): return self.compareDateTime(comp) >= 0 def __le__(self, comp): return self.compareDateTime(comp) <= 0 def __gt__(self, comp): return self.compareDateTime(comp) > 0 def __lt__(self, comp): return self.compareDateTime(comp) < 0 def compareDateTime(self, comp): if comp is None: return 1 # If either are date only, then just do date compare if self.mDateOnly or comp.mDateOnly: if self.mYear == comp.mYear: if self.mMonth == comp.mMonth: if self.mDay == comp.mDay: return 0 else: if self.mDay < comp.mDay: return -1 else: return 1 else: if self.mMonth < comp.mMonth: return -1 else: return 1 else: if self.mYear < comp.mYear: return -1 else: return 1 # If they have the same timezone do simple compare - no posix calc # needed elif (PyCalendarTimezone.same(self.mTZUTC, self.mTZID, comp.mTZUTC, comp.mTZID)): if self.mYear == comp.mYear: if self.mMonth == comp.mMonth: if self.mDay == comp.mDay: if self.mHours == comp.mHours: if self.mMinutes == comp.mMinutes: if self.mSeconds == comp.mSeconds: return 0 else: if self.mSeconds < comp.mSeconds: return -1 else: return 1 else: if self.mMinutes < comp.mMinutes: return -1 else: return 1 else: if self.mHours < comp.mHours: return -1 else: return 1 else: if self.mDay < comp.mDay: return -1 else: return 1 else: if self.mMonth < comp.mMonth: return -1 else: return 1 else: if self.mYear < comp.mYear: return -1 else: return 1 else: posix1 = self.getPosixTime() posix2 = comp.getPosixTime() if posix1 == posix2: return 0 else: if posix1 < posix2: return -1 else: return 1 def compareDate(self, comp): return (self.mYear == comp.mYear) and (self.mMonth == comp.mMonth) and (self.mDay == comp.mDay) def getPosixTime(self): # Look for cached value (or floating time which has to be calculated # each time) if (not self.mPosixTimeCached) or self.floating(): result = 0L # Add hour/mins/secs result = (self.mHours * 60L + self.mMinutes) * 60L + self.mSeconds # Number of days since 1970 result += self.daysSince1970() * 24L * 60L * 60L # Adjust for timezone offset result -= self.timeZoneSecondsOffset() # Now indicate cache state self.mPosixTimeCached = True self.mPosixTime = result return self.mPosixTime def isDateOnly(self): return self.mDateOnly def setDateOnly(self, date_only): self.mDateOnly = date_only self.changed() def getYear(self): return self.mYear def setYear(self, year): if self.mYear != year: self.mYear = year self.changed() def offsetYear(self, diff_year): self.mYear += diff_year self.normalise() def getMonth(self): return self.mMonth def setMonth(self, month): if self.mMonth != month: self.mMonth = month self.changed() def offsetMonth(self, diff_month): self.mMonth += diff_month self.normalise() def getDay(self): return self.mDay def setDay(self, day): if self.mDay != day: self.mDay = day self.changed() def offsetDay(self, diff_day): self.mDay += diff_day self.normalise() def setYearDay(self, day, allow_invalid=False): # 1 .. 366 offset from start, or # -1 .. -366 offset from end if day == 366: self.mMonth = 12 self.mDay = 31 if utils.isLeapYear(self.mYear) else 32 elif day == -366: self.mMonth = 1 if utils.isLeapYear(self.mYear) else 1 self.mDay = 1 if utils.isLeapYear(self.mYear) else 0 elif day > 0: # Offset current date to 1st January of current year self.mMonth = 1 self.mDay = 1 # Increment day self.mDay += day - 1 elif day < 0: # Offset current date to 1st January of next year self.mMonth = 12 self.mDay = 31 # Decrement day self.mDay += day + 1 if not allow_invalid: # Normalise to get proper year/month/day values self.normalise() else: self.changed() def getYearDay(self): return self.mDay + utils.daysUptoMonth(self.mMonth, self.mYear) def setMonthDay(self, day, allow_invalid=False): # 1 .. 31 offset from start, or # -1 .. -31 offset from end if day > 0: # Offset current date to 1st of current month self.mDay = 1 # Increment day self.mDay += day - 1 elif day < 0: # Offset current date to last of month self.mDay = utils.daysInMonth(self.mMonth, self.mYear) # Decrement day self.mDay += day + 1 if not allow_invalid: # Normalise to get proper year/month/day values self.normalise() else: self.changed() def isMonthDay(self, day): if day > 0: return self.mDay == day elif day < 0: return self.mDay - 1 - utils.daysInMonth(self.mMonth, self.mYear) == day else: return False def setWeekNo(self, weekno): """ Set the current date to one with the same day of the week in the current year with the specified week number. Note this might cause the year to shift backwards or forwards if the date is at the boundary between two years. @param weekno: the week number to set (currently must be positive) @type weekno: C{int} """ # Don't both if already correct if self.getWeekNo() == weekno: return # What day does the current year start on, and diff that with the current day temp = PyCalendarDateTime(year=self.mYear, month=1, day=1) first_day = temp.getDayOfWeek() current_day = self.getDayOfWeek() # Calculate and set yearday for start of week. The first week is the one that contains at least # four days (with week start defaulting to MONDAY), so that means the 1st of January would fall # on MO, TU, WE, TH. if first_day in (PyCalendarDateTime.MONDAY, PyCalendarDateTime.TUESDAY, PyCalendarDateTime.WEDNESDAY, PyCalendarDateTime.THURSDAY): year_day = (weekno - 1) * 7 + current_day - first_day else: year_day = weekno * 7 + current_day - first_day # It is possible we have a negative offset which means go back to the prior year as part of # week #1 exists at the end of that year. if year_day < 0: self.offsetYear(-1) else: year_day += 1 self.setYearDay(year_day) def getWeekNo(self): """ Return the ISO week number for the current date. """ # What day does the current year start on temp = PyCalendarDateTime(year=self.mYear, month=1, day=1) first_day = temp.getDayOfWeek() if first_day == 0: first_day = 7 current_day = self.getDayOfWeek() if current_day == 0: current_day = 7 # This arithmetic uses the ISO day of week (1-7) and the year day to get the week number week_no = (self.getYearDay() - current_day + 10) / 7 # Might need to adjust forward/backwards based on year boundaries if week_no == 0: # Last week of previous year temp = PyCalendarDateTime(year=self.mYear - 1, month=12, day=31) week_no = temp.getWeekNo() elif week_no == 53: # Might be first week of next year temp = PyCalendarDateTime(year=self.mYear + 1, month=1, day=1) first_day = temp.getDayOfWeek() if first_day in (PyCalendarDateTime.MONDAY, PyCalendarDateTime.TUESDAY, PyCalendarDateTime.WEDNESDAY, PyCalendarDateTime.THURSDAY): week_no = 1 return week_no def isWeekNo(self, weekno): # This is the iso 8601 week number definition if weekno > 0: return self.getWeekNo() == weekno else: # This needs to calculate the negative offset from the last week in # the current year return False def setDayOfWeekInYear(self, offset, day): # Set to first day in year self.mMonth = 1 self.mDay = 1 # Determine first weekday in year first_day = self.getDayOfWeek() if offset > 0: cycle = (offset - 1) * 7 + day cycle -= first_day if first_day > day: cycle += 7 self.mDay = cycle + 1 elif offset < 0: first_day += 365 + [1, 0][not utils.isLeapYear(self.mYear)] - 1 first_day %= 7 cycle = (-offset - 1) * 7 - day cycle += first_day if day > first_day: cycle += 7 self.mDay = 365 + [1, 0][not utils.isLeapYear(self.mYear)] - cycle self.normalise() def setDayOfWeekInMonth(self, offset, day, allow_invalid=False): # Set to first day in month self.mDay = 1 # Determine first weekday in month first_day = self.getDayOfWeek() if offset > 0: cycle = (offset - 1) * 7 + day cycle -= first_day if first_day > day: cycle += 7 self.mDay = cycle + 1 elif offset < 0: days_in_month = utils.daysInMonth(self.mMonth, self.mYear) first_day += days_in_month - 1 first_day %= 7 cycle = (-offset - 1) * 7 - day cycle += first_day if day > first_day: cycle += 7 self.mDay = days_in_month - cycle if not allow_invalid: self.normalise() else: self.changed() def setNextDayOfWeek(self, start, day): # Set to first day in month self.mDay = start # Determine first weekday in month first_day = self.getDayOfWeek() if first_day > day: self.mDay += 7 self.mDay += day - first_day self.normalise() def isDayOfWeekInMonth(self, offset, day): # First of the actual day must match if self.getDayOfWeek() != day: return False # If there is no count the we match any of this day in the month if offset == 0: return True # Create temp date-time with the appropriate parameters and then # compare temp = self.duplicate() temp.setDayOfWeekInMonth(offset, day) # Now compare dates return self.compareDate(temp) def getDayOfWeek(self): # Count days since 01-Jan-1970 which was a Thursday result = PyCalendarDateTime.THURSDAY + self.daysSince1970() result %= 7 if result < 0: result += 7 return result def getMonthText(self, short_txt): # Make sure range is valid if (self.mMonth < 1) or (self.mMonth > 12): return "" else: return locale.getMonth(self.mMonth, [locale.SHORT, locale.LONG][not short_txt]) def getDayOfWeekText(self, day): return locale.getDay(day, locale.SHORT) def setHHMMSS(self, hours, minutes, seconds): if (self.mHours != hours) or (self.mMinutes != minutes) or (self.mSeconds != seconds): self.mHours = hours self.mMinutes = minutes self.mSeconds = seconds self.changed() def getHours(self): return self.mHours def setHours(self, hours): if self.mHours != hours: self.mHours = hours self.changed() def offsetHours(self, diff_hour): self.mHours += diff_hour self.normalise() def getMinutes(self): return self.mMinutes def setMinutes(self, minutes): if self.mMinutes != minutes: self.mMinutes = minutes self.changed() def offsetMinutes(self, diff_minutes): self.mMinutes += diff_minutes self.normalise() def getSeconds(self): return self.mSeconds def setSeconds(self, seconds): if self.mSeconds != seconds: self.mSeconds = seconds self.changed() def offsetSeconds(self, diff_seconds): self.mSeconds += diff_seconds self.normalise() def getTimezoneUTC(self): return self.mTZUTC def setTimezoneUTC(self, utc): if self.mTZUTC != utc: self.mTZUTC = utc self.changed() def getTimezoneID(self): return self.mTZID def setTimezoneID(self, tzid): self.mTZUTC = False self.mTZID = tzid self.changed() def utc(self): return self.mTZUTC def local(self): return (not self.mTZUTC) and self.mTZID def floating(self): return (not self.mTZUTC) and not self.mTZID def getTimezone(self): return PyCalendarTimezone(utc=self.mTZUTC, tzid=self.mTZID) def setTimezone(self, tzid): self.mTZUTC = tzid.getUTC() self.mTZID = tzid.getTimezoneID() self.changed() def adjustTimezone(self, tzid): # Only if different s1 = tzid.getTimezoneID() if (tzid.getUTC() != self.mTZUTC) or (s1 != self.mTZID): if not self.mTZUTC: self.adjustToUTC() self.setTimezone(tzid) offset_to = self.timeZoneSecondsOffset(relative_to_utc=True) self.offsetSeconds(offset_to) return self def adjustToUTC(self): if self.local() and not self.mDateOnly: # Cache and restore and adjust the posix value to avoid a recalc since it won't change during this adjust tempPosix = self.mPosixTime if self.mPosixTimeCached else None utc = PyCalendarTimezone(utc=True) offset_from = self.timeZoneSecondsOffset() self.setTimezone(utc) self.offsetSeconds(-offset_from) if tempPosix is not None: self.mPosixTimeCached = True self.mPosixTime = tempPosix self.mTZOffset = 0 return self def getAdjustedTime(self, tzid=None): # Copy this and adjust to input timezone adjusted = self.duplicate() adjusted.adjustTimezone(tzid) return adjusted def setToday(self): tz = PyCalendarTimezone(utc=self.mTZUTC, tzid=self.mTZID) self.copy_ICalendarDateTime(self.getToday(tz)) @staticmethod def getToday(tzid=None): # Get from posix time now = time.time() now_tm = time.localtime(now) temp = PyCalendarDateTime(year=now_tm.tm_year, month=now_tm.tm_mon, day=now_tm.tm_mday, tzid=tzid) return temp def setNow(self): tz = PyCalendarTimezone(utc=self.mTZUTC, tzid=self.mTZID) self.copy_ICalendarDateTime(self.getNow(tz)) @staticmethod def getNow(tzid): utc = PyCalendarDateTime.getNowUTC() utc.adjustTimezone(tzid if tzid is not None else PyCalendarTimezone()) return utc def setNowUTC(self): self.copy_PyCalendarDateTime(self.getNowUTC()) @staticmethod def getNowUTC(): # Get from posix time now = time.time() now_tm = time.gmtime(now) tzid = PyCalendarTimezone(utc=True) return PyCalendarDateTime(year=now_tm.tm_year, month=now_tm.tm_mon, day=now_tm.tm_mday, hours=now_tm.tm_hour, minutes=now_tm.tm_min, seconds=now_tm.tm_sec, tzid=tzid) def recur(self, freq, interval, allow_invalid=False): # Add appropriate interval normalize = True if freq == definitions.eRecurrence_SECONDLY: self.mSeconds += interval elif freq == definitions.eRecurrence_MINUTELY: self.mMinutes += interval elif freq == definitions.eRecurrence_HOURLY: self.mHours += interval elif freq == definitions.eRecurrence_DAILY: self.mDay += interval elif freq == definitions.eRecurrence_WEEKLY: self.mDay += 7 * interval elif freq == definitions.eRecurrence_MONTHLY: # Iterate until a valid day in the next month is found. # This can happen if adding one month to e.g. 31 January. # That is an undefined operation - does it mean 28/29 February # or 1/2 May, or 31 March or what? We choose to find the next month with # the same day number as the current one. self.mMonth += interval # Normalise month normalised_month = ((self.mMonth - 1) % 12) + 1 adjustment_year = (self.mMonth - 1) / 12 if (normalised_month - 1) < 0: normalised_month += 12 adjustment_year -= 1 self.mMonth = normalised_month self.mYear += adjustment_year if not allow_invalid: self.normalise() while self.mDay > utils.daysInMonth(self.mMonth, self.mYear): self.mMonth += interval self.normalise() normalize = False elif freq == definitions.eRecurrence_YEARLY: self.mYear += interval if allow_invalid: normalize = False if normalize: # Normalise to standard date-time ranges self.normalise() else: self.changed() def getLocaleDate(self, locale): buf = StringIO.StringIO() if locale == PyCalendarDateTime.FULLDATE: buf.write(locale.getDay(self.getDayOfWeek(), locale.LONG)) buf.write(", ") buf.write(locale.getMonth(self.mMonth, locale.LONG)) buf.write(" ") buf.write(str(self.mDay)) buf.write(", ") buf.write(str(self.mYear)) elif locale == PyCalendarDateTime.ABBREVDATE: buf.write(locale.getDay(self.getDayOfWeek(), locale.SHORT)) buf.write(", ") buf.write(locale.getMonth(self.mMonth, locale.SHORT)) buf.write(" ") buf.write(str(self.mDay)) buf.write(", ") buf.write(str(self.mYear)) elif locale == PyCalendarDateTime.NUMERICDATE: buf.write(str(self.mMonth)) buf.write("/") buf.write(str(self.mDay)) buf.write("/") buf.write(str(self.mYear)) elif locale == PyCalendarDateTime.FULLDATENOYEAR: buf.write(locale.getDay(self.getDayOfWeek(), locale.LONG)) buf.write(", ") buf.write(locale.getMonth(self.mMonth, locale.LONG)) buf.write(" ") buf.write(str(self.mDay)) elif locale == PyCalendarDateTime.ABBREVDATENOYEAR: buf.write(locale.getDay(self. getDayOfWeek(), locale.SHORT)) buf.write(", ") buf.write(locale.getMonth(self.mMonth, locale.SHORT)) buf.write(" ") buf.write(str(self.mDay)) elif locale == PyCalendarDateTime.NUMERICDATENOYEAR: buf.write(str(self.mMonth)) buf.write("/") buf.write(str(self.mDay)) return buf.getvalue() def getTime(self, with_seconds, am_pm, tzid): buf = StringIO.StringIO() adjusted_hour = self.mHours if am_pm: am = True if adjusted_hour >= 12: adjusted_hour -= 12 am = False if adjusted_hour == 0: adjusted_hour = 12 buf.write(str(adjusted_hour)) buf.write(":") if self.mMinutes < 10: buf.write("0") buf.write(str(self.mMinutes)) if with_seconds: buf.write(":") if self.mSeconds < 10: buf.write("0") buf.write(str(self.mSeconds)) buf.write([" AM", " PM"][not am]) else: if self.mHours < 10: buf.write("0") buf.write(str(self.mHours)) buf.write(":") if self.mMinutes < 10: buf.write("0") buf.write(str(self.mMinutes)) if with_seconds: buf.write(":") if self.mSeconds < 10: buf.write("0") buf.write(str(self.mSeconds)) if tzid: desc = self.timeZoneDescriptor() if len(desc) > 0: buf.write(" ") buf.write(desc) return buf.getvalue() def getLocaleDateTime(self, locale, with_seconds, am_pm, tzid): return self.getLocaleDate(locale) + " " + self.getTime(with_seconds, am_pm, tzid) def getText(self): if self.mDateOnly: return "%04d%02d%02d" % (self.mYear, self.mMonth, self.mDay) else: if self.mTZUTC: return "%04d%02d%02dT%02d%02d%02dZ" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds) elif isinstance(self.mTZID, int): sign = "-" if self.mTZID < 0 else "+" hours = abs(self.mTZID) / 3600 minutes = divmod(abs(self.mTZID) / 60, 60)[1] return "%04d%02d%02dT%02d%02d%02d%s%02d%02d" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds, sign, hours, minutes) else: return "%04d%02d%02dT%02d%02d%02d" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds) def getXMLText(self): if self.mDateOnly: return "%04d-%02d-%02d" % (self.mYear, self.mMonth, self.mDay) else: if self.mTZUTC: return "%04d-%02d-%02dT%02d:%02d:%02dZ" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds) elif isinstance(self.mTZID, int): sign = "-" if self.mTZID < 0 else "+" hours = abs(self.mTZID) / 3600 minutes = divmod(abs(self.mTZID) / 60, 60)[1] return "%04d-%02d-%02dT%02d:%02d:%02d%s%02d:%02d" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds, sign, hours, minutes) else: return "%04d-%02d-%02dT%02d:%02d:%02d" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds) @classmethod def parseText(cls, data, fullISO=False): dt = cls() dt.parse(data, fullISO) return dt def parseDate(self, data, fullISO): # Get year self.mYear = int(data[0:4]) index = 4 if fullISO and data[index] == "-": index += 1 # Get month self.mMonth = int(data[index:index + 2]) index += 2 if fullISO and data[index] == "-": index += 1 # Get day self.mDay = int(data[index:index + 2]) index += 2 if ' ' in data[:index] and ParserContext.INVALID_DATETIME_LEADINGSPACE == ParserContext.PARSER_RAISE: raise ValueError if self.mYear < 0 or self.mMonth < 0 or self.mDay < 0: raise ValueError return index def parseTime(self, data, index, fullISO): # Get hours self.mHours = int(data[index:index + 2]) index += 2 if fullISO and data[index] == ":": index += 1 # Get minutes self.mMinutes = int(data[index:index + 2]) index += 2 if fullISO and data[index] == ":": index += 1 # Get seconds self.mSeconds = int(data[index:index + 2]) index += 2 return index def parseFractionalAndUTCOffset(self, data, index, dlen): # Optional fraction if data[index] == ",": index += 1 if not data[index].isdigit(): raise ValueError while index < dlen and data[index].isdigit(): index += 1 # Optional timezone descriptor if index < dlen: if data[index] == "Z": index += 1 self.mTZUTC = True else: if data[index] == "+": sign = 1 elif data[index] == "-": sign = -1 else: raise ValueError index += 1 # Get hours hours_offset = int(data[index:index + 2]) index += 2 if data[index] == ":": index += 1 # Get minutes minutes_offset = int(data[index:index + 2]) index += 2 self.mTZID = sign * (hours_offset * 60 + minutes_offset) * 60 if index < dlen: raise ValueError return index def parse(self, data, fullISO=False): # iCalendar: # parse format YYYYMMDD[THHMMSS[Z]] # vCard (fullISO) # parse format YYYY[-]MM[-]DD[THH[:]MM[:]SS[(Z/(+/-)HHMM]] try: # Parse out the date index = self.parseDate(data, fullISO) # Now look for more dlen = len(data) if index < dlen: if data[index] != 'T': raise ValueError index += 1 # Parse out the time index = self.parseTime(data, index, fullISO) self.mDateOnly = False if index < dlen: if fullISO: index = self.parseFractionalAndUTCOffset(data, index, dlen) else: if index < dlen: if data[index] != 'Z': raise ValueError index += 1 self.mTZUTC = True if index < dlen: raise ValueError else: self.mTZUTC = False else: self.mDateOnly = True except IndexError: raise ValueError # Always uncache posix time self.changed() def generate(self, os): try: os.write(self.getText()) except: pass def generateRFC2822(self, os): pass def writeXML(self, node, namespace): value = XML.SubElement( node, xmldefs.makeTag( namespace, xmldefs.value_date if self.isDateOnly() else xmldefs.value_date_time )) value.text = self.getXMLText() def invalid(self): """ Are any of the current fields invalid. """ # Right now we only care about invalid days of the month (e.g. February 30th). In the # future we may also want to look for invalid times during a DST transition. if self.mDay <= 0 or self.mDay > utils.daysInMonth(self.mMonth, self.mYear): return True return False def normalise(self): # Normalise seconds normalised_secs = self.mSeconds % 60 adjustment_mins = self.mSeconds / 60 if normalised_secs < 0: normalised_secs += 60 adjustment_mins -= 1 self.mSeconds = normalised_secs self.mMinutes += adjustment_mins # Normalise minutes normalised_mins = self.mMinutes % 60 adjustment_hours = self.mMinutes / 60 if normalised_mins < 0: normalised_mins += 60 adjustment_hours -= 1 self.mMinutes = normalised_mins self.mHours += adjustment_hours # Normalise hours normalised_hours = self.mHours % 24 adjustment_days = self.mHours / 24 if normalised_hours < 0: normalised_hours += 24 adjustment_days -= 1 self.mHours = normalised_hours self.mDay += adjustment_days # Wipe the time if date only if self.mDateOnly: self.mSeconds = self.mMinutes = self.mHours = 0 # Adjust the month first, since the day adjustment is month dependent # Normalise month normalised_month = ((self.mMonth - 1) % 12) + 1 adjustment_year = (self.mMonth - 1) / 12 if (normalised_month - 1) < 0: normalised_month += 12 adjustment_year -= 1 self.mMonth = normalised_month self.mYear += adjustment_year # Now do days if self.mDay > 0: while self.mDay > utils.daysInMonth(self.mMonth, self.mYear): self.mDay -= utils.daysInMonth(self.mMonth, self.mYear) self.mMonth += 1 if self.mMonth > 12: self.mMonth = 1 self.mYear += 1 else: while self.mDay <= 0: self.mMonth -= 1 if self.mMonth < 1: self.mMonth = 12 self.mYear -= 1 self.mDay += utils.daysInMonth(self.mMonth, self.mYear) # Always invalidate posix time cache self.changed() def timeZoneSecondsOffset(self, relative_to_utc=False): if self.mTZUTC: return 0 if self.mTZOffset is None: tz = PyCalendarTimezone(utc=self.mTZUTC, tzid=self.mTZID) self.mTZOffset = tz.timeZoneSecondsOffset(self, relative_to_utc) return self.mTZOffset def timeZoneDescriptor(self): tz = PyCalendarTimezone(utc=self.mTZUTC, tzid=self.mTZID) return tz.timeZoneDescriptor(self) def changed(self): self.mPosixTimeCached = False self.mTZOffset = None def daysSince1970(self): # Add days between 1970 and current year (ignoring leap days) result = (self.mYear - 1970) * 365 # Add leap days between years result += utils.leapDaysSince1970(self.mYear - 1970) # Add days in current year up to current month (includes leap day for # current year as needed) result += utils.daysUptoMonth(self.mMonth, self.mYear) # Add days in month result += self.mDay - 1 return result pycalendar-2.0~svn13177/src/pycalendar/componentrecur.py0000644000175000017500000006150112101017573022363 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar.component import PyCalendarComponent from pycalendar.componentexpanded import PyCalendarComponentExpanded from pycalendar.datetime import PyCalendarDateTime from pycalendar.property import PyCalendarProperty from pycalendar.recurrenceset import PyCalendarRecurrenceSet from pycalendar.timezone import PyCalendarTimezone from pycalendar.utils import set_difference import uuid class PyCalendarComponentRecur(PyCalendarComponent): propertyCardinality_STATUS_Fix = ( definitions.cICalProperty_STATUS, ) @staticmethod def mapKey(uid, rid=None): if uid: result = "u:" + uid if rid is not None: result += rid return result else: return None @staticmethod def sort_by_dtstart_allday(e1, e2): if e1.self.mStart.isDateOnly() and e2.self.mStart.isDateOnly(): return e1.self.mStart < e2.self.mStart elif e1.self.mStart.isDateOnly(): return True elif (e2.self.mStart.isDateOnly()): return False elif e1.self.mStart == e2.self.mStart: if e1.self.mEnd == e2.self.mEnd: # Put ones created earlier in earlier columns in day view return e1.self.mStamp < e2.self.mStamp else: # Put ones that end later in earlier columns in day view return e1.self.mEnd > e2.self.mEnd else: return e1.self.mStart < e2.self.mStart @staticmethod def sort_by_dtstart(e1, e2): if e1.self.mStart == e2.self.mStart: if (e1.self.mStart.isDateOnly() and e2.self.mStart.isDateOnly() or not e1.self.mStart.isDateOnly() and not e2.self.mStart.isDateOnly()): return False else: return e1.self.mStart.isDateOnly() else: return e1.self.mStart < e2.self.mStart def __init__(self, parent=None): super(PyCalendarComponentRecur, self).__init__(parent=parent) self.mMaster = self self.mMapKey = None self.mSummary = None self.mStamp = PyCalendarDateTime() self.mHasStamp = False self.mStart = PyCalendarDateTime() self.mHasStart = False self.mEnd = PyCalendarDateTime() self.mHasEnd = False self.mDuration = False self.mHasRecurrenceID = False self.mAdjustFuture = False self.mAdjustPrior = False self.mRecurrenceID = None self.mRecurrences = None # This is a special check we do only for STATUS due to a calendarserver bug self.cardinalityChecks += ( self.check_cardinality_STATUS_Fix, ) def duplicate(self, parent=None): other = super(PyCalendarComponentRecur, self).duplicate(parent=parent) # Special determination of master other.mMaster = self.mMaster if self.recurring() else self other.mMapKey = self.mMapKey other.mSummary = self.mSummary if (self.mStamp is not None): other.mStamp = self.mStamp.duplicate() other.mHasStamp = self.mHasStamp other.mStart = self.mStart.duplicate() other.mHasStart = self.mHasStart other.mEnd = self.mEnd.duplicate() other.mHasEnd = self.mHasEnd other.mDuration = self.mDuration other.mHasRecurrenceID = self.mHasRecurrenceID other.mAdjustFuture = self.mAdjustFuture other.mAdjustPrior = self.mAdjustPrior if self.mRecurrenceID is not None: other.mRecurrenceID = self.mRecurrenceID.duplicate() other._resetRecurrenceSet() return other def canGenerateInstance(self): return not self.mHasRecurrenceID def recurring(self): return (self.mMaster is not None) and (self.mMaster is not self) def setMaster(self, master): self.mMaster = master self.initFromMaster() def getMaster(self): return self.mMaster def getMapKey(self): if self.mMapKey is None: self.mMapKey = str(uuid.uuid4()) return self.mMapKey def getMasterKey(self): return PyCalendarComponentRecur.mapKey(self.mUID) def initDTSTAMP(self): # Save new one super(PyCalendarComponentRecur, self).initDTSTAMP() # Get the new one temp = self.loadValueDateTime(definitions.cICalProperty_DTSTAMP) self.mHasStamp = temp is not None if self.mHasStamp: self.mStamp = temp def getStamp(self): return self.mStamp def hasStamp(self): return self.mHasStamp def getStart(self): return self.mStart def hasStart(self): return self.mHasStart def getEnd(self): return self.mEnd def hasEnd(self): return self.mHasEnd def useDuration(self): return self.mDuration def isRecurrenceInstance(self): return self.mHasRecurrenceID def isAdjustFuture(self): return self.mAdjustFuture def isAdjustPrior(self): return self.mAdjustPrior def getRecurrenceID(self): return self.mRecurrenceID def isRecurring(self): return (self.mRecurrences is not None) and self.mRecurrences.hasRecurrence() def getRecurrenceSet(self): return self.mRecurrences def setUID(self, uid): super(PyCalendarComponentRecur, self).setUID(uid) # Update the map key if self.mHasRecurrenceID: self.mMapKey = self.mapKey(self.mUID, self.mRecurrenceID.getText()) else: self.mMapKey = self.mapKey(self.mUID) def getSummary(self): return self.mSummary def setSummary(self, summary): self.mSummary = summary def getDescription(self): # Get DESCRIPTION txt = self.loadValueString(definitions.cICalProperty_DESCRIPTION) if txt is not None: return txt else: return "" def getLocation(self): # Get LOCATION txt = self.loadValueString(definitions.cICalProperty_LOCATION) if txt is not None: return txt else: return "" def finalise(self): super(PyCalendarComponentRecur, self).finalise() # Get DTSTAMP temp = self.loadValueDateTime(definitions.cICalProperty_DTSTAMP) self.mHasStamp = temp is not None if self.mHasStamp: self.mStamp = temp # Get DTSTART temp = self.loadValueDateTime(definitions.cICalProperty_DTSTART) self.mHasStart = temp is not None if self.mHasStart: self.mStart = temp # Get DTEND temp = self.loadValueDateTime(definitions.cICalProperty_DTEND) if temp is None: # Try DURATION instead temp = self.loadValueDuration(definitions.cICalProperty_DURATION) if temp is not None: self.mHasEnd = False self.mEnd = self.mStart + temp self.mDuration = True else: # If no end or duration then use the start self.mHasEnd = False self.mEnd = self.mStart.duplicate() self.mDuration = False else: self.mHasEnd = True self.mEnd = temp self.mDuration = False # Get SUMMARY temp = self.loadValueString(definitions.cICalProperty_SUMMARY) if temp is not None: self.mSummary = temp # Get RECURRENCE-ID self.mHasRecurrenceID = (self.countProperty(definitions.cICalProperty_RECURRENCE_ID) != 0) if self.mHasRecurrenceID: self.mRecurrenceID = self.loadValueDateTime(definitions.cICalProperty_RECURRENCE_ID) # Update the map key if self.mHasRecurrenceID: self.mMapKey = self.mapKey(self.mUID, self.mRecurrenceID.getText()) # Also get the RANGE attribute attrs = self.findFirstProperty(definitions.cICalProperty_RECURRENCE_ID).getAttributes() if definitions.cICalAttribute_RANGE in attrs: self.mAdjustFuture = (attrs[definitions.cICalAttribute_RANGE][0].getFirstValue() == definitions.cICalAttribute_RANGE_THISANDFUTURE) self.mAdjustPrior = (attrs[definitions.cICalAttribute_RANGE][0].getFirstValue() == definitions.cICalAttribute_RANGE_THISANDPRIOR) else: self.mAdjustFuture = False self.mAdjustPrior = False else: self.mMapKey = self.mapKey(self.mUID) self._resetRecurrenceSet() def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems. Return a tuple containing two lists: the first describes problems that were fixed, the second problems that were not fixed. Caller can then decide what to do with unfixed issues. """ # Do normal checks fixed, unfixed = super(PyCalendarComponentRecur, self).validate(doFix) # Check that any UNTIL value matches that for DTSTART if self.mHasStart and self.mRecurrences: dtutc = self.mStart.duplicateAsUTC() for rrule in self.mRecurrences.getRules(): if rrule.getUseUntil(): if rrule.getUntil().isDateOnly() ^ self.mStart.isDateOnly(): logProblem = "[%s] Value types must match: %s, %s" % ( self.getType(), definitions.cICalProperty_DTSTART, definitions.cICalValue_RECUR_UNTIL, ) if doFix: rrule.getUntil().setDateOnly(self.mStart.isDateOnly()) if not self.mStart.isDateOnly(): rrule.getUntil().setHHMMSS(dtutc.getHours(), dtutc.getMinutes(), dtutc.getSeconds()) rrule.getUntil().setTimezone(PyCalendarTimezone(utc=True)) self.mRecurrences.changed() fixed.append(logProblem) else: unfixed.append(logProblem) return fixed, unfixed def check_cardinality_STATUS_Fix(self, fixed, unfixed, doFix): """ Special for bug with STATUS where STATUS:CANCELLED is added alongside another STATUS. In this case we want STATUS:CANCELLED to win. """ for propname in self.propertyCardinality_STATUS_Fix: if self.countProperty(propname) > 1: logProblem = "[%s] Too many properties: %s" % (self.getType(), propname) if doFix: # Check that one of them is STATUS:CANCELLED for prop in self.getProperties(propname): if prop.getTextValue().getValue().upper() == definitions.cICalProperty_STATUS_CANCELLED: self.removeProperties(propname) self.addProperty(PyCalendarProperty(propname, definitions.cICalProperty_STATUS_CANCELLED)) fixed.append(logProblem) break else: unfixed.append(logProblem) else: unfixed.append(logProblem) def _resetRecurrenceSet(self): # May need to create items self.mRecurrences = None if ((self.countProperty(definitions.cICalProperty_RRULE) != 0) or (self.countProperty(definitions.cICalProperty_RDATE) != 0) or (self.countProperty(definitions.cICalProperty_EXRULE) != 0) or (self.countProperty(definitions.cICalProperty_EXDATE) != 0)): self.mRecurrences = PyCalendarRecurrenceSet() # Get RRULEs self.loadValueRRULE(definitions.cICalProperty_RRULE, self.mRecurrences, True) # Get RDATEs self.loadValueRDATE(definitions.cICalProperty_RDATE, self.mRecurrences, True) # Get EXRULEs self.loadValueRRULE(definitions.cICalProperty_EXRULE, self.mRecurrences, False) # Get EXDATEs self.loadValueRDATE(definitions.cICalProperty_EXDATE, self.mRecurrences, False) def FixStartEnd(self): # End is always greater than start if start exists if self.mHasStart and self.mEnd <= self.mStart: # Use the start self.mEnd = self.mStart.duplicate() self.mDuration = False # Adjust to approriate non-inclusive end point if self.mStart.isDateOnly(): self.mEnd.offsetDay(1) # For all day events it makes sense to use duration self.mDuration = True else: # Use end of current day self.mEnd.offsetDay(1) self.mEnd.setHHMMSS(0, 0, 0) def expandPeriod(self, period, results): # Check for recurrence and True master if ((self.mRecurrences is not None) and self.mRecurrences.hasRecurrence() and not self.isRecurrenceInstance()): # Expand recurrences within the range items = [] self.mRecurrences.expand(self.mStart, period, items) # Look for overridden recurrence items cal = self.mParentComponent if cal is not None: # Remove recurrence instances from the list of items recurs = [] cal.getRecurrenceInstancesIds(definitions.cICalComponent_VEVENT, self.getUID(), recurs) recurs.sort() if len(recurs) != 0: temp = [] temp = set_difference(items, recurs) items = temp # Now get actual instances instances = [] cal.getRecurrenceInstancesItems(definitions.cICalComponent_VEVENT, self.getUID(), instances) # Get list of each ones with RANGE prior = [] future = [] for iter in instances: if iter.isAdjustPrior(): prior.append(iter) if iter.isAdjustFuture(): future.append(iter) # Check for special behaviour if len(prior) + len(future) == 0: # Add each expanded item for iter in items: results.append(self.createExpanded(self, iter)) else: # Sort each list first prior.sort(self.sort_by_dtstart) future.sort(self.sort_by_dtstart) # Add each expanded item for iter1 in items: # Now step through each using the slave item # instead of the master as appropriate slave = None # Find most appropriate THISANDPRIOR item for i in range(len(prior) - 1, 0, -1): riter2 = prior[i] if riter2.getStart() > iter1: slave = riter2 break # Find most appropriate THISANDFUTURE item for i in range(len(future) - 1, 0, -1): riter2 = future.elementAt(i) if riter2.getStart() < iter1: slave = riter2 break if slave is None: slave = self results.append(self.createExpanded(slave, iter1)) else: # Add each expanded item for iter in items: results.append(self.createExpanded(self, iter)) elif self.withinPeriod(period): if self.isRecurrenceInstance(): rid = self.mRecurrenceID else: rid = None results.append(PyCalendarComponentExpanded(self, rid)) def withinPeriod(self, period): # Check for recurrence if ((self.mRecurrences is not None) and self.mRecurrences.hasRecurrence()): items = [] self.mRecurrences.expand(self.mStart, period, items) return len(items) != 0 else: # Does event span the period (assume self.mEnd > self.mStart) # Check start (inclusive) and end (exclusive) if self.mEnd <= period.getStart() or self.mStart >= period.getEnd(): return False else: return True def changedRecurrence(self): # Clear cached values if self.mRecurrences is not None: self.mRecurrences.changed() # Editing def editSummary(self, summary): # Updated cached value self.mSummary = summary # Remove existing items self.editProperty(definitions.cICalProperty_SUMMARY, summary) def editDetails(self, description, location): # Edit existing items self.editProperty(definitions.cICalProperty_DESCRIPTION, description) self.editProperty(definitions.cICalProperty_LOCATION, location) def editTiming(self): # Updated cached values self.mHasStart = False self.mHasEnd = False self.mDuration = False self.mStart.setToday() self.mEnd.setToday() # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) self.removeProperties(definitions.cICalProperty_DUE) def editTimingDue(self, due): # Updated cached values self.mHasStart = False self.mHasEnd = True self.mDuration = False self.mStart = due self.mEnd = due # Remove existing DUE & DTSTART & DTEND & DURATION items self.removeProperties(definitions.cICalProperty_DUE) self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) # Now create properties prop = PyCalendarProperty(definitions.cICalProperty_DUE, due) self.addProperty(prop) def editTimingStartEnd(self, start, end): # Updated cached values self.mHasStart = self.mHasEnd = True self.mStart = start self.mEnd = end self.mDuration = False self.FixStartEnd() # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) self.removeProperties(definitions.cICalProperty_DUE) # Now create properties prop = PyCalendarProperty(definitions.cICalProperty_DTSTART, start) self.addProperty(prop) # If its an all day event and the end one day after the start, ignore it temp = start.duplicate() temp.offsetDay(1) if not start.isDateOnly() or end != temp: prop = PyCalendarProperty(definitions.cICalProperty_DTEND, end) self.addProperty(prop) def editTimingStartDuration(self, start, duration): # Updated cached values self.mHasStart = True self.mHasEnd = False self.mStart = start self.mEnd = start + duration self.mDuration = True # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) self.removeProperties(definitions.cICalProperty_DUE) # Now create properties prop = PyCalendarProperty(definitions.cICalProperty_DTSTART, start) self.addProperty(prop) # If its an all day event and the duration is one day, ignore it if (not start.isDateOnly() or (duration.getWeeks() != 0) or (duration.getDays() > 1)): prop = PyCalendarProperty(definitions.cICalProperty_DURATION, duration) self.addProperty(prop) def editRecurrenceSet(self, recurs): # Must have items if self.mRecurrences is None: self.mRecurrences = PyCalendarRecurrenceSet() # Updated cached values self.mRecurrences = recurs # Remove existing RRULE, EXRULE, RDATE & EXDATE self.removeProperties(definitions.cICalProperty_RRULE) self.removeProperties(definitions.cICalProperty_EXRULE) self.removeProperties(definitions.cICalProperty_RDATE) self.removeProperties(definitions.cICalProperty_EXDATE) # Now create properties for iter in self.mRecurrences.getRules(): prop = PyCalendarProperty(definitions.cICalProperty_RRULE, iter) self.addProperty(prop) for iter in self.getExrules(): prop = PyCalendarProperty(definitions.cICalProperty_EXRULE, iter) self.addProperty(prop) for iter in self.mRecurrences.getDates(): prop = PyCalendarProperty(definitions.cICalProperty_RDATE, iter) self.addProperty(prop) for iter in self.mRecurrences.getExdates(): prop = PyCalendarProperty(definitions.cICalProperty_EXDATE, iter) self.addProperty(prop) def excludeRecurrence(self, start): # Must have items if self.mRecurrences is None: return # Add to recurrence set and clear cache self.mRecurrences.subtract(start) # Add property prop = PyCalendarProperty(definitions.cICalProperty_EXDATE, start) self.addProperty(prop) def excludeFutureRecurrence(self, start): # Must have items if self.mRecurrences is None: return # Adjust RRULES to end before start self.mRecurrences.excludeFutureRecurrence(start) # Remove existing RRULE & RDATE self.removeProperties(definitions.cICalProperty_RRULE) self.removeProperties(definitions.cICalProperty_RDATE) # Now create properties for iter in self.mRecurrences.getRules(): prop = PyCalendarProperty(definitions.cICalProperty_RRULE, iter) self.addProperty(prop) for iter in self.mRecurrences.getDates(): prop = PyCalendarProperty(definitions.cICalProperty_RDATE, iter) self.addProperty(prop) def initFromMaster(self): # Only if not master if self.recurring(): # Redo this to get cached values from master self.finalise() # If this component does not have its own start property, use the # recurrence id # i.e. the start time of this instance has not changed - something # else has if not self.hasProperty(definitions.cICalProperty_DTSTART): self.mStart = self.mRecurrenceID # If this component does not have its own end/duration property, # the determine # the end from the master duration if (not self.hasProperty(definitions.cICalProperty_DTEND) and not self.hasProperty(definitions.cICalProperty_DURATION)): # End is based on original events settings self.mEnd = self.mStart + (self.mMaster.getEnd() - self.mMaster.getStart()) # If this instance has a duration, but no start of its own, then we # need to readjust the end # to account for the start being changed to the recurrence id elif (self.hasProperty(definitions.cICalProperty_DURATION) and not self.hasProperty(definitions.cICalProperty_DTSTART)): temp = self.loadValueDuration(definitions.cICalProperty_DURATION) self.mEnd = self.mStart + temp def createExpanded(self, master, recurid): return PyCalendarComponentExpanded(master, recurid) pycalendar-2.0~svn13177/src/pycalendar/unknownvalue.py0000644000175000017500000000211712101017573022052 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # iCalendar Unknown value - one whose default type we don't know about from pycalendar import xmldefs from pycalendar.plaintextvalue import PyCalendarPlainTextValue from pycalendar.value import PyCalendarValue class PyCalendarUnknownValue(PyCalendarPlainTextValue): def getType(self): return PyCalendarUnknownValue.VALUETYPE_UNKNOWN PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_UNKNOWN, PyCalendarUnknownValue, xmldefs.value_unknown) pycalendar-2.0~svn13177/src/pycalendar/validation.py0000644000175000017500000000401412101017573021446 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar.plaintextvalue import PyCalendarPlainTextValue # Grabbed from http://docs.python.org/library/functools.html since we need to support Python 2.5 def partial(func, *args, **keywords): def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) return func(*(args + fargs), **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunc class PropertyValueChecks(object): @staticmethod def stringValue(text, property): value = property.getValue() if value and isinstance(value, PyCalendarPlainTextValue): value = value.getValue() return value.lower() == text.lower() return False @staticmethod def alwaysUTC(property): value = property.getDateTimeValue() if value: value = value.getValue() return value.utc() return False @staticmethod def numericRange(low, high, property): value = property.getIntegerValue() if value: value = value.getValue() return value >= low and value <= high return False @staticmethod def positiveIntegerOrZero(property): value = property.getIntegerValue() if value: value = value.getValue() return value >= 0 return False pycalendar-2.0~svn13177/src/pycalendar/timezonedb.py0000644000175000017500000001107212317143440021460 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar.exceptions import PyCalendarNoTimezoneInDatabase, \ PyCalendarInvalidData import os class PyCalendarTimezoneDatabase(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): PyCalendarTimezoneDatabase.sTimezoneDatabase = PyCalendarTimezoneDatabase() PyCalendarTimezoneDatabase.sTimezoneDatabase.setPath(dbpath) @staticmethod def clearTimezoneDatabase(): if PyCalendarTimezoneDatabase.sTimezoneDatabase is not None: PyCalendarTimezoneDatabase.sTimezoneDatabase.clear() def __init__(self): from pycalendar.calendar import PyCalendar self.dbpath = None self.calendar = PyCalendar() def setPath(self, dbpath): self.dbpath = dbpath def clear(self): from pycalendar.calendar import PyCalendar self.calendar = PyCalendar() @staticmethod def getTimezoneDatabase(): if PyCalendarTimezoneDatabase.sTimezoneDatabase is None: PyCalendarTimezoneDatabase.sTimezoneDatabase = PyCalendarTimezoneDatabase() return PyCalendarTimezoneDatabase.sTimezoneDatabase @staticmethod def getTimezone(tzid): # Check whether current cached tzdb = PyCalendarTimezoneDatabase.getTimezoneDatabase() tz = tzdb.calendar.getTimezone(tzid) if tz is None: try: tzdb.cacheTimezone(tzid) except PyCalendarNoTimezoneInDatabase: pass tz = tzdb.calendar.getTimezone(tzid) return tz @staticmethod def getTimezoneInCalendar(tzid): """ Return a VTIMEZONE inside a valid VCALENDAR """ tz = PyCalendarTimezoneDatabase.getTimezone(tzid) if tz is not None: from pycalendar.calendar import PyCalendar cal = PyCalendar() cal.addComponent(tz.duplicate(cal)) return cal else: return None @staticmethod def getTimezoneOffsetSeconds(tzid, dt, relative_to_utc=False): # Cache it first tz = PyCalendarTimezoneDatabase.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 = PyCalendarTimezoneDatabase.getTimezone(tzid) if tz is not None: return tz.getTimezoneDescriptor(dt) else: return "" def cacheTimezone(self, tzid): 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: self.calendar.parseComponent(open(tzpath)) except (IOError, PyCalendarInvalidData): raise PyCalendarNoTimezoneInDatabase(self.dbpath, tzid) else: raise PyCalendarNoTimezoneInDatabase(self.dbpath, tzid) def addTimezone(self, tz): copy = tz.duplicate(self.calendar) self.calendar.addComponent(copy) @staticmethod def mergeTimezones(cal, tzs): """ Merge each timezone from other calendar. """ tzdb = PyCalendarTimezoneDatabase.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) pycalendar-2.0~svn13177/src/pycalendar/utcoffsetvalue.py0000644000175000017500000000551512101017573022362 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # iCalendar UTC Offset value from cStringIO import StringIO from pycalendar import xmldefs from pycalendar.value import PyCalendarValue class PyCalendarUTCOffsetValue(PyCalendarValue): def __init__(self, value=0): self.mValue = value def duplicate(self): return PyCalendarUTCOffsetValue(self.mValue) def getType(self): return PyCalendarValue.VALUETYPE_UTC_OFFSET def parse(self, data): # Must be of specific lengths datalen = len(data) if datalen not in (5, 7): self.mValue = 0 raise ValueError # Get sign if data[0] not in ('+', '-'): raise ValueError plus = (data[0] == '+') # Get hours hours = int(data[1:3]) # Get minutes mins = int(data[3:5]) # Get seconds if present secs = 0 if datalen == 7 : secs = int(data[5:]) self.mValue = ((hours * 60) + mins) * 60 + secs if not plus: self.mValue = -self.mValue # os - StringIO object def generate(self, os): try: abs_value = self.mValue if self.mValue < 0 : os.write("-") abs_value = -self.mValue else: os.write("+") secs = abs_value % 60 mins = (abs_value / 60) % 60 hours = abs_value / (60 * 60) if (hours < 10): os.write("0") os.write(str(hours)) if (mins < 10): os.write("0") os.write(str(mins)) if (secs != 0): if (secs < 10): os.write("0") os.write(str(secs)) except: pass def writeXML(self, node, namespace): os = StringIO.StringIO() self.generate(os) text = os.getvalue() text = text[:-2] + ":" + text[-2:] value = self.getXMLNode(node, namespace) value.text = text def getValue(self): return self.mValue def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_UTC_OFFSET, PyCalendarUTCOffsetValue, xmldefs.value_utc_offset) pycalendar-2.0~svn13177/src/pycalendar/vtimezonedaylight.py0000644000175000017500000000212212101017573023060 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions from vtimezoneelement import PyCalendarVTimezoneElement class PyCalendarVTimezoneDaylight(PyCalendarVTimezoneElement): def __init__(self, parent=None): super(PyCalendarVTimezoneDaylight, self).__init__(parent=parent) def duplicate(self, parent=None): return super(PyCalendarVTimezoneDaylight, self).duplicate(parent=parent) def getType(self): return definitions.cICalComponent_DAYLIGHT pycalendar-2.0~svn13177/src/pycalendar/multivalue.py0000644000175000017500000000470712101017573021514 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.value import PyCalendarValue class PyCalendarMultiValue(PyCalendarValue): def __init__(self, type): self.mType = type self.mValues = [] def __hash__(self): return hash(tuple(self.mValues)) def duplicate(self): other = PyCalendarMultiValue(self.mType) other.mValues = [i.duplicate() for i in self.mValues] return other def getType(self): return self.mType def getRealType(self): return PyCalendarValue.VALUETYPE_MULTIVALUE def getValue(self): return self.mValues def getValues(self): return self.mValues def addValue(self, value): self.mValues.append(value) def setValue(self, value): newValues = [] for v in value: pyCalendarValue = PyCalendarValue.createFromType(self.mType) pyCalendarValue.setValue(v) newValues.append(pyCalendarValue) self.mValues = newValues def parse(self, data): # Tokenize on comma if "," in data: tokens = data.split(",") else: tokens = (data,) for token in tokens: # Create single value, and parse data value = PyCalendarValue.createFromType(self.mType) value.parse(token) self.mValues.append(value) def generate(self, os): try: first = True for iter in self.mValues: if first: first = False else: os.write(",") iter.generate(os) except: pass def writeXML(self, node, namespace): for iter in self.mValues: iter.writeXML(node, namespace) PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_MULTIVALUE, PyCalendarMultiValue, None) pycalendar-2.0~svn13177/src/pycalendar/componentbase.py0000644000175000017500000004710612101017573022162 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 cStringIO import StringIO from pycalendar import xmldefs from pycalendar.datetimevalue import PyCalendarDateTimeValue from pycalendar.periodvalue import PyCalendarPeriodValue from pycalendar.property import PyCalendarProperty from pycalendar.value import PyCalendarValue import xml.etree.cElementTree as XML class PyCalendarComponentBase(object): # These are class attributes for sets of properties for testing cardinality constraints. The sets # must contain property names. propertyCardinality_1 = () # Must be present propertyCardinality_1_Fix_Empty = () # Must be present but can be fixed by adding an empty value propertyCardinality_0_1 = () # 0 or 1 only propertyCardinality_1_More = () # 1 or more propertyValueChecks = None # Either iCalendar or vCard validation sortSubComponents = True def __init__(self, parent=None): self.mParentComponent = parent self.mComponents = [] self.mProperties = {} # This is the set of checks we do by default for components self.cardinalityChecks = ( self.check_cardinality_1, self.check_cardinality_1_Fix_Empty, self.check_cardinality_0_1, self.check_cardinality_1_More, ) def duplicate(self, **args): other = self.__class__(**args) for component in self.mComponents: other.addComponent(component.duplicate(parent=other)) other.mProperties = {} for propname, props in self.mProperties.iteritems(): other.mProperties[propname] = [i.duplicate() for i in props] return other def __str__(self): return self.getText() def __ne__(self, other): return not self.__eq__(other) def __eq__(self, other): if not isinstance(other, PyCalendarComponentBase): return False return self.getType() == other.getType() and self.compareProperties(other) and self.compareComponents(other) def getType(self): raise NotImplementedError def getBeginDelimiter(self): return "BEGIN:" + self.getType() def getEndDelimiter(self): return "END:" + self.getType() def getSortKey(self): return "" def getParentComponent(self): return self.mParentComponent def setParentComponent(self, parent): self.mParentComponent = parent def compareComponents(self, other): mine = set(self.mComponents) theirs = set(other.mComponents) for item in mine: for another in theirs: if item == another: theirs.remove(another) break else: return False return len(theirs) == 0 def getComponents(self, compname=None): compname = compname.upper() if compname else None return [component for component in self.mComponents if compname is None or component.getType().upper() == compname] def getComponentByKey(self, key): for component in self.mComponents: if component.getMapKey() == key: return component else: return None def removeComponentByKey(self, key): for component in self.mComponents: if component.getMapKey() == key: self.removeComponent(component) return def addComponent(self, component): self.mComponents.append(component) def hasComponent(self, compname): return self.countComponents(compname) != 0 def countComponents(self, compname): return len(self.getComponents(compname)) def removeComponent(self, component): self.mComponents.remove(component) def removeAllComponent(self, compname=None): if compname: compname = compname.upper() for component in tuple(self.mComponents): if component.getType().upper() == compname: self.removeComponent(component) else: self.mComponents = [] def sortedComponentNames(self): return () def compareProperties(self, other): mine = set() for props in self.mProperties.values(): mine.update(props) theirs = set() for props in other.mProperties.values(): theirs.update(props) return mine == theirs def getProperties(self, propname=None): return self.mProperties.get(propname.upper(), []) if propname else self.mProperties def setProperties(self, props): self.mProperties = props def addProperty(self, prop): self.mProperties.setdefault(prop.getName().upper(), []).append(prop) def hasProperty(self, propname): return propname.upper() in self.mProperties def countProperty(self, propname): return len(self.mProperties.get(propname.upper(), [])) def findFirstProperty(self, propname): return self.mProperties.get(propname.upper(), [None])[0] def removeProperty(self, prop): if prop.getName().upper() in self.mProperties: self.mProperties[prop.getName().upper()].remove(prop) if len(self.mProperties[prop.getName().upper()]) == 0: del self.mProperties[prop.getName().upper()] def removeProperties(self, propname): if propname.upper() in self.mProperties: del self.mProperties[propname.upper()] def getPropertyInteger(self, prop, type=None): return self.loadValueInteger(prop, type) def getPropertyString(self, prop): return self.loadValueString(prop) def getProperty(self, prop, value): return self.loadValue(prop, value) def finalise(self): raise NotImplemented def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems. Return a tuple containing two lists: the first describes problems that were fixed, the second problems that were not fixed. Caller can then decide what to do with unfixed issues. """ fixed = [] unfixed = [] # Cardinality tests for check in self.cardinalityChecks: check(fixed, unfixed, doFix) # Value constraints - these tests come from class specific attributes if self.propertyValueChecks is not None: for properties in self.mProperties.values(): for property in properties: propname = property.getName().upper() if propname in self.propertyValueChecks: if not self.propertyValueChecks[propname](property): # Cannot fix a bad property value logProblem = "[%s] Property value incorrect: %s" % (self.getType(), propname,) unfixed.append(logProblem) # Validate all subcomponents for component in self.mComponents: morefixed, moreunfixed = component.validate(doFix) fixed.extend(morefixed) unfixed.extend(moreunfixed) return fixed, unfixed def check_cardinality_1(self, fixed, unfixed, doFix): for propname in self.propertyCardinality_1: if self.countProperty(propname) != 1: # Cannot fix a missing required property logProblem = "[%s] Missing or too many required property: %s" % (self.getType(), propname) unfixed.append(logProblem) def check_cardinality_1_Fix_Empty(self, fixed, unfixed, doFix): for propname in self.propertyCardinality_1_Fix_Empty: if self.countProperty(propname) > 1: # Cannot fix too many required property logProblem = "[%s] Too many required property: %s" % (self.getType(), propname) unfixed.append(logProblem) elif self.countProperty(propname) == 0: # Possibly fix by adding empty property logProblem = "[%s] Missing required property: %s" % (self.getType(), propname) if doFix: self.addProperty(PyCalendarProperty(propname, "")) fixed.append(logProblem) else: unfixed.append(logProblem) def check_cardinality_0_1(self, fixed, unfixed, doFix): for propname in self.propertyCardinality_0_1: if self.countProperty(propname) > 1: # Cannot be fixed - no idea which one to delete logProblem = "[%s] Too many properties present: %s" % (self.getType(), propname) unfixed.append(logProblem) def check_cardinality_1_More(self, fixed, unfixed, doFix): for propname in self.propertyCardinality_1_More: if not self.countProperty(propname) > 0: # Cannot fix a missing required property logProblem = "[%s] Missing required property: %s" % (self.getType(), propname) unfixed.append(logProblem) def getText(self): s = StringIO() self.generate(s) return s.getvalue() def generate(self, os): # Header os.write(self.getBeginDelimiter()) os.write("\r\n") # Write each property self.writeProperties(os) # Write each embedded component based on specific order self.writeComponents(os) # Footer os.write(self.getEndDelimiter()) os.write("\r\n") def generateFiltered(self, os, filter): # Header os.write(self.getBeginDelimiter()) os.write("\r\n") # Write each property self.writePropertiesFiltered(os, filter) # Write each embedded component based on specific order self.writeComponentsFiltered(os, filter) # Footer os.write(self.getEndDelimiter()) os.write("\r\n") def writeXML(self, node, namespace): # Component element comp = XML.SubElement(node, xmldefs.makeTag(namespace, self.getType())) # Each property self.writePropertiesXML(comp, namespace) # Each component self.writeComponentsXML(comp, namespace) def writeXMLFiltered(self, node, namespace, filter): # Component element comp = XML.SubElement(node, xmldefs.makeTag(namespace, self.getType())) # Each property self.writePropertiesFilteredXML(comp, namespace, filter) # Each component self.writeComponentsFilteredXML(comp, namespace, filter) def sortedComponents(self): components = self.mComponents[:] sortedcomponents = [] # Write each component based on specific order orderedNames = self.sortedComponentNames() for name in orderedNames: # Group by name then sort by map key (UID/R-ID) namedcomponents = [] for component in tuple(components): if component.getType().upper() == name: namedcomponents.append(component) components.remove(component) for component in sorted(namedcomponents, key=lambda x: x.getSortKey()): sortedcomponents.append(component) # Write out the remainder sorted by name, sortKey if self.sortSubComponents: remainder = sorted(components, key=lambda x: (x.getType().upper(), x.getSortKey(),)) else: remainder = components for component in remainder: sortedcomponents.append(component) return sortedcomponents def writeComponents(self, os): # Write out the remainder for component in self.sortedComponents(): component.generate(os) def writeComponentsFiltered(self, os, filter): # Shortcut for all sub-components if filter.isAllSubComponents(): self.writeComponents(os) elif filter.hasSubComponentFilters(): for subcomp in self.sortedcomponents(): subfilter = filter.getSubComponentFilter(subcomp.getType()) if subfilter is not None: subcomp.generateFiltered(os, subfilter) def writeComponentsXML(self, node, namespace): if self.mComponents: comps = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.components)) # Write out the remainder for component in self.sortedComponents(): component.writeXML(comps, namespace) def writeComponentsFilteredXML(self, node, namespace, filter): if self.mComponents: comps = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.components)) # Shortcut for all sub-components if filter.isAllSubComponents(): self.writeXML(comps, namespace) elif filter.hasSubComponentFilters(): for subcomp in self.sortedcomponents(): subfilter = filter.getSubComponentFilter(subcomp.getType()) if subfilter is not None: subcomp.writeFilteredXML(comps, namespace, subfilter) def loadValue(self, value_name): if self.hasProperty(value_name): return self.findFirstProperty(value_name) return None def loadValueInteger(self, value_name, type=None): if type: if self.hasProperty(value_name): if type == PyCalendarValue.VALUETYPE_INTEGER: ivalue = self.findFirstProperty(value_name).getIntegerValue() if ivalue is not None: return ivalue.getValue() elif type == PyCalendarValue.VALUETYPE_UTC_OFFSET: uvalue = self.findFirstProperty(value_name).getUTCOffsetValue() if (uvalue is not None): return uvalue.getValue() return None else: return self.loadValueInteger(value_name, PyCalendarValue.VALUETYPE_INTEGER) def loadValueString(self, value_name): if self.hasProperty(value_name): tvalue = self.findFirstProperty(value_name).getTextValue() if (tvalue is not None): return tvalue.getValue() return None def loadValueDateTime(self, value_name): if self.hasProperty(value_name): dtvalue = self.findFirstProperty(value_name).getDateTimeValue() if dtvalue is not None: return dtvalue.getValue() return None def loadValueDuration(self, value_name): if self.hasProperty(value_name): dvalue = self.findFirstProperty(value_name).getDurationValue() if (dvalue is not None): return dvalue.getValue() return None def loadValuePeriod(self, value_name): if self.hasProperty(value_name): pvalue = self.findFirstProperty(value_name).getPeriodValue() if (pvalue is not None): return pvalue.getValue() return None def loadValueRRULE(self, value_name, value, add): # Get RRULEs if self.hasProperty(value_name): items = self.getProperties()[value_name] for iter in items: rvalue = iter.getRecurrenceValue() if (rvalue is not None): if add: value.addRule(rvalue.getValue()) else: value.subtractRule(rvalue.getValue()) return True else: return False def loadValueRDATE(self, value_name, value, add): # Get RDATEs if self.hasProperty(value_name): for iter in self.getProperties(value_name): mvalue = iter.getMultiValue() if (mvalue is not None): for obj in mvalue.getValues(): # cast to date-time if isinstance(obj, PyCalendarDateTimeValue): if add: value.addDT(obj.getValue()) else: value.subtractDT(obj.getValue()) elif isinstance(obj, PyCalendarPeriodValue): if add: value.addPeriod(obj.getValue().getStart()) else: value.subtractPeriod(obj.getValue().getStart()) return True else: return False def sortedPropertyKeys(self): keys = self.mProperties.keys() keys.sort() results = [] for skey in self.sortedPropertyKeyOrder(): if skey in keys: results.append(skey) keys.remove(skey) results.extend(keys) return results def sortedPropertyKeyOrder(self): return () def writeProperties(self, os): # Sort properties by name keys = self.sortedPropertyKeys() for key in keys: props = self.mProperties[key] for prop in props: prop.generate(os) def writePropertiesFiltered(self, os, filter): # Sort properties by name keys = self.sortedPropertyKeys() # Shortcut for all properties if filter.isAllProperties(): for key in keys: for prop in self.getProperties(key): prop.generate(os) elif filter.hasPropertyFilters(): for key in keys: for prop in self.getProperties(key): prop.generateFiltered(os, filter) def writePropertiesXML(self, node, namespace): properties = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.properties)) # Sort properties by name keys = self.sortedPropertyKeys() for key in keys: props = self.mProperties[key] for prop in props: prop.writeXML(properties, namespace) def writePropertiesFilteredXML(self, node, namespace, filter): props = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.properties)) # Sort properties by name keys = self.sortedPropertyKeys() # Shortcut for all properties if filter.isAllProperties(): for key in keys: for prop in self.getProperties(key): prop.writeXML(props, namespace) elif filter.hasPropertyFilters(): for key in keys: for prop in self.getProperties(key): prop.writeFilteredXML(props, namespace, filter) def loadPrivateValue(self, value_name): # Read it in from properties list and then delete the property from the # main list result = self.loadValueString(value_name) if (result is not None): self.removeProperties(value_name) return result def writePrivateProperty(self, os, key, value): prop = PyCalendarProperty(name=key, value=value) prop.generate(os) def editProperty(self, propname, propvalue): # Remove existing items self.removeProperties(propname) # Now create properties if propvalue: self.addProperty(PyCalendarProperty(name=propname, value=propvalue)) pycalendar-2.0~svn13177/src/pycalendar/vavailability.py0000644000175000017500000000715412101017573022164 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar import itipdefinitions from pycalendar.component import PyCalendarComponent from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS class PyCalendarVAvailability(PyCalendarComponent): propertyCardinality_1 = ( definitions.cICalProperty_DTSTAMP, definitions.cICalProperty_UID, ) propertyCardinality_0_1 = ( definitions.cICalProperty_BUSYTYPE, definitions.cICalProperty_CLASS, definitions.cICalProperty_CREATED, definitions.cICalProperty_DESCRIPTION, definitions.cICalProperty_DTSTART, definitions.cICalProperty_LAST_MODIFIED, definitions.cICalProperty_ORGANIZER, definitions.cICalProperty_SEQUENCE, definitions.cICalProperty_SUMMARY, definitions.cICalProperty_URL, definitions.cICalProperty_RECURRENCE_ID, definitions.cICalProperty_DTEND, definitions.cICalProperty_DURATION, ) propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None): super(PyCalendarVAvailability, self).__init__(parent=parent) def duplicate(self, parent=None): return super(PyCalendarVAvailability, self).duplicate(parent=parent) def getType(self): return definitions.cICalComponent_VAVAILABILITY def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VAVAILABILITY def finalise(self): super(PyCalendarVAvailability, self).finalise() def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended to that. """ fixed, unfixed = super(PyCalendarVAvailability, self).validate(doFix) # Extra constraint: only one of DTEND or DURATION if self.hasProperty(definitions.cICalProperty_DTEND) and self.hasProperty(definitions.cICalProperty_DURATION): # Fix by removing the DTEND logProblem = "[%s] Properties must not both be present: %s, %s" % ( self.getType(), definitions.cICalProperty_DTEND, definitions.cICalProperty_DURATION, ) if doFix: self.removeProperties(definitions.cICalProperty_DTEND) fixed.append(logProblem) else: unfixed.append(logProblem) return fixed, unfixed def addComponent(self, comp): # We can embed the available components only if comp.getType() == definitions.cICalComponent_AVAILABLE: super(PyCalendarVAvailability, self).addComponent(comp) else: raise ValueError def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, definitions.cICalProperty_DTSTART, definitions.cICalProperty_DURATION, definitions.cICalProperty_DTEND, ) pycalendar-2.0~svn13177/src/pycalendar/property.py0000644000175000017500000006733712101017573021221 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions, xmldefs from pycalendar import stringutils from pycalendar.attribute import PyCalendarAttribute from pycalendar.binaryvalue import PyCalendarBinaryValue from pycalendar.caladdressvalue import PyCalendarCalAddressValue from pycalendar.datetime import PyCalendarDateTime from pycalendar.datetimevalue import PyCalendarDateTimeValue from pycalendar.duration import PyCalendarDuration from pycalendar.durationvalue import PyCalendarDurationValue from pycalendar.exceptions import PyCalendarInvalidProperty from pycalendar.integervalue import PyCalendarIntegerValue from pycalendar.multivalue import PyCalendarMultiValue from pycalendar.period import PyCalendarPeriod from pycalendar.periodvalue import PyCalendarPeriodValue from pycalendar.plaintextvalue import PyCalendarPlainTextValue from pycalendar.recurrence import PyCalendarRecurrence from pycalendar.recurrencevalue import PyCalendarRecurrenceValue from pycalendar.requeststatusvalue import PyCalendarRequestStatusValue from pycalendar.unknownvalue import PyCalendarUnknownValue from pycalendar.urivalue import PyCalendarURIValue from pycalendar.utcoffsetvalue import PyCalendarUTCOffsetValue from pycalendar.utils import decodeParameterValue from pycalendar.value import PyCalendarValue import cStringIO as StringIO import xml.etree.cElementTree as XML class PyCalendarProperty(object): sDefaultValueTypeMap = { # 5545 Section 3.7 definitions.cICalProperty_CALSCALE : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_METHOD : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_PRODID : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_VERSION : PyCalendarValue.VALUETYPE_TEXT, # 5545 Section 3.8.1 definitions.cICalProperty_ATTACH : PyCalendarValue.VALUETYPE_URI, definitions.cICalProperty_CATEGORIES : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_CLASS : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_COMMENT : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_DESCRIPTION : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_GEO : PyCalendarValue.VALUETYPE_GEO, definitions.cICalProperty_LOCATION : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_PERCENT_COMPLETE : PyCalendarValue.VALUETYPE_INTEGER, definitions.cICalProperty_PRIORITY : PyCalendarValue.VALUETYPE_INTEGER, definitions.cICalProperty_RESOURCES : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_STATUS : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_SUMMARY : PyCalendarValue.VALUETYPE_TEXT, # 5545 Section 3.8.2 definitions.cICalProperty_COMPLETED : PyCalendarValue.VALUETYPE_DATETIME, definitions.cICalProperty_DTEND : PyCalendarValue.VALUETYPE_DATETIME, definitions.cICalProperty_DUE : PyCalendarValue.VALUETYPE_DATETIME, definitions.cICalProperty_DTSTART : PyCalendarValue.VALUETYPE_DATETIME, definitions.cICalProperty_DURATION : PyCalendarValue.VALUETYPE_DURATION, definitions.cICalProperty_FREEBUSY : PyCalendarValue.VALUETYPE_PERIOD, definitions.cICalProperty_TRANSP : PyCalendarValue.VALUETYPE_TEXT, # 5545 Section 3.8.3 definitions.cICalProperty_TZID : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_TZNAME : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_TZOFFSETFROM : PyCalendarValue.VALUETYPE_UTC_OFFSET, definitions.cICalProperty_TZOFFSETTO : PyCalendarValue.VALUETYPE_UTC_OFFSET, definitions.cICalProperty_TZURL : PyCalendarValue.VALUETYPE_URI, # 5545 Section 3.8.4 definitions.cICalProperty_ATTENDEE : PyCalendarValue.VALUETYPE_CALADDRESS, definitions.cICalProperty_CONTACT : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_ORGANIZER : PyCalendarValue.VALUETYPE_CALADDRESS, definitions.cICalProperty_RECURRENCE_ID : PyCalendarValue.VALUETYPE_DATETIME, definitions.cICalProperty_RELATED_TO : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_URL : PyCalendarValue.VALUETYPE_URI, definitions.cICalProperty_UID : PyCalendarValue.VALUETYPE_TEXT, # 5545 Section 3.8.5 definitions.cICalProperty_EXDATE : PyCalendarValue.VALUETYPE_DATETIME, definitions.cICalProperty_EXRULE : PyCalendarValue.VALUETYPE_RECUR, # 2445 only definitions.cICalProperty_RDATE : PyCalendarValue.VALUETYPE_DATETIME, definitions.cICalProperty_RRULE : PyCalendarValue.VALUETYPE_RECUR, # 5545 Section 3.8.6 definitions.cICalProperty_ACTION : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_REPEAT : PyCalendarValue.VALUETYPE_INTEGER, definitions.cICalProperty_TRIGGER : PyCalendarValue.VALUETYPE_DURATION, # 5545 Section 3.8.7 definitions.cICalProperty_CREATED : PyCalendarValue.VALUETYPE_DATETIME, definitions.cICalProperty_DTSTAMP : PyCalendarValue.VALUETYPE_DATETIME, definitions.cICalProperty_LAST_MODIFIED : PyCalendarValue.VALUETYPE_DATETIME, definitions.cICalProperty_SEQUENCE : PyCalendarValue.VALUETYPE_INTEGER, # 5545 Section 3.8.8 definitions.cICalProperty_REQUEST_STATUS : PyCalendarValue.VALUETYPE_REQUEST_STATUS, # Extensions: draft-daboo-valarm-extensions-03 definitions.cICalProperty_ACKNOWLEDGED : PyCalendarValue.VALUETYPE_DATETIME, # Apple Extensions definitions.cICalProperty_XWRCALNAME : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_XWRCALDESC : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_XWRALARMUID : PyCalendarValue.VALUETYPE_TEXT, # Mulberry extensions definitions.cICalProperty_ACTION_X_SPEAKTEXT : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalProperty_ALARM_X_LASTTRIGGER : PyCalendarValue.VALUETYPE_DATETIME, definitions.cICalProperty_ALARM_X_ALARMSTATUS : PyCalendarValue.VALUETYPE_TEXT, } sValueTypeMap = { # 5545 Section 3.3 definitions.cICalValue_BINARY : PyCalendarValue.VALUETYPE_BINARY, definitions.cICalValue_BOOLEAN : PyCalendarValue.VALUETYPE_BOOLEAN, definitions.cICalValue_CAL_ADDRESS : PyCalendarValue.VALUETYPE_CALADDRESS, definitions.cICalValue_DATE : PyCalendarValue.VALUETYPE_DATE, definitions.cICalValue_DATE_TIME : PyCalendarValue.VALUETYPE_DATETIME, definitions.cICalValue_DURATION : PyCalendarValue.VALUETYPE_DURATION, definitions.cICalValue_FLOAT : PyCalendarValue.VALUETYPE_FLOAT, definitions.cICalValue_INTEGER : PyCalendarValue.VALUETYPE_INTEGER, definitions.cICalValue_PERIOD : PyCalendarValue.VALUETYPE_PERIOD, definitions.cICalValue_RECUR : PyCalendarValue.VALUETYPE_RECUR, definitions.cICalValue_TEXT : PyCalendarValue.VALUETYPE_TEXT, definitions.cICalValue_TIME : PyCalendarValue.VALUETYPE_TIME, definitions.cICalValue_URI : PyCalendarValue.VALUETYPE_URI, definitions.cICalValue_UTC_OFFSET : PyCalendarValue.VALUETYPE_UTC_OFFSET, } sTypeValueMap = { # 5545 Section 3.3 PyCalendarValue.VALUETYPE_BINARY : definitions.cICalValue_BINARY, PyCalendarValue.VALUETYPE_BOOLEAN : definitions.cICalValue_BOOLEAN, PyCalendarValue.VALUETYPE_CALADDRESS : definitions.cICalValue_CAL_ADDRESS, PyCalendarValue.VALUETYPE_DATE : definitions.cICalValue_DATE, PyCalendarValue.VALUETYPE_DATETIME : definitions.cICalValue_DATE_TIME, PyCalendarValue.VALUETYPE_DURATION : definitions.cICalValue_DURATION, PyCalendarValue.VALUETYPE_FLOAT : definitions.cICalValue_FLOAT, PyCalendarValue.VALUETYPE_GEO : definitions.cICalValue_FLOAT, PyCalendarValue.VALUETYPE_INTEGER : definitions.cICalValue_INTEGER, PyCalendarValue.VALUETYPE_PERIOD : definitions.cICalValue_PERIOD, PyCalendarValue.VALUETYPE_RECUR : definitions.cICalValue_RECUR, PyCalendarValue.VALUETYPE_TEXT : definitions.cICalValue_TEXT, PyCalendarValue.VALUETYPE_REQUEST_STATUS : definitions.cICalValue_TEXT, PyCalendarValue.VALUETYPE_TIME : definitions.cICalValue_TIME, PyCalendarValue.VALUETYPE_URI : definitions.cICalValue_URI, PyCalendarValue.VALUETYPE_UTC_OFFSET : definitions.cICalValue_UTC_OFFSET, } sMultiValues = set(( definitions.cICalProperty_CATEGORIES, definitions.cICalProperty_RESOURCES, definitions.cICalProperty_FREEBUSY, definitions.cICalProperty_EXDATE, definitions.cICalProperty_RDATE, )) @staticmethod def registerDefaultValue(propname, valuetype): if propname not in PyCalendarProperty.sDefaultValueTypeMap: PyCalendarProperty.sDefaultValueTypeMap[propname] = valuetype def __init__(self, name=None, value=None, valuetype=None): self._init_PyCalendarProperty() self.mName = name if name is not None else "" # The None check speeds up .duplicate() if value is None: pass # Order these based on most likely occurrence to speed up this method elif isinstance(value, str): self._init_attr_value_text(value, valuetype if valuetype else PyCalendarProperty.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_UNKNOWN)) elif isinstance(value, PyCalendarDateTime): self._init_attr_value_datetime(value) elif isinstance(value, PyCalendarDuration): self._init_attr_value_duration(value) elif isinstance(value, PyCalendarRecurrence): self._init_attr_value_recur(value) elif isinstance(value, PyCalendarPeriod): self._init_attr_value_period(value) elif isinstance(value, int): self._init_attr_value_int(value) elif isinstance(value, list): if name.upper() == definitions.cICalProperty_REQUEST_STATUS: self._init_attr_value_requeststatus(value) else: period_list = False if len(value) != 0: period_list = isinstance(value[0], PyCalendarPeriod) if period_list: self._init_attr_value_periodlist(value) else: self._init_attr_value_datetimelist(value) elif isinstance(value, PyCalendarUTCOffsetValue): self._init_attr_value_utcoffset(value) def duplicate(self): other = PyCalendarProperty(self.mName) for attrname, attrs in self.mAttributes.items(): other.mAttributes[attrname] = [i.duplicate() for i in attrs] other.mValue = self.mValue.duplicate() return other def __hash__(self): return hash(( self.mName, tuple([tuple(self.mAttributes[attrname]) for attrname in sorted(self.mAttributes.keys())]), self.mValue, )) def __ne__(self, other): return not self.__eq__(other) def __eq__(self, other): if not isinstance(other, PyCalendarProperty): return False return self.mName == other.mName and self.mValue == other.mValue and self.mAttributes == other.mAttributes def __repr__(self): return "PyCalendarProperty: %s" % (self.getText(),) def __str__(self): return self.getText() def getName(self): return self.mName def setName(self, name): self.mName = name def getAttributes(self): return self.mAttributes def setAttributes(self, attributes): self.mAttributes = dict([(k.upper(), v) for k, v in attributes.iteritems()]) def hasAttribute(self, attr): return attr.upper() in self.mAttributes def getAttributeValue(self, attr): return self.mAttributes[attr.upper()][0].getFirstValue() def addAttribute(self, attr): self.mAttributes.setdefault(attr.getName().upper(), []).append(attr) def replaceAttribute(self, attr): self.mAttributes[attr.getName().upper()] = [attr] def removeAttributes(self, attr): if attr.upper() in self.mAttributes: del self.mAttributes[attr.upper()] def getValue(self): return self.mValue def getBinaryValue(self): if isinstance(self.mValue, PyCalendarBinaryValue): return self.mValue else: return None def getCalAddressValue(self): if isinstance(self.mValue, PyCalendarCalAddressValue): return self.mValue else: return None def getDateTimeValue(self): if isinstance(self.mValue, PyCalendarDateTimeValue): return self.mValue else: return None def getDurationValue(self): if isinstance(self.mValue, PyCalendarDurationValue): return self.mValue else: return None def getIntegerValue(self): if isinstance(self.mValue, PyCalendarIntegerValue): return self.mValue else: return None def getMultiValue(self): if isinstance(self.mValue, PyCalendarMultiValue): return self.mValue else: return None def getPeriodValue(self): if isinstance(self.mValue, PyCalendarPeriodValue): return self.mValue else: return None def getRecurrenceValue(self): if isinstance(self.mValue, PyCalendarRecurrenceValue): return self.mValue else: return None def getTextValue(self): if isinstance(self.mValue, PyCalendarPlainTextValue): return self.mValue else: return None def getURIValue(self): if isinstance(self.mValue, PyCalendarURIValue): return self.mValue else: return None def getUTCOffsetValue(self): if isinstance(self.mValue, PyCalendarUTCOffsetValue): return self.mValue else: return None def parse(self, data): # Look for attribute or value delimiter prop_name, txt = stringutils.strduptokenstr(data, ";:") if not prop_name: raise PyCalendarInvalidProperty("Invalid property", data) # We have the name self.mName = prop_name # TODO: Try to use static string for the name # Now loop getting data try: while txt: if txt[0] == ';': # Parse attribute # Move past delimiter txt = txt[1:] # Get quoted string or token attribute_name, txt = stringutils.strduptokenstr(txt, "=") if attribute_name is None: raise PyCalendarInvalidProperty("Invalid property", data) txt = txt[1:] attribute_value, txt = stringutils.strduptokenstr(txt, ":;,") if attribute_value is None: raise PyCalendarInvalidProperty("Invalid property", data) # Now add attribute value (decode ^-escpaing) attrvalue = PyCalendarAttribute(name=attribute_name, value=decodeParameterValue(attribute_value)) self.mAttributes.setdefault(attribute_name.upper(), []).append(attrvalue) # Look for additional values while txt[0] == ',': txt = txt[1:] attribute_value2, txt = stringutils.strduptokenstr(txt, ":;,") if attribute_value2 is None: raise PyCalendarInvalidProperty("Invalid property", data) attrvalue.addValue(decodeParameterValue(attribute_value2)) elif txt[0] == ':': txt = txt[1:] self.createValue(txt) txt = None else: # We should never get here but if we do we need to terminate the loop raise PyCalendarInvalidProperty("Invalid property", data) except IndexError: raise PyCalendarInvalidProperty("Invalid property", data) # We must have a value of some kind if self.mValue is None: raise PyCalendarInvalidProperty("Invalid property", data) return True def getText(self): os = StringIO.StringIO() self.generate(os) return os.getvalue() def generate(self, os): # Write it out always with value self.generateValue(os, False) def generateFiltered(self, os, filter): # Check for property in filter and whether value is written out test, novalue = filter.testPropertyValue(self.mName.upper()) if test: self.generateValue(os, novalue) # Write out the actual property, possibly skipping the value def generateValue(self, os, novalue): self.setupValueAttribute() # Must write to temp buffer and then wrap sout = StringIO.StringIO() sout.write(self.mName) # Write all attributes for key in sorted(self.mAttributes.keys()): for attr in self.mAttributes[key]: sout.write(";") attr.generate(sout) # Write value sout.write(":") if self.mValue and not novalue: self.mValue.generate(sout) # Get string text temp = sout.getvalue() sout.close() # Look for line length exceed if len(temp) < 75: os.write(temp) else: # Look for valid utf8 range and write that out start = 0 written = 0 while written < len(temp): # Start 74 chars on from where we are offset = start + 74 if offset >= len(temp): line = temp[start:] os.write(line) written = len(temp) else: # Check whether next char is valid utf8 lead byte while (temp[offset] > 0x7F) and ((ord(temp[offset]) & 0xC0) == 0x80): # Step back until we have a valid char offset -= 1 line = temp[start:offset] os.write(line) os.write("\r\n ") written += offset - start start = offset os.write("\r\n") def writeXML(self, node, namespace): # Write it out always with value self.generateValueXML(node, namespace, False) def generateFilteredXML(self, node, namespace, filter): # Check for property in filter and whether value is written out test, novalue = filter.testPropertyValue(self.mName.upper()) if test: self.generateValueXML(node, namespace, novalue) # Write out the actual property, possibly skipping the value def generateValueXML(self, node, namespace, novalue): prop = XML.SubElement(node, xmldefs.makeTag(namespace, self.getName())) # Write all attributes if len(self.mAttributes): params = XML.SubElement(prop, xmldefs.makeTag(namespace, xmldefs.parameters)) for key in sorted(self.mAttributes.keys()): for attr in self.mAttributes[key]: if attr.getName() != "VALUE": attr.writeXML(params, namespace) # Write value if self.mValue and not novalue: self.mValue.writeXML(prop, namespace) def _init_PyCalendarProperty(self): self.mName = "" self.mAttributes = {} self.mValue = None def createValue(self, data): # Tidy first self.mValue = None # Get value type from property name type = PyCalendarProperty.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_UNKNOWN) # Check whether custom value is set if definitions.cICalAttribute_VALUE in self.mAttributes: type = PyCalendarProperty.sValueTypeMap.get(self.getAttributeValue(definitions.cICalAttribute_VALUE), type) # Check for multivalued if self.mName.upper() in PyCalendarProperty.sMultiValues: self.mValue = PyCalendarMultiValue(type) else: # Create the type self.mValue = PyCalendarValue.createFromType(type) # Now parse the data try: self.mValue.parse(data) except ValueError: raise PyCalendarInvalidProperty("Invalid property value", data) # Special post-create for some types if type in (PyCalendarValue.VALUETYPE_TIME, PyCalendarValue.VALUETYPE_DATETIME): # Look for TZID attribute tzid = None if (self.hasAttribute(definitions.cICalAttribute_TZID)): tzid = self.getAttributeValue(definitions.cICalAttribute_TZID) if isinstance(self.mValue, PyCalendarDateTimeValue): self.mValue.getValue().setTimezoneID(tzid) elif isinstance(self.mValue, PyCalendarMultiValue): for item in self.mValue.getValues(): if isinstance(item, PyCalendarDateTimeValue): item.getValue().setTimezoneID(tzid) def setValue(self, value): # Tidy first self.mValue = None # Get value type from property name type = PyCalendarProperty.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_TEXT) # Check whether custom value is set if definitions.cICalAttribute_VALUE in self.mAttributes: type = PyCalendarProperty.sValueTypeMap.get(self.getAttributeValue(definitions.cICalAttribute_VALUE), type) # Check for multivalued if self.mName.upper() in PyCalendarProperty.sMultiValues: self.mValue = PyCalendarMultiValue(type) else: # Create the type self.mValue = PyCalendarValue.createFromType(type) self.mValue.setValue(value) # Special post-create for some types if type in (PyCalendarValue.VALUETYPE_TIME, PyCalendarValue.VALUETYPE_DATETIME): # Look for TZID attribute tzid = None if (self.hasAttribute(definitions.cICalAttribute_TZID)): tzid = self.getAttributeValue(definitions.cICalAttribute_TZID) if isinstance(self.mValue, PyCalendarDateTimeValue): self.mValue.getValue().setTimezoneID(tzid) elif isinstance(self.mValue, PyCalendarMultiValue): for item in self.mValue.getValues(): if isinstance(item, PyCalendarDateTimeValue): item.getValue().setTimezoneID(tzid) def setupValueAttribute(self): if definitions.cICalAttribute_VALUE in self.mAttributes: del self.mAttributes[definitions.cICalAttribute_VALUE] # Only if we have a value right now if self.mValue is None: return # See if current type is default for this property. If there is no mapping available, # then always add VALUE if it is not TEXT. default_type = self.sDefaultValueTypeMap.get(self.mName.upper()) actual_type = self.mValue.getType() if default_type is None or default_type != actual_type: actual_value = self.sTypeValueMap.get(actual_type) if actual_value is not None and (default_type is not None or actual_type != PyCalendarValue.VALUETYPE_TEXT): self.mAttributes.setdefault(definitions.cICalAttribute_VALUE, []).append(PyCalendarAttribute(name=definitions.cICalAttribute_VALUE, value=actual_value)) # Creation def _init_attr_value_int(self, ival): # Value self.mValue = PyCalendarIntegerValue(value=ival) # Attributes self.setupValueAttribute() def _init_attr_value_text(self, txt, value_type): # Value self.mValue = PyCalendarValue.createFromType(value_type) if isinstance(self.mValue, PyCalendarPlainTextValue) or isinstance(self.mValue, PyCalendarUnknownValue): self.mValue.setValue(txt) # Attributes self.setupValueAttribute() def _init_attr_value_requeststatus(self, reqstatus): # Value self.mValue = PyCalendarRequestStatusValue(reqstatus) # Attributes self.setupValueAttribute() def _init_attr_value_datetime(self, dt): # Value self.mValue = PyCalendarDateTimeValue(value=dt) # Attributes self.setupValueAttribute() # Look for timezone if not dt.isDateOnly() and dt.local(): if definitions.cICalAttribute_TZID in self.mAttributes: del self.mAttributes[definitions.cICalAttribute_TZID] self.mAttributes.setdefault(definitions.cICalAttribute_TZID, []).append( PyCalendarAttribute(name=definitions.cICalAttribute_TZID, value=dt.getTimezoneID())) def _init_attr_value_datetimelist(self, dtl): # Value date_only = (len(dtl) > 0) and dtl[0].isDateOnly() if date_only: self.mValue = PyCalendarMultiValue(PyCalendarValue.VALUETYPE_DATE) else: self.mValue = PyCalendarMultiValue(PyCalendarValue.VALUETYPE_DATETIME) for dt in dtl: self.mValue.addValue(PyCalendarDateTimeValue(dt)) # Attributes self.setupValueAttribute() # Look for timezone if ((len(dtl) > 0) and not dtl[0].isDateOnly() and dtl[0].local()): if definitions.cICalAttribute_TZID in self.mAttributes: del self.mAttributes[definitions.cICalAttribute_TZID] self.mAttributes.setdefault(definitions.cICalAttribute_TZID, []).append( PyCalendarAttribute(name=definitions.cICalAttribute_TZID, value=dtl[0].getTimezoneID())) def _init_attr_value_periodlist(self, periodlist): # Value self.mValue = PyCalendarMultiValue(PyCalendarValue.VALUETYPE_PERIOD) for period in periodlist: self.mValue.addValue(PyCalendarPeriodValue(period)) # Attributes self.setupValueAttribute() def _init_attr_value_duration(self, du): # Value self.mValue = PyCalendarDurationValue(value=du) # Attributes self.setupValueAttribute() def _init_attr_value_period(self, pe): # Value self.mValue = PyCalendarPeriodValue(value=pe) # Attributes self.setupValueAttribute() def _init_attr_value_recur(self, recur): # Value self.mValue = PyCalendarRecurrenceValue(value=recur) # Attributes self.setupValueAttribute() def _init_attr_value_utcoffset(self, utcoffset): # Value self.mValue = PyCalendarUTCOffsetValue() self.mValue.setValue(utcoffset.getValue()) # Attributes self.setupValueAttribute() pycalendar-2.0~svn13177/src/pycalendar/vcard/0000755000175000017500000000000012322631215020042 5ustar rahulrahulpycalendar-2.0~svn13177/src/pycalendar/vcard/validation.py0000644000175000017500000000165512101017573022555 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar.validation import partial, PropertyValueChecks from pycalendar.vcard import definitions VCARD_VALUE_CHECKS = { definitions.Property_VERSION: partial(PropertyValueChecks.stringValue, "3.0"), definitions.Property_PROFILE: partial(PropertyValueChecks.stringValue, "VCARD"), } pycalendar-2.0~svn13177/src/pycalendar/vcard/property.py0000644000175000017500000005127012101017573022305 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import stringutils from pycalendar.adr import Adr from pycalendar.adrvalue import AdrValue from pycalendar.attribute import PyCalendarAttribute from pycalendar.datetime import PyCalendarDateTime from pycalendar.datetimevalue import PyCalendarDateTimeValue from pycalendar.exceptions import PyCalendarInvalidProperty from pycalendar.integervalue import PyCalendarIntegerValue from pycalendar.multivalue import PyCalendarMultiValue from pycalendar.n import N from pycalendar.nvalue import NValue from pycalendar.orgvalue import OrgValue from pycalendar.parser import ParserContext from pycalendar.plaintextvalue import PyCalendarPlainTextValue from pycalendar.unknownvalue import PyCalendarUnknownValue from pycalendar.utcoffsetvalue import PyCalendarUTCOffsetValue from pycalendar.utils import decodeParameterValue from pycalendar.value import PyCalendarValue from pycalendar.vcard import definitions import cStringIO as StringIO handleOptions = ("allow", "ignore", "fix", "raise") missingParameterValues = "fix" class Property(object): sDefaultValueTypeMap = { # 2425 Properties definitions.Property_SOURCE : PyCalendarValue.VALUETYPE_URI, definitions.Property_NAME : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_PROFILE : PyCalendarValue.VALUETYPE_TEXT, # 2426 vCard Properties # 2426 Section 3.1 definitions.Property_FN : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_N : PyCalendarValue.VALUETYPE_N, definitions.Property_NICKNAME : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_PHOTO : PyCalendarValue.VALUETYPE_BINARY, definitions.Property_BDAY : PyCalendarValue.VALUETYPE_DATE, # 2426 Section 3.2 definitions.Property_ADR : PyCalendarValue.VALUETYPE_ADR, definitions.Property_LABEL : PyCalendarValue.VALUETYPE_TEXT, # 2426 Section 3.3 definitions.Property_TEL : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_EMAIL : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_MAILER : PyCalendarValue.VALUETYPE_TEXT, # 2426 Section 3.4 definitions.Property_TZ : PyCalendarValue.VALUETYPE_UTC_OFFSET, definitions.Property_GEO : PyCalendarValue.VALUETYPE_GEO, # 2426 Section 3.5 definitions.Property_TITLE : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_ROLE : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_LOGO : PyCalendarValue.VALUETYPE_BINARY, definitions.Property_AGENT : PyCalendarValue.VALUETYPE_VCARD, definitions.Property_ORG : PyCalendarValue.VALUETYPE_ORG, # 2426 Section 3.6 definitions.Property_CATEGORIES : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_NOTE : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_PRODID : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_REV : PyCalendarValue.VALUETYPE_DATETIME, definitions.Property_SORT_STRING : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_SOUND : PyCalendarValue.VALUETYPE_BINARY, definitions.Property_UID : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_URL : PyCalendarValue.VALUETYPE_URI, definitions.Property_VERSION : PyCalendarValue.VALUETYPE_TEXT, # 2426 Section 3.7 definitions.Property_CLASS : PyCalendarValue.VALUETYPE_TEXT, definitions.Property_KEY : PyCalendarValue.VALUETYPE_BINARY, } sValueTypeMap = { definitions.Value_BINARY : PyCalendarValue.VALUETYPE_BINARY, definitions.Value_BOOLEAN : PyCalendarValue.VALUETYPE_BOOLEAN, definitions.Value_DATE : PyCalendarValue.VALUETYPE_DATE, definitions.Value_DATE_TIME : PyCalendarValue.VALUETYPE_DATETIME, definitions.Value_FLOAT : PyCalendarValue.VALUETYPE_FLOAT, definitions.Value_INTEGER : PyCalendarValue.VALUETYPE_INTEGER, definitions.Value_TEXT : PyCalendarValue.VALUETYPE_TEXT, definitions.Value_TIME : PyCalendarValue.VALUETYPE_TIME, definitions.Value_URI : PyCalendarValue.VALUETYPE_URI, definitions.Value_UTCOFFSET : PyCalendarValue.VALUETYPE_UTC_OFFSET, definitions.Value_VCARD : PyCalendarValue.VALUETYPE_VCARD, } sTypeValueMap = { PyCalendarValue.VALUETYPE_ADR : definitions.Value_TEXT, PyCalendarValue.VALUETYPE_BINARY : definitions.Value_BINARY, PyCalendarValue.VALUETYPE_BOOLEAN : definitions.Value_BOOLEAN, PyCalendarValue.VALUETYPE_DATE : definitions.Value_DATE, PyCalendarValue.VALUETYPE_DATETIME : definitions.Value_DATE_TIME, PyCalendarValue.VALUETYPE_FLOAT : definitions.Value_FLOAT, PyCalendarValue.VALUETYPE_GEO : definitions.Value_FLOAT, PyCalendarValue.VALUETYPE_INTEGER : definitions.Value_INTEGER, PyCalendarValue.VALUETYPE_N : definitions.Value_TEXT, PyCalendarValue.VALUETYPE_ORG : definitions.Value_TEXT, PyCalendarValue.VALUETYPE_TEXT : definitions.Value_TEXT, PyCalendarValue.VALUETYPE_TIME : definitions.Value_TIME, PyCalendarValue.VALUETYPE_URI : definitions.Value_URI, PyCalendarValue.VALUETYPE_UTC_OFFSET : definitions.Value_UTCOFFSET, PyCalendarValue.VALUETYPE_VCARD : definitions.Value_VCARD, } sMultiValues = set(( definitions.Property_NICKNAME, definitions.Property_CATEGORIES, )) sTextVariants = set(( definitions.Property_ADR, definitions.Property_N, definitions.Property_ORG, )) def __init__(self, group=None, name=None, value=None, valuetype=None): self._init_PyCalendarProperty() self.mGroup = group self.mName = name if name is not None else "" if isinstance(value, int): self._init_attr_value_int(value) elif isinstance(value, str): self._init_attr_value_text(value, valuetype if valuetype else Property.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_TEXT)) elif isinstance(value, PyCalendarDateTime): self._init_attr_value_datetime(value) elif isinstance(value, Adr): self._init_attr_value_adr(value) elif isinstance(value, N): self._init_attr_value_n(value) elif isinstance(value, list) or isinstance(value, tuple): if name.upper() == definitions.Property_ORG: self._init_attr_value_org(value) elif name.upper() == definitions.Property_GEO: self._init_attr_value_geo(value) else: # Assume everything else is a text list self._init_attr_value_text_list(value) elif isinstance(value, PyCalendarUTCOffsetValue): self._init_attr_value_utcoffset(value) def duplicate(self): other = Property(self.mGroup, self.mName) for attrname, attrs in self.mAttributes.items(): other.mAttributes[attrname] = [i.duplicate() for i in attrs] other.mValue = self.mValue.duplicate() return other def __hash__(self): return hash(( self.mName, tuple([tuple(self.mAttributes[attrname]) for attrname in sorted(self.mAttributes.keys())]), self.mValue, )) def __ne__(self, other): return not self.__eq__(other) def __eq__(self, other): if not isinstance(other, Property): return False return ( self.mGroup == self.mGroup and self.mName == other.mName and self.mValue == other.mValue and self.mAttributes == other.mAttributes ) def __repr__(self): return "vCard Property: %s" % (self.getText(),) def __str__(self): return self.getText() def getGroup(self): return self.mGroup def setGroup(self, group): self.mGroup = group def getName(self): return self.mName def setName(self, name): self.mName = name def getAttributes(self): return self.mAttributes def setAttributes(self, attributes): self.mAttributes = dict([(k.upper(), v) for k, v in attributes.iteritems()]) def hasAttribute(self, attr): return attr.upper() in self.mAttributes def getAttributeValue(self, attr): return self.mAttributes[attr.upper()][0].getFirstValue() def addAttribute(self, attr): self.mAttributes.setdefault(attr.getName().upper(), []).append(attr) def replaceAttribute(self, attr): self.mAttributes[attr.getName().upper()] = [attr] def removeAttributes(self, attr): if attr.upper() in self.mAttributes: del self.mAttributes[attr.upper()] def getValue(self): return self.mValue def parse(self, data): # Look for attribute or value delimiter prop_name, txt = stringutils.strduptokenstr(data, ";:") if not prop_name: raise PyCalendarInvalidProperty("Invalid property", data) # Check for group prefix splits = prop_name.split(".", 1) if len(splits) == 2: # We have both group and name self.mGroup = splits[0] self.mName = splits[1] else: # We have the name self.mName = prop_name # Now loop getting data try: stripValueSpaces = False # Fix for AB.app base PHOTO properties that use two spaces at start of line while txt: if txt[0] == ';': # Parse attribute # Move past delimiter txt = txt[1:] # Get quoted string or token - in iCalendar we only look for "=" here # but for "broken" vCard BASE64 property we need to also terminate on # ":;" attribute_name, txt = stringutils.strduptokenstr(txt, "=:;") if attribute_name is None: raise PyCalendarInvalidProperty("Invalid property", data) if txt[0] != "=": # Deal with parameters without values if ParserContext.VCARD_2_NO_PARAMETER_VALUES == ParserContext.PARSER_RAISE: raise PyCalendarInvalidProperty("Invalid property parameter", data) elif ParserContext.VCARD_2_NO_PARAMETER_VALUES == ParserContext.PARSER_ALLOW: attribute_value = None else: # PARSER_IGNORE and PARSER_FIX attribute_name = None if attribute_name.upper() == "BASE64" and ParserContext.VCARD_2_BASE64 == ParserContext.PARSER_FIX: attribute_name = definitions.Parameter_ENCODING attribute_value = definitions.Parameter_Value_ENCODING_B stripValueSpaces = True else: txt = txt[1:] attribute_value, txt = stringutils.strduptokenstr(txt, ":;,") if attribute_value is None: raise PyCalendarInvalidProperty("Invalid property", data) # Now add attribute value (decode ^-escaping) if attribute_name is not None: attrvalue = PyCalendarAttribute(name=attribute_name, value=decodeParameterValue(attribute_value)) self.mAttributes.setdefault(attribute_name.upper(), []).append(attrvalue) # Look for additional values while txt[0] == ',': txt = txt[1:] attribute_value2, txt = stringutils.strduptokenstr(txt, ":;,") if attribute_value2 is None: raise PyCalendarInvalidProperty("Invalid property", data) attrvalue.addValue(decodeParameterValue(attribute_value2)) elif txt[0] == ':': txt = txt[1:] if stripValueSpaces: txt = txt.replace(" ", "") self.createValue(txt) txt = None except IndexError: raise PyCalendarInvalidProperty("Invalid property", data) # We must have a value of some kind if self.mValue is None: raise PyCalendarInvalidProperty("Invalid property", data) return True def getText(self): os = StringIO.StringIO() self.generate(os) return os.getvalue() def generate(self, os): # Write it out always with value self.generateValue(os, False) def generateFiltered(self, os, filter): # Check for property in filter and whether value is written out test, novalue = filter.testPropertyValue(self.mName.upper()) if test: self.generateValue(os, novalue) # Write out the actual property, possibly skipping the value def generateValue(self, os, novalue): self.setupValueAttribute() # Must write to temp buffer and then wrap sout = StringIO.StringIO() if self.mGroup: sout.write(self.mGroup + ".") sout.write(self.mName) # Write all attributes for key in sorted(self.mAttributes.keys()): for attr in self.mAttributes[key]: sout.write(";") attr.generate(sout) # Write value sout.write(":") if self.mName.upper() == "PHOTO" and self.mValue.getType() == PyCalendarValue.VALUETYPE_BINARY: # Handle AB.app PHOTO values sout.write("\r\n") value = self.mValue.getText() value_len = len(value) offset = 0 while(value_len > 72): sout.write(" ") sout.write(value[offset:offset + 72]) sout.write("\r\n") value_len -= 72 offset += 72 sout.write(" ") sout.write(value[offset:]) os.write(sout.getvalue()) else: if self.mValue and not novalue: self.mValue.generate(sout) # Get string text temp = sout.getvalue() sout.close() # Look for line length exceed if len(temp) < 75: os.write(temp) else: # Look for valid utf8 range and write that out start = 0 written = 0 lineWrap = 74 while written < len(temp): # Start 74 chars on from where we are offset = start + lineWrap if offset >= len(temp): line = temp[start:] os.write(line) written = len(temp) else: # Check whether next char is valid utf8 lead byte while (temp[offset] > 0x7F) and ((ord(temp[offset]) & 0xC0) == 0x80): # Step back until we have a valid char offset -= 1 line = temp[start:offset] os.write(line) os.write("\r\n ") lineWrap = 73 # We are now adding a space at the start written += offset - start start = offset os.write("\r\n") def _init_PyCalendarProperty(self): self.mGroup = None self.mName = "" self.mAttributes = {} self.mValue = None def createValue(self, data): # Tidy first self.mValue = None # Get value type from property name valueType = Property.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarValue.VALUETYPE_TEXT) # Check whether custom value is set if definitions.Parameter_VALUE in self.mAttributes: attr = self.getAttributeValue(definitions.Parameter_VALUE) if attr != definitions.Value_TEXT or self.mName.upper() not in Property.sTextVariants: valueType = Property.sValueTypeMap.get(attr, valueType) # Check for multivalued if self.mName.upper() in Property.sMultiValues: self.mValue = PyCalendarMultiValue(valueType) else: # Create the type self.mValue = PyCalendarValue.createFromType(valueType) # Now parse the data try: if valueType in (PyCalendarValue.VALUETYPE_DATE, PyCalendarValue.VALUETYPE_DATETIME): # vCard supports a slightly different, expanded form, of date self.mValue.parse(data, fullISO=True) else: self.mValue.parse(data) except ValueError: raise PyCalendarInvalidProperty("Invalid property value", data) def setValue(self, value): # Tidy first self.mValue = None # Get value type from property name valueType = Property.sDefaultValueTypeMap.get(self.mName.upper(), PyCalendarUnknownValue) # Check whether custom value is set if definitions.Parameter_VALUE in self.mAttributes: attr = self.getAttributeValue(definitions.Parameter_VALUE) if attr != definitions.Value_TEXT or self.mName.upper() not in Property.sTextVariants: valueType = Property.sValueTypeMap.get(attr, valueType) # Check for multivalued if self.mName.upper() in Property.sMultiValues: self.mValue = PyCalendarMultiValue(valueType) else: # Create the type self.mValue = PyCalendarValue.createFromType(valueType) self.mValue.setValue(value) def setupValueAttribute(self): if definitions.Parameter_VALUE in self.mAttributes: del self.mAttributes[definitions.Parameter_VALUE] # Only if we have a value right now if self.mValue is None: return # See if current type is default for this property. If there is no mapping available, # then always add VALUE if it is not TEXT. default_type = Property.sDefaultValueTypeMap.get(self.mName.upper()) actual_type = self.mValue.getType() if default_type is None or default_type != actual_type: actual_value = self.sTypeValueMap.get(actual_type) if actual_value is not None and (default_type is not None or actual_type != PyCalendarValue.VALUETYPE_TEXT): self.mAttributes.setdefault(definitions.Parameter_VALUE, []).append(PyCalendarAttribute(name=definitions.Parameter_VALUE, value=actual_value)) # Creation def _init_attr_value_int(self, ival): # Value self.mValue = PyCalendarIntegerValue(value=ival) # Attributes self.setupValueAttribute() def _init_attr_value_text(self, txt, value_type): # Value self.mValue = PyCalendarValue.createFromType(value_type) if isinstance(self.mValue, PyCalendarPlainTextValue) or isinstance(self.mValue, PyCalendarUnknownValue): self.mValue.setValue(txt) # Attributes self.setupValueAttribute() def _init_attr_value_adr(self, reqstatus): # Value self.mValue = AdrValue(reqstatus) # Attributes self.setupValueAttribute() def _init_attr_value_n(self, reqstatus): # Value self.mValue = NValue(reqstatus) # Attributes self.setupValueAttribute() def _init_attr_value_org(self, reqstatus): # Value self.mValue = OrgValue(reqstatus) # Attributes self.setupValueAttribute() def _init_attr_value_datetime(self, dt): # Value self.mValue = PyCalendarDateTimeValue(value=dt) # Attributes self.setupValueAttribute() def _init_attr_value_utcoffset(self, utcoffset): # Value self.mValue = PyCalendarUTCOffsetValue() self.mValue.setValue(utcoffset.getValue()) # Attributes self.setupValueAttribute() pycalendar-2.0~svn13177/src/pycalendar/vcard/tests/0000755000175000017500000000000012322631215021204 5ustar rahulrahulpycalendar-2.0~svn13177/src/pycalendar/vcard/tests/test_property.py0000644000175000017500000001011512146674036024513 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.attribute import PyCalendarAttribute from pycalendar.exceptions import PyCalendarInvalidProperty from pycalendar.parser import ParserContext from pycalendar.vcard.property import Property import unittest class TestProperty(unittest.TestCase): test_data = ( # Different value types "PHOTO;VALUE=URI:http://example.com/photo.jpg", "photo;VALUE=URI:http://example.com/photo.jpg", "TEL;type=WORK;type=pref:1-555-555-5555", "REV:20060226T120000Z", "X-FOO:BAR", "NOTE:Some \\ntext", "note:Some \\ntext", "item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;CA;11111;USA", "X-Test:Some\, text.", "X-Test;VALUE=URI:geio:123.123,123.123", ) def testParseGenerate(self): for data in TestProperty.test_data: prop = Property() prop.parse(data) propstr = str(prop) self.assertEqual(propstr[:-2], data, "Failed parse/generate: %s to %s" % (data, propstr,)) def testEquality(self): for data in TestProperty.test_data: prop1 = Property() prop1.parse(data) prop2 = Property() prop2.parse(data) self.assertEqual(prop1, prop2, "Failed equality: %s" % (data,)) def testParseBad(self): test_bad_data = ( "REV:20060226T120", "NOTE:Some \\atext", ) save = ParserContext.INVALID_ESCAPE_SEQUENCES for data in test_bad_data: ParserContext.INVALID_ESCAPE_SEQUENCES = ParserContext.PARSER_RAISE prop = Property() self.assertRaises(PyCalendarInvalidProperty, prop.parse, data) ParserContext.INVALID_ESCAPE_SEQUENCES = save def testHash(self): hashes = [] for item in TestProperty.test_data: prop = Property() prop.parse(item) hashes.append(hash(prop)) hashes.sort() for i in range(1, len(hashes)): self.assertNotEqual(hashes[i - 1], hashes[i]) def testDefaultValueCreate(self): test_data = ( ("SOURCE", "http://example.com/source", "SOURCE:http://example.com/source\r\n"), ("souRCE", "http://example.com/source", "souRCE:http://example.com/source\r\n"), ("PHOTO", "YWJj", "PHOTO:\r\n YWJj\r\n"), ("photo", "YWJj", "photo:\r\n YWJj\r\n"), ("URL", "http://example.com/tz1", "URL:http://example.com/tz1\r\n"), ) for propname, propvalue, result in test_data: prop = Property(name=propname, value=propvalue) self.assertEqual(str(prop), result) def testParameterEncodingDecoding(self): prop = Property(name="X-FOO", value="Test") prop.addAttribute(PyCalendarAttribute("X-BAR", "\"Check\"")) self.assertEqual(str(prop), "X-FOO;X-BAR=^'Check^':Test\r\n") prop.addAttribute(PyCalendarAttribute("X-BAR2", "Check\nThis\tOut\n")) self.assertEqual(str(prop), "X-FOO;X-BAR=^'Check^';X-BAR2=Check^nThis\tOut^n:Test\r\n") data = "X-FOO;X-BAR=^'Check^':Test" prop = Property() prop.parse(data) self.assertEqual(prop.getAttributeValue("X-BAR"), "\"Check\"") data = "X-FOO;X-BAR=^'Check^';X-BAR2=Check^nThis\tOut^n:Test" prop = Property() prop.parse(data) self.assertEqual(prop.getAttributeValue("X-BAR"), "\"Check\"") self.assertEqual(prop.getAttributeValue("X-BAR2"), "Check\nThis\tOut\n") pycalendar-2.0~svn13177/src/pycalendar/vcard/tests/test_validation.py0000644000175000017500000001621212101017573024751 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar.exceptions import PyCalendarValidationError from pycalendar.vcard.card import Card import unittest class TestValidation(unittest.TestCase): def test_basic(self): data = ( ( "No problems", """BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD """.replace("\n", "\r\n"), set(), set(), ), ( "No VERSION", """BEGIN:VCARD VERSION:3.0 FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD """.replace("\n", "\r\n"), set(), set(( "[VCARD] Missing or too many required property: N", )), ), ) for title, item, test_fixed, test_unfixed in data: card = Card.parseText(item) fixed, unfixed = card.validate(doFix=False) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_mode_no_raise(self): data = ( ( "OK", """BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson N:Thompson;Default;;; TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), set(), set(), ), ( "Unfixable only", """BEGIN:VCARD VERSION:3.0 FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), set(), set(( "[VCARD] Missing or too many required property: N", )), ), ) for title, test_old, test_new, test_fixed, test_unfixed in data: card = Card.parseText(test_old) fixed, unfixed = card.validate(doFix=False, doRaise=False) self.assertEqual(str(card), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_mode_raise(self): data = ( ( "OK", """BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson N:Thompson;Default;;; TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), set(), set(), False, ), ( "Unfixable only", """BEGIN:VCARD VERSION:3.0 FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), set(), set(( "[VCARD] Missing or too many required property: N", )), True, ), ) for title, test_old, test_new, test_fixed, test_unfixed, test_raises in data: card = Card.parseText(test_old) if test_raises: self.assertRaises(PyCalendarValidationError, card.validate, doFix=False, doRaise=True) else: try: fixed, unfixed = card.validate(doFix=False, doRaise=False) except: self.fail(msg="Failed test: %s" % (title,)) self.assertEqual(str(card), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) pycalendar-2.0~svn13177/src/pycalendar/vcard/tests/test_card.py0000644000175000017500000004034312101017573023532 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.exceptions import PyCalendarInvalidData from pycalendar.parser import ParserContext from pycalendar.vcard.card import Card from pycalendar.vcard.property import Property import cStringIO as StringIO import difflib import unittest class TestCard(unittest.TestCase): data = ( ( """BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson N:Thompson;Default;;; TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), ), ( """BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us BDAY:2001-01-02 REV:2011-01-02T12:34:56-0500 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA BDAY:20010102 EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson N:Thompson;Default;;; REV:20110102T123456-0500 TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), ), ( """BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us BDAY:2001-01-02 REV:2011-01-02T12:34:56-0500 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA BDAY:20010102 EMAIL;type=INTERNET;type=pref;WORK:lthompson@example.com FN:Default Thompson N:Thompson;Default;;; REV:20110102T123456-0500 TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), ), ( """BEGIN:VCARD VERSION:3.0 N:Picture;With;;; FN:With Picture EMAIL;type=INTERNET;type=WORK;type=pref:withpicture@example.com TEL;type=WORK;type=pref:777-777-7777 TEL;type=CELL:8888888888 item1.ADR;type=WORK;type=pref:;;1234 Golly Street;Sunnyside;CA;99999;USA item1.X-ABADR:us PHOTO;Encoding=b:QkVHSU46VkNBUkQKVkVSU0lPTjozLjAKTjpQaWN0dXJlO1dpdGg7Ozs KRk46V2l0aCBQaWN0dXJlCkVNQUlMO3R5cGU9SU5URVJORVQ7dHlwZT1XT1JLO3R5cGU9cH JlZjp3aXRocGljdHVyZUBleGFtcGxlLmNvbQpURUw7dHlwZT1XT1JLO3R5cGU9cHJlZjo3N zctNzc3LTc3NzcKVEVMO3R5cGU9Q0VMTDo4ODg4ODg4ODg4Cml0ZW0xLkFEUjt0eXBlPVdP Uks7dHlwZT1wcmVmOjs7MTIzNCBHb2xseSBTdHJlZXQ7U3VubnlzaWRlO0NBOzk5OTk5O1V TQQppdGVtMS5YLUFCQURSOnVzClBIT1RPO0JBU0U2NDoKVUlEOjkzNDczMUM2LTFDOTUtNE M0MC1CRTFGLUZBNDIxNUIyMzA3QjpBQlBlcnNvbgpFTkQ6VkNBUkQK UID:934731C6-1C95-4C40-BE1F-FA4215B2307B:ABPerson END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 UID:934731C6-1C95-4C40-BE1F-FA4215B2307B:ABPerson item1.ADR;type=WORK;type=pref:;;1234 Golly Street;Sunnyside;CA;99999;USA EMAIL;type=INTERNET;type=WORK;type=pref:withpicture@example.com FN:With Picture N:Picture;With;;; PHOTO;Encoding=b: QkVHSU46VkNBUkQKVkVSU0lPTjozLjAKTjpQaWN0dXJlO1dpdGg7OzsKRk46V2l0aCBQaWN0 dXJlCkVNQUlMO3R5cGU9SU5URVJORVQ7dHlwZT1XT1JLO3R5cGU9cHJlZjp3aXRocGljdHVy ZUBleGFtcGxlLmNvbQpURUw7dHlwZT1XT1JLO3R5cGU9cHJlZjo3NzctNzc3LTc3NzcKVEVM O3R5cGU9Q0VMTDo4ODg4ODg4ODg4Cml0ZW0xLkFEUjt0eXBlPVdPUks7dHlwZT1wcmVmOjs7 MTIzNCBHb2xseSBTdHJlZXQ7U3VubnlzaWRlO0NBOzk5OTk5O1VTQQppdGVtMS5YLUFCQURS OnVzClBIT1RPO0JBU0U2NDoKVUlEOjkzNDczMUM2LTFDOTUtNEM0MC1CRTFGLUZBNDIxNUIy MzA3QjpBQlBlcnNvbgpFTkQ6VkNBUkQK TEL;type=WORK;type=pref:777-777-7777 TEL;type=CELL:8888888888 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), ), ( """BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson X-APPLE-STRUCTURED-LOCATION;VALUE=URI:geo:123.123,123.123 X-Test:Some\, text. END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson N:Thompson;Default;;; TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us X-APPLE-STRUCTURED-LOCATION;VALUE=URI:geo:123.123,123.123 X-Test:Some\, text. END:VCARD """.replace("\n", "\r\n"), ), ) def testRoundtrip(self): def _doRoundtrip(caldata, resultdata=None): test1 = resultdata if resultdata is not None else caldata card = Card() card.parse(StringIO.StringIO(caldata)) s = StringIO.StringIO() card.generate(s) test2 = s.getvalue() self.assertEqual( test1, test2, "\n".join(difflib.unified_diff(test1.splitlines(), test2.splitlines())), ) for item, result in self.data: _doRoundtrip(item, result) def testRoundtripDuplicate(self): def _doDuplicateRoundtrip(caldata, result): card = Card() card.parse(StringIO.StringIO(caldata)) card = card.duplicate() s = StringIO.StringIO() card.generate(s) test = s.getvalue() self.assertEqual(test, result, "\n".join(difflib.unified_diff(test.splitlines(), result.splitlines()))) for item, result in self.data: _doDuplicateRoundtrip(item, result) def testEquality(self): def _doEquality(caldata): card1 = Card() card1.parse(StringIO.StringIO(caldata)) card2 = Card() card2.parse(StringIO.StringIO(caldata)) self.assertEqual(card1, card2, "\n".join(difflib.unified_diff(str(card1).splitlines(), str(card2).splitlines()))) def _doNonEquality(caldata): card1 = Card() card1.parse(StringIO.StringIO(caldata)) card2 = Card() card2.parse(StringIO.StringIO(caldata)) card2.addProperty(Property("X-FOO", "BAR")) self.assertNotEqual(card1, card2) for item, _ignore in self.data: _doEquality(item) _doNonEquality(item) def testMultiple(self): data = ( ( """BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD """.replace("\n", "\r\n"), ( """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson N:Thompson;Default;;; TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), )), ( """BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E2:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:athompson@example.com FN:Another Thompson N:Thompson;Another;;; TEL;type=WORK;type=pref:1-555-555-5556 TEL;type=CELL:1-444-444-4445 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), ( """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson N:Thompson;Default;;; TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E2:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:athompson@example.com FN:Another Thompson N:Thompson;Another;;; TEL;type=WORK;type=pref:1-555-555-5556 TEL;type=CELL:1-444-444-4445 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), )), ) for item, results in data: cards = Card.parseMultiple(StringIO.StringIO(item)) self.assertEqual(len(cards), len(results)) for card, result in zip(cards, results): self.assertEqual(str(card), result, "\n".join(difflib.unified_diff(str(card).splitlines(), result.splitlines()))) def testABapp(self): data = """BEGIN:VCARD VERSION:3.0 N:Card;Test;;; FN:Test Card EMAIL;type=INTERNET;type=WORK;type=pref:sagen@apple.com TEL;type=WORK;type=pref:555-1212 TEL;type=HOME:532-1234 PHOTO;BASE64: TU0AKgAAMAj////////////////////////////////////////////////////////////+///+ SW1hZ2VNYWdpY2sgNS4zLjkgMTAvMDEvMDEgUToxNiBodHRwOi8vd3d3LmltYWdlbWFnaWNrLm9y ZwA= CATEGORIES:My Contacts X-ABUID:5B77BC10-E9DB-48C4-8BE1-BAB5E38E1E43\:ABPerson UID:128ad7ee-a656-4773-95ce-f07f77e8cc23 REV:2011-03-23T20:20:04Z END:VCARD """.replace("\n", "\r\n") result = """BEGIN:VCARD VERSION:3.0 UID:128ad7ee-a656-4773-95ce-f07f77e8cc23 CATEGORIES:My Contacts EMAIL;type=INTERNET;type=WORK;type=pref:sagen@apple.com FN:Test Card N:Card;Test;;; PHOTO;ENCODING=B: TU0AKgAAMAj////////////////////////////////////////////////////////////+ ///+SW1hZ2VNYWdpY2sgNS4zLjkgMTAvMDEvMDEgUToxNiBodHRwOi8vd3d3LmltYWdlbWFn aWNrLm9yZwA= REV:20110323T202004Z TEL;type=WORK;type=pref:555-1212 TEL;type=HOME:532-1234 X-ABUID:5B77BC10-E9DB-48C4-8BE1-BAB5E38E1E43:ABPerson END:VCARD """.replace("\n", "\r\n") card = Card.parseText(data) self.assertEqual(str(card), result) def testParseFail(self): data = ( """BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson """.replace("\n", "\r\n"), """BEGIN:VCALENDAR PRODID:-//mulberrymail.com//Mulberry v4.0//EN VERSION:2.0 END:VCALENDAR """.replace("\n", "\r\n"), """BOGUS BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD """.replace("\n", "\r\n"), """BOGUS BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD BOGUS """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 N:Thompson;Default;;; FN:Default Thompson EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA item1.X-ABADR:us UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson END:VCARD BOGUS """.replace("\n", "\r\n"), ) for item in data: self.assertRaises(PyCalendarInvalidData, Card.parseText, item) def testParseBlank(self): data = ( """ BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson N:Thompson;Default;;; TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), """ BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson N:Thompson;Default;;; TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson N:Thompson;Default;;; TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson N:Thompson;Default;;; TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), """BEGIN:VCARD VERSION:3.0 UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1:ABPerson item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;U SA EMAIL;type=INTERNET;type=WORK;type=pref:lthompson@example.com FN:Default Thompson N:Thompson;Default;;; TEL;type=WORK;type=pref:1-555-555-5555 TEL;type=CELL:1-444-444-4444 item1.X-ABADR:us END:VCARD """.replace("\n", "\r\n"), ) save = ParserContext.BLANK_LINES_IN_DATA for item in data: ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_RAISE self.assertRaises(PyCalendarInvalidData, Card.parseText, item) ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_IGNORE lines = item.split("\r\n") result = "\r\n".join([line for line in lines if line]) + "\r\n" self.assertEqual(str(Card.parseText(item)), result) ParserContext.BLANK_LINES_IN_DATA = save pycalendar-2.0~svn13177/src/pycalendar/vcard/tests/__init__.py0000644000175000017500000000120112101017573023307 0ustar rahulrahul# # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## pycalendar-2.0~svn13177/src/pycalendar/vcard/definitions.py0000644000175000017500000000404712101017573022734 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # 2426 Component VCARD = "VCARD" # 2425 Properties Property_SOURCE = "SOURCE" Property_NAME = "NAME" Property_PROFILE = "PROFILE" # 2426 vCard Properties # 2426 Section 3.1 Property_FN = "FN" Property_N = "N" Property_NICKNAME = "NICKNAME" Property_PHOTO = "PHOTO" Property_BDAY = "BDAY" # 2426 Section 3.2 Property_ADR = "ADR" Property_LABEL = "LABEL" # 2426 Section 3.3 Property_TEL = "TEL" Property_EMAIL = "EMAIL" Property_MAILER = "MAILER" # 2426 Section 3.4 Property_TZ = "TZ" Property_GEO = "GEO" # 2426 Section 3.5 Property_TITLE = "TITLE" Property_ROLE = "ROLE" Property_LOGO = "LOGO" Property_AGENT = "AGENT" Property_ORG = "ORG" # 2426 Section 3.6 Property_CATEGORIES = "CATEGORIES" Property_NOTE = "NOTE" Property_PRODID = "PRODID" Property_REV = "REV" Property_SORT_STRING = "SORT-STRING" Property_SOUND = "SOUND" Property_UID = "UID" Property_URL = "URL" Property_VERSION = "VERSION" # 2426 Section 3.7 Property_CLASS = "CLASS" Property_KEY = "KEY" # 2426 Value Types Value_BINARY = "BINARY" Value_BOOLEAN = "BOOLEAN" Value_DATE = "DATE" Value_DATE_TIME = "DATE-TIME" Value_FLOAT = "FLOAT" Value_INTEGER = "INTEGER" Value_TEXT = "TEXT" Value_TIME = "TIME" Value_URI = "URI" Value_UTCOFFSET = "UTCOFFSET" Value_VCARD = "VCARD" Parameter_ENCODING = "ENCODING" Parameter_LANGUAGE = "LANGUAGE" Parameter_TYPE = "TYPE" Parameter_VALUE = "VALUE" Parameter_Value_ENCODING_B = "B" pycalendar-2.0~svn13177/src/pycalendar/vcard/card.py0000644000175000017500000002066612101017573021337 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 cStringIO import StringIO from pycalendar.componentbase import PyCalendarComponentBase from pycalendar.exceptions import PyCalendarInvalidData, \ PyCalendarValidationError from pycalendar.parser import ParserContext from pycalendar.utils import readFoldedLine from pycalendar.vcard import definitions from pycalendar.vcard.definitions import VCARD, Property_VERSION, \ Property_PRODID, Property_UID from pycalendar.vcard.property import Property from pycalendar.vcard.validation import VCARD_VALUE_CHECKS class Card(PyCalendarComponentBase): sProdID = "-//mulberrymail.com//Mulberry v4.0//EN" sDomain = "mulberrymail.com" @staticmethod def setPRODID(prodid): Card.sProdID = prodid @staticmethod def setDomain(domain): Card.sDomain = domain propertyCardinality_1 = ( definitions.Property_VERSION, definitions.Property_N, ) propertyCardinality_0_1 = ( definitions.Property_BDAY, definitions.Property_PRODID, definitions.Property_REV, definitions.Property_UID, ) propertyCardinality_1_More = ( definitions.Property_FN, ) propertyValueChecks = VCARD_VALUE_CHECKS def __init__(self, add_defaults=True): super(Card, self).__init__() if add_defaults: self.addDefaultProperties() def duplicate(self): return super(Card, self).duplicate() def getType(self): return VCARD def finalise(self): pass def validate(self, doFix=False, doRaise=False): """ Validate the data in this component and optionally fix any problems. Return a tuple containing two lists: the first describes problems that were fixed, the second problems that were not fixed. Caller can then decide what to do with unfixed issues. """ # Optional raise behavior fixed, unfixed = super(Card, self).validate(doFix) if doRaise and unfixed: raise PyCalendarValidationError(";".join(unfixed)) return fixed, unfixed def sortedPropertyKeyOrder(self): return ( Property_VERSION, Property_PRODID, Property_UID, ) @staticmethod def parseMultiple(ins): results = [] card = Card(add_defaults=False) LOOK_FOR_VCARD = 0 GET_PROPERTY = 1 state = LOOK_FOR_VCARD # Get lines looking for start of calendar lines = [None, None] while readFoldedLine(ins, lines): line = lines[0] if state == LOOK_FOR_VCARD: # Look for start if line == card.getBeginDelimiter(): # Next state state = GET_PROPERTY # Handle blank line elif len(line) == 0: # Raise if requested, otherwise just ignore if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE: raise PyCalendarInvalidData("vCard data has blank lines") # Unrecognized data else: raise PyCalendarInvalidData("vCard data not recognized", line) elif state == GET_PROPERTY: # Look for end of object if line == card.getEndDelimiter(): # Finalise the current calendar card.finalise() # Validate some things if not card.hasProperty("VERSION"): raise PyCalendarInvalidData("vCard missing VERSION", "") results.append(card) # Change state card = Card(add_defaults=False) state = LOOK_FOR_VCARD # Blank line elif len(line) == 0: # Raise if requested, otherwise just ignore if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE: raise PyCalendarInvalidData("vCard data has blank lines") # Must be a property else: # Parse attribute/value for top-level calendar item prop = Property() if prop.parse(line): # Check for valid property if not card.validProperty(prop): raise PyCalendarInvalidData("Invalid property", str(prop)) else: card.addProperty(prop) # Check for truncated data if state != LOOK_FOR_VCARD: raise PyCalendarInvalidData("vCard data not complete") return results @staticmethod def parseText(data): cal = Card(add_defaults=False) if cal.parse(StringIO(data)): return cal else: return None def parse(self, ins): result = False self.setProperties({}) LOOK_FOR_VCARD = 0 GET_PROPERTY = 1 state = LOOK_FOR_VCARD # Get lines looking for start of calendar lines = [None, None] while readFoldedLine(ins, lines): line = lines[0] if state == LOOK_FOR_VCARD: # Look for start if line == self.getBeginDelimiter(): # Next state state = GET_PROPERTY # Indicate success at this point result = True # Handle blank line elif len(line) == 0: # Raise if requested, otherwise just ignore if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE: raise PyCalendarInvalidData("vCard data has blank lines") # Unrecognized data else: raise PyCalendarInvalidData("vCard data not recognized", line) elif state == GET_PROPERTY: # Look for end of object if line == self.getEndDelimiter(): # Finalise the current calendar self.finalise() # Change state state = LOOK_FOR_VCARD # Blank line elif len(line) == 0: # Raise if requested, otherwise just ignore if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE: raise PyCalendarInvalidData("vCard data has blank lines") # Must be a property else: # Parse attribute/value for top-level calendar item prop = Property() try: if prop.parse(line): # Check for valid property if not self.validProperty(prop): raise PyCalendarInvalidData("Invalid property", str(prop)) else: self.addProperty(prop) except IndexError: print line # Check for truncated data if state != LOOK_FOR_VCARD: raise PyCalendarInvalidData("vCard data not complete", "") # Validate some things if result and not self.hasProperty("VERSION"): raise PyCalendarInvalidData("vCard missing VERSION", "") return result def addDefaultProperties(self): self.addProperty(Property(definitions.Property_PRODID, Card.sProdID)) self.addProperty(Property(definitions.Property_VERSION, "3.0")) def validProperty(self, prop): if prop.getName() == definitions.Property_VERSION: tvalue = prop.getValue() if ((tvalue is None) or (tvalue.getValue() != "3.0")): return False return True pycalendar-2.0~svn13177/src/pycalendar/vcard/__init__.py0000644000175000017500000000120112101017573022145 0ustar rahulrahul# # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## pycalendar-2.0~svn13177/src/pycalendar/duration.py0000644000175000017500000001676212101017573021156 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.parser import ParserContext from pycalendar.stringutils import strtoul from pycalendar.valueutils import ValueMixin class PyCalendarDuration(ValueMixin): def __init__(self, duration=None, weeks=0, days=0, hours=0, minutes=0, seconds=0): self.mForward = True self.mWeeks = 0 self.mDays = 0 self.mHours = 0 self.mMinutes = 0 self.mSeconds = 0 if duration is None: duration = (((weeks * 7 + days) * 24 + hours) * 60 + minutes) * 60 + seconds self.setDuration(duration) def duplicate(self): other = PyCalendarDuration(None) other.mForward = self.mForward other.mWeeks = self.mWeeks other.mDays = self.mDays other.mHours = self.mHours other.mMinutes = self.mMinutes other.mSeconds = self.mSeconds return other def __hash__(self): return hash(self.getTotalSeconds()) def __eq__(self, comp): return self.getTotalSeconds() == comp.getTotalSeconds() def __gt__(self, comp): return self.getTotalSeconds() > comp.getTotalSeconds() def __lt__(self, comp): return self.getTotalSeconds() < comp.getTotalSeconds() def getTotalSeconds(self): return [1, -1][not self.mForward] \ * (self.mSeconds + (self.mMinutes + (self.mHours + (self.mDays + (self.mWeeks * 7)) * 24) * 60) * 60) def setDuration(self, seconds): self.mForward = seconds >= 0 remainder = seconds if remainder < 0: remainder = -remainder # Is it an exact number of weeks - if so use the weeks value, otherwise # days, hours, minutes, seconds if remainder % (7 * 24 * 60 * 60) == 0: self.mWeeks = remainder / (7 * 24 * 60 * 60) self.mDays = 0 self.mHours = 0 self.mMinutes = 0 self.mSeconds = 0 else: self.mSeconds = remainder % 60 remainder -= self.mSeconds remainder /= 60 self.mMinutes = remainder % 60 remainder -= self.mMinutes remainder /= 60 self.mHours = remainder % 24 remainder -= self.mHours self.mDays = remainder / 24 self.mWeeks = 0 def getForward(self): return self.mForward def getWeeks(self): return self.mWeeks def getDays(self): return self.mDays def getHours(self): return self.mHours def getMinutes(self): return self.mMinutes def getSeconds(self): return self.mSeconds @classmethod def parseText(cls, data): dur = cls() dur.parse(data) return dur def parse(self, data): # parse format ([+]/-) "P" (dur-date / dur-time / dur-week) try: offset = 0 maxoffset = len(data) # Look for +/- self.mForward = True if data[offset] in ('-', '+'): self.mForward = data[offset] == '+' offset += 1 # Must have a 'P' if data[offset] != "P": raise ValueError offset += 1 # Look for time if data[offset] != "T": # Must have a number num, offset = strtoul(data, offset) # Now look at character if data[offset] == "W": # Have a number of weeks self.mWeeks = num offset += 1 # There cannot be anything else after this so just exit if offset != maxoffset: if ParserContext.INVALID_DURATION_VALUE != ParserContext.PARSER_RAISE: return raise ValueError return elif data[offset] == "D": # Have a number of days self.mDays = num offset += 1 # Look for more data - exit if none if offset == maxoffset: return # Look for time - exit if none if data[offset] != "T": raise ValueError else: # Error in format raise ValueError # Have time offset += 1 # Strictly speaking T must always be followed by time values, but some clients # send T with no additional text if offset == maxoffset: if ParserContext.INVALID_DURATION_VALUE == ParserContext.PARSER_RAISE: raise ValueError else: return num, offset = strtoul(data, offset) # Look for hour if data[offset] == "H": # Get hours self.mHours = num offset += 1 # Look for more data - exit if none if offset == maxoffset: return # Parse the next number num, offset = strtoul(data, offset) # Look for minute if data[offset] == "M": # Get hours self.mMinutes = num offset += 1 # Look for more data - exit if none if offset == maxoffset: return # Parse the next number num, offset = strtoul(data, offset) # Look for seconds if data[offset] == "S": # Get hours self.mSeconds = num offset += 1 # No more data - exit if offset == maxoffset: return raise ValueError except IndexError: raise ValueError def generate(self, os): try: if not self.mForward and (self.mWeeks or self.mDays or self.mHours or self.mMinutes or self.mSeconds): os.write("-") os.write("P") if self.mWeeks != 0: os.write("%dW" % (self.mWeeks,)) else: if self.mDays != 0: os.write("%dD" % (self.mDays,)) if (self.mHours != 0) or (self.mMinutes != 0) or (self.mSeconds != 0): os.write("T") if self.mHours != 0: os.write("%dH" % (self.mHours,)) if (self.mMinutes != 0) or ((self.mHours != 0) and (self.mSeconds != 0)): os.write("%dM" % (self.mMinutes,)) if self.mSeconds != 0: os.write("%dS" % (self.mSeconds,)) elif self.mDays == 0: os.write("T0S") except: pass def writeXML(self, node, namespace): node.text = self.getText() pycalendar-2.0~svn13177/src/pycalendar/adrvalue.py0000644000175000017500000000246212101017573021124 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # vCard ADR value from pycalendar.adr import Adr from pycalendar.value import PyCalendarValue class AdrValue(PyCalendarValue): def __init__(self, value=None): self.mValue = value if value is not None else Adr() def duplicate(self): return AdrValue(self.mValue.duplicate()) def getType(self): return PyCalendarValue.VALUETYPE_ADR def parse(self, data): self.mValue.parse(data) def generate(self, os): self.mValue.generate(os) def getValue(self): return self.mValue def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_ADR, AdrValue, None) pycalendar-2.0~svn13177/src/pycalendar/timezone.py0000644000175000017500000001021012317143440021143 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import stringutils from pycalendar.timezonedb import PyCalendarTimezoneDatabase class PyCalendarTimezone(object): """ Wrapper around a timezone specification. There are three options: UTC - when mUTC is True TZID - when mUTC is False and tzid is a str UTCOFFSET - when mUTC is False and tzid is an int """ sDefaultTimezone = None def __init__(self, utc=None, tzid=None): if utc is not None: self.mUTC = utc self.mTimezone = tzid elif tzid is not None: self.mUTC = tzid.lower() == 'utc' self.mTimezone = None if tzid.lower() == 'utc' else tzid else: self.mUTC = True self.mTimezone = None # Copy default timezone if it exists if PyCalendarTimezone.sDefaultTimezone is not None: self.mUTC = PyCalendarTimezone.sDefaultTimezone.mUTC self.mTimezone = PyCalendarTimezone.sDefaultTimezone.mTimezone def duplicate(self): return PyCalendarTimezone(self.mUTC, self.mTimezone) def equals(self, comp): # Always match if any one of them is 'floating' if self.floating() or comp.floating(): return True elif self.mUTC != comp.mUTC: return False else: return self.mUTC or stringutils.compareStringsSafe(self.mTimezone, comp.mTimezone) @staticmethod def same(utc1, tzid1, utc2, tzid2): # Always match if any one of them is 'floating' if PyCalendarTimezone.is_float(utc1, tzid1) or PyCalendarTimezone.is_float(utc2, tzid2): return True elif utc1 != utc2: return False else: return utc1 or stringutils.compareStringsSafe(tzid1, tzid2) @staticmethod def is_float(utc, tzid): return not utc and not tzid def getUTC(self): return self.mUTC def setUTC(self, utc): self.mUTC = utc def getTimezoneID(self): return self.mTimezone def setTimezoneID(self, tzid): self.mTimezone = tzid def floating(self): return not self.mUTC and self.mTimezone is None def hasTZID(self): return not self.mUTC and self.mTimezone is not None def timeZoneSecondsOffset(self, dt, relative_to_utc=False): if self.mUTC: return 0 elif self.mTimezone is None: return PyCalendarTimezoneDatabase.getTimezoneOffsetSeconds(PyCalendarTimezone.sDefaultTimezone.getTimezoneID(), dt, relative_to_utc) elif isinstance(self.mTimezone, int): return self.mTimezone else: # Look up timezone and resolve date using default timezones return PyCalendarTimezoneDatabase.getTimezoneOffsetSeconds(self.mTimezone, dt, relative_to_utc) def timeZoneDescriptor(self, dt): if self.mUTC: return "(UTC)" elif self.mTimezone is None: return PyCalendarTimezoneDatabase.getTimezoneDescriptor(PyCalendarTimezone.sDefaultTimezone.getTimezoneID(), dt) elif isinstance(self.mTimezone, int): sign = "-" if self.mTimezone < 0 else "+" hours = abs(self.mTimezone) / 3600 minutes = divmod(abs(self.mTimezone) / 60, 60)[1] return "%s%02d%02d" % (sign, hours, minutes,) else: # Look up timezone and resolve date using default timezones return PyCalendarTimezoneDatabase.getTimezoneDescriptor(self.mTimezone, dt) PyCalendarTimezone.sDefaultTimezone = PyCalendarTimezone() pycalendar-2.0~svn13177/src/pycalendar/componentexpanded.py0000644000175000017500000001222412101017573023031 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.datetime import PyCalendarDateTime class PyCalendarComponentExpanded(object): @staticmethod def sort_by_dtstart_allday(e1, e2): if e1.mInstanceStart.isDateOnly() and e2.mInstanceStart.isDateOnly(): return e1.mInstanceStart < e2.mInstanceStart elif e1.mInstanceStart.isDateOnly(): return True elif e2.mInstanceStart.isDateOnly(): return False elif e1.mInstanceStart == e2.mInstanceStart: if e1.mInstanceEnd == e2.mInstanceEnd: # Put ones created earlier in earlier columns in day view return e1.getOwner().getStamp() < e2.getOwner().getStamp() else: # Put ones that end later in earlier columns in day view return e1.mInstanceEnd > e2.mInstanceEnd else: return e1.mInstanceStart < e2.mInstanceStart @staticmethod def sort_by_dtstart(e1, e2): if e1.mInstanceStart == e2.mInstanceStart: if (e1.mInstanceStart.isDateOnly() and not e2.mInstanceStart.isDateOnly() or not e1.mInstanceStart.isDateOnly() and e2.mInstanceStart.isDateOnly()): return e1.mInstanceStart.isDateOnly() else: return False else: return e1.mInstanceStart < e2.mInstanceStart def __init__(self, owner, rid): self.mOwner = owner self.initFromOwner(rid) def duplicate(self): other = PyCalendarComponentExpanded(self.mOwner, None) other.mInstanceStart = self.mInstanceStart.duplicate() other.mInstanceEnd = self.mInstanceEnd.duplicate() other.mRecurring = self.mRecurring return other def close(self): # Clean-up self.mOwner = None def getOwner(self): return self.mOwner def getMaster(self): return self.mOwner def getTrueMaster(self): return self.mOwner.getMaster() def getInstanceStart(self): return self.mInstanceStart def getInstanceEnd(self): return self.mInstanceEnd def recurring(self): return self.mRecurring def isNow(self): # Check instance start/end against current date-time now = PyCalendarDateTime.getNowUTC() return self.mInstanceStart <= now and self.mInstanceEnd > now def initFromOwner(self, rid): # There are four possibilities here: # # 1: this instance is the instance for the master component # # 2: this instance is an expanded instance derived directly from the # master component # # 3: This instance is the instance for a slave (overridden recurrence # instance) # # 4: This instance is the expanded instance for a slave with a RANGE # parameter # # rid is not set if the owner is the master (case 1) if rid is None: # Just get start/end from owner self.mInstanceStart = self.mOwner.getStart() self.mInstanceEnd = self.mOwner.getEnd() self.mRecurring = False # If the owner is not a recurrence instance then it is case 2 elif not self.mOwner.isRecurrenceInstance(): # Derive start/end from rid and duration of master # Start of the recurrence instance is the recurrence id self.mInstanceStart = rid # End is based on original events settings if self.mOwner.hasEnd(): self.mInstanceEnd = self.mInstanceStart + (self.mOwner.getEnd() - self.mOwner.getStart()) else: self.mInstanceEnd = self.mInstanceStart.duplicate() self.mRecurring = True # If the owner is a recurrence item and the passed in rid is the same # as the component rid we have case 3 elif rid == self.mOwner.getRecurrenceID(): # Derive start/end directly from the owner self.mInstanceStart = self.mOwner.getStart() self.mInstanceEnd = self.mOwner.getEnd() self.mRecurring = True # case 4 - the complicated one! else: # We need to use the rid as the starting point, but adjust it by # the offset between the slave's # rid and its start self.mInstanceStart = rid + (self.mOwner.getStart() - self.mOwner.getRecurrenceID()) # End is based on duration of owner self.mInstanceEnd = self.mInstanceStart + (self.mOwner.getEnd() - self.mOwner.getStart()) self.mRecurring = True pycalendar-2.0~svn13177/src/pycalendar/value.py0000644000175000017500000000517512101017573020441 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # ICalendar Value class from pycalendar.valueutils import ValueMixin from pycalendar import xmldefs import xml.etree.cElementTree as XML class PyCalendarValue(ValueMixin): ( VALUETYPE_ADR, VALUETYPE_BINARY, VALUETYPE_BOOLEAN, VALUETYPE_CALADDRESS, VALUETYPE_DATE, VALUETYPE_DATETIME, VALUETYPE_DURATION, VALUETYPE_FLOAT, VALUETYPE_GEO, VALUETYPE_INTEGER, VALUETYPE_N, VALUETYPE_ORG, VALUETYPE_PERIOD, VALUETYPE_RECUR, VALUETYPE_REQUEST_STATUS, VALUETYPE_TEXT, VALUETYPE_TIME, VALUETYPE_UNKNOWN, VALUETYPE_URI, VALUETYPE_UTC_OFFSET, VALUETYPE_VCARD, VALUETYPE_MULTIVALUE, VALUETYPE_XNAME, ) = range(23) _typeMap = {} _xmlMap = {} def __hash__(self): return hash((self.getType(), self.getValue())) def __ne__(self, other): return not self.__eq__(other) def __eq__(self, other): if not isinstance(other, PyCalendarValue): return False return self.getType() == other.getType() and self.getValue() == other.getValue() @classmethod def registerType(clz, type, cls, xmlNode): clz._typeMap[type] = cls clz._xmlMap[type] = xmlNode @classmethod def createFromType(clz, type): # Create the type created = clz._typeMap.get(type, None) if created: return created() else: return clz._typeMap.get(PyCalendarValue.VALUETYPE_UNKNOWN)(type) def getType(self): raise NotImplementedError def getRealType(self): return self.getType() def getValue(self): raise NotImplementedError def setValue(self, value): raise NotImplementedError def writeXML(self, node, namespace): raise NotImplementedError def getXMLNode(self, node, namespace): return XML.SubElement(node, xmldefs.makeTag(namespace, self._xmlMap[self.getType()])) pycalendar-2.0~svn13177/src/pycalendar/n.py0000644000175000017500000000516612101017573017562 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # vCard ADR value from pycalendar import utils from pycalendar.valueutils import ValueMixin class N(ValueMixin): """ mValue is a tuple of seven str or tuples of str """ ( LAST, FIRST, MIDDLE, PREFIX, SUFFIX, MAXITEMS ) = range(6) def __init__(self, last="", first="", middle="", prefix="", suffix=""): self.mValue = (last, first, middle, prefix, suffix) def duplicate(self): return N(*self.mValue) def __hash__(self): return hash(self.mValue) def __repr__(self): return "N %s" % (self.getText(),) def __eq__(self, comp): return self.mValue == comp.mValue def getFirst(self): return self.mValue[N.FIRST] def setFirst(self, value): self.mValue[N.FIRST] = value def getLast(self): return self.mValue[N.LAST] def setLast(self, value): self.mValue[N.LAST] = value def getMiddle(self): return self.mValue[N.MIDDLE] def setMiddle(self, value): self.mValue[N.MIDDLE] = value def getPrefix(self): return self.mValue[N.PREFIX] def setPrefix(self, value): self.mValue[N.PREFIX] = value def getSuffix(self): return self.mValue[N.SUFFIX] def setSuffix(self, value): self.mValue[N.SUFFIX] = value def getFullName(self): def _stringOrList(item): return item if isinstance(item, basestring) else " ".join(item) results = [] for i in (N.PREFIX, N.FIRST, N.MIDDLE, N.LAST, N.SUFFIX): result = _stringOrList(self.mValue[i]) if result: results.append(result) return " ".join(results) def parse(self, data): self.mValue = utils.parseDoubleNestedList(data, N.MAXITEMS) def generate(self, os): utils.generateDoubleNestedList(os, self.mValue) def getValue(self): return self.mValue def setValue(self, value): self.mValue = value pycalendar-2.0~svn13177/src/pycalendar/attribute.py0000644000175000017500000000635312101017573021327 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## """ ICalendar attribute. The attribute can consist of one or more values, all string. """ from pycalendar import xmldefs from pycalendar.utils import encodeParameterValue import xml.etree.cElementTree as XML class PyCalendarAttribute(object): def __init__(self, name, value=None): self.mName = name if value is None: self.mValues = [] elif isinstance(value, basestring): self.mValues = [value] else: self.mValues = value def duplicate(self): other = PyCalendarAttribute(self.mName) other.mValues = self.mValues[:] return other def __hash__(self): return hash((self.mName.upper(), tuple(self.mValues))) def __ne__(self, other): return not self.__eq__(other) def __eq__(self, other): if not isinstance(other, PyCalendarAttribute): return False return self.mName.upper() == other.mName.upper() and self.mValues == other.mValues def getName(self): return self.mName def setName(self, name): self.mName = name def getFirstValue(self): return self.mValues[0] def getValues(self): return self.mValues def setValues(self, values): self.mValues = values def addValue(self, value): self.mValues.append(value) def removeValue(self, value): self.mValues.remove(value) return len(self.mValues) def generate(self, os): try: os.write(self.mName) # To support vCard 2.1 syntax we allow parameters without values if self.mValues: os.write("=") first = True for s in self.mValues: if first: first = False else: os.write(",") # Write with quotation if required self.generateValue(os, s) except: # We ignore errors pass def generateValue(self, os, str): # ^-escaping str = encodeParameterValue(str) # Look for quoting if str.find(":") != -1 or str.find(";") != -1 or str.find(",") != -1: os.write("\"%s\"" % (str,)) else: os.write(str) def writeXML(self, node, namespace): param = XML.SubElement(node, xmldefs.makeTag(namespace, self.getName())) for value in self.getValues(): # TODO: need to figure out proper value types text = XML.SubElement(param, xmldefs.makeTag(namespace, xmldefs.value_text)) text.text = value pycalendar-2.0~svn13177/src/pycalendar/requeststatusvalue.py0000644000175000017500000000653212127044213023313 0ustar rahulrahul## # Copyright (c) 2011-2013 Cyrus Daboo. 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. ## # iCalendar REQUEST-STATUS value from pycalendar import utils, xmldefs from pycalendar.parser import ParserContext from pycalendar.value import PyCalendarValue import xml.etree.cElementTree as XML class PyCalendarRequestStatusValue(PyCalendarValue): """ The value is a list of strings (either 2 or 3 items) """ def __init__(self, value=None): self.mValue = value if value is not None else ["2.0", "Success"] def __hash__(self): return hash(tuple(self.mValue)) def duplicate(self): return PyCalendarRequestStatusValue(self.mValue[:]) def getType(self): return PyCalendarValue.VALUETYPE_REQUEST_STATUS def parse(self, data): result = utils.parseTextList(data, always_list=True) if len(result) == 1: if ParserContext.INVALID_REQUEST_STATUS_VALUE != ParserContext.PARSER_RAISE: if ";" in result[0]: code, desc = result[0].split(";", 1) else: code = result[0] desc = "" rest = None else: raise ValueError elif len(result) == 2: code, desc = result rest = None elif len(result) == 3: code, desc, rest = result else: if ParserContext.INVALID_REQUEST_STATUS_VALUE != ParserContext.PARSER_RAISE: code, desc, rest = result[:3] else: raise ValueError if "\\" in code and ParserContext.INVALID_REQUEST_STATUS_VALUE in (ParserContext.PARSER_IGNORE, ParserContext.PARSER_FIX): code = code.replace("\\", "") elif ParserContext.INVALID_REQUEST_STATUS_VALUE == ParserContext.PARSER_RAISE: raise ValueError # Decoding required self.mValue = [code, desc, rest, ] if rest else [code, desc, ] # os - StringIO object def generate(self, os): utils.generateTextList(os, self.mValue if len(self.mValue) < 3 or self.mValue[2] else self.mValue[:2]) def writeXML(self, node, namespace): code = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.req_status_code)) code.text = self.mValue[0] description = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.req_status_description)) description.text = self.mValue[1] if len(self.mValue) == 3 and self.mValue[2]: data = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.req_status_data)) data.text = self.mValue[1] def getValue(self): return self.mValue def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_REQUEST_STATUS, PyCalendarRequestStatusValue, None) pycalendar-2.0~svn13177/src/pycalendar/calendar.py0000644000175000017500000006375612317143440021111 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 cStringIO import StringIO from pycalendar import definitions, xmldefs from pycalendar.available import PyCalendarAvailable from pycalendar.componentbase import PyCalendarComponentBase from pycalendar.componentexpanded import PyCalendarComponentExpanded from pycalendar.componentrecur import PyCalendarComponentRecur from pycalendar.datetime import PyCalendarDateTime from pycalendar.exceptions import PyCalendarInvalidData, \ PyCalendarValidationError from pycalendar.freebusy import PyCalendarFreeBusy from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS from pycalendar.parser import ParserContext from pycalendar.period import PyCalendarPeriod from pycalendar.property import PyCalendarProperty from pycalendar.utils import readFoldedLine from pycalendar.valarm import PyCalendarVAlarm from pycalendar.vavailability import PyCalendarVAvailability from pycalendar.vevent import PyCalendarVEvent from pycalendar.vfreebusy import PyCalendarVFreeBusy from pycalendar.vjournal import PyCalendarVJournal from pycalendar.vtimezone import PyCalendarVTimezone from pycalendar.vtimezonedaylight import PyCalendarVTimezoneDaylight from pycalendar.vtimezonestandard import PyCalendarVTimezoneStandard from pycalendar.vtodo import PyCalendarVToDo from pycalendar.vunknown import PyCalendarUnknownComponent import collections import xml.etree.cElementTree as XML class PyCalendar(PyCalendarComponentBase): REMOVE_ALL = 0 REMOVE_ONLY_THIS = 1 REMOVE_THIS_AND_FUTURE = 2 FIND_EXACT = 0 FIND_MASTER = 1 sProdID = "-//mulberrymail.com//Mulberry v4.0//EN" sDomain = "mulberrymail.com" @staticmethod def setPRODID(prodid): PyCalendar.sProdID = prodid @staticmethod def setDomain(domain): PyCalendar.sDomain = domain propertyCardinality_1 = ( definitions.cICalProperty_PRODID, definitions.cICalProperty_VERSION, ) propertyCardinality_0_1 = ( definitions.cICalProperty_CALSCALE, definitions.cICalProperty_METHOD, ) propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None, add_defaults=True): super(PyCalendar, self).__init__(None) self.mName = "" self.mDescription = "" self.mMasterComponentsByTypeAndUID = collections.defaultdict(lambda: collections.defaultdict(list)) self.mOverriddenComponentsByUID = collections.defaultdict(list) if add_defaults: self.addDefaultProperties() def duplicate(self): other = super(PyCalendar, self).duplicate() other.mName = self.mName other.mDescription = self.mDescription return other def getType(self): return definitions.cICalComponent_VCALENDAR def getName(self): return self.mName def setName(self, name): self.mName = name def editName(self, name): if self.mName != name: # Updated cached value self.mName = name # Remove existing items self.removeProperties(definitions.cICalProperty_XWRCALNAME) # Now create properties if len(name): self.ddProperty(PyCalendarProperty(definitions.cICalProperty_XWRCALNAME, name)) def getDescription(self): return self.mDescription def setDescription(self, description): self.mDescription = description def editDescription(self, description): if self.mDescription != description: # Updated cached value self.mDescription = description # Remove existing items self.removeProperties(definitions.cICalProperty_XWRCALDESC) # Now create properties if len(description): self.addProperty(PyCalendarProperty(definitions.cICalProperty_XWRCALDESC, description)) def getMethod(self): result = "" if self.hasProperty(definitions.cICalProperty_METHOD): result = self.loadValueString(definitions.cICalProperty_METHOD) return result def changeUID(self, oldUID, newUID): """ Change the UID of all components with a matching UID to a new value. We need to do this at the calendar level because this object maintains mappings based on UID which need to be updated whenever the UID changes. @param oldUID: the old value to match @type oldUID: C{str} @param newUID: the new value to match @type newUID: C{str} """ # Each component for component in self.mComponents: if component.getUID() == oldUID: component.setUID(newUID) # Maps if oldUID in self.mOverriddenComponentsByUID: self.mOverriddenComponentsByUID[newUID] = self.mOverriddenComponentsByUID[oldUID] del self.mOverriddenComponentsByUID[oldUID] for ctype in self.mMasterComponentsByTypeAndUID: if oldUID in self.mMasterComponentsByTypeAndUID[ctype]: self.mMasterComponentsByTypeAndUID[ctype][newUID] = self.mMasterComponentsByTypeAndUID[ctype][oldUID] del self.mMasterComponentsByTypeAndUID[ctype][oldUID] def finalise(self): # Get calendar name if present # Get X-WR-CALNAME temps = self.loadValueString(definitions.cICalProperty_XWRCALNAME) if temps is not None: self.mName = temps # Get X-WR-CALDESC temps = self.loadValueString(definitions.cICalProperty_XWRCALDESC) if temps is not None: self.mDescription = temps def validate(self, doFix=False, doRaise=False): """ Validate the data in this component and optionally fix any problems. Return a tuple containing two lists: the first describes problems that were fixed, the second problems that were not fixed. Caller can then decide what to do with unfixed issues. """ # Optional raise behavior fixed, unfixed = super(PyCalendar, self).validate(doFix) if doRaise and unfixed: raise PyCalendarValidationError(";".join(unfixed)) return fixed, unfixed def sortedComponentNames(self): return ( definitions.cICalComponent_VTIMEZONE, definitions.cICalComponent_VEVENT, definitions.cICalComponent_VTODO, definitions.cICalComponent_VJOURNAL, definitions.cICalComponent_VFREEBUSY, definitions.cICalComponent_VAVAILABILITY, ) def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_VERSION, definitions.cICalProperty_CALSCALE, definitions.cICalProperty_METHOD, definitions.cICalProperty_PRODID, ) @staticmethod def parseText(data): cal = PyCalendar(add_defaults=False) if cal.parse(StringIO(data)): return cal else: return None def parse(self, ins): result = False self.setProperties({}) LOOK_FOR_VCALENDAR = 0 GET_PROPERTY_OR_COMPONENT = 1 state = LOOK_FOR_VCALENDAR # Get lines looking for start of calendar lines = [None, None] comp = self compend = None componentstack = [] while readFoldedLine(ins, lines): line = lines[0] if state == LOOK_FOR_VCALENDAR: # Look for start if line == self.getBeginDelimiter(): # Next state state = GET_PROPERTY_OR_COMPONENT # Indicate success at this point result = True # Handle blank line elif len(line) == 0: # Raise if requested, otherwise just ignore if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE: raise PyCalendarInvalidData("iCalendar data has blank lines") # Unrecognized data else: raise PyCalendarInvalidData("iCalendar data not recognized", line) elif state == GET_PROPERTY_OR_COMPONENT: # Parse property or look for start of component if line.startswith("BEGIN:"): # Push previous details to stack componentstack.append((comp, compend,)) # Start a new component comp = PyCalendar.makeComponent(line[6:], comp) compend = comp.getEndDelimiter() # Look for end of object elif line == self.getEndDelimiter(): # Finalise the current calendar self.finalise() # Change state state = LOOK_FOR_VCALENDAR # Look for end of current component elif line == compend: # Finalise the component (this caches data from the properties) comp.finalise() # Embed component in parent and reset to use parent componentstack[-1][0].addComponent(comp) comp, compend = componentstack.pop() # Blank line elif len(line) == 0: # Raise if requested, otherwise just ignore if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE: raise PyCalendarInvalidData("iCalendar data has blank lines") # Must be a property else: # Parse attribute/value for top-level calendar item prop = PyCalendarProperty() if prop.parse(line): # Check for valid property if comp is self: if not comp.validProperty(prop): raise PyCalendarInvalidData("Invalid property", str(prop)) elif not comp.ignoreProperty(prop): comp.addProperty(prop) else: comp.addProperty(prop) # Check for truncated data if state != LOOK_FOR_VCALENDAR: raise PyCalendarInvalidData("iCalendar data not complete") # We need to store all timezones in the static object so they can be accessed by any date object from timezonedb import PyCalendarTimezoneDatabase PyCalendarTimezoneDatabase.mergeTimezones(self, self.getComponents(definitions.cICalComponent_VTIMEZONE)) # Validate some things if result and not self.hasProperty("VERSION"): raise PyCalendarInvalidData("iCalendar missing VERSION") return result def parseComponent(self, ins): result = None LOOK_FOR_VCALENDAR = 0 GET_PROPERTY_OR_COMPONENT = 1 GOT_VCALENDAR = 4 state = LOOK_FOR_VCALENDAR # Get lines looking for start of calendar lines = [None, None] comp = self compend = None componentstack = [] got_timezone = False while readFoldedLine(ins, lines): if state == LOOK_FOR_VCALENDAR: # Look for start if lines[0] == self.getBeginDelimiter(): # Next state state = GET_PROPERTY_OR_COMPONENT # Handle blank line elif len(lines[0]) == 0: # Raise if requested, otherwise just ignore if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE: raise PyCalendarInvalidData("iCalendar data has blank lines") # Unrecognized data else: raise PyCalendarInvalidData("iCalendar data not recognized", lines[0]) elif state == GET_PROPERTY_OR_COMPONENT: # Parse property or look for start of component if lines[0].startswith("BEGIN:"): # Push previous details to stack componentstack.append((comp, compend,)) # Start a new component comp = PyCalendar.makeComponent(lines[0][6:], comp) compend = comp.getEndDelimiter() # Cache as result - but only the first one, we ignore the rest if result is None: result = comp # Look for timezone component to trigger timezone merge only if one is present if comp.getType() == definitions.cICalComponent_VTIMEZONE: got_timezone = True elif lines[0] == self.getEndDelimiter(): # Change state state = GOT_VCALENDAR # Look for end of current component elif lines[0] == compend: # Finalise the component (this caches data from the properties) comp.finalise() # Embed component in parent and reset to use parent componentstack[-1][0].addComponent(comp) comp, compend = componentstack.pop() # Blank line elif len(lines[0]) == 0: # Raise if requested, otherwise just ignore if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE: raise PyCalendarInvalidData("iCalendar data has blank lines") # Ignore top-level items elif comp is self: pass # Must be a property else: # Parse attribute/value for top-level calendar item prop = PyCalendarProperty() if prop.parse(lines[0]): # Check for valid property if comp is not self: comp.addProperty(prop) # Exit if we have one - ignore all the rest if state == GOT_VCALENDAR: break # We need to store all timezones in the static object so they can be accessed by any date object # Only do this if we read in a timezone if got_timezone: from timezonedb import PyCalendarTimezoneDatabase PyCalendarTimezoneDatabase.mergeTimezones(self, self.getComponents(definitions.cICalComponent_VTIMEZONE)) return result def addComponent(self, component): """ Override to track components by UID. """ super(PyCalendar, self).addComponent(component) if isinstance(component, PyCalendarComponentRecur): uid = component.getUID() rid = component.getRecurrenceID() if rid: self.mOverriddenComponentsByUID[uid].append(component) else: self.mMasterComponentsByTypeAndUID[component.getType()][uid] = component def removeComponent(self, component): """ Override to track components by UID. """ super(PyCalendar, self).removeComponent(component) if isinstance(component, PyCalendarComponentRecur): uid = component.getUID() rid = component.getRecurrenceID() if rid: self.mOverriddenComponentsByUID[uid].remove(component) else: del self.mMasterComponentsByTypeAndUID[component.getType()][uid] def getText(self, includeTimezones=False): s = StringIO() self.generate(s, includeTimezones=includeTimezones) return s.getvalue() def generate(self, os, includeTimezones=False): # Make sure all required timezones are in this object if includeTimezones: self.includeTimezones() super(PyCalendar, self).generate(os) def getTextXML(self, includeTimezones=False): node = self.writeXML(includeTimezones) return xmldefs.toString(node) def writeXML(self, includeTimezones=False): # Make sure all required timezones are in this object if includeTimezones: self.includeTimezones() # Root node structure root = XML.Element(xmldefs.makeTag(xmldefs.iCalendar20_namespace, xmldefs.icalendar)) super(PyCalendar, self).writeXML(root, xmldefs.iCalendar20_namespace) return root # Get expanded components def getVEvents(self, period, list, all_day_at_top=True): # Look at each VEvent for vevent in self.getComponents(definitions.cICalComponent_VEVENT): vevent.expandPeriod(period, list) if (all_day_at_top): list.sort(PyCalendarComponentExpanded.sort_by_dtstart_allday) else: list.sort(PyCalendarComponentExpanded.sort_by_dtstart) def getVToDos(self, only_due, all_dates, upto_due_date, list): # Get current date-time less one day to test for completed events during the last day minusoneday = PyCalendarDateTime() minusoneday.setNowUTC() minusoneday.offsetDay(-1) today = PyCalendarDateTime() today.setToday() # Look at each VToDo for vtodo in self.getComponents(definitions.cICalComponent_VTODO): # Filter out done (that were complted more than a day ago) or cancelled to dos if required if only_due: if vtodo.getStatus() == definitions.eStatus_VToDo_Cancelled: continue elif ((vtodo.getStatus() == definitions.eStatus_VToDo_Completed) and (not vtodo.hasCompleted() or (vtodo.getCompleted() < minusoneday))): continue # Filter out those with end after chosen date if required if not all_dates: if vtodo.hasEnd() and (vtodo.getEnd() > upto_due_date): continue elif not vtodo.hasEnd() and (today > upto_due_date): continue # TODO: fix this #list.append(PyCalendarComponentExpandedShared(PyCalendarComponentExpanded(vtodo, None))) def getRecurrenceInstancesItems(self, type, uid, items): # Get instances from list items.extend(self.mOverriddenComponentsByUID.get(uid, ())) def getRecurrenceInstancesIds(self, type, uid, ids): # Get instances from list ids.extend([comp.getRecurrenceID() for comp in self.mOverriddenComponentsByUID.get(uid, ())]) # Freebusy generation def getVFreeBusyList(self, period, list): # Look at each VFreeBusy for vfreebusy in self.getComponents(definitions.cICalComponent_VFREEBUSY): vfreebusy.expandPeriod(period, list) def getVFreeBusyFB(self, period, fb): # First create expanded set # TODO: fix this #list = PyCalendarExpandedComponents() self.getVEvents(period, list) if len(list) == 0: return # Get start/end list for each non-all-day expanded components dtstart = [] dtend = [] for dt in list: # Ignore if all-day if dt.getInstanceStart().isDateOnly(): continue # Ignore if transparent to free-busy transp = "" if dt.getOwner().getProperty(definitions.cICalProperty_TRANSP, transp) and (transp == definitions.cICalProperty_TRANSPARENT): continue # Add start/end to list dtstart.append(dt.getInstanceStart()) dtend.append(dt.getInstanceEnd()) # No longer need the expanded items list.clear() # Create non-overlapping periods as properties in the freebusy component temp = PyCalendarPeriod(dtstart.front(), dtend.front()) dtstart_iter = dtstart.iter() dtstart_iter.next() dtend_iter = dtend.iter() dtend_iter.next() for i in i: # Check for non-overlap if dtstart_iter > temp.getEnd(): # Current period is complete fb.addProperty(PyCalendarProperty(definitions.cICalProperty_FREEBUSY, temp)) # Reset period to new range temp = PyCalendarPeriod(dtstart_iter, dtend_iter) # They overlap - check for extended end if dtend_iter > temp.getEnd(): # Extend the end temp = PyCalendarPeriod(temp.getStart(), dtend_iter) # Add remaining period as property fb.addProperty(PyCalendarProperty(definitions.cICalProperty_FREEBUSY, temp)) def getFreeBusy(self, period, fb): # First create expanded set list = [] self.getVEvents(period, list) # Get start/end list for each non-all-day expanded components for comp in list: # Ignore if all-day if comp.getInstanceStart().isDateOnly(): continue # Ignore if transparent to free-busy transp = "" if comp.getOwner().getProperty(definitions.cICalProperty_TRANSP, transp) and (transp == definitions.cICalProperty_TRANSPARENT): continue # Add free busy item to list status = comp.getMaster().getStatus() if status in (definitions.eStatus_VEvent_None, definitions.eStatus_VEvent_Confirmed): fb.append(PyCalendarFreeBusy(PyCalendarFreeBusy.BUSY, PyCalendarPeriod(comp.getInstanceStart(), comp.getInstanceEnd()))) elif status == definitions.eStatus_VEvent_Tentative: fb.append(PyCalendarFreeBusy(PyCalendarFreeBusy.BUSYTENTATIVE, PyCalendarPeriod(comp.getInstanceStart(), comp.getInstanceEnd()))) break elif status == definitions.eStatus_VEvent_Cancelled: # Cancelled => does not contribute to busy time pass # Now get the VFREEBUSY info list2 = [] self.getVFreeBusy(period, list2) # Get start/end list for each free-busy for comp in list2: # Expand component and add free busy info to list comp.expandPeriod(period, fb) # Add remaining period as property PyCalendarFreeBusy.resolveOverlaps(fb) def getTimezoneOffsetSeconds(self, tzid, dt, relative_to_utc=False): # Find timezone that matches the name (which is the same as the map key) timezone = self.getTimezone(tzid) return timezone.getTimezoneOffsetSeconds(dt, relative_to_utc) if timezone else 0 def getTimezoneDescriptor(self, tzid, dt): # Find timezone that matches the name (which is the same as the map key) timezone = self.getTimezone(tzid) return timezone.getTimezoneDescriptor(dt) if timezone else "" def getTimezone(self, tzid): # Find timezone that matches the name (which is the same as the map key) for timezone in self.getComponents(definitions.cICalComponent_VTIMEZONE): if timezone.getID() == tzid: return timezone else: return None def addDefaultProperties(self): self.addProperty(PyCalendarProperty(definitions.cICalProperty_PRODID, PyCalendar.sProdID)) self.addProperty(PyCalendarProperty(definitions.cICalProperty_VERSION, "2.0")) self.addProperty(PyCalendarProperty(definitions.cICalProperty_CALSCALE, "GREGORIAN")) def validProperty(self, prop): if prop.getName() == definitions.cICalProperty_VERSION: tvalue = prop.getTextValue() if ((tvalue is None) or (tvalue.getValue() != "2.0")): return False elif prop.getName() == definitions.cICalProperty_CALSCALE: tvalue = prop.getTextValue() if ((tvalue is None) or (tvalue.getValue() != "GREGORIAN")): return False return True def ignoreProperty(self, prop): return False #prop.getName() in (definitions.cICalProperty_VERSION, definitions.cICalProperty_CALSCALE, definitions.cICalProperty_PRODID) def includeTimezones(self): # Get timezone names from each component tzids = set() for component in self.mComponents: if component.getType() != definitions.cICalComponent_VTIMEZONE: component.getTimezones(tzids) # Make sure each timezone is in current calendar from timezonedb import PyCalendarTimezoneDatabase for tzid in tzids: tz = self.getTimezone(tzid) if tz is None: # Find it in the static object tz = PyCalendarTimezoneDatabase.getTimezone(tzid) if tz is not None: dup = tz.duplicate() self.addComponent(dup) @staticmethod def makeComponent(compname, parent): mapper = { definitions.cICalComponent_VEVENT: PyCalendarVEvent, definitions.cICalComponent_VTODO: PyCalendarVToDo, definitions.cICalComponent_VJOURNAL: PyCalendarVJournal, definitions.cICalComponent_VFREEBUSY: PyCalendarVFreeBusy, definitions.cICalComponent_VTIMEZONE: PyCalendarVTimezone, definitions.cICalComponent_VAVAILABILITY: PyCalendarVAvailability, definitions.cICalComponent_VALARM: PyCalendarVAlarm, definitions.cICalComponent_AVAILABLE: PyCalendarAvailable, definitions.cICalComponent_STANDARD: PyCalendarVTimezoneStandard, definitions.cICalComponent_DAYLIGHT: PyCalendarVTimezoneDaylight, } try: cls = mapper[compname] return cls(parent=parent) except KeyError: return PyCalendarUnknownComponent(parent=parent, comptype=compname) pycalendar-2.0~svn13177/src/pycalendar/urivalue.py0000644000175000017500000000352412126720253021160 0ustar rahulrahul## # Copyright (c) 2007-2013 Cyrus Daboo. 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. ## # iCalendar URI value from pycalendar import xmldefs, utils from pycalendar.plaintextvalue import PyCalendarPlainTextValue from pycalendar.value import PyCalendarValue from pycalendar.parser import ParserContext class PyCalendarURIValue(PyCalendarPlainTextValue): def getType(self): return PyCalendarURIValue.VALUETYPE_URI def parse(self, data): if ParserContext.BACKSLASH_IN_URI_VALUE == ParserContext.PARSER_FIX: # Decoding required self.mValue = utils.decodeTextValue(data) else: # No decoding required self.mValue = data # os - StringIO object def generate(self, os): """ Handle a client bug where it sometimes includes a \n in the value and we need to make sure that gets encoded rather than included literally which would break syntax. """ if '\n' in self.mValue: try: # No encoding required os.write(self.mValue.replace("\n", "\\n")) except: pass else: super(PyCalendarURIValue, self).generate(os) PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_URI, PyCalendarURIValue, xmldefs.value_uri) pycalendar-2.0~svn13177/src/pycalendar/caladdressvalue.py0000644000175000017500000000205612101017573022462 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # iCalendar UTC Offset value from pycalendar import xmldefs from pycalendar.plaintextvalue import PyCalendarPlainTextValue from pycalendar.value import PyCalendarValue class PyCalendarCalAddressValue(PyCalendarPlainTextValue): def getType(self): return PyCalendarValue.VALUETYPE_CALADDRESS PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_CALADDRESS, PyCalendarCalAddressValue, xmldefs.value_cal_address) pycalendar-2.0~svn13177/src/pycalendar/valarm.py0000644000175000017500000006111712101017573020605 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar.attribute import PyCalendarAttribute from pycalendar.component import PyCalendarComponent from pycalendar.datetime import PyCalendarDateTime from pycalendar.duration import PyCalendarDuration from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS from pycalendar.property import PyCalendarProperty from pycalendar.value import PyCalendarValue class PyCalendarVAlarm(PyCalendarComponent): sActionMap = { definitions.cICalProperty_ACTION_AUDIO: definitions.eAction_VAlarm_Audio, definitions.cICalProperty_ACTION_DISPLAY: definitions.eAction_VAlarm_Display, definitions.cICalProperty_ACTION_EMAIL: definitions.eAction_VAlarm_Email, definitions.cICalProperty_ACTION_PROCEDURE: definitions.eAction_VAlarm_Procedure, definitions.cICalProperty_ACTION_URI: definitions.eAction_VAlarm_URI, definitions.cICalProperty_ACTION_NONE: definitions.eAction_VAlarm_None, } sActionValueMap = { definitions.eAction_VAlarm_Audio: definitions.cICalProperty_ACTION_AUDIO, definitions.eAction_VAlarm_Display: definitions.cICalProperty_ACTION_DISPLAY, definitions.eAction_VAlarm_Email: definitions.cICalProperty_ACTION_EMAIL, definitions.eAction_VAlarm_Procedure: definitions.cICalProperty_ACTION_PROCEDURE, definitions.eAction_VAlarm_URI: definitions.cICalProperty_ACTION_URI, definitions.eAction_VAlarm_None: definitions.cICalProperty_ACTION_NONE, } # Classes for each action encapsulating action-specific data class PyCalendarVAlarmAction(object): propertyCardinality_1 = () propertyCardinality_1_Fix_Empty = () propertyCardinality_0_1 = () propertyCardinality_1_More = () def __init__(self, type): self.mType = type def duplicate(self): return PyCalendarVAlarm.PyCalendarVAlarmAction(self.mType) def load(self, valarm): pass def add(self, valarm): pass def remove(self, valarm): pass def getType(self): return self.mType class PyCalendarVAlarmAudio(PyCalendarVAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, ) propertyCardinality_0_1 = ( definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, definitions.cICalProperty_ATTACH, definitions.cICalProperty_ACKNOWLEDGED, ) def __init__(self, speak=None): super(PyCalendarVAlarm.PyCalendarVAlarmAudio, self).__init__(type=definitions.eAction_VAlarm_Audio) self.mSpeakText = speak def duplicate(self): return PyCalendarVAlarm.PyCalendarVAlarmAudio(self.mSpeakText) def load(self, valarm): # Get properties self.mSpeakText = valarm.loadValueString(definitions.cICalProperty_ACTION_X_SPEAKTEXT) def add(self, valarm): # Delete existing then add self.remove(valarm) prop = PyCalendarProperty(definitions.cICalProperty_ACTION_X_SPEAKTEXT, self.mSpeakText) valarm.addProperty(prop) def remove(self, valarm): valarm.removeProperties(definitions.cICalProperty_ACTION_X_SPEAKTEXT) def isSpeakText(self): return len(self.mSpeakText) != 0 def getSpeakText(self): return self.mSpeakText class PyCalendarVAlarmDisplay(PyCalendarVAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, ) propertyCardinality_1_Fix_Empty = ( definitions.cICalProperty_DESCRIPTION, ) propertyCardinality_0_1 = ( definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, definitions.cICalProperty_ACKNOWLEDGED, ) def __init__(self, description=None): super(PyCalendarVAlarm.PyCalendarVAlarmDisplay, self).__init__(type=definitions.eAction_VAlarm_Display) self.mDescription = description def duplicate(self): return PyCalendarVAlarm.PyCalendarVAlarmDisplay(self.mDescription) def load(self, valarm): # Get properties self.mDescription = valarm.loadValueString(definitions.cICalProperty_DESCRIPTION) def add(self, valarm): # Delete existing then add self.remove(valarm) prop = PyCalendarProperty(definitions.cICalProperty_DESCRIPTION, self.mDescription) valarm.addProperty(prop) def remove(self, valarm): valarm.removeProperties(definitions.cICalProperty_DESCRIPTION) def getDescription(self): return self.mDescription class PyCalendarVAlarmEmail(PyCalendarVAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, ) propertyCardinality_1_Fix_Empty = ( definitions.cICalProperty_DESCRIPTION, definitions.cICalProperty_SUMMARY, ) propertyCardinality_0_1 = ( definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, definitions.cICalProperty_ACKNOWLEDGED, ) propertyCardinality_1_More = ( definitions.cICalProperty_ATTENDEE, ) def __init__(self, description=None, summary=None, attendees=None): super(PyCalendarVAlarm.PyCalendarVAlarmEmail, self).__init__(type=definitions.eAction_VAlarm_Email) self.mDescription = description self.mSummary = summary self.mAttendees = attendees def duplicate(self): return PyCalendarVAlarm.PyCalendarVAlarmEmail(self.mDescription, self.mSummary, self.mAttendees) def load(self, valarm): # Get properties self.mDescription = valarm.loadValueString(definitions.cICalProperty_DESCRIPTION) self.mSummary = valarm.loadValueString(definitions.cICalProperty_SUMMARY) self.mAttendees = [] if valarm.hasProperty(definitions.cICalProperty_ATTENDEE): # Get each attendee range = valarm.getProperties().get(definitions.cICalProperty_ATTENDEE, ()) for iter in range: # Get the attendee value attendee = iter.getCalAddressValue() if attendee is not None: self.mAttendees.append(attendee.getValue()) def add(self, valarm): # Delete existing then add self.remove(valarm) prop = PyCalendarProperty(definitions.cICalProperty_DESCRIPTION, self.mDescription) valarm.addProperty(prop) prop = PyCalendarProperty(definitions.cICalProperty_SUMMARY, self.mSummary) valarm.addProperty(prop) for iter in self.mAttendees: prop = PyCalendarProperty(definitions.cICalProperty_ATTENDEE, iter, PyCalendarValue.VALUETYPE_CALADDRESS) valarm.addProperty(prop) def remove(self, valarm): valarm.removeProperties(definitions.cICalProperty_DESCRIPTION) valarm.removeProperties(definitions.cICalProperty_SUMMARY) valarm.removeProperties(definitions.cICalProperty_ATTENDEE) def getDescription(self): return self.mDescription def getSummary(self): return self.mSummary def getAttendees(self): return self.mAttendees class PyCalendarVAlarmUnknown(PyCalendarVAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, ) propertyCardinality_0_1 = ( definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, definitions.cICalProperty_ACKNOWLEDGED, ) def __init__(self): super(PyCalendarVAlarm.PyCalendarVAlarmUnknown, self).__init__(type=definitions.eAction_VAlarm_Unknown) def duplicate(self): return PyCalendarVAlarm.PyCalendarVAlarmUnknown() class PyCalendarVAlarmURI(PyCalendarVAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, definitions.cICalProperty_URL, ) propertyCardinality_0_1 = ( definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, definitions.cICalProperty_ACKNOWLEDGED, ) def __init__(self, uri=None): super(PyCalendarVAlarm.PyCalendarVAlarmURI, self).__init__(type=definitions.eAction_VAlarm_URI) self.mURI = uri def duplicate(self): return PyCalendarVAlarm.PyCalendarVAlarmURI(self.mURI) def load(self, valarm): # Get properties self.mURI = valarm.loadValueString(definitions.cICalProperty_URL) def add(self, valarm): # Delete existing then add self.remove(valarm) prop = PyCalendarProperty(definitions.cICalProperty_URL, self.mURI) valarm.addProperty(prop) def remove(self, valarm): valarm.removeProperties(definitions.cICalProperty_URL) def getURI(self): return self.mURI class PyCalendarVAlarmNone(PyCalendarVAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, ) def __init__(self): super(PyCalendarVAlarm.PyCalendarVAlarmNone, self).__init__(type=definitions.eAction_VAlarm_None) def duplicate(self): return PyCalendarVAlarm.PyCalendarVAlarmNone() def getMimeComponentName(self): # Cannot be sent as a separate MIME object return None sActionToAlarmMap = { definitions.eAction_VAlarm_Audio: PyCalendarVAlarmAudio, definitions.eAction_VAlarm_Display: PyCalendarVAlarmDisplay, definitions.eAction_VAlarm_Email: PyCalendarVAlarmEmail, definitions.eAction_VAlarm_URI: PyCalendarVAlarmURI, definitions.eAction_VAlarm_None: PyCalendarVAlarmNone, } propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None): super(PyCalendarVAlarm, self).__init__(parent=parent) self.mAction = definitions.eAction_VAlarm_Display self.mTriggerAbsolute = False self.mTriggerOnStart = True self.mTriggerOn = PyCalendarDateTime() # Set duration default to 1 hour self.mTriggerBy = PyCalendarDuration() self.mTriggerBy.setDuration(60 * 60) # Does not repeat by default self.mRepeats = 0 self.mRepeatInterval = PyCalendarDuration() self.mRepeatInterval.setDuration(5 * 60) # Five minutes # Status self.mStatusInit = False self.mAlarmStatus = definitions.eAlarm_Status_Pending self.mLastTrigger = PyCalendarDateTime() self.mNextTrigger = PyCalendarDateTime() self.mDoneCount = 0 # Create action data self.mActionData = PyCalendarVAlarm.PyCalendarVAlarmDisplay("") def duplicate(self, parent=None): other = super(PyCalendarVAlarm, self).duplicate(parent=parent) other.mAction = self.mAction other.mTriggerAbsolute = self.mTriggerAbsolute other.mTriggerOn = self.mTriggerOn.duplicate() other.mTriggerBy = self.mTriggerBy.duplicate() other.mTriggerOnStart = self.mTriggerOnStart other.mRepeats = self.mRepeats other.mRepeatInterval = self.mRepeatInterval.duplicate() other.mAlarmStatus = self.mAlarmStatus if self.mLastTrigger is not None: other.mLastTrigger = self.mLastTrigger.duplicate() if self.mNextTrigger is not None: other.mNextTrigger = self.mNextTrigger.duplicate() other.mDoneCount = self.mDoneCount other.mActionData = self.mActionData.duplicate() return other def getType(self): return definitions.cICalComponent_VALARM def getAction(self): return self.mAction def getActionData(self): return self.mActionData def isTriggerAbsolute(self): return self.mTriggerAbsolute def getTriggerOn(self): return self.mTriggerOn def getTriggerDuration(self): return self.mTriggerBy def isTriggerOnStart(self): return self.mTriggerOnStart def getRepeats(self): return self.mRepeats def getInterval(self): return self.mRepeatInterval def added(self): # Added to calendar so add to calendar notifier # calstore::CCalendarNotifier::sCalendarNotifier.AddAlarm(this) # Do inherited super(PyCalendarVAlarm, self).added() def removed(self): # Removed from calendar so add to calendar notifier # calstore::CCalendarNotifier::sCalendarNotifier.RemoveAlarm(this) # Do inherited super(PyCalendarVAlarm, self).removed() def changed(self): # Always force recalc of trigger status self.mStatusInit = False # Changed in calendar so change in calendar notifier # calstore::CCalendarNotifier::sCalendarNotifier.ChangedAlarm(this) # Do not do inherited as this is always a sub-component and we do not # do top-level component changes # super.changed() def finalise(self): # Do inherited super(PyCalendarVAlarm, self).finalise() # Get the ACTION temp = self.loadValueString(definitions.cICalProperty_ACTION) if temp is not None: self.mAction = PyCalendarVAlarm.sActionMap.get(temp, definitions.eAction_VAlarm_Unknown) self.loadAction() # Get the trigger if self.hasProperty(definitions.cICalProperty_TRIGGER): # Determine the type of the value temp1 = self.loadValueDateTime(definitions.cICalProperty_TRIGGER) temp2 = self.loadValueDuration(definitions.cICalProperty_TRIGGER) if temp1 is not None: self.mTriggerAbsolute = True self.mTriggerOn = temp1 elif temp2 is not None: self.mTriggerAbsolute = False self.mTriggerBy = temp2 # Get the property prop = self.findFirstProperty(definitions.cICalProperty_TRIGGER) # Look for RELATED attribute if prop.hasAttribute(definitions.cICalAttribute_RELATED): temp = prop.getAttributeValue(definitions.cICalAttribute_RELATED) if temp == definitions.cICalAttribute_RELATED_START: self.mTriggerOnStart = True elif temp == definitions.cICalAttribute_RELATED_END: self.mTriggerOnStart = False else: self.mTriggerOnStart = True # Get repeat & interval temp = self.loadValueInteger(definitions.cICalProperty_REPEAT) if temp is not None: self.mRepeats = temp temp = self.loadValueDuration(definitions.cICalProperty_DURATION) if temp is not None: self.mRepeatInterval = temp # Set a map key for sorting self.mMapKey = "%s:%s" % (self.mAction, self.mTriggerOn if self.mTriggerAbsolute else self.mTriggerBy,) # Alarm status - private to Mulberry status = self.loadValueString(definitions.cICalProperty_ALARM_X_ALARMSTATUS) if status is not None: if status == definitions.cICalProperty_ALARM_X_ALARMSTATUS_PENDING: self.mAlarmStatus = definitions.eAlarm_Status_Pending elif status == definitions.cICalProperty_ALARM_X_ALARMSTATUS_COMPLETED: self.mAlarmStatus = definitions.eAlarm_Status_Completed elif status == definitions.cICalProperty_ALARM_X_ALARMSTATUS_DISABLED: self.mAlarmStatus = definitions.eAlarm_Status_Disabled else: self.mAlarmStatus = definitions.eAlarm_Status_Pending # Last trigger time - private to Mulberry temp = self.loadValueDateTime(definitions.cICalProperty_ALARM_X_LASTTRIGGER) if temp is not None: self.mLastTrigger = temp def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended to that. """ # Validate using action specific constraints self.propertyCardinality_1 = self.mActionData.propertyCardinality_1 self.propertyCardinality_1_Fix_Empty = self.mActionData.propertyCardinality_1_Fix_Empty self.propertyCardinality_0_1 = self.mActionData.propertyCardinality_0_1 self.propertyCardinality_1_More = self.mActionData.propertyCardinality_1_More fixed, unfixed = super(PyCalendarVAlarm, self).validate(doFix) # Extra constraint: both DURATION and REPEAT must be present togethe if self.hasProperty(definitions.cICalProperty_DURATION) ^ self.hasProperty(definitions.cICalProperty_REPEAT): # Cannot fix this logProblem = "[%s] Properties must be present together: %s, %s" % ( self.getType(), definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, ) unfixed.append(logProblem) return fixed, unfixed def editStatus(self, status): # Remove existing self.removeProperties(definitions.cICalProperty_ALARM_X_ALARMSTATUS) # Updated cached values self.mAlarmStatus = status # Add new status_txt = "" if self.mAlarmStatus == definitions.eAlarm_Status_Pending: status_txt = definitions.cICalProperty_ALARM_X_ALARMSTATUS_PENDING elif self.mAlarmStatus == definitions.eAlarm_Status_Completed: status_txt = definitions.cICalProperty_ALARM_X_ALARMSTATUS_COMPLETED elif self.mAlarmStatus == definitions.eAlarm_Status_Disabled: status_txt = definitions.cICalProperty_ALARM_X_ALARMSTATUS_DISABLED self.addProperty(PyCalendarProperty(definitions.cICalProperty_ALARM_X_ALARMSTATUS, status_txt)) def editAction(self, action, data): # Remove existing self.removeProperties(definitions.cICalProperty_ACTION) self.mActionData.remove(self) self.mActionData = None # Updated cached values self.mAction = action self.mActionData = data # Add new properties to alarm action_txt = PyCalendarVAlarm.sActionValueMap.get(self.mAction, definitions.cICalProperty_ACTION_PROCEDURE) prop = PyCalendarProperty(definitions.cICalProperty_ACTION, action_txt) self.addProperty(prop) self.mActionData.add(self) def editTriggerOn(self, dt): # Remove existing self.removeProperties(definitions.cICalProperty_TRIGGER) # Updated cached values self.mTriggerAbsolute = True self.mTriggerOn = dt # Add new prop = PyCalendarProperty(definitions.cICalProperty_TRIGGER, dt) self.addProperty(prop) def editTriggerBy(self, duration, trigger_start): # Remove existing self.removeProperties(definitions.cICalProperty_TRIGGER) # Updated cached values self.mTriggerAbsolute = False self.mTriggerBy = duration self.mTriggerOnStart = trigger_start # Add new (with attribute) prop = PyCalendarProperty(definitions.cICalProperty_TRIGGER, duration) attr = PyCalendarAttribute(definitions.cICalAttribute_RELATED, (definitions.cICalAttribute_RELATED_START, definitions.cICalAttribute_RELATED_END)[not trigger_start]) prop.addAttribute(attr) self.addProperty(prop) def editRepeats(self, repeat, interval): # Remove existing self.removeProperties(definitions.cICalProperty_REPEAT) self.removeProperties(definitions.cICalProperty_DURATION) # Updated cached values self.mRepeats = repeat self.mRepeatInterval = interval # Add new if self.mRepeats > 0: self.addProperty(PyCalendarProperty(definitions.cICalProperty_REPEAT, repeat)) self.addProperty(PyCalendarProperty(definitions.cICalProperty_DURATION, interval)) def getAlarmStatus(self): return self.mAlarmStatus def getNextTrigger(self, dt): if not self.mStatusInit: self.initNextTrigger() dt.copy(self.mNextTrigger) def alarmTriggered(self, dt): # Remove existing self.removeProperties(definitions.cICalProperty_ALARM_X_LASTTRIGGER) self.removeProperties(definitions.cICalProperty_ALARM_X_ALARMSTATUS) # Updated cached values self.mLastTrigger.copy(dt) if self.mDoneCount < self.mRepeats: self.mNextTrigger = self.mLastTrigger + self.mRepeatInterval dt.copy(self.mNextTrigger) self.mDoneCount += 1 self.mAlarmStatus = definitions.eAlarm_Status_Pending else: self.mAlarmStatus = definitions.eAlarm_Status_Completed # Add new self.addProperty(PyCalendarProperty(definitions.cICalProperty_ALARM_X_LASTTRIGGER, dt)) status = "" if self.mAlarmStatus == definitions.eAlarm_Status_Pending: status = definitions.cICalProperty_ALARM_X_ALARMSTATUS_PENDING elif self.mAlarmStatus == definitions.eAlarm_Status_Completed: status = definitions.cICalProperty_ALARM_X_ALARMSTATUS_COMPLETED elif self.mAlarmStatus == definitions.eAlarm_Status_Disabled: status = definitions.cICalProperty_ALARM_X_ALARMSTATUS_DISABLED self.addProperty(PyCalendarProperty(definitions.cICalProperty_ALARM_X_ALARMSTATUS, status)) # Now update dt to the next alarm time return self.mAlarmStatus == definitions.eAlarm_Status_Pending def loadAction(self): # Delete current one self.mActionData = None self.mActionData = PyCalendarVAlarm.sActionToAlarmMap.get(self.mAction, PyCalendarVAlarm.PyCalendarVAlarmUnknown)() self.mActionData.load(self) def initNextTrigger(self): # Do not bother if its completed if self.mAlarmStatus == definitions.eAlarm_Status_Completed: return self.mStatusInit = True # Look for trigger immediately preceeding or equal to utc now nowutc = PyCalendarDateTime.getNowUTC() # Init done counter self.mDoneCount = 0 # Determine the first trigger trigger = PyCalendarDateTime() self.getFirstTrigger(trigger) while self.mDoneCount < self.mRepeats: # See if next trigger is later than now next_trigger = trigger + self.mRepeatInterval if next_trigger > nowutc: break self.mDoneCount += 1 trigger = next_trigger # Check for completion if trigger == self.mLastTrigger or (nowutc - trigger).getTotalSeconds() > 24 * 60 * 60: if self.mDoneCount == self.mRepeats: self.mAlarmStatus = definitions.eAlarm_Status_Completed return else: trigger = trigger + self.mRepeatInterval self.mDoneCount += 1 self.mNextTrigger = trigger def getFirstTrigger(self, dt): # If absolute trigger, use that if self.isTriggerAbsolute(): # Get the trigger on dt.copy(self.getTriggerOn()) else: # Get the parent embedder class (must be CICalendarComponentRecur type) owner = self.getEmbedder() if owner is not None: # Determine time at which alarm will trigger trigger = (owner.getStart(), owner.getEnd())[not self.isTriggerOnStart()] # Offset by duration dt.copy(trigger + self.getTriggerDuration()) pycalendar-2.0~svn13177/src/pycalendar/manager.py0000644000175000017500000000353312101017573020733 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.timezone import PyCalendarTimezone class PyCalendarManager(object): sICalendarManager = None def __init__(self): PyCalendarTimezone.sDefaultTimezone = PyCalendarTimezone() def initManager(self): # TODO: - read in timezones from vtimezones.ics file # Eventually we need to read these from prefs - for now they are # hard-coded to my personal prefs! self.setDefaultTimezone(PyCalendarTimezone(utc=False, tzid="US/Eastern")) def setDefaultTimezoneID(self, tzid): # Check for UTC if tzid == "UTC": temp = PyCalendarTimezone(utc=True) self.setDefaultTimezone(temp) else: temp = PyCalendarTimezone(utc=False, tzid=tzid) self.setDefaultTimezone(temp) def setDefaultTimezone(self, tzid): PyCalendarTimezone.sDefaultTimezone = tzid def getDefaultTimezoneID(self): if PyCalendarTimezone.sDefaultTimezone.getUTC(): return "UTC" else: return PyCalendarTimezone.sDefaultTimezone.getTimezoneID() def getDefaultTimezone(self): return PyCalendarTimezone.sDefaultTimezone PyCalendarManager.sICalendarManager = PyCalendarManager() pycalendar-2.0~svn13177/src/pycalendar/durationvalue.py0000644000175000017500000000306212101017573022200 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import xmldefs from pycalendar.duration import PyCalendarDuration from pycalendar.value import PyCalendarValue class PyCalendarDurationValue(PyCalendarValue): def __init__(self, value=None): self.mValue = value if value is not None else PyCalendarDuration() def duplicate(self): return PyCalendarDurationValue(self.mValue.duplicate()) def getType(self): return PyCalendarValue.VALUETYPE_DURATION def parse(self, data): self.mValue.parse(data) def generate(self, os): self.mValue.generate(os) def writeXML(self, node, namespace): value = self.getXMLNode(node, namespace) value.text = self.mValue.writeXML() def getValue(self): return self.mValue def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_DURATION, PyCalendarDurationValue, xmldefs.value_duration) pycalendar-2.0~svn13177/src/pycalendar/integervalue.py0000644000175000017500000000310112101017573022002 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # iCalendar UTC Offset value from pycalendar import xmldefs from pycalendar.value import PyCalendarValue class PyCalendarIntegerValue(PyCalendarValue): def __init__(self, value=None): self.mValue = value if value is not None else 0 def duplicate(self): return PyCalendarIntegerValue(self.mValue) def getType(self): return PyCalendarValue.VALUETYPE_INTEGER def parse(self, data): self.mValue = int(data) # os - StringIO object def generate(self, os): try: os.write(str(self.mValue)) except: pass def writeXML(self, node, namespace): value = self.getXMLNode(node, namespace) value.text = str(self.mValue) def getValue(self): return self.mValue def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_INTEGER, PyCalendarIntegerValue, xmldefs.value_integer) pycalendar-2.0~svn13177/src/pycalendar/parser.py0000644000175000017500000000620312301206170020605 0ustar rahulrahul## # Copyright (c) 2011-2013 Cyrus Daboo. 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. ## class ParserContext(object): """ Ultimately want to have these states as per-object so we can pass a context down through the entire parse call chain so that we can use different error handling for different situations. For now though it is a module static. """ ( PARSER_ALLOW, # Pass the "suspect" data through to the object model PARSER_IGNORE, # Ignore the "suspect" data PARSER_FIX, # Fix (or if not possible ignore) the "suspect" data PARSER_RAISE, # Raise an exception ) = range(4) # Some clients escape ":" - fix INVALID_COLON_ESCAPE_SEQUENCE = PARSER_FIX # Other escape sequences - raise INVALID_ESCAPE_SEQUENCES = PARSER_RAISE # Some client generate empty lines in the body of the data BLANK_LINES_IN_DATA = PARSER_FIX # Some clients still generate vCard 2 parameter syntax VCARD_2_NO_PARAMETER_VALUES = PARSER_ALLOW # Use this to fix v2 BASE64 to v3 ENCODING=b - only PARSER_FIX or PARSER_ALLOW VCARD_2_BASE64 = PARSER_FIX # Allow DATE values when DATETIME specified (and vice versa) INVALID_DATETIME_VALUE = PARSER_FIX # Allow leading space instead of leading zeros for year in DATE or DATE-TIME values INVALID_DATETIME_LEADINGSPACE = PARSER_ALLOW # Allow slightly invalid DURATION values INVALID_DURATION_VALUE = PARSER_FIX # Truncate over long ADR and N values INVALID_ADR_N_VALUES = PARSER_FIX # REQUEST-STATUS values with \; as the first separator or single element INVALID_REQUEST_STATUS_VALUE = PARSER_FIX # Remove \-escaping in URI values when parsing - only PARSER_FIX or PARSER_ALLOW BACKSLASH_IN_URI_VALUE = PARSER_FIX @staticmethod def allRaise(): """ Make all tests raise an error - never fix """ ParserContext.INVALID_COLON_ESCAPE_SEQUENCE = ParserContext.PARSER_RAISE ParserContext.INVALID_ESCAPE_SEQUENCES = ParserContext.PARSER_RAISE ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_RAISE ParserContext.VCARD_2_NO_PARAMETER_VALUES = ParserContext.PARSER_RAISE ParserContext.VCARD_2_BASE64 = ParserContext.PARSER_RAISE ParserContext.INVALID_DURATION_VALUE = ParserContext.PARSER_RAISE ParserContext.INVALID_ADR_N_VALUES = ParserContext.PARSER_RAISE ParserContext.INVALID_REQUEST_STATUS_VALUE = ParserContext.PARSER_RAISE ParserContext.BACKSLASH_IN_URI_VALUE = ParserContext.PARSER_RAISE ParserContext.INVALID_REQUEST_STATUS = ParserContext.PARSER_RAISE pycalendar-2.0~svn13177/src/pycalendar/icalendar/0000755000175000017500000000000012322631215020665 5ustar rahulrahulpycalendar-2.0~svn13177/src/pycalendar/icalendar/validation.py0000644000175000017500000000321212101017573023367 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar.validation import partial, PropertyValueChecks ICALENDAR_VALUE_CHECKS = { definitions.cICalProperty_CALSCALE: partial(PropertyValueChecks.stringValue, "GREGORIAN"), definitions.cICalProperty_VERSION: partial(PropertyValueChecks.stringValue, "2.0"), definitions.cICalProperty_PERCENT_COMPLETE: partial(PropertyValueChecks.numericRange, 0, 100), definitions.cICalProperty_PRIORITY: partial(PropertyValueChecks.numericRange, 0, 9), definitions.cICalProperty_COMPLETED: PropertyValueChecks.alwaysUTC, definitions.cICalProperty_REPEAT: PropertyValueChecks.positiveIntegerOrZero, definitions.cICalProperty_CREATED: PropertyValueChecks.alwaysUTC, definitions.cICalProperty_DTSTAMP: PropertyValueChecks.alwaysUTC, definitions.cICalProperty_LAST_MODIFIED: PropertyValueChecks.alwaysUTC, definitions.cICalProperty_SEQUENCE: PropertyValueChecks.positiveIntegerOrZero, definitions.cICalProperty_ACKNOWLEDGED: PropertyValueChecks.alwaysUTC, } pycalendar-2.0~svn13177/src/pycalendar/icalendar/tests/0000755000175000017500000000000012322631215022027 5ustar rahulrahulpycalendar-2.0~svn13177/src/pycalendar/icalendar/tests/test_validation.py0000644000175000017500000024707712101017573025613 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar.calendar import PyCalendar from pycalendar.exceptions import PyCalendarValidationError import unittest class TestValidation(unittest.TestCase): def test_basic(self): data = ( ( "No problems", """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"), set(), set(), ), ( "No PRODID", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN 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"), set(), set(( "[VCALENDAR] Missing or too many required property: PRODID", )), ), ) for title, item, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(item) fixed, unfixed = cal.validate(doFix=False) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_mode_no_fix_no_raise(self): data = ( ( "OK", """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"), """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"), set(), set(), ), ( "Unfixable only", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN 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"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN 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"), set(), set(( "[VCALENDAR] Missing or too many required property: PRODID", )), ), ( "Fixable only", """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 DURATION:P1D 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"), """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 DURATION:P1D 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"), set(), set(( "[VEVENT] Properties must not both be present: DTEND, DURATION", )), ), ( "Fixable and unfixable", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DURATION:P1D 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"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DURATION:P1D 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"), set(), set(( "[VCALENDAR] Missing or too many required property: PRODID", "[VEVENT] Properties must not both be present: DTEND, DURATION", )), ), ) for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=False, doRaise=False) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_mode_fix_no_raise(self): data = ( ( "OK", """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"), """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"), set(), set(), ), ( "Unfixable only", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN 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"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN 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"), set(), set(( "[VCALENDAR] Missing or too many required property: PRODID", )), ), ( "Fixable only", """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 DURATION:P1D 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"), """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 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(( "[VEVENT] Properties must not both be present: DTEND, DURATION", )), set(), ), ( "Fixable and unfixable", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DURATION:P1D 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"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(( "[VEVENT] Properties must not both be present: DTEND, DURATION", )), set(( "[VCALENDAR] Missing or too many required property: PRODID", )), ), ) for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True, doRaise=False) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_mode_no_fix_raise(self): data = ( ( "OK", """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"), """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"), set(), set(), False, ), ( "Unfixable only", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN 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"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN 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"), set(), set(( "[VCALENDAR] Missing or too many required property: PRODID", )), True, ), ( "Fixable only", """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 DURATION:P1D 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"), """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 DURATION:P1D 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"), set(), set(( "[VEVENT] Properties must not both be present: DTEND, DURATION", )), True, ), ( "Fixable and unfixable", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DURATION:P1D 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"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DURATION:P1D 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"), set(), set(( "[VCALENDAR] Missing or too many required property: PRODID", "[VEVENT] Properties must not both be present: DTEND, DURATION", )), True, ), ) for title, test_old, test_new, test_fixed, test_unfixed, test_raises in data: cal = PyCalendar.parseText(test_old) if test_raises: self.assertRaises(PyCalendarValidationError, cal.validate, doFix=False, doRaise=True) else: try: fixed, unfixed = cal.validate(doFix=False, doRaise=False) except: self.fail(msg="Failed test: %s" % (title,)) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_mode_fix_raise(self): data = ( ( "OK", """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"), """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"), set(), set(), False, ), ( "Unfixable only", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN 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"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN 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"), set(), set(( "[VCALENDAR] Missing or too many required property: PRODID", )), True, ), ( "Fixable only", """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 DURATION:P1D 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"), """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 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(( "[VEVENT] Properties must not both be present: DTEND, DURATION", )), set(), False, ), ( "Fixable and unfixable", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DURATION:P1D 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"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DURATION:P1D 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"), set(( "[VEVENT] Properties must not both be present: DTEND, DURATION", )), set(( "[VCALENDAR] Missing or too many required property: PRODID", )), True, ), ) for title, test_old, test_new, test_fixed, test_unfixed, test_raises in data: cal = PyCalendar.parseText(test_old) if test_raises: self.assertRaises(PyCalendarValidationError, cal.validate, doFix=False, doRaise=True) else: try: fixed, unfixed = cal.validate(doFix=True, doRaise=False) except: self.fail(msg="Failed test: %s" % (title,)) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_vevent(self): data = ( ( "No problem", """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"), """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"), set(), set(), ), ( "Missing required", """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 RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VEVENT] Missing or too many required property: DTSTAMP", )), ), ( "Too many", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 UID:C3184A66-1ED0-11D9-A5E0-000A958A3253 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day SUMMARY:New Year's Eve END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 UID:C3184A66-1ED0-11D9-A5E0-000A958A3253 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day SUMMARY:New Year's Eve END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VEVENT] Missing or too many required property: UID", "[VEVENT] Too many properties present: SUMMARY", )), ), ( "PROP value", """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:20020101T000000 RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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:20020101T000000 RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VEVENT] Property value incorrect: DTSTAMP", )), ), ( "No DTSTART without METHOD", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN METHOD:CANCEL PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTAMP:20020101T000000Z END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN METHOD:CANCEL PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTAMP:20020101T000000Z END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ( "Combo fix", """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 DURATION:P1D 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"), """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 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(( "[VEVENT] Properties must not both be present: DTEND, DURATION", )), set(), ), ( "Mismatch UNTIL as DATE-TIME", """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 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231T120000;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(( "[VEVENT] Value types must match: DTSTART, UNTIL", )), set(), ), ( "Mismatch UNTIL as DATE", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART:20020101T121212Z DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART:20020101T121212Z DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231T121212Z;BYMONTH=1 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(( "[VEVENT] Value types must match: DTSTART, UNTIL", )), set(), ), ) for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_vfreebusy(self): data = ( ( "No problem", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VFREEBUSY UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z END:VFREEBUSY END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VFREEBUSY UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z END:VFREEBUSY END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ( "Missing required", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VFREEBUSY UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 END:VFREEBUSY END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VFREEBUSY UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 END:VFREEBUSY END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VFREEBUSY] Missing or too many required property: DTSTAMP", )), ), ) for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_vjournal(self): data = ( ( "No problem", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VJOURNAL UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTSTAMP:20020101T000000Z END:VJOURNAL END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VJOURNAL UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTSTAMP:20020101T000000Z END:VJOURNAL END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ( "Missing required", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VJOURNAL UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 END:VJOURNAL END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VJOURNAL UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 END:VJOURNAL END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VJOURNAL] Missing or too many required property: DTSTAMP", )), ), ) for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_vtimezone(self): data = ( ( "No problem", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTIMEZONE TZID:America/New_York LAST-MODIFIED:20050809T050000Z 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 END:VTIMEZONE END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTIMEZONE TZID:America/New_York LAST-MODIFIED:20050809T050000Z 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 END:VTIMEZONE END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ( "Missing required", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTIMEZONE LAST-MODIFIED:20050809T050000Z 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 END:VTIMEZONE END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTIMEZONE LAST-MODIFIED:20050809T050000Z 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 END:VTIMEZONE END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VTIMEZONE] Missing or too many required property: TZID", )), ), ( "Missing components", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTIMEZONE TZID:America/New_York LAST-MODIFIED:20050809T050000Z END:VTIMEZONE END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTIMEZONE TZID:America/New_York LAST-MODIFIED:20050809T050000Z END:VTIMEZONE END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VTIMEZONE] At least one component must be present: STANDARD or DAYLIGHT", )), ), ) for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_vtodo(self): data = ( ( "No problem", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ( "Missing required", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VTODO] Missing or too many required property: DTSTAMP", )), ), ( "Too many", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 UID:C3184A66-1ED0-11D9-A5E0-000A958A3253 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day SUMMARY:New Year's Eve END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 UID:C3184A66-1ED0-11D9-A5E0-000A958A3253 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day SUMMARY:New Year's Eve END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VTODO] Missing or too many required property: UID", "[VTODO] Too many properties present: SUMMARY", )), ), ( "PROP value", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000 RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000 RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VTODO] Property value incorrect: DTSTAMP", )), ), ( "DURATION without DTSTART", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VTODO] Property must be present: DTSTART with DURATION", )), ), ( "Combo fix", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DURATION:P1D DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), set(( "[VTODO] Properties must not both be present: DUE, DURATION", )), set(), ), ) for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_vavailability(self): data = ( ( "No problem", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ( "Missing required", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VAVAILABILITY] Missing or too many required property: DTSTAMP", )), ), ( "Too many", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com UID:20111005T133225Z-00001@example.com DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com UID:20111005T133225Z-00001@example.com DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VAVAILABILITY] Missing or too many required property: UID", "[VAVAILABILITY] Too many properties present: ORGANIZER", )), ), ( "Combo fix", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com DTSTART;TZID=America/Montreal:20111002T090000 DURATION:P1D DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com DTSTART;TZID=America/Montreal:20111002T090000 DURATION:P1D DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), set(( "[VAVAILABILITY] Properties must not both be present: DTEND, DURATION", )), set(), ), ) for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_available(self): data = ( ( "No problem", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ( "Missing required", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[AVAILABLE] Missing or too many required property: DTSTAMP", )), ), ( "Too many", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 SUMMARY:Monday to Friday from 9 am to 5 pm END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 SUMMARY:Monday to Friday from 9 am to 5 pm END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[AVAILABLE] Missing or too many required property: UID", "[AVAILABLE] Too many properties present: SUMMARY", )), ), ( "Combo fix", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DURATION:P1D DTEND;TZID=America/Montreal:20111002T170000 DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VAVAILABILITY UID:20111005T133225Z-00001@example.com DTSTAMP:20111005T133225Z ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20111005T133225Z-00001-A@example.com DTSTART;TZID=America/Montreal:20111002T090000 DURATION:P1D DTSTAMP:20111005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR SUMMARY:Monday to Friday from 9:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), set(( "[AVAILABLE] Properties must not both be present: DTEND, DURATION", )), set(), ), ) for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_valarm(self): data = ( ( "No problem", """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 BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Reminder TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Reminder TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ( "Missing required", """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 BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Reminder END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Reminder END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VALARM] Missing or too many required property: TRIGGER", )), ), ( "Missing required with fix", """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 BEGIN:VALARM ACTION:DISPLAY END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VALARM ACTION:DISPLAY DESCRIPTION: END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(( "[VALARM] Missing required property: DESCRIPTION", )), set(( "[VALARM] Missing or too many required property: TRIGGER", )), ), ( "Too many", """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 BEGIN:VALARM ACTION:AUDIO ATTACH:http://example.com/audio/boink ATTACH:http://example.com/audio/quack TRIGGER:-PT15M TRIGGER:-PT30M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VALARM ACTION:AUDIO ATTACH:http://example.com/audio/boink ATTACH:http://example.com/audio/quack TRIGGER:-PT15M TRIGGER:-PT30M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VALARM] Missing or too many required property: TRIGGER", "[VALARM] Too many properties present: ATTACH", )), ), ( "Too few", """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 BEGIN:VALARM ACTION:EMAIL DESCRIPTION:Reminder SUMMARY:Reminder TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VALARM ACTION:EMAIL DESCRIPTION:Reminder SUMMARY:Reminder TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VALARM] Missing required property: ATTENDEE", )), ), ( "PROP value", """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 BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Reminder DURATION:-P1D REPEAT:-1 TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Reminder DURATION:-P1D REPEAT:-1 TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VALARM] Property value incorrect: REPEAT", )), ), ( "DUARTION and REPEAT together", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN METHOD:CANCEL 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 BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Reminder REPEAT:2 TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN METHOD:CANCEL 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 BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Reminder REPEAT:2 TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VALARM] Properties must be present together: DURATION, REPEAT", )), ), ( "Combo fix", """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 DURATION:P1D DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Reminder END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 SUMMARY:New Year's Day BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Reminder END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(( "[VEVENT] Properties must not both be present: DTEND, DURATION", )), set(( "[VALARM] Missing or too many required property: TRIGGER", )), ), ( "ACKNOWLEDGED OK", """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 BEGIN:VALARM ACKNOWLEDGED:20020101T000000Z ACTION:DISPLAY DESCRIPTION:Reminder TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VALARM ACKNOWLEDGED:20020101T000000Z ACTION:DISPLAY DESCRIPTION:Reminder TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ( "ACKNOWLEDGED Bad value", """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 BEGIN:VALARM ACKNOWLEDGED:20020101T000000 ACTION:DISPLAY DESCRIPTION:Reminder TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VALARM ACKNOWLEDGED:20020101T000000 ACTION:DISPLAY DESCRIPTION:Reminder TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VALARM] Property value incorrect: ACKNOWLEDGED", )), ), ( "ACKNOWLEDGED too many", """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 BEGIN:VALARM ACKNOWLEDGED:20020101T000000Z ACKNOWLEDGED:20020102T000000Z ACTION:DISPLAY DESCRIPTION:Reminder TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VALARM ACKNOWLEDGED:20020101T000000Z ACKNOWLEDGED:20020102T000000Z ACTION:DISPLAY DESCRIPTION:Reminder TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VALARM] Too many properties present: ACKNOWLEDGED", )), ), ( "No problem ACTION=URI", """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 BEGIN:VALARM ACTION:URI TRIGGER:-PT15M URL:http://test.example.com END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VALARM ACTION:URI TRIGGER:-PT15M URL:http://test.example.com END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ( "ACTION=URI missing URL", """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 BEGIN:VALARM ACTION:URI TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VALARM ACTION:URI TRIGGER:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[VALARM] Missing or too many required property: URL", )), ), ( "No problem ACTION=NONE", """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 BEGIN:VALARM ACTION:NONE END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VALARM ACTION:NONE END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ( "No problem ACTION=NONE with TRIGGER", """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 BEGIN:VALARM ACTION:NONE TRIGGER;VALUE=DATE-TIME:19760401T005545Z END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VALARM ACTION:NONE TRIGGER;VALUE=DATE-TIME:19760401T005545Z END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ) for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_xcomponents(self): data = ( ( "No problem #1", """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 BEGIN:X-COMPONENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 SUMMARY:New Year's Day END:X-COMPONENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:X-COMPONENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 SUMMARY:New Year's Day END:X-COMPONENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ( "No problem #2", """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 BEGIN:X-COMPONENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTEND;VALUE=DATE:20020102 DTSTART;VALUE=DATE:20020101 DURATION:P1D SUMMARY:New Year's Day END:X-COMPONENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:X-COMPONENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTEND;VALUE=DATE:20020102 DTSTART;VALUE=DATE:20020101 DURATION:P1D SUMMARY:New Year's Day END:X-COMPONENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(), ), ( "Prop value", """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 BEGIN:X-COMPONENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000 DTSTART;VALUE=DATE:20020101 SUMMARY:New Year's Day END:X-COMPONENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:X-COMPONENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T000000 DTSTART;VALUE=DATE:20020101 SUMMARY:New Year's Day END:X-COMPONENT END:VCALENDAR """.replace("\n", "\r\n"), set(), set(( "[X-COMPONENT] Property value incorrect: DTSTAMP", )), ), ) for title, test_old, test_new, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=True) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) def test_STATUS_fix(self): """ Test calendarserver issue where multiple STATUS properties can be present in components. Want to make sure we report those as an error or fix them. """ data = ( ( "1.1 Two STATUS in VEVENT - fix", """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 STATUS:CONFIRMED STATUS:CANCELLED END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 STATUS:CANCELLED SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), True, set(( "[VEVENT] Too many properties: STATUS", )), set(), ), ( "1.2 Two STATUS in VEVENT - no fix", """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 STATUS:CONFIRMED STATUS:CANCELLED END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 STATUS:CONFIRMED STATUS:CANCELLED SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), False, set(), set(( "[VEVENT] Too many properties: STATUS", )), ), ( "1.3 Two STATUS in VEVENT, none CANCELLED - fix", """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 STATUS:CONFIRMED STATUS:TENTATIVE END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 STATUS:CONFIRMED STATUS:TENTATIVE SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), True, set(), set(( "[VEVENT] Too many properties: STATUS", )), ), ( "1.4 Two STATUS in VEVENT, none CANCELLED - no fix", """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 STATUS:CONFIRMED STATUS:TENTATIVE END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 STATUS:CONFIRMED STATUS:TENTATIVE SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), False, set(), set(( "[VEVENT] Too many properties: STATUS", )), ), ( "2.1 Two STATUS in VTODO - fix", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 STATUS:NEEDS-ACTION STATUS:CANCELLED SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 STATUS:CANCELLED SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), True, set(( "[VTODO] Too many properties: STATUS", )), set(), ), ( "2.2 Two STATUS in VTODO - no fix", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 STATUS:NEEDS-ACTION STATUS:CANCELLED SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 STATUS:NEEDS-ACTION STATUS:CANCELLED SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), False, set(), set(( "[VTODO] Too many properties: STATUS", )), ), ( "2.3 Two STATUS in VTODO, none CANCELLED - fix", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 STATUS:NEEDS-ACTION STATUS:COMPLETED SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 STATUS:NEEDS-ACTION STATUS:COMPLETED SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), True, set(), set(( "[VTODO] Too many properties: STATUS", )), ), ( "2.4 Two STATUS in VTODO, none CANCELLED - no fix", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 STATUS:NEEDS-ACTION STATUS:COMPLETED SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VTODO UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DUE;VALUE=DATE:20020102 DTSTAMP:20020101T000000Z RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1 STATUS:NEEDS-ACTION STATUS:COMPLETED SUMMARY:New Year's Day END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), False, set(), set(( "[VTODO] Too many properties: STATUS", )), ), ( "3.1 Two STATUS in VJOURNAL - fix", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VJOURNAL UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTSTAMP:20020101T000000Z STATUS:DRAFT STATUS:CANCELLED END:VJOURNAL END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VJOURNAL UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTSTAMP:20020101T000000Z STATUS:CANCELLED END:VJOURNAL END:VCALENDAR """.replace("\n", "\r\n"), True, set(( "[VJOURNAL] Too many properties: STATUS", )), set(), ), ( "3.2 Two STATUS in VJOURNAL - no fix", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VJOURNAL UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTSTAMP:20020101T000000Z STATUS:DRAFT STATUS:CANCELLED END:VJOURNAL END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VJOURNAL UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTSTAMP:20020101T000000Z STATUS:DRAFT STATUS:CANCELLED END:VJOURNAL END:VCALENDAR """.replace("\n", "\r\n"), False, set(), set(( "[VJOURNAL] Too many properties: STATUS", )), ), ( "3.3 Two STATUS in VJOURNAL, none CANCELLED - fix", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VJOURNAL UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTSTAMP:20020101T000000Z STATUS:DRAFT STATUS:FINAL END:VJOURNAL END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VJOURNAL UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTSTAMP:20020101T000000Z STATUS:DRAFT STATUS:FINAL END:VJOURNAL END:VCALENDAR """.replace("\n", "\r\n"), True, set(), set(( "[VJOURNAL] Too many properties: STATUS", )), ), ( "3.4 Two STATUS in VJOURNAL, none CANCELLED - no fix", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VJOURNAL UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTSTAMP:20020101T000000Z STATUS:DRAFT STATUS:FINAL END:VJOURNAL END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VJOURNAL UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART;VALUE=DATE:20020101 DTSTAMP:20020101T000000Z STATUS:DRAFT STATUS:FINAL END:VJOURNAL END:VCALENDAR """.replace("\n", "\r\n"), False, set(), set(( "[VJOURNAL] Too many properties: STATUS", )), ), ) for title, test_old, test_new, test_doFix, test_fixed, test_unfixed in data: cal = PyCalendar.parseText(test_old) fixed, unfixed = cal.validate(doFix=test_doFix) self.assertEqual(str(cal), test_new, msg="Failed test: %s" % (title,)) self.assertEqual(set(fixed), test_fixed, msg="Failed test: %s" % (title,)) self.assertEqual(set(unfixed), test_unfixed, msg="Failed test: %s" % (title,)) pycalendar-2.0~svn13177/src/pycalendar/icalendar/tests/__init__.py0000644000175000017500000000120112101017573024132 0ustar rahulrahul# # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## pycalendar-2.0~svn13177/src/pycalendar/icalendar/__init__.py0000644000175000017500000000120112101017573022770 0ustar rahulrahul# # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## pycalendar-2.0~svn13177/src/pycalendar/plaintextvalue.py0000644000175000017500000000256712101017573022374 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # iCalendar UTC Offset value from pycalendar.value import PyCalendarValue class PyCalendarPlainTextValue(PyCalendarValue): def __init__(self, value=''): self.mValue = value def duplicate(self): return self.__class__(self.mValue) def parse(self, data): # No decoding required self.mValue = data # os - StringIO object def generate(self, os): try: # No encoding required os.write(self.mValue) except: pass def writeXML(self, node, namespace): value = self.getXMLNode(node, namespace) value.text = self.mValue def getValue(self): return self.mValue def setValue(self, value): self.mValue = value pycalendar-2.0~svn13177/src/pycalendar/recurrence.py0000644000175000017500000016351212320545600021462 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar import xmldefs from pycalendar.datetime import PyCalendarDateTime from pycalendar.period import PyCalendarPeriod from pycalendar.valueutils import ValueMixin import cStringIO as StringIO import xml.etree.cElementTree as XML def WeekDayNumCompare_compare(w1, w2): if w1[0] < w2[0]: return -1 elif w1[0] > w2[0]: return 1 elif w1[1] < w2[1]: return -1 elif w1[1] > w2[1]: return 1 else: return 0 def WeekDayNumSort_less_than(w1, w2): return (w1[0] < w2[0]) or (w1[0] == w2[0]) and (w1[1] < w2[1]) class PyCalendarRecurrence(ValueMixin): cFreqMap = { definitions.cICalValue_RECUR_SECONDLY : definitions.eRecurrence_SECONDLY, definitions.cICalValue_RECUR_MINUTELY : definitions.eRecurrence_MINUTELY, definitions.cICalValue_RECUR_HOURLY : definitions.eRecurrence_HOURLY, definitions.cICalValue_RECUR_DAILY : definitions.eRecurrence_DAILY, definitions.cICalValue_RECUR_WEEKLY : definitions.eRecurrence_WEEKLY, definitions.cICalValue_RECUR_MONTHLY : definitions.eRecurrence_MONTHLY, definitions.cICalValue_RECUR_YEARLY : definitions.eRecurrence_YEARLY, } cFreqToXMLMap = { definitions.eRecurrence_SECONDLY: xmldefs.recur_freq_secondly, definitions.eRecurrence_MINUTELY: xmldefs.recur_freq_minutely, definitions.eRecurrence_HOURLY: xmldefs.recur_freq_hourly, definitions.eRecurrence_DAILY: xmldefs.recur_freq_daily, definitions.eRecurrence_WEEKLY: xmldefs.recur_freq_weekly, definitions.eRecurrence_MONTHLY: xmldefs.recur_freq_monthly, definitions.eRecurrence_YEARLY: xmldefs.recur_freq_yearly, } cRecurMap = { definitions.cICalValue_RECUR_FREQ : definitions.eRecurrence_FREQ, definitions.cICalValue_RECUR_UNTIL : definitions.eRecurrence_UNTIL, definitions.cICalValue_RECUR_COUNT : definitions.eRecurrence_COUNT, definitions.cICalValue_RECUR_INTERVAL : definitions.eRecurrence_INTERVAL, definitions.cICalValue_RECUR_BYSECOND : definitions.eRecurrence_BYSECOND, definitions.cICalValue_RECUR_BYMINUTE : definitions.eRecurrence_BYMINUTE, definitions.cICalValue_RECUR_BYHOUR : definitions.eRecurrence_BYHOUR, definitions.cICalValue_RECUR_BYDAY : definitions.eRecurrence_BYDAY, definitions.cICalValue_RECUR_BYMONTHDAY : definitions.eRecurrence_BYMONTHDAY, definitions.cICalValue_RECUR_BYYEARDAY : definitions.eRecurrence_BYYEARDAY, definitions.cICalValue_RECUR_BYWEEKNO : definitions.eRecurrence_BYWEEKNO, definitions.cICalValue_RECUR_BYMONTH : definitions.eRecurrence_BYMONTH, definitions.cICalValue_RECUR_BYSETPOS : definitions.eRecurrence_BYSETPOS, definitions.cICalValue_RECUR_WKST : definitions.eRecurrence_WKST, } cWeekdayMap = { definitions.cICalValue_RECUR_WEEKDAY_SU : definitions.eRecurrence_WEEKDAY_SU, definitions.cICalValue_RECUR_WEEKDAY_MO : definitions.eRecurrence_WEEKDAY_MO, definitions.cICalValue_RECUR_WEEKDAY_TU : definitions.eRecurrence_WEEKDAY_TU, definitions.cICalValue_RECUR_WEEKDAY_WE : definitions.eRecurrence_WEEKDAY_WE, definitions.cICalValue_RECUR_WEEKDAY_TH : definitions.eRecurrence_WEEKDAY_TH, definitions.cICalValue_RECUR_WEEKDAY_FR : definitions.eRecurrence_WEEKDAY_FR, definitions.cICalValue_RECUR_WEEKDAY_SA : definitions.eRecurrence_WEEKDAY_SA, } cWeekdayRecurMap = dict([(v, k) for k, v in cWeekdayMap.items()]) cUnknownIndex = -1 def __init__(self): self.init_PyCalendarRecurrence() def duplicate(self): other = PyCalendarRecurrence() other.mFreq = self.mFreq other.mUseCount = self.mUseCount other.mCount = self.mCount other.mUseUntil = self.mUseUntil if other.mUseUntil: other.mUntil = self.mUntil.duplicate() other.mInterval = self.mInterval if self.mBySeconds is not None: other.mBySeconds = self.mBySeconds[:] if self.mByMinutes is not None: other.mByMinutes = self.mByMinutes[:] if self.mByHours is not None: other.mByHours = self.mByHours[:] if self.mByDay is not None: other.mByDay = self.mByDay[:] if self.mByMonthDay is not None: other.mByMonthDay = self.mByMonthDay[:] if self.mByYearDay is not None: other.mByYearDay = self.mByYearDay[:] if self.mByWeekNo is not None: other.mByWeekNo = self.mByWeekNo[:] if self.mByMonth is not None: other.mByMonth = self.mByMonth[:] if self.mBySetPos is not None: other.mBySetPos = self.mBySetPos[:] other.mWeekstart = self.mWeekstart other.mCached = self.mCached if self.mCacheStart: other.mCacheStart = self.mCacheStart.duplicate() if self.mCacheUpto: other.mCacheUpto = self.mCacheUpto.duplicate() other.mFullyCached = self.mFullyCached if self.mRecurrences: other.mRecurrences = self.mRecurrences[:] return other def init_PyCalendarRecurrence(self): self.mFreq = definitions.eRecurrence_YEARLY self.mUseCount = False self.mCount = 0 self.mUseUntil = False self.mUntil = None self.mInterval = 1 self.mBySeconds = None self.mByMinutes = None self.mByHours = None self.mByDay = None self.mByMonthDay = None self.mByYearDay = None self.mByWeekNo = None self.mByMonth = None self.mBySetPos = None self.mWeekstart = definitions.eRecurrence_WEEKDAY_MO self.mCached = False self.mCacheStart = None self.mCacheUpto = None self.mFullyCached = False self.mRecurrences = None def __hash__(self): return hash(( self.mFreq, self.mUseCount, self.mCount, self.mUseUntil, self.mUntil, self.mInterval, tuple(self.mBySeconds) if self.mBySeconds else None, tuple(self.mByMinutes) if self.mByMinutes else None, tuple(self.mByHours) if self.mByHours else None, tuple(self.mByDay) if self.mByDay else None, tuple(self.mByMonthDay) if self.mByMonthDay else None, tuple(self.mByYearDay) if self.mByYearDay else None, tuple(self.mByWeekNo) if self.mByWeekNo else None, tuple(self.mByMonth) if self.mByMonth else None, tuple(self.mBySetPos) if self.mBySetPos else None, self.mWeekstart, )) def __ne__(self, other): return not self.__eq__(other) def __eq__(self, other): if not isinstance(other, PyCalendarRecurrence): return False return self.equals(other) def equals(self, comp): return (self.mFreq == comp.mFreq) \ and (self.mUseCount == comp.mUseCount) and (self.mCount == comp.mCount) \ and (self.mUseUntil == comp.mUseUntil) and (self.mUntil == comp.mUntil) \ and (self.mInterval == comp.mInterval) \ and self.equalsNum(self.mBySeconds, comp.mBySeconds) \ and self.equalsNum(self.mByMinutes, comp.mByMinutes) \ and self.equalsNum(self.mByHours, comp.mByHours) \ and self.equalsDayNum(self.mByDay, comp.mByDay) \ and self.equalsNum(self.mByMonthDay, comp.mByMonthDay) \ and self.equalsNum(self.mByYearDay, comp.mByYearDay) \ and self.equalsNum(self.mByWeekNo, comp.mByWeekNo) \ and self.equalsNum(self.mByMonth, comp.mByMonth) \ and self.equalsNum(self.mBySetPos, comp.mBySetPos) \ and (self.mWeekstart == comp.mWeekstart) def equalsNum(self, items1, items2): # Check sizes first if items1 is None: items1 = [] if items2 is None: items2 = [] if len(items1) != len(items2): return False elif len(items1) == 0: return True # Copy and sort each one for comparison temp1 = items1[:] temp2 = items2[:] temp1.sort() temp2.sort() for i in range(0, len(temp1)): if temp1[i] != temp2[i]: return False return True def equalsDayNum(self, items1, items2): # Check sizes first if items1 is None: items1 = [] if items2 is None: items2 = [] if len(items1) != len(items2): return False elif len(items1) == 0: return True # Copy and sort each one for comparison temp1 = items1[:] temp2 = items2[:] temp1.sort() temp2.sort() for i in range(0, len(temp1)): if temp1[i] != temp2[i]: return False return True def _setAndclearIfChanged(self, attr, value): if getattr(self, attr) != value: self.clear() setattr(self, attr, value) def getFreq(self): return self.mFreq def setFreq(self, freq): self._setAndclearIfChanged("mFreq", freq) def getUseUntil(self): return self.mUseUntil def setUseUntil(self, use_until): self._setAndclearIfChanged("mUseUntil", use_until) def getUntil(self): return self.mUntil def setUntil(self, until): self._setAndclearIfChanged("mUntil", until) def getUseCount(self): return self.mUseCount def setUseCount(self, use_count): self._setAndclearIfChanged("mUseCount", use_count) def getCount(self): return self.mCount def setCount(self, count): self._setAndclearIfChanged("mCount", count) def getInterval(self): return self.mInterval def setInterval(self, interval): self._setAndclearIfChanged("mInterval", interval) def getByMonth(self): return self.mByMonth def setByMonth(self, by): self._setAndclearIfChanged("mByMonth", by[:]) def getByMonthDay(self): return self.mByMonthDay def setByMonthDay(self, by): self._setAndclearIfChanged("mByMonthDay", by[:]) def getByYearDay(self): return self.mByYearDay def setByYearDay(self, by): self._setAndclearIfChanged("mByYearDay", by[:]) def getByDay(self): return self.mByDay def setByDay(self, by): self._setAndclearIfChanged("mByDay", by[:]) def getBySetPos(self): return self.mBySetPos def setBySetPos(self, by): self._setAndclearIfChanged("mBySetPos", by[:]) def parse(self, data): self.init_PyCalendarRecurrence() # Tokenise using '' tokens = data.split(";") tokens.reverse() if len(tokens) == 0: raise ValueError("PyCalendarRecurrence: Invalid recurrence rule value") while len(tokens) != 0: # Get next token token = tokens.pop() try: tname, tvalue = token.split("=") except ValueError: raise ValueError("PyCalendarRecurrence: Invalid token '%s'" % (token,)) # Determine token type index = PyCalendarRecurrence.cRecurMap.get(tname, PyCalendarRecurrence.cUnknownIndex) if index == PyCalendarRecurrence.cUnknownIndex: raise ValueError("PyCalendarRecurrence: Invalid token '%s'" % (tname,)) # Parse remainder based on index if index == definitions.eRecurrence_FREQ: # Get the FREQ value index = PyCalendarRecurrence.cFreqMap.get(tvalue, PyCalendarRecurrence.cUnknownIndex) if index == PyCalendarRecurrence.cUnknownIndex: raise ValueError("PyCalendarRecurrence: Invalid FREQ value") self.mFreq = index elif index == definitions.eRecurrence_UNTIL: if self.mUseCount: raise ValueError("PyCalendarRecurrence: Can't have both UNTIL and COUNT") self.mUseUntil = True if self.mUntil is None: self.mUntil = PyCalendarDateTime() try: self.mUntil.parse(tvalue) except ValueError: raise ValueError("PyCalendarRecurrence: Invalid UNTIL value") elif index == definitions.eRecurrence_COUNT: if self.mUseUntil: raise ValueError("PyCalendarRecurrence: Can't have both UNTIL and COUNT") self.mUseCount = True try: self.mCount = int(tvalue) except ValueError: raise ValueError("PyCalendarRecurrence: Invalid COUNT value") # Must not be less than one if self.mCount < 1: raise ValueError("PyCalendarRecurrence: Invalid COUNT value") elif index == definitions.eRecurrence_INTERVAL: try: self.mInterval = int(tvalue) except ValueError: raise ValueError("PyCalendarRecurrence: Invalid INTERVAL value") # Must NOT be less than one if self.mInterval < 1: raise ValueError("PyCalendarRecurrence: Invalid INTERVAL value") elif index == definitions.eRecurrence_BYSECOND: if self.mBySeconds is not None: raise ValueError("PyCalendarRecurrence: Only one BYSECOND allowed") self.mBySeconds = [] self.parseList(tvalue, self.mBySeconds, 0, 60, errmsg="PyCalendarRecurrence: Invalid BYSECOND value") elif index == definitions.eRecurrence_BYMINUTE: if self.mByMinutes is not None: raise ValueError("PyCalendarRecurrence: Only one BYMINUTE allowed") self.mByMinutes = [] self.parseList(tvalue, self.mByMinutes, 0, 59, errmsg="PyCalendarRecurrence: Invalid BYMINUTE value") elif index == definitions.eRecurrence_BYHOUR: if self.mByHours is not None: raise ValueError("PyCalendarRecurrence: Only one BYHOUR allowed") self.mByHours = [] self.parseList(tvalue, self.mByHours, 0, 23, errmsg="PyCalendarRecurrence: Invalid BYHOUR value") elif index == definitions.eRecurrence_BYDAY: if self.mByDay is not None: raise ValueError("PyCalendarRecurrence: Only one BYDAY allowed") self.mByDay = [] self.parseListDW(tvalue, self.mByDay, errmsg="PyCalendarRecurrence: Invalid BYDAY value") elif index == definitions.eRecurrence_BYMONTHDAY: if self.mByMonthDay is not None: raise ValueError("PyCalendarRecurrence: Only one BYMONTHDAY allowed") self.mByMonthDay = [] self.parseList(tvalue, self.mByMonthDay, 1, 31, True, errmsg="PyCalendarRecurrence: Invalid BYMONTHDAY value") elif index == definitions.eRecurrence_BYYEARDAY: if self.mByYearDay is not None: raise ValueError("PyCalendarRecurrence: Only one BYYEARDAY allowed") self.mByYearDay = [] self.parseList(tvalue, self.mByYearDay, 1, 366, True, errmsg="PyCalendarRecurrence: Invalid BYYEARDAY value") elif index == definitions.eRecurrence_BYWEEKNO: if self.mByWeekNo is not None: raise ValueError("PyCalendarRecurrence: Only one BYWEEKNO allowed") self.mByWeekNo = [] self.parseList(tvalue, self.mByWeekNo, 1, 53, True, errmsg="PyCalendarRecurrence: Invalid BYWEEKNO value") elif index == definitions.eRecurrence_BYMONTH: if self.mByMonth is not None: raise ValueError("PyCalendarRecurrence: Only one BYMONTH allowed") self.mByMonth = [] self.parseList(tvalue, self.mByMonth, 1, 12, errmsg="PyCalendarRecurrence: Invalid BYMONTH value") elif index == definitions.eRecurrence_BYSETPOS: if self.mBySetPos is not None: raise ValueError("PyCalendarRecurrence: Only one BYSETPOS allowed") self.mBySetPos = [] self.parseList(tvalue, self.mBySetPos, allowNegative=True, errmsg="PyCalendarRecurrence: Invalid BYSETPOS value") elif index == definitions.eRecurrence_WKST: index = PyCalendarRecurrence.cWeekdayMap.get(tvalue, PyCalendarRecurrence.cUnknownIndex) if (index == PyCalendarRecurrence.cUnknownIndex): raise ValueError("PyCalendarRecurrence: Invalid WKST value") self.mWeekstart = index def parseList(self, txt, list, min=None, max=None, allowNegative=False, errmsg=""): if "," in txt: tokens = txt.split(",") else: tokens = (txt,) for token in tokens: value = int(token) if not allowNegative and value < 0: raise ValueError(errmsg) avalue = abs(value) if min is not None and avalue < min: raise ValueError(errmsg) if max is not None and avalue > max: raise ValueError(errmsg) list.append(value) def parseListDW(self, txt, list, errmsg=""): if "," in txt: tokens = txt.split(",") else: tokens = (txt,) for token in tokens: # Get number if present num = 0 if (len(token) > 0) and token[0] in "+-1234567890": offset = 0 while (offset < len(token)) and token[offset] in "+-1234567890": offset += 1 num = int(token[0:offset]) token = token[offset:] anum = abs(num) if anum < 1: raise ValueError(errmsg) if anum > 53: raise ValueError(errmsg) # Get day index = PyCalendarRecurrence.cWeekdayMap.get(token, PyCalendarRecurrence.cUnknownIndex) if (index == PyCalendarRecurrence.cUnknownIndex): raise ValueError(errmsg) wday = index list.append((num, wday)) def generate(self, os): try: os.write(definitions.cICalValue_RECUR_FREQ) os.write("=") if self.mFreq == definitions.eRecurrence_SECONDLY: os.write(definitions.cICalValue_RECUR_SECONDLY) elif self.mFreq == definitions.eRecurrence_MINUTELY: os.write(definitions.cICalValue_RECUR_MINUTELY) elif self.mFreq == definitions.eRecurrence_HOURLY: os.write(definitions.cICalValue_RECUR_HOURLY) elif self.mFreq == definitions.eRecurrence_DAILY: os.write(definitions.cICalValue_RECUR_DAILY) elif self.mFreq == definitions.eRecurrence_WEEKLY: os.write(definitions.cICalValue_RECUR_WEEKLY) elif self.mFreq == definitions.eRecurrence_MONTHLY: os.write(definitions.cICalValue_RECUR_MONTHLY) elif self.mFreq == definitions.eRecurrence_YEARLY: os.write(definitions.cICalValue_RECUR_YEARLY) if self.mUseCount: os.write(";") os.write(definitions.cICalValue_RECUR_COUNT) os.write("=") os.write(str(self.mCount)) elif self.mUseUntil: os.write(";") os.write(definitions.cICalValue_RECUR_UNTIL) os.write("=") self.mUntil.generate(os) if self.mInterval > 1: os.write(";") os.write(definitions.cICalValue_RECUR_INTERVAL) os.write("=") os.write(str(self.mInterval)) self.generateList(os, definitions.cICalValue_RECUR_BYSECOND, self.mBySeconds) self.generateList(os, definitions.cICalValue_RECUR_BYMINUTE, self.mByMinutes) self.generateList(os, definitions.cICalValue_RECUR_BYHOUR, self.mByHours) if (self.mByDay is not None) and (len(self.mByDay) != 0): os.write(";") os.write(definitions.cICalValue_RECUR_BYDAY) os.write("=") comma = False for iter in self.mByDay: if comma: os.write(",") comma = True if iter[0] != 0: os.write(str(iter[0])) if iter[1] == definitions.eRecurrence_WEEKDAY_SU: os.write(definitions.cICalValue_RECUR_WEEKDAY_SU) elif iter[1] == definitions.eRecurrence_WEEKDAY_MO: os.write(definitions.cICalValue_RECUR_WEEKDAY_MO) elif iter[1] == definitions.eRecurrence_WEEKDAY_TU: os.write(definitions.cICalValue_RECUR_WEEKDAY_TU) elif iter[1] == definitions.eRecurrence_WEEKDAY_WE: os.write(definitions.cICalValue_RECUR_WEEKDAY_WE) elif iter[1] == definitions.eRecurrence_WEEKDAY_TH: os.write(definitions.cICalValue_RECUR_WEEKDAY_TH) elif iter[1] == definitions.eRecurrence_WEEKDAY_FR: os.write(definitions.cICalValue_RECUR_WEEKDAY_FR) elif iter[1] == definitions.eRecurrence_WEEKDAY_SA: os.write(definitions.cICalValue_RECUR_WEEKDAY_SA) self.generateList(os, definitions.cICalValue_RECUR_BYMONTHDAY, self.mByMonthDay) self.generateList(os, definitions.cICalValue_RECUR_BYYEARDAY, self.mByYearDay) self.generateList(os, definitions.cICalValue_RECUR_BYWEEKNO, self.mByWeekNo) self.generateList(os, definitions.cICalValue_RECUR_BYMONTH, self.mByMonth) self.generateList(os, definitions.cICalValue_RECUR_BYSETPOS, self.mBySetPos) # MO is the default so we do not need it if self.mWeekstart != definitions.eRecurrence_WEEKDAY_MO: os.write(";") os.write(definitions.cICalValue_RECUR_WKST) os.write("=") if self.mWeekstart == definitions.eRecurrence_WEEKDAY_SU: os.write(definitions.cICalValue_RECUR_WEEKDAY_SU) elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_MO: os.write(definitions.cICalValue_RECUR_WEEKDAY_MO) elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_TU: os.write(definitions.cICalValue_RECUR_WEEKDAY_TU) elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_WE: os.write(definitions.cICalValue_RECUR_WEEKDAY_WE) elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_TH: os.write(definitions.cICalValue_RECUR_WEEKDAY_TH) elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_FR: os.write(definitions.cICalValue_RECUR_WEEKDAY_FR) elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_SA: os.write(definitions.cICalValue_RECUR_WEEKDAY_SA) except: pass def generateList(self, os, title, items): if (items is not None) and (len(items) != 0): os.write(";") os.write(title) os.write("=") comma = False for e in items: if comma: os.write(",") comma = True os.write(str(e)) def writeXML(self, node, namespace): recur = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.value_recur)) freq = XML.SubElement(recur, xmldefs.makeTag(namespace, xmldefs.recur_freq)) freq.text = self.cFreqToXMLMap[self.mFreq] if self.mUseCount: count = XML.SubElement(recur, xmldefs.makeTag(namespace, xmldefs.recur_count)) count.text = str(self.mCount) elif self.mUseUntil: until = XML.SubElement(recur, xmldefs.makeTag(namespace, xmldefs.recur_until)) self.mUntil.writeXML(until, namespace) if self.mInterval > 1: interval = XML.SubElement(recur, xmldefs.makeTag(namespace, xmldefs.recur_interval)) interval.text = str(self.mInterval) self.writeXMLList(recur, namespace, xmldefs.recur_bysecond, self.mBySeconds) self.writeXMLList(recur, namespace, xmldefs.recur_byminute, self.mByMinutes) self.writeXMLList(recur, namespace, xmldefs.recur_byhour, self.mByHours) if self.mByDay is not None and len(self.mByDay) != 0: for iter in self.mByDay: byday = XML.SubElement(recur, xmldefs.makeTag(namespace, xmldefs.recur_byday)) data = "" if iter[0] != 0: data = str(iter[0]) data += self.cWeekdayRecurMap.get(iter[1], "") byday.text = data self.writeXMLList(recur, namespace, xmldefs.recur_bymonthday, self.mByMonthDay) self.writeXMLList(recur, namespace, xmldefs.recur_byyearday, self.mByYearDay) self.writeXMLList(recur, namespace, xmldefs.recur_byweekno, self.mByWeekNo) self.writeXMLList(recur, namespace, xmldefs.recur_bymonth, self.mByMonth) self.writeXMLList(recur, namespace, xmldefs.recur_bysetpos, self.mBySetPos) # MO is the default so we do not need it if self.mWeekstart != definitions.eRecurrence_WEEKDAY_MO: wkst = XML.SubElement(recur, xmldefs.makeTag(namespace, xmldefs.recur_wkst)) wkst.text = self.cWeekdayRecurMap.get(self.mWeekstart, definitions.cICalValue_RECUR_WEEKDAY_MO) def writeXMLList(self, node, namespace, name, items): if items is not None and len(items) != 0: for item in items: child = XML.SubElement(node, xmldefs.makeTag(namespace, name)) child.text = str(item) def hasBy(self): return (self.mBySeconds is not None) and (len(self.mBySeconds) != 0) \ or (self.mByMinutes is not None) and (len(self.mByMinutes) != 0) \ or (self.mByHours is not None) and (len(self.mByHours) != 0) \ or (self.mByDay is not None) and (len(self.mByDay) != 0) \ or (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0) \ or (self.mByYearDay is not None) and (len(self.mByYearDay) != 0) \ or (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0) \ or (self.mByMonth is not None) and (len(self.mByMonth) != 0) \ or (self.mBySetPos is not None) and (len(self.mBySetPos) != 0) def isSimpleRule(self): # One that has no BYxxx rules return not self.hasBy() def isAdvancedRule(self): # One that has BYMONTH, # BYMONTHDAY (with no negative value), # BYDAY (with multiple unnumbered, or numbered with all the same number # (1..4, -2, -1) # BYSETPOS with +1, or -1 only # no others # First checks the ones we do not handle at all if ((self.mBySeconds is not None) and (len(self.mBySeconds) != 0) \ or (self.mByMinutes is not None) and (len(self.mByMinutes) != 0) \ or (self.mByHours is not None) and (len(self.mByHours) != 0) \ or (self.mByYearDay is not None) and (len(self.mByYearDay) != 0) \ or (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0)): return False # Check BYMONTHDAY numbers (we can handle -7...-1, 1..31) if self.mByMonthDay is not None: for iter in self.mByMonthDay: if (iter < -7) or (iter > 31) or (iter == 0): return False # Check BYDAY numbers if self.mByDay is not None: number = 0 first = True for iter in self.mByDay: # Get the first number if (first): number = iter[0] first = False # Check number range if (number > 4) or (number < -2): return False # If current differs from last, then we have an error elif number != iter[0]: return False # Check BYSETPOS numbers if self.mBySetPos is not None: if len(self.mBySetPos) > 1: return False if (len(self.mBySetPos) == 1) and (self.mBySetPos[0] != -1) and (self.mBySetPos[0] != 1): return False # If we get here it must be OK return True def getUIDescription(self): try: # For now just use iCal item sout = StringIO() self.generate(sout) result = sout.getvalue() except: result = "" return result def expand(self, start, range, items, float_offset=0): # Have to normalize this to be very sure we are starting with a valid date, as otherwise # we could end up looping forever when doing recurrence. start.normalise() # Must have recurrence list at this point if self.mRecurrences is None: self.mRecurrences = [] # Wipe cache if start is different if self.mCached and (start != self.mCacheStart): self.mCached = False self.mFullyCached = False self.mRecurrences = [] # Is the current cache complete or does it extend past the requested # range end if not self.mCached or not self.mFullyCached \ and (self.mCacheUpto is None or self.mCacheUpto < range.getEnd()): cache_range = range.duplicate() # If partially cached just cache from previous cache end up to new # end if self.mCached: cache_range = PyCalendarPeriod(self.mCacheUpto, range.getEnd()) # Simple expansion is one where there is no BYXXX rule part if not self.hasBy(): self.mFullyCached = self.simpleExpand(start, cache_range, self.mRecurrences, float_offset) else: self.mFullyCached = self.complexExpand(start, cache_range, self.mRecurrences, float_offset) # Set cache values self.mCached = True self.mCacheStart = start self.mCacheUpto = range.getEnd() # Just return the cached items in the requested range limited = not self.mFullyCached for iter in self.mRecurrences: if range.isDateWithinPeriod(iter): items.append(iter) else: limited = True return limited def simpleExpand(self, start, range, items, float_offset): start_iter = start.duplicate() ctr = 0 if self.mUseUntil: float_until = self.mUntil.duplicate() if start.floating(): float_until.setTimezoneID(0) float_until.offsetSeconds(float_offset) while True: # Exit if after period we want if range.isDateAfterPeriod(start_iter): return False # Add current one to list items.append(start_iter.duplicate()) # Get next item start_iter.recur(self.mFreq, self.mInterval, allow_invalid=True) while start_iter.invalid(): start_iter.recur(self.mFreq, self.mInterval, allow_invalid=True) # Check limits if self.mUseCount: # Bump counter and exit if over ctr += 1 if ctr >= self.mCount: return True elif self.mUseUntil: # Exit if next item is after until (its OK if its the same as # UNTIL as UNTIL is inclusive) if start_iter > float_until: return True def complexExpand(self, start, range, items, float_offset): start_iter = start.duplicate() ctr = 0 if self.mUseUntil: float_until = self.mUntil.duplicate() if start.floating(): float_until.setTimezoneID(None) float_until.offsetSeconds(float_offset) # Always add the initial instance DTSTART if self.mUseCount: # Bump counter and exit if over ctr += 1 if ctr >= self.mCount: return True # Need to re-initialise start based on BYxxx rules while True: # Behaviour is based on frequency set_items = [] if self.mFreq == definitions.eRecurrence_SECONDLY: self.generateSecondlySet(start_iter, set_items) elif self.mFreq == definitions.eRecurrence_MINUTELY: self.generateMinutelySet(start_iter, set_items) elif self.mFreq == definitions.eRecurrence_HOURLY: self.generateHourlySet(start_iter, set_items) elif self.mFreq == definitions.eRecurrence_DAILY: self.generateDailySet(start_iter, set_items) elif self.mFreq == definitions.eRecurrence_WEEKLY: self.generateWeeklySet(start_iter, set_items) elif self.mFreq == definitions.eRecurrence_MONTHLY: self.generateMonthlySet(start_iter, set_items) elif self.mFreq == definitions.eRecurrence_YEARLY: self.generateYearlySet(start_iter, set_items) # Ignore if it is invalid set_items = filter(lambda x: not x.invalid(), set_items) # Always sort the set as BYxxx rules may not be sorted #set_items.sort(cmp=PyCalendarDateTime.sort) set_items.sort(key=lambda x: x.getPosixTime()) # Process each one in the generated set for iter in set_items: # Ignore if it is before the actual start - we need this # because the expansion # can go back in time from the real start, but we must exclude # those when counting # even if they are not within the requested range if iter < start: continue # Exit if after period we want if range.isDateAfterPeriod(iter): return False # Exit if beyond the UNTIL limit if self.mUseUntil: # Exit if next item is after until (its OK if its the same # as UNTIL as UNTIL is inclusive) if iter > float_until: return True # Special for start instance if (ctr == 1) and (start == iter): continue # Add current one to list items.append(iter) # Check limits if self.mUseCount: # Bump counter and exit if over ctr += 1 if ctr >= self.mCount: return True # Exit if after period we want if range.isDateAfterPeriod(start_iter): return False # Get next item start_iter.recur(self.mFreq, self.mInterval, allow_invalid=True) def clear(self): self.mCached = False self.mFullyCached = False if self.mRecurrences is not None: self.mRecurrences = [] # IMPORTANT ExcludeFutureRecurrence assumes mCacheStart is setup with the # owning VEVENT's DTSTART # Currently this method is only called when a recurrence is being removed # so # the recurrence data should be cached # Exclude dates on or after the chosen one def excludeFutureRecurrence(self, exclude): # Expand the rule up to the exclude date items = [] period = PyCalendarPeriod() period.init(self.mCacheStart, exclude) self.expand(self.mCacheStart, period, items) # Adjust UNTIL or add one if no COUNT if self.getUseUntil() or not self.getUseCount(): # The last one is just less than the exclude date if len(items) != 0: # Now use the data as the UNTIL self.mUseUntil = True self.mUntil = items[-1] # Adjust COUNT elif self.getUseCount(): # The last one is just less than the exclude date self.mUseCount = True self.mCount = len(items) # Now clear out the cached set after making changes self.clear() def generateYearlySet(self, start, items): # All possible BYxxx are valid, though some combinations are not # Start with initial date-time items.append(start.duplicate()) if (self.mByMonth is not None) and (len(self.mByMonth) != 0): items[:] = self.byMonthExpand(items) if (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0): items[:] = self.byWeekNoExpand(items) if (self.mByYearDay is not None) and (len(self.mByYearDay) != 0): items[:] = self.byYearDayExpand(items) if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0): items[:] = self.byMonthDayExpand(items) if (self.mByDay is not None) and (len(self.mByDay) != 0): # BYDAY is complicated: # if BYDAY is included with BYYEARDAY or BYMONTHDAY then it # contracts the recurrence set # else it expands it, but the expansion depends on the frequency # and other BYxxx periodicities if ((self.mByYearDay is not None) and (len(self.mByYearDay) != 0)) \ or ((self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0)): items[:] = self.byDayLimit(items) elif (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0): items[:] = self.byDayExpandWeekly(items) elif (self.mByMonth is not None) and (len(self.mByMonth) != 0): items[:] = self.byDayExpandMonthly(items) else: items[:] = self.byDayExpandYearly(items) if (self.mByHours is not None) and (len(self.mByHours) != 0): items[:] = self.byHourExpand(items) if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0): items[:] = self.byMinuteExpand(items) if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0): items[:] = self.bySecondExpand(items) if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0): items[:] = self.bySetPosLimit(items) def generateMonthlySet(self, start, items): # Cannot have BYYEARDAY and BYWEEKNO # Start with initial date-time items.append(start.duplicate()) if (self.mByMonth is not None) and (len(self.mByMonth) != 0): # BYMONTH limits the range of possible values items[:] = self.byMonthLimit(items) if (len(items) == 0): return # No BYWEEKNO # No BYYEARDAY if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0): items[:] = self.byMonthDayExpand(items) if (self.mByDay is not None) and (len(self.mByDay) != 0): # BYDAY is complicated: # if BYDAY is included with BYYEARDAY or BYMONTHDAY then it # contracts the recurrence set # else it expands it, but the expansion depends on the frequency # and other BYxxx periodicities if ((self.mByYearDay is not None) and (len(self.mByYearDay) != 0)) \ or ((self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0)): items[:] = self.byDayLimit(items) else: items[:] = self.byDayExpandMonthly(items) if ((self.mByHours is not None) and (len(self.mByHours) != 0)): items[:] = self.byHourExpand(items) if ((self.mByMinutes is not None) and (len(self.mByMinutes) != 0)): items[:] = self.byMinuteExpand(items) if ((self.mBySeconds is not None) and (len(self.mBySeconds) != 0)): items[:] = self.bySecondExpand(items) if ((self.mBySetPos is not None) and (len(self.mBySetPos) != 0)): items[:] = self.bySetPosLimit(items) def generateWeeklySet(self, start, items): # Cannot have BYYEARDAY and BYMONTHDAY # Start with initial date-time items.append(start.duplicate()) if (self.mByMonth is not None) and (len(self.mByMonth) != 0): # BYMONTH limits the range of possible values items[:] = self.byMonthLimit(items) if (len(items) == 0): return if (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0): items[:] = self.byWeekNoLimit(items) if (len(items) == 0): return # No BYYEARDAY # No BYMONTHDAY if (self.mByDay is not None) and (len(self.mByDay) != 0): items[:] = self.byDayExpandWeekly(items) if (self.mByHours is not None) and (len(self.mByHours) != 0): items[:] = self.byHourExpand(items) if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0): items[:] = self.byMinuteExpand(items) if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0): items[:] = self.bySecondExpand(items) if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0): items[:] = self.bySetPosLimit(items) def generateDailySet(self, start, items): # Cannot have BYYEARDAY # Start with initial date-time items.append(start.duplicate()) if (self.mByMonth is not None) and (len(self.mByMonth) != 0): # BYMONTH limits the range of possible values items[:] = self.byMonthLimit(items) if (len(items) == 0): return if (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0): items[:] = self.byWeekNoLimit(items) if (len(items) == 0): return # No BYYEARDAY if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0): items[:] = self.byMonthDayLimit(items) if (len(items) == 0): return if (self.mByDay is not None) and (len(self.mByDay) != 0): items[:] = self.byDayLimit(items) if (len(items) == 0): return if (self.mByHours is not None) and (len(self.mByHours) != 0): items[:] = self.byHourExpand(items) if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0): items[:] = self.byMinuteExpand(items) if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0): items[:] = self.bySecondExpand(items) if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0): items[:] = self.bySetPosLimit(items) def generateHourlySet(self, start, items): # Cannot have BYYEARDAY # Start with initial date-time items.append(start.duplicate()) if (self.mByMonth is not None) and (len(self.mByMonth) != 0): # BYMONTH limits the range of possible values items[:] = self.byMonthLimit(items) if (len(items) == 0): return if (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0): items[:] = self.byWeekNoLimit(items) if (len(items) == 0): return # No BYYEARDAY if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0): items[:] = self.byMonthDayLimit(items) if (len(items) == 0): return if (self.mByDay is not None) and (len(self.mByDay) != 0): items[:] = self.byDayLimit(items) if (len(items) == 0): return if (self.mByHours is not None) and (len(self.mByHours) != 0): items[:] = self.byHourLimit(items) if (len(items) == 0): return if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0): items[:] = self.byMinuteExpand(items) if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0): items[:] = self.bySecondExpand(items) if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0): items[:] = self.bySetPosLimit(items) def generateMinutelySet(self, start, items): # Cannot have BYYEARDAY # Start with initial date-time items.append(start.duplicate()) if (self.mByMonth is not None) and (len(self.mByMonth) != 0): # BYMONTH limits the range of possible values items[:] = self.byMonthLimit(items) if (len(items) == 0): return if (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0): items[:] = self.byWeekNoLimit(items) if (len(items) == 0): return # No BYYEARDAY if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0): items[:] = self.byMonthDayLimit(items) if (len(items) == 0): return if (self.mByDay is not None) and (len(self.mByDay) != 0): items[:] = self.byDayLimit(items) if (len(items) == 0): return if (self.mByHours is not None) and (len(self.mByHours) != 0): items[:] = self.byHourLimit(items) if (len(items) == 0): return if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0): items[:] = self.byMinuteLimit(items) if (len(items) == 0): return if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0): items[:] = self.bySecondExpand(items) if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0): items[:] = self.bySetPosLimit(items) def generateSecondlySet(self, start, items): # Cannot have BYYEARDAY # Start with initial date-time items.append(start.duplicate()) if (self.mByMonth is not None) and (len(self.mByMonth) != 0): # BYMONTH limits the range of possible values items[:] = self.byMonthLimit(items) if (len(items) == 0): return if (self.mByWeekNo is not None) and (len(self.mByWeekNo) != 0): items[:] = self.byWeekNoLimit(items) if (len(items) == 0): return # No BYYEARDAY if (self.mByMonthDay is not None) and (len(self.mByMonthDay) != 0): items[:] = self.byMonthDayLimit(items) if (len(items) == 0): return if (self.mByDay is not None) and (len(self.mByDay) != 0): items[:] = self.byDayLimit(items) if (len(items) == 0): return if (self.mByHours is not None) and (len(self.mByHours) != 0): items[:] = self.byHourLimit(items) if (len(items) == 0): return if (self.mByMinutes is not None) and (len(self.mByMinutes) != 0): items[:] = self.byMinuteLimit(items) if (len(items) == 0): return if (self.mBySeconds is not None) and (len(self.mBySeconds) != 0): items[:] = self.bySecondLimit(items) if (len(items) == 0): return if (self.mBySetPos is not None) and (len(self.mBySetPos) != 0): items[:] = self.bySetPosLimit(items) def byMonthExpand(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYMONTH and generating a new date-time for it and # insert into output for iter2 in self.mByMonth: temp = iter1.duplicate() temp.setMonth(iter2) output.append(temp) return output def byWeekNoExpand(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYWEEKNO and generating a new date-time for it and # insert into output for iter2 in self.mByWeekNo: temp = iter1.duplicate() temp.setWeekNo(iter2) output.append(temp) return output def byYearDayExpand(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYYEARDAY and generating a new date-time for it # and insert into output for iter2 in self.mByYearDay: temp = iter1.duplicate() temp.setYearDay(iter2, allow_invalid=True) output.append(temp) return output def byMonthDayExpand(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYMONTHDAY and generating a new date-time for it # and insert into output for iter2 in self.mByMonthDay: temp = iter1.duplicate() temp.setMonthDay(iter2, allow_invalid=True) output.append(temp) return output def byDayExpandYearly(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYDAY and generating a new date-time for it and # insert into output for iter2 in self.mByDay: # Numeric value means specific instance if iter2[0] != 0: temp = iter1.duplicate() temp.setDayOfWeekInYear(iter2[0], iter2[1]) output.append(temp) else: # Every matching day in the year for i in range(1, 54): temp = iter1.duplicate() temp.setDayOfWeekInYear(i, iter2[1]) if temp.getYear() == (iter1).getYear(): output.append(temp) return output def byDayExpandMonthly(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYDAY and generating a new date-time for it and # insert into output for iter2 in self.mByDay: # Numeric value means specific instance if iter2[0] != 0: temp = iter1.duplicate() temp.setDayOfWeekInMonth(iter2[0], iter2[1], allow_invalid=True) output.append(temp) else: # Every matching day in the month for i in range(1, 7): temp = iter1.duplicate() temp.setDayOfWeekInMonth(i, iter2[1], allow_invalid=True) if temp.getMonth() == iter1.getMonth(): output.append(temp) return output def byDayExpandWeekly(self, dates): # Must take into account the WKST value # Loop over all input items output = [] for iter1 in dates: # Loop over each BYDAY and generating a new date-time for it and # insert into output for iter2 in self.mByDay: # Numeric values are meaningless so ignore them if iter2[0] == 0: temp = iter1.duplicate() # Determine amount of offset to apply to temp to shift it # to the start of the week (backwards) week_start_offset = self.mWeekstart - temp.getDayOfWeek() if week_start_offset > 0: week_start_offset -= 7 # Determine amount of offset from the start of the week to # the day we want (forwards) day_in_week_offset = iter2[1] - self.mWeekstart if day_in_week_offset < 0: day_in_week_offset += 7 # Apply offsets temp.offsetDay(week_start_offset + day_in_week_offset) output.append(temp) return output def byHourExpand(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYHOUR and generating a new date-time for it and # insert into output for iter2 in self.mByHours: temp = iter1.duplicate() temp.setHours(iter2) output.append(temp) return output def byMinuteExpand(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYMINUTE and generating a new date-time for it and # insert into output for iter2 in self.mByMinutes: temp = iter1.duplicate() temp.setMinutes(iter2) output.append(temp) return output def bySecondExpand(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYSECOND and generating a new date-time for it and # insert into output for iter2 in self.mBySeconds: temp = iter1.duplicate() temp.setSeconds(iter2) output.append(temp) return output def byMonthLimit(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYMONTH and indicate keep if input month matches keep = False for iter2 in self.mByMonth: keep = (iter1.getMonth() == iter2) if keep: break if keep: output.append(iter1) return output def byWeekNoLimit(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYWEEKNO and indicate keep if input month matches keep = False for iter2 in self.mByWeekNo: keep = iter1.isWeekNo(iter2) if keep: break if keep: output.append(iter1) return output def byMonthDayLimit(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYMONTHDAY and indicate keep if input month # matches keep = False for iter2 in self.mByMonthDay: keep = iter1.isMonthDay(iter2) if keep: break if keep: output.append(iter1) return output def byDayLimit(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYDAY and indicate keep if input month matches keep = False for iter2 in self.mByDay: keep = iter1.isDayOfWeekInMonth(iter2[0], iter2[1]) if keep: break if keep: output.append(iter1) return output def byHourLimit(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYHOUR and indicate keep if input hour matches keep = False for iter2 in self.mByHours: keep = (iter1.getHours() == iter2) if keep: break if keep: output.append(iter1) return output def byMinuteLimit(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYMINUTE and indicate keep if input minute matches keep = False for iter2 in self.mByMinutes: keep = (iter1.getMinutes() == iter2) if keep: break if keep: output.append(iter1) return output def bySecondLimit(self, dates): # Loop over all input items output = [] for iter1 in dates: # Loop over each BYSECOND and indicate keep if input second matches keep = False for iter2 in self.mBySeconds: keep = (iter1.getSeconds() == iter2) if keep: break if keep: output.append(iter1) return output def bySetPosLimit(self, dates): # The input dates MUST be sorted in order for this to work properly #dates.sort(cmp=PyCalendarDateTime.sort) dates.sort(key=lambda x: x.getPosixTime()) # Loop over each BYSETPOS and extract the relevant component from the # input array and add to the output output = [] input_size = len(dates) for iter in self.mBySetPos: if iter > 0: # Positive values are offset from the start if iter <= input_size: output.append(dates[iter - 1]) elif iter < 0: # Negative values are offset from the end if -iter <= input_size: output.append(dates[input_size + iter]) return output pycalendar-2.0~svn13177/src/pycalendar/available.py0000644000175000017500000000620512101017573021240 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar.componentrecur import PyCalendarComponentRecur from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS class PyCalendarAvailable(PyCalendarComponentRecur): propertyCardinality_1 = ( definitions.cICalProperty_DTSTAMP, definitions.cICalProperty_DTSTART, definitions.cICalProperty_UID, ) propertyCardinality_0_1 = ( definitions.cICalProperty_CREATED, definitions.cICalProperty_DESCRIPTION, definitions.cICalProperty_GEO, definitions.cICalProperty_LAST_MODIFIED, definitions.cICalProperty_LOCATION, definitions.cICalProperty_RECURRENCE_ID, definitions.cICalProperty_RRULE, definitions.cICalProperty_SEQUENCE, definitions.cICalProperty_SUMMARY, definitions.cICalProperty_DTEND, definitions.cICalProperty_DURATION, ) propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None): super(PyCalendarAvailable, self).__init__(parent=parent) def duplicate(self, parent=None): return super(PyCalendarAvailable, self).duplicate(parent=parent) def getType(self): return definitions.cICalComponent_AVAILABLE def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended to that. """ fixed, unfixed = super(PyCalendarAvailable, self).validate(doFix) # Extra constraint: only one of DTEND or DURATION if self.hasProperty(definitions.cICalProperty_DTEND) and self.hasProperty(definitions.cICalProperty_DURATION): # Fix by removing the DTEND logProblem = "[%s] Properties must not both be present: %s, %s" % ( self.getType(), definitions.cICalProperty_DTEND, definitions.cICalProperty_DURATION, ) if doFix: self.removeProperties(definitions.cICalProperty_DTEND) fixed.append(logProblem) else: unfixed.append(logProblem) return fixed, unfixed def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, definitions.cICalProperty_RECURRENCE_ID, definitions.cICalProperty_DTSTART, definitions.cICalProperty_DURATION, definitions.cICalProperty_DTEND, ) pycalendar-2.0~svn13177/src/pycalendar/binaryvalue.py0000644000175000017500000000202512101017573021635 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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. ## # iCalendar Binary value from pycalendar import xmldefs from pycalendar.plaintextvalue import PyCalendarPlainTextValue from pycalendar.value import PyCalendarValue class PyCalendarBinaryValue(PyCalendarPlainTextValue): def getType(self): return PyCalendarValue.VALUETYPE_BINARY PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_BINARY, PyCalendarBinaryValue, xmldefs.value_binary) pycalendar-2.0~svn13177/src/pycalendar/vtimezoneelement.py0000644000175000017500000001761412101017573022720 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 bisect import bisect_right from pycalendar import definitions from pycalendar.component import PyCalendarComponent from pycalendar.datetime import PyCalendarDateTime from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS from pycalendar.period import PyCalendarPeriod from pycalendar.recurrenceset import PyCalendarRecurrenceSet from pycalendar.value import PyCalendarValue class PyCalendarVTimezoneElement(PyCalendarComponent): propertyCardinality_1 = ( definitions.cICalProperty_DTSTART, definitions.cICalProperty_TZOFFSETTO, definitions.cICalProperty_TZOFFSETFROM, ) propertyCardinality_0_1 = ( definitions.cICalProperty_RRULE, ) propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None, dt=None, offset=None): super(PyCalendarVTimezoneElement, self).__init__(parent=parent) self.mStart = dt if dt is not None else PyCalendarDateTime() self.mTZName = "" self.mUTCOffset = offset if offset is not None else 0 self.mUTCOffsetFrom = 0 self.mRecurrences = PyCalendarRecurrenceSet() self.mCachedExpandBelow = None self.mCachedExpandBelowItems = None def duplicate(self, parent=None): other = super(PyCalendarVTimezoneElement, self).duplicate(parent=parent) other.mStart = self.mStart.duplicate() other.mTZName = self.mTZName other.mUTCOffset = self.mUTCOffset other.mUTCOffsetFrom = self.mUTCOffsetFrom other.mRecurrences = self.mRecurrences.duplicate() other.mCachedExpandBelow = None other.mCachedExpandBelowItems = None return other def finalise(self): # Get DTSTART temp = self.loadValueDateTime(definitions.cICalProperty_DTSTART) if temp is not None: self.mStart = temp # Get TZOFFSETTO temp = self.loadValueInteger(definitions.cICalProperty_TZOFFSETTO, PyCalendarValue.VALUETYPE_UTC_OFFSET) if temp is not None: self.mUTCOffset = temp # Get TZOFFSETFROM temp = self.loadValueInteger(definitions.cICalProperty_TZOFFSETFROM, PyCalendarValue.VALUETYPE_UTC_OFFSET) if temp is not None: self.mUTCOffsetFrom = temp # Get TZNAME temps = self.loadValueString(definitions.cICalProperty_TZNAME) if temps is not None: self.mTZName = temps # Get RRULEs self.loadValueRRULE(definitions.cICalProperty_RRULE, self.mRecurrences, True) # Get RDATEs self.loadValueRDATE(definitions.cICalProperty_RDATE, self.mRecurrences, True) # Do inherited super(PyCalendarVTimezoneElement, self).finalise() def getSortKey(self): """ We do not want these components sorted. """ return "" def getStart(self): return self.mStart def getUTCOffset(self): return self.mUTCOffset def getUTCOffsetFrom(self): return self.mUTCOffsetFrom def getTZName(self): return self.mTZName def expandBelow(self, below): # Look for recurrences if not self.mRecurrences.hasRecurrence() or self.mStart > below: # Return DTSTART even if it is newer return self.mStart else: # We want to allow recurrence calculation caching to help us here # as this method # gets called a lot - most likely for ever increasing dt values # (which will therefore # invalidate the recurrence cache). # # What we will do is round up the date-time to the next year so # that the recurrence # cache is invalidated less frequently temp = PyCalendarDateTime(below.getYear(), 1, 1, 0, 0, 0) # Use cache of expansion if self.mCachedExpandBelowItems is None: self.mCachedExpandBelowItems = [] if self.mCachedExpandBelow is None: self.mCachedExpandBelow = self.mStart.duplicate() if temp > self.mCachedExpandBelow: self.mCachedExpandBelowItems = [] period = PyCalendarPeriod(self.mStart, temp) self.mRecurrences.expand(self.mStart, period, self.mCachedExpandBelowItems, float_offset=self.mUTCOffsetFrom) self.mCachedExpandBelow = temp if len(self.mCachedExpandBelowItems) != 0: # List comes back sorted so we pick the element just less than # the dt value we want i = bisect_right(self.mCachedExpandBelowItems, below) if i != 0: return self.mCachedExpandBelowItems[i - 1] # The first one in the list is the one we want return self.mCachedExpandBelowItems[0] return self.mStart def expandAll(self, start, end, with_name): if start is None: start = self.mStart # Ignore if there is no change in offset offsetto = self.loadValueInteger(definitions.cICalProperty_TZOFFSETTO, PyCalendarValue.VALUETYPE_UTC_OFFSET) offsetfrom = self.loadValueInteger(definitions.cICalProperty_TZOFFSETFROM, PyCalendarValue.VALUETYPE_UTC_OFFSET) # if offsetto == offsetfrom: # return () # Look for recurrences if self.mStart > end: # Return nothing return () elif not self.mRecurrences.hasRecurrence(): # Return DTSTART even if it is newer if self.mStart >= start: result = (self.mStart, offsetfrom, offsetto,) if with_name: result += (self.getTZName(),) return (result,) else: return () else: # We want to allow recurrence calculation caching to help us here # as this method # gets called a lot - most likely for ever increasing dt values # (which will therefore # invalidate the recurrence cache). # # What we will do is round up the date-time to the next year so # that the recurrence # cache is invalidated less frequently temp = PyCalendarDateTime(end.getYear(), 1, 1, 0, 0, 0) # Use cache of expansion if self.mCachedExpandBelowItems is None: self.mCachedExpandBelowItems = [] if self.mCachedExpandBelow is None: self.mCachedExpandBelow = self.mStart.duplicate() if temp > self.mCachedExpandBelow: self.mCachedExpandBelowItems = [] period = PyCalendarPeriod(self.mStart, end) self.mRecurrences.expand(self.mStart, period, self.mCachedExpandBelowItems, float_offset=self.mUTCOffsetFrom) self.mCachedExpandBelow = temp if len(self.mCachedExpandBelowItems) != 0: # Return them all within the range results = [] for dt in self.mCachedExpandBelowItems: if dt >= start and dt < end: result = (dt, offsetfrom, offsetto,) if with_name: result += (self.getTZName(),) results.append(result) return results return () pycalendar-2.0~svn13177/src/pycalendar/vevent.py0000644000175000017500000001412112101017573020623 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar import itipdefinitions from pycalendar.componentrecur import PyCalendarComponentRecur from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS from pycalendar.property import PyCalendarProperty class PyCalendarVEvent(PyCalendarComponentRecur): propertyCardinality_1 = ( definitions.cICalProperty_DTSTAMP, definitions.cICalProperty_UID, ) propertyCardinality_0_1 = ( definitions.cICalProperty_CLASS, definitions.cICalProperty_CREATED, definitions.cICalProperty_DESCRIPTION, definitions.cICalProperty_GEO, definitions.cICalProperty_LAST_MODIFIED, definitions.cICalProperty_LOCATION, definitions.cICalProperty_ORGANIZER, definitions.cICalProperty_PRIORITY, definitions.cICalProperty_SEQUENCE, # definitions.cICalProperty_STATUS, # Special fix done for multiple STATUS definitions.cICalProperty_SUMMARY, definitions.cICalProperty_TRANSP, definitions.cICalProperty_URL, definitions.cICalProperty_RECURRENCE_ID, definitions.cICalProperty_RRULE, definitions.cICalProperty_DTEND, definitions.cICalProperty_DURATION, ) propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None): super(PyCalendarVEvent, self).__init__(parent=parent) self.mStatus = definitions.eStatus_VEvent_None def duplicate(self, parent=None): other = super(PyCalendarVEvent, self).duplicate(parent=parent) other.mStatus = self.mStatus return other def getType(self): return definitions.cICalComponent_VEVENT def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VEVENT def addComponent(self, comp): # We can embed the alarm components only if comp.getType() == definitions.cICalComponent_VALARM: super(PyCalendarVEvent, self).addComponent(comp) else: raise ValueError def getStatus(self): return self.mStatus def setStatus(self, status): self.mStatus = status def finalise(self): # Do inherited super(PyCalendarVEvent, self).finalise() temp = self.loadValueString(definitions.cICalProperty_STATUS) if temp is not None: if temp == definitions.cICalProperty_STATUS_TENTATIVE: self.mStatus = definitions.eStatus_VEvent_Tentative elif temp == definitions.cICalProperty_STATUS_CONFIRMED: self.mStatus = definitions.eStatus_VEvent_Confirmed elif temp == definitions.cICalProperty_STATUS_CANCELLED: self.mStatus = definitions.eStatus_VEvent_Cancelled def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended to that. """ fixed, unfixed = super(PyCalendarVEvent, self).validate(doFix) # Extra constraint: if METHOD not present, DTSTART must be if self.mParentComponent and not self.mParentComponent.hasProperty(definitions.cICalProperty_METHOD): if not self.hasProperty(definitions.cICalProperty_DTSTART): # Cannot fix a missing required property logProblem = "[%s] Missing required property: %s" % (self.getType(), definitions.cICalProperty_DTSTART,) unfixed.append(logProblem) # Extra constraint: only one of DTEND or DURATION if self.hasProperty(definitions.cICalProperty_DTEND) and self.hasProperty(definitions.cICalProperty_DURATION): # Fix by removing the DTEND logProblem = "[%s] Properties must not both be present: %s, %s" % ( self.getType(), definitions.cICalProperty_DTEND, definitions.cICalProperty_DURATION, ) if doFix: self.removeProperties(definitions.cICalProperty_DTEND) fixed.append(logProblem) else: unfixed.append(logProblem) return fixed, unfixed # Editing def editStatus(self, status): # Only if it is different if self.mStatus != status: # Updated cached values self.mStatus = status # Remove existing STATUS items self.removeProperties(definitions.cICalProperty_STATUS) # Now create properties value = None if status == definitions.eStatus_VEvent_None: pass elif status == definitions.eStatus_VEvent_Tentative: value = definitions.cICalProperty_STATUS_TENTATIVE elif status == definitions.eStatus_VEvent_Confirmed: value = definitions.cICalProperty_STATUS_CONFIRMED elif status == definitions.eStatus_VEvent_Cancelled: value = definitions.cICalProperty_STATUS_CANCELLED else: pass if value is not None: prop = PyCalendarProperty(definitions.cICalProperty_STATUS, value) self.addProperty(prop) def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, definitions.cICalProperty_RECURRENCE_ID, definitions.cICalProperty_DTSTART, definitions.cICalProperty_DURATION, definitions.cICalProperty_DTEND, ) pycalendar-2.0~svn13177/src/pycalendar/vfreebusy.py0000644000175000017500000002603412101017573021334 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar import itipdefinitions from pycalendar.component import PyCalendarComponent from pycalendar.datetime import PyCalendarDateTime from pycalendar.freebusy import PyCalendarFreeBusy from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS from pycalendar.period import PyCalendarPeriod from pycalendar.periodvalue import PyCalendarPeriodValue from pycalendar.property import PyCalendarProperty from pycalendar.value import PyCalendarValue class PyCalendarVFreeBusy(PyCalendarComponent): propertyCardinality_1 = ( definitions.cICalProperty_DTSTAMP, definitions.cICalProperty_UID, ) propertyCardinality_0_1 = ( definitions.cICalProperty_CONTACT, definitions.cICalProperty_DTSTART, definitions.cICalProperty_DTEND, definitions.cICalProperty_ORGANIZER, definitions.cICalProperty_URL, ) propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None): super(PyCalendarVFreeBusy, self).__init__(parent=parent) self.mStart = PyCalendarDateTime() self.mHasStart = False self.mEnd = PyCalendarDateTime() self.mHasEnd = False self.mDuration = False self.mCachedBusyTime = False self.mSpanPeriod = None self.mBusyTime = None def duplicate(self, parent=None): other = super(PyCalendarVFreeBusy, self).duplicate(parent=parent) other.mStart = self.mStart.duplicate() other.mHasStart = self.mHasStart other.mEnd = self.mEnd.duplicate() other.mHasEnd = self.mHasEnd other.mDuration = self.mDuration other.mCachedBusyTime = False other.mBusyTime = None return other def getType(self): return definitions.cICalComponent_VFREEBUSY def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VFREEBUSY def finalise(self): # Do inherited super(PyCalendarVFreeBusy, self).finalise() # Get DTSTART temp = self.loadValueDateTime(definitions.cICalProperty_DTSTART) self.mHasStart = temp is not None if self.mHasStart: self.mStart = temp # Get DTEND temp = self.loadValueDateTime(definitions.cICalProperty_DTEND) if temp is None: # Try DURATION instead temp = self.loadValueDuration(definitions.cICalProperty_DURATION) if temp is not None: self.mEnd = self.mStart + temp self.mDuration = True else: # Force end to start, which will then be fixed to sensible # value later self.mEnd = self.mStart else: self.mHasEnd = True self.mDuration = False self.mEnd = temp def fixStartEnd(self): # End is always greater than start if start exists if self.mHasStart and self.mEnd <= self.mStart: # Use the start self.mEnd = self.mStart.duplicate() self.mDuration = False # Adjust to appropiate non-inclusive end point if self.mStart.isDateOnly(): self.mEnd.offsetDay(1) # For all day events it makes sense to use duration self.mDuration = True else: # Use end of current day self.mEnd.offsetDay(1) self.mEnd.setHHMMSS(0, 0, 0) def getStart(self): return self.mStart def hasStart(self): return self.mHasStart def getEnd(self): return self.mEnd def hasEnd(self): return self.mHasEnd def useDuration(self): return self.mDuration def getSpanPeriod(self): return self.mSpanPeriod def getBusyTime(self): return self.mBusyTime def editTiming(self): # Updated cached values self.mHasStart = False self.mHasEnd = False self.mDuration = False self.mStart.setToday() self.mEnd.setToday() # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) def editTimingStartEnd(self, start, end): # Updated cached values self.mHasStart = self.mHasEnd = True self.mStart = start self.mEnd = end self.mDuration = False self.fixStartEnd() # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) # Now create properties prop = PyCalendarProperty(definitions.cICalProperty_DTSTART, start) self.addProperty(prop) # If its an all day event and the end one day after the start, ignore it temp = start.duplicate() temp.offsetDay(1) if not start.isDateOnly() or end != temp: prop = PyCalendarProperty(definitions.cICalProperty_DTEND, end) self.addProperty(prop) def editTimingStartDuration(self, start, duration): # Updated cached values self.mHasStart = True self.mHasEnd = False self.mStart = start self.mEnd = start + duration self.mDuration = True # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) self.removeProperties(definitions.cICalProperty_DUE) # Now create properties prop = PyCalendarProperty(definitions.cICalProperty_DTSTART, start) self.addProperty(prop) # If its an all day event and the duration is one day, ignore it if (not start.isDateOnly() or (duration.getWeeks() != 0) or (duration.getDays() > 1)): prop = PyCalendarProperty(definitions.cICalProperty_DURATION, duration) self.addProperty(prop) # Generating info def expandPeriodComp(self, period, list): # Cache the busy-time details if not done already if not self.mCachedBusyTime: self.cacheBusyTime() # See if period intersects the busy time span range if (self.mBusyTime is not None) and period.isPeriodOverlap(self.mSpanPeriod): list.append(self) def expandPeriodFB(self, period, list): # Cache the busy-time details if not done already if not self.mCachedBusyTime: self.cacheBusyTime() # See if period intersects the busy time span range if (self.mBusyTime is not None) and period.isPeriodOverlap(self.mSpanPeriod): for fb in self.mBusyTime: list.append(PyCalendarFreeBusy(fb)) def cacheBusyTime(self): # Clear out any existing cache self.mBusyTime = [] # Get all FREEBUSY items and add those that are BUSY min_start = PyCalendarDateTime() max_end = PyCalendarDateTime() props = self.getProperties() result = props.get(definitions.cICalProperty_FREEBUSY, ()) for iter in result: # Check the properties FBTYPE attribute type = 0 is_busy = False if iter.hasAttribute(definitions.cICalAttribute_FBTYPE): fbyype = iter.getAttributeValue(definitions.cICalAttribute_FBTYPE) if fbyype.upper() == definitions.cICalAttribute_FBTYPE_BUSY: is_busy = True type = PyCalendarFreeBusy.BUSY elif fbyype.upper() == definitions.cICalAttribute_FBTYPE_BUSYUNAVAILABLE: is_busy = True type = PyCalendarFreeBusy.BUSYUNAVAILABLE elif fbyype.upper() == definitions.cICalAttribute_FBTYPE_BUSYTENTATIVE: is_busy = True type = PyCalendarFreeBusy.BUSYTENTATIVE else: is_busy = False type = PyCalendarFreeBusy.FREE else: # Default is busy when no attribute is_busy = True type = PyCalendarFreeBusy.BUSY # Add this period if is_busy: multi = iter.getMultiValue() if (multi is not None) and (multi.getType() == PyCalendarValue.VALUETYPE_PERIOD): for o in multi.getValues(): # Double-check type period = None if isinstance(o, PyCalendarPeriodValue): period = o # Double-check type if period is not None: self.mBusyTime.append(PyCalendarFreeBusy(type, period.getValue())) if len(self.mBusyTime) == 1: min_start = period.getValue().getStart() max_end = period.getValue().getEnd() else: if min_start > period.getValue().getStart(): min_start = period.getValue().getStart() if max_end < period.getValue().getEnd(): max_end = period.getValue().getEnd() # If nothing present, empty the list if len(self.mBusyTime) == 0: self.mBusyTime = None else: # Sort the list by period self.mBusyTime.sort(cmp=lambda x, y: x.getPeriod().getStart().compareDateTime(y.getPeriod().getStart())) # Determine range start = PyCalendarDateTime() end = PyCalendarDateTime() if self.mHasStart: start = self.mStart else: start = min_start if self.mHasEnd: end = self.mEnd else: end = max_end self.mSpanPeriod = PyCalendarPeriod(start, end) self.mCachedBusyTime = True def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, definitions.cICalProperty_DTSTART, definitions.cICalProperty_DURATION, definitions.cICalProperty_DTEND, ) pycalendar-2.0~svn13177/src/pycalendar/tests/0000755000175000017500000000000012322631215020105 5ustar rahulrahulpycalendar-2.0~svn13177/src/pycalendar/tests/test_orgvalue.py0000644000175000017500000000346212101017573023347 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar.orgvalue import OrgValue from pycalendar.vcard.property import Property import unittest class TestNValue(unittest.TestCase): def testParseValue(self): items = ( ("", ""), ("Example", "Example"), ("Example\, Inc.", "Example\, Inc."), ("Example\; Inc;Dept. of Silly Walks", "Example\; Inc;Dept. of Silly Walks"), ) for item, result in items: req = OrgValue() req.parse(item) test = req.getText() self.assertEqual(test, result, "Failed to parse and re-generate '%s'" % (item,)) def testParseProperty(self): items = ( ("ORG:", "ORG:"), ("ORG:Example", "ORG:Example"), ("ORG:Example\, Inc.", "ORG:Example\, Inc."), ("ORG:Example\; Inc;Dept. of Silly Walks", "ORG:Example\; Inc;Dept. of Silly Walks"), ("ORG;VALUE=TEXT:Example", "ORG:Example"), ) for item, result in items: prop = Property() prop.parse(item) test = prop.getText() self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,)) pycalendar-2.0~svn13177/src/pycalendar/tests/test_requeststatus.py0000644000175000017500000000644712127044213024464 0ustar rahulrahul## # Copyright (c) 2011-2013 Cyrus Daboo. 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 pycalendar.parser import ParserContext from pycalendar.property import PyCalendarProperty from pycalendar.requeststatusvalue import PyCalendarRequestStatusValue import unittest class TestRequestStatus(unittest.TestCase): def testParseValue(self): items = ( "2.0;Success", "2.0;Success\;here", "2.0;Success;Extra", "2.0;Success\;here;Extra", "2.0;Success;Extra\;here", "2.0;Success\;here;Extra\;here too", ) for item in items: req = PyCalendarRequestStatusValue() req.parse(item) self.assertEqual(req.getText(), item, "Failed to parse and re-generate '%s'" % (item,)) def testBadValue(self): bad_value = "2.0\;Success" ok_value = "2.0;Success" # Fix the value oldContext = ParserContext.INVALID_REQUEST_STATUS_VALUE ParserContext.INVALID_REQUEST_STATUS_VALUE = ParserContext.PARSER_FIX req = PyCalendarRequestStatusValue() req.parse(bad_value) self.assertEqual(req.getText(), ok_value, "Failed to parse and re-generate '%s'" % (bad_value,)) # Raise the value ParserContext.INVALID_REQUEST_STATUS_VALUE = ParserContext.PARSER_RAISE req = PyCalendarRequestStatusValue() self.assertRaises(ValueError, req.parse, bad_value) ParserContext.INVALID_REQUEST_STATUS_VALUE = oldContext def testTruncatedValue(self): bad_value = "2.0" ok_value = "2.0;" # Fix the value oldContext = ParserContext.INVALID_REQUEST_STATUS_VALUE ParserContext.INVALID_REQUEST_STATUS_VALUE = ParserContext.PARSER_FIX req = PyCalendarRequestStatusValue() req.parse(bad_value) self.assertEqual(req.getText(), ok_value, "Failed to parse and re-generate '%s'" % (bad_value,)) # Raise the value ParserContext.INVALID_REQUEST_STATUS_VALUE = ParserContext.PARSER_RAISE req = PyCalendarRequestStatusValue() self.assertRaises(ValueError, req.parse, bad_value) ParserContext.INVALID_REQUEST_STATUS_VALUE = oldContext def testParseProperty(self): items = ( "REQUEST-STATUS:2.0;Success", "REQUEST-STATUS:2.0;Success\;here", "REQUEST-STATUS:2.0;Success;Extra", "REQUEST-STATUS:2.0;Success\;here;Extra", "REQUEST-STATUS:2.0;Success;Extra\;here", "REQUEST-STATUS:2.0;Success\;here;Extra\;here too", ) for item in items: req = PyCalendarProperty() req.parse(item) self.assertEqual(req.getText(), item + "\r\n", "Failed to parse and re-generate '%s'" % (item,)) pycalendar-2.0~svn13177/src/pycalendar/tests/test_adr.py0000644000175000017500000000303112101017573022261 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar.adr import Adr import unittest class TestAdrValue(unittest.TestCase): def testInit(self): data = ( ( ("pobox", "extended", "street", "locality", "region", "postalcode", "country"), "pobox;extended;street;locality;region;postalcode;country", ), ( (("pobox",), ("extended",), ("street1", "street2",), "locality", "region", (), "country"), "pobox;extended;street1,street2;locality;region;;country", ), ) for args, result in data: a = Adr(*args) self.assertEqual( a.getValue(), args, ) self.assertEqual( a.getText(), result, ) self.assertEqual( a.duplicate().getText(), result, ) pycalendar-2.0~svn13177/src/pycalendar/tests/test_nvalue.py0000644000175000017500000000363512101017573023017 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar.nvalue import NValue from pycalendar.vcard.property import Property import unittest class TestNValue(unittest.TestCase): def testParseValue(self): items = ( ("", ";;;;"), (";", ";;;;"), (";;;;", ";;;;"), ("Cyrus;Daboo;;Dr.", "Cyrus;Daboo;;Dr.;"), (";;;;PhD.", ";;;;PhD."), ("Cyrus", "Cyrus;;;;"), (";Daboo", ";Daboo;;;"), ) for item, result in items: req = NValue() req.parse(item) test = req.getText() self.assertEqual(test, result, "Failed to parse and re-generate '%s'" % (item,)) def testParseProperty(self): items = ( ("N:", "N:;;;;"), ("N:;", "N:;;;;"), ("N:;;;;", "N:;;;;"), ("N:Cyrus;Daboo;;Dr.", "N:Cyrus;Daboo;;Dr.;"), ("N:;;;;PhD.", "N:;;;;PhD."), ("N:Cyrus", "N:Cyrus;;;;"), ("N:;Daboo", "N:;Daboo;;;"), ("N;VALUE=TEXT:Cyrus;Daboo;;Dr.", "N:Cyrus;Daboo;;Dr.;"), ) for item, result in items: prop = Property() prop.parse(item) test = prop.getText() self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,)) pycalendar-2.0~svn13177/src/pycalendar/tests/test_duration.py0000644000175000017500000000634512101017573023353 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 cStringIO import StringIO from pycalendar.duration import PyCalendarDuration from pycalendar.parser import ParserContext import unittest class TestDuration(unittest.TestCase): test_data = ( (0, "PT0S"), (1, "PT1S"), (1, "+PT1S"), (-1, "-PT1S"), (60, "PT1M"), (60 + 2, "PT1M2S"), (1 * 60 * 60, "PT1H"), (1 * 60 * 60 + 2 * 60, "PT1H2M"), (1 * 60 * 60 + 1, "PT1H0M1S"), (1 * 60 * 60 + 2 * 60 + 1, "PT1H2M1S"), (24 * 60 * 60, "P1D"), (24 * 60 * 60 + 3 * 60 * 60, "P1DT3H"), (24 * 60 * 60 + 2 * 60, "P1DT2M"), (24 * 60 * 60 + 3 * 60 * 60 + 2 * 60, "P1DT3H2M"), (24 * 60 * 60 + 1, "P1DT1S"), (24 * 60 * 60 + 2 * 60 + 1, "P1DT2M1S"), (24 * 60 * 60 + 3 * 60 * 60 + 1, "P1DT3H0M1S"), (24 * 60 * 60 + 3 * 60 * 60 + 2 * 60 + 1, "P1DT3H2M1S"), (14 * 24 * 60 * 60, "P2W"), (15 * 24 * 60 * 60, "P15D"), (14 * 24 * 60 * 60 + 1, "P14DT1S"), ) def testGenerate(self): def _doTest(d, result): if result[0] == "+": result = result[1:] os = StringIO() d.generate(os) self.assertEqual(os.getvalue(), result) for seconds, result in TestDuration.test_data: _doTest(PyCalendarDuration(duration=seconds), result) def testParse(self): for seconds, result in TestDuration.test_data: duration = PyCalendarDuration().parseText(result) self.assertEqual(duration.getTotalSeconds(), seconds) def testParseBad(self): test_bad_data = ( "", "ABC", "P", "PABC", "P12", "P12D1", "P12DTAB", "P12DT1HA", "P12DT1MA", "P12DT1SA", ) for data in test_bad_data: self.assertRaises(ValueError, PyCalendarDuration.parseText, data) def testRelaxedBad(self): test_relaxed_data = ( ("P12DT", 12 * 24 * 60 * 60, "P12D"), ("-P1WT", -7 * 24 * 60 * 60, "-P1W"), ("-P1W1D", -7 * 24 * 60 * 60, "-P1W"), ) for text, seconds, result in test_relaxed_data: ParserContext.INVALID_DURATION_VALUE = ParserContext.PARSER_FIX self.assertEqual(PyCalendarDuration.parseText(text).getTotalSeconds(), seconds) self.assertEqual(PyCalendarDuration.parseText(text).getText(), result) ParserContext.INVALID_DURATION_VALUE = ParserContext.PARSER_RAISE self.assertRaises(ValueError, PyCalendarDuration.parseText, text) pycalendar-2.0~svn13177/src/pycalendar/tests/test_componentrecur.py0000644000175000017500000000357012101017573024566 0ustar rahulrahul# Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.calendar import PyCalendar import cStringIO as StringIO import unittest class TestCalendar(unittest.TestCase): def testDuplicateWithRecurrenceChange(self): data = ( """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 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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;COUNT=400 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ) cal1 = PyCalendar() cal1.parse(StringIO.StringIO(data[0])) cal2 = cal1.duplicate() vevent = cal2.getComponents()[0] rrules = vevent.getRecurrenceSet() for rrule in rrules.getRules(): rrule.setUseCount(True) rrule.setCount(400) rrules.changed() self.assertEqual(data[0], str(cal1)) self.assertEqual(data[1], str(cal2)) pycalendar-2.0~svn13177/src/pycalendar/tests/test_xml.py0000644000175000017500000000565612101017573022332 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.calendar import PyCalendar import cStringIO as StringIO import difflib import unittest class TestCalendar(unittest.TestCase): data = ( ( """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:12345-67890-3 DTSTART:20071114T000000Z ATTENDEE:mailto:user2@example.com EXDATE:20081114T000000Z ORGANIZER:mailto:user1@example.com RRULE:FREQ=YEARLY END:VEVENT X-TEST:Testing END:VCALENDAR """.replace("\n", "\r\n"), """ 2.0 -//mulberrymail.com//Mulberry v4.0//EN Testing 12345-67890-3 2007-11-14T00:00:00Z mailto:user2@example.com 2008-11-14T00:00:00Z mailto:user1@example.com YEARLY """, ), ) def testGenerateXML(self): def _doRoundtrip(caldata, resultdata=None): test1 = resultdata if resultdata is not None else caldata cal = PyCalendar() cal.parse(StringIO.StringIO(caldata)) test2 = cal.getTextXML() self.assertEqual( test1, test2, "\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines())) ) for item1, item2 in self.data: _doRoundtrip(item1, item2) pycalendar-2.0~svn13177/src/pycalendar/tests/test_datetime.py0000644000175000017500000003014512301206170023310 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar.calendar import PyCalendar from pycalendar.datetime import PyCalendarDateTime from pycalendar.parser import ParserContext from pycalendar.timezone import PyCalendarTimezone import unittest class TestDateTime(unittest.TestCase): def _patch(self, obj, attr, value): oldvalue = getattr(obj, attr) setattr(obj, attr, value) def _restore(): setattr(obj, attr, oldvalue) self.addCleanup(_restore) def testDuplicateASUTC(self): items = ( ( PyCalendarDateTime(2011, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), PyCalendarDateTime(2011, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), ), ( PyCalendarDateTime(2011, 1, 1, 0, 0, 0), PyCalendarDateTime(2011, 1, 1, 0, 0, 0), ), ( PyCalendarDateTime(2011, 1, 1), PyCalendarDateTime(2011, 1, 1), ) ) for item, result in items: dup = item.duplicateAsUTC() self.assertEqual(str(dup), str(result), "Failed to convert '%s'" % (item,)) def testDuplicateInSet(self): s = set( ( PyCalendarDateTime(2011, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), PyCalendarDateTime(2011, 1, 2, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)), ) ) self.assertTrue(PyCalendarDateTime(2011, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) in s) self.assertFalse(PyCalendarDateTime(2011, 1, 3, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) in s) def testRoundtrip(self): data1 = ( "20110102", "20110103T121212", "20110103T121212Z", "00010102", "00010103T121212", "00010103T121212Z", ) data2 = ( ("20110102", "20110102"), ("2011-01-02", "20110102"), ("20110103T121212", "20110103T121212"), ("2011-01-03T12:12:12", "20110103T121212"), ("20110103T121212Z", "20110103T121212Z"), ("2011-01-03T12:12:12Z", "20110103T121212Z"), ("20110103T121212+0100", "20110103T121212+0100"), ("2011-01-03T12:12:12-0500", "20110103T121212-0500"), ("20110103T121212,123", "20110103T121212"), ("2011-01-03T12:12:12,123", "20110103T121212"), ("20110103T121212,123Z", "20110103T121212Z"), ("2011-01-03T12:12:12,123Z", "20110103T121212Z"), ("20110103T121212,123+0100", "20110103T121212+0100"), ("2011-01-03T12:12:12,123-0500", "20110103T121212-0500"), ) for item in data1: dt = PyCalendarDateTime.parseText(item, False) self.assertEqual(dt.getText(), item, "Failed on: %s" % (item,)) for item, result in data2: dt = PyCalendarDateTime.parseText(item, True) self.assertEqual(dt.getText(), result, "Failed on: %s" % (item,)) def testBadParse(self): self._patch(ParserContext, "INVALID_DATETIME_LEADINGSPACE", ParserContext.PARSER_RAISE) data1 = ( "2011", "201101023", "20110103t121212", "20110103T1212", "20110103T1212123", "20110103T121212A", "2011-01-03T121212Z", "20110103T12:12:12Z", "20110103T121212+0500", " 10102", " 10102T010101", "2011 102", "201101 3T121212", "-1110102", "2011-102", "201101-3T121212", ) data2 = ( "2011-01+02", "20110103T12-12-12", "2011-01-03T12:12:12,", "2011-01-03T12:12:12,ABC", "20110103T12:12:12-1", ) for item in data1: self.assertRaises(ValueError, PyCalendarDateTime.parseText, item, False) for item in data2: self.assertRaises(ValueError, PyCalendarDateTime.parseText, item, False) def testBadParseFixed(self): self._patch(ParserContext, "INVALID_DATETIME_LEADINGSPACE", ParserContext.PARSER_ALLOW) data = ( (" 10102", "00010102"), ("2001 102", "20010102"), ("200101 2", "20010102"), (" 10102T010101", "00010102T010101"), ("2001 102T010101", "20010102T010101"), ("200101 2T010101", "20010102T010101"), ) for item, result in data: dt = PyCalendarDateTime.parseText(item) self.assertEqual(str(dt), result) def testCachePreserveOnAdjustment(self): # UTC first dt = PyCalendarDateTime(2012, 6, 7, 12, 0, 0, PyCalendarTimezone(tzid="utc")) dt.getPosixTime() # check existing cache is complete self.assertTrue(dt.mPosixTimeCached) self.assertNotEqual(dt.mPosixTime, 0) self.assertEqual(dt.mTZOffset, None) # duplicate preserves cache details dt2 = dt.duplicate() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, dt.mTZOffset) # adjust preserves cache details dt2.adjustToUTC() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, dt.mTZOffset) # Now timezone tzdata = """BEGIN:VCALENDAR CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:America/Pittsburgh 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 """.replace("\n", "\r\n") PyCalendar.parseText(tzdata) dt = PyCalendarDateTime(2012, 6, 7, 12, 0, 0, PyCalendarTimezone(tzid="America/Pittsburgh")) dt.getPosixTime() # check existing cache is complete self.assertTrue(dt.mPosixTimeCached) self.assertNotEqual(dt.mPosixTime, 0) self.assertEqual(dt.mTZOffset, -14400) # duplicate preserves cache details dt2 = dt.duplicate() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, dt.mTZOffset) # adjust preserves cache details dt2.adjustToUTC() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, 0) def testSetWeekNo(self): dt = PyCalendarDateTime(2013, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) self.assertEqual(dt.getWeekNo(), 1) dt.setWeekNo(1) self.assertEqual(dt, PyCalendarDateTime(2013, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) self.assertEqual(dt.getWeekNo(), 1) dt = PyCalendarDateTime(2013, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) self.assertEqual(dt.getWeekNo(), 1) dt.setWeekNo(2) self.assertEqual(dt, PyCalendarDateTime(2013, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) self.assertEqual(dt.getWeekNo(), 2) dt = PyCalendarDateTime(2013, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) self.assertEqual(dt.getWeekNo(), 2) dt.setWeekNo(1) self.assertEqual(dt, PyCalendarDateTime(2013, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) self.assertEqual(dt.getWeekNo(), 1) dt = PyCalendarDateTime(2014, 1, 7, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) self.assertEqual(dt.getWeekNo(), 2) dt.setWeekNo(1) self.assertEqual(dt, PyCalendarDateTime(2013, 12, 31, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) self.assertEqual(dt.getWeekNo(), 1) dt = PyCalendarDateTime(2012, 12, 31, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) self.assertEqual(dt.getWeekNo(), 1) dt.setWeekNo(1) self.assertEqual(dt, PyCalendarDateTime(2012, 12, 31, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) self.assertEqual(dt.getWeekNo(), 1) dt = PyCalendarDateTime(2016, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) self.assertEqual(dt.getWeekNo(), 53) dt.setWeekNo(1) self.assertEqual(dt, PyCalendarDateTime(2016, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) self.assertEqual(dt.getWeekNo(), 1) dt.setWeekNo(2) self.assertEqual(dt, PyCalendarDateTime(2016, 1, 15, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) self.assertEqual(dt.getWeekNo(), 2) dt = PyCalendarDateTime(2016, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) self.assertEqual(dt.getWeekNo(), 1) dt.setWeekNo(1) self.assertEqual(dt, PyCalendarDateTime(2016, 1, 8, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))) self.assertEqual(dt.getWeekNo(), 1) pycalendar-2.0~svn13177/src/pycalendar/tests/test_n.py0000644000175000017500000000510512101017573021754 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar.n import N import unittest class TestAdrValue(unittest.TestCase): def testInit(self): data = ( ( ("last", "first", "middle", "prefix", "suffix"), "last;first;middle;prefix;suffix", "prefix first middle last suffix", ), ( ("last", ("first",), ("middle1", "middle2",), (), ("suffix",)), "last;first;middle1,middle2;;suffix", "first middle1 middle2 last suffix", ), ) for args, result, fullName in data: n = N(*args) self.assertEqual( n.getValue(), args, ) self.assertEqual( n.getText(), result, ) self.assertEqual( n.getFullName(), fullName, ) self.assertEqual( n.duplicate().getText(), result, ) def testInitWithKeywords(self): data = ( ( {"first": "first", "last": "last", "middle": "middle", "prefix": "prefix", "suffix": "suffix"}, "last;first;middle;prefix;suffix", "prefix first middle last suffix", ), ( {"first": ("first",), "last": "last", "middle": ("middle1", "middle2",), "prefix": (), "suffix": ("suffix",)}, "last;first;middle1,middle2;;suffix", "first middle1 middle2 last suffix", ), ) for kwargs, result, fullName in data: n = N(**kwargs) self.assertEqual( n.getText(), result, ) self.assertEqual( n.getFullName(), fullName, ) self.assertEqual( n.duplicate().getText(), result, ) pycalendar-2.0~svn13177/src/pycalendar/tests/test_property.py0000644000175000017500000001600612146674036023421 0ustar rahulrahul## # Copyright (c) 2007-2013 Cyrus Daboo. 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 pycalendar.attribute import PyCalendarAttribute from pycalendar.exceptions import PyCalendarInvalidProperty from pycalendar.parser import ParserContext from pycalendar.property import PyCalendarProperty from pycalendar.value import PyCalendarValue import unittest class TestProperty(unittest.TestCase): test_data = ( # Different value types "ATTACH;VALUE=BINARY:VGVzdA==", "attach;VALUE=BINARY:VGVzdA==", "ORGANIZER:mailto:jdoe@example.com", "DTSTART;TZID=US/Eastern:20060226T120000", "DTSTART;VALUE=DATE:20060226", "DTSTART:20060226T130000Z", "X-FOO:BAR", "DURATION:PT10M", "duraTION:PT10M", "SEQUENCE:1", "RDATE:20060226T120000Z,20060227T120000Z", "FREEBUSY:20060226T120000Z/20060227T120000Z", "SUMMARY:Some \\ntext", "RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=-1", "REQUEST-STATUS:2.0;Success", "URI:http://www.example.com", "TZOFFSETFROM:-0500", "X-Test:Some\, text.", "X-Test:Some:, text.", "X-APPLE-STRUCTURED-LOCATION;VALUE=URI:geo:123.123,123.123", "X-CALENDARSERVER-PRIVATE-COMMENT:This\\ntest\\nis\\, here.\\n", # Various parameters "DTSTART;TZID=\"Somewhere, else\":20060226T120000", "ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:jdoe@example.com", "X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-APPLE-ABUID=ab\\://Work;X-TITLE=\"10\\n XX S. XXX Dr.\\nSuite XXX\\nXX XX XXXXX\\nUnited States\":\"geo:11.111111,-11.111111\"", # Parameter escaping "ATTENDEE;CN=My ^'Test^' Name;ROLE=CHAIR:mailto:jdoe@example.com", ) def testParseGenerate(self): for data in TestProperty.test_data: prop = PyCalendarProperty() prop.parse(data) propstr = str(prop).replace("\r\n ", "") self.assertEqual(propstr[:-2], data, "Failed parse/generate: %s to %s" % (data, propstr,)) def testEquality(self): for data in TestProperty.test_data: prop1 = PyCalendarProperty() prop1.parse(data) prop2 = PyCalendarProperty() prop2.parse(data) self.assertEqual(prop1, prop2, "Failed equality: %s" % (data,)) def testParseBad(self): test_bad_data = ( "DTSTART;TZID=US/Eastern:abc", "DTSTART;VALUE=DATE:20060226T", "DTSTART:20060226T120000A", "X-FOO;:BAR", "DURATION:A", "SEQUENCE:b", "RDATE:20060226T120000Z;20060227T120000Z", "FREEBUSY:20060226T120000Z/ABC", "SUMMARY:Some \\qtext", "RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,VE;BYSETPOS=-1", "TZOFFSETFROM:-050", """ATTENDEE;CN="\\";CUTYPE=INDIVIDUAL;PARTSTAT=X-UNDELIVERABLE:invalid:nomai l""", ) save = ParserContext.INVALID_ESCAPE_SEQUENCES for data in test_bad_data: ParserContext.INVALID_ESCAPE_SEQUENCES = ParserContext.PARSER_RAISE prop = PyCalendarProperty() self.assertRaises(PyCalendarInvalidProperty, prop.parse, data) ParserContext.INVALID_ESCAPE_SEQUENCES = save def testHash(self): hashes = [] for item in TestProperty.test_data: prop = PyCalendarProperty() prop.parse(item) hashes.append(hash(prop)) hashes.sort() for i in range(1, len(hashes)): self.assertNotEqual(hashes[i - 1], hashes[i]) def testDefaultValueCreate(self): test_data = ( ("ATTENDEE", "mailto:attendee@example.com", "ATTENDEE:mailto:attendee@example.com\r\n"), ("attendee", "mailto:attendee@example.com", "attendee:mailto:attendee@example.com\r\n"), ("ORGANIZER", "mailto:organizer@example.com", "ORGANIZER:mailto:organizer@example.com\r\n"), ("ORGANizer", "mailto:organizer@example.com", "ORGANizer:mailto:organizer@example.com\r\n"), ("URL", "http://example.com/tz1", "URL:http://example.com/tz1\r\n"), ("TZURL", "http://example.com/tz2", "TZURL:http://example.com/tz2\r\n"), ) for propname, propvalue, result in test_data: prop = PyCalendarProperty(name=propname, value=propvalue) self.assertEqual(str(prop), result) def testGEOValueRoundtrip(self): data = "GEO:123.456,789.101" prop = PyCalendarProperty() prop.parse(data) self.assertEqual(str(prop), data + "\r\n") def testUnknownValueRoundtrip(self): data = "X-FOO:Text, not escaped" prop = PyCalendarProperty() prop.parse(data) self.assertEqual(str(prop), data + "\r\n") prop = PyCalendarProperty("X-FOO", "Text, not escaped") self.assertEqual(str(prop), data + "\r\n") data = "X-FOO:Text\\, escaped\\n" prop = PyCalendarProperty() prop.parse(data) self.assertEqual(str(prop), data + "\r\n") prop = PyCalendarProperty("X-FOO", "Text\\, escaped\\n") self.assertEqual(str(prop), data + "\r\n") def testNewRegistrationValueRoundtrip(self): PyCalendarProperty.registerDefaultValue("X-SPECIAL-REGISTRATION", PyCalendarValue.VALUETYPE_TEXT) data = "X-SPECIAL-REGISTRATION:Text\\, escaped\\n" prop = PyCalendarProperty() prop.parse(data) self.assertEqual(str(prop), "X-SPECIAL-REGISTRATION:Text\\, escaped\\n\r\n") prop = PyCalendarProperty("X-SPECIAL-REGISTRATION", "Text, escaped\n") self.assertEqual(str(prop), "X-SPECIAL-REGISTRATION:Text\\, escaped\\n\r\n") def testParameterEncodingDecoding(self): prop = PyCalendarProperty("X-FOO", "Test") prop.addAttribute(PyCalendarAttribute("X-BAR", "\"Check\"")) self.assertEqual(str(prop), "X-FOO;X-BAR=^'Check^':Test\r\n") prop.addAttribute(PyCalendarAttribute("X-BAR2", "Check\nThis\tOut\n")) self.assertEqual(str(prop), "X-FOO;X-BAR=^'Check^';X-BAR2=Check^nThis\tOut^n:Test\r\n") data = "X-FOO;X-BAR=^'Check^':Test" prop = PyCalendarProperty() prop.parse(data) self.assertEqual(prop.getAttributeValue("X-BAR"), "\"Check\"") data = "X-FOO;X-BAR=^'Check^';X-BAR2=Check^nThis\tOut^n:Test" prop = PyCalendarProperty() prop.parse(data) self.assertEqual(prop.getAttributeValue("X-BAR"), "\"Check\"") self.assertEqual(prop.getAttributeValue("X-BAR2"), "Check\nThis\tOut\n") pycalendar-2.0~svn13177/src/pycalendar/tests/test_utils.py0000644000175000017500000000345012146674036022674 0ustar rahulrahul## # Copyright (c) 2012 Cyrus Daboo. 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. ## import unittest from pycalendar.utils import encodeParameterValue, decodeParameterValue class TestUtils(unittest.TestCase): def test_encodeParameterValue(self): """ Round trip encodeParameterValue and decodeParameterValue. """ data = ( ("abc", "abc", None), ("\"abc\"", "^'abc^'", None), ("abc\ndef", "abc^ndef", None), ("abc\rdef", "abc^ndef", "abc\ndef"), ("abc\r\ndef", "abc^ndef", "abc\ndef"), ("abc\n\tdef", "abc^n\tdef", None), ("abc^2", "abc^^2", None), ("^abc^", "^^abc^^", None), ) for value, encoded, decoded in data: if decoded is None: decoded = value self.assertEqual(encodeParameterValue(value), encoded) self.assertEqual(decodeParameterValue(encoded), decoded) def test_decodeParameterValue(self): """ Special cases for decodeParameterValue. """ data = ( ("^a^bc^", "^a^bc^"), ("^^^abc", "^^abc"), ) for value, decoded in data: self.assertEqual(decodeParameterValue(value), decoded) pycalendar-2.0~svn13177/src/pycalendar/tests/test_i18n.py0000644000175000017500000000377712101017573022313 0ustar rahulrahul# coding: utf-8 ## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.attribute import PyCalendarAttribute from pycalendar.calendar import PyCalendar import cStringIO as StringIO import unittest class TestCalendar(unittest.TestCase): def testAddCN(self): data = ( """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 ORGANIZER:user01@example.com SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), "まだ", """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 ORGANIZER;CN=まだ:user01@example.com SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ) cal1 = PyCalendar() cal1.parse(StringIO.StringIO(data[0])) vevent = cal1.getComponents("VEVENT")[0] organizer = vevent.getProperties("ORGANIZER")[0] organizer.addAttribute(PyCalendarAttribute("CN", data[1])) cal2 = PyCalendar() cal2.parse(StringIO.StringIO(data[2])) self.assertEqual(str(cal1), str(cal2)) pycalendar-2.0~svn13177/src/pycalendar/tests/test_validation.py0000644000175000017500000000560612101017573023657 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar.property import PyCalendarProperty from pycalendar.validation import partial, PropertyValueChecks import unittest class TestValidation(unittest.TestCase): def test_partial(self): def _test(a, b): return (a, b) self.assertEqual(partial(_test, "a", "b")(), ("a", "b",)) self.assertEqual(partial(_test, "a")("b"), ("a", "b",)) self.assertEqual(partial(_test)("a", "b"), ("a", "b",)) def test_stringValue(self): props = ( ("SUMMARY:Test", "Test", True,), ("SUMMARY:Test", "TEST", True,), ("DTSTART:20110623T174806", "Test", False), ) for prop, test, result in props: property = PyCalendarProperty() property.parse(prop) self.assertEqual(PropertyValueChecks.stringValue(test, property), result) def test_alwaysUTC(self): props = ( ("SUMMARY:Test", False,), ("DTSTART:20110623T174806", False), ("DTSTART;VALUE=DATE:20110623", False), ("DTSTART:20110623T174806Z", True), ) for prop, result in props: property = PyCalendarProperty() property.parse(prop) self.assertEqual(PropertyValueChecks.alwaysUTC(property), result) def test_numericRange(self): props = ( ("SUMMARY:Test", 0, 100, False,), ("PERCENT-COMPLETE:0", 0, 100, True,), ("PERCENT-COMPLETE:100", 0, 100, True,), ("PERCENT-COMPLETE:50", 0, 100, True,), ("PERCENT-COMPLETE:200", 0, 100, False,), ("PERCENT-COMPLETE:-1", 0, 100, False,), ) for prop, low, high, result in props: property = PyCalendarProperty() property.parse(prop) self.assertEqual(PropertyValueChecks.numericRange(low, high, property), result) def test_positiveIntegerOrZero(self): props = ( ("SUMMARY:Test", False,), ("REPEAT:0", True,), ("REPEAT:100", True,), ("REPEAT:-1", False,), ) for prop, result in props: property = PyCalendarProperty() property.parse(prop) self.assertEqual(PropertyValueChecks.positiveIntegerOrZero(property), result) pycalendar-2.0~svn13177/src/pycalendar/tests/test_calendar.py0000644000175000017500000004765212101017573023305 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.calendar import PyCalendar from pycalendar.datetime import PyCalendarDateTime from pycalendar.exceptions import PyCalendarInvalidData from pycalendar.parser import ParserContext from pycalendar.period import PyCalendarPeriod from pycalendar.property import PyCalendarProperty import cStringIO as StringIO import difflib import unittest class TestCalendar(unittest.TestCase): data = ( """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"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN X-WR-CALNAME:PayDay BEGIN:VTIMEZONE TZID:US/Eastern 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:DC3D0301C7790B38631F1FBB@ninevah.local DTSTART;VALUE=DATE:20040227 DTSTAMP:20050211T173501Z RRULE:FREQ=MONTHLY;BYDAY=-1MO,-1TU,-1WE,-1TH,-1FR;BYSETPOS=-1 SUMMARY:PAY DAY BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Alarm for Organizer! TRIGGER;RELATED=START:-PT15M END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:12345-67890-3 DTSTART:20071114T000000Z ATTENDEE:mailto:user2@example.com EXDATE:20081114T000000Z ORGANIZER:mailto:user1@example.com RRULE:FREQ=YEARLY END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 ATTACH:http://example.com/test.jpg DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 ATTACH;ENCODING=BASE64;VALUE=BINARY:dGVzdA== DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:VAVAILABILITY UID:20061005T133225Z-00001-availability@example.com DTSTART;TZID=America/Montreal:20060101T000000 DTEND;TZID=America/Montreal:20060108T000000 DTSTAMP:20061005T133225Z ORGANIZER:mailto:bernard@example.com BEGIN:AVAILABLE UID:20061005T133225Z-00001-A-availability@example.com DTSTART;TZID=America/Montreal:20060102T090000 DTEND;TZID=America/Montreal:20060102T120000 DTSTAMP:20061005T133225Z RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR SUMMARY:Monday\\, Wednesday and Friday from 9:00 to 12:00 END:AVAILABLE BEGIN:AVAILABLE UID:20061005T133225Z-00001-A-availability@example.com RECURRENCE-ID;TZID=America/Montreal:20060106T090000 DTSTART;TZID=America/Montreal:20060106T120000 DTEND;TZID=America/Montreal:20060106T170000 DTSTAMP:20061005T133225Z SUMMARY:Friday override from 12:00 to 17:00 END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//Example Inc.//Example Calendar//EN BEGIN:VTODO UID:event1@ninevah.local CREATED:20060101T150000Z DTSTAMP:20051222T205953Z SUMMARY:event 1 END:VTODO END:VCALENDAR """.replace("\n", "\r\n"), """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 BEGIN:X-COMPONENT UID:1234 END:X-COMPONENT END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//Apple Inc.//iCal 4.0.1//EN BEGIN:VTIMEZONE TZID:US/Pacific 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 BEGIN:VEVENT UID:uid4 DTSTART;TZID=US/Pacific:20100207T170000 DTEND;TZID=US/Pacific:20100207T173000 CREATED:20100203T013849Z DTSTAMP:20100203T013909Z SEQUENCE:3 SUMMARY:New Event TRANSP:OPAQUE BEGIN:VALARM ACTION:AUDIO ATTACH:Basso TRIGGER:-PT20M X-WR-ALARMUID:1377CCC7-F85C-4610-8583-9513D4B364E1 END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 ATTACH:http://example.com/test.jpg DTSTAMP:20020101T000000Z SUMMARY:New Year's Day X-APPLE-STRUCTURED-LOCATION:geo:123.123,123.123 X-Test:Some\, text. END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), """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 ATTACH:http://example.com/test.jpg DTSTAMP:20020101T000000Z SUMMARY:New Year's Day X-APPLE-STRUCTURED-LOCATION;VALUE=URI:geo:123.123,123.123 X-Test:Some\, text. END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ) data2 = ( ( """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:12345-67890-3 DTSTART:20071114T000000Z ATTENDEE:mailto:user2@example.com EXDATE:20081114T000000Z ORGANIZER:mailto:user1@example.com RRULE:FREQ=YEARLY END:VEVENT X-TEST:Testing END:VCALENDAR """.replace("\n", "\r\n"), """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//mulberrymail.com//Mulberry v4.0//EN X-TEST:Testing BEGIN:VEVENT UID:12345-67890-3 DTSTART:20071114T000000Z ATTENDEE:mailto:user2@example.com EXDATE:20081114T000000Z ORGANIZER:mailto:user1@example.com RRULE:FREQ=YEARLY END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ), ) def testRoundtrip(self): def _doRoundtrip(caldata, resultdata=None): test1 = resultdata if resultdata is not None else caldata cal = PyCalendar() cal.parse(StringIO.StringIO(caldata)) s = StringIO.StringIO() cal.generate(s) test2 = s.getvalue() self.assertEqual( test1, test2, "\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines())) ) for item in self.data: _doRoundtrip(item) for item1, item2 in self.data2: _doRoundtrip(item1, item2) def testRoundtripDuplicate(self): def _doDuplicateRoundtrip(caldata): cal = PyCalendar() cal.parse(StringIO.StringIO(caldata)) cal = cal.duplicate() s = StringIO.StringIO() cal.generate(s) self.assertEqual(caldata, s.getvalue()) for item in self.data: _doDuplicateRoundtrip(item) def testEquality(self): def _doEquality(caldata): cal1 = PyCalendar() cal1.parse(StringIO.StringIO(caldata)) cal2 = PyCalendar() cal2.parse(StringIO.StringIO(caldata)) self.assertEqual(cal1, cal2, "%s\n\n%s" % (cal1, cal2,)) def _doNonEquality(caldata): cal1 = PyCalendar() cal1.parse(StringIO.StringIO(caldata)) cal2 = PyCalendar() cal2.parse(StringIO.StringIO(caldata)) cal2.addProperty(PyCalendarProperty("X-FOO", "BAR")) self.assertNotEqual(cal1, cal2) for item in self.data: _doEquality(item) _doNonEquality(item) 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 = PyCalendar() cal.parse(StringIO.StringIO(data1)) cal.parseComponent(StringIO.StringIO(data2)) self.assertEqual(str(cal), result) def testParseFail(self): data = ( """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 """.replace("\n", "\r\n"), """BEGIN:VCARD PRODID:-//mulberrymail.com//Mulberry v4.0//EN VERSION:2.0 END:VCARD """.replace("\n", "\r\n"), """BOGUS 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"), """BOGUS 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"), """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 BOGUS """.replace("\n", "\r\n"), """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 BOGUS """.replace("\n", "\r\n"), ) for item in data: self.assertRaises(PyCalendarInvalidData, PyCalendar.parseText, item) def testParseBlank(self): data = ( """ 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"), """ 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"), """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"), """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"), """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"), ) save = ParserContext.BLANK_LINES_IN_DATA for item in data: ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_RAISE self.assertRaises(PyCalendarInvalidData, PyCalendar.parseText, item) ParserContext.BLANK_LINES_IN_DATA = ParserContext.PARSER_IGNORE lines = item.split("\r\n") result = "\r\n".join([line for line in lines if line]) + "\r\n" self.assertEqual(str(PyCalendar.parseText(item)), result) ParserContext.BLANK_LINES_IN_DATA = save def testGetVEvents(self): data = ( ( "Non-recurring match", """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:20110601 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (PyCalendarDateTime(2011, 6, 1),), ), ( "Non-recurring no-match", """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:20110501 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (), ), ( "Recurring match", """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:20110601 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ( PyCalendarDateTime(2011, 6, 1), PyCalendarDateTime(2011, 6, 2), ), ), ( "Recurring no match", """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:20110501 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (), ), ( "Recurring with override match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART:20110601T120000 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;VALUE=DATE:20110602T120000 DTSTART;VALUE=DATE:20110602T130000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ( PyCalendarDateTime(2011, 6, 1, 12, 0, 0), PyCalendarDateTime(2011, 6, 2, 13, 0, 0), ), ), ( "Recurring with override no match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART:20110501T120000 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;VALUE=DATE:20110502T120000 DTSTART;VALUE=DATE:20110502T130000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), (), ), ( "Recurring partial match", """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:20110531 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ( PyCalendarDateTime(2011, 6, 1), ), ), ( "Recurring with override partial match", """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//mulberrymail.com//Mulberry v4.0//EN BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 DTSTART:20110531T120000 DURATION:P1D DTSTAMP:20020101T000000Z RRULE:FREQ=DAILY;COUNT=2 SUMMARY:New Year's Day END:VEVENT BEGIN:VEVENT UID:C3184A66-1ED0-11D9-A5E0-000A958A3252 RECURRENCE-ID;VALUE=DATE:20110601T120000 DTSTART;VALUE=DATE:20110601T130000 DURATION:P1D DTSTAMP:20020101T000000Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n"), ( PyCalendarDateTime(2011, 6, 1, 13, 0, 0), ), ), ) for title, caldata, result in data: calendar = PyCalendar.parseText(caldata) instances = [] calendar.getVEvents( PyCalendarPeriod( start=PyCalendarDateTime(2011, 6, 1), end=PyCalendarDateTime(2011, 7, 1), ), instances ) instances = tuple([instance.getInstanceStart() for instance in instances]) self.assertEqual(instances, result, "Failed in %s: got %s, expected %s" % (title, instances, result)) pycalendar-2.0~svn13177/src/pycalendar/tests/test_urivalue.py0000644000175000017500000000653212101017573023360 0ustar rahulrahul## # Copyright (c) 2012 Cyrus Daboo. 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 pycalendar.parser import ParserContext from pycalendar.urivalue import PyCalendarURIValue from pycalendar.vcard.property import Property import unittest class TestNValue(unittest.TestCase): def testParseValue(self): # Restore BACKSLASH_IN_URI_VALUE after test old_state = ParserContext.BACKSLASH_IN_URI_VALUE self.addCleanup(setattr, ParserContext, "BACKSLASH_IN_URI_VALUE", old_state) # Test with BACKSLASH_IN_URI_VALUE = PARSER_FIX ParserContext.BACKSLASH_IN_URI_VALUE = ParserContext.PARSER_FIX items = ( ("http://example.com", "http://example.com"), ("http://example.com&abd\\,def", "http://example.com&abd,def"), ) for item, result in items: req = PyCalendarURIValue() req.parse(item) test = req.getText() self.assertEqual(test, result, "Failed to parse and re-generate '%s'" % (item,)) # Test with BACKSLASH_IN_URI_VALUE = PARSER_ALLOW ParserContext.BACKSLASH_IN_URI_VALUE = ParserContext.PARSER_ALLOW items = ( ("http://example.com", "http://example.com"), ("http://example.com&abd\\,def", "http://example.com&abd\\,def"), ) for item, result in items: req = PyCalendarURIValue() req.parse(item) test = req.getText() self.assertEqual(test, result, "Failed to parse and re-generate '%s'" % (item,)) def testParseProperty(self): # Restore BACKSLASH_IN_URI_VALUE after test old_state = ParserContext.BACKSLASH_IN_URI_VALUE self.addCleanup(setattr, ParserContext, "BACKSLASH_IN_URI_VALUE", old_state) # Test with BACKSLASH_IN_URI_VALUE = PARSER_FIX ParserContext.BACKSLASH_IN_URI_VALUE = ParserContext.PARSER_FIX items = ( ("URL:http://example.com", "URL:http://example.com"), ("URL:http://example.com&abd\\,def", "URL:http://example.com&abd,def"), ) for item, result in items: prop = Property() prop.parse(item) test = prop.getText() self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,)) # Test with BACKSLASH_IN_URI_VALUE = PARSER_ALLOW ParserContext.BACKSLASH_IN_URI_VALUE = ParserContext.PARSER_ALLOW items = ( ("URL:http://example.com", "URL:http://example.com"), ("URL:http://example.com&abd\\,def", "URL:http://example.com&abd\\,def"), ) for item, result in items: prop = Property() prop.parse(item) test = prop.getText() self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,)) pycalendar-2.0~svn13177/src/pycalendar/tests/test_recurrence.py0000644000175000017500000003271612320545600023664 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar.datetime import PyCalendarDateTime from pycalendar.period import PyCalendarPeriod from pycalendar.recurrence import PyCalendarRecurrence from pycalendar.timezone import PyCalendarTimezone import unittest class TestRecurrence(unittest.TestCase): items = ( "FREQ=DAILY", "FREQ=YEARLY;COUNT=400", "FREQ=MONTHLY;UNTIL=20110102", "FREQ=MONTHLY;UNTIL=20110102T090000", "FREQ=MONTHLY;UNTIL=20110102T100000Z", "FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=-1", # These are from RFC5545 examples "FREQ=YEARLY;INTERVAL=2;BYMINUTE=30;BYHOUR=8,9;BYDAY=SU;BYMONTH=1", "FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4", "FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4", "FREQ=YEARLY;BYDAY=2SU;BYMONTH=3", "FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10", "FREQ=DAILY;INTERVAL=2", "FREQ=DAILY;COUNT=5;INTERVAL=10", "FREQ=DAILY;UNTIL=20000131T140000Z;BYMONTH=1", "FREQ=WEEKLY;INTERVAL=2;WKST=SU", "FREQ=WEEKLY;UNTIL=19971007T000000Z;BYDAY=TU,TH;WKST=SU", "FREQ=WEEKLY;COUNT=10;BYDAY=TU,TH;WKST=SU", "FREQ=WEEKLY;UNTIL=19971224T000000Z;INTERVAL=2;BYDAY=MO,WE,FR;WKST=SU", "FREQ=WEEKLY;COUNT=8;INTERVAL=2;BYDAY=TU,TH;WKST=SU", "FREQ=MONTHLY;BYMONTHDAY=-3", "FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1", "FREQ=MONTHLY;COUNT=10;INTERVAL=18;BYMONTHDAY=10,11,12,13,14,15", "FREQ=YEARLY;COUNT=10;INTERVAL=3;BYYEARDAY=1,100,200", "FREQ=YEARLY;BYDAY=MO;BYWEEKNO=20", "FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3", "FREQ=DAILY;BYMINUTE=0,20,40;BYHOUR=9,10,11,12,13,14,15,16", ) def testParse(self): for item in TestRecurrence.items: recur = PyCalendarRecurrence() recur.parse(item) self.assertEqual(recur.getText(), item, "Failed to parse and re-generate '%s'" % (item,)) def testParseInvalid(self): items = ( "", "FREQ=", "FREQ=MICROSECONDLY", "FREQ=YEARLY;COUNT=ABC", "FREQ=YEARLY;COUNT=123;UNTIL=20110102", "FREQ=MONTHLY;UNTIL=20110102T", "FREQ=MONTHLY;UNTIL=20110102t090000", "FREQ=MONTHLY;UNTIL=20110102T100000z", "FREQ=MONTHLY;UNTIL=20110102TAABBCCz", "FREQ=MONTHLY;BYDAY=A", "FREQ=MONTHLY;BYDAY=+1,3MO", "FREQ=MONTHLY;BYHOUR=A", "FREQ=MONTHLY;BYHOUR=54", ) for item in items: self.assertRaises(ValueError, PyCalendarRecurrence().parse, item) def testEquality(self): recur1 = PyCalendarRecurrence() recur1.parse("FREQ=YEARLY;COUNT=400") recur2 = PyCalendarRecurrence() recur2.parse("COUNT=400;FREQ=YEARLY") self.assertEqual(recur1, recur2) def testInequality(self): recur1 = PyCalendarRecurrence() recur1.parse("FREQ=YEARLY;COUNT=400") recur2 = PyCalendarRecurrence() recur2.parse("COUNT=400;FREQ=YEARLY;BYMONTH=1") self.assertNotEqual(recur1, recur2) def testHash(self): hashes = [] for item in TestRecurrence.items: recur = PyCalendarRecurrence() recur.parse(item) hashes.append(hash(recur)) hashes.sort() for i in range(1, len(hashes)): self.assertNotEqual(hashes[i - 1], hashes[i]) def testByWeekNoExpand(self): recur = PyCalendarRecurrence() recur.parse("FREQ=YEARLY;BYWEEKNO=1,2") start = PyCalendarDateTime(2013, 1, 1, 0, 0, 0) end = PyCalendarDateTime(2017, 1, 1, 0, 0, 0) items = [] range = PyCalendarPeriod(start, end) recur.expand(start, range, items) self.assertEqual( items, [ PyCalendarDateTime(2013, 1, 1, 0, 0, 0), PyCalendarDateTime(2013, 1, 8, 0, 0, 0), PyCalendarDateTime(2014, 1, 1, 0, 0, 0), PyCalendarDateTime(2014, 1, 8, 0, 0, 0), PyCalendarDateTime(2015, 1, 1, 0, 0, 0), PyCalendarDateTime(2015, 1, 8, 0, 0, 0), PyCalendarDateTime(2016, 1, 8, 0, 0, 0), PyCalendarDateTime(2016, 1, 15, 0, 0, 0), ], ) def testMonthlyInvalidStart(self): recur = PyCalendarRecurrence() recur.parse("FREQ=MONTHLY") start = PyCalendarDateTime(2014, 1, 40, 12, 0, 0) end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0) items = [] range = PyCalendarPeriod(start, end) recur.expand(start, range, items) self.assertEqual( items, [ PyCalendarDateTime(2014, 2, 9, 12, 0, 0), PyCalendarDateTime(2014, 3, 9, 12, 0, 0), PyCalendarDateTime(2014, 4, 9, 12, 0, 0), PyCalendarDateTime(2014, 5, 9, 12, 0, 0), PyCalendarDateTime(2014, 6, 9, 12, 0, 0), PyCalendarDateTime(2014, 7, 9, 12, 0, 0), PyCalendarDateTime(2014, 8, 9, 12, 0, 0), PyCalendarDateTime(2014, 9, 9, 12, 0, 0), PyCalendarDateTime(2014, 10, 9, 12, 0, 0), PyCalendarDateTime(2014, 11, 9, 12, 0, 0), PyCalendarDateTime(2014, 12, 9, 12, 0, 0), ], ) def testMonthlyInUTC(self): recur = PyCalendarRecurrence() recur.parse("FREQ=MONTHLY") start = PyCalendarDateTime(2014, 1, 1, 12, 0, 0, tzid=PyCalendarTimezone(utc=True)) end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True)) items = [] range = PyCalendarPeriod(start, end) recur.expand(PyCalendarDateTime(2014, 1, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), range, items) self.assertEqual( items, [ PyCalendarDateTime(2014, 1, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), PyCalendarDateTime(2014, 2, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), PyCalendarDateTime(2014, 3, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), PyCalendarDateTime(2014, 4, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), PyCalendarDateTime(2014, 5, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), PyCalendarDateTime(2014, 6, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), PyCalendarDateTime(2014, 7, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), PyCalendarDateTime(2014, 8, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), PyCalendarDateTime(2014, 9, 1, 12, 0, 0, tzid=PyCalendarTimezone(tzid="America/New_York")), PyCalendarDateTime(2014, 10, 1, 12, 0, 0), PyCalendarDateTime(2014, 11, 1, 12, 0, 0), PyCalendarDateTime(2014, 12, 1, 12, 0, 0), ], ) def testMonthlyStart31st(self): recur = PyCalendarRecurrence() recur.parse("FREQ=MONTHLY") start = PyCalendarDateTime(2014, 1, 31, 12, 0, 0) end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0) items = [] range = PyCalendarPeriod(start, end) recur.expand(start, range, items) self.assertEqual( items, [ PyCalendarDateTime(2014, 1, 31, 12, 0, 0), PyCalendarDateTime(2014, 3, 31, 12, 0, 0), PyCalendarDateTime(2014, 5, 31, 12, 0, 0), PyCalendarDateTime(2014, 7, 31, 12, 0, 0), PyCalendarDateTime(2014, 8, 31, 12, 0, 0), PyCalendarDateTime(2014, 10, 31, 12, 0, 0), PyCalendarDateTime(2014, 12, 31, 12, 0, 0), ], ) def testMonthlyByMonthDay31(self): recur = PyCalendarRecurrence() recur.parse("FREQ=MONTHLY;BYMONTHDAY=31") start = PyCalendarDateTime(2014, 1, 31, 12, 0, 0) end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0) items = [] range = PyCalendarPeriod(start, end) recur.expand(start, range, items) self.assertEqual( items, [ PyCalendarDateTime(2014, 1, 31, 12, 0, 0), PyCalendarDateTime(2014, 3, 31, 12, 0, 0), PyCalendarDateTime(2014, 5, 31, 12, 0, 0), PyCalendarDateTime(2014, 7, 31, 12, 0, 0), PyCalendarDateTime(2014, 8, 31, 12, 0, 0), PyCalendarDateTime(2014, 10, 31, 12, 0, 0), PyCalendarDateTime(2014, 12, 31, 12, 0, 0), ], ) def testMonthlyByMonthDayMinus31(self): recur = PyCalendarRecurrence() recur.parse("FREQ=MONTHLY;BYMONTHDAY=-31") start = PyCalendarDateTime(2014, 1, 1, 12, 0, 0) end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0) items = [] range = PyCalendarPeriod(start, end) recur.expand(start, range, items) self.assertEqual( items, [ PyCalendarDateTime(2014, 1, 1, 12, 0, 0), PyCalendarDateTime(2014, 3, 1, 12, 0, 0), PyCalendarDateTime(2014, 5, 1, 12, 0, 0), PyCalendarDateTime(2014, 7, 1, 12, 0, 0), PyCalendarDateTime(2014, 8, 1, 12, 0, 0), PyCalendarDateTime(2014, 10, 1, 12, 0, 0), PyCalendarDateTime(2014, 12, 1, 12, 0, 0), ], ) def testMonthlyByLastFridayExpand(self): recur = PyCalendarRecurrence() recur.parse("FREQ=MONTHLY;BYDAY=-1FR") start = PyCalendarDateTime(2014, 1, 31, 12, 0, 0) end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0) items = [] range = PyCalendarPeriod(start, end) recur.expand(start, range, items) self.assertEqual( items, [ PyCalendarDateTime(2014, 1, 31, 12, 0, 0), PyCalendarDateTime(2014, 2, 28, 12, 0, 0), PyCalendarDateTime(2014, 3, 28, 12, 0, 0), PyCalendarDateTime(2014, 4, 25, 12, 0, 0), PyCalendarDateTime(2014, 5, 30, 12, 0, 0), PyCalendarDateTime(2014, 6, 27, 12, 0, 0), PyCalendarDateTime(2014, 7, 25, 12, 0, 0), PyCalendarDateTime(2014, 8, 29, 12, 0, 0), PyCalendarDateTime(2014, 9, 26, 12, 0, 0), PyCalendarDateTime(2014, 10, 31, 12, 0, 0), PyCalendarDateTime(2014, 11, 28, 12, 0, 0), PyCalendarDateTime(2014, 12, 26, 12, 0, 0), ], ) def testMonthlyByFifthFridayExpand(self): recur = PyCalendarRecurrence() recur.parse("FREQ=MONTHLY;BYDAY=5FR") start = PyCalendarDateTime(2014, 1, 31, 12, 0, 0) end = PyCalendarDateTime(2015, 1, 1, 0, 0, 0) items = [] range = PyCalendarPeriod(start, end) recur.expand(start, range, items) self.assertEqual( items, [ PyCalendarDateTime(2014, 1, 31, 12, 0, 0), PyCalendarDateTime(2014, 5, 30, 12, 0, 0), PyCalendarDateTime(2014, 8, 29, 12, 0, 0), PyCalendarDateTime(2014, 10, 31, 12, 0, 0), ], ) def testYearlyLeapDay(self): recur = PyCalendarRecurrence() recur.parse("FREQ=YEARLY") start = PyCalendarDateTime(2012, 2, 29, 12, 0, 0) end = PyCalendarDateTime(2020, 1, 1, 0, 0, 0) items = [] range = PyCalendarPeriod(start, end) recur.expand(start, range, items) self.assertEqual( items, [ PyCalendarDateTime(2012, 2, 29, 12, 0, 0), PyCalendarDateTime(2016, 2, 29, 12, 0, 0), ], ) def testYearlyYearDay(self): recur = PyCalendarRecurrence() recur.parse("FREQ=YEARLY;BYYEARDAY=366") start = PyCalendarDateTime(2012, 12, 31, 12, 0, 0) end = PyCalendarDateTime(2020, 1, 1, 0, 0, 0) items = [] range = PyCalendarPeriod(start, end) recur.expand(start, range, items) self.assertEqual( items, [ PyCalendarDateTime(2012, 12, 31, 12, 0, 0), PyCalendarDateTime(2016, 12, 31, 12, 0, 0), ], ) def testClearOnChange(self): recur = PyCalendarRecurrence() recur.parse("FREQ=DAILY") start = PyCalendarDateTime(2013, 1, 1, 0, 0, 0) end = PyCalendarDateTime(2017, 1, 1, 0, 0, 0) range = PyCalendarPeriod(start, end) items = [] recur.expand(start, range, items) self.assertTrue(recur.mCached) self.assertTrue(len(items) > 100) recur.setUseCount(True) recur.setCount(10) self.assertFalse(recur.mCached) items = [] recur.expand(start, range, items) self.assertEqual(len(items), 10) pycalendar-2.0~svn13177/src/pycalendar/tests/test_adrvalue.py0000644000175000017500000000474212101017573023330 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar.adrvalue import AdrValue from pycalendar.vcard.property import Property import unittest class TestAdrValue(unittest.TestCase): def testParseValue(self): items = ( ("", ";;;;;;"), (";", ";;;;;;"), (";;;;;;", ";;;;;;"), (";;123 Main Street;Any Town;CA;91921-1234", ";;123 Main Street;Any Town;CA;91921-1234;"), (";;;;;;USA", ";;;;;;USA"), ("POB1", "POB1;;;;;;"), (";EXT", ";EXT;;;;;"), (";;123 Main Street,The Cards;Any Town;CA;91921-1234", ";;123 Main Street,The Cards;Any Town;CA;91921-1234;"), (";;123 Main\, Street,The Cards;Any Town;CA;91921-1234", ";;123 Main\, Street,The Cards;Any Town;CA;91921-1234;"), (";;123 Main\, Street,The\, Cards;Any Town;CA;91921-1234", ";;123 Main\, Street,The\, Cards;Any Town;CA;91921-1234;"), ) for item, result in items: req = AdrValue() req.parse(item) test = req.getText() self.assertEqual(test, result, "Failed to parse and re-generate '%s'" % (item,)) def testParseProperty(self): items = ( ("ADR:", "ADR:;;;;;;"), ("ADR:;", "ADR:;;;;;;"), ("ADR:;;;;;;", "ADR:;;;;;;"), ("ADR:;;123 Main Street;Any Town;CA;91921-1234", "ADR:;;123 Main Street;Any Town;CA;91921-1234;"), ("ADR:;;;;;;USA", "ADR:;;;;;;USA"), ("ADR:POB1", "ADR:POB1;;;;;;"), ("ADR:;EXT", "ADR:;EXT;;;;;"), ("ADR;VALUE=TEXT:;;123 Main Street;Any Town;CA;91921-1234", "ADR:;;123 Main Street;Any Town;CA;91921-1234;"), ) for item, result in items: prop = Property() prop.parse(item) test = prop.getText() self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,)) pycalendar-2.0~svn13177/src/pycalendar/tests/test_timezone.py0000644000175000017500000003503612317143440023361 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.calendar import PyCalendar from pycalendar.datetime import PyCalendarDateTime from pycalendar.timezone import PyCalendarTimezone import unittest class TestCalendar(unittest.TestCase): def testOffsets(self): data = ( ("""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 """, ( (PyCalendarDateTime(1942, 2, 8), False, -5), (PyCalendarDateTime(1942, 2, 10), False, -4), (PyCalendarDateTime(2011, 1, 1), False, -5), (PyCalendarDateTime(2011, 4, 1), False, -4), (PyCalendarDateTime(2011, 10, 24), False, -4), (PyCalendarDateTime(2011, 11, 8), False, -5), (PyCalendarDateTime(2006, 1, 1), False, -5), (PyCalendarDateTime(2006, 4, 1), False, -5), (PyCalendarDateTime(2006, 5, 1), False, -4), (PyCalendarDateTime(2006, 10, 1), False, -4), (PyCalendarDateTime(2006, 10, 24), False, -4), (PyCalendarDateTime(2006, 11, 8), False, -5), (PyCalendarDateTime(2014, 3, 8, 23, 0, 0), False, -5), (PyCalendarDateTime(2014, 3, 9, 0, 0, 0), False, -5), (PyCalendarDateTime(2014, 3, 9, 3, 0, 0), False, -4), (PyCalendarDateTime(2014, 3, 9, 8, 0, 0), False, -4), (PyCalendarDateTime(2014, 3, 8, 23, 0, 0), True, -5), (PyCalendarDateTime(2014, 3, 9, 0, 0, 0), True, -5), (PyCalendarDateTime(2014, 3, 9, 3, 0, 0), True, -5), (PyCalendarDateTime(2014, 3, 9, 8, 0, 0), True, -4), (PyCalendarDateTime(2014, 11, 1, 23, 0, 0), False, -4), (PyCalendarDateTime(2014, 11, 2, 0, 0, 0), False, -4), (PyCalendarDateTime(2014, 11, 2, 3, 0, 0), False, -5), (PyCalendarDateTime(2014, 11, 2, 8, 0, 0), False, -5), (PyCalendarDateTime(2014, 11, 1, 23, 0, 0), True, -4), (PyCalendarDateTime(2014, 11, 2, 0, 0, 0), True, -4), (PyCalendarDateTime(2014, 11, 2, 3, 0, 0), True, -4), (PyCalendarDateTime(2014, 11, 2, 8, 0, 0), True, -5), ) ), ("""BEGIN:VCALENDAR CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:Etc/GMT+8 X-LIC-LOCATION:Etc/GMT+8 BEGIN:STANDARD DTSTART:18000101T000000 RDATE:18000101T000000 TZNAME:GMT+8 TZOFFSETFROM:-0800 TZOFFSETTO:-0800 END:STANDARD END:VTIMEZONE END:VCALENDAR """, ( (PyCalendarDateTime(1942, 2, 8), False, -8), (PyCalendarDateTime(1942, 2, 10), False, -8), (PyCalendarDateTime(2011, 1, 1), False, -8), (PyCalendarDateTime(2011, 4, 1), False, -8), ) ), ) for tzdata, offsets in data: cal = PyCalendar.parseText(tzdata.replace("\n", "\r\n")) tz = cal.getComponents()[0] for dt, relative_to_utc, offset in offsets: tzoffset = tz.getTimezoneOffsetSeconds(dt, relative_to_utc) self.assertEqual(tzoffset, offset * 60 * 60, "Failed to match offset for %s at %s with caching" % (tz.getID(), dt,)) for dt, relative_to_utc, offset in reversed(offsets): tzoffset = tz.getTimezoneOffsetSeconds(dt, relative_to_utc) self.assertEqual(tzoffset, offset * 60 * 60, "Failed to match offset for %s at %s with caching, reversed" % (tz.getID(), dt,)) for dt, relative_to_utc, offset in offsets: tz.mCachedExpandAllMax = None tzoffset = tz.getTimezoneOffsetSeconds(dt, relative_to_utc) self.assertEqual(tzoffset, offset * 60 * 60, "Failed to match offset for %s at %s without caching" % (tz.getID(), dt,)) for dt, relative_to_utc, offset in reversed(offsets): tz.mCachedExpandAllMax = None tzoffset = tz.getTimezoneOffsetSeconds(dt, relative_to_utc) self.assertEqual(tzoffset, offset * 60 * 60, "Failed to match offset for %s at %s without caching, reversed" % (tz.getID(), dt,)) def testConversions(self): tzdata = """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 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 """ data = ( ( PyCalendarDateTime(2014, 3, 8, 23, 0, 0, PyCalendarTimezone(tzid="America/New_York")), PyCalendarDateTime(2014, 3, 8, 20, 0, 0, PyCalendarTimezone(tzid="America/Los_Angeles")), ), ( PyCalendarDateTime(2014, 3, 9, 3, 0, 0, PyCalendarTimezone(utc=True)), PyCalendarDateTime(2014, 3, 8, 19, 0, 0, PyCalendarTimezone(tzid="America/Los_Angeles")), ), ( PyCalendarDateTime(2014, 3, 9, 13, 0, 0, PyCalendarTimezone(utc=True)), PyCalendarDateTime(2014, 3, 9, 6, 0, 0, PyCalendarTimezone(tzid="America/Los_Angeles")), ), ) PyCalendar.parseText(tzdata.replace("\n", "\r\n")) for dtfrom, dtto in data: self.assertEqual(dtfrom, dtto) newdtfrom = dtfrom.duplicate() newdtfrom.adjustTimezone(dtto.getTimezone()) self.assertEqual(newdtfrom, dtto) self.assertEqual(newdtfrom.getHours(), dtto.getHours()) pycalendar-2.0~svn13177/src/pycalendar/tests/__init__.py0000644000175000017500000000120212101017573022211 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## pycalendar-2.0~svn13177/src/pycalendar/tests/test_multivalue.py0000644000175000017500000000312712101017573023710 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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. ## import unittest from pycalendar.multivalue import PyCalendarMultiValue from pycalendar.value import PyCalendarValue class TestMultiValue(unittest.TestCase): def testParseValue(self): items = ( ("", "", 1), ("Example", "Example", 1), ("Example1,Example2", "Example1,Example2", 2), ) for item, result, count in items: req = PyCalendarMultiValue(PyCalendarValue.VALUETYPE_TEXT) req.parse(item) test = req.getText() self.assertEqual(test, result, "Failed to parse and re-generate '%s'" % (item,)) self.assertEqual(len(req.mValues), count, "Failed to parse and re-generate '%s'" % (item,)) def testSetValue(self): req = PyCalendarMultiValue(PyCalendarValue.VALUETYPE_TEXT) req.parse("Example1, Example2") req.setValue(("Example3", "Example4",)) test = req.getText() self.assertEqual(test, "Example3,Example4") pycalendar-2.0~svn13177/src/pycalendar/utils.py0000644000175000017500000003337712127624310020473 0ustar rahulrahul## # Copyright (c) 2007-2013 Cyrus Daboo. 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 pycalendar.parser import ParserContext import cStringIO as StringIO def readFoldedLine(ins, lines): # If line2 already has data, transfer that into line1 if lines[1] is not None: lines[0] = lines[1] else: # Fill first line try: myline = ins.readline() if len(myline) == 0: raise ValueError if myline[-1] == "\n": if myline[-2] == "\r": lines[0] = myline[:-2] else: lines[0] = myline[:-1] elif myline[-1] == "\r": lines[0] = myline[:-1] else: lines[0] = myline except IndexError: lines[0] = "" except: lines[0] = None return False # Now loop looking ahead at the next line to see if it is folded while True: # Get next line try: myline = ins.readline() if len(myline) == 0: raise ValueError if myline[-1] == "\n": if myline[-2] == "\r": lines[1] = myline[:-2] else: lines[1] = myline[:-1] elif myline[-1] == "\r": lines[1] = myline[:-1] else: lines[1] = myline except IndexError: lines[1] = "" except: lines[1] = None return True if not lines[1]: return True # Does it start with a space => folded if lines[1][0].isspace(): # Copy folded line (without space) to current line and cycle # for more lines[0] = lines[0] + lines[1][1:] else: # Not folded - just exit loop break return True def find_first_of(text, tokens, offset): for ctr, c in enumerate(text[offset:]): if c in tokens: return offset + ctr return -1 def escapeTextValue(value): os = StringIO.StringIO() writeTextValue(os, value) return os.getvalue() def writeTextValue(os, value): try: start_pos = 0 end_pos = find_first_of(value, "\r\n;\\,", start_pos) if end_pos != -1: while True: # Write current segment os.write(value[start_pos:end_pos]) # Write escape os.write("\\") c = value[end_pos] if c == '\r': os.write("r") elif c == '\n': os.write("n") elif c == ';': os.write(";") elif c == '\\': os.write("\\") elif c == ',': os.write(",") # Bump past escapee and look for next segment start_pos = end_pos + 1 end_pos = find_first_of(value, "\r\n;\\,", start_pos) if end_pos == -1: os.write(value[start_pos:]) break else: os.write(value) except: pass def decodeTextValue(value): os = StringIO.StringIO() start_pos = 0 end_pos = find_first_of(value, "\\", start_pos) size_pos = len(value) if end_pos != -1: while True: # Write current segment upto but not including the escape char os.write(value[start_pos:end_pos]) # Bump to escapee char but not past the end end_pos += 1 if end_pos >= size_pos: break # Unescape c = value[end_pos] if c == 'r': os.write('\r') elif c == 'n': os.write('\n') elif c == 'N': os.write('\n') elif c == '': os.write('') elif c == '\\': os.write('\\') elif c == ',': os.write(',') elif c == ';': os.write(';') elif c == ':': # ":" escape normally invalid if ParserContext.INVALID_COLON_ESCAPE_SEQUENCE == ParserContext.PARSER_RAISE: raise ValueError elif ParserContext.INVALID_COLON_ESCAPE_SEQUENCE == ParserContext.PARSER_FIX: os.write(':') # Other escaped chars normally not allowed elif ParserContext.INVALID_ESCAPE_SEQUENCES == ParserContext.PARSER_RAISE: raise ValueError elif ParserContext.INVALID_ESCAPE_SEQUENCES == ParserContext.PARSER_FIX: os.write(c) # Bump past escapee and look for next segment (not past the end) start_pos = end_pos + 1 if start_pos >= size_pos: break end_pos = find_first_of(value, "\\", start_pos) if end_pos == -1: os.write(value[start_pos:]) break else: os.write(value) return os.getvalue() def encodeParameterValue(value): """ RFC6868 parameter encoding. """ encoded = [] last = '' for c in value: if c == '\r': encoded.append('^') encoded.append('n') elif c == '\n': if last != '\r': encoded.append('^') encoded.append('n') elif c == '"': encoded.append('^') encoded.append('\'') elif c == '^': encoded.append('^') encoded.append('^') else: encoded.append(c) last = c return "".join(encoded) def decodeParameterValue(value): """ RFC6868 parameter decoding. """ if value is None: return None decoded = [] last = '' for c in value: if last == '^': if c == 'n': decoded.append('\n') elif c == '\'': decoded.append('"') elif c == '^': decoded.append('^') c = '' else: decoded.append('^') decoded.append(c) elif c != '^': decoded.append(c) last = c if last == '^': decoded.append('^') return "".join(decoded) # vCard text list parsing/generation def parseTextList(data, sep=';', always_list=False): """ Each element of the list has to be separately un-escaped """ results = [] item = [] pre_s = '' for s in data: if s == sep and pre_s != '\\': results.append(decodeTextValue("".join(item))) item = [] else: item.append(s) pre_s = s results.append(decodeTextValue("".join(item))) return tuple(results) if len(results) > 1 or always_list else (results[0] if len(results) else "") def generateTextList(os, data, sep=';'): """ Each element of the list must be separately escaped """ try: if isinstance(data, basestring): data = (data,) results = [escapeTextValue(value) for value in data] os.write(sep.join(results)) except: pass # vCard double-nested list parsing/generation def parseDoubleNestedList(data, maxsize): results = [] items = [""] pre_s = '' for s in data: if s == ';' and pre_s != '\\': if len(items) > 1: results.append(tuple([decodeTextValue(item) for item in items])) elif len(items) == 1: results.append(decodeTextValue(items[0])) else: results.append("") items = [""] elif s == ',' and pre_s != '\\': items.append("") else: items[-1] += s pre_s = s if len(items) > 1: results.append(tuple([decodeTextValue(item) for item in items])) elif len(items) == 1: results.append(decodeTextValue(items[0])) else: results.append("") for _ignore in range(maxsize - len(results)): results.append("") if len(results) > maxsize: if ParserContext.INVALID_ADR_N_VALUES == ParserContext.PARSER_FIX: results = results[:maxsize] elif ParserContext.INVALID_ADR_N_VALUES == ParserContext.PARSER_RAISE: raise ValueError return tuple(results) def generateDoubleNestedList(os, data): try: def _writeElement(item): if isinstance(item, basestring): writeTextValue(os, item) else: if item: writeTextValue(os, item[0]) for bit in item[1:]: os.write(",") writeTextValue(os, bit) for item in data[:-1]: _writeElement(item) os.write(";") _writeElement(data[-1]) except: pass # Date/time calcs days_in_month = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) days_in_month_leap = (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) def daysInMonth(month, year): # NB month is 1..12 so use dummy value at start of array to avoid index # adjustment if isLeapYear(year): return days_in_month_leap[month] else: return days_in_month[month] days_upto_month = (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334) days_upto_month_leap = (0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335) def daysUptoMonth(month, year): # NB month is 1..12 so use dummy value at start of array to avoid index # adjustment if isLeapYear(year): return days_upto_month_leap[month] else: return days_upto_month[month] cachedLeapYears = {} def isLeapYear(year): try: return cachedLeapYears[year] except KeyError: if year <= 1752: result = (year % 4 == 0) else: result = ((year % 4 == 0) and (year % 100 != 0)) or (year % 400 == 0) cachedLeapYears[year] = result return result cachedLeapDaysSince1970 = {} def leapDaysSince1970(year_offset): try: return cachedLeapDaysSince1970[year_offset] except KeyError: if year_offset > 2: result = (year_offset + 1) / 4 elif year_offset < -1: # Python will round down negative numbers (i.e. -5/4 = -2, but we want -1), so # what is (year_offset - 2) in C code is actually (year_offset - 2 + 3) in Python. result = (year_offset + 1) / 4 else: result = 0 cachedLeapDaysSince1970[year_offset] = result return result # Packed date def packDate(year, month, day): return (year << 16) | (month << 8) | (day + 128) def unpackDate(data, unpacked): unpacked[0] = (data & 0xFFFF0000) >> 16 unpacked[1] = (data & 0x0000FF00) >> 8 unpacked[2] = (data & 0xFF) - 128 def unpackDateYear(data): return (data & 0xFFFF0000) >> 16 def unpackDateMonth(data): return (data & 0x0000FF00) >> 8 def unpackDateDay(data): return (data & 0xFF) - 128 # Display elements def getMonthTable(month, year, weekstart, table, today_index): from pycalendar.datetime import PyCalendarDateTime # Get today today = PyCalendarDateTime.getToday(None) today_index = [-1, -1] # Start with empty table table = [] # Determine first weekday in month temp = PyCalendarDateTime(year, month, 1, 0) row = -1 initial_col = temp.getDayOfWeek() - weekstart if initial_col < 0: initial_col += 7 col = initial_col # Counters max_day = daysInMonth(month, year) # Fill up each row for day in range(1, max_day + 1): # Insert new row if we are at the start of a row if (col == 0) or (day == 1): table.extend([0] * 7) row += 1 # Set the table item to the current day table[row][col] = packDate(temp.getYear(), temp.getMonth(), day) # Check on today if (temp.getYear() == today.getYear()) and (temp.getMonth() == today.getMonth()) and (day == today.getDay()): today_index = [row, col] # Bump column (modulo 7) col += 1 if (col > 6): col = 0 # Add next month to remainder temp.offsetMonth(1) if col != 0: day = 1 while col < 7: table[row][col] = packDate(temp.getYear(), temp.getMonth(), -day) # Check on today if (temp.getYear() == today.getYear()) and (temp.getMonth() == today.getMonth()) and (day == today.getDay()): today_index = [row, col] day += 1 col += 1 # Add previous month to start temp.offsetMonth(-2) if (initial_col != 0): day = daysInMonth(temp.getMonth(), temp.getYear()) back_col = initial_col - 1 while(back_col >= 0): table[row][back_col] = packDate(temp.getYear(), temp.getMonth(), -day) # Check on today if (temp.getYear() == today.getYear()) and (temp.getMonth() == today.getMonth()) and (day == today.getDay()): today_index = [0, back_col] back_col -= 1 day -= 1 return table, today_index def set_difference(v1, v2): if len(v1) == 0 or len(v2) == 0: return v1 s1 = set(v1) s2 = set(v2) s3 = s1.difference(s2) return list(s3) pycalendar-2.0~svn13177/src/pycalendar/locale.py0000644000175000017500000000352012101017573020554 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## LONG = 0 SHORT = 1 ABBREVIATED = 2 cLongDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] cShortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] cAbbrevDays = ["S", "M", "T", "W", "T", "F", "S"] cLongMonths = ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] cShortMonths = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] cAbbrevMonths = ["", "J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"] s24HourTime = False sDDMMDate = False # 0..6 - Sunday - Saturday def getDay(day, strl): return {LONG: cLongDays[day], SHORT: cShortDays[day], ABBREVIATED: cAbbrevDays[day]}[strl] # 1..12 - January - December def getMonth(month, strl): return {LONG: cLongMonths[month], SHORT: cShortMonths[month], ABBREVIATED: cAbbrevMonths[month]}[strl] # Use 24 hour time display def use24HourTime(): # TODO: get 24 hour option from system prefs return s24HourTime # Use DD/MM date display def useDDMMDate(): # TODO: get 24 hour option from system prefs return sDDMMDate pycalendar-2.0~svn13177/src/pycalendar/periodvalue.py0000644000175000017500000000304012101017573021631 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import xmldefs from pycalendar.period import PyCalendarPeriod from pycalendar.value import PyCalendarValue class PyCalendarPeriodValue(PyCalendarValue): def __init__(self, value=None): self.mValue = value if value is not None else PyCalendarPeriod() def duplicate(self): return PyCalendarPeriodValue(self.mValue.duplicate()) def getType(self): return PyCalendarValue.VALUETYPE_PERIOD def parse(self, data): self.mValue.parse(data) def generate(self, os): self.mValue.generate(os) def writeXML(self, node, namespace): value = self.getXMLNode(node, namespace) value.text = self.mValue.writeXML() def getValue(self): return self.mValue def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_PERIOD, PyCalendarPeriodValue, xmldefs.value_period) pycalendar-2.0~svn13177/src/pycalendar/exceptions.py0000644000175000017500000000212212101017573021473 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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. ## class PyCalendarError(Exception): def __init__(self, reason, data=""): self.mReason = reason self.mData = data class PyCalendarInvalidData(PyCalendarError): pass class PyCalendarInvalidProperty(PyCalendarError): pass class PyCalendarValidationError(PyCalendarError): pass class PyCalendarNoTimezoneInDatabase(Exception): def __init__(self, dbpath, tzid): self.mTZDBpath = dbpath self.mTZID = tzid pycalendar-2.0~svn13177/src/pycalendar/vtodo.py0000644000175000017500000003220512101017573020452 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar import itipdefinitions from pycalendar.componentrecur import PyCalendarComponentRecur from pycalendar.datetime import PyCalendarDateTime from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS from pycalendar.property import PyCalendarProperty import cStringIO as StringIO class PyCalendarVToDo(PyCalendarComponentRecur): OVERDUE = 0 DUE_NOW = 1 DUE_LATER = 2 DONE = 3 CANCELLED = 4 @staticmethod def sort_for_display(e1, e2): s1 = e1.getMaster() s2 = e2.getMaster() # Check status first (convert None -> Needs action for tests) status1 = s1.self.mStatus status2 = s2.self.mStatus if status1 == definitions.eStatus_VToDo_None: status1 = definitions.eStatus_VToDo_NeedsAction if status2 == definitions.eStatus_VToDo_None: status2 = definitions.eStatus_VToDo_NeedsAction if status1 != status2: # More important ones at the top return status1 < status2 # At this point the status of each is the same # If status is cancelled sort by start time if s1.self.mStatus == definitions.eStatus_VToDo_Cancelled: # Older ones at the bottom return s1.mStart > s2.mStart # If status is completed sort by completion time if s1.self.mStatus == definitions.eStatus_VToDo_Completed: # Older ones at the bottom return s1.self.mCompleted > s2.self.mCompleted # Check due date exists if s1.mHasEnd != s2.mHasEnd: now = PyCalendarDateTime() now.setToday() # Ones with due dates after today below ones without due dates if s1.hasEnd(): return s1.mEnd <= now elif s2.hasEnd(): return now < s2.mEnd # Check due dates if present if s1.mHasEnd: if s1.mEnd != s2.mEnd: # Soonest dues dates above later ones return s1.mEnd < s2.mEnd # Check priority next if s1.self.mPriority != s2.self.mPriority: # Higher priority above lower ones return s1.self.mPriority < s2.self.mPriority # Just use start time - older ones at the top return s1.mStart < s2.mStart propertyCardinality_1 = ( definitions.cICalProperty_DTSTAMP, definitions.cICalProperty_UID, ) propertyCardinality_0_1 = ( definitions.cICalProperty_CLASS, definitions.cICalProperty_COMPLETED, definitions.cICalProperty_CREATED, definitions.cICalProperty_DESCRIPTION, definitions.cICalProperty_DTSTART, definitions.cICalProperty_GEO, definitions.cICalProperty_LAST_MODIFIED, definitions.cICalProperty_LOCATION, definitions.cICalProperty_ORGANIZER, definitions.cICalProperty_PERCENT_COMPLETE, definitions.cICalProperty_PRIORITY, definitions.cICalProperty_RECURRENCE_ID, definitions.cICalProperty_SEQUENCE, # definitions.cICalProperty_STATUS, # Special fix done for multiple STATUS definitions.cICalProperty_SUMMARY, definitions.cICalProperty_URL, definitions.cICalProperty_RRULE, definitions.cICalProperty_DUE, definitions.cICalProperty_DURATION, ) propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None): super(PyCalendarVToDo, self).__init__(parent=parent) self.mPriority = 0 self.mStatus = definitions.eStatus_VToDo_None self.mPercentComplete = 0 self.mCompleted = PyCalendarDateTime() self.mHasCompleted = False def duplicate(self, parent=None): other = super(PyCalendarVToDo, self).duplicate(parent=parent) other.mPriority = self.mPriority other.mStatus = self.mStatus other.mPercentComplete = self.mPercentComplete other.mCompleted = self.mCompleted.duplicate() other.mHasCompleted = self.mHasCompleted return other def getType(self): return definitions.cICalComponent_VTODO def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VTODO def addComponent(self, comp): # We can embed the alarm components only if comp.getType() == definitions.cICalComponent_VALARM: super(PyCalendarVToDo, self).addComponent(comp) else: raise ValueError def getStatus(self): return self.mStatus def setStatus(self, status): self.mStatus = status def getStatusText(self): sout = StringIO() if self.mStatus in (definitions.eStatus_VToDo_NeedsAction, definitions.eStatus_VToDo_InProcess): if self.hasEnd(): # Check due date today = PyCalendarDateTime() today.setToday() if self.getEnd() > today: sout.append("Due: ") whendue = self.getEnd() - today if (whendue.getDays() > 0) and (whendue.getDays() <= 7): sout.write(whendue.getDays()) sout.write(" days") else: sout.write(self.getEnd().getLocaleDate(PyCalendarDateTime.NUMERICDATE)) elif self.getEnd() == today: sout.write("Due today") else: sout.write("Overdue: ") overdue = today - self.getEnd() if overdue.getWeeks() != 0: sout.write(overdue.getWeeks()) sout.write(" weeks") else: sout.write(overdue.getDays() + 1) sout.write(" days") else: sout.write("Not Completed") elif self.mStatus == definitions.eStatus_VToDo_Completed: if self.hasCompleted(): sout.write("Completed: ") sout.write(self.getCompleted().getLocaleDate(PyCalendarDateTime.NUMERICDATE)) else: sout.write("Completed") elif definitions.eStatus_VToDo_Cancelled: sout.write("Cancelled") return sout.toString() def getCompletionState(self): if self.mStatus in (definitions.eStatus_VToDo_NeedsAction, definitions.eStatus_VToDo_InProcess): if self.hasEnd(): # Check due date today = PyCalendarDateTime() today.setToday() if self.getEnd() > today: return PyCalendarVToDo.DUE_LATER elif self.getEnd() == today: return PyCalendarVToDo.DUE_NOW else: return PyCalendarVToDo.OVERDUE else: return PyCalendarVToDo.DUE_NOW elif self.mStatus == definitions.eStatus_VToDo_Completed: return PyCalendarVToDo.DONE elif self.mStatus == definitions.eStatus_VToDo_Cancelled: return PyCalendarVToDo.CANCELLED def getPriority(self): return self.mPriority def setPriority(self, priority): self.mPriority = priority def getCompleted(self): return self.mCompleted def hasCompleted(self): return self.mHasCompleted def finalise(self): # Do inherited super(PyCalendarVToDo, self).finalise() # Get DUE temp = self.loadValueDateTime(definitions.cICalProperty_DUE) if temp is None: # Try DURATION instead temp = self.loadValueDuration(definitions.cICalProperty_DURATION) if temp is not None: self.mEnd = self.mStart + temp self.mHasEnd = True else: self.mHasEnd = False else: self.mHasEnd = True self.mEnd = temp # Get PRIORITY self.mPriority = self.loadValueInteger(definitions.cICalProperty_PRIORITY) # Get STATUS temp = self.loadValueString(definitions.cICalProperty_STATUS) if temp is not None: if temp == definitions.cICalProperty_STATUS_NEEDS_ACTION: self.mStatus = definitions.eStatus_VToDo_NeedsAction elif temp == definitions.cICalProperty_STATUS_COMPLETED: self.mStatus = definitions.eStatus_VToDo_Completed elif temp == definitions.cICalProperty_STATUS_IN_PROCESS: self.mStatus = definitions.eStatus_VToDo_InProcess elif temp == definitions.cICalProperty_STATUS_CANCELLED: self.mStatus = definitions.eStatus_VToDo_Cancelled # Get PERCENT-COMPLETE self.mPercentComplete = self.loadValueInteger(definitions.cICalProperty_PERCENT_COMPLETE) # Get COMPLETED temp = self.loadValueDateTime(definitions.cICalProperty_COMPLETED) self.mHasCompleted = temp is not None if self.mHasCompleted: self.mCompleted = temp def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended to that. """ fixed, unfixed = super(PyCalendarVToDo, self).validate(doFix) # Extra constraint: only one of DUE or DURATION if self.hasProperty(definitions.cICalProperty_DUE) and self.hasProperty(definitions.cICalProperty_DURATION): # Fix by removing the DURATION logProblem = "[%s] Properties must not both be present: %s, %s" % ( self.getType(), definitions.cICalProperty_DUE, definitions.cICalProperty_DURATION, ) if doFix: self.removeProperties(definitions.cICalProperty_DURATION) fixed.append(logProblem) else: unfixed.append(logProblem) # Extra constraint: DTSTART must be present if DURATION is present if self.hasProperty(definitions.cICalProperty_DURATION) and not self.hasProperty(definitions.cICalProperty_DTSTART): # Cannot fix this one logProblem = "[%s] Property must be present: %s with %s" % ( self.getType(), definitions.cICalProperty_DTSTART, definitions.cICalProperty_DURATION, ) unfixed.append(logProblem) return fixed, unfixed # Editing def editStatus(self, status): # Only if it is different if self.mStatus != status: # Updated cached values self.mStatus = status # Remove existing STATUS & COMPLETED items self.removeProperties(definitions.cICalProperty_STATUS) self.removeProperties(definitions.cICalProperty_COMPLETED) self.mHasCompleted = False # Now create properties value = None if status == definitions.eStatus_VToDo_NeedsAction: value = definitions.cICalProperty_STATUS_NEEDS_ACTION if status == definitions.eStatus_VToDo_Completed: value = definitions.cICalProperty_STATUS_COMPLETED # Add the completed item self.mCompleted.setNowUTC() self.mHasCompleted = True prop = PyCalendarProperty(definitions.cICalProperty_STATUS_COMPLETED, self.mCompleted) self.addProperty(prop) elif status == definitions.eStatus_VToDo_InProcess: value = definitions.cICalProperty_STATUS_IN_PROCESS elif status == definitions.eStatus_VToDo_Cancelled: value = definitions.cICalProperty_STATUS_CANCELLED prop = PyCalendarProperty(definitions.cICalProperty_STATUS, value) self.addProperty(prop) def editCompleted(self, completed): # Remove existing COMPLETED item self.removeProperties(definitions.cICalProperty_COMPLETED) self.mHasCompleted = False # Always UTC self.mCompleted = completed.duplicate() self.mCompleted.adjustToUTC() self.mHasCompleted = True prop = PyCalendarProperty(definitions.cICalProperty_STATUS_COMPLETED, self.mCompleted) self.addProperty(prop) def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, definitions.cICalProperty_RECURRENCE_ID, definitions.cICalProperty_DTSTART, definitions.cICalProperty_DURATION, definitions.cICalProperty_DUE, definitions.cICalProperty_COMPLETED, ) pycalendar-2.0~svn13177/src/pycalendar/recurrencevalue.py0000644000175000017500000000300612101017573022506 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import xmldefs from pycalendar.recurrence import PyCalendarRecurrence from pycalendar.value import PyCalendarValue class PyCalendarRecurrenceValue(PyCalendarValue): def __init__(self, value=None): self.mValue = value if value is not None else PyCalendarRecurrence() def duplicate(self): return PyCalendarRecurrenceValue(self.mValue.duplicate()) def getType(self): return PyCalendarValue.VALUETYPE_RECUR def parse(self, data): self.mValue.parse(data) def generate(self, os): self.mValue.generate(os) def writeXML(self, node, namespace): self.mValue.writeXML(node, namespace) def getValue(self): return self.mValue def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_RECUR, PyCalendarRecurrenceValue, xmldefs.value_recur) pycalendar-2.0~svn13177/src/pycalendar/outputfilter.py0000644000175000017500000000520512101017573022065 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## class PyCalendarOutputFilter(object): def __init__(self, type): self.mType = type self.mAllSubComponents = False self.mSubComponents = None self.mAllProperties = False self.mProperties = None def getType(self): return self.mType # Test to see if component type can be written out def testComponent(self, oftype): return self.mType == oftype def isAllSubComponents(self): return self.mAllSubComponents def setAllSubComponents(self): self.mAllSubComponents = True self.mSubComponents = None def addSubComponent(self, comp): if self.mSubComponents == None: self.mSubComponents = {} self.mSubComponents[comp.getType()] = comp # Test to see if sub-component type can be written out def testSubComponent(self, oftype): return self.mAllSubComponents or (self.mSubComponents is not None) \ and oftype in self.mSubComponents def hasSubComponentFilters(self): return self.mSubComponents is not None def getSubComponentFilter(self, type): if self.mSubComponents is not None: return self.mSubComponents.get(type, None) else: return None def isAllProperties(self): return self.mAllProperties def setAllProperties(self): self.mAllProperties = True self.mProperties = None def addProperty(self, name, no_value): if self.mProperties is None: self.mProperties = {} self.mProperties[name] = no_value def hasPropertyFilters(self): return self.mProperties is not None # Test to see if property can be written out and also return whether # the property value is used def testPropertyValue(self, name): if self.mAllProperties: return True, False if self.mProperties is None: return False, False result = self.mProperties.get(name, None) return result is not None, result pycalendar-2.0~svn13177/src/pycalendar/nvalue.py0000644000175000017500000000244212101017573020611 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # vCard ADR value from pycalendar.n import N from pycalendar.value import PyCalendarValue class NValue(PyCalendarValue): def __init__(self, value=None): self.mValue = value if value is not None else N() def duplicate(self): return NValue(self.mValue.duplicate()) def getType(self): return PyCalendarValue.VALUETYPE_N def parse(self, data): self.mValue.parse(data) def generate(self, os): self.mValue.generate(os) def getValue(self): return self.mValue def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_N, NValue, None) pycalendar-2.0~svn13177/src/pycalendar/recurrenceset.py0000644000175000017500000002120512101017573022166 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar.utils import set_difference class PyCalendarRecurrenceSet(object): def __init__(self): self.mRrules = [] self.mExrules = [] self.mRdates = [] self.mExdates = [] self.mRperiods = [] self.mExperiods = [] def duplicate(self): other = PyCalendarRecurrenceSet() other.mRrules = [i.duplicate() for i in self.mRrules] other.mExrules = [i.duplicate() for i in self.mExrules] other.mRdates = [i.duplicate() for i in self.mRdates] other.mExdates = [i.duplicate() for i in self.mExdates] other.mRperiods = [i.duplicate() for i in self.mRperiods] other.mExperiods = [i.duplicate() for i in self.mExperiods] return other def hasRecurrence(self): return ((len(self.mRrules) != 0) or (len(self.mRdates) != 0) or (len(self.mRperiods) != 0) or (len(self.mExrules) != 0) or (len(self.mExdates) != 0) or (len(self.mExperiods) != 0)) def equals(self, comp): # Look at RRULEs if not self.equalsRules(self.mRrules, comp.self.mRrules): return False # Look at EXRULEs if not self.equalsRules(self.mExrules, comp.self.mExrules): return False # Look at RDATEs if not self.equalsDates(self.mRdates, comp.self.mRdates): return False if not self.equalsPeriods(self.mRperiods, comp.self.mRperiods): return False # Look at EXDATEs if not self.equalsDates(self.mExdates, comp.self.mExdates): return False if not self.equalsPeriods(self.mExperiods, comp.self.mExperiods): return False # If we get here they match return True def equalsRules(self, rules1, rules2): # Check sizes first if len(rules1) != len(rules2): return False elif len(rules1) == 0: return True # Do sledge hammer O(n^2) approach as its not easy to sort these things # for a smarter test. # In most cases there will only be one rule anyway, so this should not # be too painful. temp2 = rules2[:] for r1 in rules1: found = False for r2 in temp2: if r1.equals(r2): # Remove the one found so it is not tested again temp2.remove(r2) found = True break if not found: return False return True def equalsDates(self, dates1, dates2): # Check sizes first if len(dates1) != len(dates2): return False elif len(dates1) == 0: return True # Copy each and sort for comparison dt1 = dates1[:] dt2 = dates2[:] dt1.sort(key=lambda x: x.getPosixTime()) dt2.sort(key=lambda x: x.getPosixTime()) return dt1.equal(dt2) def equalsPeriods(self, periods1, periods2): # Check sizes first if len(periods1) != len(periods2): return False elif len(periods1) == 0: return True # Copy each and sort for comparison p1 = periods1[:] p2 = periods2[:] p1.sort() p2.sort() return p1.equal(p2) def addRule(self, rule): self.mRrules.append(rule) def subtractRule(self, rule): self.mExrules.append(rule) def addDT(self, dt): self.mRdates.append(dt) def subtractDT(self, dt): self.mExdates.append(dt) def addPeriod(self, p): self.mRperiods.append(p) def subtractPeriod(self, p): self.mExperiods.append(p) def getRules(self): return self.mRrules def getExrules(self): return self.mExrules def getDates(self): return self.mRdates def getExdates(self): return self.mExdates def getPeriods(self): return self.mRperiods def getExperiods(self): return self.mExperiods def expand(self, start, range, items, float_offset=0): # Need to return whether the limit was applied or not limited = False # Now create list of items to include include = [] # Always include the initial DTSTART if within the range if range.isDateWithinPeriod(start): include.append(start) else: limited = True # RRULES for iter in self.mRrules: if iter.expand(start, range, include, float_offset=float_offset): limited = True # RDATES for iter in self.mRdates: if range.isDateWithinPeriod(iter): include.append(iter) else: limited = True for iter in self.mRperiods: if range.isPeriodOverlap(iter): include.append(iter.getStart()) else: limited = True # Make sure the list is unique include = [x for x in set(include)] include.sort(key=lambda x: x.getPosixTime()) # Now create list of items to exclude exclude = [] # EXRULES for iter in self.mExrules: iter.expand(start, range, exclude, float_offset=float_offset) # EXDATES for iter in self.mExdates: if range.isDateWithinPeriod(iter): exclude.append(iter) for iter in self.mExperiods: if range.isPeriodOverlap(iter): exclude.append(iter.getStart()) # Make sure the list is unique exclude = [x for x in set(exclude)] exclude.sort(key=lambda x: x.getPosixTime()) # Add difference between to the two sets (include - exclude) to the # results items.extend(set_difference(include, exclude)) return limited def changed(self): # RRULES for iter in self.mRrules: iter.clear() # EXRULES for iter in self.mExrules: iter.clear() def excludeFutureRecurrence(self, exclude): # Adjust RRULES to end before start for iter in self.mRrules: iter.excludeFutureRecurrence(exclude) # Remove RDATES on or after start self.mRdates.removeOnOrAfter(exclude) for iter in self.mRperiods: if iter > exclude: self.mRperiods.remove(iter) # UI operations def isSimpleUI(self): # Right now the Event dialog only handles a single RRULE (but we allow # any number of EXDATES as deleted # instances will appear as EXDATES) if ((len(self.mRrules) > 1) or (len(self.mExrules) > 0) or (len(self.mRdates) > 0) or (len(self.mRperiods) > 0)): return False # Also, check the rule iteself elif len(self.mRrules) == 1: return self.mRrules.firstElement().isSimpleRule() else: return True def isAdvancedUI(self): # Right now the Event dialog only handles a single RRULE if ((len(self.mRrules) > 1) or (len(self.mExrules) > 0) or (len(self.mRdates) > 0) or (len(self.mRperiods) > 0)): return False # Also, check the rule iteself elif len(self.mRrules) == 1: return self.mRrules.firstElement().isAdvancedRule() else: return True def getUIRecurrence(self): if len(self.mRrules) == 1: return self.mRrules[0] else: return None def getUIDescription(self): # Check for anything if not self.hasRecurrence(): return "No Recurrence" # Look for a single RRULE and return its descriptor if ((len(self.mRrules) == 1) and (len(self.mExrules) == 0) and (len(self.mRdates) == 0) and (len(self.mExdates) == 0) and (len(self.mRperiods) == 0) and (len(self.mExperiods) == 0)): return self.mRrules.firstElement().getUIDescription() # Indicate some form of complex recurrence return "Multiple recurrence rules, dates or exclusions" pycalendar-2.0~svn13177/src/pycalendar/component.py0000644000175000017500000001237412101017573021326 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar import stringutils from pycalendar.componentbase import PyCalendarComponentBase from pycalendar.datetime import PyCalendarDateTime from pycalendar.property import PyCalendarProperty import os import time import uuid class PyCalendarComponent(PyCalendarComponentBase): uid_ctr = 1 def __init__(self, parent=None): super(PyCalendarComponent, self).__init__(parent) self.mUID = "" self.mSeq = 0 self.mOriginalSeq = 0 self.mChanged = False def duplicate(self, parent=None, **args): other = super(PyCalendarComponent, self).duplicate(parent=parent, **args) other.mUID = self.mUID other.mSeq = self.mSeq other.mOriginalSeq = self.mOriginalSeq other.mChanged = self.mChanged return other def __repr__(self): return "%s: UID: %s" % (self.getType(), self.getMapKey(),) def getMimeComponentName(self): raise NotImplementedError def getMapKey(self): if hasattr(self, "mMapKey"): return self.mMapKey elif self.mUID: return self.mUID else: self.mMapKey = str(uuid.uuid4()) return self.mMapKey def getSortKey(self): return self.getMapKey() def getMasterKey(self): return self.mUID def getUID(self): return self.mUID def setUID(self, uid): if uid: self.mUID = uid else: # Get left-side of UID (first 24 chars of MD5 digest of time, pid # and ctr) lhs_txt = "" lhs_txt += str(time.time()) lhs_txt += "." lhs_txt += str(os.getpid()) lhs_txt += "." lhs_txt += str(PyCalendarComponent.uid_ctr) PyCalendarComponent.uid_ctr += 1 lhs = stringutils.md5digest(lhs_txt) # Get right side (domain) of message-id rhs = None # Use app name from pycalendar.calendar import PyCalendar domain = PyCalendar.sDomain domain += str(PyCalendarComponent.uid_ctr) # Use first 24 chars of MD5 digest of the domain as the # right-side of message-id rhs = stringutils.md5digest(domain) # Generate the UID string new_uid = lhs new_uid += "@" new_uid += rhs self.mUID = new_uid self.removeProperties(definitions.cICalProperty_UID) prop = PyCalendarProperty(definitions.cICalProperty_UID, self.mUID) self.addProperty(prop) def getSeq(self): return self.mSeq def setSeq(self, seq): self.mSeq = seq self.removeProperties(definitions.cICalProperty_SEQUENCE) prop = PyCalendarProperty(definitions.cICalProperty_SEQUENCE, self.mSeq) self.addProperty(prop) def getOriginalSeq(self): return self.mOriginalSeq def getChanged(self): return self.mChanged def setChanged(self, changed): self.mChanged = changed def initDTSTAMP(self): self.removeProperties(definitions.cICalProperty_DTSTAMP) prop = PyCalendarProperty(definitions.cICalProperty_DTSTAMP, PyCalendarDateTime.getNowUTC()) self.addProperty(prop) def updateLastModified(self): self.removeProperties(definitions.cICalProperty_LAST_MODIFIED) prop = PyCalendarProperty(definitions.cICalProperty_LAST_MODIFIED, PyCalendarDateTime.getNowUTC()) self.addProperty(prop) def finalise(self): # Get UID temps = self.loadValueString(definitions.cICalProperty_UID) if temps is not None: self.mUID = temps # Get SEQ temp = self.loadValueInteger(definitions.cICalProperty_SEQUENCE) if temp is not None: self.mSeq = temp # Cache the original sequence when the component is read in. # This will be used to synchronise changes between two instances of the # same calendar self.mOriginalSeq = self.mSeq def canGenerateInstance(self): return True def getTimezones(self, tzids): # Look for all date-time properties for props in self.mProperties.itervalues(): for prop in props: # Try to get a date-time value from the property dtv = prop.getDateTimeValue() if dtv is not None: # Add timezone id if appropriate if dtv.getValue().getTimezoneID(): tzids.add(dtv.getValue().getTimezoneID()) pycalendar-2.0~svn13177/src/pycalendar/validator.py0000755000175000017500000000446612120354250021314 0ustar rahulrahul#!/usr/bin/env python ## # Copyright (c) 2012 Cyrus Daboo. 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 pycalendar.calendar import PyCalendar from pycalendar.exceptions import PyCalendarError from pycalendar.parser import ParserContext from pycalendar.vcard.card import Card import os import sys def validate(fname): """ Check whether the contents of the specified file is valid iCalendar or vCard data. """ data = open(fname).read() ParserContext.allRaise() if data.find("BEGIN:VCALENDAR") != -1: try: cal = PyCalendar.parseText(data) except PyCalendarError, e: print "Failed to parse iCalendar: %r" % (e,) sys.exit(1) elif data.find("BEGIN:VCARD") != -1: try: cal = Card.parseText(data) except PyCalendarError, e: print "Failed to parse vCard: %r" % (e,) sys.exit(1) else: print "Failed to find valid iCalendar or vCard data" sys.exit(1) _ignore_fixed, unfixed = cal.validate(doFix=False, doRaise=False) if unfixed: print "List of problems: %s" % (unfixed,) else: print "No problems" # Control character check - only HTAB, CR, LF allowed for characters in the range 0x00-0x1F s = str(data) if len(s.translate(None, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F")) != len(s): for ctr, i in enumerate(data): if i in "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F": print "Control character %d at position %d" % (ord(i), ctr,) if __name__ == '__main__': fname = os.path.expanduser(sys.argv[1]) validate(fname) pycalendar-2.0~svn13177/src/pycalendar/itipdefinitions.py0000644000175000017500000000264112101017573022521 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # 2446 Section 3 cICalMethod_PUBLISH = "PUBLISH" cICalMethod_REQUEST = "REQUEST" cICalMethod_REFRESH = "REFRESH" cICalMethod_CANCEL = "CANCEL" cICalMethod_ADD = "ADD" cICalMethod_REPLY = "REPLY" cICalMethod_COUNTER = "COUNTER" cICalMethod_DECLINECOUNTER = "DECLINECOUNTER" # 2447 Section 2.4 cICalMIMEMethod_PUBLISH = "publish" cICalMIMEMethod_REQUEST = "request" cICalMIMEMethod_REFRESH = "refresh" cICalMIMEMethod_CANCEL = "cancel" cICalMIMEMethod_ADD = "add" cICalMIMEMethod_REPLY = "reply" cICalMIMEMethod_COUNTER = "counter" cICalMIMEMethod_DECLINECOUNTER = "declinecounter" cICalMIMEComponent_VEVENT = "vevent" cICalMIMEComponent_VTODO = "vtodo" cICalMIMEComponent_VJOURNAL = "vjournal" cICalMIMEComponent_VFREEBUSY = "vfreebusy" cICalMIMEComponent_VAVAILABILITY = "vavailability" pycalendar-2.0~svn13177/src/pycalendar/valueutils.py0000644000175000017500000000227212101017573021515 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # Helpers for value classes from cStringIO import StringIO class ValueMixin(object): def __str__(self): return self.getText() @classmethod def parseText(cls, data): value = cls() value.parse(data) return value def parse(self, data): raise NotImplementedError def generate(self, os): raise NotImplementedError def getText(self): os = StringIO() self.generate(os) return os.getvalue() def writeXML(self, node, namespace): raise NotImplementedError pycalendar-2.0~svn13177/src/pycalendar/freebusy.py0000644000175000017500000000261712101017573021147 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## class PyCalendarFreeBusy(object): FREE = 0 BUSYTENTATIVE = 1 BUSYUNAVAILABLE = 2 BUSY = 3 def __init__(self, type=None, period=None): self.mType = type if type else PyCalendarFreeBusy.FREE self.mPeriod = period.duplicate() if period is not None else None def duplicate(self): return PyCalendarFreeBusy(self.mType, self.mPeriod) def setType(self, type): self.mType = type def getType(self): return self.mType def setPeriod(self, period): self.mPeriod = period.duplicate() def getPeriod(self): return self.mPeriod def isPeriodOverlap(self, period): return self.mPeriod.isPeriodOverlap(period) def resolveOverlaps(self, fb): # TODO: pass pycalendar-2.0~svn13177/src/pycalendar/definitions.py0000644000175000017500000002344112101017573021634 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # 5545 Components cICalComponent_VCALENDAR = "VCALENDAR" cICalComponent_VEVENT = "VEVENT" cICalComponent_VTODO = "VTODO" cICalComponent_VJOURNAL = "VJOURNAL" cICalComponent_VFREEBUSY = "VFREEBUSY" cICalComponent_VTIMEZONE = "VTIMEZONE" cICalComponent_VALARM = "VALARM" cICalComponent_STANDARD = "STANDARD" cICalComponent_DAYLIGHT = "DAYLIGHT" # 5545 Calendar Property Attributes # 5545 Section 3.2 cICalAttribute_ALTREP = "ALTREP" cICalAttribute_CN = "CN" cICalAttribute_CUTYPE = "CUTYPE" cICalAttribute_DELEGATED_FROM = "DELEGATED-FROM" cICalAttribute_DELEGATED_TO = "DELEGATED-TO" cICalAttribute_DIR = "DIR" cICalAttribute_ENCODING = "ENCODING" cICalAttribute_FMTTYPE = "FMTTYPE" cICalAttribute_FBTYPE = "FBTYPE" cICalAttribute_LANGUAGE = "LANGUAGE" cICalAttribute_MEMBER = "MEMBER" cICalAttribute_PARTSTAT = "PARTSTAT" cICalAttribute_RANGE = "RANGE" cICalAttribute_RELATED = "RELATED" cICalAttribute_RELTYPE = "RELTYPE" cICalAttribute_ROLE = "ROLE" cICalAttribute_RSVP = "RSVP" cICalAttribute_RSVP_TRUE = "TRUE" cICalAttribute_RSVP_FALSE = "FALSE" cICalAttribute_SENT_BY = "SENT-BY" cICalAttribute_TZID = "TZID" cICalAttribute_VALUE = "VALUE" # 5545 Section 3.2.9 cICalAttribute_FBTYPE_FREE = "FREE" cICalAttribute_FBTYPE_BUSY = "BUSY" cICalAttribute_FBTYPE_BUSYUNAVAILABLE = "BUSY-UNAVAILABLE" cICalAttribute_FBTYPE_BUSYTENTATIVE = "BUSY-TENTATIVE" # 5545 Section 3.2.12 ePartStat_NeedsAction = 0 ePartStat_Accepted = 1 ePartStat_Declined = 2 ePartStat_Tentative = 3 ePartStat_Delegated = 4 ePartStat_Completed = 5 ePartStat_InProcess = 6 cICalAttribute_PARTSTAT_NEEDSACTION = "NEEDS-ACTION" cICalAttribute_PARTSTAT_ACCEPTED = "ACCEPTED" cICalAttribute_PARTSTAT_DECLINED = "DECLINED" cICalAttribute_PARTSTAT_TENTATIVE = "TENTATIVE" cICalAttribute_PARTSTAT_DELEGATED = "DELEGATED" cICalAttribute_PARTSTAT_COMPLETED = "COMPLETE" cICalAttribute_PARTSTAT_INPROCESS = "IN-PROCESS" # 5545 Section 3.2.13 cICalAttribute_RANGE_THISANDFUTURE = "THISANDFUTURE" cICalAttribute_RANGE_THISANDPRIOR = "THISANDPRIOR" # 2445 only # 5545 Section 3.2.14 cICalAttribute_RELATED_START = "START" cICalAttribute_RELATED_END = "END" # 5545 Section 3.2.16 ePartRole_Chair = 0 ePartRole_Required = 1 ePartRole_Optional = 2 ePartRole_Non = 3 cICalAttribute_ROLE_CHAIR = "CHAIR" cICalAttribute_ROLE_REQ_PART = "REQ-PARTICIPANT" cICalAttribute_ROLE_OPT_PART = "OPT-PARTICIPANT" cICalAttribute_ROLE_NON_PART = "NON-PARTICIPANT" # 5545 Section 3.2.3 eCutype_Individual = 0 eCutype_Group = 1 eCutype_Resource = 2 eCutype_Room = 3 eCutype_Unknown = 4 cICalAttribute_CUTYPE_INDIVIDUAL = "INDIVIDUAL" cICalAttribute_CUTYPE_GROUP = "GROUP" cICalAttribute_CUTYPE_RESOURCE = "RESOURCE" cICalAttribute_CUTYPE_ROOM = "ROOM" cICalAttribute_CUTYPE_UNKNOWN = "UNKNOWN" # 5545 Value types # 5545 Section 3.3 cICalValue_BINARY = "BINARY" cICalValue_BOOLEAN = "BOOLEAN" cICalValue_CAL_ADDRESS = "CAL-ADDRESS" cICalValue_DATE = "DATE" cICalValue_DATE_TIME = "DATE-TIME" cICalValue_DURATION = "DURATION" cICalValue_FLOAT = "FLOAT" cICalValue_INTEGER = "INTEGER" cICalValue_PERIOD = "PERIOD" cICalValue_RECUR = "RECUR" cICalValue_TEXT = "TEXT" cICalValue_TIME = "TIME" cICalValue_URI = "URI" cICalValue_UTC_OFFSET = "UTC-OFFSET" # 5545 Calendar Properties # 5545 Section 3.7 cICalProperty_CALSCALE = "CALSCALE" cICalProperty_METHOD = "METHOD" cICalProperty_PRODID = "PRODID" cICalProperty_VERSION = "VERSION" # Apple Extensions cICalProperty_XWRCALNAME = "X-WR-CALNAME" cICalProperty_XWRCALDESC = "X-WR-CALDESC" cICalProperty_XWRALARMUID = "X-WR-ALARMUID" # 5545 Component Property names # 5545 Section 3.8.1 cICalProperty_ATTACH = "ATTACH" cICalProperty_CATEGORIES = "CATEGORIES" cICalProperty_CLASS = "CLASS" cICalProperty_COMMENT = "COMMENT" cICalProperty_DESCRIPTION = "DESCRIPTION" cICalProperty_GEO = "GEO" cICalProperty_LOCATION = "LOCATION" cICalProperty_PERCENT_COMPLETE = "PERCENT-COMPLETE" cICalProperty_PRIORITY = "PRIORITY" cICalProperty_RESOURCES = "RESOURCES" cICalProperty_STATUS = "STATUS" cICalProperty_SUMMARY = "SUMMARY" # 5545 Section 3.8.2 cICalProperty_COMPLETED = "COMPLETED" cICalProperty_DTEND = "DTEND" cICalProperty_DUE = "DUE" cICalProperty_DTSTART = "DTSTART" cICalProperty_DURATION = "DURATION" cICalProperty_FREEBUSY = "FREEBUSY" cICalProperty_TRANSP = "TRANSP" cICalProperty_OPAQUE = "OPAQUE" cICalProperty_TRANSPARENT = "TRANSPARENT" # 5545 Section 3.8.3 cICalProperty_TZID = "TZID" cICalProperty_TZNAME = "TZNAME" cICalProperty_TZOFFSETFROM = "TZOFFSETFROM" cICalProperty_TZOFFSETTO = "TZOFFSETTO" cICalProperty_TZURL = "TZURL" # 5545 Section 3.8.4 cICalProperty_ATTENDEE = "ATTENDEE" cICalProperty_CONTACT = "CONTACT" cICalProperty_ORGANIZER = "ORGANIZER" cICalProperty_RECURRENCE_ID = "RECURRENCE-ID" cICalProperty_RELATED_TO = "RELATED-TO" cICalProperty_URL = "URL" cICalProperty_UID = "UID" # 5545 Section 3.8.5 cICalProperty_EXDATE = "EXDATE" cICalProperty_EXRULE = "EXRULE" # 2445 only cICalProperty_RDATE = "RDATE" cICalProperty_RRULE = "RRULE" # 5545 Section 3.8.6 cICalProperty_ACTION = "ACTION" cICalProperty_REPEAT = "REPEAT" cICalProperty_TRIGGER = "TRIGGER" # 5545 Section 3.8.7 cICalProperty_CREATED = "CREATED" cICalProperty_DTSTAMP = "DTSTAMP" cICalProperty_LAST_MODIFIED = "LAST-MODIFIED" cICalProperty_SEQUENCE = "SEQUENCE" # 5545 Section 3.8.8.3 cICalProperty_REQUEST_STATUS = "REQUEST-STATUS" # Enums # Use ascending order for sensible sorting # 5545 Section 3.3.10 eRecurrence_SECONDLY = 0 eRecurrence_MINUTELY = 1 eRecurrence_HOURLY = 2 eRecurrence_DAILY = 3 eRecurrence_WEEKLY = 4 eRecurrence_MONTHLY = 5 eRecurrence_YEARLY = 6 eRecurrence_FREQ = 0 eRecurrence_UNTIL = 1 eRecurrence_COUNT = 2 eRecurrence_INTERVAL = 3 eRecurrence_BYSECOND = 4 eRecurrence_BYMINUTE = 5 eRecurrence_BYHOUR = 6 eRecurrence_BYDAY = 7 eRecurrence_BYMONTHDAY = 8 eRecurrence_BYYEARDAY = 9 eRecurrence_BYWEEKNO = 10 eRecurrence_BYMONTH = 11 eRecurrence_BYSETPOS = 12 eRecurrence_WKST = 13 cICalValue_RECUR_FREQ = "FREQ" cICalValue_RECUR_FREQ_LEN = 5 cICalValue_RECUR_SECONDLY = "SECONDLY" cICalValue_RECUR_MINUTELY = "MINUTELY" cICalValue_RECUR_HOURLY = "HOURLY" cICalValue_RECUR_DAILY = "DAILY" cICalValue_RECUR_WEEKLY = "WEEKLY" cICalValue_RECUR_MONTHLY = "MONTHLY" cICalValue_RECUR_YEARLY = "YEARLY" cICalValue_RECUR_UNTIL = "UNTIL" cICalValue_RECUR_COUNT = "COUNT" cICalValue_RECUR_INTERVAL = "INTERVAL" cICalValue_RECUR_BYSECOND = "BYSECOND" cICalValue_RECUR_BYMINUTE = "BYMINUTE" cICalValue_RECUR_BYHOUR = "BYHOUR" cICalValue_RECUR_BYDAY = "BYDAY" cICalValue_RECUR_BYMONTHDAY = "BYMONTHDAY" cICalValue_RECUR_BYYEARDAY = "BYYEARDAY" cICalValue_RECUR_BYWEEKNO = "BYWEEKNO" cICalValue_RECUR_BYMONTH = "BYMONTH" cICalValue_RECUR_BYSETPOS = "BYSETPOS" cICalValue_RECUR_WKST = "WKST" eRecurrence_WEEKDAY_SU = 0 eRecurrence_WEEKDAY_MO = 1 eRecurrence_WEEKDAY_TU = 2 eRecurrence_WEEKDAY_WE = 3 eRecurrence_WEEKDAY_TH = 4 eRecurrence_WEEKDAY_FR = 5 eRecurrence_WEEKDAY_SA = 6 cICalValue_RECUR_WEEKDAY_SU = "SU" cICalValue_RECUR_WEEKDAY_MO = "MO" cICalValue_RECUR_WEEKDAY_TU = "TU" cICalValue_RECUR_WEEKDAY_WE = "WE" cICalValue_RECUR_WEEKDAY_TH = "TH" cICalValue_RECUR_WEEKDAY_FR = "FR" cICalValue_RECUR_WEEKDAY_SA = "SA" # 5545 Section 3.8.1.11 eStatus_VEvent_None = 0 eStatus_VEvent_Confirmed = 1 eStatus_VEvent_Tentative = 2 eStatus_VEvent_Cancelled = 3 eStatus_VToDo_None = 0 eStatus_VToDo_NeedsAction = 1 eStatus_VToDo_InProcess = 2 eStatus_VToDo_Completed = 3 eStatus_VToDo_Cancelled = 4 eStatus_VJournal_None = 0 eStatus_VJournal_Final = 1 eStatus_VJournal_Draft = 2 eStatus_VJournal_Cancelled = 3 cICalProperty_STATUS_TENTATIVE = "TENTATIVE" cICalProperty_STATUS_CONFIRMED = "CONFIRMED" cICalProperty_STATUS_CANCELLED = "CANCELLED" cICalProperty_STATUS_NEEDS_ACTION = "NEEDS-ACTION" cICalProperty_STATUS_COMPLETED = "COMPLETED" cICalProperty_STATUS_IN_PROCESS = "IN-PROCESS" cICalProperty_STATUS_DRAFT = "DRAFT" cICalProperty_STATUS_FINAL = "FINAL" # 5545 Section 3.8.6.1 eAction_VAlarm_Audio = 0 eAction_VAlarm_Display = 1 eAction_VAlarm_Email = 2 eAction_VAlarm_Procedure = 3 eAction_VAlarm_Unknown = 4 cICalProperty_ACTION_AUDIO = "AUDIO" cICalProperty_ACTION_DISPLAY = "DISPLAY" cICalProperty_ACTION_EMAIL = "EMAIL" cICalProperty_ACTION_PROCEDURE = "PROCEDURE" # Extensions: draft-daboo-calendar-availability-02 # Section 3.1 cICalComponent_VAVAILABILITY = "VAVAILABILITY" cICalComponent_AVAILABLE = "AVAILABLE" # Section 3.2 cICalProperty_BUSYTYPE = "BUSYTYPE" # Extensions: draft-daboo-valarm-extensions-03 # Section 5 eAction_VAlarm_URI = 5 cICalProperty_ACTION_URI = "URI" # Section 7.1 cICalProperty_ACKNOWLEDGED = "ACKNOWLEDGED" eAction_VAlarm_None = 6 cICalProperty_ACTION_NONE = "NONE" # Mulberry extensions cICalProperty_ACTION_X_SPEAKTEXT = "X-MULBERRY-SPEAK-TEXT" cICalProperty_ALARM_X_LASTTRIGGER = "X-MULBERRY-LAST-TRIGGER" cICalProperty_ALARM_X_ALARMSTATUS = "X-MULBERRY-ALARM-STATUS" eAlarm_Status_Pending = 0 eAlarm_Status_Completed = 1 eAlarm_Status_Disabled = 2 cICalProperty_ALARM_X_ALARMSTATUS_PENDING = "PENDING" cICalProperty_ALARM_X_ALARMSTATUS_COMPLETED = "COMPLETED" cICalProperty_ALARM_X_ALARMSTATUS_DISABLED = "DISABLED" cICalAttribute_ORGANIZER_X_IDENTITY = "X-MULBERRY-IDENTITY" cICalAttribute_ATTENDEE_X_NEEDS_ITIP = "X-MULBERRY-NEEDS-ITIP" pycalendar-2.0~svn13177/src/pycalendar/xmldefs.py0000644000175000017500000000531712101017573020765 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## import xml.etree.cElementTree as XML # iCalendar/vCard XML definitions iCalendar20_namespace = "urn:ietf:params:xml:ns:icalendar-2.0" icalendar = "icalendar" components = "components" properties = "properties" parameters = "parameters" value_binary = "binary" value_boolean = "boolean" value_cal_address = "cal-address" value_date = "date" value_date_time = "date-time" value_duration = "duration" value_integer = "integer" value_period = "period" value_recur = "recur" value_text = "text" value_unknown = "unknown" value_uri = "uri" value_utc_offset = "utc-offset" period_start = "start" period_end = "end" period_duration = "duration" recur_freq = "freq" recur_freq_secondly = "SECONDLY" recur_freq_minutely = "MINUTELY" recur_freq_hourly = "HOURLY" recur_freq_daily = "DAILY" recur_freq_weekly = "WEEKLY" recur_freq_monthly = "MONTHLY" recur_freq_yearly = "YEARLY" recur_count = "count" recur_until = "until" recur_interval = "interval" recur_bysecond = "bysecond" recur_byminute = "byminute" recur_byhour = "byhour" recur_byday = "byday" recur_bymonthday = "bymonthday" recur_byyearday = "byyearday" recur_byweekno = "byweekno" recur_bymonth = "bymonth" recur_bysetpos = "bysetpos" recur_wkst = "wkst" req_status_code = "code" req_status_description = "description" req_status_data = "data" vCard40_namespace = "urn:ietf:params:xml:ns:vcard-4.0" def makeTag(namespace, name): return "{%s}%s" % (namespace, name.lower(),) def toString(root): data = """\n""" INDENT = 2 # Generate indentation def _indentNode(node, level=0): if node.text is not None and node.text.strip(): return elif len(node.getchildren()): indent = "\n" + " " * (level + 1) * INDENT node.text = indent for child in node.getchildren(): child.tail = indent _indentNode(child, level + 1) if len(node.getchildren()): node.getchildren()[-1].tail = "\n" + " " * level * INDENT _indentNode(root, 0) data += XML.tostring(root) + "\n" return data pycalendar-2.0~svn13177/src/pycalendar/stringutils.py0000644000175000017500000000555112101017573021712 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 hashlib import md5 def strduptokenstr(txt, tokens): result = None start = 0 # First punt over any leading space for s in txt: if s == " ": start += 1 else: break else: return None, "" # Handle quoted string if txt[start] == '\"': maxlen = len(txt) # Punt leading quote start += 1 end = start done = False while not done: if end == maxlen: return None, txt if txt[end] == '\"': done = True elif txt[end] == '\\': # Punt past quote end += 2 else: end += 1 if end >= maxlen: return None, txt return txt[start:end], txt[end + 1:] else: for relend, s in enumerate(txt[start:]): if s in tokens: if relend: result = txt[start:start + relend] else: result = "" return result, txt[start + relend:] return txt[start:], "" def strtoul(s, offset=0): max = len(s) startoffset = offset while offset < max: if s[offset] in "0123456789": offset += 1 continue elif offset == 0: raise ValueError else: return int(s[startoffset:offset]), offset else: if offset == 0: raise ValueError else: return int(s[startoffset:]), offset def strindexfind(s, ss, default_index): if s and ss: i = 0 s = s.upper() while ss[i]: if s == ss[i]: return i i += 1 return default_index def strnindexfind(s, ss, default_index): if s and ss: i = 0 s = s.upper() while ss[i]: if s.startswith(ss[i]): return i i += 1 return default_index def compareStringsSafe(s1, s2): if s1 is None and s2 is None: return True elif (s1 is None and s2 is not None) or (s1 is not None and s2 is None): return False else: return s1 == s2 def md5digest(txt): return md5.new(txt).hexdigest() pycalendar-2.0~svn13177/src/pycalendar/__init__.py0000644000175000017500000000327712101017573021065 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## __all__ = [ "attribute", "available", "binaryvalue", "caladdressvalue", "calendar", "datetime", "datetimevalue", "definitions", "duration", "durationvalue", "exceptions", "freebusy", "integervalue", "locale", "manager", "multivalue", "period", "periodvalue", "plaintextvalue", "property", "recurrence", "recurrencevalue", "requeststatusvalue", "textvalue", "timezone", "timezonedb", "unknownvalue", "urivalue", "utcoffsetvalue", "valarm", "value", "vevent", "vfreebusy", "vjournal", "vtimezone", "vtimezonedaylight", "vtimezonestandard", "vtodo", "vunknown", ] # Import these to register the values import binaryvalue import caladdressvalue import datetimevalue import durationvalue import integervalue import multivalue import periodvalue import recurrencevalue import requeststatusvalue import textvalue import unknownvalue import urivalue import utcoffsetvalue # Import these to force static initialisation import property pycalendar-2.0~svn13177/src/pycalendar/vtimezonestandard.py0000644000175000017500000000213512101017573023057 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar.vtimezoneelement import PyCalendarVTimezoneElement class PyCalendarVTimezoneStandard(PyCalendarVTimezoneElement): def __init__(self, parent=None): super(PyCalendarVTimezoneStandard, self).__init__(parent=parent) def duplicate(self, parent=None): return super(PyCalendarVTimezoneStandard, self).duplicate(parent=parent) def getType(self): return definitions.cICalComponent_STANDARD pycalendar-2.0~svn13177/src/pycalendar/period.py0000644000175000017500000001073212146673726020623 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import xmldefs from pycalendar.datetime import PyCalendarDateTime from pycalendar.duration import PyCalendarDuration from pycalendar.valueutils import ValueMixin import xml.etree.cElementTree as XML class PyCalendarPeriod(ValueMixin): def __init__(self, start=None, end=None, duration=None): self.mStart = start if start is not None else PyCalendarDateTime() if end is not None: self.mEnd = end self.mDuration = self.mEnd - self.mStart self.mUseDuration = False elif duration is not None: self.mDuration = duration self.mEnd = self.mStart + self.mDuration self.mUseDuration = True else: self.mEnd = self.mStart.duplicate() self.mDuration = PyCalendarDuration() self.mUseDuration = False def duplicate(self): other = PyCalendarPeriod(start=self.mStart.duplicate(), end=self.mEnd.duplicate()) other.mUseDuration = self.mUseDuration return other def __hash__(self): return hash((self.mStart, self.mEnd,)) def __repr__(self): return "PyCalendarPeriod %s" % (self.getText(),) def __str__(self): return self.getText() def __eq__(self, comp): return self.mStart == comp.mStart and self.mEnd == comp.mEnd def __gt__(self, comp): return self.mStart > comp def __lt__(self, comp): return self.mStart < comp.mStart \ or (self.mStart == comp.mStart) and self.mEnd < comp.mEnd @classmethod def parseText(cls, data): period = cls() period.parse(data) return period def parse(self, data): splits = data.split('/', 1) if len(splits) == 2: start = splits[0] end = splits[1] self.mStart.parse(start) if end[0] == 'P': self.mDuration.parse(end) self.mUseDuration = True self.mEnd = self.mStart + self.mDuration else: self.mEnd.parse(end) self.mUseDuration = False self.mDuration = self.mEnd - self.mStart else: raise ValueError def generate(self, os): try: self.mStart.generate(os) os.write("/") if self.mUseDuration: self.mDuration.generate(os) else: self.mEnd.generate(os) except: pass def writeXML(self, node, namespace): start = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.period_start)) start.text = self.mStart.getXMLText() if self.mUseDuration: duration = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.period_duration)) duration.text = self.mDuration.getText() else: end = XML.SubElement(node, xmldefs.makeTag(namespace, xmldefs.period_end)) end.text = self.mEnd.getXMLText() def getStart(self): return self.mStart def getEnd(self): return self.mEnd def getDuration(self): return self.mDuration def getUseDuration(self): return self.mUseDuration def setUseDuration(self, use): self.mUseDuration = use def isDateWithinPeriod(self, dt): # Inclusive start, exclusive end return dt >= self.mStart and dt < self.mEnd def isDateBeforePeriod(self, dt): # Inclusive start return dt < self.mStart def isDateAfterPeriod(self, dt): # Exclusive end return dt >= self.mEnd def isPeriodOverlap(self, p): # Inclusive start, exclusive end return not (self.mStart >= p.mEnd or self.mEnd <= p.mStart) def adjustToUTC(self): self.mStart.adjustToUTC() self.mEnd.adjustToUTC() def describeDuration(self): return "" pycalendar-2.0~svn13177/src/pycalendar/vunknown.py0000644000175000017500000000337612101017573021213 0ustar rahulrahul## # Copyright (c) 2011-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar.component import PyCalendarComponent from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS import uuid class PyCalendarUnknownComponent(PyCalendarComponent): propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None, comptype=""): super(PyCalendarUnknownComponent, self).__init__(parent=parent) self.mType = comptype self.mMapKey = str(uuid.uuid4()) def duplicate(self, parent=None): return super(PyCalendarUnknownComponent, self).duplicate(parent=parent, comptype=self.mType) def getType(self): return self.mType def getBeginDelimiter(self): return "BEGIN:" + self.mType def getEndDelimiter(self): return "END:" + self.mType def getMimeComponentName(self): return "unknown" def getMapKey(self): return self.mMapKey def getSortKey(self): """ We do not want unknown components sorted. """ return "" def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, ) pycalendar-2.0~svn13177/src/pycalendar/vjournal.py0000644000175000017500000000437612101017573021167 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar import itipdefinitions from pycalendar.componentrecur import PyCalendarComponentRecur from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS class PyCalendarVJournal(PyCalendarComponentRecur): propertyCardinality_1 = ( definitions.cICalProperty_DTSTAMP, definitions.cICalProperty_UID, ) propertyCardinality_0_1 = ( definitions.cICalProperty_CLASS, definitions.cICalProperty_CREATED, definitions.cICalProperty_DTSTART, definitions.cICalProperty_LAST_MODIFIED, definitions.cICalProperty_ORGANIZER, definitions.cICalProperty_RECURRENCE_ID, definitions.cICalProperty_SEQUENCE, # definitions.cICalProperty_STATUS, # Special fix done for multiple STATUS definitions.cICalProperty_SUMMARY, definitions.cICalProperty_URL, definitions.cICalProperty_RRULE, ) propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None): super(PyCalendarVJournal, self).__init__(parent=parent) def duplicate(self, parent=None): return super(PyCalendarVJournal, self).duplicate(parent=parent) def getType(self): return definitions.cICalComponent_VJOURNAL def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VJOURNAL def finalise(self): super(PyCalendarVJournal, self).finalise() def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, definitions.cICalProperty_RECURRENCE_ID, definitions.cICalProperty_DTSTART, ) pycalendar-2.0~svn13177/src/pycalendar/datetimevalue.py0000644000175000017500000000330312101017573022145 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import xmldefs from pycalendar.datetime import PyCalendarDateTime from pycalendar.value import PyCalendarValue class PyCalendarDateTimeValue(PyCalendarValue): def __init__(self, value=None): self.mValue = value if value is not None else PyCalendarDateTime() def duplicate(self): return PyCalendarDateTimeValue(self.mValue.duplicate()) def getType(self): return (PyCalendarValue.VALUETYPE_DATETIME, PyCalendarValue.VALUETYPE_DATE)[self.mValue.isDateOnly()] def parse(self, data, fullISO=False): self.mValue.parse(data, fullISO) def generate(self, os): self.mValue.generate(os) def writeXML(self, node, namespace): self.mValue.writeXML(node, namespace) def getValue(self): return self.mValue def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_DATE, PyCalendarDateTimeValue, xmldefs.value_date) PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_DATETIME, PyCalendarDateTimeValue, xmldefs.value_date_time) pycalendar-2.0~svn13177/src/pycalendar/textvalue.py0000644000175000017500000000247212101017573021343 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # iCalendar UTC Offset value from pycalendar import utils, xmldefs from pycalendar.plaintextvalue import PyCalendarPlainTextValue from pycalendar.value import PyCalendarValue class PyCalendarTextValue(PyCalendarPlainTextValue): def getType(self): return PyCalendarValue.VALUETYPE_TEXT def parse(self, data): # Decoding required self.mValue = utils.decodeTextValue(data) # os - StringIO object def generate(self, os): try: # Encoding required utils.writeTextValue(os, self.mValue) except: pass PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_TEXT, PyCalendarTextValue, xmldefs.value_text) pycalendar-2.0~svn13177/src/pycalendar/adr.py0000644000175000017500000000530612101017573020067 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # vCard ADR value from pycalendar import utils from pycalendar.valueutils import ValueMixin class Adr(ValueMixin): """ mValue is a tuple of seven str or tuples of str """ ( POBOX, EXTENDED, STREET, LOCALITY, REGION, POSTALCODE, COUNTRY, MAXITEMS ) = range(8) def __init__(self, pobox="", extended="", street="", locality="", region="", postalcode="", country=""): self.mValue = (pobox, extended, street, locality, region, postalcode, country) def duplicate(self): return Adr(*self.mValue) def __hash__(self): return hash(self.mValue) def __repr__(self): return "ADR %s" % (self.getText(),) def __eq__(self, comp): return self.mValue == comp.mValue def getPobox(self): return self.mValue[Adr.POBOX] def setPobox(self, value): self.mValue[Adr.POBOX] = value def getExtended(self): return self.mValue[Adr.EXTENDED] def setExtended(self, value): self.mValue[Adr.EXTENDED] = value def getStreet(self): return self.mValue[Adr.STREET] def setStreet(self, value): self.mValue[Adr.STREET] = value def getLocality(self): return self.mValue[Adr.LOCALITY] def setLocality(self, value): self.mValue[Adr.LOCALITY] = value def getRegion(self): return self.mValue[Adr.REGION] def setRegion(self, value): self.mValue[Adr.REGION] = value def getPostalCode(self): return self.mValue[Adr.POSTALCODE] def setPostalCode(self, value): self.mValue[Adr.POSTALCODE] = value def getCountry(self): return self.mValue[Adr.COUNTRY] def setCountry(self, value): self.mValue[Adr.COUNTRY] = value def parse(self, data): self.mValue = utils.parseDoubleNestedList(data, Adr.MAXITEMS) def generate(self, os): utils.generateDoubleNestedList(os, self.mValue) def getValue(self): return self.mValue def setValue(self, value): self.mValue = value pycalendar-2.0~svn13177/src/pycalendar/orgvalue.py0000644000175000017500000000254112101017573021143 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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. ## # vCard ORG value from pycalendar import utils from pycalendar.value import PyCalendarValue class OrgValue(PyCalendarValue): """ mValue is a str or tuple of str """ def __init__(self, value=None): self.mValue = value def duplicate(self): return OrgValue(self.mValue) def getType(self): return PyCalendarValue.VALUETYPE_ORG def parse(self, data): self.mValue = utils.parseTextList(data, ';') def generate(self, os): utils.generateTextList(os, self.mValue, ';') def getValue(self): return self.mValue def setValue(self, value): self.mValue = value PyCalendarValue.registerType(PyCalendarValue.VALUETYPE_ORG, OrgValue, None) pycalendar-2.0~svn13177/src/pycalendar/vtimezone.py0000644000175000017500000002410012317143440021334 0ustar rahulrahul## # Copyright (c) 2007-2012 Cyrus Daboo. 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 pycalendar import definitions from pycalendar.component import PyCalendarComponent from pycalendar.datetime import PyCalendarDateTime from pycalendar.icalendar.validation import ICALENDAR_VALUE_CHECKS class PyCalendarVTimezone(PyCalendarComponent): propertyCardinality_1 = ( definitions.cICalProperty_TZID, ) propertyCardinality_0_1 = ( definitions.cICalProperty_LAST_MODIFIED, definitions.cICalProperty_TZURL, ) propertyValueChecks = ICALENDAR_VALUE_CHECKS UTCOFFSET_CACHE_MAX_ENTRIES = 100000 sortSubComponents = False def __init__(self, parent=None): super(PyCalendarVTimezone, self).__init__(parent=parent) self.mID = "" self.mUTCOffsetSortKey = None self.mCachedExpandAllMaxYear = None self.mCachedOffsets = None def duplicate(self, parent=None): other = super(PyCalendarVTimezone, self).duplicate(parent=parent) other.mID = self.mID other.mUTCOffsetSortKey = self.mUTCOffsetSortKey return other def getType(self): return definitions.cICalComponent_VTIMEZONE def getMimeComponentName(self): # Cannot be sent as a separate MIME object return None def addComponent(self, comp): # We can embed the timezone components only if ((comp.getType() == definitions.cICalComponent_STANDARD) or (comp.getType() == definitions.cICalComponent_DAYLIGHT)): super(PyCalendarVTimezone, self).addComponent(comp) else: raise ValueError def getMapKey(self): return self.mID def finalise(self): # Get TZID temp = self.loadValueString(definitions.cICalProperty_TZID) if temp is not None: self.mID = temp # Sort sub-components by DTSTART self.mComponents.sort(key=lambda x: x.getStart()) # Do inherited super(PyCalendarVTimezone, self).finalise() def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended to that. """ fixed, unfixed = super(PyCalendarVTimezone, self).validate(doFix) # Must have at least one STANDARD or DAYLIGHT sub-component for component in self.mComponents: if component.getType() in (definitions.cICalComponent_STANDARD, definitions.cICalComponent_DAYLIGHT): break else: # Cannot fix a missing required component logProblem = "[%s] At least one component must be present: %s or %s" % ( self.getType(), definitions.cICalComponent_STANDARD, definitions.cICalComponent_DAYLIGHT, ) unfixed.append(logProblem) return fixed, unfixed def getID(self): return self.mID def getUTCOffsetSortKey(self): if self.mUTCOffsetSortKey is None: # Take time from first element if len(self.mComponents) > 0: # Initial offset provides the primary key utc_offset1 = self.mComponents[0].getUTCOffset() # Presence of secondary is the next key utc_offset2 = utc_offset1 if len(self.mComponents) > 1: utc_offset2 = self.mComponents[1].getUTCOffset() # Create key self.mUTCOffsetSortKey = (utc_offset1 + utc_offset2) / 2 else: self.mUTCOffsetSortKey = 0 return self.mUTCOffsetSortKey def getTimezoneOffsetSeconds(self, dt, relative_to_utc=False): """ Caching implementation of expansion. We cache the entire set of transitions up to one year ahead of the requested time. We need to handle calculating the offset based on both a local time and a UTC time. The later is needed when converting from one timezone offset to another which is best done by determining the UTC time as an intermediate value. @param dt: a date-time to determine the offset for @type dt: L{DateTime} @param relative_to_utc: if L{False}, then the L{dt} value is the local time for which an offset is desired, if L{True}, then the L{dt} value is a UTC time for which an offset is desired. @type relative_to_utc: L{bool} """ # Need to make the incoming date-time relative to the DTSTART in the # timezone component for proper comparison. # This means making the incoming date-time a floating (no timezone) # item temp = dt.duplicate() temp.setTimezoneID(None) # Check whether we need to recache if self.mCachedExpandAllMaxYear is None or temp.mYear >= self.mCachedExpandAllMaxYear: cacheMax = temp.duplicate() cacheMax.setHHMMSS(0, 0, 0) cacheMax.offsetYear(2) cacheMax.setMonth(1) cacheMax.setDay(1) self.mCachedExpandAll = self.expandAll(None, cacheMax) self.mCachedExpandAllMaxYear = cacheMax.mYear self.mCachedOffsets = {} # Now search for the transition just below the time we want if len(self.mCachedExpandAll): cacheKey = (temp.mYear, temp.mMonth, temp.mDay, temp.mHours, temp.mMinutes, relative_to_utc) i = self.mCachedOffsets.get(cacheKey) if i is None: i = PyCalendarVTimezone.tuple_bisect_right(self.mCachedExpandAll, temp, relative_to_utc) if len(self.mCachedOffsets) >= self.UTCOFFSET_CACHE_MAX_ENTRIES: self.mCachedOffsets = {} self.mCachedOffsets[cacheKey] = i if i != 0: return self.mCachedExpandAll[i - 1][3] return 0 def getTimezoneDescriptor(self, dt): result = "" # Get the closet matching element to the time found = self.findTimezoneElement(dt) # Get it if found is not None: if len(found.getTZName()) == 0: tzoffset = found.getUTCOffset() negative = False if tzoffset < 0: tzoffset = -tzoffset negative = True result = ("+", "-")[negative] hours_offset = tzoffset / (60 * 60) if hours_offset < 10: result += "0" result += str(hours_offset) mins_offset = (tzoffset / 60) % 60 if mins_offset < 10: result += "0" result += str(mins_offset) else: result = "(" result += found.getTZName() result += ")" return result def mergeTimezone(self, tz): pass @staticmethod def tuple_bisect_right(a, x, relative_to_utc=False): """ Same as bisect_right except that the values being compared are the first elements of a tuple. """ lo = 0 hi = len(a) while lo < hi: mid = (lo + hi) // 2 if x < a[mid][1 if relative_to_utc else 0]: hi = mid else: lo = mid + 1 return lo def findTimezoneElement(self, dt): # Need to make the incoming date-time relative to the DTSTART in the # timezone component for proper comparison. # This means making the incoming date-time a floating (no timezone) # item temp = dt.duplicate() temp.setTimezoneID(None) # Had to rework this because some VTIMEZONEs have sub-components where the DST instances are interleaved. That # means we have to evaluate each and every sub-component to find the instance immediately less than the time we are checking. # Now do the expansion for each one found and pick the lowest found = None dt_found = PyCalendarDateTime() for item in self.mComponents: dt_item = item.expandBelow(temp) if temp >= dt_item: if found is not None: # Compare with the one previously cached and switch to this # one if newer if dt_item > dt_found: found = item dt_found = dt_item else: found = item dt_found = dt_item return found def expandAll(self, start, end, with_name=False): results = [] for item in self.mComponents: results.extend(item.expandAll(start, end, with_name)) utc_results = [] for items in set(results): items = list(items) utcdt = items[0].duplicate() utcdt.offsetSeconds(-items[1]) items.insert(1, utcdt) utc_results.append(tuple(items)) utc_results.sort(key=lambda x: x[0].getPosixTime()) return utc_results def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_TZID, definitions.cICalProperty_LAST_MODIFIED, definitions.cICalProperty_TZURL, ) @staticmethod def sortByUTCOffsetComparator(tz1, tz2): sort1 = tz1.getUTCOffsetSortKey() sort2 = tz2.getUTCOffsetSortKey() if sort1 == sort2: return tz1.getID().compareToIgnoreCase(tz2.getID()) else: return (1, -1)[sort1 < sort2] pycalendar-2.0~svn13177/.pydevproject0000644000175000017500000000065312301206170016550 0ustar rahulrahul python 2.7 /${PROJECT_DIR_NAME}/src Default