Show times in a configurable time zone
Nightly Build / nightly-build (push) Successful in 1m32s
🧪 Test Plugin / test (push) Successful in 48s
🚀 Release Plugin / build-and-release (push) Successful in 1m18s
🏗️ Build Plugin / build (push) Successful in 12m28s

SRG sends times with an offset (e.g. 13:55+02:00). They are stored as
DateTime, which deserialisation converts to server-local time, and two
places then wrote that straight into text: the "[dd.MM HH:mm]" prefix
on upcoming livestream names and the timestamp in recording file names
(DateTime.Now). On a server running in UTC both were two hours behind
Swiss time, e.g. a qualifying starting at 13:55 was listed as 11:55.

A new "Display Time Zone" setting (IANA id, e.g. Europe/Zurich) now
decides the zone for both; empty keeps the server's zone, and an
unknown id falls back to it rather than breaking the listing.
Utilities/DisplayTime does the conversion.

Also fixes PremiereDate on upcoming livestreams, which was set to that
server-local value although Jellyfin expects UTC. It was only correct
on UTC servers; it is now converted, like the other channel items.

Verified on simulated UTC and Europe/Paris servers: the same instant
gives 11:55 / 13:55 / 07:55 for server zone / Europe/Zurich /
America/New_York, and PremiereDate is 11:55 UTC on both.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-25 19:29:05 -04:00
co-authored by Claude Opus 5.5
parent 536fef85a8
commit 95ce4edf9b
5 changed files with 60 additions and 3 deletions
@@ -0,0 +1,42 @@
using System;
namespace Jellyfin.Plugin.SRFPlay.Utilities;
/// <summary>
/// Converts times into the time zone the plugin writes into text (item names, file names).
/// SRG sends times with an offset, and DateTime deserialisation turns them into server-local
/// values, so without this the text follows whatever zone the server happens to run in.
/// </summary>
public static class DisplayTime
{
/// <summary>
/// Converts a time to the configured display time zone (the server's if none is set).
/// </summary>
/// <param name="value">The time. Unspecified values are treated as server-local, like deserialised ones.</param>
/// <returns>The same instant expressed in the display time zone.</returns>
public static DateTime From(DateTime value)
{
return TimeZoneInfo.ConvertTimeFromUtc(value.ToUniversalTime(), GetZone());
}
/// <summary>
/// Gets the current time in the display time zone.
/// </summary>
/// <returns>The current time in the display time zone.</returns>
public static DateTime Now()
{
return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, GetZone());
}
private static TimeZoneInfo GetZone()
{
var id = Plugin.Instance?.Configuration.DisplayTimeZone;
if (string.IsNullOrWhiteSpace(id))
{
return TimeZoneInfo.Local;
}
// An unknown id falls back to the server's zone rather than breaking listings.
return TimeZoneInfo.TryFindSystemTimeZoneById(id.Trim(), out var zone) ? zone : TimeZoneInfo.Local;
}
}