summaryrefslogtreecommitdiff
path: root/Biz/PodcastItLater
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2025-09-05 13:54:32 -0400
committerBen Sima (aider) <ben@bsima.me>2025-09-05 13:54:32 -0400
commitdffdcc53103f429509f5ac7d3520694c4f66008f (patch)
treeb6cd5916a8d88fe20a14f22a05728c759a3e1cca /Biz/PodcastItLater
parentd7213d19bdaf9cbfadfaacf8684287b1b4769587 (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.
Diffstat (limited to 'Biz/PodcastItLater')
-rw-r--r--Biz/PodcastItLater/Web.py66
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,