22from datetime import datetime , timedelta
33from decimal import Decimal
44from typing import Union
5+ from datetime import datetime , date
56
67from .generic_utils import nhs_number_mod11_check , is_valid_simple_snomed
78
@@ -91,12 +92,15 @@ def for_date(field_value: str, field_location: str):
9192 raise TypeError (f"{ field_location } must be a string" )
9293
9394 try :
94- datetime .strptime (field_value , "%Y-%m-%d" ).date ()
95+ parsed_date = datetime .strptime (field_value , "%Y-%m-%d" ).date ()
9596 except ValueError as value_error :
9697 raise ValueError (
9798 f'{ field_location } must be a valid date string in the format "YYYY-MM-DD"'
9899 ) from value_error
99100
101+ # Enforce not-in-the-future rule using central checker
102+ PreValidation .check_if_future_date (parsed_date )
103+
100104 @staticmethod
101105 def for_date_time (field_value : str , field_location : str , strict_timezone : bool = True ):
102106 """
@@ -117,10 +121,12 @@ def for_date_time(field_value: str, field_location: str, strict_timezone: bool =
117121 "- 'YYYY-MM-DDThh:mm:ss%z' — Full date and time with timezone (e.g. +00:00 or +01:00)"
118122 "- 'YYYY-MM-DDThh:mm:ss.f%z' — Full date and time with milliseconds and timezone"
119123 )
120-
121124 if strict_timezone :
122- error_message += "Only '+00:00' and '+01:00' are accepted as valid timezone offsets.\n "
123- error_message += f"Note that partial dates are not allowed for { field_location } in this service."
125+ error_message += (
126+ "Only '+00:00' and '+01:00' are accepted as valid timezone offsets.\n "
127+ f"Note that partial dates are not allowed for { field_location } in this service.\n "
128+ "Date must not be in the future."
129+ )
124130
125131 allowed_suffixes = {"+00:00" , "+01:00" , "+0000" , "+0100" ,}
126132
@@ -135,8 +141,11 @@ def for_date_time(field_value: str, field_location: str, strict_timezone: bool =
135141 fhir_date = datetime .strptime (field_value , fmt )
136142
137143 if strict_timezone and fhir_date .tzinfo is not None :
144+ if PreValidation .check_if_future_date (fhir_date ):
145+ raise ValueError (error_message )
138146 if not any (field_value .endswith (suffix ) for suffix in allowed_suffixes ):
139147 raise ValueError (error_message )
148+ # Enforce not-in-the-future rule using central checker
140149 return fhir_date .isoformat ()
141150 except ValueError :
142151 continue
@@ -234,3 +243,15 @@ def for_nhs_number(nhs_number: str, field_location: str):
234243 """
235244 if not nhs_number_mod11_check (nhs_number ):
236245 raise ValueError (f"{ field_location } is not a valid NHS number" )
246+
247+ @staticmethod
248+ def check_if_future_date (parsed_value : date | datetime ):
249+ """
250+ Ensure a parsed date or datetime object is not in the future.
251+ """
252+ if isinstance (parsed_value , datetime ):
253+ now = datetime .now (parsed_value .tzinfo ) if parsed_value .tzinfo else datetime .now ()
254+ elif isinstance (parsed_value , date ):
255+ now = datetime .now ().date ()
256+ if parsed_value > now :
257+ return True
0 commit comments