def check_valid_min_max_args(min_val, max_val, step, parser, range_axis): """ If a val range was specified, checks that they are valid numbers and the min is less than the max """ from cis.parse_datetime import parse_as_number_or_datetime, parse_as_float_or_time_delta, \ parse_datetimestr_to_std_time from cis.time_util import convert_datetime_to_std_time import datetime ax_range = {} if min_val is not None: dt = parse_as_number_or_datetime(min_val, range_axis + "min", parser) if isinstance(dt, datetime.datetime): ax_range[range_axis + "min"] = convert_datetime_to_std_time(dt) else: ax_range[range_axis + "min"] = dt if max_val is not None: dt = parse_as_number_or_datetime(max_val, range_axis + "max", parser) if isinstance(dt, datetime.datetime): ax_range[range_axis + "max"] = parse_datetimestr_to_std_time(str(datetime.datetime(*dt))) else: ax_range[range_axis + "max"] = dt if step is not None: ax_range[range_axis + "step"] = parse_as_float_or_time_delta(step, range_axis + "step", parser) return ax_range
def get_subset_limits(subsetlimits, parser): """ :param subsetlimits: List of subset limit strings :param parser: The parser used to report errors :return: The parsed datagroups as a list of dictionaries """ from cis.parse_datetime import parse_datetime, parse_as_number_or_datetime from cis.subsetting.subset_limits import SubsetLimits # Split into the limits for each dimension. split_input = split_outside_brackets(subsetlimits) if len(split_input) == 0: parser.error("Limits for at least one dimension must be specified for subsetting") limit_dict = {} for seg in split_input: # Parse out dimension name and limit value strings; the expected format is: # <dim_name>=[<start_value>,<end_value>] # or # <dim_name>=[<start_value>] match = re.match(r'(?P<dim>[^=]+)=\[(?P<start>[^],]+)(?:,(?P<end>[^],]+))?\]$', seg) if match is None or match.group('dim') is None or match.group('start') is None: parser.error( "A dimension for subsetting does not have dimension name, start value and/or end value specified") else: dim_name = match.group('dim') limit1 = match.group('start') if match.group('end') is None: limit2 = limit1 else: limit2 = match.group('end') # If the dimension is specified as x, y, z, or t, assume that the dimension is spatial or temporal in the # obvious way. Otherwise, parse what is found as a date/time or number. is_time = None if dim_name.lower() == 't': limit1_parsed = parse_datetime(limit1, 'subset range start date/time', parser) limit2_parsed = parse_datetime(limit2, 'subset range end date/time', parser) is_time = True elif dim_name.lower() in ['x', 'y', 'z']: limit1_parsed = parse_float(limit1, 'subset range start coordinate', parser) limit2_parsed = parse_float(limit2, 'subset range start coordinate', parser) is_time = False if dim_name.lower() == 'x': if not limit1_parsed <= limit2_parsed: parser.error("Longitude limits must be monotonically increasing (i.e. for x[A,B] A <= B). For " "example, x=[90,-90] is invalid but x=[90,270] is valid") if not limit2_parsed - limit1_parsed <= 360: parser.error("Longitude limits should not be more than 360 degrees apart " "(i.e. for x[A,B] B-A <= 360)") else: limit1_parsed = parse_as_number_or_datetime(limit1, 'subset range start coordinate', parser) limit2_parsed = parse_as_number_or_datetime(limit2, 'subset range start coordinate', parser) limit_dict[dim_name] = SubsetLimits(limit1_parsed, limit2_parsed, is_time) return limit_dict
def get_aggregate_grid(aggregategrid, parser): """ :param aggregategrid: List of aggregate grid specifications :param parser: The parser used to report errors :return: The parsed datagroups as a list of dictionaries """ from cis.parse_datetime import parse_as_number_or_datetime, parse_as_number_or_datetime_delta # Split into the limits for each dimension. split_input = split_outside_brackets(aggregategrid) if len(split_input) == 0: parser.error("Limits for at least one dimension must be specified for aggregation") grid_dict = {} for seg in split_input: # Parse out dimension name and new grid spacing; the expected format is: # <dim_name>=[<start_value>,<end_value,<delta>] match = re.match(r'(?P<dim>[^=]+)(?:=)?(?:\[(?P<start>[^],]+)?(?:,(?P<end>[^],]+))?(?:,(?P<delta>[^]]+))?\])?', seg) if match is None or match.group('dim') is None: parser.error("A dimension for aggregation does not have a valid dimension name") elif match.group('start') is None and match.group('delta') is None: # This is for gridded aggregation where we just have a list of dims with no grid grid_dict[match.group('dim')] = None elif match.group('end') is None: parser.error("A dimension for aggregation has a start point but no end or delta value, an end and a delta " "value must be supplied, for example x=[0,360,30].") elif match.group('delta') is None: parser.error("A dimension for aggregation has a start point but no delta value, a delta value must be " "supplied, for example x=[0,360,30].") else: dim_name = match.group('dim') start_parsed = parse_as_number_or_datetime(match.group('start')) end_parsed = parse_as_number_or_datetime(match.group('end')) delta_parsed = parse_as_number_or_datetime_delta(match.group('delta')) if dim_name.lower() == 'x': if not start_parsed <= end_parsed: parser.error("Longitude grid must be monotonically increasing (i.e. for x[A,B,C] A <= B). For " "example, x=[90,-90,10] is invalid but x=[90,270,10] is valid") if not end_parsed - start_parsed <= 360: parser.error("Longitude grid should not be wider than 360 degrees " "(i.e. for x[A,B,C] B-A <= 360)") grid_dict[dim_name] = slice(start_parsed, end_parsed, delta_parsed) return grid_dict
def parse_as_number_or_datetime_can_parse_float(): dt = parse_as_number_or_datetime('12.345') assert (dt == 12.345)
def parse_as_number_or_datetime_can_parse_integer(): dt = parse_as_number_or_datetime('2010') assert (dt == 2010)
def parse_as_number_or_datetime_can_parse_date_as_datetime(): from datetime import datetime dt = parse_as_number_or_datetime('2010-07-01') assert (dt == datetime(2010, 7, 1))
def get_aggregate_grid(aggregategrid, parser): """ :param aggregategrid: List of aggregate grid specifications :param parser: The parser used to report errors :return: The parsed datagroups as a list of dictionaries """ from cis.parse_datetime import parse_datetime, parse_datetime_delta, parse_as_number_or_datetime from cis.aggregation.aggregation_grid import AggregationGrid # Split into the limits for each dimension. split_input = split_outside_brackets(aggregategrid) if len(split_input) == 0: parser.error("Limits for at least one dimension must be specified for aggregation") grid_dict = {} for seg in split_input: # Parse out dimension name and new grid spacing; the expected format is: # <dim_name>=[<start_value>,<end_value,<delta>] match = re.match(r'(?P<dim>[^=]+)(?:=)?(?:\[(?P<start>[^],]+)?(?:,(?P<end>[^],]+))?(?:,(?P<delta>[^]]+))?\])?', seg) if match is None or match.group('dim') is None: parser.error("A dimension for aggregation does not have a valid dimension name") elif match.group('start') is None and match.group('delta') is None: dim_name = match.group('dim') grid_dict[dim_name] = AggregationGrid(float('NaN'), float('NaN'), float('NaN'), None) elif match.group('end') is None: parser.error("A dimension for aggregation has a start point but no end or delta value, an end and a delta " "value must be supplied, for example x=[0,360,30].") elif match.group('delta') is None: parser.error("A dimension for aggregation has a start point but no delta value, a delta value must be " "supplied, for example x=[0,360,30].") else: dim_name = match.group('dim') start = match.group('start') end = match.group('end') delta = match.group('delta') # If the dimension is specified as x, y, z, p or t, assume that the dimension is spatial or temporal in the # obvious way. Otherwise, parse what is found as a date/time or number. is_time = None if dim_name.lower() == 't': start_parsed = parse_datetime(start, 'aggregation grid start date/time', parser) end_parsed = parse_datetime(end, 'aggregation grid end date/time', parser) delta_parsed = parse_datetime_delta(delta, 'aggregation grid delta date/time', parser) is_time = True elif dim_name.lower() in ['x', 'y', 'z', 'p']: start_parsed = parse_float(start, 'aggregation grid start coordinate', parser) end_parsed = parse_float(end, 'aggregation grid end coordinate', parser) delta_parsed = parse_float(delta, 'aggregation grid delta coordinate', parser) is_time = False if dim_name.lower() == 'x': if not start_parsed <= end_parsed: parser.error("Longitude grid must be monotonically increasing (i.e. for x[A,B,C] A <= B). For " "example, x=[90,-90,10] is invalid but x=[90,270,10] is valid") if not end_parsed - start_parsed <= 360: parser.error("Longitude grid should not be wider than 360 degrees " "(i.e. for x[A,B,C] B-A <= 360)") else: start_parsed = parse_as_number_or_datetime(start, 'aggregation grid start coordinate', parser) end_parsed = parse_as_number_or_datetime(end, 'aggregation grid end coordinate', parser) delta_parsed = parse_as_number_or_datetime(delta, 'aggregation grid delta coordinate', parser) grid_dict[dim_name] = AggregationGrid(start_parsed, end_parsed, delta_parsed, is_time) return grid_dict
def parse_as_number_or_datetime_can_parse_float(): dt = parse_as_number_or_datetime('12.345') assert (dt == 12.345)
def parse_as_number_or_datetime_can_parse_integer(): dt = parse_as_number_or_datetime('2010') assert (dt == 2010)
def parse_as_number_or_datetime_can_parse_date_as_datetime(): from datetime import datetime dt = parse_as_number_or_datetime('2010-07-01') assert (dt == datetime(2010, 7, 1))
def parse_as_number_or_datetime_can_parse_float(): parser = MockParser() dt = parse_as_number_or_datetime('12.345', 'limit arg', parser) assert (dt == 12.345)
def parse_as_number_or_datetime_can_parse_integer(): parser = MockParser() dt = parse_as_number_or_datetime('2010', 'limit arg', parser) assert (dt == 2010)
def parse_as_number_or_datetime_can_parse_date_as_datetime(): from datetime import datetime from cis.time_util import cis_standard_time_unit parser = MockParser() dt = parse_as_number_or_datetime('2010-07-01', 'date/time arg', parser) assert (datetime(*dt) == datetime(2010, 7, 1))