LessonsExtensions: Built inLesson 74

Time zones

The time anywhere in the world with the Time Zone library: the zones it knows, their offsets from UTC, summer time, a zone's clock for any moment, and a meeting time from one city to another.

  • Extensions
  • Lesson 74 of 83
  • About 10 minutes
  • 7 examples

The world's time zones

It is noon in London when it is nine at night in Tokyo, and the difference changes twice a year in many places, on dates each country chooses. The Time Zone library carries the tz database, the list of every zone's rules that phones and computers everywhere use, so a program can know the time anywhere. Each zone has a name, a region and its biggest city: Europe/Paris, America/New_York. TimeZones lists them:

ExampleRun it in the playground
put TimeZones() into tZones
put the number of lines of tZones > 300
put "Europe/Paris" is among the lines of tZones
put "Asia/Kolkata" is among the lines of tZones
put "Mars/Olympus_Mons" is among the lines of tZones
Output
true
true
true
false

Use these names, not abbreviations like EST or CET: an abbreviation can mean different zones in different countries, and does not say whether summer time applies.

Offsets from UTC

Every zone's clock is a number of hours ahead of or behind UTC, the world's reference time, which is the time in London in winter. the seconds counts in UTC, the same at that moment everywhere. FromUniversalTime takes a moment in UTC seconds and a zone, and gives that zone's clock as seconds, so the difference is the zone's offset:

ExampleRun it in the playground
put 1768478400 into tMoment -- 12:00 UTC on 15 January 2026
repeat for each item tZone in "Europe/London,America/New_York,Asia/Tokyo,Asia/Kolkata,Asia/Kathmandu,Australia/Sydney"
   put tZone && (FromUniversalTime(tMoment, tZone) - tMoment) / 3600
end repeat
Output
Europe/London 0
America/New_York -5
Asia/Tokyo 9
Asia/Kolkata 5.5
Asia/Kathmandu 5.75
Australia/Sydney 11

An offset is not always whole hours: India is five and a half hours ahead, Nepal five and three quarters. Sydney is eleven ahead in January because it is summer there.

Summer time

Many zones put their clocks forward an hour in summer, and each does it on its own dates; the south's summer is the north's winter. The same zone at two moments half a year apart:

ExampleRun it in the playground
function offsetHours pZone, pMoment
   return (FromUniversalTime(pMoment, pZone) - pMoment) / 3600
end offsetHours

put 1768478400 into tJanuary -- 12:00 UTC on 15 January 2026
put 1784116800 into tJuly -- 12:00 UTC on 15 July 2026
repeat for each item tZone in "Europe/London,America/New_York,Australia/Sydney,Asia/Tokyo"
   put tZone && offsetHours(tZone, tJanuary) && offsetHours(tZone, tJuly)
end repeat
Output
Europe/London 0 1
America/New_York -5 -4
Australia/Sydney 11 10
Asia/Tokyo 9 9

London and New York go forward in July, Sydney goes back, and Japan has no summer time at all. The library knows the rules for past years too, as they were, so old dates come out right.

A zone's clock

The seconds FromUniversalTime gives are a clock reading: read as if they were UTC, they show that zone's time. A day is 86400 seconds and an hour 3600, so mod and div take the hours and minutes out, and comparing whole days says whether the zone is already in the next day, or still in the day before:

ExampleRun it in the playground
function clockOf pSeconds
   return format("%02d:%02d", (pSeconds mod 86400) div 3600, (pSeconds mod 3600) div 60)
end clockOf

function dayOf pLocal, pMoment
   put (pLocal div 86400) - (pMoment div 86400) into tDays
   if tDays > 0 then return "the next day"
   if tDays < 0 then return "the day before"
   return "the same day"
end dayOf

put 1768478400 into tMoment -- 12:00 UTC on 15 January 2026
put "UTC" && clockOf(tMoment)
repeat for each item tZone in "America/Los_Angeles,Asia/Tokyo,Pacific/Auckland"
   put FromUniversalTime(tMoment, tZone) into tLocal
   put tZone && clockOf(tLocal) && dayOf(tLocal, tMoment)
end repeat
Output
UTC 12:00
America/Los_Angeles 04:00 the same day
Asia/Tokyo 21:00 the same day
Pacific/Auckland 01:00 the next day

With the seconds in place of the fixed moment, it is a world clock, a different answer every minute:

ExampleRun it in the playground
function clockOf pSeconds
   return format("%02d:%02d", (pSeconds mod 86400) div 3600, (pSeconds mod 3600) div 60)
end clockOf

put the seconds into tNow
repeat for each item tZone in "Europe/London,America/New_York,Asia/Tokyo"
   put tZone && clockOf(FromUniversalTime(tNow, tZone))
end repeat
Output (yours will differ: it changes from run to run)
Europe/London 14:05
America/New_York 09:05
Asia/Tokyo 23:05

A meeting across zones

ToUniversalTime goes the other way: from a zone's clock reading, as seconds, to the moment in UTC. A clock reading as seconds is the day's midnight, counted as if in UTC, plus the hours and minutes. A call set for nine in the morning in New York on 15 June 2026, from Paris and Tokyo:

ExampleRun it in the playground
function clockOf pSeconds
   return format("%02d:%02d", (pSeconds mod 86400) div 3600, (pSeconds mod 3600) div 60)
end clockOf

put 1781481600 into tDay -- midnight at the start of 15 June 2026, as UTC seconds
put tDay + 9 * 3600 into tNine -- 9:00 on that day, on a clock
put ToUniversalTime(tNine, "America/New_York") into tMoment
put "UTC" && clockOf(tMoment)
put "Paris" && clockOf(FromUniversalTime(tMoment, "Europe/Paris"))
put "Tokyo" && clockOf(FromUniversalTime(tMoment, "Asia/Tokyo"))
Output
UTC 13:00
Paris 15:00
Tokyo 22:00

Store moments in UTC seconds, as the seconds gives them, and turn them into a zone's clock only to show them. Then a list of events from people in many zones sorts into the right order, and summer time never makes one an hour out.

Try it yourself

Write a function isWorkingHours that takes a zone and a moment in UTC seconds, and returns true when that zone's clock is from 9:00 up to but not including 17:00, so a program can say whom it is a good time to call.

Show a solution
ExampleRun it in the playground
function isWorkingHours pZone, pMoment
   local tHour
   put (FromUniversalTime(pMoment, pZone) mod 86400) div 3600 into tHour
   return tHour >= 9 and tHour < 17
end isWorkingHours

put 1768478400 into tMoment -- 12:00 UTC on 15 January 2026
repeat for each item tZone in "Europe/Berlin,America/Chicago,Asia/Singapore"
   put tZone && isWorkingHours(tZone, tMoment)
end repeat
Output
Europe/Berlin true
America/Chicago false
Asia/Singapore false

Words in this lesson

Each one finds every lesson that uses it. The Dictionary has the whole story of each, with its syntax and more examples.