fix timezone handling for weather modules
This commit is contained in:
		| @@ -1,3 +1,9 @@ | |||||||
|  | """ | ||||||
|  | Inkycal opwenweather API abstraction | ||||||
|  | - retrieves free weather data from OWM 2.5 API endpoints (given provided API key) | ||||||
|  | - handles unit, language and timezone conversions | ||||||
|  | - provides ready-to-use current weather, hourly and daily forecasts | ||||||
|  | """ | ||||||
| import json | import json | ||||||
| import logging | import logging | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| @@ -9,13 +15,11 @@ from typing import Literal | |||||||
| import requests | import requests | ||||||
| from dateutil import tz | from dateutil import tz | ||||||
|  |  | ||||||
| from inkycal.custom.functions import get_system_tz |  | ||||||
|  |  | ||||||
| TEMP_UNITS = Literal["celsius", "fahrenheit"] | TEMP_UNITS = Literal["celsius", "fahrenheit"] | ||||||
| WIND_UNITS = Literal["meters_sec", "km_hour", "miles_hour", "knots", "beaufort"] | WIND_UNITS = Literal["meters_sec", "km_hour", "miles_hour", "knots", "beaufort"] | ||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| logger.setLevel("DEBUG") | logger.setLevel(level=logging.INFO) | ||||||
|  |  | ||||||
|  |  | ||||||
| def is_timestamp_within_range(timestamp: datetime, start_time: datetime, end_time: datetime) -> bool: | def is_timestamp_within_range(timestamp: datetime, start_time: datetime, end_time: datetime) -> bool: | ||||||
| @@ -31,6 +35,7 @@ class OpenWeatherMap: | |||||||
|         temp_unit: TEMP_UNITS = "celsius", |         temp_unit: TEMP_UNITS = "celsius", | ||||||
|         wind_unit: WIND_UNITS = "meters_sec", |         wind_unit: WIND_UNITS = "meters_sec", | ||||||
|         language: str = "en", |         language: str = "en", | ||||||
|  |         tz_name: str = "UTC" | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         self.api_key = api_key |         self.api_key = api_key | ||||||
|         self.city_id = city_id |         self.city_id = city_id | ||||||
| @@ -39,7 +44,8 @@ class OpenWeatherMap: | |||||||
|         self.language = language |         self.language = language | ||||||
|         self._api_version = "2.5" |         self._api_version = "2.5" | ||||||
|         self._base_url = f"https://api.openweathermap.org/data/{self._api_version}" |         self._base_url = f"https://api.openweathermap.org/data/{self._api_version}" | ||||||
|         self.tz_zone = tz.gettz(get_system_tz()) |         self.tz_zone = tz.gettz(tz_name) | ||||||
|  |         logger.info(f"OWM wrapper initialized for city id {self.city_id}, language {self.language} and timezone {tz_name}.") | ||||||
|  |  | ||||||
|     def get_current_weather(self) -> Dict: |     def get_current_weather(self) -> Dict: | ||||||
|         """ |         """ | ||||||
| @@ -57,7 +63,7 @@ class OpenWeatherMap: | |||||||
|                 f"Failure getting the current weather: code {response.status_code}. Reason: {response.text}" |                 f"Failure getting the current weather: code {response.status_code}. Reason: {response.text}" | ||||||
|             ) |             ) | ||||||
|         current_data = json.loads(response.text) |         current_data = json.loads(response.text) | ||||||
|  |          | ||||||
|         current_weather = {} |         current_weather = {} | ||||||
|         current_weather["detailed_status"] = current_data["weather"][0]["description"] |         current_weather["detailed_status"] = current_data["weather"][0]["description"] | ||||||
|         current_weather["weather_icon_name"] = current_data["weather"][0]["icon"] |         current_weather["weather_icon_name"] = current_data["weather"][0]["icon"] | ||||||
| @@ -71,13 +77,17 @@ class OpenWeatherMap: | |||||||
|         current_weather["wind"] = self.get_converted_windspeed( |         current_weather["wind"] = self.get_converted_windspeed( | ||||||
|             current_data["wind"]["speed"] |             current_data["wind"]["speed"] | ||||||
|         )  # OWM Unit Default: meter/sec, Metric: meter/sec |         )  # OWM Unit Default: meter/sec, Metric: meter/sec | ||||||
|         current_weather["wind_gust"] = self.get_converted_windspeed(current_data["wind"]["gust"]) |         if "gust" in current_data["wind"]: | ||||||
|  |             current_weather["wind_gust"] = self.get_converted_windspeed(current_data["wind"]["gust"]) | ||||||
|  |         else: | ||||||
|  |             logger.info(f"OpenWeatherMap response did not contain a wind gust speed. Using base wind: {current_weather['wind']} m/s.") | ||||||
|  |             current_weather["wind_gust"] = current_weather["wind"] | ||||||
|         current_weather["uvi"] = None  # TODO: this is no longer supported with 2.5 API, find alternative |         current_weather["uvi"] = None  # TODO: this is no longer supported with 2.5 API, find alternative | ||||||
|         current_weather["sunrise"] = current_data["sys"]["sunrise"]  # unix timestamp |         current_weather["sunrise"] = datetime.fromtimestamp(current_data["sys"]["sunrise"], tz=self.tz_zone)  # unix timestamp -> to our timezone | ||||||
|         current_weather["sunset"] = current_data["sys"]["sunset"] |         current_weather["sunset"] = datetime.fromtimestamp(current_data["sys"]["sunset"], tz=self.tz_zone) | ||||||
|  |  | ||||||
|         self.current_weather = current_weather |         self.current_weather = current_weather | ||||||
|          |  | ||||||
|         return current_weather |         return current_weather | ||||||
|  |  | ||||||
|     def get_weather_forecast(self) -> List[Dict]: |     def get_weather_forecast(self) -> List[Dict]: | ||||||
| @@ -136,7 +146,8 @@ class OpenWeatherMap: | |||||||
|     def get_forecast_for_day(self, days_from_today: int) -> Dict: |     def get_forecast_for_day(self, days_from_today: int) -> Dict: | ||||||
|         """ |         """ | ||||||
|         Get temperature range, rain and most frequent icon code |         Get temperature range, rain and most frequent icon code | ||||||
|         for the day that is days_from_today away |         for the day that is days_from_today away. | ||||||
|  |         "Today" is based on our local system timezone. | ||||||
|         :param days_from_today: |         :param days_from_today: | ||||||
|             should be int from 0-4: e.g. 2 -> 2 days from today |             should be int from 0-4: e.g. 2 -> 2 days from today | ||||||
|         :return: |         :return: | ||||||
| @@ -146,7 +157,7 @@ class OpenWeatherMap: | |||||||
|         _ = self.get_weather_forecast() |         _ = self.get_weather_forecast() | ||||||
|          |          | ||||||
|         # Calculate the start and end times for the specified number of days from now |         # Calculate the start and end times for the specified number of days from now | ||||||
|         current_time = datetime.now() |         current_time = datetime.now(tz=self.tz_zone) | ||||||
|         start_time = ( |         start_time = ( | ||||||
|             (current_time + timedelta(days=days_from_today)) |             (current_time + timedelta(days=days_from_today)) | ||||||
|             .replace(hour=0, minute=0, second=0, microsecond=0) |             .replace(hour=0, minute=0, second=0, microsecond=0) | ||||||
| @@ -178,7 +189,7 @@ class OpenWeatherMap: | |||||||
|  |  | ||||||
|         # Return a dict with that day's data |         # Return a dict with that day's data | ||||||
|         day_data = { |         day_data = { | ||||||
|             "datetime": start_time.timestamp(), |             "datetime": start_time, | ||||||
|             "icon": icon, |             "icon": icon, | ||||||
|             "temp_min": min(temps), |             "temp_min": min(temps), | ||||||
|             "temp_max": max(temps), |             "temp_max": max(temps), | ||||||
| @@ -277,7 +288,7 @@ def main(): | |||||||
|     key = "" |     key = "" | ||||||
|     city = 2643743 |     city = 2643743 | ||||||
|     lang = "de" |     lang = "de" | ||||||
|     owm = OpenWeatherMap(api_key=key, city_id=city, language=lang) |     owm = OpenWeatherMap(api_key=key, city_id=city, language=lang, tz="Europe/Berlin") | ||||||
|  |  | ||||||
|     current_weather = owm.get_current_weather() |     current_weather = owm.get_current_weather() | ||||||
|     print(current_weather) |     print(current_weather) | ||||||
|   | |||||||
| @@ -13,22 +13,24 @@ import matplotlib.dates as mdates | |||||||
| import matplotlib.pyplot as plt | import matplotlib.pyplot as plt | ||||||
| import matplotlib.ticker as ticker | import matplotlib.ticker as ticker | ||||||
| import numpy as np | import numpy as np | ||||||
|  | from dateutil import tz | ||||||
| from PIL import Image | from PIL import Image | ||||||
| from PIL import ImageDraw | from PIL import ImageDraw | ||||||
| from PIL import ImageFont | from PIL import ImageFont | ||||||
| from PIL import ImageOps | from PIL import ImageOps | ||||||
|  |  | ||||||
| from icons.weather_icons.weather_icons import get_weather_icon | from icons.weather_icons.weather_icons import get_weather_icon | ||||||
| from inkycal.custom import openweathermap_wrapper |  | ||||||
| from inkycal.custom.functions import fonts | from inkycal.custom.functions import fonts | ||||||
|  | from inkycal.custom.functions import get_system_tz | ||||||
| from inkycal.custom.functions import internet_available | from inkycal.custom.functions import internet_available | ||||||
| from inkycal.custom.functions import top_level | from inkycal.custom.functions import top_level | ||||||
| from inkycal.custom.inkycal_exceptions import NetworkNotReachableError | from inkycal.custom.inkycal_exceptions import NetworkNotReachableError | ||||||
|  | from inkycal.custom.openweathermap_wrapper import OpenWeatherMap | ||||||
| from inkycal.modules.inky_image import image_to_palette | from inkycal.modules.inky_image import image_to_palette | ||||||
| from inkycal.modules.template import inkycal_module | from inkycal.modules.template import inkycal_module | ||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| logger.setLevel("INFO") | logger.setLevel(logging.INFO) | ||||||
|  |  | ||||||
| icons_dir = os.path.join(top_level, "icons", "ui-icons") | icons_dir = os.path.join(top_level, "icons", "ui-icons") | ||||||
|  |  | ||||||
| @@ -106,10 +108,6 @@ class Fullweather(inkycal_module): | |||||||
|             "label": "Your locale", |             "label": "Your locale", | ||||||
|             "options": ["de_DE.UTF-8", "en_GB.UTF-8"], |             "options": ["de_DE.UTF-8", "en_GB.UTF-8"], | ||||||
|         }, |         }, | ||||||
|         "tz": { |  | ||||||
|             "label": "Your timezone", |  | ||||||
|             "options": ["Europe/Berlin", "UTC"], |  | ||||||
|         }, |  | ||||||
|         "font": { |         "font": { | ||||||
|             "label": "Font family to use for the entire screen", |             "label": "Font family to use for the entire screen", | ||||||
|             "options": ["NotoSans", "Roboto", "Poppins"], |             "options": ["NotoSans", "Roboto", "Poppins"], | ||||||
| @@ -135,6 +133,8 @@ class Fullweather(inkycal_module): | |||||||
|  |  | ||||||
|         config = config["config"] |         config = config["config"] | ||||||
|  |  | ||||||
|  |         self.tz = get_system_tz() | ||||||
|  |  | ||||||
|         # Check if all required parameters are present |         # Check if all required parameters are present | ||||||
|         for param in self.requires: |         for param in self.requires: | ||||||
|             if not param in config: |             if not param in config: | ||||||
| @@ -207,11 +207,6 @@ class Fullweather(inkycal_module): | |||||||
|         locale.setlocale(locale.LC_TIME, self.locale) |         locale.setlocale(locale.LC_TIME, self.locale) | ||||||
|         self.language = self.locale.split("_")[0] |         self.language = self.locale.split("_")[0] | ||||||
|  |  | ||||||
|         if "tz" in config: |  | ||||||
|             self.tz = config["tz"] |  | ||||||
|         else: |  | ||||||
|             self.tz = "UTC" |  | ||||||
|  |  | ||||||
|         if "icon_outline" in config: |         if "icon_outline" in config: | ||||||
|             self.icon_outline = config["icon_outline"] |             self.icon_outline = config["icon_outline"] | ||||||
|         else: |         else: | ||||||
| @@ -250,8 +245,8 @@ class Fullweather(inkycal_module): | |||||||
|         image_draw.rectangle((0, 0, rect_width, self.height), fill=0) |         image_draw.rectangle((0, 0, rect_width, self.height), fill=0) | ||||||
|  |  | ||||||
|         # Add text with current date |         # Add text with current date | ||||||
|         now = datetime.now() |         tz_info = tz.gettz(self.tz) | ||||||
|         dateString = now.strftime("%d. %B") |         dateString = datetime.now(tz=tz_info).strftime("%d. %B") | ||||||
|         dateFont = self.get_font(style="Bold", size=self.font_size) |         dateFont = self.get_font(style="Bold", size=self.font_size) | ||||||
|         # Get the width of the text |         # Get the width of the text | ||||||
|         dateStringbbox = dateFont.getbbox(dateString) |         dateStringbbox = dateFont.getbbox(dateString) | ||||||
| @@ -467,9 +462,9 @@ class Fullweather(inkycal_module): | |||||||
|         ax2.set_yticks(ax2.get_yticks()) |         ax2.set_yticks(ax2.get_yticks()) | ||||||
|         ax2.set_yticklabels([f"{value:.0f}" for value in ax2.get_yticks()]) |         ax2.set_yticklabels([f"{value:.0f}" for value in ax2.get_yticks()]) | ||||||
|  |  | ||||||
|         fig.gca().xaxis.set_major_locator(mdates.DayLocator(interval=1)) |         fig.gca().xaxis.set_major_locator(mdates.DayLocator(interval=1, tz=self.tz)) | ||||||
|         fig.gca().xaxis.set_major_formatter(mdates.DateFormatter("%a")) |         fig.gca().xaxis.set_major_formatter(mdates.DateFormatter(fmt="%a", tz=self.tz)) | ||||||
|         fig.gca().xaxis.set_minor_locator(mdates.HourLocator(interval=3)) |         fig.gca().xaxis.set_minor_locator(mdates.HourLocator(interval=3, tz=self.tz)) | ||||||
|         fig.tight_layout()  # Adjust layout to prevent clipping of labels |         fig.tight_layout()  # Adjust layout to prevent clipping of labels | ||||||
|  |  | ||||||
|         # Get image from plot and add it to the image |         # Get image from plot and add it to the image | ||||||
| @@ -515,8 +510,8 @@ class Fullweather(inkycal_module): | |||||||
|             # Date string: Day of week on line 1, date on line 2 |             # Date string: Day of week on line 1, date on line 2 | ||||||
|             short_day_font = self.get_font("ExtraBold", self.font_size + 4) |             short_day_font = self.get_font("ExtraBold", self.font_size + 4) | ||||||
|             short_month_day_font = self.get_font("Bold", self.font_size - 4) |             short_month_day_font = self.get_font("Bold", self.font_size - 4) | ||||||
|             short_day_name = datetime.fromtimestamp(day_data["datetime"]).strftime("%a") |             short_day_name = day_data["datetime"].strftime("%a") | ||||||
|             short_month_day = datetime.fromtimestamp(day_data["datetime"]).strftime("%b %d") |             short_month_day = day_data["datetime"].strftime("%b %d") | ||||||
|             short_day_name_text = rect_draw.textbbox((0, 0), short_day_name, font=short_day_font) |             short_day_name_text = rect_draw.textbbox((0, 0), short_day_name, font=short_day_font) | ||||||
|             short_month_day_text = rect_draw.textbbox((0, 0), short_month_day, font=short_month_day_font) |             short_month_day_text = rect_draw.textbbox((0, 0), short_month_day, font=short_month_day_font) | ||||||
|             day_name_x = (rectangle_width - short_day_name_text[2] + short_day_name_text[0]) / 2 |             day_name_x = (rectangle_width - short_day_name_text[2] + short_day_name_text[0]) / 2 | ||||||
| @@ -605,22 +600,16 @@ class Fullweather(inkycal_module): | |||||||
|             raise NetworkNotReachableError |             raise NetworkNotReachableError | ||||||
|  |  | ||||||
|         # Get the weather |         # Get the weather | ||||||
|         self.my_owm = openweathermap_wrapper.OpenWeatherMap( |         self.my_owm = OpenWeatherMap( | ||||||
|             api_key=self.api_key, |             api_key=self.api_key, | ||||||
|             city_id=self.location, |             city_id=self.location, | ||||||
|             temp_unit=self.temp_unit, |             temp_unit=self.temp_unit, | ||||||
|             wind_unit=self.wind_unit, |             wind_unit=self.wind_unit, | ||||||
|             language=self.language, |             language=self.language, | ||||||
|  |             tz_name=self.tz, | ||||||
|         ) |         ) | ||||||
|         self.current_weather = self.my_owm.get_current_weather() |         self.current_weather = self.my_owm.get_current_weather() | ||||||
|         self.hourly_forecasts = self.my_owm.get_weather_forecast() |         self.hourly_forecasts = self.my_owm.get_weather_forecast() | ||||||
|         # (self.current_weather, self.hourly_forecasts) = owm_forecasts.get_owm_data( |  | ||||||
|         #    token=self.api_key, |  | ||||||
|         #    city_id=self.location, |  | ||||||
|         #    temp_unit=self.temp_unit, |  | ||||||
|         #    wind_unit=self.wind_unit, |  | ||||||
|         #    language=self.language, |  | ||||||
|         # ) |  | ||||||
|  |  | ||||||
|         ## Create Base Image |         ## Create Base Image | ||||||
|         self.createBaseImage() |         self.createBaseImage() | ||||||
| @@ -640,9 +629,6 @@ class Fullweather(inkycal_module): | |||||||
|         if self.orientation == "horizontal": |         if self.orientation == "horizontal": | ||||||
|             self.image = self.image.rotate(90, expand=True) |             self.image = self.image.rotate(90, expand=True) | ||||||
|  |  | ||||||
|         # TODO: only for debugging, remove this: |  | ||||||
|         self.image.save("./openweather_full.png") |  | ||||||
|  |  | ||||||
|         logger.info("Fullscreen weather forecast generated successfully.") |         logger.info("Fullscreen weather forecast generated successfully.") | ||||||
|         # Convert images according to specified palette |         # Convert images according to specified palette | ||||||
|         im_black, im_colour = image_to_palette(image=self.image, palette="bwr", dither=True) |         im_black, im_colour = image_to_palette(image=self.image, palette="bwr", dither=True) | ||||||
| @@ -652,6 +638,7 @@ class Fullweather(inkycal_module): | |||||||
|  |  | ||||||
|     def get_font(self, style, size): |     def get_font(self, style, size): | ||||||
|         # Returns the TrueType font object with the given characteristics |         # Returns the TrueType font object with the given characteristics | ||||||
|  |         # Some workarounds for typefaces that do not exist in some fonts out there | ||||||
|         if self.font == "Roboto" and style == "ExtraBold": |         if self.font == "Roboto" and style == "ExtraBold": | ||||||
|             style = "Black" |             style = "Black" | ||||||
|         elif self.font in ["Ubuntu", "NotoSansUI"] and style in ["ExtraBold", "Black"]: |         elif self.font in ["Ubuntu", "NotoSansUI"] and style in ["ExtraBold", "Black"]: | ||||||
|   | |||||||
| @@ -3,16 +3,27 @@ Inkycal weather module | |||||||
| Copyright by aceinnolab | Copyright by aceinnolab | ||||||
| """ | """ | ||||||
|  |  | ||||||
|  | import datetime | ||||||
|  | import arrow | ||||||
| import decimal | import decimal | ||||||
|  | import logging | ||||||
| import math | import math | ||||||
|  |  | ||||||
| import arrow | from PIL import Image | ||||||
|  | from PIL import ImageDraw | ||||||
|  | from PIL import ImageFont | ||||||
|  |  | ||||||
| from inkycal.custom import * | from inkycal.custom.functions import draw_border | ||||||
|  | from inkycal.custom.functions import fonts | ||||||
|  | from inkycal.custom.functions import get_system_tz | ||||||
|  | from inkycal.custom.functions import internet_available | ||||||
|  | from inkycal.custom.functions import write | ||||||
|  | from inkycal.custom.inkycal_exceptions import NetworkNotReachableError | ||||||
| from inkycal.custom.openweathermap_wrapper import OpenWeatherMap | from inkycal.custom.openweathermap_wrapper import OpenWeatherMap | ||||||
| from inkycal.modules.template import inkycal_module | from inkycal.modules.template import inkycal_module | ||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
|  | logger.setLevel(level=logging.DEBUG) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Weather(inkycal_module): | class Weather(inkycal_module): | ||||||
| @@ -75,6 +86,8 @@ class Weather(inkycal_module): | |||||||
|  |  | ||||||
|         config = config['config'] |         config = config['config'] | ||||||
|  |  | ||||||
|  |         self.timezone = get_system_tz() | ||||||
|  |  | ||||||
|         # Check if all required parameters are present |         # Check if all required parameters are present | ||||||
|         for param in self.requires: |         for param in self.requires: | ||||||
|             if not param in config: |             if not param in config: | ||||||
| @@ -103,8 +116,14 @@ class Weather(inkycal_module): | |||||||
|         self.locale = config['language'] |         self.locale = config['language'] | ||||||
|         # additional configuration |         # additional configuration | ||||||
|  |  | ||||||
|         self.owm = OpenWeatherMap(api_key=self.api_key, city_id=self.location, wind_unit=self.wind_unit, temp_unit=self.temp_unit,language=self.locale) |         self.owm = OpenWeatherMap( | ||||||
|         self.timezone = get_system_tz() |             api_key=self.api_key,  | ||||||
|  |             city_id=self.location,  | ||||||
|  |             wind_unit=self.wind_unit,  | ||||||
|  |             temp_unit=self.temp_unit, | ||||||
|  |             language=self.locale,  | ||||||
|  |             tz_name=self.timezone | ||||||
|  |             ) | ||||||
|          |          | ||||||
|         self.weatherfont = ImageFont.truetype( |         self.weatherfont = ImageFont.truetype( | ||||||
|             fonts['weathericons-regular-webfont'], size=self.fontsize) |             fonts['weathericons-regular-webfont'], size=self.fontsize) | ||||||
| @@ -392,7 +411,7 @@ class Weather(inkycal_module): | |||||||
|         logging.debug(f'decimals temperature: {dec_temp} | decimals wind: {dec_wind}') |         logging.debug(f'decimals temperature: {dec_temp} | decimals wind: {dec_wind}') | ||||||
|  |  | ||||||
|         # Get current time |         # Get current time | ||||||
|         now = arrow.utcnow() |         now = arrow.utcnow().to(self.timezone) | ||||||
|  |  | ||||||
|         fc_data = {} |         fc_data = {} | ||||||
|  |  | ||||||
| @@ -400,73 +419,28 @@ class Weather(inkycal_module): | |||||||
|  |  | ||||||
|             logger.debug("getting hourly forecasts") |             logger.debug("getting hourly forecasts") | ||||||
|  |  | ||||||
|             # Forecasts are provided for every 3rd full hour |             # Add next 4 forecasts to fc_data dictionary, since we only have | ||||||
|             # find out how many hours there are until the next 3rd full hour |  | ||||||
|             if (now.hour % 3) != 0: |  | ||||||
|                 hour_gap = 3 - (now.hour % 3) |  | ||||||
|             else: |  | ||||||
|                 hour_gap = 3 |  | ||||||
|  |  | ||||||
|             # Create timings for hourly forecasts |  | ||||||
|             forecast_timings = [now.shift(hours=+ hour_gap + _).floor('hour') |  | ||||||
|                                 for _ in range(0, 12, 3)] |  | ||||||
|  |  | ||||||
|             # Create forecast objects for given timings |  | ||||||
|             hourly_forecasts = [_ for _ in weather_forecasts if arrow.get(_["datetime"]) in forecast_timings] |  | ||||||
|  |  | ||||||
|             # Add forecast-data to fc_data dictionary |  | ||||||
|             fc_data = {} |             fc_data = {} | ||||||
|             for index, forecast in enumerate(hourly_forecasts): |             for index, forecast in enumerate(weather_forecasts[0:4]): | ||||||
|                 temp = f"{forecast['temp']:.{dec_temp}f}{self.tempDispUnit}" |  | ||||||
|  |  | ||||||
|                 icon = forecast["icon"] |  | ||||||
|                 fc_data['fc' + str(index + 1)] = { |                 fc_data['fc' + str(index + 1)] = { | ||||||
|                     'temp': temp, |                     'temp': f"{forecast['temp']:.{dec_temp}f}{self.tempDispUnit}", | ||||||
|                     'icon': icon, |                     'icon': forecast["icon"], | ||||||
|                     'stamp': forecast_timings[index].to( |                     'stamp': forecast["datetime"].strftime("%I %p" if self.hour_format == 12 else "%H:%M")} | ||||||
|                         get_system_tz()).format('H.00' if self.hour_format == 24 else 'h a') |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|         elif self.forecast_interval == 'daily': |         elif self.forecast_interval == 'daily': | ||||||
|  |  | ||||||
|             logger.debug("getting daily forecasts") |             logger.debug("getting daily forecasts") | ||||||
|  |  | ||||||
|             def calculate_forecast(days_from_today) -> dict: |             daily_forecasts = [self.owm.get_forecast_for_day(days) for days in range(1, 5)] | ||||||
|                 """Get temperature range and most frequent icon code for forecast |  | ||||||
|                 days_from_today should be int from 1-4: e.g. 2 -> 2 days from today |  | ||||||
|                 """ |  | ||||||
|  |  | ||||||
|                 # Create a list containing time-objects for every 3rd hour of the day |  | ||||||
|                 time_range = list( |  | ||||||
|                     arrow.Arrow.range('hour', |  | ||||||
|                                       now.shift(days=days_from_today).floor('day'), |  | ||||||
|                                       now.shift(days=days_from_today).ceil('day') |  | ||||||
|                                       ))[::3] |  | ||||||
|  |  | ||||||
|                 # Get forecasts for each time-object |  | ||||||
|                 my_forecasts = [_ for _ in weather_forecasts if arrow.get(_["datetime"]) in time_range] |  | ||||||
|  |  | ||||||
|                 # Get all temperatures for this day |  | ||||||
|                 daily_temp = [round(_["temp"], ndigits=dec_temp) for _ in my_forecasts] |  | ||||||
|                 # Calculate min. and max. temp for this day |  | ||||||
|                 temp_range = f'{min(daily_temp)}{self.tempDispUnit}/{max(daily_temp)}{self.tempDispUnit}' |  | ||||||
|  |  | ||||||
|                 # Get all weather icon codes for this day |  | ||||||
|                 daily_icons = [_["icon"] for _ in my_forecasts] |  | ||||||
|                 # Find most common element from all weather icon codes |  | ||||||
|                 status = max(set(daily_icons), key=daily_icons.count) |  | ||||||
|  |  | ||||||
|                 weekday = now.shift(days=days_from_today).format('ddd', locale=self.locale) |  | ||||||
|                 return {'temp': temp_range, 'icon': status, 'stamp': weekday} |  | ||||||
|  |  | ||||||
|             daily_forecasts = [calculate_forecast(days) for days in range(1, 5)] |  | ||||||
|  |  | ||||||
|             for index, forecast in enumerate(daily_forecasts): |             for index, forecast in enumerate(daily_forecasts): | ||||||
|                 fc_data['fc' + str(index +1)] = { |                 fc_data['fc' + str(index +1)] = { | ||||||
|                     'temp': forecast['temp'], |                     'temp': f'{forecast["temp_min"]:.{dec_temp}f}{self.tempDispUnit}/{forecast["temp_max"]:.{dec_temp}f}{self.tempDispUnit}', | ||||||
|                     'icon': forecast['icon'], |                     'icon': forecast['icon'], | ||||||
|                     'stamp': forecast['stamp'] |                     'stamp': forecast['datetime'].strftime("%A") | ||||||
|                 } |                 } | ||||||
|  |         else: | ||||||
|  |             logger.error(f"Invalid forecast interval specified: {self.forecast_interval}. Check your settings!") | ||||||
|  |  | ||||||
|         for key, val in fc_data.items(): |         for key, val in fc_data.items(): | ||||||
|             logger.debug((key, val)) |             logger.debug((key, val)) | ||||||
| @@ -477,6 +451,7 @@ class Weather(inkycal_module): | |||||||
|  |  | ||||||
|         weather_icon = current_weather["weather_icon_name"] |         weather_icon = current_weather["weather_icon_name"] | ||||||
|         humidity = str(current_weather["humidity"]) |         humidity = str(current_weather["humidity"]) | ||||||
|  |  | ||||||
|         sunrise_raw = arrow.get(current_weather["sunrise"]).to(self.timezone) |         sunrise_raw = arrow.get(current_weather["sunrise"]).to(self.timezone) | ||||||
|         sunset_raw = arrow.get(current_weather["sunset"]).to(self.timezone) |         sunset_raw = arrow.get(current_weather["sunset"]).to(self.timezone) | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 mrbwburns
					mrbwburns