Files
jsmr/JSMR.Infrastructure/Common/Time/TimeProvider.cs

69 lines
2.3 KiB
C#

namespace JSMR.Infrastructure.Common.Time;
public interface ITimeProvider
{
DateTimeOffset Now();
DateTimeOffset Local(int year, int month, int day, int hour);
DateTimeOffset Local(DateTimeOffset offset);
}
public abstract class TimeProvider : ITimeProvider
{
protected abstract string Id { get; }
protected abstract string[] TimeZoneIds { get; }
private readonly IClock _clock;
private readonly TimeZoneInfo _timeZone;
public TimeProvider(IClock clock)
{
_clock = clock;
_timeZone = ResolveTimeZone();
}
private TimeZoneInfo ResolveTimeZone()
{
foreach (string timeZoneId in TimeZoneIds)
{
if (TimeZoneInfo.TryFindSystemTimeZoneById(timeZoneId, out TimeZoneInfo? timeZoneInfo))
return timeZoneInfo;
}
throw new TimeZoneNotFoundException($"Unable to resolve time zone for: {Id} ({string.Join(" / ", TimeZoneIds)})");
}
public DateTimeOffset Now() => TimeZoneInfo.ConvertTime(_clock.UtcNow, _timeZone);
public DateTimeOffset Local(DateTimeOffset offset) => TimeZoneInfo.ConvertTime(offset, _timeZone);
public DateTimeOffset Local(int year, int month, int day, int hour)
{
DateTime local = new(year, month, day, hour, 0, 0, DateTimeKind.Unspecified);
TimeSpan offset = _timeZone.GetUtcOffset(local);
return new DateTimeOffset(local, offset);
}
public DateTimeOffset CurrentScanAnchor()
{
DateTimeOffset now = Now();
DateTimeOffset midnight = Local(now.Year, now.Month, now.Day, 0);
DateTimeOffset fourPm = Local(now.Year, now.Month, now.Day, 16);
return now >= fourPm ? fourPm : midnight;
}
public DateTimeOffset PreviousScanAnchor(DateTimeOffset scanAnchorTokyo)
{
// Normalize to Tokyo (no-op if already)
var a = TimeZoneInfo.ConvertTime(scanAnchorTokyo, _timeZone);
return a.Hour == 16
? Local(a.Year, a.Month, a.Day, 0)
: Local(a.AddDays(-1).Year, a.AddDays(-1).Month, a.AddDays(-1).Day, 16);
}
}
public class TokyoTimeProvider(IClock clock) : TimeProvider(clock)
{
protected override string Id => "Tokyo Standard Time";
protected override string[] TimeZoneIds => ["Tokyo Standard Time", "Asia/Tokyo"];
}