Skip to content

Commit 5d0018d

Browse files
authoredMar 5, 2025
Support DateOnly / TimeOnly in DynamoDBContext (#3677)
* Add support for DateOnly / TimeOnly in DynamoDbContext * Add test that saved DateOnly is successully read as DateTime * Add fallback to non-exact format during parsing * Add DevConfig changelog
1 parent b4a9df9 commit 5d0018d

File tree

5 files changed

+131
-5
lines changed

5 files changed

+131
-5
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"services": [
3+
{
4+
"serviceName": "DynamoDBv2",
5+
"type": "patch",
6+
"changeLogMessages": [
7+
"Add Support for DateOnly and TimeOnly in DynamoDB high level libraries. This support is available in .NET 8 and above."
8+
]
9+
}
10+
]
11+
}

‎sdk/src/Services/DynamoDBv2/Custom/Conversion/DynamoDBEntryConversion.cs

+10-2
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ public bool TryConvertFromEntry([DynamicallyAccessedMembers(DynamicallyAccessedM
322322
return converter.TryFromEntry(entry, outputType, out value);
323323
}
324324

325-
#endregion
325+
#endregion
326326

327327
#region Internal members
328328

@@ -378,7 +378,7 @@ internal PrimitiveList ItemsToPrimitiveList(IEnumerable items)
378378
return pl;
379379
}
380380

381-
#endregion
381+
#endregion
382382

383383
#region Private members
384384

@@ -413,6 +413,10 @@ private void SetV1Converters()
413413
AddConverter(new BoolConverterV1());
414414
AddConverter(new PrimitiveCollectionConverterV1());
415415
AddConverter(new DictionaryConverterV1());
416+
#if NET8_0_OR_GREATER
417+
AddConverter(new DateOnlyConverterV1());
418+
AddConverter(new TimeOnlyConverterV1());
419+
#endif
416420
}
417421

418422
private void SetV2Converters()
@@ -438,6 +442,10 @@ private void SetV2Converters()
438442
AddConverter(new EnumConverterV2());
439443
AddConverter(new BoolConverterV2());
440444
AddConverter(new CollectionConverterV2());
445+
#if NET8_0_OR_GREATER
446+
AddConverter(new DateOnlyConverterV2());
447+
AddConverter(new TimeOnlyConverterV2());
448+
#endif
441449
}
442450

443451
// Converts items to Primitives.

‎sdk/src/Services/DynamoDBv2/Custom/Conversion/SchemaV1.cs

+49-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ internal class ByteConverterV1 : Converter<byte>
3636
{
3737
public override IEnumerable<Type> GetTargetTypes()
3838
{
39-
return new[] { typeof(byte), typeof(Nullable<byte>) };
39+
return new[] { typeof(byte), typeof(Nullable<byte>) };
4040
}
4141

4242
protected override bool TryTo(byte value, out Primitive p)
@@ -444,7 +444,53 @@ private static Enum ConvertEnum(string s, Type targetType)
444444
}
445445
}
446446

447-
#endregion
447+
#if NET8_0_OR_GREATER
448+
internal class DateOnlyConverterV1 : Converter<DateOnly>
449+
{
450+
private const string DateOnlyFormat = "yyyy-MM-dd";
451+
452+
public override IEnumerable<Type> GetTargetTypes()
453+
{
454+
return new[] { typeof(DateOnly), typeof(DateOnly?) };
455+
}
456+
457+
protected override bool TryTo(DateOnly value, out Primitive p)
458+
{
459+
p = new Primitive(value.ToString(DateOnlyFormat, CultureInfo.InvariantCulture), DynamoDBEntryType.String);
460+
return true;
461+
}
462+
463+
protected override bool TryFrom(Primitive p, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)] Type targetType, out DateOnly result)
464+
{
465+
return DateOnly.TryParseExact(p.StringValue, DateOnlyFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)
466+
|| DateOnly.TryParse(p.StringValue, CultureInfo.InvariantCulture, out result);
467+
}
468+
}
469+
470+
internal class TimeOnlyConverterV1 : Converter<TimeOnly>
471+
{
472+
private const string TimeOnlyFormat = "HH:mm:ss.fff";
473+
474+
public override IEnumerable<Type> GetTargetTypes()
475+
{
476+
return new[] { typeof(TimeOnly), typeof(TimeOnly?) };
477+
}
478+
479+
protected override bool TryTo(TimeOnly value, out Primitive p)
480+
{
481+
p = new Primitive(value.ToString(TimeOnlyFormat, CultureInfo.InvariantCulture), DynamoDBEntryType.String);
482+
return true;
483+
}
484+
485+
protected override bool TryFrom(Primitive p, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)] Type targetType, out TimeOnly result)
486+
{
487+
return TimeOnly.TryParseExact(p.StringValue, TimeOnlyFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)
488+
|| TimeOnly.TryParse(p.StringValue, CultureInfo.InvariantCulture, out result);
489+
}
490+
}
491+
#endif
492+
493+
#endregion
448494

449495
#region Converters supporting reading V2 DDB items, but writing V1 items
450496

@@ -569,5 +615,5 @@ public override bool TryTo(object value, out Document d)
569615
}
570616
}
571617

572-
#endregion
618+
#endregion
573619
}

‎sdk/src/Services/DynamoDBv2/Custom/Conversion/SchemaV2.cs

+8
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ internal class DictionaryConverterV2 : DictionaryConverterV1
8686
internal class EnumConverterV2 : EnumConverterV1
8787
{ }
8888

89+
#if NET8_0_OR_GREATER
90+
internal class DateOnlyConverterV2 : DateOnlyConverterV1
91+
{ }
92+
93+
internal class TimeOnlyConverterV2 : TimeOnlyConverterV1
94+
{ }
95+
#endif
96+
8997
#endregion
9098

9199
/// <summary>

‎sdk/test/Services/DynamoDBv2/UnitTests/Custom/DynamoDBEntryConversionTests.cs

+53
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,59 @@ public void V2_ConvertToEntry_HashSet()
244244
Assert.AreEqual(expectedList, entry);
245245
}
246246

247+
#if NET8_0_OR_GREATER
248+
[DataTestMethod]
249+
[DynamicData((nameof(DynamoDBEntryConversions)))]
250+
public void ConvertToEntry_DateOnly(DynamoDBEntryConversion conversion)
251+
{
252+
var dateOnly = new DateOnly(2024, 07, 03);
253+
var entry = conversion.ConvertToEntry(dateOnly);
254+
Assert.AreEqual(new Primitive("2024-07-03", false), entry);
255+
}
256+
257+
[DataTestMethod]
258+
[DynamicData((nameof(DynamoDBEntryConversions)))]
259+
public void ConvertFromEntry_DateOnly(DynamoDBEntryConversion conversion)
260+
{
261+
var entry = new Primitive("2024-07-03", false);
262+
var actualDateOnly = conversion.ConvertFromEntry<DateOnly>(entry);
263+
var expectedDateOnly = new DateOnly(2024, 07, 03);
264+
Assert.AreEqual(expectedDateOnly, actualDateOnly);
265+
}
266+
267+
[DataTestMethod]
268+
[DynamicData((nameof(DynamoDBEntryConversions)))]
269+
public void ConvertToEntry_TimeOnly(DynamoDBEntryConversion conversion)
270+
{
271+
var timeOnly = new TimeOnly(18, 31, 56, 123);
272+
var entry = conversion.ConvertToEntry(timeOnly);
273+
Assert.AreEqual(new Primitive("18:31:56.123", false), entry);
274+
}
275+
276+
[DataTestMethod]
277+
[DynamicData((nameof(DynamoDBEntryConversions)))]
278+
public void ConvertFromEntry_TimeOnly(DynamoDBEntryConversion conversion)
279+
{
280+
var entry = new Primitive("18:31:56.123", false);
281+
var actualTimeOnly = conversion.ConvertFromEntry<TimeOnly>(entry);
282+
var expectedTimeOnly = new TimeOnly(18, 31, 56, 123);
283+
Assert.AreEqual(expectedTimeOnly, actualTimeOnly);
284+
}
285+
286+
[DataTestMethod]
287+
[DynamicData((nameof(DynamoDBEntryConversions)))]
288+
public void ConvertFromEntry_ShouldBeAbleToReadDateOnlyAsDateTime(DynamoDBEntryConversion conversion)
289+
{
290+
var dateOnly = new DateOnly(2024, 07, 03);
291+
var entry = conversion.ConvertToEntry(dateOnly);
292+
293+
var actualDateTime = conversion.ConvertFromEntry<DateTime>(entry);
294+
var expectedDateTime = new DateTime(2024, 07, 03, 0, 0, 0, DateTimeKind.Utc);
295+
296+
Assert.AreEqual(expectedDateTime, actualDateTime);
297+
}
298+
#endif
299+
247300
private void AssertAllConvertersAreRegistered(DynamoDBEntryConversion conversion, string suffix)
248301
{
249302
var converters = GetConverters(suffix);

0 commit comments

Comments
 (0)
Failed to load comments.