This was harder than it should be

This post gives you the code I found to help you get a user's country and culture code and display a UTC date and time relative to the user who is viewing in their browser.

How does it work?

There is some code to resolve the culture and country from the user's browser. Then there is a method to get the time zone based on the country code (this falls down when you have countries like USA with multiple time zones). It uses a NuGet package called NodaTime to help with the time zones. Once you have the time zone there is a method to create a new local date using the utc datetime and the time zone. It also uses session to store the resolved country and caching for the time zone.

How do you use it?

I created it as an extension method for DateTime, so you can do the following:


Before you get started.

You will need to install NodaTime from NuGet, so open Package Manager Console and add it using this command.

Install-Package NodaTime -Version 2.2.0

If you want to install the latest version then take off "-Version 2.2.0"

Here's the code:

using NodaTime;
using NodaTime.TimeZones;
using System;
using System.Globalization;
using System.Linq;
using System.Web;
namespace CodeShare.Library.Globalization {
    /// <summary>     /// A set of methods to help display Utc DateTimes based on the language and country code in the user's browser.     /// </summary>     public static class CultureHelper     {
        /// <summary>         /// Gets and sets the country code in the session to save keep resolving it from the browser every time.         /// </summary>         /// <returns>A two letter country code</returns>         private static string UserCountryCode()         {             const string sessionKeyName = "UserCountryCode";             string countryCode = "";
            if (HttpContext.Current.Session[sessionKeyName] == null)             {                 countryCode = ResolveCountry().ToString();                 HttpContext.Current.Session[sessionKeyName] = countryCode;             }             else             {                 countryCode = (string)HttpContext.Current.Session[sessionKeyName];             }             return countryCode;         }
        /// <summary>         /// Gets the Culture from the browser. Found this here:         ///         /// </summary>         /// <returns>A CultureInfo object based on the user language from the browser</returns>         public static CultureInfo ResolveCulture()         {             string DEFAULT_CULTURE = System.Web.Configuration.WebConfigurationManager.AppSettings["DefaultCountryLCID"].ToLower();             string[] languages = HttpContext.Current.Request.UserLanguages;
            if (languages == null || languages.Length == 0)                 return CultureInfo.CreateSpecificCulture(DEFAULT_CULTURE);
            try             {                 string language = languages[0].ToLowerInvariant().Trim();                 return CultureInfo.CreateSpecificCulture(language);             }             catch (ArgumentException)             {                 return CultureInfo.CreateSpecificCulture(DEFAULT_CULTURE);             }         }
        /// <summary>         /// Gets the Culture from the browser. Found this here:         ///         /// </summary>         /// <returns>A RegionInfo object based the users CultureInfo</returns>         public static RegionInfo ResolveCountry()         {             int DEFAULT_LCID = int.Parse(System.Web.Configuration.WebConfigurationManager.AppSettings["DefaultCountryLCID"]);             CultureInfo culture = ResolveCulture();             return new RegionInfo(culture != null ? culture.LCID : DEFAULT_LCID);         }
        /// <summary>         /// An extension method for DateTime. It converts a Utc DateTime to a local date time using the user's time zone         /// </summary>         /// <param name="utcDateTime">The datetime object which the extension method is called from</param>         /// <param name="cachingTimeInMins">Caching time in minutes, used so you don't need to keep getting the timezone every time.</param>         /// <returns>A DateTime object local to the user</returns>         public static DateTime ConvertUtcToLocalDateTime(this DateTime utcDateTime)         {             int cachingTimeInMins = int.Parse(System.Web.Configuration.WebConfigurationManager.AppSettings["TimeZoneCachingTimeInMins"]);             //I got this line from StackOverflow. It helped a lot at the end.             var newUtcDateTime = DateTime.SpecifyKind(utcDateTime, DateTimeKind.Utc);             DateTimeZone TimeZone = GetDateTimeZoneFromCache(UserCountryCode(), cachingTimeInMins);             DateTime objdate = Instant.FromDateTimeUtc(newUtcDateTime)                       .InZone(TimeZone)                       .ToDateTimeUnspecified();             return objdate;         }
        /// <summary>         /// Gets the time zone using the country code         /// I got this code from here I'm so grateful as it really helped.         /// </summary>         /// <param name="countryCode">Two digit country code.</param>         /// <returns>A DateTimeZone from NodaTime based on the country code.</returns>         public static DateTimeZone GetDateTimeZoneFromCountryCode(string countryCode)         {             var CountryInfo = (from location in TzdbDateTimeZoneSource.Default.ZoneLocations                                where location.CountryCode.Equals(countryCode,                                           StringComparison.OrdinalIgnoreCase)                                select new { location.ZoneId, location.CountryName })                              .FirstOrDefault();             DateTimeZone TimeZone = DateTimeZoneProviders.Tzdb[CountryInfo.ZoneId];             return TimeZone;         }
        /// <summary>         /// Calls the method for getting the TimeZone from the country code, but sets the country code before calling it.          /// Helps to do this when using caching and the delegate method would have had parameters.         /// </summary>         /// <returns>A DateTimeZone from NodaTime based on the country code.</returns>         public static DateTimeZone GetDateTimeZone()         {             return GetDateTimeZoneFromCountryCode(UserCountryCode());         }
        /// <summary>         /// Gets the DateTimeZone from the cache, or from the GetDateTimeZone if it's not in the cache yet.         /// See this post for a simple .NET caching example.         /// </summary>         /// <param name="countryCode">Two digit country code.</param>         /// <param name="cachingTimeInMins">Caching time in minutes, used so you don't need to keep getting the timezone every time.</param>         /// <returns></returns>         public static DateTimeZone GetDateTimeZoneFromCache(string countryCode, int cachingTimeInMins)         {             return Caching.GetObjectFromCache("dateTimeZone-" + countryCode, cachingTimeInMins, GetDateTimeZone);         }     } }

Want to thank me?

If I've helped you out and you want to thank me, why not buy me a coffee?

About the author

Paul Seal

Umbraco MVP and .NET Web Developer from Derby (UK) who specialises in building Content Management System (CMS) websites using MVC with Umbraco as a framework. Paul is passionate about web development and programming as a whole. Apart from when he's with his wife and son, if he's not writing code, he's thinking about it or listening to a podcast about it.

Related Posts

How to create a dynamic image from multiple images using ImageProcessor

This post shows you how I created a handler to create dynamic header images for my umbraco website u…

Read Post

How to send Slack messages programmatically using C#

In this post I show you how you can send Slack messages programmatically using C#

Read Post

How to include scripts from partial views in MVC and Umbraco

This post will show you how you can include scripts from inside an MVC partial view rather than havi…

Read Post

How to fix the error "Authentication failed because the remote party has closed the transport stream"

This post will show you how to fix the error "Authentication failed because the remote party has clo…

Read Post