Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

basic form validation, added end time, start of notifications, some f…

…ixes to queries, and maybe more
  • Loading branch information...
commit 47a7f7aac0da8b2399aae520d66673557572f3ad 1 parent fcf7602
@progrium progrium authored
Showing with 3,019 additions and 30 deletions.
  1. +7 −0 cron.yaml
  2. +1 −0  icalendar/prop.py
  3. +5 −0 index.yaml
  4. +120 −30 main.py
  5. +1,427 −0 pytz/__init__.py
  6. +127 −0 pytz/reference.py
  7. +35 −0 pytz/tests/test_docs.py
  8. +640 −0 pytz/tests/test_tzinfo.py
  9. +122 −0 pytz/tzfile.py
  10. +535 −0 pytz/tzinfo.py
  11. BIN  pytz/zoneinfo/Africa/Abidjan
  12. BIN  pytz/zoneinfo/Africa/Accra
  13. BIN  pytz/zoneinfo/Africa/Addis_Ababa
  14. BIN  pytz/zoneinfo/Africa/Algiers
  15. BIN  pytz/zoneinfo/Africa/Asmara
  16. BIN  pytz/zoneinfo/Africa/Asmera
  17. BIN  pytz/zoneinfo/Africa/Bamako
  18. BIN  pytz/zoneinfo/Africa/Bangui
  19. BIN  pytz/zoneinfo/Africa/Banjul
  20. BIN  pytz/zoneinfo/Africa/Bissau
  21. BIN  pytz/zoneinfo/Africa/Blantyre
  22. BIN  pytz/zoneinfo/Africa/Brazzaville
  23. BIN  pytz/zoneinfo/Africa/Bujumbura
  24. BIN  pytz/zoneinfo/Africa/Cairo
  25. BIN  pytz/zoneinfo/Africa/Casablanca
  26. BIN  pytz/zoneinfo/Africa/Ceuta
  27. BIN  pytz/zoneinfo/Africa/Conakry
  28. BIN  pytz/zoneinfo/Africa/Dakar
  29. BIN  pytz/zoneinfo/Africa/Dar_es_Salaam
  30. BIN  pytz/zoneinfo/Africa/Djibouti
  31. BIN  pytz/zoneinfo/Africa/Douala
  32. BIN  pytz/zoneinfo/Africa/El_Aaiun
  33. BIN  pytz/zoneinfo/Africa/Freetown
  34. BIN  pytz/zoneinfo/Africa/Gaborone
  35. BIN  pytz/zoneinfo/Africa/Harare
  36. BIN  pytz/zoneinfo/Africa/Johannesburg
  37. BIN  pytz/zoneinfo/Africa/Kampala
  38. BIN  pytz/zoneinfo/Africa/Khartoum
  39. BIN  pytz/zoneinfo/Africa/Kigali
  40. BIN  pytz/zoneinfo/Africa/Kinshasa
  41. BIN  pytz/zoneinfo/Africa/Lagos
  42. BIN  pytz/zoneinfo/Africa/Libreville
  43. BIN  pytz/zoneinfo/Africa/Lome
  44. BIN  pytz/zoneinfo/Africa/Luanda
  45. BIN  pytz/zoneinfo/Africa/Lubumbashi
  46. BIN  pytz/zoneinfo/Africa/Lusaka
  47. BIN  pytz/zoneinfo/Africa/Malabo
  48. BIN  pytz/zoneinfo/Africa/Maputo
  49. BIN  pytz/zoneinfo/Africa/Maseru
  50. BIN  pytz/zoneinfo/Africa/Mbabane
  51. BIN  pytz/zoneinfo/Africa/Mogadishu
  52. BIN  pytz/zoneinfo/Africa/Monrovia
  53. BIN  pytz/zoneinfo/Africa/Nairobi
  54. BIN  pytz/zoneinfo/Africa/Ndjamena
  55. BIN  pytz/zoneinfo/Africa/Niamey
  56. BIN  pytz/zoneinfo/Africa/Nouakchott
  57. BIN  pytz/zoneinfo/Africa/Ouagadougou
  58. BIN  pytz/zoneinfo/Africa/Porto-Novo
  59. BIN  pytz/zoneinfo/Africa/Sao_Tome
  60. BIN  pytz/zoneinfo/Africa/Timbuktu
  61. BIN  pytz/zoneinfo/Africa/Tripoli
  62. BIN  pytz/zoneinfo/Africa/Tunis
  63. BIN  pytz/zoneinfo/Africa/Windhoek
  64. BIN  pytz/zoneinfo/America/Adak
  65. BIN  pytz/zoneinfo/America/Anchorage
  66. BIN  pytz/zoneinfo/America/Anguilla
  67. BIN  pytz/zoneinfo/America/Antigua
  68. BIN  pytz/zoneinfo/America/Araguaina
  69. BIN  pytz/zoneinfo/America/Argentina/Buenos_Aires
  70. BIN  pytz/zoneinfo/America/Argentina/Catamarca
  71. BIN  pytz/zoneinfo/America/Argentina/ComodRivadavia
  72. BIN  pytz/zoneinfo/America/Argentina/Cordoba
  73. BIN  pytz/zoneinfo/America/Argentina/Jujuy
  74. BIN  pytz/zoneinfo/America/Argentina/La_Rioja
  75. BIN  pytz/zoneinfo/America/Argentina/Mendoza
  76. BIN  pytz/zoneinfo/America/Argentina/Rio_Gallegos
  77. BIN  pytz/zoneinfo/America/Argentina/Salta
  78. BIN  pytz/zoneinfo/America/Argentina/San_Juan
  79. BIN  pytz/zoneinfo/America/Argentina/San_Luis
  80. BIN  pytz/zoneinfo/America/Argentina/Tucuman
  81. BIN  pytz/zoneinfo/America/Argentina/Ushuaia
  82. BIN  pytz/zoneinfo/America/Aruba
  83. BIN  pytz/zoneinfo/America/Asuncion
  84. BIN  pytz/zoneinfo/America/Atikokan
  85. BIN  pytz/zoneinfo/America/Atka
  86. BIN  pytz/zoneinfo/America/Bahia
  87. BIN  pytz/zoneinfo/America/Barbados
  88. BIN  pytz/zoneinfo/America/Belem
  89. BIN  pytz/zoneinfo/America/Belize
  90. BIN  pytz/zoneinfo/America/Blanc-Sablon
  91. BIN  pytz/zoneinfo/America/Boa_Vista
  92. BIN  pytz/zoneinfo/America/Bogota
  93. BIN  pytz/zoneinfo/America/Boise
  94. BIN  pytz/zoneinfo/America/Buenos_Aires
  95. BIN  pytz/zoneinfo/America/Cambridge_Bay
  96. BIN  pytz/zoneinfo/America/Campo_Grande
  97. BIN  pytz/zoneinfo/America/Cancun
  98. BIN  pytz/zoneinfo/America/Caracas
  99. BIN  pytz/zoneinfo/America/Catamarca
  100. BIN  pytz/zoneinfo/America/Cayenne
  101. BIN  pytz/zoneinfo/America/Cayman
  102. BIN  pytz/zoneinfo/America/Chicago
  103. BIN  pytz/zoneinfo/America/Chihuahua
  104. BIN  pytz/zoneinfo/America/Coral_Harbour
  105. BIN  pytz/zoneinfo/America/Cordoba
  106. BIN  pytz/zoneinfo/America/Costa_Rica
  107. BIN  pytz/zoneinfo/America/Cuiaba
  108. BIN  pytz/zoneinfo/America/Curacao
  109. BIN  pytz/zoneinfo/America/Danmarkshavn
  110. BIN  pytz/zoneinfo/America/Dawson
  111. BIN  pytz/zoneinfo/America/Dawson_Creek
  112. BIN  pytz/zoneinfo/America/Denver
  113. BIN  pytz/zoneinfo/America/Detroit
  114. BIN  pytz/zoneinfo/America/Dominica
  115. BIN  pytz/zoneinfo/America/Edmonton
  116. BIN  pytz/zoneinfo/America/Eirunepe
  117. BIN  pytz/zoneinfo/America/El_Salvador
  118. BIN  pytz/zoneinfo/America/Ensenada
  119. BIN  pytz/zoneinfo/America/Fort_Wayne
  120. BIN  pytz/zoneinfo/America/Fortaleza
  121. BIN  pytz/zoneinfo/America/Glace_Bay
  122. BIN  pytz/zoneinfo/America/Godthab
  123. BIN  pytz/zoneinfo/America/Goose_Bay
  124. BIN  pytz/zoneinfo/America/Grand_Turk
  125. BIN  pytz/zoneinfo/America/Grenada
  126. BIN  pytz/zoneinfo/America/Guadeloupe
  127. BIN  pytz/zoneinfo/America/Guatemala
  128. BIN  pytz/zoneinfo/America/Guayaquil
  129. BIN  pytz/zoneinfo/America/Guyana
  130. BIN  pytz/zoneinfo/America/Halifax
  131. BIN  pytz/zoneinfo/America/Havana
  132. BIN  pytz/zoneinfo/America/Hermosillo
  133. BIN  pytz/zoneinfo/America/Indiana/Indianapolis
  134. BIN  pytz/zoneinfo/America/Indiana/Knox
  135. BIN  pytz/zoneinfo/America/Indiana/Marengo
  136. BIN  pytz/zoneinfo/America/Indiana/Petersburg
  137. BIN  pytz/zoneinfo/America/Indiana/Tell_City
  138. BIN  pytz/zoneinfo/America/Indiana/Vevay
  139. BIN  pytz/zoneinfo/America/Indiana/Vincennes
  140. BIN  pytz/zoneinfo/America/Indiana/Winamac
  141. BIN  pytz/zoneinfo/America/Indianapolis
  142. BIN  pytz/zoneinfo/America/Inuvik
  143. BIN  pytz/zoneinfo/America/Iqaluit
  144. BIN  pytz/zoneinfo/America/Jamaica
  145. BIN  pytz/zoneinfo/America/Jujuy
  146. BIN  pytz/zoneinfo/America/Juneau
  147. BIN  pytz/zoneinfo/America/Kentucky/Louisville
  148. BIN  pytz/zoneinfo/America/Kentucky/Monticello
  149. BIN  pytz/zoneinfo/America/Knox_IN
  150. BIN  pytz/zoneinfo/America/La_Paz
  151. BIN  pytz/zoneinfo/America/Lima
  152. BIN  pytz/zoneinfo/America/Los_Angeles
  153. BIN  pytz/zoneinfo/America/Louisville
  154. BIN  pytz/zoneinfo/America/Maceio
  155. BIN  pytz/zoneinfo/America/Managua
  156. BIN  pytz/zoneinfo/America/Manaus
  157. BIN  pytz/zoneinfo/America/Marigot
  158. BIN  pytz/zoneinfo/America/Martinique
  159. BIN  pytz/zoneinfo/America/Matamoros
  160. BIN  pytz/zoneinfo/America/Mazatlan
  161. BIN  pytz/zoneinfo/America/Mendoza
  162. BIN  pytz/zoneinfo/America/Menominee
  163. BIN  pytz/zoneinfo/America/Merida
  164. BIN  pytz/zoneinfo/America/Mexico_City
  165. BIN  pytz/zoneinfo/America/Miquelon
  166. BIN  pytz/zoneinfo/America/Moncton
  167. BIN  pytz/zoneinfo/America/Monterrey
  168. BIN  pytz/zoneinfo/America/Montevideo
  169. BIN  pytz/zoneinfo/America/Montreal
  170. BIN  pytz/zoneinfo/America/Montserrat
  171. BIN  pytz/zoneinfo/America/Nassau
  172. BIN  pytz/zoneinfo/America/New_York
  173. BIN  pytz/zoneinfo/America/Nipigon
  174. BIN  pytz/zoneinfo/America/Nome
  175. BIN  pytz/zoneinfo/America/Noronha
  176. BIN  pytz/zoneinfo/America/North_Dakota/Center
  177. BIN  pytz/zoneinfo/America/North_Dakota/New_Salem
  178. BIN  pytz/zoneinfo/America/Ojinaga
  179. BIN  pytz/zoneinfo/America/Panama
  180. BIN  pytz/zoneinfo/America/Pangnirtung
  181. BIN  pytz/zoneinfo/America/Paramaribo
  182. BIN  pytz/zoneinfo/America/Phoenix
  183. BIN  pytz/zoneinfo/America/Port-au-Prince
  184. BIN  pytz/zoneinfo/America/Port_of_Spain
  185. BIN  pytz/zoneinfo/America/Porto_Acre
  186. BIN  pytz/zoneinfo/America/Porto_Velho
  187. BIN  pytz/zoneinfo/America/Puerto_Rico
  188. BIN  pytz/zoneinfo/America/Rainy_River
  189. BIN  pytz/zoneinfo/America/Rankin_Inlet
  190. BIN  pytz/zoneinfo/America/Recife
  191. BIN  pytz/zoneinfo/America/Regina
  192. BIN  pytz/zoneinfo/America/Resolute
  193. BIN  pytz/zoneinfo/America/Rio_Branco
  194. BIN  pytz/zoneinfo/America/Rosario
  195. BIN  pytz/zoneinfo/America/Santa_Isabel
  196. BIN  pytz/zoneinfo/America/Santarem
  197. BIN  pytz/zoneinfo/America/Santiago
  198. BIN  pytz/zoneinfo/America/Santo_Domingo
  199. BIN  pytz/zoneinfo/America/Sao_Paulo
  200. BIN  pytz/zoneinfo/America/Scoresbysund
  201. BIN  pytz/zoneinfo/America/Shiprock
  202. BIN  pytz/zoneinfo/America/St_Barthelemy
  203. BIN  pytz/zoneinfo/America/St_Johns
  204. BIN  pytz/zoneinfo/America/St_Kitts
  205. BIN  pytz/zoneinfo/America/St_Lucia
  206. BIN  pytz/zoneinfo/America/St_Thomas
  207. BIN  pytz/zoneinfo/America/St_Vincent
  208. BIN  pytz/zoneinfo/America/Swift_Current
  209. BIN  pytz/zoneinfo/America/Tegucigalpa
  210. BIN  pytz/zoneinfo/America/Thule
  211. BIN  pytz/zoneinfo/America/Thunder_Bay
  212. BIN  pytz/zoneinfo/America/Tijuana
  213. BIN  pytz/zoneinfo/America/Toronto
  214. BIN  pytz/zoneinfo/America/Tortola
  215. BIN  pytz/zoneinfo/America/Vancouver
  216. BIN  pytz/zoneinfo/America/Virgin
  217. BIN  pytz/zoneinfo/America/Whitehorse
  218. BIN  pytz/zoneinfo/America/Winnipeg
  219. BIN  pytz/zoneinfo/America/Yakutat
  220. BIN  pytz/zoneinfo/America/Yellowknife
  221. BIN  pytz/zoneinfo/Antarctica/Casey
  222. BIN  pytz/zoneinfo/Antarctica/Davis
  223. BIN  pytz/zoneinfo/Antarctica/DumontDUrville
  224. BIN  pytz/zoneinfo/Antarctica/Mawson
  225. BIN  pytz/zoneinfo/Antarctica/McMurdo
  226. BIN  pytz/zoneinfo/Antarctica/Palmer
  227. BIN  pytz/zoneinfo/Antarctica/Rothera
  228. BIN  pytz/zoneinfo/Antarctica/South_Pole
  229. BIN  pytz/zoneinfo/Antarctica/Syowa
  230. BIN  pytz/zoneinfo/Antarctica/Vostok
  231. BIN  pytz/zoneinfo/Arctic/Longyearbyen
  232. BIN  pytz/zoneinfo/Asia/Aden
  233. BIN  pytz/zoneinfo/Asia/Almaty
  234. BIN  pytz/zoneinfo/Asia/Amman
  235. BIN  pytz/zoneinfo/Asia/Anadyr
  236. BIN  pytz/zoneinfo/Asia/Aqtau
  237. BIN  pytz/zoneinfo/Asia/Aqtobe
  238. BIN  pytz/zoneinfo/Asia/Ashgabat
  239. BIN  pytz/zoneinfo/Asia/Ashkhabad
  240. BIN  pytz/zoneinfo/Asia/Baghdad
  241. BIN  pytz/zoneinfo/Asia/Bahrain
  242. BIN  pytz/zoneinfo/Asia/Baku
  243. BIN  pytz/zoneinfo/Asia/Bangkok
  244. BIN  pytz/zoneinfo/Asia/Beirut
  245. BIN  pytz/zoneinfo/Asia/Bishkek
  246. BIN  pytz/zoneinfo/Asia/Brunei
  247. BIN  pytz/zoneinfo/Asia/Calcutta
  248. BIN  pytz/zoneinfo/Asia/Choibalsan
  249. BIN  pytz/zoneinfo/Asia/Chongqing
  250. BIN  pytz/zoneinfo/Asia/Chungking
  251. BIN  pytz/zoneinfo/Asia/Colombo
  252. BIN  pytz/zoneinfo/Asia/Dacca
  253. BIN  pytz/zoneinfo/Asia/Damascus
  254. BIN  pytz/zoneinfo/Asia/Dhaka
  255. BIN  pytz/zoneinfo/Asia/Dili
  256. BIN  pytz/zoneinfo/Asia/Dubai
  257. BIN  pytz/zoneinfo/Asia/Dushanbe
  258. BIN  pytz/zoneinfo/Asia/Gaza
  259. BIN  pytz/zoneinfo/Asia/Harbin
  260. BIN  pytz/zoneinfo/Asia/Ho_Chi_Minh
  261. BIN  pytz/zoneinfo/Asia/Hong_Kong
  262. BIN  pytz/zoneinfo/Asia/Hovd
  263. BIN  pytz/zoneinfo/Asia/Irkutsk
  264. BIN  pytz/zoneinfo/Asia/Istanbul
  265. BIN  pytz/zoneinfo/Asia/Jakarta
  266. BIN  pytz/zoneinfo/Asia/Jayapura
  267. BIN  pytz/zoneinfo/Asia/Jerusalem
  268. BIN  pytz/zoneinfo/Asia/Kabul
  269. BIN  pytz/zoneinfo/Asia/Kamchatka
  270. BIN  pytz/zoneinfo/Asia/Karachi
  271. BIN  pytz/zoneinfo/Asia/Kashgar
  272. BIN  pytz/zoneinfo/Asia/Kathmandu
  273. BIN  pytz/zoneinfo/Asia/Katmandu
  274. BIN  pytz/zoneinfo/Asia/Kolkata
  275. BIN  pytz/zoneinfo/Asia/Krasnoyarsk
  276. BIN  pytz/zoneinfo/Asia/Kuala_Lumpur
  277. BIN  pytz/zoneinfo/Asia/Kuching
  278. BIN  pytz/zoneinfo/Asia/Kuwait
  279. BIN  pytz/zoneinfo/Asia/Macao
  280. BIN  pytz/zoneinfo/Asia/Macau
  281. BIN  pytz/zoneinfo/Asia/Magadan
  282. BIN  pytz/zoneinfo/Asia/Makassar
  283. BIN  pytz/zoneinfo/Asia/Manila
  284. BIN  pytz/zoneinfo/Asia/Muscat
  285. BIN  pytz/zoneinfo/Asia/Nicosia
  286. BIN  pytz/zoneinfo/Asia/Novokuznetsk
  287. BIN  pytz/zoneinfo/Asia/Novosibirsk
  288. BIN  pytz/zoneinfo/Asia/Omsk
  289. BIN  pytz/zoneinfo/Asia/Oral
  290. BIN  pytz/zoneinfo/Asia/Phnom_Penh
  291. BIN  pytz/zoneinfo/Asia/Pontianak
  292. BIN  pytz/zoneinfo/Asia/Pyongyang
  293. BIN  pytz/zoneinfo/Asia/Qatar
  294. BIN  pytz/zoneinfo/Asia/Qyzylorda
  295. BIN  pytz/zoneinfo/Asia/Rangoon
  296. BIN  pytz/zoneinfo/Asia/Riyadh
  297. BIN  pytz/zoneinfo/Asia/Riyadh87
  298. BIN  pytz/zoneinfo/Asia/Riyadh88
  299. BIN  pytz/zoneinfo/Asia/Riyadh89
  300. BIN  pytz/zoneinfo/Asia/Saigon
Sorry, we could not display the entire diff because too many files (587) changed.
View
7 cron.yaml
@@ -0,0 +1,7 @@
+cron:
+- description: expire old pending events
+ url: /expire
+ schedule: every 24 hours
+- description: reminder of expiring events in 10 days
+ url: /expiring
+ schedule: every 24 hours
View
1  icalendar/prop.py
@@ -255,6 +255,7 @@ def _isdst(self, dt):
tt = _time.localtime(stamp)
return tt.tm_isdst > 0
+
####################################################
View
5 index.yaml
@@ -13,4 +13,9 @@ indexes:
- kind: Event
properties:
- name: status
+ - name: expired
+
+- kind: Event
+ properties:
+ - name: status
- name: start_time
View
150 main.py
@@ -1,13 +1,20 @@
from google.appengine.ext import webapp, db
from google.appengine.ext.webapp import util, template
-from google.appengine.api import urlfetch, memcache, users
+from google.appengine.api import urlfetch, memcache, users, mail
+
from django.utils import simplejson
-from datetime import datetime, timedelta
from django.template.defaultfilters import slugify
-import time
-from datetime import datetime
+from icalendar import Calendar, Event as CalendarEvent
import logging, urllib
-from icalendar import Calendar, Event as CalendarEvent, UTC # timezone
+
+from datetime import datetime, timedelta, time
+from pytz import timezone
+import pytz
+
+ROOM_OPTIONS = ['cave', 'deck', 'savanna', 'frontarea', '140b']
+GUESTS_PER_STAFF = 25
+PENDING_LIFETIME = 30 # days
+FROM_ADDRESS = "no-reply@hackerdojo-events.appspot.com"
# Hacker Dojo Domain API helper with caching
def dojo(path):
@@ -15,10 +22,7 @@ def dojo(path):
cache_ttl = 3600
resp = memcache.get(path)
if not resp:
- resp = urlfetch.fetch(base_url + path)
- if 'Refreshing' in resp.content:
- time.sleep(2)
- return urlfetch.fetch(base_url + path)
+ resp = urlfetch.fetch(base_url + path, deadline=10)
try:
resp = simplejson.loads(resp.content)
except Exception, e:
@@ -30,8 +34,28 @@ def dojo(path):
def username(user):
return user.nickname().split('@')[0] if user else None
-ROOM_OPTIONS = ['cave', 'hall', 'savanna', 'sunroom', 'greenroom', 'frontarea', '140b']
-GUESTS_PER_STAFF = 25
+def notify_owner_confirmation(event):
+ mail.send_mail(FROM_ADDRESS, event.member.email(),
+ "Event application submitted",
+ """This is a confirmation that your event:\n\n%s\n\n
+ has been submitted for approval. If staff is needed for your event, they
+ will be notified of your request. You will be notified as soon as it's
+ approved and on the calendar.""" % event.name)
+
+def notify_staff_needed(event):
+ pass
+
+def notify_new_event(event):
+ pass
+
+def notify_owner_approved(event):
+ pass
+
+def notify_owner_expiring(event):
+ pass
+
+def notify_owner_expired(event):
+ pass
class Event(db.Model):
status = db.StringProperty(required=True, default='pending', choices=set(
@@ -53,9 +77,24 @@ class Event(db.Model):
contact_name = db.StringProperty(required=True)
contact_phone = db.StringProperty(required=True)
+ expired = db.DateTimeProperty()
created = db.DateTimeProperty(auto_now_add=True)
updated = db.DateTimeProperty(auto_now=True)
+ @classmethod
+ def get_approved_list(cls):
+ return cls.all() \
+ .filter('start_time >', datetime.today()) \
+ .filter('status IN', ['approved', 'canceled']) \
+ .order('start_time')
+
+ @classmethod
+ def get_pending_list(cls):
+ return cls.all() \
+ .filter('start_time >', datetime.today()) \
+ .filter('status IN', ['pending', 'understaffed', 'onhold', 'expired']) \
+ .order('start_time')
+
def is_staffed(self):
return len(self.staff) >= int(self.estimated_size) / GUESTS_PER_STAFF
@@ -67,6 +106,7 @@ def start_date(self):
def approve(self):
if self.is_staffed():
+ self.expired = None
self.status = 'approved'
else:
self.status = 'understaffed'
@@ -76,6 +116,11 @@ def cancel(self):
self.status = 'canceled'
self.put()
+ def expire(self):
+ self.expired = datetime.now()
+ self.status = 'expired'
+ self.put()
+
def add_staff(self, user):
self.staff.append(user)
if self.is_staffed() and self.status == 'understaffed':
@@ -85,9 +130,32 @@ def add_staff(self, user):
def to_ical(self):
event = CalendarEvent()
event.add('summary', self.name if self.status == 'approved' else self.name + ' (%s)' % self.status.upper())
- event.add('dtstart', self.start_time)
+ event.add('dtstart', self.start_time.replace(tzinfo=timezone('US/Pacific')))
return event
+class ExpireCron(webapp.RequestHandler):
+ def post(self):
+ # Expire events marked to expire today
+ today = datetime.combine(datetime.today(), time())
+ events = Event.all() \
+ .filter('status IN', ['pending', 'understaffed']) \
+ .filter('expired >=', today) \
+ .filter('expired <', today + timedelta(days=1))
+ for event in events:
+ event.expire()
+ notify_owner_expired(event)
+
+class ExpireReminderCron(webapp.RequestHandler):
+ def post(self):
+ # Find events expiring in 10 days to warn owner
+ ten_days = datetime.combine(datetime.today(), time()) + timedelta(days=10)
+ events = Event.all() \
+ .filter('status IN', ['pending', 'understaffed']) \
+ .filter('expired >=', ten_days) \
+ .filter('expired <', ten_days + timedelta(days=1))
+ for event in events:
+ notify_owner_expiring(event)
+
class EventsHandler(webapp.RequestHandler):
def get(self, format):
if format == 'ics':
@@ -125,6 +193,11 @@ def post(self, id):
event.add_staff(user)
if state.lower() == 'cancel' and is_admin:
event.cancel()
+ if state.lower() == 'expire' and is_admin:
+ event.expire()
+
+ if event.status == 'approved':
+ notify_owner_approved(event)
self.redirect('/event/%s-%s' % (event.key().id(), slugify(event.name)))
class ApprovedHandler(webapp.RequestHandler):
@@ -134,8 +207,8 @@ def get(self):
logout_url = users.create_logout_url('/')
else:
login_url = users.create_login_url('/')
- events = Event.all().filter('status IN', ['approved', 'canceled']).order('start_time')
today = datetime.today()
+ events = Event.get_approved_list()
tomorrow = today + timedelta(days=1)
self.response.out.write(template.render('templates/approved.html', locals()))
@@ -146,7 +219,7 @@ def get(self):
logout_url = users.create_logout_url('/')
else:
login_url = users.create_login_url('/')
- events = Event.all().filter('status IN', ['pending', 'understaffed', 'onhold', 'expired']).order('start_time')
+ events = Event.get_pending_list()
today = datetime.today()
tomorrow = today + timedelta(days=1)
is_admin = username(user) in dojo('/groups/events')
@@ -166,24 +239,39 @@ def get(self):
def post(self):
user = users.get_current_user()
start_time = datetime.strptime("%s %s:%s %s" % (
- self.request.get('start_date'),
+ self.request.get('date'),
self.request.get('start_time_hour'),
self.request.get('start_time_minute'),
self.request.get('start_time_ampm')), "%d/%m/%Y %I:%M %p")
- event = Event(
- name = self.request.get('name'),
- start_time = start_time,
- type = self.request.get('type'),
- estimated_size = self.request.get('estimated_size'),
- contact_name = self.request.get('contact_name'),
- contact_phone = self.request.get('contact_phone'),
- details = self.request.get('details'),
- url = self.request.get('url'),
- fee = self.request.get('fee'),
- notes = self.request.get('notes'),
- rooms = self.request.get_all('rooms'))
- event.put()
- self.redirect('/event/%s-%s' % (event.key().id(), slugify(event.name)))
+ end_time = datetime.strptime("%s %s:%s %s" % (
+ self.request.get('date'),
+ self.request.get('end_time_hour'),
+ self.request.get('end_time_minute'),
+ self.request.get('end_time_ampm')), "%d/%m/%Y %I:%M %p")
+ if (end_time-start_time).days < 0:
+ raise ValueError("End time must be after start time")
+ else:
+ event = Event(
+ name = self.request.get('name'),
+ start_time = start_time,
+ end_time = end_time,
+ type = self.request.get('type'),
+ estimated_size = self.request.get('estimated_size'),
+ contact_name = self.request.get('contact_name'),
+ contact_phone = self.request.get('contact_phone'),
+ details = self.request.get('details'),
+ url = self.request.get('url'),
+ fee = self.request.get('fee'),
+ notes = self.request.get('notes'),
+ rooms = self.request.get_all('rooms'),
+ expired = datetime.today() + timedelta(days=PENDING_LIFETIME), # Set expected expiration date
+ )
+ event.put()
+ notify_owner_confirmation(event)
+ if not event.is_staffed():
+ notify_staff_needed(event)
+ notify_new_event(event)
+ self.redirect('/event/%s-%s' % (event.key().id(), slugify(event.name)))
def main():
application = webapp.WSGIApplication([
@@ -191,7 +279,9 @@ def main():
('/events\.(.+)', EventsHandler),
('/pending', PendingHandler),
('/new', NewHandler),
- ('/event/(\d+).*', EventHandler), ],debug=True)
+ ('/event/(\d+).*', EventHandler),
+ ('/expire', ExpireCron),
+ ('/expiring', ExpireReminderCron), ],debug=True)
util.run_wsgi_app(application)
if __name__ == '__main__':
View
1,427 pytz/__init__.py
@@ -0,0 +1,1427 @@
+'''
+datetime.tzinfo timezone definitions generated from the
+Olson timezone database:
+
+ ftp://elsie.nci.nih.gov/pub/tz*.tar.gz
+
+See the datetime section of the Python Library Reference for information
+on how to use these modules.
+'''
+
+# The Olson database is updated several times a year.
+OLSON_VERSION = '2010e'
+VERSION = OLSON_VERSION
+# Version format for a patch release - only one so far.
+#VERSION = OLSON_VERSION + '.2'
+__version__ = OLSON_VERSION
+
+OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling
+
+__all__ = [
+ 'timezone', 'utc', 'country_timezones', 'country_names',
+ 'AmbiguousTimeError', 'InvalidTimeError',
+ 'NonExistentTimeError', 'UnknownTimeZoneError',
+ 'all_timezones', 'all_timezones_set',
+ 'common_timezones', 'common_timezones_set',
+ ]
+
+import sys, datetime, os.path, gettext
+from UserDict import DictMixin
+
+try:
+ from pkg_resources import resource_stream
+except ImportError:
+ resource_stream = None
+
+from tzinfo import AmbiguousTimeError, InvalidTimeError, NonExistentTimeError
+from tzinfo import unpickler
+from tzfile import build_tzinfo
+
+# Use 2.3 sets module implementation if set builtin is not available
+try:
+ set
+except NameError:
+ from sets import Set as set
+
+
+def open_resource(name):
+ """Open a resource from the zoneinfo subdir for reading.
+
+ Uses the pkg_resources module if available and no standard file
+ found at the calculated location.
+ """
+ name_parts = name.lstrip('/').split('/')
+ for part in name_parts:
+ if part == os.path.pardir or os.path.sep in part:
+ raise ValueError('Bad path segment: %r' % part)
+ filename = os.path.join(os.path.dirname(__file__),
+ 'zoneinfo', *name_parts)
+ if not os.path.exists(filename) and resource_stream is not None:
+ # http://bugs.launchpad.net/bugs/383171 - we avoid using this
+ # unless absolutely necessary to help when a broken version of
+ # pkg_resources is installed.
+ return resource_stream(__name__, 'zoneinfo/' + name)
+ return open(filename, 'rb')
+
+
+def resource_exists(name):
+ """Return true if the given resource exists"""
+ try:
+ open_resource(name)
+ return True
+ except IOError:
+ return False
+
+
+# Enable this when we get some translations?
+# We want an i18n API that is useful to programs using Python's gettext
+# module, as well as the Zope3 i18n package. Perhaps we should just provide
+# the POT file and translations, and leave it up to callers to make use
+# of them.
+#
+# t = gettext.translation(
+# 'pytz', os.path.join(os.path.dirname(__file__), 'locales'),
+# fallback=True
+# )
+# def _(timezone_name):
+# """Translate a timezone name using the current locale, returning Unicode"""
+# return t.ugettext(timezone_name)
+
+
+class UnknownTimeZoneError(KeyError):
+ '''Exception raised when pytz is passed an unknown timezone.
+
+ >>> isinstance(UnknownTimeZoneError(), LookupError)
+ True
+
+ This class is actually a subclass of KeyError to provide backwards
+ compatibility with code relying on the undocumented behavior of earlier
+ pytz releases.
+
+ >>> isinstance(UnknownTimeZoneError(), KeyError)
+ True
+ '''
+ pass
+
+
+_tzinfo_cache = {}
+
+def timezone(zone):
+ r''' Return a datetime.tzinfo implementation for the given timezone
+
+ >>> from datetime import datetime, timedelta
+ >>> utc = timezone('UTC')
+ >>> eastern = timezone('US/Eastern')
+ >>> eastern.zone
+ 'US/Eastern'
+ >>> timezone(u'US/Eastern') is eastern
+ True
+ >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)
+ >>> loc_dt = utc_dt.astimezone(eastern)
+ >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
+ >>> loc_dt.strftime(fmt)
+ '2002-10-27 01:00:00 EST (-0500)'
+ >>> (loc_dt - timedelta(minutes=10)).strftime(fmt)
+ '2002-10-27 00:50:00 EST (-0500)'
+ >>> eastern.normalize(loc_dt - timedelta(minutes=10)).strftime(fmt)
+ '2002-10-27 01:50:00 EDT (-0400)'
+ >>> (loc_dt + timedelta(minutes=10)).strftime(fmt)
+ '2002-10-27 01:10:00 EST (-0500)'
+
+ Raises UnknownTimeZoneError if passed an unknown zone.
+
+ >>> timezone('Asia/Shangri-La')
+ Traceback (most recent call last):
+ ...
+ UnknownTimeZoneError: 'Asia/Shangri-La'
+
+ >>> timezone(u'\N{TRADE MARK SIGN}')
+ Traceback (most recent call last):
+ ...
+ UnknownTimeZoneError: u'\u2122'
+ '''
+ if zone.upper() == 'UTC':
+ return utc
+
+ try:
+ zone = zone.encode('US-ASCII')
+ except UnicodeEncodeError:
+ # All valid timezones are ASCII
+ raise UnknownTimeZoneError(zone)
+
+ zone = _unmunge_zone(zone)
+ if zone not in _tzinfo_cache:
+ if zone in all_timezones_set:
+ _tzinfo_cache[zone] = build_tzinfo(zone, open_resource(zone))
+ else:
+ raise UnknownTimeZoneError(zone)
+
+ return _tzinfo_cache[zone]
+
+
+def _unmunge_zone(zone):
+ """Undo the time zone name munging done by older versions of pytz."""
+ return zone.replace('_plus_', '+').replace('_minus_', '-')
+
+
+ZERO = datetime.timedelta(0)
+HOUR = datetime.timedelta(hours=1)
+
+
+class UTC(datetime.tzinfo):
+ """UTC
+
+ Identical to the reference UTC implementation given in Python docs except
+ that it unpickles using the single module global instance defined beneath
+ this class declaration.
+
+ Also contains extra attributes and methods to match other pytz tzinfo
+ instances.
+ """
+ zone = "UTC"
+
+ def utcoffset(self, dt):
+ return ZERO
+
+ def tzname(self, dt):
+ return "UTC"
+
+ def dst(self, dt):
+ return ZERO
+
+ def __reduce__(self):
+ return _UTC, ()
+
+ def localize(self, dt, is_dst=False):
+ '''Convert naive time to local time'''
+ if dt.tzinfo is not None:
+ raise ValueError, 'Not naive datetime (tzinfo is already set)'
+ return dt.replace(tzinfo=self)
+
+ def normalize(self, dt, is_dst=False):
+ '''Correct the timezone information on the given datetime'''
+ if dt.tzinfo is None:
+ raise ValueError, 'Naive time - no tzinfo set'
+ return dt.replace(tzinfo=self)
+
+ def __repr__(self):
+ return "<UTC>"
+
+ def __str__(self):
+ return "UTC"
+
+
+UTC = utc = UTC() # UTC is a singleton
+
+
+def _UTC():
+ """Factory function for utc unpickling.
+
+ Makes sure that unpickling a utc instance always returns the same
+ module global.
+
+ These examples belong in the UTC class above, but it is obscured; or in
+ the README.txt, but we are not depending on Python 2.4 so integrating
+ the README.txt examples with the unit tests is not trivial.
+
+ >>> import datetime, pickle
+ >>> dt = datetime.datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc)
+ >>> naive = dt.replace(tzinfo=None)
+ >>> p = pickle.dumps(dt, 1)
+ >>> naive_p = pickle.dumps(naive, 1)
+ >>> len(p), len(naive_p), len(p) - len(naive_p)
+ (60, 43, 17)
+ >>> new = pickle.loads(p)
+ >>> new == dt
+ True
+ >>> new is dt
+ False
+ >>> new.tzinfo is dt.tzinfo
+ True
+ >>> utc is UTC is timezone('UTC')
+ True
+ >>> utc is timezone('GMT')
+ False
+ """
+ return utc
+_UTC.__safe_for_unpickling__ = True
+
+
+def _p(*args):
+ """Factory function for unpickling pytz tzinfo instances.
+
+ Just a wrapper around tzinfo.unpickler to save a few bytes in each pickle
+ by shortening the path.
+ """
+ return unpickler(*args)
+_p.__safe_for_unpickling__ = True
+
+
+class _LazyDict(DictMixin):
+ """Dictionary populated on first use."""
+ data = None
+ def __getitem__(self, key):
+ if self.data is None:
+ self._fill()
+ return self.data[key.upper()]
+
+ def keys(self):
+ if self.data is None:
+ self._fill()
+ return self.data.keys()
+
+
+class _CountryTimezoneDict(_LazyDict):
+ """Map ISO 3166 country code to a list of timezone names commonly used
+ in that country.
+
+ iso3166_code is the two letter code used to identify the country.
+
+ >>> country_timezones['ch']
+ ['Europe/Zurich']
+ >>> country_timezones['CH']
+ ['Europe/Zurich']
+ >>> country_timezones[u'ch']
+ ['Europe/Zurich']
+ >>> country_timezones['XXX']
+ Traceback (most recent call last):
+ ...
+ KeyError: 'XXX'
+
+ Previously, this information was exposed as a function rather than a
+ dictionary. This is still supported::
+
+ >>> country_timezones('nz')
+ ['Pacific/Auckland', 'Pacific/Chatham']
+ """
+ def __call__(self, iso3166_code):
+ """Backwards compatibility."""
+ return self[iso3166_code]
+
+ def _fill(self):
+ data = {}
+ zone_tab = open_resource('zone.tab')
+ for line in zone_tab:
+ if line.startswith('#'):
+ continue
+ code, coordinates, zone = line.split(None, 4)[:3]
+ if zone not in all_timezones_set:
+ continue
+ try:
+ data[code].append(zone)
+ except KeyError:
+ data[code] = [zone]
+ self.data = data
+
+country_timezones = _CountryTimezoneDict()
+
+
+class _CountryNameDict(_LazyDict):
+ '''Dictionary proving ISO3166 code -> English name.
+
+ >>> country_names['au']
+ 'Australia'
+ '''
+ def _fill(self):
+ data = {}
+ zone_tab = open_resource('iso3166.tab')
+ for line in zone_tab.readlines():
+ if line.startswith('#'):
+ continue
+ code, name = line.split(None, 1)
+ data[code] = name.strip()
+ self.data = data
+
+country_names = _CountryNameDict()
+
+
+# Time-zone info based solely on fixed offsets
+
+class _FixedOffset(datetime.tzinfo):
+
+ zone = None # to match the standard pytz API
+
+ def __init__(self, minutes):
+ if abs(minutes) >= 1440:
+ raise ValueError("absolute offset is too large", minutes)
+ self._minutes = minutes
+ self._offset = datetime.timedelta(minutes=minutes)
+
+ def utcoffset(self, dt):
+ return self._offset
+
+ def __reduce__(self):
+ return FixedOffset, (self._minutes, )
+
+ def dst(self, dt):
+ return None
+
+ def tzname(self, dt):
+ return None
+
+ def __repr__(self):
+ return 'pytz.FixedOffset(%d)' % self._minutes
+
+ def localize(self, dt, is_dst=False):
+ '''Convert naive time to local time'''
+ if dt.tzinfo is not None:
+ raise ValueError, 'Not naive datetime (tzinfo is already set)'
+ return dt.replace(tzinfo=self)
+
+ def normalize(self, dt, is_dst=False):
+ '''Correct the timezone information on the given datetime'''
+ if dt.tzinfo is None:
+ raise ValueError, 'Naive time - no tzinfo set'
+ return dt.replace(tzinfo=self)
+
+
+def FixedOffset(offset, _tzinfos = {}):
+ """return a fixed-offset timezone based off a number of minutes.
+
+ >>> one = FixedOffset(-330)
+ >>> one
+ pytz.FixedOffset(-330)
+ >>> one.utcoffset(datetime.datetime.now())
+ datetime.timedelta(-1, 66600)
+
+ >>> two = FixedOffset(1380)
+ >>> two
+ pytz.FixedOffset(1380)
+ >>> two.utcoffset(datetime.datetime.now())
+ datetime.timedelta(0, 82800)
+
+ The datetime.timedelta must be between the range of -1 and 1 day,
+ non-inclusive.
+
+ >>> FixedOffset(1440)
+ Traceback (most recent call last):
+ ...
+ ValueError: ('absolute offset is too large', 1440)
+
+ >>> FixedOffset(-1440)
+ Traceback (most recent call last):
+ ...
+ ValueError: ('absolute offset is too large', -1440)
+
+ An offset of 0 is special-cased to return UTC.
+
+ >>> FixedOffset(0) is UTC
+ True
+
+ There should always be only one instance of a FixedOffset per timedelta.
+ This should be true for multiple creation calls.
+
+ >>> FixedOffset(-330) is one
+ True
+ >>> FixedOffset(1380) is two
+ True
+
+ It should also be true for pickling.
+
+ >>> import pickle
+ >>> pickle.loads(pickle.dumps(one)) is one
+ True
+ >>> pickle.loads(pickle.dumps(two)) is two
+ True
+ """
+ if offset == 0:
+ return UTC
+
+ info = _tzinfos.get(offset)
+ if info is None:
+ # We haven't seen this one before. we need to save it.
+
+ # Use setdefault to avoid a race condition and make sure we have
+ # only one
+ info = _tzinfos.setdefault(offset, _FixedOffset(offset))
+
+ return info
+
+FixedOffset.__safe_for_unpickling__ = True
+
+
+def _test():
+ import doctest, os, sys
+ sys.path.insert(0, os.pardir)
+ import pytz
+ return doctest.testmod(pytz)
+
+if __name__ == '__main__':
+ _test()
+
+all_timezones = \
+['Africa/Abidjan',
+ 'Africa/Accra',
+ 'Africa/Addis_Ababa',
+ 'Africa/Algiers',
+ 'Africa/Asmara',
+ 'Africa/Asmera',
+ 'Africa/Bamako',
+ 'Africa/Bangui',
+ 'Africa/Banjul',
+ 'Africa/Bissau',
+ 'Africa/Blantyre',
+ 'Africa/Brazzaville',
+ 'Africa/Bujumbura',
+ 'Africa/Cairo',
+ 'Africa/Casablanca',
+ 'Africa/Ceuta',
+ 'Africa/Conakry',
+ 'Africa/Dakar',
+ 'Africa/Dar_es_Salaam',
+ 'Africa/Djibouti',
+ 'Africa/Douala',
+ 'Africa/El_Aaiun',
+ 'Africa/Freetown',
+ 'Africa/Gaborone',
+ 'Africa/Harare',
+ 'Africa/Johannesburg',
+ 'Africa/Kampala',
+ 'Africa/Khartoum',
+ 'Africa/Kigali',
+ 'Africa/Kinshasa',
+ 'Africa/Lagos',
+ 'Africa/Libreville',
+ 'Africa/Lome',
+ 'Africa/Luanda',
+ 'Africa/Lubumbashi',
+ 'Africa/Lusaka',
+ 'Africa/Malabo',
+ 'Africa/Maputo',
+ 'Africa/Maseru',
+ 'Africa/Mbabane',
+ 'Africa/Mogadishu',
+ 'Africa/Monrovia',
+ 'Africa/Nairobi',
+ 'Africa/Ndjamena',
+ 'Africa/Niamey',
+ 'Africa/Nouakchott',
+ 'Africa/Ouagadougou',
+ 'Africa/Porto-Novo',
+ 'Africa/Sao_Tome',
+ 'Africa/Timbuktu',
+ 'Africa/Tripoli',
+ 'Africa/Tunis',
+ 'Africa/Windhoek',
+ 'America/Adak',
+ 'America/Anchorage',
+ 'America/Anguilla',
+ 'America/Antigua',
+ 'America/Araguaina',
+ 'America/Argentina/Buenos_Aires',
+ 'America/Argentina/Catamarca',
+ 'America/Argentina/ComodRivadavia',
+ 'America/Argentina/Cordoba',
+ 'America/Argentina/Jujuy',
+ 'America/Argentina/La_Rioja',
+ 'America/Argentina/Mendoza',
+ 'America/Argentina/Rio_Gallegos',
+ 'America/Argentina/Salta',
+ 'America/Argentina/San_Juan',
+ 'America/Argentina/San_Luis',
+ 'America/Argentina/Tucuman',
+ 'America/Argentina/Ushuaia',
+ 'America/Aruba',
+ 'America/Asuncion',
+ 'America/Atikokan',
+ 'America/Atka',
+ 'America/Bahia',
+ 'America/Barbados',
+ 'America/Belem',
+ 'America/Belize',
+ 'America/Blanc-Sablon',
+ 'America/Boa_Vista',
+ 'America/Bogota',
+ 'America/Boise',
+ 'America/Buenos_Aires',
+ 'America/Cambridge_Bay',
+ 'America/Campo_Grande',
+ 'America/Cancun',
+ 'America/Caracas',
+ 'America/Catamarca',
+ 'America/Cayenne',
+ 'America/Cayman',
+ 'America/Chicago',
+ 'America/Chihuahua',
+ 'America/Coral_Harbour',
+ 'America/Cordoba',
+ 'America/Costa_Rica',
+ 'America/Cuiaba',
+ 'America/Curacao',
+ 'America/Danmarkshavn',
+ 'America/Dawson',
+ 'America/Dawson_Creek',
+ 'America/Denver',
+ 'America/Detroit',
+ 'America/Dominica',
+ 'America/Edmonton',
+ 'America/Eirunepe',
+ 'America/El_Salvador',
+ 'America/Ensenada',
+ 'America/Fort_Wayne',
+ 'America/Fortaleza',
+ 'America/Glace_Bay',
+ 'America/Godthab',
+ 'America/Goose_Bay',
+ 'America/Grand_Turk',
+ 'America/Grenada',
+ 'America/Guadeloupe',
+ 'America/Guatemala',
+ 'America/Guayaquil',
+ 'America/Guyana',
+ 'America/Halifax',
+ 'America/Havana',
+ 'America/Hermosillo',
+ 'America/Indiana/Indianapolis',
+ 'America/Indiana/Knox',
+ 'America/Indiana/Marengo',
+ 'America/Indiana/Petersburg',
+ 'America/Indiana/Tell_City',
+ 'America/Indiana/Vevay',
+ 'America/Indiana/Vincennes',
+ 'America/Indiana/Winamac',
+ 'America/Indianapolis',
+ 'America/Inuvik',
+ 'America/Iqaluit',
+ 'America/Jamaica',
+ 'America/Jujuy',
+ 'America/Juneau',
+ 'America/Kentucky/Louisville',
+ 'America/Kentucky/Monticello',
+ 'America/Knox_IN',
+ 'America/La_Paz',
+ 'America/Lima',
+ 'America/Los_Angeles',
+ 'America/Louisville',
+ 'America/Maceio',
+ 'America/Managua',
+ 'America/Manaus',
+ 'America/Marigot',
+ 'America/Martinique',
+ 'America/Matamoros',
+ 'America/Mazatlan',
+ 'America/Mendoza',
+ 'America/Menominee',
+ 'America/Merida',
+ 'America/Mexico_City',
+ 'America/Miquelon',
+ 'America/Moncton',
+ 'America/Monterrey',
+ 'America/Montevideo',
+ 'America/Montreal',
+ 'America/Montserrat',
+ 'America/Nassau',
+ 'America/New_York',
+ 'America/Nipigon',
+ 'America/Nome',
+ 'America/Noronha',
+ 'America/North_Dakota/Center',
+ 'America/North_Dakota/New_Salem',
+ 'America/Ojinaga',
+ 'America/Panama',
+ 'America/Pangnirtung',
+ 'America/Paramaribo',
+ 'America/Phoenix',
+ 'America/Port-au-Prince',
+ 'America/Port_of_Spain',
+ 'America/Porto_Acre',
+ 'America/Porto_Velho',
+ 'America/Puerto_Rico',
+ 'America/Rainy_River',
+ 'America/Rankin_Inlet',
+ 'America/Recife',
+ 'America/Regina',
+ 'America/Resolute',
+ 'America/Rio_Branco',
+ 'America/Rosario',
+ 'America/Santa_Isabel',
+ 'America/Santarem',
+ 'America/Santiago',
+ 'America/Santo_Domingo',
+ 'America/Sao_Paulo',
+ 'America/Scoresbysund',
+ 'America/Shiprock',
+ 'America/St_Barthelemy',
+ 'America/St_Johns',
+ 'America/St_Kitts',
+ 'America/St_Lucia',
+ 'America/St_Thomas',
+ 'America/St_Vincent',
+ 'America/Swift_Current',
+ 'America/Tegucigalpa',
+ 'America/Thule',
+ 'America/Thunder_Bay',
+ 'America/Tijuana',
+ 'America/Toronto',
+ 'America/Tortola',
+ 'America/Vancouver',
+ 'America/Virgin',
+ 'America/Whitehorse',
+ 'America/Winnipeg',
+ 'America/Yakutat',
+ 'America/Yellowknife',
+ 'Antarctica/Casey',
+ 'Antarctica/Davis',
+ 'Antarctica/DumontDUrville',
+ 'Antarctica/Mawson',
+ 'Antarctica/McMurdo',
+ 'Antarctica/Palmer',
+ 'Antarctica/Rothera',
+ 'Antarctica/South_Pole',
+ 'Antarctica/Syowa',
+ 'Antarctica/Vostok',
+ 'Arctic/Longyearbyen',
+ 'Asia/Aden',
+ 'Asia/Almaty',
+ 'Asia/Amman',
+ 'Asia/Anadyr',
+ 'Asia/Aqtau',
+ 'Asia/Aqtobe',
+ 'Asia/Ashgabat',
+ 'Asia/Ashkhabad',
+ 'Asia/Baghdad',
+ 'Asia/Bahrain',
+ 'Asia/Baku',
+ 'Asia/Bangkok',
+ 'Asia/Beirut',
+ 'Asia/Bishkek',
+ 'Asia/Brunei',
+ 'Asia/Calcutta',
+ 'Asia/Choibalsan',
+ 'Asia/Chongqing',
+ 'Asia/Chungking',
+ 'Asia/Colombo',
+ 'Asia/Dacca',
+ 'Asia/Damascus',
+ 'Asia/Dhaka',
+ 'Asia/Dili',
+ 'Asia/Dubai',
+ 'Asia/Dushanbe',
+ 'Asia/Gaza',
+ 'Asia/Harbin',
+ 'Asia/Ho_Chi_Minh',
+ 'Asia/Hong_Kong',
+ 'Asia/Hovd',
+ 'Asia/Irkutsk',
+ 'Asia/Istanbul',
+ 'Asia/Jakarta',
+ 'Asia/Jayapura',
+ 'Asia/Jerusalem',
+ 'Asia/Kabul',
+ 'Asia/Kamchatka',
+ 'Asia/Karachi',
+ 'Asia/Kashgar',
+ 'Asia/Kathmandu',
+ 'Asia/Katmandu',
+ 'Asia/Kolkata',
+ 'Asia/Krasnoyarsk',
+ 'Asia/Kuala_Lumpur',
+ 'Asia/Kuching',
+ 'Asia/Kuwait',
+ 'Asia/Macao',
+ 'Asia/Macau',
+ 'Asia/Magadan',
+ 'Asia/Makassar',
+ 'Asia/Manila',
+ 'Asia/Muscat',
+ 'Asia/Nicosia',
+ 'Asia/Novokuznetsk',
+ 'Asia/Novosibirsk',
+ 'Asia/Omsk',
+ 'Asia/Oral',
+ 'Asia/Phnom_Penh',
+ 'Asia/Pontianak',
+ 'Asia/Pyongyang',
+ 'Asia/Qatar',
+ 'Asia/Qyzylorda',
+ 'Asia/Rangoon',
+ 'Asia/Riyadh',
+ 'Asia/Saigon',
+ 'Asia/Sakhalin',
+ 'Asia/Samarkand',
+ 'Asia/Seoul',
+ 'Asia/Shanghai',
+ 'Asia/Singapore',
+ 'Asia/Taipei',
+ 'Asia/Tashkent',
+ 'Asia/Tbilisi',
+ 'Asia/Tehran',
+ 'Asia/Tel_Aviv',
+ 'Asia/Thimbu',
+ 'Asia/Thimphu',
+ 'Asia/Tokyo',
+ 'Asia/Ujung_Pandang',
+ 'Asia/Ulaanbaatar',
+ 'Asia/Ulan_Bator',
+ 'Asia/Urumqi',
+ 'Asia/Vientiane',
+ 'Asia/Vladivostok',
+ 'Asia/Yakutsk',
+ 'Asia/Yekaterinburg',
+ 'Asia/Yerevan',
+ 'Atlantic/Azores',
+ 'Atlantic/Bermuda',
+ 'Atlantic/Canary',
+ 'Atlantic/Cape_Verde',
+ 'Atlantic/Faeroe',
+ 'Atlantic/Faroe',
+ 'Atlantic/Jan_Mayen',
+ 'Atlantic/Madeira',
+ 'Atlantic/Reykjavik',
+ 'Atlantic/South_Georgia',
+ 'Atlantic/St_Helena',
+ 'Atlantic/Stanley',
+ 'Australia/ACT',
+ 'Australia/Adelaide',
+ 'Australia/Brisbane',
+ 'Australia/Broken_Hill',
+ 'Australia/Canberra',
+ 'Australia/Currie',
+ 'Australia/Darwin',
+ 'Australia/Eucla',
+ 'Australia/Hobart',
+ 'Australia/LHI',
+ 'Australia/Lindeman',
+ 'Australia/Lord_Howe',
+ 'Australia/Melbourne',
+ 'Australia/NSW',
+ 'Australia/North',
+ 'Australia/Perth',
+ 'Australia/Queensland',
+ 'Australia/South',
+ 'Australia/Sydney',
+ 'Australia/Tasmania',
+ 'Australia/Victoria',
+ 'Australia/West',
+ 'Australia/Yancowinna',
+ 'Brazil/Acre',
+ 'Brazil/DeNoronha',
+ 'Brazil/East',
+ 'Brazil/West',
+ 'CET',
+ 'CST6CDT',
+ 'Canada/Atlantic',
+ 'Canada/Central',
+ 'Canada/East-Saskatchewan',
+ 'Canada/Eastern',
+ 'Canada/Mountain',
+ 'Canada/Newfoundland',
+ 'Canada/Pacific',
+ 'Canada/Saskatchewan',
+ 'Canada/Yukon',
+ 'Chile/Continental',
+ 'Chile/EasterIsland',
+ 'Cuba',
+ 'EET',
+ 'EST',
+ 'EST5EDT',
+ 'Egypt',
+ 'Eire',
+ 'Etc/GMT',
+ 'Etc/GMT+0',
+ 'Etc/GMT+1',
+ 'Etc/GMT+10',
+ 'Etc/GMT+11',
+ 'Etc/GMT+12',
+ 'Etc/GMT+2',
+ 'Etc/GMT+3',
+ 'Etc/GMT+4',
+ 'Etc/GMT+5',
+ 'Etc/GMT+6',
+ 'Etc/GMT+7',
+ 'Etc/GMT+8',
+ 'Etc/GMT+9',
+ 'Etc/GMT-0',
+ 'Etc/GMT-1',
+ 'Etc/GMT-10',
+ 'Etc/GMT-11',
+ 'Etc/GMT-12',
+ 'Etc/GMT-13',
+ 'Etc/GMT-14',
+ 'Etc/GMT-2',
+ 'Etc/GMT-3',
+ 'Etc/GMT-4',
+ 'Etc/GMT-5',
+ 'Etc/GMT-6',
+ 'Etc/GMT-7',
+ 'Etc/GMT-8',
+ 'Etc/GMT-9',
+ 'Etc/GMT0',
+ 'Etc/Greenwich',
+ 'Etc/UCT',
+ 'Etc/UTC',
+ 'Etc/Universal',
+ 'Etc/Zulu',
+ 'Europe/Amsterdam',
+ 'Europe/Andorra',
+ 'Europe/Athens',
+ 'Europe/Belfast',
+ 'Europe/Belgrade',
+ 'Europe/Berlin',
+ 'Europe/Bratislava',
+ 'Europe/Brussels',
+ 'Europe/Bucharest',
+ 'Europe/Budapest',
+ 'Europe/Chisinau',
+ 'Europe/Copenhagen',
+ 'Europe/Dublin',
+ 'Europe/Gibraltar',
+ 'Europe/Guernsey',
+ 'Europe/Helsinki',
+ 'Europe/Isle_of_Man',
+ 'Europe/Istanbul',
+ 'Europe/Jersey',
+ 'Europe/Kaliningrad',
+ 'Europe/Kiev',
+ 'Europe/Lisbon',
+ 'Europe/Ljubljana',
+ 'Europe/London',
+ 'Europe/Luxembourg',
+ 'Europe/Madrid',
+ 'Europe/Malta',
+ 'Europe/Mariehamn',
+ 'Europe/Minsk',
+ 'Europe/Monaco',
+ 'Europe/Moscow',
+ 'Europe/Nicosia',
+ 'Europe/Oslo',
+ 'Europe/Paris',
+ 'Europe/Podgorica',
+ 'Europe/Prague',
+ 'Europe/Riga',
+ 'Europe/Rome',
+ 'Europe/Samara',
+ 'Europe/San_Marino',
+ 'Europe/Sarajevo',
+ 'Europe/Simferopol',
+ 'Europe/Skopje',
+ 'Europe/Sofia',
+ 'Europe/Stockholm',
+ 'Europe/Tallinn',
+ 'Europe/Tirane',
+ 'Europe/Tiraspol',
+ 'Europe/Uzhgorod',
+ 'Europe/Vaduz',
+ 'Europe/Vatican',
+ 'Europe/Vienna',
+ 'Europe/Vilnius',
+ 'Europe/Volgograd',
+ 'Europe/Warsaw',
+ 'Europe/Zagreb',
+ 'Europe/Zaporozhye',
+ 'Europe/Zurich',
+ 'GB',
+ 'GB-Eire',
+ 'GMT',
+ 'GMT+0',
+ 'GMT-0',
+ 'GMT0',
+ 'Greenwich',
+ 'HST',
+ 'Hongkong',
+ 'Iceland',
+ 'Indian/Antananarivo',
+ 'Indian/Chagos',
+ 'Indian/Christmas',
+ 'Indian/Cocos',
+ 'Indian/Comoro',
+ 'Indian/Kerguelen',
+ 'Indian/Mahe',
+ 'Indian/Maldives',
+ 'Indian/Mauritius',
+ 'Indian/Mayotte',
+ 'Indian/Reunion',
+ 'Iran',
+ 'Israel',
+ 'Jamaica',
+ 'Japan',
+ 'Kwajalein',
+ 'Libya',
+ 'MET',
+ 'MST',
+ 'MST7MDT',
+ 'Mexico/BajaNorte',
+ 'Mexico/BajaSur',
+ 'Mexico/General',
+ 'NZ',
+ 'NZ-CHAT',
+ 'Navajo',
+ 'PRC',
+ 'PST8PDT',
+ 'Pacific/Apia',
+ 'Pacific/Auckland',
+ 'Pacific/Chatham',
+ 'Pacific/Easter',
+ 'Pacific/Efate',
+ 'Pacific/Enderbury',
+ 'Pacific/Fakaofo',
+ 'Pacific/Fiji',
+ 'Pacific/Funafuti',
+ 'Pacific/Galapagos',
+ 'Pacific/Gambier',
+ 'Pacific/Guadalcanal',
+ 'Pacific/Guam',
+ 'Pacific/Honolulu',
+ 'Pacific/Johnston',
+ 'Pacific/Kiritimati',
+ 'Pacific/Kosrae',
+ 'Pacific/Kwajalein',
+ 'Pacific/Majuro',
+ 'Pacific/Marquesas',
+ 'Pacific/Midway',
+ 'Pacific/Nauru',
+ 'Pacific/Niue',
+ 'Pacific/Norfolk',
+ 'Pacific/Noumea',
+ 'Pacific/Pago_Pago',
+ 'Pacific/Palau',
+ 'Pacific/Pitcairn',
+ 'Pacific/Ponape',
+ 'Pacific/Port_Moresby',
+ 'Pacific/Rarotonga',
+ 'Pacific/Saipan',
+ 'Pacific/Samoa',
+ 'Pacific/Tahiti',
+ 'Pacific/Tarawa',
+ 'Pacific/Tongatapu',
+ 'Pacific/Truk',
+ 'Pacific/Wake',
+ 'Pacific/Wallis',
+ 'Pacific/Yap',
+ 'Poland',
+ 'Portugal',
+ 'ROC',
+ 'ROK',
+ 'Singapore',
+ 'Turkey',
+ 'UCT',
+ 'US/Alaska',
+ 'US/Aleutian',
+ 'US/Arizona',
+ 'US/Central',
+ 'US/East-Indiana',
+ 'US/Eastern',
+ 'US/Hawaii',
+ 'US/Indiana-Starke',
+ 'US/Michigan',
+ 'US/Mountain',
+ 'US/Pacific',
+ 'US/Pacific-New',
+ 'US/Samoa',
+ 'UTC',
+ 'Universal',
+ 'W-SU',
+ 'WET',
+ 'Zulu']
+all_timezones = [
+ tz for tz in all_timezones if resource_exists(tz)]
+
+all_timezones_set = set(all_timezones)
+common_timezones = \
+['Africa/Abidjan',
+ 'Africa/Accra',
+ 'Africa/Addis_Ababa',
+ 'Africa/Algiers',
+ 'Africa/Asmara',
+ 'Africa/Bamako',
+ 'Africa/Bangui',
+ 'Africa/Banjul',
+ 'Africa/Bissau',
+ 'Africa/Blantyre',
+ 'Africa/Brazzaville',
+ 'Africa/Bujumbura',
+ 'Africa/Cairo',
+ 'Africa/Casablanca',
+ 'Africa/Ceuta',
+ 'Africa/Conakry',
+ 'Africa/Dakar',
+ 'Africa/Dar_es_Salaam',
+ 'Africa/Djibouti',
+ 'Africa/Douala',
+ 'Africa/El_Aaiun',
+ 'Africa/Freetown',
+ 'Africa/Gaborone',
+ 'Africa/Harare',
+ 'Africa/Johannesburg',
+ 'Africa/Kampala',
+ 'Africa/Khartoum',
+ 'Africa/Kigali',
+ 'Africa/Kinshasa',
+ 'Africa/Lagos',
+ 'Africa/Libreville',
+ 'Africa/Lome',
+ 'Africa/Luanda',
+ 'Africa/Lubumbashi',
+ 'Africa/Lusaka',
+ 'Africa/Malabo',
+ 'Africa/Maputo',
+ 'Africa/Maseru',
+ 'Africa/Mbabane',
+ 'Africa/Mogadishu',
+ 'Africa/Monrovia',
+ 'Africa/Nairobi',
+ 'Africa/Ndjamena',
+ 'Africa/Niamey',
+ 'Africa/Nouakchott',
+ 'Africa/Ouagadougou',
+ 'Africa/Porto-Novo',
+ 'Africa/Sao_Tome',
+ 'Africa/Tripoli',
+ 'Africa/Tunis',
+ 'Africa/Windhoek',
+ 'America/Adak',
+ 'America/Anchorage',
+ 'America/Anguilla',
+ 'America/Antigua',
+ 'America/Araguaina',
+ 'America/Argentina/Buenos_Aires',
+ 'America/Argentina/Catamarca',
+ 'America/Argentina/Cordoba',
+ 'America/Argentina/Jujuy',
+ 'America/Argentina/La_Rioja',
+ 'America/Argentina/Mendoza',
+ 'America/Argentina/Rio_Gallegos',
+ 'America/Argentina/Salta',
+ 'America/Argentina/San_Juan',
+ 'America/Argentina/San_Luis',
+ 'America/Argentina/Tucuman',
+ 'America/Argentina/Ushuaia',
+ 'America/Aruba',
+ 'America/Asuncion',
+ 'America/Atikokan',
+ 'America/Bahia',
+ 'America/Barbados',
+ 'America/Belem',
+ 'America/Belize',
+ 'America/Blanc-Sablon',
+ 'America/Boa_Vista',
+ 'America/Bogota',
+ 'America/Boise',
+ 'America/Cambridge_Bay',
+ 'America/Campo_Grande',
+ 'America/Cancun',
+ 'America/Caracas',
+ 'America/Cayenne',
+ 'America/Cayman',
+ 'America/Chicago',
+ 'America/Chihuahua',
+ 'America/Costa_Rica',
+ 'America/Cuiaba',
+ 'America/Curacao',
+ 'America/Danmarkshavn',
+ 'America/Dawson',
+ 'America/Dawson_Creek',
+ 'America/Denver',
+ 'America/Detroit',
+ 'America/Dominica',
+ 'America/Edmonton',
+ 'America/Eirunepe',
+ 'America/El_Salvador',
+ 'America/Fortaleza',
+ 'America/Glace_Bay',
+ 'America/Godthab',
+ 'America/Goose_Bay',
+ 'America/Grand_Turk',
+ 'America/Grenada',
+ 'America/Guadeloupe',
+ 'America/Guatemala',
+ 'America/Guayaquil',
+ 'America/Guyana',
+ 'America/Halifax',
+ 'America/Havana',
+ 'America/Hermosillo',
+ 'America/Indiana/Indianapolis',
+ 'America/Indiana/Knox',
+ 'America/Indiana/Marengo',
+ 'America/Indiana/Petersburg',
+ 'America/Indiana/Tell_City',
+ 'America/Indiana/Vevay',
+ 'America/Indiana/Vincennes',
+ 'America/Indiana/Winamac',
+ 'America/Inuvik',
+ 'America/Iqaluit',
+ 'America/Jamaica',
+ 'America/Juneau',
+ 'America/Kentucky/Louisville',
+ 'America/Kentucky/Monticello',
+ 'America/La_Paz',
+ 'America/Lima',
+ 'America/Los_Angeles',
+ 'America/Maceio',
+ 'America/Managua',
+ 'America/Manaus',
+ 'America/Martinique',
+ 'America/Matamoros',
+ 'America/Mazatlan',
+ 'America/Menominee',
+ 'America/Merida',
+ 'America/Mexico_City',
+ 'America/Miquelon',
+ 'America/Moncton',
+ 'America/Monterrey',
+ 'America/Montevideo',
+ 'America/Montreal',
+ 'America/Montserrat',
+ 'America/Nassau',
+ 'America/New_York',
+ 'America/Nipigon',
+ 'America/Nome',
+ 'America/Noronha',
+ 'America/North_Dakota/Center',
+ 'America/North_Dakota/New_Salem',
+ 'America/Ojinaga',
+ 'America/Panama',
+ 'America/Pangnirtung',
+ 'America/Paramaribo',
+ 'America/Phoenix',
+ 'America/Port-au-Prince',
+ 'America/Port_of_Spain',
+ 'America/Porto_Velho',
+ 'America/Puerto_Rico',
+ 'America/Rainy_River',
+ 'America/Rankin_Inlet',
+ 'America/Recife',
+ 'America/Regina',
+ 'America/Resolute',
+ 'America/Rio_Branco',
+ 'America/Santa_Isabel',
+ 'America/Santarem',
+ 'America/Santiago',
+ 'America/Santo_Domingo',
+ 'America/Sao_Paulo',
+ 'America/Scoresbysund',
+ 'America/St_Johns',
+ 'America/St_Kitts',
+ 'America/St_Lucia',
+ 'America/St_Thomas',
+ 'America/St_Vincent',
+ 'America/Swift_Current',
+ 'America/Tegucigalpa',
+ 'America/Thule',
+ 'America/Thunder_Bay',
+ 'America/Tijuana',
+ 'America/Toronto',
+ 'America/Tortola',
+ 'America/Vancouver',
+ 'America/Whitehorse',
+ 'America/Winnipeg',
+ 'America/Yakutat',
+ 'America/Yellowknife',
+ 'Antarctica/Casey',
+ 'Antarctica/Davis',
+ 'Antarctica/DumontDUrville',
+ 'Antarctica/Mawson',
+ 'Antarctica/McMurdo',
+ 'Antarctica/Palmer',
+ 'Antarctica/Rothera',
+ 'Antarctica/Syowa',
+ 'Antarctica/Vostok',
+ 'Asia/Aden',
+ 'Asia/Almaty',
+ 'Asia/Amman',
+ 'Asia/Anadyr',
+ 'Asia/Aqtau',
+ 'Asia/Aqtobe',
+ 'Asia/Ashgabat',
+ 'Asia/Baghdad',
+ 'Asia/Bahrain',
+ 'Asia/Baku',
+ 'Asia/Bangkok',
+ 'Asia/Beirut',
+ 'Asia/Bishkek',
+ 'Asia/Brunei',
+ 'Asia/Choibalsan',
+ 'Asia/Chongqing',
+ 'Asia/Colombo',
+ 'Asia/Damascus',
+ 'Asia/Dhaka',
+ 'Asia/Dili',
+ 'Asia/Dubai',
+ 'Asia/Dushanbe',
+ 'Asia/Gaza',
+ 'Asia/Harbin',
+ 'Asia/Ho_Chi_Minh',
+ 'Asia/Hong_Kong',
+ 'Asia/Hovd',
+ 'Asia/Irkutsk',
+ 'Asia/Jakarta',
+ 'Asia/Jayapura',
+ 'Asia/Jerusalem',
+ 'Asia/Kabul',
+ 'Asia/Kamchatka',
+ 'Asia/Karachi',
+ 'Asia/Kashgar',
+ 'Asia/Kathmandu',
+ 'Asia/Kolkata',
+ 'Asia/Krasnoyarsk',
+ 'Asia/Kuala_Lumpur',
+ 'Asia/Kuching',
+ 'Asia/Kuwait',
+ 'Asia/Macau',
+ 'Asia/Magadan',
+ 'Asia/Makassar',
+ 'Asia/Manila',
+ 'Asia/Muscat',
+ 'Asia/Nicosia',
+ 'Asia/Novokuznetsk',
+ 'Asia/Novosibirsk',
+ 'Asia/Omsk',
+ 'Asia/Oral',
+ 'Asia/Phnom_Penh',
+ 'Asia/Pontianak',
+ 'Asia/Pyongyang',
+ 'Asia/Qatar',
+ 'Asia/Qyzylorda',
+ 'Asia/Rangoon',
+ 'Asia/Riyadh',
+ 'Asia/Sakhalin',
+ 'Asia/Samarkand',
+ 'Asia/Seoul',
+ 'Asia/Shanghai',
+ 'Asia/Singapore',
+ 'Asia/Taipei',
+ 'Asia/Tashkent',
+ 'Asia/Tbilisi',
+ 'Asia/Tehran',
+ 'Asia/Thimphu',
+ 'Asia/Tokyo',
+ 'Asia/Ulaanbaatar',
+ 'Asia/Urumqi',
+ 'Asia/Vientiane',
+ 'Asia/Vladivostok',
+ 'Asia/Yakutsk',
+ 'Asia/Yekaterinburg',
+ 'Asia/Yerevan',
+ 'Atlantic/Azores',
+ 'Atlantic/Bermuda',
+ 'Atlantic/Canary',
+ 'Atlantic/Cape_Verde',
+ 'Atlantic/Faroe',
+ 'Atlantic/Madeira',
+ 'Atlantic/Reykjavik',
+ 'Atlantic/South_Georgia',
+ 'Atlantic/St_Helena',
+ 'Atlantic/Stanley',
+ 'Australia/Adelaide',
+ 'Australia/Brisbane',
+ 'Australia/Broken_Hill',
+ 'Australia/Currie',
+ 'Australia/Darwin',
+ 'Australia/Eucla',
+ 'Australia/Hobart',
+ 'Australia/Lindeman',
+ 'Australia/Lord_Howe',
+ 'Australia/Melbourne',
+ 'Australia/Perth',
+ 'Australia/Sydney',
+ 'Canada/Atlantic',
+ 'Canada/Central',
+ 'Canada/Eastern',
+ 'Canada/Mountain',
+ 'Canada/Newfoundland',
+ 'Canada/Pacific',
+ 'Europe/Amsterdam',
+ 'Europe/Andorra',
+ 'Europe/Athens',
+ 'Europe/Belgrade',
+ 'Europe/Berlin',
+ 'Europe/Brussels',
+ 'Europe/Bucharest',
+ 'Europe/Budapest',
+ 'Europe/Chisinau',
+ 'Europe/Copenhagen',
+ 'Europe/Dublin',
+ 'Europe/Gibraltar',
+ 'Europe/Helsinki',
+ 'Europe/Istanbul',
+ 'Europe/Kaliningrad',
+ 'Europe/Kiev',
+ 'Europe/Lisbon',
+ 'Europe/London',
+ 'Europe/Luxembourg',
+ 'Europe/Madrid',
+ 'Europe/Malta',
+ 'Europe/Minsk',
+ 'Europe/Monaco',
+ 'Europe/Moscow',
+ 'Europe/Oslo',
+ 'Europe/Paris',
+ 'Europe/Prague',
+ 'Europe/Riga',
+ 'Europe/Rome',
+ 'Europe/Samara',
+ 'Europe/Simferopol',
+ 'Europe/Sofia',
+ 'Europe/Stockholm',
+ 'Europe/Tallinn',
+ 'Europe/Tirane',
+ 'Europe/Uzhgorod',
+ 'Europe/Vaduz',
+ 'Europe/Vienna',
+ 'Europe/Vilnius',
+ 'Europe/Volgograd',
+ 'Europe/Warsaw',
+ 'Europe/Zaporozhye',
+ 'Europe/Zurich',
+ 'GMT',
+ 'Indian/Antananarivo',
+ 'Indian/Chagos',
+ 'Indian/Christmas',
+ 'Indian/Cocos',
+ 'Indian/Comoro',
+ 'Indian/Kerguelen',
+ 'Indian/Mahe',
+ 'Indian/Maldives',
+ 'Indian/Mauritius',
+ 'Indian/Mayotte',
+ 'Indian/Reunion',
+ 'Pacific/Apia',
+ 'Pacific/Auckland',
+ 'Pacific/Chatham',
+ 'Pacific/Easter',
+ 'Pacific/Efate',
+ 'Pacific/Enderbury',
+ 'Pacific/Fakaofo',
+ 'Pacific/Fiji',
+ 'Pacific/Funafuti',
+ 'Pacific/Galapagos',
+ 'Pacific/Gambier',
+ 'Pacific/Guadalcanal',
+ 'Pacific/Guam',
+ 'Pacific/Honolulu',
+ 'Pacific/Johnston',
+ 'Pacific/Kiritimati',
+ 'Pacific/Kosrae',
+ 'Pacific/Kwajalein',
+ 'Pacific/Majuro',
+ 'Pacific/Marquesas',
+ 'Pacific/Midway',
+ 'Pacific/Nauru',
+ 'Pacific/Niue',
+ 'Pacific/Norfolk',
+ 'Pacific/Noumea',
+ 'Pacific/Pago_Pago',
+ 'Pacific/Palau',
+ 'Pacific/Pitcairn',
+ 'Pacific/Ponape',
+ 'Pacific/Port_Moresby',
+ 'Pacific/Rarotonga',
+ 'Pacific/Saipan',
+ 'Pacific/Tahiti',
+ 'Pacific/Tarawa',
+ 'Pacific/Tongatapu',
+ 'Pacific/Truk',
+ 'Pacific/Wake',
+ 'Pacific/Wallis',
+ 'US/Alaska',
+ 'US/Arizona',
+ 'US/Central',
+ 'US/Eastern',
+ 'US/Hawaii',
+ 'US/Mountain',
+ 'US/Pacific',
+ 'UTC']
+common_timezones = [
+ tz for tz in common_timezones if tz in all_timezones]
+
+common_timezones_set = set(common_timezones)
View
127 pytz/reference.py
@@ -0,0 +1,127 @@
+'''
+Reference tzinfo implementations from the Python docs.
+Used for testing against as they are only correct for the years
+1987 to 2006. Do not use these for real code.
+'''
+
+from datetime import tzinfo, timedelta, datetime
+from pytz import utc, UTC, HOUR, ZERO
+
+# A class building tzinfo objects for fixed-offset time zones.
+# Note that FixedOffset(0, "UTC") is a different way to build a
+# UTC tzinfo object.
+
+class FixedOffset(tzinfo):
+ """Fixed offset in minutes east from UTC."""
+
+ def __init__(self, offset, name):
+ self.__offset = timedelta(minutes = offset)
+ self.__name = name
+
+ def utcoffset(self, dt):
+ return self.__offset
+
+ def tzname(self, dt):
+ return self.__name
+
+ def dst(self, dt):
+ return ZERO
+
+# A class capturing the platform's idea of local time.
+
+import time as _time
+
+STDOFFSET = timedelta(seconds = -_time.timezone)
+if _time.daylight:
+ DSTOFFSET = timedelta(seconds = -_time.altzone)
+else:
+ DSTOFFSET = STDOFFSET
+
+DSTDIFF = DSTOFFSET - STDOFFSET
+
+class LocalTimezone(tzinfo):
+
+ def utcoffset(self, dt):
+ if self._isdst(dt):
+ return DSTOFFSET
+ else:
+ return STDOFFSET
+
+ def dst(self, dt):
+ if self._isdst(dt):
+ return DSTDIFF
+ else:
+ return ZERO
+
+ def tzname(self, dt):
+ return _time.tzname[self._isdst(dt)]
+
+ def _isdst(self, dt):
+ tt = (dt.year, dt.month, dt.day,
+ dt.hour, dt.minute, dt.second,
+ dt.weekday(), 0, -1)
+ stamp = _time.mktime(tt)
+ tt = _time.localtime(stamp)
+ return tt.tm_isdst > 0
+
+Local = LocalTimezone()
+
+# A complete implementation of current DST rules for major US time zones.
+
+def first_sunday_on_or_after(dt):
+ days_to_go = 6 - dt.weekday()
+ if days_to_go:
+ dt += timedelta(days_to_go)
+ return dt
+
+# In the US, DST starts at 2am (standard time) on the first Sunday in April.
+DSTSTART = datetime(1, 4, 1, 2)
+# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.
+# which is the first Sunday on or after Oct 25.
+DSTEND = datetime(1, 10, 25, 1)
+
+class USTimeZone(tzinfo):
+
+ def __init__(self, hours, reprname, stdname, dstname):
+ self.stdoffset = timedelta(hours=hours)
+ self.reprname = reprname
+ self.stdname = stdname
+ self.dstname = dstname
+
+ def __repr__(self):
+ return self.reprname
+
+ def tzname(self, dt):
+ if self.dst(dt):
+ return self.dstname
+ else:
+ return self.stdname
+
+ def utcoffset(self, dt):
+ return self.stdoffset + self.dst(dt)
+
+ def dst(self, dt):
+ if dt is None or dt.tzinfo is None:
+ # An exception may be sensible here, in one or both cases.
+ # It depends on how you want to treat them. The default
+ # fromutc() implementation (called by the default astimezone()
+ # implementation) passes a datetime with dt.tzinfo is self.
+ return ZERO
+ assert dt.tzinfo is self
+
+ # Find first Sunday in April & the last in October.
+ start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
+ end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
+
+ # Can't compare naive to aware objects, so strip the timezone from
+ # dt first.
+ if start <= dt.replace(tzinfo=None) < end:
+ return HOUR
+ else:
+ return ZERO
+
+Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
+Central = USTimeZone(-6, "Central", "CST", "CDT")
+Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
+Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
+
View
35 pytz/tests/test_docs.py
@@ -0,0 +1,35 @@
+# -*- coding: ascii -*-
+
+import unittest, os, os.path, sys
+from doctest import DocTestSuite
+
+# We test the documentation this way instead of using DocFileSuite so
+# we can run the tests under Python 2.3
+def test_README():
+ pass
+
+this_dir = os.path.dirname(__file__)
+locs = [
+ os.path.join(this_dir, os.pardir, 'README.txt'),
+ os.path.join(this_dir, os.pardir, os.pardir, 'README.txt'),
+ ]
+for loc in locs:
+ if os.path.exists(loc):
+ test_README.__doc__ = open(loc).read()
+ break
+if test_README.__doc__ is None:
+ raise RuntimeError('README.txt not found')
+
+
+def test_suite():
+ "For the Z3 test runner"
+ return DocTestSuite()
+
+
+if __name__ == '__main__':
+ sys.path.insert(0, os.path.abspath(os.path.join(
+ this_dir, os.pardir, os.pardir
+ )))
+ unittest.main(defaultTest='test_suite')
+
+
View
640 pytz/tests/test_tzinfo.py
@@ -0,0 +1,640 @@
+# -*- coding: ascii -*-
+
+import sys, os, os.path
+import unittest, doctest
+import cPickle as pickle
+from datetime import datetime, tzinfo, timedelta
+
+if __name__ == '__main__':
+ # Only munge path if invoked as a script. Testrunners should have setup
+ # the paths already
+ sys.path.insert(0, os.path.abspath(os.path.join(os.pardir, os.pardir)))
+
+import pytz
+from pytz import reference
+
+# I test for expected version to ensure the correct version of pytz is
+# actually being tested.
+EXPECTED_VERSION='2010e'
+
+fmt = '%Y-%m-%d %H:%M:%S %Z%z'
+
+NOTIME = timedelta(0)
+
+# GMT is a tzinfo.StaticTzInfo--the class we primarily want to test--while
+# UTC is reference implementation. They both have the same timezone meaning.
+UTC = pytz.timezone('UTC')
+GMT = pytz.timezone('GMT')
+
+
+def prettydt(dt):
+ """datetime as a string using a known format.
+
+ We don't use strftime as it doesn't handle years earlier than 1900
+ per http://bugs.python.org/issue1777412
+ """
+ if dt.utcoffset() >= timedelta(0):
+ offset = '+%s' % (dt.utcoffset(),)
+ else:
+ offset = '-%s' % (-1 * dt.utcoffset(),)
+ return '%04d-%02d-%02d %02d:%02d:%02d %s %s' % (
+ dt.year, dt.month, dt.day,
+ dt.hour, dt.minute, dt.second,
+ dt.tzname(), offset)
+
+class BasicTest(unittest.TestCase):
+
+ def testVersion(self):
+ # Ensuring the correct version of pytz has been loaded
+ self.failUnlessEqual(EXPECTED_VERSION, pytz.__version__,
+ 'Incorrect pytz version loaded. Import path is stuffed '
+ 'or this test needs updating. (Wanted %s, got %s)'
+ % (EXPECTED_VERSION, pytz.__version__)
+ )
+
+ def testGMT(self):
+ now = datetime.now(tz=GMT)
+ self.failUnless(now.utcoffset() == NOTIME)
+ self.failUnless(now.dst() == NOTIME)
+ self.failUnless(now.timetuple() == now.utctimetuple())
+ self.failUnless(now==now.replace(tzinfo=UTC))
+
+ def testReferenceUTC(self):
+ now = datetime.now(tz=UTC)
+ self.failUnless(now.utcoffset() == NOTIME)
+ self.failUnless(now.dst() == NOTIME)
+ self.failUnless(now.timetuple() == now.utctimetuple())
+
+
+class PicklingTest(unittest.TestCase):
+
+ def _roundtrip_tzinfo(self, tz):
+ p = pickle.dumps(tz)
+ unpickled_tz = pickle.loads(p)
+ self.failUnless(tz is unpickled_tz, '%s did not roundtrip' % tz.zone)
+
+ def _roundtrip_datetime(self, dt):
+ # Ensure that the tzinfo attached to a datetime instance
+ # is identical to the one returned. This is important for
+ # DST timezones, as some state is stored in the tzinfo.
+ tz = dt.tzinfo
+ p = pickle.dumps(dt)
+ unpickled_dt = pickle.loads(p)
+ unpickled_tz = unpickled_dt.tzinfo
+ self.failUnless(tz is unpickled_tz, '%s did not roundtrip' % tz.zone)
+
+ def testDst(self):
+ tz = pytz.timezone('Europe/Amsterdam')
+ dt = datetime(2004, 2, 1, 0, 0, 0)
+
+ for localized_tz in tz._tzinfos.values():
+ self._roundtrip_tzinfo(localized_tz)
+ self._roundtrip_datetime(dt.replace(tzinfo=localized_tz))
+
+ def testRoundtrip(self):
+ dt = datetime(2004, 2, 1, 0, 0, 0)
+ for zone in pytz.all_timezones:
+ tz = pytz.timezone(zone)
+ self._roundtrip_tzinfo(tz)
+
+ def testDatabaseFixes(self):
+ # Hack the pickle to make it refer to a timezone abbreviation
+ # that does not match anything. The unpickler should be able
+ # to repair this case
+ tz = pytz.timezone('Australia/Melbourne')
+ p = pickle.dumps(tz)
+ tzname = tz._tzname
+ hacked_p = p.replace(tzname, '???')
+ self.failIfEqual(p, hacked_p)
+ unpickled_tz = pickle.loads(hacked_p)
+ self.failUnless(tz is unpickled_tz)
+
+ # Simulate a database correction. In this case, the incorrect
+ # data will continue to be used.
+ p = pickle.dumps(tz)
+ new_utcoffset = tz._utcoffset.seconds + 42
+ hacked_p = p.replace(str(tz._utcoffset.seconds), str(new_utcoffset))
+ self.failIfEqual(p, hacked_p)
+ unpickled_tz = pickle.loads(hacked_p)
+ self.failUnlessEqual(unpickled_tz._utcoffset.seconds, new_utcoffset)
+ self.failUnless(tz is not unpickled_tz)
+
+ def testOldPickles(self):
+ # Ensure that applications serializing pytz instances as pickles
+ # have no troubles upgrading to a new pytz release. These pickles
+ # where created with pytz2006j
+ east1 = pickle.loads(
+ "cpytz\n_p\np1\n(S'US/Eastern'\np2\nI-18000\n"
+ "I0\nS'EST'\np3\ntRp4\n."
+ )
+ east2 = pytz.timezone('US/Eastern')
+ self.failUnless(east1 is east2)
+
+ # Confirm changes in name munging between 2006j and 2007c cause
+ # no problems.
+ pap1 = pickle.loads(
+ "cpytz\n_p\np1\n(S'America/Port_minus_au_minus_Prince'"
+ "\np2\nI-17340\nI0\nS'PPMT'\np3\ntRp4\n."
+ )
+ pap2 = pytz.timezone('America/Port-au-Prince')
+ self.failUnless(pap1 is pap2)
+
+ gmt1 = pickle.loads("cpytz\n_p\np1\n(S'Etc/GMT_plus_10'\np2\ntRp3\n.")
+ gmt2 = pytz.timezone('Etc/GMT+10')
+ self.failUnless(gmt1 is gmt2)
+
+
+class USEasternDSTStartTestCase(unittest.TestCase):
+ tzinfo = pytz.timezone('US/Eastern')
+
+ # 24 hours before DST changeover
+ transition_time = datetime(2002, 4, 7, 7, 0, 0, tzinfo=UTC)
+
+ # Increase for 'flexible' DST transitions due to 1 minute granularity
+ # of Python's datetime library
+ instant = timedelta(seconds=1)
+
+ # before transition
+ before = {
+ 'tzname': 'EST',
+ 'utcoffset': timedelta(hours = -5),
+ 'dst': timedelta(hours = 0),
+ }
+
+ # after transition
+ after = {
+ 'tzname': 'EDT',
+ 'utcoffset': timedelta(hours = -4),
+ 'dst': timedelta(hours = 1),
+ }
+
+ def _test_tzname(self, utc_dt, wanted):
+ tzname = wanted['tzname']
+ dt = utc_dt.astimezone(self.tzinfo)
+ self.failUnlessEqual(dt.tzname(), tzname,
+ 'Expected %s as tzname for %s. Got %s' % (
+ tzname, str(utc_dt), dt.tzname()
+ )
+ )
+
+ def _test_utcoffset(self, utc_dt, wanted):
+ utcoffset = wanted['utcoffset']
+ dt = utc_dt.astimezone(self.tzinfo)
+ self.failUnlessEqual(
+ dt.utcoffset(), wanted['utcoffset'],
+ 'Expected %s as utcoffset for %s. Got %s' % (
+ utcoffset, utc_dt, dt.utcoffset()
+ )
+ )
+
+ def _test_dst(self, utc_dt, wanted):
+ dst = wanted['dst']
+ dt = utc_dt.astimezone(self.tzinfo)
+ self.failUnlessEqual(dt.dst(),dst,
+ 'Expected %s as dst for %s. Got %s' % (
+ dst, utc_dt, dt.dst()
+ )
+ )
+
+ def test_arithmetic(self):
+ utc_dt = self.transition_time
+
+ for days in range(-420, 720, 20):
+ delta = timedelta(days=days)
+
+ # Make sure we can get back where we started
+ dt = utc_dt.astimezone(self.tzinfo)
+ dt2 = dt + delta
+ dt2 = dt2 - delta
+ self.failUnlessEqual(dt, dt2)
+
+ # Make sure arithmetic crossing DST boundaries ends
+ # up in the correct timezone after normalization
+ utc_plus_delta = (utc_dt + delta).astimezone(self.tzinfo)
+ local_plus_delta = self.tzinfo.normalize(dt + delta)
+ self.failUnlessEqual(
+ prettydt(utc_plus_delta),
+ prettydt(local_plus_delta),
+ 'Incorrect result for delta==%d days. Wanted %r. Got %r'%(
+ days,
+ prettydt(utc_plus_delta),
+ prettydt(local_plus_delta),
+ )
+ )
+
+ def _test_all(self, utc_dt, wanted):
+ self._test_utcoffset(utc_dt, wanted)
+ self._test_tzname(utc_dt, wanted)
+ self._test_dst(utc_dt, wanted)
+
+ def testDayBefore(self):
+ self._test_all(
+ self.transition_time - timedelta(days=1), self.before
+ )
+
+ def testTwoHoursBefore(self):
+ self._test_all(
+ self.transition_time - timedelta(hours=2), self.before
+ )
+
+ def testHourBefore(self):
+ self._test_all(
+ self.transition_time - timedelta(hours=1), self.before
+ )
+
+ def testInstantBefore(self):
+ self._test_all(
+ self.transition_time - self.instant, self.before
+ )
+
+ def testTransition(self):
+ self._test_all(
+ self.transition_time, self.after
+ )
+
+ def testInstantAfter(self):
+ self._test_all(
+ self.transition_time + self.instant, self.after
+ )
+
+ def testHourAfter(self):
+ self._test_all(
+ self.transition_time + timedelta(hours=1), self.after
+ )
+
+ def testTwoHoursAfter(self):
+ self._test_all(
+ self.transition_time + timedelta(hours=1), self.after
+ )
+
+ def testDayAfter(self):
+ self._test_all(
+ self.transition_time + timedelta(days=1), self.after
+ )
+
+
+class USEasternDSTEndTestCase(USEasternDSTStartTestCase):
+ tzinfo = pytz.timezone('US/Eastern')
+ transition_time = datetime(2002, 10, 27, 6, 0, 0, tzinfo=UTC)
+ before = {