diff --git a/src/calibre/utils/date.py b/src/calibre/utils/date.py index 7576c850c1..f19631be23 100644 --- a/src/calibre/utils/date.py +++ b/src/calibre/utils/date.py @@ -6,12 +6,12 @@ __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' import re -from datetime import datetime, time as dtime, timedelta, MINYEAR, MAXYEAR +from datetime import MAXYEAR, MINYEAR, datetime, time as dtime, timedelta from functools import partial from calibre import strftime -from calibre.constants import iswindows, ismacos, preferred_encoding -from calibre.utils.iso8601 import utc_tz, local_tz, UNDEFINED_DATE +from calibre.constants import ismacos, iswindows, preferred_encoding +from calibre.utils.iso8601 import UNDEFINED_DATE, local_tz, utc_tz from calibre.utils.localization import lcdata from polyglot.builtins import native_string_type @@ -152,24 +152,33 @@ def dt_factory(time_t, assume_utc=False, as_utc=True): def safeyear(x): - return min(max(x, MINYEAR), MAXYEAR) + return min(max(MINYEAR, x), MAXYEAR) def qt_to_dt(qdate_or_qdatetime, as_utc=True): + from qt.core import Qt, QDateTime o = qdate_or_qdatetime if o is None: return UNDEFINED_DATE - if hasattr(o, 'toUTC'): - # QDateTime - o = o.toUTC() - d, t = o.date(), o.time() - try: - ans = datetime(safeyear(d.year()), d.month(), d.day(), t.hour(), t.minute(), t.second(), t.msec()*1000, utc_tz) - except ValueError: - ans = datetime(safeyear(d.year()), d.month(), 1, t.hour(), t.minute(), t.second(), t.msec()*1000, utc_tz) - if not as_utc: - ans = ans.astimezone(local_tz) - return ans + if hasattr(o, 'toUTC'): # QDateTime + def c(o: QDateTime, tz=utc_tz): + d, t = o.date(), o.time() + try: + return datetime(safeyear(d.year()), d.month(), d.day(), t.hour(), t.minute(), t.second(), t.msec()*1000, tz) + except ValueError: + return datetime(safeyear(d.year()), d.month(), 1, t.hour(), t.minute(), t.second(), t.msec()*1000, tz) + + # DST causes differences in how python and Qt convert automatically from local to UTC, so convert explicitly ourselves + # QDateTime::toUTC() and datetime.astimezone(utc_tz) give + # different results for datetimes in the local_tz when DST is involved. Sigh. + spec = o.timeSpec() + if spec == Qt.TimeSpec.LocalTime: + ans = c(o, local_tz) + elif spec == Qt.TimeSpec.UTC: + ans = c(o, utc_tz) + else: + ans = c(o.toUTC(), utc_tz) + return ans.astimezone(utc_tz if as_utc else local_tz) try: dt = datetime(safeyear(o.year()), o.month(), o.day()).replace(tzinfo=_local_tz) @@ -178,14 +187,16 @@ def qt_to_dt(qdate_or_qdatetime, as_utc=True): return dt.astimezone(_utc_tz if as_utc else _local_tz) -def qt_from_dt(d, as_utc=False, assume_utc=False): - from qt.core import QDateTime, QTimeZone +def qt_from_dt(d: datetime, as_utc=False, assume_utc=False): + from qt.core import QDate, QDateTime, QTime, QTimeZone if d.tzinfo is None: d = d.replace(tzinfo=utc_tz if assume_utc else local_tz) - d = d.astimezone(utc_tz) - ans = QDateTime.fromMSecsSinceEpoch(int(d.timestamp() * 1000), QTimeZone.utc()) - if not as_utc: - ans = ans.toLocalTime() + if as_utc: + d = d.astimezone(utc_tz) + ans = QDateTime.fromMSecsSinceEpoch(int(d.timestamp() * 1000), QTimeZone.utc()) + else: + d = d.astimezone(local_tz) + ans = QDateTime(QDate(d.year, d.month, d.day), QTime(d.hour, d.minute, d.second, int(d.microsecond / 1000))) return ans