Skip to content

Commit 79f5a4b

Browse files
committed
Imports and some helpers
1 parent cd718eb commit 79f5a4b

File tree

3 files changed

+74
-48
lines changed

3 files changed

+74
-48
lines changed

gperiod/f.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime
1+
import datetime as dtm
22
import operator
33
import typing as t
44

@@ -26,7 +26,7 @@ def descend_end(*periods: g.PeriodProto, reverse: bool = False,
2626
# misc
2727

2828
def to_timestamps(*periods: g.PeriodProto,
29-
) -> t.Generator[datetime, None, None]:
29+
) -> t.Generator[dtm.datetime, None, None]:
3030
"""Flatten periods into sequence of edges
3131
3232
:param periods: period-like objects

gperiod/g.py

+71-46
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

3-
from datetime import datetime, timedelta # noqa: H301
3+
import contextlib
4+
import datetime as dtm
45
import functools
56
import operator
67
import typing as t
@@ -17,6 +18,14 @@
1718
_SORT_KEY_START = operator.attrgetter(_F_START)
1819

1920

21+
def _now(tz: dtm.timezone = dtm.UTC) -> dtm.datetime:
22+
return dtm.datetime.now(tz=tz)
23+
24+
25+
def utcnow() -> dtm.datetime:
26+
return dtm.datetime.now(tz=dtm.UTC)
27+
28+
2029
def _jumping_sequence(length: int) -> t.Generator[int, None, None]:
2130
middle, tail = divmod(length, 2)
2231
for left, right in zip(range(middle - 1, -1, -1),
@@ -27,66 +36,72 @@ def _jumping_sequence(length: int) -> t.Generator[int, None, None]:
2736
yield length - 1
2837

2938

30-
def Tuple(start: datetime, end: datetime) -> _T_DT_PAIR:
39+
def Tuple(start: dtm.datetime, end: dtm.datetime) -> _T_DT_PAIR:
3140
return start, end
3241

3342

3443
class PeriodProto(t.Protocol):
35-
start: datetime
36-
end: datetime
44+
start: dtm.datetime
45+
end: dtm.datetime
3746

3847

39-
_T_DT_PAIR = t.Tuple[datetime, datetime]
48+
_T_DT_PAIR = t.Tuple[dtm.datetime, dtm.datetime]
4049

41-
_T_FACTORY = t.Callable[[datetime, datetime], t.Any]
42-
_T_FACTORY_RESULT = t.Union[PeriodProto, t.Tuple[datetime, datetime]]
43-
_T_FACTORY_RESULT_OPT = t.Union[PeriodProto, t.Tuple[datetime, datetime], None]
50+
_T_FACTORY = t.Callable[[dtm.datetime, dtm.datetime], t.Any]
51+
_T_FACTORY_RESULT = t.Union[PeriodProto, t.Tuple[dtm.datetime, dtm.datetime]]
52+
_T_FACTORY_RESULT_OPT = t.Union[PeriodProto, t.Tuple[dtm.datetime, dtm.datetime], None]
4453

4554

4655
class Period:
4756

48-
start: datetime
49-
end: datetime
57+
start: dtm.datetime
58+
end: dtm.datetime
5059

5160
__slots__ = (_F_START, _F_END, _F__DURATION)
5261

53-
def __init__(self, start: datetime, end: datetime):
62+
def __init__(self, start: dtm.datetime, end: dtm.datetime):
5463
validate_edges(start, end)
5564
object.__setattr__(self, _F_START, start)
5665
object.__setattr__(self, _F_END, end)
5766

58-
def __set_duration(self) -> timedelta:
67+
def __set_duration(self) -> dtm.timedelta:
5968
duration = self.end - self.start
6069
object.__setattr__(self, _F__DURATION, duration)
6170
return duration
6271

6372
@property
64-
def duration(self) -> timedelta:
73+
def duration(self) -> dtm.timedelta:
6574
try:
6675
return getattr(self, _F__DURATION)
6776
except AttributeError:
6877
return self.__set_duration()
6978

7079
@classmethod
71-
def load_edges(cls, start: datetime, end: datetime) -> Period:
80+
def load_edges(cls, start: dtm.datetime, end: dtm.datetime) -> Period:
7281
"""Unsafe load Period from edges without edge validation"""
7382
inst = cls.__new__(cls)
7483
object.__setattr__(inst, _F_START, start)
7584
object.__setattr__(inst, _F_END, end)
7685
return inst
7786

7887
@classmethod
79-
def from_start(cls, start: datetime, duration: timedelta) -> Period:
88+
def from_start(cls, start: dtm.datetime, duration: dtm.timedelta) -> Period:
8089
"""Make a Period from start and duration"""
8190

8291
return cls(start, start + duration)
8392

8493
@classmethod
85-
def from_end(cls, end: datetime, duration: timedelta) -> Period:
94+
def from_end(cls, end: dtm.datetime, duration: dtm.timedelta) -> Period:
8695
"""Make a Period from end and duration"""
8796

8897
return cls(end - duration, end)
8998

99+
@classmethod
100+
def record(cls, start: dtm.datetime) -> Period:
101+
"""Make a Period from start and now()"""
102+
103+
return cls(start, _now(tz=t.cast(dtm.timezone, start.tzinfo)))
104+
90105
def __setattr__(self, key: str, value: t.Any) -> None:
91106
raise NotImplementedError("method not allowed")
92107

@@ -121,8 +136,8 @@ def __deepcopy__(self, memo): # TODO(d.burmistrov)
121136
return memo[self]
122137

123138
def replace(self,
124-
start: t.Optional[datetime] = None,
125-
end: t.Optional[datetime] = None,
139+
start: t.Optional[dtm.datetime] = None,
140+
end: t.Optional[dtm.datetime] = None,
126141
) -> Period:
127142
"""Return Period with new specified fields."""
128143

@@ -144,12 +159,12 @@ def as_args(self) -> _T_DT_PAIR:
144159
def as_tuple(self): # TOD
145160
return as_tuple(self)
146161

147-
def as_kwargs(self) -> dict[str, datetime]:
162+
def as_kwargs(self) -> dict[str, dtm.datetime]:
148163
"""Return a dictionary of edges"""
149164

150165
return dict(start=self.start, end=self.end)
151166

152-
def as_dict(self) -> dict[str, datetime | timedelta]:
167+
def as_dict(self) -> dict[str, dtm.datetime | dtm.timedelta]:
153168
"""Return a dictionary of edges and durations"""
154169

155170
return dict(start=self.start, end=self.end, duration=self.duration)
@@ -243,7 +258,7 @@ def ascend_start(*periods: PeriodProto,
243258

244259
# validation
245260

246-
def validate_edges(start: datetime, end: datetime) -> None:
261+
def validate_edges(start: dtm.datetime, end: dtm.datetime) -> None:
247262
f"""Validate period edges
248263
249264
Exception will be raised for invalid data.
@@ -256,9 +271,9 @@ def validate_edges(start: datetime, end: datetime) -> None:
256271
"""
257272

258273
# types
259-
if not isinstance(start, datetime):
274+
if not isinstance(start, dtm.datetime):
260275
raise TypeError(f"'{_F_START}' must be datetime: '{type(start)}'")
261-
elif not isinstance(end, datetime):
276+
elif not isinstance(end, dtm.datetime):
262277
raise TypeError(f"'{_F_END}' must be datetime: '{type(end)}'")
263278

264279
# timezones
@@ -292,14 +307,14 @@ def validate_period(period: PeriodProto) -> None:
292307

293308
# ~set proto
294309

295-
def contains(period: PeriodProto, item: datetime | PeriodProto) -> bool:
310+
def contains(period: PeriodProto, item: dtm.datetime | PeriodProto) -> bool:
296311
"""Report whether period contains another period or timestamp
297312
298313
:param period: period-like object
299314
:param item: timestamp or period-like object
300315
"""
301316

302-
if isinstance(item, datetime):
317+
if isinstance(item, dtm.datetime):
303318
return period.start <= item <= period.end
304319

305320
return (period.start <= item.start) and (item.end <= period.end)
@@ -445,10 +460,10 @@ def difference(period: PeriodProto,
445460
# I. "p + timedelta"
446461
# II. "p1 + p2"
447462
def add(period: PeriodProto,
448-
other: PeriodProto | timedelta,
463+
other: PeriodProto | dtm.timedelta,
449464
factory: _T_FACTORY = Period,
450465
) -> _T_FACTORY_RESULT_OPT:
451-
if not isinstance(other, timedelta):
466+
if not isinstance(other, dtm.timedelta):
452467
return join(period, other, factory=factory)
453468

454469
secs = other.total_seconds()
@@ -466,10 +481,10 @@ def add(period: PeriodProto,
466481
# I. "p - timedelta"
467482
# II. "p1 - p2"
468483
def sub(period: PeriodProto,
469-
other: PeriodProto | timedelta,
484+
other: PeriodProto | dtm.timedelta,
470485
factory: _T_FACTORY = Period,
471486
) -> _T_FACTORY_RESULT_OPT:
472-
if isinstance(other, timedelta):
487+
if isinstance(other, dtm.timedelta):
473488
return add(period, -other, factory=factory)
474489

475490
# TODO(d.burmistrov): extract this to `cut(period, other, *others, ...)`
@@ -496,24 +511,24 @@ def mul(period: PeriodProto, factor: int | float, factory: _T_FACTORY = Period,
496511
return factory(start, period.end)
497512

498513

499-
def floordiv(period: PeriodProto, other: timedelta | int,
500-
) -> timedelta | int:
501-
if not isinstance(other, (timedelta, int)):
514+
def floordiv(period: PeriodProto, other: dtm.timedelta | int,
515+
) -> dtm.timedelta | int:
516+
if not isinstance(other, (dtm.timedelta, int)):
502517
raise NotImplementedError()
503518

504519
return (period.end - period.start) // other
505520

506521

507-
def mod(period: PeriodProto, other: timedelta) -> timedelta:
508-
if not isinstance(other, timedelta):
522+
def mod(period: PeriodProto, other: dtm.timedelta) -> dtm.timedelta:
523+
if not isinstance(other, dtm.timedelta):
509524
raise NotImplementedError()
510525

511526
return (period.end - period.start) % other
512527

513528

514-
def truediv(period: PeriodProto, other: timedelta | int | float,
515-
) -> timedelta | float:
516-
if not isinstance(other, (timedelta, int, float)):
529+
def truediv(period: PeriodProto, other: dtm.timedelta | int | float,
530+
) -> dtm.timedelta | float:
531+
if not isinstance(other, (dtm.timedelta, int, float)):
517532
raise NotImplementedError()
518533

519534
return (period.end - period.start) / other
@@ -550,24 +565,24 @@ def eq(period: PeriodProto, other: PeriodProto, *others: PeriodProto) -> bool:
550565

551566

552567
def lshift(period: PeriodProto,
553-
delta: timedelta,
568+
delta: dtm.timedelta,
554569
factory: _T_FACTORY = Period,
555570
) -> _T_FACTORY_RESULT:
556571
"""Shift left right by timedelta (p << delta)"""
557572

558-
if not isinstance(delta, timedelta):
573+
if not isinstance(delta, dtm.timedelta):
559574
raise NotImplementedError()
560575

561576
return factory(period.start - delta, period.end - delta)
562577

563578

564579
def rshift(period: PeriodProto,
565-
delta: timedelta,
580+
delta: dtm.timedelta,
566581
factory: _T_FACTORY = Period,
567582
) -> _T_FACTORY_RESULT:
568583
"""Shift period right by timedelta (p >> delta)"""
569584

570-
if not isinstance(delta, timedelta):
585+
if not isinstance(delta, dtm.timedelta):
571586
raise NotImplementedError()
572587

573588
return factory(period.start + delta, period.end + delta)
@@ -577,7 +592,7 @@ def rshift(period: PeriodProto,
577592

578593
# TODO(d.burmistrov): jumping search + check ISO spec for sep alphabets
579594
def fromisoformat(s: str, sep: str = _SEP, factory: _T_FACTORY = Period):
580-
conv = datetime.fromisoformat
595+
conv = dtm.datetime.fromisoformat
581596
start, _, end = s.partition(sep)
582597
return factory(conv(start), conv(end))
583598

@@ -587,7 +602,7 @@ def isoformat(obj: PeriodProto,
587602
dt_sep=_DT_SEP,
588603
timespec=_TIMESPEC,
589604
sep: str = _SEP) -> str:
590-
conv = functools.partial(datetime.isoformat,
605+
conv = functools.partial(dtm.datetime.isoformat,
591606
sep=dt_sep, timespec=timespec)
592607
return f"{conv(obj.start)}{sep}{conv(obj.end)}"
593608

@@ -616,8 +631,8 @@ def strptime(period_string: str,
616631
continue
617632

618633
try:
619-
start = datetime.strptime(period_string[:i], date_format)
620-
end = datetime.strptime(period_string[j:], date_format)
634+
start = dtm.datetime.strptime(period_string[:i], date_format)
635+
end = dtm.datetime.strptime(period_string[j:], date_format)
621636
except ValueError:
622637
continue
623638
else:
@@ -649,7 +664,17 @@ def as_tuple(period: PeriodProto) -> _T_DT_PAIR:
649664
return period.start, period.end
650665

651666

652-
def as_dict(period: PeriodProto) -> dict[str, datetime]:
667+
def as_dict(period: PeriodProto) -> dict[str, dtm.datetime]:
653668
"""Return a dictionary of edges"""
654669

655670
return dict(start=period.start, end=period.end)
671+
672+
673+
@contextlib.contextmanager
674+
def timer():
675+
box = [utcnow()]
676+
try:
677+
yield box
678+
finally:
679+
box.append(utcnow())
680+
box.append(Period.load_edges(*box))

tox.ini

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ project_name = gperiod
88

99
[flake8]
1010
show-source = true
11+
max-line-length = 88
1112

1213
[testenv]
1314
deps =

0 commit comments

Comments
 (0)