From dffdcc53103f429509f5ac7d3520694c4f66008f Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 5 Sep 2025 13:54:32 -0400 Subject: Add Duration Formatting Function with Tests Implement a new `format_duration` function to convert seconds into a human-readable time format. The function handles various duration scenarios, including minutes, hours, and mixed time representations. Added comprehensive test cases to validate the formatting logic, including edge cases and rounding behavior. --- Biz/PodcastItLater/Web.py | 66 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 3 deletions(-) (limited to 'Biz/PodcastItLater') diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py index a4c3c16..ee63b60 100644 --- a/Biz/PodcastItLater/Web.py +++ b/Biz/PodcastItLater/Web.py @@ -79,6 +79,36 @@ RSS_CONFIG = { } +def format_duration(seconds: int | None) -> str: + """Format duration from seconds to human-readable format. + + Examples: + 300 -> "5m" + 3840 -> "1h 4m" + 11520 -> "3h 12m" + """ + if seconds is None or seconds <= 0: + return "Unknown" + + # Constants for time conversion + seconds_per_minute = 60 + minutes_per_hour = 60 + + # Round up to nearest minute + minutes = (seconds + seconds_per_minute - 1) // seconds_per_minute + + if minutes < minutes_per_hour: + return f"{minutes}m" + + hours = minutes // minutes_per_hour + remaining_minutes = minutes % minutes_per_hour + + if remaining_minutes == 0: + return f"{hours}h" + + return f"{hours}h {remaining_minutes}m" + + def extract_og_metadata(url: str) -> tuple[str | None, str | None]: """Extract Open Graph title and author from URL. @@ -409,9 +439,7 @@ class EpisodeList(Component[AnyChildren, EpisodeListAttrs]): episode_items = [] for episode in episodes: - duration_str = ( - f"{episode['duration']}s" if episode["duration"] else "Unknown" - ) + duration_str = format_duration(episode.get("duration")) episode_items.append( html.div( html.h4(episode["title"]), @@ -970,6 +998,37 @@ class BaseWebTest(Test.TestCase): Core.Database.teardown() +class TestDurationFormatting(Test.TestCase): + """Test duration formatting functionality.""" + + def test_format_duration_minutes_only(self) -> None: + """Test formatting durations less than an hour.""" + self.assertEqual(format_duration(60), "1m") + self.assertEqual(format_duration(240), "4m") + self.assertEqual(format_duration(300), "5m") + self.assertEqual(format_duration(3599), "60m") + + def test_format_duration_hours_and_minutes(self) -> None: + """Test formatting durations with hours and minutes.""" + self.assertEqual(format_duration(3600), "1h") + self.assertEqual(format_duration(3840), "1h 4m") + self.assertEqual(format_duration(11520), "3h 12m") + self.assertEqual(format_duration(7320), "2h 2m") + + def test_format_duration_round_up(self) -> None: + """Test that seconds are rounded up to nearest minute.""" + self.assertEqual(format_duration(61), "2m") + self.assertEqual(format_duration(119), "2m") + self.assertEqual(format_duration(121), "3m") + self.assertEqual(format_duration(3601), "1h 1m") + + def test_format_duration_edge_cases(self) -> None: + """Test edge cases for duration formatting.""" + self.assertEqual(format_duration(None), "Unknown") + self.assertEqual(format_duration(0), "Unknown") + self.assertEqual(format_duration(-100), "Unknown") + + class TestAuthentication(BaseWebTest): """Test authentication functionality.""" @@ -1509,6 +1568,7 @@ def test() -> None: Test.run( App.Area.Test, [ + TestDurationFormatting, TestAuthentication, TestArticleSubmission, TestRSSFeed, -- cgit v1.2.3