Of Timestamps and Timezones in the new Logging Application Block

One of the changes we made in the Logging Application Block in the new January 2006 release was to change the LogEntry.TimeStamp property from being a local time to a UTC (aka GMT) time. This change was done with good intentions based on feedback from a couple of customers who had a requirement to correlate logging timestamps across timezones and to predictably deal with the transition in and out of daylight savings time.

However more than a couple of people of already told me that this new behavior can be confusing (someone thought it was taking 4 hours for their log messages to arrive, as they were interpreting the timestamp string as a local time!). So having seen quite a bit of feedback on this design change at this early stage, I accept that we may not have fully thought this through, and for this I apologize. Still, one of the great things about patterns & practices deliverables is that it's all source, and it's easy to make it work which ever way you want.

The obvious thing to do would be to find the LogEntry.CollectIntrinisicProperties method and change the first line from this.TimeStamp = DateTime.UtcNow; to this.TimeStamp = DateTime.Now; But while this would work, it doesn't sit too well with me. After all, the problem isn't really about how the timestamp is stored internally, it's about how it's displayed to the end user.

A nicer solution would be to modify the TextFormatter class so that it's able to display the timestamp in either local or UTC time, depending on what the user specifies in the template. While not quite as trivial as the 3 character change listed above, it's still pretty simple. I just modified the existing TextFormatter (actually one of its supporting classes) to support this behavior. If you're a real purist you may choose to subclass the original classes to add the new behavior, but since this change doesn't alter any of the existing behavior of the class, this would be overkill in my humble opinion.

What I did was modify the TimeStampToken class. This class is used by the TextFormatter for replacing {timestamp} tokens with format parameters in the text template, such as {timestamp(F)}. All I did was add a few extra lines of logic to the FormatToken method that can understand a new local: prefix in the template, and if it is present, convert the timestamp to local time before formatting.

Here is the full text of the modified method. (Keep in mind I'm a Product Manager and I'm not a full time coder anymore, so I'm not promising beautiful code, but it does seem to do the job :-)

/// <summary>
/// Formats the timestamp property with the specified date time format string.
/// </summary>
/// <param name="tokenTemplate">Date time format string. Prefix with 'local:' to convert to local time.</param>
/// <param name="log">Log entry containing the timestamp.</param>
/// <returns>Returns the formatted time stamp.</returns>
public override string FormatToken(string tokenTemplate, LogEntry log)
{
if (tokenTemplate.Equals("local", System.StringComparison.InvariantCultureIgnoreCase))
    {
System.DateTime localTime = log.TimeStamp.ToLocalTime();
return localTime.ToString();
    }
else if (tokenTemplate.StartsWith("local:", System.StringComparison.InvariantCultureIgnoreCase))
    {
string formatTemplate = tokenTemplate.Substring(6);
System.DateTime localTime = log.TimeStamp.ToLocalTime();
return localTime.ToString(formatTemplate, CultureInfo.CurrentCulture);
    }
    else
    {
return log.TimeStamp.ToString(tokenTemplate, CultureInfo.CurrentCulture);
    }
}

With this simple change in place, you can now change your TextFormatter templates to support local times. For example, I created a template that shows several timezones in different formats:

Timestamp: {timestamp}
Timestamp local: {timestamp(local)}
Timestamp F: {timestamp(F)}
Timestamp local:F {timestamp(local:F)}

Once the Logging Application Block does its magic over my log message, I get a nicely formatted text message that looks like this:

Timestamp: 1/23/2006 2:24:18 AM
Timestamp local: 1/22/2006 6:24:18 PM
Timestamp F: Monday, January 23, 2006 2:24:18 AM
Timestamp local:F Sunday, January 22, 2006 6:24:18 PM

Looks pretty useful to me. Again, sorry that we couldn't make it work that way after a fresh install, but don't be afraid to crack open the source code to make these kinds of changes - after all, that's the reason we give it to you!

This posting is provided "AS IS" with no warranties, and confers no rights.