More timezone aware fixes

This commit is contained in:
Kovid Goyal 2010-02-15 12:15:01 -07:00
parent 137d83c0e2
commit c82f3e4138
11 changed files with 76 additions and 63 deletions

View File

@ -7,12 +7,11 @@ __docformat__ = 'restructuredtext en'
Fetch metadata using Amazon AWS
'''
import sys, re
from datetime import datetime
from lxml import etree
from calibre import browser
from calibre.utils.date import parse_date
from calibre.utils.date import parse_date, utcnow
from calibre.ebooks.metadata import MetaInformation, string_to_authors
AWS_NS = 'http://webservices.amazon.com/AWSECommerceService/2005-10-05'
@ -44,7 +43,7 @@ def get_social_metadata(title, authors, publisher, isbn):
try:
d = root.findtext('.//'+AWS('PublicationDate'))
if d:
default = datetime.utcnow().replace(day=15)
default = utcnow().replace(day=15)
d = parse_date(d[0].text, assume_utc=True, default=default)
mi.pubdate = d
except:

View File

@ -6,14 +6,13 @@ __docformat__ = 'restructuredtext en'
import sys, textwrap
from urllib import urlencode
from functools import partial
from datetime import datetime
from lxml import etree
from calibre import browser, preferred_encoding
from calibre.ebooks.metadata import MetaInformation
from calibre.utils.config import OptionParser
from calibre.utils.date import parse_date
from calibre.utils.date import parse_date, utcnow
NAMESPACES = {
'openSearch':'http://a9.com/-/spec/opensearchrss/1.0/',
@ -156,7 +155,7 @@ class ResultList(list):
try:
d = date(entry)
if d:
default = datetime.utcnow().replace(day=15)
default = utcnow().replace(day=15)
d = parse_date(d[0].text, assume_utc=True, default=default)
else:
d = None

View File

@ -11,11 +11,11 @@ __docformat__ = 'restructuredtext en'
from struct import pack, unpack
from cStringIO import StringIO
from datetime import datetime
from calibre.ebooks.mobi import MobiError
from calibre.ebooks.mobi.writer import rescale_image, MAX_THUMB_DIMEN
from calibre.ebooks.mobi.langcodes import iana2mobi
from calibre.utils.date import now as nowf
class StreamSlicer(object):
@ -331,7 +331,7 @@ class MetadataUpdater(object):
recs.append((106, self.timestamp))
pop_exth_record(106)
else:
recs.append((106, str(datetime.now()).encode(self.codec, 'replace')))
recs.append((106, nowf().isoformat().encode(self.codec, 'replace')))
pop_exth_record(106)
if self.cover_record is not None:
recs.append((201, pack('>I', self.cover_rindex)))

View File

@ -4,13 +4,11 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
Read data from .mobi files
'''
import datetime
import functools
import os
import re
import struct
import textwrap
import cStringIO
try:
@ -23,6 +21,7 @@ from lxml import html, etree
from calibre import entity_to_unicode, CurrentDir
from calibre.utils.filenames import ascii_filename
from calibre.utils.date import parse_date
from calibre.ptempfile import TemporaryDirectory
from calibre.ebooks import DRMError
from calibre.ebooks.chardet import ENCODING_PATS
@ -96,8 +95,7 @@ class EXTHHeader(object):
self.mi.tags = list(set(self.mi.tags))
elif id == 106:
try:
self.mi.publish_date = datetime.datetime.strptime(
content, '%Y-%m-%d', ).date()
self.mi.pubdate = parse_date(content, as_utc=False)
except:
pass
elif id == 108:

View File

@ -10,7 +10,6 @@ import os
import re
import time
import traceback
from datetime import datetime, timedelta
from PyQt4.Qt import SIGNAL, QObject, QCoreApplication, Qt, QTimer, QThread, QDate, \
QPixmap, QListWidgetItem, QDialog
@ -29,6 +28,7 @@ from calibre.ebooks.metadata.library_thing import cover_from_isbn
from calibre import islinux
from calibre.ebooks.metadata.meta import get_metadata
from calibre.utils.config import prefs, tweaks
from calibre.utils.date import qt_to_dt
from calibre.customize.ui import run_plugins_on_import, get_isbndb_key
from calibre.gui2.dialogs.config.social import SocialMetadata
@ -354,12 +354,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.comments.setPlainText(comments if comments else '')
cover = self.db.cover(row)
pubdate = db.pubdate(self.id, index_is_id=True)
self.local_timezone_offset = timedelta(seconds=time.timezone) - timedelta(hours=time.daylight)
pubdate = pubdate - self.local_timezone_offset
self.pubdate.setDate(QDate(pubdate.year, pubdate.month,
pubdate.day))
timestamp = db.timestamp(self.id, index_is_id=True)
timestamp = timestamp - self.local_timezone_offset
self.date.setDate(QDate(timestamp.year, timestamp.month,
timestamp.day))
@ -583,7 +580,6 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
if book.isbn: self.isbn.setText(book.isbn)
if book.pubdate:
d = book.pubdate
d = d - self.local_timezone_offset
self.pubdate.setDate(QDate(d.year, d.month, d.day))
summ = book.comments
if summ:
@ -656,12 +652,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.db.set_series_index(self.id, self.series_index.value(), notify=False)
self.db.set_comment(self.id, qstring_to_unicode(self.comments.toPlainText()), notify=False)
d = self.pubdate.date()
d = datetime(d.year(), d.month(), d.day())
d = d + self.local_timezone_offset
d = qt_to_dt(d)
self.db.set_pubdate(self.id, d)
d = self.date.date()
d = datetime(d.year(), d.month(), d.day())
d = d + self.local_timezone_offset
d = qt_to_dt(d)
self.db.set_timestamp(self.id, d)
if self.cover_changed:

View File

@ -1,8 +1,7 @@
from calibre.ebooks.metadata import authors_to_string
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, textwrap, traceback, time, re
from datetime import timedelta, datetime
import os, textwrap, traceback, re
from operator import attrgetter
from math import cos, sin, pi
@ -25,6 +24,7 @@ from calibre.utils.search_query_parser import SearchQueryParser
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
from calibre.ebooks.metadata import string_to_authors, fmt_sidx
from calibre.utils.config import tweaks
from calibre.utils.date import dt_factory, qt_to_dt, isoformat
class LibraryDelegate(QItemDelegate):
COLOR = QColor("blue")
@ -567,13 +567,11 @@ class BooksModel(QAbstractTableModel):
def timestamp(r):
dt = self.db.data[r][tmdx]
if dt:
dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
return QDate(dt.year, dt.month, dt.day)
def pubdate(r):
dt = self.db.data[r][pddx]
if dt:
dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
return QDate(dt.year, dt.month, dt.day)
def rating(r):
@ -670,13 +668,11 @@ class BooksModel(QAbstractTableModel):
elif column == 'timestamp':
if val.isNull() or not val.isValid():
return False
dt = datetime(val.year(), val.month(), val.day()) + timedelta(seconds=time.timezone) - timedelta(hours=time.daylight)
self.db.set_timestamp(id, dt)
self.db.set_timestamp(id, qt_to_dt(val, as_utc=False))
elif column == 'pubdate':
if val.isNull() or not val.isValid():
return False
dt = datetime(val.year(), val.month(), val.day()) + timedelta(seconds=time.timezone) - timedelta(hours=time.daylight)
self.db.set_pubdate(id, dt)
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
else:
self.db.set(row, column, val)
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
@ -1032,7 +1028,8 @@ class DeviceBooksModel(BooksModel):
def datecmp(x, y):
x = self.db[x].datetime
y = self.db[y].datetime
return cmp(datetime(*x[0:6]), datetime(*y[0:6]))
return cmp(dt_factory(x, assume_utc=True), dt_factory(y,
assume_utc=True))
def sizecmp(x, y):
x, y = int(self.db[x].size), int(self.db[y].size)
return cmp(x, y)
@ -1081,10 +1078,8 @@ class DeviceBooksModel(BooksModel):
type = ext[1:].lower()
data[_('Format')] = type
data[_('Path')] = item.path
dt = item.datetime
dt = datetime(*dt[0:6])
dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
data[_('Timestamp')] = strftime('%a %b %d %H:%M:%S %Y', dt.timetuple())
dt = dt_factory(item.datetime, assume_utc=True)
data[_('Timestamp')] = isoformat(dt, sep=' ', as_utc=False)
data[_('Tags')] = ', '.join(item.tags)
self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), data)
@ -1119,8 +1114,7 @@ class DeviceBooksModel(BooksModel):
return QVariant(BooksView.human_readable(size))
elif col == 3:
dt = self.db[self.map[row]].datetime
dt = datetime(*dt[0:6])
dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
dt = dt_factory(dt, assume_utc=True, as_utc=False)
return QVariant(strftime(BooksView.TIME_FMT, dt.timetuple()))
elif col == 4:
tags = self.db[self.map[row]].tags

View File

@ -11,7 +11,6 @@ import sys, textwrap, operator, os, re, logging, cStringIO
import __builtin__
from itertools import repeat
from logging.handlers import RotatingFileHandler
from datetime import datetime
from threading import Thread
import cherrypy
@ -31,15 +30,16 @@ from calibre.utils.config import config_dir
from calibre.utils.mdns import publish as publish_zeroconf, \
stop_server as stop_zeroconf
from calibre.ebooks.metadata import fmt_sidx, title_sort
from calibre.utils.date import now as nowf, fromtimestamp
def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None):
if not hasattr(dt, 'timetuple'):
dt = datetime.now()
dt = nowf()
dt = dt.timetuple()
try:
return _strftime(fmt, dt)
except:
return _strftime(fmt, datetime.now().timetuple())
return _strftime(fmt, nowf().timetuple())
def expose(func):
@ -351,7 +351,7 @@ class LibraryServer(object):
map(int, self.opts.max_cover.split('x'))
self.max_stanza_items = opts.max_opds_items
path = P('content_server')
self.build_time = datetime.fromtimestamp(os.stat(path).st_mtime)
self.build_time = fromtimestamp(os.stat(path).st_mtime)
self.default_cover = open(P('content_server/default_cover.jpg'), 'rb').read()
cherrypy.config.update({
@ -429,7 +429,7 @@ class LibraryServer(object):
cherrypy.response.headers['Content-Type'] = 'image/jpeg'
cherrypy.response.timeout = 3600
path = getattr(cover, 'name', False)
updated = datetime.utcfromtimestamp(os.stat(path).st_mtime) if path and \
updated = fromtimestamp(os.stat(path).st_mtime) if path and \
os.access(path, os.R_OK) else self.build_time
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
try:
@ -476,7 +476,7 @@ class LibraryServer(object):
cherrypy.response.timeout = 3600
path = getattr(fmt, 'name', None)
if path and os.path.exists(path):
updated = datetime.utcfromtimestamp(os.stat(path).st_mtime)
updated = fromtimestamp(os.stat(path).st_mtime)
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
return fmt.read()
@ -841,7 +841,7 @@ class LibraryServer(object):
if not os.path.exists(path):
raise cherrypy.HTTPError(404, '%s not found'%name)
if self.opts.develop:
lm = datetime.fromtimestamp(os.stat(path).st_mtime)
lm = fromtimestamp(os.stat(path).st_mtime)
cherrypy.response.headers['Last-Modified'] = self.last_modified(lm)
return open(path, 'rb').read()

View File

@ -11,8 +11,8 @@ from datetime import datetime
from dateutil.parser import parse
from dateutil.tz import tzlocal, tzutc
_utc_tz = tzutc()
_local_tz = tzlocal()
utc_tz = _utc_tz = tzutc()
local_tz = _local_tz = tzlocal()
def parse_date(date_string, assume_utc=False, as_utc=True, default=None):
'''
@ -34,17 +34,48 @@ def parse_date(date_string, assume_utc=False, as_utc=True, default=None):
dt = parse(date_string, default=default)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=_utc_tz if assume_utc else _local_tz)
dt = dt.astimezone(_utc_tz if as_utc else _local_tz)
return dt.astimezone(_utc_tz if as_utc else _local_tz)
def strptime(val, fmt, assume_utc=False, as_utc=True):
dt = datetime.strptime(val, fmt)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=_utc_tz if assume_utc else _local_tz)
return dt.astimezone(_utc_tz if as_utc else _local_tz)
def dt_factory(time_t, assume_utc=False, as_utc=True):
dt = datetime(time_t[0:6])
if dt.tzinfo is None:
dt = dt.replace(tzinfo=_utc_tz if assume_utc else _local_tz)
return dt.astimezone(_utc_tz if as_utc else _local_tz)
def qt_to_dt(qdate_or_qdatetime, as_utc=True):
from PyQt4.Qt import Qt
o = qdate_or_qdatetime
if hasattr(o, 'toUTC'):
# QDateTime
o = unicode(o.toUTC().toString(Qt.ISODate))
return parse_date(o, assume_utc=True, as_utc=as_utc)
dt = datetime(o.year(), o.month(), o.day()).replace(tzinfo=_local_tz)
return dt.astimezone(_utc_tz if as_utc else _local_tz)
def fromtimestamp(ctime, as_utc=True):
dt = datetime.utcfromtimestamp().replace(tzinfo=_utc_tz)
if not as_utc:
dt = dt.astimezone(_local_tz)
return dt
def isoformat(date_time, assume_utc=False, as_utc=True):
def fromordinal(day, as_utc=True):
return datetime.fromordinal(day).replace(
tzinfo=_utc_tz if as_utc else _local_tz)
def isoformat(date_time, assume_utc=False, as_utc=True, sep='T'):
if not hasattr(date_time, 'tzinfo'):
return unicode(date_time.isoformat())
if date_time.tzinfo is None:
date_time = date_time.replace(tzinfo=_utc_tz if assume_utc else
_local_tz)
date_time = date_time.astimezone(_utc_tz if as_utc else _local_tz)
return unicode(date_time.isoformat())
return unicode(date_time.isoformat(sep))
def now():
return datetime.now().replace(tzinfo=_local_tz)

View File

@ -6,17 +6,16 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
Contains the logic for parsing feeds.
'''
import time, traceback, copy, re
from datetime import datetime
from lxml import html
from calibre.web.feeds.feedparser import parse
from calibre.utils.logging import default_log
from calibre import entity_to_unicode
from lxml import html
from calibre.utils.date import dt_factory, utcnow, local_tz
class Article(object):
time_offset = datetime.now() - datetime.utcnow()
def __init__(self, id, title, url, author, summary, published, content):
self.downloaded = False
self.id = id
@ -48,8 +47,8 @@ class Article(object):
self.author = author
self.content = content
self.date = published
self.utctime = datetime(*self.date[:6])
self.localtime = self.utctime + self.time_offset
self.utctime = dt_factory(self.date, assume_utc=True, as_utc=True)
self.localtime = self.utctime.astimezone(local_tz)
@dynamic_property
def title(self):
@ -146,7 +145,7 @@ class Feed(object):
content = item.get('content', '')
author = item.get('author', '')
article = Article(id, title, link, author, description, published, content)
delta = datetime.utcnow() - article.utctime
delta = utcnow() - article.utctime
if delta.days*24*3600 + delta.seconds <= 24*3600*self.oldest_article:
self.articles.append(article)
else:
@ -183,7 +182,7 @@ class Feed(object):
if not link and not content:
return
article = Article(id, title, link, author, description, published, content)
delta = datetime.utcnow() - article.utctime
delta = utcnow() - article.utctime
if delta.days*24*3600 + delta.seconds <= 24*3600*self.oldest_article:
self.articles.append(article)
else:

View File

@ -11,7 +11,6 @@ import os, time, traceback, re, urlparse, sys
from collections import defaultdict
from functools import partial
from contextlib import nested, closing
from datetime import datetime
from calibre import browser, __appname__, iswindows, \
@ -29,7 +28,7 @@ from calibre.web.fetch.simple import RecursiveFetcher
from calibre.utils.threadpool import WorkRequest, ThreadPool, NoResultsPending
from calibre.ptempfile import PersistentTemporaryFile, \
PersistentTemporaryDirectory
from calibre.utils.date import now as nowf
class BasicNewsRecipe(Recipe):
'''
@ -1080,11 +1079,11 @@ class BasicNewsRecipe(Recipe):
mi.publisher = __appname__
mi.author_sort = __appname__
mi.publication_type = 'periodical:'+self.publication_type
mi.timestamp = datetime.now()
mi.timestamp = nowf()
mi.comments = self.description
if not isinstance(mi.comments, unicode):
mi.comments = mi.comments.decode('utf-8', 'replace')
mi.pubdate = datetime.now()
mi.pubdate = nowf()
opf_path = os.path.join(dir, 'index.opf')
ncx_path = os.path.join(dir, 'index.ncx')

View File

@ -8,14 +8,14 @@ __docformat__ = 'restructuredtext en'
import os, calendar
from threading import RLock
from datetime import datetime, timedelta
from datetime import timedelta
from lxml import etree
from lxml.builder import ElementMaker
from calibre import browser
from calibre.utils.date import parse_date, now as nowf, utcnow, tzlocal, \
isoformat
isoformat, fromordinal
NS = 'http://calibre-ebook.com/recipe_collection'
E = ElementMaker(namespace=NS, nsmap={None:NS})
@ -163,7 +163,7 @@ class SchedulerConfig(object):
self.root.remove(x)
break
if last_downloaded is None:
last_downloaded = datetime.fromordinal(1)
last_downloaded = fromordinal(1)
sr = E.scheduled_recipe({
'id' : recipe.get('id'),
'title': recipe.get('title'),