1+ #!/usr/bin/env python
2+
3+ """
4+ NAME
5+ tzparse
6+
7+ SYNOPSIS
8+ >>> tzparse("2008-09-08 14:40:35 +0200", "%Y-%m-%d %H:%M:%S %Z")
9+ datetime.datetime(2008, 9, 8, 14, 40, 35, tzinfo=pytz.FixedOffset(120))
10+
11+ >>> print tzparse("14:40:35 CEST, 08 Sep 2008", "%H:%M:%S %Z, %d %b %Y")
12+ 2008-09-08 14:40:35+02:00
13+
14+ DESCRIPTION
15+ This describes the python 'tzparse' module. It exports only one function: tzparse().
16+
17+ tzparse() parses a string according to a specified format, exactly as time.strptime()
18+ does, but with the added capability to parse most common timezone specifications,
19+ such as 'UTC', the standard timezones ('NST', 'EST', 'CST', 'MST', 'PST', 'HNY'
20+ [North America], 'WET', 'CET', 'EET', 'MSK' [Europe], and more), the summer timezones
21+ ('CEST', 'EEST', 'EDT', PDT' etc.), military timezones ('A' .. 'Z') and numeric
22+ timezone indications ('+0200', '-0700', '-03:30' etc.).
23+
24+ The time zone specification may be placed anywhere, not only at the end.
25+
26+ tzparse() calls time.strptime() to parse everything except the timezone. To parse
27+ the timezone, it first tries to use the pytz module, but if that doesn't give
28+ any joy, it falls back to a hardcoded list of common time zone abbreviations and
29+ their offset from UTC.
30+
31+ BUGS
32+
33+ * tzparse() cannot parse all valid RFC 3339 formats: it doesn't extract
34+ fractional seconds, and the underlying time.strptime() doesn't parse fractional
35+ seconds.
36+
37+ * Parsing according to format specifications using the generic %c, %x and %X
38+ specifiers will only succeed if there are explicit delimiting characters
39+ between the %Z specifier and the %c, %x or %X part.
40+
41+ COPYRIGHT
42+ Copyright 2009 Henrik Levkowetz
43+
44+ Licensed under the Apache License, Version 2.0 (the "License");
45+ you may not use this file except in compliance with the License.
46+ You may obtain a copy of the License at
47+
48+ http://www.apache.org/licenses/LICENSE-2.0
49+
50+ Unless required by applicable law or agreed to in writing, software
51+ distributed under the License is distributed on an "AS IS" BASIS,
52+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
53+ See the License for the specific language governing permissions and
54+ limitations under the License.
55+ """
56+
57+ import re
58+ import time
59+ from datetime import datetime as Datetime , timedelta as Timedelta
60+ import pytz
61+
62+ tzdef = {
63+ "A" : "+0100" , "ACDT" : "+1030" , "ACST" : "+0930" , "ADT" : "-0300" ,
64+ "AEDT" : "+1100" , "AEST" : "+1000" , "AKDT" : "-0800" , "AKST" : "-0900" ,
65+ "AST" : "-0400" , "AWDT" : "+0900" , "AWST" : "+0800" , "B" : "+0200" ,
66+ "BST" : "+0100" , "C" : "+0300" , "CDT" : "+1030" , "CDT" : "-0500" ,
67+ "CEDT" : "+0200" , "CEST" : "+0200" , "CET" : "+0100" , "CST" : "+1030" ,
68+ "CST" : "+0930" , "CST" : "-0600" , "CXT" : "+0700" , "D" : "+0400" ,
69+ "E" : "+0500" , "EDT" : "+1100" , "EDT" : "-0400" , "EEDT" : "+0300" ,
70+ "EEST" : "+0300" , "EET" : "+0200" , "EST" : "+1100" , "EST" : "+1000" ,
71+ "EST" : "-0500" , "F" : "+0600" , "G" : "+0700" , "GMT" : "+0000" ,
72+ "H" : "+0800" , "HAA" : "-0300" , "HAC" : "-0500" , "HADT" : "-0900" ,
73+ "HAE" : "-0400" , "HAP" : "-0700" , "HAR" : "-0600" , "HAST" : "-1000" ,
74+ "HAT" : "-0230" , "HAY" : "-0800" , "HNA" : "-0400" , "HNC" : "-0600" ,
75+ "HNE" : "-0500" , "HNP" : "-0800" , "HNR" : "-0700" , "HNT" : "-0330" ,
76+ "HNY" : "-0900" , "I" : "+0900" , "IST" : "+0100" , "K" : "+1000" ,
77+ "L" : "+1100" , "M" : "+1200" , "MDT" : "-0600" , "MESZ" : "+0200" ,
78+ "MEZ" : "+0100" , "MSD" : "+0400" , "MSK" : "+0300" , "MST" : "-0700" ,
79+ "N" : "-0100" , "NDT" : "-0230" , "NFT" : "+1130" , "NST" : "-0330" ,
80+ "O" : "-0200" , "P" : "-0300" , "PDT" : "-0700" , "PST" : "-0800" ,
81+ "Q" : "-0400" , "R" : "-0500" , "S" : "-0600" , "T" : "-0700" ,
82+ "U" : "-0800" , "UTC" : "+0000" , "V" : "-0900" , "W" : "-1000" ,
83+ "WDT" : "+0900" , "WEDT" : "+0100" , "WEST" : "+0100" , "WET" : "+0000" ,
84+ "WST" : "+0900" , "WST" : "+0800" , "X" : "-1100" , "Y" : "-1200" ,
85+ "Z" : "+0000" ,
86+ }
87+
88+
89+ def tzparse (string , format ):
90+ # It's surprising that there's no tz parsing capability in the python standard
91+ # library...
92+
93+ """
94+ Given a time specification string and a format, tzparse() returns a localized
95+ datetime.datetime.
96+
97+ >>> print tzparse("9 Oct 2009 CEST 13:58", "%d %b %Y %Z %H:%M")
98+ 2009-10-09 13:58:00+02:00
99+
100+ >>> print tzparse("9 Oct 2009 13:58:00 Europe/Stockholm", "%d %b %Y %H:%M:%S %Z")
101+ 2009-10-09 13:58:00+02:00
102+
103+ >>> print tzparse("9 Oct 2009 13:58:00 +0200", "%d %b %Y %H:%M:%S %Z")
104+ 2009-10-09 13:58:00+02:00
105+
106+ >>> print tzparse("Fri, 9 Oct 2009 13:58:00 +0200", "%a, %d %b %Y %H:%M:%S %Z")
107+ 2009-10-09 13:58:00+02:00
108+
109+ >>> print tzparse("2009-10-09 13:58:00 EST", '%Y-%m-%d %H:%M:%S %Z')
110+ 2009-10-09 13:58:00-05:00
111+
112+ >>> print tzparse("2009-10-09 13:58:00+02:00", "%Y-%m-%d %H:%M:%S%Z")
113+ 2009-10-09 13:58:00+02:00
114+
115+ >>> print tzparse("1985-04-12T23:20:50Z", "%Y-%m-%dT%H:%M:%S%Z")
116+ 1985-04-12 23:20:50+00:00
117+
118+ >>> print tzparse("1996-12-19T16:39:57-08:00", "%Y-%m-%dT%H:%M:%S%Z")
119+ 1996-12-19 16:39:57-08:00
120+
121+ >>> print tzparse("1996-12-19T16:39:57", "%Y-%m-%dT%H:%M:%S")
122+ 1996-12-19 16:39:57+01:00
123+
124+ """
125+
126+ if not "%Z" in format :
127+ timetuple = time .strptime (string , format )
128+ tzstr = time .tzname [0 ]
129+ else :
130+ # extract the %Z part from the format and build a pattern to extract it
131+ # from the string, too.
132+
133+ def fmt2pat (s ):
134+ s = re .sub ("%[dHIjmMSUwWyY]" , "\d+" , s )
135+ s = re .sub ("%[aAbBp]" , "\w+" , s )
136+ s = re .sub ("%[cxX]" , ".+" , s )
137+ s = s .replace ("%%" , "%" )
138+ return s
139+
140+ frontfmt , backfmt = format .split ("%Z" )
141+ frontpat = "^" + fmt2pat (frontfmt )
142+ backpat = fmt2pat (backfmt ) + "$"
143+
144+
145+ frontstr = re .search (frontpat , string ) and re .search (frontpat , string ).group (0 ) or ""
146+ backstr = re .search (backpat , string ) and re .search (backpat , string ).group (0 ) or ""
147+ tzstr = string .replace (frontstr , "" ).replace (backstr , "" ) # This will fail is backstr occurs twice
148+
149+ timetuple = time .strptime (frontstr + backstr , frontfmt + backfmt )
150+ dt = Datetime (* timetuple [:6 ])
151+
152+ if not tzstr :
153+ tzstr = time .tzname [0 ]
154+ #raise ValueError("No timezone string found in '%s', but format contained %Z: '%s'."%(string, format))
155+ try :
156+ tz = pytz .timezone (tzstr )
157+ except KeyError :
158+ if tzstr in tzdef :
159+ # if we know the offset of the abbreviation, fall back to that
160+ tzstr = tzdef [tzstr ]
161+ if re .search ("^[+-][0-9][0-9]:?[0-9][0-9]$" , tzstr ):
162+ if ":" in tzstr :
163+ tzstr = tzstr [:3 ]+ tzstr [4 :]
164+ # convert numeric timezone to minutes
165+ sign = tzstr [0 ]
166+ h = int (tzstr [1 :3 ])
167+ m = h * 60 + int (tzstr [3 :5 ])
168+ if sign == "-" :
169+ m = - m
170+ tz = pytz .FixedOffset (m )
171+ else :
172+ raise ValueError ("Unknown timezone '%s'" % tzstr )
173+ dt = tz .localize (dt )
174+
175+ return dt
176+
177+ if __name__ == "__main__" :
178+ import sys
179+ if len (sys .argv [1 :]) == 2 :
180+ print tzparse (sys .argv [1 ], sys .argv [2 ])
181+ else :
182+ print "Running module tests:\n "
183+ import doctest
184+ print doctest .testmod ()
185+
0 commit comments