Problem
I want to modify the awful.widget.textclock
widget in awesome-wm to immediately reflect a change in the system timezone. This widget and all of the awesome-wm config is written in lua.
Currently, if the system timezone is changed the widget continues to display the time according to the timezone set at runtime. The widget uses the os.time
function to retrieve the time, but this does not match the system time.
Solution as provided below
lua script:
local tz=require"luatz";
require "luatz.tzcache".clear_tz_cache()
print("Before Changes (America/Los_Angeles)")
print(os.date("!%H:%M",tz.time_in()))
os.execute("timedatectl set-timezone America/Chicago")
require "luatz.tzcache".clear_tz_cache()
print("America/Chicago")
print(os.date("!%H:%M",tz.time_in()))
os.execute("timedatectl set-timezone America/New_York")
require "luatz.tzcache".clear_tz_cache()
print("America/New_York")
print(os.date("!%H:%M",tz.time_in()))
Output:
Before Changes (America/Los_Angeles)
15:33
America/Chicago
17:33
America/New_York
18:33
Workaround
This can be resolved by restarting awesome window manager, which causes the widget to get the correct timezone again. Naturally, if the timezone changes this requires another restart of the window manager.
The desired effect is to update the timezone from the system when it changes, either periodically or every time the os.time
function is called.
Use Case
If you are curious, the use case for this is on a laptop. I travel frequently and run tzupdate
on a systemd timer
. I would like to automate changing my timezone. This works fine except that the widget which actually displays the time doesn't notice the system timezone change.
Tried so far
- Unset the '$TZ' environment variable. But Arch Linux never sets this variable in the first place, so I am unsure how lua even determines the correct timezone.
- Use the
luatz
library and specifically thetzcache.clear_tz_cache()
function. This seems to have no effect. - Retrieve the system time with functions other than
os.time()
:luatz.time()
andluatz.gettime.gettime()
. These retrieve the same time as the other functions. - Use the
luatz.time_in()
function, but this returns the time with the timezone offset applied twice to UTC time.luatz.time()
returns the correct local time, but is supposed to return UTC time.
UPDATED luatz
I tried messing with the luatz
library as recommended, but it doesn't seem to recheck the system timezone, even after calling the tzcache.clear_tz_cache()
function.
I cloned the luatz
repo and copied luatz
to the system modules directory. The script seems to load correctly, but has not changed the effect of ignoring the system timezone change. As far as I can tell, this does not do anything differently than the os.time()
function.
luatz test script:
local luatz = require "luatz"
local tzcache = require "luatz.tzcache"
local gettime = require "luatz.gettime"
print ("\nBefore Change - System TZ is ")
os.execute("timedatectl | grep 'Time zone' | awk '{ print $3 }'")
print("\nos.time(): "..os.date("%H:%M", os.time()))
print("luatz.time(): "..os.date("%H:%M", luatz.time()))
print("gettime..gettime(): "..os.date("%H:%M", gettime.gettime()))
print("\nTime zone changed to America/New_York")
os.execute("timedatectl set-timezone America/New_York")
tzcache.clear_tz_cache()
print ("\nAfter Change - System TZ is ")
os.execute("timedatectl | grep 'Time zone' | awk '{ print $3 }'")
print ("\nos.time(): "..os.date("%H:%M", os.time()))
print ("luatz.time(): "..os.date("%H:%M", luatz.time()))
print("gettime.gettime(): "..os.date("%H:%M", gettime.gettime()))
Output:
Before Change - System TZ is
America/Los_Angeles
os.time(): 11:54
luatz.time(): 11:54
gettime..gettime(): 11:54
Time zone changed to America/New_York
After Change - System TZ is
America/New_York
os.time(): 11:54
luatz.time(): 11:54
gettime.gettime(): 11:54
luatz.time_in()
So the luatz.time_in()
function does update as the system time zone changes and I am excited about that! However, time_in()
does not display the correct local time. It adds the timezone offset to the correct local time, resulting in the time being several hours behind. I experimented with setting the TZ
environment variable, but this had no effect. For some reason, luatz.time()
is returning local time and luatz.time_in()
is returning the result of applying the timezone offset twice.
lua script:
local tz=require"luatz";
require "luatz.tzcache".clear_tz_cache()
print("Before Changes (America/Los_Angeles)")
print(os.date("%H:%M",tz.time_in()))
os.execute("timedatectl set-timezone America/Chicago")
require "luatz.tzcache".clear_tz_cache()
print("America/Chicago")
print(os.date("%H:%M",tz.time_in()))
os.execute("timedatectl set-timezone America/New_York")
require "luatz.tzcache".clear_tz_cache()
print("America/New_York")
print(os.date("%H:%M",tz.time_in()))
output:
Before Changes (America/Los_Angeles)
08:59
America/Chicago
10:59
America/New_York
11:59
Actual system local time: 15:59
.
The low level function behind
os.date
,localtime(3)
"acts as if it calledtzset(3)
",tzset
uses the environmental variableTZ
to determine the timezone, and if that doesn't exist, read from/etc/localtime
.Environmental variables are mostly determined before your program starts, therefore, to get your timezone change to take place, you could find a way to set your
TZ
variable. Aos.setenv
is available via a few lua libraries, e.g. lua-exIf that doesn't seem a reasonable course of action, you might be able to just ensure your script is started without
TZ
set at all; which will forcetzset
to read from/etc/localtime
. Sadly, most of the time this file is cached, and you will not get updates; this depends on your system.Alternatively, you could use a different library to obtain the time, instead of the
os
library. Inluatz
you can clear it's timezone cache withrequire "luatz.tzcache".clear_tz_cache()
, you could call this function before fetching the time.