mirror of
https://github.com/pocketpy/pocketpy
synced 2025-11-10 13:40:16 +00:00
Create datetime.py
This commit is contained in:
parent
c9492355f0
commit
fa5754ef65
871
python/datetime.py
Normal file
871
python/datetime.py
Normal file
@ -0,0 +1,871 @@
|
||||
'''
|
||||
object
|
||||
timedelta
|
||||
tzinfo
|
||||
timezone
|
||||
time
|
||||
date
|
||||
datetime
|
||||
'''
|
||||
import builtins
|
||||
import time
|
||||
import math
|
||||
import sys
|
||||
|
||||
def _get_class_module(self):
|
||||
module_name = self.__name__
|
||||
if module_name == 'datetime':
|
||||
return 'datetime'
|
||||
else:
|
||||
return module_name
|
||||
|
||||
MINYEAR = 1
|
||||
|
||||
MAXYEAR = 9999
|
||||
|
||||
MONTH_DAYS = [-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||
_DAYS_BEFORE_MONTH = [-1] # -1 is a placeholder for indexing purposes.
|
||||
|
||||
|
||||
def is_leap(year):
|
||||
return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
|
||||
|
||||
def _is_ascii_digit(c):
|
||||
return c in "0123456789"
|
||||
|
||||
def _days_before_year(year):
|
||||
"year -> number of days before January 1st of year."
|
||||
y = year - 1
|
||||
return y*365 + y//4 - y//100 + y//400
|
||||
|
||||
def _days_in_month(year, month):
|
||||
"year, month -> number of days in that month in that year."
|
||||
assert 1 <= month <= 12, month
|
||||
if month == 2 and is_leap(year):
|
||||
return 29
|
||||
return MONTH_DAYS[month]
|
||||
|
||||
def _days_before_month(year, month):
|
||||
"year, month -> number of days in year preceding first day of month."
|
||||
assert 1 <= month <= 12, 'month must be in 1..12'
|
||||
return _DAYS_BEFORE_MONTH[month] + (month > 2 and is_leap(year))
|
||||
|
||||
def _ymd2ord(year, month, day):
|
||||
"year, month, day -> ordinal, considering 01-Jan-0001 as day 1."
|
||||
assert 1 <= month <= 12, 'month must be in 1..12'
|
||||
dim = _days_in_month(year, month)
|
||||
assert 1 <= day <= dim, ('day must be in 1..%d' % dim)
|
||||
return (_days_before_year(year) +
|
||||
_days_before_month(year, month) +
|
||||
day)
|
||||
|
||||
_DI400Y = _days_before_year(401) # number of days in 400 years
|
||||
_DI100Y = _days_before_year(101) # " " " " 100 "
|
||||
_DI4Y = _days_before_year(5) # " " " " 4 "
|
||||
|
||||
def _ord2ymd(n):
|
||||
"ordinal -> (year, month, day), considering 01-Jan-0001 as day 1."
|
||||
|
||||
# n is a 1-based index, starting at 1-Jan-1. The pattern of leap years
|
||||
# repeats exactly every 400 years. The basic strategy is to find the
|
||||
# closest 400-year boundary at or before n, then work with the offset
|
||||
# from that boundary to n. Life is much clearer if we subtract 1 from
|
||||
# n first -- then the values of n at 400-year boundaries are exactly
|
||||
# those divisible by _DI400Y:
|
||||
#
|
||||
# D M Y n n-1
|
||||
# -- --- ---- ---------- ----------------
|
||||
# 31 Dec -400 -_DI400Y -_DI400Y -1
|
||||
# 1 Jan -399 -_DI400Y +1 -_DI400Y 400-year boundary
|
||||
# ...
|
||||
# 30 Dec 000 -1 -2
|
||||
# 31 Dec 000 0 -1
|
||||
# 1 Jan 001 1 0 400-year boundary
|
||||
# 2 Jan 001 2 1
|
||||
# 3 Jan 001 3 2
|
||||
# ...
|
||||
# 31 Dec 400 _DI400Y _DI400Y -1
|
||||
# 1 Jan 401 _DI400Y +1 _DI400Y 400-year boundary
|
||||
n -= 1
|
||||
n400, n = divmod(n, _DI400Y)
|
||||
year = n400 * 400 + 1 # ..., -399, 1, 401, ...
|
||||
|
||||
# Now n is the (non-negative) offset, in days, from January 1 of year, to
|
||||
# the desired date. Now compute how many 100-year cycles precede n.
|
||||
# Note that it's possible for n100 to equal 4! In that case 4 full
|
||||
# 100-year cycles precede the desired day, which implies the desired
|
||||
# day is December 31 at the end of a 400-year cycle.
|
||||
n100, n = divmod(n, _DI100Y)
|
||||
|
||||
# Now compute how many 4-year cycles precede it.
|
||||
n4, n = divmod(n, _DI4Y)
|
||||
|
||||
# And now how many single years. Again n1 can be 4, and again meaning
|
||||
# that the desired day is December 31 at the end of the 4-year cycle.
|
||||
n1, n = divmod(n, 365)
|
||||
|
||||
year += n100 * 100 + n4 * 4 + n1
|
||||
if n1 == 4 or n100 == 4:
|
||||
assert n == 0
|
||||
return year-1, 12, 31
|
||||
|
||||
# Now the year is correct, and n is the offset from January 1. We find
|
||||
# the month via an estimate that's either exact or one too large.
|
||||
leapyear = n1 == 3 and (n4 != 24 or n100 == 3)
|
||||
assert leapyear == is_leap(year)
|
||||
month = (n + 50) >> 5
|
||||
preceding = _DAYS_BEFORE_MONTH[month] + (month > 2 and leapyear)
|
||||
if preceding > n: # estimate is too large
|
||||
month -= 1
|
||||
preceding -= MONTH_DAYS[month] + (month == 2 and leapyear)
|
||||
n -= preceding
|
||||
assert 0 <= n < _days_in_month(year, month)
|
||||
|
||||
# Now the year and month are correct, and n is the offset from the
|
||||
# start of that month: we're done!
|
||||
return year, month, n+1
|
||||
|
||||
def _format_time(hh, mm, ss, us, timespec='auto'):
|
||||
specs = {
|
||||
'hours': '{:02d}',
|
||||
'minutes': '{:02d}:{:02d}',
|
||||
'seconds': '{:02d}:{:02d}:{:02d}',
|
||||
'milliseconds': '{:02d}:{:02d}:{:02d}.{:03d}',
|
||||
'microseconds': '{:02d}:{:02d}:{:02d}.{:06d}'
|
||||
}
|
||||
|
||||
if timespec == 'auto':
|
||||
# Skip trailing microseconds when us==0.
|
||||
timespec = 'microseconds' if us else 'seconds'
|
||||
elif timespec == 'milliseconds':
|
||||
us //= 1000
|
||||
try:
|
||||
fmt = specs[timespec]
|
||||
except KeyError:
|
||||
raise ValueError('Unknown timespec value')
|
||||
else:
|
||||
return fmt.format(hh, mm, ss, us)
|
||||
|
||||
|
||||
def _format_offset(off):
|
||||
s = ''
|
||||
if off is not None:
|
||||
if off.days < 0:
|
||||
sign = "-"
|
||||
off = -off
|
||||
else:
|
||||
sign = "+"
|
||||
hh, mm = divmod(off, timedelta(hours=1))
|
||||
mm, ss = divmod(mm, timedelta(minutes=1))
|
||||
s += "%s%02d:%02d" % (sign, hh, mm)
|
||||
if ss or ss.microseconds:
|
||||
s += ":%02d" % ss.seconds
|
||||
|
||||
if ss.microseconds:
|
||||
s += '.%06d' % ss.microseconds
|
||||
return s
|
||||
|
||||
def _check_utc_offset(name, offset):
|
||||
assert name in ("utcoffset", "dst")
|
||||
if offset is None:
|
||||
return
|
||||
if not isinstance(offset, timedelta):
|
||||
raise TypeError("tzinfo.%s() must return None "
|
||||
"or timedelta, not '%s'" % (name, type(offset)))
|
||||
if not -timedelta(1) < offset < timedelta(1):
|
||||
raise ValueError("%s()=%s, must be strictly between "
|
||||
"-timedelta(hours=24) and timedelta(hours=24)" %
|
||||
(name, offset))
|
||||
|
||||
|
||||
class timedelta:
|
||||
def __init__(self, days=0, seconds=0, microseconds=0,
|
||||
milliseconds=0, minutes=0, hours=0, weeks=0):
|
||||
days += 7 * weeks
|
||||
seconds += 60 * minutes + 3600 * hours
|
||||
microseconds += 1000 * milliseconds #微秒 = 1000*毫秒
|
||||
|
||||
if microseconds >= 1000000:
|
||||
seconds += microseconds // 1000000
|
||||
microseconds %= 1000000
|
||||
if seconds >= 3600 * 24:
|
||||
days += seconds // (3600 * 24)
|
||||
seconds %= (3600 * 24)
|
||||
if builtins.abs(days) > 999999999:
|
||||
raise OverflowError('timedelta # of days is Overflow')
|
||||
|
||||
self.days = days
|
||||
self.second = seconds
|
||||
self.microseconds = microseconds
|
||||
|
||||
def _getstate(self):
|
||||
return (self._days, self._seconds, self._microseconds)
|
||||
|
||||
def _cmp(self, a, b):
|
||||
if a[0] > b[0]:
|
||||
return 1
|
||||
if a[0] < b[0]:
|
||||
return -1
|
||||
if a[1] > b[1]:
|
||||
return 1
|
||||
if a[1] < b[1]:
|
||||
return -1
|
||||
if a[2] > b[2]:
|
||||
return 1
|
||||
if a[2] < b[2]:
|
||||
return -1
|
||||
return 0
|
||||
|
||||
def __cmp__(self, other):
|
||||
assert isinstance(other, timedelta)
|
||||
return self._cmp(self._getstate(), other._getstate())
|
||||
|
||||
def __str__(self):
|
||||
s = 'datetime.timedelta(days=' + str(self.days) + ', '
|
||||
s += 'seconds=' + str(self.second) + ', '
|
||||
s += 'microseconds=' + str(self.microseconds) + ')'
|
||||
return s
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, timedelta):
|
||||
return timedelta(days = self.days + other.days,
|
||||
seconds = self.second + other.second,
|
||||
microseconds = self.microseconds + other.microseconds)
|
||||
return NotImplemented
|
||||
|
||||
def __sub__(self, other):
|
||||
if isinstance(other, timedelta):
|
||||
return timedelta(days = self.days - other.days,
|
||||
seconds = self.second - other.second,
|
||||
microseconds = self.microseconds - other.microseconds)
|
||||
return NotImplemented
|
||||
def __neg__(self):
|
||||
return timedelta(days = -self.days,
|
||||
seconds = -self.second,
|
||||
microseconds= -self.microseconds)
|
||||
def __pos__(self):
|
||||
return self
|
||||
|
||||
def __abs__(self):
|
||||
if self._days < 0:
|
||||
return -self
|
||||
else:
|
||||
return self
|
||||
|
||||
class tzinfo:
|
||||
#返回当地时间相对于UTC的偏移量,以UTC以东分钟为单位
|
||||
def utcoffset(self, dt):
|
||||
raise NotImplementedError("tzinfo subclass must override utcoffset()")
|
||||
|
||||
#返回夏令时(DST)调整,以UTC以东的分钟为单位;如果夏令时信息未知,则返回None
|
||||
def dst(self, dt):
|
||||
raise NotImplementedError("tzinfo subclass must override dst()")
|
||||
|
||||
#将对应于 datetime 对象 dt 的时区名称作为字符串返回
|
||||
def tzname(self, dt):
|
||||
raise NotImplementedError("tzinfo subclass must override tzname()")
|
||||
|
||||
def fromutc(self, dt):
|
||||
# raise ValueError error if dt.tzinfo is not self
|
||||
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")
|
||||
dtoff = dt.utcoffset()
|
||||
# raise ValueError if dtoff is None or dtdst is None
|
||||
if dtoff is None:
|
||||
raise ValueError("fromutc() requires a non-None utcoffset() "
|
||||
"result")
|
||||
dtdst = dt.dst()
|
||||
delta = dtoff - dtdst # this is self's standard offset
|
||||
if delta:
|
||||
dt += delta # convert to standard local time
|
||||
dtdst = dt.dst()
|
||||
# raise ValueError if dtdst is None
|
||||
if dtdst is None:
|
||||
raise ValueError("fromutc(): dt.dst gave inconsistent "
|
||||
"results; cannot convert")
|
||||
if dtdst:
|
||||
return dt + dtdst
|
||||
else:
|
||||
return dt
|
||||
|
||||
def _check_tzinfo_arg(tz):
|
||||
if tz is not None and not isinstance(tz, tzinfo):
|
||||
raise TypeError("tzinfo argument must be None or of a tzinfo subclass")
|
||||
|
||||
class timezone(tzinfo):
|
||||
_Omitted = object()
|
||||
|
||||
def __init__(self, offset, name=_Omitted):
|
||||
if not isinstance(offset, timedelta):
|
||||
raise TypeError("offset must be a timedelta")
|
||||
if name is self._Omitted:
|
||||
if not offset:
|
||||
return self.utc
|
||||
name = None
|
||||
elif name != None and not isinstance(name, str):
|
||||
raise TypeError("name must be a string")
|
||||
if not self._minoffset.second <= offset.second <= self._maxoffset.second:
|
||||
raise ValueError("offset must be a timedelta "
|
||||
"strictly between -timedelta(hours=24) and "
|
||||
"timedelta(hours=24).")
|
||||
self._offset = offset
|
||||
self._name = name
|
||||
#self.utc = timezone(timedelta(0))
|
||||
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.tzname(None)
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if isinstance(dt, datetime) or dt is None:
|
||||
return self._offset
|
||||
raise TypeError("utcoffset() argument must be a datetime instance"
|
||||
" or None")
|
||||
|
||||
def tzname(self, dt):
|
||||
if isinstance(dt, datetime) or dt is None:
|
||||
if self._name is None:
|
||||
return self._name_from_offset(self._offset)
|
||||
return self._name
|
||||
raise TypeError("tzname() argument must be a datetime instance"
|
||||
" or None")
|
||||
|
||||
def dst(self, dt):
|
||||
if isinstance(dt, datetime) or dt is None:
|
||||
return None
|
||||
raise TypeError("dst() argument must be a datetime instance"
|
||||
" or None")
|
||||
|
||||
def fromutc(self, dt):
|
||||
if isinstance(dt, datetime):
|
||||
if dt.tzinfo is not self:
|
||||
raise ValueError("fromutc: dt.tzinfo "
|
||||
"is not self")
|
||||
return dt + self._offset
|
||||
raise TypeError("fromutc() argument must be a datetime instance"
|
||||
" or None")
|
||||
|
||||
_maxoffset = timedelta(hours=24, microseconds=-1)
|
||||
_minoffset = -_maxoffset
|
||||
'''
|
||||
TODO:
|
||||
- 运算符重载
|
||||
- hash()
|
||||
- cmp()
|
||||
'''
|
||||
|
||||
|
||||
def check_time_arg(hour=0, minute=0,
|
||||
second=0, microsecond=0, fold=0):
|
||||
if hour < 0 or hour >= 24 or minute < 0 or minute >= 60 or second < 0 or second >= 60 or microsecond < 0 or microsecond >= 1000000 or fold not in [
|
||||
0, 1]:
|
||||
raise ValueError('time() argument error, eg: 0 <= hour < 24, please to check')
|
||||
|
||||
class time:
|
||||
def __init__(self, hour=0, minute=0,
|
||||
second=0, microsecond=0, tzinfo=None, *, fold=0):
|
||||
_check_tzinfo_arg(tzinfo)
|
||||
check_time_arg(hour, minute, second, microsecond, fold)
|
||||
|
||||
self._hour = hour
|
||||
self._minute = minute
|
||||
self._second = second
|
||||
self._microsecond = microsecond
|
||||
self._tzinfo = tzinfo
|
||||
self._hashcode = -1
|
||||
self._fold = fold
|
||||
|
||||
def replace(self, hour=None, minute=None,
|
||||
second=None, microsecond=None, tzinfo=True, *, fold=None):
|
||||
|
||||
if hour is None:
|
||||
hour = self.hour
|
||||
if minute is None:
|
||||
minute = self.minute
|
||||
if second is None:
|
||||
second = self.second
|
||||
if microsecond is None:
|
||||
microsecond = self.microsecond
|
||||
if tzinfo is True:
|
||||
tzinfo = self.tzinfo
|
||||
if fold is None:
|
||||
fold = self._fold
|
||||
|
||||
if hour < 0 or hour >= 24 or minute < 0 or minute >= 60 or second < 0 or second >= 60 or microsecond < 0 or microsecond >= 1000000 or fold not in [0, 1]:
|
||||
raise ValueError('time() argument error, eg: 0 <= hour < 24, please to check')
|
||||
|
||||
|
||||
return type(self)(hour, minute, second, microsecond, tzinfo, fold=fold)
|
||||
|
||||
def utcoffset(self):
|
||||
"""Return the timezone offset as timedelta, positive east of UTC
|
||||
(negative west of UTC)."""
|
||||
if self._tzinfo is None:
|
||||
return None
|
||||
offset = self._tzinfo.utcoffset(None)
|
||||
_check_utc_offset("utcoffset", offset)
|
||||
return offset
|
||||
|
||||
def _tzstr(self):
|
||||
"""Return formatted timezone offset (+xx:xx) or an empty string."""
|
||||
off = self.utcoffset()
|
||||
return _format_offset(off)
|
||||
|
||||
def isoformat(self, timespec='auto'):
|
||||
"""Return the time formatted according to ISO.
|
||||
The full format is 'HH:MM:SS.mmmmmm+zz:zz'. By default, the fractional
|
||||
part is omitted if self.microsecond == 0.
|
||||
The optional argument timespec specifies the number of additional
|
||||
terms of the time to include. Valid options are 'auto', 'hours',
|
||||
'minutes', 'seconds', 'milliseconds' and 'microseconds'.
|
||||
"""
|
||||
s = _format_time(self._hour, self._minute, self._second,
|
||||
self._microsecond, timespec)
|
||||
tz = self._tzstr()
|
||||
if tz:
|
||||
s += tz
|
||||
return s
|
||||
|
||||
class date:
|
||||
def __init__(self, year=None, month=None, day=None):
|
||||
if year < MINYEAR or year > MAXYEAR:
|
||||
raise ValueError('year must be in [1, 9999]')
|
||||
if month < 1 or month > 12:
|
||||
raise ValueError('month must be in [1, 12]')
|
||||
_max_day = MONTH_DAYS[month]
|
||||
if month == 2 and is_leap(year):
|
||||
_max_day += 1
|
||||
if day < 1 or day > _max_day:
|
||||
raise ValueError('day must be in [1, ', MONTH_DAYS[month], ']')
|
||||
|
||||
self.year = year
|
||||
self.month = month
|
||||
self.day = day
|
||||
|
||||
def fromtimestamp(self, t):
|
||||
y, m, d, hh, mm, ss, weekday, jday, dst = time.localtime(t)
|
||||
return date(y, m, d)
|
||||
|
||||
def today(self):
|
||||
t = time.time()
|
||||
return self.fromtimestamp(t)
|
||||
|
||||
def fromisoformat(date_string):
|
||||
"""Construct a date from a string in ISO 8601 format."""
|
||||
if not isinstance(date_string, str):
|
||||
raise TypeError('fromisoformat: argument must be str')
|
||||
|
||||
if len(date_string) not in (7, 8, 10):
|
||||
raise ValueError(f'Invalid isoformat string: {date_string}')
|
||||
|
||||
try:
|
||||
return datetime(*_parse_isoformat_date(date_string))
|
||||
except Exception:
|
||||
raise ValueError(f'Invalid isoformat string: {date_string}')
|
||||
|
||||
'''
|
||||
def __repr__(self):
|
||||
"""Convert to formal string, for repr().
|
||||
|
||||
>>> dt = datetime(2010, 1, 1)
|
||||
>>> repr(dt)
|
||||
'datetime.datetime(2010, 1, 1, 0, 0)'
|
||||
|
||||
>>> dt = datetime(2010, 1, 1, tzinfo=timezone.utc)
|
||||
>>> repr(dt)
|
||||
'datetime.datetime(2010, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)'
|
||||
"""
|
||||
return "%s.%s(%d, %d, %d)" % (_get_class_module(self),
|
||||
self.__class__.__qualname__,
|
||||
self._year,
|
||||
self._month,
|
||||
self._day)
|
||||
'''
|
||||
def _find_isoformat_datetime_separator(dtstr):
|
||||
# See the comment in _datetimemodule.c:_find_isoformat_datetime_separator
|
||||
len_dtstr = len(dtstr)
|
||||
if len_dtstr == 7:
|
||||
return 7
|
||||
|
||||
assert len_dtstr > 7
|
||||
date_separator = "-"
|
||||
week_indicator = "W"
|
||||
|
||||
if dtstr[4] == date_separator:
|
||||
if dtstr[5] == week_indicator:
|
||||
if len_dtstr < 8:
|
||||
raise ValueError("Invalid ISO string")
|
||||
if len_dtstr > 8 and dtstr[8] == date_separator:
|
||||
if len_dtstr == 9:
|
||||
raise ValueError("Invalid ISO string")
|
||||
if len_dtstr > 10 and _is_ascii_digit(dtstr[10]):
|
||||
# This is as far as we need to resolve the ambiguity for
|
||||
# the moment - if we have YYYY-Www-##, the separator is
|
||||
# either a hyphen at 8 or a number at 10.
|
||||
#
|
||||
# We'll assume it's a hyphen at 8 because it's way more
|
||||
# likely that someone will use a hyphen as a separator than
|
||||
# a number, but at this point it's really best effort
|
||||
# because this is an extension of the spec anyway.
|
||||
# TODO(pganssle): Document this
|
||||
return 8
|
||||
return 10
|
||||
else:
|
||||
# YYYY-Www (8)
|
||||
return 8
|
||||
else:
|
||||
# YYYY-MM-DD (10)
|
||||
return 10
|
||||
else:
|
||||
if dtstr[4] == week_indicator:
|
||||
# YYYYWww (7) or YYYYWwwd (8)
|
||||
idx = 7
|
||||
while idx < len_dtstr:
|
||||
if not _is_ascii_digit(dtstr[idx]):
|
||||
break
|
||||
idx += 1
|
||||
|
||||
if idx < 9:
|
||||
return idx
|
||||
|
||||
if idx % 2 == 0:
|
||||
# If the index of the last number is even, it's YYYYWwwd
|
||||
return 7
|
||||
else:
|
||||
return 8
|
||||
else:
|
||||
# YYYYMMDD (8)
|
||||
return 8
|
||||
|
||||
def _isoweek1monday(year):
|
||||
# Helper to calculate the day number of the Monday starting week 1
|
||||
# XXX This could be done more efficiently
|
||||
THURSDAY = 3
|
||||
firstday = _ymd2ord(year, 1, 1)
|
||||
firstweekday = (firstday + 6) % 7 # See weekday() above
|
||||
week1monday = firstday - firstweekday
|
||||
if firstweekday > THURSDAY:
|
||||
week1monday += 7
|
||||
return week1monday
|
||||
|
||||
def _isoweek_to_gregorian(year, week, day):
|
||||
# Year is bounded this way because 9999-12-31 is (9999, 52, 5)
|
||||
if not MINYEAR <= year <= MAXYEAR:
|
||||
raise ValueError(f"Year is out of range: {year}")
|
||||
|
||||
if not 0 < week < 53:
|
||||
out_of_range = True
|
||||
|
||||
if week == 53:
|
||||
# ISO years have 53 weeks in them on years starting with a
|
||||
# Thursday and leap years starting on a Wednesday
|
||||
first_weekday = _ymd2ord(year, 1, 1) % 7
|
||||
if (first_weekday == 4 or (first_weekday == 3 and
|
||||
is_leap(year))):
|
||||
out_of_range = False
|
||||
|
||||
if out_of_range:
|
||||
raise ValueError(f"Invalid week: {week}")
|
||||
|
||||
if not 0 < day < 8:
|
||||
raise ValueError(f"Invalid weekday: {day} (range is [1, 7])")
|
||||
|
||||
# Now compute the offset from (Y, 1, 1) in days:
|
||||
day_offset = (week - 1) * 7 + (day - 1)
|
||||
|
||||
# Calculate the ordinal day for monday, week 1
|
||||
day_1 = _isoweek1monday(year)
|
||||
ord_day = day_1 + day_offset
|
||||
|
||||
return _ord2ymd(ord_day)
|
||||
|
||||
def _parse_isoformat_date(dtstr):
|
||||
# It is assumed that this is an ASCII-only string of lengths 7, 8 or 10,
|
||||
# see the comment on Modules/_datetimemodule.c:_find_isoformat_datetime_separator
|
||||
assert len(dtstr) in (7, 8, 10)
|
||||
year = int(dtstr[0:4])
|
||||
has_sep = dtstr[4] == '-'
|
||||
|
||||
pos = 4 + has_sep
|
||||
if dtstr[pos:pos + 1] == "W":
|
||||
# YYYY-?Www-?D?
|
||||
pos += 1
|
||||
weekno = int(dtstr[pos:pos + 2])
|
||||
pos += 2
|
||||
|
||||
dayno = 1
|
||||
if len(dtstr) > pos:
|
||||
if (dtstr[pos:pos + 1] == '-') != has_sep:
|
||||
raise ValueError("Inconsistent use of dash separator")
|
||||
|
||||
pos += has_sep
|
||||
|
||||
dayno = int(dtstr[pos:pos + 1])
|
||||
|
||||
return list(_isoweek_to_gregorian(year, weekno, dayno))
|
||||
else:
|
||||
month = int(dtstr[pos:pos + 2])
|
||||
pos += 2
|
||||
if (dtstr[pos:pos + 1] == "-") != has_sep:
|
||||
raise ValueError("Inconsistent use of dash separator")
|
||||
|
||||
pos += has_sep
|
||||
day = int(dtstr[pos:pos + 2])
|
||||
|
||||
return [year, month, day]
|
||||
|
||||
_FRACTION_CORRECTION = [100000, 10000, 1000, 100, 10]
|
||||
|
||||
def _parse_hh_mm_ss_ff(tstr):
|
||||
# Parses things of the form HH[:?MM[:?SS[{.,}fff[fff]]]]
|
||||
len_str = len(tstr)
|
||||
|
||||
time_comps = [0, 0, 0, 0]
|
||||
pos = 0
|
||||
for comp in range(0, 3):
|
||||
if (len_str - pos) < 2:
|
||||
raise ValueError("Incomplete time component")
|
||||
|
||||
time_comps[comp] = int(tstr[pos:pos+2])
|
||||
|
||||
pos += 2
|
||||
next_char = tstr[pos:pos+1]
|
||||
|
||||
if comp == 0:
|
||||
has_sep = next_char == ':'
|
||||
|
||||
if not next_char or comp >= 2:
|
||||
break
|
||||
|
||||
if has_sep and next_char != ':':
|
||||
raise ValueError("Invalid time separator: %c" % next_char)
|
||||
|
||||
pos += has_sep
|
||||
|
||||
if pos < len_str:
|
||||
if tstr[pos] not in '.,':
|
||||
raise ValueError("Invalid microsecond component")
|
||||
else:
|
||||
pos += 1
|
||||
|
||||
len_remainder = len_str - pos
|
||||
|
||||
if len_remainder >= 6:
|
||||
to_parse = 6
|
||||
else:
|
||||
to_parse = len_remainder
|
||||
|
||||
time_comps[3] = int(tstr[pos:(pos+to_parse)])
|
||||
if to_parse < 6:
|
||||
time_comps[3] *= _FRACTION_CORRECTION[to_parse-1]
|
||||
if (len_remainder > to_parse
|
||||
and not all(map(_is_ascii_digit, tstr[(pos+to_parse):]))):
|
||||
raise ValueError("Non-digit values in unparsed fraction")
|
||||
|
||||
return time_comps
|
||||
|
||||
def _parse_isoformat_time(tstr):
|
||||
# Format supported is HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]
|
||||
len_str = len(tstr)
|
||||
if len_str < 2:
|
||||
raise ValueError("Isoformat time too short")
|
||||
|
||||
# This is equivalent to re.search('[+-Z]', tstr), but faster
|
||||
tz_pos = (tstr.find('-') + 1 or tstr.find('+') + 1 or tstr.find('Z') + 1)
|
||||
timestr = tstr[:tz_pos-1] if tz_pos > 0 else tstr
|
||||
|
||||
time_comps = _parse_hh_mm_ss_ff(timestr)
|
||||
|
||||
tzi = None
|
||||
if tz_pos == len_str and tstr[-1] == 'Z':
|
||||
tzi = timezone.utc
|
||||
elif tz_pos > 0:
|
||||
tzstr = tstr[tz_pos:]
|
||||
|
||||
# Valid time zone strings are:
|
||||
# HH len: 2
|
||||
# HHMM len: 4
|
||||
# HH:MM len: 5
|
||||
# HHMMSS len: 6
|
||||
# HHMMSS.f+ len: 7+
|
||||
# HH:MM:SS len: 8
|
||||
# HH:MM:SS.f+ len: 10+
|
||||
|
||||
if len(tzstr) in (0, 1, 3):
|
||||
raise ValueError("Malformed time zone string")
|
||||
|
||||
tz_comps = _parse_hh_mm_ss_ff(tzstr)
|
||||
|
||||
if all(x == 0 for x in tz_comps):
|
||||
tzi = timezone.utc
|
||||
else:
|
||||
tzsign = -1 if tstr[tz_pos - 1] == '-' else 1
|
||||
|
||||
td = timedelta(hours=tz_comps[0], minutes=tz_comps[1],
|
||||
seconds=tz_comps[2], microseconds=tz_comps[3])
|
||||
|
||||
tzi = timezone(tzsign * td)
|
||||
|
||||
time_comps.append(tzi)
|
||||
|
||||
return time_comps
|
||||
|
||||
|
||||
class datetime(date):
|
||||
def __init__(self, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0):
|
||||
check_time_arg(hour, minute, second, microsecond, fold)
|
||||
self._year = year
|
||||
self._month = month
|
||||
self._day = day
|
||||
self._hour = hour
|
||||
self._minute = minute
|
||||
self._second = second
|
||||
self._microsecond = microsecond
|
||||
self._tzinfo = tzinfo
|
||||
self._hashcode = -1
|
||||
self._fold = fold
|
||||
|
||||
def hour(self):
|
||||
"""hour (0-23)"""
|
||||
return self._hour
|
||||
|
||||
def minute(self):
|
||||
"""minute (0-59)"""
|
||||
return self._minute
|
||||
|
||||
def second(self):
|
||||
"""second (0-59)"""
|
||||
return self._second
|
||||
|
||||
def microsecond(self):
|
||||
"""microsecond (0-999999)"""
|
||||
return self._microsecond
|
||||
|
||||
def tzinfo(self):
|
||||
"""timezone info object"""
|
||||
return self._tzinfo
|
||||
|
||||
def fold(self):
|
||||
return self._fold
|
||||
|
||||
def _fromtimestamp(self, t, utc, tz):
|
||||
"""Construct a datetime from a POSIX timestamp (like time.time()).
|
||||
|
||||
A timezone info object may be passed in as well.
|
||||
"""
|
||||
frac, t = math.modf(t)
|
||||
us = round(frac * 1e6)
|
||||
if us >= 1000000:
|
||||
t += 1
|
||||
us -= 1000000
|
||||
elif us < 0:
|
||||
t -= 1
|
||||
us += 1000000
|
||||
|
||||
converter = time.gmtime if utc else time.localtime
|
||||
y, m, d, hh, mm, ss, weekday, jday, dst = converter(t)
|
||||
ss = min(ss, 59) # clamp out leap seconds if the platform has them
|
||||
result = self(y, m, d, hh, mm, ss, us, tz)
|
||||
if tz is None and not utc:
|
||||
# As of version 2015f max fold in IANA database is
|
||||
# 23 hours at 1969-09-30 13:00:00 in Kwajalein.
|
||||
# Let's probe 24 hours in the past to detect a transition:
|
||||
max_fold_seconds = 24 * 3600
|
||||
|
||||
# On Windows localtime_s throws an OSError for negative values,
|
||||
# thus we can't perform fold detection for values of time less
|
||||
# than the max time fold. See comments in _datetimemodule's
|
||||
# version of this method for more details.
|
||||
if t < max_fold_seconds and sys.platform.startswith("win"):
|
||||
return result
|
||||
|
||||
y, m, d, hh, mm, ss = converter(t - max_fold_seconds)[:6]
|
||||
probe1 = self(y, m, d, hh, mm, ss, us, tz)
|
||||
trans = result - probe1 - timedelta(0, max_fold_seconds)
|
||||
if trans.days < 0:
|
||||
y, m, d, hh, mm, ss = converter(t + trans // timedelta(0, 1))[:6]
|
||||
probe2 = self(y, m, d, hh, mm, ss, us, tz)
|
||||
if probe2 == result:
|
||||
result._fold = 1
|
||||
elif tz is not None:
|
||||
result = tz.fromutc(result)
|
||||
return result
|
||||
|
||||
def fromtimestamp(self, timestamp, tz=None):
|
||||
_check_tzinfo_arg(tz)
|
||||
return self._fromtimestamp(timestamp, tz is not None, tz)
|
||||
|
||||
def now(self, tz=None):
|
||||
t = time.time()
|
||||
return self.fromtimestamp(t, tz)
|
||||
|
||||
def date(self):
|
||||
"Return the date part."
|
||||
return date(self._year, self._month, self._day)
|
||||
|
||||
def time(self):
|
||||
"Return the time part, with tzinfo None."
|
||||
return time(self.hour, self.minute, self.second, self.microsecond, fold=self.fold)
|
||||
|
||||
def timetz(self):
|
||||
"Return the time part, with same tzinfo."
|
||||
return time(self.hour, self.minute, self.second, self.microsecond,
|
||||
self._tzinfo, fold=self.fold)
|
||||
|
||||
def utcoffset(self):
|
||||
"""Return the timezone offset as timedelta, positive east of UTC
|
||||
(negative west of UTC)."""
|
||||
if self._tzinfo is None:
|
||||
return None
|
||||
offset = self._tzinfo.utcoffset(None)
|
||||
_check_utc_offset("utcoffset", offset)
|
||||
return offset
|
||||
|
||||
def isoformat(self, sep='T', timespec='auto'):
|
||||
"""Return the time formatted according to ISO.
|
||||
|
||||
The full format looks like 'YYYY-MM-DD HH:MM:SS.mmmmmm'.
|
||||
By default, the fractional part is omitted if self.microsecond == 0.
|
||||
|
||||
If self.tzinfo is not None, the UTC offset is also attached, giving
|
||||
giving a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM'.
|
||||
|
||||
Optional argument sep specifies the separator between date and
|
||||
time, default 'T'.
|
||||
|
||||
The optional argument timespec specifies the number of additional
|
||||
terms of the time to include. Valid options are 'auto', 'hours',
|
||||
'minutes', 'seconds', 'milliseconds' and 'microseconds'.
|
||||
"""
|
||||
s = ("%04d-%02d-%02d%c" % (self._year, self._month, self._day, sep) +
|
||||
_format_time(self._hour, self._minute, self._second,
|
||||
self._microsecond, timespec))
|
||||
|
||||
off = self.utcoffset()
|
||||
tz = _format_offset(off)
|
||||
if tz:
|
||||
s += tz
|
||||
|
||||
return s
|
||||
def __repr__(self):
|
||||
"""Convert to formal string, for repr().
|
||||
|
||||
>>> dt = datetime(2010, 1, 1)
|
||||
>>> repr(dt)
|
||||
'datetime.datetime(2010, 1, 1, 0, 0)'
|
||||
|
||||
>>> dt = datetime(2010, 1, 1, tzinfo=timezone.utc)
|
||||
>>> repr(dt)
|
||||
'datetime.datetime(2010, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)'
|
||||
"""
|
||||
return "datetime.datetime(%d, %d, %d)" % (self._year,
|
||||
self._month,
|
||||
self._day)
|
||||
'''
|
||||
def __str__(self):
|
||||
"Convert to string, for str()."
|
||||
return self.isoformat(sep=' ')
|
||||
'''
|
||||
Loading…
x
Reference in New Issue
Block a user