Get the localization and time zone of your visitors

 
 
  • Gérald Barré

Knowing the time zone of your users is critical when your application deals with dates and times. For example, sending an email at 3 PM means something very different for someone in the USA versus someone in France. Things get more complicated because a single country can span multiple time zones: the USA has 9, while China uses only one. Many websites let users select their time zone from a drop-down list, but the experience is much better when the field is automatically pre-filled with the correct value during account creation.

The approach used in this post is to determine the location and time zone of a user based on their IP address and the GeoIP2/GeoLite2 database.

Time zone is not as trivial as it seems. You can read more about time zones in the blog post of Matt Johnson: What is a Time Zone?

Source: http://www.physicalgeography.net/fundamentals/2c.html

#GeoIP2 and GeoLite2 by MaxMind

GeoIP2 is a database that returns location information for a user based on their IP address. It also includes additional fields such as longitude, latitude, and time zone. GeoLite2 is the free version of the database and is less accurate than GeoIP2. For our purposes, high accuracy is not required. As you can see from the map above, the level of precision needed is relatively low.

There are 3 ways to use the database:

  • Using the API (paid version only)

    Good if you want to always use the most up-to-date version of the database, or don't want to host the database on your servers.

  • Using the MaxMind DB format

    A single binary file of about 50MB that can be read using the NuGet package MaxMind.GeoIP2 (.NET and .NET Core). Very easy to use and update.

  • Using CSV files

    Use this if you want to import the data into a SQL database or somewhere else. By the way, Meziantou.Framework.Csv does have a CSV reader. You can also look at the previous post to quickly insert the millions of records of the GeoIP2 database into a SQL Server database.

#Use the GeoIP2 database

In this blog post, I'll use the GeoLite2 database in the MaxMind DB format because it's free and easy to use. Before using the following code, you must:

In an ASP.NET application, you can get the IP address of the user using HttpContext.Current.Request.UserHostAddress. If you have a proxy or a load balancer, you may use the value of the X-Forwarded-For header (read more).

C#
class IPMapping
{
    public string Country { get; set; }
    public string IanaTimeZone { get; set; }

    public static IPMapping Load(HttpContext context)
    {
        return Load(context.Request.UserHostAddress);
    }

    public static IPMapping Load(string address)
    {
        return Load(IPAddress.Parse(address));
    }

    public static IPMapping Load(IPAddress ipAddress)
    {
        using (var reader = new DatabaseReader(@"GeoLite2-City.mmdb"))
        {
            var city = reader.City(ipAddress);
            if (city == null)
                return null;

            var mapping = new IPMapping();
            mapping.Country = city.Country.IsoCode;
            mapping.IanaTimeZone = city.Location.TimeZone;
            return mapping;
        }
    }
}

You now have the location information for the user. Note the IanaTimeZone property name: there are two main time zone databases, IANA and Windows, and you may need to convert between them.

#Converting IANA to Windows time zone identifier

The IANA time zone database is the most widely used standard for referencing time zones by name. Many libraries use it, including GeoIP2. In .NET, NodaTime supports IANA time zones.

Windows uses its own time zone database rather than IANA. You can access it in .NET via the TimeZoneInfo class.

Here's an example of a time zone identifier in both systems:

  • IANA: "America/New_York"
  • Windows: "Eastern Standard Time"

So, you can use the IANA time zones directly using NodaTime. But, if you need to interact with other applications that use Windows time zones, you must convert the value provided by GeoIP2. For instance, SQL Server 2016 has introduced a new view to get time zones sys.time_zone_info which uses the Windows time zones. So, if you store the time zone of the user in the database, and you want to do some time computations directly in SQL using this view, you need to store the Windows identifier.

There are 2 main ways to convert time zone identifiers. Both use CLDR (Common Locale Data Repository), so you should get the same result.

  • Using NodaTime

    C#
    private string IanaToWindows(string ianaZoneId)
    {
        var utcZones = new[] { "Etc/UTC", "Etc/UCT", "Etc/GMT" };
        if (utcZones.Contains(ianaZoneId, StringComparer.Ordinal))
            return "UTC";
        var tzdbSource = NodaTime.TimeZones.TzdbDateTimeZoneSource.Default;
        // resolve any link, since the CLDR doesn't necessarily use canonical IDs
        var links = tzdbSource.CanonicalIdMap
            .Where(x => x.Value.Equals(ianaZoneId, StringComparison.Ordinal))
            .Select(x => x.Key);
    
        // resolve canonical zones, and include original zone as well
        var possibleZones = tzdbSource.CanonicalIdMap.ContainsKey(ianaZoneId)
            ? links.Concat(new[] { tzdbSource.CanonicalIdMap[ianaZoneId], ianaZoneId })
            : links;
    
        // map the windows zone
        var mappings = tzdbSource.WindowsMapping.MapZones;
        var item = mappings.FirstOrDefault(x => x.TzdbIds.Any(possibleZones.Contains));
        if (item == null)
            return null;
    
        return item.WindowsId;
    }
  • TimeZoneNames (GitHub, NuGet)

    C#
    TimeZoneNames.TZNames.GetNamesForTimeZone(iana, "en-US");

#Conclusion

If your application serves users worldwide and works with dates and times, you need to handle their time zones. Rather than asking users to manually select their time zone, you can infer it from their IP address using the GeoIP2 database. Keep in mind that some users may be behind a proxy or VPN, which can mask their actual IP address.

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?