Skip to content

Commit 1a887c1

Browse files
committed
Added a module which handles parsing of time strings which include timezone information, tzparse.py.
- Legacy-Id: 4975
1 parent 8aa4922 commit 1a887c1

1 file changed

Lines changed: 185 additions & 0 deletions

File tree

tzparse.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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

Comments
 (0)