Skip to content

Commit d27f23d

Browse files
authored
VED-1147: Convert Upstream Time Format to ISO (#1327)
* convert time to rfc33399 format
1 parent 9bbdd6f commit d27f23d

8 files changed

Lines changed: 68 additions & 5 deletions

File tree

lambdas/mns_publisher/src/create_notification.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from common.api_clients.constants import MnsNotificationPayload
1010
from common.api_clients.get_pds_details import pds_get_patient_details
1111
from common.clients import logger
12+
from common.converter_utils import timestamp_to_rfc3339
1213
from common.get_service_url import get_service_url
1314
from constants import IMMUNISATION_EVENT_SOURCE, IMMUNISATION_EVENT_TYPE, SPEC_VERSION
1415

@@ -41,13 +42,14 @@ def create_mns_notification(sqs_event: SQSMessage) -> MnsNotificationPayload:
4142

4243
patient_age = calculate_age_at_vaccination(person_dob, date_and_time)
4344
gp_ods_code = get_practitioner_details_from_pds(nhs_number)
45+
mns_timestamp = timestamp_to_rfc3339(date_and_time)
4446

4547
return {
4648
"specversion": SPEC_VERSION,
4749
"id": str(uuid.uuid4()),
4850
"source": IMMUNISATION_EVENT_SOURCE,
4951
"type": IMMUNISATION_EVENT_TYPE,
50-
"time": date_and_time,
52+
"time": mns_timestamp,
5153
"subject": nhs_number,
5254
"dataref": f"{immunisation_url}/Immunization/{imms_id}",
5355
"filtering": {

lambdas/mns_publisher/tests/test_lambda_handler.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,6 @@ def test_successful_notification_creation_with_gp(self, mock_logger, mock_get_to
282282

283283
sqs_event = {"Records": [self.sample_sqs_record]}
284284
result = lambda_handler(sqs_event, Mock())
285-
286285
self.assertEqual(result, {"batchItemFailures": []})
287286

288287
self.assertEqual(mns_response.call_count, 1)

lambdas/mns_publisher/tests/test_sqs_dynamo_utils.py

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
DEFAULT_BASE_PATH = "immunisation-fhir-api/FHIR/R4"
22
PR_ENV_PREFIX = "pr-"
3+
ALLOWED_OFFSET_SUFFIXES: frozenset[str] = frozenset({"00", "01"})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from datetime import datetime, timedelta, timezone
2+
3+
from common.constants import ALLOWED_OFFSET_SUFFIXES
4+
5+
6+
def timestamp_to_rfc3339(value: str, field_name: str = "DATE_AND_TIME") -> str:
7+
if not isinstance(value, str):
8+
raise TypeError(f"{field_name} must be a string")
9+
10+
if not value:
11+
raise ValueError(f"{field_name} is required")
12+
13+
offset_suffix = value[-2:]
14+
if offset_suffix not in ALLOWED_OFFSET_SUFFIXES:
15+
raise ValueError(f"{field_name} timezone suffix must be 00 or 01")
16+
17+
try:
18+
parsed_date = datetime.strptime(value[:-2], "%Y%m%dT%H%M%S")
19+
except ValueError as e:
20+
raise ValueError(f"{field_name} must contain a valid compact timestamp") from e
21+
22+
tz = timezone(timedelta(hours=int(offset_suffix)))
23+
return parsed_date.replace(tzinfo=tz).isoformat(timespec="seconds").replace("+00:00", "Z")
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import re
2+
import unittest
3+
4+
from common.converter_utils import timestamp_to_rfc3339
5+
6+
7+
class TestTimestampToRfc3339(unittest.TestCase):
8+
def test_utc_conversion(self):
9+
self.assertEqual(timestamp_to_rfc3339("20260212T17443700"), "2026-02-12T17:44:37Z")
10+
11+
def test_bst_conversion(self):
12+
self.assertEqual(timestamp_to_rfc3339("20260212T17443701"), "2026-02-12T17:44:37+01:00")
13+
14+
def test_too_short_raises(self):
15+
with self.assertRaises(ValueError):
16+
timestamp_to_rfc3339("20260212T1744")
17+
18+
def test_output_is_rfc3339(self):
19+
rfc3339 = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2})$")
20+
self.assertRegex(timestamp_to_rfc3339("20260212T17443700"), rfc3339)
21+
22+
def test_non_string_raises_type_error(self):
23+
with self.assertRaises(TypeError):
24+
timestamp_to_rfc3339(20260212)
25+
26+
def test_empty_string_raises(self):
27+
with self.assertRaises(ValueError):
28+
timestamp_to_rfc3339("")
29+
30+
def test_unsupported_offset_raises(self):
31+
with self.assertRaises(ValueError):
32+
timestamp_to_rfc3339("20260212T17443705")

tests/e2e_automation/features/APITests/steps/common_steps.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
get_delete_url_header,
3737
get_update_url_header,
3838
)
39-
from utilities.date_helper import is_valid_date, iso_to_compact
39+
from utilities.date_helper import is_valid_date, normalize_utc_suffix
4040
from utilities.enums import Operation
4141
from utilities.http_requests_session import http_requests_session
4242
from utilities.sqs_message_halder import read_message
@@ -444,9 +444,10 @@ def validate_sqs_message(context, message_body, action):
444444

445445
check.is_true(is_valid_uuid(message_body.id), f"Invalid UUID: {message_body.id}")
446446

447+
imms_date_time = normalize_utc_suffix(context.immunization_object.occurrenceDateTime)
447448
check.is_true(
448-
message_body.time == iso_to_compact(context.immunization_object.occurrenceDateTime),
449-
f"msn event for {action} Time missing or empty: {message_body.time}",
449+
message_body.time == imms_date_time,
450+
f"msn event for {action} Time missing or mismatch: message_body.time = {message_body.time}, imms_date_time = {imms_date_time}",
450451
)
451452
expected_nhs_number = context.patient.identifier[0].value
452453
if expected_nhs_number is None:

tests/e2e_automation/utilities/date_helper.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ def iso_to_compact(dt_str):
2424
return dt.strftime("%Y%m%dT%H%M%S00")
2525

2626

27+
def normalize_utc_suffix(timestamp: str) -> str:
28+
dt = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
29+
return dt.replace(microsecond=0).isoformat(timespec="seconds").replace("+00:00", "Z")
30+
31+
2732
def is_valid_date(date_str: str) -> bool:
2833
try:
2934
datetime.strptime(date_str, "%Y-%m-%d")

0 commit comments

Comments
 (0)