From 67371322311ef1befc1c45cdeb4cd99a059c2f11 Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Sun, 1 Nov 2020 13:56:28 +0100
Subject: [PATCH] Fixed #28 - weather icons: don't show sun at nighttime

---
 src/logic/Helpers.py                          | 21 ++++++++--
 src/logic/tile/tiles/CurrentWeatherTile.html  |  8 +++-
 src/logic/tile/tiles/CurrentWeatherTile.py    | 15 +++++--
 src/logic/tile/tiles/HourlyForecastTile.html  |  8 +++-
 src/logic/tile/tiles/HourlyForecastTile.py    | 14 ++++---
 src/logic/tile/tiles/SevenDaysForecastTile.py |  2 +-
 test/__init__.py                              |  0
 test/logic/HelpersTest.py                     | 39 +++++++++++++++++++
 test/logic/__init__.py                        |  0
 9 files changed, 93 insertions(+), 14 deletions(-)
 create mode 100644 test/__init__.py
 create mode 100644 test/logic/HelpersTest.py
 create mode 100644 test/logic/__init__.py

diff --git a/src/logic/Helpers.py b/src/logic/Helpers.py
index a497f4b..2eee764 100644
--- a/src/logic/Helpers.py
+++ b/src/logic/Helpers.py
@@ -1,3 +1,5 @@
+from datetime import datetime
+
 from logic import Constants
 
 
@@ -29,7 +31,7 @@ def determine_color_for_wind(windSpeed: float) -> str:
         return Constants.RED.to_rgba()
 
 
-def determine_color_for_weather_icon(iconId: int):
+def determine_color_for_weather_icon(iconId: int, isDayTime: bool):
     if 200 <= iconId < 300:  # thunderstorm
         return Constants.RED.to_rgba()
     elif 300 <= iconId < 400:  # drizzle
@@ -40,7 +42,20 @@ def determine_color_for_weather_icon(iconId: int):
         return Constants.WHITE.to_rgba()
     elif 700 <= iconId < 800:  # fog
         return Constants.WHITE.to_rgba()
-    elif 800 <= iconId < 802:  # clear of partly cloudy (up to 25%)
-        return Constants.ORANGE.to_rgba()
+    elif 800 <= iconId < 802:  # clear or partly cloudy (up to 25%)
+        if isDayTime:
+            return Constants.ORANGE.to_rgba()
+        return Constants.WHITE.to_rgba()
     elif 802 <= iconId < 805:  # clouds > 25%
         return Constants.WHITE.to_rgba()
+
+
+def is_dayTime(sunrise: int, sunset: int, currentTimestamp: int = None) -> bool:
+    if not currentTimestamp:
+        currentTimestamp = int(datetime.now().timestamp())
+    return sunrise < currentTimestamp < sunset
+
+
+def timestamp_to_timezone(timestamp: int, timeZone: datetime.tzinfo):
+    timestamp = datetime.utcfromtimestamp(timestamp)
+    return timeZone.fromutc(timestamp)
diff --git a/src/logic/tile/tiles/CurrentWeatherTile.html b/src/logic/tile/tiles/CurrentWeatherTile.html
index 8363486..71adec4 100644
--- a/src/logic/tile/tiles/CurrentWeatherTile.html
+++ b/src/logic/tile/tiles/CurrentWeatherTile.html
@@ -63,7 +63,13 @@
 
 <div class="currentTemperatureTile">
     <div class="content">
-        <i class="wi wi-owm-{{ data['icon'] }} icon" style="color: {{ data['iconColor'] }};"></i>
+        {% if data['isDayTime'] %}
+            {% set iconPrefix = 'wi-owm-day-' %}
+        {% else %}
+            {% set iconPrefix = 'wi-owm-night-' %}
+        {% endif %}
+
+        <i class="wi {{ iconPrefix }}{{ data['icon'] }} icon" style="color: {{ data['iconColor'] }};"></i>
         <div class="entries">
             <div class="temperatures">
                 <div class="temperature-entry">
diff --git a/src/logic/tile/tiles/CurrentWeatherTile.py b/src/logic/tile/tiles/CurrentWeatherTile.py
index 237fde0..3fd7910 100644
--- a/src/logic/tile/tiles/CurrentWeatherTile.py
+++ b/src/logic/tile/tiles/CurrentWeatherTile.py
@@ -1,6 +1,8 @@
 import os
+from datetime import datetime
 from typing import Dict
 
+import pytz
 from flask import Blueprint
 
 from logic import Helpers
@@ -12,7 +14,8 @@ class CurrentWeatherTile(Tile):
     EXAMPLE_SETTINGS = {
         "lat": "51.012825",
         "lon": "13.666365",
-        "apiKey": "myApiKey"
+        "apiKey": "myApiKey",
+        "timeZone": "Europe/Berlin"
     }
 
     def __init__(self, uniqueName: str, settings: Dict, intervalInSeconds: int):
@@ -23,6 +26,8 @@ class CurrentWeatherTile(Tile):
 
         fetchIntervalInSeconds = 60 * 10  # query api less often
 
+        timeZone = pytz.timezone(self._settings['timeZone'])
+
         # cache key will be determined in service
         weatherData = weatherService.get_data('', fetchIntervalInSeconds, self._settings)
         currentWeather = weatherData['current']
@@ -30,6 +35,9 @@ class CurrentWeatherTile(Tile):
         feelsLike = currentWeather['feels_like']
         windSpeed = currentWeather['wind_speed'] * 3.6
         icon = currentWeather['weather'][0]['id']
+        sunrise = Helpers.timestamp_to_timezone(currentWeather['sunrise'], timeZone).timestamp()
+        sunset = Helpers.timestamp_to_timezone(currentWeather['sunset'], timeZone).timestamp()
+        isDayTime = Helpers.is_dayTime(sunrise, sunset)
 
         return {
             'temperature': Helpers.round_to_decimals(currentTemperature, 1),
@@ -37,10 +45,11 @@ class CurrentWeatherTile(Tile):
             'feelsLike': Helpers.round_to_decimals(feelsLike, 1),
             'feelsLikeColor': Helpers.determine_color_for_temperature(feelsLike),
             'icon': icon,
-            'iconColor': Helpers.determine_color_for_weather_icon(icon),
+            'iconColor': Helpers.determine_color_for_weather_icon(icon, isDayTime),
             'windDegrees': currentWeather['wind_deg'],
             'windSpeed': f'{Helpers.round_to_decimals(windSpeed, 1)} km/h',
-            'windSpeedColor': Helpers.determine_color_for_wind(windSpeed)
+            'windSpeedColor': Helpers.determine_color_for_wind(windSpeed),
+            'isDayTime': isDayTime
         }
 
     def render(self, data: Dict) -> str:
diff --git a/src/logic/tile/tiles/HourlyForecastTile.html b/src/logic/tile/tiles/HourlyForecastTile.html
index 9fc2159..515bf1e 100644
--- a/src/logic/tile/tiles/HourlyForecastTile.html
+++ b/src/logic/tile/tiles/HourlyForecastTile.html
@@ -64,9 +64,15 @@
 <div class="hourlyForecastTile">
     <div class="content">
         {% for item in data %}
+            {% if data['isDayTime'] %}
+                {% set iconPrefix = 'wi-owm-day-' %}
+            {% else %}
+                {% set iconPrefix = 'wi-owm-night-' %}
+            {% endif %}
+
             <div class="entry">
                 <div class="hour">{{ item['hour'] }} Uhr</div>
-                <i class="wi wi-owm-{{ item['icon'] }} icon" style="color: {{ item['iconColor'] }};"></i>
+                <i class="wi {{ iconPrefix }}{{ item['icon'] }} icon" style="color: {{ item['iconColor'] }};"></i>
                 <div class="temperature" style="color: {{ item['temperatureColor'] }}">{{ item['temperature'] }}&deg;C</div>
                 <div class="rain">
                     <i class="wi wi-raindrops"></i>
diff --git a/src/logic/tile/tiles/HourlyForecastTile.py b/src/logic/tile/tiles/HourlyForecastTile.py
index 8747ede..92caa78 100644
--- a/src/logic/tile/tiles/HourlyForecastTile.py
+++ b/src/logic/tile/tiles/HourlyForecastTile.py
@@ -31,27 +31,31 @@ class HourlyForecastTile(Tile):
         # cache key will be determined in service
         weatherData = weatherService.get_data('', fetchIntervalInSeconds, self._settings)
 
+        sunrise = Helpers.timestamp_to_timezone(weatherData['current']['sunrise'], timeZone).timestamp()
+        sunset = Helpers.timestamp_to_timezone(weatherData['current']['sunset'], timeZone).timestamp()
+
         hourData = []
         hourlyForecast = weatherData['hourly']
         for entry in hourlyForecast[:12]:
-            timestamp = entry['dt']
-            timestamp = datetime.utcfromtimestamp(timestamp)
-            timestamp = timeZone.fromutc(timestamp)
+            timestamp = Helpers.timestamp_to_timezone(entry['dt'], timeZone)
 
             temperature = entry['temp']
             icon = entry['weather'][0]['id']
             rainProbability = round(entry['pop'] * 100, -1)  # -1 rounds to the next ten
             windSpeed = entry['wind_speed'] * 3.6
 
+            isDayTime = Helpers.is_dayTime(sunrise, sunset, currentTimestamp=timestamp.timestamp())
+
             hourData.append({
                 'hour': timestamp.strftime('%H'),
                 'temperature': Helpers.round_to_decimals(temperature, 0),
                 'temperatureColor': Helpers.determine_color_for_temperature(temperature),
                 'icon': icon,
-                'iconColor': Helpers.determine_color_for_weather_icon(icon),
+                'iconColor': Helpers.determine_color_for_weather_icon(icon, isDayTime),
                 'windSpeed': f'{Helpers.round_to_decimals(windSpeed, 0)} km/h',
                 'windSpeedColor': Helpers.determine_color_for_wind(windSpeed),
-                'rainProbability': f'{Helpers.round_to_decimals(rainProbability, 0)} %'
+                'rainProbability': f'{Helpers.round_to_decimals(rainProbability, 0)} %',
+                'isDayTime': isDayTime
             })
 
         return {
diff --git a/src/logic/tile/tiles/SevenDaysForecastTile.py b/src/logic/tile/tiles/SevenDaysForecastTile.py
index d518eaf..2557683 100644
--- a/src/logic/tile/tiles/SevenDaysForecastTile.py
+++ b/src/logic/tile/tiles/SevenDaysForecastTile.py
@@ -39,7 +39,7 @@ class SevenDaysForecastTile(Tile):
             date = datetime.fromtimestamp(date)
             date = datetime.strftime(date, self.DATE_FORMAT)
             icon = day['weather'][0]['id']
-            iconColor = Helpers.determine_color_for_weather_icon(icon)
+            iconColor = Helpers.determine_color_for_weather_icon(icon, True)
             icons.append((icon, iconColor))
             forecastData[date] = (int(day['temp']['min']), int(day['temp']['max']))
 
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/test/logic/HelpersTest.py b/test/logic/HelpersTest.py
new file mode 100644
index 0000000..7b76e78
--- /dev/null
+++ b/test/logic/HelpersTest.py
@@ -0,0 +1,39 @@
+import unittest
+from datetime import datetime
+
+from logic import Helpers
+
+
+class HelpersTest(unittest.TestCase):
+    def test_join_url_parts(self):
+        self.assertEqual('https://myWebsite/eimer/0815', Helpers.join_url_parts('https://myWebsite', 'eimer', '0815'))
+
+    def test_round_to_decimals_one(self):
+        self.assertEqual('0.4', Helpers.round_to_decimals(0.428, 1))
+
+    def test_round_to_decimals_zero(self):
+        self.assertEqual('0', Helpers.round_to_decimals(0.428, 0))
+
+    def test_is_dayTime_true(self):
+        sunrise = datetime(year=2020, month=11, day=1, hour=8, minute=0, second=0).timestamp()
+        sunset = datetime(year=2020, month=11, day=1, hour=17, minute=0, second=0).timestamp()
+
+        currentTimestamp = datetime(year=2020, month=11, day=1, hour=12, minute=0, second=0).timestamp()
+
+        self.assertTrue(Helpers.is_dayTime(sunrise, sunset, currentTimestamp))
+
+    def test_is_dayTime_false_before(self):
+        sunrise = datetime(year=2020, month=11, day=1, hour=8, minute=0, second=0).timestamp()
+        sunset = datetime(year=2020, month=11, day=1, hour=17, minute=0, second=0).timestamp()
+
+        currentTimestamp = datetime(year=2020, month=11, day=1, hour=4, minute=0, second=0).timestamp()
+
+        self.assertFalse(Helpers.is_dayTime(sunrise, sunset, currentTimestamp))
+
+    def test_is_dayTime_false_after(self):
+        sunrise = datetime(year=2020, month=11, day=1, hour=8, minute=0, second=0).timestamp()
+        sunset = datetime(year=2020, month=11, day=1, hour=17, minute=0, second=0).timestamp()
+
+        currentTimestamp = datetime(year=2020, month=11, day=1, hour=18, minute=0, second=0).timestamp()
+
+        self.assertFalse(Helpers.is_dayTime(sunrise, sunset, currentTimestamp))
diff --git a/test/logic/__init__.py b/test/logic/__init__.py
new file mode 100644
index 0000000..e69de29
-- 
GitLab