02 Oct 2017
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.
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.
I created it as an extension method for DateTime, so you can do the following:
DateTime.UtcNow.ConvertUtcToLocalDateTime().ToString()
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"
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: /// https://madskristensen.net/post/get-language-and-country-from-a-browser-in-aspnet /// </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: /// https://madskristensen.net/post/get-language-and-country-from-a-browser-in-aspnet /// </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. https://stackoverflow.com/a/41662352/4782728 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 https://stackoverflow.com/a/24907552/4782728. 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. http://www.codeshare.co.uk/blog/simple-reusable-net-caching-example-code-in-c/ /// </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); } } }