mirror of
https://github.com/weewx/weewx.git
synced 2026-07-31 02:27:38 -04:00
Merge remote-tracking branch 'origin/development' into development
This commit is contained in:
@@ -16,6 +16,10 @@ Example `xstats.py` had nonsensical time context values. Fixed in
|
||||
[PR #1072](https://github.com/weewx/weewx/pull/1072). Thanks to user
|
||||
evilbunny2008.
|
||||
|
||||
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.
|
||||
|
||||
Prevent `AttributeError` when the `weectl` command is run without a subcommand.
|
||||
|
||||
Guard against an empty string being passed to the copy generator.
|
||||
|
||||
@@ -446,6 +446,12 @@ There are several _aggregation periods_ that can be used:
|
||||
<td class="code">$month.outTemp.min</td>
|
||||
<td>The minimum temperature this month.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="first_col code">$season</td>
|
||||
<td>This season</td>
|
||||
<td class="code">$season.outTemp.avg</td>
|
||||
<td>The average temparature this season so far</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="first_col code">$year</td>
|
||||
<td>This year (since 1-Jan).</td>
|
||||
@@ -460,6 +466,13 @@ There are several _aggregation periods_ that can be used:
|
||||
<td class="code">$rainyear.rain.sum</td>
|
||||
<td>The total rainfall for this rain year. </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="first_col code">$seasonsyear</td>
|
||||
<td>From the beginning of the last spring to the beginning of the
|
||||
next spring, observing hemisphere</td>
|
||||
<td class="code">$seasonsyear.outTemp.min</td>
|
||||
<td>The minimum temperature during that time</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="first_col code">$alltime</td>
|
||||
<td>
|
||||
@@ -509,15 +522,38 @@ Here are some examples:
|
||||
<td class="code">$month(months_ago=1).outTemp.max</td>
|
||||
<td>The maximum temperature last month.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="first_col code">$season(seasons_ago=<em>s</em>)</td>
|
||||
<td class="code">$season(seasons_ago=1).outTemp.min</td>
|
||||
<td>The minimum temperature of the previous season</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="first_col code">$season(years_ago=<em>y</em>)</td>
|
||||
<td class="code">$season(years_ago=1).outTemp.min</td>
|
||||
<td>The minimum temperature of the same season in the previous year</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="first_col code">$year(years_ago=<em>y</em>)
|
||||
</td>
|
||||
<td class="code">$year(years_ago=1).outTemp.max</td>
|
||||
<td>The maximum temperature last year.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="first_col code"$seasonsyear(years_ago=<em>y</em></td>
|
||||
<td class="code">$seasonsyear(years_ago=1).outTemp.max</td>
|
||||
<td>The maximum temperature of the previous seasons year</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
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:
|
||||
<td class="code first_col">.months</td>
|
||||
<td>Iterate by months</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="code first_col">.seasons</td>
|
||||
<td>Iterate by seasons. Depending on the parameter `season_type` of
|
||||
`$seasonsyear`, meteorological or astronomical seasons are returned.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="code first_col">.years</td>
|
||||
<td>Iterate by years</td>
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -600,6 +600,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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -85,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)
|
||||
@@ -97,6 +100,57 @@ 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':
|
||||
# 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 += 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)
|
||||
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)
|
||||
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 +177,45 @@ 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', 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
|
||||
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
|
||||
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)
|
||||
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(
|
||||
@@ -237,6 +330,42 @@ class TimespanBinder:
|
||||
'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 start < stop:
|
||||
start_dt = time.localtime(start)
|
||||
end_year = start_dt.tm_year
|
||||
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':
|
||||
# astronomical season
|
||||
lat = self.option_dict.get('lat', 0.0)
|
||||
lon = self.option_dict.get('lon', 0.0)
|
||||
|
||||
def genSeasonSpans(start, stop):
|
||||
while start < stop:
|
||||
alm = weewx.almanac.Almanac(start + 90000, lat, lon)
|
||||
equinox = weeutil.weeutil.startOfDay(alm.next_equinox.unix_epoch.raw)
|
||||
solstice = weeutil.weeutil.startOfDay(alm.next_solstice.unix_epoch.raw)
|
||||
end = equinox if equinox < solstice else solstice
|
||||
yield weeutil.weeutil.TimeSpan(start, end)
|
||||
start = end
|
||||
else:
|
||||
raise ValueError("season type can be 'meteorological' or 'astronomical'")
|
||||
return TimespanBinder._seqGenerator(genSeasonSpans, self.timespan,
|
||||
self.db_lookup, self.data_binding,
|
||||
'season', self.formatter, self.converter,
|
||||
**self.option_dict)
|
||||
|
||||
# Static method used to implement the iteration:
|
||||
@staticmethod
|
||||
def _seqGenerator(genSpanFunc, timespan, *args, **option_dict):
|
||||
@@ -259,7 +388,8 @@ class TimespanBinder:
|
||||
# Return the length of the timespan
|
||||
@property
|
||||
def length(self):
|
||||
val = weewx.units.ValueTuple(self.timespan.stop-self.timespan.start, 'second', 'group_deltatime')
|
||||
val = weewx.units.ValueTuple(self.timespan.stop - self.timespan.start, 'second',
|
||||
'group_deltatime')
|
||||
return weewx.units.ValueHelper(val, self.context, self.formatter, self.converter)
|
||||
|
||||
# Alias for the start time:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -189,6 +189,7 @@ unit_system = metricwx
|
||||
"Right ascension" = "Rektaszension"
|
||||
"Rise" = "Aufgang"
|
||||
"RMS Wind" = "Windstärke (Effektivwert)"
|
||||
"Season" = "Jahreszeit"
|
||||
"select month" = "Monat wählen"
|
||||
"select year" = "Jahr wählen"
|
||||
"Sensor Status" = "Sensorenstatus"
|
||||
|
||||
@@ -192,6 +192,7 @@ unit_system = us
|
||||
"Right ascension" = "Right ascension"
|
||||
"Rise" = "Rise"
|
||||
"RMS Wind" = "RMS Wind"
|
||||
"Season" = "Season"
|
||||
"select month" = "Select Month"
|
||||
"select year" = "Select Year"
|
||||
"Sensor Status" = "Sensor Status"
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -188,6 +188,7 @@ unit_system = metricwx
|
||||
"Right ascension" = "Δεξιά ανύψωση"
|
||||
"Rise" = "Aνατολή"
|
||||
"RMS Wind" = "RMS Ανέμου"
|
||||
"Season" = "Εποχή"
|
||||
"select month" = "Επιλέξτε"
|
||||
"select year" = "Επιλέξτε"
|
||||
"Sensor Status" = "Κατάσταση Αισθητήρα"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
#errorCatcher Echo
|
||||
#encoding UTF-8
|
||||
|
||||
#set $time_tags = [$day, $week, $month, $year, $rainyear]
|
||||
#set $season_type = $DisplayOptions.get('season_type','meteorological')
|
||||
#set $time_tags = [$day, $week, $month, $season(season_type=$season_type), $year, $rainyear]
|
||||
|
||||
## Get the list of observations from the configuration file, otherwise fallback
|
||||
## to a very rudimentary set of observations.
|
||||
@@ -29,6 +30,7 @@
|
||||
<th>$gettext("Today")</th>
|
||||
<th class="hilo_week">$gettext("Week")</th>
|
||||
<th class="hilo_month">$gettext("Month")</th>
|
||||
<th class="hilo_season">$gettext("Season")</th>
|
||||
<th class="hilo_year">$gettext("Year")</th>
|
||||
<th class="hilo_rainyear">$gettext("Rain Year")</th>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user