From c4c977fe3ba242eda13bf4dde3f97a6b78f79873 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 23:58:50 +0000 Subject: [PATCH 1/4] Initial plan From 64df4e735173ff7a2c90ad23803bbcdedff25be0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:02:46 +0000 Subject: [PATCH 2/4] Extend INTL0202 analyzer to flag DateTimeOffset(DateTime) constructor Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com> --- .../DateTimeConversionTests.cs | 108 +++++++++++++++++- ...licitDateTimeToDateTimeOffsetConversion.cs | 35 +++++- 2 files changed, 140 insertions(+), 3 deletions(-) diff --git a/IntelliTect.Analyzer/IntelliTect.Analyzer.Test/DateTimeConversionTests.cs b/IntelliTect.Analyzer/IntelliTect.Analyzer.Test/DateTimeConversionTests.cs index f2fec53a..5b1973b4 100644 --- a/IntelliTect.Analyzer/IntelliTect.Analyzer.Test/DateTimeConversionTests.cs +++ b/IntelliTect.Analyzer/IntelliTect.Analyzer.Test/DateTimeConversionTests.cs @@ -30,7 +30,7 @@ static void Main(string[] args) { Id = "INTL0202", Severity = DiagnosticSeverity.Warning, - Message = "Using the symbol 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' can result in unpredictable behavior", + Message = "Using 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' or 'new DateTimeOffset(DateTime)' can result in unpredictable behavior", Locations = [ new DiagnosticResultLocation("Test0.cs", 10, 38) @@ -71,7 +71,7 @@ static void Main(string[] args) { Id = "INTL0202", Severity = DiagnosticSeverity.Warning, - Message = "Using the symbol 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' can result in unpredictable behavior", + Message = "Using 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' or 'new DateTimeOffset(DateTime)' can result in unpredictable behavior", Locations = [ new DiagnosticResultLocation("Test0.cs", 17, 17) @@ -101,6 +101,110 @@ static void Main(string[] args) } + [TestMethod] + public void UsageOfDateTimeOffsetConstructorWithDateTime_ProducesWarningMessage() + { + string source = @" +using System; + +namespace ConsoleApp44 + { + class Program + { + static void Main(string[] args) + { + DateTimeOffset ofs = new DateTimeOffset(DateTime.Now); + } + } + }"; + + VerifyCSharpDiagnostic(source, + new DiagnosticResult + { + Id = "INTL0202", + Severity = DiagnosticSeverity.Warning, + Message = "Using 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' or 'new DateTimeOffset(DateTime)' can result in unpredictable behavior", + Locations = + [ + new DiagnosticResultLocation("Test0.cs", 10, 38) + ] + }); + + } + + [TestMethod] + public void UsageOfDateTimeOffsetConstructorWithDateTimeAndTimeSpan_ProducesNothing() + { + string source = @" +using System; + +namespace ConsoleApp45 + { + class Program + { + static void Main(string[] args) + { + DateTimeOffset ofs = new DateTimeOffset(DateTime.Now, TimeSpan.Zero); + } + } + }"; + + VerifyCSharpDiagnostic(source); + + } + + [TestMethod] + public void UsageOfDateTimeOffsetConstructorWithYearMonthDay_ProducesNothing() + { + string source = @" +using System; + +namespace ConsoleApp46 + { + class Program + { + static void Main(string[] args) + { + DateTimeOffset ofs = new DateTimeOffset(2022, 2, 2, 0, 0, 0, TimeSpan.Zero); + } + } + }"; + + VerifyCSharpDiagnostic(source); + + } + + [TestMethod] + public void UsageOfDateTimeOffsetConstructorWithNewDateTime_ProducesWarningMessage() + { + string source = @" +using System; + +namespace ConsoleApp47 + { + class Program + { + static void Main(string[] args) + { + DateTimeOffset dto = new DateTimeOffset(new DateTime(2022, 2, 2)); + } + } + }"; + + VerifyCSharpDiagnostic(source, + new DiagnosticResult + { + Id = "INTL0202", + Severity = DiagnosticSeverity.Warning, + Message = "Using 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' or 'new DateTimeOffset(DateTime)' can result in unpredictable behavior", + Locations = + [ + new DiagnosticResultLocation("Test0.cs", 10, 38) + ] + }); + + } + protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() { return new Analyzers.BanImplicitDateTimeToDateTimeOffsetConversion(); diff --git a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs index 46940659..86349269 100644 --- a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs +++ b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs @@ -29,6 +29,7 @@ public override void Initialize(AnalysisContext context) context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.EnableConcurrentExecution(); context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Conversion); + context.RegisterOperationAction(AnalyzeObjectCreation, OperationKind.ObjectCreation); } private void AnalyzeInvocation(OperationAnalysisContext context) @@ -51,11 +52,43 @@ private void AnalyzeInvocation(OperationAnalysisContext context) } + private void AnalyzeObjectCreation(OperationAnalysisContext context) + { + if (context.Operation is not IObjectCreationOperation objectCreation) + { + return; + } + + INamedTypeSymbol dateTimeOffsetType = context.Compilation.GetTypeByMetadataName("System.DateTimeOffset"); + INamedTypeSymbol dateTimeType = context.Compilation.GetTypeByMetadataName("System.DateTime"); + + if (dateTimeOffsetType is null || dateTimeType is null) + { + return; + } + + // Check if we're creating a DateTimeOffset + if (!SymbolEqualityComparer.Default.Equals(objectCreation.Type, dateTimeOffsetType)) + { + return; + } + + // Check if the constructor has exactly one parameter and it's a DateTime + if (objectCreation.Constructor?.Parameters.Length == 1) + { + IParameterSymbol parameter = objectCreation.Constructor.Parameters[0]; + if (SymbolEqualityComparer.Default.Equals(parameter.Type, dateTimeType)) + { + context.ReportDiagnostic(Diagnostic.Create(_Rule202, objectCreation.Syntax.GetLocation())); + } + } + } + private static class Rule202 { internal const string DiagnosticId = "INTL0202"; internal const string Title = "Do not use implicit conversion from `DateTime` to `DateTimeOffset`"; - internal const string MessageFormat = "Using the symbol 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' can result in unpredictable behavior"; + internal const string MessageFormat = "Using 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' or 'new DateTimeOffset(DateTime)' can result in unpredictable behavior"; #pragma warning disable INTL0001 // Allow field to not be prefixed with an underscore to match the style internal static readonly string HelpMessageUri = DiagnosticUrlBuilder.GetUrl(Title, DiagnosticId); From c9faa449b6033be57cf7d1f36b6ba9202d04f7b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:03:50 +0000 Subject: [PATCH 3/4] Fix trailing whitespace in BanImplicitDateTimeToDateTimeOffsetConversion.cs Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com> --- .../Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs index 86349269..fd98c6c9 100644 --- a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs +++ b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs @@ -61,7 +61,7 @@ private void AnalyzeObjectCreation(OperationAnalysisContext context) INamedTypeSymbol dateTimeOffsetType = context.Compilation.GetTypeByMetadataName("System.DateTimeOffset"); INamedTypeSymbol dateTimeType = context.Compilation.GetTypeByMetadataName("System.DateTime"); - + if (dateTimeOffsetType is null || dateTimeType is null) { return; From cf59c38d98e29d057104f82d16df02fabcc58f8f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 15:44:31 +0000 Subject: [PATCH 4/4] Throw exception when DateTimeOffset or DateTime types cannot be found Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com> --- .../BanImplicitDateTimeToDateTimeOffsetConversion.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs index fd98c6c9..303e11f1 100644 --- a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs +++ b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs @@ -59,13 +59,10 @@ private void AnalyzeObjectCreation(OperationAnalysisContext context) return; } - INamedTypeSymbol dateTimeOffsetType = context.Compilation.GetTypeByMetadataName("System.DateTimeOffset"); - INamedTypeSymbol dateTimeType = context.Compilation.GetTypeByMetadataName("System.DateTime"); - - if (dateTimeOffsetType is null || dateTimeType is null) - { - return; - } + INamedTypeSymbol dateTimeOffsetType = context.Compilation.GetTypeByMetadataName("System.DateTimeOffset") + ?? throw new InvalidOperationException("Unable to find DateTimeOffset type"); + INamedTypeSymbol dateTimeType = context.Compilation.GetTypeByMetadataName("System.DateTime") + ?? throw new InvalidOperationException("Unable to find DateTime type"); // Check if we're creating a DateTimeOffset if (!SymbolEqualityComparer.Default.Equals(objectCreation.Type, dateTimeOffsetType))