Skip to content

Commit

Permalink
fix: subtract timeframe from superset returns empty 🐛 (#14)
Browse files Browse the repository at this point in the history
* fix: subtract timeframe from superset returns empty 🐛

fixes #9

* add inclusion with in keyword

add relevant test and docs

* constant deprecation warning
  • Loading branch information
meysam81 authored Nov 5, 2022
1 parent d25064d commit 9dd1f1a
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 20 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ from timeframe import TimeFrame

### Inclusion

#### New API

```python
>>> tf1 = TimeFrame(datetime(2021, 1, 1), datetime(2021, 1, 2))
>>> tf2 = TimeFrame(datetime(2021, 1, 1, 12), datetime(2021, 1, 1, 13))
>>> tf2 in tf1
True
```

#### Deprecated

This implies whether or not one time frame includes another; it can also be
used to check if a `datetime` is inside one `TimeFrame`.

Expand Down
9 changes: 9 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from datetime import datetime

import pytest

from timeframe import BatchTimeFrame, TimeFrame
Expand Down Expand Up @@ -26,3 +28,10 @@ def faker_seed():
import time

return time.time()


@pytest.fixture
def empty_timeframe():
return TimeFrame(datetime(2022, 1, 1), datetime(2022, 1, 2)) * TimeFrame(
datetime(2022, 1, 3), datetime(2022, 1, 4)
)
72 changes: 66 additions & 6 deletions test/test_batch_timeframe.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime
from datetime import datetime, timedelta
from functools import reduce

import pytest
Expand Down Expand Up @@ -180,6 +180,66 @@ def test_batch_timeframe_includes_with_wrong_type_raises_error():
btf.includes([1, 1.0, "dummy"])


def test_batch_timeframe_inclusion_check_with_in_keyword(faker, empty_timeframe):
dt1 = faker.date_time_between(start_date="-1y", end_date="-6M")
dt2 = faker.date_time_between(start_date=dt1, end_date="now")

dt12 = faker.date_time_between(start_date=dt1, end_date=dt2)

dt3 = dt1 + timedelta(days=1)
dt4 = dt2 - timedelta(days=1)

dt5 = faker.date_time_between(start_date="now", end_date="+1y")
dt6 = faker.date_time_between(start_date=dt5)

tf1 = TimeFrame(dt1, dt2)
tf2 = TimeFrame(dt3, dt4)
tf3 = TimeFrame(dt5, dt6)

assert tf2 in BatchTimeFrame([tf1])
assert tf1 in BatchTimeFrame([tf1])
assert BatchTimeFrame([tf1]) in BatchTimeFrame([tf1])
assert tf3 not in BatchTimeFrame([tf1])
assert dt12 in BatchTimeFrame([tf1])
assert dt5 not in BatchTimeFrame([tf1])
assert tf1.start in BatchTimeFrame([tf1])
assert tf1.end in BatchTimeFrame([tf1])
assert empty_timeframe in BatchTimeFrame([tf1])


def test_batch_timeframe_inclusion_check_with_other_types_raise_value_error(faker):
dt1 = faker.date_time_between(start_date="-1y", end_date="-6M")
dt2 = faker.date_time_between(start_date=dt1, end_date="now")

with pytest.raises(TypeError):
faker.pyint() in BatchTimeFrame([TimeFrame(dt1, dt2)])

with pytest.raises(TypeError):
faker.pyfloat() in BatchTimeFrame([TimeFrame(dt1, dt2)])

with pytest.raises(TypeError):
faker.text() in BatchTimeFrame([TimeFrame(dt1, dt2)])

with pytest.raises(TypeError):
[] in BatchTimeFrame([TimeFrame(dt1, dt2)])

with pytest.raises(TypeError):
[faker.pyint(), faker.pyfloat(), faker.text()] in BatchTimeFrame(
[TimeFrame(dt1, dt2)]
)

with pytest.raises(TypeError):
faker.pybool() in BatchTimeFrame([TimeFrame(dt1, dt2)])


def test_batch_timeframe_inclusion_check_with_include_warns_deprecation(
random_timeframe, random_batch_timeframes
):

with pytest.warns(DeprecationWarning):
random_batch_timeframes.includes(random_timeframe)


# ======================= Summation ============================
def test_batch_timeframe_add_two_instances_successfully():
tf1 = TimeFrame(datetime(2021, 1, 18, 10), datetime(2021, 1, 18, 11))
Expand Down Expand Up @@ -364,8 +424,8 @@ def test_batch_timeframe_multiply_with_non_base_timeframe_raises_type_error():
btf * [1, 1.0, "dummy", True]


# ======================= Substraction ============================
def test_batch_timeframe_substract_two_instances_successfully():
# ======================= Subtraction ============================
def test_batch_timeframe_subtract_two_instances_successfully():
tf1 = TimeFrame(datetime(2021, 1, 18, 10), datetime(2021, 1, 18, 11))
tf2 = TimeFrame(datetime(2021, 1, 18, 12), datetime(2021, 1, 18, 14))
tf3 = TimeFrame(datetime(2021, 1, 18, 18), datetime(2021, 1, 18, 20))
Expand All @@ -386,7 +446,7 @@ def test_batch_timeframe_substract_two_instances_successfully():
assert btf2.duration == btf1.duration


def test_batch_timeframe_substract_with_timeframe_successfully():
def test_batch_timeframe_subtract_with_timeframe_successfully():
tf1 = TimeFrame(datetime(2021, 1, 18, 10), datetime(2021, 1, 18, 11))
tf2 = TimeFrame(datetime(2021, 1, 18, 12), datetime(2021, 1, 18, 14))
tf3 = TimeFrame(datetime(2021, 1, 18, 18), datetime(2021, 1, 18, 20))
Expand All @@ -408,7 +468,7 @@ def test_batch_timeframe_substract_with_timeframe_successfully():
assert btf2.duration == (tf1 + tf2 + tf3 - tf5).duration


def test_batch_timeframe_substract_from_empty_timeframe_results_in_the_same_object():
def test_batch_timeframe_subtract_from_empty_timeframe_results_in_the_same_object():
tf1 = TimeFrame(datetime(2021, 1, 18, 10), datetime(2021, 1, 18, 11))
tf2 = TimeFrame(datetime(2021, 1, 18, 12), datetime(2021, 1, 18, 14))
tf3 = TimeFrame(datetime(2021, 1, 18, 18), datetime(2021, 1, 18, 20))
Expand All @@ -430,7 +490,7 @@ def test_batch_timeframe_substract_from_empty_timeframe_results_in_the_same_obje
assert btf2.duration == btf1.duration


def test_batch_timeframe_substract_from_non_base_timeframe_raises_type_error():
def test_batch_timeframe_subtract_from_non_base_timeframe_raises_type_error():
tf1 = TimeFrame(datetime(2021, 1, 17, 10), datetime(2021, 1, 17, 11))
tf2 = TimeFrame(datetime(2021, 1, 17, 12), datetime(2021, 1, 17, 14))
tf3 = TimeFrame(datetime(2021, 1, 17, 18), datetime(2021, 1, 17, 20))
Expand Down
19 changes: 19 additions & 0 deletions test/test_empty.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from datetime import datetime

import pytest

from timeframe import TimeFrame


Expand All @@ -17,6 +19,23 @@ def test_empty_timeframe_doesnt_include_anything():
assert not (tf1 * tf2).includes(tf5)


def test_empty_timeframe_inclusion_check_always_returns_false(
faker, random_timeframe, random_batch_timeframes, empty_timeframe
):
for instance in [faker.date_time(), random_timeframe, random_batch_timeframes]:
assert instance not in empty_timeframe


def test_empty_timeframe_inclusion_check_with_include_warns_deprecation(
faker, empty_timeframe
):
dt = faker.date_time()

with pytest.warns(DeprecationWarning):
empty_timeframe.includes(dt)


# ======================= Repr ============================
def test_empty_timeframe_repr():
tf1 = TimeFrame(datetime(2022, 10, 15), datetime(2022, 10, 16))
tf2 = TimeFrame(datetime(2022, 10, 17), datetime(2022, 10, 18))
Expand Down
78 changes: 71 additions & 7 deletions test/test_timeframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,54 @@ def test_timeframe_never_includes_empty_timeframe():
assert not tf1.includes(tf3 * tf5)


def test_timeframe_inclusion_check_with_in_keyword(faker):
dt1 = faker.date_time()
dt2 = faker.date_time_between(start_date=dt1)
dt3 = faker.date_time_between(start_date=dt1, end_date=dt2)
dt4 = faker.date_time_between(start_date=dt2)

tf = TimeFrame(dt1, dt2)

assert dt3 in tf
assert dt4 not in tf


def test_timeframe_inclusion_check_with_in_keyword_other_types_raise_type_error(faker):
dt1 = faker.date_time()
dt2 = faker.date_time_between(start_date=dt1)
tf = TimeFrame(dt1, dt2)

with pytest.raises(TypeError):
faker.pyint() in tf

with pytest.raises(TypeError):
faker.pyfloat() in tf

with pytest.raises(TypeError):
faker.text() in tf


def test_timeframe_inclusion_check_with_in_keyword_to_batch_timeframe(faker):
dt1 = faker.date_time()
dt2 = faker.date_time_between(start_date=dt1)

dt3 = faker.date_time_between(start_date=dt1, end_date=dt2)
dt4 = faker.date_time_between(start_date=dt3, end_date=dt2)

tf = TimeFrame(dt1, dt2)

assert BatchTimeFrame([TimeFrame(dt3, dt4)]) in tf


def test_timeframe_inclusion_check_with_include_warns_deprecation(
faker, random_timeframe
):
dt = faker.date_time()

with pytest.warns(DeprecationWarning):
random_timeframe.includes(dt)


# ======================= Duration ============================
def test_timeframe_duration_calculation_is_correct():
tf = TimeFrame(datetime(2021, 1, 15, 12), datetime(2021, 1, 15, 13))
Expand Down Expand Up @@ -389,8 +437,8 @@ def test_timeframe_add_with_non_batch_timeframe_raises_type_error():
tf + [1, 1.0, "dummy", True]


# ======================= Substraction ============================
def test_timeframe_sub_without_overlap_gives_accurate_substract():
# ======================= Subtraction ============================
def test_timeframe_sub_without_overlap_gives_accurate_subtract():
tf1 = TimeFrame(datetime(2021, 1, 17, 10), datetime(2021, 1, 17, 11))
tf2 = TimeFrame(datetime(2021, 1, 16, 22), datetime(2021, 1, 17, 10))

Expand All @@ -400,7 +448,7 @@ def test_timeframe_sub_without_overlap_gives_accurate_substract():
assert assertion.duration == tf1.duration


def test_timeframe_sub_with_half_overlap_greater_start_gives_accurate_substract():
def test_timeframe_sub_with_half_overlap_greater_start_gives_accurate_subtract():
tf1 = TimeFrame(datetime(2021, 1, 16, 22), datetime(2021, 1, 17, 10, 30))
tf2 = TimeFrame(datetime(2021, 1, 17, 10), datetime(2021, 1, 17, 11))

Expand All @@ -411,7 +459,7 @@ def test_timeframe_sub_with_half_overlap_greater_start_gives_accurate_substract(
assert assertion.duration == expected.duration


def test_timeframe_sub_with_half_overlap_lower_start_gives_accurate_substract():
def test_timeframe_sub_with_half_overlap_lower_start_gives_accurate_subtract():
tf1 = TimeFrame(datetime(2021, 1, 17, 10), datetime(2021, 1, 17, 11))
tf2 = TimeFrame(datetime(2021, 1, 16, 22), datetime(2021, 1, 17, 10, 30))

Expand All @@ -422,7 +470,7 @@ def test_timeframe_sub_with_half_overlap_lower_start_gives_accurate_substract():
assert assertion.duration == expected.duration


def test_timeframe_sub_from_superset_gives_accurate_substract():
def test_timeframe_sub_from_superset_gives_accurate_subtract():
tf1 = TimeFrame(datetime(2021, 1, 17, 8), datetime(2021, 1, 17, 12))
tf2 = TimeFrame(datetime(2021, 1, 17, 7), datetime(2021, 1, 17, 13))

Expand All @@ -431,7 +479,7 @@ def test_timeframe_sub_from_superset_gives_accurate_substract():
assert assertion.duration == 0


def test_timeframe_substract_from_batch_timeframe_successfully():
def test_timeframe_subtract_from_batch_timeframe_successfully():
tf1 = TimeFrame(datetime(2021, 1, 18, 10), datetime(2021, 1, 18, 11))
tf2 = TimeFrame(datetime(2021, 1, 18, 12), datetime(2021, 1, 18, 14))
tf3 = TimeFrame(datetime(2021, 1, 18, 18), datetime(2021, 1, 18, 20))
Expand Down Expand Up @@ -460,7 +508,7 @@ def test_timeframe_sub_from_subset_gives_a_batch_timeframe():
)


def test_timeframe_substraction_with_non_base_timeframe_raises_type_error():
def test_timeframe_subtraction_with_non_base_timeframe_raises_type_error():
tf = TimeFrame(datetime(2021, 1, 17, 10), datetime(2021, 1, 17, 11))

with pytest.raises(TypeError):
Expand All @@ -480,3 +528,19 @@ def test_timeframe_substraction_with_non_base_timeframe_raises_type_error():

with pytest.raises(TypeError):
tf - [1, 1.0, "dummy", True]


def test_timeframe_subtraction_from_superset_returns_empty(empty_timeframe):
tf1 = TimeFrame(datetime(2022, 2, 22), datetime(2022, 2, 23))
tf2 = TimeFrame(datetime(2022, 2, 22), datetime(2022, 2, 24))

assert tf2 - tf1 == TimeFrame(
datetime(2022, 2, 23) + timedelta(microseconds=1), datetime(2022, 2, 24)
)
assert isinstance(tf1 - tf2, type(empty_timeframe))


def test_timeframe_subtraction_from_empty_returns_self(empty_timeframe):
tf1 = TimeFrame(datetime(2022, 2, 22), datetime(2022, 2, 23))

assert tf1 - empty_timeframe is tf1
Loading

0 comments on commit 9dd1f1a

Please sign in to comment.