Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ internal static bool IsBuiltInTypeConversion(

return IsStringToFromSystemTypeConversion(sourceTypeName, targetTypeName) ||
IsStringToFromUriConversion(sourceTypeName, targetTypeName) ||
IsStringToFromEnumConversion(sourceType, targetType, sourceTypeName, targetTypeName);
IsStringToFromEnumConversion(sourceType, targetType, sourceTypeName, targetTypeName) ||
IsDateTimeOffsetToFromDateTimeConversion(sourceTypeName, targetTypeName);
}

private static bool IsStringToFromSystemTypeConversion(
Expand Down Expand Up @@ -126,6 +127,17 @@ private static bool IsStringToFromUriConversion(
(normalizedSource == "System.Uri" && normalizedTarget == "string");
}

private static bool IsDateTimeOffsetToFromDateTimeConversion(
string sourceTypeName,
string targetTypeName)
{
var normalizedSource = sourceTypeName.TrimEnd('?');
var normalizedTarget = targetTypeName.TrimEnd('?');

return (normalizedSource == "System.DateTimeOffset" && normalizedTarget == "System.DateTime") ||
(normalizedSource == "System.DateTime" && normalizedTarget == "System.DateTimeOffset");
}

private static bool IsStringToFromEnumConversion(
ITypeSymbol sourceType,
ITypeSymbol targetType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2518,12 +2518,50 @@ private static string GenerateBuiltInTypeConversion(
return $"bool.Parse({sourcePropertyAccess})";
}

// DateTimeOffset <-> DateTime conversions
if (normalizedSource == "System.DateTimeOffset" && normalizedTarget == "System.DateTime")
{
var sourceIsNullableValue = MappingTypeAnalyzer.IsNullableValueType(prop.SourceProperty.Type);
var targetIsNullableValue = MappingTypeAnalyzer.IsNullableValueType(prop.TargetProperty.Type);

if (sourceIsNullableValue && targetIsNullableValue)
{
return $"{sourcePropertyAccess}?.DateTime";
}

if (sourceIsNullableValue)
{
return $"{sourcePropertyAccess}!.Value.DateTime";
}

return $"{sourcePropertyAccess}.DateTime";
}

if (normalizedSource == "System.DateTime" && normalizedTarget == "System.DateTimeOffset")
{
var sourceIsNullableValue = MappingTypeAnalyzer.IsNullableValueType(prop.SourceProperty.Type);
var targetIsNullableValue = MappingTypeAnalyzer.IsNullableValueType(prop.TargetProperty.Type);

if (sourceIsNullableValue && targetIsNullableValue)
{
var varName = $"__{char.ToLowerInvariant(prop.SourceProperty.Name[0])}{prop.SourceProperty.Name.Substring(1)}";
return $"{sourcePropertyAccess} is {{ }} {varName} ? new global::System.DateTimeOffset({varName}) : null";
}

if (sourceIsNullableValue)
{
return $"new global::System.DateTimeOffset({sourcePropertyAccess}!.Value)";
}

return $"new global::System.DateTimeOffset({sourcePropertyAccess})";
}

// string <-> Uri conversions
if (normalizedSource == "string" && normalizedTarget == "System.Uri")
{
if (sourceIsNullableRef || targetIsNullableRef)
{
return $"{sourcePropertyAccess} is not null ? new global::System.Uri({sourcePropertyAccess}) : null";
return $"!string.IsNullOrEmpty({sourcePropertyAccess}) ? new global::System.Uri({sourcePropertyAccess}) : null";
}

return $"new global::System.Uri({sourcePropertyAccess})";
Expand Down
21 changes: 21 additions & 0 deletions src/Atc.SourceGenerators/Generators/ObjectMappingGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,15 @@ private static bool IsBuiltInTypeConversion(
return true;
}

// DateTimeOffset <-> DateTime
var normalizedSource = sourceTypeName.TrimEnd('?');
var normalizedTarget = targetTypeName.TrimEnd('?');
if ((normalizedSource == "System.DateTimeOffset" && normalizedTarget == "System.DateTime") ||
(normalizedSource == "System.DateTime" && normalizedTarget == "System.DateTimeOffset"))
{
return true;
}

return false;
}

Expand Down Expand Up @@ -1917,6 +1926,18 @@ private static string GenerateBuiltInTypeConversion(
return $"bool.Parse({sourcePropertyAccess})";
}

// DateTimeOffset -> DateTime
if (sourceTypeName == "System.DateTimeOffset" && targetTypeName == "System.DateTime")
{
return $"{sourcePropertyAccess}.DateTime";
}

// DateTime -> DateTimeOffset
if (sourceTypeName == "System.DateTime" && targetTypeName == "System.DateTimeOffset")
{
return $"new global::System.DateTimeOffset({sourcePropertyAccess})";
}

// Fallback (should not reach here if IsBuiltInTypeConversion is correct)
return sourcePropertyAccess;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public static partial class ItemMappings
var (diagnostics, output) = GetGeneratedOutput(source);

Assert.Empty(diagnostics);
Assert.Contains("is not null ? new global::System.Uri(source.Link) : null", output, StringComparison.Ordinal);
Assert.Contains("!string.IsNullOrEmpty(source.Link) ? new global::System.Uri(source.Link) : null", output, StringComparison.Ordinal);
}

[Fact]
Expand Down Expand Up @@ -644,4 +644,164 @@ public static partial class ItemMappings
Assert.Contains("Enum.TryParse<global::TargetNs.StatusType>(source.Status, out var __parsed)", output, StringComparison.Ordinal);
Assert.Contains("(double)source.Value", output, StringComparison.Ordinal);
}

[Fact]
public void Config_Should_Map_DateTimeOffset_To_DateTime()
{
const string source = """
using System;
using Atc.SourceGenerators.Annotations;

namespace SourceNs
{
public class Item
{
public DateTimeOffset Timestamp { get; set; }
}
}

namespace TargetNs
{
public class Item
{
public DateTime Timestamp { get; set; }
}
}

namespace Mappings
{
[MappingConfiguration]
public static partial class ItemMappings
{
public static partial TargetNs.Item MapToItem(this SourceNs.Item source);
}
}
""";

var (diagnostics, output) = GetGeneratedOutput(source);

Assert.Empty(diagnostics);
Assert.Contains("source.Timestamp.DateTime", output, StringComparison.Ordinal);
}

[Fact]
public void Config_Should_Map_DateTime_To_DateTimeOffset()
{
const string source = """
using System;
using Atc.SourceGenerators.Annotations;

namespace SourceNs
{
public class Item
{
public DateTime Timestamp { get; set; }
}
}

namespace TargetNs
{
public class Item
{
public DateTimeOffset Timestamp { get; set; }
}
}

namespace Mappings
{
[MappingConfiguration]
public static partial class ItemMappings
{
public static partial TargetNs.Item MapToItem(this SourceNs.Item source);
}
}
""";

var (diagnostics, output) = GetGeneratedOutput(source);

Assert.Empty(diagnostics);
Assert.Contains("new global::System.DateTimeOffset(source.Timestamp)", output, StringComparison.Ordinal);
}

[Fact]
public void Config_Should_Map_Nullable_DateTimeOffset_To_Nullable_DateTime()
{
const string source = """
using System;
using Atc.SourceGenerators.Annotations;

#nullable enable

namespace SourceNs
{
public class Item
{
public DateTimeOffset? Timestamp { get; set; }
}
}

namespace TargetNs
{
public class Item
{
public DateTime? Timestamp { get; set; }
}
}

namespace Mappings
{
[MappingConfiguration]
public static partial class ItemMappings
{
public static partial TargetNs.Item MapToItem(this SourceNs.Item source);
}
}
""";

var (diagnostics, output) = GetGeneratedOutput(source);

Assert.Empty(diagnostics);
Assert.Contains("source.Timestamp?.DateTime", output, StringComparison.Ordinal);
}

[Fact]
public void Config_Should_Map_Nullable_DateTime_To_Nullable_DateTimeOffset()
{
const string source = """
using System;
using Atc.SourceGenerators.Annotations;

#nullable enable

namespace SourceNs
{
public class Item
{
public DateTime? Timestamp { get; set; }
}
}

namespace TargetNs
{
public class Item
{
public DateTimeOffset? Timestamp { get; set; }
}
}

namespace Mappings
{
[MappingConfiguration]
public static partial class ItemMappings
{
public static partial TargetNs.Item MapToItem(this SourceNs.Item source);
}
}
""";

var (diagnostics, output) = GetGeneratedOutput(source);

Assert.Empty(diagnostics);
Assert.Contains("is { } __timestamp ? new global::System.DateTimeOffset(__timestamp) : null", output, StringComparison.Ordinal);
}
}