def __call__(self, form: "QuestionnaireForm", from_field: DateField, to_field: DateField) -> None: from_date = parse_datetime(from_field.data) to_date = parse_datetime(to_field.data) if from_date and to_date: if from_date >= to_date: raise validators.ValidationError( self.messages["INVALID_DATE_RANGE"]) answered_range_relative = relativedelta(to_date, from_date) if self.period_min: min_range = self._return_relative_delta(self.period_min) if self._is_first_relative_delta_largest(min_range, answered_range_relative): raise validators.ValidationError( self.messages["DATE_PERIOD_TOO_SMALL"] % dict(min=self._build_range_length_error(self.period_min))) if self.period_max: max_range = self._return_relative_delta(self.period_max) if self._is_first_relative_delta_largest(answered_range_relative, max_range): raise validators.ValidationError( self.messages["DATE_PERIOD_TOO_LARGE"] % dict(max=self._build_range_length_error(self.period_max)))
def test_minimum_and_maximum_offset_dates(app, value_source_resolver, rule_evaluator): value_source_resolver.metadata = {"date": "2018-02-20"} answer_store = AnswerStore() test_answer_id = Answer(answer_id="date", value="2018-03-20") answer_store.add_or_update(test_answer_id) value_source_resolver.answer_store = answer_store answer = { "id": "date_answer", "type": "Date", "minimum": { "value": {"identifier": "date", "source": "metadata"}, "offset_by": {"days": -10}, }, "maximum": { "value": {"identifier": "date", "source": "answers"}, "offset_by": {"years": 1}, }, } handler = DateHandler(answer, value_source_resolver, rule_evaluator, error_messages) minimum_date = handler.get_date_value("minimum") maximum_date = handler.get_date_value("maximum") assert minimum_date == parse_datetime("2018-02-10") assert maximum_date == parse_datetime("2019-03-20")
def test_valid_single_date_period(mock_form, mock_field): minimum_date = parse_datetime("2016-03-20") maximum_date = parse_datetime("2016-03-31") validator = SingleDatePeriodCheck( minimum_date=minimum_date, maximum_date=maximum_date ) mock_form.data = "2016-03-26" validator(mock_form, mock_field)
def test_get_referenced_offset_value_with_list_item_id( app, value_source_resolver, rule_evaluator ): list_item_id = "abcde" test_answer_id = Answer( answer_id="date", value="2018-03-20", list_item_id=list_item_id ) location = Location(section_id="test", list_item_id=list_item_id) answer_store = AnswerStore() answer_store.add_or_update(test_answer_id) value_source_resolver.answer_store = answer_store value_source_resolver.location = location value_source_resolver.list_item_id = list_item_id answer = { "maximum": { "value": {"identifier": "date", "source": "answers"}, "offset_by": {"months": 1}, } } handler = DateHandler(answer, value_source_resolver, rule_evaluator, error_messages) maximum_date = handler.get_date_value("maximum") assert maximum_date == parse_datetime("2018-04-20")
def get_date_match_value(date_comparison, answer_store, schema, metadata): match_value = None if "value" in date_comparison: if date_comparison["value"] == "now": match_value = datetime.now(timezone.utc).strftime("%Y-%m-%d") else: match_value = date_comparison["value"] elif "id" in date_comparison: match_value = get_answer_value(date_comparison["id"], answer_store, schema) elif "meta" in date_comparison: match_value = get_metadata_value(metadata, date_comparison["meta"]) match_value = parse_datetime(match_value) if "offset_by" in date_comparison and match_value: offset = date_comparison["offset_by"] match_value = match_value + relativedelta( days=offset.get("days", 0), months=offset.get("months", 0), years=offset.get("years", 0), ) return match_value
def test_get_referenced_offset_value_for_value( app, value_source_resolver, rule_evaluator ): answer = {"minimum": {"value": "2017-06-11"}} handler = DateHandler(answer, value_source_resolver, rule_evaluator, error_messages) minimum_date = handler.get_date_value("minimum") minimum_date = handler.transform_date_by_offset(minimum_date, {"days": 10}) assert minimum_date == parse_datetime("2017-06-21")
def test_single_date_period_custom_message_invalid_raises(mock_form, mock_field): maximum_date = parse_datetime("2016-03-31") message = {"SINGLE_DATE_PERIOD_TOO_LATE": "Test %(max)s"} validator = SingleDatePeriodCheck(messages=message, maximum_date=maximum_date) mock_form.data = "2016-04-29" with pytest.raises(ValidationError) as exc: validator(mock_form, mock_field) assert "Test 1 April 2016" == str(exc.value)
def test_get_referenced_offset_value_for_meta( app, value_source_resolver, rule_evaluator ): value_source_resolver.metadata = {"date": "2018-02-20"} answer = {"minimum": {"value": {"identifier": "date", "source": "metadata"}}} handler = DateHandler(answer, value_source_resolver, rule_evaluator, error_messages) minimum_date = handler.get_date_value("minimum") minimum_date = handler.transform_date_by_offset(minimum_date, {"days": -10}) assert minimum_date == parse_datetime("2018-02-10")
def calculate_date_difference(first_date: str, second_date: str) -> str: time = relativedelta( parse_datetime(second_date), parse_datetime(first_date), ) if time.years: year_string: str = ngettext("{number_of_years} year", "{number_of_years} years", time.years) return year_string.format(number_of_years=time.years) if time.months: month_string: str = ngettext("{number_of_months} month", "{number_of_months} months", time.months) return month_string.format(number_of_months=time.months) day_string: str = ngettext("{number_of_days} day", "{number_of_days} days", time.days) return day_string.format(number_of_days=time.days)
def evaluate_date_rule(when, answer_store, schema, metadata, answer_value): date_comparison = when["date_comparison"] answer_value = parse_datetime(answer_value) match_value = get_date_match_value(date_comparison, answer_store, schema, metadata) condition = when.get("condition") if not answer_value or not match_value or not condition: return False # Evaluate the condition on the routing rule return evaluate_condition(condition, answer_value, match_value)
def test_operation_date(date_string: str, offset, get_operator): operands = (date_string, offset) operator = get_operator(Operator.DATE) offset = offset or {} expected_result = (parse_datetime(date_string).date() + relativedelta( days=offset.get("days", 0), months=offset.get("months", 0), years=offset.get("years", 0), ) if date_string else None) assert operator.evaluate(operands) == expected_result
def test_get_referenced_offset_value_for_answer_id( app, value_source_resolver, rule_evaluator ): answer_store = AnswerStore() test_answer_id = Answer(answer_id="date", value="2018-03-20") answer_store.add_or_update(test_answer_id) value_source_resolver.answer_store = answer_store answer = {"maximum": {"value": {"identifier": "date", "source": "answers"}}} handler = DateHandler(answer, value_source_resolver, rule_evaluator, error_messages) maximum_date = handler.get_date_value("maximum") maximum_date = handler.transform_date_by_offset(maximum_date, {"months": 1}) assert maximum_date == parse_datetime("2018-04-20")
def __call__(self, form: "QuestionnaireForm", field: StringField) -> None: date = parse_datetime(form.data) if self.minimum_date and date and date < self.minimum_date: raise validators.ValidationError( self.messages["SINGLE_DATE_PERIOD_TOO_EARLY"] % dict(min=self._format_playback_date( self.minimum_date + relativedelta(days=-1), self.date_format))) if self.maximum_date and date and date > self.maximum_date: raise validators.ValidationError( self.messages["SINGLE_DATE_PERIOD_TOO_LATE"] % dict(max=self._format_playback_date( self.maximum_date + relativedelta(days=+1), self.date_format)))
def get_format_date(value): """Format a datetime string. :param (jinja2.nodes.EvalContext) context: Evaluation context. :param (any) value: Value representing a datetime. :returns (str): Formatted datetime. """ value = value[0] if isinstance(value, list) else value date_format = "d MMMM yyyy" if value and re.match(r"\d{4}-\d{2}$", value): date_format = "MMMM yyyy" if value and re.match(r"\d{4}$", value): date_format = "yyyy" date_to_format = parse_datetime(value).date() date = flask_babel.format_date(date_to_format, format=date_format) return f"<span class='date'>{date}</span>"
def resolve_date_from_string( date_string: Optional[str], offset: Optional[DateOffset] = None, offset_by_full_weeks: bool = False, ) -> Optional[date]: datetime_value = parse_datetime(date_string) if not datetime_value: return None value_as_date = datetime_value.date() if offset: days_offset = offset.get("days", 0) if day_of_week_offset := offset.get("day_of_week"): if 0 > days_offset > -7: raise ValueError( "Negative days offset must be less than or equal to -7 when used with `day_of_week` offset" ) days_difference = ( value_as_date.weekday() - DAYS_OF_WEEK[day_of_week_offset] ) days_to_reduce = days_difference % 7 if not offset_by_full_weeks and ( days_offset < 0 and days_difference < 0 ): # A negative day difference means that the `day_of_week` offset went back to the previous week; # therefore, if we also have a negative days offset, # then the no. of days we reduce the offset by must be adjusted by 7 to prevent going back two weeks. days_to_reduce -= 7 days_offset -= days_to_reduce value_as_date += relativedelta( days=days_offset, months=offset.get("months", 0), years=offset.get("years", 0), )
def test_parse_datetime(date_string, date_format): expected_date = (parser.isoparse(date_string) if date_format == "iso8601" else datetime.strptime(date_string, date_format)) assert parse_datetime(date_string) == expected_date.replace( tzinfo=timezone.utc)
import pytest from wtforms.validators import ValidationError from app.forms import error_messages from app.forms.validators import SingleDatePeriodCheck from app.questionnaire.rules.utils import parse_datetime @pytest.mark.parametrize( "validator,data,error_type,error_message", ( ( SingleDatePeriodCheck(minimum_date=parse_datetime("2016-03-31")), "2016-01-29", "SINGLE_DATE_PERIOD_TOO_EARLY", {"min": "30 March 2016"}, ), ( SingleDatePeriodCheck(maximum_date=parse_datetime("2016-03-31")), "2016-04-29", "SINGLE_DATE_PERIOD_TOO_LATE", {"max": "1 April 2016"}, ), ), ) @pytest.mark.usefixtures("app") def test_single_date_period_invalid_raises_ValidationError( validator, data, error_type, error_message, mock_form, mock_field ): mock_form.data = data
def test_parse_date_exception(date_string): with pytest.raises(ValueError): parse_datetime(date_string)
def test_parse_datetime_now(): assert parse_datetime("now") == datetime.now(timezone.utc)