diff --git a/app/Helpers/components_helper.php b/app/Helpers/components_helper.php
index 58f80a3a7a258009bde7c4d14da1e05e2521da4d..fc61c0cadafc2fc7b2398eecbfd3ac2168954966 100644
--- a/app/Helpers/components_helper.php
+++ b/app/Helpers/components_helper.php
@@ -514,3 +514,24 @@ if (! function_exists('audio_player')) {
         CODE_SAMPLE;
     }
 }
+
+// ------------------------------------------------------------------------
+
+if (! function_exists('relative_time')) {
+    function relative_time(Time $time, string $class = ''): string
+    {
+        $translatedDate = lang('Common.mediumDate', [$time]);
+        $datetime = $time->toDateTimeString();
+
+        return <<<CODE_SAMPLE
+            <time-ago class="${class}" datetime="${datetime}">
+                <time
+                    itemprop="published"
+                    datetime="${datetime}"
+                    title="${time}">${translatedDate}</time>
+            </time-ago>
+        CODE_SAMPLE;
+    }
+}
+
+// ------------------------------------------------------------------------
diff --git a/app/Resources/js/admin.ts b/app/Resources/js/admin.ts
index 79f83272161367a9b5ff9139995b35bf47964eaf..9f9522cb3d82b80f6fd6c773ea9ede0b5b5eed43 100644
--- a/app/Resources/js/admin.ts
+++ b/app/Resources/js/admin.ts
@@ -1,4 +1,5 @@
 import "@github/markdown-toolbar-element";
+import "@github/time-elements";
 import ClientTimezone from "./modules/ClientTimezone";
 import Clipboard from "./modules/Clipboard";
 import DateTimePicker from "./modules/DateTimePicker";
diff --git a/app/Resources/js/podcast.ts b/app/Resources/js/podcast.ts
index 67235fe7829d31ec8d0f3a9136c9dececd17c0cf..f3036ec29722eb51b69344ec5eeececbb1f3dd64 100644
--- a/app/Resources/js/podcast.ts
+++ b/app/Resources/js/podcast.ts
@@ -1,3 +1,4 @@
+import "@github/time-elements";
 import Dropdown from "./modules/Dropdown";
 import Time from "./modules/Time";
 import Toggler from "./modules/Toggler";
diff --git a/app/Views/admin/episode/publish_edit.php b/app/Views/admin/episode/publish_edit.php
index d71485ddf86a941829e412b1369b2952d2c91640..8d273db3f0665747b4eb03f30c410ec184357b1a 100644
--- a/app/Views/admin/episode/publish_edit.php
+++ b/app/Views/admin/episode/publish_edit.php
@@ -42,12 +42,7 @@
                 <span class="text-sm text-gray-500 truncate">@<?= $podcast
                                                                     ->actor->username ?></span>
             </p>
-            <time class="text-xs text-gray-500" itemprop="published" datetime="<?= $status->published_at->format(
-                                                                                    DateTime::ATOM,
-                                                                                ) ?>" title="<?= $status->published_at ?>"><?= lang(
-                                                            'Common.mediumDate',
-                                                            [$status->published_at],
-                                                        ) ?></time>
+            <?= relative_time($status->published_at, 'text-xs text-gray-500') ?>
         </div>
     </div>
     <div class="px-4 mb-2">
@@ -78,13 +73,7 @@
                     ) ?>
                 </div>
                 <div class="text-xs text-gray-600">
-                    <time itemprop="published" datetime="<?= $episode->published_at->format(
-                                                                DateTime::ATOM,
-                                                            ) ?>" title="<?= $episode->published_at ?>">
-                        <?= lang('Common.mediumDate', [
-                            $episode->published_at,
-                        ]) ?>
-                    </time>
+                    <?= relative_time($episode->published_at) ?>
                     <span class="mx-1">•</span>
                     <time datetime="PT<?= $episode->audio_file_duration ?>S">
                         <?= format_duration($episode->audio_file_duration) ?>
diff --git a/app/Views/podcast/_partials/episode_card.php b/app/Views/podcast/_partials/episode_card.php
index 9da514f0ecd1a91af9d33c99ce5a06d926755dc2..1aff9de6b664e8b8e9766715e7d6cd23a64730c8 100644
--- a/app/Views/podcast/_partials/episode_card.php
+++ b/app/Views/podcast/_partials/episode_card.php
@@ -21,11 +21,7 @@
                     <span class="mx-1">-</span>
                     <?= $episode->title ?>
                 </h2>
-                <time class="text-xs whitespace-nowrap" itemprop="published" datetime="<?= $episode->published_at->format(DateTime::ATOM,) ?>" title="<?= $episode->published_at ?>">
-                    <?= lang('Common.mediumDate', [
-                        $episode->published_at,
-                    ]) ?>
-                </time>
+                <?= relative_time($episode->published_at, 'text-xs whitespace-nowrap') ?>
             </a>
             <div class="flex mt-auto gap-x-4">
                 <?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype, 'mt-auto') ?>
diff --git a/app/Views/podcast/_partials/episode_preview_card.php b/app/Views/podcast/_partials/episode_preview_card.php
index 15b5292c043d185cee1d75f7d9dcccc56c829fd4..25650339d231d9bc75de4d1cd695946c82f68e3d 100644
--- a/app/Views/podcast/_partials/episode_preview_card.php
+++ b/app/Views/podcast/_partials/episode_preview_card.php
@@ -19,13 +19,7 @@
                 <span class="mx-1">-</span>
                 <?= $episode->title ?>
             </div>
-            <time
-                class="text-xs"
-                itemprop="published"
-                datetime="<?= $episode->published_at->format(DateTime::ATOM) ?>"
-                title="<?= $episode->published_at ?>">
-                <?= lang('Common.mediumDate', [$episode->published_at]) ?>
-            </time> 
+            <?= relative_time($episode->published_at, 'text-xs whitespace-nowrap') ?>
         </a>
         <?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype, 'mt-auto') ?>
     </div>
diff --git a/app/Views/podcast/_partials/reblog.php b/app/Views/podcast/_partials/reblog.php
index bcf979ea74664bd8f2ad4bb3113f5d7d6d1c3507..2abc307e99b62a17b21cc2304b586adbb533cd86 100644
--- a/app/Views/podcast/_partials/reblog.php
+++ b/app/Views/podcast/_partials/reblog.php
@@ -25,11 +25,7 @@
             </a>
             <a href="<?= route_to('status', $podcast->handle, $status->id) ?>"
             class="text-xs text-gray-500">
-                <time
-                itemprop="published"
-                datetime="<?= $status->published_at->format(DateTime::ATOM) ?>"
-                title="<?= $status->published_at ?>"
-                ><?= lang('Common.mediumDate', [$status->published_at]) ?></time>
+                <?= relative_time($status->published_at) ?>
             </a>
         </div>
     </header>
diff --git a/app/Views/podcast/_partials/reblog_authenticated.php b/app/Views/podcast/_partials/reblog_authenticated.php
index a4d12387654693b6d0656ec314f58a16c005488d..11c5d3012e7931ee6b1cbfd83184aa81fccacfb1 100644
--- a/app/Views/podcast/_partials/reblog_authenticated.php
+++ b/app/Views/podcast/_partials/reblog_authenticated.php
@@ -25,11 +25,7 @@
             </a>
             <a href="<?= route_to('status', $podcast->handle, $status->id) ?>"
             class="text-xs text-gray-500">
-                <time
-                itemprop="published"
-                datetime="<?= $status->published_at->format(DateTime::ATOM) ?>"
-                title="<?= $status->published_at ?>"
-                ><?= lang('Common.mediumDate', [$status->published_at]) ?></time>
+                <?= relative_time($status->published_at) ?>
             </a>
         </div>
     </header>
diff --git a/app/Views/podcast/_partials/reply.php b/app/Views/podcast/_partials/reply.php
index 8fb48bf8b2d2faf5c6539d82310fdfee3329eb6f..12977a3608ae68821ce6d8e972f3e1fcba34e71a 100644
--- a/app/Views/podcast/_partials/reply.php
+++ b/app/Views/podcast/_partials/reply.php
@@ -11,12 +11,7 @@
     ->display_name ?><span class="ml-1 text-sm font-normal text-gray-600">@<?= $reply
     ->actor->username .
     ($reply->actor->is_local ? '' : '@' . $reply->actor->domain) ?></span></a>
-            <time
-            class="flex-shrink-0 ml-auto text-xs text-gray-600"
-            itemprop="published"
-            datetime="<?= $reply->published_at->format(DateTime::ATOM) ?>"
-            title="<?= $reply->published_at ?>"
-            ><?= lang('Common.mediumDate', [$reply->published_at]) ?></time>
+            <?= relative_time($status->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
         </header>
         <p class="mb-2 status-content"><?= $reply->message_html ?></p>
         <?php if ($reply->has_preview_card): ?>
diff --git a/app/Views/podcast/_partials/reply_authenticated.php b/app/Views/podcast/_partials/reply_authenticated.php
index ab264ce53d1693adeb2b6d9c287ec6b28b7af998..1d85461cc949be7cec42e70d7922765ee37a58d0 100644
--- a/app/Views/podcast/_partials/reply_authenticated.php
+++ b/app/Views/podcast/_partials/reply_authenticated.php
@@ -11,12 +11,7 @@
     ->display_name ?><span class="ml-1 text-sm font-normal text-gray-600">@<?= $reply
     ->actor->username .
     ($reply->actor->is_local ? '' : '@' . $reply->actor->domain) ?></span></a>
-            <time
-            class="flex-shrink-0 ml-auto text-xs text-gray-600"
-            itemprop="published"
-            datetime="<?= $reply->published_at->format(DateTime::ATOM) ?>"
-            title="<?= $reply->published_at ?>"
-            ><?= lang('Common.mediumDate', [$reply->published_at]) ?></time>
+            <?= relative_time($status->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
         </header>
         <p class="mb-2 status-content"><?= $reply->message_html ?></p>
         <?php if ($reply->has_preview_card): ?>
diff --git a/app/Views/podcast/_partials/status.php b/app/Views/podcast/_partials/status.php
index 9d3b0f186ecb7d36185466d4da4e51526ef743b3..21d3d0807ce7399410d6bb66786405d786dc012d 100644
--- a/app/Views/podcast/_partials/status.php
+++ b/app/Views/podcast/_partials/status.php
@@ -18,11 +18,7 @@
             </a>
             <a href="<?= route_to('status', $podcast->handle, $status->id) ?>"
             class="text-xs text-gray-500">
-                <time
-                itemprop="published"
-                datetime="<?= $status->published_at->format(DateTime::ATOM) ?>"
-                title="<?= $status->published_at ?>"
-                ><?= lang('Common.mediumDate', [$status->published_at]) ?></time>
+                <?= relative_time($status->published_at) ?>
             </a>
         </div>
     </header>
diff --git a/app/Views/podcast/_partials/status_authenticated.php b/app/Views/podcast/_partials/status_authenticated.php
index 3a1771c900cb6fe66b865d49920aaa6bf87aa88d..143be143fcc614e91ed46084ffd1cc53263bcd00 100644
--- a/app/Views/podcast/_partials/status_authenticated.php
+++ b/app/Views/podcast/_partials/status_authenticated.php
@@ -18,11 +18,7 @@
             </a>
             <a href="<?= route_to('status', $podcast->handle, $status->id) ?>"
             class="text-xs text-gray-500">
-                <time
-                itemprop="published"
-                datetime="<?= $status->published_at->format(DateTime::ATOM) ?>"
-                title="<?= $status->published_at ?>"
-                ><?= lang('Common.mediumDate', [$status->published_at]) ?></time>
+                <?= relative_time($status->published_at) ?>
             </a>
         </div>
     </header>
diff --git a/app/Views/podcast/episode.php b/app/Views/podcast/episode.php
index 834de56a3418380291c52eab49f05a667d6b9d14..5d7fb5550ef248c235bbd6014c59b5baab53e848 100644
--- a/app/Views/podcast/episode.php
+++ b/app/Views/podcast/episode.php
@@ -59,13 +59,7 @@
                     'text-gray-700',
                 ) ?>
                 <div class="mb-4 text-xs">
-                    <time pubdate datetime="<?= $episode->published_at->format(
-                        DateTime::ATOM,
-                    ) ?>" title="<?= $episode->published_at ?>">
-                        <?= lang('Common.mediumDate', [
-                            $episode->published_at,
-                        ]) ?>
-                    </time>
+                    <?= relative_time($episode->published_at) ?>
                     <span class="mx-1">•</span>
                     <time datetime="PT<?= $episode->audio_file_duration ?>S">
                         <?= format_duration($episode->audio_file_duration) ?>
diff --git a/app/Views/podcast/episode_authenticated.php b/app/Views/podcast/episode_authenticated.php
index 695cddd0cf36567595d6178ac9e2c54f8d792ed4..743230cce4258cb9107cf33bb030a2de965f514b 100644
--- a/app/Views/podcast/episode_authenticated.php
+++ b/app/Views/podcast/episode_authenticated.php
@@ -59,13 +59,7 @@
                     'text-gray-700',
                 ) ?>
                 <div class="mb-4 text-xs">
-                    <time pubdate datetime="<?= $episode->published_at->format(
-                        DateTime::ATOM,
-                    ) ?>" title="<?= $episode->published_at ?>">
-                        <?= lang('Common.mediumDate', [
-                            $episode->published_at,
-                        ]) ?>
-                    </time>
+                    <?= relative_time($episode->published_at) ?>
                     <span class="mx-1">•</span>
                     <time datetime="PT<?= $episode->audio_file_duration ?>S">
                         <?= format_duration($episode->audio_file_duration) ?>
diff --git a/package-lock.json b/package-lock.json
index 098dd27692248b52a27aec8e730299812165145d..605508c75853537e5d1475925d2169c4354ea46a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
         "@amcharts/amcharts4": "^4.10.20",
         "@amcharts/amcharts4-geodata": "^4.1.21",
         "@github/markdown-toolbar-element": "^1.5.1",
+        "@github/time-elements": "^3.1.2",
         "@popperjs/core": "^2.9.2",
         "@vime/core": "^5.0.33",
         "choices.js": "^9.0.1",
@@ -1039,6 +1040,11 @@
       "resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-1.5.3.tgz",
       "integrity": "sha512-fNgAuHBWWzqxMvkTMM7ijqPryLmnPVOi4OWp8YyUC74YgtJCRvQyL7gl9SzNLMXmGQxiRVIYbUJxa61X1oRObw=="
     },
+    "node_modules/@github/time-elements": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@github/time-elements/-/time-elements-3.1.2.tgz",
+      "integrity": "sha512-YVtZVLBikP6I7na22kfB9PKIseISwX41MFJ7lPuNz1VVH2IR5cpRRU6F1X6kcchPChljuvMUR4OiwMWHOJQ8kQ=="
+    },
     "node_modules/@humanwhocodes/config-array": {
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
@@ -20246,6 +20252,11 @@
       "resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-1.5.3.tgz",
       "integrity": "sha512-fNgAuHBWWzqxMvkTMM7ijqPryLmnPVOi4OWp8YyUC74YgtJCRvQyL7gl9SzNLMXmGQxiRVIYbUJxa61X1oRObw=="
     },
+    "@github/time-elements": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@github/time-elements/-/time-elements-3.1.2.tgz",
+      "integrity": "sha512-YVtZVLBikP6I7na22kfB9PKIseISwX41MFJ7lPuNz1VVH2IR5cpRRU6F1X6kcchPChljuvMUR4OiwMWHOJQ8kQ=="
+    },
     "@humanwhocodes/config-array": {
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
diff --git a/package.json b/package.json
index 077fcc0173c22fb9d1b313c57caf0758b53ba1c6..005821c76719d75867ff6eb36db6dfd3edd279d4 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
     "@amcharts/amcharts4-geodata": "^4.1.21",
     "@amcharts/amcharts4": "^4.10.20",
     "@github/markdown-toolbar-element": "^1.5.1",
+    "@github/time-elements": "^3.1.2",
     "@popperjs/core": "^2.9.2",
     "@vime/core": "^5.0.33",
     "choices.js": "^9.0.1",