tz.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. """tzinfo implementations for psycopg2
  2. This module holds two different tzinfo implementations that can be used as
  3. the 'tzinfo' argument to datetime constructors, directly passed to psycopg
  4. functions or used to set the .tzinfo_factory attribute in cursors.
  5. """
  6. # psycopg/tz.py - tzinfo implementation
  7. #
  8. # Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org>
  9. # Copyright (C) 2020-2021 The Psycopg Team
  10. #
  11. # psycopg2 is free software: you can redistribute it and/or modify it
  12. # under the terms of the GNU Lesser General Public License as published
  13. # by the Free Software Foundation, either version 3 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # In addition, as a special exception, the copyright holders give
  17. # permission to link this program with the OpenSSL library (or with
  18. # modified versions of OpenSSL that use the same license as OpenSSL),
  19. # and distribute linked combinations including the two.
  20. #
  21. # You must obey the GNU Lesser General Public License in all respects for
  22. # all of the code used other than OpenSSL.
  23. #
  24. # psycopg2 is distributed in the hope that it will be useful, but WITHOUT
  25. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  26. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  27. # License for more details.
  28. import datetime
  29. import time
  30. ZERO = datetime.timedelta(0)
  31. class FixedOffsetTimezone(datetime.tzinfo):
  32. """Fixed offset in minutes east from UTC.
  33. This is exactly the implementation__ found in Python 2.3.x documentation,
  34. with a small change to the `!__init__()` method to allow for pickling
  35. and a default name in the form ``sHH:MM`` (``s`` is the sign.).
  36. The implementation also caches instances. During creation, if a
  37. FixedOffsetTimezone instance has previously been created with the same
  38. offset and name that instance will be returned. This saves memory and
  39. improves comparability.
  40. .. versionchanged:: 2.9
  41. The constructor can take either a timedelta or a number of minutes of
  42. offset. Previously only minutes were supported.
  43. .. __: https://docs.python.org/library/datetime.html
  44. """
  45. _name = None
  46. _offset = ZERO
  47. _cache = {}
  48. def __init__(self, offset=None, name=None):
  49. if offset is not None:
  50. if not isinstance(offset, datetime.timedelta):
  51. offset = datetime.timedelta(minutes=offset)
  52. self._offset = offset
  53. if name is not None:
  54. self._name = name
  55. def __new__(cls, offset=None, name=None):
  56. """Return a suitable instance created earlier if it exists
  57. """
  58. key = (offset, name)
  59. try:
  60. return cls._cache[key]
  61. except KeyError:
  62. tz = super().__new__(cls, offset, name)
  63. cls._cache[key] = tz
  64. return tz
  65. def __repr__(self):
  66. return "psycopg2.tz.FixedOffsetTimezone(offset=%r, name=%r)" \
  67. % (self._offset, self._name)
  68. def __eq__(self, other):
  69. if isinstance(other, FixedOffsetTimezone):
  70. return self._offset == other._offset
  71. else:
  72. return NotImplemented
  73. def __ne__(self, other):
  74. if isinstance(other, FixedOffsetTimezone):
  75. return self._offset != other._offset
  76. else:
  77. return NotImplemented
  78. def __getinitargs__(self):
  79. return self._offset, self._name
  80. def utcoffset(self, dt):
  81. return self._offset
  82. def tzname(self, dt):
  83. if self._name is not None:
  84. return self._name
  85. minutes, seconds = divmod(self._offset.total_seconds(), 60)
  86. hours, minutes = divmod(minutes, 60)
  87. rv = "%+03d" % hours
  88. if minutes or seconds:
  89. rv += ":%02d" % minutes
  90. if seconds:
  91. rv += ":%02d" % seconds
  92. return rv
  93. def dst(self, dt):
  94. return ZERO
  95. STDOFFSET = datetime.timedelta(seconds=-time.timezone)
  96. if time.daylight:
  97. DSTOFFSET = datetime.timedelta(seconds=-time.altzone)
  98. else:
  99. DSTOFFSET = STDOFFSET
  100. DSTDIFF = DSTOFFSET - STDOFFSET
  101. class LocalTimezone(datetime.tzinfo):
  102. """Platform idea of local timezone.
  103. This is the exact implementation from the Python 2.3 documentation.
  104. """
  105. def utcoffset(self, dt):
  106. if self._isdst(dt):
  107. return DSTOFFSET
  108. else:
  109. return STDOFFSET
  110. def dst(self, dt):
  111. if self._isdst(dt):
  112. return DSTDIFF
  113. else:
  114. return ZERO
  115. def tzname(self, dt):
  116. return time.tzname[self._isdst(dt)]
  117. def _isdst(self, dt):
  118. tt = (dt.year, dt.month, dt.day,
  119. dt.hour, dt.minute, dt.second,
  120. dt.weekday(), 0, -1)
  121. stamp = time.mktime(tt)
  122. tt = time.localtime(stamp)
  123. return tt.tm_isdst > 0
  124. LOCAL = LocalTimezone()
  125. # TODO: pre-generate some interesting time zones?