using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Elternbeirat.PocketBase;
///
/// 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 -- no timezone shift.
///
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);
}
}
///
/// Reads PocketBase date values as local wall-clock time.
///
/// 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 instant, so no
/// timezone shift is ever applied when the value is later formatted.
///
///
public sealed class LocalDateTimeConverter : JsonConverter
{
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));
}
///
/// Nullable counterpart of . PocketBase sends
/// an empty string for an unset optional date (e.g. an event without an end);
/// that maps to null.
///
public sealed class NullableLocalDateTimeConverter : JsonConverter
{
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));
}
}