From fa7ca1a8036f805c9b4fae8783e088c6a9b04517 Mon Sep 17 00:00:00 2001 From: roe-dl Date: Thu, 4 Jun 2026 16:11:12 +0200 Subject: [PATCH 1/7] tags `$seasons`, `$seasonsyear`, iterator `.seasons` --- docs_src/changes.md | 2 + docs_src/custom/cheetah-generator.md | 41 ++++++++ src/weewx/cheetahgenerator.py | 2 + src/weewx/defaults.py | 2 + src/weewx/tags.py | 108 +++++++++++++++++++- src/weewx_data/skins/Seasons/lang/de.conf | 1 + src/weewx_data/skins/Seasons/lang/en.conf | 1 + src/weewx_data/skins/Seasons/statistics.inc | 4 +- 8 files changed, 159 insertions(+), 2 deletions(-) diff --git a/docs_src/changes.md b/docs_src/changes.md index 25dd49cb..84154ea2 100644 --- a/docs_src/changes.md +++ b/docs_src/changes.md @@ -9,6 +9,8 @@ Example `xstats.py` had nonsensical time context values. Fixed in [PR #1072](https://github.com/weewx/weewx/pull/1072). Thanks to user evilbunny2008. +Cheetahgenerator seasons timespans `$season`, `$seasonsyear`, seasons +iterator `.seasons`. ### 5.3.1 03/03/2026 diff --git a/docs_src/custom/cheetah-generator.md b/docs_src/custom/cheetah-generator.md index 5c99ff0c..eb9b5b76 100644 --- a/docs_src/custom/cheetah-generator.md +++ b/docs_src/custom/cheetah-generator.md @@ -446,6 +446,12 @@ There are several _aggregation periods_ that can be used: $month.outTemp.min The minimum temperature this month. + + $season + This season + $season.outTemp.avg + The average temparature this season so far + $year This year (since 1-Jan). @@ -460,6 +466,13 @@ There are several _aggregation periods_ that can be used: $rainyear.rain.sum The total rainfall for this rain year. + + $seasonsyear + From the beginning of the last spring to the beginning of the + next spring, observing hemisphere + $seasonsyear.outTemp.min + The minimum temperature during that time + $alltime @@ -509,15 +522,38 @@ Here are some examples: $month(months_ago=1).outTemp.max The maximum temperature last month. + + $season(seasons_ago=s) + $season(seasons_ago=1).outTemp.min + The minimum temperature of the previous season + + + $season(years_ago=y) + $season(years_ago=1).outTemp.min + The minimum temperature of the same season in the previous year + $year(years_ago=y) $year(years_ago=1).outTemp.max The maximum temperature last year. + + y + $seasonsyear(years_ago=1).outTemp.max + The maximum temperature of the previous seasons year + +There are two kinds of seasons: meterological and astronomical. The +meteorological season always starts at the 1st of March, June, September, +and December. The astronomical season starts at equinox or solstice. +There is an optional parameter `season_type` for `$season` and `$seasonsyear` +to specify, which one of them is to be used. Set it to `meteorological` +(the default if not provided) or `astronomical`. Both words can be +abbreviated. + ### Unit conversion options The option _`unit_conversion`_ can be used with either current observations @@ -1233,6 +1269,11 @@ It is possible to iterate over the following: .months Iterate by months + + .seasons + Iterate by seasons. Depending on the parameter `season_type` of + `$seasonsyear`, meteorological or astronomical seasons are returned. + .years Iterate by years diff --git a/src/weewx/cheetahgenerator.py b/src/weewx/cheetahgenerator.py index e85033a3..26a2c541 100644 --- a/src/weewx/cheetahgenerator.py +++ b/src/weewx/cheetahgenerator.py @@ -596,6 +596,8 @@ class Stats(SearchList): converter=self.generator.converter, week_start=self.generator.stn_info.week_start, rain_year_start=self.generator.stn_info.rain_year_start, + lat = self.generator.stn_info.latitude_f, + lon = self.generator.stn_info.longitude_f, trend=trend_dict, skin_dict=self.generator.skin_dict) diff --git a/src/weewx/defaults.py b/src/weewx/defaults.py index 1b516633..5d033327 100644 --- a/src/weewx/defaults.py +++ b/src/weewx/defaults.py @@ -225,8 +225,10 @@ log_failure = False day = %X week = %X (%A) month = %x %X + season = %x %X year = %x %X rainyear = %x %X + seasonsyear = %x %X current = %x %X ephem_day = %X ephem_year = %x %X diff --git a/src/weewx/tags.py b/src/weewx/tags.py index c73817cb..adfd19ed 100644 --- a/src/weewx/tags.py +++ b/src/weewx/tags.py @@ -5,9 +5,11 @@ # """Classes for implementing the weewx tag 'code' codes.""" +import time import weeutil.weeutil import weewx.units import weewx.xtypes +import weewx.almanac from weeutil.weeutil import to_int from weewx.units import ValueTuple @@ -97,6 +99,47 @@ class TimeBinder: context='month', formatter=self.formatter, converter=self.converter, **self.option_dict) + def season(self, data_binding=None, seasons_ago=0, years_ago=0, lat=None, lon=None, season_type='m'): + if season_type[0].lower()=='m': + # meteorological season + years_ago, seasons_ago = seasons_ago//4+years_ago, seasons_ago%4 + time_dt = time.localtime(self.report_time) + start_year = time_dt.tm_year - years_ago + start_month = int(time_dt.tm_mon//3-seasons_ago)*3 + while start_month<=0: + start_month += 12 + start_year -= 1 + end_year = start_year + end_month = start_month + 3 + if end_month>12: + end_month -= 12 + end_year += 1 + start_of_season = int(time.mktime((start_year, start_month, 1, 0, 0, 0, 0, 0, -1))) + end_of_season = int(time.mktime((end_year, end_month, 1, 0, 0, 0, 0, 0, -1))) + elif season_type[0].lower()=='a': + # astronomical season + if lat is None: lat = self.option_dict.get('lat',0.0) + if lon is None: lon = self.option_dict.get('lon',0.0) + alm = weewx.almanac.Almanac(self.report_time, lat, lon) + equinox = alm.previous_equinox.unix_epoch.raw + solstice = alm.previous_solstice.unix_epoch.raw + if equinox>solstice: + # spring or autumn + start_of_season = weeutil.weeutil.startOfDay(equinox) + end_of_season = weeutil.weeutil.startOfDay(alm.next_solstice.unix_epoch.raw) + else: + # summer or winter + start_of_season = weeutil.weeutil.startOfDay(solstice) + end_of_season = weeutil.weeutil.startOfDay(alm.next_equinox.unix_epoch.raw) + else: + raise ValueError("season type can be 'meteorological' or 'astronomical'") + return TimespanBinder( + weeutil.weeutil.TimeSpan(start_of_season,end_of_season), + self.db_lookup, data_binding=data_binding, + context='season', formatter=self.formatter, converter=self.converter, + season_type=season_type, + **self.option_dict) + def year(self, data_binding=None, years_ago=0): return TimespanBinder( weeutil.weeutil.archiveYearSpan(self.report_time, years_ago=years_ago), @@ -123,6 +166,35 @@ class TimeBinder: context='rainyear', formatter=self.formatter, converter=self.converter, **self.option_dict) + def seasonsyear(self, data_binding=None, years_ago=0, lat=None, lon=None, season_type='m'): + """ meteorological or astronomical seasons year """ + if lat is None: lat = self.option_dict.get('lat') + if lon is None: lon = self.option_dict.get('lon') + if season_type[0].lower()=='m': + # meteorological seasons + seasons_start_month = 9 if lat<0.0 else 3 + time_dt = time.localtime(self.report_time) + year = (time_dt.tm_year if time_dt.tm_mon>=seasons_start_month else time_dt.tm_year-1) - years_ago + start_of_seasons_year = int(time.mktime((year, seasons_start_month, 1, 0, 0, 0, 0, 0, -1))) + end_of_seasons_year = int(time.mktime((year+1, seasons_start_month, 1, 0, 0, 0, 0, 0, -1))) + elif season_type[0].lower()=='a': + # astronomical seasons + alm = weewx.almanac.Almanac(self.report_time, lat, lon) + if lat<0.0: + start_of_seasons_year = weeutil.weeutil.startOfDay(alm.previous_autumnal_equinox.unix_epoch.raw) + end_of_seasons_year = weeutil.weeutil.startOfDay(alm.next_autumnal_equinox.unix_epoch.raw) + else: + start_of_seasons_year = weeutil.weeutil.startOfDay(alm.previous_vernal_equinox.unix_epoch.raw) + end_of_seasons_year = weeutil.weeutil.startOfDay(alm.next_vernal_equinox.unix_epoch.raw) + else: + raise ValueError("season type can be 'meteorological' or 'astronomical'") + return TimespanBinder( + weeutil.weeutil.TimeSpan(start_of_seasons_year,end_of_seasons_year), + self.db_lookup, data_binding=data_binding, + context='seasonsyear', formatter=self.formatter, converter=self.converter, + season_type=season_type, + **self.option_dict) + def span(self, data_binding=None, time_delta=0, hour_delta=0, day_delta=0, week_delta=0, month_delta=0, year_delta=0, boundary=None): return TimespanBinder( @@ -236,7 +308,41 @@ class TimespanBinder: self.db_lookup, self.data_binding, 'year', self.formatter, self.converter, **self.option_dict) - + # Iterate over seasons in the time period: + def seasons(self): + season_type = self.option_dict.get('season_type','m')[0].lower() + if season_type=='m': + # meteorological season + def genSeasonSpans(start, stop): + while start12: + end_year +=1 + end_month -= 12 + end = int(time.mktime((end_year, end_month, 1, 0, 0, 0, 0, 0, -1))) + yield weeutil.weeutil.TimeSpan(start, end) + start = end + elif season_type=='a': + # astronomical season + lat = self.option_dict.get('lat',0.0) + lon = self.option_dict.get('lon',0.0) + def genSeasonSpans(start, stop): + while start$gettext("Today") $gettext("Week") $gettext("Month") + $gettext("Season") $gettext("Year") $gettext("Rain Year") From da2aa81fc0c95ae4e454a7fbd56db8ca4e4ba576 Mon Sep 17 00:00:00 2001 From: roe-dl Date: Thu, 4 Jun 2026 19:52:23 +0200 Subject: [PATCH 2/7] parameter `seasons_ago` and `years_ago` --- src/weewx/tags.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/weewx/tags.py b/src/weewx/tags.py index adfd19ed..551e3b51 100644 --- a/src/weewx/tags.py +++ b/src/weewx/tags.py @@ -100,9 +100,9 @@ class TimeBinder: **self.option_dict) def season(self, data_binding=None, seasons_ago=0, years_ago=0, lat=None, lon=None, season_type='m'): + years_ago, seasons_ago = seasons_ago//4+years_ago, seasons_ago%4 if season_type[0].lower()=='m': # meteorological season - years_ago, seasons_ago = seasons_ago//4+years_ago, seasons_ago%4 time_dt = time.localtime(self.report_time) start_year = time_dt.tm_year - years_ago start_month = int(time_dt.tm_mon//3-seasons_ago)*3 @@ -120,9 +120,16 @@ class TimeBinder: # astronomical season if lat is None: lat = self.option_dict.get('lat',0.0) if lon is None: lon = self.option_dict.get('lon',0.0) - alm = weewx.almanac.Almanac(self.report_time, lat, lon) - equinox = alm.previous_equinox.unix_epoch.raw - solstice = alm.previous_solstice.unix_epoch.raw + time_ts = self.report_time + if years_ago: + time_td = time.localtime(time_ts) + time_ts = time.mktime((time_td.tm_year-years_ago, time_td.tm_mon, time_td.tm_mday, time_td.tm_hour, time_td.tm_min, time_td.tm_sec, 0, 0, time_td.tm_isdst)) + for i in range(seasons_ago,-1,-1): + alm = weewx.almanac.Almanac(time_ts, lat, lon) + equinox = alm.previous_equinox.unix_epoch.raw + solstice = alm.previous_solstice.unix_epoch.raw + if not i: break + time_ts = (equinox if equinox>solstice else solstice)-90000 if equinox>solstice: # spring or autumn start_of_season = weeutil.weeutil.startOfDay(equinox) @@ -179,7 +186,11 @@ class TimeBinder: end_of_seasons_year = int(time.mktime((year+1, seasons_start_month, 1, 0, 0, 0, 0, 0, -1))) elif season_type[0].lower()=='a': # astronomical seasons - alm = weewx.almanac.Almanac(self.report_time, lat, lon) + time_ts = self.report_time + if years_ago: + time_td = time.localtime(time_ts) + time_ts = time.mktime((time_td.tm_year-years_ago, time_td.tm_mon, time_td.tm_mday, time_td.tm_hour, time_td.tm_min, time_td.tm_sec, 0, 0, time_td.tm_isdst)) + alm = weewx.almanac.Almanac(time_ts, lat, lon) if lat<0.0: start_of_seasons_year = weeutil.weeutil.startOfDay(alm.previous_autumnal_equinox.unix_epoch.raw) end_of_seasons_year = weeutil.weeutil.startOfDay(alm.next_autumnal_equinox.unix_epoch.raw) @@ -308,6 +319,7 @@ class TimespanBinder: self.db_lookup, self.data_binding, 'year', self.formatter, self.converter, **self.option_dict) + # Iterate over seasons in the time period: def seasons(self): season_type = self.option_dict.get('season_type','m')[0].lower() From d5e2f9405a0a5c4b3b48027389fbb55d93e9d437 Mon Sep 17 00:00:00 2001 From: roe-dl Date: Thu, 4 Jun 2026 20:16:26 +0200 Subject: [PATCH 3/7] tests for `$season` tag --- src/weewx/tests/test_daily.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/weewx/tests/test_daily.py b/src/weewx/tests/test_daily.py index b87061f3..270239fa 100644 --- a/src/weewx/tests/test_daily.py +++ b/src/weewx/tests/test_daily.py @@ -258,6 +258,12 @@ def testTags(config_dict): assert str(tagStats.month().barometer.max) == "31.000 inHg" assert str(tagStats.month().barometer.mintime) == "03/03/10 00:00:00" assert str(tagStats.month().barometer.maxtime) == "03/05/10 00:00:00" + assert str(tagStats.season().barometer.avg) == "30.000 inHg" + assert str(tagStats.season().barometer.min) == "29.000 inHg" + assert str(tagStats.season().barometer.max) == "31.000 inHg" + assert str(tagStats.season(season_type='astronomical').barometer.avg) == "30.007 inHg" + assert str(tagStats.season(season_type='astronomical').barometer.min) == "29.000 inHg" + assert str(tagStats.season(season_type='astronomical').barometer.max) == "31.000 inHg" assert str(tagStats.year().barometer.avg) == "29.996 inHg" assert str(tagStats.year().barometer.min) == "29.000 inHg" assert str(tagStats.year().barometer.max) == "31.000 inHg" @@ -278,6 +284,12 @@ def testTags(config_dict): assert str(tagStats.month().outTemp.max) == "58.9°F" assert str(tagStats.month().outTemp.mintime) == "03/01/10 03:00:00" assert str(tagStats.month().outTemp.maxtime) == "03/31/10 16:00:00" + assert str(tagStats.season().outTemp.avg) == "48.4°F" + assert str(tagStats.season().outTemp.min) == "-1.0°F" + assert str(tagStats.season().outTemp.max) == "94.1°F" + assert str(tagStats.season(season_type='astronomical').outTemp.avg) == "59.4°F" + assert str(tagStats.season(season_type='astronomical').outTemp.min) == "11.0°F" + assert str(tagStats.season(season_type='astronomical').outTemp.max) == "99.2°F" assert str(tagStats.year().outTemp.avg) == "48.3°F" assert str(tagStats.year().outTemp.min) == "-20.0°F" assert str(tagStats.year().outTemp.max) == "100.0°F" From 2d84321a118ed581826e662e3314198c2115b8b6 Mon Sep 17 00:00:00 2001 From: roe-dl Date: Thu, 4 Jun 2026 21:52:30 +0200 Subject: [PATCH 4/7] season nominal interval --- src/weeutil/weeutil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/weeutil/weeutil.py b/src/weeutil/weeutil.py index 30bf21c5..9950a45d 100644 --- a/src/weeutil/weeutil.py +++ b/src/weeutil/weeutil.py @@ -215,6 +215,7 @@ nominal_intervals = { 'day': 86400, 'week': 7 * 86400, 'month': int(365.25 / 12 * 86400), + 'season': int(365.25 / 4 * 86400), 'year': int(365.25 * 86400), } duration_synonyms = { From 3104d6c19c09419fd8c58051a3ed6cb244aec0ae Mon Sep 17 00:00:00 2001 From: roe-dl Date: Fri, 5 Jun 2026 15:30:56 +0200 Subject: [PATCH 5/7] localization --- src/weewx_data/skins/Seasons/lang/cz.conf | 1 + src/weewx_data/skins/Seasons/lang/fr.conf | 1 + src/weewx_data/skins/Seasons/lang/gr.conf | 1 + src/weewx_data/skins/Seasons/lang/it.conf | 1 + src/weewx_data/skins/Seasons/lang/nl.conf | 1 + 5 files changed, 5 insertions(+) diff --git a/src/weewx_data/skins/Seasons/lang/cz.conf b/src/weewx_data/skins/Seasons/lang/cz.conf index ff6fd4e6..dbf33fa4 100644 --- a/src/weewx_data/skins/Seasons/lang/cz.conf +++ b/src/weewx_data/skins/Seasons/lang/cz.conf @@ -190,6 +190,7 @@ unit_system = metricwx "Right ascension" = "Vzestup" "Rise" = "Východ" "RMS Wind" = "Efektivní hodnota větru" + "Season" = "Roční období" "select month" = "Vyberte měsíc" "select year" = "Vyberte rok" "Sensor Status" = "Stav senzoru" diff --git a/src/weewx_data/skins/Seasons/lang/fr.conf b/src/weewx_data/skins/Seasons/lang/fr.conf index b41a1001..0e3533d6 100644 --- a/src/weewx_data/skins/Seasons/lang/fr.conf +++ b/src/weewx_data/skins/Seasons/lang/fr.conf @@ -191,6 +191,7 @@ unit_system = metricwx "Right ascension" = "Ascension droite" "Rise" = "Lever" "RMS Wind" = "Valeur Efficace (RMS) Vent" + "Season" = "Saison" "select month" = "Choisir le mois" "select year" = "Choisir l'année" "Sensor Status" = "Statut capteur(s)" diff --git a/src/weewx_data/skins/Seasons/lang/gr.conf b/src/weewx_data/skins/Seasons/lang/gr.conf index 4f0dd155..3a9a691c 100644 --- a/src/weewx_data/skins/Seasons/lang/gr.conf +++ b/src/weewx_data/skins/Seasons/lang/gr.conf @@ -188,6 +188,7 @@ unit_system = metricwx "Right ascension" = "Δεξιά ανύψωση" "Rise" = "Aνατολή" "RMS Wind" = "RMS Ανέμου" + "Season" = "Εποχή" "select month" = "Επιλέξτε" "select year" = "Επιλέξτε" "Sensor Status" = "Κατάσταση Αισθητήρα" diff --git a/src/weewx_data/skins/Seasons/lang/it.conf b/src/weewx_data/skins/Seasons/lang/it.conf index 944d09c7..67d9b85b 100644 --- a/src/weewx_data/skins/Seasons/lang/it.conf +++ b/src/weewx_data/skins/Seasons/lang/it.conf @@ -197,6 +197,7 @@ unit_system = metricwx "Right ascension" = "Ascensione retta" "Rise" = "Sorge" "RMS Wind" = "RMS vento" + "Season" = "Stagione" "select month" = "Seleziona mese" "select year" = "Seleziona anno" "Sensor Status" = "Stato dei sensori" diff --git a/src/weewx_data/skins/Seasons/lang/nl.conf b/src/weewx_data/skins/Seasons/lang/nl.conf index 0222735a..6824ba9d 100644 --- a/src/weewx_data/skins/Seasons/lang/nl.conf +++ b/src/weewx_data/skins/Seasons/lang/nl.conf @@ -189,6 +189,7 @@ unit_system = metricwx "Right ascension" = "Rechte Klimming" "Rise" = "Opkomst" "RMS Wind" = "RMS Wind" + "Season" = "Seizoen" "select month" = "Selecteer Maand" "select year" = "Selecteer Jaar" "Sensor Status" = "Sensor Status" From b7b9573e285576c711ca5b2783d183856f42bb20 Mon Sep 17 00:00:00 2001 From: Tom Keffer Date: Wed, 10 Jun 2026 17:25:09 -0700 Subject: [PATCH 6/7] Reformat --- src/weewx/tags.py | 100 ++++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 44 deletions(-) diff --git a/src/weewx/tags.py b/src/weewx/tags.py index 551e3b51..6fba35ab 100644 --- a/src/weewx/tags.py +++ b/src/weewx/tags.py @@ -87,7 +87,8 @@ class TimeBinder: def week(self, data_binding=None, weeks_ago=0): week_start = to_int(self.option_dict.get('week_start', 6)) return TimespanBinder( - weeutil.weeutil.archiveWeekSpan(self.report_time, startOfWeek=week_start, weeks_ago=weeks_ago), + weeutil.weeutil.archiveWeekSpan(self.report_time, startOfWeek=week_start, + weeks_ago=weeks_ago), self.db_lookup, data_binding=data_binding, context='week', formatter=self.formatter, converter=self.converter, **self.option_dict) @@ -99,38 +100,41 @@ class TimeBinder: context='month', formatter=self.formatter, converter=self.converter, **self.option_dict) - def season(self, data_binding=None, seasons_ago=0, years_ago=0, lat=None, lon=None, season_type='m'): - years_ago, seasons_ago = seasons_ago//4+years_ago, seasons_ago%4 - if season_type[0].lower()=='m': + def season(self, data_binding=None, seasons_ago=0, years_ago=0, lat=None, lon=None, + season_type='m'): + years_ago, seasons_ago = seasons_ago // 4 + years_ago, seasons_ago % 4 + if season_type[0].lower() == 'm': # meteorological season time_dt = time.localtime(self.report_time) start_year = time_dt.tm_year - years_ago - start_month = int(time_dt.tm_mon//3-seasons_ago)*3 - while start_month<=0: + start_month = int(time_dt.tm_mon // 3 - seasons_ago) * 3 + while start_month <= 0: start_month += 12 start_year -= 1 end_year = start_year end_month = start_month + 3 - if end_month>12: + if end_month > 12: end_month -= 12 end_year += 1 start_of_season = int(time.mktime((start_year, start_month, 1, 0, 0, 0, 0, 0, -1))) end_of_season = int(time.mktime((end_year, end_month, 1, 0, 0, 0, 0, 0, -1))) - elif season_type[0].lower()=='a': + elif season_type[0].lower() == 'a': # astronomical season - if lat is None: lat = self.option_dict.get('lat',0.0) - if lon is None: lon = self.option_dict.get('lon',0.0) + if lat is None: lat = self.option_dict.get('lat', 0.0) + if lon is None: lon = self.option_dict.get('lon', 0.0) time_ts = self.report_time if years_ago: time_td = time.localtime(time_ts) - time_ts = time.mktime((time_td.tm_year-years_ago, time_td.tm_mon, time_td.tm_mday, time_td.tm_hour, time_td.tm_min, time_td.tm_sec, 0, 0, time_td.tm_isdst)) - for i in range(seasons_ago,-1,-1): + time_ts = time.mktime( + (time_td.tm_year - years_ago, time_td.tm_mon, time_td.tm_mday, time_td.tm_hour, + time_td.tm_min, time_td.tm_sec, 0, 0, time_td.tm_isdst)) + for i in range(seasons_ago, -1, -1): alm = weewx.almanac.Almanac(time_ts, lat, lon) equinox = alm.previous_equinox.unix_epoch.raw solstice = alm.previous_solstice.unix_epoch.raw if not i: break - time_ts = (equinox if equinox>solstice else solstice)-90000 - if equinox>solstice: + time_ts = (equinox if equinox > solstice else solstice) - 90000 + if equinox > solstice: # spring or autumn start_of_season = weeutil.weeutil.startOfDay(equinox) end_of_season = weeutil.weeutil.startOfDay(alm.next_solstice.unix_epoch.raw) @@ -141,12 +145,12 @@ class TimeBinder: else: raise ValueError("season type can be 'meteorological' or 'astronomical'") return TimespanBinder( - weeutil.weeutil.TimeSpan(start_of_season,end_of_season), + weeutil.weeutil.TimeSpan(start_of_season, end_of_season), self.db_lookup, data_binding=data_binding, context='season', formatter=self.formatter, converter=self.converter, season_type=season_type, **self.option_dict) - + def year(self, data_binding=None, years_ago=0): return TimespanBinder( weeutil.weeutil.archiveYearSpan(self.report_time, years_ago=years_ago), @@ -175,32 +179,38 @@ class TimeBinder: def seasonsyear(self, data_binding=None, years_ago=0, lat=None, lon=None, season_type='m'): """ meteorological or astronomical seasons year """ - if lat is None: lat = self.option_dict.get('lat') - if lon is None: lon = self.option_dict.get('lon') - if season_type[0].lower()=='m': + if lat is None: lat = self.option_dict.get('lat', 0.0) + if lon is None: lon = self.option_dict.get('lon', 0.0) + if season_type[0].lower() == 'm': # meteorological seasons - seasons_start_month = 9 if lat<0.0 else 3 + seasons_start_month = 9 if lat < 0.0 else 3 time_dt = time.localtime(self.report_time) - year = (time_dt.tm_year if time_dt.tm_mon>=seasons_start_month else time_dt.tm_year-1) - years_ago + year = (time_dt.tm_year if time_dt.tm_mon >= seasons_start_month else time_dt.tm_year - 1) - years_ago start_of_seasons_year = int(time.mktime((year, seasons_start_month, 1, 0, 0, 0, 0, 0, -1))) - end_of_seasons_year = int(time.mktime((year+1, seasons_start_month, 1, 0, 0, 0, 0, 0, -1))) - elif season_type[0].lower()=='a': + end_of_seasons_year = int(time.mktime((year + 1, seasons_start_month, 1, 0, 0, 0, 0, 0, -1))) + elif season_type[0].lower() == 'a': # astronomical seasons time_ts = self.report_time if years_ago: time_td = time.localtime(time_ts) - time_ts = time.mktime((time_td.tm_year-years_ago, time_td.tm_mon, time_td.tm_mday, time_td.tm_hour, time_td.tm_min, time_td.tm_sec, 0, 0, time_td.tm_isdst)) + time_ts = time.mktime( + (time_td.tm_year - years_ago, time_td.tm_mon, time_td.tm_mday, time_td.tm_hour, + time_td.tm_min, time_td.tm_sec, 0, 0, time_td.tm_isdst)) alm = weewx.almanac.Almanac(time_ts, lat, lon) - if lat<0.0: - start_of_seasons_year = weeutil.weeutil.startOfDay(alm.previous_autumnal_equinox.unix_epoch.raw) - end_of_seasons_year = weeutil.weeutil.startOfDay(alm.next_autumnal_equinox.unix_epoch.raw) + if lat < 0.0: + start_of_seasons_year = weeutil.weeutil.startOfDay( + alm.previous_autumnal_equinox.unix_epoch.raw) + end_of_seasons_year = weeutil.weeutil.startOfDay( + alm.next_autumnal_equinox.unix_epoch.raw) else: - start_of_seasons_year = weeutil.weeutil.startOfDay(alm.previous_vernal_equinox.unix_epoch.raw) - end_of_seasons_year = weeutil.weeutil.startOfDay(alm.next_vernal_equinox.unix_epoch.raw) + start_of_seasons_year = weeutil.weeutil.startOfDay( + alm.previous_vernal_equinox.unix_epoch.raw) + end_of_seasons_year = weeutil.weeutil.startOfDay( + alm.next_vernal_equinox.unix_epoch.raw) else: raise ValueError("season type can be 'meteorological' or 'astronomical'") return TimespanBinder( - weeutil.weeutil.TimeSpan(start_of_seasons_year,end_of_seasons_year), + weeutil.weeutil.TimeSpan(start_of_seasons_year, end_of_seasons_year), self.db_lookup, data_binding=data_binding, context='seasonsyear', formatter=self.formatter, converter=self.converter, season_type=season_type, @@ -322,30 +332,31 @@ class TimespanBinder: # Iterate over seasons in the time period: def seasons(self): - season_type = self.option_dict.get('season_type','m')[0].lower() - if season_type=='m': + season_type = self.option_dict.get('season_type', 'm')[0].lower() + if season_type == 'm': # meteorological season def genSeasonSpans(start, stop): - while start12: - end_year +=1 + end_month = (start_dt.tm_mon // 3 + 1) * 3 + if end_month > 12: + end_year += 1 end_month -= 12 end = int(time.mktime((end_year, end_month, 1, 0, 0, 0, 0, 0, -1))) yield weeutil.weeutil.TimeSpan(start, end) start = end - elif season_type=='a': + elif season_type == 'a': # astronomical season - lat = self.option_dict.get('lat',0.0) - lon = self.option_dict.get('lon',0.0) + lat = self.option_dict.get('lat', 0.0) + lon = self.option_dict.get('lon', 0.0) + def genSeasonSpans(start, stop): - while start Date: Wed, 10 Jun 2026 17:25:35 -0700 Subject: [PATCH 7/7] Reword PR #1095 change log --- docs_src/changes.md | 2 +- src/weewx/almanac.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs_src/changes.md b/docs_src/changes.md index 8ae2da0b..def36b63 100644 --- a/docs_src/changes.md +++ b/docs_src/changes.md @@ -12,7 +12,7 @@ Example `xstats.py` had nonsensical time context values. Fixed in [PR #1072](https://github.com/weewx/weewx/pull/1072). Thanks to user evilbunny2008. -Cheetahgenerator seasons timespans `$season`, `$seasonsyear`, seasons +Added Cheetah generator seasons timespans `$season`, `$seasonsyear`, and seasons iterator `.seasons`. [PR #1095](https://github.com/weewx/weewx/pull/1095). Thanks to user roe-dl. diff --git a/src/weewx/almanac.py b/src/weewx/almanac.py index 33399fdc..10676df7 100644 --- a/src/weewx/almanac.py +++ b/src/weewx/almanac.py @@ -215,7 +215,7 @@ class Almanac: Args: - time_ts (int): A unix epoch timestamp with the time of the almanac. If None, the + time_ts (int|float): A unix epoch timestamp with the time of the almanac. If None, the present time will be used. lat (float): Observer's latitude in degrees. lon (float): Observer's longitude in degrees.