76 lines
2.9 KiB
C#
76 lines
2.9 KiB
C#
using System.Globalization;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace Elternbeirat.PocketBase;
|
|
|
|
/// <summary>
|
|
/// Shared parsing of a PocketBase date string as wall-clock time. The trailing
|
|
/// "Z" is stripped rather than honoured, so the number is taken at face value and
|
|
/// the result carries <see cref="DateTimeKind.Unspecified"/> -- no timezone shift.
|
|
/// </summary>
|
|
internal static class WallClock
|
|
{
|
|
public static DateTime Parse(string raw)
|
|
{
|
|
// Drop a trailing "Z" so DateTime.Parse does not treat the value as UTC
|
|
// and convert it to local time (which would shift 19:30 to 20:30/21:30).
|
|
var value = raw.EndsWith('Z') ? raw[..^1] : raw;
|
|
var parsed = DateTime.Parse(value, CultureInfo.InvariantCulture,
|
|
DateTimeStyles.None);
|
|
return DateTime.SpecifyKind(parsed, DateTimeKind.Unspecified);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads PocketBase date values as local wall-clock time.
|
|
/// <para>
|
|
/// PocketBase stores every date in UTC and serializes it with a trailing "Z"
|
|
/// (e.g. "2026-10-08 19:30:00.000Z"). By project convention the stored number
|
|
/// IS the local time (Europe/Berlin) and the "Z" is ignored -- see the timezone
|
|
/// decision in the data model. This converter therefore parses the value and
|
|
/// returns it as an <see cref="DateTimeKind.Unspecified"/> instant, so no
|
|
/// timezone shift is ever applied when the value is later formatted.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class LocalDateTimeConverter : JsonConverter<DateTime>
|
|
{
|
|
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
{
|
|
var raw = reader.GetString();
|
|
return string.IsNullOrEmpty(raw)
|
|
? default
|
|
: WallClock.Parse(raw);
|
|
}
|
|
|
|
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
|
=> writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss.fff'Z'",
|
|
CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Nullable counterpart of <see cref="LocalDateTimeConverter"/>. PocketBase sends
|
|
/// an empty string for an unset optional date (e.g. an event without an end);
|
|
/// that maps to null.
|
|
/// </summary>
|
|
public sealed class NullableLocalDateTimeConverter : JsonConverter<DateTime?>
|
|
{
|
|
public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
{
|
|
var raw = reader.GetString();
|
|
if (string.IsNullOrEmpty(raw))
|
|
return null;
|
|
|
|
return WallClock.Parse(raw);
|
|
}
|
|
|
|
public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
|
|
{
|
|
if (value is null)
|
|
writer.WriteStringValue("");
|
|
else
|
|
writer.WriteStringValue(value.Value.ToString("yyyy-MM-dd HH:mm:ss.fff'Z'",
|
|
CultureInfo.InvariantCulture));
|
|
}
|
|
}
|