From 297ac3b9ff3ee40474a559314d6fb1c6116a1a06 Mon Sep 17 00:00:00 2001 From: Arjun Narendra Date: Fri, 13 Mar 2026 11:27:28 -0700 Subject: [PATCH 1/2] Add support for parsing DateTime values --- src/Core/Services/ExecutionHelper.cs | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/Core/Services/ExecutionHelper.cs b/src/Core/Services/ExecutionHelper.cs index 79bdc6af9c..3b896ca993 100644 --- a/src/Core/Services/ExecutionHelper.cs +++ b/src/Core/Services/ExecutionHelper.cs @@ -395,9 +395,74 @@ private static bool TryGetPropertyFromParent( SupportedHotChocolateTypes.SINGLE_TYPE => value is IntValueNode intValueNode ? intValueNode.ToSingle() : ((FloatValueNode)value).ToSingle(), SupportedHotChocolateTypes.FLOAT_TYPE => value is IntValueNode intValueNode ? intValueNode.ToDouble() : ((FloatValueNode)value).ToDouble(), SupportedHotChocolateTypes.DECIMAL_TYPE => value is IntValueNode intValueNode ? intValueNode.ToDecimal() : ((FloatValueNode)value).ToDecimal(), + SupportedHotChocolateTypes.DATETIME_TYPE => ParseDateTimeValue(value.Value), + SupportedHotChocolateTypes.DATETIMEOFFSET_TYPE => ParseDateTimeOffsetValue(value.Value), SupportedHotChocolateTypes.UUID_TYPE => Guid.TryParse(value.Value!.ToString(), out Guid guidValue) ? guidValue : value.Value, _ => value.Value }; + + static object? ParseDateTimeValue(object? raw) + { + if (raw is null) + { + return null; + } + + if (raw is DateTime dt) + { + return dt; + } + + if (raw is DateTimeOffset dto) + { + return dto.UtcDateTime; + } + + if (raw is string s) + { + // HotChocolate DateTime inputs are supplied as strings; parse them so DB providers + // (notably PostgreSQL) receive a typed parameter instead of text. + if (DateTimeOffset.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTimeOffset parsedDto)) + { + return parsedDto.UtcDateTime; + } + + if (DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTime parsedDt)) + { + return parsedDt; + } + } + + return raw; + } + + static object? ParseDateTimeOffsetValue(object? raw) + { + if (raw is null) + { + return null; + } + + if (raw is DateTimeOffset dto) + { + return dto; + } + + if (raw is DateTime dt) + { + return new DateTimeOffset(dt); + } + + if (raw is string s) + { + if (DateTimeOffset.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTimeOffset parsedDto)) + { + return parsedDto; + } + } + + return raw; + } } /// From deb85227c95fc9cbb4c0d2b3f64a44bb2062d217 Mon Sep 17 00:00:00 2001 From: Arjun Narendra Date: Tue, 21 Apr 2026 08:52:32 -0700 Subject: [PATCH 2/2] Update parsing to reflect that only support (for now) is for DATETIME_TYPE and ensure that timezone is configured to UTC for standardization --- src/Core/Resolvers/PostgreSqlExecutor.cs | 5 +++ src/Core/Services/ExecutionHelper.cs | 51 ++++++------------------ 2 files changed, 18 insertions(+), 38 deletions(-) diff --git a/src/Core/Resolvers/PostgreSqlExecutor.cs b/src/Core/Resolvers/PostgreSqlExecutor.cs index 70fa0f1079..3c525329c7 100644 --- a/src/Core/Resolvers/PostgreSqlExecutor.cs +++ b/src/Core/Resolvers/PostgreSqlExecutor.cs @@ -84,6 +84,11 @@ private void ConfigurePostgreSqlQueryExecutor() { NpgsqlConnectionStringBuilder builder = new(dataSource.ConnectionString); + if (string.IsNullOrEmpty(builder.Timezone)) + { + builder.Timezone = "UTC"; + } + if (_runtimeConfigProvider.IsLateConfigured) { builder.SslMode = SslMode.VerifyFull; diff --git a/src/Core/Services/ExecutionHelper.cs b/src/Core/Services/ExecutionHelper.cs index 3b896ca993..f3d605e36c 100644 --- a/src/Core/Services/ExecutionHelper.cs +++ b/src/Core/Services/ExecutionHelper.cs @@ -63,7 +63,7 @@ public async ValueTask ExecuteQueryAsync(IMiddlewareContext context) IQueryEngine queryEngine = _queryEngineFactory.GetQueryEngine(ds.DatabaseType); IDictionary parameters = GetParametersFromContext(context); - + if (context.Selection.Type.IsListType()) { Tuple, IMetadata?> result = @@ -396,7 +396,6 @@ private static bool TryGetPropertyFromParent( SupportedHotChocolateTypes.FLOAT_TYPE => value is IntValueNode intValueNode ? intValueNode.ToDouble() : ((FloatValueNode)value).ToDouble(), SupportedHotChocolateTypes.DECIMAL_TYPE => value is IntValueNode intValueNode ? intValueNode.ToDecimal() : ((FloatValueNode)value).ToDecimal(), SupportedHotChocolateTypes.DATETIME_TYPE => ParseDateTimeValue(value.Value), - SupportedHotChocolateTypes.DATETIMEOFFSET_TYPE => ParseDateTimeOffsetValue(value.Value), SupportedHotChocolateTypes.UUID_TYPE => Guid.TryParse(value.Value!.ToString(), out Guid guidValue) ? guidValue : value.Value, _ => value.Value }; @@ -410,7 +409,12 @@ private static bool TryGetPropertyFromParent( if (raw is DateTime dt) { - return dt; + return dt.Kind switch + { + DateTimeKind.Utc => dt, + DateTimeKind.Unspecified => DateTime.SpecifyKind(dt, DateTimeKind.Utc), + _ => dt.ToUniversalTime() + }; } if (raw is DateTimeOffset dto) @@ -421,44 +425,15 @@ private static bool TryGetPropertyFromParent( if (raw is string s) { // HotChocolate DateTime inputs are supplied as strings; parse them so DB providers - // (notably PostgreSQL) receive a typed parameter instead of text. - if (DateTimeOffset.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTimeOffset parsedDto)) + // (notably PostgreSQL) receive a typed UTC parameter instead of text. + if (DateTimeOffset.TryParse( + s, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, + out DateTimeOffset parsedDto)) { return parsedDto.UtcDateTime; } - - if (DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTime parsedDt)) - { - return parsedDt; - } - } - - return raw; - } - - static object? ParseDateTimeOffsetValue(object? raw) - { - if (raw is null) - { - return null; - } - - if (raw is DateTimeOffset dto) - { - return dto; - } - - if (raw is DateTime dt) - { - return new DateTimeOffset(dt); - } - - if (raw is string s) - { - if (DateTimeOffset.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTimeOffset parsedDto)) - { - return parsedDto; - } } return raw;