Header Ads Widget

Ticker

Detect location and local timezone of users in JavaScript

 


Detecting the location of your users can be really useful if you want to personalize your user’s experience when they browse through your website.

Want to show a uniquely tailored promotion? Want to change the language of your site or design based on where your users are coming from?

These are some common use cases for detecting user location. It can also be to limit access to your website to either remain compliant or if you simply do not yet cater to certain locations.

We will explore the various ways we can get location as well as their timezone (especially if you intend to send a lot of emails or generate a lot of reports for them).

Prerequisites

  1. Basic knowledge of JavaScript

Options for detecting location

There are two very popular ways to detect a user’s location in the browser directly:

  1. Using Geolocation API
  2. Looking up IP Address

Geolocation API Geolocation API allows you to ask the user to share their present location. You can argue that it is the most trusted method for detecting location, as the user will tell you themselves.

However, in a scenario where you want to format the content displayed to the user before it gets rendered, this isn’t exactly ideal. Also, the Geolocation API may not work depending on the user browser permission settings.

To use the Geolocation API, you can do the following:

// Excerpt from https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API
function geoFindMe() {
  if (!navigator.geolocation){
   console.log("Geolocation is not supported by your browser");
    return;
  }
  function success(position) {
    var latitude  = position.coords.latitude;
    var longitude = position.coords.longitude;
    reverseGeocodingWithGoogle(longitude, latitude)
  }
  function error() {
    console.log("Unable to retrieve your location");
  }
  navigator.geolocation.getCurrentPosition(success, error);
}

It first checks that the browser has/supports Geolocation API. If it does, it executes the rest of the code which includes a success and error callback function. The navigator.geolocation.getCurrentPosition(success, error) gives you the exact coordinates of the user which you can put into Google maps to get the exact user location.

You can send a request to Google’s reverse Geocoding API. It would require getting an API key.

function reverseGeocodingWithGoogle(latitude, longitude) {
  fetch(`https://maps.googleapis.com/maps/api/geocode/json?
      latlng=${latitude},${longitude}&key={GOOGLE_MAP_KEY}`)
  .then( res => res.json())
  .then(response => {
      console.log("User's Location Info: ", response)
   })
   .catch(status => {
      console.log('Request failed.  Returned status of', status)
   })
}

The downside of using this method is that if the user does not allow you to get their location, you cannot detect their position accurately, or you may not even detect it at all. Also, it only works on secure servers (https). It is not supported on Internet Explorer 10 and below and OperaMini.

Looking up IP Addresses This is by far the most common way of detecting user location. Unlike Geolocation IP, it can only give you limited information like Country and maybe City, depending on the IP lookup provider you are using.

Here is a simple lookup:

fetch('https://extreme-ip-lookup.com/json/')
.then( res => res.json())
.then(response => {
    console.log("Country: ", response.country);
 })
 .catch((data, status) => {
    console.log('Request failed');
 })

This works by making a request to the https://extreme-ip-lookup.com/json/ URL from the user’s browser, so that their location is detected. This resource provides country, city, time zone, longitude, and latitude among other things.

With a single query, you can tell which country a user is in and get their timezone. How convenient.

Many times, the information provided by IP lookup might be all you need. In that case, there would be no need getting the exact coordinates to pinpoint the user’s exact location in their city.

Here is a list of other places to check out when performing IP lookup:

Understand that IP lookup mostly gives you accurate information about country and timezone of the originating request. The city may be the location of the your ISP. If you intend to get the exact city or region of a user, you should use the Geolocation API and find ways to convince the user to share their location with you.

Getting local timezone of users in JavaScript

It is tempting to conclude “this is easy”. Well, you could easily create a date object and send to your server and store that time. However, it comes with a lot of challenges as you have to worry about doing internal calculations and not having them go off. This is why it is more important to fetch the user timezone above everything else.

As you saw above, we can detect timezone from IP address lookup. All you have to do is pick out the timezone from the response object along with the user location. Below, we will explore other ways of detecting timezone.

Moment Timezone

Moment Timezone has a function that guesses the timezone of a user. It is quite accurate, provided you are using the most recent version.

Below is a quick example of how it works:

<div id="guess"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.23/moment-timezone-with-data-2012-2022.min.js"></script>
<script>
  document.getElementById('guess').innerText = moment.tz.guess();
</script>

When you load the page, it shows you your current timezone. Create a index.html file and open the file in a browser. Copy the above code into the file and save it. Then refresh the browser tab you just opened. Cool right?

MomentTz uses Intl API which is a built-in JavaScript internationalization API. It also has its data bank it checks the result of the Intl API against to provide more accurate information. It also requires you to have included Moment.js before it can work.

Jstz package

Jstz is a simple lighter package for detecting timezone. I say lighter in comparison with Moment.js. It also makes use of the Intl API, so you can be confident of its results. To use the package, you can grab the CDN as follows:

<div id="guess"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstimezonedetect/1.0.6/jstz.min.js"></script>
<script>
  document.getElementById('guess').innerText = jstz.determine().name();
</script>

What you can notice from these libraries used for detecting timezone is that they do not require any network calls from your end. This means if you intend to only pick user timezones, you may not need to do IP lookups. And that’s good because they can get expensive as you’re paying for every call to the API.

Intl API itself

Don’t hate me, but let’s face it, if I showed you this, you may have ignored the rest of this article lol.

Ok, so here is how to use it:

<div id="guess"></div>
<script>
  document.getElementById('guess').innerText = Intl.DateTimeFormat().resolvedOptions().timeZone;
</script>

Before you go like “Wow!!!”, understand that the packages highlighted above take a lot into consideration when detecting timezones. This makes them slightly more accurate than Intl API alone. You may never notice the difference, but it feels good knowing someone’s got your back.

Magic App

“Talk is cheap. Show me the code.” or so the saying goes. Let’s go one better and build a really simple app that detects a user’s location and time zone information, and tell them what the time would be like in three other time zones around the world.

Here is what the simple application looks like:



Here is the how we pieced the code together to achieve that: Place it in a file named index.html

<!-- index.html -->
<!doctype html>
<html class="no-js" lang="">
<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <title>Magic App</title>
    <meta name="description" content="Magic App">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="index.css">
</head> 
<body>
    <h1>Magic App Here</h1>
    <div id="main">
        <div id="time" class="sub">
            <div>
                <h2>Your local time</h2>
                <span class="local"></span>
            </div>
            <div>
                <h2>Magic App Server Time</h2>
                <span class="server"></span>
            </div>
        </div>
        <div id="location" class="sub">
            <h2>And you are in...</h2>
            <div class="address"></div>
        </div>
    </div>
    <!-- Script here -->
</body>
</html>

Create the index.css file and add the following:

/*index.css*/
h1 {
  text-align: center
}
#main {
  max-width: 900px;
  display: table;
  margin-left: 30vw;
  margin-top: 10vh;
}

#main .sub {
  display: block;
  float: left;
  min-width: 300px;
  min-height: 300px;
  border: 0.5px solid #bbb;
  border-radius: 4px;
  padding: 15px;
}
#main .sub h2{
  font-size: 20px;
}
#main #time {
  margin-right: 5vw;
}

#main #time div {
  display: block;
  min-height: 120px;
}

This is probably the most simple application you may have ever seen. We are not doing anything fancy, just going straight to the point. Standard HTML and CSS, that’s all.

Now, let’s add the JavaScript that brings all of these to life: Add the following to index.html

[...]
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstimezonedetect/1.0.6/jstz.min.js"></script>
<script>
  document.addEventListener("DOMContentLoaded", function(event) {
      // The main sauce here
  });
</script>
[...]

We have laid the foundation for what is to come. The first thing we want to do is detect if the browser has/supports the Geolocation API:

[...]
document.addEventListener("DOMContentLoaded", function(event) {
  if (!navigator.geolocation){
    console.log("Geolocation is not supported by your browser");
    ipLookup();
  } else {
    navigator.geolocation.getCurrentPosition(success, error);
  }
  // More sauce here
});
[...]

Now, we get to execute our code if Geolocation API is available/accessible. If it is not, we just fall back to IP Lookup like we saw above.

Let’s create the successerror and ipLookup methods:

[...]
document.addEventListener("DOMContentLoaded", function(event) {
  [...]
  function success(position) {
    var latitude  = position.coords.latitude;
    var longitude = position.coords.longitude;
    reverseGeocodingWithGoogle(longitude, latitude)
  }
  function error() {
    console.log("Unable to retrieve your location");
  }
  function ipLookup() {
    fetch('https://extreme-ip-lookup.com/json/')
    .then( res => res.json())
    .then(response => {
        fallbackProcess(response)
    })
    .catch((data, status) => {
        console.log('We could not find your location');
    })
  }
    // More sauce here
});
[...]

We have already seen how these work above, so I’m going to skip explaining what each does. Let’s add the reverseGeocodingWithGoogle method now:

[...]
document.addEventListener("DOMContentLoaded", function(event) {
  [...]
  function reverseGeocodingWithGoogle(latitude, longitude) {
    fetch(`https://maps.googleapis.com/maps/api/geocode/json?
      latlng=${latitude},${longitude}&key={GOOGLE_MAP_KEY}`)
    .then( res => res.json())
    .then(response => {
      processUserData(response)
    })
    .catch(status => {
      ipLookup()
    })
  }
  // Even more sauce here
});
[...]

You may have noticed that I introduced two new functions processUserData and fallbackProcess. These are just to keep things clean and reusable. Let’s now add the both of them:

[...]
document.addEventListener("DOMContentLoaded", function(event) {
  [...]
  function processUserData(response) {
    var address = document.querySelector('.address')
    address.innerText = response.results[0].formatted_address
  }
  
  function fallbackProcess(response) {
    var address = document.querySelector('.address')
    address.innerText = `${response.city}, ${response.country}`
  }
  // timezone sauce here
});
[...]

You see the methods just perform assignments and nothing too complex. For the address variable, I should ordinarily define it in the global scope, but I brought it into the function so you would not miss it. This is not the best way to reuse code.

Now, let’s detect timezone:

[...]
document.addEventListener("DOMContentLoaded", function(event) {
  [...]
  var localTime = jstz.determine().name();
  var serverTime = "Asia/Novosibirsk";
  document.querySelector('.server').innerText = new Date().toLocaleString("en-US", {timeZone: serverTime});
  document.querySelector('.local').innerText = new Date().toLocaleString("en-US", {timeZone: localTime});
});
[...]

Conclusion

I hope this article has been useful to you. I hope it helps you improve your user experience and to build a more internationalized application with JavaScript.

One last thing that can come in handy would be to get a DateTime string in a particular timezone using JavaScript. Not to worry, I got you. Here is the simple line of code you need:

new Date().toLocaleString("en-US", {timeZone: "TIMEZONE_STRING_HERE"})

Share your experiences using these tools, let’s all learn together.

Post a Comment

0 Comments