diff options
| author | Ben Sima <ben@bsima.me> | 2025-09-05 13:54:32 -0400 |
|---|---|---|
| committer | Ben Sima (aider) <ben@bsima.me> | 2025-09-05 13:54:32 -0400 |
| commit | dffdcc53103f429509f5ac7d3520694c4f66008f (patch) | |
| tree | b6cd5916a8d88fe20a14f22a05728c759a3e1cca | |
| parent | d7213d19bdaf9cbfadfaacf8684287b1b4769587 (diff) | |
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.
| -rw-r--r-- | Biz/PodcastItLater/Web.py | 66 |
1 files changed, 63 insertions, 3 deletions
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, |
