Skip to content

Commit cda9fba

Browse files
authored
Allow up to 9 fractional second digits for input to Date/Time scalars (#9347)
1 parent 1da8251 commit cda9fba

10 files changed

Lines changed: 77 additions & 24 deletions

File tree

src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/HotChocolate/Core/src/Types/Properties/TypeResources.resx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ Type: `{0}`</value>
10691069
<value>The stability must follow the GraphQL type name rules.</value>
10701070
</data>
10711071
<data name="DateTimeOptions_InputPrecision_InvalidValue" xml:space="preserve">
1072-
<value>InputPrecision must be less than or equal to 7.</value>
1072+
<value>InputPrecision must be less than or equal to 9.</value>
10731073
</data>
10741074
<data name="DateTimeOptions_OutputPrecision_InvalidValue" xml:space="preserve">
10751075
<value>OutputPrecision must be less than or equal to 7.</value>

src/HotChocolate/Core/src/Types/Types/Scalars/DateTimeOptions.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ namespace HotChocolate.Types;
88
/// </summary>
99
public struct DateTimeOptions
1010
{
11-
public const byte DefaultInputPrecision = 7;
11+
public const byte DefaultInputPrecision = 9;
12+
13+
// DateTimeOffset, DateTime, and TimeOnly all have a maximum of 7 fractional second digits.
1214
public const byte DefaultOutputPrecision = 7;
1315

1416
public DateTimeOptions()
@@ -17,17 +19,20 @@ public DateTimeOptions()
1719

1820
/// <summary>
1921
/// Gets the maximum number of fractional second digits to expect when parsing date and time
20-
/// input values.
22+
/// input values. Note that the underlying .NET types (<see cref="DateTimeOffset"/>,
23+
/// <see cref="DateTime"/>, and <see cref="TimeOnly"/>) have a maximum resolution of 7
24+
/// fractional digits (100-nanosecond ticks), so digits beyond the 7th are rounded during
25+
/// parsing.
2126
/// </summary>
2227
/// <exception cref="ArgumentOutOfRangeException">
23-
/// Thrown when the value is greater than 7.
28+
/// Thrown when the value is greater than 9.
2429
/// </exception>
2530
public byte InputPrecision
2631
{
2732
get;
2833
init
2934
{
30-
if (value > 7)
35+
if (value > 9)
3136
{
3237
throw new ArgumentOutOfRangeException(
3338
nameof(InputPrecision),

src/HotChocolate/Core/src/Types/Types/Scalars/DateTimeType.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,9 @@ private Regex GetDateTimeRegex()
196196
4 => DateTimeRegex4(),
197197
5 => DateTimeRegex5(),
198198
6 => DateTimeRegex6(),
199-
_ => DateTimeRegex7()
199+
7 => DateTimeRegex7(),
200+
8 => DateTimeRegex8(),
201+
_ => DateTimeRegex9()
200202
};
201203

202204
[GeneratedRegex(
@@ -238,4 +240,14 @@ private Regex GetDateTimeRegex()
238240
@"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,7})?(Z|[+-][0-9]{2}:[0-9]{2})\z",
239241
RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase)]
240242
private static partial Regex DateTimeRegex7();
243+
244+
[GeneratedRegex(
245+
@"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,8})?(Z|[+-][0-9]{2}:[0-9]{2})\z",
246+
RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase)]
247+
private static partial Regex DateTimeRegex8();
248+
249+
[GeneratedRegex(
250+
@"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})\z",
251+
RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase)]
252+
private static partial Regex DateTimeRegex9();
241253
}

src/HotChocolate/Core/src/Types/Types/Scalars/LocalDateTimeType.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,9 @@ private Regex GetLocalDateTimeRegex()
176176
4 => LocalDateTimeRegex4(),
177177
5 => LocalDateTimeRegex5(),
178178
6 => LocalDateTimeRegex6(),
179-
_ => LocalDateTimeRegex7()
179+
7 => LocalDateTimeRegex7(),
180+
8 => LocalDateTimeRegex8(),
181+
_ => LocalDateTimeRegex9()
180182
};
181183

182184
[GeneratedRegex(@"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\z",
@@ -210,4 +212,12 @@ private Regex GetLocalDateTimeRegex()
210212
[GeneratedRegex(@"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,7})?\z",
211213
RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase)]
212214
private static partial Regex LocalDateTimeRegex7();
215+
216+
[GeneratedRegex(@"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,8})?\z",
217+
RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase)]
218+
private static partial Regex LocalDateTimeRegex8();
219+
220+
[GeneratedRegex(@"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,9})?\z",
221+
RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase)]
222+
private static partial Regex LocalDateTimeRegex9();
213223
}

src/HotChocolate/Core/src/Types/Types/Scalars/LocalTimeType.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,9 @@ private Regex GetLocalTimeRegex()
174174
4 => LocalTimeRegex4(),
175175
5 => LocalTimeRegex5(),
176176
6 => LocalTimeRegex6(),
177-
_ => LocalTimeRegex7()
177+
7 => LocalTimeRegex7(),
178+
8 => LocalTimeRegex8(),
179+
_ => LocalTimeRegex9()
178180
};
179181

180182
[GeneratedRegex(@"^[0-9]{2}:[0-9]{2}:[0-9]{2}\z", RegexOptions.ExplicitCapture)]
@@ -200,4 +202,10 @@ private Regex GetLocalTimeRegex()
200202

201203
[GeneratedRegex(@"^[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,7})?\z", RegexOptions.ExplicitCapture)]
202204
private static partial Regex LocalTimeRegex7();
205+
206+
[GeneratedRegex(@"^[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,8})?\z", RegexOptions.ExplicitCapture)]
207+
private static partial Regex LocalTimeRegex8();
208+
209+
[GeneratedRegex(@"^[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,9})?\z", RegexOptions.ExplicitCapture)]
210+
private static partial Regex LocalTimeRegex9();
203211
}

src/HotChocolate/Core/test/Types.Tests/Types/Scalars/DateTimeOptionsTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public void DefaultConstructor_ShouldSetDefaultPrecisions()
1717
public void DefaultConstants_ShouldBeCorrect()
1818
{
1919
// assert
20-
Assert.Equal(7, DateTimeOptions.DefaultInputPrecision);
20+
Assert.Equal(9, DateTimeOptions.DefaultInputPrecision);
2121
Assert.Equal(7, DateTimeOptions.DefaultOutputPrecision);
2222
}
2323

@@ -30,6 +30,8 @@ public void DefaultConstants_ShouldBeCorrect()
3030
[InlineData(5)]
3131
[InlineData(6)]
3232
[InlineData(7)]
33+
[InlineData(8)]
34+
[InlineData(9)]
3335
public void InputPrecision_ValidValues_ShouldSet(byte precision)
3436
{
3537
// arrange & act
@@ -58,8 +60,6 @@ public void OutputPrecision_ValidValues_ShouldSet(byte precision)
5860
}
5961

6062
[Theory]
61-
[InlineData(8)]
62-
[InlineData(9)]
6363
[InlineData(10)]
6464
[InlineData(255)]
6565
public void InputPrecision_InvalidValues_ShouldThrow(byte precision)

src/HotChocolate/Core/test/Types.Tests/Types/Scalars/DateTimeTypeTests.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,8 @@ public void DateTime_Relaxed_Format_Check()
328328
[InlineData(5, @"^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d{1,5})?(?:[Zz]|[+-]\d{2}:\d{2})$")]
329329
[InlineData(6, @"^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:[Zz]|[+-]\d{2}:\d{2})$")]
330330
[InlineData(7, @"^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d{1,7})?(?:[Zz]|[+-]\d{2}:\d{2})$")]
331+
[InlineData(8, @"^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d{1,8})?(?:[Zz]|[+-]\d{2}:\d{2})$")]
332+
[InlineData(9, @"^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:[Zz]|[+-]\d{2}:\d{2})$")]
331333
public void Pattern_Should_Match_InputPrecision(byte precision, string expectedPattern)
332334
{
333335
// arrange & act
@@ -354,8 +356,8 @@ public static TheoryData<byte, string, DateTimeOffset> ValidInput()
354356
},
355357
{
356358
DateTimeOptions.DefaultInputPrecision,
357-
"2023-12-24T15:30:00.1234567+01:00",
358-
new DateTimeOffset(2023, 12, 24, 15, 30, 0, 123, 456, TimeSpan.FromHours(1)).AddTicks(7)
359+
"2023-12-24T15:30:00.123456789+01:00", // Rounded to ".1234568".
360+
new DateTimeOffset(2023, 12, 24, 15, 30, 0, 123, 456, TimeSpan.FromHours(1)).AddTicks(8)
359361
}
360362
};
361363
}
@@ -376,13 +378,17 @@ public static TheoryData<byte, string> InvalidInput()
376378
// ReSharper disable once GrammarMistakeInComment
377379
// Invalid date (February 30th).
378380
{ DateTimeOptions.DefaultInputPrecision, "2023-02-30T15:30:00Z" },
379-
// More than 7 fractional second digits.
380-
{ DateTimeOptions.DefaultInputPrecision, "2023-12-24T15:30:00.12345678Z" },
381+
// More than 9 fractional second digits.
382+
{ DateTimeOptions.DefaultInputPrecision, "2023-12-24T15:30:00.1234567890Z" },
381383
// Invalid offset (exceeds maximum).
382384
{ DateTimeOptions.DefaultInputPrecision, "2023-12-24T15:30:00+25:00" },
383385
// Invalid offset format.
384386
{ DateTimeOptions.DefaultInputPrecision, "2023-12-24T15:30:00 UTC" },
385387
// Additional cases.
388+
// More than 8 fractional second digits with precision set to 8.
389+
{ 8, "2023-12-24T15:30:00.123456789Z" },
390+
// More than 7 fractional second digits with precision set to 7.
391+
{ 7, "2023-12-24T15:30:00.12345678Z" },
386392
// More than 6 fractional second digits with precision set to 6.
387393
{ 6, "2023-12-24T15:30:00.1234567Z" },
388394
// More than 5 fractional second digits with precision set to 5.

src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalDateTimeTypeTests.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ public void LocalDateTime_Relaxed_Format_Check()
323323
[InlineData(5, @"^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d{1,5})?$")]
324324
[InlineData(6, @"^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?$")]
325325
[InlineData(7, @"^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d{1,7})?$")]
326+
[InlineData(8, @"^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d{1,8})?$")]
327+
[InlineData(9, @"^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?$")]
326328
public void Pattern_Should_Match_InputPrecision(byte precision, string expectedPattern)
327329
{
328330
// arrange & act
@@ -375,8 +377,8 @@ public static TheoryData<byte, string, DateTime> ValidInput()
375377
},
376378
{
377379
DateTimeOptions.DefaultInputPrecision,
378-
"2023-12-24t15:30:00.1234567",
379-
new DateTime(2023, 12, 24, 15, 30, 0, 123, 456).AddTicks(7)
380+
"2023-12-24t15:30:00.123456789", // Rounded to ".1234568".
381+
new DateTime(2023, 12, 24, 15, 30, 0, 123, 456).AddTicks(8)
380382
}
381383
};
382384
}
@@ -399,9 +401,13 @@ public static TheoryData<byte, string> InvalidInput()
399401
// ReSharper disable once GrammarMistakeInComment
400402
// Invalid date (February 30th).
401403
{ DateTimeOptions.DefaultInputPrecision, "2023-02-30T15:30:00" },
402-
// More than 7 fractional second digits.
403-
{ DateTimeOptions.DefaultInputPrecision, "2023-12-24T15:30:00.12345678" },
404+
// More than 9 fractional second digits.
405+
{ DateTimeOptions.DefaultInputPrecision, "2023-12-24T15:30:00.1234567890" },
404406
// Additional cases.
407+
// More than 8 fractional second digits with precision set to 8.
408+
{ 8, "2023-12-24T15:30:00.123456789" },
409+
// More than 7 fractional second digits with precision set to 7.
410+
{ 7, "2023-12-24T15:30:00.12345678" },
405411
// More than 6 fractional second digits with precision set to 6.
406412
{ 6, "2023-12-24T15:30:00.1234567" },
407413
// More than 5 fractional second digits with precision set to 5.

src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalTimeTypeTests.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ public void LocalTime_Relaxed_Format_Check()
323323
[InlineData(5, @"^\d{2}:\d{2}:\d{2}(?:\.\d{1,5})?$")]
324324
[InlineData(6, @"^\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?$")]
325325
[InlineData(7, @"^\d{2}:\d{2}:\d{2}(?:\.\d{1,7})?$")]
326+
[InlineData(8, @"^\d{2}:\d{2}:\d{2}(?:\.\d{1,8})?$")]
327+
[InlineData(9, @"^\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?$")]
326328
public void Pattern_Should_Match_InputPrecision(byte precision, string expectedPattern)
327329
{
328330
// arrange & act
@@ -372,8 +374,8 @@ public static TheoryData<byte, string, TimeOnly> ValidInput()
372374
},
373375
{
374376
DateTimeOptions.DefaultInputPrecision,
375-
"07:30:00.1234567",
376-
new TimeOnly(7, 30, 0, 123, 456).Add(TimeSpan.FromTicks(7))
377+
"07:30:00.123456789", // Rounded to ".1234568".
378+
new TimeOnly(7, 30, 0, 123, 456).Add(TimeSpan.FromTicks(8))
377379
}
378380
};
379381
}
@@ -395,9 +397,13 @@ public static TheoryData<byte, string> InvalidInput()
395397
{ DateTimeOptions.DefaultInputPrecision, "24:00:00" },
396398
// Invalid minute (60).
397399
{ DateTimeOptions.DefaultInputPrecision, "15:60:00" },
398-
// More than 7 fractional second digits.
399-
{ DateTimeOptions.DefaultInputPrecision, "15:30:00.12345678" },
400+
// More than 9 fractional second digits.
401+
{ DateTimeOptions.DefaultInputPrecision, "15:30:00.1234567890" },
400402
// Additional cases.
403+
// More than 8 fractional second digits with precision set to 8.
404+
{ 8, "15:30:00.123456789" },
405+
// More than 7 fractional second digits with precision set to 7.
406+
{ 7, "15:30:00.12345678" },
401407
// More than 6 fractional second digits with precision set to 6.
402408
{ 6, "15:30:00.1234567" },
403409
// More than 5 fractional second digits with precision set to 5.

0 commit comments

Comments
 (0)