diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityDateTimeHelper.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityDateTimeHelper.cs
new file mode 100644
index 0000000000..63027679a4
--- /dev/null
+++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityDateTimeHelper.cs
@@ -0,0 +1,82 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// https://github.com/dotnet/runtime/blob/75662173e3918f2176b74e467dc8e41d4f01d4d4/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.DateTime.netfx.cs
+
+// Adjusted to our coding standards and moved to separate class
+
+using System.Diagnostics;
+
+namespace OpenTelemetry.Instrumentation.AspNet;
+
+internal static class ActivityDateTimeHelper
+{
+#pragma warning disable CA1823 // suppress unused field warning, as it's used to keep the timer alive
+ private static readonly Timer SyncTimeUpdater = InitializeSyncTimer();
+#pragma warning restore CA1823
+ private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency;
+
+ private static TimeSync timeSync = new();
+
+ ///
+ /// Returns high resolution (1 DateTime tick) current UTC DateTime.
+ ///
+ /// High resolution UTC DateTime.
+ internal static DateTime GetUtcNow()
+ {
+ // DateTime.UtcNow accuracy on .NET Framework is ~16ms, this method
+ // uses combination of Stopwatch and DateTime to calculate accurate UtcNow.
+
+ var tmp = timeSync;
+
+ // Timer ticks need to be converted to DateTime ticks
+ long dateTimeTicksDiff = (long)((Stopwatch.GetTimestamp() - tmp.SyncStopwatchTicks) * TickFrequency);
+
+ // DateTime.AddSeconds (or Milliseconds) rounds value to 1 ms, use AddTicks to prevent it
+ return tmp.SyncUtcNow.AddTicks(dateTimeTicksDiff);
+ }
+
+ private static void Sync()
+ {
+ // wait for DateTime.UtcNow update to the next granular value
+ Thread.Sleep(1);
+ timeSync = new TimeSync();
+ }
+
+ [System.Security.SecuritySafeCritical]
+ private static Timer InitializeSyncTimer()
+ {
+ Timer timer;
+
+ // Don't capture the current ExecutionContext and its AsyncLocals onto the timer causing them to live forever
+ bool restoreFlow = false;
+ try
+ {
+ if (!ExecutionContext.IsFlowSuppressed())
+ {
+ ExecutionContext.SuppressFlow();
+ restoreFlow = true;
+ }
+
+ timer = new Timer(static _ => Sync(), null, 0, 7_200_000); // 2 hours
+ }
+ finally
+ {
+ // Restore the current ExecutionContext
+ if (restoreFlow)
+ {
+ ExecutionContext.RestoreFlow();
+ }
+ }
+
+ return timer;
+ }
+
+ private sealed class TimeSync
+ {
+ public readonly DateTime SyncUtcNow = DateTime.UtcNow;
+ public readonly long SyncStopwatchTicks = Stopwatch.GetTimestamp();
+ }
+}
diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs
index dddce54315..173135a544 100644
--- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs
+++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs
@@ -120,7 +120,7 @@ public static void StopAspNetActivity(TextMapPropagator textMapPropagator, Activ
// Note that the activity must not be stopped before the callback is called.
if (aspNetActivity.Duration == TimeSpan.Zero)
{
- aspNetActivity.SetEndTime(DateTime.UtcNow);
+ aspNetActivity.SetEndTime(ActivityDateTimeHelper.GetUtcNow());
}
try
diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md
index 09aee59426..6651d48593 100644
--- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md
@@ -11,6 +11,9 @@
responsible for providing function returning `Activity`.
([#3151](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3151))
+* Fixed an inaccurate span end of time.
+ ([#3171](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3171))
+
## 1.12.0-beta.2
Released 2025-Sep-18