Introduction

I am writing ASP.NET Core code for an automated cloud directory synchronization process that will run on Google Cloud Run and Kubernetes. This code requires the current date and time in the local time zone. Simple problem, or so I thought.

This is the original code that I wrote.

This code works just fine on my development system, Microsoft Windows [Version 10.0.19041.508]. This code failed when I deployed to Google Cloud Run based on the container image mcr.microsoft.com/dotnet/core/aspnet:3.1.

The error message:

I then deployed the same code as a Docker container and received the same error. That is good as the debugging cycle is faster with Docker and confirms that this is not a Google Cloud Run issue. Note: the error message is a Bootstrap dialog that reports error messages in the client browser. The actual error text is “The time zone ID ‘Pacific Standard Time’ was not found on the local computer” is reported via an exception caught in the calling code.

The first solution

A quick solution is to add exception handling code in my function and return UTC time instead of local time if my code hits an exception.

Some developers might stop there and just report this as fixed with a runtime exception notice in the README. I wanted to understand why this code failed.

Investigate

I then wrote a simple program that I could run under Windows and Docker.

I ran this program under Windows, and below is a partial output. Looks normal. I am just displaying the entry for Pacific Standard Time to keep this short:

I ran the code under Docker. The time zone IDs are different. Linux has its own way of naming things that are often different from Windows.

This shows that there are two different time zone IDs:

  • Windows -> “Pacific Standard Time”
  • Linux -> “America/Los_Angeles”

Windows maintains a list of time zones in the registry. These values are published here (link).

Linux distributions use the list of time zones maintained by the Internet Assigned Numbers Authority (IANA) (link).

Why didn’t I just use a library or .NET package?

I did not want to introduce another dependency. In our environment, which is high security (government and financial industries), source code must go through a review process. By keeping the required code to a minimum, the code review is simpler. Plus external libraries might not pass our tests delaying the project.

Code, test, investigate and repeat

I rewrote GetLocalDateTime() to catch the exception and try again using the other time zone ID.

That worked. However, the returned date/time string is different (which I am ignoring for this project). Windows 10 is returning the time based upon a 12-hour clock with AM/PM and Linux is returning the time based upon a 24-hour clock. The solution is to use a custom date and time format string (link).

Windows:

  • 9/20/2020 11:08:19 AM

Linux:

  • 09/20/2020 11:08:12

I did not like the new code. Seems sloppy and lazy. Let’s dig further into the error that is happening.

I removed the try/catch code so I could see the exception type and error message. The line of code that is failing is:

The exception message:

Now I know the exception name (System.TimeZoneNotFoundException). Reviewing the documentation for FindSystemTimeZoneById() (link), shows that TimeZoneNotFoundException is documented with this description:

Microsoft description:

The time zone identifier specified by id was not found. This means that a time zone identifier whose name matches id does not exist, or that the identifier exists but does not contain any time zone data.

Let’s move FindSystemTimeZoneById() into a separate function passing the two different time zone IDs, one for Windows and the other for Linux, and let the function try each one until success. If neither time zone IDs work, this function returns null.

Now we can rewrite the GetLocalDateTime() function:

The function GetTimeZoneInfo() uses exceptions to handle errors. Another technique is to loop through the list of time zones and check if the ID exists:

If you prefer to use Linq:

Benchmark

Which coding technique should I use in my code? I decided to benchmark each GetTimeZoneInfo function. The results surprised me.

I wrote a test harness and called each function ten million times under Windows 10 and under Docker Linux on Windows 10:

Windows 10 native:

Docker Linux on Windows 10:

Highlights:

  • Docker is much slower.
  • Why is Method #1 so much slower on Docker?
  • Why is Method #2 the slowest on Window but the fastest on Docker?

I reversed the order of the comparison by swapping tz_id_1 and tz_id_2. This showed some interesting changes in timings.

Windows 10 native:

Docker Linux on Windows 10:

The numbers look much better for Docker now. My theory for Method #1 is that c# exceptions are expensive.

Since exceptions are expensive, check the platform we are running on to provide hints for our code.

New benchmark results.

Windows 10 native:

Docker Linux on Windows 10:

By using hints, Method #1 is now the best method. By selecting the correct time zone ID for the platform, we remove exceptions that are slow.

Final Code

Here are the results of this work. After testing this, I changed the code to put the time zone IDs in appSettings.json and then wired up configurations. The configuration changes are not part of this code to keep things simple.

Summary

My solution may not be the best solution for you. Managing time zones is complicated. The details of existing IDs change and new IDs are created. Getting the world to agree on time is very problematic. For example, there was a time zone update this month (September) and one last month (link). Time keeps changing …

  • If you only deploy on one platform, then you will not worry about time zone ID differences between Windows and Linux.
  • You may prefer to use a .NET package and not worry about the details. Example .NET package:
  • Instead of storing two separate time zone IDs, you may prefer a lookup table.
  • Consider storing and displaying all dates and times in UTC. Time zones do not matter for UTC.

Note: I chose not to use a lookup table as that is another item to manage, test and verify. Time zone details keep changing. I decided to require the administrator who configures the application, to lookup the correct time zone IDs instead of hard coding them in source code.

More Information