summaryrefslogtreecommitdiff
path: root/Biz/PodcastItLater
diff options
context:
space:
mode:
Diffstat (limited to 'Biz/PodcastItLater')
-rw-r--r--Biz/PodcastItLater/UI.py36
-rw-r--r--Biz/PodcastItLater/Web.py68
2 files changed, 52 insertions, 52 deletions
diff --git a/Biz/PodcastItLater/UI.py b/Biz/PodcastItLater/UI.py
index 940abd5..99ac29b 100644
--- a/Biz/PodcastItLater/UI.py
+++ b/Biz/PodcastItLater/UI.py
@@ -9,6 +9,42 @@ Common UI components and utilities shared across web pages.
import ludic.html as html
+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
+ seconds_per_hour = 3600
+
+ # Round up to nearest minute
+ minutes = (seconds + seconds_per_minute - 1) // seconds_per_minute
+
+ # Show as minutes only if under 60 minutes (exclusive)
+ # 3599 seconds rounds up to 60 minutes, which we keep as "60m"
+ if minutes <= minutes_per_hour:
+ # If exactly 3600 seconds (already 60 full minutes without rounding)
+ if seconds >= seconds_per_hour:
+ return "1h"
+ 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 create_bootstrap_styles() -> html.style:
"""Load Bootstrap CSS and icons."""
return html.style(
diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py
index 3524be8..3a76e93 100644
--- a/Biz/PodcastItLater/Web.py
+++ b/Biz/PodcastItLater/Web.py
@@ -81,42 +81,6 @@ 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
- seconds_per_hour = 3600
-
- # Round up to nearest minute
- minutes = (seconds + seconds_per_minute - 1) // seconds_per_minute
-
- # Show as minutes only if under 60 minutes (exclusive)
- # 3599 seconds rounds up to 60 minutes, which we keep as "60m"
- if minutes <= minutes_per_hour:
- # If exactly 3600 seconds (already 60 full minutes without rounding)
- if seconds >= seconds_per_hour:
- return "1h"
- 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.
@@ -531,7 +495,7 @@ class EpisodeList(Component[AnyChildren, EpisodeListAttrs]):
episode_items = []
for episode in episodes:
- duration_str = format_duration(episode.get("duration"))
+ duration_str = UI.format_duration(episode.get("duration"))
episode_items.append(
html.div(
html.div(
@@ -1506,30 +1470,30 @@ class TestDurationFormatting(Test.TestCase):
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")
+ self.assertEqual(UI.format_duration(60), "1m")
+ self.assertEqual(UI.format_duration(240), "4m")
+ self.assertEqual(UI.format_duration(300), "5m")
+ self.assertEqual(UI.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")
+ self.assertEqual(UI.format_duration(3600), "1h")
+ self.assertEqual(UI.format_duration(3840), "1h 4m")
+ self.assertEqual(UI.format_duration(11520), "3h 12m")
+ self.assertEqual(UI.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")
+ self.assertEqual(UI.format_duration(61), "2m")
+ self.assertEqual(UI.format_duration(119), "2m")
+ self.assertEqual(UI.format_duration(121), "3m")
+ self.assertEqual(UI.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")
+ self.assertEqual(UI.format_duration(None), "Unknown")
+ self.assertEqual(UI.format_duration(0), "Unknown")
+ self.assertEqual(UI.format_duration(-100), "Unknown")
class TestAuthentication(BaseWebTest):