|
|
@@ -1,710 +0,0 @@ |
|
|
|
#!/usr/bin/python |
|
|
|
# |
|
|
|
# Copyright 2009 Google Inc. |
|
|
|
# |
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
# you may not use this file except in compliance with the License. |
|
|
|
# You may obtain a copy of the License at |
|
|
|
# |
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0 |
|
|
|
# |
|
|
|
# Unless required by applicable law or agreed to in writing, software |
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS, |
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
|
|
# See the License for the specific language governing permissions and |
|
|
|
# limitations under the License. |
|
|
|
# |
|
|
|
# |
|
|
|
# Disable the invalid name warning as we are inheriting from a standard library |
|
|
|
# object. |
|
|
|
# pylint: disable-msg=C6409,W0212 |
|
|
|
|
|
|
|
"""A version of the datetime module which *cares* about timezones. |
|
|
|
|
|
|
|
This module will never return a naive datetime object. This requires the module |
|
|
|
know your local timezone, which it tries really hard to figure out. |
|
|
|
|
|
|
|
You can override the detection by using the datetime.tzaware.defaulttz_set |
|
|
|
method. It the module is unable to figure out the timezone itself this method |
|
|
|
*must* be called before the normal module is imported. If done before importing |
|
|
|
it can also speed up the time taken to import as the defaulttz will no longer |
|
|
|
try and do the detection. |
|
|
|
""" |
|
|
|
|
|
|
|
__author__ = "tansell@google.com (Tim Ansell)" |
|
|
|
|
|
|
|
import calendar |
|
|
|
import datetime |
|
|
|
import os |
|
|
|
import os.path |
|
|
|
import re |
|
|
|
import time |
|
|
|
import warnings |
|
|
|
import dateutil.parser |
|
|
|
import dateutil.relativedelta |
|
|
|
import dateutil.tz |
|
|
|
import pytz |
|
|
|
from . import pytz_abbr |
|
|
|
|
|
|
|
|
|
|
|
try: |
|
|
|
# pylint: disable-msg=C6204 |
|
|
|
import functools |
|
|
|
except ImportError as e: |
|
|
|
|
|
|
|
class functools(object): |
|
|
|
"""Fake replacement for a full functools.""" |
|
|
|
|
|
|
|
# pylint: disable-msg=W0613 |
|
|
|
@staticmethod |
|
|
|
def wraps(f, *args, **kw): |
|
|
|
return f |
|
|
|
|
|
|
|
|
|
|
|
# Need to patch pytz.utc to have a _utcoffset so you can normalize/localize |
|
|
|
# using it. |
|
|
|
pytz.utc._utcoffset = datetime.timedelta() |
|
|
|
|
|
|
|
|
|
|
|
timedelta = datetime.timedelta |
|
|
|
|
|
|
|
|
|
|
|
def _tzinfome(tzinfo): |
|
|
|
"""Gets a tzinfo object from a string. |
|
|
|
|
|
|
|
Args: |
|
|
|
tzinfo: A string (or string like) object, or a datetime.tzinfo object. |
|
|
|
|
|
|
|
Returns: |
|
|
|
An datetime.tzinfo object. |
|
|
|
|
|
|
|
Raises: |
|
|
|
UnknownTimeZoneError: If the timezone given can't be decoded. |
|
|
|
""" |
|
|
|
if not isinstance(tzinfo, datetime.tzinfo): |
|
|
|
try: |
|
|
|
tzinfo = pytz.timezone(tzinfo) |
|
|
|
except AttributeError: |
|
|
|
raise pytz.UnknownTimeZoneError("Unknown timezone! %s" % tzinfo) |
|
|
|
return tzinfo |
|
|
|
|
|
|
|
|
|
|
|
# Our "local" timezone |
|
|
|
_localtz = None |
|
|
|
|
|
|
|
|
|
|
|
def localtz(): |
|
|
|
"""Get the local timezone. |
|
|
|
|
|
|
|
Returns: |
|
|
|
The localtime timezone as a tzinfo object. |
|
|
|
""" |
|
|
|
# pylint: disable-msg=W0603 |
|
|
|
global _localtz |
|
|
|
if _localtz is None: |
|
|
|
_localtz = detect_timezone() |
|
|
|
return _localtz |
|
|
|
|
|
|
|
|
|
|
|
def localtz_set(timezone): |
|
|
|
"""Set the local timezone.""" |
|
|
|
# pylint: disable-msg=W0603 |
|
|
|
global _localtz |
|
|
|
_localtz = _tzinfome(timezone) |
|
|
|
|
|
|
|
|
|
|
|
def detect_timezone(): |
|
|
|
"""Try and detect the timezone that Python is currently running in. |
|
|
|
|
|
|
|
We have a bunch of different methods for trying to figure this out (listed in |
|
|
|
order they are attempted). |
|
|
|
* Try TZ environment variable. |
|
|
|
* Try and find /etc/timezone file (with timezone name). |
|
|
|
* Try and find /etc/localtime file (with timezone data). |
|
|
|
* Try and match a TZ to the current dst/offset/shortname. |
|
|
|
|
|
|
|
Returns: |
|
|
|
The detected local timezone as a tzinfo object |
|
|
|
|
|
|
|
Raises: |
|
|
|
pytz.UnknownTimeZoneError: If it was unable to detect a timezone. |
|
|
|
""" |
|
|
|
# First we try the TZ variable |
|
|
|
tz = _detect_timezone_environ() |
|
|
|
if tz is not None: |
|
|
|
return tz |
|
|
|
|
|
|
|
# Second we try /etc/timezone and use the value in that |
|
|
|
tz = _detect_timezone_etc_timezone() |
|
|
|
if tz is not None: |
|
|
|
return tz |
|
|
|
|
|
|
|
# Next we try and see if something matches the tzinfo in /etc/localtime |
|
|
|
tz = _detect_timezone_etc_localtime() |
|
|
|
if tz is not None: |
|
|
|
return tz |
|
|
|
|
|
|
|
# Next we try and use a similiar method to what PHP does. |
|
|
|
# We first try to search on time.tzname, time.timezone, time.daylight to |
|
|
|
# match a pytz zone. |
|
|
|
warnings.warn("Had to fall back to worst detection method (the 'PHP' " |
|
|
|
"method).") |
|
|
|
|
|
|
|
tz = _detect_timezone_php() |
|
|
|
if tz is not None: |
|
|
|
return tz |
|
|
|
|
|
|
|
raise pytz.UnknownTimeZoneError("Unable to detect your timezone!") |
|
|
|
|
|
|
|
|
|
|
|
def _detect_timezone_environ(): |
|
|
|
if "TZ" in os.environ: |
|
|
|
try: |
|
|
|
return pytz.timezone(os.environ["TZ"]) |
|
|
|
except (IOError, pytz.UnknownTimeZoneError): |
|
|
|
warnings.warn("You provided a TZ environment value (%r) we did not " |
|
|
|
"understand!" % os.environ["TZ"]) |
|
|
|
|
|
|
|
|
|
|
|
def _detect_timezone_etc_timezone(): |
|
|
|
if os.path.exists("/etc/timezone"): |
|
|
|
try: |
|
|
|
tz = file("/etc/timezone").read().strip() |
|
|
|
try: |
|
|
|
return pytz.timezone(tz) |
|
|
|
except (IOError, pytz.UnknownTimeZoneError) as ei: |
|
|
|
warnings.warn("Your /etc/timezone file references a timezone (%r) that" |
|
|
|
" is not valid (%r)." % (tz, ei)) |
|
|
|
|
|
|
|
# Problem reading the /etc/timezone file |
|
|
|
except IOError as eo: |
|
|
|
warnings.warn("Could not access your /etc/timezone file: %s" % eo) |
|
|
|
|
|
|
|
|
|
|
|
def _detect_timezone_etc_localtime(): |
|
|
|
matches = [] |
|
|
|
if os.path.exists("/etc/localtime"): |
|
|
|
localtime = pytz.tzfile.build_tzinfo("/etc/localtime", |
|
|
|
file("/etc/localtime")) |
|
|
|
|
|
|
|
# See if we can find a "Human Name" for this.. |
|
|
|
for tzname in pytz.all_timezones: |
|
|
|
tz = _tzinfome(tzname) |
|
|
|
|
|
|
|
if dir(tz) != dir(localtime): |
|
|
|
continue |
|
|
|
|
|
|
|
for attrib in dir(tz): |
|
|
|
# Ignore functions and specials |
|
|
|
if callable(getattr(tz, attrib)) or attrib.startswith("__"): |
|
|
|
continue |
|
|
|
|
|
|
|
# This will always be different |
|
|
|
if attrib == "zone" or attrib == "_tzinfos": |
|
|
|
continue |
|
|
|
|
|
|
|
if getattr(tz, attrib) != getattr(localtime, attrib): |
|
|
|
break |
|
|
|
|
|
|
|
# We get here iff break didn't happen, i.e. no meaningful attributes |
|
|
|
# differ between tz and localtime |
|
|
|
else: |
|
|
|
matches.append(tzname) |
|
|
|
|
|
|
|
if len(matches) == 1: |
|
|
|
return _tzinfome(matches[0]) |
|
|
|
else: |
|
|
|
# Warn the person about this! |
|
|
|
warning = "Could not get a human name for your timezone: " |
|
|
|
if len(matches) > 1: |
|
|
|
warning += ("We detected multiple matches for your /etc/localtime. " |
|
|
|
"(Matches where %s)" % matches) |
|
|
|
return _tzinfome(matches[0]) |
|
|
|
else: |
|
|
|
warning += "We detected no matches for your /etc/localtime." |
|
|
|
warnings.warn(warning) |
|
|
|
|
|
|
|
# Register /etc/localtime as the timezone loaded. |
|
|
|
pytz._tzinfo_cache['/etc/localtime'] = localtime |
|
|
|
return localtime |
|
|
|
|
|
|
|
|
|
|
|
def _detect_timezone_php(): |
|
|
|
tomatch = (time.tzname[0], time.timezone, time.daylight) |
|
|
|
now = datetime.datetime.now() |
|
|
|
|
|
|
|
matches = [] |
|
|
|
for tzname in pytz.all_timezones: |
|
|
|
try: |
|
|
|
tz = pytz.timezone(tzname) |
|
|
|
except IOError: |
|
|
|
continue |
|
|
|
|
|
|
|
try: |
|
|
|
indst = tz.localize(now).timetuple()[-1] |
|
|
|
|
|
|
|
if tomatch == (tz._tzname, -tz._utcoffset.seconds, indst): |
|
|
|
matches.append(tzname) |
|
|
|
|
|
|
|
# pylint: disable-msg=W0704 |
|
|
|
except AttributeError: |
|
|
|
pass |
|
|
|
|
|
|
|
if len(matches) > 1: |
|
|
|
warnings.warn("We detected multiple matches for the timezone, choosing " |
|
|
|
"the first %s. (Matches where %s)" % (matches[0], matches)) |
|
|
|
return pytz.timezone(matches[0]) |
|
|
|
|
|
|
|
|
|
|
|
class datetime_tz(datetime.datetime): |
|
|
|
"""An extension of the inbuilt datetime adding more functionality. |
|
|
|
|
|
|
|
The extra functionality includes: |
|
|
|
* Partial parsing support (IE 2006/02/30 matches %Y/%M/%D %H:%M) |
|
|
|
* Full integration with pytz (just give it the string of the timezone!) |
|
|
|
* Proper support for going to/from Unix timestamps (which are in UTC!). |
|
|
|
""" |
|
|
|
__slots__ = ["is_dst"] |
|
|
|
|
|
|
|
def __new__(cls, *args, **kw): |
|
|
|
args = list(args) |
|
|
|
if not args: |
|
|
|
raise TypeError("Not enough arguments given.") |
|
|
|
|
|
|
|
# See if we are given a tzinfo object... |
|
|
|
tzinfo = None |
|
|
|
if isinstance(args[-1], (datetime.tzinfo, str)): |
|
|
|
tzinfo = _tzinfome(args.pop(-1)) |
|
|
|
elif kw.get("tzinfo", None) is not None: |
|
|
|
tzinfo = _tzinfome(kw.pop("tzinfo")) |
|
|
|
|
|
|
|
# Create a datetime object if we don't have one |
|
|
|
if isinstance(args[0], datetime.datetime): |
|
|
|
# Convert the datetime instance to a datetime object. |
|
|
|
newargs = (list(args[0].timetuple()[0:6]) + |
|
|
|
[args[0].microsecond, args[0].tzinfo]) |
|
|
|
dt = datetime.datetime(*newargs) |
|
|
|
|
|
|
|
if tzinfo is None and dt.tzinfo is None: |
|
|
|
raise TypeError("Must specify a timezone!") |
|
|
|
|
|
|
|
if tzinfo is not None and dt.tzinfo is not None: |
|
|
|
raise TypeError("Can not give a timezone with timezone aware" |
|
|
|
" datetime object! (Use localize.)") |
|
|
|
else: |
|
|
|
dt = datetime.datetime(*args, **kw) |
|
|
|
|
|
|
|
if dt.tzinfo is not None: |
|
|
|
# Re-normalize the dt object |
|
|
|
dt = dt.tzinfo.normalize(dt) |
|
|
|
|
|
|
|
else: |
|
|
|
if tzinfo is None: |
|
|
|
tzinfo = localtz() |
|
|
|
|
|
|
|
try: |
|
|
|
dt = tzinfo.localize(dt, is_dst=None) |
|
|
|
except pytz.AmbiguousTimeError: |
|
|
|
is_dst = None |
|
|
|
if "is_dst" in kw: |
|
|
|
is_dst = kw.pop("is_dst") |
|
|
|
|
|
|
|
try: |
|
|
|
dt = tzinfo.localize(dt, is_dst) |
|
|
|
except IndexError: |
|
|
|
raise pytz.AmbiguousTimeError("No such time exists!") |
|
|
|
|
|
|
|
newargs = list(dt.timetuple()[0:6])+[dt.microsecond, dt.tzinfo] |
|
|
|
obj = datetime.datetime.__new__(cls, *newargs) |
|
|
|
obj.is_dst = obj.dst() != datetime.timedelta(0) |
|
|
|
return obj |
|
|
|
|
|
|
|
def asdatetime(self, naive=True): |
|
|
|
"""Return this datetime_tz as a datetime object. |
|
|
|
|
|
|
|
Args: |
|
|
|
naive: Return *without* any tz info. |
|
|
|
|
|
|
|
Returns: |
|
|
|
This datetime_tz as a datetime object. |
|
|
|
""" |
|
|
|
args = list(self.timetuple()[0:6])+[self.microsecond] |
|
|
|
if not naive: |
|
|
|
args.append(self.tzinfo) |
|
|
|
return datetime.datetime(*args) |
|
|
|
|
|
|
|
def asdate(self): |
|
|
|
"""Return this datetime_tz as a date object. |
|
|
|
|
|
|
|
Returns: |
|
|
|
This datetime_tz as a date object. |
|
|
|
""" |
|
|
|
return datetime.date(self.year, self.month, self.day) |
|
|
|
|
|
|
|
def totimestamp(self): |
|
|
|
"""Convert this datetime object back to a unix timestamp. |
|
|
|
|
|
|
|
The Unix epoch is the time 00:00:00 UTC on January 1, 1970. |
|
|
|
|
|
|
|
Returns: |
|
|
|
Unix timestamp. |
|
|
|
""" |
|
|
|
return calendar.timegm(self.utctimetuple())+1e-6*self.microsecond |
|
|
|
|
|
|
|
def astimezone(self, tzinfo): |
|
|
|
"""Returns a version of this timestamp converted to the given timezone. |
|
|
|
|
|
|
|
Args: |
|
|
|
tzinfo: Either a datetime.tzinfo object or a string (which will be looked |
|
|
|
up in pytz. |
|
|
|
|
|
|
|
Returns: |
|
|
|
A datetime_tz object in the given timezone. |
|
|
|
""" |
|
|
|
# Assert we are not a naive datetime object |
|
|
|
assert self.tzinfo is not None |
|
|
|
|
|
|
|
tzinfo = _tzinfome(tzinfo) |
|
|
|
|
|
|
|
d = self.asdatetime(naive=False).astimezone(tzinfo) |
|
|
|
return datetime_tz(d) |
|
|
|
|
|
|
|
# pylint: disable-msg=C6113 |
|
|
|
def replace(self, **kw): |
|
|
|
"""Return datetime with new specified fields given as arguments. |
|
|
|
|
|
|
|
For example, dt.replace(days=4) would return a new datetime_tz object with |
|
|
|
exactly the same as dt but with the days attribute equal to 4. |
|
|
|
|
|
|
|
Any attribute can be replaced, but tzinfo can not be set to None. |
|
|
|
|
|
|
|
Args: |
|
|
|
Any datetime_tz attribute. |
|
|
|
|
|
|
|
Returns: |
|
|
|
A datetime_tz object with the attributes replaced. |
|
|
|
|
|
|
|
Raises: |
|
|
|
TypeError: If the given replacement is invalid. |
|
|
|
""" |
|
|
|
if "tzinfo" in kw: |
|
|
|
if kw["tzinfo"] is None: |
|
|
|
raise TypeError("Can not remove the timezone use asdatetime()") |
|
|
|
|
|
|
|
is_dst = None |
|
|
|
if "is_dst" in kw: |
|
|
|
is_dst = kw["is_dst"] |
|
|
|
del kw["is_dst"] |
|
|
|
else: |
|
|
|
# Use our own DST setting.. |
|
|
|
is_dst = self.is_dst |
|
|
|
|
|
|
|
replaced = self.asdatetime().replace(**kw) |
|
|
|
|
|
|
|
return datetime_tz(replaced, tzinfo=self.tzinfo.zone, is_dst=is_dst) |
|
|
|
|
|
|
|
# pylint: disable-msg=C6310 |
|
|
|
@classmethod |
|
|
|
def smartparse(cls, toparse, tzinfo=None): |
|
|
|
"""Method which uses dateutil.parse and extras to try and parse the string. |
|
|
|
|
|
|
|
Valid dates are found at: |
|
|
|
http://labix.org/python-dateutil#head-1443e0f14ad5dff07efd465e080d1110920673d8-2 |
|
|
|
|
|
|
|
Other valid formats include: |
|
|
|
"now" or "today" |
|
|
|
"yesterday" |
|
|
|
"tommorrow" |
|
|
|
"5 minutes ago" |
|
|
|
"10 hours ago" |
|
|
|
"10h5m ago" |
|
|
|
"start of yesterday" |
|
|
|
"end of tommorrow" |
|
|
|
"end of 3rd of March" |
|
|
|
|
|
|
|
Args: |
|
|
|
toparse: The string to parse. |
|
|
|
tzinfo: Timezone for the resultant datetime_tz object should be in. |
|
|
|
(Defaults to your local timezone.) |
|
|
|
|
|
|
|
Returns: |
|
|
|
New datetime_tz object. |
|
|
|
|
|
|
|
Raises: |
|
|
|
ValueError: If unable to make sense of the input. |
|
|
|
""" |
|
|
|
# Default for empty fields are: |
|
|
|
# year/month/day == now |
|
|
|
# hour/minute/second/microsecond == 0 |
|
|
|
toparse = toparse.strip() |
|
|
|
|
|
|
|
if tzinfo is None: |
|
|
|
dt = cls.now() |
|
|
|
else: |
|
|
|
dt = cls.now(tzinfo) |
|
|
|
|
|
|
|
default = dt.replace(hour=0, minute=0, second=0, microsecond=0) |
|
|
|
|
|
|
|
# Remove "start of " and "end of " prefix in the string |
|
|
|
if toparse.lower().startswith("end of "): |
|
|
|
toparse = toparse[7:].strip() |
|
|
|
|
|
|
|
dt += datetime.timedelta(days=1) |
|
|
|
dt = dt.replace(hour=0, minute=0, second=0, microsecond=0) |
|
|
|
dt -= datetime.timedelta(microseconds=1) |
|
|
|
|
|
|
|
default = dt |
|
|
|
|
|
|
|
elif toparse.lower().startswith("start of "): |
|
|
|
toparse = toparse[9:].strip() |
|
|
|
|
|
|
|
dt = dt.replace(hour=0, minute=0, second=0, microsecond=0) |
|
|
|
default = dt |
|
|
|
|
|
|
|
# Handle strings with "now", "today", "yesterday", "tomorrow" and "ago". |
|
|
|
# Need to use lowercase |
|
|
|
toparselower = toparse.lower() |
|
|
|
|
|
|
|
if toparselower in ["now", "today"]: |
|
|
|
pass |
|
|
|
|
|
|
|
elif toparselower == "yesterday": |
|
|
|
dt -= datetime.timedelta(days=1) |
|
|
|
|
|
|
|
elif toparselower == "tommorrow": |
|
|
|
dt += datetime.timedelta(days=1) |
|
|
|
|
|
|
|
elif "ago" in toparselower: |
|
|
|
# Remove the "ago" bit |
|
|
|
toparselower = toparselower[:-3] |
|
|
|
# Replace all "a day and an hour" with "1 day 1 hour" |
|
|
|
toparselower = toparselower.replace("a ", "1 ") |
|
|
|
toparselower = toparselower.replace("an ", "1 ") |
|
|
|
toparselower = toparselower.replace(" and ", " ") |
|
|
|
|
|
|
|
# Match the following |
|
|
|
# 1 hour ago |
|
|
|
# 1h ago |
|
|
|
# 1 h ago |
|
|
|
# 1 hour ago |
|
|
|
# 2 hours ago |
|
|
|
# Same with minutes, seconds, etc. |
|
|
|
|
|
|
|
tocheck = ("seconds", "minutes", "hours", "days", "weeks", "months", |
|
|
|
"years") |
|
|
|
result = {} |
|
|
|
for match in re.finditer("([0-9]+)([^0-9]*)", toparselower): |
|
|
|
amount = int(match.group(1)) |
|
|
|
unit = match.group(2).strip() |
|
|
|
|
|
|
|
for bit in tocheck: |
|
|
|
regex = "^([%s]|((%s)s?))$" % ( |
|
|
|
bit[0], bit[:-1]) |
|
|
|
|
|
|
|
bitmatch = re.search(regex, unit) |
|
|
|
if bitmatch: |
|
|
|
result[bit] = amount |
|
|
|
break |
|
|
|
else: |
|
|
|
raise ValueError("Was not able to parse date unit %r!" % unit) |
|
|
|
|
|
|
|
delta = dateutil.relativedelta.relativedelta(**result) |
|
|
|
dt -= delta |
|
|
|
|
|
|
|
else: |
|
|
|
# Handle strings with normal datetime format, use original case. |
|
|
|
dt = dateutil.parser.parse(toparse, default=default.asdatetime(), |
|
|
|
tzinfos=pytz_abbr.tzinfos) |
|
|
|
if dt is None: |
|
|
|
raise ValueError("Was not able to parse date!") |
|
|
|
|
|
|
|
if dt.tzinfo is pytz_abbr.unknown: |
|
|
|
dt = dt.replace(tzinfo=None) |
|
|
|
|
|
|
|
if dt.tzinfo is None: |
|
|
|
if tzinfo is None: |
|
|
|
tzinfo = localtz() |
|
|
|
dt = cls(dt, tzinfo) |
|
|
|
else: |
|
|
|
if isinstance(dt.tzinfo, pytz_abbr.tzabbr): |
|
|
|
abbr = dt.tzinfo |
|
|
|
dt = dt.replace(tzinfo=None) |
|
|
|
dt = cls(dt, abbr.zone, is_dst=abbr.dst) |
|
|
|
|
|
|
|
dt = cls(dt) |
|
|
|
|
|
|
|
return dt |
|
|
|
|
|
|
|
@classmethod |
|
|
|
def utcfromtimestamp(cls, timestamp): |
|
|
|
"""Returns a datetime object of a given timestamp (in UTC).""" |
|
|
|
obj = datetime.datetime.utcfromtimestamp(timestamp) |
|
|
|
obj = pytz.utc.localize(obj) |
|
|
|
return cls(obj) |
|
|
|
|
|
|
|
@classmethod |
|
|
|
def fromtimestamp(cls, timestamp): |
|
|
|
"""Returns a datetime object of a given timestamp (in local tz).""" |
|
|
|
d = cls.utcfromtimestamp(timestamp) |
|
|
|
return d.astimezone(localtz()) |
|
|
|
|
|
|
|
@classmethod |
|
|
|
def utcnow(cls): |
|
|
|
"""Return a new datetime representing UTC day and time.""" |
|
|
|
obj = datetime.datetime.utcnow() |
|
|
|
obj = cls(obj, tzinfo=pytz.utc) |
|
|
|
return obj |
|
|
|
|
|
|
|
@classmethod |
|
|
|
def now(cls, tzinfo=None): |
|
|
|
"""[tz] -> new datetime with tz's local day and time.""" |
|
|
|
obj = cls.utcnow() |
|
|
|
if tzinfo is None: |
|
|
|
tzinfo = localtz() |
|
|
|
return obj.astimezone(tzinfo) |
|
|
|
|
|
|
|
today = now |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def fromordinal(ordinal): |
|
|
|
raise SyntaxError("Not enough information to create a datetime_tz object " |
|
|
|
"from an ordinal. Please use datetime.date.fromordinal") |
|
|
|
|
|
|
|
|
|
|
|
class iterate(object): |
|
|
|
"""Helpful iterators for working with datetime_tz objects.""" |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def between(start, delta, end=None): |
|
|
|
"""Return an iterator between this date till given end point. |
|
|
|
|
|
|
|
Example usage: |
|
|
|
>>> d = datetime_tz.smartparse("5 days ago") |
|
|
|
2008/05/12 11:45 |
|
|
|
>>> for i in d.between(timedelta(days=1), datetime_tz.now()): |
|
|
|
>>> print i |
|
|
|
2008/05/12 11:45 |
|
|
|
2008/05/13 11:45 |
|
|
|
2008/05/14 11:45 |
|
|
|
2008/05/15 11:45 |
|
|
|
2008/05/16 11:45 |
|
|
|
|
|
|
|
Args: |
|
|
|
start: The date to start at. |
|
|
|
delta: The interval to iterate with. |
|
|
|
end: (Optional) Date to end at. If not given the iterator will never |
|
|
|
terminate. |
|
|
|
|
|
|
|
Yields: |
|
|
|
datetime_tz objects. |
|
|
|
""" |
|
|
|
toyield = start |
|
|
|
while end is None or toyield < end: |
|
|
|
yield toyield |
|
|
|
toyield += delta |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def weeks(start, end=None): |
|
|
|
"""Iterate over the weeks between the given datetime_tzs. |
|
|
|
|
|
|
|
Args: |
|
|
|
start: datetime_tz to start from. |
|
|
|
end: (Optional) Date to end at, if not given the iterator will never |
|
|
|
terminate. |
|
|
|
|
|
|
|
Returns: |
|
|
|
An iterator which generates datetime_tz objects a week apart. |
|
|
|
""" |
|
|
|
return iterate.between(start, datetime.timedelta(days=7), end) |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def days(start, end=None): |
|
|
|
"""Iterate over the days between the given datetime_tzs. |
|
|
|
|
|
|
|
Args: |
|
|
|
start: datetime_tz to start from. |
|
|
|
end: (Optional) Date to end at, if not given the iterator will never |
|
|
|
terminate. |
|
|
|
|
|
|
|
Returns: |
|
|
|
An iterator which generates datetime_tz objects a day apart. |
|
|
|
""" |
|
|
|
return iterate.between(start, datetime.timedelta(days=1), end) |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def hours(start, end=None): |
|
|
|
"""Iterate over the hours between the given datetime_tzs. |
|
|
|
|
|
|
|
Args: |
|
|
|
start: datetime_tz to start from. |
|
|
|
end: (Optional) Date to end at, if not given the iterator will never |
|
|
|
terminate. |
|
|
|
|
|
|
|
Returns: |
|
|
|
An iterator which generates datetime_tz objects a hour apart. |
|
|
|
""" |
|
|
|
return iterate.between(start, datetime.timedelta(hours=1), end) |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def minutes(start, end=None): |
|
|
|
"""Iterate over the minutes between the given datetime_tzs. |
|
|
|
|
|
|
|
Args: |
|
|
|
start: datetime_tz to start from. |
|
|
|
end: (Optional) Date to end at, if not given the iterator will never |
|
|
|
terminate. |
|
|
|
|
|
|
|
Returns: |
|
|
|
An iterator which generates datetime_tz objects a minute apart. |
|
|
|
""" |
|
|
|
return iterate.between(start, datetime.timedelta(minutes=1), end) |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def seconds(start, end=None): |
|
|
|
"""Iterate over the seconds between the given datetime_tzs. |
|
|
|
|
|
|
|
Args: |
|
|
|
start: datetime_tz to start from. |
|
|
|
end: (Optional) Date to end at, if not given the iterator will never |
|
|
|
terminate. |
|
|
|
|
|
|
|
Returns: |
|
|
|
An iterator which generates datetime_tz objects a second apart. |
|
|
|
""" |
|
|
|
return iterate.between(start, datetime.timedelta(minutes=1), end) |
|
|
|
|
|
|
|
|
|
|
|
def _wrap_method(name): |
|
|
|
"""Wrap a method. |
|
|
|
|
|
|
|
Patch a method which might return a datetime.datetime to return a |
|
|
|
datetime_tz.datetime_tz instead. |
|
|
|
|
|
|
|
Args: |
|
|
|
name: The name of the method to patch |
|
|
|
""" |
|
|
|
method = getattr(datetime.datetime, name) |
|
|
|
|
|
|
|
# Have to give the second argument as method has no __module__ option. |
|
|
|
@functools.wraps(method, ("__name__", "__doc__"), ()) |
|
|
|
def wrapper(*args, **kw): |
|
|
|
r = method(*args, **kw) |
|
|
|
|
|
|
|
if isinstance(r, datetime.datetime) and not isinstance(r, datetime_tz): |
|
|
|
r = datetime_tz(r) |
|
|
|
return r |
|
|
|
|
|
|
|
setattr(datetime_tz, name, wrapper) |
|
|
|
|
|
|
|
for methodname in ["__add__", "__radd__", "__rsub__", "__sub__", "combine"]: |
|
|
|
|
|
|
|
# Make sure we have not already got an override for this method |
|
|
|
assert methodname not in datetime_tz.__dict__ |
|
|
|
|
|
|
|
_wrap_method(methodname) |
|
|
|
|
|
|
|
|
|
|
|
__all__ = ['datetime_tz', 'detect_timezone', 'iterate', 'localtz', |
|
|
|
'localtz_set', 'timedelta', '_detect_timezone_environ', |
|
|
|
'_detect_timezone_etc_localtime', '_detect_timezone_etc_timezone', |
|
|
|
'_detect_timezone_php'] |