mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Fix editing dates with days sometimes off by one day. Fixes #2042815 [problems with dates](https://bugs.launchpad.net/calibre/+bug/2042815)
The problem seems to be some change in PyQt that causes auto-conversion of python datetime objects to QDateTime to lose timezone information. So we convert manually preserving that information.
This commit is contained in:
parent
378e21b907
commit
f3b37944c4
@ -11,7 +11,7 @@ import threading
|
|||||||
from contextlib import contextmanager, suppress
|
from contextlib import contextmanager, suppress
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from qt.core import (
|
from qt.core import (
|
||||||
QApplication, QBuffer, QByteArray, QColor, QDateTime, QDesktopServices, QDialog,
|
QApplication, QBuffer, QByteArray, QColor, QDesktopServices, QDialog,
|
||||||
QDialogButtonBox, QEvent, QFile, QFileDialog, QFileIconProvider, QFileInfo, QFont,
|
QDialogButtonBox, QEvent, QFile, QFileDialog, QFileIconProvider, QFileInfo, QFont,
|
||||||
QFontDatabase, QFontInfo, QFontMetrics, QGuiApplication, QIcon, QImageReader,
|
QFontDatabase, QFontInfo, QFontMetrics, QGuiApplication, QIcon, QImageReader,
|
||||||
QImageWriter, QIODevice, QLocale, QNetworkProxyFactory, QObject, QPalette,
|
QImageWriter, QIODevice, QLocale, QNetworkProxyFactory, QObject, QPalette,
|
||||||
@ -37,7 +37,7 @@ from calibre.gui2.qt_file_dialogs import FileDialog
|
|||||||
from calibre.ptempfile import base_dir
|
from calibre.ptempfile import base_dir
|
||||||
from calibre.utils.config import Config, ConfigProxy, JSONConfig, dynamic
|
from calibre.utils.config import Config, ConfigProxy, JSONConfig, dynamic
|
||||||
from calibre.utils.config_base import tweaks
|
from calibre.utils.config_base import tweaks
|
||||||
from calibre.utils.date import UNDEFINED_DATE
|
from calibre.utils.date import UNDEFINED_DATE, qt_from_dt
|
||||||
from calibre.utils.file_type_icons import EXT_MAP
|
from calibre.utils.file_type_icons import EXT_MAP
|
||||||
from calibre.utils.img import set_image_allocation_limit
|
from calibre.utils.img import set_image_allocation_limit
|
||||||
from calibre.utils.localization import get_lang
|
from calibre.utils.localization import get_lang
|
||||||
@ -428,7 +428,7 @@ create_defs()
|
|||||||
del create_defs
|
del create_defs
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
UNDEFINED_QDATETIME = QDateTime(UNDEFINED_DATE)
|
UNDEFINED_QDATETIME = qt_from_dt(UNDEFINED_DATE, as_utc=True)
|
||||||
QT_HIDDEN_CLEAR_ACTION = '_q_qlineeditclearaction'
|
QT_HIDDEN_CLEAR_ACTION = '_q_qlineeditclearaction'
|
||||||
ALL_COLUMNS = ['title', 'ondevice', 'authors', 'size', 'timestamp', 'rating', 'publisher',
|
ALL_COLUMNS = ['title', 'ondevice', 'authors', 'size', 'timestamp', 'rating', 'publisher',
|
||||||
'tags', 'series', 'pubdate']
|
'tags', 'series', 'pubdate']
|
||||||
|
@ -9,7 +9,7 @@ import os
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from qt.core import (
|
from qt.core import (
|
||||||
QApplication, QCheckBox, QComboBox, QDateTime, QDialog, QDoubleSpinBox, QGridLayout,
|
QApplication, QCheckBox, QComboBox, QDialog, QDoubleSpinBox, QGridLayout,
|
||||||
QGroupBox, QHBoxLayout, QIcon, QLabel, QLineEdit, QMessageBox, QPlainTextEdit,
|
QGroupBox, QHBoxLayout, QIcon, QLabel, QLineEdit, QMessageBox, QPlainTextEdit,
|
||||||
QSizePolicy, QSpacerItem, QSpinBox, QStyle, Qt, QToolButton, QUrl, QVBoxLayout,
|
QSizePolicy, QSpacerItem, QSpinBox, QStyle, Qt, QToolButton, QUrl, QVBoxLayout,
|
||||||
QWidget,
|
QWidget,
|
||||||
@ -18,15 +18,16 @@ from qt.core import (
|
|||||||
from calibre.ebooks.metadata import title_sort
|
from calibre.ebooks.metadata import title_sort
|
||||||
from calibre.gui2 import UNDEFINED_QDATETIME, elided_text, error_dialog, gprefs
|
from calibre.gui2 import UNDEFINED_QDATETIME, elided_text, error_dialog, gprefs
|
||||||
from calibre.gui2.comments_editor import Editor as CommentsEditor
|
from calibre.gui2.comments_editor import Editor as CommentsEditor
|
||||||
from calibre.gui2.markdown_editor import Editor as MarkdownEditor
|
|
||||||
from calibre.gui2.complete2 import EditWithComplete as EWC
|
from calibre.gui2.complete2 import EditWithComplete as EWC
|
||||||
from calibre.gui2.dialogs.tag_editor import TagEditor
|
from calibre.gui2.dialogs.tag_editor import TagEditor
|
||||||
from calibre.gui2.library.delegates import ClearingDoubleSpinBox, ClearingSpinBox
|
from calibre.gui2.library.delegates import ClearingDoubleSpinBox, ClearingSpinBox
|
||||||
|
from calibre.gui2.markdown_editor import Editor as MarkdownEditor
|
||||||
from calibre.gui2.widgets2 import DateTimeEdit as DateTimeEditBase, RatingEditor
|
from calibre.gui2.widgets2 import DateTimeEdit as DateTimeEditBase, RatingEditor
|
||||||
from calibre.library.comments import comments_to_html
|
from calibre.library.comments import comments_to_html
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
from calibre.utils.date import (
|
from calibre.utils.date import (
|
||||||
as_local_time, as_utc, internal_iso_format_string, is_date_undefined, now, qt_to_dt,
|
as_local_time, as_utc, internal_iso_format_string, is_date_undefined, now,
|
||||||
|
qt_from_dt, qt_to_dt,
|
||||||
)
|
)
|
||||||
from calibre.utils.icu import lower as icu_lower, sort_key
|
from calibre.utils.icu import lower as icu_lower, sort_key
|
||||||
|
|
||||||
@ -349,7 +350,7 @@ class DateTimeEdit(DateTimeEditBase):
|
|||||||
DateTimeEditBase.focusOutEvent(self, x)
|
DateTimeEditBase.focusOutEvent(self, x)
|
||||||
|
|
||||||
def set_to_today(self):
|
def set_to_today(self):
|
||||||
self.setDateTime(now())
|
self.setDateTime(qt_from_dt(now()))
|
||||||
|
|
||||||
def set_to_clear(self):
|
def set_to_clear(self):
|
||||||
self.setDateTime(UNDEFINED_QDATETIME)
|
self.setDateTime(UNDEFINED_QDATETIME)
|
||||||
@ -400,12 +401,12 @@ class DateTime(Base):
|
|||||||
if val is None:
|
if val is None:
|
||||||
val = self.dte.minimumDateTime()
|
val = self.dte.minimumDateTime()
|
||||||
else:
|
else:
|
||||||
val = QDateTime(val)
|
val = qt_from_dt(val)
|
||||||
self.dte.setDateTime(val)
|
self.dte.setDateTime(val)
|
||||||
|
|
||||||
def getter(self):
|
def getter(self):
|
||||||
val = self.dte.dateTime()
|
val = self.dte.dateTime()
|
||||||
if val <= UNDEFINED_QDATETIME:
|
if is_date_undefined(val):
|
||||||
val = None
|
val = None
|
||||||
else:
|
else:
|
||||||
val = qt_to_dt(val)
|
val = qt_to_dt(val)
|
||||||
@ -1251,13 +1252,13 @@ class BulkDateTime(BulkBase):
|
|||||||
if val is None:
|
if val is None:
|
||||||
val = self.main_widget.minimumDateTime()
|
val = self.main_widget.minimumDateTime()
|
||||||
else:
|
else:
|
||||||
val = QDateTime(val)
|
val = qt_from_dt(val)
|
||||||
self.main_widget.setDateTime(val)
|
self.main_widget.setDateTime(val)
|
||||||
self.ignore_change_signals = False
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
def getter(self):
|
def getter(self):
|
||||||
val = self.main_widget.dateTime()
|
val = self.main_widget.dateTime()
|
||||||
if val <= UNDEFINED_QDATETIME:
|
if is_date_undefined(val):
|
||||||
val = None
|
val = None
|
||||||
else:
|
else:
|
||||||
val = qt_to_dt(val)
|
val = qt_to_dt(val)
|
||||||
|
@ -15,8 +15,8 @@ import traceback
|
|||||||
from collections import defaultdict, namedtuple
|
from collections import defaultdict, namedtuple
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
from qt.core import (
|
from qt.core import (
|
||||||
QAbstractTableModel, QApplication, QColor, QDateTime, QFont, QFontMetrics, QIcon,
|
QAbstractTableModel, QApplication, QColor, QFont, QFontMetrics, QIcon, QImage,
|
||||||
QImage, QModelIndex, QPainter, QPixmap, Qt, pyqtSignal,
|
QModelIndex, QPainter, QPixmap, Qt, pyqtSignal,
|
||||||
)
|
)
|
||||||
|
|
||||||
from calibre import (
|
from calibre import (
|
||||||
@ -34,7 +34,7 @@ from calibre.library.save_to_disk import find_plugboard
|
|||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.utils.config import device_prefs, prefs, tweaks
|
from calibre.utils.config import device_prefs, prefs, tweaks
|
||||||
from calibre.utils.date import (
|
from calibre.utils.date import (
|
||||||
UNDEFINED_DATE, as_local_time, dt_factory, is_date_undefined, qt_to_dt,
|
UNDEFINED_DATE, dt_factory, is_date_undefined, qt_from_dt, qt_to_dt,
|
||||||
)
|
)
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
from calibre.utils.localization import calibre_langcode_to_name, ngettext
|
from calibre.utils.localization import calibre_langcode_to_name, ngettext
|
||||||
@ -919,7 +919,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
elif dt == 'datetime':
|
elif dt == 'datetime':
|
||||||
def func(idx):
|
def func(idx):
|
||||||
val = fffunc(field_obj, idfunc(idx), default_value=UNDEFINED_DATE)
|
val = fffunc(field_obj, idfunc(idx), default_value=UNDEFINED_DATE)
|
||||||
return None if is_date_undefined(val) else QDateTime(as_local_time(val))
|
return None if is_date_undefined(val) else qt_from_dt(val)
|
||||||
elif dt == 'rating':
|
elif dt == 'rating':
|
||||||
rating_fields[field] = m['display'].get('allow_half_stars', False)
|
rating_fields[field] = m['display'].get('allow_half_stars', False)
|
||||||
|
|
||||||
|
@ -36,7 +36,9 @@ from calibre.gui2.comments_editor import Editor
|
|||||||
from calibre.gui2.complete2 import EditWithComplete
|
from calibre.gui2.complete2 import EditWithComplete
|
||||||
from calibre.gui2.dialogs.tag_editor import TagEditor
|
from calibre.gui2.dialogs.tag_editor import TagEditor
|
||||||
from calibre.gui2.languages import LanguagesEdit as LE
|
from calibre.gui2.languages import LanguagesEdit as LE
|
||||||
from calibre.gui2.widgets import EnLineEdit, FormatList as _FormatList, ImageView, LineEditIndicators
|
from calibre.gui2.widgets import (
|
||||||
|
EnLineEdit, FormatList as _FormatList, ImageView, LineEditIndicators,
|
||||||
|
)
|
||||||
from calibre.gui2.widgets2 import (
|
from calibre.gui2.widgets2 import (
|
||||||
DateTimeEdit, Dialog, RatingEditor, RightClickButton, access_key,
|
DateTimeEdit, Dialog, RatingEditor, RightClickButton, access_key,
|
||||||
populate_standard_spinbox_context_menu,
|
populate_standard_spinbox_context_menu,
|
||||||
@ -46,7 +48,7 @@ from calibre.ptempfile import PersistentTemporaryFile, SpooledTemporaryFile
|
|||||||
from calibre.utils.config import prefs, tweaks
|
from calibre.utils.config import prefs, tweaks
|
||||||
from calibre.utils.date import (
|
from calibre.utils.date import (
|
||||||
UNDEFINED_DATE, as_local_time, internal_iso_format_string, is_date_undefined,
|
UNDEFINED_DATE, as_local_time, internal_iso_format_string, is_date_undefined,
|
||||||
local_tz, parse_only_date, qt_to_dt, utcfromtimestamp,
|
local_tz, parse_only_date, qt_from_dt, qt_to_dt, utcfromtimestamp,
|
||||||
)
|
)
|
||||||
from calibre.utils.filenames import make_long_path_useable
|
from calibre.utils.filenames import make_long_path_useable
|
||||||
from calibre.utils.icu import sort_key, strcmp
|
from calibre.utils.icu import sort_key, strcmp
|
||||||
@ -188,6 +190,8 @@ def make_undoable(spinbox):
|
|||||||
if hasattr(self, 'setDateTime'):
|
if hasattr(self, 'setDateTime'):
|
||||||
if isinstance(val, date) and not isinstance(val, datetime) and not is_date_undefined(val):
|
if isinstance(val, date) and not isinstance(val, datetime) and not is_date_undefined(val):
|
||||||
val = parse_only_date(val.isoformat(), assume_utc=False, as_utc=False)
|
val = parse_only_date(val.isoformat(), assume_utc=False, as_utc=False)
|
||||||
|
if isinstance(val, datetime):
|
||||||
|
val = qt_from_dt(val)
|
||||||
self.setDateTime(val)
|
self.setDateTime(val)
|
||||||
elif hasattr(self, 'setValue'):
|
elif hasattr(self, 'setValue'):
|
||||||
self.setValue(val)
|
self.setValue(val)
|
||||||
|
@ -178,6 +178,17 @@ def qt_to_dt(qdate_or_qdatetime, as_utc=True):
|
|||||||
return dt.astimezone(_utc_tz if as_utc else _local_tz)
|
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
|
||||||
|
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()
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def fromtimestamp(ctime, as_utc=True):
|
def fromtimestamp(ctime, as_utc=True):
|
||||||
dt = datetime.utcfromtimestamp(ctime).replace(tzinfo=_utc_tz)
|
dt = datetime.utcfromtimestamp(ctime).replace(tzinfo=_utc_tz)
|
||||||
if not as_utc:
|
if not as_utc:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user