alcuni fix per kodi 19
This commit is contained in:
+1
-1
@@ -113,7 +113,7 @@ class Downloader(object):
|
|||||||
line2 = config.get_localized_string(59983) % ( self.downloaded[1], self.downloaded[2], self.size[1], self.size[2], self.speed[1], self.speed[2], self.connections[0], self.connections[1])
|
line2 = config.get_localized_string(59983) % ( self.downloaded[1], self.downloaded[2], self.size[1], self.size[2], self.speed[1], self.speed[2], self.connections[0], self.connections[1])
|
||||||
line3 = config.get_localized_string(60202) % (self.remaining_time)
|
line3 = config.get_localized_string(60202) % (self.remaining_time)
|
||||||
|
|
||||||
progreso.update(int(self.progress), line1, line2 + " " + line3)
|
progreso.update(int(self.progress), line1 + '\n' + line2 + " " + line3)
|
||||||
self.__update_json()
|
self.__update_json()
|
||||||
finally:
|
finally:
|
||||||
progreso.close()
|
progreso.close()
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ def decodeHtmlentities(data):
|
|||||||
|
|
||||||
if match.group(1) == "#" and ent.replace(";", "").isdigit():
|
if match.group(1) == "#" and ent.replace(";", "").isdigit():
|
||||||
ent = unichr(int(ent.replace(";", "")))
|
ent = unichr(int(ent.replace(";", "")))
|
||||||
return ent.encode('utf-8')
|
return ent if PY3 else ent.encode('utf-8')
|
||||||
else:
|
else:
|
||||||
cp = html5.get(ent)
|
cp = html5.get(ent)
|
||||||
if cp:
|
if cp:
|
||||||
@@ -112,9 +112,11 @@ def unescape(text):
|
|||||||
# character reference
|
# character reference
|
||||||
try:
|
try:
|
||||||
if text[:3] == "&#x":
|
if text[:3] == "&#x":
|
||||||
return unichr(int(text[3:-1], 16)).encode("utf-8")
|
ret = unichr(int(text[3:-1], 16))
|
||||||
|
return ret if PY3 else ret.encode("utf-8")
|
||||||
else:
|
else:
|
||||||
return unichr(int(text[2:-1])).encode("utf-8")
|
ret = unichr(int(text[2:-1]))
|
||||||
|
return ret if PY3 else ret.encode("utf-8")
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.error("error de valor")
|
logger.error("error de valor")
|
||||||
|
|||||||
+3
-1
@@ -21,8 +21,10 @@ from core.item import Item
|
|||||||
from lib import unshortenit
|
from lib import unshortenit
|
||||||
from platformcode import config
|
from platformcode import config
|
||||||
from platformcode.logger import log
|
from platformcode.logger import log
|
||||||
|
from platformcode import logger
|
||||||
from specials import autoplay
|
from specials import autoplay
|
||||||
|
|
||||||
|
|
||||||
def hdpass_get_servers(item):
|
def hdpass_get_servers(item):
|
||||||
def get_hosts(url, quality):
|
def get_hosts(url, quality):
|
||||||
ret = []
|
ret = []
|
||||||
@@ -466,7 +468,7 @@ def scrape(func):
|
|||||||
|
|
||||||
# if url may be changed and channel has findhost to update
|
# if url may be changed and channel has findhost to update
|
||||||
if 'findhost' in func.__globals__ and not itemlist:
|
if 'findhost' in func.__globals__ and not itemlist:
|
||||||
logger.info('running findhost ' + func.__module__)
|
log('running findhost ' + func.__module__)
|
||||||
host = func.__globals__['findhost']()
|
host = func.__globals__['findhost']()
|
||||||
parse = list(urlparse.urlparse(item.url))
|
parse = list(urlparse.urlparse(item.url))
|
||||||
from core import jsontools
|
from core import jsontools
|
||||||
|
|||||||
+2
-2
@@ -100,12 +100,12 @@ def find_and_set_infoLabels(item):
|
|||||||
otvdb_global = Tvdb(tvdb_id=item.infoLabels['tvdb_id'])
|
otvdb_global = Tvdb(tvdb_id=item.infoLabels['tvdb_id'])
|
||||||
|
|
||||||
if not item.contentSeason:
|
if not item.contentSeason:
|
||||||
p_dialog.update(50, config.get_localized_string(60296), config.get_localized_string(60295))
|
p_dialog.update(50, config.get_localized_string(60296) + '\n' + config.get_localized_string(60295))
|
||||||
results, info_load = otvdb_global.get_list_results()
|
results, info_load = otvdb_global.get_list_results()
|
||||||
logger.debug("results: %s" % results)
|
logger.debug("results: %s" % results)
|
||||||
|
|
||||||
if not item.contentSeason:
|
if not item.contentSeason:
|
||||||
p_dialog.update(100, config.get_localized_string(60296), config.get_localized_string(60297) % len(results))
|
p_dialog.update(100, config.get_localized_string(60296) + '\n' + config.get_localized_string(60297) % len(results))
|
||||||
p_dialog.close()
|
p_dialog.close()
|
||||||
|
|
||||||
if len(results) > 1:
|
if len(results) > 1:
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ def save_movie(item, silent=False):
|
|||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
if filetools.write(json_path, item.tojson()):
|
if filetools.write(json_path, item.tojson()):
|
||||||
if not silent: p_dialog.update(100, config.get_localized_string(60062), item.contentTitle)
|
if not silent: p_dialog.update(100, config.get_localized_string(60062) + '\n' + item.contentTitle)
|
||||||
item_nfo.library_urls[item.channel] = item.url
|
item_nfo.library_urls[item.channel] = item.url
|
||||||
|
|
||||||
if filetools.write(nfo_path, head_nfo + item_nfo.tojson()):
|
if filetools.write(nfo_path, head_nfo + item_nfo.tojson()):
|
||||||
@@ -221,7 +221,7 @@ def save_movie(item, silent=False):
|
|||||||
# If we get to this point it is because something has gone wrong
|
# If we get to this point it is because something has gone wrong
|
||||||
logger.error("Could not save %s in the video library" % item.contentTitle)
|
logger.error("Could not save %s in the video library" % item.contentTitle)
|
||||||
if not silent:
|
if not silent:
|
||||||
p_dialog.update(100, config.get_localized_string(60063), item.contentTitle)
|
p_dialog.update(100, config.get_localized_string(60063) + '\n' + item.contentTitle)
|
||||||
p_dialog.close()
|
p_dialog.close()
|
||||||
return 0, 0, -1, path
|
return 0, 0, -1, path
|
||||||
|
|
||||||
|
|||||||
@@ -245,7 +245,6 @@ class ConverterManager(object):
|
|||||||
def parse(str):
|
def parse(str):
|
||||||
import re
|
import re
|
||||||
match = re.match('(?P<name>\w+) = (?P<module>[a-z0-9.]+):(?P<class>\w+)', str)
|
match = re.match('(?P<name>\w+) = (?P<module>[a-z0-9.]+):(?P<class>\w+)', str)
|
||||||
print match.groupdict()
|
|
||||||
return match.groupdict()
|
return match.groupdict()
|
||||||
for ep in (parse(c) for c in self.registered_converters + self.internal_converters):
|
for ep in (parse(c) for c in self.registered_converters + self.internal_converters):
|
||||||
if ep.get('name') == name:
|
if ep.get('name') == name:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from __future__ import unicode_literals
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from functools import partial
|
from functools import partial
|
||||||
# from pkg_resources import resource_stream # @UnresolvedImport
|
# from pkg_resources import resource_stream # @UnresolvedImport
|
||||||
import os
|
import os, io
|
||||||
from .converters import ConverterManager
|
from .converters import ConverterManager
|
||||||
from . import basestr
|
from . import basestr
|
||||||
|
|
||||||
@@ -19,10 +19,10 @@ COUNTRY_MATRIX = []
|
|||||||
#: The namedtuple used in the :data:`COUNTRY_MATRIX`
|
#: The namedtuple used in the :data:`COUNTRY_MATRIX`
|
||||||
IsoCountry = namedtuple('IsoCountry', ['name', 'alpha2'])
|
IsoCountry = namedtuple('IsoCountry', ['name', 'alpha2'])
|
||||||
|
|
||||||
f = open(os.path.join(os.path.dirname(__file__), 'data/iso-3166-1.txt'))
|
f = io.open(os.path.join(os.path.dirname(__file__), 'data/iso-3166-1.txt'), encoding='utf-8')
|
||||||
f.readline()
|
f.readline()
|
||||||
for l in f:
|
for l in f:
|
||||||
iso_country = IsoCountry(*l.decode('utf-8').strip().split(';'))
|
iso_country = IsoCountry(*l.strip().split(';'))
|
||||||
COUNTRIES[iso_country.alpha2] = iso_country.name
|
COUNTRIES[iso_country.alpha2] = iso_country.name
|
||||||
COUNTRY_MATRIX.append(iso_country)
|
COUNTRY_MATRIX.append(iso_country)
|
||||||
f.close()
|
f.close()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import os
|
import os, io
|
||||||
# from pkg_resources import resource_stream # @UnresolvedImport
|
# from pkg_resources import resource_stream # @UnresolvedImport
|
||||||
from .converters import ConverterManager
|
from .converters import ConverterManager
|
||||||
from .country import Country
|
from .country import Country
|
||||||
@@ -22,10 +22,10 @@ LANGUAGE_MATRIX = []
|
|||||||
#: The namedtuple used in the :data:`LANGUAGE_MATRIX`
|
#: The namedtuple used in the :data:`LANGUAGE_MATRIX`
|
||||||
IsoLanguage = namedtuple('IsoLanguage', ['alpha3', 'alpha3b', 'alpha3t', 'alpha2', 'scope', 'type', 'name', 'comment'])
|
IsoLanguage = namedtuple('IsoLanguage', ['alpha3', 'alpha3b', 'alpha3t', 'alpha2', 'scope', 'type', 'name', 'comment'])
|
||||||
|
|
||||||
f = open(os.path.join(os.path.dirname(__file__), 'data/iso-639-3.tab'))
|
f = io.open(os.path.join(os.path.dirname(__file__), 'data/iso-639-3.tab'), encoding='utf-8')
|
||||||
f.readline()
|
f.readline()
|
||||||
for l in f:
|
for l in f:
|
||||||
iso_language = IsoLanguage(*l.decode('utf-8').split('\t'))
|
iso_language = IsoLanguage(*l.split('\t'))
|
||||||
LANGUAGES.add(iso_language.alpha3)
|
LANGUAGES.add(iso_language.alpha3)
|
||||||
LANGUAGE_MATRIX.append(iso_language)
|
LANGUAGE_MATRIX.append(iso_language)
|
||||||
f.close()
|
f.close()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#
|
#
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os, io
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
# from pkg_resources import resource_stream # @UnresolvedImport
|
# from pkg_resources import resource_stream # @UnresolvedImport
|
||||||
from . import basestr
|
from . import basestr
|
||||||
@@ -20,10 +20,10 @@ SCRIPT_MATRIX = []
|
|||||||
#: The namedtuple used in the :data:`SCRIPT_MATRIX`
|
#: The namedtuple used in the :data:`SCRIPT_MATRIX`
|
||||||
IsoScript = namedtuple('IsoScript', ['code', 'number', 'name', 'french_name', 'pva', 'date'])
|
IsoScript = namedtuple('IsoScript', ['code', 'number', 'name', 'french_name', 'pva', 'date'])
|
||||||
|
|
||||||
f = open(os.path.join(os.path.dirname(__file__), 'data/iso15924-utf8-20131012.txt'))
|
f = io.open(os.path.join(os.path.dirname(__file__), 'data/iso15924-utf8-20131012.txt'), encoding='utf-8')
|
||||||
f.readline()
|
f.readline()
|
||||||
for l in f:
|
for l in f:
|
||||||
l = l.decode('utf-8').strip()
|
l = l.strip()
|
||||||
if not l or l.startswith('#'):
|
if not l or l.startswith('#'):
|
||||||
continue
|
continue
|
||||||
script = IsoScript._make(l.split(';'))
|
script = IsoScript._make(l.split(';'))
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
"""
|
# -*- coding: utf-8 -*-
|
||||||
Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
|
try:
|
||||||
|
from ._version import version as __version__
|
||||||
|
except ImportError:
|
||||||
|
__version__ = 'unknown'
|
||||||
|
|
||||||
This module offers extensions to the standard python 2.3+
|
__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz',
|
||||||
datetime module.
|
'utils', 'zoneinfo']
|
||||||
"""
|
|
||||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
|
||||||
__license__ = "PSF License"
|
|
||||||
__version__ = "1.5.0.1"
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"""
|
||||||
|
Common code used in multiple modules.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class weekday(object):
|
||||||
|
__slots__ = ["weekday", "n"]
|
||||||
|
|
||||||
|
def __init__(self, weekday, n=None):
|
||||||
|
self.weekday = weekday
|
||||||
|
self.n = n
|
||||||
|
|
||||||
|
def __call__(self, n):
|
||||||
|
if n == self.n:
|
||||||
|
return self
|
||||||
|
else:
|
||||||
|
return self.__class__(self.weekday, n)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
try:
|
||||||
|
if self.weekday != other.weekday or self.n != other.n:
|
||||||
|
return False
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((
|
||||||
|
self.weekday,
|
||||||
|
self.n,
|
||||||
|
))
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not (self == other)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
|
||||||
|
if not self.n:
|
||||||
|
return s
|
||||||
|
else:
|
||||||
|
return "%s(%+d)" % (s, self.n)
|
||||||
|
|
||||||
|
# vim:ts=4:sw=4:et
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
# file generated by setuptools_scm
|
||||||
|
# don't change, don't track in version control
|
||||||
|
version = '2.8.1'
|
||||||
+24
-27
@@ -1,19 +1,17 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
This module offers a generic easter computing method for any given year, using
|
||||||
|
Western, Orthodox or Julian algorithms.
|
||||||
This module offers extensions to the standard python 2.3+
|
|
||||||
datetime module.
|
|
||||||
"""
|
"""
|
||||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
|
||||||
__license__ = "PSF License"
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"]
|
__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"]
|
||||||
|
|
||||||
EASTER_JULIAN = 1
|
EASTER_JULIAN = 1
|
||||||
EASTER_ORTHODOX = 2
|
EASTER_ORTHODOX = 2
|
||||||
EASTER_WESTERN = 3
|
EASTER_WESTERN = 3
|
||||||
|
|
||||||
|
|
||||||
def easter(year, method=EASTER_WESTERN):
|
def easter(year, method=EASTER_WESTERN):
|
||||||
"""
|
"""
|
||||||
@@ -25,7 +23,7 @@ def easter(year, method=EASTER_WESTERN):
|
|||||||
|
|
||||||
This algorithm implements three different easter
|
This algorithm implements three different easter
|
||||||
calculation methods:
|
calculation methods:
|
||||||
|
|
||||||
1 - Original calculation in Julian calendar, valid in
|
1 - Original calculation in Julian calendar, valid in
|
||||||
dates after 326 AD
|
dates after 326 AD
|
||||||
2 - Original method, with date converted to Gregorian
|
2 - Original method, with date converted to Gregorian
|
||||||
@@ -35,24 +33,24 @@ def easter(year, method=EASTER_WESTERN):
|
|||||||
|
|
||||||
These methods are represented by the constants:
|
These methods are represented by the constants:
|
||||||
|
|
||||||
EASTER_JULIAN = 1
|
* ``EASTER_JULIAN = 1``
|
||||||
EASTER_ORTHODOX = 2
|
* ``EASTER_ORTHODOX = 2``
|
||||||
EASTER_WESTERN = 3
|
* ``EASTER_WESTERN = 3``
|
||||||
|
|
||||||
The default method is method 3.
|
The default method is method 3.
|
||||||
|
|
||||||
More about the algorithm may be found at:
|
More about the algorithm may be found at:
|
||||||
|
|
||||||
http://users.chariot.net.au/~gmarts/eastalg.htm
|
`GM Arts: Easter Algorithms <http://www.gmarts.org/index.php?go=415>`_
|
||||||
|
|
||||||
and
|
and
|
||||||
|
|
||||||
http://www.tondering.dk/claus/calendar.html
|
`The Calendar FAQ: Easter <https://www.tondering.dk/claus/cal/easter.php>`_
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not (1 <= method <= 3):
|
if not (1 <= method <= 3):
|
||||||
raise ValueError, "invalid method"
|
raise ValueError("invalid method")
|
||||||
|
|
||||||
# g - Golden year - 1
|
# g - Golden year - 1
|
||||||
# c - Century
|
# c - Century
|
||||||
@@ -69,24 +67,23 @@ def easter(year, method=EASTER_WESTERN):
|
|||||||
e = 0
|
e = 0
|
||||||
if method < 3:
|
if method < 3:
|
||||||
# Old method
|
# Old method
|
||||||
i = (19*g+15)%30
|
i = (19*g + 15) % 30
|
||||||
j = (y+y//4+i)%7
|
j = (y + y//4 + i) % 7
|
||||||
if method == 2:
|
if method == 2:
|
||||||
# Extra dates to convert Julian to Gregorian date
|
# Extra dates to convert Julian to Gregorian date
|
||||||
e = 10
|
e = 10
|
||||||
if y > 1600:
|
if y > 1600:
|
||||||
e = e+y//100-16-(y//100-16)//4
|
e = e + y//100 - 16 - (y//100 - 16)//4
|
||||||
else:
|
else:
|
||||||
# New method
|
# New method
|
||||||
c = y//100
|
c = y//100
|
||||||
h = (c-c//4-(8*c+13)//25+19*g+15)%30
|
h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30
|
||||||
i = h-(h//28)*(1-(h//28)*(29//(h+1))*((21-g)//11))
|
i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11))
|
||||||
j = (y+y//4+i+2-c+c//4)%7
|
j = (y + y//4 + i + 2 - c + c//4) % 7
|
||||||
|
|
||||||
# p can be from -6 to 56 corresponding to dates 22 March to 23 May
|
# p can be from -6 to 56 corresponding to dates 22 March to 23 May
|
||||||
# (later dates apply to method 2, although 23 May never actually occurs)
|
# (later dates apply to method 2, although 23 May never actually occurs)
|
||||||
p = i-j+e
|
p = i - j + e
|
||||||
d = 1+(p+27+(p+6)//40)%31
|
d = 1 + (p + 27 + (p + 6)//40) % 31
|
||||||
m = 3+(p+26)//30
|
m = 3 + (p + 26)//30
|
||||||
return datetime.date(int(y),int(m),int(d))
|
return datetime.date(int(y), int(m), int(d))
|
||||||
|
|
||||||
|
|||||||
@@ -1,886 +0,0 @@
|
|||||||
# -*- coding:iso-8859-1 -*-
|
|
||||||
"""
|
|
||||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
|
||||||
|
|
||||||
This module offers extensions to the standard python 2.3+
|
|
||||||
datetime module.
|
|
||||||
"""
|
|
||||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
|
||||||
__license__ = "PSF License"
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import string
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
try:
|
|
||||||
from cStringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
from StringIO import StringIO
|
|
||||||
|
|
||||||
import relativedelta
|
|
||||||
import tz
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["parse", "parserinfo"]
|
|
||||||
|
|
||||||
|
|
||||||
# Some pointers:
|
|
||||||
#
|
|
||||||
# http://www.cl.cam.ac.uk/~mgk25/iso-time.html
|
|
||||||
# http://www.iso.ch/iso/en/prods-services/popstds/datesandtime.html
|
|
||||||
# http://www.w3.org/TR/NOTE-datetime
|
|
||||||
# http://ringmaster.arc.nasa.gov/tools/time_formats.html
|
|
||||||
# http://search.cpan.org/author/MUIR/Time-modules-2003.0211/lib/Time/ParseDate.pm
|
|
||||||
# http://stein.cshl.org/jade/distrib/docs/java.text.SimpleDateFormat.html
|
|
||||||
|
|
||||||
|
|
||||||
class _timelex(object):
|
|
||||||
|
|
||||||
def __init__(self, instream):
|
|
||||||
if isinstance(instream, basestring):
|
|
||||||
instream = StringIO(instream)
|
|
||||||
self.instream = instream
|
|
||||||
self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
|
|
||||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
|
|
||||||
'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
|
|
||||||
'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
|
|
||||||
self.numchars = '0123456789'
|
|
||||||
self.whitespace = ' \t\r\n'
|
|
||||||
self.charstack = []
|
|
||||||
self.tokenstack = []
|
|
||||||
self.eof = False
|
|
||||||
|
|
||||||
def get_token(self):
|
|
||||||
if self.tokenstack:
|
|
||||||
return self.tokenstack.pop(0)
|
|
||||||
seenletters = False
|
|
||||||
token = None
|
|
||||||
state = None
|
|
||||||
wordchars = self.wordchars
|
|
||||||
numchars = self.numchars
|
|
||||||
whitespace = self.whitespace
|
|
||||||
while not self.eof:
|
|
||||||
if self.charstack:
|
|
||||||
nextchar = self.charstack.pop(0)
|
|
||||||
else:
|
|
||||||
nextchar = self.instream.read(1)
|
|
||||||
while nextchar == '\x00':
|
|
||||||
nextchar = self.instream.read(1)
|
|
||||||
if not nextchar:
|
|
||||||
self.eof = True
|
|
||||||
break
|
|
||||||
elif not state:
|
|
||||||
token = nextchar
|
|
||||||
if nextchar in wordchars:
|
|
||||||
state = 'a'
|
|
||||||
elif nextchar in numchars:
|
|
||||||
state = '0'
|
|
||||||
elif nextchar in whitespace:
|
|
||||||
token = ' '
|
|
||||||
break # emit token
|
|
||||||
else:
|
|
||||||
break # emit token
|
|
||||||
elif state == 'a':
|
|
||||||
seenletters = True
|
|
||||||
if nextchar in wordchars:
|
|
||||||
token += nextchar
|
|
||||||
elif nextchar == '.':
|
|
||||||
token += nextchar
|
|
||||||
state = 'a.'
|
|
||||||
else:
|
|
||||||
self.charstack.append(nextchar)
|
|
||||||
break # emit token
|
|
||||||
elif state == '0':
|
|
||||||
if nextchar in numchars:
|
|
||||||
token += nextchar
|
|
||||||
elif nextchar == '.':
|
|
||||||
token += nextchar
|
|
||||||
state = '0.'
|
|
||||||
else:
|
|
||||||
self.charstack.append(nextchar)
|
|
||||||
break # emit token
|
|
||||||
elif state == 'a.':
|
|
||||||
seenletters = True
|
|
||||||
if nextchar == '.' or nextchar in wordchars:
|
|
||||||
token += nextchar
|
|
||||||
elif nextchar in numchars and token[-1] == '.':
|
|
||||||
token += nextchar
|
|
||||||
state = '0.'
|
|
||||||
else:
|
|
||||||
self.charstack.append(nextchar)
|
|
||||||
break # emit token
|
|
||||||
elif state == '0.':
|
|
||||||
if nextchar == '.' or nextchar in numchars:
|
|
||||||
token += nextchar
|
|
||||||
elif nextchar in wordchars and token[-1] == '.':
|
|
||||||
token += nextchar
|
|
||||||
state = 'a.'
|
|
||||||
else:
|
|
||||||
self.charstack.append(nextchar)
|
|
||||||
break # emit token
|
|
||||||
if (state in ('a.', '0.') and
|
|
||||||
(seenletters or token.count('.') > 1 or token[-1] == '.')):
|
|
||||||
l = token.split('.')
|
|
||||||
token = l[0]
|
|
||||||
for tok in l[1:]:
|
|
||||||
self.tokenstack.append('.')
|
|
||||||
if tok:
|
|
||||||
self.tokenstack.append(tok)
|
|
||||||
return token
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def next(self):
|
|
||||||
token = self.get_token()
|
|
||||||
if token is None:
|
|
||||||
raise StopIteration
|
|
||||||
return token
|
|
||||||
|
|
||||||
def split(cls, s):
|
|
||||||
return list(cls(s))
|
|
||||||
split = classmethod(split)
|
|
||||||
|
|
||||||
|
|
||||||
class _resultbase(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
for attr in self.__slots__:
|
|
||||||
setattr(self, attr, None)
|
|
||||||
|
|
||||||
def _repr(self, classname):
|
|
||||||
l = []
|
|
||||||
for attr in self.__slots__:
|
|
||||||
value = getattr(self, attr)
|
|
||||||
if value is not None:
|
|
||||||
l.append("%s=%s" % (attr, `value`))
|
|
||||||
return "%s(%s)" % (classname, ", ".join(l))
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self._repr(self.__class__.__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class parserinfo(object):
|
|
||||||
|
|
||||||
# m from a.m/p.m, t from ISO T separator
|
|
||||||
JUMP = [" ", ".", ",", ";", "-", "/", "'",
|
|
||||||
"at", "on", "and", "ad", "m", "t", "of",
|
|
||||||
"st", "nd", "rd", "th"]
|
|
||||||
|
|
||||||
WEEKDAYS = [("Mon", "Monday"),
|
|
||||||
("Tue", "Tuesday"),
|
|
||||||
("Wed", "Wednesday"),
|
|
||||||
("Thu", "Thursday"),
|
|
||||||
("Fri", "Friday"),
|
|
||||||
("Sat", "Saturday"),
|
|
||||||
("Sun", "Sunday")]
|
|
||||||
MONTHS = [("Jan", "January"),
|
|
||||||
("Feb", "February"),
|
|
||||||
("Mar", "March"),
|
|
||||||
("Apr", "April"),
|
|
||||||
("May", "May"),
|
|
||||||
("Jun", "June"),
|
|
||||||
("Jul", "July"),
|
|
||||||
("Aug", "August"),
|
|
||||||
("Sep", "September"),
|
|
||||||
("Oct", "October"),
|
|
||||||
("Nov", "November"),
|
|
||||||
("Dec", "December")]
|
|
||||||
HMS = [("h", "hour", "hours"),
|
|
||||||
("m", "minute", "minutes"),
|
|
||||||
("s", "second", "seconds")]
|
|
||||||
AMPM = [("am", "a"),
|
|
||||||
("pm", "p")]
|
|
||||||
UTCZONE = ["UTC", "GMT", "Z"]
|
|
||||||
PERTAIN = ["of"]
|
|
||||||
TZOFFSET = {}
|
|
||||||
|
|
||||||
def __init__(self, dayfirst=False, yearfirst=False):
|
|
||||||
self._jump = self._convert(self.JUMP)
|
|
||||||
self._weekdays = self._convert(self.WEEKDAYS)
|
|
||||||
self._months = self._convert(self.MONTHS)
|
|
||||||
self._hms = self._convert(self.HMS)
|
|
||||||
self._ampm = self._convert(self.AMPM)
|
|
||||||
self._utczone = self._convert(self.UTCZONE)
|
|
||||||
self._pertain = self._convert(self.PERTAIN)
|
|
||||||
|
|
||||||
self.dayfirst = dayfirst
|
|
||||||
self.yearfirst = yearfirst
|
|
||||||
|
|
||||||
self._year = time.localtime().tm_year
|
|
||||||
self._century = self._year//100*100
|
|
||||||
|
|
||||||
def _convert(self, lst):
|
|
||||||
dct = {}
|
|
||||||
for i in range(len(lst)):
|
|
||||||
v = lst[i]
|
|
||||||
if isinstance(v, tuple):
|
|
||||||
for v in v:
|
|
||||||
dct[v.lower()] = i
|
|
||||||
else:
|
|
||||||
dct[v.lower()] = i
|
|
||||||
return dct
|
|
||||||
|
|
||||||
def jump(self, name):
|
|
||||||
return name.lower() in self._jump
|
|
||||||
|
|
||||||
def weekday(self, name):
|
|
||||||
if len(name) >= 3:
|
|
||||||
try:
|
|
||||||
return self._weekdays[name.lower()]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
return None
|
|
||||||
|
|
||||||
def month(self, name):
|
|
||||||
if len(name) >= 3:
|
|
||||||
try:
|
|
||||||
return self._months[name.lower()]+1
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
return None
|
|
||||||
|
|
||||||
def hms(self, name):
|
|
||||||
try:
|
|
||||||
return self._hms[name.lower()]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def ampm(self, name):
|
|
||||||
try:
|
|
||||||
return self._ampm[name.lower()]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def pertain(self, name):
|
|
||||||
return name.lower() in self._pertain
|
|
||||||
|
|
||||||
def utczone(self, name):
|
|
||||||
return name.lower() in self._utczone
|
|
||||||
|
|
||||||
def tzoffset(self, name):
|
|
||||||
if name in self._utczone:
|
|
||||||
return 0
|
|
||||||
return self.TZOFFSET.get(name)
|
|
||||||
|
|
||||||
def convertyear(self, year):
|
|
||||||
if year < 100:
|
|
||||||
year += self._century
|
|
||||||
if abs(year-self._year) >= 50:
|
|
||||||
if year < self._year:
|
|
||||||
year += 100
|
|
||||||
else:
|
|
||||||
year -= 100
|
|
||||||
return year
|
|
||||||
|
|
||||||
def validate(self, res):
|
|
||||||
# move to info
|
|
||||||
if res.year is not None:
|
|
||||||
res.year = self.convertyear(res.year)
|
|
||||||
if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
|
|
||||||
res.tzname = "UTC"
|
|
||||||
res.tzoffset = 0
|
|
||||||
elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
|
|
||||||
res.tzoffset = 0
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class parser(object):
|
|
||||||
|
|
||||||
def __init__(self, info=None):
|
|
||||||
self.info = info or parserinfo()
|
|
||||||
|
|
||||||
def parse(self, timestr, default=None,
|
|
||||||
ignoretz=False, tzinfos=None,
|
|
||||||
**kwargs):
|
|
||||||
if not default:
|
|
||||||
default = datetime.datetime.now().replace(hour=0, minute=0,
|
|
||||||
second=0, microsecond=0)
|
|
||||||
res = self._parse(timestr, **kwargs)
|
|
||||||
if res is None:
|
|
||||||
raise ValueError, "unknown string format"
|
|
||||||
repl = {}
|
|
||||||
for attr in ["year", "month", "day", "hour",
|
|
||||||
"minute", "second", "microsecond"]:
|
|
||||||
value = getattr(res, attr)
|
|
||||||
if value is not None:
|
|
||||||
repl[attr] = value
|
|
||||||
ret = default.replace(**repl)
|
|
||||||
if res.weekday is not None and not res.day:
|
|
||||||
ret = ret+relativedelta.relativedelta(weekday=res.weekday)
|
|
||||||
if not ignoretz:
|
|
||||||
if callable(tzinfos) or tzinfos and res.tzname in tzinfos:
|
|
||||||
if callable(tzinfos):
|
|
||||||
tzdata = tzinfos(res.tzname, res.tzoffset)
|
|
||||||
else:
|
|
||||||
tzdata = tzinfos.get(res.tzname)
|
|
||||||
if isinstance(tzdata, datetime.tzinfo):
|
|
||||||
tzinfo = tzdata
|
|
||||||
elif isinstance(tzdata, basestring):
|
|
||||||
tzinfo = tz.tzstr(tzdata)
|
|
||||||
elif isinstance(tzdata, int):
|
|
||||||
tzinfo = tz.tzoffset(res.tzname, tzdata)
|
|
||||||
else:
|
|
||||||
raise ValueError, "offset must be tzinfo subclass, " \
|
|
||||||
"tz string, or int offset"
|
|
||||||
ret = ret.replace(tzinfo=tzinfo)
|
|
||||||
elif res.tzname and res.tzname in time.tzname:
|
|
||||||
ret = ret.replace(tzinfo=tz.tzlocal())
|
|
||||||
elif res.tzoffset == 0:
|
|
||||||
ret = ret.replace(tzinfo=tz.tzutc())
|
|
||||||
elif res.tzoffset:
|
|
||||||
ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
class _result(_resultbase):
|
|
||||||
__slots__ = ["year", "month", "day", "weekday",
|
|
||||||
"hour", "minute", "second", "microsecond",
|
|
||||||
"tzname", "tzoffset"]
|
|
||||||
|
|
||||||
def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False):
|
|
||||||
info = self.info
|
|
||||||
if dayfirst is None:
|
|
||||||
dayfirst = info.dayfirst
|
|
||||||
if yearfirst is None:
|
|
||||||
yearfirst = info.yearfirst
|
|
||||||
res = self._result()
|
|
||||||
l = _timelex.split(timestr)
|
|
||||||
try:
|
|
||||||
|
|
||||||
# year/month/day list
|
|
||||||
ymd = []
|
|
||||||
|
|
||||||
# Index of the month string in ymd
|
|
||||||
mstridx = -1
|
|
||||||
|
|
||||||
len_l = len(l)
|
|
||||||
i = 0
|
|
||||||
while i < len_l:
|
|
||||||
|
|
||||||
# Check if it's a number
|
|
||||||
try:
|
|
||||||
value_repr = l[i]
|
|
||||||
value = float(value_repr)
|
|
||||||
except ValueError:
|
|
||||||
value = None
|
|
||||||
|
|
||||||
if value is not None:
|
|
||||||
# Token is a number
|
|
||||||
len_li = len(l[i])
|
|
||||||
i += 1
|
|
||||||
if (len(ymd) == 3 and len_li in (2, 4)
|
|
||||||
and (i >= len_l or (l[i] != ':' and
|
|
||||||
info.hms(l[i]) is None))):
|
|
||||||
# 19990101T23[59]
|
|
||||||
s = l[i-1]
|
|
||||||
res.hour = int(s[:2])
|
|
||||||
if len_li == 4:
|
|
||||||
res.minute = int(s[2:])
|
|
||||||
elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6):
|
|
||||||
# YYMMDD or HHMMSS[.ss]
|
|
||||||
s = l[i-1]
|
|
||||||
if not ymd and l[i-1].find('.') == -1:
|
|
||||||
ymd.append(info.convertyear(int(s[:2])))
|
|
||||||
ymd.append(int(s[2:4]))
|
|
||||||
ymd.append(int(s[4:]))
|
|
||||||
else:
|
|
||||||
# 19990101T235959[.59]
|
|
||||||
res.hour = int(s[:2])
|
|
||||||
res.minute = int(s[2:4])
|
|
||||||
res.second, res.microsecond = _parsems(s[4:])
|
|
||||||
elif len_li == 8:
|
|
||||||
# YYYYMMDD
|
|
||||||
s = l[i-1]
|
|
||||||
ymd.append(int(s[:4]))
|
|
||||||
ymd.append(int(s[4:6]))
|
|
||||||
ymd.append(int(s[6:]))
|
|
||||||
elif len_li in (12, 14):
|
|
||||||
# YYYYMMDDhhmm[ss]
|
|
||||||
s = l[i-1]
|
|
||||||
ymd.append(int(s[:4]))
|
|
||||||
ymd.append(int(s[4:6]))
|
|
||||||
ymd.append(int(s[6:8]))
|
|
||||||
res.hour = int(s[8:10])
|
|
||||||
res.minute = int(s[10:12])
|
|
||||||
if len_li == 14:
|
|
||||||
res.second = int(s[12:])
|
|
||||||
elif ((i < len_l and info.hms(l[i]) is not None) or
|
|
||||||
(i+1 < len_l and l[i] == ' ' and
|
|
||||||
info.hms(l[i+1]) is not None)):
|
|
||||||
# HH[ ]h or MM[ ]m or SS[.ss][ ]s
|
|
||||||
if l[i] == ' ':
|
|
||||||
i += 1
|
|
||||||
idx = info.hms(l[i])
|
|
||||||
while True:
|
|
||||||
if idx == 0:
|
|
||||||
res.hour = int(value)
|
|
||||||
if value%1:
|
|
||||||
res.minute = int(60*(value%1))
|
|
||||||
elif idx == 1:
|
|
||||||
res.minute = int(value)
|
|
||||||
if value%1:
|
|
||||||
res.second = int(60*(value%1))
|
|
||||||
elif idx == 2:
|
|
||||||
res.second, res.microsecond = \
|
|
||||||
_parsems(value_repr)
|
|
||||||
i += 1
|
|
||||||
if i >= len_l or idx == 2:
|
|
||||||
break
|
|
||||||
# 12h00
|
|
||||||
try:
|
|
||||||
value_repr = l[i]
|
|
||||||
value = float(value_repr)
|
|
||||||
except ValueError:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
i += 1
|
|
||||||
idx += 1
|
|
||||||
if i < len_l:
|
|
||||||
newidx = info.hms(l[i])
|
|
||||||
if newidx is not None:
|
|
||||||
idx = newidx
|
|
||||||
elif i+1 < len_l and l[i] == ':':
|
|
||||||
# HH:MM[:SS[.ss]]
|
|
||||||
res.hour = int(value)
|
|
||||||
i += 1
|
|
||||||
value = float(l[i])
|
|
||||||
res.minute = int(value)
|
|
||||||
if value%1:
|
|
||||||
res.second = int(60*(value%1))
|
|
||||||
i += 1
|
|
||||||
if i < len_l and l[i] == ':':
|
|
||||||
res.second, res.microsecond = _parsems(l[i+1])
|
|
||||||
i += 2
|
|
||||||
elif i < len_l and l[i] in ('-', '/', '.'):
|
|
||||||
sep = l[i]
|
|
||||||
ymd.append(int(value))
|
|
||||||
i += 1
|
|
||||||
if i < len_l and not info.jump(l[i]):
|
|
||||||
try:
|
|
||||||
# 01-01[-01]
|
|
||||||
ymd.append(int(l[i]))
|
|
||||||
except ValueError:
|
|
||||||
# 01-Jan[-01]
|
|
||||||
value = info.month(l[i])
|
|
||||||
if value is not None:
|
|
||||||
ymd.append(value)
|
|
||||||
assert mstridx == -1
|
|
||||||
mstridx = len(ymd)-1
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
i += 1
|
|
||||||
if i < len_l and l[i] == sep:
|
|
||||||
# We have three members
|
|
||||||
i += 1
|
|
||||||
value = info.month(l[i])
|
|
||||||
if value is not None:
|
|
||||||
ymd.append(value)
|
|
||||||
mstridx = len(ymd)-1
|
|
||||||
assert mstridx == -1
|
|
||||||
else:
|
|
||||||
ymd.append(int(l[i]))
|
|
||||||
i += 1
|
|
||||||
elif i >= len_l or info.jump(l[i]):
|
|
||||||
if i+1 < len_l and info.ampm(l[i+1]) is not None:
|
|
||||||
# 12 am
|
|
||||||
res.hour = int(value)
|
|
||||||
if res.hour < 12 and info.ampm(l[i+1]) == 1:
|
|
||||||
res.hour += 12
|
|
||||||
elif res.hour == 12 and info.ampm(l[i+1]) == 0:
|
|
||||||
res.hour = 0
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
# Year, month or day
|
|
||||||
ymd.append(int(value))
|
|
||||||
i += 1
|
|
||||||
elif info.ampm(l[i]) is not None:
|
|
||||||
# 12am
|
|
||||||
res.hour = int(value)
|
|
||||||
if res.hour < 12 and info.ampm(l[i]) == 1:
|
|
||||||
res.hour += 12
|
|
||||||
elif res.hour == 12 and info.ampm(l[i]) == 0:
|
|
||||||
res.hour = 0
|
|
||||||
i += 1
|
|
||||||
elif not fuzzy:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check weekday
|
|
||||||
value = info.weekday(l[i])
|
|
||||||
if value is not None:
|
|
||||||
res.weekday = value
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check month name
|
|
||||||
value = info.month(l[i])
|
|
||||||
if value is not None:
|
|
||||||
ymd.append(value)
|
|
||||||
assert mstridx == -1
|
|
||||||
mstridx = len(ymd)-1
|
|
||||||
i += 1
|
|
||||||
if i < len_l:
|
|
||||||
if l[i] in ('-', '/'):
|
|
||||||
# Jan-01[-99]
|
|
||||||
sep = l[i]
|
|
||||||
i += 1
|
|
||||||
ymd.append(int(l[i]))
|
|
||||||
i += 1
|
|
||||||
if i < len_l and l[i] == sep:
|
|
||||||
# Jan-01-99
|
|
||||||
i += 1
|
|
||||||
ymd.append(int(l[i]))
|
|
||||||
i += 1
|
|
||||||
elif (i+3 < len_l and l[i] == l[i+2] == ' '
|
|
||||||
and info.pertain(l[i+1])):
|
|
||||||
# Jan of 01
|
|
||||||
# In this case, 01 is clearly year
|
|
||||||
try:
|
|
||||||
value = int(l[i+3])
|
|
||||||
except ValueError:
|
|
||||||
# Wrong guess
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Convert it here to become unambiguous
|
|
||||||
ymd.append(info.convertyear(value))
|
|
||||||
i += 4
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check am/pm
|
|
||||||
value = info.ampm(l[i])
|
|
||||||
if value is not None:
|
|
||||||
if value == 1 and res.hour < 12:
|
|
||||||
res.hour += 12
|
|
||||||
elif value == 0 and res.hour == 12:
|
|
||||||
res.hour = 0
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check for a timezone name
|
|
||||||
if (res.hour is not None and len(l[i]) <= 5 and
|
|
||||||
res.tzname is None and res.tzoffset is None and
|
|
||||||
not [x for x in l[i] if x not in string.ascii_uppercase]):
|
|
||||||
res.tzname = l[i]
|
|
||||||
res.tzoffset = info.tzoffset(res.tzname)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# Check for something like GMT+3, or BRST+3. Notice
|
|
||||||
# that it doesn't mean "I am 3 hours after GMT", but
|
|
||||||
# "my time +3 is GMT". If found, we reverse the
|
|
||||||
# logic so that timezone parsing code will get it
|
|
||||||
# right.
|
|
||||||
if i < len_l and l[i] in ('+', '-'):
|
|
||||||
l[i] = ('+', '-')[l[i] == '+']
|
|
||||||
res.tzoffset = None
|
|
||||||
if info.utczone(res.tzname):
|
|
||||||
# With something like GMT+3, the timezone
|
|
||||||
# is *not* GMT.
|
|
||||||
res.tzname = None
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check for a numbered timezone
|
|
||||||
if res.hour is not None and l[i] in ('+', '-'):
|
|
||||||
signal = (-1,1)[l[i] == '+']
|
|
||||||
i += 1
|
|
||||||
len_li = len(l[i])
|
|
||||||
if len_li == 4:
|
|
||||||
# -0300
|
|
||||||
res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60
|
|
||||||
elif i+1 < len_l and l[i+1] == ':':
|
|
||||||
# -03:00
|
|
||||||
res.tzoffset = int(l[i])*3600+int(l[i+2])*60
|
|
||||||
i += 2
|
|
||||||
elif len_li <= 2:
|
|
||||||
# -[0]3
|
|
||||||
res.tzoffset = int(l[i][:2])*3600
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
i += 1
|
|
||||||
res.tzoffset *= signal
|
|
||||||
|
|
||||||
# Look for a timezone name between parenthesis
|
|
||||||
if (i+3 < len_l and
|
|
||||||
info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and
|
|
||||||
3 <= len(l[i+2]) <= 5 and
|
|
||||||
not [x for x in l[i+2]
|
|
||||||
if x not in string.ascii_uppercase]):
|
|
||||||
# -0300 (BRST)
|
|
||||||
res.tzname = l[i+2]
|
|
||||||
i += 4
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check jumps
|
|
||||||
if not (info.jump(l[i]) or fuzzy):
|
|
||||||
return None
|
|
||||||
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# Process year/month/day
|
|
||||||
len_ymd = len(ymd)
|
|
||||||
if len_ymd > 3:
|
|
||||||
# More than three members!?
|
|
||||||
return None
|
|
||||||
elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2):
|
|
||||||
# One member, or two members with a month string
|
|
||||||
if mstridx != -1:
|
|
||||||
res.month = ymd[mstridx]
|
|
||||||
del ymd[mstridx]
|
|
||||||
if len_ymd > 1 or mstridx == -1:
|
|
||||||
if ymd[0] > 31:
|
|
||||||
res.year = ymd[0]
|
|
||||||
else:
|
|
||||||
res.day = ymd[0]
|
|
||||||
elif len_ymd == 2:
|
|
||||||
# Two members with numbers
|
|
||||||
if ymd[0] > 31:
|
|
||||||
# 99-01
|
|
||||||
res.year, res.month = ymd
|
|
||||||
elif ymd[1] > 31:
|
|
||||||
# 01-99
|
|
||||||
res.month, res.year = ymd
|
|
||||||
elif dayfirst and ymd[1] <= 12:
|
|
||||||
# 13-01
|
|
||||||
res.day, res.month = ymd
|
|
||||||
else:
|
|
||||||
# 01-13
|
|
||||||
res.month, res.day = ymd
|
|
||||||
if len_ymd == 3:
|
|
||||||
# Three members
|
|
||||||
if mstridx == 0:
|
|
||||||
res.month, res.day, res.year = ymd
|
|
||||||
elif mstridx == 1:
|
|
||||||
if ymd[0] > 31 or (yearfirst and ymd[2] <= 31):
|
|
||||||
# 99-Jan-01
|
|
||||||
res.year, res.month, res.day = ymd
|
|
||||||
else:
|
|
||||||
# 01-Jan-01
|
|
||||||
# Give precendence to day-first, since
|
|
||||||
# two-digit years is usually hand-written.
|
|
||||||
res.day, res.month, res.year = ymd
|
|
||||||
elif mstridx == 2:
|
|
||||||
# WTF!?
|
|
||||||
if ymd[1] > 31:
|
|
||||||
# 01-99-Jan
|
|
||||||
res.day, res.year, res.month = ymd
|
|
||||||
else:
|
|
||||||
# 99-01-Jan
|
|
||||||
res.year, res.day, res.month = ymd
|
|
||||||
else:
|
|
||||||
if ymd[0] > 31 or \
|
|
||||||
(yearfirst and ymd[1] <= 12 and ymd[2] <= 31):
|
|
||||||
# 99-01-01
|
|
||||||
res.year, res.month, res.day = ymd
|
|
||||||
elif ymd[0] > 12 or (dayfirst and ymd[1] <= 12):
|
|
||||||
# 13-01-01
|
|
||||||
res.day, res.month, res.year = ymd
|
|
||||||
else:
|
|
||||||
# 01-13-01
|
|
||||||
res.month, res.day, res.year = ymd
|
|
||||||
|
|
||||||
except (IndexError, ValueError, AssertionError):
|
|
||||||
return None
|
|
||||||
|
|
||||||
if not info.validate(res):
|
|
||||||
return None
|
|
||||||
return res
|
|
||||||
|
|
||||||
DEFAULTPARSER = parser()
|
|
||||||
def parse(timestr, parserinfo=None, **kwargs):
|
|
||||||
if parserinfo:
|
|
||||||
return parser(parserinfo).parse(timestr, **kwargs)
|
|
||||||
else:
|
|
||||||
return DEFAULTPARSER.parse(timestr, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class _tzparser(object):
|
|
||||||
|
|
||||||
class _result(_resultbase):
|
|
||||||
|
|
||||||
__slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset",
|
|
||||||
"start", "end"]
|
|
||||||
|
|
||||||
class _attr(_resultbase):
|
|
||||||
__slots__ = ["month", "week", "weekday",
|
|
||||||
"yday", "jyday", "day", "time"]
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self._repr("")
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
_resultbase.__init__(self)
|
|
||||||
self.start = self._attr()
|
|
||||||
self.end = self._attr()
|
|
||||||
|
|
||||||
def parse(self, tzstr):
|
|
||||||
res = self._result()
|
|
||||||
l = _timelex.split(tzstr)
|
|
||||||
try:
|
|
||||||
|
|
||||||
len_l = len(l)
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
while i < len_l:
|
|
||||||
# BRST+3[BRDT[+2]]
|
|
||||||
j = i
|
|
||||||
while j < len_l and not [x for x in l[j]
|
|
||||||
if x in "0123456789:,-+"]:
|
|
||||||
j += 1
|
|
||||||
if j != i:
|
|
||||||
if not res.stdabbr:
|
|
||||||
offattr = "stdoffset"
|
|
||||||
res.stdabbr = "".join(l[i:j])
|
|
||||||
else:
|
|
||||||
offattr = "dstoffset"
|
|
||||||
res.dstabbr = "".join(l[i:j])
|
|
||||||
i = j
|
|
||||||
if (i < len_l and
|
|
||||||
(l[i] in ('+', '-') or l[i][0] in "0123456789")):
|
|
||||||
if l[i] in ('+', '-'):
|
|
||||||
# Yes, that's right. See the TZ variable
|
|
||||||
# documentation.
|
|
||||||
signal = (1,-1)[l[i] == '+']
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
signal = -1
|
|
||||||
len_li = len(l[i])
|
|
||||||
if len_li == 4:
|
|
||||||
# -0300
|
|
||||||
setattr(res, offattr,
|
|
||||||
(int(l[i][:2])*3600+int(l[i][2:])*60)*signal)
|
|
||||||
elif i+1 < len_l and l[i+1] == ':':
|
|
||||||
# -03:00
|
|
||||||
setattr(res, offattr,
|
|
||||||
(int(l[i])*3600+int(l[i+2])*60)*signal)
|
|
||||||
i += 2
|
|
||||||
elif len_li <= 2:
|
|
||||||
# -[0]3
|
|
||||||
setattr(res, offattr,
|
|
||||||
int(l[i][:2])*3600*signal)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
i += 1
|
|
||||||
if res.dstabbr:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
if i < len_l:
|
|
||||||
for j in range(i, len_l):
|
|
||||||
if l[j] == ';': l[j] = ','
|
|
||||||
|
|
||||||
assert l[i] == ','
|
|
||||||
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
if i >= len_l:
|
|
||||||
pass
|
|
||||||
elif (8 <= l.count(',') <= 9 and
|
|
||||||
not [y for x in l[i:] if x != ','
|
|
||||||
for y in x if y not in "0123456789"]):
|
|
||||||
# GMT0BST,3,0,30,3600,10,0,26,7200[,3600]
|
|
||||||
for x in (res.start, res.end):
|
|
||||||
x.month = int(l[i])
|
|
||||||
i += 2
|
|
||||||
if l[i] == '-':
|
|
||||||
value = int(l[i+1])*-1
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
value = int(l[i])
|
|
||||||
i += 2
|
|
||||||
if value:
|
|
||||||
x.week = value
|
|
||||||
x.weekday = (int(l[i])-1)%7
|
|
||||||
else:
|
|
||||||
x.day = int(l[i])
|
|
||||||
i += 2
|
|
||||||
x.time = int(l[i])
|
|
||||||
i += 2
|
|
||||||
if i < len_l:
|
|
||||||
if l[i] in ('-','+'):
|
|
||||||
signal = (-1,1)[l[i] == "+"]
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
signal = 1
|
|
||||||
res.dstoffset = (res.stdoffset+int(l[i]))*signal
|
|
||||||
elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
|
|
||||||
not [y for x in l[i:] if x not in (',','/','J','M',
|
|
||||||
'.','-',':')
|
|
||||||
for y in x if y not in "0123456789"]):
|
|
||||||
for x in (res.start, res.end):
|
|
||||||
if l[i] == 'J':
|
|
||||||
# non-leap year day (1 based)
|
|
||||||
i += 1
|
|
||||||
x.jyday = int(l[i])
|
|
||||||
elif l[i] == 'M':
|
|
||||||
# month[-.]week[-.]weekday
|
|
||||||
i += 1
|
|
||||||
x.month = int(l[i])
|
|
||||||
i += 1
|
|
||||||
assert l[i] in ('-', '.')
|
|
||||||
i += 1
|
|
||||||
x.week = int(l[i])
|
|
||||||
if x.week == 5:
|
|
||||||
x.week = -1
|
|
||||||
i += 1
|
|
||||||
assert l[i] in ('-', '.')
|
|
||||||
i += 1
|
|
||||||
x.weekday = (int(l[i])-1)%7
|
|
||||||
else:
|
|
||||||
# year day (zero based)
|
|
||||||
x.yday = int(l[i])+1
|
|
||||||
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
if i < len_l and l[i] == '/':
|
|
||||||
i += 1
|
|
||||||
# start time
|
|
||||||
len_li = len(l[i])
|
|
||||||
if len_li == 4:
|
|
||||||
# -0300
|
|
||||||
x.time = (int(l[i][:2])*3600+int(l[i][2:])*60)
|
|
||||||
elif i+1 < len_l and l[i+1] == ':':
|
|
||||||
# -03:00
|
|
||||||
x.time = int(l[i])*3600+int(l[i+2])*60
|
|
||||||
i += 2
|
|
||||||
if i+1 < len_l and l[i+1] == ':':
|
|
||||||
i += 2
|
|
||||||
x.time += int(l[i])
|
|
||||||
elif len_li <= 2:
|
|
||||||
# -[0]3
|
|
||||||
x.time = (int(l[i][:2])*3600)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
assert i == len_l or l[i] == ','
|
|
||||||
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
assert i >= len_l
|
|
||||||
|
|
||||||
except (IndexError, ValueError, AssertionError):
|
|
||||||
return None
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
DEFAULTTZPARSER = _tzparser()
|
|
||||||
def _parsetz(tzstr):
|
|
||||||
return DEFAULTTZPARSER.parse(tzstr)
|
|
||||||
|
|
||||||
|
|
||||||
def _parsems(value):
|
|
||||||
"""Parse a I[.F] seconds value into (seconds, microseconds)."""
|
|
||||||
if "." not in value:
|
|
||||||
return int(value), 0
|
|
||||||
else:
|
|
||||||
i, f = value.split(".")
|
|
||||||
return int(i), int(f.ljust(6, "0")[:6])
|
|
||||||
|
|
||||||
|
|
||||||
# vim:ts=4:sw=4:et
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from ._parser import parse, parser, parserinfo, ParserError
|
||||||
|
from ._parser import DEFAULTPARSER, DEFAULTTZPARSER
|
||||||
|
from ._parser import UnknownTimezoneWarning
|
||||||
|
|
||||||
|
from ._parser import __doc__
|
||||||
|
|
||||||
|
from .isoparser import isoparser, isoparse
|
||||||
|
|
||||||
|
__all__ = ['parse', 'parser', 'parserinfo',
|
||||||
|
'isoparse', 'isoparser',
|
||||||
|
'ParserError',
|
||||||
|
'UnknownTimezoneWarning']
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Deprecate portions of the private interface so that downstream code that
|
||||||
|
# is improperly relying on it is given *some* notice.
|
||||||
|
|
||||||
|
|
||||||
|
def __deprecated_private_func(f):
|
||||||
|
from functools import wraps
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
msg = ('{name} is a private function and may break without warning, '
|
||||||
|
'it will be moved and or renamed in future versions.')
|
||||||
|
msg = msg.format(name=f.__name__)
|
||||||
|
|
||||||
|
@wraps(f)
|
||||||
|
def deprecated_func(*args, **kwargs):
|
||||||
|
warnings.warn(msg, DeprecationWarning)
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
return deprecated_func
|
||||||
|
|
||||||
|
def __deprecate_private_class(c):
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
msg = ('{name} is a private class and may break without warning, '
|
||||||
|
'it will be moved and or renamed in future versions.')
|
||||||
|
msg = msg.format(name=c.__name__)
|
||||||
|
|
||||||
|
class private_class(c):
|
||||||
|
__doc__ = c.__doc__
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
warnings.warn(msg, DeprecationWarning)
|
||||||
|
super(private_class, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
private_class.__name__ = c.__name__
|
||||||
|
|
||||||
|
return private_class
|
||||||
|
|
||||||
|
|
||||||
|
from ._parser import _timelex, _resultbase
|
||||||
|
from ._parser import _tzparser, _parsetz
|
||||||
|
|
||||||
|
_timelex = __deprecate_private_class(_timelex)
|
||||||
|
_tzparser = __deprecate_private_class(_tzparser)
|
||||||
|
_resultbase = __deprecate_private_class(_resultbase)
|
||||||
|
_parsetz = __deprecated_private_func(_parsetz)
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,411 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
This module offers a parser for ISO-8601 strings
|
||||||
|
|
||||||
|
It is intended to support all valid date, time and datetime formats per the
|
||||||
|
ISO-8601 specification.
|
||||||
|
|
||||||
|
..versionadded:: 2.7.0
|
||||||
|
"""
|
||||||
|
from datetime import datetime, timedelta, time, date
|
||||||
|
import calendar
|
||||||
|
from dateutil import tz
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
import re
|
||||||
|
import six
|
||||||
|
|
||||||
|
__all__ = ["isoparse", "isoparser"]
|
||||||
|
|
||||||
|
|
||||||
|
def _takes_ascii(f):
|
||||||
|
@wraps(f)
|
||||||
|
def func(self, str_in, *args, **kwargs):
|
||||||
|
# If it's a stream, read the whole thing
|
||||||
|
str_in = getattr(str_in, 'read', lambda: str_in)()
|
||||||
|
|
||||||
|
# If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII
|
||||||
|
if isinstance(str_in, six.text_type):
|
||||||
|
# ASCII is the same in UTF-8
|
||||||
|
try:
|
||||||
|
str_in = str_in.encode('ascii')
|
||||||
|
except UnicodeEncodeError as e:
|
||||||
|
msg = 'ISO-8601 strings should contain only ASCII characters'
|
||||||
|
six.raise_from(ValueError(msg), e)
|
||||||
|
|
||||||
|
return f(self, str_in, *args, **kwargs)
|
||||||
|
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
class isoparser(object):
|
||||||
|
def __init__(self, sep=None):
|
||||||
|
"""
|
||||||
|
:param sep:
|
||||||
|
A single character that separates date and time portions. If
|
||||||
|
``None``, the parser will accept any single character.
|
||||||
|
For strict ISO-8601 adherence, pass ``'T'``.
|
||||||
|
"""
|
||||||
|
if sep is not None:
|
||||||
|
if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'):
|
||||||
|
raise ValueError('Separator must be a single, non-numeric ' +
|
||||||
|
'ASCII character')
|
||||||
|
|
||||||
|
sep = sep.encode('ascii')
|
||||||
|
|
||||||
|
self._sep = sep
|
||||||
|
|
||||||
|
@_takes_ascii
|
||||||
|
def isoparse(self, dt_str):
|
||||||
|
"""
|
||||||
|
Parse an ISO-8601 datetime string into a :class:`datetime.datetime`.
|
||||||
|
|
||||||
|
An ISO-8601 datetime string consists of a date portion, followed
|
||||||
|
optionally by a time portion - the date and time portions are separated
|
||||||
|
by a single character separator, which is ``T`` in the official
|
||||||
|
standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be
|
||||||
|
combined with a time portion.
|
||||||
|
|
||||||
|
Supported date formats are:
|
||||||
|
|
||||||
|
Common:
|
||||||
|
|
||||||
|
- ``YYYY``
|
||||||
|
- ``YYYY-MM`` or ``YYYYMM``
|
||||||
|
- ``YYYY-MM-DD`` or ``YYYYMMDD``
|
||||||
|
|
||||||
|
Uncommon:
|
||||||
|
|
||||||
|
- ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0)
|
||||||
|
- ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day
|
||||||
|
|
||||||
|
The ISO week and day numbering follows the same logic as
|
||||||
|
:func:`datetime.date.isocalendar`.
|
||||||
|
|
||||||
|
Supported time formats are:
|
||||||
|
|
||||||
|
- ``hh``
|
||||||
|
- ``hh:mm`` or ``hhmm``
|
||||||
|
- ``hh:mm:ss`` or ``hhmmss``
|
||||||
|
- ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits)
|
||||||
|
|
||||||
|
Midnight is a special case for `hh`, as the standard supports both
|
||||||
|
00:00 and 24:00 as a representation. The decimal separator can be
|
||||||
|
either a dot or a comma.
|
||||||
|
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
Support for fractional components other than seconds is part of the
|
||||||
|
ISO-8601 standard, but is not currently implemented in this parser.
|
||||||
|
|
||||||
|
Supported time zone offset formats are:
|
||||||
|
|
||||||
|
- `Z` (UTC)
|
||||||
|
- `±HH:MM`
|
||||||
|
- `±HHMM`
|
||||||
|
- `±HH`
|
||||||
|
|
||||||
|
Offsets will be represented as :class:`dateutil.tz.tzoffset` objects,
|
||||||
|
with the exception of UTC, which will be represented as
|
||||||
|
:class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such
|
||||||
|
as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`.
|
||||||
|
|
||||||
|
:param dt_str:
|
||||||
|
A string or stream containing only an ISO-8601 datetime string
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns a :class:`datetime.datetime` representing the string.
|
||||||
|
Unspecified components default to their lowest value.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
As of version 2.7.0, the strictness of the parser should not be
|
||||||
|
considered a stable part of the contract. Any valid ISO-8601 string
|
||||||
|
that parses correctly with the default settings will continue to
|
||||||
|
parse correctly in future versions, but invalid strings that
|
||||||
|
currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not
|
||||||
|
guaranteed to continue failing in future versions if they encode
|
||||||
|
a valid date.
|
||||||
|
|
||||||
|
.. versionadded:: 2.7.0
|
||||||
|
"""
|
||||||
|
components, pos = self._parse_isodate(dt_str)
|
||||||
|
|
||||||
|
if len(dt_str) > pos:
|
||||||
|
if self._sep is None or dt_str[pos:pos + 1] == self._sep:
|
||||||
|
components += self._parse_isotime(dt_str[pos + 1:])
|
||||||
|
else:
|
||||||
|
raise ValueError('String contains unknown ISO components')
|
||||||
|
|
||||||
|
if len(components) > 3 and components[3] == 24:
|
||||||
|
components[3] = 0
|
||||||
|
return datetime(*components) + timedelta(days=1)
|
||||||
|
|
||||||
|
return datetime(*components)
|
||||||
|
|
||||||
|
@_takes_ascii
|
||||||
|
def parse_isodate(self, datestr):
|
||||||
|
"""
|
||||||
|
Parse the date portion of an ISO string.
|
||||||
|
|
||||||
|
:param datestr:
|
||||||
|
The string portion of an ISO string, without a separator
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns a :class:`datetime.date` object
|
||||||
|
"""
|
||||||
|
components, pos = self._parse_isodate(datestr)
|
||||||
|
if pos < len(datestr):
|
||||||
|
raise ValueError('String contains unknown ISO ' +
|
||||||
|
'components: {}'.format(datestr))
|
||||||
|
return date(*components)
|
||||||
|
|
||||||
|
@_takes_ascii
|
||||||
|
def parse_isotime(self, timestr):
|
||||||
|
"""
|
||||||
|
Parse the time portion of an ISO string.
|
||||||
|
|
||||||
|
:param timestr:
|
||||||
|
The time portion of an ISO string, without a separator
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns a :class:`datetime.time` object
|
||||||
|
"""
|
||||||
|
components = self._parse_isotime(timestr)
|
||||||
|
if components[0] == 24:
|
||||||
|
components[0] = 0
|
||||||
|
return time(*components)
|
||||||
|
|
||||||
|
@_takes_ascii
|
||||||
|
def parse_tzstr(self, tzstr, zero_as_utc=True):
|
||||||
|
"""
|
||||||
|
Parse a valid ISO time zone string.
|
||||||
|
|
||||||
|
See :func:`isoparser.isoparse` for details on supported formats.
|
||||||
|
|
||||||
|
:param tzstr:
|
||||||
|
A string representing an ISO time zone offset
|
||||||
|
|
||||||
|
:param zero_as_utc:
|
||||||
|
Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns :class:`dateutil.tz.tzoffset` for offsets and
|
||||||
|
:class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is
|
||||||
|
specified) offsets equivalent to UTC.
|
||||||
|
"""
|
||||||
|
return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc)
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
_DATE_SEP = b'-'
|
||||||
|
_TIME_SEP = b':'
|
||||||
|
_FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)')
|
||||||
|
|
||||||
|
def _parse_isodate(self, dt_str):
|
||||||
|
try:
|
||||||
|
return self._parse_isodate_common(dt_str)
|
||||||
|
except ValueError:
|
||||||
|
return self._parse_isodate_uncommon(dt_str)
|
||||||
|
|
||||||
|
def _parse_isodate_common(self, dt_str):
|
||||||
|
len_str = len(dt_str)
|
||||||
|
components = [1, 1, 1]
|
||||||
|
|
||||||
|
if len_str < 4:
|
||||||
|
raise ValueError('ISO string too short')
|
||||||
|
|
||||||
|
# Year
|
||||||
|
components[0] = int(dt_str[0:4])
|
||||||
|
pos = 4
|
||||||
|
if pos >= len_str:
|
||||||
|
return components, pos
|
||||||
|
|
||||||
|
has_sep = dt_str[pos:pos + 1] == self._DATE_SEP
|
||||||
|
if has_sep:
|
||||||
|
pos += 1
|
||||||
|
|
||||||
|
# Month
|
||||||
|
if len_str - pos < 2:
|
||||||
|
raise ValueError('Invalid common month')
|
||||||
|
|
||||||
|
components[1] = int(dt_str[pos:pos + 2])
|
||||||
|
pos += 2
|
||||||
|
|
||||||
|
if pos >= len_str:
|
||||||
|
if has_sep:
|
||||||
|
return components, pos
|
||||||
|
else:
|
||||||
|
raise ValueError('Invalid ISO format')
|
||||||
|
|
||||||
|
if has_sep:
|
||||||
|
if dt_str[pos:pos + 1] != self._DATE_SEP:
|
||||||
|
raise ValueError('Invalid separator in ISO string')
|
||||||
|
pos += 1
|
||||||
|
|
||||||
|
# Day
|
||||||
|
if len_str - pos < 2:
|
||||||
|
raise ValueError('Invalid common day')
|
||||||
|
components[2] = int(dt_str[pos:pos + 2])
|
||||||
|
return components, pos + 2
|
||||||
|
|
||||||
|
def _parse_isodate_uncommon(self, dt_str):
|
||||||
|
if len(dt_str) < 4:
|
||||||
|
raise ValueError('ISO string too short')
|
||||||
|
|
||||||
|
# All ISO formats start with the year
|
||||||
|
year = int(dt_str[0:4])
|
||||||
|
|
||||||
|
has_sep = dt_str[4:5] == self._DATE_SEP
|
||||||
|
|
||||||
|
pos = 4 + has_sep # Skip '-' if it's there
|
||||||
|
if dt_str[pos:pos + 1] == b'W':
|
||||||
|
# YYYY-?Www-?D?
|
||||||
|
pos += 1
|
||||||
|
weekno = int(dt_str[pos:pos + 2])
|
||||||
|
pos += 2
|
||||||
|
|
||||||
|
dayno = 1
|
||||||
|
if len(dt_str) > pos:
|
||||||
|
if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep:
|
||||||
|
raise ValueError('Inconsistent use of dash separator')
|
||||||
|
|
||||||
|
pos += has_sep
|
||||||
|
|
||||||
|
dayno = int(dt_str[pos:pos + 1])
|
||||||
|
pos += 1
|
||||||
|
|
||||||
|
base_date = self._calculate_weekdate(year, weekno, dayno)
|
||||||
|
else:
|
||||||
|
# YYYYDDD or YYYY-DDD
|
||||||
|
if len(dt_str) - pos < 3:
|
||||||
|
raise ValueError('Invalid ordinal day')
|
||||||
|
|
||||||
|
ordinal_day = int(dt_str[pos:pos + 3])
|
||||||
|
pos += 3
|
||||||
|
|
||||||
|
if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)):
|
||||||
|
raise ValueError('Invalid ordinal day' +
|
||||||
|
' {} for year {}'.format(ordinal_day, year))
|
||||||
|
|
||||||
|
base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1)
|
||||||
|
|
||||||
|
components = [base_date.year, base_date.month, base_date.day]
|
||||||
|
return components, pos
|
||||||
|
|
||||||
|
def _calculate_weekdate(self, year, week, day):
|
||||||
|
"""
|
||||||
|
Calculate the day of corresponding to the ISO year-week-day calendar.
|
||||||
|
|
||||||
|
This function is effectively the inverse of
|
||||||
|
:func:`datetime.date.isocalendar`.
|
||||||
|
|
||||||
|
:param year:
|
||||||
|
The year in the ISO calendar
|
||||||
|
|
||||||
|
:param week:
|
||||||
|
The week in the ISO calendar - range is [1, 53]
|
||||||
|
|
||||||
|
:param day:
|
||||||
|
The day in the ISO calendar - range is [1 (MON), 7 (SUN)]
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns a :class:`datetime.date`
|
||||||
|
"""
|
||||||
|
if not 0 < week < 54:
|
||||||
|
raise ValueError('Invalid week: {}'.format(week))
|
||||||
|
|
||||||
|
if not 0 < day < 8: # Range is 1-7
|
||||||
|
raise ValueError('Invalid weekday: {}'.format(day))
|
||||||
|
|
||||||
|
# Get week 1 for the specific year:
|
||||||
|
jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it
|
||||||
|
week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1)
|
||||||
|
|
||||||
|
# Now add the specific number of weeks and days to get what we want
|
||||||
|
week_offset = (week - 1) * 7 + (day - 1)
|
||||||
|
return week_1 + timedelta(days=week_offset)
|
||||||
|
|
||||||
|
def _parse_isotime(self, timestr):
|
||||||
|
len_str = len(timestr)
|
||||||
|
components = [0, 0, 0, 0, None]
|
||||||
|
pos = 0
|
||||||
|
comp = -1
|
||||||
|
|
||||||
|
if len(timestr) < 2:
|
||||||
|
raise ValueError('ISO time too short')
|
||||||
|
|
||||||
|
has_sep = len_str >= 3 and timestr[2:3] == self._TIME_SEP
|
||||||
|
|
||||||
|
while pos < len_str and comp < 5:
|
||||||
|
comp += 1
|
||||||
|
|
||||||
|
if timestr[pos:pos + 1] in b'-+Zz':
|
||||||
|
# Detect time zone boundary
|
||||||
|
components[-1] = self._parse_tzstr(timestr[pos:])
|
||||||
|
pos = len_str
|
||||||
|
break
|
||||||
|
|
||||||
|
if comp < 3:
|
||||||
|
# Hour, minute, second
|
||||||
|
components[comp] = int(timestr[pos:pos + 2])
|
||||||
|
pos += 2
|
||||||
|
if (has_sep and pos < len_str and
|
||||||
|
timestr[pos:pos + 1] == self._TIME_SEP):
|
||||||
|
pos += 1
|
||||||
|
|
||||||
|
if comp == 3:
|
||||||
|
# Fraction of a second
|
||||||
|
frac = self._FRACTION_REGEX.match(timestr[pos:])
|
||||||
|
if not frac:
|
||||||
|
continue
|
||||||
|
|
||||||
|
us_str = frac.group(1)[:6] # Truncate to microseconds
|
||||||
|
components[comp] = int(us_str) * 10**(6 - len(us_str))
|
||||||
|
pos += len(frac.group())
|
||||||
|
|
||||||
|
if pos < len_str:
|
||||||
|
raise ValueError('Unused components in ISO string')
|
||||||
|
|
||||||
|
if components[0] == 24:
|
||||||
|
# Standard supports 00:00 and 24:00 as representations of midnight
|
||||||
|
if any(component != 0 for component in components[1:4]):
|
||||||
|
raise ValueError('Hour may only be 24 at 24:00:00.000')
|
||||||
|
|
||||||
|
return components
|
||||||
|
|
||||||
|
def _parse_tzstr(self, tzstr, zero_as_utc=True):
|
||||||
|
if tzstr == b'Z' or tzstr == b'z':
|
||||||
|
return tz.UTC
|
||||||
|
|
||||||
|
if len(tzstr) not in {3, 5, 6}:
|
||||||
|
raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters')
|
||||||
|
|
||||||
|
if tzstr[0:1] == b'-':
|
||||||
|
mult = -1
|
||||||
|
elif tzstr[0:1] == b'+':
|
||||||
|
mult = 1
|
||||||
|
else:
|
||||||
|
raise ValueError('Time zone offset requires sign')
|
||||||
|
|
||||||
|
hours = int(tzstr[1:3])
|
||||||
|
if len(tzstr) == 3:
|
||||||
|
minutes = 0
|
||||||
|
else:
|
||||||
|
minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):])
|
||||||
|
|
||||||
|
if zero_as_utc and hours == 0 and minutes == 0:
|
||||||
|
return tz.UTC
|
||||||
|
else:
|
||||||
|
if minutes > 59:
|
||||||
|
raise ValueError('Invalid minutes in time zone offset')
|
||||||
|
|
||||||
|
if hours > 23:
|
||||||
|
raise ValueError('Invalid hours in time zone offset')
|
||||||
|
|
||||||
|
return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60)
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_ISOPARSER = isoparser()
|
||||||
|
isoparse = DEFAULT_ISOPARSER.isoparse
|
||||||
+363
-196
@@ -1,109 +1,105 @@
|
|||||||
"""
|
# -*- coding: utf-8 -*-
|
||||||
Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
|
|
||||||
|
|
||||||
This module offers extensions to the standard python 2.3+
|
|
||||||
datetime module.
|
|
||||||
"""
|
|
||||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
|
||||||
__license__ = "PSF License"
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import calendar
|
import calendar
|
||||||
|
|
||||||
|
import operator
|
||||||
|
from math import copysign
|
||||||
|
|
||||||
|
from six import integer_types
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
|
from ._common import weekday
|
||||||
|
|
||||||
|
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
|
||||||
|
|
||||||
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
||||||
|
|
||||||
class weekday(object):
|
|
||||||
__slots__ = ["weekday", "n"]
|
|
||||||
|
|
||||||
def __init__(self, weekday, n=None):
|
class relativedelta(object):
|
||||||
self.weekday = weekday
|
|
||||||
self.n = n
|
|
||||||
|
|
||||||
def __call__(self, n):
|
|
||||||
if n == self.n:
|
|
||||||
return self
|
|
||||||
else:
|
|
||||||
return self.__class__(self.weekday, n)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
try:
|
|
||||||
if self.weekday != other.weekday or self.n != other.n:
|
|
||||||
return False
|
|
||||||
except AttributeError:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
|
|
||||||
if not self.n:
|
|
||||||
return s
|
|
||||||
else:
|
|
||||||
return "%s(%+d)" % (s, self.n)
|
|
||||||
|
|
||||||
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
|
|
||||||
|
|
||||||
class relativedelta:
|
|
||||||
"""
|
"""
|
||||||
The relativedelta type is based on the specification of the excelent
|
The relativedelta type is designed to be applied to an existing datetime and
|
||||||
work done by M.-A. Lemburg in his mx.DateTime extension. However,
|
can replace specific components of that datetime, or represents an interval
|
||||||
notice that this type does *NOT* implement the same algorithm as
|
of time.
|
||||||
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
|
|
||||||
|
|
||||||
There's two different ways to build a relativedelta instance. The
|
It is based on the specification of the excellent work done by M.-A. Lemburg
|
||||||
first one is passing it two date/datetime classes:
|
in his
|
||||||
|
`mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension.
|
||||||
|
However, notice that this type does *NOT* implement the same algorithm as
|
||||||
|
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
|
||||||
|
|
||||||
relativedelta(datetime1, datetime2)
|
There are two different ways to build a relativedelta instance. The
|
||||||
|
first one is passing it two date/datetime classes::
|
||||||
|
|
||||||
And the other way is to use the following keyword arguments:
|
relativedelta(datetime1, datetime2)
|
||||||
|
|
||||||
year, month, day, hour, minute, second, microsecond:
|
The second one is passing it any number of the following keyword arguments::
|
||||||
Absolute information.
|
|
||||||
|
|
||||||
years, months, weeks, days, hours, minutes, seconds, microseconds:
|
relativedelta(arg1=x,arg2=y,arg3=z...)
|
||||||
Relative information, may be negative.
|
|
||||||
|
|
||||||
weekday:
|
year, month, day, hour, minute, second, microsecond:
|
||||||
One of the weekday instances (MO, TU, etc). These instances may
|
Absolute information (argument is singular); adding or subtracting a
|
||||||
receive a parameter N, specifying the Nth weekday, which could
|
relativedelta with absolute information does not perform an arithmetic
|
||||||
be positive or negative (like MO(+1) or MO(-2). Not specifying
|
operation, but rather REPLACES the corresponding value in the
|
||||||
it is the same as specifying +1. You can also use an integer,
|
original datetime with the value(s) in relativedelta.
|
||||||
where 0=MO.
|
|
||||||
|
|
||||||
leapdays:
|
years, months, weeks, days, hours, minutes, seconds, microseconds:
|
||||||
Will add given days to the date found, if year is a leap
|
Relative information, may be negative (argument is plural); adding
|
||||||
year, and the date found is post 28 of february.
|
or subtracting a relativedelta with relative information performs
|
||||||
|
the corresponding arithmetic operation on the original datetime value
|
||||||
|
with the information in the relativedelta.
|
||||||
|
|
||||||
yearday, nlyearday:
|
weekday:
|
||||||
Set the yearday or the non-leap year day (jump leap days).
|
One of the weekday instances (MO, TU, etc) available in the
|
||||||
These are converted to day/month/leapdays information.
|
relativedelta module. These instances may receive a parameter N,
|
||||||
|
specifying the Nth weekday, which could be positive or negative
|
||||||
|
(like MO(+1) or MO(-2)). Not specifying it is the same as specifying
|
||||||
|
+1. You can also use an integer, where 0=MO. This argument is always
|
||||||
|
relative e.g. if the calculated date is already Monday, using MO(1)
|
||||||
|
or MO(-1) won't change the day. To effectively make it absolute, use
|
||||||
|
it in combination with the day argument (e.g. day=1, MO(1) for first
|
||||||
|
Monday of the month).
|
||||||
|
|
||||||
Here is the behavior of operations with relativedelta:
|
leapdays:
|
||||||
|
Will add given days to the date found, if year is a leap
|
||||||
|
year, and the date found is post 28 of february.
|
||||||
|
|
||||||
1) Calculate the absolute year, using the 'year' argument, or the
|
yearday, nlyearday:
|
||||||
original datetime year, if the argument is not present.
|
Set the yearday or the non-leap year day (jump leap days).
|
||||||
|
These are converted to day/month/leapdays information.
|
||||||
|
|
||||||
2) Add the relative 'years' argument to the absolute year.
|
There are relative and absolute forms of the keyword
|
||||||
|
arguments. The plural is relative, and the singular is
|
||||||
|
absolute. For each argument in the order below, the absolute form
|
||||||
|
is applied first (by setting each attribute to that value) and
|
||||||
|
then the relative form (by adding the value to the attribute).
|
||||||
|
|
||||||
3) Do steps 1 and 2 for month/months.
|
The order of attributes considered when this relativedelta is
|
||||||
|
added to a datetime is:
|
||||||
|
|
||||||
4) Calculate the absolute day, using the 'day' argument, or the
|
1. Year
|
||||||
original datetime day, if the argument is not present. Then,
|
2. Month
|
||||||
subtract from the day until it fits in the year and month
|
3. Day
|
||||||
found after their operations.
|
4. Hours
|
||||||
|
5. Minutes
|
||||||
|
6. Seconds
|
||||||
|
7. Microseconds
|
||||||
|
|
||||||
5) Add the relative 'days' argument to the absolute day. Notice
|
Finally, weekday is applied, using the rule described above.
|
||||||
that the 'weeks' argument is multiplied by 7 and added to
|
|
||||||
'days'.
|
|
||||||
|
|
||||||
6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
|
For example
|
||||||
microsecond/microseconds.
|
|
||||||
|
>>> from datetime import datetime
|
||||||
|
>>> from dateutil.relativedelta import relativedelta, MO
|
||||||
|
>>> dt = datetime(2018, 4, 9, 13, 37, 0)
|
||||||
|
>>> delta = relativedelta(hours=25, day=1, weekday=MO(1))
|
||||||
|
>>> dt + delta
|
||||||
|
datetime.datetime(2018, 4, 2, 14, 37)
|
||||||
|
|
||||||
|
First, the day is set to 1 (the first of the month), then 25 hours
|
||||||
|
are added, to get to the 2nd day and 14th hour, finally the
|
||||||
|
weekday is applied, but since the 2nd is already a Monday there is
|
||||||
|
no effect.
|
||||||
|
|
||||||
7) If the 'weekday' argument is present, calculate the weekday,
|
|
||||||
with the given (wday, nth) tuple. wday is the index of the
|
|
||||||
weekday (0-6, 0=Mon), and nth is the number of weeks to add
|
|
||||||
forward or backward, depending on its signal. Notice that if
|
|
||||||
the calculated date is already Monday, for example, using
|
|
||||||
(0, 1) or (0, -1) won't change the day.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, dt1=None, dt2=None,
|
def __init__(self, dt1=None, dt2=None,
|
||||||
@@ -112,15 +108,22 @@ Here is the behavior of operations with relativedelta:
|
|||||||
year=None, month=None, day=None, weekday=None,
|
year=None, month=None, day=None, weekday=None,
|
||||||
yearday=None, nlyearday=None,
|
yearday=None, nlyearday=None,
|
||||||
hour=None, minute=None, second=None, microsecond=None):
|
hour=None, minute=None, second=None, microsecond=None):
|
||||||
|
|
||||||
if dt1 and dt2:
|
if dt1 and dt2:
|
||||||
if not isinstance(dt1, datetime.date) or \
|
# datetime is a subclass of date. So both must be date
|
||||||
not isinstance(dt2, datetime.date):
|
if not (isinstance(dt1, datetime.date) and
|
||||||
raise TypeError, "relativedelta only diffs datetime/date"
|
isinstance(dt2, datetime.date)):
|
||||||
if type(dt1) is not type(dt2):
|
raise TypeError("relativedelta only diffs datetime/date")
|
||||||
|
|
||||||
|
# We allow two dates, or two datetimes, so we coerce them to be
|
||||||
|
# of the same type
|
||||||
|
if (isinstance(dt1, datetime.datetime) !=
|
||||||
|
isinstance(dt2, datetime.datetime)):
|
||||||
if not isinstance(dt1, datetime.datetime):
|
if not isinstance(dt1, datetime.datetime):
|
||||||
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
||||||
elif not isinstance(dt2, datetime.datetime):
|
elif not isinstance(dt2, datetime.datetime):
|
||||||
dt2 = datetime.datetime.fromordinal(dt2.toordinal())
|
dt2 = datetime.datetime.fromordinal(dt2.toordinal())
|
||||||
|
|
||||||
self.years = 0
|
self.years = 0
|
||||||
self.months = 0
|
self.months = 0
|
||||||
self.days = 0
|
self.days = 0
|
||||||
@@ -139,31 +142,48 @@ Here is the behavior of operations with relativedelta:
|
|||||||
self.microsecond = None
|
self.microsecond = None
|
||||||
self._has_time = 0
|
self._has_time = 0
|
||||||
|
|
||||||
months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month)
|
# Get year / month delta between the two
|
||||||
|
months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month)
|
||||||
self._set_months(months)
|
self._set_months(months)
|
||||||
|
|
||||||
|
# Remove the year/month delta so the timedelta is just well-defined
|
||||||
|
# time units (seconds, days and microseconds)
|
||||||
dtm = self.__radd__(dt2)
|
dtm = self.__radd__(dt2)
|
||||||
|
|
||||||
|
# If we've overshot our target, make an adjustment
|
||||||
if dt1 < dt2:
|
if dt1 < dt2:
|
||||||
while dt1 > dtm:
|
compare = operator.gt
|
||||||
months += 1
|
increment = 1
|
||||||
self._set_months(months)
|
|
||||||
dtm = self.__radd__(dt2)
|
|
||||||
else:
|
else:
|
||||||
while dt1 < dtm:
|
compare = operator.lt
|
||||||
months -= 1
|
increment = -1
|
||||||
self._set_months(months)
|
|
||||||
dtm = self.__radd__(dt2)
|
while compare(dt1, dtm):
|
||||||
|
months += increment
|
||||||
|
self._set_months(months)
|
||||||
|
dtm = self.__radd__(dt2)
|
||||||
|
|
||||||
|
# Get the timedelta between the "months-adjusted" date and dt1
|
||||||
delta = dt1 - dtm
|
delta = dt1 - dtm
|
||||||
self.seconds = delta.seconds+delta.days*86400
|
self.seconds = delta.seconds + delta.days * 86400
|
||||||
self.microseconds = delta.microseconds
|
self.microseconds = delta.microseconds
|
||||||
else:
|
else:
|
||||||
self.years = years
|
# Check for non-integer values in integer-only quantities
|
||||||
self.months = months
|
if any(x is not None and x != int(x) for x in (years, months)):
|
||||||
self.days = days+weeks*7
|
raise ValueError("Non-integer years and months are "
|
||||||
|
"ambiguous and not currently supported.")
|
||||||
|
|
||||||
|
# Relative information
|
||||||
|
self.years = int(years)
|
||||||
|
self.months = int(months)
|
||||||
|
self.days = days + weeks * 7
|
||||||
self.leapdays = leapdays
|
self.leapdays = leapdays
|
||||||
self.hours = hours
|
self.hours = hours
|
||||||
self.minutes = minutes
|
self.minutes = minutes
|
||||||
self.seconds = seconds
|
self.seconds = seconds
|
||||||
self.microseconds = microseconds
|
self.microseconds = microseconds
|
||||||
|
|
||||||
|
# Absolute information
|
||||||
self.year = year
|
self.year = year
|
||||||
self.month = month
|
self.month = month
|
||||||
self.day = day
|
self.day = day
|
||||||
@@ -172,7 +192,15 @@ Here is the behavior of operations with relativedelta:
|
|||||||
self.second = second
|
self.second = second
|
||||||
self.microsecond = microsecond
|
self.microsecond = microsecond
|
||||||
|
|
||||||
if type(weekday) is int:
|
if any(x is not None and int(x) != x
|
||||||
|
for x in (year, month, day, hour,
|
||||||
|
minute, second, microsecond)):
|
||||||
|
# For now we'll deprecate floats - later it'll be an error.
|
||||||
|
warn("Non-integer value passed as absolute information. " +
|
||||||
|
"This is not a well-defined condition and will raise " +
|
||||||
|
"errors in future versions.", DeprecationWarning)
|
||||||
|
|
||||||
|
if isinstance(weekday, integer_types):
|
||||||
self.weekday = weekdays[weekday]
|
self.weekday = weekdays[weekday]
|
||||||
else:
|
else:
|
||||||
self.weekday = weekday
|
self.weekday = weekday
|
||||||
@@ -185,7 +213,8 @@ Here is the behavior of operations with relativedelta:
|
|||||||
if yearday > 59:
|
if yearday > 59:
|
||||||
self.leapdays = -1
|
self.leapdays = -1
|
||||||
if yday:
|
if yday:
|
||||||
ydayidx = [31,59,90,120,151,181,212,243,273,304,334,366]
|
ydayidx = [31, 59, 90, 120, 151, 181, 212,
|
||||||
|
243, 273, 304, 334, 366]
|
||||||
for idx, ydays in enumerate(ydayidx):
|
for idx, ydays in enumerate(ydayidx):
|
||||||
if yday <= ydays:
|
if yday <= ydays:
|
||||||
self.month = idx+1
|
self.month = idx+1
|
||||||
@@ -195,56 +224,143 @@ Here is the behavior of operations with relativedelta:
|
|||||||
self.day = yday-ydayidx[idx-1]
|
self.day = yday-ydayidx[idx-1]
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise ValueError, "invalid year day (%d)" % yday
|
raise ValueError("invalid year day (%d)" % yday)
|
||||||
|
|
||||||
self._fix()
|
self._fix()
|
||||||
|
|
||||||
def _fix(self):
|
def _fix(self):
|
||||||
if abs(self.microseconds) > 999999:
|
if abs(self.microseconds) > 999999:
|
||||||
s = self.microseconds//abs(self.microseconds)
|
s = _sign(self.microseconds)
|
||||||
div, mod = divmod(self.microseconds*s, 1000000)
|
div, mod = divmod(self.microseconds * s, 1000000)
|
||||||
self.microseconds = mod*s
|
self.microseconds = mod * s
|
||||||
self.seconds += div*s
|
self.seconds += div * s
|
||||||
if abs(self.seconds) > 59:
|
if abs(self.seconds) > 59:
|
||||||
s = self.seconds//abs(self.seconds)
|
s = _sign(self.seconds)
|
||||||
div, mod = divmod(self.seconds*s, 60)
|
div, mod = divmod(self.seconds * s, 60)
|
||||||
self.seconds = mod*s
|
self.seconds = mod * s
|
||||||
self.minutes += div*s
|
self.minutes += div * s
|
||||||
if abs(self.minutes) > 59:
|
if abs(self.minutes) > 59:
|
||||||
s = self.minutes//abs(self.minutes)
|
s = _sign(self.minutes)
|
||||||
div, mod = divmod(self.minutes*s, 60)
|
div, mod = divmod(self.minutes * s, 60)
|
||||||
self.minutes = mod*s
|
self.minutes = mod * s
|
||||||
self.hours += div*s
|
self.hours += div * s
|
||||||
if abs(self.hours) > 23:
|
if abs(self.hours) > 23:
|
||||||
s = self.hours//abs(self.hours)
|
s = _sign(self.hours)
|
||||||
div, mod = divmod(self.hours*s, 24)
|
div, mod = divmod(self.hours * s, 24)
|
||||||
self.hours = mod*s
|
self.hours = mod * s
|
||||||
self.days += div*s
|
self.days += div * s
|
||||||
if abs(self.months) > 11:
|
if abs(self.months) > 11:
|
||||||
s = self.months//abs(self.months)
|
s = _sign(self.months)
|
||||||
div, mod = divmod(self.months*s, 12)
|
div, mod = divmod(self.months * s, 12)
|
||||||
self.months = mod*s
|
self.months = mod * s
|
||||||
self.years += div*s
|
self.years += div * s
|
||||||
if (self.hours or self.minutes or self.seconds or self.microseconds or
|
if (self.hours or self.minutes or self.seconds or self.microseconds
|
||||||
self.hour is not None or self.minute is not None or
|
or self.hour is not None or self.minute is not None or
|
||||||
self.second is not None or self.microsecond is not None):
|
self.second is not None or self.microsecond is not None):
|
||||||
self._has_time = 1
|
self._has_time = 1
|
||||||
else:
|
else:
|
||||||
self._has_time = 0
|
self._has_time = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def weeks(self):
|
||||||
|
return int(self.days / 7.0)
|
||||||
|
|
||||||
|
@weeks.setter
|
||||||
|
def weeks(self, value):
|
||||||
|
self.days = self.days - (self.weeks * 7) + value * 7
|
||||||
|
|
||||||
def _set_months(self, months):
|
def _set_months(self, months):
|
||||||
self.months = months
|
self.months = months
|
||||||
if abs(self.months) > 11:
|
if abs(self.months) > 11:
|
||||||
s = self.months//abs(self.months)
|
s = _sign(self.months)
|
||||||
div, mod = divmod(self.months*s, 12)
|
div, mod = divmod(self.months * s, 12)
|
||||||
self.months = mod*s
|
self.months = mod * s
|
||||||
self.years = div*s
|
self.years = div * s
|
||||||
else:
|
else:
|
||||||
self.years = 0
|
self.years = 0
|
||||||
|
|
||||||
def __radd__(self, other):
|
def normalized(self):
|
||||||
|
"""
|
||||||
|
Return a version of this object represented entirely using integer
|
||||||
|
values for the relative attributes.
|
||||||
|
|
||||||
|
>>> relativedelta(days=1.5, hours=2).normalized()
|
||||||
|
relativedelta(days=+1, hours=+14)
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns a :class:`dateutil.relativedelta.relativedelta` object.
|
||||||
|
"""
|
||||||
|
# Cascade remainders down (rounding each to roughly nearest microsecond)
|
||||||
|
days = int(self.days)
|
||||||
|
|
||||||
|
hours_f = round(self.hours + 24 * (self.days - days), 11)
|
||||||
|
hours = int(hours_f)
|
||||||
|
|
||||||
|
minutes_f = round(self.minutes + 60 * (hours_f - hours), 10)
|
||||||
|
minutes = int(minutes_f)
|
||||||
|
|
||||||
|
seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8)
|
||||||
|
seconds = int(seconds_f)
|
||||||
|
|
||||||
|
microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds))
|
||||||
|
|
||||||
|
# Constructor carries overflow back up with call to _fix()
|
||||||
|
return self.__class__(years=self.years, months=self.months,
|
||||||
|
days=days, hours=hours, minutes=minutes,
|
||||||
|
seconds=seconds, microseconds=microseconds,
|
||||||
|
leapdays=self.leapdays, year=self.year,
|
||||||
|
month=self.month, day=self.day,
|
||||||
|
weekday=self.weekday, hour=self.hour,
|
||||||
|
minute=self.minute, second=self.second,
|
||||||
|
microsecond=self.microsecond)
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
if isinstance(other, relativedelta):
|
||||||
|
return self.__class__(years=other.years + self.years,
|
||||||
|
months=other.months + self.months,
|
||||||
|
days=other.days + self.days,
|
||||||
|
hours=other.hours + self.hours,
|
||||||
|
minutes=other.minutes + self.minutes,
|
||||||
|
seconds=other.seconds + self.seconds,
|
||||||
|
microseconds=(other.microseconds +
|
||||||
|
self.microseconds),
|
||||||
|
leapdays=other.leapdays or self.leapdays,
|
||||||
|
year=(other.year if other.year is not None
|
||||||
|
else self.year),
|
||||||
|
month=(other.month if other.month is not None
|
||||||
|
else self.month),
|
||||||
|
day=(other.day if other.day is not None
|
||||||
|
else self.day),
|
||||||
|
weekday=(other.weekday if other.weekday is not None
|
||||||
|
else self.weekday),
|
||||||
|
hour=(other.hour if other.hour is not None
|
||||||
|
else self.hour),
|
||||||
|
minute=(other.minute if other.minute is not None
|
||||||
|
else self.minute),
|
||||||
|
second=(other.second if other.second is not None
|
||||||
|
else self.second),
|
||||||
|
microsecond=(other.microsecond if other.microsecond
|
||||||
|
is not None else
|
||||||
|
self.microsecond))
|
||||||
|
if isinstance(other, datetime.timedelta):
|
||||||
|
return self.__class__(years=self.years,
|
||||||
|
months=self.months,
|
||||||
|
days=self.days + other.days,
|
||||||
|
hours=self.hours,
|
||||||
|
minutes=self.minutes,
|
||||||
|
seconds=self.seconds + other.seconds,
|
||||||
|
microseconds=self.microseconds + other.microseconds,
|
||||||
|
leapdays=self.leapdays,
|
||||||
|
year=self.year,
|
||||||
|
month=self.month,
|
||||||
|
day=self.day,
|
||||||
|
weekday=self.weekday,
|
||||||
|
hour=self.hour,
|
||||||
|
minute=self.minute,
|
||||||
|
second=self.second,
|
||||||
|
microsecond=self.microsecond)
|
||||||
if not isinstance(other, datetime.date):
|
if not isinstance(other, datetime.date):
|
||||||
raise TypeError, "unsupported type for add operation"
|
return NotImplemented
|
||||||
elif self._has_time and not isinstance(other, datetime.datetime):
|
elif self._has_time and not isinstance(other, datetime.datetime):
|
||||||
other = datetime.datetime.fromordinal(other.toordinal())
|
other = datetime.datetime.fromordinal(other.toordinal())
|
||||||
year = (self.year or other.year)+self.years
|
year = (self.year or other.year)+self.years
|
||||||
@@ -276,60 +392,70 @@ Here is the behavior of operations with relativedelta:
|
|||||||
microseconds=self.microseconds))
|
microseconds=self.microseconds))
|
||||||
if self.weekday:
|
if self.weekday:
|
||||||
weekday, nth = self.weekday.weekday, self.weekday.n or 1
|
weekday, nth = self.weekday.weekday, self.weekday.n or 1
|
||||||
jumpdays = (abs(nth)-1)*7
|
jumpdays = (abs(nth) - 1) * 7
|
||||||
if nth > 0:
|
if nth > 0:
|
||||||
jumpdays += (7-ret.weekday()+weekday)%7
|
jumpdays += (7 - ret.weekday() + weekday) % 7
|
||||||
else:
|
else:
|
||||||
jumpdays += (ret.weekday()-weekday)%7
|
jumpdays += (ret.weekday() - weekday) % 7
|
||||||
jumpdays *= -1
|
jumpdays *= -1
|
||||||
ret += datetime.timedelta(days=jumpdays)
|
ret += datetime.timedelta(days=jumpdays)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
return self.__add__(other)
|
||||||
|
|
||||||
def __rsub__(self, other):
|
def __rsub__(self, other):
|
||||||
return self.__neg__().__radd__(other)
|
return self.__neg__().__radd__(other)
|
||||||
|
|
||||||
def __add__(self, other):
|
|
||||||
if not isinstance(other, relativedelta):
|
|
||||||
raise TypeError, "unsupported type for add operation"
|
|
||||||
return relativedelta(years=other.years+self.years,
|
|
||||||
months=other.months+self.months,
|
|
||||||
days=other.days+self.days,
|
|
||||||
hours=other.hours+self.hours,
|
|
||||||
minutes=other.minutes+self.minutes,
|
|
||||||
seconds=other.seconds+self.seconds,
|
|
||||||
microseconds=other.microseconds+self.microseconds,
|
|
||||||
leapdays=other.leapdays or self.leapdays,
|
|
||||||
year=other.year or self.year,
|
|
||||||
month=other.month or self.month,
|
|
||||||
day=other.day or self.day,
|
|
||||||
weekday=other.weekday or self.weekday,
|
|
||||||
hour=other.hour or self.hour,
|
|
||||||
minute=other.minute or self.minute,
|
|
||||||
second=other.second or self.second,
|
|
||||||
microsecond=other.second or self.microsecond)
|
|
||||||
|
|
||||||
def __sub__(self, other):
|
def __sub__(self, other):
|
||||||
if not isinstance(other, relativedelta):
|
if not isinstance(other, relativedelta):
|
||||||
raise TypeError, "unsupported type for sub operation"
|
return NotImplemented # In case the other object defines __rsub__
|
||||||
return relativedelta(years=other.years-self.years,
|
return self.__class__(years=self.years - other.years,
|
||||||
months=other.months-self.months,
|
months=self.months - other.months,
|
||||||
days=other.days-self.days,
|
days=self.days - other.days,
|
||||||
hours=other.hours-self.hours,
|
hours=self.hours - other.hours,
|
||||||
minutes=other.minutes-self.minutes,
|
minutes=self.minutes - other.minutes,
|
||||||
seconds=other.seconds-self.seconds,
|
seconds=self.seconds - other.seconds,
|
||||||
microseconds=other.microseconds-self.microseconds,
|
microseconds=self.microseconds - other.microseconds,
|
||||||
leapdays=other.leapdays or self.leapdays,
|
leapdays=self.leapdays or other.leapdays,
|
||||||
year=other.year or self.year,
|
year=(self.year if self.year is not None
|
||||||
month=other.month or self.month,
|
else other.year),
|
||||||
day=other.day or self.day,
|
month=(self.month if self.month is not None else
|
||||||
weekday=other.weekday or self.weekday,
|
other.month),
|
||||||
hour=other.hour or self.hour,
|
day=(self.day if self.day is not None else
|
||||||
minute=other.minute or self.minute,
|
other.day),
|
||||||
second=other.second or self.second,
|
weekday=(self.weekday if self.weekday is not None else
|
||||||
microsecond=other.second or self.microsecond)
|
other.weekday),
|
||||||
|
hour=(self.hour if self.hour is not None else
|
||||||
|
other.hour),
|
||||||
|
minute=(self.minute if self.minute is not None else
|
||||||
|
other.minute),
|
||||||
|
second=(self.second if self.second is not None else
|
||||||
|
other.second),
|
||||||
|
microsecond=(self.microsecond if self.microsecond
|
||||||
|
is not None else
|
||||||
|
other.microsecond))
|
||||||
|
|
||||||
|
def __abs__(self):
|
||||||
|
return self.__class__(years=abs(self.years),
|
||||||
|
months=abs(self.months),
|
||||||
|
days=abs(self.days),
|
||||||
|
hours=abs(self.hours),
|
||||||
|
minutes=abs(self.minutes),
|
||||||
|
seconds=abs(self.seconds),
|
||||||
|
microseconds=abs(self.microseconds),
|
||||||
|
leapdays=self.leapdays,
|
||||||
|
year=self.year,
|
||||||
|
month=self.month,
|
||||||
|
day=self.day,
|
||||||
|
weekday=self.weekday,
|
||||||
|
hour=self.hour,
|
||||||
|
minute=self.minute,
|
||||||
|
second=self.second,
|
||||||
|
microsecond=self.microsecond)
|
||||||
|
|
||||||
def __neg__(self):
|
def __neg__(self):
|
||||||
return relativedelta(years=-self.years,
|
return self.__class__(years=-self.years,
|
||||||
months=-self.months,
|
months=-self.months,
|
||||||
days=-self.days,
|
days=-self.days,
|
||||||
hours=-self.hours,
|
hours=-self.hours,
|
||||||
@@ -346,7 +472,7 @@ Here is the behavior of operations with relativedelta:
|
|||||||
second=self.second,
|
second=self.second,
|
||||||
microsecond=self.microsecond)
|
microsecond=self.microsecond)
|
||||||
|
|
||||||
def __nonzero__(self):
|
def __bool__(self):
|
||||||
return not (not self.years and
|
return not (not self.years and
|
||||||
not self.months and
|
not self.months and
|
||||||
not self.days and
|
not self.days and
|
||||||
@@ -363,29 +489,37 @@ Here is the behavior of operations with relativedelta:
|
|||||||
self.minute is None and
|
self.minute is None and
|
||||||
self.second is None and
|
self.second is None and
|
||||||
self.microsecond is None)
|
self.microsecond is None)
|
||||||
|
# Compatibility with Python 2.x
|
||||||
|
__nonzero__ = __bool__
|
||||||
|
|
||||||
def __mul__(self, other):
|
def __mul__(self, other):
|
||||||
f = float(other)
|
try:
|
||||||
return relativedelta(years = int(round(self.years*f)),
|
f = float(other)
|
||||||
months = int(round(self.months*f)),
|
except TypeError:
|
||||||
days = int(round(self.days*f)),
|
return NotImplemented
|
||||||
hours = int(round(self.hours*f)),
|
|
||||||
minutes = int(round(self.minutes*f)),
|
return self.__class__(years=int(self.years * f),
|
||||||
seconds = int(round(self.seconds*f)),
|
months=int(self.months * f),
|
||||||
microseconds = self.microseconds*f,
|
days=int(self.days * f),
|
||||||
leapdays = self.leapdays,
|
hours=int(self.hours * f),
|
||||||
year = self.year,
|
minutes=int(self.minutes * f),
|
||||||
month = self.month,
|
seconds=int(self.seconds * f),
|
||||||
day = self.day,
|
microseconds=int(self.microseconds * f),
|
||||||
weekday = self.weekday,
|
leapdays=self.leapdays,
|
||||||
hour = self.hour,
|
year=self.year,
|
||||||
minute = self.minute,
|
month=self.month,
|
||||||
second = self.second,
|
day=self.day,
|
||||||
microsecond = self.microsecond)
|
weekday=self.weekday,
|
||||||
|
hour=self.hour,
|
||||||
|
minute=self.minute,
|
||||||
|
second=self.second,
|
||||||
|
microsecond=self.microsecond)
|
||||||
|
|
||||||
|
__rmul__ = __mul__
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, relativedelta):
|
if not isinstance(other, relativedelta):
|
||||||
return False
|
return NotImplemented
|
||||||
if self.weekday or other.weekday:
|
if self.weekday or other.weekday:
|
||||||
if not self.weekday or not other.weekday:
|
if not self.weekday or not other.weekday:
|
||||||
return False
|
return False
|
||||||
@@ -400,6 +534,7 @@ Here is the behavior of operations with relativedelta:
|
|||||||
self.hours == other.hours and
|
self.hours == other.hours and
|
||||||
self.minutes == other.minutes and
|
self.minutes == other.minutes and
|
||||||
self.seconds == other.seconds and
|
self.seconds == other.seconds and
|
||||||
|
self.microseconds == other.microseconds and
|
||||||
self.leapdays == other.leapdays and
|
self.leapdays == other.leapdays and
|
||||||
self.year == other.year and
|
self.year == other.year and
|
||||||
self.month == other.month and
|
self.month == other.month and
|
||||||
@@ -409,11 +544,38 @@ Here is the behavior of operations with relativedelta:
|
|||||||
self.second == other.second and
|
self.second == other.second and
|
||||||
self.microsecond == other.microsecond)
|
self.microsecond == other.microsecond)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((
|
||||||
|
self.weekday,
|
||||||
|
self.years,
|
||||||
|
self.months,
|
||||||
|
self.days,
|
||||||
|
self.hours,
|
||||||
|
self.minutes,
|
||||||
|
self.seconds,
|
||||||
|
self.microseconds,
|
||||||
|
self.leapdays,
|
||||||
|
self.year,
|
||||||
|
self.month,
|
||||||
|
self.day,
|
||||||
|
self.hour,
|
||||||
|
self.minute,
|
||||||
|
self.second,
|
||||||
|
self.microsecond,
|
||||||
|
))
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
def __div__(self, other):
|
def __div__(self, other):
|
||||||
return self.__mul__(1/float(other))
|
try:
|
||||||
|
reciprocal = 1 / float(other)
|
||||||
|
except TypeError:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
return self.__mul__(reciprocal)
|
||||||
|
|
||||||
|
__truediv__ = __div__
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
l = []
|
l = []
|
||||||
@@ -421,12 +583,17 @@ Here is the behavior of operations with relativedelta:
|
|||||||
"hours", "minutes", "seconds", "microseconds"]:
|
"hours", "minutes", "seconds", "microseconds"]:
|
||||||
value = getattr(self, attr)
|
value = getattr(self, attr)
|
||||||
if value:
|
if value:
|
||||||
l.append("%s=%+d" % (attr, value))
|
l.append("{attr}={value:+g}".format(attr=attr, value=value))
|
||||||
for attr in ["year", "month", "day", "weekday",
|
for attr in ["year", "month", "day", "weekday",
|
||||||
"hour", "minute", "second", "microsecond"]:
|
"hour", "minute", "second", "microsecond"]:
|
||||||
value = getattr(self, attr)
|
value = getattr(self, attr)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
l.append("%s=%s" % (attr, `value`))
|
l.append("{attr}={value}".format(attr=attr, value=repr(value)))
|
||||||
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
|
return "{classname}({attrs})".format(classname=self.__class__.__name__,
|
||||||
|
attrs=", ".join(l))
|
||||||
|
|
||||||
|
|
||||||
|
def _sign(x):
|
||||||
|
return int(copysign(1, x))
|
||||||
|
|
||||||
# vim:ts=4:sw=4:et
|
# vim:ts=4:sw=4:et
|
||||||
|
|||||||
+922
-295
File diff suppressed because it is too large
Load Diff
@@ -1,958 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
|
||||||
|
|
||||||
This module offers extensions to the standard python 2.3+
|
|
||||||
datetime module.
|
|
||||||
"""
|
|
||||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
|
||||||
__license__ = "PSF License"
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import struct
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
relativedelta = None
|
|
||||||
parser = None
|
|
||||||
rrule = None
|
|
||||||
|
|
||||||
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
|
||||||
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"]
|
|
||||||
|
|
||||||
try:
|
|
||||||
from dateutil.tzwin import tzwin, tzwinlocal
|
|
||||||
except (ImportError, OSError):
|
|
||||||
tzwin, tzwinlocal = None, None
|
|
||||||
|
|
||||||
ZERO = datetime.timedelta(0)
|
|
||||||
EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal()
|
|
||||||
|
|
||||||
class tzutc(datetime.tzinfo):
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
|
||||||
return ZERO
|
|
||||||
|
|
||||||
def dst(self, dt):
|
|
||||||
return ZERO
|
|
||||||
|
|
||||||
def tzname(self, dt):
|
|
||||||
return "UTC"
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return (isinstance(other, tzutc) or
|
|
||||||
(isinstance(other, tzoffset) and other._offset == ZERO))
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "%s()" % self.__class__.__name__
|
|
||||||
|
|
||||||
__reduce__ = object.__reduce__
|
|
||||||
|
|
||||||
class tzoffset(datetime.tzinfo):
|
|
||||||
|
|
||||||
def __init__(self, name, offset):
|
|
||||||
self._name = name
|
|
||||||
self._offset = datetime.timedelta(seconds=offset)
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
|
||||||
return self._offset
|
|
||||||
|
|
||||||
def dst(self, dt):
|
|
||||||
return ZERO
|
|
||||||
|
|
||||||
def tzname(self, dt):
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return (isinstance(other, tzoffset) and
|
|
||||||
self._offset == other._offset)
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "%s(%s, %s)" % (self.__class__.__name__,
|
|
||||||
`self._name`,
|
|
||||||
self._offset.days*86400+self._offset.seconds)
|
|
||||||
|
|
||||||
__reduce__ = object.__reduce__
|
|
||||||
|
|
||||||
class tzlocal(datetime.tzinfo):
|
|
||||||
|
|
||||||
_std_offset = datetime.timedelta(seconds=-time.timezone)
|
|
||||||
if time.daylight:
|
|
||||||
_dst_offset = datetime.timedelta(seconds=-time.altzone)
|
|
||||||
else:
|
|
||||||
_dst_offset = _std_offset
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
|
||||||
if self._isdst(dt):
|
|
||||||
return self._dst_offset
|
|
||||||
else:
|
|
||||||
return self._std_offset
|
|
||||||
|
|
||||||
def dst(self, dt):
|
|
||||||
if self._isdst(dt):
|
|
||||||
return self._dst_offset-self._std_offset
|
|
||||||
else:
|
|
||||||
return ZERO
|
|
||||||
|
|
||||||
def tzname(self, dt):
|
|
||||||
return time.tzname[self._isdst(dt)]
|
|
||||||
|
|
||||||
def _isdst(self, dt):
|
|
||||||
# We can't use mktime here. It is unstable when deciding if
|
|
||||||
# the hour near to a change is DST or not.
|
|
||||||
#
|
|
||||||
# timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
|
|
||||||
# dt.minute, dt.second, dt.weekday(), 0, -1))
|
|
||||||
# return time.localtime(timestamp).tm_isdst
|
|
||||||
#
|
|
||||||
# The code above yields the following result:
|
|
||||||
#
|
|
||||||
#>>> import tz, datetime
|
|
||||||
#>>> t = tz.tzlocal()
|
|
||||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
|
||||||
#'BRDT'
|
|
||||||
#>>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
|
|
||||||
#'BRST'
|
|
||||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
|
||||||
#'BRST'
|
|
||||||
#>>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
|
|
||||||
#'BRDT'
|
|
||||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
|
||||||
#'BRDT'
|
|
||||||
#
|
|
||||||
# Here is a more stable implementation:
|
|
||||||
#
|
|
||||||
timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
|
|
||||||
+ dt.hour * 3600
|
|
||||||
+ dt.minute * 60
|
|
||||||
+ dt.second)
|
|
||||||
return time.localtime(timestamp+time.timezone).tm_isdst
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, tzlocal):
|
|
||||||
return False
|
|
||||||
return (self._std_offset == other._std_offset and
|
|
||||||
self._dst_offset == other._dst_offset)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "%s()" % self.__class__.__name__
|
|
||||||
|
|
||||||
__reduce__ = object.__reduce__
|
|
||||||
|
|
||||||
class _ttinfo(object):
|
|
||||||
__slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"]
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
for attr in self.__slots__:
|
|
||||||
setattr(self, attr, None)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
l = []
|
|
||||||
for attr in self.__slots__:
|
|
||||||
value = getattr(self, attr)
|
|
||||||
if value is not None:
|
|
||||||
l.append("%s=%s" % (attr, `value`))
|
|
||||||
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, _ttinfo):
|
|
||||||
return False
|
|
||||||
return (self.offset == other.offset and
|
|
||||||
self.delta == other.delta and
|
|
||||||
self.isdst == other.isdst and
|
|
||||||
self.abbr == other.abbr and
|
|
||||||
self.isstd == other.isstd and
|
|
||||||
self.isgmt == other.isgmt)
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other)
|
|
||||||
|
|
||||||
def __getstate__(self):
|
|
||||||
state = {}
|
|
||||||
for name in self.__slots__:
|
|
||||||
state[name] = getattr(self, name, None)
|
|
||||||
return state
|
|
||||||
|
|
||||||
def __setstate__(self, state):
|
|
||||||
for name in self.__slots__:
|
|
||||||
if name in state:
|
|
||||||
setattr(self, name, state[name])
|
|
||||||
|
|
||||||
class tzfile(datetime.tzinfo):
|
|
||||||
|
|
||||||
# http://www.twinsun.com/tz/tz-link.htm
|
|
||||||
# ftp://elsie.nci.nih.gov/pub/tz*.tar.gz
|
|
||||||
|
|
||||||
def __init__(self, fileobj):
|
|
||||||
if isinstance(fileobj, basestring):
|
|
||||||
self._filename = fileobj
|
|
||||||
fileobj = open(fileobj)
|
|
||||||
elif hasattr(fileobj, "name"):
|
|
||||||
self._filename = fileobj.name
|
|
||||||
else:
|
|
||||||
self._filename = `fileobj`
|
|
||||||
|
|
||||||
# From tzfile(5):
|
|
||||||
#
|
|
||||||
# The time zone information files used by tzset(3)
|
|
||||||
# begin with the magic characters "TZif" to identify
|
|
||||||
# them as time zone information files, followed by
|
|
||||||
# sixteen bytes reserved for future use, followed by
|
|
||||||
# six four-byte values of type long, written in a
|
|
||||||
# ``standard'' byte order (the high-order byte
|
|
||||||
# of the value is written first).
|
|
||||||
|
|
||||||
if fileobj.read(4) != "TZif":
|
|
||||||
raise ValueError, "magic not found"
|
|
||||||
|
|
||||||
fileobj.read(16)
|
|
||||||
|
|
||||||
(
|
|
||||||
# The number of UTC/local indicators stored in the file.
|
|
||||||
ttisgmtcnt,
|
|
||||||
|
|
||||||
# The number of standard/wall indicators stored in the file.
|
|
||||||
ttisstdcnt,
|
|
||||||
|
|
||||||
# The number of leap seconds for which data is
|
|
||||||
# stored in the file.
|
|
||||||
leapcnt,
|
|
||||||
|
|
||||||
# The number of "transition times" for which data
|
|
||||||
# is stored in the file.
|
|
||||||
timecnt,
|
|
||||||
|
|
||||||
# The number of "local time types" for which data
|
|
||||||
# is stored in the file (must not be zero).
|
|
||||||
typecnt,
|
|
||||||
|
|
||||||
# The number of characters of "time zone
|
|
||||||
# abbreviation strings" stored in the file.
|
|
||||||
charcnt,
|
|
||||||
|
|
||||||
) = struct.unpack(">6l", fileobj.read(24))
|
|
||||||
|
|
||||||
# The above header is followed by tzh_timecnt four-byte
|
|
||||||
# values of type long, sorted in ascending order.
|
|
||||||
# These values are written in ``standard'' byte order.
|
|
||||||
# Each is used as a transition time (as returned by
|
|
||||||
# time(2)) at which the rules for computing local time
|
|
||||||
# change.
|
|
||||||
|
|
||||||
if timecnt:
|
|
||||||
self._trans_list = struct.unpack(">%dl" % timecnt,
|
|
||||||
fileobj.read(timecnt*4))
|
|
||||||
else:
|
|
||||||
self._trans_list = []
|
|
||||||
|
|
||||||
# Next come tzh_timecnt one-byte values of type unsigned
|
|
||||||
# char; each one tells which of the different types of
|
|
||||||
# ``local time'' types described in the file is associated
|
|
||||||
# with the same-indexed transition time. These values
|
|
||||||
# serve as indices into an array of ttinfo structures that
|
|
||||||
# appears next in the file.
|
|
||||||
|
|
||||||
if timecnt:
|
|
||||||
self._trans_idx = struct.unpack(">%dB" % timecnt,
|
|
||||||
fileobj.read(timecnt))
|
|
||||||
else:
|
|
||||||
self._trans_idx = []
|
|
||||||
|
|
||||||
# Each ttinfo structure is written as a four-byte value
|
|
||||||
# for tt_gmtoff of type long, in a standard byte
|
|
||||||
# order, followed by a one-byte value for tt_isdst
|
|
||||||
# and a one-byte value for tt_abbrind. In each
|
|
||||||
# structure, tt_gmtoff gives the number of
|
|
||||||
# seconds to be added to UTC, tt_isdst tells whether
|
|
||||||
# tm_isdst should be set by localtime(3), and
|
|
||||||
# tt_abbrind serves as an index into the array of
|
|
||||||
# time zone abbreviation characters that follow the
|
|
||||||
# ttinfo structure(s) in the file.
|
|
||||||
|
|
||||||
ttinfo = []
|
|
||||||
|
|
||||||
for i in range(typecnt):
|
|
||||||
ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
|
|
||||||
|
|
||||||
abbr = fileobj.read(charcnt)
|
|
||||||
|
|
||||||
# Then there are tzh_leapcnt pairs of four-byte
|
|
||||||
# values, written in standard byte order; the
|
|
||||||
# first value of each pair gives the time (as
|
|
||||||
# returned by time(2)) at which a leap second
|
|
||||||
# occurs; the second gives the total number of
|
|
||||||
# leap seconds to be applied after the given time.
|
|
||||||
# The pairs of values are sorted in ascending order
|
|
||||||
# by time.
|
|
||||||
|
|
||||||
# Not used, for now
|
|
||||||
if leapcnt:
|
|
||||||
leap = struct.unpack(">%dl" % (leapcnt*2),
|
|
||||||
fileobj.read(leapcnt*8))
|
|
||||||
|
|
||||||
# Then there are tzh_ttisstdcnt standard/wall
|
|
||||||
# indicators, each stored as a one-byte value;
|
|
||||||
# they tell whether the transition times associated
|
|
||||||
# with local time types were specified as standard
|
|
||||||
# time or wall clock time, and are used when
|
|
||||||
# a time zone file is used in handling POSIX-style
|
|
||||||
# time zone environment variables.
|
|
||||||
|
|
||||||
if ttisstdcnt:
|
|
||||||
isstd = struct.unpack(">%db" % ttisstdcnt,
|
|
||||||
fileobj.read(ttisstdcnt))
|
|
||||||
|
|
||||||
# Finally, there are tzh_ttisgmtcnt UTC/local
|
|
||||||
# indicators, each stored as a one-byte value;
|
|
||||||
# they tell whether the transition times associated
|
|
||||||
# with local time types were specified as UTC or
|
|
||||||
# local time, and are used when a time zone file
|
|
||||||
# is used in handling POSIX-style time zone envi-
|
|
||||||
# ronment variables.
|
|
||||||
|
|
||||||
if ttisgmtcnt:
|
|
||||||
isgmt = struct.unpack(">%db" % ttisgmtcnt,
|
|
||||||
fileobj.read(ttisgmtcnt))
|
|
||||||
|
|
||||||
# ** Everything has been read **
|
|
||||||
|
|
||||||
# Build ttinfo list
|
|
||||||
self._ttinfo_list = []
|
|
||||||
for i in range(typecnt):
|
|
||||||
gmtoff, isdst, abbrind = ttinfo[i]
|
|
||||||
# Round to full-minutes if that's not the case. Python's
|
|
||||||
# datetime doesn't accept sub-minute timezones. Check
|
|
||||||
# http://python.org/sf/1447945 for some information.
|
|
||||||
gmtoff = (gmtoff+30)//60*60
|
|
||||||
tti = _ttinfo()
|
|
||||||
tti.offset = gmtoff
|
|
||||||
tti.delta = datetime.timedelta(seconds=gmtoff)
|
|
||||||
tti.isdst = isdst
|
|
||||||
tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)]
|
|
||||||
tti.isstd = (ttisstdcnt > i and isstd[i] != 0)
|
|
||||||
tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)
|
|
||||||
self._ttinfo_list.append(tti)
|
|
||||||
|
|
||||||
# Replace ttinfo indexes for ttinfo objects.
|
|
||||||
trans_idx = []
|
|
||||||
for idx in self._trans_idx:
|
|
||||||
trans_idx.append(self._ttinfo_list[idx])
|
|
||||||
self._trans_idx = tuple(trans_idx)
|
|
||||||
|
|
||||||
# Set standard, dst, and before ttinfos. before will be
|
|
||||||
# used when a given time is before any transitions,
|
|
||||||
# and will be set to the first non-dst ttinfo, or to
|
|
||||||
# the first dst, if all of them are dst.
|
|
||||||
self._ttinfo_std = None
|
|
||||||
self._ttinfo_dst = None
|
|
||||||
self._ttinfo_before = None
|
|
||||||
if self._ttinfo_list:
|
|
||||||
if not self._trans_list:
|
|
||||||
self._ttinfo_std = self._ttinfo_first = self._ttinfo_list[0]
|
|
||||||
else:
|
|
||||||
for i in range(timecnt-1,-1,-1):
|
|
||||||
tti = self._trans_idx[i]
|
|
||||||
if not self._ttinfo_std and not tti.isdst:
|
|
||||||
self._ttinfo_std = tti
|
|
||||||
elif not self._ttinfo_dst and tti.isdst:
|
|
||||||
self._ttinfo_dst = tti
|
|
||||||
if self._ttinfo_std and self._ttinfo_dst:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if self._ttinfo_dst and not self._ttinfo_std:
|
|
||||||
self._ttinfo_std = self._ttinfo_dst
|
|
||||||
|
|
||||||
for tti in self._ttinfo_list:
|
|
||||||
if not tti.isdst:
|
|
||||||
self._ttinfo_before = tti
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
self._ttinfo_before = self._ttinfo_list[0]
|
|
||||||
|
|
||||||
# Now fix transition times to become relative to wall time.
|
|
||||||
#
|
|
||||||
# I'm not sure about this. In my tests, the tz source file
|
|
||||||
# is setup to wall time, and in the binary file isstd and
|
|
||||||
# isgmt are off, so it should be in wall time. OTOH, it's
|
|
||||||
# always in gmt time. Let me know if you have comments
|
|
||||||
# about this.
|
|
||||||
laststdoffset = 0
|
|
||||||
self._trans_list = list(self._trans_list)
|
|
||||||
for i in range(len(self._trans_list)):
|
|
||||||
tti = self._trans_idx[i]
|
|
||||||
if not tti.isdst:
|
|
||||||
# This is std time.
|
|
||||||
self._trans_list[i] += tti.offset
|
|
||||||
laststdoffset = tti.offset
|
|
||||||
else:
|
|
||||||
# This is dst time. Convert to std.
|
|
||||||
self._trans_list[i] += laststdoffset
|
|
||||||
self._trans_list = tuple(self._trans_list)
|
|
||||||
|
|
||||||
def _find_ttinfo(self, dt, laststd=0):
|
|
||||||
timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
|
|
||||||
+ dt.hour * 3600
|
|
||||||
+ dt.minute * 60
|
|
||||||
+ dt.second)
|
|
||||||
idx = 0
|
|
||||||
for trans in self._trans_list:
|
|
||||||
if timestamp < trans:
|
|
||||||
break
|
|
||||||
idx += 1
|
|
||||||
else:
|
|
||||||
return self._ttinfo_std
|
|
||||||
if idx == 0:
|
|
||||||
return self._ttinfo_before
|
|
||||||
if laststd:
|
|
||||||
while idx > 0:
|
|
||||||
tti = self._trans_idx[idx-1]
|
|
||||||
if not tti.isdst:
|
|
||||||
return tti
|
|
||||||
idx -= 1
|
|
||||||
else:
|
|
||||||
return self._ttinfo_std
|
|
||||||
else:
|
|
||||||
return self._trans_idx[idx-1]
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
|
||||||
if not self._ttinfo_std:
|
|
||||||
return ZERO
|
|
||||||
return self._find_ttinfo(dt).delta
|
|
||||||
|
|
||||||
def dst(self, dt):
|
|
||||||
if not self._ttinfo_dst:
|
|
||||||
return ZERO
|
|
||||||
tti = self._find_ttinfo(dt)
|
|
||||||
if not tti.isdst:
|
|
||||||
return ZERO
|
|
||||||
|
|
||||||
# The documentation says that utcoffset()-dst() must
|
|
||||||
# be constant for every dt.
|
|
||||||
return tti.delta-self._find_ttinfo(dt, laststd=1).delta
|
|
||||||
|
|
||||||
# An alternative for that would be:
|
|
||||||
#
|
|
||||||
# return self._ttinfo_dst.offset-self._ttinfo_std.offset
|
|
||||||
#
|
|
||||||
# However, this class stores historical changes in the
|
|
||||||
# dst offset, so I belive that this wouldn't be the right
|
|
||||||
# way to implement this.
|
|
||||||
|
|
||||||
def tzname(self, dt):
|
|
||||||
if not self._ttinfo_std:
|
|
||||||
return None
|
|
||||||
return self._find_ttinfo(dt).abbr
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, tzfile):
|
|
||||||
return False
|
|
||||||
return (self._trans_list == other._trans_list and
|
|
||||||
self._trans_idx == other._trans_idx and
|
|
||||||
self._ttinfo_list == other._ttinfo_list)
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other)
|
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "%s(%s)" % (self.__class__.__name__, `self._filename`)
|
|
||||||
|
|
||||||
def __reduce__(self):
|
|
||||||
if not os.path.isfile(self._filename):
|
|
||||||
raise ValueError, "Unpickable %s class" % self.__class__.__name__
|
|
||||||
return (self.__class__, (self._filename,))
|
|
||||||
|
|
||||||
class tzrange(datetime.tzinfo):
|
|
||||||
|
|
||||||
def __init__(self, stdabbr, stdoffset=None,
|
|
||||||
dstabbr=None, dstoffset=None,
|
|
||||||
start=None, end=None):
|
|
||||||
global relativedelta
|
|
||||||
if not relativedelta:
|
|
||||||
from dateutil import relativedelta
|
|
||||||
self._std_abbr = stdabbr
|
|
||||||
self._dst_abbr = dstabbr
|
|
||||||
if stdoffset is not None:
|
|
||||||
self._std_offset = datetime.timedelta(seconds=stdoffset)
|
|
||||||
else:
|
|
||||||
self._std_offset = ZERO
|
|
||||||
if dstoffset is not None:
|
|
||||||
self._dst_offset = datetime.timedelta(seconds=dstoffset)
|
|
||||||
elif dstabbr and stdoffset is not None:
|
|
||||||
self._dst_offset = self._std_offset+datetime.timedelta(hours=+1)
|
|
||||||
else:
|
|
||||||
self._dst_offset = ZERO
|
|
||||||
if dstabbr and start is None:
|
|
||||||
self._start_delta = relativedelta.relativedelta(
|
|
||||||
hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
|
|
||||||
else:
|
|
||||||
self._start_delta = start
|
|
||||||
if dstabbr and end is None:
|
|
||||||
self._end_delta = relativedelta.relativedelta(
|
|
||||||
hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
|
|
||||||
else:
|
|
||||||
self._end_delta = end
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
|
||||||
if self._isdst(dt):
|
|
||||||
return self._dst_offset
|
|
||||||
else:
|
|
||||||
return self._std_offset
|
|
||||||
|
|
||||||
def dst(self, dt):
|
|
||||||
if self._isdst(dt):
|
|
||||||
return self._dst_offset-self._std_offset
|
|
||||||
else:
|
|
||||||
return ZERO
|
|
||||||
|
|
||||||
def tzname(self, dt):
|
|
||||||
if self._isdst(dt):
|
|
||||||
return self._dst_abbr
|
|
||||||
else:
|
|
||||||
return self._std_abbr
|
|
||||||
|
|
||||||
def _isdst(self, dt):
|
|
||||||
if not self._start_delta:
|
|
||||||
return False
|
|
||||||
year = datetime.datetime(dt.year,1,1)
|
|
||||||
start = year+self._start_delta
|
|
||||||
end = year+self._end_delta
|
|
||||||
dt = dt.replace(tzinfo=None)
|
|
||||||
if start < end:
|
|
||||||
return dt >= start and dt < end
|
|
||||||
else:
|
|
||||||
return dt >= start or dt < end
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, tzrange):
|
|
||||||
return False
|
|
||||||
return (self._std_abbr == other._std_abbr and
|
|
||||||
self._dst_abbr == other._dst_abbr and
|
|
||||||
self._std_offset == other._std_offset and
|
|
||||||
self._dst_offset == other._dst_offset and
|
|
||||||
self._start_delta == other._start_delta and
|
|
||||||
self._end_delta == other._end_delta)
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "%s(...)" % self.__class__.__name__
|
|
||||||
|
|
||||||
__reduce__ = object.__reduce__
|
|
||||||
|
|
||||||
class tzstr(tzrange):
|
|
||||||
|
|
||||||
def __init__(self, s):
|
|
||||||
global parser
|
|
||||||
if not parser:
|
|
||||||
from dateutil import parser
|
|
||||||
self._s = s
|
|
||||||
|
|
||||||
res = parser._parsetz(s)
|
|
||||||
if res is None:
|
|
||||||
raise ValueError, "unknown string format"
|
|
||||||
|
|
||||||
# Here we break the compatibility with the TZ variable handling.
|
|
||||||
# GMT-3 actually *means* the timezone -3.
|
|
||||||
if res.stdabbr in ("GMT", "UTC"):
|
|
||||||
res.stdoffset *= -1
|
|
||||||
|
|
||||||
# We must initialize it first, since _delta() needs
|
|
||||||
# _std_offset and _dst_offset set. Use False in start/end
|
|
||||||
# to avoid building it two times.
|
|
||||||
tzrange.__init__(self, res.stdabbr, res.stdoffset,
|
|
||||||
res.dstabbr, res.dstoffset,
|
|
||||||
start=False, end=False)
|
|
||||||
|
|
||||||
if not res.dstabbr:
|
|
||||||
self._start_delta = None
|
|
||||||
self._end_delta = None
|
|
||||||
else:
|
|
||||||
self._start_delta = self._delta(res.start)
|
|
||||||
if self._start_delta:
|
|
||||||
self._end_delta = self._delta(res.end, isend=1)
|
|
||||||
|
|
||||||
def _delta(self, x, isend=0):
|
|
||||||
kwargs = {}
|
|
||||||
if x.month is not None:
|
|
||||||
kwargs["month"] = x.month
|
|
||||||
if x.weekday is not None:
|
|
||||||
kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
|
|
||||||
if x.week > 0:
|
|
||||||
kwargs["day"] = 1
|
|
||||||
else:
|
|
||||||
kwargs["day"] = 31
|
|
||||||
elif x.day:
|
|
||||||
kwargs["day"] = x.day
|
|
||||||
elif x.yday is not None:
|
|
||||||
kwargs["yearday"] = x.yday
|
|
||||||
elif x.jyday is not None:
|
|
||||||
kwargs["nlyearday"] = x.jyday
|
|
||||||
if not kwargs:
|
|
||||||
# Default is to start on first sunday of april, and end
|
|
||||||
# on last sunday of october.
|
|
||||||
if not isend:
|
|
||||||
kwargs["month"] = 4
|
|
||||||
kwargs["day"] = 1
|
|
||||||
kwargs["weekday"] = relativedelta.SU(+1)
|
|
||||||
else:
|
|
||||||
kwargs["month"] = 10
|
|
||||||
kwargs["day"] = 31
|
|
||||||
kwargs["weekday"] = relativedelta.SU(-1)
|
|
||||||
if x.time is not None:
|
|
||||||
kwargs["seconds"] = x.time
|
|
||||||
else:
|
|
||||||
# Default is 2AM.
|
|
||||||
kwargs["seconds"] = 7200
|
|
||||||
if isend:
|
|
||||||
# Convert to standard time, to follow the documented way
|
|
||||||
# of working with the extra hour. See the documentation
|
|
||||||
# of the tzinfo class.
|
|
||||||
delta = self._dst_offset-self._std_offset
|
|
||||||
kwargs["seconds"] -= delta.seconds+delta.days*86400
|
|
||||||
return relativedelta.relativedelta(**kwargs)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
|
||||||
|
|
||||||
class _tzicalvtzcomp:
|
|
||||||
def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
|
|
||||||
tzname=None, rrule=None):
|
|
||||||
self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
|
|
||||||
self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
|
|
||||||
self.tzoffsetdiff = self.tzoffsetto-self.tzoffsetfrom
|
|
||||||
self.isdst = isdst
|
|
||||||
self.tzname = tzname
|
|
||||||
self.rrule = rrule
|
|
||||||
|
|
||||||
class _tzicalvtz(datetime.tzinfo):
|
|
||||||
def __init__(self, tzid, comps=[]):
|
|
||||||
self._tzid = tzid
|
|
||||||
self._comps = comps
|
|
||||||
self._cachedate = []
|
|
||||||
self._cachecomp = []
|
|
||||||
|
|
||||||
def _find_comp(self, dt):
|
|
||||||
if len(self._comps) == 1:
|
|
||||||
return self._comps[0]
|
|
||||||
dt = dt.replace(tzinfo=None)
|
|
||||||
try:
|
|
||||||
return self._cachecomp[self._cachedate.index(dt)]
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
lastcomp = None
|
|
||||||
lastcompdt = None
|
|
||||||
for comp in self._comps:
|
|
||||||
if not comp.isdst:
|
|
||||||
# Handle the extra hour in DST -> STD
|
|
||||||
compdt = comp.rrule.before(dt-comp.tzoffsetdiff, inc=True)
|
|
||||||
else:
|
|
||||||
compdt = comp.rrule.before(dt, inc=True)
|
|
||||||
if compdt and (not lastcompdt or lastcompdt < compdt):
|
|
||||||
lastcompdt = compdt
|
|
||||||
lastcomp = comp
|
|
||||||
if not lastcomp:
|
|
||||||
# RFC says nothing about what to do when a given
|
|
||||||
# time is before the first onset date. We'll look for the
|
|
||||||
# first standard component, or the first component, if
|
|
||||||
# none is found.
|
|
||||||
for comp in self._comps:
|
|
||||||
if not comp.isdst:
|
|
||||||
lastcomp = comp
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
lastcomp = comp[0]
|
|
||||||
self._cachedate.insert(0, dt)
|
|
||||||
self._cachecomp.insert(0, lastcomp)
|
|
||||||
if len(self._cachedate) > 10:
|
|
||||||
self._cachedate.pop()
|
|
||||||
self._cachecomp.pop()
|
|
||||||
return lastcomp
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
|
||||||
return self._find_comp(dt).tzoffsetto
|
|
||||||
|
|
||||||
def dst(self, dt):
|
|
||||||
comp = self._find_comp(dt)
|
|
||||||
if comp.isdst:
|
|
||||||
return comp.tzoffsetdiff
|
|
||||||
else:
|
|
||||||
return ZERO
|
|
||||||
|
|
||||||
def tzname(self, dt):
|
|
||||||
return self._find_comp(dt).tzname
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<tzicalvtz %s>" % `self._tzid`
|
|
||||||
|
|
||||||
__reduce__ = object.__reduce__
|
|
||||||
|
|
||||||
class tzical:
|
|
||||||
def __init__(self, fileobj):
|
|
||||||
global rrule
|
|
||||||
if not rrule:
|
|
||||||
from dateutil import rrule
|
|
||||||
|
|
||||||
if isinstance(fileobj, basestring):
|
|
||||||
self._s = fileobj
|
|
||||||
fileobj = open(fileobj)
|
|
||||||
elif hasattr(fileobj, "name"):
|
|
||||||
self._s = fileobj.name
|
|
||||||
else:
|
|
||||||
self._s = `fileobj`
|
|
||||||
|
|
||||||
self._vtz = {}
|
|
||||||
|
|
||||||
self._parse_rfc(fileobj.read())
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
return self._vtz.keys()
|
|
||||||
|
|
||||||
def get(self, tzid=None):
|
|
||||||
if tzid is None:
|
|
||||||
keys = self._vtz.keys()
|
|
||||||
if len(keys) == 0:
|
|
||||||
raise ValueError, "no timezones defined"
|
|
||||||
elif len(keys) > 1:
|
|
||||||
raise ValueError, "more than one timezone available"
|
|
||||||
tzid = keys[0]
|
|
||||||
return self._vtz.get(tzid)
|
|
||||||
|
|
||||||
def _parse_offset(self, s):
|
|
||||||
s = s.strip()
|
|
||||||
if not s:
|
|
||||||
raise ValueError, "empty offset"
|
|
||||||
if s[0] in ('+', '-'):
|
|
||||||
signal = (-1,+1)[s[0]=='+']
|
|
||||||
s = s[1:]
|
|
||||||
else:
|
|
||||||
signal = +1
|
|
||||||
if len(s) == 4:
|
|
||||||
return (int(s[:2])*3600+int(s[2:])*60)*signal
|
|
||||||
elif len(s) == 6:
|
|
||||||
return (int(s[:2])*3600+int(s[2:4])*60+int(s[4:]))*signal
|
|
||||||
else:
|
|
||||||
raise ValueError, "invalid offset: "+s
|
|
||||||
|
|
||||||
def _parse_rfc(self, s):
|
|
||||||
lines = s.splitlines()
|
|
||||||
if not lines:
|
|
||||||
raise ValueError, "empty string"
|
|
||||||
|
|
||||||
# Unfold
|
|
||||||
i = 0
|
|
||||||
while i < len(lines):
|
|
||||||
line = lines[i].rstrip()
|
|
||||||
if not line:
|
|
||||||
del lines[i]
|
|
||||||
elif i > 0 and line[0] in (" ", "\t"):
|
|
||||||
lines[i-1] += line[1:]
|
|
||||||
del lines[i]
|
|
||||||
else:
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
tzid = None
|
|
||||||
comps = []
|
|
||||||
invtz = False
|
|
||||||
comptype = None
|
|
||||||
for line in lines:
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
name, value = line.split(':', 1)
|
|
||||||
parms = name.split(';')
|
|
||||||
if not parms:
|
|
||||||
raise ValueError, "empty property name"
|
|
||||||
name = parms[0].upper()
|
|
||||||
parms = parms[1:]
|
|
||||||
if invtz:
|
|
||||||
if name == "BEGIN":
|
|
||||||
if value in ("STANDARD", "DAYLIGHT"):
|
|
||||||
# Process component
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise ValueError, "unknown component: "+value
|
|
||||||
comptype = value
|
|
||||||
founddtstart = False
|
|
||||||
tzoffsetfrom = None
|
|
||||||
tzoffsetto = None
|
|
||||||
rrulelines = []
|
|
||||||
tzname = None
|
|
||||||
elif name == "END":
|
|
||||||
if value == "VTIMEZONE":
|
|
||||||
if comptype:
|
|
||||||
raise ValueError, \
|
|
||||||
"component not closed: "+comptype
|
|
||||||
if not tzid:
|
|
||||||
raise ValueError, \
|
|
||||||
"mandatory TZID not found"
|
|
||||||
if not comps:
|
|
||||||
raise ValueError, \
|
|
||||||
"at least one component is needed"
|
|
||||||
# Process vtimezone
|
|
||||||
self._vtz[tzid] = _tzicalvtz(tzid, comps)
|
|
||||||
invtz = False
|
|
||||||
elif value == comptype:
|
|
||||||
if not founddtstart:
|
|
||||||
raise ValueError, \
|
|
||||||
"mandatory DTSTART not found"
|
|
||||||
if tzoffsetfrom is None:
|
|
||||||
raise ValueError, \
|
|
||||||
"mandatory TZOFFSETFROM not found"
|
|
||||||
if tzoffsetto is None:
|
|
||||||
raise ValueError, \
|
|
||||||
"mandatory TZOFFSETFROM not found"
|
|
||||||
# Process component
|
|
||||||
rr = None
|
|
||||||
if rrulelines:
|
|
||||||
rr = rrule.rrulestr("\n".join(rrulelines),
|
|
||||||
compatible=True,
|
|
||||||
ignoretz=True,
|
|
||||||
cache=True)
|
|
||||||
comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
|
|
||||||
(comptype == "DAYLIGHT"),
|
|
||||||
tzname, rr)
|
|
||||||
comps.append(comp)
|
|
||||||
comptype = None
|
|
||||||
else:
|
|
||||||
raise ValueError, \
|
|
||||||
"invalid component end: "+value
|
|
||||||
elif comptype:
|
|
||||||
if name == "DTSTART":
|
|
||||||
rrulelines.append(line)
|
|
||||||
founddtstart = True
|
|
||||||
elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
|
|
||||||
rrulelines.append(line)
|
|
||||||
elif name == "TZOFFSETFROM":
|
|
||||||
if parms:
|
|
||||||
raise ValueError, \
|
|
||||||
"unsupported %s parm: %s "%(name, parms[0])
|
|
||||||
tzoffsetfrom = self._parse_offset(value)
|
|
||||||
elif name == "TZOFFSETTO":
|
|
||||||
if parms:
|
|
||||||
raise ValueError, \
|
|
||||||
"unsupported TZOFFSETTO parm: "+parms[0]
|
|
||||||
tzoffsetto = self._parse_offset(value)
|
|
||||||
elif name == "TZNAME":
|
|
||||||
if parms:
|
|
||||||
raise ValueError, \
|
|
||||||
"unsupported TZNAME parm: "+parms[0]
|
|
||||||
tzname = value
|
|
||||||
elif name == "COMMENT":
|
|
||||||
pass
|
|
||||||
elif name.upper().startswith('X-'):
|
|
||||||
# Ignore experimental properties.
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise ValueError, "unsupported property: "+name
|
|
||||||
else:
|
|
||||||
if name == "TZID":
|
|
||||||
for p in parms:
|
|
||||||
if not p.upper().startswith('X-'):
|
|
||||||
raise ValueError, \
|
|
||||||
"unsupported TZID parm: "+p
|
|
||||||
tzid = value
|
|
||||||
elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
|
|
||||||
pass
|
|
||||||
elif name.upper().startswith('X-'):
|
|
||||||
# Ignore experimental properties.
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise ValueError, "unsupported property: "+name
|
|
||||||
elif name == "BEGIN" and value == "VTIMEZONE":
|
|
||||||
tzid = None
|
|
||||||
comps = []
|
|
||||||
invtz = True
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
|
||||||
|
|
||||||
if sys.platform != "win32":
|
|
||||||
TZFILES = ["/etc/localtime", "localtime"]
|
|
||||||
TZPATHS = ["/usr/share/zoneinfo", "/usr/lib/zoneinfo", "/etc/zoneinfo"]
|
|
||||||
else:
|
|
||||||
TZFILES = []
|
|
||||||
TZPATHS = []
|
|
||||||
|
|
||||||
def gettz(name=None):
|
|
||||||
tz = None
|
|
||||||
if not name:
|
|
||||||
try:
|
|
||||||
name = os.environ["TZ"]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
if name is None or name == ":":
|
|
||||||
for filepath in TZFILES:
|
|
||||||
if not os.path.isabs(filepath):
|
|
||||||
filename = filepath
|
|
||||||
for path in TZPATHS:
|
|
||||||
filepath = os.path.join(path, filename)
|
|
||||||
if os.path.isfile(filepath):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
if os.path.isfile(filepath):
|
|
||||||
try:
|
|
||||||
tz = tzfile(filepath)
|
|
||||||
break
|
|
||||||
except (IOError, OSError, ValueError):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
tz = tzlocal()
|
|
||||||
else:
|
|
||||||
if name.startswith(":"):
|
|
||||||
name = name[:-1]
|
|
||||||
if os.path.isabs(name):
|
|
||||||
if os.path.isfile(name):
|
|
||||||
tz = tzfile(name)
|
|
||||||
else:
|
|
||||||
tz = None
|
|
||||||
else:
|
|
||||||
for path in TZPATHS:
|
|
||||||
filepath = os.path.join(path, name)
|
|
||||||
if not os.path.isfile(filepath):
|
|
||||||
filepath = filepath.replace(' ','_')
|
|
||||||
if not os.path.isfile(filepath):
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
tz = tzfile(filepath)
|
|
||||||
break
|
|
||||||
except (IOError, OSError, ValueError):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
tz = None
|
|
||||||
if tzwin:
|
|
||||||
try:
|
|
||||||
tz = tzwin(name)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
if not tz:
|
|
||||||
from dateutil.zoneinfo import gettz
|
|
||||||
tz = gettz(name)
|
|
||||||
if not tz:
|
|
||||||
for c in name:
|
|
||||||
# name must have at least one offset to be a tzstr
|
|
||||||
if c in "0123456789":
|
|
||||||
try:
|
|
||||||
tz = tzstr(name)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if name in ("GMT", "UTC"):
|
|
||||||
tz = tzutc()
|
|
||||||
elif name in time.tzname:
|
|
||||||
tz = tzlocal()
|
|
||||||
return tz
|
|
||||||
|
|
||||||
# vim:ts=4:sw=4:et
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from .tz import *
|
||||||
|
from .tz import __doc__
|
||||||
|
|
||||||
|
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
||||||
|
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz",
|
||||||
|
"enfold", "datetime_ambiguous", "datetime_exists",
|
||||||
|
"resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"]
|
||||||
|
|
||||||
|
|
||||||
|
class DeprecatedTzFormatWarning(Warning):
|
||||||
|
"""Warning raised when time zones are parsed from deprecated formats."""
|
||||||
@@ -0,0 +1,419 @@
|
|||||||
|
from six import PY2
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta, tzinfo
|
||||||
|
|
||||||
|
|
||||||
|
ZERO = timedelta(0)
|
||||||
|
|
||||||
|
__all__ = ['tzname_in_python2', 'enfold']
|
||||||
|
|
||||||
|
|
||||||
|
def tzname_in_python2(namefunc):
|
||||||
|
"""Change unicode output into bytestrings in Python 2
|
||||||
|
|
||||||
|
tzname() API changed in Python 3. It used to return bytes, but was changed
|
||||||
|
to unicode strings
|
||||||
|
"""
|
||||||
|
if PY2:
|
||||||
|
@wraps(namefunc)
|
||||||
|
def adjust_encoding(*args, **kwargs):
|
||||||
|
name = namefunc(*args, **kwargs)
|
||||||
|
if name is not None:
|
||||||
|
name = name.encode()
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
||||||
|
return adjust_encoding
|
||||||
|
else:
|
||||||
|
return namefunc
|
||||||
|
|
||||||
|
|
||||||
|
# The following is adapted from Alexander Belopolsky's tz library
|
||||||
|
# https://github.com/abalkin/tz
|
||||||
|
if hasattr(datetime, 'fold'):
|
||||||
|
# This is the pre-python 3.6 fold situation
|
||||||
|
def enfold(dt, fold=1):
|
||||||
|
"""
|
||||||
|
Provides a unified interface for assigning the ``fold`` attribute to
|
||||||
|
datetimes both before and after the implementation of PEP-495.
|
||||||
|
|
||||||
|
:param fold:
|
||||||
|
The value for the ``fold`` attribute in the returned datetime. This
|
||||||
|
should be either 0 or 1.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns an object for which ``getattr(dt, 'fold', 0)`` returns
|
||||||
|
``fold`` for all versions of Python. In versions prior to
|
||||||
|
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
|
||||||
|
subclass of :py:class:`datetime.datetime` with the ``fold``
|
||||||
|
attribute added, if ``fold`` is 1.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
"""
|
||||||
|
return dt.replace(fold=fold)
|
||||||
|
|
||||||
|
else:
|
||||||
|
class _DatetimeWithFold(datetime):
|
||||||
|
"""
|
||||||
|
This is a class designed to provide a PEP 495-compliant interface for
|
||||||
|
Python versions before 3.6. It is used only for dates in a fold, so
|
||||||
|
the ``fold`` attribute is fixed at ``1``.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
"""
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def replace(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Return a datetime with the same attributes, except for those
|
||||||
|
attributes given new values by whichever keyword arguments are
|
||||||
|
specified. Note that tzinfo=None can be specified to create a naive
|
||||||
|
datetime from an aware datetime with no conversion of date and time
|
||||||
|
data.
|
||||||
|
|
||||||
|
This is reimplemented in ``_DatetimeWithFold`` because pypy3 will
|
||||||
|
return a ``datetime.datetime`` even if ``fold`` is unchanged.
|
||||||
|
"""
|
||||||
|
argnames = (
|
||||||
|
'year', 'month', 'day', 'hour', 'minute', 'second',
|
||||||
|
'microsecond', 'tzinfo'
|
||||||
|
)
|
||||||
|
|
||||||
|
for arg, argname in zip(args, argnames):
|
||||||
|
if argname in kwargs:
|
||||||
|
raise TypeError('Duplicate argument: {}'.format(argname))
|
||||||
|
|
||||||
|
kwargs[argname] = arg
|
||||||
|
|
||||||
|
for argname in argnames:
|
||||||
|
if argname not in kwargs:
|
||||||
|
kwargs[argname] = getattr(self, argname)
|
||||||
|
|
||||||
|
dt_class = self.__class__ if kwargs.get('fold', 1) else datetime
|
||||||
|
|
||||||
|
return dt_class(**kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fold(self):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def enfold(dt, fold=1):
|
||||||
|
"""
|
||||||
|
Provides a unified interface for assigning the ``fold`` attribute to
|
||||||
|
datetimes both before and after the implementation of PEP-495.
|
||||||
|
|
||||||
|
:param fold:
|
||||||
|
The value for the ``fold`` attribute in the returned datetime. This
|
||||||
|
should be either 0 or 1.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns an object for which ``getattr(dt, 'fold', 0)`` returns
|
||||||
|
``fold`` for all versions of Python. In versions prior to
|
||||||
|
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
|
||||||
|
subclass of :py:class:`datetime.datetime` with the ``fold``
|
||||||
|
attribute added, if ``fold`` is 1.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
"""
|
||||||
|
if getattr(dt, 'fold', 0) == fold:
|
||||||
|
return dt
|
||||||
|
|
||||||
|
args = dt.timetuple()[:6]
|
||||||
|
args += (dt.microsecond, dt.tzinfo)
|
||||||
|
|
||||||
|
if fold:
|
||||||
|
return _DatetimeWithFold(*args)
|
||||||
|
else:
|
||||||
|
return datetime(*args)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_fromutc_inputs(f):
|
||||||
|
"""
|
||||||
|
The CPython version of ``fromutc`` checks that the input is a ``datetime``
|
||||||
|
object and that ``self`` is attached as its ``tzinfo``.
|
||||||
|
"""
|
||||||
|
@wraps(f)
|
||||||
|
def fromutc(self, dt):
|
||||||
|
if not isinstance(dt, datetime):
|
||||||
|
raise TypeError("fromutc() requires a datetime argument")
|
||||||
|
if dt.tzinfo is not self:
|
||||||
|
raise ValueError("dt.tzinfo is not self")
|
||||||
|
|
||||||
|
return f(self, dt)
|
||||||
|
|
||||||
|
return fromutc
|
||||||
|
|
||||||
|
|
||||||
|
class _tzinfo(tzinfo):
|
||||||
|
"""
|
||||||
|
Base class for all ``dateutil`` ``tzinfo`` objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def is_ambiguous(self, dt):
|
||||||
|
"""
|
||||||
|
Whether or not the "wall time" of a given datetime is ambiguous in this
|
||||||
|
zone.
|
||||||
|
|
||||||
|
:param dt:
|
||||||
|
A :py:class:`datetime.datetime`, naive or time zone aware.
|
||||||
|
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns ``True`` if ambiguous, ``False`` otherwise.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
dt = dt.replace(tzinfo=self)
|
||||||
|
|
||||||
|
wall_0 = enfold(dt, fold=0)
|
||||||
|
wall_1 = enfold(dt, fold=1)
|
||||||
|
|
||||||
|
same_offset = wall_0.utcoffset() == wall_1.utcoffset()
|
||||||
|
same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None)
|
||||||
|
|
||||||
|
return same_dt and not same_offset
|
||||||
|
|
||||||
|
def _fold_status(self, dt_utc, dt_wall):
|
||||||
|
"""
|
||||||
|
Determine the fold status of a "wall" datetime, given a representation
|
||||||
|
of the same datetime as a (naive) UTC datetime. This is calculated based
|
||||||
|
on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all
|
||||||
|
datetimes, and that this offset is the actual number of hours separating
|
||||||
|
``dt_utc`` and ``dt_wall``.
|
||||||
|
|
||||||
|
:param dt_utc:
|
||||||
|
Representation of the datetime as UTC
|
||||||
|
|
||||||
|
:param dt_wall:
|
||||||
|
Representation of the datetime as "wall time". This parameter must
|
||||||
|
either have a `fold` attribute or have a fold-naive
|
||||||
|
:class:`datetime.tzinfo` attached, otherwise the calculation may
|
||||||
|
fail.
|
||||||
|
"""
|
||||||
|
if self.is_ambiguous(dt_wall):
|
||||||
|
delta_wall = dt_wall - dt_utc
|
||||||
|
_fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst()))
|
||||||
|
else:
|
||||||
|
_fold = 0
|
||||||
|
|
||||||
|
return _fold
|
||||||
|
|
||||||
|
def _fold(self, dt):
|
||||||
|
return getattr(dt, 'fold', 0)
|
||||||
|
|
||||||
|
def _fromutc(self, dt):
|
||||||
|
"""
|
||||||
|
Given a timezone-aware datetime in a given timezone, calculates a
|
||||||
|
timezone-aware datetime in a new timezone.
|
||||||
|
|
||||||
|
Since this is the one time that we *know* we have an unambiguous
|
||||||
|
datetime object, we take this opportunity to determine whether the
|
||||||
|
datetime is ambiguous and in a "fold" state (e.g. if it's the first
|
||||||
|
occurrence, chronologically, of the ambiguous datetime).
|
||||||
|
|
||||||
|
:param dt:
|
||||||
|
A timezone-aware :class:`datetime.datetime` object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Re-implement the algorithm from Python's datetime.py
|
||||||
|
dtoff = dt.utcoffset()
|
||||||
|
if dtoff is None:
|
||||||
|
raise ValueError("fromutc() requires a non-None utcoffset() "
|
||||||
|
"result")
|
||||||
|
|
||||||
|
# The original datetime.py code assumes that `dst()` defaults to
|
||||||
|
# zero during ambiguous times. PEP 495 inverts this presumption, so
|
||||||
|
# for pre-PEP 495 versions of python, we need to tweak the algorithm.
|
||||||
|
dtdst = dt.dst()
|
||||||
|
if dtdst is None:
|
||||||
|
raise ValueError("fromutc() requires a non-None dst() result")
|
||||||
|
delta = dtoff - dtdst
|
||||||
|
|
||||||
|
dt += delta
|
||||||
|
# Set fold=1 so we can default to being in the fold for
|
||||||
|
# ambiguous dates.
|
||||||
|
dtdst = enfold(dt, fold=1).dst()
|
||||||
|
if dtdst is None:
|
||||||
|
raise ValueError("fromutc(): dt.dst gave inconsistent "
|
||||||
|
"results; cannot convert")
|
||||||
|
return dt + dtdst
|
||||||
|
|
||||||
|
@_validate_fromutc_inputs
|
||||||
|
def fromutc(self, dt):
|
||||||
|
"""
|
||||||
|
Given a timezone-aware datetime in a given timezone, calculates a
|
||||||
|
timezone-aware datetime in a new timezone.
|
||||||
|
|
||||||
|
Since this is the one time that we *know* we have an unambiguous
|
||||||
|
datetime object, we take this opportunity to determine whether the
|
||||||
|
datetime is ambiguous and in a "fold" state (e.g. if it's the first
|
||||||
|
occurrence, chronologically, of the ambiguous datetime).
|
||||||
|
|
||||||
|
:param dt:
|
||||||
|
A timezone-aware :class:`datetime.datetime` object.
|
||||||
|
"""
|
||||||
|
dt_wall = self._fromutc(dt)
|
||||||
|
|
||||||
|
# Calculate the fold status given the two datetimes.
|
||||||
|
_fold = self._fold_status(dt, dt_wall)
|
||||||
|
|
||||||
|
# Set the default fold value for ambiguous dates
|
||||||
|
return enfold(dt_wall, fold=_fold)
|
||||||
|
|
||||||
|
|
||||||
|
class tzrangebase(_tzinfo):
|
||||||
|
"""
|
||||||
|
This is an abstract base class for time zones represented by an annual
|
||||||
|
transition into and out of DST. Child classes should implement the following
|
||||||
|
methods:
|
||||||
|
|
||||||
|
* ``__init__(self, *args, **kwargs)``
|
||||||
|
* ``transitions(self, year)`` - this is expected to return a tuple of
|
||||||
|
datetimes representing the DST on and off transitions in standard
|
||||||
|
time.
|
||||||
|
|
||||||
|
A fully initialized ``tzrangebase`` subclass should also provide the
|
||||||
|
following attributes:
|
||||||
|
* ``hasdst``: Boolean whether or not the zone uses DST.
|
||||||
|
* ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects
|
||||||
|
representing the respective UTC offsets.
|
||||||
|
* ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short
|
||||||
|
abbreviations in DST and STD, respectively.
|
||||||
|
* ``_hasdst``: Whether or not the zone has DST.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
raise NotImplementedError('tzrangebase is an abstract base class')
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
isdst = self._isdst(dt)
|
||||||
|
|
||||||
|
if isdst is None:
|
||||||
|
return None
|
||||||
|
elif isdst:
|
||||||
|
return self._dst_offset
|
||||||
|
else:
|
||||||
|
return self._std_offset
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
isdst = self._isdst(dt)
|
||||||
|
|
||||||
|
if isdst is None:
|
||||||
|
return None
|
||||||
|
elif isdst:
|
||||||
|
return self._dst_base_offset
|
||||||
|
else:
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
@tzname_in_python2
|
||||||
|
def tzname(self, dt):
|
||||||
|
if self._isdst(dt):
|
||||||
|
return self._dst_abbr
|
||||||
|
else:
|
||||||
|
return self._std_abbr
|
||||||
|
|
||||||
|
def fromutc(self, dt):
|
||||||
|
""" Given a datetime in UTC, return local time """
|
||||||
|
if not isinstance(dt, datetime):
|
||||||
|
raise TypeError("fromutc() requires a datetime argument")
|
||||||
|
|
||||||
|
if dt.tzinfo is not self:
|
||||||
|
raise ValueError("dt.tzinfo is not self")
|
||||||
|
|
||||||
|
# Get transitions - if there are none, fixed offset
|
||||||
|
transitions = self.transitions(dt.year)
|
||||||
|
if transitions is None:
|
||||||
|
return dt + self.utcoffset(dt)
|
||||||
|
|
||||||
|
# Get the transition times in UTC
|
||||||
|
dston, dstoff = transitions
|
||||||
|
|
||||||
|
dston -= self._std_offset
|
||||||
|
dstoff -= self._std_offset
|
||||||
|
|
||||||
|
utc_transitions = (dston, dstoff)
|
||||||
|
dt_utc = dt.replace(tzinfo=None)
|
||||||
|
|
||||||
|
isdst = self._naive_isdst(dt_utc, utc_transitions)
|
||||||
|
|
||||||
|
if isdst:
|
||||||
|
dt_wall = dt + self._dst_offset
|
||||||
|
else:
|
||||||
|
dt_wall = dt + self._std_offset
|
||||||
|
|
||||||
|
_fold = int(not isdst and self.is_ambiguous(dt_wall))
|
||||||
|
|
||||||
|
return enfold(dt_wall, fold=_fold)
|
||||||
|
|
||||||
|
def is_ambiguous(self, dt):
|
||||||
|
"""
|
||||||
|
Whether or not the "wall time" of a given datetime is ambiguous in this
|
||||||
|
zone.
|
||||||
|
|
||||||
|
:param dt:
|
||||||
|
A :py:class:`datetime.datetime`, naive or time zone aware.
|
||||||
|
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns ``True`` if ambiguous, ``False`` otherwise.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
"""
|
||||||
|
if not self.hasdst:
|
||||||
|
return False
|
||||||
|
|
||||||
|
start, end = self.transitions(dt.year)
|
||||||
|
|
||||||
|
dt = dt.replace(tzinfo=None)
|
||||||
|
return (end <= dt < end + self._dst_base_offset)
|
||||||
|
|
||||||
|
def _isdst(self, dt):
|
||||||
|
if not self.hasdst:
|
||||||
|
return False
|
||||||
|
elif dt is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
transitions = self.transitions(dt.year)
|
||||||
|
|
||||||
|
if transitions is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
dt = dt.replace(tzinfo=None)
|
||||||
|
|
||||||
|
isdst = self._naive_isdst(dt, transitions)
|
||||||
|
|
||||||
|
# Handle ambiguous dates
|
||||||
|
if not isdst and self.is_ambiguous(dt):
|
||||||
|
return not self._fold(dt)
|
||||||
|
else:
|
||||||
|
return isdst
|
||||||
|
|
||||||
|
def _naive_isdst(self, dt, transitions):
|
||||||
|
dston, dstoff = transitions
|
||||||
|
|
||||||
|
dt = dt.replace(tzinfo=None)
|
||||||
|
|
||||||
|
if dston < dstoff:
|
||||||
|
isdst = dston <= dt < dstoff
|
||||||
|
else:
|
||||||
|
isdst = not dstoff <= dt < dston
|
||||||
|
|
||||||
|
return isdst
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _dst_base_offset(self):
|
||||||
|
return self._dst_offset - self._std_offset
|
||||||
|
|
||||||
|
__hash__ = None
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not (self == other)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(...)" % self.__class__.__name__
|
||||||
|
|
||||||
|
__reduce__ = object.__reduce__
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
from datetime import timedelta
|
||||||
|
import weakref
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from six.moves import _thread
|
||||||
|
|
||||||
|
|
||||||
|
class _TzSingleton(type):
|
||||||
|
def __init__(cls, *args, **kwargs):
|
||||||
|
cls.__instance = None
|
||||||
|
super(_TzSingleton, cls).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def __call__(cls):
|
||||||
|
if cls.__instance is None:
|
||||||
|
cls.__instance = super(_TzSingleton, cls).__call__()
|
||||||
|
return cls.__instance
|
||||||
|
|
||||||
|
|
||||||
|
class _TzFactory(type):
|
||||||
|
def instance(cls, *args, **kwargs):
|
||||||
|
"""Alternate constructor that returns a fresh instance"""
|
||||||
|
return type.__call__(cls, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class _TzOffsetFactory(_TzFactory):
|
||||||
|
def __init__(cls, *args, **kwargs):
|
||||||
|
cls.__instances = weakref.WeakValueDictionary()
|
||||||
|
cls.__strong_cache = OrderedDict()
|
||||||
|
cls.__strong_cache_size = 8
|
||||||
|
|
||||||
|
cls._cache_lock = _thread.allocate_lock()
|
||||||
|
|
||||||
|
def __call__(cls, name, offset):
|
||||||
|
if isinstance(offset, timedelta):
|
||||||
|
key = (name, offset.total_seconds())
|
||||||
|
else:
|
||||||
|
key = (name, offset)
|
||||||
|
|
||||||
|
instance = cls.__instances.get(key, None)
|
||||||
|
if instance is None:
|
||||||
|
instance = cls.__instances.setdefault(key,
|
||||||
|
cls.instance(name, offset))
|
||||||
|
|
||||||
|
# This lock may not be necessary in Python 3. See GH issue #901
|
||||||
|
with cls._cache_lock:
|
||||||
|
cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
|
||||||
|
|
||||||
|
# Remove an item if the strong cache is overpopulated
|
||||||
|
if len(cls.__strong_cache) > cls.__strong_cache_size:
|
||||||
|
cls.__strong_cache.popitem(last=False)
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class _TzStrFactory(_TzFactory):
|
||||||
|
def __init__(cls, *args, **kwargs):
|
||||||
|
cls.__instances = weakref.WeakValueDictionary()
|
||||||
|
cls.__strong_cache = OrderedDict()
|
||||||
|
cls.__strong_cache_size = 8
|
||||||
|
|
||||||
|
cls.__cache_lock = _thread.allocate_lock()
|
||||||
|
|
||||||
|
def __call__(cls, s, posix_offset=False):
|
||||||
|
key = (s, posix_offset)
|
||||||
|
instance = cls.__instances.get(key, None)
|
||||||
|
|
||||||
|
if instance is None:
|
||||||
|
instance = cls.__instances.setdefault(key,
|
||||||
|
cls.instance(s, posix_offset))
|
||||||
|
|
||||||
|
# This lock may not be necessary in Python 3. See GH issue #901
|
||||||
|
with cls.__cache_lock:
|
||||||
|
cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
|
||||||
|
|
||||||
|
# Remove an item if the strong cache is overpopulated
|
||||||
|
if len(cls.__strong_cache) > cls.__strong_cache_size:
|
||||||
|
cls.__strong_cache.popitem(last=False)
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,370 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
This module provides an interface to the native time zone data on Windows,
|
||||||
|
including :py:class:`datetime.tzinfo` implementations.
|
||||||
|
|
||||||
|
Attempting to import this module on a non-Windows platform will raise an
|
||||||
|
:py:obj:`ImportError`.
|
||||||
|
"""
|
||||||
|
# This code was originally contributed by Jeffrey Harris.
|
||||||
|
import datetime
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from six.moves import winreg
|
||||||
|
from six import text_type
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
from ctypes import wintypes
|
||||||
|
except ValueError:
|
||||||
|
# ValueError is raised on non-Windows systems for some horrible reason.
|
||||||
|
raise ImportError("Running tzwin on non-Windows system")
|
||||||
|
|
||||||
|
from ._common import tzrangebase
|
||||||
|
|
||||||
|
__all__ = ["tzwin", "tzwinlocal", "tzres"]
|
||||||
|
|
||||||
|
ONEWEEK = datetime.timedelta(7)
|
||||||
|
|
||||||
|
TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
|
||||||
|
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
|
||||||
|
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
|
||||||
|
|
||||||
|
|
||||||
|
def _settzkeyname():
|
||||||
|
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
||||||
|
try:
|
||||||
|
winreg.OpenKey(handle, TZKEYNAMENT).Close()
|
||||||
|
TZKEYNAME = TZKEYNAMENT
|
||||||
|
except WindowsError:
|
||||||
|
TZKEYNAME = TZKEYNAME9X
|
||||||
|
handle.Close()
|
||||||
|
return TZKEYNAME
|
||||||
|
|
||||||
|
|
||||||
|
TZKEYNAME = _settzkeyname()
|
||||||
|
|
||||||
|
|
||||||
|
class tzres(object):
|
||||||
|
"""
|
||||||
|
Class for accessing ``tzres.dll``, which contains timezone name related
|
||||||
|
resources.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5.0
|
||||||
|
"""
|
||||||
|
p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char
|
||||||
|
|
||||||
|
def __init__(self, tzres_loc='tzres.dll'):
|
||||||
|
# Load the user32 DLL so we can load strings from tzres
|
||||||
|
user32 = ctypes.WinDLL('user32')
|
||||||
|
|
||||||
|
# Specify the LoadStringW function
|
||||||
|
user32.LoadStringW.argtypes = (wintypes.HINSTANCE,
|
||||||
|
wintypes.UINT,
|
||||||
|
wintypes.LPWSTR,
|
||||||
|
ctypes.c_int)
|
||||||
|
|
||||||
|
self.LoadStringW = user32.LoadStringW
|
||||||
|
self._tzres = ctypes.WinDLL(tzres_loc)
|
||||||
|
self.tzres_loc = tzres_loc
|
||||||
|
|
||||||
|
def load_name(self, offset):
|
||||||
|
"""
|
||||||
|
Load a timezone name from a DLL offset (integer).
|
||||||
|
|
||||||
|
>>> from dateutil.tzwin import tzres
|
||||||
|
>>> tzr = tzres()
|
||||||
|
>>> print(tzr.load_name(112))
|
||||||
|
'Eastern Standard Time'
|
||||||
|
|
||||||
|
:param offset:
|
||||||
|
A positive integer value referring to a string from the tzres dll.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Offsets found in the registry are generally of the form
|
||||||
|
``@tzres.dll,-114``. The offset in this case is 114, not -114.
|
||||||
|
|
||||||
|
"""
|
||||||
|
resource = self.p_wchar()
|
||||||
|
lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR)
|
||||||
|
nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0)
|
||||||
|
return resource[:nchar]
|
||||||
|
|
||||||
|
def name_from_string(self, tzname_str):
|
||||||
|
"""
|
||||||
|
Parse strings as returned from the Windows registry into the time zone
|
||||||
|
name as defined in the registry.
|
||||||
|
|
||||||
|
>>> from dateutil.tzwin import tzres
|
||||||
|
>>> tzr = tzres()
|
||||||
|
>>> print(tzr.name_from_string('@tzres.dll,-251'))
|
||||||
|
'Dateline Daylight Time'
|
||||||
|
>>> print(tzr.name_from_string('Eastern Standard Time'))
|
||||||
|
'Eastern Standard Time'
|
||||||
|
|
||||||
|
:param tzname_str:
|
||||||
|
A timezone name string as returned from a Windows registry key.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns the localized timezone string from tzres.dll if the string
|
||||||
|
is of the form `@tzres.dll,-offset`, else returns the input string.
|
||||||
|
"""
|
||||||
|
if not tzname_str.startswith('@'):
|
||||||
|
return tzname_str
|
||||||
|
|
||||||
|
name_splt = tzname_str.split(',-')
|
||||||
|
try:
|
||||||
|
offset = int(name_splt[1])
|
||||||
|
except:
|
||||||
|
raise ValueError("Malformed timezone string.")
|
||||||
|
|
||||||
|
return self.load_name(offset)
|
||||||
|
|
||||||
|
|
||||||
|
class tzwinbase(tzrangebase):
|
||||||
|
"""tzinfo class based on win32's timezones available in the registry."""
|
||||||
|
def __init__(self):
|
||||||
|
raise NotImplementedError('tzwinbase is an abstract base class')
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
# Compare on all relevant dimensions, including name.
|
||||||
|
if not isinstance(other, tzwinbase):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
return (self._std_offset == other._std_offset and
|
||||||
|
self._dst_offset == other._dst_offset and
|
||||||
|
self._stddayofweek == other._stddayofweek and
|
||||||
|
self._dstdayofweek == other._dstdayofweek and
|
||||||
|
self._stdweeknumber == other._stdweeknumber and
|
||||||
|
self._dstweeknumber == other._dstweeknumber and
|
||||||
|
self._stdhour == other._stdhour and
|
||||||
|
self._dsthour == other._dsthour and
|
||||||
|
self._stdminute == other._stdminute and
|
||||||
|
self._dstminute == other._dstminute and
|
||||||
|
self._std_abbr == other._std_abbr and
|
||||||
|
self._dst_abbr == other._dst_abbr)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def list():
|
||||||
|
"""Return a list of all time zones known to the system."""
|
||||||
|
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||||
|
with winreg.OpenKey(handle, TZKEYNAME) as tzkey:
|
||||||
|
result = [winreg.EnumKey(tzkey, i)
|
||||||
|
for i in range(winreg.QueryInfoKey(tzkey)[0])]
|
||||||
|
return result
|
||||||
|
|
||||||
|
def display(self):
|
||||||
|
"""
|
||||||
|
Return the display name of the time zone.
|
||||||
|
"""
|
||||||
|
return self._display
|
||||||
|
|
||||||
|
def transitions(self, year):
|
||||||
|
"""
|
||||||
|
For a given year, get the DST on and off transition times, expressed
|
||||||
|
always on the standard time side. For zones with no transitions, this
|
||||||
|
function returns ``None``.
|
||||||
|
|
||||||
|
:param year:
|
||||||
|
The year whose transitions you would like to query.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns a :class:`tuple` of :class:`datetime.datetime` objects,
|
||||||
|
``(dston, dstoff)`` for zones with an annual DST transition, or
|
||||||
|
``None`` for fixed offset zones.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.hasdst:
|
||||||
|
return None
|
||||||
|
|
||||||
|
dston = picknthweekday(year, self._dstmonth, self._dstdayofweek,
|
||||||
|
self._dsthour, self._dstminute,
|
||||||
|
self._dstweeknumber)
|
||||||
|
|
||||||
|
dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek,
|
||||||
|
self._stdhour, self._stdminute,
|
||||||
|
self._stdweeknumber)
|
||||||
|
|
||||||
|
# Ambiguous dates default to the STD side
|
||||||
|
dstoff -= self._dst_base_offset
|
||||||
|
|
||||||
|
return dston, dstoff
|
||||||
|
|
||||||
|
def _get_hasdst(self):
|
||||||
|
return self._dstmonth != 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _dst_base_offset(self):
|
||||||
|
return self._dst_base_offset_
|
||||||
|
|
||||||
|
|
||||||
|
class tzwin(tzwinbase):
|
||||||
|
"""
|
||||||
|
Time zone object created from the zone info in the Windows registry
|
||||||
|
|
||||||
|
These are similar to :py:class:`dateutil.tz.tzrange` objects in that
|
||||||
|
the time zone data is provided in the format of a single offset rule
|
||||||
|
for either 0 or 2 time zone transitions per year.
|
||||||
|
|
||||||
|
:param: name
|
||||||
|
The name of a Windows time zone key, e.g. "Eastern Standard Time".
|
||||||
|
The full list of keys can be retrieved with :func:`tzwin.list`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||||
|
tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name)
|
||||||
|
with winreg.OpenKey(handle, tzkeyname) as tzkey:
|
||||||
|
keydict = valuestodict(tzkey)
|
||||||
|
|
||||||
|
self._std_abbr = keydict["Std"]
|
||||||
|
self._dst_abbr = keydict["Dlt"]
|
||||||
|
|
||||||
|
self._display = keydict["Display"]
|
||||||
|
|
||||||
|
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
||||||
|
tup = struct.unpack("=3l16h", keydict["TZI"])
|
||||||
|
stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
|
||||||
|
dstoffset = stdoffset-tup[2] # + DaylightBias * -1
|
||||||
|
self._std_offset = datetime.timedelta(minutes=stdoffset)
|
||||||
|
self._dst_offset = datetime.timedelta(minutes=dstoffset)
|
||||||
|
|
||||||
|
# for the meaning see the win32 TIME_ZONE_INFORMATION structure docs
|
||||||
|
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
|
||||||
|
(self._stdmonth,
|
||||||
|
self._stddayofweek, # Sunday = 0
|
||||||
|
self._stdweeknumber, # Last = 5
|
||||||
|
self._stdhour,
|
||||||
|
self._stdminute) = tup[4:9]
|
||||||
|
|
||||||
|
(self._dstmonth,
|
||||||
|
self._dstdayofweek, # Sunday = 0
|
||||||
|
self._dstweeknumber, # Last = 5
|
||||||
|
self._dsthour,
|
||||||
|
self._dstminute) = tup[12:17]
|
||||||
|
|
||||||
|
self._dst_base_offset_ = self._dst_offset - self._std_offset
|
||||||
|
self.hasdst = self._get_hasdst()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "tzwin(%s)" % repr(self._name)
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
return (self.__class__, (self._name,))
|
||||||
|
|
||||||
|
|
||||||
|
class tzwinlocal(tzwinbase):
|
||||||
|
"""
|
||||||
|
Class representing the local time zone information in the Windows registry
|
||||||
|
|
||||||
|
While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time`
|
||||||
|
module) to retrieve time zone information, ``tzwinlocal`` retrieves the
|
||||||
|
rules directly from the Windows registry and creates an object like
|
||||||
|
:class:`dateutil.tz.tzwin`.
|
||||||
|
|
||||||
|
Because Windows does not have an equivalent of :func:`time.tzset`, on
|
||||||
|
Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the
|
||||||
|
time zone settings *at the time that the process was started*, meaning
|
||||||
|
changes to the machine's time zone settings during the run of a program
|
||||||
|
on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`.
|
||||||
|
Because ``tzwinlocal`` reads the registry directly, it is unaffected by
|
||||||
|
this issue.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||||
|
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
|
||||||
|
keydict = valuestodict(tzlocalkey)
|
||||||
|
|
||||||
|
self._std_abbr = keydict["StandardName"]
|
||||||
|
self._dst_abbr = keydict["DaylightName"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME,
|
||||||
|
sn=self._std_abbr)
|
||||||
|
with winreg.OpenKey(handle, tzkeyname) as tzkey:
|
||||||
|
_keydict = valuestodict(tzkey)
|
||||||
|
self._display = _keydict["Display"]
|
||||||
|
except OSError:
|
||||||
|
self._display = None
|
||||||
|
|
||||||
|
stdoffset = -keydict["Bias"]-keydict["StandardBias"]
|
||||||
|
dstoffset = stdoffset-keydict["DaylightBias"]
|
||||||
|
|
||||||
|
self._std_offset = datetime.timedelta(minutes=stdoffset)
|
||||||
|
self._dst_offset = datetime.timedelta(minutes=dstoffset)
|
||||||
|
|
||||||
|
# For reasons unclear, in this particular key, the day of week has been
|
||||||
|
# moved to the END of the SYSTEMTIME structure.
|
||||||
|
tup = struct.unpack("=8h", keydict["StandardStart"])
|
||||||
|
|
||||||
|
(self._stdmonth,
|
||||||
|
self._stdweeknumber, # Last = 5
|
||||||
|
self._stdhour,
|
||||||
|
self._stdminute) = tup[1:5]
|
||||||
|
|
||||||
|
self._stddayofweek = tup[7]
|
||||||
|
|
||||||
|
tup = struct.unpack("=8h", keydict["DaylightStart"])
|
||||||
|
|
||||||
|
(self._dstmonth,
|
||||||
|
self._dstweeknumber, # Last = 5
|
||||||
|
self._dsthour,
|
||||||
|
self._dstminute) = tup[1:5]
|
||||||
|
|
||||||
|
self._dstdayofweek = tup[7]
|
||||||
|
|
||||||
|
self._dst_base_offset_ = self._dst_offset - self._std_offset
|
||||||
|
self.hasdst = self._get_hasdst()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "tzwinlocal()"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
# str will return the standard name, not the daylight name.
|
||||||
|
return "tzwinlocal(%s)" % repr(self._std_abbr)
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
return (self.__class__, ())
|
||||||
|
|
||||||
|
|
||||||
|
def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
|
||||||
|
""" dayofweek == 0 means Sunday, whichweek 5 means last instance """
|
||||||
|
first = datetime.datetime(year, month, 1, hour, minute)
|
||||||
|
|
||||||
|
# This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6),
|
||||||
|
# Because 7 % 7 = 0
|
||||||
|
weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1)
|
||||||
|
wd = weekdayone + ((whichweek - 1) * ONEWEEK)
|
||||||
|
if (wd.month != month):
|
||||||
|
wd -= ONEWEEK
|
||||||
|
|
||||||
|
return wd
|
||||||
|
|
||||||
|
|
||||||
|
def valuestodict(key):
|
||||||
|
"""Convert a registry key's values to a dictionary."""
|
||||||
|
dout = {}
|
||||||
|
size = winreg.QueryInfoKey(key)[1]
|
||||||
|
tz_res = None
|
||||||
|
|
||||||
|
for i in range(size):
|
||||||
|
key_name, value, dtype = winreg.EnumValue(key, i)
|
||||||
|
if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN:
|
||||||
|
# If it's a DWORD (32-bit integer), it's stored as unsigned - convert
|
||||||
|
# that to a proper signed integer
|
||||||
|
if value & (1 << 31):
|
||||||
|
value = value - (1 << 32)
|
||||||
|
elif dtype == winreg.REG_SZ:
|
||||||
|
# If it's a reference to the tzres DLL, load the actual string
|
||||||
|
if value.startswith('@tzres'):
|
||||||
|
tz_res = tz_res or tzres()
|
||||||
|
value = tz_res.name_from_string(value)
|
||||||
|
|
||||||
|
value = value.rstrip('\x00') # Remove trailing nulls
|
||||||
|
|
||||||
|
dout[key_name] = value
|
||||||
|
|
||||||
|
return dout
|
||||||
+2
-180
@@ -1,180 +1,2 @@
|
|||||||
# This code was originally contributed by Jeffrey Harris.
|
# tzwin has moved to dateutil.tz.win
|
||||||
import datetime
|
from .tz.win import *
|
||||||
import struct
|
|
||||||
import _winreg
|
|
||||||
|
|
||||||
__author__ = "Jeffrey Harris & Gustavo Niemeyer <gustavo@niemeyer.net>"
|
|
||||||
|
|
||||||
__all__ = ["tzwin", "tzwinlocal"]
|
|
||||||
|
|
||||||
ONEWEEK = datetime.timedelta(7)
|
|
||||||
|
|
||||||
TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
|
|
||||||
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
|
|
||||||
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
|
|
||||||
|
|
||||||
def _settzkeyname():
|
|
||||||
global TZKEYNAME
|
|
||||||
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
|
|
||||||
try:
|
|
||||||
_winreg.OpenKey(handle, TZKEYNAMENT).Close()
|
|
||||||
TZKEYNAME = TZKEYNAMENT
|
|
||||||
except WindowsError:
|
|
||||||
TZKEYNAME = TZKEYNAME9X
|
|
||||||
handle.Close()
|
|
||||||
|
|
||||||
_settzkeyname()
|
|
||||||
|
|
||||||
class tzwinbase(datetime.tzinfo):
|
|
||||||
"""tzinfo class based on win32's timezones available in the registry."""
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
|
||||||
if self._isdst(dt):
|
|
||||||
return datetime.timedelta(minutes=self._dstoffset)
|
|
||||||
else:
|
|
||||||
return datetime.timedelta(minutes=self._stdoffset)
|
|
||||||
|
|
||||||
def dst(self, dt):
|
|
||||||
if self._isdst(dt):
|
|
||||||
minutes = self._dstoffset - self._stdoffset
|
|
||||||
return datetime.timedelta(minutes=minutes)
|
|
||||||
else:
|
|
||||||
return datetime.timedelta(0)
|
|
||||||
|
|
||||||
def tzname(self, dt):
|
|
||||||
if self._isdst(dt):
|
|
||||||
return self._dstname
|
|
||||||
else:
|
|
||||||
return self._stdname
|
|
||||||
|
|
||||||
def list():
|
|
||||||
"""Return a list of all time zones known to the system."""
|
|
||||||
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
|
|
||||||
tzkey = _winreg.OpenKey(handle, TZKEYNAME)
|
|
||||||
result = [_winreg.EnumKey(tzkey, i)
|
|
||||||
for i in range(_winreg.QueryInfoKey(tzkey)[0])]
|
|
||||||
tzkey.Close()
|
|
||||||
handle.Close()
|
|
||||||
return result
|
|
||||||
list = staticmethod(list)
|
|
||||||
|
|
||||||
def display(self):
|
|
||||||
return self._display
|
|
||||||
|
|
||||||
def _isdst(self, dt):
|
|
||||||
dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek,
|
|
||||||
self._dsthour, self._dstminute,
|
|
||||||
self._dstweeknumber)
|
|
||||||
dstoff = picknthweekday(dt.year, self._stdmonth, self._stddayofweek,
|
|
||||||
self._stdhour, self._stdminute,
|
|
||||||
self._stdweeknumber)
|
|
||||||
if dston < dstoff:
|
|
||||||
return dston <= dt.replace(tzinfo=None) < dstoff
|
|
||||||
else:
|
|
||||||
return not dstoff <= dt.replace(tzinfo=None) < dston
|
|
||||||
|
|
||||||
|
|
||||||
class tzwin(tzwinbase):
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
self._name = name
|
|
||||||
|
|
||||||
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
|
|
||||||
tzkey = _winreg.OpenKey(handle, "%s\%s" % (TZKEYNAME, name))
|
|
||||||
keydict = valuestodict(tzkey)
|
|
||||||
tzkey.Close()
|
|
||||||
handle.Close()
|
|
||||||
|
|
||||||
self._stdname = keydict["Std"].encode("iso-8859-1")
|
|
||||||
self._dstname = keydict["Dlt"].encode("iso-8859-1")
|
|
||||||
|
|
||||||
self._display = keydict["Display"]
|
|
||||||
|
|
||||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
|
||||||
tup = struct.unpack("=3l16h", keydict["TZI"])
|
|
||||||
self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
|
|
||||||
self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1
|
|
||||||
|
|
||||||
(self._stdmonth,
|
|
||||||
self._stddayofweek, # Sunday = 0
|
|
||||||
self._stdweeknumber, # Last = 5
|
|
||||||
self._stdhour,
|
|
||||||
self._stdminute) = tup[4:9]
|
|
||||||
|
|
||||||
(self._dstmonth,
|
|
||||||
self._dstdayofweek, # Sunday = 0
|
|
||||||
self._dstweeknumber, # Last = 5
|
|
||||||
self._dsthour,
|
|
||||||
self._dstminute) = tup[12:17]
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "tzwin(%s)" % repr(self._name)
|
|
||||||
|
|
||||||
def __reduce__(self):
|
|
||||||
return (self.__class__, (self._name,))
|
|
||||||
|
|
||||||
|
|
||||||
class tzwinlocal(tzwinbase):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
|
|
||||||
|
|
||||||
tzlocalkey = _winreg.OpenKey(handle, TZLOCALKEYNAME)
|
|
||||||
keydict = valuestodict(tzlocalkey)
|
|
||||||
tzlocalkey.Close()
|
|
||||||
|
|
||||||
self._stdname = keydict["StandardName"].encode("iso-8859-1")
|
|
||||||
self._dstname = keydict["DaylightName"].encode("iso-8859-1")
|
|
||||||
|
|
||||||
try:
|
|
||||||
tzkey = _winreg.OpenKey(handle, "%s\%s"%(TZKEYNAME, self._stdname))
|
|
||||||
_keydict = valuestodict(tzkey)
|
|
||||||
self._display = _keydict["Display"]
|
|
||||||
tzkey.Close()
|
|
||||||
except OSError:
|
|
||||||
self._display = None
|
|
||||||
|
|
||||||
handle.Close()
|
|
||||||
|
|
||||||
self._stdoffset = -keydict["Bias"]-keydict["StandardBias"]
|
|
||||||
self._dstoffset = self._stdoffset-keydict["DaylightBias"]
|
|
||||||
|
|
||||||
|
|
||||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
|
||||||
tup = struct.unpack("=8h", keydict["StandardStart"])
|
|
||||||
|
|
||||||
(self._stdmonth,
|
|
||||||
self._stddayofweek, # Sunday = 0
|
|
||||||
self._stdweeknumber, # Last = 5
|
|
||||||
self._stdhour,
|
|
||||||
self._stdminute) = tup[1:6]
|
|
||||||
|
|
||||||
tup = struct.unpack("=8h", keydict["DaylightStart"])
|
|
||||||
|
|
||||||
(self._dstmonth,
|
|
||||||
self._dstdayofweek, # Sunday = 0
|
|
||||||
self._dstweeknumber, # Last = 5
|
|
||||||
self._dsthour,
|
|
||||||
self._dstminute) = tup[1:6]
|
|
||||||
|
|
||||||
def __reduce__(self):
|
|
||||||
return (self.__class__, ())
|
|
||||||
|
|
||||||
def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
|
|
||||||
"""dayofweek == 0 means Sunday, whichweek 5 means last instance"""
|
|
||||||
first = datetime.datetime(year, month, 1, hour, minute)
|
|
||||||
weekdayone = first.replace(day=((dayofweek-first.isoweekday())%7+1))
|
|
||||||
for n in xrange(whichweek):
|
|
||||||
dt = weekdayone+(whichweek-n)*ONEWEEK
|
|
||||||
if dt.month == month:
|
|
||||||
return dt
|
|
||||||
|
|
||||||
def valuestodict(key):
|
|
||||||
"""Convert a registry key's values to a dictionary."""
|
|
||||||
dict = {}
|
|
||||||
size = _winreg.QueryInfoKey(key)[1]
|
|
||||||
for i in range(size):
|
|
||||||
data = _winreg.EnumValue(key, i)
|
|
||||||
dict[data[0]] = data[1]
|
|
||||||
return dict
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
This module offers general convenience and utility functions for dealing with
|
||||||
|
datetimes.
|
||||||
|
|
||||||
|
.. versionadded:: 2.7.0
|
||||||
|
"""
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from datetime import datetime, time
|
||||||
|
|
||||||
|
|
||||||
|
def today(tzinfo=None):
|
||||||
|
"""
|
||||||
|
Returns a :py:class:`datetime` representing the current day at midnight
|
||||||
|
|
||||||
|
:param tzinfo:
|
||||||
|
The time zone to attach (also used to determine the current day).
|
||||||
|
|
||||||
|
:return:
|
||||||
|
A :py:class:`datetime.datetime` object representing the current day
|
||||||
|
at midnight.
|
||||||
|
"""
|
||||||
|
|
||||||
|
dt = datetime.now(tzinfo)
|
||||||
|
return datetime.combine(dt.date(), time(0, tzinfo=tzinfo))
|
||||||
|
|
||||||
|
|
||||||
|
def default_tzinfo(dt, tzinfo):
|
||||||
|
"""
|
||||||
|
Sets the ``tzinfo`` parameter on naive datetimes only
|
||||||
|
|
||||||
|
This is useful for example when you are provided a datetime that may have
|
||||||
|
either an implicit or explicit time zone, such as when parsing a time zone
|
||||||
|
string.
|
||||||
|
|
||||||
|
.. doctest::
|
||||||
|
|
||||||
|
>>> from dateutil.tz import tzoffset
|
||||||
|
>>> from dateutil.parser import parse
|
||||||
|
>>> from dateutil.utils import default_tzinfo
|
||||||
|
>>> dflt_tz = tzoffset("EST", -18000)
|
||||||
|
>>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz))
|
||||||
|
2014-01-01 12:30:00+00:00
|
||||||
|
>>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz))
|
||||||
|
2014-01-01 12:30:00-05:00
|
||||||
|
|
||||||
|
:param dt:
|
||||||
|
The datetime on which to replace the time zone
|
||||||
|
|
||||||
|
:param tzinfo:
|
||||||
|
The :py:class:`datetime.tzinfo` subclass instance to assign to
|
||||||
|
``dt`` if (and only if) it is naive.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns an aware :py:class:`datetime.datetime`.
|
||||||
|
"""
|
||||||
|
if dt.tzinfo is not None:
|
||||||
|
return dt
|
||||||
|
else:
|
||||||
|
return dt.replace(tzinfo=tzinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def within_delta(dt1, dt2, delta):
|
||||||
|
"""
|
||||||
|
Useful for comparing two datetimes that may a negilible difference
|
||||||
|
to be considered equal.
|
||||||
|
"""
|
||||||
|
delta = abs(delta)
|
||||||
|
difference = dt1 - dt2
|
||||||
|
return -delta <= difference <= delta
|
||||||
@@ -1,85 +1,167 @@
|
|||||||
"""
|
# -*- coding: utf-8 -*-
|
||||||
Copyright (c) 2003-2005 Gustavo Niemeyer <gustavo@niemeyer.net>
|
import warnings
|
||||||
|
import json
|
||||||
|
|
||||||
This module offers extensions to the standard python 2.3+
|
|
||||||
datetime module.
|
|
||||||
"""
|
|
||||||
from dateutil.tz import tzfile
|
|
||||||
from tarfile import TarFile
|
from tarfile import TarFile
|
||||||
import os
|
from pkgutil import get_data
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
from dateutil.tz import tzfile as _tzfile
|
||||||
__license__ = "PSF License"
|
|
||||||
|
|
||||||
__all__ = ["setcachesize", "gettz", "rebuild"]
|
__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"]
|
||||||
|
|
||||||
CACHE = {}
|
ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
|
||||||
|
METADATA_FN = 'METADATA'
|
||||||
|
|
||||||
class tzfile(tzfile):
|
|
||||||
|
class tzfile(_tzfile):
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
return (gettz, (self._filename,))
|
return (gettz, (self._filename,))
|
||||||
|
|
||||||
def getzoneinfofile():
|
|
||||||
filenames = os.listdir(os.path.join(os.path.dirname(__file__)))
|
|
||||||
filenames.sort()
|
|
||||||
filenames.reverse()
|
|
||||||
for entry in filenames:
|
|
||||||
if entry.startswith("zoneinfo") and ".tar." in entry:
|
|
||||||
return os.path.join(os.path.dirname(__file__), entry)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def buildcache():
|
def getzoneinfofile_stream():
|
||||||
global CACHE
|
try:
|
||||||
zoneinfofile = getzoneinfofile()
|
return BytesIO(get_data(__name__, ZONEFILENAME))
|
||||||
if zoneinfofile:
|
except IOError as e: # TODO switch to FileNotFoundError?
|
||||||
tf = TarFile.open(zoneinfofile)
|
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
|
||||||
try:
|
return None
|
||||||
for tarinfo in tf.getmembers():
|
|
||||||
if tarinfo.islnk() or tarinfo.isfile():
|
|
||||||
zonefile = tf.extractfile(tarinfo)
|
|
||||||
CACHE[tarinfo.name] = tzfile(zonefile)
|
|
||||||
finally:
|
|
||||||
tf.close()
|
|
||||||
|
|
||||||
buildcache()
|
|
||||||
|
|
||||||
del getzoneinfofile
|
class ZoneInfoFile(object):
|
||||||
del buildcache
|
def __init__(self, zonefile_stream=None):
|
||||||
|
if zonefile_stream is not None:
|
||||||
|
with TarFile.open(fileobj=zonefile_stream) as tf:
|
||||||
|
self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name)
|
||||||
|
for zf in tf.getmembers()
|
||||||
|
if zf.isfile() and zf.name != METADATA_FN}
|
||||||
|
# deal with links: They'll point to their parent object. Less
|
||||||
|
# waste of memory
|
||||||
|
links = {zl.name: self.zones[zl.linkname]
|
||||||
|
for zl in tf.getmembers() if
|
||||||
|
zl.islnk() or zl.issym()}
|
||||||
|
self.zones.update(links)
|
||||||
|
try:
|
||||||
|
metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
|
||||||
|
metadata_str = metadata_json.read().decode('UTF-8')
|
||||||
|
self.metadata = json.loads(metadata_str)
|
||||||
|
except KeyError:
|
||||||
|
# no metadata in tar file
|
||||||
|
self.metadata = None
|
||||||
|
else:
|
||||||
|
self.zones = {}
|
||||||
|
self.metadata = None
|
||||||
|
|
||||||
|
def get(self, name, default=None):
|
||||||
|
"""
|
||||||
|
Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method
|
||||||
|
for retrieving zones from the zone dictionary.
|
||||||
|
|
||||||
|
:param name:
|
||||||
|
The name of the zone to retrieve. (Generally IANA zone names)
|
||||||
|
|
||||||
|
:param default:
|
||||||
|
The value to return in the event of a missing key.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.zones.get(name, default)
|
||||||
|
|
||||||
|
|
||||||
|
# The current API has gettz as a module function, although in fact it taps into
|
||||||
|
# a stateful class. So as a workaround for now, without changing the API, we
|
||||||
|
# will create a new "global" class instance the first time a user requests a
|
||||||
|
# timezone. Ugly, but adheres to the api.
|
||||||
|
#
|
||||||
|
# TODO: Remove after deprecation period.
|
||||||
|
_CLASS_ZONE_INSTANCE = []
|
||||||
|
|
||||||
|
|
||||||
|
def get_zonefile_instance(new_instance=False):
|
||||||
|
"""
|
||||||
|
This is a convenience function which provides a :class:`ZoneInfoFile`
|
||||||
|
instance using the data provided by the ``dateutil`` package. By default, it
|
||||||
|
caches a single instance of the ZoneInfoFile object and returns that.
|
||||||
|
|
||||||
|
:param new_instance:
|
||||||
|
If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and
|
||||||
|
used as the cached instance for the next call. Otherwise, new instances
|
||||||
|
are created only as necessary.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns a :class:`ZoneInfoFile` object.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
"""
|
||||||
|
if new_instance:
|
||||||
|
zif = None
|
||||||
|
else:
|
||||||
|
zif = getattr(get_zonefile_instance, '_cached_instance', None)
|
||||||
|
|
||||||
|
if zif is None:
|
||||||
|
zif = ZoneInfoFile(getzoneinfofile_stream())
|
||||||
|
|
||||||
|
get_zonefile_instance._cached_instance = zif
|
||||||
|
|
||||||
|
return zif
|
||||||
|
|
||||||
def setcachesize(_):
|
|
||||||
# Since the cache now eagerly initialized at
|
|
||||||
# import time, there's no point in controlling
|
|
||||||
# its size.
|
|
||||||
pass
|
|
||||||
|
|
||||||
def gettz(name):
|
def gettz(name):
|
||||||
return CACHE.get(name)
|
"""
|
||||||
|
This retrieves a time zone from the local zoneinfo tarball that is packaged
|
||||||
|
with dateutil.
|
||||||
|
|
||||||
def rebuild(filename, tag=None, format="gz"):
|
:param name:
|
||||||
import tempfile, shutil
|
An IANA-style time zone name, as found in the zoneinfo file.
|
||||||
tmpdir = tempfile.mkdtemp()
|
|
||||||
zonedir = os.path.join(tmpdir, "zoneinfo")
|
:return:
|
||||||
moduledir = os.path.dirname(__file__)
|
Returns a :class:`dateutil.tz.tzfile` time zone object.
|
||||||
if tag: tag = "-"+tag
|
|
||||||
targetname = "zoneinfo%s.tar.%s" % (tag, format)
|
.. warning::
|
||||||
try:
|
It is generally inadvisable to use this function, and it is only
|
||||||
tf = TarFile.open(filename)
|
provided for API compatibility with earlier versions. This is *not*
|
||||||
for name in tf.getnames():
|
equivalent to ``dateutil.tz.gettz()``, which selects an appropriate
|
||||||
if not (name.endswith(".sh") or
|
time zone based on the inputs, favoring system zoneinfo. This is ONLY
|
||||||
name.endswith(".tab") or
|
for accessing the dateutil-specific zoneinfo (which may be out of
|
||||||
name == "leapseconds"):
|
date compared to the system zoneinfo).
|
||||||
tf.extract(name, tmpdir)
|
|
||||||
filepath = os.path.join(tmpdir, name)
|
.. deprecated:: 2.6
|
||||||
os.system("zic -d %s %s" % (zonedir, filepath))
|
If you need to use a specific zoneinfofile over the system zoneinfo,
|
||||||
tf.close()
|
instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call
|
||||||
target = os.path.join(moduledir, targetname)
|
:func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead.
|
||||||
for entry in os.listdir(moduledir):
|
|
||||||
if entry.startswith("zoneinfo") and ".tar." in entry:
|
Use :func:`get_zonefile_instance` to retrieve an instance of the
|
||||||
os.unlink(os.path.join(moduledir, entry))
|
dateutil-provided zoneinfo.
|
||||||
tf = TarFile.open(target, "w:%s" % format)
|
"""
|
||||||
for entry in os.listdir(zonedir):
|
warnings.warn("zoneinfo.gettz() will be removed in future versions, "
|
||||||
entrypath = os.path.join(zonedir, entry)
|
"to use the dateutil-provided zoneinfo files, instantiate a "
|
||||||
tf.add(entrypath, entry)
|
"ZoneInfoFile object and use ZoneInfoFile.zones.get() "
|
||||||
tf.close()
|
"instead. See the documentation for details.",
|
||||||
finally:
|
DeprecationWarning)
|
||||||
shutil.rmtree(tmpdir)
|
|
||||||
|
if len(_CLASS_ZONE_INSTANCE) == 0:
|
||||||
|
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
||||||
|
return _CLASS_ZONE_INSTANCE[0].zones.get(name)
|
||||||
|
|
||||||
|
|
||||||
|
def gettz_db_metadata():
|
||||||
|
""" Get the zonefile metadata
|
||||||
|
|
||||||
|
See `zonefile_metadata`_
|
||||||
|
|
||||||
|
:returns:
|
||||||
|
A dictionary with the database metadata
|
||||||
|
|
||||||
|
.. deprecated:: 2.6
|
||||||
|
See deprecation warning in :func:`zoneinfo.gettz`. To get metadata,
|
||||||
|
query the attribute ``zoneinfo.ZoneInfoFile.metadata``.
|
||||||
|
"""
|
||||||
|
warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future "
|
||||||
|
"versions, to use the dateutil-provided zoneinfo files, "
|
||||||
|
"ZoneInfoFile object and query the 'metadata' attribute "
|
||||||
|
"instead. See the documentation for details.",
|
||||||
|
DeprecationWarning)
|
||||||
|
|
||||||
|
if len(_CLASS_ZONE_INSTANCE) == 0:
|
||||||
|
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
||||||
|
return _CLASS_ZONE_INSTANCE[0].metadata
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,53 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
import json
|
||||||
|
from subprocess import check_call
|
||||||
|
from tarfile import TarFile
|
||||||
|
|
||||||
|
from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME
|
||||||
|
|
||||||
|
|
||||||
|
def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
|
||||||
|
"""Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*
|
||||||
|
|
||||||
|
filename is the timezone tarball from ``ftp.iana.org/tz``.
|
||||||
|
|
||||||
|
"""
|
||||||
|
tmpdir = tempfile.mkdtemp()
|
||||||
|
zonedir = os.path.join(tmpdir, "zoneinfo")
|
||||||
|
moduledir = os.path.dirname(__file__)
|
||||||
|
try:
|
||||||
|
with TarFile.open(filename) as tf:
|
||||||
|
for name in zonegroups:
|
||||||
|
tf.extract(name, tmpdir)
|
||||||
|
filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
|
||||||
|
try:
|
||||||
|
check_call(["zic", "-d", zonedir] + filepaths)
|
||||||
|
except OSError as e:
|
||||||
|
_print_on_nosuchfile(e)
|
||||||
|
raise
|
||||||
|
# write metadata file
|
||||||
|
with open(os.path.join(zonedir, METADATA_FN), 'w') as f:
|
||||||
|
json.dump(metadata, f, indent=4, sort_keys=True)
|
||||||
|
target = os.path.join(moduledir, ZONEFILENAME)
|
||||||
|
with TarFile.open(target, "w:%s" % format) as tf:
|
||||||
|
for entry in os.listdir(zonedir):
|
||||||
|
entrypath = os.path.join(zonedir, entry)
|
||||||
|
tf.add(entrypath, entry)
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(tmpdir)
|
||||||
|
|
||||||
|
|
||||||
|
def _print_on_nosuchfile(e):
|
||||||
|
"""Print helpful troubleshooting message
|
||||||
|
|
||||||
|
e is an exception raised by subprocess.check_call()
|
||||||
|
|
||||||
|
"""
|
||||||
|
if e.errno == 2:
|
||||||
|
logging.error(
|
||||||
|
"Could not find zic. Perhaps you need to install "
|
||||||
|
"libc-bin or some other package that provides it, "
|
||||||
|
"or it's not in your PATH?")
|
||||||
Binary file not shown.
@@ -41,7 +41,7 @@ def download_and_play(url, file_name, download_path):
|
|||||||
dialog.update(0)
|
dialog.update(0)
|
||||||
|
|
||||||
while not cancelled and download_thread.isAlive():
|
while not cancelled and download_thread.isAlive():
|
||||||
dialog.update(download_thread.get_progress(), config.get_localized_string(60313),
|
dialog.update(download_thread.get_progress(), config.get_localized_string(60313) + '\n' +
|
||||||
config.get_localized_string(60314) + str(int(old_div(download_thread.get_speed(), 1024))) + " KB/s " + str(
|
config.get_localized_string(60314) + str(int(old_div(download_thread.get_speed(), 1024))) + " KB/s " + str(
|
||||||
download_thread.get_actual_size()) + config.get_localized_string(60316) + str( download_thread.get_total_size()) + "MB",
|
download_thread.get_actual_size()) + config.get_localized_string(60316) + str( download_thread.get_total_size()) + "MB",
|
||||||
config.get_localized_string(60202) % (str(downloadtools.sec_to_hms(download_thread.get_remaining_time()))))
|
config.get_localized_string(60202) % (str(downloadtools.sec_to_hms(download_thread.get_remaining_time()))))
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ def updateFromZip(message=config.get_localized_string(80050)):
|
|||||||
if os.path.isfile(localfilename):
|
if os.path.isfile(localfilename):
|
||||||
logger.info('il file esiste')
|
logger.info('il file esiste')
|
||||||
|
|
||||||
dp.update(80, config.get_localized_string(20000), config.get_localized_string(80032))
|
dp.update(80, config.get_localized_string(20000) + '\n' + config.get_localized_string(80032))
|
||||||
|
|
||||||
import zipfile
|
import zipfile
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -843,7 +843,7 @@ def update_db(old_path, new_path, old_movies_folder, new_movies_folder, old_tvsh
|
|||||||
return
|
return
|
||||||
|
|
||||||
p = 80
|
p = 80
|
||||||
progress.update(p, config.get_localized_string(20000), config.get_localized_string(80013))
|
progress.update(p, config.get_localized_string(20000) + '\n' + config.get_localized_string(80013))
|
||||||
|
|
||||||
for OldFolder, NewFolder in [[old_movies_folder, new_movies_folder], [old_tvshows_folder, new_tvshows_folder]]:
|
for OldFolder, NewFolder in [[old_movies_folder, new_movies_folder], [old_tvshows_folder, new_tvshows_folder]]:
|
||||||
sql_old_folder = sql_old_path + OldFolder
|
sql_old_folder = sql_old_path + OldFolder
|
||||||
@@ -906,7 +906,7 @@ def update_db(old_path, new_path, old_movies_folder, new_movies_folder, old_tvsh
|
|||||||
logger.info('sql: ' + sql)
|
logger.info('sql: ' + sql)
|
||||||
nun_records, records = execute_sql_kodi(sql)
|
nun_records, records = execute_sql_kodi(sql)
|
||||||
p += 5
|
p += 5
|
||||||
progress.update(p, config.get_localized_string(20000), config.get_localized_string(80013))
|
progress.update(p, config.get_localized_string(20000) + '\n' + config.get_localized_string(80013))
|
||||||
|
|
||||||
progress.update(100)
|
progress.update(100)
|
||||||
xbmc.sleep(1000)
|
xbmc.sleep(1000)
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
"active": true,
|
|
||||||
"find_videos": {
|
|
||||||
"ignore_urls": [],
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"pattern": "https://hdplayer\\.casa//public/dist/index\\.html\\?id=([a-z0-9]+)",
|
|
||||||
"url": "https://hdplayer.casa/public/dist/index.html?id=\\1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"free": true,
|
|
||||||
"id": "hdplayer",
|
|
||||||
"name": "HDPlayer",
|
|
||||||
"settings": [
|
|
||||||
{
|
|
||||||
"default": false,
|
|
||||||
"enabled": true,
|
|
||||||
"id": "black_list",
|
|
||||||
"label": "@60654",
|
|
||||||
"type": "bool",
|
|
||||||
"visible": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": 0,
|
|
||||||
"enabled": true,
|
|
||||||
"id": "favorites_servers_list",
|
|
||||||
"label": "@60655",
|
|
||||||
"lvalues": [
|
|
||||||
"No",
|
|
||||||
"1",
|
|
||||||
"2",
|
|
||||||
"3",
|
|
||||||
"4",
|
|
||||||
"5"
|
|
||||||
],
|
|
||||||
"type": "list",
|
|
||||||
"visible": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
from core import httptools
|
|
||||||
from core import scrapertools
|
|
||||||
from platformcode import config
|
|
||||||
|
|
||||||
|
|
||||||
def test_video_exists(page_url):
|
|
||||||
stream_url = get_stream_url(page_url)
|
|
||||||
data = httptools.downloadpage(stream_url).data
|
|
||||||
if "Error Playlist" in data:
|
|
||||||
return False, config.get_localized_string(70449) % "HDPlayer"
|
|
||||||
return True, ""
|
|
||||||
|
|
||||||
def get_stream_url(url):
|
|
||||||
id = scrapertools.find_single_match(url, 'id=([a-z0-9]+)')
|
|
||||||
return 'https://hdplayer.casa/hls/' + id + '/' + id + '.playlist.m3u8'
|
|
||||||
|
|
||||||
def get_video_url(page_url, premium=False, user="", password="", video_password=""):
|
|
||||||
return [('.m3u8', get_stream_url(page_url))]
|
|
||||||
@@ -653,7 +653,7 @@ def download_from_server(item):
|
|||||||
channel = __import__('channels.%s' % item.contentChannel, None, None, ['channels.%s' % item.contentChannel])
|
channel = __import__('channels.%s' % item.contentChannel, None, None, ['channels.%s' % item.contentChannel])
|
||||||
if hasattr(channel, "play") and not item.play_menu:
|
if hasattr(channel, "play") and not item.play_menu:
|
||||||
|
|
||||||
progreso.update(50, config.get_localized_string(70178) % item.server, config.get_localized_string(70180) % item.contentChannel)
|
progreso.update(50, config.get_localized_string(70178) % item.server + '\n' + config.get_localized_string(70180) % item.contentChannel)
|
||||||
try:
|
try:
|
||||||
itemlist = getattr(channel, "play")(item.clone(channel=item.contentChannel, action=item.contentAction))
|
itemlist = getattr(channel, "play")(item.clone(channel=item.contentChannel, action=item.contentAction))
|
||||||
except:
|
except:
|
||||||
@@ -732,7 +732,7 @@ def download_from_best_server(item):
|
|||||||
else:
|
else:
|
||||||
channel = __import__('channels.%s' % item.contentChannel, None, None, ['channels.%s' % item.contentChannel])
|
channel = __import__('channels.%s' % item.contentChannel, None, None, ['channels.%s' % item.contentChannel])
|
||||||
|
|
||||||
progreso.update(50, config.get_localized_string(70184), config.get_localized_string(70180) % item.contentChannel)
|
progreso.update(50, config.get_localized_string(70184) + '\n' + config.get_localized_string(70180) % item.contentChannel)
|
||||||
|
|
||||||
if hasattr(channel, item.contentAction):
|
if hasattr(channel, item.contentAction):
|
||||||
play_items = getattr(channel, item.contentAction)(item.clone(action=item.contentAction, channel=item.contentChannel))
|
play_items = getattr(channel, item.contentAction)(item.clone(action=item.contentAction, channel=item.contentChannel))
|
||||||
@@ -741,7 +741,7 @@ def download_from_best_server(item):
|
|||||||
|
|
||||||
play_items = [x for x in play_items if x.action == "play" and not "trailer" in x.title.lower()]
|
play_items = [x for x in play_items if x.action == "play" and not "trailer" in x.title.lower()]
|
||||||
|
|
||||||
progreso.update(100, config.get_localized_string(70183), config.get_localized_string(70181) % len(play_items))
|
progreso.update(100, config.get_localized_string(70183) + '\n' + config.get_localized_string(70181) % len(play_items))
|
||||||
|
|
||||||
# if config.get_setting("server_reorder", "downloads") == 1:
|
# if config.get_setting("server_reorder", "downloads") == 1:
|
||||||
play_items.sort(key=sort_method)
|
play_items.sort(key=sort_method)
|
||||||
@@ -784,7 +784,7 @@ def select_server(item):
|
|||||||
channel = __import__('specials.%s' % item.contentChannel, None, None, ['specials.%s' % item.contentChannel])
|
channel = __import__('specials.%s' % item.contentChannel, None, None, ['specials.%s' % item.contentChannel])
|
||||||
else:
|
else:
|
||||||
channel = __import__('channels.%s' % item.contentChannel, None, None, ['channels.%s' % item.contentChannel])
|
channel = __import__('channels.%s' % item.contentChannel, None, None, ['channels.%s' % item.contentChannel])
|
||||||
progreso.update(50, config.get_localized_string(70184), config.get_localized_string(70180) % item.contentChannel)
|
progreso.update(50, config.get_localized_string(70184) + '\n' + config.get_localized_string(70180) % item.contentChannel)
|
||||||
|
|
||||||
if hasattr(channel, item.contentAction):
|
if hasattr(channel, item.contentAction):
|
||||||
play_items = getattr(channel, item.contentAction)(
|
play_items = getattr(channel, item.contentAction)(
|
||||||
@@ -793,7 +793,7 @@ def select_server(item):
|
|||||||
play_items = servertools.find_video_items(item.clone(action=item.contentAction, channel=item.contentChannel))
|
play_items = servertools.find_video_items(item.clone(action=item.contentAction, channel=item.contentChannel))
|
||||||
|
|
||||||
play_items = [x for x in play_items if x.action == "play" and not "trailer" in x.title.lower()]
|
play_items = [x for x in play_items if x.action == "play" and not "trailer" in x.title.lower()]
|
||||||
progreso.update(100, config.get_localized_string(70183), config.get_localized_string(70181) % len(play_items))
|
progreso.update(100, config.get_localized_string(70183) + '\n' + config.get_localized_string(70181) % len(play_items))
|
||||||
finally:
|
finally:
|
||||||
progreso.close()
|
progreso.close()
|
||||||
|
|
||||||
@@ -1092,11 +1092,11 @@ def save_download_tvshow(item):
|
|||||||
if config.get_setting("lowerize_title", "videolibrary"):
|
if config.get_setting("lowerize_title", "videolibrary"):
|
||||||
item.downloadFilename = item.downloadFilename.lower()
|
item.downloadFilename = item.downloadFilename.lower()
|
||||||
|
|
||||||
progreso.update(0, config.get_localized_string(70186), config.get_localized_string(70180) % item.contentChannel)
|
progreso.update(0, config.get_localized_string(70186) + '\n' + config.get_localized_string(70180) % item.contentChannel)
|
||||||
|
|
||||||
episodes = get_episodes(item)
|
episodes = get_episodes(item)
|
||||||
|
|
||||||
progreso.update(0, config.get_localized_string(70190), " ")
|
progreso.update(0, config.get_localized_string(70190))
|
||||||
|
|
||||||
for x, i in enumerate(episodes):
|
for x, i in enumerate(episodes):
|
||||||
progreso.update(old_div(x * 100, len(episodes)), "%dx%0.2d: %s" % (i.contentSeason, i.contentEpisodeNumber, i.contentTitle))
|
progreso.update(old_div(x * 100, len(episodes)), "%dx%0.2d: %s" % (i.contentSeason, i.contentEpisodeNumber, i.contentTitle))
|
||||||
|
|||||||
@@ -757,7 +757,7 @@ def compartir_lista(item):
|
|||||||
upload_url = scrapertools.find_single_match(data, 'form action="([^"]+)')
|
upload_url = scrapertools.find_single_match(data, 'form action="([^"]+)')
|
||||||
sessionid = scrapertools.find_single_match(upload_url, 'sid=(.+)')
|
sessionid = scrapertools.find_single_match(upload_url, 'sid=(.+)')
|
||||||
|
|
||||||
progreso.update(10, config.get_localized_string(70645), config.get_localized_string(70646))
|
progreso.update(10, config.get_localized_string(70645) + '\n' + config.get_localized_string(70646))
|
||||||
|
|
||||||
# Sending the file to tinyupload using multipart / form-data
|
# Sending the file to tinyupload using multipart / form-data
|
||||||
from future import standard_library
|
from future import standard_library
|
||||||
|
|||||||
+3
-3
@@ -279,7 +279,7 @@ def novedades(item):
|
|||||||
t.start()
|
t.start()
|
||||||
threads.append(t)
|
threads.append(t)
|
||||||
if mode == 'normal':
|
if mode == 'normal':
|
||||||
progreso.update(percentage, "", config.get_localized_string(60520) % channel_title)
|
progreso.update(percentage, config.get_localized_string(60520) % channel_title)
|
||||||
|
|
||||||
# Modo single Thread
|
# Modo single Thread
|
||||||
else:
|
else:
|
||||||
@@ -299,7 +299,7 @@ def novedades(item):
|
|||||||
list_pendent_names = [a.getName() for a in pendent]
|
list_pendent_names = [a.getName() for a in pendent]
|
||||||
if mode == 'normal':
|
if mode == 'normal':
|
||||||
mensaje = config.get_localized_string(30994) % (", ".join(list_pendent_names))
|
mensaje = config.get_localized_string(30994) % (", ".join(list_pendent_names))
|
||||||
progreso.update(percentage, config.get_localized_string(60521) % (len(threads) - len(pendent), len(threads)),
|
progreso.update(percentage, config.get_localized_string(60521) % (len(threads) - len(pendent), len(threads)) + '\n' +
|
||||||
mensaje)
|
mensaje)
|
||||||
logger.debug(mensaje)
|
logger.debug(mensaje)
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ def novedades(item):
|
|||||||
pendent = [a for a in threads if a.isAlive()]
|
pendent = [a for a in threads if a.isAlive()]
|
||||||
if mode == 'normal':
|
if mode == 'normal':
|
||||||
mensaje = config.get_localized_string(60522) % (len(list_newest), time.time() - start_time)
|
mensaje = config.get_localized_string(60522) % (len(list_newest), time.time() - start_time)
|
||||||
progreso.update(100, mensaje, " ", " ")
|
progreso.update(100, mensaje)
|
||||||
logger.info(mensaje)
|
logger.info(mensaje)
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
# logger.debug(start_time)
|
# logger.debug(start_time)
|
||||||
|
|||||||
+18
-19
@@ -1,10 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import os
|
import os, sys, ssl
|
||||||
import ssl
|
PY3 = False
|
||||||
try:
|
if sys.version_info[0] >= 3: PY3 = True; unicode = str; unichr = chr; long = int
|
||||||
import urlparse
|
if PY3:
|
||||||
except:
|
|
||||||
import urllib.parse as urlparse
|
import urllib.parse as urlparse
|
||||||
|
import _ssl
|
||||||
|
DEFAULT_CIPHERS = _ssl._DEFAULT_CIPHERS
|
||||||
|
else:
|
||||||
|
DEFAULT_CIPHERS = ssl._DEFAULT_CIPHERS
|
||||||
|
|
||||||
from lib.requests_toolbelt.adapters import host_header_ssl
|
from lib.requests_toolbelt.adapters import host_header_ssl
|
||||||
from lib import doh
|
from lib import doh
|
||||||
@@ -25,24 +28,20 @@ elif 'PROTOCOL_SSLv23' in ssl.__dict__:
|
|||||||
else:
|
else:
|
||||||
protocol = ssl.PROTOCOL_SSLv3
|
protocol = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
class CustomSocket(ssl.SSLSocket):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(CustomSocket, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
class CustomContext(ssl.SSLContext):
|
class CustomContext(ssl.SSLContext):
|
||||||
def __init__(self, protocol, hostname, *args, **kwargs):
|
def __init__(self, protocol, hostname, *args, **kwargs):
|
||||||
self.hostname = hostname
|
self.hostname = hostname
|
||||||
super(CustomContext, self).__init__(protocol)
|
if PY3:
|
||||||
|
super(CustomContext, self).__init__()
|
||||||
|
else:
|
||||||
|
super(CustomContext, self).__init__(protocol)
|
||||||
|
self.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
def wrap_socket(self, sock, server_side=False,
|
def wrap_socket(self, *args, **kwargs):
|
||||||
do_handshake_on_connect=True,
|
kwargs['server_hostname'] = self.hostname
|
||||||
suppress_ragged_eofs=True,
|
self.verify_mode = ssl.CERT_NONE
|
||||||
server_hostname=None):
|
return super(CustomContext, self).wrap_socket(*args, **kwargs)
|
||||||
return CustomSocket(sock=sock, server_side=server_side,
|
|
||||||
do_handshake_on_connect=do_handshake_on_connect,
|
|
||||||
suppress_ragged_eofs=suppress_ragged_eofs,
|
|
||||||
server_hostname=self.hostname,
|
|
||||||
_context=self)
|
|
||||||
|
|
||||||
|
|
||||||
class CipherSuiteAdapter(host_header_ssl.HostHeaderSSLAdapter):
|
class CipherSuiteAdapter(host_header_ssl.HostHeaderSSLAdapter):
|
||||||
@@ -52,7 +51,7 @@ class CipherSuiteAdapter(host_header_ssl.HostHeaderSSLAdapter):
|
|||||||
self.cur = self.conn.cursor()
|
self.cur = self.conn.cursor()
|
||||||
self.ssl_context = CustomContext(protocol, domain)
|
self.ssl_context = CustomContext(protocol, domain)
|
||||||
self.CF = CF # if cloudscrape is in action
|
self.CF = CF # if cloudscrape is in action
|
||||||
self.cipherSuite = kwargs.pop('cipherSuite', ssl._DEFAULT_CIPHERS)
|
self.cipherSuite = kwargs.pop('cipherSuite', DEFAULT_CIPHERS)
|
||||||
|
|
||||||
super(CipherSuiteAdapter, self).__init__(**kwargs)
|
super(CipherSuiteAdapter, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -249,7 +249,7 @@ def channel_search(item):
|
|||||||
cnt += 1
|
cnt += 1
|
||||||
searching_titles.remove(searching_titles[searching.index(channel)])
|
searching_titles.remove(searching_titles[searching.index(channel)])
|
||||||
searching.remove(channel)
|
searching.remove(channel)
|
||||||
progress.update(old_div(((total_search_actions - len(search_action_list)) * 100), total_search_actions), config.get_localized_string(70744) % str(len(channel_list) - cnt), ', '.join(searching_titles))
|
progress.update(old_div(((total_search_actions - len(search_action_list)) * 100), total_search_actions), config.get_localized_string(70744) % str(len(channel_list) - cnt) + '\n' + ', '.join(searching_titles))
|
||||||
|
|
||||||
progress.close()
|
progress.close()
|
||||||
|
|
||||||
@@ -262,7 +262,7 @@ def channel_search(item):
|
|||||||
ch_name = channel_titles[channel_list.index(key)]
|
ch_name = channel_titles[channel_list.index(key)]
|
||||||
grouped = list()
|
grouped = list()
|
||||||
cnt += 1
|
cnt += 1
|
||||||
progress.update(old_div((cnt * 100), len(ch_list)), config.get_localized_string(60295), config.get_localized_string(60293))
|
progress.update(old_div((cnt * 100), len(ch_list)), config.get_localized_string(60295) + '\n' + config.get_localized_string(60293))
|
||||||
if len(value) <= max_results and item.mode != 'all':
|
if len(value) <= max_results and item.mode != 'all':
|
||||||
if len(value) == 1:
|
if len(value) == 1:
|
||||||
if not value[0].action or config.get_localized_string(70006).lower() in value[0].title.lower():
|
if not value[0].action or config.get_localized_string(70006).lower() in value[0].title.lower():
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ def get_results(nfo_path, root, Type, local=False):
|
|||||||
else:
|
else:
|
||||||
update_text = config.get_localized_string(60023)
|
update_text = config.get_localized_string(60023)
|
||||||
value = 1
|
value = 1
|
||||||
item.title += " [B]" + u"\u2022".encode('utf-8') + "[/B]"
|
item.title += " [B]" + u"\u2022" + "[/B]"
|
||||||
|
|
||||||
# Context menu: Delete series / channel
|
# Context menu: Delete series / channel
|
||||||
channels_num = len(item.library_urls)
|
channels_num = len(item.library_urls)
|
||||||
@@ -645,7 +645,7 @@ def move_videolibrary(current_path, new_path, current_movies_folder, new_movies_
|
|||||||
return
|
return
|
||||||
|
|
||||||
config.verify_directories_created()
|
config.verify_directories_created()
|
||||||
progress.update(10, config.get_localized_string(20000), config.get_localized_string(80012))
|
progress.update(10, config.get_localized_string(20000) + '\n' + config.get_localized_string(80012))
|
||||||
if current_movies_path != new_movies_path:
|
if current_movies_path != new_movies_path:
|
||||||
if filetools.listdir(current_movies_path):
|
if filetools.listdir(current_movies_path):
|
||||||
dir_util.copy_tree(current_movies_path, new_movies_path)
|
dir_util.copy_tree(current_movies_path, new_movies_path)
|
||||||
|
|||||||
Reference in New Issue
Block a user