Remove - and : from example DTSTAMPs
[pycalendar.git] / pycalendar / feed.py
1 # Copyright (C) 2013 W. Trevor King <wking@tremily.us>
2 #
3 # This file is part of pycalender.
4 #
5 # pycalender is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 3 of the License, or (at your option) any later
8 # version.
9 #
10 # pycalender is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along with
15 # pycalender.  If not, see <http://www.gnu.org/licenses/>.
16
17 import logging as _logging
18 import urllib.request as _urllib_request
19
20 from . import USER_AGENT as _USER_AGENT
21 from . import entry as _entry
22
23
24 _LOG = _logging.getLogger(__name__)
25
26
27 class Feed (_entry.Entry):
28     r"""An iCalendar feed (:RFC:`5545`)
29
30     Figure out where the example feed is located, relative to the
31     directory from which you run this doctest (i.e., the project's
32     root directory).
33
34     >>> import os
35     >>> root_dir = os.curdir
36     >>> data_file = os.path.abspath(os.path.join(
37     ...         root_dir, 'test', 'data', 'geohash.ics'))
38     >>> url = 'file://{}'.format(data_file.replace(os.sep, '/'))
39
40     Create a new feed pointing to this URL.
41
42     >>> f = Feed(url=url)
43     >>> f  # doctest: +ELLIPSIS
44     <Feed url:file://.../test/data/geohash.ics>
45     >>> print(f)
46     <BLANKLINE>
47
48     Load the feed content.
49
50     >>> f.fetch()
51
52     The ``.__str__`` method displays the feed content using Python's
53     universal newlines.
54
55     >>> print(f)  # doctest: +REPORT_UDIFF
56     BEGIN:VCALENDAR
57     VERSION:2.0
58     PRODID:-//Example Calendar//NONSGML v1.0//EN
59     BEGIN:VEVENT
60     UID:2013-06-30@geohash.invalid
61     DTSTAMP:20130630T000000Z
62     DTSTART;VALUE=DATE:20130630
63     DTEND;VALUE=DATE:20130701
64     SUMMARY:XKCD geohashing\, Boston graticule
65     URL:http://xkcd.com/426/
66     LOCATION:Snow Hill\, Dover\, Massachusetts
67     GEO:42.226663;-71.28676
68     END:VEVENT
69     END:VCALENDAR
70
71     To get the CRLF line endings specified in :RFC:`5545`, use the
72     ``.write`` method.
73
74     >>> import io
75     >>> stream = io.StringIO()
76     >>> f.write(stream=stream)
77     >>> stream.getvalue()  # doctest: +ELLIPSIS
78     'BEGIN:VCALENDAR\r\nVERSION:2.0\r\n...END:VCALENDAR\r\n'
79
80     You can also iterate through events:
81
82     >>> for event in f['VEVENT']:
83     ...     print(repr(event))
84     ...     print(event)
85     <Entry type:VEVENT>
86     BEGIN:VEVENT
87     UID:2013-06-30@geohash.invalid
88     DTSTAMP:20130630T000000Z
89     DTSTART;VALUE=DATE:20130630
90     DTEND;VALUE=DATE:20130701
91     SUMMARY:XKCD geohashing\, Boston graticule
92     URL:http://xkcd.com/426/
93     LOCATION:Snow Hill\, Dover\, Massachusetts
94     GEO:42.226663;-71.28676
95     END:VEVENT
96     """
97     def __init__(self, url, user_agent=None):
98         super(Feed, self).__init__(type='VCALENDAR')
99         self.url = url
100         if user_agent is None:
101             user_agent = _USER_AGENT
102         self.user_agent = user_agent
103
104     def __repr__(self):
105         return '<{} url:{}>'.format(type(self).__name__, self.url)
106
107     def fetch(self, force=False):
108         if self.content is None or force:
109             self._fetch()
110             self.process()
111
112     def _fetch(self):
113         request = _urllib_request.Request(
114             url=self.url,
115             headers={
116                 'User-Agent': self.user_agent,
117                 },
118             )
119         with _urllib_request.urlopen(url=request) as f:
120             info = f.info()
121             content_type = info.get('Content-type', None)
122             if content_type != 'text/calendar':
123                 raise ValueError(content_type)
124             byte_content = f.read()
125         self.content = str(byte_content, encoding='UTF-8')