|
1 """ This module provides a set of constructors and routines to convert |
|
2 between DateTime[Delta] instances and ARPA representations of date |
|
3 and time. The format is specified by RFC822 + RFC1123. |
|
4 |
|
5 Note: Timezones are only interpreted by ParseDateTimeGMT(). All |
|
6 other constructors silently ignore the time zone information. |
|
7 |
|
8 Copyright (c) 1998-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com |
|
9 Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com |
|
10 See the documentation for further information on copyrights, |
|
11 or contact the author. All Rights Reserved. |
|
12 |
|
13 """ |
|
14 import DateTime,Timezone |
|
15 import re,string |
|
16 |
|
17 # Grammar: RFC822 + RFC1123 + depreciated RFC850 |
|
18 _litday = '(?P<litday>Mon|Tue|Wed|Thu|Fri|Sat|Sun)[a-z]*' |
|
19 _litmonth = '(?P<litmonth>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'\ |
|
20 '[a-z]*' |
|
21 _date = ('(?:(?P<day>\d?\d)(?: +' + _litmonth + |
|
22 ' +|-(?P<month>\d?\d)-)(?P<year>(?:\d\d)?\d\d))') |
|
23 _zone = Timezone.zone |
|
24 _time = ('(?:(?P<hour>\d\d):(?P<minute>\d\d)' |
|
25 '(?::(?P<second>\d\d))?(?: +'+_zone+')?)') |
|
26 # Timezone information is made optional because some mail apps |
|
27 # forget to add it (most of these seem to be spamming engines, btw). |
|
28 # It defaults to UTC. |
|
29 |
|
30 _arpadate = '(?:'+ _litday + ',? )? *' + _date |
|
31 _arpadatetime = '(?:'+ _litday + ',? )? *' + _date + ' +' + _time |
|
32 |
|
33 # We are not strict about the extra characters: some applications |
|
34 # add extra information to the date header field. Additional spaces |
|
35 # between the fields and extra characters in the literal day |
|
36 # and month fields are also silently ignored. |
|
37 |
|
38 arpadateRE = re.compile(_arpadate) |
|
39 arpadatetimeRE = re.compile(_arpadatetime) |
|
40 |
|
41 # Translation tables |
|
42 litdaytable = {'mon':0, 'tue':1, 'wed':2, 'thu':3, 'fri':4, 'sat':5, 'sun':6 } |
|
43 litmonthtable = {'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6, |
|
44 'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12 } |
|
45 _days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] |
|
46 _months = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', |
|
47 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] |
|
48 |
|
49 def ParseDate(arpastring,parse_arpadate=arpadateRE.match, |
|
50 |
|
51 strip=string.strip,atoi=string.atoi,atof=string.atof, |
|
52 lower=string.lower): |
|
53 |
|
54 """ParseDate(arpastring) |
|
55 |
|
56 Returns a DateTime instance reflecting the given ARPA |
|
57 date. Only the date part is parsed, any time part will be |
|
58 ignored. The instance's time is set to 0:00:00. |
|
59 |
|
60 """ |
|
61 s = strip(arpastring) |
|
62 date = parse_arpadate(s) |
|
63 if not date: |
|
64 raise ValueError,'wrong format' |
|
65 litday,day,litmonth,month,year = date.groups() |
|
66 if len(year) == 2: |
|
67 year = DateTime.add_century(atoi(year)) |
|
68 else: |
|
69 year = atoi(year) |
|
70 if litmonth: |
|
71 litmonth = lower(litmonth) |
|
72 try: |
|
73 month = litmonthtable[litmonth] |
|
74 except KeyError: |
|
75 raise ValueError,'wrong month format' |
|
76 else: |
|
77 month = atoi(month) |
|
78 day = atoi(day) |
|
79 # litday and timezone are ignored |
|
80 return DateTime.DateTime(year,month,day) |
|
81 |
|
82 def ParseDateTime(arpastring,parse_arpadatetime=arpadatetimeRE.match, |
|
83 |
|
84 strip=string.strip,atoi=string.atoi,atof=string.atof, |
|
85 lower=string.lower): |
|
86 |
|
87 """ParseDateTime(arpastring) |
|
88 |
|
89 Returns a DateTime instance reflecting the given ARPA date assuming |
|
90 it is local time (timezones are silently ignored). |
|
91 """ |
|
92 s = strip(arpastring) |
|
93 date = parse_arpadatetime(s) |
|
94 if not date: |
|
95 raise ValueError,'wrong format or unknown time zone' |
|
96 litday,day,litmonth,month,year,hour,minute,second,zone = date.groups() |
|
97 if len(year) == 2: |
|
98 year = DateTime.add_century(atoi(year)) |
|
99 else: |
|
100 year = atoi(year) |
|
101 if litmonth: |
|
102 litmonth = lower(litmonth) |
|
103 try: |
|
104 month = litmonthtable[litmonth] |
|
105 except KeyError: |
|
106 raise ValueError,'wrong month format' |
|
107 else: |
|
108 month = atoi(month) |
|
109 day = atoi(day) |
|
110 hour = atoi(hour) |
|
111 minute = atoi(minute) |
|
112 if second is None: |
|
113 second = 0.0 |
|
114 else: |
|
115 second = atof(second) |
|
116 # litday and timezone are ignored |
|
117 return DateTime.DateTime(year,month,day,hour,minute,second) |
|
118 |
|
119 def ParseDateTimeGMT(arpastring,parse_arpadatetime=arpadatetimeRE.match, |
|
120 |
|
121 strip=string.strip,atoi=string.atoi,atof=string.atof, |
|
122 lower=string.lower): |
|
123 |
|
124 """ParseDateTimeGMT(arpastring) |
|
125 |
|
126 Returns a DateTime instance reflecting the given ARPA date converting |
|
127 it to UTC (timezones are honored). |
|
128 """ |
|
129 s = strip(arpastring) |
|
130 date = parse_arpadatetime(s) |
|
131 if not date: |
|
132 raise ValueError,'wrong format or unknown time zone' |
|
133 litday,day,litmonth,month,year,hour,minute,second,zone = date.groups() |
|
134 if len(year) == 2: |
|
135 year = DateTime.add_century(atoi(year)) |
|
136 else: |
|
137 year = atoi(year) |
|
138 if litmonth: |
|
139 litmonth = lower(litmonth) |
|
140 try: |
|
141 month = litmonthtable[litmonth] |
|
142 except KeyError: |
|
143 raise ValueError,'wrong month format' |
|
144 else: |
|
145 month = atoi(month) |
|
146 day = atoi(day) |
|
147 hour = atoi(hour) |
|
148 minute = atoi(minute) |
|
149 if second is None: |
|
150 second = 0.0 |
|
151 else: |
|
152 second = atof(second) |
|
153 offset = Timezone.utc_offset(zone) |
|
154 # litday is ignored |
|
155 return DateTime.DateTime(year,month,day,hour,minute,second) - offset |
|
156 |
|
157 # Alias |
|
158 ParseDateTimeUTC = ParseDateTimeGMT |
|
159 |
|
160 def str(datetime,tz=None): |
|
161 |
|
162 """str(datetime,tz=DateTime.tz_offset(datetime)) |
|
163 |
|
164 Returns the datetime instance as ARPA date string. tz can be given |
|
165 as DateTimeDelta instance providing the time zone difference from |
|
166 datetime's zone to UTC. It defaults to |
|
167 DateTime.tz_offset(datetime) which assumes local time. """ |
|
168 |
|
169 if tz is None: |
|
170 tz = datetime.gmtoffset() |
|
171 return '%s, %02i %s %04i %02i:%02i:%02i %+03i%02i' % ( |
|
172 _days[datetime.day_of_week], datetime.day, |
|
173 _months[datetime.month], datetime.year, |
|
174 datetime.hour, datetime.minute, datetime.second, |
|
175 tz.hour,tz.minute) |
|
176 |
|
177 def strGMT(datetime): |
|
178 |
|
179 """ strGMT(datetime) |
|
180 |
|
181 Returns the datetime instance as ARPA date string assuming it |
|
182 is given in GMT. """ |
|
183 |
|
184 return '%s, %02i %s %04i %02i:%02i:%02i GMT' % ( |
|
185 _days[datetime.day_of_week], datetime.day, |
|
186 _months[datetime.month], datetime.year, |
|
187 datetime.hour, datetime.minute, datetime.second) |
|
188 |
|
189 def strUTC(datetime): |
|
190 |
|
191 """ strUTC(datetime) |
|
192 |
|
193 Returns the datetime instance as ARPA date string assuming it |
|
194 is given in UTC. """ |
|
195 |
|
196 return '%s, %02i %s %04i %02i:%02i:%02i UTC' % ( |
|
197 _days[datetime.day_of_week], datetime.day, |
|
198 _months[datetime.month], datetime.year, |
|
199 datetime.hour, datetime.minute, datetime.second) |
|
200 |
|
201 def _test(): |
|
202 import sys, os, rfc822 |
|
203 file = os.path.join(os.environ['HOME'], 'nsmail/Inbox') |
|
204 f = open(file, 'r') |
|
205 while 1: |
|
206 m = rfc822.Message(f) |
|
207 if not m: |
|
208 break |
|
209 print 'From:', m.getaddr('from') |
|
210 print 'To:', m.getaddrlist('to') |
|
211 print 'Subject:', m.getheader('subject') |
|
212 raw = m.getheader('date') |
|
213 try: |
|
214 date = ParseDateTimeUTC(raw) |
|
215 print 'Date:',strUTC(date) |
|
216 except ValueError,why: |
|
217 print 'PROBLEMS:',repr(raw),'-->',why |
|
218 raw_input('...hit return to continue') |
|
219 print |
|
220 # Netscape mail file |
|
221 while 1: |
|
222 line = f.readline() |
|
223 if line[:6] == 'From -': |
|
224 break |
|
225 |
|
226 if __name__ == '__main__': |
|
227 _test() |