def test_dispatch_scale_type_on_creation(data): scale = parameters.Scale("amount_scale", data, "") first_jan = periods.Instant((2017, 11, 1)) result = scale.get_at_instant(first_jan) assert isinstance(result, taxscales.SingleAmountTaxScale)
def test_assign_amounts_on_creation(data): scale = parameters.Scale("amount_scale", data, "") first_jan = periods.Instant((2017, 11, 1)) scale_at_instant = scale.get_at_instant(first_jan) result = scale_at_instant.amounts assert result == [6]
def instant(instant): """Return a new instant, aka a triple of integers (year, month, day). >>> instant(2014) Instant((2014, 1, 1)) >>> instant('2014') Instant((2014, 1, 1)) >>> instant('2014-02') Instant((2014, 2, 1)) >>> instant('2014-3-2') Instant((2014, 3, 2)) >>> instant(instant('2014-3-2')) Instant((2014, 3, 2)) >>> instant(period('month', '2014-3-2')) Instant((2014, 3, 2)) >>> instant(None) """ if instant is None: return None if isinstance(instant, periods.Instant): return instant if isinstance(instant, str): if not config.INSTANT_PATTERN.match(instant): raise ValueError("'{}' is not a valid instant. Instants are described using the 'YYYY-MM-DD' format, for instance '2015-06-15'.".format(instant)) instant = periods.Instant( int(fragment) for fragment in instant.split('-', 2)[:3] ) elif isinstance(instant, datetime.date): instant = periods.Instant((instant.year, instant.month, instant.day)) elif isinstance(instant, int): instant = (instant,) elif isinstance(instant, list): assert 1 <= len(instant) <= 3 instant = tuple(instant) elif isinstance(instant, periods.Period): instant = instant.start else: assert isinstance(instant, tuple), instant assert 1 <= len(instant) <= 3 if len(instant) == 1: return periods.Instant((instant[0], 1, 1)) if len(instant) == 2: return periods.Instant((instant[0], instant[1], 1)) return periods.Instant(instant)
def parse_simple_period(value): """ Parses simple periods respecting the ISO format, such as 2012 or 2015-03 """ try: date = datetime.datetime.strptime(value, '%Y') except ValueError: try: date = datetime.datetime.strptime(value, '%Y-%m') except ValueError: try: date = datetime.datetime.strptime(value, '%Y-%m-%d') except ValueError: return None else: return periods.Period((config.DAY, periods.Instant((date.year, date.month, date.day)), 1)) else: return periods.Period((config.MONTH, periods.Instant((date.year, date.month, 1)), 1)) else: return periods.Period((config.YEAR, periods.Instant((date.year, date.month, 1)), 1))
def str_to_instant(s): return periods.Instant(tuple(map(lambda s: int(s), s.split("-"))))
def period(value): """Return a new period, aka a triple (unit, start_instant, size). >>> period('2014') Period((YEAR, Instant((2014, 1, 1)), 1)) >>> period('year:2014') Period((YEAR, Instant((2014, 1, 1)), 1)) >>> period('2014-2') Period((MONTH, Instant((2014, 2, 1)), 1)) >>> period('2014-02') Period((MONTH, Instant((2014, 2, 1)), 1)) >>> period('month:2014-2') Period((MONTH, Instant((2014, 2, 1)), 1)) >>> period('year:2014-2') Period((YEAR, Instant((2014, 2, 1)), 1)) """ if isinstance(value, periods.Period): return value if isinstance(value, periods.Instant): return periods.Period((config.DAY, value, 1)) def parse_simple_period(value): """ Parses simple periods respecting the ISO format, such as 2012 or 2015-03 """ try: date = datetime.datetime.strptime(value, '%Y') except ValueError: try: date = datetime.datetime.strptime(value, '%Y-%m') except ValueError: try: date = datetime.datetime.strptime(value, '%Y-%m-%d') except ValueError: return None else: return periods.Period((config.DAY, periods.Instant((date.year, date.month, date.day)), 1)) else: return periods.Period((config.MONTH, periods.Instant((date.year, date.month, 1)), 1)) else: return periods.Period((config.YEAR, periods.Instant((date.year, date.month, 1)), 1)) def raise_error(value): message = os.linesep.join([ "Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got: '{}'.".format(value), "Learn more about legal period formats in OpenFisca:", "<https://openfisca.org/doc/coding-the-legislation/35_periods.html#periods-in-simulations>." ]) raise ValueError(message) if value == 'ETERNITY' or value == config.ETERNITY: return periods.Period(('eternity', instant(datetime.date.min), float("inf"))) # check the type if isinstance(value, int): return periods.Period((config.YEAR, periods.Instant((value, 1, 1)), 1)) if not isinstance(value, str): raise_error(value) # try to parse as a simple period period = parse_simple_period(value) if period is not None: return period # complex period must have a ':' in their strings if ":" not in value: raise_error(value) components = value.split(':') # left-most component must be a valid unit unit = components[0] if unit not in (config.DAY, config.MONTH, config.YEAR): raise_error(value) # middle component must be a valid iso period base_period = parse_simple_period(components[1]) if not base_period: raise_error(value) # period like year:2015-03 have a size of 1 if len(components) == 2: size = 1 # if provided, make sure the size is an integer elif len(components) == 3: try: size = int(components[2]) except ValueError: raise_error(value) # if there is more than 2 ":" in the string, the period is invalid else: raise_error(value) # reject ambiguous period such as month:2014 if unit_weight(base_period.unit) > unit_weight(unit): raise_error(value) return periods.Period((unit, base_period.start, size))
def stop(self) -> periods.Instant: """ Return the last day of the period as an Instant instance. >>> period('year', 2014).stop Instant((2014, 12, 31)) >>> period('month', 2014).stop Instant((2014, 12, 31)) >>> period('day', 2014).stop Instant((2014, 12, 31)) >>> period('year', '2012-2-29').stop Instant((2013, 2, 28)) >>> period('month', '2012-2-29').stop Instant((2012, 3, 28)) >>> period('day', '2012-2-29').stop Instant((2012, 2, 29)) >>> period('year', '2012-2-29', 2).stop Instant((2014, 2, 28)) >>> period('month', '2012-2-29', 2).stop Instant((2012, 4, 28)) >>> period('day', '2012-2-29', 2).stop Instant((2012, 3, 1)) """ unit, start_instant, size = self year, month, day = start_instant if unit == config.ETERNITY: return periods.Instant((float("inf"), float("inf"), float("inf"))) if unit == 'day': if size > 1: day += size - 1 month_last_day = calendar.monthrange(year, month)[1] while day > month_last_day: month += 1 if month == 13: year += 1 month = 1 day -= month_last_day month_last_day = calendar.monthrange(year, month)[1] else: if unit == 'month': month += size while month > 12: year += 1 month -= 12 else: assert unit == 'year', 'Invalid unit: {} of type {}'.format(unit, type(unit)) year += size day -= 1 if day < 1: month -= 1 if month == 0: year -= 1 month = 12 day += calendar.monthrange(year, month)[1] else: month_last_day = calendar.monthrange(year, month)[1] if day > month_last_day: month += 1 if month == 13: year += 1 month = 1 day -= month_last_day return periods.Instant((year, month, day))