From 4eafd4e87f76b1b88e0d7152e4a30c19e5f173a2 Mon Sep 17 00:00:00 2001 From: Guy Khmelnitsky <3136012+GuyKh@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:38:20 +0300 Subject: [PATCH] fix: Fix DateTime according to DST (#70) --- ims_envista/commons.py | 6 ++++++ ims_envista/meteo_data.py | 25 ++++++++++++++++++++++--- tests/unit/test_ims_envista.py | 32 ++++++++++++++++++++------------ 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/ims_envista/commons.py b/ims_envista/commons.py index ca17663..0fa7401 100644 --- a/ims_envista/commons.py +++ b/ims_envista/commons.py @@ -66,6 +66,12 @@ def _verify_response_or_raise(response: ClientResponse) -> None: raise ImsEnvistaApiClientAuthenticationError( msg, ) + content_type = response.headers.get("Content-Type") + if content_type and "application/json" not in content_type: + msg = f"Invalid response from IMS - bad Content-Type: {content_type}" + raise ImsEnvistaApiClientError( + msg, + ) response.raise_for_status() diff --git a/ims_envista/meteo_data.py b/ims_envista/meteo_data.py index 0a49efc..e119867 100644 --- a/ims_envista/meteo_data.py +++ b/ims_envista/meteo_data.py @@ -151,12 +151,31 @@ def __repr__(self) -> str: tz = pytz.timezone("Asia/Jerusalem") + +def _fix_datetime_offset(dt): + dt = dt.replace(tzinfo=None) + dt = tz.localize(dt) + + # Get the UTC offset in seconds + offset_seconds = dt.utcoffset().total_seconds() + + # Create a fixed timezone with the same offset and name + fixed_timezone = datetime.timezone(datetime.timedelta(seconds=offset_seconds), dt.tzname()) + + # Replace the pytz tzinfo with the fixed timezone + dt = dt.replace(tzinfo=fixed_timezone) + + is_dst = dt.dst() != datetime.timedelta(0) + if is_dst: + dt = dt + datetime.timedelta(hours=1) + + return dt,is_dst + + def meteo_data_from_json(station_id: int, data: dict) -> MeteorologicalData: """Create a MeteorologicalData object from a JSON object.""" - is_dst = bool(time.localtime(time.time()).tm_isdst) - dt = datetime.datetime.fromisoformat(data[API_DATETIME]) - dt.replace(tzinfo=tz) + dt, is_dst = _fix_datetime_offset(dt) channel_value_dict = {} for channel_value in data[API_CHANNELS]: diff --git a/tests/unit/test_ims_envista.py b/tests/unit/test_ims_envista.py index 2ddc364..c045792 100644 --- a/tests/unit/test_ims_envista.py +++ b/tests/unit/test_ims_envista.py @@ -5,6 +5,7 @@ from datetime import date, datetime, timedelta from zoneinfo import ZoneInfo +import pytz from aiohttp import ClientSession from ims_envista import IMSEnvista @@ -27,6 +28,7 @@ async def asyncSetUp(self) -> None: # Initialize the session in an async context self.session = ClientSession() + self.tz = pytz.timezone("Asia/Jerusalem") self.ims = IMSEnvista(self.token, session=self.session) async def asyncTearDown(self) -> None: @@ -117,7 +119,7 @@ async def test_get_earliest_station_data_with_channel(self) -> None: async def test_get_station_data_from_date(self) -> None: """Test get_station_data_from_date endpoint.""" station_data = await self.ims.get_station_data_from_date( - self.station_id, datetime.now(tz=ZoneInfo("Asia/Jerusalem")) + self.station_id, self.tz.localize(datetime.now()) ) assert station_data is not None @@ -125,13 +127,13 @@ async def test_get_station_data_from_date(self) -> None: assert station_data.data is not None assert len(station_data.data) > 0 for station_reading in station_data.data: - assert station_reading.datetime.date() == datetime.now(tz=ZoneInfo("Asia/Jerusalem")).date() + assert station_reading.datetime.date() == self.tz.localize(datetime.now()).date() async def test_get_station_data_from_date_with_channel(self) -> None: """Test get_station_data_from_date endpoint with channel.""" station_data = await self.ims.get_station_data_from_date( - self.station_id, datetime.now(tz=ZoneInfo("Asia/Jerusalem")), self.channel_id + self.station_id, self.tz.localize(datetime.now()), self.channel_id ) assert station_data is not None @@ -139,13 +141,16 @@ async def test_get_station_data_from_date_with_channel(self) -> None: assert station_data.data is not None assert len(station_data.data) > 0 for station_reading in station_data.data: - assert station_reading.datetime.date() == datetime.now(tz=ZoneInfo("Asia/Jerusalem")).date() + assert station_reading.datetime.date() == self.tz.localize(datetime.now()).date() async def test_get_station_data_by_date_range(self) -> None: """Test get_station_data_by_date_range endpoint.""" - today = datetime.now(tz=ZoneInfo("Asia/Jerusalem")) + today = self.tz.localize(datetime.now()) + today = today.replace(hour=0, minute=0, second=0, microsecond=0) yesterday = today - timedelta(days=1) + # `hour=1` for DST fix cases + today = today.replace(hour=2, minute=0, second=0, microsecond=0) station_data = await self.ims.get_station_data_by_date_range( self.station_id, from_date=yesterday, to_date=today ) @@ -156,14 +161,17 @@ async def test_get_station_data_by_date_range(self) -> None: assert len(station_data.data) > 0 for station_reading in station_data.data: assert station_reading.datetime >= to_date_time(yesterday) - assert station_reading.datetime < to_date_time(today) + assert station_reading.datetime < today assert station_reading.td > 0 async def test_get_station_data_by_date_range_with_channel(self) -> None: """Test get_station_data_by_date_range endpoint with channel.""" - today = datetime.now(tz=ZoneInfo("Asia/Jerusalem")) + today = self.tz.localize(datetime.now()) + today = today.replace(hour=0, minute=0, second=0, microsecond=0) yesterday = today - timedelta(days=1) + # `hour=1` for DST fix cases + today = today.replace(hour=2, minute=0, second=0, microsecond=0) station_data = await self.ims.get_station_data_by_date_range( self.station_id, from_date=yesterday, @@ -177,14 +185,14 @@ async def test_get_station_data_by_date_range_with_channel(self) -> None: assert len(station_data.data) > 0 for station_reading in station_data.data: assert station_reading.datetime >= to_date_time(yesterday) - assert station_reading.datetime < to_date_time(today) + assert station_reading.datetime < today assert station_reading.td > 0 async def test_get_monthly_station_data(self) -> None: """Test get_monthly_station_data endpoint.""" - year = datetime.now(tz=ZoneInfo("Asia/Jerusalem")).strftime("%Y") - month = datetime.now(tz=ZoneInfo("Asia/Jerusalem")).strftime("%m") + year = self.tz.localize(datetime.now()).strftime("%Y") + month = self.tz.localize(datetime.now()).strftime("%m") station_data = await self.ims.get_monthly_station_data( self.station_id, month=month, year=year ) @@ -201,8 +209,8 @@ async def test_get_monthly_station_data(self) -> None: async def test_get_monthly_station_data_with_channel(self) -> None: """Test get_monthly_station_data endpoint with channel.""" - year = datetime.now(tz=ZoneInfo("Asia/Jerusalem")).strftime("%Y") - month = datetime.now(tz=ZoneInfo("Asia/Jerusalem")).strftime("%m") + year = self.tz.localize(datetime.now()).strftime("%Y") + month = self.tz.localize(datetime.now()).strftime("%m") station_data = await self.ims.get_monthly_station_data( self.station_id, channel_id=self.channel_id, month=month, year=year )