Visitar URL original
Add `${datetimeoffset}` layout renderer (fixes #6161) by LorneCash · Pull Request #6162 · NLog/NLog · GitHub
Skip to content

Add ${datetimeoffset} layout renderer (fixes #6161) - #6162

Closed
LorneCash wants to merge 1 commit into
NLog:devfrom
LorneCash:feature/datetimeoffset-layout-renderer
Closed

LorneCash wants to merge 1 commit into
NLog:devfrom
LorneCash:feature/datetimeoffset-layout-renderer

Conversation

@LorneCash

Copy link
Copy Markdown

Fixes #6161

Introduces a dedicated DateTimeOffsetLayoutRenderer (${datetimeoffset}) so that DatabaseTarget with parameterType="System.DateTimeOffset" can flow timestamps directly to a SQL DATETIMEOFFSET column without a string round-trip or workaround via LogEventInfo.Properties.

Changes

  • New DateTimeOffsetLayoutRenderer (${datetimeoffset}) — TryGetRawValue always returns a typed DateTimeOffset; Format, Culture and UniversalTime properties match the existing date renderers; default format yyyy/MM/dd HH:mm:ss.fff keeps string output identical to ${date} out of the box.
  • AssemblyExtensionTypes.cs — registered "datetimeoffset" so the renderer is available in XML config and SimpleLayout without manual registration.
  • ShortDateLayoutRenderer — fixed GetValue: when UniversalTime=false, ToLocalTime() is now only called when timestamp.Kind == Utc. Calling it unconditionally on DateTimeKind.Unspecified caused an off-by-one date on UTC-offset machines.
  • Tests updated in DateTests and LongDateTests to cover the new renderer.

Copilot AI review requested due to automatic review settings April 22, 2026 00:28
@coderabbitai

coderabbitai Bot commented Apr 22, 2026 •

Copy link
Copy Markdown

Walkthrough

A new DateTimeOffsetLayoutRenderer layout renderer is introduced to expose LogEventInfo.TimeStamp as a typed DateTimeOffset raw value with configurable format, culture, and universal time options. A timezone conversion bug in ShortDateLayoutRenderer is corrected. The renderer is registered and validated with comprehensive test coverage.

Changes

Cohort / File(s) Summary
New Layout Renderer
src/NLog/LayoutRenderers/DateTimeOffsetLayoutRenderer.cs
New DateTimeOffsetLayoutRenderer class implementing IRawValue and IStringValueRenderer, with configurable Format (default: yyyy/MM/dd HH:mm:ss.fff), Culture (default: InvariantCulture), and UniversalTime switch to control UTC/local output. Properly handles DateTimeKind.Utc, Local, and Unspecified timestamp conversions.
Registration
src/NLog/Config/AssemblyExtensionTypes.cs
Added registration of DateTimeOffsetLayoutRenderer under the "datetimeoffset" layout alias.
Timezone Conversion Fix
src/NLog/LayoutRenderers/ShortDateLayoutRenderer.cs
Corrected timezone conversion logic when UniversalTime is false to convert to local time only when Kind == DateTimeKind.Utc, leaving Local and Unspecified timestamps unchanged.
Test Coverage
tests/NLog.UnitTests/LayoutRenderers/DateTests.cs, tests/NLog.UnitTests/LayoutRenderers/LongDateTests.cs
Added eight new xUnit facts validating TryGetRawValue typed behavior for ${date}, ${longdate}, and ${datetimeoffset} renderers with various DateTimeKind scenarios and format variations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A datetimeoffset renderer hops into view,
Typed values now flow straight through,
No string round-trips, no timezone pain,
Just UTC and local time—crystal and plain!
SQL columns rejoice, the workaround's through. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically summarizes the main change: adding a new ${datetimeoffset} layout renderer and referencing the fixed issue.
Description check ✅ Passed The PR description is detailed and directly related to the changeset, explaining the new DateTimeOffsetLayoutRenderer, registrations, fixes to ShortDateLayoutRenderer, and test updates.
Linked Issues check ✅ Passed The PR fully addresses issue #6161 by implementing Option B (new ${datetimeoffset} renderer) with Format, Culture, UniversalTime properties, IRawValue/IStringValueRenderer interfaces, and proper registration.
Out of Scope Changes check ✅ Passed The ShortDateLayoutRenderer fix for UniversalTime=false logic is a closely related bug fix addressing a documented issue and aligns with the objectives of correct timestamp handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/NLog/LayoutRenderers/ShortDateLayoutRenderer.cs (1)

92-92: Clean up the inline comment and add a focused regression case.

The behavior is correct, but Utc?Local looks like a malformed arrow. Since this fixes an Unspecified-kind date shift, please consider adding a test that sets LogEventInfo.TimeStamp to DateTimeKind.Unspecified with UniversalTime=false and asserts no conversion.

🧹 Proposed comment cleanup
-                    timestamp = timestamp.ToLocalTime();  // Only convert Utc?Local; Local/Unspecified are already local
+                    timestamp = timestamp.ToLocalTime();  // Only convert UTC to Local; Local/Unspecified are already local
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NLog/LayoutRenderers/ShortDateLayoutRenderer.cs` at line 92, Update the
inline comment on the timestamp conversion in ShortDateLayoutRenderer.cs to be
clear (e.g., "Only convert when Kind == Utc; Local and Unspecified are already
local") instead of the malformed "Utc?Local", and add a focused regression unit
test that constructs a LogEventInfo with TimeStamp having
DateTimeKind.Unspecified and UniversalTime=false then renders with
ShortDateLayoutRenderer (or calls the method that contains timestamp =
timestamp.ToLocalTime()) and asserts the timestamp is not converted (remains
treated as local). Ensure the test references the ShortDateLayoutRenderer
rendering method so it fails if Unspecified gets converted.
src/NLog/LayoutRenderers/DateTimeOffsetLayoutRenderer.cs (1)

69-69: Consider marking Format nullable or asserting non-null.

Format is typed as non-nullable string but can be set to null via XML/config or reflection, in which case DateTimeOffset.ToString((string)null, provider) falls back to the "G" format — which silently ignores the documented default yyyy/MM/dd HH:mm:ss.fff. If the project uses nullable reference types, annotating as string? (or guarding in GetValue/Append) would make the contract explicit. Not a blocker since this mirrors existing date renderers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NLog/LayoutRenderers/DateTimeOffsetLayoutRenderer.cs` at line 69, The
Format property on DateTimeOffsetLayoutRenderer is declared as string but can be
null via config/reflection; update the contract and guards: either change the
property declaration to string? Format to allow null, or leave as string and add
a null-forcing assertion; additionally ensure GetValue/Append (methods in
DateTimeOffsetLayoutRenderer) guard against null by using the documented default
format "yyyy/MM/dd HH:mm:ss.fff" when Format is null before calling
DateTimeOffset.ToString(format, provider). Make the change so the code
explicitly handles/configures nulls rather than relying on
DateTimeOffset.ToString((string)null) fallback.
tests/NLog.UnitTests/LayoutRenderers/DateTests.cs (1)

202-297: LGTM — raw-value and rendering tests are well-targeted.

Tests cover both DateTimeKind.Local and DateTimeKind.Utc paths, derive expected offsets from TimeZoneInfo.Local (so they remain timezone-agnostic on CI), and verify rendered output parity with ${date}. One minor gap worth considering as a follow-up: a test for DateTimeKind.Unspecified (which the renderer explicitly documents as "treat as local") would lock in that behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/NLog.UnitTests/LayoutRenderers/DateTests.cs` around lines 202 - 297,
Add a unit test that covers DateTimeKind.Unspecified being treated as local:
create a new test method (e.g.
DateTimeOffsetLayoutRenderer_TryGetRawValue_ReturnsDateTimeOffset_UnspecifiedKind)
that uses SimpleLayout l = "${datetimeoffset}", sets logEventInfo.TimeStamp to a
DateTime with DateTimeKind.Unspecified, calls l.TryGetRawValue(logEventInfo, out
var value), asserts success and that value is a DateTimeOffset, and compares it
to an expected DateTimeOffset constructed with the same timestamp and
TimeZoneInfo.Local.GetUtcOffset(timestamp) (mirroring the Local-kind test
pattern).
tests/NLog.UnitTests/LayoutRenderers/LongDateTests.cs (1)

215-311: LGTM — parity with ${longdate} is correctly asserted.

Tests appropriately use the renderer instance directly (rather than SimpleLayout) for the raw-value checks and cover both kinds plus rendered-string parity against LongDateLayoutRenderer using the yyyy-MM-dd HH:mm:ss.ffff format. Consider adding the DateTimeKind.Unspecified case here too for symmetry.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/NLog.UnitTests/LayoutRenderers/LongDateTests.cs` around lines 215 -
311, Add a symmetric test for DateTimeKind.Unspecified by creating a new fact
(e.g.,
DateTimeOffsetLayoutRenderer_TryGetRawValue_LongDateFormat_UnspecifiedKind) that
mirrors DateTimeOffsetLayoutRenderer_TryGetRawValue_LongDateFormat_LocalKind:
instantiate DateTimeOffsetLayoutRenderer, set Format = "yyyy-MM-dd
HH:mm:ss.ffff", create timestamp with DateTime.SpecifyKind(...,
DateTimeKind.Unspecified), assign it to logEventInfo.TimeStamp, call
((NLog.Internal.IRawValue)renderer).TryGetRawValue(logEventInfo, out var value),
Assert.True(success), Assert.IsType<DateTimeOffset>(value) and
Assert.Equal(expected, (DateTimeOffset)value) where expected is new
DateTimeOffset(timestamp, TimeZoneInfo.Local.GetUtcOffset(timestamp)); this adds
the missing Unspecified-kind coverage consistent with the Local-kind test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/NLog/LayoutRenderers/DateTimeOffsetLayoutRenderer.cs`:
- Around line 102-129: GetValue currently treats _universalTime unset
differently from explicit false for UTC timestamps; change it so only the true
case forces UTC and all other cases (unset or false) use ToDateTimeOffset to
preserve DateTime.Kind semantics. Concretely, update GetValue to: if
(_universalTime == true) convert/return UTC DateTimeOffset (preserving zero
offset) otherwise return ToDateTimeOffset(timestamp); remove the separate
else-branch that converts UTC to local, and ensure ToDateTimeOffset is the
single code path for non-UTC-forcing behavior (references: GetValue,
_universalTime, ToDateTimeOffset, UniversalTime).

---

Nitpick comments:
In `@src/NLog/LayoutRenderers/DateTimeOffsetLayoutRenderer.cs`:
- Line 69: The Format property on DateTimeOffsetLayoutRenderer is declared as
string but can be null via config/reflection; update the contract and guards:
either change the property declaration to string? Format to allow null, or leave
as string and add a null-forcing assertion; additionally ensure GetValue/Append
(methods in DateTimeOffsetLayoutRenderer) guard against null by using the
documented default format "yyyy/MM/dd HH:mm:ss.fff" when Format is null before
calling DateTimeOffset.ToString(format, provider). Make the change so the code
explicitly handles/configures nulls rather than relying on
DateTimeOffset.ToString((string)null) fallback.

In `@src/NLog/LayoutRenderers/ShortDateLayoutRenderer.cs`:
- Line 92: Update the inline comment on the timestamp conversion in
ShortDateLayoutRenderer.cs to be clear (e.g., "Only convert when Kind == Utc;
Local and Unspecified are already local") instead of the malformed "Utc?Local",
and add a focused regression unit test that constructs a LogEventInfo with
TimeStamp having DateTimeKind.Unspecified and UniversalTime=false then renders
with ShortDateLayoutRenderer (or calls the method that contains timestamp =
timestamp.ToLocalTime()) and asserts the timestamp is not converted (remains
treated as local). Ensure the test references the ShortDateLayoutRenderer
rendering method so it fails if Unspecified gets converted.

In `@tests/NLog.UnitTests/LayoutRenderers/DateTests.cs`:
- Around line 202-297: Add a unit test that covers DateTimeKind.Unspecified
being treated as local: create a new test method (e.g.
DateTimeOffsetLayoutRenderer_TryGetRawValue_ReturnsDateTimeOffset_UnspecifiedKind)
that uses SimpleLayout l = "${datetimeoffset}", sets logEventInfo.TimeStamp to a
DateTime with DateTimeKind.Unspecified, calls l.TryGetRawValue(logEventInfo, out
var value), asserts success and that value is a DateTimeOffset, and compares it
to an expected DateTimeOffset constructed with the same timestamp and
TimeZoneInfo.Local.GetUtcOffset(timestamp) (mirroring the Local-kind test
pattern).

In `@tests/NLog.UnitTests/LayoutRenderers/LongDateTests.cs`:
- Around line 215-311: Add a symmetric test for DateTimeKind.Unspecified by
creating a new fact (e.g.,
DateTimeOffsetLayoutRenderer_TryGetRawValue_LongDateFormat_UnspecifiedKind) that
mirrors DateTimeOffsetLayoutRenderer_TryGetRawValue_LongDateFormat_LocalKind:
instantiate DateTimeOffsetLayoutRenderer, set Format = "yyyy-MM-dd
HH:mm:ss.ffff", create timestamp with DateTime.SpecifyKind(...,
DateTimeKind.Unspecified), assign it to logEventInfo.TimeStamp, call
((NLog.Internal.IRawValue)renderer).TryGetRawValue(logEventInfo, out var value),
Assert.True(success), Assert.IsType<DateTimeOffset>(value) and
Assert.Equal(expected, (DateTimeOffset)value) where expected is new
DateTimeOffset(timestamp, TimeZoneInfo.Local.GetUtcOffset(timestamp)); this adds
the missing Unspecified-kind coverage consistent with the Local-kind test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 516be682-e5db-4a37-a660-4b6ba04b9596

📥 Commits

Reviewing files that changed from the base of the PR and between c9f2941 and e493e38.

📒 Files selected for processing (5)
  • src/NLog/Config/AssemblyExtensionTypes.cs
  • src/NLog/LayoutRenderers/DateTimeOffsetLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ShortDateLayoutRenderer.cs
  • tests/NLog.UnitTests/LayoutRenderers/DateTests.cs
  • tests/NLog.UnitTests/LayoutRenderers/LongDateTests.cs

Comment on lines +102 to +129
private DateTimeOffset GetValue(LogEventInfo logEvent)
{
var timestamp = logEvent.TimeStamp;
if (_universalTime.HasValue)
{
if (_universalTime.Value)
{
var utc = timestamp.Kind != DateTimeKind.Utc ? timestamp.ToUniversalTime() : timestamp;
return new DateTimeOffset(utc, TimeSpan.Zero);
}
else
{
var local = timestamp.Kind == DateTimeKind.Utc ? timestamp.ToLocalTime() : timestamp;
return new DateTimeOffset(DateTime.SpecifyKind(local, DateTimeKind.Local), TimeZoneInfo.Local.GetUtcOffset(local));
}
}
return ToDateTimeOffset(timestamp);
}

private static DateTimeOffset ToDateTimeOffset(DateTime timestamp)
{
if (timestamp.Kind == DateTimeKind.Utc)
return new DateTimeOffset(timestamp, TimeSpan.Zero);
if (timestamp.Kind == DateTimeKind.Local)
return new DateTimeOffset(timestamp, TimeZoneInfo.Local.GetUtcOffset(timestamp));
// Unspecified: treat as local
return new DateTimeOffset(DateTime.SpecifyKind(timestamp, DateTimeKind.Local), TimeZoneInfo.Local.GetUtcOffset(timestamp));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Subtle semantic difference between UniversalTime unset vs. explicit false for UTC timestamps.

GetValue uses _universalTime.HasValue to switch behavior, which means:

  • Unset (default) + Kind == Utc → DateTimeOffset(timestamp, TimeSpan.Zero) (preserves UTC).
  • Explicit UniversalTime="false" + Kind == Utc → converts via ToLocalTime() and returns with the local offset (different wall-clock value).

Users setting UniversalTime="false" in XML (which looks like a no-op since false is the documented default) will silently get a different DateTimeOffset than users who omit the attribute, whenever TimeStamp.Kind == Utc (e.g., when an AccurateUtcTimeSource is configured). This also contrasts with LongDateLayoutRenderer.GetValue, which only branches on UniversalTime being true.

Consider collapsing the two paths so that the unset case and the explicit-false case produce the same result (both preserving offset info from Kind), e.g.:

♻️ Proposed simplification
         private DateTimeOffset GetValue(LogEventInfo logEvent)
         {
             var timestamp = logEvent.TimeStamp;
-            if (_universalTime.HasValue)
-            {
-                if (_universalTime.Value)
-                {
-                    var utc = timestamp.Kind != DateTimeKind.Utc ? timestamp.ToUniversalTime() : timestamp;
-                    return new DateTimeOffset(utc, TimeSpan.Zero);
-                }
-                else
-                {
-                    var local = timestamp.Kind == DateTimeKind.Utc ? timestamp.ToLocalTime() : timestamp;
-                    return new DateTimeOffset(DateTime.SpecifyKind(local, DateTimeKind.Local), TimeZoneInfo.Local.GetUtcOffset(local));
-                }
-            }
-            return ToDateTimeOffset(timestamp);
+            if (UniversalTime)
+            {
+                var utc = timestamp.Kind == DateTimeKind.Utc ? timestamp : timestamp.ToUniversalTime();
+                return new DateTimeOffset(utc, TimeSpan.Zero);
+            }
+            return ToDateTimeOffset(timestamp);
         }

This removes the code duplication with ToDateTimeOffset and makes UniversalTime=false equivalent to the default, matching the documented "Default: false" contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NLog/LayoutRenderers/DateTimeOffsetLayoutRenderer.cs` around lines 102 -
129, GetValue currently treats _universalTime unset differently from explicit
false for UTC timestamps; change it so only the true case forces UTC and all
other cases (unset or false) use ToDateTimeOffset to preserve DateTime.Kind
semantics. Concretely, update GetValue to: if (_universalTime == true)
convert/return UTC DateTimeOffset (preserving zero offset) otherwise return
ToDateTimeOffset(timestamp); remove the separate else-branch that converts UTC
to local, and ensure ToDateTimeOffset is the single code path for
non-UTC-forcing behavior (references: GetValue, _universalTime,
ToDateTimeOffset, UniversalTime).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class ${datetimeoffset} support to NLog layout rendering so DatabaseTarget can pass a typed DateTimeOffset to ADO.NET parameters (e.g., SQL DATETIMEOFFSET) without string round-tripping, and fixes a ShortDateLayoutRenderer timezone conversion edge case.

Changes:

  • Introduces DateTimeOffsetLayoutRenderer (${datetimeoffset}) that always exposes a typed DateTimeOffset via TryGetRawValue, while keeping default string output aligned with ${date}.
  • Registers the new renderer in AssemblyExtensionTypes so it’s available without manual registration.
  • Fixes ShortDateLayoutRenderer local conversion so ToLocalTime() is only applied when the timestamp is DateTimeKind.Utc.
  • Updates date-related unit tests to cover the new renderer and document current raw-value behavior of existing renderers.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/NLog/LayoutRenderers/DateTimeOffsetLayoutRenderer.cs New ${datetimeoffset} renderer with typed DateTimeOffset raw-value support and formatting options.
src/NLog/Config/AssemblyExtensionTypes.cs Registers datetimeoffset renderer for config/SimpleLayout usage.
src/NLog/LayoutRenderers/ShortDateLayoutRenderer.cs Fixes incorrect ToLocalTime() conversion for DateTimeKind.Unspecified.
tests/NLog.UnitTests/LayoutRenderers/DateTests.cs Adds tests for ${datetimeoffset} raw value and string equivalence with ${date}.
tests/NLog.UnitTests/LayoutRenderers/LongDateTests.cs Adds tests for ${datetimeoffset} with longdate-style formatting and raw-value behavior documentation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 89 to +92
if (_universalTime.Value)
timestamp = timestamp.ToUniversalTime();
else
timestamp = timestamp.ToLocalTime();
else if (timestamp.Kind == DateTimeKind.Utc)
timestamp = timestamp.ToLocalTime(); // Only convert Utc?Local; Local/Unspecified are already local

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ShortDate LocalTime path was adjusted to avoid calling ToLocalTime() for DateTimeKind.Unspecified, but there isn’t a unit test that would fail on non-UTC machines (the existing ShortDateTests mostly assert string output, which can still pass in UTC timezones). Consider adding a test that sets logEvent.TimeStamp.Kind=Unspecified and asserts the raw value (IRawValue) Kind stays Unspecified (or otherwise proves no conversion happens) when UniversalTime=false, so the regression is caught regardless of the machine timezone.

Copilot uses AI. Check for mistakes.
Comment on lines +102 to +129
private DateTimeOffset GetValue(LogEventInfo logEvent)
{
var timestamp = logEvent.TimeStamp;
if (_universalTime.HasValue)
{
if (_universalTime.Value)
{
var utc = timestamp.Kind != DateTimeKind.Utc ? timestamp.ToUniversalTime() : timestamp;
return new DateTimeOffset(utc, TimeSpan.Zero);
}
else
{
var local = timestamp.Kind == DateTimeKind.Utc ? timestamp.ToLocalTime() : timestamp;
return new DateTimeOffset(DateTime.SpecifyKind(local, DateTimeKind.Local), TimeZoneInfo.Local.GetUtcOffset(local));
}
}
return ToDateTimeOffset(timestamp);
}

private static DateTimeOffset ToDateTimeOffset(DateTime timestamp)
{
if (timestamp.Kind == DateTimeKind.Utc)
return new DateTimeOffset(timestamp, TimeSpan.Zero);
if (timestamp.Kind == DateTimeKind.Local)
return new DateTimeOffset(timestamp, TimeZoneInfo.Local.GetUtcOffset(timestamp));
// Unspecified: treat as local
return new DateTimeOffset(DateTime.SpecifyKind(timestamp, DateTimeKind.Local), TimeZoneInfo.Local.GetUtcOffset(timestamp));
}

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DateTimeOffsetLayoutRenderer has non-trivial conversion rules for DateTimeKind.Unspecified and for the UniversalTime=true/false overrides, but the added tests only cover Local and Utc kinds with the default behavior. Add unit tests that cover Unspecified (default and UniversalTime=false), and UniversalTime=true (verifying offset becomes zero and UtcDateTime matches) to ensure the typed DateTimeOffset semantics don’t regress.

Copilot uses AI. Check for mistakes.
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
74.1% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@snakefoot

snakefoot commented Apr 22, 2026 •

Copy link
Copy Markdown
Contributor

Curious why the conversion introduced with NLog v6.1.2 as part of #6116 is not working for you?

if (typeCode == TypeCode.DateTime && typeof(DateTimeOffset) == propertyType)
   return new DateTimeOffset(convertibleValue.ToDateTime(formatProvider));

See also NLog/NLog.Targets.Database#66 where ${date} converts using DbType.DateTimeOffset (without string-conversion)

@LorneCash

Copy link
Copy Markdown
Author

I had tried to update my code before and it wasn't working but now it works fine with just "${date:format=O}" so I don't know what I did before but please disregard this PR. I'm fine with how it is currently. I'm sorry for the trouble.

@LorneCash LorneCash closed this Apr 22, 2026
@snakefoot

snakefoot commented Apr 22, 2026 •

Copy link
Copy Markdown
Contributor

It should also work if you do this "${date}" (without specifying format=O, so skipping the string-parsing)

@LorneCash

Copy link
Copy Markdown
Author

It does, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add DateTimeOffset raw-value support to ${date} / ${longdate} (or add a new ${datetimeoffset} renderer)

3 participants