npdatetime 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- npdatetime/__init__.py +1090 -0
- npdatetime/__init__.pyi +293 -0
- npdatetime/_custom_strptime.py +305 -0
- npdatetime/config.py +14 -0
- npdatetime/data/calendar_bs.csv +127 -0
- npdatetime-0.1.1.dist-info/LICENSE +20 -0
- npdatetime-0.1.1.dist-info/METADATA +180 -0
- npdatetime-0.1.1.dist-info/RECORD +10 -0
- npdatetime-0.1.1.dist-info/WHEEL +5 -0
- npdatetime-0.1.1.dist-info/top_level.txt +1 -0
npdatetime/__init__.py
ADDED
|
@@ -0,0 +1,1090 @@
|
|
|
1
|
+
"""An inspired library from Python's `datetime` library which will operate on top of
|
|
2
|
+
Bikram Sambat (B.S) date. Currently supported B.S date range is 1975 - 2100. Most of the code &
|
|
3
|
+
documentation are derived from Python3.5 's datetime.py module & later modified to support
|
|
4
|
+
npdatetime.
|
|
5
|
+
|
|
6
|
+
Supports >= Python3.5
|
|
7
|
+
"""
|
|
8
|
+
# __version__ = "0.1.1"
|
|
9
|
+
|
|
10
|
+
__author__ = "Amrit Giri <amritgiri02595@gmail.com>"
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import csv
|
|
14
|
+
import time as _time
|
|
15
|
+
import math as _math
|
|
16
|
+
import datetime as _actual_datetime
|
|
17
|
+
|
|
18
|
+
from .config import CALENDAR_PATH, MINDATE, MAXDATE, REFERENCE_DATE_AD
|
|
19
|
+
|
|
20
|
+
MINYEAR = MINDATE['year']
|
|
21
|
+
MAXYEAR = MAXDATE['year']
|
|
22
|
+
|
|
23
|
+
NEPAL_TIME_UTC_OFFSET = 20700
|
|
24
|
+
|
|
25
|
+
_MONTHNAMES = (None, "Bai", "Jes", "Asa", "Shr", "Bha", "Asw", "Kar", "Man", "Pou", "Mag", "Fal", "Cha")
|
|
26
|
+
_FULLMONTHNAMES = (None, "Baishakh", "Jestha", "Asar", "Shrawan", "Bhadau", "Aswin", "Kartik", "Mangsir", "Poush", "Magh", "Falgun", "Chaitra")
|
|
27
|
+
_MONTHNAMES_NP = (None, "वैशाख", "जेष्ठ", "असार", "श्रावण", "भदौ", "आश्विन", "कार्तिक", "मंसिर", "पौष", "माघ", "फाल्गुण", "चैत्र")
|
|
28
|
+
|
|
29
|
+
_DAYNAMES = (None, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
|
|
30
|
+
_FULLDAYNAMES = (None, "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
|
|
31
|
+
_FULLDAYNAMES_NP = (None, "सोमबार", "मंगलबार", "बुधवार", "बिहिबार", "शुक्रबार", "शनिबार", "आइतबार")
|
|
32
|
+
|
|
33
|
+
_DIGIT_NP = "०१२३४५६७८९"
|
|
34
|
+
_EPOCH = _actual_datetime.datetime(1970, 1, 1, tzinfo=_actual_datetime.timezone.utc)
|
|
35
|
+
|
|
36
|
+
_STRFTIME_CUSTOM_MAP = {
|
|
37
|
+
'a': lambda o: '%s' % _DAYNAMES[(o.weekday() % 7) or 7],
|
|
38
|
+
'A': lambda o: '%s' % _FULLDAYNAMES[(o.weekday() % 7) or 7],
|
|
39
|
+
'G': lambda o: '%s' % _FULLDAYNAMES_NP[(o.weekday() % 7) or 7],
|
|
40
|
+
'w': lambda o: '%d' % o.weekday(),
|
|
41
|
+
'd': lambda o: '%02d' % o.day,
|
|
42
|
+
'D': lambda o: ''.join(_DIGIT_NP[int(i)] for i in '%02d' % o.day),
|
|
43
|
+
'b': lambda o: '%s' % _MONTHNAMES[o.month],
|
|
44
|
+
'B': lambda o: '%s' % _FULLMONTHNAMES[o.month],
|
|
45
|
+
'N': lambda o: '%s' % _MONTHNAMES_NP[o.month],
|
|
46
|
+
'm': lambda o: '%02d' % o.month,
|
|
47
|
+
'n': lambda o: ''.join(_DIGIT_NP[int(i)] for i in '%02d' % o.month),
|
|
48
|
+
'y': lambda o: '%02d' % (o.year % 100),
|
|
49
|
+
'Y': lambda o: '%d' % o.year,
|
|
50
|
+
'k': lambda o: ''.join(_DIGIT_NP[int(i)] for i in '%02d' % (o.year % 100)),
|
|
51
|
+
'K': lambda o: ''.join(_DIGIT_NP[int(i)] for i in '%d' % o.year),
|
|
52
|
+
'H': lambda o: '%02d' % getattr(o, 'hour', 0),
|
|
53
|
+
'h': lambda o: ''.join(_DIGIT_NP[int(i)] for i in '%02d' % getattr(o, 'hour', 0)),
|
|
54
|
+
'I': lambda o: '%02d' % (getattr(o, 'hour', 0) % 12,),
|
|
55
|
+
'i': lambda o: ''.join(_DIGIT_NP[int(i)] for i in '%02d' % (getattr(o, 'hour', 0) % 12,)),
|
|
56
|
+
'p': lambda o: 'AM' if getattr(o, 'hour', 0) < 12 else 'PM',
|
|
57
|
+
'M': lambda o: '%02d' % getattr(o, 'minute', 0),
|
|
58
|
+
'l': lambda o: ''.join(_DIGIT_NP[int(i)] for i in '%02d' % getattr(o, 'minute', 0)),
|
|
59
|
+
'S': lambda o: '%02d' % getattr(o, 'second', 0),
|
|
60
|
+
's': lambda o: ''.join(_DIGIT_NP[int(i)] for i in '%02d' % getattr(o, 'second', 0)),
|
|
61
|
+
'U': lambda o: '%02d' % ((o.timetuple().tm_yday + 7 - o.weekday()) // 7,),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_CALENDAR = {}
|
|
65
|
+
_DAYS_BEFORE_YEAR = []
|
|
66
|
+
|
|
67
|
+
with open(CALENDAR_PATH, 'r') as calendar_file:
|
|
68
|
+
file = csv.reader(calendar_file)
|
|
69
|
+
next(file)
|
|
70
|
+
for row in file:
|
|
71
|
+
_CALENDAR[int(row[0])] = [-1, *[sum(int(j) for j in row[1:i]) for i in range(2, 14)]]
|
|
72
|
+
_DAYS_BEFORE_YEAR.append(sum(int(i) for i in row[1:]) + (_DAYS_BEFORE_YEAR[-1] if _DAYS_BEFORE_YEAR else 0))
|
|
73
|
+
|
|
74
|
+
_MAXORDINAL = _DAYS_BEFORE_YEAR[-1]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _build_struct_time(y, m, d, hh, mm, ss, dstflag):
|
|
78
|
+
wday = (_ymd2ord(y, m, d) + 5) % 7
|
|
79
|
+
dnum = _days_before_month(y, m) + d
|
|
80
|
+
return _time.struct_time((y, m, d, hh, mm, ss, wday, dnum, dstflag))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _format_time(hh, mm, ss, us):
|
|
84
|
+
# Skip trailing microseconds when us==0.
|
|
85
|
+
result = "%02d:%02d:%02d" % (hh, mm, ss)
|
|
86
|
+
if us:
|
|
87
|
+
result += ".%06d" % us
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _wrap_strftime(object, format):
|
|
92
|
+
# Don't call utcoffset() or tzname() unless actually needed.
|
|
93
|
+
freplace = None # the string to use for %f
|
|
94
|
+
zreplace = None # the string to use for %z
|
|
95
|
+
Zreplace = None # the string to use for %Z
|
|
96
|
+
|
|
97
|
+
# Scan format for %z and %Z escapes, replacing as needed.
|
|
98
|
+
newformat = []
|
|
99
|
+
push = newformat.append
|
|
100
|
+
i, n = 0, len(format)
|
|
101
|
+
while i < n:
|
|
102
|
+
ch = format[i]
|
|
103
|
+
i += 1
|
|
104
|
+
if ch == '%':
|
|
105
|
+
if i < n:
|
|
106
|
+
ch = format[i]
|
|
107
|
+
i += 1
|
|
108
|
+
if ch == 'f':
|
|
109
|
+
if freplace is None:
|
|
110
|
+
freplace = '%06d' % getattr(object, 'microsecond', 0)
|
|
111
|
+
newformat.append(freplace)
|
|
112
|
+
elif ch == 'z':
|
|
113
|
+
if zreplace is None:
|
|
114
|
+
zreplace = ""
|
|
115
|
+
if hasattr(object, "utcoffset"):
|
|
116
|
+
offset = object.utcoffset()
|
|
117
|
+
if offset is not None:
|
|
118
|
+
sign = '+'
|
|
119
|
+
if offset.days < 0:
|
|
120
|
+
offset = -offset
|
|
121
|
+
sign = '-'
|
|
122
|
+
h, m = divmod(offset, _actual_datetime.timedelta(hours=1))
|
|
123
|
+
assert not m % _actual_datetime.timedelta(minutes=1), "whole minute"
|
|
124
|
+
m //= _actual_datetime.timedelta(minutes=1)
|
|
125
|
+
zreplace = '%c%02d%02d' % (sign, h, m)
|
|
126
|
+
assert '%' not in zreplace
|
|
127
|
+
newformat.append(zreplace)
|
|
128
|
+
elif ch == 'Z':
|
|
129
|
+
if Zreplace is None:
|
|
130
|
+
Zreplace = ""
|
|
131
|
+
if hasattr(object, "tzname"):
|
|
132
|
+
s = object.tzname()
|
|
133
|
+
if s is not None:
|
|
134
|
+
# strftime is going to have at this: escape %
|
|
135
|
+
Zreplace = s.replace('%', '%%')
|
|
136
|
+
newformat.append(Zreplace)
|
|
137
|
+
elif ch in _STRFTIME_CUSTOM_MAP.keys():
|
|
138
|
+
newformat.append(_STRFTIME_CUSTOM_MAP[ch](object))
|
|
139
|
+
else:
|
|
140
|
+
push('%')
|
|
141
|
+
push(ch)
|
|
142
|
+
else:
|
|
143
|
+
push('%')
|
|
144
|
+
else:
|
|
145
|
+
push(ch)
|
|
146
|
+
newformat = "".join(newformat)
|
|
147
|
+
return newformat
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _bin_search(key, *arr):
|
|
151
|
+
index = 0
|
|
152
|
+
while True:
|
|
153
|
+
if len(arr) == 1:
|
|
154
|
+
break
|
|
155
|
+
mid = len(arr) // 2 - 1 + len(arr) % 2
|
|
156
|
+
index += mid
|
|
157
|
+
if key == arr[mid]:
|
|
158
|
+
break
|
|
159
|
+
elif key < arr[mid]:
|
|
160
|
+
index -= mid
|
|
161
|
+
arr = arr[:mid + 1]
|
|
162
|
+
else:
|
|
163
|
+
index += 1
|
|
164
|
+
arr = arr[mid + 1:]
|
|
165
|
+
return index
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _check_tzname(name):
|
|
169
|
+
if name is not None and not isinstance(name, str):
|
|
170
|
+
raise TypeError("tzinfo.tzname() must return None or string, not '%s'" % type(name))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _check_utc_offset(name, offset):
|
|
174
|
+
assert name in ("utcoffset", "dst")
|
|
175
|
+
if offset is None:
|
|
176
|
+
return
|
|
177
|
+
if not isinstance(offset, _actual_datetime.timedelta):
|
|
178
|
+
raise TypeError("tzinfo.%s() must return None "
|
|
179
|
+
"or _actual_datetime.timedelta, not '%s'" % (name, type(offset)))
|
|
180
|
+
if offset % _actual_datetime.timedelta(minutes=1) or offset.microseconds:
|
|
181
|
+
raise ValueError("tzinfo.%s() must return a whole number "
|
|
182
|
+
"of minutes, got %s" % (name, offset))
|
|
183
|
+
if not -_actual_datetime.timedelta(1) < offset < _actual_datetime.timedelta(1):
|
|
184
|
+
raise ValueError("%s()=%s, must be must be strictly between "
|
|
185
|
+
"-timedelta(hours=24) and timedelta(hours=24)" %
|
|
186
|
+
(name, offset))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _check_int_field(value):
|
|
190
|
+
if isinstance(value, int):
|
|
191
|
+
return value
|
|
192
|
+
if not isinstance(value, float):
|
|
193
|
+
try:
|
|
194
|
+
value = value.__int__()
|
|
195
|
+
except AttributeError:
|
|
196
|
+
pass
|
|
197
|
+
else:
|
|
198
|
+
if isinstance(value, int):
|
|
199
|
+
return value
|
|
200
|
+
raise TypeError('__int__ returned non-int (type %s)' % type(value).__name__)
|
|
201
|
+
raise TypeError('an integer is required (got type %s)' % type(value).__name__)
|
|
202
|
+
raise TypeError('integer argument expected, got float')
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _days_in_month(year, month):
|
|
206
|
+
assert 1 <= month <= 12, month
|
|
207
|
+
if month == 1:
|
|
208
|
+
return _CALENDAR[year][1]
|
|
209
|
+
return _CALENDAR[year][month] - _CALENDAR[year][month - 1]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _days_before_year(year):
|
|
213
|
+
"""year -> number of days before Baishak 1st of year."""
|
|
214
|
+
assert MINYEAR <= year <= MAXYEAR, "year must be in %s..%s" % (MINYEAR, MAXYEAR)
|
|
215
|
+
if year == MINYEAR:
|
|
216
|
+
return 0
|
|
217
|
+
return _DAYS_BEFORE_YEAR[year - MINYEAR - 1]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _days_before_month(year, month):
|
|
221
|
+
"""year, month -> number of days in year preceding first day of month."""
|
|
222
|
+
assert 1 <= month <= 12, 'month must be in 1..12'
|
|
223
|
+
if month == 1:
|
|
224
|
+
return 0
|
|
225
|
+
return _CALENDAR[year][month - 1]
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _ymd2ord(year, month, day):
|
|
229
|
+
"""year, month, day -> ordinal, considering 1975-Bai-01 as day 1."""
|
|
230
|
+
assert 1 <= month <= 12, 'month must be in 1..12'
|
|
231
|
+
dim = _days_in_month(year, month)
|
|
232
|
+
assert 1 <= day <= dim, ('day must be in 1..%d' % dim)
|
|
233
|
+
return _days_before_year(year) + _days_before_month(year, month) + day
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _ord2ymd(n):
|
|
237
|
+
year = MINYEAR + _bin_search(n, *_DAYS_BEFORE_YEAR)
|
|
238
|
+
if year > MINYEAR:
|
|
239
|
+
n -= _DAYS_BEFORE_YEAR[year - MINYEAR - 1]
|
|
240
|
+
month = 1 + _bin_search(n, *_CALENDAR[year][1:])
|
|
241
|
+
if month > 1:
|
|
242
|
+
n -= _CALENDAR[year][month - 1]
|
|
243
|
+
return year, month, n
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _check_date_fields(year, month, day):
|
|
247
|
+
year = _check_int_field(year)
|
|
248
|
+
month = _check_int_field(month)
|
|
249
|
+
day = _check_int_field(day)
|
|
250
|
+
if not MINYEAR <= year <= MAXYEAR:
|
|
251
|
+
raise ValueError('year must be in %d..%d' % (MINYEAR, MAXYEAR), year)
|
|
252
|
+
if not 1 <= month <= 12:
|
|
253
|
+
raise ValueError('month must be in 1..12', month)
|
|
254
|
+
dim = _days_in_month(year, month)
|
|
255
|
+
if not 1 <= day <= dim:
|
|
256
|
+
raise ValueError('day must be in 1..%d' % dim, day)
|
|
257
|
+
return year, month, day
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _check_time_fields(hour, minute, second, microsecond):
|
|
261
|
+
hour = _check_int_field(hour)
|
|
262
|
+
minute = _check_int_field(minute)
|
|
263
|
+
second = _check_int_field(second)
|
|
264
|
+
microsecond = _check_int_field(microsecond)
|
|
265
|
+
if not 0 <= hour <= 23:
|
|
266
|
+
raise ValueError('hour must be in 0..23', hour)
|
|
267
|
+
if not 0 <= minute <= 59:
|
|
268
|
+
raise ValueError('minute must be in 0..59', minute)
|
|
269
|
+
if not 0 <= second <= 59:
|
|
270
|
+
raise ValueError('second must be in 0..59', second)
|
|
271
|
+
if not 0 <= microsecond <= 999999:
|
|
272
|
+
raise ValueError('microsecond must be in 0..999999', microsecond)
|
|
273
|
+
return hour, minute, second, microsecond
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _check_tzinfo_arg(tz):
|
|
277
|
+
if tz is not None and not isinstance(tz, UTC0545):
|
|
278
|
+
raise TypeError("tzinfo argument must be None or of a UTC0545 subclass")
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _cmperror(x, y):
|
|
282
|
+
raise TypeError("can't compare '%s' to '%s'" % (type(x).__name__, type(y).__name__))
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _cmp(x, y):
|
|
286
|
+
return 0 if x == y else 1 if x > y else -1
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class UTC0545(_actual_datetime.tzinfo):
|
|
290
|
+
_offset = _actual_datetime.timedelta(seconds=NEPAL_TIME_UTC_OFFSET)
|
|
291
|
+
_dst = _actual_datetime.timedelta(0)
|
|
292
|
+
_name = "+0545"
|
|
293
|
+
|
|
294
|
+
def utcoffset(self, dt):
|
|
295
|
+
return self.__class__._offset
|
|
296
|
+
|
|
297
|
+
def dst(self, dt):
|
|
298
|
+
return self.__class__._dst
|
|
299
|
+
|
|
300
|
+
def tzname(self, dt):
|
|
301
|
+
return self.__class__._name
|
|
302
|
+
|
|
303
|
+
def fromutc(self, dt):
|
|
304
|
+
"""datetime in UTC -> datetime in local time."""
|
|
305
|
+
|
|
306
|
+
if not isinstance(dt, datetime) and not isinstance(dt, _actual_datetime.datetime):
|
|
307
|
+
raise TypeError("fromutc() requires a npdatetime.datetime or datetime.datetime argument")
|
|
308
|
+
if dt.tzinfo is not self:
|
|
309
|
+
raise ValueError("dt.tzinfo is not self")
|
|
310
|
+
|
|
311
|
+
dtoff = dt.utcoffset()
|
|
312
|
+
if dtoff is None:
|
|
313
|
+
raise ValueError("fromutc() requires a non-None utcoffset() "
|
|
314
|
+
"result")
|
|
315
|
+
|
|
316
|
+
dtdst = dt.dst()
|
|
317
|
+
if dtdst is None:
|
|
318
|
+
raise ValueError("fromutc() requires a non-None dst() result")
|
|
319
|
+
delta = dtoff - dtdst
|
|
320
|
+
if delta:
|
|
321
|
+
dt += delta
|
|
322
|
+
dtdst = dt.dst()
|
|
323
|
+
if dtdst is None:
|
|
324
|
+
raise ValueError("fromutc(): dt.dst gave inconsistent "
|
|
325
|
+
"results; cannot convert")
|
|
326
|
+
return dt + dtdst
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class date:
|
|
330
|
+
__slots__ = ('_year', '_month', '_day')
|
|
331
|
+
|
|
332
|
+
def __new__(cls, year, month=None, day=None):
|
|
333
|
+
year, month, day = _check_date_fields(year, month, day)
|
|
334
|
+
self = object.__new__(cls)
|
|
335
|
+
self._year = year
|
|
336
|
+
self._month = month
|
|
337
|
+
self._day = day
|
|
338
|
+
return self
|
|
339
|
+
|
|
340
|
+
@classmethod
|
|
341
|
+
def fromtimestamp(cls, t):
|
|
342
|
+
"""Construct a date from a POSIX timestamp (like time.time())."""
|
|
343
|
+
y, m, d, hh, mm, ss, weekday, jday, dst = _time.gmtime(t + NEPAL_TIME_UTC_OFFSET)
|
|
344
|
+
return cls.from_datetime_date(_actual_datetime.date(y, m, d))
|
|
345
|
+
|
|
346
|
+
@classmethod
|
|
347
|
+
def today(cls):
|
|
348
|
+
"""Construct a date from time.time()."""
|
|
349
|
+
t = _time.time()
|
|
350
|
+
return cls.fromtimestamp(t)
|
|
351
|
+
|
|
352
|
+
@classmethod
|
|
353
|
+
def fromordinal(cls, n):
|
|
354
|
+
"""Construct a date from a (MINYEAR, 1, 1).
|
|
355
|
+
|
|
356
|
+
Baishak 1 of year 1975 is day 1. Only the year, month and day are
|
|
357
|
+
non-zero in the result.
|
|
358
|
+
"""
|
|
359
|
+
y, m, d = _ord2ymd(n)
|
|
360
|
+
return cls(y, m, d)
|
|
361
|
+
|
|
362
|
+
@classmethod
|
|
363
|
+
def from_datetime_date(cls, from_date):
|
|
364
|
+
"""Convert datetime.date to npdatetime.date (A.D date to B.S).
|
|
365
|
+
|
|
366
|
+
Parameters
|
|
367
|
+
----------
|
|
368
|
+
from_date: datetime.date
|
|
369
|
+
The AD date object to be converted.
|
|
370
|
+
|
|
371
|
+
Returns
|
|
372
|
+
-------
|
|
373
|
+
npdatetime.date
|
|
374
|
+
The converted npdatetime.date object.
|
|
375
|
+
"""
|
|
376
|
+
if not isinstance(from_date, _actual_datetime.date):
|
|
377
|
+
raise TypeError("Unsupported type {}.".format(type(from_date)))
|
|
378
|
+
return cls(MINYEAR, 1, 1) + (from_date - _actual_datetime.date(**REFERENCE_DATE_AD))
|
|
379
|
+
|
|
380
|
+
def to_datetime_date(self):
|
|
381
|
+
"""Convert npdatetime.date to datetime.date (B.S date to A.D).
|
|
382
|
+
|
|
383
|
+
Returns
|
|
384
|
+
-------
|
|
385
|
+
datetime.date
|
|
386
|
+
The converted datetime.date object.
|
|
387
|
+
"""
|
|
388
|
+
return _actual_datetime.date(**REFERENCE_DATE_AD) + _actual_datetime.timedelta(days=self.toordinal() - 1)
|
|
389
|
+
|
|
390
|
+
def calendar(self, justify=4):
|
|
391
|
+
format_str = '{:>%s}' % justify
|
|
392
|
+
|
|
393
|
+
def _mark_today(indx):
|
|
394
|
+
indx_day = cal[indx].index(format_str.format(self.day))
|
|
395
|
+
cal[indx][indx_day] = '\033[31m{}\033[39m'.format(cal[indx][indx_day])
|
|
396
|
+
|
|
397
|
+
total_days_month = _days_in_month(self.year, self.month)
|
|
398
|
+
start_weekday = self.__class__(self.year, self.month, 1).weekday()
|
|
399
|
+
cal = [[('{:^%s}' % ((justify + 1) * 7)).format(self.strftime('%B %Y'))],
|
|
400
|
+
[format_str.format('Sun'), *(format_str.format(j) for j in _DAYNAMES[1:-1])],
|
|
401
|
+
[format_str.format(' ') for _ in range(start_weekday)]]
|
|
402
|
+
cal[-1].extend([format_str.format(j) for j in range(1, 8 - start_weekday)])
|
|
403
|
+
cal_cursor = 8 - start_weekday
|
|
404
|
+
cal_range = [(1, 7 - start_weekday)]
|
|
405
|
+
|
|
406
|
+
total_mid_weeks = (total_days_month - cal_cursor) // 7
|
|
407
|
+
for i in range(total_mid_weeks):
|
|
408
|
+
cal_range.append((cal_cursor, cal_cursor + 6))
|
|
409
|
+
cal.append([format_str.format(j) for j in range(cal_cursor, cal_cursor + 7)])
|
|
410
|
+
cal_cursor += 7
|
|
411
|
+
|
|
412
|
+
if cal_cursor <= total_days_month:
|
|
413
|
+
cal.append([format_str.format(j) for j in range(cal_cursor, total_days_month + 1)])
|
|
414
|
+
cal_range.append((cal_cursor, total_days_month))
|
|
415
|
+
|
|
416
|
+
if sys.platform.startswith('linux'):
|
|
417
|
+
# currently only linux supported
|
|
418
|
+
for i, cr in enumerate(cal_range):
|
|
419
|
+
if cr[0] <= self.day <= cr[1]:
|
|
420
|
+
_mark_today(-len(cal_range) + i)
|
|
421
|
+
break
|
|
422
|
+
|
|
423
|
+
cal = '\n' + '\n'.join(' '.join(j) for j in cal) + '\n\n'
|
|
424
|
+
sys.stdout.write(cal)
|
|
425
|
+
|
|
426
|
+
def __repr__(self):
|
|
427
|
+
return "%s.%s(%d, %d, %d)" % (
|
|
428
|
+
self.__class__.__module__,
|
|
429
|
+
self.__class__.__qualname__,
|
|
430
|
+
self._year,
|
|
431
|
+
self._month,
|
|
432
|
+
self._day
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
def ctime(self):
|
|
436
|
+
"""Return ctime() style string."""
|
|
437
|
+
weekday = (self.toordinal() + 5) % 7 or 7
|
|
438
|
+
return "%s %s %2d 00:00:00 %04d" % (_DAYNAMES[weekday], _MONTHNAMES[self._month], self._day, self._year)
|
|
439
|
+
|
|
440
|
+
def strftime(self, fmt):
|
|
441
|
+
"""Format using strftime()."""
|
|
442
|
+
return _wrap_strftime(self, fmt)
|
|
443
|
+
|
|
444
|
+
def __format__(self, fmt):
|
|
445
|
+
if not isinstance(fmt, str):
|
|
446
|
+
raise TypeError("must be str, not %s" % type(fmt).__name__)
|
|
447
|
+
if len(fmt) != 0:
|
|
448
|
+
return self.strftime(fmt)
|
|
449
|
+
return str(self)
|
|
450
|
+
|
|
451
|
+
def isoformat(self):
|
|
452
|
+
return "%04d-%02d-%02d" % (self._year, self._month, self._day)
|
|
453
|
+
|
|
454
|
+
__str__ = isoformat
|
|
455
|
+
|
|
456
|
+
@property
|
|
457
|
+
def year(self):
|
|
458
|
+
"""year (1975-2100)"""
|
|
459
|
+
return self._year
|
|
460
|
+
|
|
461
|
+
@property
|
|
462
|
+
def month(self):
|
|
463
|
+
"""month (1-12)"""
|
|
464
|
+
return self._month
|
|
465
|
+
|
|
466
|
+
@property
|
|
467
|
+
def day(self):
|
|
468
|
+
"""day (1-32)"""
|
|
469
|
+
return self._day
|
|
470
|
+
|
|
471
|
+
def timetuple(self):
|
|
472
|
+
"""Return local time tuple compatible with time.localtime()."""
|
|
473
|
+
return _build_struct_time(self._year, self._month, self._day, 0, 0, 0, -1)
|
|
474
|
+
|
|
475
|
+
def toordinal(self):
|
|
476
|
+
"""Baishak 1 of year 1975 is day 1. Only the year, month and day values contribute to the result."""
|
|
477
|
+
return _ymd2ord(self._year, self._month, self._day)
|
|
478
|
+
|
|
479
|
+
def replace(self, year=None, month=None, day=None):
|
|
480
|
+
"""Return a new date with new values for the specified fields."""
|
|
481
|
+
if year is None:
|
|
482
|
+
year = self._year
|
|
483
|
+
if month is None:
|
|
484
|
+
month = self._month
|
|
485
|
+
if day is None:
|
|
486
|
+
day = self._day
|
|
487
|
+
return date(year, month, day)
|
|
488
|
+
|
|
489
|
+
def __eq__(self, other):
|
|
490
|
+
if isinstance(other, date):
|
|
491
|
+
return self._cmp(other) == 0
|
|
492
|
+
return NotImplemented
|
|
493
|
+
|
|
494
|
+
def __le__(self, other):
|
|
495
|
+
if isinstance(other, date):
|
|
496
|
+
return self._cmp(other) <= 0
|
|
497
|
+
return NotImplemented
|
|
498
|
+
|
|
499
|
+
def __lt__(self, other):
|
|
500
|
+
if isinstance(other, date):
|
|
501
|
+
return self._cmp(other) < 0
|
|
502
|
+
return NotImplemented
|
|
503
|
+
|
|
504
|
+
def __ge__(self, other):
|
|
505
|
+
if isinstance(other, date):
|
|
506
|
+
return self._cmp(other) >= 0
|
|
507
|
+
return NotImplemented
|
|
508
|
+
|
|
509
|
+
def __gt__(self, other):
|
|
510
|
+
if isinstance(other, date):
|
|
511
|
+
return self._cmp(other) > 0
|
|
512
|
+
return NotImplemented
|
|
513
|
+
|
|
514
|
+
def _cmp(self, other):
|
|
515
|
+
assert isinstance(other, date)
|
|
516
|
+
y, m, d = self._year, self._month, self._day
|
|
517
|
+
y2, m2, d2 = other._year, other._month, other._day
|
|
518
|
+
return _cmp((y, m, d), (y2, m2, d2))
|
|
519
|
+
|
|
520
|
+
def __hash__(self):
|
|
521
|
+
return NotImplemented
|
|
522
|
+
|
|
523
|
+
def __add__(self, other):
|
|
524
|
+
"""Add two npdatetime.date objects.
|
|
525
|
+
Parameters
|
|
526
|
+
----------
|
|
527
|
+
other: datetime.timedelta
|
|
528
|
+
The other object added to self.
|
|
529
|
+
|
|
530
|
+
Returns
|
|
531
|
+
-------
|
|
532
|
+
npdatetime.date
|
|
533
|
+
The new npdatetime.date object after addition operation.
|
|
534
|
+
"""
|
|
535
|
+
if isinstance(other, _actual_datetime.timedelta):
|
|
536
|
+
o = self.toordinal() + other.days
|
|
537
|
+
if 0 < o <= _MAXORDINAL:
|
|
538
|
+
return date.fromordinal(o)
|
|
539
|
+
raise OverflowError("result out of range")
|
|
540
|
+
return NotImplemented
|
|
541
|
+
|
|
542
|
+
__radd__ = __add__
|
|
543
|
+
|
|
544
|
+
def __sub__(self, other):
|
|
545
|
+
"""Subtract two npdatetime.date objects.
|
|
546
|
+
|
|
547
|
+
Parameters
|
|
548
|
+
----------
|
|
549
|
+
other: datetime.timedelta
|
|
550
|
+
The other object to which the self is subtracted from.
|
|
551
|
+
|
|
552
|
+
Returns
|
|
553
|
+
-------
|
|
554
|
+
npdatetime.date
|
|
555
|
+
The new npdatetime.date object after subtraction operation.
|
|
556
|
+
"""
|
|
557
|
+
if isinstance(other, _actual_datetime.timedelta):
|
|
558
|
+
return self + _actual_datetime.timedelta(-other.days)
|
|
559
|
+
if isinstance(other, date):
|
|
560
|
+
days1 = self.toordinal()
|
|
561
|
+
days2 = other.toordinal()
|
|
562
|
+
return _actual_datetime.timedelta(days1 - days2)
|
|
563
|
+
return NotImplemented
|
|
564
|
+
|
|
565
|
+
def weekday(self):
|
|
566
|
+
"""Return day of the week, where Sunday == 0 ... Saturday == 6."""
|
|
567
|
+
return (self.toordinal() + 5) % 7
|
|
568
|
+
|
|
569
|
+
def isoweekday(self):
|
|
570
|
+
return NotImplemented
|
|
571
|
+
|
|
572
|
+
def isocalendar(self):
|
|
573
|
+
return NotImplemented
|
|
574
|
+
|
|
575
|
+
def _getstate(self):
|
|
576
|
+
return NotImplemented
|
|
577
|
+
|
|
578
|
+
def __setstate(self, string):
|
|
579
|
+
return NotImplemented
|
|
580
|
+
|
|
581
|
+
def __reduce__(self):
|
|
582
|
+
return NotImplemented
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
_date_class = date # so functions w/ args named "date" can get at the class
|
|
586
|
+
|
|
587
|
+
date.min = date(**MINDATE)
|
|
588
|
+
date.max = date(**MAXDATE)
|
|
589
|
+
date.resolution = _actual_datetime.timedelta(days=1)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
class datetime(date):
|
|
593
|
+
"""datetime(year, month, day[, hour[, minute[, second[, microsecond[, tzinfo]]]]])
|
|
594
|
+
|
|
595
|
+
The year, month and day arguments are required. tzinfo may be None, or an
|
|
596
|
+
instance of a tzinfo subclass. The remaining arguments may be ints.
|
|
597
|
+
"""
|
|
598
|
+
__slots__ = date.__slots__ + ('_hour', '_minute', '_second', '_microsecond', '_tzinfo', '_hashcode')
|
|
599
|
+
|
|
600
|
+
def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
|
|
601
|
+
year, month, day = _check_date_fields(year, month, day)
|
|
602
|
+
hour, minute, second, microsecond = _check_time_fields(hour, minute, second, microsecond)
|
|
603
|
+
_check_tzinfo_arg(tzinfo)
|
|
604
|
+
self = object.__new__(cls)
|
|
605
|
+
self._year = year
|
|
606
|
+
self._month = month
|
|
607
|
+
self._day = day
|
|
608
|
+
self._hour = hour
|
|
609
|
+
self._minute = minute
|
|
610
|
+
self._second = second
|
|
611
|
+
self._microsecond = microsecond
|
|
612
|
+
self._tzinfo = UTC0545() if tzinfo is None else tzinfo
|
|
613
|
+
self._hashcode = -1
|
|
614
|
+
return self
|
|
615
|
+
|
|
616
|
+
@property
|
|
617
|
+
def hour(self):
|
|
618
|
+
"""hour (0-23)"""
|
|
619
|
+
return self._hour
|
|
620
|
+
|
|
621
|
+
@property
|
|
622
|
+
def minute(self):
|
|
623
|
+
"""minute (0-59)"""
|
|
624
|
+
return self._minute
|
|
625
|
+
|
|
626
|
+
@property
|
|
627
|
+
def second(self):
|
|
628
|
+
"""second (0-59)"""
|
|
629
|
+
return self._second
|
|
630
|
+
|
|
631
|
+
@property
|
|
632
|
+
def microsecond(self):
|
|
633
|
+
"""microsecond (0-999999)"""
|
|
634
|
+
return self._microsecond
|
|
635
|
+
|
|
636
|
+
@property
|
|
637
|
+
def tzinfo(self):
|
|
638
|
+
"""timezone info object"""
|
|
639
|
+
return self._tzinfo
|
|
640
|
+
|
|
641
|
+
@classmethod
|
|
642
|
+
def _fromtimestamp(cls, t, utc, tz):
|
|
643
|
+
"""Construct a datetime from a POSIX timestamp (like time.time()).
|
|
644
|
+
|
|
645
|
+
A timezone info object may be passed in as well.
|
|
646
|
+
"""
|
|
647
|
+
frac, t = _math.modf(t)
|
|
648
|
+
us = round(frac * 1e6)
|
|
649
|
+
if us >= 1000000:
|
|
650
|
+
t += 1
|
|
651
|
+
us -= 1000000
|
|
652
|
+
elif us < 0:
|
|
653
|
+
t -= 1
|
|
654
|
+
us += 1000000
|
|
655
|
+
|
|
656
|
+
converter = _time.gmtime if utc else _time.localtime
|
|
657
|
+
y, m, d, hh, mm, ss, weekday, jday, dst = converter(t)
|
|
658
|
+
dt = super().from_datetime_date(_actual_datetime.date(y, m, d))
|
|
659
|
+
y, m, d = dt.year, dt.month, dt.day
|
|
660
|
+
ss = min(ss, 59) # clamp out leap seconds if the platform has them
|
|
661
|
+
return cls(y, m, d, hh, mm, ss, us, tz)
|
|
662
|
+
|
|
663
|
+
@classmethod
|
|
664
|
+
def fromtimestamp(cls, t, tz=None):
|
|
665
|
+
"""Construct a datetime from a POSIX timestamp (like time.time()).
|
|
666
|
+
|
|
667
|
+
A timezone info object may be passed in as well.
|
|
668
|
+
"""
|
|
669
|
+
_check_tzinfo_arg(tz)
|
|
670
|
+
|
|
671
|
+
result = cls._fromtimestamp(t, tz is not None, tz)
|
|
672
|
+
if tz is not None:
|
|
673
|
+
result = tz.fromutc(result)
|
|
674
|
+
return result
|
|
675
|
+
|
|
676
|
+
@classmethod
|
|
677
|
+
def utcfromtimestamp(cls, t):
|
|
678
|
+
"""Construct a naive UTC datetime from a POSIX timestamp."""
|
|
679
|
+
return cls._fromtimestamp(t, True, None)
|
|
680
|
+
|
|
681
|
+
@classmethod
|
|
682
|
+
def now(cls):
|
|
683
|
+
"""Construct a datetime from time.time() and optional time zone info."""
|
|
684
|
+
t = _time.time()
|
|
685
|
+
return cls.fromtimestamp(t, UTC0545())
|
|
686
|
+
|
|
687
|
+
@classmethod
|
|
688
|
+
def utcnow(cls):
|
|
689
|
+
"""Construct a UTC datetime from time.time()."""
|
|
690
|
+
t = _time.time()
|
|
691
|
+
return cls.utcfromtimestamp(t)
|
|
692
|
+
|
|
693
|
+
@classmethod
|
|
694
|
+
def combine(cls, date, time):
|
|
695
|
+
"""Construct a datetime from a given date and a given time."""
|
|
696
|
+
if not isinstance(date, _date_class):
|
|
697
|
+
raise TypeError("date argument must be a date instance")
|
|
698
|
+
if not isinstance(time, _actual_datetime.time):
|
|
699
|
+
raise TypeError("time argument must be a time instance")
|
|
700
|
+
return cls(
|
|
701
|
+
date.year, date.month, date.day,
|
|
702
|
+
time.hour, time.minute, time.second, time.microsecond,
|
|
703
|
+
time.tzinfo
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
def timetuple(self):
|
|
707
|
+
"""Return local time tuple compatible with time.localtime()."""
|
|
708
|
+
dst = self.dst()
|
|
709
|
+
if dst is None:
|
|
710
|
+
dst = -1
|
|
711
|
+
elif dst:
|
|
712
|
+
dst = 1
|
|
713
|
+
else:
|
|
714
|
+
dst = 0
|
|
715
|
+
return _build_struct_time(
|
|
716
|
+
self.year, self.month, self.day,
|
|
717
|
+
self.hour, self.minute, self.second,
|
|
718
|
+
dst
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
@classmethod
|
|
722
|
+
def from_datetime_datetime(cls, from_datetime):
|
|
723
|
+
"""Convert datetime.date to npdatetime.datetime (A.D datetime to B.S).
|
|
724
|
+
|
|
725
|
+
Parameters
|
|
726
|
+
----------
|
|
727
|
+
from_date: datetime.datetime
|
|
728
|
+
The AD datetime object to be converted.
|
|
729
|
+
|
|
730
|
+
Returns
|
|
731
|
+
-------
|
|
732
|
+
npdatetime.datetime
|
|
733
|
+
The converted npdatetime.datetime object.
|
|
734
|
+
"""
|
|
735
|
+
from_datetime = from_datetime.astimezone(UTC0545())
|
|
736
|
+
return cls.combine(cls.from_datetime_date(from_datetime.date()), from_datetime.time())
|
|
737
|
+
|
|
738
|
+
def to_datetime_datetime(self):
|
|
739
|
+
"""Convert npdatetime.datetime to datetime.datetime (B.S datetime to A.D).
|
|
740
|
+
|
|
741
|
+
Returns
|
|
742
|
+
-------
|
|
743
|
+
datetime.datetime
|
|
744
|
+
The converted datetime.datetime object.
|
|
745
|
+
"""
|
|
746
|
+
return _actual_datetime.datetime.fromtimestamp(self.timestamp())
|
|
747
|
+
|
|
748
|
+
def _mktime(self):
|
|
749
|
+
"""Return integer POSIX timestamp."""
|
|
750
|
+
max_fold_seconds = 24 * 3600
|
|
751
|
+
t = (self - _EPOCH_BS) // _actual_datetime.timedelta(0, 1)
|
|
752
|
+
|
|
753
|
+
def local(u):
|
|
754
|
+
y, m, d, hh, mm, ss = _time.localtime(u)[:6]
|
|
755
|
+
return (datetime(y, m, d, hh, mm, ss) - _EPOCH_BS) // _actual_datetime.timedelta(0, 1)
|
|
756
|
+
|
|
757
|
+
# Our goal is to solve t = local(u) for u.
|
|
758
|
+
a = local(t) - t
|
|
759
|
+
u1 = t - a
|
|
760
|
+
t1 = local(u1)
|
|
761
|
+
if t1 == t:
|
|
762
|
+
# We found one solution, but it may not be the one we need.
|
|
763
|
+
# Look for an earlier solution (if `fold` is 0), or a
|
|
764
|
+
# later one (if `fold` is 1).
|
|
765
|
+
u2 = u1 + (-max_fold_seconds, max_fold_seconds)[self.fold]
|
|
766
|
+
b = local(u2) - u2
|
|
767
|
+
if a == b:
|
|
768
|
+
return u1
|
|
769
|
+
else:
|
|
770
|
+
b = t1 - u1
|
|
771
|
+
assert a != b
|
|
772
|
+
u2 = t - b
|
|
773
|
+
t2 = local(u2)
|
|
774
|
+
if t2 == t:
|
|
775
|
+
return u2
|
|
776
|
+
if t1 == t:
|
|
777
|
+
return u1
|
|
778
|
+
# We have found both offsets a and b, but neither t - a nor t - b is
|
|
779
|
+
# a solution. This means t is in the gap.
|
|
780
|
+
return (max, min)[self.fold](u1, u2)
|
|
781
|
+
|
|
782
|
+
def timestamp(self):
|
|
783
|
+
"""Return POSIX timestamp as float"""
|
|
784
|
+
if self._tzinfo is None:
|
|
785
|
+
s = self._mktime()
|
|
786
|
+
return s + self.microsecond / 1e6
|
|
787
|
+
else:
|
|
788
|
+
return (self - _EPOCH_BS).total_seconds()
|
|
789
|
+
|
|
790
|
+
def utctimetuple(self):
|
|
791
|
+
"""Return UTC time tuple compatible with time.gmtime()."""
|
|
792
|
+
offset = self.utcoffset()
|
|
793
|
+
if offset:
|
|
794
|
+
self -= offset
|
|
795
|
+
y, m, d = self.year, self.month, self.day
|
|
796
|
+
hh, mm, ss = self.hour, self.minute, self.second
|
|
797
|
+
return _build_struct_time(y, m, d, hh, mm, ss, 0)
|
|
798
|
+
|
|
799
|
+
def date(self):
|
|
800
|
+
"""Return the date part."""
|
|
801
|
+
return date(self._year, self._month, self._day)
|
|
802
|
+
|
|
803
|
+
def time(self):
|
|
804
|
+
"""Return the time part, with tzinfo None."""
|
|
805
|
+
return _actual_datetime.time(self.hour, self.minute, self.second, self.microsecond)
|
|
806
|
+
|
|
807
|
+
def timetz(self):
|
|
808
|
+
"""Return the time part, with same tzinfo."""
|
|
809
|
+
return _actual_datetime.time(self.hour, self.minute, self.second, self.microsecond, self._tzinfo)
|
|
810
|
+
|
|
811
|
+
def replace(self, year=None, month=None, day=None, hour=None,
|
|
812
|
+
minute=None, second=None, microsecond=None, tzinfo=True):
|
|
813
|
+
"""Return a new datetime with new values for the specified fields."""
|
|
814
|
+
if year is None:
|
|
815
|
+
year = self.year
|
|
816
|
+
if month is None:
|
|
817
|
+
month = self.month
|
|
818
|
+
if day is None:
|
|
819
|
+
day = self.day
|
|
820
|
+
if hour is None:
|
|
821
|
+
hour = self.hour
|
|
822
|
+
if minute is None:
|
|
823
|
+
minute = self.minute
|
|
824
|
+
if second is None:
|
|
825
|
+
second = self.second
|
|
826
|
+
if microsecond is None:
|
|
827
|
+
microsecond = self.microsecond
|
|
828
|
+
if tzinfo is True:
|
|
829
|
+
tzinfo = self.tzinfo
|
|
830
|
+
return datetime(year, month, day, hour, minute, second, microsecond, tzinfo)
|
|
831
|
+
|
|
832
|
+
def astimezone(self, tz=None):
|
|
833
|
+
if tz is None:
|
|
834
|
+
tz = UTC0545()
|
|
835
|
+
elif not isinstance(tz, _actual_datetime.tzinfo):
|
|
836
|
+
raise TypeError("tz argument must be an instance of tzinfo")
|
|
837
|
+
|
|
838
|
+
mytz = self.tzinfo
|
|
839
|
+
if mytz is None:
|
|
840
|
+
mytz = self._local_timezone()
|
|
841
|
+
myoffset = mytz.utcoffset(self)
|
|
842
|
+
else:
|
|
843
|
+
myoffset = mytz.utcoffset(self)
|
|
844
|
+
if myoffset is None:
|
|
845
|
+
mytz = self.replace(tzinfo=None)._local_timezone()
|
|
846
|
+
myoffset = mytz.utcoffset(self)
|
|
847
|
+
|
|
848
|
+
if tz is mytz:
|
|
849
|
+
return self
|
|
850
|
+
|
|
851
|
+
utc = (self - myoffset).replace(tzinfo=tz)
|
|
852
|
+
|
|
853
|
+
return tz.fromutc(utc)
|
|
854
|
+
|
|
855
|
+
def ctime(self):
|
|
856
|
+
"""Return ctime() style string."""
|
|
857
|
+
weekday = (self.toordinal() + 5) % 7 or 7
|
|
858
|
+
return "%s %s %2d %02d:%02d:%02d %04d" % (
|
|
859
|
+
_DAYNAMES[weekday],
|
|
860
|
+
_MONTHNAMES[self._month],
|
|
861
|
+
self._day,
|
|
862
|
+
self._hour, self._minute, self._second,
|
|
863
|
+
self._year
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
def isoformat(self, sep='T'):
|
|
867
|
+
"""Return the time formatted according to ISO.
|
|
868
|
+
|
|
869
|
+
This is 'YYYY-MM-DD HH:MM:SS.mmmmmm', or 'YYYY-MM-DD HH:MM:SS' if
|
|
870
|
+
self.microsecond == 0.
|
|
871
|
+
|
|
872
|
+
If self.tzinfo is not None, the UTC offset is also attached, giving
|
|
873
|
+
'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM' or 'YYYY-MM-DD HH:MM:SS+HH:MM'.
|
|
874
|
+
|
|
875
|
+
Optional argument sep specifies the separator between date and
|
|
876
|
+
time, default 'T'.
|
|
877
|
+
"""
|
|
878
|
+
s = (
|
|
879
|
+
"%04d-%02d-%02d%c" % (self._year, self._month, self._day, sep) +
|
|
880
|
+
_format_time(self._hour, self._minute, self._second, self._microsecond)
|
|
881
|
+
)
|
|
882
|
+
off = self.utcoffset()
|
|
883
|
+
if off is not None:
|
|
884
|
+
if off.days < 0:
|
|
885
|
+
sign = "-"
|
|
886
|
+
off = -off
|
|
887
|
+
else:
|
|
888
|
+
sign = "+"
|
|
889
|
+
hh, mm = divmod(off, _actual_datetime.timedelta(hours=1))
|
|
890
|
+
assert not mm % _actual_datetime.timedelta(minutes=1), "whole minute"
|
|
891
|
+
mm //= _actual_datetime.timedelta(minutes=1)
|
|
892
|
+
s += "%s%02d:%02d" % (sign, hh, mm)
|
|
893
|
+
return s
|
|
894
|
+
|
|
895
|
+
def __repr__(self):
|
|
896
|
+
"""Convert to formal string, for repr()."""
|
|
897
|
+
L = [self._year, self._month, self._day, # These are never zero
|
|
898
|
+
self._hour, self._minute, self._second, self._microsecond]
|
|
899
|
+
if L[-1] == 0:
|
|
900
|
+
del L[-1]
|
|
901
|
+
if L[-1] == 0:
|
|
902
|
+
del L[-1]
|
|
903
|
+
s = "%s.%s(%s)" % (self.__class__.__module__,
|
|
904
|
+
self.__class__.__qualname__,
|
|
905
|
+
", ".join(map(str, L)))
|
|
906
|
+
if self._tzinfo is not None:
|
|
907
|
+
assert s[-1:] == ")"
|
|
908
|
+
s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
|
|
909
|
+
return s
|
|
910
|
+
|
|
911
|
+
def __str__(self):
|
|
912
|
+
"""Convert to string, for str()."""
|
|
913
|
+
return self.isoformat(sep=' ')
|
|
914
|
+
|
|
915
|
+
@classmethod
|
|
916
|
+
def strptime(cls, date_string, format):
|
|
917
|
+
"""string, format -> new datetime parsed from a string (like time.strptime())."""
|
|
918
|
+
from . import _custom_strptime
|
|
919
|
+
return _custom_strptime._strptime_datetime(cls, date_string, format)
|
|
920
|
+
|
|
921
|
+
def utcoffset(self):
|
|
922
|
+
"""Return the timezone offset in minutes east of UTC (negative west of UTC)."""
|
|
923
|
+
if self._tzinfo is None:
|
|
924
|
+
return None
|
|
925
|
+
offset = self._tzinfo.utcoffset(self)
|
|
926
|
+
_check_utc_offset("utcoffset", offset)
|
|
927
|
+
return offset
|
|
928
|
+
|
|
929
|
+
def tzname(self):
|
|
930
|
+
"""Return the timezone name.
|
|
931
|
+
|
|
932
|
+
Note that the name is 100% informational -- there's no requirement that
|
|
933
|
+
it mean anything in particular. For example, "GMT", "UTC", "-500",
|
|
934
|
+
"-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies.
|
|
935
|
+
"""
|
|
936
|
+
if self._tzinfo is None:
|
|
937
|
+
return None
|
|
938
|
+
name = self._tzinfo.tzname(self)
|
|
939
|
+
_check_tzname(name)
|
|
940
|
+
return name
|
|
941
|
+
|
|
942
|
+
def dst(self):
|
|
943
|
+
"""Return 0 if DST is not in effect, or the DST offset (in minutes
|
|
944
|
+
eastward) if DST is in effect.
|
|
945
|
+
|
|
946
|
+
This is purely informational; the DST offset has already been added to
|
|
947
|
+
the UTC offset returned by utcoffset() if applicable, so there's no
|
|
948
|
+
need to consult dst() unless you're interested in displaying the DST
|
|
949
|
+
info.
|
|
950
|
+
"""
|
|
951
|
+
if self._tzinfo is None:
|
|
952
|
+
return None
|
|
953
|
+
offset = self._tzinfo.dst(self)
|
|
954
|
+
_check_utc_offset("dst", offset)
|
|
955
|
+
return offset
|
|
956
|
+
|
|
957
|
+
def __eq__(self, other):
|
|
958
|
+
if isinstance(other, datetime):
|
|
959
|
+
return self._cmp(other, allow_mixed=True) == 0
|
|
960
|
+
elif not isinstance(other, date):
|
|
961
|
+
return NotImplemented
|
|
962
|
+
else:
|
|
963
|
+
return False
|
|
964
|
+
|
|
965
|
+
def __le__(self, other):
|
|
966
|
+
if isinstance(other, datetime):
|
|
967
|
+
return self._cmp(other) <= 0
|
|
968
|
+
elif not isinstance(other, date):
|
|
969
|
+
return NotImplemented
|
|
970
|
+
else:
|
|
971
|
+
_cmperror(self, other)
|
|
972
|
+
|
|
973
|
+
def __lt__(self, other):
|
|
974
|
+
if isinstance(other, datetime):
|
|
975
|
+
return self._cmp(other) < 0
|
|
976
|
+
elif not isinstance(other, date):
|
|
977
|
+
return NotImplemented
|
|
978
|
+
else:
|
|
979
|
+
_cmperror(self, other)
|
|
980
|
+
|
|
981
|
+
def __ge__(self, other):
|
|
982
|
+
if isinstance(other, datetime):
|
|
983
|
+
return self._cmp(other) >= 0
|
|
984
|
+
elif not isinstance(other, date):
|
|
985
|
+
return NotImplemented
|
|
986
|
+
else:
|
|
987
|
+
_cmperror(self, other)
|
|
988
|
+
|
|
989
|
+
def __gt__(self, other):
|
|
990
|
+
if isinstance(other, datetime):
|
|
991
|
+
return self._cmp(other) > 0
|
|
992
|
+
elif not isinstance(other, date):
|
|
993
|
+
return NotImplemented
|
|
994
|
+
else:
|
|
995
|
+
_cmperror(self, other)
|
|
996
|
+
|
|
997
|
+
def _cmp(self, other, allow_mixed=False):
|
|
998
|
+
assert isinstance(other, datetime)
|
|
999
|
+
mytz = self._tzinfo
|
|
1000
|
+
ottz = other._tzinfo
|
|
1001
|
+
myoff = otoff = None
|
|
1002
|
+
|
|
1003
|
+
if mytz is ottz:
|
|
1004
|
+
base_compare = True
|
|
1005
|
+
else:
|
|
1006
|
+
myoff = self.utcoffset()
|
|
1007
|
+
otoff = other.utcoffset()
|
|
1008
|
+
base_compare = myoff == otoff
|
|
1009
|
+
|
|
1010
|
+
if base_compare:
|
|
1011
|
+
return _cmp((self._year, self._month, self._day,
|
|
1012
|
+
self._hour, self._minute, self._second,
|
|
1013
|
+
self._microsecond),
|
|
1014
|
+
(other._year, other._month, other._day,
|
|
1015
|
+
other._hour, other._minute, other._second,
|
|
1016
|
+
other._microsecond))
|
|
1017
|
+
if myoff is None or otoff is None:
|
|
1018
|
+
if allow_mixed:
|
|
1019
|
+
return 2 # arbitrary non-zero value
|
|
1020
|
+
else:
|
|
1021
|
+
raise TypeError("cannot compare naive and aware datetimes")
|
|
1022
|
+
# XXX What follows could be done more efficiently...
|
|
1023
|
+
diff = self - other # this will take offsets into account
|
|
1024
|
+
if diff.days < 0:
|
|
1025
|
+
return -1
|
|
1026
|
+
return diff and 1 or 0
|
|
1027
|
+
|
|
1028
|
+
def __add__(self, other):
|
|
1029
|
+
"""Add a datetime and a timedelta."""
|
|
1030
|
+
if not isinstance(other, _actual_datetime.timedelta):
|
|
1031
|
+
return NotImplemented
|
|
1032
|
+
delta = _actual_datetime.timedelta(
|
|
1033
|
+
self.toordinal(),
|
|
1034
|
+
hours=self._hour,
|
|
1035
|
+
minutes=self._minute,
|
|
1036
|
+
seconds=self._second,
|
|
1037
|
+
microseconds=self._microsecond
|
|
1038
|
+
)
|
|
1039
|
+
delta += other
|
|
1040
|
+
hour, rem = divmod(delta.seconds, 3600)
|
|
1041
|
+
minute, second = divmod(rem, 60)
|
|
1042
|
+
if 0 < delta.days <= _MAXORDINAL:
|
|
1043
|
+
return datetime.combine(
|
|
1044
|
+
date.fromordinal(delta.days),
|
|
1045
|
+
_actual_datetime.time(hour, minute, second, delta.microseconds, tzinfo=self._tzinfo)
|
|
1046
|
+
)
|
|
1047
|
+
raise OverflowError("result out of range")
|
|
1048
|
+
|
|
1049
|
+
__radd__ = __add__
|
|
1050
|
+
|
|
1051
|
+
def __sub__(self, other):
|
|
1052
|
+
"""Subtract two datetimes, or a datetime and a timedelta."""
|
|
1053
|
+
if not isinstance(other, datetime):
|
|
1054
|
+
if isinstance(other, _actual_datetime.timedelta):
|
|
1055
|
+
return self + -other
|
|
1056
|
+
return NotImplemented
|
|
1057
|
+
|
|
1058
|
+
days1 = self.toordinal()
|
|
1059
|
+
days2 = other.toordinal()
|
|
1060
|
+
secs1 = self._second + self._minute * 60 + self._hour * 3600
|
|
1061
|
+
secs2 = other._second + other._minute * 60 + other._hour * 3600
|
|
1062
|
+
base = _actual_datetime.timedelta(days1 - days2, secs1 - secs2, self._microsecond - other._microsecond)
|
|
1063
|
+
if self._tzinfo is other._tzinfo:
|
|
1064
|
+
return base
|
|
1065
|
+
myoff = self.utcoffset()
|
|
1066
|
+
otoff = other.utcoffset()
|
|
1067
|
+
if myoff == otoff:
|
|
1068
|
+
return base
|
|
1069
|
+
if myoff is None or otoff is None:
|
|
1070
|
+
raise TypeError("cannot mix naive and timezone-aware time")
|
|
1071
|
+
return base + otoff - myoff
|
|
1072
|
+
|
|
1073
|
+
def __hash__(self):
|
|
1074
|
+
return NotImplemented
|
|
1075
|
+
|
|
1076
|
+
def _getstate(self):
|
|
1077
|
+
return NotImplemented
|
|
1078
|
+
|
|
1079
|
+
def __setstate(self, string, tzinfo):
|
|
1080
|
+
return NotImplemented
|
|
1081
|
+
|
|
1082
|
+
def __reduce__(self):
|
|
1083
|
+
return NotImplemented
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
datetime.min = datetime(1975, 1, 1)
|
|
1087
|
+
datetime.max = datetime(2100, 12, 30, 23, 59, 59, 999999)
|
|
1088
|
+
datetime.resolution = _actual_datetime.timedelta(microseconds=1)
|
|
1089
|
+
|
|
1090
|
+
_EPOCH_BS = datetime.from_datetime_datetime(_actual_datetime.datetime(1970, 1, 1, tzinfo=_actual_datetime.timezone.utc))
|