How to update time in lua to reflect system timezo

2019-07-04 15:53发布

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

  1. 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.
  2. Use the luatz library and specifically the tzcache.clear_tz_cache() function. This seems to have no effect.
  3. Retrieve the system time with functions other than os.time(): luatz.time() and luatz.gettime.gettime(). These retrieve the same time as the other functions.
  4. 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.

1条回答
男人必须洒脱
2楼-- · 2019-07-04 16:32

The low level function behind os.date, localtime(3) "acts as if it called tzset(3)", tzset uses the environmental variable TZ 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. A os.setenv is available via a few lua libraries, e.g. lua-ex

If 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 force tzset 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. In luatz you can clear it's timezone cache with require "luatz.tzcache".clear_tz_cache(), you could call this function before fetching the time.

查看更多
登录 后发表回答