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
21 changes: 21 additions & 0 deletions src/log4net.Tests/Layout/PatternLayoutTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,27 @@ public void TestExceptionPattern()

stringAppender.Reset();
}

[Test]
public void ConvertMultipleDatePatternsTest()
{
StringAppender stringAppender = new()
{
Layout = NewPatternLayout("%utcdate{ABSOLUTE} - %utcdate{ISO8601}")
};

ILoggerRepository rep = LogManager.CreateRepository(Guid.NewGuid().ToString());
BasicConfigurator.Configure(rep, stringAppender);

ILog logger = LogManager.GetLogger(rep.Name, nameof(ConvertMultipleDatePatternsTest));

logger.Logger.Log(new(new() { TimeStampUtc = new(2025, 02, 10, 13, 01, 02, 123, DateTimeKind.Utc), Message = "test", Level = Level.Info }));
Assert.That(stringAppender.GetString(), Is.EqualTo("13:01:02,123 - 2025-02-10 13:01:02,123"));
stringAppender.Reset();
logger.Logger.Log(new(new() { TimeStampUtc = new(2025, 02, 10, 13, 01, 03, 123, DateTimeKind.Utc), Message = "test", Level = Level.Info }));
Assert.That(stringAppender.GetString(), Is.EqualTo("13:01:03,123 - 2025-02-10 13:01:03,123"));
}

#if NET8_0_OR_GREATER
[Test]
public void ConvertMicrosecondsPatternTest()
Expand Down
50 changes: 20 additions & 30 deletions src/log4net/DateFormatter/AbsoluteTimeDateFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ namespace log4net.DateFormatter;
/// <author>Gert Driesen</author>
public class AbsoluteTimeDateFormatter : IDateFormatter
{
private readonly record struct TimeString(long TimeToTheSecond, string AsString);

/// <summary>
/// Renders the date into a string. Format is <c>"HH:mm:ss"</c>.
/// </summary>
Expand Down Expand Up @@ -95,24 +97,16 @@ protected virtual void FormatDateWithoutMillis(DateTime dateToFormat, StringBuil
/// </remarks>
public virtual void FormatDate(DateTime dateToFormat, TextWriter writer)
{
string timeString = _sLastTimeStrings.AddOrUpdate(GetType(),
_ => BuildTimeString(),
(_, existing) =>
{
// Calculate the current time precise only to the second
long currentTimeToTheSecond = dateToFormat.Ticks - (dateToFormat.Ticks % TimeSpan.TicksPerSecond);

// Compare this time with the stored last time
// If we are in the same second then append
// the previously calculated time string
if (_sLastTimeToTheSecond == currentTimeToTheSecond)
{
return existing;
}
_sLastTimeToTheSecond = currentTimeToTheSecond;
return BuildTimeString();
});
writer.EnsureNotNull().Write(timeString);
// Calculate the current time precise only to the second
long timeToTheSecond = dateToFormat.Ticks - (dateToFormat.Ticks % TimeSpan.TicksPerSecond);
// Compare this time with the stored last time
// If we are in the same second then append the previously calculated time string
if (!_sLastTimeStrings.TryGetValue(GetType(), out TimeString timeString) || timeString.TimeToTheSecond != timeToTheSecond)
{
timeString = BuildTimeString(dateToFormat, timeToTheSecond);
_sLastTimeStrings[GetType()] = timeString;
}
writer.EnsureNotNull().Write(timeString.AsString);

// Append the current millisecond info
writer.Write(',');
Expand All @@ -127,12 +121,13 @@ public virtual void FormatDate(DateTime dateToFormat, TextWriter writer)
}
writer.Write(millis);

string BuildTimeString()
{
var sb = new StringBuilder();
FormatDateWithoutMillis(dateToFormat, sb);
return sb.ToString();
}
}

private TimeString BuildTimeString(DateTime dateToFormat, long timeToTheSecond)
{
StringBuilder sb = new();
FormatDateWithoutMillis(dateToFormat, sb);
return new(timeToTheSecond, sb.ToString());
}

/// <summary>
Expand All @@ -150,14 +145,9 @@ string BuildTimeString()
/// </summary>
public const string Iso8601TimeDateFormat = "ISO8601";

/// <summary>
/// Last stored time with precision up to the second.
/// </summary>
private static long _sLastTimeToTheSecond;

/// <summary>
/// Last stored time with precision up to the second, formatted
/// as a string.
/// </summary>
private static readonly ConcurrentDictionary<Type, string> _sLastTimeStrings = new();
private static readonly ConcurrentDictionary<Type, TimeString> _sLastTimeStrings = new();
}