Merge ~cjwatson/launchpad:dateutil.tz into launchpad:master
- Git
- lp:~cjwatson/launchpad
- dateutil.tz
- Merge into master
Proposed by
Colin Watson
Status: | Merged |
---|---|
Approved by: | Colin Watson |
Approved revision: | c22c48bb6327ebe213ae61c44b00e689c1a012c5 |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~cjwatson/launchpad:dateutil.tz |
Merge into: | launchpad:master |
Diff against target: |
504 lines (+108/-69) 15 files modified
lib/lp/app/browser/tales.py (+2/-2) lib/lp/app/widgets/date.py (+16/-18) lib/lp/blueprints/browser/sprint.py (+10/-6) lib/lp/blueprints/browser/sprintattendance.py (+6/-6) lib/lp/bugs/doc/bugnotification-email.rst (+3/-3) lib/lp/bugs/doc/externalbugtracker.rst (+2/-2) lib/lp/bugs/tests/bugs-emailinterface.rst (+5/-5) lib/lp/registry/browser/person.py (+5/-7) lib/lp/services/webapp/doc/launchbag.rst (+1/-1) lib/lp/services/webapp/launchbag.py (+2/-2) lib/lp/services/worlddata/doc/vocabularies.rst (+2/-4) lib/lp/services/worlddata/vocabularies.py (+49/-8) lib/lp/snappy/tests/test_snapbuildbehaviour.py (+3/-3) requirements/types.txt (+1/-1) setup.cfg (+1/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jürgen Gmach | Approve | ||
Review via email: mp+444821@code.launchpad.net |
Commit message
Remove direct dependencies on pytz
Description of the change
`dateutil.tz` is a better fit for Python's modern timezone provider interface, and has fewer footguns as a result.
The only significant downside is that we have to reimplement something similar to `pytz.common_
To post a comment you must log in.
Revision history for this message
Jürgen Gmach (jugmac00) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/app/browser/tales.py b/lib/lp/app/browser/tales.py |
2 | index 0e0b0e5..44781e8 100644 |
3 | --- a/lib/lp/app/browser/tales.py |
4 | +++ b/lib/lp/app/browser/tales.py |
5 | @@ -12,7 +12,7 @@ from email.utils import formatdate, mktime_tz |
6 | from textwrap import dedent |
7 | from urllib.parse import quote |
8 | |
9 | -import pytz |
10 | +from dateutil import tz |
11 | from lazr.restful.utils import get_current_browser_request |
12 | from lazr.uri import URI |
13 | from zope.browserpage import ViewPageTemplateFile |
14 | @@ -1293,7 +1293,7 @@ class PersonFormatterAPI(ObjectFormatterAPI): |
15 | def local_time(self): |
16 | """Return the local time for this person.""" |
17 | time_zone = self._context.time_zone |
18 | - dt = datetime.now(pytz.timezone(time_zone)) |
19 | + dt = datetime.now(tz.gettz(time_zone)) |
20 | return "%s %s" % (dt.strftime("%T"), tzname(dt)) |
21 | |
22 | def url(self, view_name=None, rootsite="mainsite"): |
23 | diff --git a/lib/lp/app/widgets/date.py b/lib/lp/app/widgets/date.py |
24 | index e024dfa..ce27881 100644 |
25 | --- a/lib/lp/app/widgets/date.py |
26 | +++ b/lib/lp/app/widgets/date.py |
27 | @@ -19,7 +19,7 @@ __all__ = [ |
28 | |
29 | from datetime import datetime, timezone, tzinfo |
30 | |
31 | -import pytz |
32 | +from dateutil import tz |
33 | from zope.browserpage import ViewPageTemplateFile |
34 | from zope.component import getUtility |
35 | from zope.datetime import DateTimeError, parse |
36 | @@ -217,14 +217,23 @@ class DateTimeWidget(TextWidget): |
37 | >>> widget.required_time_zone_name = "Africa/Maseru" |
38 | >>> print(widget.time_zone_name) |
39 | Africa/Maseru |
40 | - >>> print(widget.time_zone) |
41 | - Africa/Maseru |
42 | + >>> print(widget.time_zone) # doctest: +ELLIPSIS |
43 | + tzfile('.../Africa/Maseru') |
44 | + |
45 | + When the required_time_zone_name is invalid, we fall back to UTC. |
46 | + |
47 | + >>> widget.required_time_zone_name = "Some/Nonsense" |
48 | + >>> print(widget.time_zone_name) |
49 | + Some/Nonsense |
50 | + >>> print(repr(widget.time_zone)) |
51 | + datetime.timezone.utc |
52 | |
53 | """ |
54 | if self.time_zone_name == "UTC": |
55 | return timezone.utc |
56 | else: |
57 | - return pytz.timezone(self.time_zone_name) |
58 | + zone = tz.gettz(self.time_zone_name) |
59 | + return zone if zone is not None else timezone.utc |
60 | |
61 | def _align_date_constraints_with_time_zone(self): |
62 | """Ensure that from_date and to_date use the widget time zone.""" |
63 | @@ -232,22 +241,14 @@ class DateTimeWidget(TextWidget): |
64 | if self.from_date.tzinfo is None: |
65 | # Timezone-naive constraint is interpreted as being in the |
66 | # widget time zone. |
67 | - if hasattr(self.time_zone, "localize"): # pytz |
68 | - self.from_date = self.time_zone.localize(self.from_date) |
69 | - else: |
70 | - self.from_date = self.from_date.replace( |
71 | - tzinfo=self.time_zone |
72 | - ) |
73 | + self.from_date = self.from_date.replace(tzinfo=self.time_zone) |
74 | else: |
75 | self.from_date = self.from_date.astimezone(self.time_zone) |
76 | if isinstance(self.to_date, datetime): |
77 | if self.to_date.tzinfo is None: |
78 | # Timezone-naive constraint is interpreted as being in the |
79 | # widget time zone. |
80 | - if hasattr(self.time_zone, "localize"): # pytz |
81 | - self.to_date = self.time_zone.localize(self.to_date) |
82 | - else: |
83 | - self.to_date = self.to_date.replace(tzinfo=self.time_zone) |
84 | + self.to_date = self.to_date.replace(tzinfo=self.time_zone) |
85 | else: |
86 | self.to_date = self.to_date.astimezone(self.time_zone) |
87 | |
88 | @@ -426,10 +427,7 @@ class DateTimeWidget(TextWidget): |
89 | dt = datetime(year, month, day, hour, minute, int(second), micro) |
90 | except (DateTimeError, ValueError, IndexError) as v: |
91 | raise ConversionError("Invalid date value", v) |
92 | - if hasattr(self.time_zone, "localize"): # pytz |
93 | - return self.time_zone.localize(dt) |
94 | - else: |
95 | - return dt.replace(tzinfo=self.time_zone) |
96 | + return dt.replace(tzinfo=self.time_zone) |
97 | |
98 | def _toFormValue(self, value): |
99 | """Convert a date to its string representation. |
100 | diff --git a/lib/lp/blueprints/browser/sprint.py b/lib/lp/blueprints/browser/sprint.py |
101 | index 41148c0..debe896 100644 |
102 | --- a/lib/lp/blueprints/browser/sprint.py |
103 | +++ b/lib/lp/blueprints/browser/sprint.py |
104 | @@ -27,7 +27,7 @@ import io |
105 | from collections import defaultdict |
106 | from typing import List |
107 | |
108 | -import pytz |
109 | +from dateutil import tz |
110 | from lazr.restful.utils import smartquote |
111 | from zope.component import getUtility |
112 | from zope.formlib.widget import CustomWidgetFactory |
113 | @@ -190,7 +190,7 @@ class SprintView(HasSpecificationsView): |
114 | def initialize(self): |
115 | self.notices = [] |
116 | self.latest_specs_limit = 5 |
117 | - self.tzinfo = pytz.timezone(self.context.time_zone) |
118 | + self.tzinfo = tz.gettz(self.context.time_zone) |
119 | |
120 | def attendance(self): |
121 | """establish if this user is attending""" |
122 | @@ -246,14 +246,18 @@ class SprintView(HasSpecificationsView): |
123 | @property |
124 | def local_start(self): |
125 | """The sprint start time, in the local time zone, as text.""" |
126 | - tz = pytz.timezone(self.context.time_zone) |
127 | - return self._formatLocal(self.context.time_starts.astimezone(tz)) |
128 | + return self._formatLocal( |
129 | + self.context.time_starts.astimezone( |
130 | + tz.gettz(self.context.time_zone) |
131 | + ) |
132 | + ) |
133 | |
134 | @property |
135 | def local_end(self): |
136 | """The sprint end time, in the local time zone, as text.""" |
137 | - tz = pytz.timezone(self.context.time_zone) |
138 | - return self._formatLocal(self.context.time_ends.astimezone(tz)) |
139 | + return self._formatLocal( |
140 | + self.context.time_ends.astimezone(tz.gettz(self.context.time_zone)) |
141 | + ) |
142 | |
143 | |
144 | class SprintAddView(LaunchpadFormView): |
145 | diff --git a/lib/lp/blueprints/browser/sprintattendance.py b/lib/lp/blueprints/browser/sprintattendance.py |
146 | index 194f340..baeea2f 100644 |
147 | --- a/lib/lp/blueprints/browser/sprintattendance.py |
148 | +++ b/lib/lp/blueprints/browser/sprintattendance.py |
149 | @@ -10,7 +10,7 @@ __all__ = [ |
150 | |
151 | from datetime import timedelta |
152 | |
153 | -import pytz |
154 | +from dateutil import tz |
155 | from zope.formlib.widget import CustomWidgetFactory |
156 | |
157 | from lp import _ |
158 | @@ -56,7 +56,7 @@ class BaseSprintAttendanceAddView(LaunchpadFormView): |
159 | # after the sprint. We will accept a time just before or just after |
160 | # and map those to the beginning and end times, respectively, in |
161 | # self.getDates(). |
162 | - time_zone = pytz.timezone(self.context.time_zone) |
163 | + time_zone = tz.gettz(self.context.time_zone) |
164 | from_date = self.context.time_starts.astimezone(time_zone) |
165 | to_date = self.context.time_ends.astimezone(time_zone) |
166 | self.starts_widget.from_date = from_date - timedelta(days=1) |
167 | @@ -142,16 +142,16 @@ class BaseSprintAttendanceAddView(LaunchpadFormView): |
168 | @property |
169 | def local_start(self): |
170 | """The sprint start time, in the local time zone, as text.""" |
171 | - tz = pytz.timezone(self.context.time_zone) |
172 | - return self.context.time_starts.astimezone(tz).strftime( |
173 | + time_zone = tz.gettz(self.context.time_zone) |
174 | + return self.context.time_starts.astimezone(time_zone).strftime( |
175 | self._local_timeformat |
176 | ) |
177 | |
178 | @property |
179 | def local_end(self): |
180 | """The sprint end time, in the local time zone, as text.""" |
181 | - tz = pytz.timezone(self.context.time_zone) |
182 | - return self.context.time_ends.astimezone(tz).strftime( |
183 | + time_zone = tz.gettz(self.context.time_zone) |
184 | + return self.context.time_ends.astimezone(time_zone).strftime( |
185 | self._local_timeformat |
186 | ) |
187 | |
188 | diff --git a/lib/lp/bugs/doc/bugnotification-email.rst b/lib/lp/bugs/doc/bugnotification-email.rst |
189 | index 9f07a1e..94a7a2b 100644 |
190 | --- a/lib/lp/bugs/doc/bugnotification-email.rst |
191 | +++ b/lib/lp/bugs/doc/bugnotification-email.rst |
192 | @@ -585,12 +585,12 @@ method requires a from address, a to person, a body, a subject and a sending |
193 | date for the mail. |
194 | |
195 | >>> from datetime import datetime |
196 | - >>> import pytz |
197 | + >>> from dateutil import tz |
198 | |
199 | >>> from_address = get_bugmail_from_address(lp_janitor, bug_four) |
200 | >>> to_person = getUtility(IPersonSet).getByEmail("foo.bar@canonical.com") |
201 | - >>> sending_date = pytz.timezone("Europe/Prague").localize( |
202 | - ... datetime(2008, 5, 20, 11, 5, 47) |
203 | + >>> sending_date = datetime( |
204 | + ... 2008, 5, 20, 11, 5, 47, tzinfo=tz.gettz("Europe/Prague") |
205 | ... ) |
206 | |
207 | >>> notification_email = bug_four_notification_builder.build( |
208 | diff --git a/lib/lp/bugs/doc/externalbugtracker.rst b/lib/lp/bugs/doc/externalbugtracker.rst |
209 | index 84a1835..b2288ef 100644 |
210 | --- a/lib/lp/bugs/doc/externalbugtracker.rst |
211 | +++ b/lib/lp/bugs/doc/externalbugtracker.rst |
212 | @@ -361,8 +361,8 @@ the time is. |
213 | If the difference between what we and the remote system think the time |
214 | is, an error is raised. |
215 | |
216 | - >>> import pytz |
217 | >>> from datetime import datetime, timedelta, timezone |
218 | + >>> from dateutil import tz |
219 | >>> utc_now = datetime.now(timezone.utc) |
220 | >>> class PositiveTimeSkewExternalBugTracker(TestExternalBugTracker): |
221 | ... def getCurrentDBTime(self): |
222 | @@ -417,7 +417,7 @@ than the UTC time. |
223 | |
224 | >>> class LocalTimeExternalBugTracker(TestExternalBugTracker): |
225 | ... def getCurrentDBTime(self): |
226 | - ... local_time = utc_now.astimezone(pytz.timezone("US/Eastern")) |
227 | + ... local_time = utc_now.astimezone(tz.gettz("US/Eastern")) |
228 | ... return local_time + timedelta(minutes=1) |
229 | ... |
230 | >>> bug_watch_updater.updateBugWatches( |
231 | diff --git a/lib/lp/bugs/tests/bugs-emailinterface.rst b/lib/lp/bugs/tests/bugs-emailinterface.rst |
232 | index b0d3575..5f87927 100644 |
233 | --- a/lib/lp/bugs/tests/bugs-emailinterface.rst |
234 | +++ b/lib/lp/bugs/tests/bugs-emailinterface.rst |
235 | @@ -3193,7 +3193,7 @@ we'll create a new bug on firefox and link it to a remote bug. |
236 | >>> no_priv = getUtility(IPersonSet).getByName("no-priv") |
237 | |
238 | >>> from datetime import datetime, timezone |
239 | - >>> import pytz |
240 | + >>> from dateutil import tz |
241 | >>> creation_date = datetime(2008, 4, 12, 10, 12, 12, tzinfo=timezone.utc) |
242 | |
243 | We create the initial bug message separately from the bug itself so that |
244 | @@ -3238,7 +3238,7 @@ importing machinery. |
245 | >>> bug_watch = getUtility(IBugWatchSet).get(bug_watch.id) |
246 | |
247 | >>> comment_date = datetime( |
248 | - ... 2008, 5, 19, 16, 19, 12, tzinfo=pytz.timezone("Europe/Prague") |
249 | + ... 2008, 5, 19, 16, 19, 12, tzinfo=tz.gettz("Europe/Prague") |
250 | ... ) |
251 | |
252 | >>> initial_mail = ( |
253 | @@ -3265,7 +3265,7 @@ Now someone uses the email interface to respond to the comment that has |
254 | been submitted. |
255 | |
256 | >>> comment_date = datetime( |
257 | - ... 2008, 5, 20, 11, 24, 12, tzinfo=pytz.timezone("Europe/Prague") |
258 | + ... 2008, 5, 20, 11, 24, 12, tzinfo=tz.gettz("Europe/Prague") |
259 | ... ) |
260 | |
261 | >>> reply_mail = ( |
262 | @@ -3318,7 +3318,7 @@ to an email that isn't linked to the bug, the new message will be linked |
263 | to the bug and will not have its bugwatch field set. |
264 | |
265 | >>> comment_date = datetime( |
266 | - ... 2008, 5, 21, 11, 9, 12, tzinfo=pytz.timezone("Europe/Prague") |
267 | + ... 2008, 5, 21, 11, 9, 12, tzinfo=tz.gettz("Europe/Prague") |
268 | ... ) |
269 | |
270 | >>> initial_mail = ( |
271 | @@ -3338,7 +3338,7 @@ to the bug and will not have its bugwatch field set. |
272 | >>> message = getUtility(IMessageSet).fromEmail(initial_mail, no_priv) |
273 | |
274 | >>> comment_date = datetime( |
275 | - ... 2008, 5, 21, 12, 52, 12, tzinfo=pytz.timezone("Europe/Prague") |
276 | + ... 2008, 5, 21, 12, 52, 12, tzinfo=tz.gettz("Europe/Prague") |
277 | ... ) |
278 | |
279 | >>> reply_mail = ( |
280 | diff --git a/lib/lp/registry/browser/person.py b/lib/lp/registry/browser/person.py |
281 | index 1484dab..f6c7ca2 100644 |
282 | --- a/lib/lp/registry/browser/person.py |
283 | +++ b/lib/lp/registry/browser/person.py |
284 | @@ -56,7 +56,7 @@ from operator import attrgetter, itemgetter |
285 | from textwrap import dedent |
286 | from urllib.parse import quote, urlencode |
287 | |
288 | -import pytz |
289 | +from dateutil import tz |
290 | from lazr.config import as_timedelta |
291 | from lazr.delegates import delegate_to |
292 | from lazr.restful.interface import copy_field |
293 | @@ -2043,9 +2043,7 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin): |
294 | @property |
295 | def time_zone_offset(self): |
296 | """Return a string with offset from UTC""" |
297 | - return datetime.now(pytz.timezone(self.context.time_zone)).strftime( |
298 | - "%z" |
299 | - ) |
300 | + return datetime.now(tz.gettz(self.context.time_zone)).strftime("%z") |
301 | |
302 | |
303 | class PersonParticipationView(LaunchpadView): |
304 | @@ -4419,13 +4417,13 @@ class PersonEditTimeZoneView(LaunchpadFormView): |
305 | @action(_("Update"), name="update") |
306 | def action_update(self, action, data): |
307 | """Set the time zone for the person.""" |
308 | - tz = data.get("time_zone") |
309 | - if tz is None: |
310 | + time_zone = data.get("time_zone") |
311 | + if time_zone is None: |
312 | raise UnexpectedFormData("No location received.") |
313 | # XXX salgado, 2012-02-16, bug=933699: Use setLocation() because it's |
314 | # the cheaper way to set the timezone of a person. Once the bug is |
315 | # fixed we'll be able to get rid of this hack. |
316 | - self.context.setLocation(None, None, tz, self.user) |
317 | + self.context.setLocation(None, None, time_zone, self.user) |
318 | |
319 | |
320 | def archive_to_person(archive): |
321 | diff --git a/lib/lp/services/webapp/doc/launchbag.rst b/lib/lp/services/webapp/doc/launchbag.rst |
322 | index 41e7b6e..091abaa 100644 |
323 | --- a/lib/lp/services/webapp/doc/launchbag.rst |
324 | +++ b/lib/lp/services/webapp/doc/launchbag.rst |
325 | @@ -115,4 +115,4 @@ After the LaunchBag has been cleared, the correct time zone is returned. |
326 | >>> launchbag.time_zone_name |
327 | 'Europe/Paris' |
328 | >>> launchbag.time_zone |
329 | - <... 'Europe/Paris' ...> |
330 | + tzfile('.../Europe/Paris') |
331 | diff --git a/lib/lp/services/webapp/launchbag.py b/lib/lp/services/webapp/launchbag.py |
332 | index f20dfe0..34118a3 100644 |
333 | --- a/lib/lp/services/webapp/launchbag.py |
334 | +++ b/lib/lp/services/webapp/launchbag.py |
335 | @@ -10,7 +10,7 @@ The collection of stuff we have traversed. |
336 | import threading |
337 | from datetime import timezone |
338 | |
339 | -import pytz |
340 | +from dateutil import tz |
341 | from zope.component import getUtility |
342 | from zope.interface import implementer |
343 | |
344 | @@ -161,7 +161,7 @@ class LaunchBag: |
345 | if self.time_zone_name == "UTC": |
346 | self._store.time_zone = timezone.utc |
347 | else: |
348 | - self._store.time_zone = pytz.timezone(self.time_zone_name) |
349 | + self._store.time_zone = tz.gettz(self.time_zone_name) |
350 | return self._store.time_zone |
351 | |
352 | |
353 | diff --git a/lib/lp/services/worlddata/doc/vocabularies.rst b/lib/lp/services/worlddata/doc/vocabularies.rst |
354 | index 4e4ec6e..1f698b3 100644 |
355 | --- a/lib/lp/services/worlddata/doc/vocabularies.rst |
356 | +++ b/lib/lp/services/worlddata/doc/vocabularies.rst |
357 | @@ -12,12 +12,10 @@ TimezoneName |
358 | The TimezoneName vocabulary should only contain timezone names that |
359 | do not raise an exception when instantiated. |
360 | |
361 | - >>> import pytz |
362 | + >>> from dateutil import tz |
363 | >>> timezone_vocabulary = vocabulary_registry.get(None, "TimezoneName") |
364 | >>> for timezone in timezone_vocabulary: |
365 | - ... # Assign the return value of pytz.timezone() to the zone |
366 | - ... # variable to prevent printing out the return value. |
367 | - ... zone = pytz.timezone(timezone.value) |
368 | + ... _ = tz.gettz(timezone.value) |
369 | ... |
370 | |
371 | LanguageVocabulary |
372 | diff --git a/lib/lp/services/worlddata/vocabularies.py b/lib/lp/services/worlddata/vocabularies.py |
373 | index bed76b1..72db756 100644 |
374 | --- a/lib/lp/services/worlddata/vocabularies.py |
375 | +++ b/lib/lp/services/worlddata/vocabularies.py |
376 | @@ -7,8 +7,6 @@ __all__ = [ |
377 | "TimezoneNameVocabulary", |
378 | ] |
379 | |
380 | -import pytz |
381 | -import six |
382 | from zope.component import getUtility |
383 | from zope.interface import alsoProvides |
384 | from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary |
385 | @@ -19,14 +17,57 @@ from lp.services.worlddata.interfaces.timezone import ITimezoneNameVocabulary |
386 | from lp.services.worlddata.model.country import Country |
387 | from lp.services.worlddata.model.language import Language |
388 | |
389 | -# create a sorted list of the common time zone names, with UTC at the start |
390 | -_values = sorted(six.ensure_text(tz) for tz in pytz.common_timezones) |
391 | -_values.remove("UTC") |
392 | -_values.insert(0, "UTC") |
393 | |
394 | -_timezone_vocab = SimpleVocabulary.fromValues(_values) |
395 | +def _common_timezones(): |
396 | + """A list of useful, current time zone names. |
397 | + |
398 | + This is inspired by `pytz.common_timezones`, which seems to be |
399 | + approximately the list supported by `tzdata` with the additions of some |
400 | + Canada- and US-specific names. Since we're aiming for current rather |
401 | + than historical zone names, `zone1970.tab` seems appropriate. |
402 | + """ |
403 | + zones = set() |
404 | + with open("/usr/share/zoneinfo/zone.tab") as zone_tab: |
405 | + for line in zone_tab: |
406 | + if line.startswith("#"): |
407 | + continue |
408 | + zones.add(line.rstrip("\n").split("\t")[2]) |
409 | + # Backward-compatible US zone names, still in common use. |
410 | + zones.update( |
411 | + { |
412 | + "US/Alaska", |
413 | + "US/Arizona", |
414 | + "US/Central", |
415 | + "US/Eastern", |
416 | + "US/Hawaii", |
417 | + "US/Mountain", |
418 | + "US/Pacific", |
419 | + } |
420 | + ) |
421 | + # Backward-compatible Canadian zone names; see |
422 | + # https://bugs.launchpad.net/pytz/+bug/506341. |
423 | + zones.update( |
424 | + { |
425 | + "Canada/Atlantic", |
426 | + "Canada/Central", |
427 | + "Canada/Eastern", |
428 | + "Canada/Mountain", |
429 | + "Canada/Newfoundland", |
430 | + "Canada/Pacific", |
431 | + } |
432 | + ) |
433 | + # pytz has this in addition to UTC. Perhaps it's more understandable |
434 | + # for people not steeped in time zone lore. |
435 | + zones.add("GMT") |
436 | + |
437 | + # UTC comes first, then everything else. |
438 | + yield "UTC" |
439 | + zones.discard("UTC") |
440 | + yield from sorted(zones) |
441 | + |
442 | + |
443 | +_timezone_vocab = SimpleVocabulary.fromValues(_common_timezones()) |
444 | alsoProvides(_timezone_vocab, ITimezoneNameVocabulary) |
445 | -del _values |
446 | |
447 | |
448 | def TimezoneNameVocabulary(context=None): |
449 | diff --git a/lib/lp/snappy/tests/test_snapbuildbehaviour.py b/lib/lp/snappy/tests/test_snapbuildbehaviour.py |
450 | index 28ffc4b..6508601 100644 |
451 | --- a/lib/lp/snappy/tests/test_snapbuildbehaviour.py |
452 | +++ b/lib/lp/snappy/tests/test_snapbuildbehaviour.py |
453 | @@ -12,8 +12,8 @@ from textwrap import dedent |
454 | from urllib.parse import urlsplit |
455 | |
456 | import fixtures |
457 | -import pytz |
458 | from aptsources.sourceslist import SourceEntry |
459 | +from dateutil import tz |
460 | from pymacaroons import Macaroon |
461 | from testtools import ExpectedException |
462 | from testtools.matchers import ( |
463 | @@ -101,8 +101,8 @@ class FormatAsRfc3339TestCase(TestCase): |
464 | self.assertEqual("2016-01-01T00:00:00Z", format_as_rfc3339(ts)) |
465 | |
466 | def test_tzinfo_is_ignored(self): |
467 | - tz = datetime(2016, 1, 1, tzinfo=pytz.timezone("US/Eastern")) |
468 | - self.assertEqual("2016-01-01T00:00:00Z", format_as_rfc3339(tz)) |
469 | + time_zone = datetime(2016, 1, 1, tzinfo=tz.gettz("US/Eastern")) |
470 | + self.assertEqual("2016-01-01T00:00:00Z", format_as_rfc3339(time_zone)) |
471 | |
472 | |
473 | class TestSnapBuildBehaviourBase(TestCaseWithFactory): |
474 | diff --git a/requirements/types.txt b/requirements/types.txt |
475 | index 221aad3..c42c102 100644 |
476 | --- a/requirements/types.txt |
477 | +++ b/requirements/types.txt |
478 | @@ -4,7 +4,7 @@ types-beautifulsoup4==4.9.0 |
479 | types-bleach==3.3.1 |
480 | types-oauthlib==3.1.0 |
481 | types-psycopg2==2.9.21.4 |
482 | -types-pytz==0.1.0 |
483 | +types-python-dateutil==2.8.1 |
484 | types-requests==0.1.13 |
485 | types-six==0.1.9 |
486 | types-urllib3==1.26.25.4 |
487 | diff --git a/setup.cfg b/setup.cfg |
488 | index ef37294..b2a0f29 100644 |
489 | --- a/setup.cfg |
490 | +++ b/setup.cfg |
491 | @@ -79,12 +79,12 @@ install_requires = |
492 | pymemcache |
493 | pyparsing |
494 | pystache |
495 | + python-dateutil |
496 | python-debian |
497 | python-keystoneclient |
498 | python-openid2 |
499 | python-subunit |
500 | python-swiftclient |
501 | - pytz |
502 | PyYAML |
503 | rabbitfixture |
504 | requests |