More fixes for conversion to/from QDateTime and datetime objects

Fixes #2043943 [problems with dates not completely fixed](https://bugs.launchpad.net/calibre/+bug/2043943)
This commit is contained in:
Kovid Goyal 2023-11-20 18:35:53 +05:30
parent b36475fdae
commit db33abeea9
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C

View File

@ -6,12 +6,12 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__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