Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt formatter for ISO durations #459

Merged
merged 5 commits into from Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions flexmeasures/api/common/schemas/sensor_data.py
Expand Up @@ -2,7 +2,7 @@
from typing import List, Union

from flask_login import current_user
from isodate import datetime_isoformat, duration_isoformat
from isodate import datetime_isoformat
from marshmallow import fields, post_load, validates_schema, ValidationError
from marshmallow.validate import OneOf
from marshmallow_polyfield import PolyField
Expand All @@ -16,7 +16,7 @@
from flexmeasures.api.common.utils.api_utils import upsample_values
from flexmeasures.data.schemas.times import AwareDateTimeField, DurationField
from flexmeasures.data.services.time_series import simplify_index
from flexmeasures.utils.time_utils import server_now
from flexmeasures.utils.time_utils import duration_isoformat, server_now
from flexmeasures.utils.unit_utils import (
convert_units,
units_are_convertible,
Expand Down
3 changes: 2 additions & 1 deletion flexmeasures/api/v1_1/tests/utils.py
@@ -1,7 +1,7 @@
"""Useful test messages"""
from typing import Optional, Dict, Any, List, Union
from datetime import timedelta
from isodate import duration_isoformat, parse_duration, parse_datetime
from isodate import parse_datetime, parse_duration

import pandas as pd
from numpy import tile
Expand All @@ -10,6 +10,7 @@

from flexmeasures.api.common.schemas.sensors import SensorField
from flexmeasures.data.models.time_series import Sensor, TimedBelief
from flexmeasures.utils.time_utils import duration_isoformat


def message_for_get_prognosis(
Expand Down
3 changes: 2 additions & 1 deletion flexmeasures/api/v1_2/implementations.py
Expand Up @@ -39,6 +39,7 @@
)
from flexmeasures.data.models.time_series import Sensor
from flexmeasures.data.services.resources import has_assets, can_access_asset
from flexmeasures.utils.time_utils import duration_isoformat


@type_accepted("GetDeviceMessageRequest")
Expand Down Expand Up @@ -120,7 +121,7 @@ def get_device_message_response(generic_asset_name_groups, duration):
new_event_groups, value_groups, generic_asset_type_name="event"
)
response["start"] = isodate.datetime_isoformat(start)
response["duration"] = isodate.duration_isoformat(duration)
response["duration"] = duration_isoformat(duration)
response["unit"] = unit

d, s = request_processed()
Expand Down
3 changes: 2 additions & 1 deletion flexmeasures/api/v1_3/implementations.py
Expand Up @@ -44,6 +44,7 @@
from flexmeasures.data.queries.utils import simplify_index
from flexmeasures.data.services.resources import has_assets, can_access_asset
from flexmeasures.data.services.scheduling import create_scheduling_job
from flexmeasures.utils.time_utils import duration_isoformat


p = inflect.engine()
Expand Down Expand Up @@ -182,7 +183,7 @@ def get_device_message_response(generic_asset_name_groups, duration):
new_event_groups, value_groups, generic_asset_type_name="event"
)
response["start"] = isodate.datetime_isoformat(start)
response["duration"] = isodate.duration_isoformat(duration)
response["duration"] = duration_isoformat(duration)
response["unit"] = unit

d, s = request_processed()
Expand Down
3 changes: 2 additions & 1 deletion flexmeasures/api/v2_0/tests/utils.py
@@ -1,6 +1,6 @@
from typing import Optional
from datetime import timedelta
from isodate import duration_isoformat, parse_duration, parse_datetime
from isodate import parse_datetime, parse_duration

import pandas as pd
import timely_beliefs as tb
Expand All @@ -11,6 +11,7 @@
from flexmeasures.api.v1_1.tests.utils import (
message_for_post_price_data as v1_1_message_for_post_price_data,
)
from flexmeasures.utils.time_utils import duration_isoformat


def get_asset_post_data() -> dict:
Expand Down
3 changes: 2 additions & 1 deletion flexmeasures/api/v3_0/sensors.py
Expand Up @@ -43,6 +43,7 @@
from flexmeasures.data.schemas import AwareDateTimeField
from flexmeasures.data.services.sensors import get_sensors
from flexmeasures.data.services.scheduling import create_scheduling_job
from flexmeasures.utils.time_utils import duration_isoformat
from flexmeasures.utils.unit_utils import ur


Expand Down Expand Up @@ -526,7 +527,7 @@ def get_schedule(self, sensor: Sensor, job_id: str, duration: timedelta, **kwarg
response = dict(
values=consumption_schedule.tolist(),
start=isodate.datetime_isoformat(start),
duration=isodate.duration_isoformat(duration),
duration=duration_isoformat(duration),
unit=sensor.unit,
)

Expand Down
32 changes: 32 additions & 0 deletions flexmeasures/utils/time_utils.py
@@ -1,3 +1,4 @@
import re
from datetime import datetime, timedelta
from typing import List, Union, Tuple, Optional

Expand Down Expand Up @@ -290,3 +291,34 @@ def supported_horizons() -> List[timedelta]:

def timedelta_to_pandas_freq_str(resolution: timedelta) -> str:
return to_offset(resolution).freqstr


def duration_isoformat(duration: timedelta):
"""Adapted version of isodate.duration_isoformat for formatting a datetime.timedelta.

The difference is that absolute days are not formatted as nominal days.
Workaround for https://github.com/gweis/isodate/issues/74.
"""
ret = []
usecs = abs(
(duration.days * 24 * 60 * 60 + duration.seconds) * 1000000
+ duration.microseconds
)
seconds, usecs = divmod(usecs, 1000000)
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
if hours or minutes or seconds or usecs:
ret.append("T")
if hours:
ret.append("%sH" % hours)
if minutes:
ret.append("%sM" % minutes)
if seconds or usecs:
if usecs:
ret.append(("%d.%06d" % (seconds, usecs)).rstrip("0"))
else:
ret.append("%d" % seconds)
ret.append("S")
# at least one component has to be there.
repl = ret and "".join(ret) or "T0H"
return re.sub("%P", repl, "P%P")