Skip to content

Commit

Permalink
Added human-readable time window formatting functions to `human_reada…
Browse files Browse the repository at this point in the history
…ble`.
  • Loading branch information
ondrasej committed Dec 4, 2023
1 parent a3636b4 commit 5fbcc68
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 0 deletions.
62 changes: 62 additions & 0 deletions python/cfr/json/human_readable.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Provides functions for formatting CFR JSON objects to human-readable form."""

from collections.abc import Iterable

from . import cfr_json


Expand All @@ -12,6 +14,66 @@ def lat_lng(latlng: cfr_json.LatLng) -> str:
return f"{latitude}, {longitude}"


def time_window(window: cfr_json.TimeWindow | None) -> str:
"""Formats a time window as a human readable string.
Args:
window: The time window to format.
Returns:
A human-readable string representation of the time window. Returns an empty
string when `window` is None.
"""
if window is None:
return ""
parts = []
start = window.get("startTime")
end = window.get("endTime")
if start is not None or end is not None:
parts.append(
f"{timestring_or_default(start, '...')} -"
f" {timestring_or_default(end, '...')}"
)
soft_start = window.get("softStartTime")
soft_end = window.get("softEndTime")
if soft_start is not None or soft_end is not None:
parts.append(
f"soft: {timestring_or_default(soft_start, '...')} -"
f" {timestring_or_default(soft_end, '...')}"
)
return " ".join(parts)


def time_windows(
windows: Iterable[cfr_json.TimeWindow] | None, separator: str = " | "
) -> str:
"""Formats a collection of time windows as a human readable string.
Args:
windows: The collection of time windows to be formatted.
separator: The separator string placed between the time windows.
Returns:
A human-readable string representation of the time windows. Returns an empty
string when `windows` is None or empty.
"""
if not windows:
return ""
return separator.join(time_window(window) for window in windows)


def timestring_or_default(
value: cfr_json.TimeString | None, default: str
) -> str:
"""Returns a formatted timestamp or `default`, if `value` is `None`."""
# TODO(ondrasej): If the global span of the scenario is <= 24 hours, do not
# show the date. Also, normalize all timestamps to the same timezone and do
# not show the timezone suffix.
if value is not None:
return str(cfr_json.parse_time_string(value))
return default


def transition_duration(transition: cfr_json.Transition) -> str:
"""Returns human-readable travel duration info for a transition."""
# NOTE(ondrasej): Breaks have their own rows, so we do not need to include
Expand Down
126 changes: 126 additions & 0 deletions python/cfr/json/human_readable_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,132 @@ def test_lat_lng(self):
)


class TimeWindowTest(unittest.TestCase):
"""Tests for time_window."""

def test_none(self):
self.assertEqual(human_readable.time_window(None), "")

def test_start_and_end(self):
self.assertEqual(
human_readable.time_window({
"startTime": "2023-11-21T12:00:32Z",
"endTime": "2023-11-21T17:00:00Z",
}),
"2023-11-21 12:00:32+00:00 - 2023-11-21 17:00:00+00:00",
)

def test_start_only(self):
self.assertEqual(
human_readable.time_window({"startTime": "2023-11-21T08:00:00Z"}),
"2023-11-21 08:00:00+00:00 - ...",
)

def test_end_only(self):
self.assertEqual(
human_readable.time_window({"endTime": "2023-11-21T18:15:00Z"}),
"... - 2023-11-21 18:15:00+00:00",
)

def test_soft_start_end(self):
self.assertEqual(
human_readable.time_window({
"softStartTime": "2023-11-21T12:00:32Z",
"softEndTime": "2023-11-21T17:00:00Z",
}),
"soft: 2023-11-21 12:00:32+00:00 - 2023-11-21 17:00:00+00:00",
)

def test_soft_start_only(self):
self.assertEqual(
human_readable.time_window({
"softStartTime": "2023-11-21T12:00:32Z",
}),
"soft: 2023-11-21 12:00:32+00:00 - ...",
)

def test_soft_end_only(self):
self.assertEqual(
human_readable.time_window({
"softEndTime": "2023-11-21T21:00:00Z",
}),
"soft: ... - 2023-11-21 21:00:00+00:00",
)

def test_hard_and_soft(self):
self.assertEqual(
human_readable.time_window({
"startTime": "2023-11-21T08:00:00Z",
"endTime": "2023-11-21T22:00:00Z",
"softStartTime": "2023-11-21T12:00:32Z",
"softEndTime": "2023-11-21T19:00:00Z",
}),
"2023-11-21 08:00:00+00:00 - 2023-11-21 22:00:00+00:00"
" soft: 2023-11-21 12:00:32+00:00 - 2023-11-21 19:00:00+00:00",
)


class TimeWindowsTest(unittest.TestCase):
"""Tests for time_windows."""

def test_none(self):
self.assertEqual(human_readable.time_windows(None), "")

def test_empty(self):
self.assertEqual(human_readable.time_windows(()), "")

def test_one_time_window(self):
self.assertEqual(
human_readable.time_windows(({"startTime": "2023-11-21T08:00:00Z"},)),
"2023-11-21 08:00:00+00:00 - ...",
)

def test_multiple_time_windows(self):
self.assertEqual(
human_readable.time_windows((
{
"startTime": "2023-11-21T12:00:32Z",
"endTime": "2023-11-21T17:00:00Z",
},
{
"softStartTime": "2023-11-21T12:00:32Z",
},
)),
"2023-11-21 12:00:32+00:00 - 2023-11-21 17:00:00+00:00"
" | soft: 2023-11-21 12:00:32+00:00 - ...",
)

def test_non_default_separator(self):
self.assertEqual(
human_readable.time_windows(
(
{
"startTime": "2023-11-21T12:00:32Z",
"endTime": "2023-11-21T17:00:00Z",
},
{
"softStartTime": "2023-11-21T12:00:32Z",
},
),
separator="\n",
),
"2023-11-21 12:00:32+00:00 - 2023-11-21 17:00:00+00:00"
"\nsoft: 2023-11-21 12:00:32+00:00 - ...",
)


class TimeStringOrDefault(unittest.TestCase):

def test_default(self):
self.assertEqual(human_readable.timestring_or_default(None, "..."), "...")

def test_not_default(self):
self.assertEqual(
human_readable.timestring_or_default("2023-12-21T16:32:00Z", "..."),
"2023-12-21 16:32:00+00:00",
)


class TransitionDurationTest(unittest.TestCase):
"""Tests for transition_duration."""

Expand Down

0 comments on commit 5fbcc68

Please sign in to comment.