Skip to content

Commit

Permalink
Migrate from pendulum to datetime library
Browse files Browse the repository at this point in the history
  • Loading branch information
dostuffthatmatters committed Aug 9, 2023
1 parent 22edc40 commit 71ce8e9
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 51 deletions.
2 changes: 1 addition & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ repository = "https://github.com/tum-esm/em27-metadata"
[tool.poetry.dependencies]
python = "^3.9"
tum-esm-utils = "^1.4.0"
pendulum = "^2.1.2"
pydantic = "^2.1.1"

[tool.poetry.group.dev]
Expand Down
48 changes: 22 additions & 26 deletions tests/test_data_integrity.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import datetime
import os
import json
from os.path import dirname
import pendulum
import pytest
from tum_esm_em27_metadata import interfaces, types

Expand All @@ -26,11 +26,9 @@ def test_data_integrity() -> None:
example_sensor = location_data.sensors[0]
example_sensor_location = example_sensor.locations[0]

date = example_sensor_location.from_datetime.to_rfc3339_string()[:10] # type: ignore
from_datetime = pendulum.parse(f"{date}T00:00:00+00:00")
to_datetime = pendulum.parse(f"{date}T23:59:59+00:00")
assert isinstance(from_datetime, pendulum.DateTime)
assert isinstance(to_datetime, pendulum.DateTime)
date_string = example_sensor_location.from_datetime.strftime("%Y-%m-%d")
from_datetime = datetime.datetime.fromisoformat(f"{date_string}T00:00:00+00:00")
to_datetime = datetime.datetime.fromisoformat(f"{date_string}T23:59:59+00:00")

example_sensor_data_contexts = location_data.get(
example_sensor.sensor_id, from_datetime, to_datetime
Expand Down Expand Up @@ -141,10 +139,8 @@ def test_getter_function() -> None:

location_data = interfaces.EM27MetadataInterface(locations, sensors, campaigns=[])

from_datetime = pendulum.parse("2020-02-01T00:00:00+00:00")
to_datetime = pendulum.parse("2020-02-01T23:59:59+00:00")
assert isinstance(from_datetime, pendulum.DateTime), "must be a datetime"
assert isinstance(to_datetime, pendulum.DateTime), "must be a datetime"
from_datetime = datetime.datetime.fromisoformat("2020-02-01T00:00:00+00:00")
to_datetime = datetime.datetime.fromisoformat("2020-02-01T23:59:59+00:00")

chunks = location_data.get("sid1", from_datetime, to_datetime)

Expand All @@ -154,25 +150,25 @@ def test_getter_function() -> None:

from_datetimes = [c.from_datetime for c in chunks]
assert from_datetimes == [
pendulum.parse("2020-02-01T01:00:00+00:00"),
pendulum.parse("2020-02-01T02:00:00+00:00"),
pendulum.parse("2020-02-01T12:00:00+00:00"),
pendulum.parse("2020-02-01T13:00:00+00:00"),
pendulum.parse("2020-02-01T14:00:00+00:00"),
pendulum.parse("2020-02-01T15:00:00+00:00"),
pendulum.parse("2020-02-01T16:00:00+00:00"),
pendulum.parse("2020-02-01T22:00:00+00:00"),
datetime.datetime.fromisoformat("2020-02-01T01:00:00+00:00"),
datetime.datetime.fromisoformat("2020-02-01T02:00:00+00:00"),
datetime.datetime.fromisoformat("2020-02-01T12:00:00+00:00"),
datetime.datetime.fromisoformat("2020-02-01T13:00:00+00:00"),
datetime.datetime.fromisoformat("2020-02-01T14:00:00+00:00"),
datetime.datetime.fromisoformat("2020-02-01T15:00:00+00:00"),
datetime.datetime.fromisoformat("2020-02-01T16:00:00+00:00"),
datetime.datetime.fromisoformat("2020-02-01T22:00:00+00:00"),
]
to_datetimes = [c.to_datetime for c in chunks]
assert to_datetimes == [
pendulum.parse("2020-02-01T01:59:59+00:00"),
pendulum.parse("2020-02-01T09:59:59+00:00"),
pendulum.parse("2020-02-01T12:59:59+00:00"),
pendulum.parse("2020-02-01T13:59:59+00:00"),
pendulum.parse("2020-02-01T14:59:59+00:00"),
pendulum.parse("2020-02-01T15:59:59+00:00"),
pendulum.parse("2020-02-01T21:59:59+00:00"),
pendulum.parse("2020-02-01T22:59:59+00:00"),
datetime.datetime.fromisoformat("2020-02-01T01:59:59+00:00"),
datetime.datetime.fromisoformat("2020-02-01T09:59:59+00:00"),
datetime.datetime.fromisoformat("2020-02-01T12:59:59+00:00"),
datetime.datetime.fromisoformat("2020-02-01T13:59:59+00:00"),
datetime.datetime.fromisoformat("2020-02-01T14:59:59+00:00"),
datetime.datetime.fromisoformat("2020-02-01T15:59:59+00:00"),
datetime.datetime.fromisoformat("2020-02-01T21:59:59+00:00"),
datetime.datetime.fromisoformat("2020-02-01T22:59:59+00:00"),
]

utc_offsets = [c.utc_offset for c in chunks]
Expand Down
4 changes: 2 additions & 2 deletions tests/test_datetime_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def test_time_series_element() -> None:
from_datetime="2016-10-01T00:00:00+00:00",
to_datetime="2016-10-01T23:00:00+02:00",
)
actual_dt_seconds = (tse1.to_datetime - tse1.from_datetime).as_timedelta().total_seconds()
actual_dt_seconds = (tse1.to_datetime - tse1.from_datetime).total_seconds()
expected_dt_seconds = 3600 * 21
assert (
actual_dt_seconds == expected_dt_seconds
Expand All @@ -27,7 +27,7 @@ def test_time_series_element() -> None:
tse2 = TimeSeriesElement(
from_datetime="2016-10-01T00:00:00+00:00", to_datetime="2016-10-03T13:24:35-05:30"
)
actual_dt_seconds = (tse2.to_datetime - tse2.from_datetime).as_timedelta().total_seconds()
actual_dt_seconds = (tse2.to_datetime - tse2.from_datetime).total_seconds()
expected_dt_seconds = 61 * 3600 + (24 * 60) + 35 + (5 * 3600) + (30 * 60)
assert (
actual_dt_seconds == expected_dt_seconds
Expand Down
34 changes: 21 additions & 13 deletions tum_esm_em27_metadata/interfaces.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import datetime
from typing import Any, TypeVar
import pendulum
import pydantic
from tum_esm_em27_metadata import types

Expand Down Expand Up @@ -33,8 +33,8 @@ def __init__(
def get(
self,
sensor_id: str,
from_datetime: pendulum.DateTime,
to_datetime: pendulum.DateTime,
from_datetime: datetime.datetime,
to_datetime: datetime.datetime,
) -> list[types.SensorDataContext]:
"""For a given `sensor_id` and `date`, return the metadata. The
returned list contains all locations at which the sensor has been
Expand Down Expand Up @@ -99,17 +99,23 @@ def fill_ts_data_gaps_with_default(ds: list[T], default_item: T) -> list[T]:
if ds[0].from_datetime > from_datetime:
new_first_element = default_item.model_copy()
new_first_element.from_datetime = from_datetime
new_first_element.to_datetime = ds[0].from_datetime.subtract(seconds=1) # type: ignore
new_first_element.to_datetime = ds[0].from_datetime - datetime.timedelta(
seconds=1
)
out.append(new_first_element)

# iterate over all elements and add default elements in between
# if there is a time gap between two elements
for i in range(len(ds) - 1):
out.append(ds[i])
if ds[i].to_datetime < ds[i + 1].from_datetime.subtract(seconds=1): # type: ignore
if ds[i].to_datetime < ds[i + 1].from_datetime - datetime.timedelta(seconds=1):
new_element = default_item.model_copy()
new_element.from_datetime = ds[i].to_datetime.add(seconds=1)
new_element.to_datetime = ds[i + 1].from_datetime.subtract(seconds=1) # type: ignore
new_element.from_datetime = ds[i].to_datetime + datetime.timedelta(
seconds=1
)
new_element.to_datetime = ds[i + 1].from_datetime - datetime.timedelta(
seconds=1
)
out.append(new_element)

# append last element
Expand All @@ -118,15 +124,17 @@ def fill_ts_data_gaps_with_default(ds: list[T], default_item: T) -> list[T]:
# if last element ends before requested time period
if ds[-1].to_datetime < to_datetime:
new_last_element = default_item.model_copy()
new_last_element.from_datetime = ds[-1].to_datetime.add(seconds=1)
new_last_element.from_datetime = ds[-1].to_datetime + datetime.timedelta(
seconds=1
)
new_last_element.to_datetime = to_datetime
out.append(new_last_element)

assert len(out) > 0, "This is a bug in the library"
for _e1, _e2 in zip(out[:-1], out[1:]):
assert (
_e1.to_datetime.add(seconds=1) == _e2.from_datetime
), "This is a bug in the library"
_e1.to_datetime + datetime.timedelta(seconds=1)
) == _e2.from_datetime, "This is a bug in the library"

return out

Expand Down Expand Up @@ -181,7 +189,7 @@ def fill_ts_data_gaps_with_default(ds: list[T], default_item: T) -> list[T]:
# we split the context into to. The we do this joining/splitting for the other time
# series properties as well.

breakpoints: list[pendulum.DateTime] = list(
breakpoints: list[datetime.datetime] = list(
sorted(
set(
[
Expand Down Expand Up @@ -254,8 +262,8 @@ def _get_segment_property(property_list: list[T]) -> T:
class _DatetimeSeriesItem(pydantic.BaseModel):
model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)

from_datetime: pendulum.DateTime
to_datetime: pendulum.DateTime
from_datetime: datetime.datetime
to_datetime: datetime.datetime


def _test_data_integrity(
Expand Down
31 changes: 23 additions & 8 deletions tum_esm_em27_metadata/types.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
import re
from typing import Any, Optional
import pendulum
Expand All @@ -7,24 +8,30 @@
class TimeSeriesElement(pydantic.BaseModel):
model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)

from_datetime: pendulum.DateTime
to_datetime: pendulum.DateTime
from_datetime: datetime.datetime
to_datetime: datetime.datetime

@pydantic.field_validator("from_datetime", "to_datetime", mode="before")
def name_must_contain_space(cls, v: str) -> pendulum.DateTime:
def name_must_contain_space(cls, v: str) -> datetime.datetime:
assert isinstance(v, str), "must be a string"
assert TimeSeriesElement.matches_datetime_regex(
v
), "must match the pattern YYYY-MM-DDTHH:MM:SS+HH:MM"
new_v: Any = pendulum.parser.parse(v, strict=True)
assert isinstance(new_v, pendulum.DateTime), "must be a datetime"
return new_v
return datetime.datetime.fromisoformat(v)

@staticmethod
def matches_datetime_regex(s: str) -> bool:
datetime_regex = r"^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([\+\-])(\d{2}):(\d{2})$"
return re.match(datetime_regex, s) is not None

@property
def from_date(self) -> datetime.date:
return self.from_datetime.date()

@property
def to_date(self) -> datetime.date:
return self.to_datetime.date()


class SensorTypes:
class DifferentUTCOffset(TimeSeriesElement):
Expand Down Expand Up @@ -148,8 +155,8 @@ class SensorDataContext(pydantic.BaseModel):

sensor_id: str
serial_number: int
from_datetime: pendulum.DateTime
to_datetime: pendulum.DateTime
from_datetime: datetime.datetime
to_datetime: datetime.datetime

location: LocationMetadata
utc_offset: float
Expand All @@ -160,3 +167,11 @@ class SensorDataContext(pydantic.BaseModel):
output_calibration_factors_xch4: list[float]
output_calibration_factors_xco: list[float]
output_calibration_scheme: Optional[str]

@property
def from_date(self) -> datetime.date:
return self.from_datetime.date()

@property
def to_date(self) -> datetime.date:
return self.to_datetime.date()

0 comments on commit 71ce8e9

Please sign in to comment.