Skip to content

Commit

Permalink
PB-1319: made format_time() more robust
Browse files Browse the repository at this point in the history
Currently during several steps of timestamp handling and conversions, the displayed departure
times in the viewer are 1 hour off. The new format_time() should be more robust and
also corresponsing tests are added.
  • Loading branch information
hansmannj committed Jan 7, 2025
1 parent 05da83a commit 45f65a8
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 20 deletions.
34 changes: 14 additions & 20 deletions chsdi/lib/opentransapi/opentransapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,23 @@
import xml.etree.ElementTree as et
from pytz import timezone
from datetime import datetime
from dateutil import tz
from dateutil.parser import isoparse
import re


def format_time(str_date_time, fmt="%Y-%m-%dT%H:%M:%SZ"):
from_zone = tz.tzutc()
to_zone = tz.gettz('Europe/Zurich')
def format_time(str_date_time):
# Though the documentation of the OJP 2.0 API is not too verbose on this point (see:
# https://opentransportdata.swiss/de/cookbook/ojpstopeventrequest/), it seems, the
# timestamps are always handeled in some form of ISO 8601 datetime format.
# Using isoparse() should be able to handle all the needed cases, e.g.
# - "Z" as timezone designator
# - timezone offsets, e.g. "+01:00"
# - sometimes the returned timestamps have an unexpected number of
# fractional seconds, e.g. 7 instead of 6, should be handled, too
date_time = isoparse(str_date_time)

try:
date_time = datetime.strptime(str_date_time, fmt)
except ValueError:
# sometimes the timestamp of the OJP 2.0 API's response has 7 digits for the
# milliseconds. 6 are expected and only 6 can be handled by Python.
# Hence we need to safely truncate everything between the last . and
# the +01:00 part of the timestamp, e.g.:
# 2024-11-01T15:39:45.5348804+01:00
# Use regex to capture and truncate everything between the last '.' and
# the first '+' to 6 digits
truncated_date_time = re.sub(r'(\.\d{6})\d*(?=\+)', r'\1', str_date_time)
date_time = datetime.strptime(truncated_date_time, '%Y-%m-%dT%H:%M:%S.%f%z')
date_time_utc = date_time.replace(tzinfo=from_zone)
date_time_zurich = date_time_utc.astimezone(to_zone)
return date_time_zurich.strftime('%d/%m/%Y %H:%M')
# Return time in local time, as needed.
return date_time.strftime('%d/%m/%Y %H:%M')


class OpenTrans:
Expand Down Expand Up @@ -80,7 +74,7 @@ def xml_to_array(self, xml_data):
results.append({
'id': el_id,
'label': el_service_name,
'currentDate': format_time(el_current_date, fmt="%Y-%m-%dT%H:%M:%S.%f%z"),
'currentDate': format_time(el_current_date),
'departureDate': format_time(el_departure_date),
'estimatedDate': self._convert_estimated_date(el.find('.//ojp:ServiceDeparture/ojp:EstimatedTime', ns)),
'destinationName': el_destination_name,
Expand Down
6 changes: 6 additions & 0 deletions tests/integration/test_opentransapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ def test_stationboard(self, mock_requests):
self.assertEqual(results[0]["destinationName"], "Hogwarts")
self.assertEqual(results[0]["destinationId"], "ch:1:sloid:91178::3")

# assert, that several timestamp formats are correctly handled and transformed into the
# correct local time
self.assertEqual(format_time("2024-11-19T08:52:00Z"), "19/11/2024 08:52")
self.assertEqual(format_time("2024-11-19T08:52:00.1234567"), "19/11/2024 08:52")
self.assertEqual(format_time("2024-11-19T08:52:00+01:00"), "19/11/2024 08:52")

@requests_mock.Mocker()
def test_stationboard_nonexisting_station(self, mock_requests):
now = datetime.now(timezone('Europe/Zurich')).isoformat(timespec="microseconds")
Expand Down

0 comments on commit 45f65a8

Please sign in to comment.