How to find the current time for any location using Mapbox free tier

shademap.app simulates shadows based on the current time of day, but the current time of day in Seattle is not the current time of day in London. When it’s morning in Seattle and the shadows are cast to the west, it’s already evening in London and the shadows are being cast to the east.

While ShadeMap has always displayed the correct shadows, it has never adjusted times based on the location of the map. This meant that if a user from Seattle wanted to view 7AM shadows in London, they would have to set the time slider to 11PM Pacific Standard Time (which is 7AM in London). This is confusing as most people would not consider 11PM to be an early morning time. You can see an example of this below.

ShadeMap user time

ShadeMap needed to display times using the map’s timezone, not the user’s timezone. In this case, September 22nd in London, the correct timezone to use is British Summer Time. Using British Summer Time, ShadeMap displays a more sensible morning time of 7:44AM.

ShadeMap location time

So how do I display a given location’s current time? I will give the answer in this blogpost. That answer can be broken down into two parts:

  1. Using the Javascript Date object
  2. Using freely available data to lookup a location’s timezone

Javascript Date object #

The Javascript side is simple. The Date object’s .toLocaleTimeString() method takes timeZone as a parameter. If the timeZone parameter is present, the method returns the time in the given timezone (don’t forget to capitalize the Z or the timeZone parameter will be ignored and the method will return your local time). I am in Seattle, so 7:00AM here is 10:00AM in New York City:

>> (new Date("11/18/22 7:00 AM")).toLocaleTimeString([], { 
  timeZone: "America/New_York" 
});
// "10:00:00 AM"

You can try another timeZone if you know it’s name by heart, but if you don’t then know that they come from tz database which is a collaborative compilation of information about timezones intended to be used by computers. You can find available names in this table under the TZ database name column.

Timezone data for maps #

Because ShadeMap already uses Mapbox, I wanted a solution that worked well with Mapbox GL JS. There is this example of using Mapbox Tilequery API to lookup timezones but Mapbox only offers 100,000 free requests per month. Because we need to calculate the timezone any time the user moves or zooms the map these requests will add up.

Mapbox timezone tileset

Instead, you can merge the vector map tileset used by the Tilequery example (examples.4ze9z6tv) with your current Mapbox tileset (ShadeMap uses Outdoors) using Mapbox Studio and then use the Mapbox GL JS queryRenderedFeatures([x,y]) method to retrieve the timezone data from your map itself. Below is a screenshot of adding the timezone data as a new Layer on my existing tileset. (I set the layer opacity to 0 to make it invisible)

Mapbox Studio timezones

In my tileset the Layer is named Timezones and this is how I retrieve the the timezone name for the center of the map anytime the map is moved (using the moveend event)

map.queryRenderedFeatures(
    [window.innerWidth / 2, window.innerHeight / 2],
    {
      layers: ["Timezones"],
    }
  );

One problem #

The Mapbox timezone tileset does not work in all cases. It does not define timezones in the oceans and does not have data for low zoom levels. In those instances, queryRenderedFeatures will fail to return a timezone. (update: the return value could also be “unihabited”, which you should treat as undefined) If this happens, we can approximate the timezone by using 15 degree bands of longitude as illustrated on this early time zone chart.

Early timezone chart

For each 15 degrees we travel west or east, the time changes by one hour and we can use the corresponding timezone names of Etc/GMT+1, Etc/GMT+2, etc or Etc/GMT-1, Etc/GMT-2, etc to adjust the users time accordingly. Below is a Javascript implementation of this approximation:

const getLongitudeTimeZone = (lng: number) => {
  return `Etc/GMT+${-Math.ceil((lng / 7.5 - 1) / 2)}`.replace("+-", "-");
};

The approach documented in this blog is not perfect, but it is the approach that ShadeMap currently uses in production. It is cross browser, performant and best of all, it is free if you already use Mapbox GL JS to host your maps.

For more updates on how I build ShadeMap, you can follow me on Twitter @shademap

 
6
Kudos
 
6
Kudos

Now read this

Mount Rainier

I first tried to climb Mt Rainier in 2011 with my friends Jordan and Jake. I had just spent the previous week on the John Muir trail camping above 12,000’ so I felt acclimated and confident I could do it. Jake was not feeling his best... Continue →