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 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))