Example #1
0
    def _check_eligibility(obs_type, timespan, db_manager, aggregate_type):

        # It has to be a type we know about
        if not hasattr(db_manager, 'daykeys') or obs_type not in db_manager.daykeys:
            raise weewx.UnknownType(obs_type)

        # We cannot use the day summaries if the starting and ending times of the aggregation
        # interval are not on midnight boundaries, and are not the first or last records in the
        # database.
        if db_manager.first_timestamp is None or db_manager.last_timestamp is None:
            raise weewx.UnknownAggregation(aggregate_type)
        if not (isStartOfDay(timespan.start) or timespan.start == db_manager.first_timestamp) \
                or not (isStartOfDay(timespan.stop) or timespan.stop == db_manager.last_timestamp):
            raise weewx.UnknownAggregation(aggregate_type)
Example #2
0
    def get_aggregate(obs_type, timespan, aggregate_type, db_manager, **option_dict):
        """Returns an aggregation of an observation type over a given time period, using the
        main archive table.
    
        obs_type: The type over which aggregation is to be done (e.g., 'barometer',
        'outTemp', 'rain', ...)
    
        timespan: An instance of weeutil.Timespan with the time period over which
        aggregation is to be done.
    
        aggregate_type: The type of aggregation to be done.
    
        db_manager: An instance of weewx.manager.Manager or subclass.
    
        option_dict: Not used in this version.
    
        returns: A ValueTuple containing the result."""

        if aggregate_type not in ['sum', 'count', 'avg', 'max', 'min'] \
                + list(ArchiveTable.agg_sql_dict.keys()):
            raise weewx.UnknownAggregation(aggregate_type)

        interpolate_dict = {
            'aggregate_type': aggregate_type,
            'obs_type': obs_type,
            'table_name': db_manager.table_name,
            'start': timespan.start,
            'stop': timespan.stop
        }

        select_stmt = ArchiveTable.agg_sql_dict.get(aggregate_type,
                                                    ArchiveTable.simple_agg_sql) % interpolate_dict

        try:
            row = db_manager.getSql(select_stmt)
        except weedb.NoColumnError:
            raise weewx.UnknownType(aggregate_type)

        value = row[0] if row else None

        # Look up the unit type and group of this combination of observation type and aggregation:
        u, g = weewx.units.getStandardUnitType(db_manager.std_unit_system, obs_type,
                                               aggregate_type)

        # Time derivatives have special rules. For example, the time derivative of watt-hours is
        # watts, scaled by the number of seconds in an hour. The unit group also changes to
        # group_power.
        if aggregate_type == 'tderiv':
            if u == 'watt_second':
                u = 'watt'
            elif u == 'watt_hour':
                u = 'watt'
                value *= 3600
            elif u == 'kilowatt_hour':
                u = 'kilowatt'
                value *= 3600
            g = 'group_power'

        # Form the ValueTuple and return it:
        return weewx.units.ValueTuple(value, u, g)
Example #3
0
    def get_aggregate(obs_type, timespan, aggregate_type, db_manager, **option_dict):
        """Returns an aggregation of pm2_5_aqi over a timespan by using the main archive
        table.

        obs_type

        timespan: An instance of weeutil.Timespan with the time period over which aggregation is to
        be done.

        aggregate_type: The type of aggregation to be done. For this function, must be 'avg',
        'sum', 'count', 'first', 'last', 'min', or 'max'. Anything else will cause
        weewx.UnknownAggregation to be raised.

        db_manager: An instance of weewx.manager.Manager or subclass.

        option_dict: Not used in this version.

        returns: A ValueTuple containing the result.
        """
        if obs_type not in [ 'pm2_5_aqi', 'pm2_5_aqi_color' ]:
            raise weewx.UnknownType(obs_type)

        log.debug('get_aggregate(%s, %s, %s, aggregate:%s)' % (
            obs_type, timestamp_to_string(timespan.start),
            timestamp_to_string(timespan.stop), aggregate_type))

        aggregate_type = aggregate_type.lower()

        # Raise exception if we don't know about this type of aggregation
        if aggregate_type not in list(AQI.agg_sql_dict.keys()):
            raise weewx.UnknownAggregation(aggregate_type)

        # Form the interpolation dictionary
        interpolation_dict = {
            'start': timespan.start,
            'stop': timespan.stop,
            'table_name': db_manager.table_name
        }

        select_stmt = AQI.agg_sql_dict[aggregate_type] % interpolation_dict
        row = db_manager.getSql(select_stmt)
        if row:
            value, std_unit_system = row
        else:
            value = None
            std_unit_system = None

        if value is not None:
            if obs_type == 'pm2_5_aqi':
                value = AQI.compute_pm2_5_aqi(value)
            if obs_type == 'pm2_5_aqi_color':
                value = AQI.compute_pm2_5_aqi_color(AQI.compute_pm2_5_aqi(value))
        t, g = weewx.units.getStandardUnitType(std_unit_system, obs_type, aggregate_type)
        # Form the ValueTuple and return it:
        log.debug('get_aggregate(%s, %s, %s, aggregate:%s, select_stmt: %s, returning %s)' % (
            obs_type, timestamp_to_string(timespan.start), timestamp_to_string(timespan.stop),
            aggregate_type, select_stmt, value))
        return weewx.units.ValueTuple(value, t, g)
Example #4
0
    def get_aggregate(obs_type, timespan, aggregate_type, db_manager,
                      **option_dict):
        """Optimization for calculating 'avg' aggregations for type 'windvec'. The
        timespan must be on a daily boundary."""

        # We can only do observation type 'windvec'
        if obs_type != 'windvec':
            # We can't handle it.
            raise weewx.UnknownType(obs_type)

        # We can only do 'avg' or 'not_null
        if aggregate_type not in ['avg', 'not_null']:
            raise weewx.UnknownAggregation(aggregate_type)

        # We cannot use the day summaries if the starting and ending times of the aggregation
        # interval are not on midnight boundaries, and are not the first or last records in the
        # database.
        if not (isStartOfDay(timespan.start) or timespan.start == db_manager.first_timestamp) \
                or not (isStartOfDay(timespan.stop) or timespan.stop == db_manager.last_timestamp):
            raise weewx.UnknownAggregation(aggregate_type)

        if aggregate_type == 'not_null':
            # Aggregate type 'not_null' is actually run against 'wind'.
            return DailySummaries.get_aggregate('wind', timespan, 'not_null',
                                                db_manager, **option_dict)

        sql = 'SELECT SUM(xsum), SUM(ysum), SUM(dirsumtime) ' \
              'FROM %s_day_wind WHERE dateTime>=? AND dateTime<?;' % db_manager.table_name

        row = db_manager.getSql(sql, timespan)

        if not row or None in row or not row[2]:
            # If no row was returned, or if it contains any nulls (meaning that not
            # all required data was available to calculate the requested aggregate),
            # then set the resulting value to None.
            value = None
        else:
            value = complex(row[0], row[1]) / row[2]

        # Look up the unit type and group of the result:
        t, g = weewx.units.getStandardUnitType(db_manager.std_unit_system,
                                               obs_type, aggregate_type)
        # Return as a value tuple
        return weewx.units.ValueTuple(value, t, g)
Example #5
0
def get_aggregate(obs_type, timespan, aggregate_type, db_manager, **option_dict):
    """Calculate an aggregation over a timespan"""
    # Search the list, looking for a get_aggregate() method that does not raise an exception
    for xtype in xtypes:
        try:
            # Try this function. It will raise an exception if it doesn't know about the type of aggregation.
            return xtype.get_aggregate(obs_type, timespan, aggregate_type, db_manager, **option_dict)
        except (weewx.UnknownAggregation, weewx.UnknownType):
            pass
    raise weewx.UnknownAggregation("%s('%s')" % (aggregate_type, obs_type))
Example #6
0
    def get_series(obs_type,
                   timespan,
                   db_manager,
                   aggregate_type=None,
                   aggregate_interval=None,
                   **option_dict):
        """Get a series of an xtype, by using the main archive table. Works only for no
        aggregation. """

        start_vec = list()
        stop_vec = list()
        data_vec = list()

        if aggregate_type:
            # This version does not know how to do aggregations, although this could be
            # added in the future.
            raise weewx.UnknownAggregation(aggregate_type)

        else:
            # No aggregation

            std_unit_system = None

            # Hit the database.
            for record in db_manager.genBatchRecords(*timespan):

                if std_unit_system:
                    if std_unit_system != record['usUnits']:
                        raise weewx.UnsupportedFeature(
                            "Unit system cannot change "
                            "within a series.")
                else:
                    std_unit_system = record['usUnits']

                # Given a record, use the xtypes system to calculate a value:
                try:
                    value = get_scalar(obs_type, record, db_manager)
                    data_vec.append(value[0])
                except weewx.CannotCalculate:
                    data_vec.append(None)
                start_vec.append(record['dateTime'] - record['interval'] * 60)
                stop_vec.append(record['dateTime'])

            unit, unit_group = weewx.units.getStandardUnitType(
                std_unit_system, obs_type)

        return (ValueTuple(start_vec, 'unix_epoch', 'group_time'),
                ValueTuple(stop_vec, 'unix_epoch',
                           'group_time'), ValueTuple(data_vec, unit,
                                                     unit_group))
Example #7
0
 def get_series(self,
                obs_type,
                timespan,
                db_manager,
                aggregate_type=None,
                aggregate_interval=None):
     key_agg_tuple = obs_type.split('_') + [NULL]
     (key, agg) = key_agg_tuple[:2]
     (time_stamp_start_vt, time_stamp_stop_vt, gdd_vt,
      gdd_cumulative_vt) = self.get_gdd_series(key, timespan, db_manager)
     if aggregate_type is None:
         is_cumulative = agg.lower() in ['accum']
         if is_cumulative:
             result = [
                 time_stamp_start_vt, time_stamp_stop_vt, gdd_cumulative_vt
             ]
         else:
             result = [time_stamp_start_vt, time_stamp_stop_vt, gdd_vt]
     else:
         raise weewx.UnknownAggregation(aggregate_type)
     return result
Example #8
0
    def get_aggregate(obs_type, timespan, aggregate_type, db_manager, **option_dict):
        """Returns an aggregation of a wind vector type over a timespan by using the main archive table.

        obs_type: The type over which aggregation is to be done. For this function,
        it must be 'windvec' or 'windgustvec'. Anything else will cause weewx.UnknownType
        to be raised.

        timespan: An instance of weeutil.Timespan with the time period over which
        aggregation is to be done.

        aggregate_type: The type of aggregation to be done. For this function, must be
        'avg', 'sum', 'count', 'first', 'last', 'min', or 'max'. Anything else will cause
        weewx.UnknownAggregation to be raised.

        db_manager: An instance of weewx.manager.Manager or subclass.

        option_dict: Not used in this version.

        returns: A ValueTuple containing the result. Note that the value contained in the ValueTuple
        will be a complex number.
        """
        if obs_type not in WindVec.windvec_types:
            raise weewx.UnknownType(obs_type)

        aggregate_type = aggregate_type.lower()

        # Raise exception if we don't know about this type of aggregation
        if aggregate_type not in ['avg', 'sum'] + list(WindVec.agg_sql_dict.keys()):
            raise weewx.UnknownAggregation(aggregate_type)

        # Form the interpolation dictionary
        interpolation_dict = {
            'dir': WindVec.windvec_types[obs_type][1],
            'mag': WindVec.windvec_types[obs_type][0],
            'start': weeutil.weeutil.startOfDay(timespan.start),
            'stop': timespan.stop,
            'table_name': db_manager.table_name
        }

        if aggregate_type in WindVec.agg_sql_dict:
            # For these types (e.g., first, last, etc.), we can do the aggregation in a SELECT statement.
            select_stmt = WindVec.agg_sql_dict[aggregate_type] % interpolation_dict
            row = db_manager.getSql(select_stmt)
            if row:
                if aggregate_type == 'count':
                    value, std_unit_system = row
                else:
                    magnitude, direction, std_unit_system = row
                    value = weeutil.weeutil.to_complex(magnitude, direction)
            else:
                std_unit_system = db_manager.std_unit_system
                value = None
        else:
            # The result is more complex, requiring vector arithmetic. We will have to do it
            # in Python
            std_unit_system = None
            xsum = ysum = 0.0
            count = 0
            select_stmt = WindVec.complex_sql_wind % interpolation_dict

            for rec in db_manager.genSql(select_stmt, timespan):

                # Unpack the record
                mag, direction, unit_system = rec

                # Ignore rows where magnitude is NULL
                if mag is None:
                    continue

                # A good direction is necessary unless the mag is zero:
                if mag == 0.0 or direction is not None:
                    if std_unit_system:
                        if std_unit_system != unit_system:
                            raise weewx.UnsupportedFeature("Unit type cannot change within a time interval.")
                    else:
                        std_unit_system = unit_system

                    # An undefined direction is OK (and expected) if the magnitude
                    # is zero. But, in that case, it doesn't contribute to the sums either.
                    if direction is None:
                        # Sanity check
                        if weewx.debug:
                            assert (mag == 0.0)
                    else:
                        xsum += mag * math.cos(math.radians(90.0 - direction))
                        ysum += mag * math.sin(math.radians(90.0 - direction))
                    count += 1

            # We've gone through the whole interval. Were there any good data?
            if count:
                # Form the requested aggregation:
                if aggregate_type == 'sum':
                    value = complex(xsum, ysum)
                else:
                    # Must be 'avg'
                    value = complex(xsum, ysum) / count
            else:
                value = None

        # Look up the unit type and group of this combination of observation type and aggregation:
        t, g = weewx.units.getStandardUnitType(std_unit_system, obs_type, aggregate_type)
        # Form the ValueTuple and return it:
        return weewx.units.ValueTuple(value, t, g)
Example #9
0
    def get_aggregate(obs_type, timespan, aggregate_type, db_manager, **option_dict):
        """Returns heating and cooling degree days over a time period.

        obs_type: The type over which aggregation is to be done.  Must be one of 'heatdeg', 'cooldeg', or 'growdeg'.

        timespan: An instance of weeutil.Timespan with the time period over which
        aggregation is to be done.

        aggregate_type: The type of aggregation to be done. Must be 'avg' or 'sum'.

        db_manager: An instance of weewx.manager.Manager or subclass.

        option_dict: Not used in this version.

        returns: A ValueTuple containing the result.
        """

        # Check to see whether heating or cooling degree days are being asked for:
        if obs_type not in ['heatdeg', 'cooldeg', 'growdeg']:
            raise weewx.UnknownType(obs_type)

        # Only summation (total) or average heating or cooling degree days is supported:
        if aggregate_type not in ['sum', 'avg']:
            raise weewx.UnknownAggregation(aggregate_type)

        # Get the base for heating and cooling degree-days
        units_dict = option_dict.get('skin_dict', {}).get('Units', {})
        dd_dict = units_dict.get('DegreeDays', {})
        heatbase = dd_dict.get('heating_base', AggregateHeatCool.default_heatbase)
        coolbase = dd_dict.get('cooling_base', AggregateHeatCool.default_coolbase)
        growbase = dd_dict.get('growing_base', AggregateHeatCool.default_growbase)
        # Convert to a ValueTuple in the same unit system as the database
        heatbase_t = weewx.units.convertStd((float(heatbase[0]), heatbase[1], "group_temperature"),
                                            db_manager.std_unit_system)
        coolbase_t = weewx.units.convertStd((float(coolbase[0]), coolbase[1], "group_temperature"),
                                            db_manager.std_unit_system)
        growbase_t = weewx.units.convertStd((float(growbase[0]), growbase[1], "group_temperature"),
                                            db_manager.std_unit_system)

        total = 0.0
        count = 0
        for daySpan in weeutil.weeutil.genDaySpans(timespan.start, timespan.stop):
            # Get the average temperature for the day as a value tuple:
            Tavg_t = DailySummaries.get_aggregate('outTemp', daySpan, 'avg', db_manager)
            # Make sure it's valid before including it in the aggregation:
            if Tavg_t is not None and Tavg_t[0] is not None:
                if obs_type == 'heatdeg':
                    total += weewx.wxformulas.heating_degrees(Tavg_t[0], heatbase_t[0])
                elif obs_type == 'cooldeg':
                    total += weewx.wxformulas.cooling_degrees(Tavg_t[0], coolbase_t[0])
                else:
                    total += weewx.wxformulas.cooling_degrees(Tavg_t[0], growbase_t[0])

                count += 1

        if aggregate_type == 'sum':
            value = total
        else:
            value = total / count if count else None

        # Look up the unit type and group of the result:
        t, g = weewx.units.getStandardUnitType(db_manager.std_unit_system, obs_type, aggregate_type)
        # Return as a value tuple
        return weewx.units.ValueTuple(value, t, g)
Example #10
0
    def get_aggregate(obs_type, timespan, aggregate_type, db_manager, **option_dict):
        """Returns an aggregation of a statistical type for a given time period,
        by using the daily summaries.
    
        obs_type: The type over which aggregation is to be done (e.g., 'barometer',
        'outTemp', 'rain', ...)
    
        timespan: An instance of weeutil.Timespan with the time period over which
        aggregation is to be done.
    
        aggregate_type: The type of aggregation to be done.
    
        db_manager: An instance of weewx.manager.Manager or subclass.
    
        option_dict: Not used in this version.
    
        returns: A ValueTuple containing the result."""

        # Check to see if this is a valid daily summary type:
        if not hasattr(db_manager, 'daykeys') or obs_type not in db_manager.daykeys:
            raise weewx.UnknownType(obs_type)

        aggregate_type = aggregate_type.lower()

        # Raise exception if we don't know about this type of aggregation
        if aggregate_type not in DailySummaries.daily_sql_dict:
            raise weewx.UnknownAggregation(aggregate_type)

        # We cannot use the day summaries if the starting and ending times of the aggregation interval are not on
        # midnight boundaries, and are not the first or last records in the database.
        if not (isStartOfDay(timespan.start) or timespan.start == db_manager.first_timestamp) \
                or not (isStartOfDay(timespan.stop) or timespan.stop == db_manager.last_timestamp):
            raise weewx.UnknownAggregation(aggregate_type)

        val = option_dict.get('val')
        if val is None:
            target_val = None
        else:
            # The following is for backwards compatibility when ValueTuples had
            # just two members. This hack avoids breaking old skins.
            if len(val) == 2:
                if val[1] in ['degree_F', 'degree_C']:
                    val += ("group_temperature",)
                elif val[1] in ['inch', 'mm', 'cm']:
                    val += ("group_rain",)
            target_val = weewx.units.convertStd(val, db_manager.std_unit_system)[0]

        # Form the interpolation dictionary
        interDict = {
            'start': weeutil.weeutil.startOfDay(timespan.start),
            'stop': timespan.stop,
            'obs_key': obs_type,
            'aggregate_type': aggregate_type,
            'val': target_val,
            'table_name': db_manager.table_name
        }

        # Run the query against the database:
        row = db_manager.getSql(DailySummaries.daily_sql_dict[aggregate_type] % interDict)

        # Each aggregation type requires a slightly different calculation.
        if not row or None in row:
            # If no row was returned, or if it contains any nulls (meaning that not
            # all required data was available to calculate the requested aggregate),
            # then set the resulting value to None.
            value = None

        elif aggregate_type in ['min', 'maxmin', 'max', 'minmax', 'meanmin', 'meanmax',
                                'maxsum', 'minsum', 'sum', 'gustdir']:
            # These aggregates are passed through 'as is'.
            value = row[0]

        elif aggregate_type in ['mintime', 'maxmintime', 'maxtime', 'minmaxtime', 'maxsumtime',
                                'minsumtime', 'count', 'max_ge', 'max_le', 'min_ge', 'min_le',
                                'sum_ge', 'sum_le']:
            # These aggregates are always integers:
            value = int(row[0])

        elif aggregate_type == 'avg':
            value = row[0] / row[1] if row[1] else None

        elif aggregate_type == 'rms':
            value = math.sqrt(row[0] / row[1]) if row[1] else None

        elif aggregate_type == 'vecavg':
            value = math.sqrt((row[0] ** 2 + row[1] ** 2) / row[2] ** 2) if row[2] else None

        elif aggregate_type == 'vecdir':
            if row == (0.0, 0.0):
                value = None
            deg = 90.0 - math.degrees(math.atan2(row[1], row[0]))
            value = deg if deg >= 0 else deg + 360.0
        else:
            # Unknown aggregation. Should not have gotten this far...
            raise ValueError("Unexpected error. Aggregate type '%s'" % aggregate_type)

        # Look up the unit type and group of this combination of observation type and aggregation:
        t, g = weewx.units.getStandardUnitType(db_manager.std_unit_system, obs_type, aggregate_type)
        # Form the ValueTuple and return it:
        return weewx.units.ValueTuple(value, t, g)
Example #11
0
    def get_series(obs_type, timespan, db_manager, aggregate_type=None, aggregate_interval=None,
                   **option_dict):

        # We cannot use the daily summaries if there is no aggregation
        if not aggregate_type:
            raise weewx.UnknownAggregation(aggregate_type)

        aggregate_type = aggregate_type.lower()

        # Raise exception if we don't know about this type of aggregation
        if aggregate_type not in DailySummaries.common:
            raise weewx.UnknownAggregation(aggregate_type)

        # Check to see whether we can use the daily summaries:
        DailySummaries._check_eligibility(obs_type, timespan, db_manager, aggregate_type)

        # We also have to make sure the aggregation interval is either the length of a nominal
        # month or year, or some multiple of a calendar day.
        aggregate_interval = weeutil.weeutil.nominal_spans(aggregate_interval)
        if aggregate_interval != weeutil.weeutil.nominal_intervals['year'] \
                and aggregate_interval != weeutil.weeutil.nominal_intervals['month'] \
                and aggregate_interval % 86400:
            raise weewx.UnknownAggregation(aggregate_interval)

        # We're good. Proceed.
        dbtype = db_manager.connection.dbtype
        interp_dict = {
            'agg_days': aggregate_interval / 86400,
            'day_table': "%s_day_%s" % (db_manager.table_name, obs_type),
            'obs_type': obs_type,
            'sod': weeutil.weeutil.startOfDay(timespan.start),
            'start': timespan.start,
            'stop': timespan.stop,
        }
        if aggregate_interval == weeutil.weeutil.nominal_intervals['year']:
            group_by_group = 'year'
        elif aggregate_interval == weeutil.weeutil.nominal_intervals['month']:
            group_by_group = 'month'
        else:
            group_by_group = 'day'
        # Add the database-specific GROUP_BY clause to the interpolation dictionary
        interp_dict['group_def'] = DailySummaries.group_defs[dbtype][group_by_group] % interp_dict
        # This is the final SELECT statement.
        sql_stmt = DailySummaries.common[aggregate_type] % interp_dict

        start_list = list()
        stop_list = list()
        data_list = list()

        for row in db_manager.genSql(sql_stmt):
            # Find the start of this aggregation interval. That's easy: it's the minimum value.
            start_time = row[0]
            # The stop is a little trickier. It's the maximum dateTime in the interval, plus one
            # day. The extra day is needed because the timestamp marks the beginning of a day in a
            # daily summary.
            stop_date = datetime.date.fromtimestamp(row[1]) + datetime.timedelta(days=1)
            stop_time = int(time.mktime(stop_date.timetuple()))

            if aggregate_type in {'min', 'max', 'sum', 'count'}:
                data = row[2]
            elif aggregate_type == 'avg':
                data = row[2] / row[3] if row[3] else None
            else:
                # Shouldn't really have made it here. Fail hard
                raise ValueError("Unknown aggregation type %s" % aggregate_type)

            start_list.append(start_time)
            stop_list.append(stop_time)
            data_list.append(data)

        # Look up the unit type and group of this combination of observation type and aggregation:
        unit, unit_group = weewx.units.getStandardUnitType(db_manager.std_unit_system, obs_type,
                                                           aggregate_type)
        return (ValueTuple(start_list, 'unix_epoch', 'group_time'),
                ValueTuple(stop_list, 'unix_epoch', 'group_time'),
                ValueTuple(data_list, unit, unit_group))
Example #12
0
    def get_aggregate(self, obs_type, timespan, aggregate_type, db_manager,
                      **option_dict):

        if obs_type is None:
            raise weewx.UnknownType("obs_type is None")

        # time offset of local mean time (LMT)
        if obs_type == 'utcoffsetLMT':
            return weewx.units.ValueTuple(
                self.lmt_tz.utcoffset(None).total_seconds(), 'second',
                'group_deltatime')

        # energy_integral can be calculated for group_radiation observation
        # types like 'radiation' and 'maxSolarRad'
        if aggregate_type == 'energy_integral':
            if obs_type in weewx.units.obs_group_dict and weewx.units.obs_group_dict[
                    obs_type] == 'group_radiation':
                return self.calc_radiation_integral(obs_type, timespan,
                                                    db_manager)

        # growing degree days == Wachstumsgradtage
        # https://de.wikipedia.org/wiki/Wachstumsgradtag
        # https://en.wikipedia.org/wiki/Growing_degree-day
        if aggregate_type in ['growdeg', 'GDD']:
            # growing degree day can only be calculated for a temperature
            if weewx.units.obs_group_dict.get(obs_type,
                                              '') != 'group_temperature':
                raise weewx.CannotCalculate(
                    "%s is not temperature for aggregation %s" %
                    (obs_type, aggregate_type))
            # if the base value is defined in skin.conf or weewx.conf, get
            # it for default
            units_dict = option_dict.get('skin_dict', {}).get('Units', {})
            dd_dict = units_dict.get('DegreeDays', {})
            base_vt = dd_dict.get(
                'growing_base',
                weewx.xtypes.AggregateHeatCool.default_growbase)
            # if parameters are specified get them
            val = option_dict.get('val')
            #loginf("%s" % type(val))
            #loginf(val)
            if val:
                # GDD with parameters
                try:
                    # dict
                    method = val.get('method', 'integral')
                    base_vt = val.get('base', base_vt)
                    limit_vt = val.get('limit', self.GDD_LIMIT_VT)
                    stop_vt = val.get('stop')
                except TypeError:
                    # tuple used as base temperature
                    base_vt = weewx.units.ValueTuple(float(val[0]), val[1],
                                                     'group_temperature')
                    limit_vt = None
                    stop_vt = None
                    method = 'integral'
            else:
                # GDD alone: use defaults
                method = 'integral'
                limit_vt = self.GDD_LIMIT_VT
                stop_vt = None
            # Convert to a ValueTuple in the same unit system as the database
            __base = weewx.units.convertStd(
                (float(base_vt[0]), base_vt[1], 'group_temperature'),
                db_manager.std_unit_system)[0]
            if limit_vt:
                try:
                    __limit = weewx.units.convertStd(
                        (float(limit_vt[0]), limit_vt[1], 'group_temperature'),
                        db_manager.std_unit_system)[0]
                except IndexError:
                    if limit_vt.lower() == 'none': __limit = None
            else:
                __limit = None
            if stop_vt:
                try:
                    __stop = weewx.units.convertStd(
                        (float(stop_vt[0]), stop_vt[1], 'group_temperature'),
                        db_manager.std_unit_system)[0]
                except IndexError:
                    if stop_vt.lower() == 'none': __stop = None
            else:
                __stop = None
            #loginf("method %s" % method)
            #loginf(base_vt)
            #loginf(limit_vt)
            # calculate GDD sum
            if method == 'integral':
                # integral over timespan
                return self.calc_GDD_integral(obs_type, timespan, db_manager,
                                              __base, __limit, __stop)
            if method in ['hiloavgA', 'hiloavgB', 'dayavg']:
                # based on daily average or average of high and low.
                # Check if day border should be based on Local Mean Time
                # or local timezone time
                __lmt_tz = option_dict.get('LMT', {}).get('timezone')
                if __lmt_tz is None:
                    __lmt_tz = option_dict.get('dayboundary',
                                               {}).get('timezone')
                return self.calc_GDD_avg(obs_type, timespan, db_manager,
                                         method, __base, __limit, __stop,
                                         __lmt_tz)
            if method == 'weewx' and obs_type == 'outTemp':
                # call builtin method of WeeWX for outTemp
                return weewx.xtypes.get_aggregate('growdeg', timespan, 'sum',
                                                  db_manager, **option_dict)
            raise weewx.CannotCalculate("GDD %s: unknown method" % method)

        # accumulated growing degree days
        if obs_type == 'yearGDD' or obs_type == 'seasonGDD':
            #loginf("GDD %s" % option_dict)
            #loginf("GDD %s" % aggregate_type)
            if aggregate_type.lower() == 'avg':
                if timespan.start > time.time() or (
                        timespan.start +
                        timespan.stop) / 2 > time.time() + 90000:
                    return weewx.units.ValueTuple(None, 'degree_C_day',
                                                  'group_degree_days')
                return self.get_scalar(
                    obs_type,
                    {'dateTime': (timespan.start + timespan.stop) / 2},
                    db_manager, **option_dict)
            if aggregate_type.lower() == 'last':
                return self.get_scalar(obs_type, {'dateTime': timespan.stop},
                                       db_manager, **option_dict)
            raise weewx.UnknownAggregation("%s undefinded aggregation %s" %
                                           (obs_type, aggregation_type))

        # This function handles 'GTS' and 'GTSdate'.
        if obs_type != 'GTS' and obs_type != 'GTSdate':
            raise weewx.UnknownType(obs_type)

        # aggregation types that are defined for those values
        if aggregate_type not in [
                'avg', 'max', 'min', 'last', 'maxtime', 'mintime', 'lasttime'
        ]:
            raise weewx.UnknownAggregation("%s undefinded aggregation %s" %
                                           (obs_type, aggregation_type))

        if timespan is None:
            raise weewx.CannotCalculate("%s %s no timespan" %
                                        (obs_type, aggregate_type))
        if db_manager is None:
            if self.db_manager_ok:
                logerr("%s: no database reference" % obs_type)
                self.db_manager_ok = False
            raise weewx.CannotCalculate("%s: no database reference" % obs_type)

        # needed timestamps
        _soya_ts = startOfYearTZ(timespan.start + 1, self.lmt_tz)
        _soye_ts = startOfYearTZ(timespan.stop, self.lmt_tz)

        # calculate GTS values for the years included in timespan
        # (if time span is within the current year, the
        # value is calculated up to the current day (today))
        __max = 0
        __maxtime = None
        __min = 10000000
        __mintime = None
        __ts = _soya_ts
        # Even if the time span starts after May 31st, the end value
        # is needed for some aggregations. So we have to calculate
        # that year, too.
        while __ts <= _soye_ts:
            # calculate GTS values for the year
            self.calc_gts(__ts, db_manager)
            # update minimum and maximum
            if __ts in self.gts_values:
                for __i, __val in enumerate(self.gts_values[__ts]):
                    if __val is not None and __val > __max:
                        __max = __val
                        __maxtime = __ts + __i * 86400
                    if __val is not None and __val < __min:
                        __min = __val
                        __mintime = __ts + __i * 86400
            # next year
            __ts = startOfYearTZ(__ts + 31708800, self.lmt_tz)

        if obs_type == 'GTS':
            if aggregate_type == 'avg':
                # 1 day is 86400s, but once a year it is 90000s or 82800s
                # when the daylight savings time is switched on or off.
                if timespan.stop - timespan.start <= 90000:
                    __a = startOfDayTZ(timespan.start, _soya_ts)
                    __b = startOfDayTZ(timespan.stop, _soye_ts)
                    if __a != __b:
                        # begin and end of timespan are different days
                        # according to timezone self.lmt_tz
                        # timespan.start <= __b <= timespan.stop
                        if __b - timespan.start > timespan.stop - __b:
                            __b = __a
                            _soye_ts = _soya_ts
                    if __b >= _soye_ts + 13046400:
                        __x = weewx.units.ValueTuple(None, 'degree_C_day',
                                                     'group_degree_day')
                    else:
                        __x = self.get_gts(obs_type, __b, _soye_ts)
                elif _soya_ts == _soye_ts and _soya_ts in self.gts_values:
                    # timespan within the same year but more than one day
                    # (not much use, but calculated anyway)
                    __a = dayOfGTSYear(timespan.start, _soya_ts)
                    __b = dayOfGTSYear(timespan.stop, _soye_ts)
                    if __a == __b:
                        __x = self.gts_values[_soya_ts][__a]
                    else:
                        __x = 0
                        for __i in xrange(__a, __b):
                            if self.gts_values[_soya_ts][__i] is not None:
                                __x += self.gts_values[_soya_ts][__i]
                        __x /= __b - __a
                else:
                    raise weewx.CannotCalculate(
                        "%s %s invalid timespan %s %s" %
                        (obs_type, aggregate_type,
                         timespan.stop - timespan.start,
                         time.strftime("%Y-%m-%d %H:%M:%S",
                                       time.localtime(timespan.start))))
            elif aggregate_type == 'lasttime':
                if timespan.stop >= self.last_gts_date:
                    # today or in future
                    __ts = self.last_gts_date
                else:
                    # before today
                    if _soye_ts not in self.gts_values:
                        raise weewx.CannotCalculate("%s %s" %
                                                    (obs_type, aggregate_type))
                    __ts = dayOfGTSYear(timespan.stop, _soye_ts)
                    for __i, __v in reversed(
                            enumerate(self.gts_values[_soye_ts])):
                        if __v is not None and __i <= __ts:
                            __ts = _soye_ts + 86400 * __i
                            break
                    else:
                        __ts = _soye_ts
                __x = weewx.units.ValueTuple(__ts, 'unix_epoch', 'group_time')
            elif aggregate_type == 'last':
                if timespan.stop >= _soye_ts + 13046400:
                    # after May 31st
                    __ts = _soye_ts + 13046400 - 86400
                else:
                    # between Jan 1st and May 31st
                    __ts = startOfDayTZ(timespan.stop, _soye_ts)
                    # startOfDay() returns for 24:00 the start of the
                    # next day. So we need to look for the day before.
                    if __ts == timespan.stop:
                        __ts -= 86400
                    # for today there is no value so far
                    if __ts == startOfDayTZ(time.time(), _soye_ts):
                        __ts -= 86400
                __x = self.get_gts(obs_type, __ts, _soye_ts)
            elif aggregate_type == 'max':
                __x = weewx.units.ValueTuple(__max, 'degree_C_day',
                                             'group_degree_day')
            elif aggregate_type == 'maxtime':
                __x = weewx.units.ValueTuple(__maxtime, 'unix_epoch',
                                             'group_time')
            elif aggregate_type == 'min':
                __x = weewx.units.ValueTuple(__min, 'degree_C_day',
                                             'group_degree_day')
            elif aggregate_type == 'mintime':
                __x = weewx.units.ValueTuple(__mintime, 'unix_epoch',
                                             'group_time')
            else:
                raise weewx.CannotCalculate("%s %s" %
                                            (obs_type, aggregate_type))
            """
            try:
              a=str(__x[0])
            except:
              logerr("get_aggregate 0")
              a=""
            try:
              b=str(__x[1])
            except:
              logerr("get_aggregate 1")
              b=""
            try:
              c=str(__x[2])
            except:
              logerr("get_aggregate 2")
              c=""
            loginf("get_aggregate %s,%s,%s" % (a,b,c))
            """
            return __x

        if obs_type == 'GTSdate':
            if aggregate_type == 'last' or aggregate_type == 'max':
                if _soye_ts in self.gts_date and self.gts_date[
                        _soye_ts] is not None and timespan.stop >= self.gts_date[
                            _soye_ts]:
                    __x = self.gts_date[_soye_ts]
                else:
                    __x = None
                return weewx.units.ValueTuple(__x, 'unix_epoch', 'group_time')

        raise weewx.CannotCalculate("%s %s" % (obs_type, aggregate_type))