1from sqlalchemy import types
2
3from ..exceptions import ImproperlyConfigured
4from .scalar_coercible import ScalarCoercible
5
6
7class TimezoneType(ScalarCoercible, types.TypeDecorator):
8 """
9 TimezoneType provides a way for saving timezones objects into database.
10 TimezoneType saves timezone objects as strings on the way in and converts
11 them back to objects when querying the database.
12
13 ::
14
15 from sqlalchemy_utils import TimezoneType
16
17 class User(Base):
18 __tablename__ = 'user'
19
20 # Pass backend='pytz' to change it to use pytz. Other values:
21 # 'dateutil' (default), and 'zoneinfo'.
22 timezone = sa.Column(TimezoneType(backend='pytz'))
23
24 :param backend:
25 Whether to use 'dateutil', 'pytz' or 'zoneinfo' for timezones.
26 'zoneinfo' uses the standard library module.
27
28 """
29
30 impl = types.Unicode(50)
31 python_type = None
32 cache_ok = True
33
34 def __init__(self, backend='dateutil'):
35 self.backend = backend
36 if backend == 'dateutil':
37 try:
38 from dateutil.tz import tzfile
39 from dateutil.zoneinfo import get_zonefile_instance
40
41 self.python_type = tzfile
42 self._to = get_zonefile_instance().zones.get
43 self._from = lambda x: str(x._filename)
44
45 except ImportError:
46 raise ImproperlyConfigured(
47 "'python-dateutil' is required to use the "
48 "'dateutil' backend for 'TimezoneType'"
49 )
50
51 elif backend == 'pytz':
52 try:
53 from pytz import timezone
54 from pytz.tzinfo import BaseTzInfo
55
56 self.python_type = BaseTzInfo
57 self._to = timezone
58 self._from = str
59
60 except ImportError:
61 raise ImproperlyConfigured(
62 "'pytz' is required to use the 'pytz' backend for 'TimezoneType'"
63 )
64
65 elif backend == 'zoneinfo':
66 import zoneinfo
67
68 self.python_type = zoneinfo.ZoneInfo
69 self._to = zoneinfo.ZoneInfo
70 self._from = str
71
72 else:
73 raise ImproperlyConfigured(
74 "'pytz', 'dateutil' or 'zoneinfo' are the backends "
75 "supported for 'TimezoneType'"
76 )
77
78 def _coerce(self, value):
79 if value is not None and not isinstance(value, self.python_type):
80 obj = self._to(value)
81 if obj is None:
82 raise ValueError("unknown time zone '%s'" % value)
83 return obj
84 return value
85
86 def process_bind_param(self, value, dialect):
87 return self._from(self._coerce(value)) if value else None
88
89 def process_result_value(self, value, dialect):
90 return self._to(value) if value else None