Get the localization and time zone of your visitors

Brouillon

À venir

0 comment

Knowing the time zone of your customers is very important when your application needs to deal with date and time. Indeed, sending an email at 3PM is not the same for someone in USA or someone in France. This is more complicated; a country can span across many different time zones. For instance, USA has 9 different time zones, whereas China decided to use only one time zone. On many web sites, you can select your time zone using a drop-down list. However, this is easier when the field is automatically set with the expected value when you create your account.

The solution I use in this post is to get the location and the time zone of the user based on his or her 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 allows to get the location of a user based on his or her IP address. The database also contains additional information such as longitude, latitude, time zone, and a few more fields. GeoLite2 is the free version of the database, and is less accurate than GeoIP2. For our needs, we don't need a good accuracy. Indeed, if you look at the map here-before, you notice the precision is not very important.

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, CodeFluent Runtime 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).

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 information you want on the user. But, you may notice the name of the property IanaTimeZone. In fact, there are 2 main time zone databases: IANA and Microsoft. So, you may need a conversion!

Converting IANA to Windows time zone identifier

The IANA time zones database is probably the most used standard to reference a time zone by name. A lot of libraries use it, including GeoIP2. in .NET, NodaTime allows to use IANA time zones.

Windows doesn't use the IANA time zone database (it would be too easy). Instead, Microsoft has its own time zone database. You can use it in .NET framework by using 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 which 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
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.TZNames.GetNamesForTimeZone(iana, "en-US");

Conclusion

If your application is used all around the world and uses dates and times, you will need to use the time zone of the user. Instead of asking users to set his/her time zone, you can guess it using their IP address and the GeoIP2 database. Be careful, some users use a proxy or a VPN which may hide their actual IP address.

Gérald Barré