Locales, Org timestamps, and format-time-string
Table of Contents
system-time-localeUse English on my PCSetting $LANG or $LC_TIME for the serverAdvising
Org mode's timestamps include a day-of-week part (like this:
<2019-10-09 Wed>). That day-of-week is localized.
org-todo is rescheduling a repeating task and encounters a timestamp with a day-of-week in a different locale (eg.
<2019-10-09 水 .+1d>), it updates the format but does not update the time described by the timestamp. When this happens, I have to run
org-todo again, potentially creating a duplicate entry in the heading's logbook. This is normally not an issue, unless you edit the same Org file from two machines in different locales — exactly why I'm bitten by this quirk.
I use Emacs in Termux on my phone, which does not offer locales other than English (not sure if it's an English locale or POSIX). I use Emacs in server mode on my PC (set to the Japanese locale because I'm used to it), which for some reason does not honor
system-time-locale and only uses the system-wide locale. Now I have two machines accessing the same Org file, stuck in two different locales.
There are several issues at display. It'd be nice if Termux, or Android, offers more locales.
org-todo should update both the format and the time / date of the timestamp. Emacs should honor
system-time-locale even when run in server mode.
All of the above is really complicated. For now, I just want a workaround to the problem.
Before I found out this only works when I'm not using the Emacs daemon, I used this in my init file:
(setq system-time-locale "en") (setenv "LANG" "en")
which normally makes
format-time-string use English day-of-week names, and makes sure any processes started from this Emacs also do that. This did not work:
(format-time-string "%a") still returns "水" on a Wednesday on my PC.
Use English on my PC
Of course this workaround can always be done, but I don't want to change the rest of my system just to work around one issue in Emacs.
Setting $LANG or $LC_TIME for the server
Another workaround is to set $LANG so that Emacs server thinks the system is in the English locale.
As I use Emacs 26's new systemd unit, I have to copy the user unit to my home directory and use it to overwrite the unit in
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[Unit] Description=Emacs text editor Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/ [Service] Type=simple ExecStart=/usr/bin/emacs --fg-daemon ExecStop=/usr/bin/emacsclient --eval "(kill-emacs)" Environment=SSH_AUTH_SOCK=%t/keyring/ssh # added by me Environment=LC_TIME=en Restart=on-failure [Install] WantedBy=default.target
This also does not work. Something with the English locale, or the way I set it, makes it so that Fcitx doesn't work in Emacs anymore. I have to do something else.
Eventually, I decided the best way to do this is probably to advise
format-time-string. While the advice will not affect calls to it from C code, since Org is all Emacs Lisp, this should make sure Org timestamps are all in English.
Luckily, calendar.el provides
calendar-day-name that returns day name for any date, so we simply have to convert an existing (or current) time value to Calendar's format (month, day, year).
1 2 3 4 5 6 7 8 9 10 11
(defun kisaragi/english-dow (&optional time zone abbreviated) "Return ABBREVIATED name of the day of week at TIME and ZONE. If TIME or ZONE is nil, use `current-time' or `current-time-zone'." (unless time (setq time (current-time))) (unless zone (setq zone (current-time-zone))) (calendar-day-name (pcase-let ((`(,_ ,_ ,_ ,d ,m ,y . ,_) (decode-time time zone))) (list m d y)) abbreviated))
Then we need to parse the format, replace the
%A format strings.
1 2 3 4 5 6 7 8 9 10
(defun kisaragi/advice-format-time-string (func format &optional time zone) "Pass FORMAT, TIME, and ZONE to FUNC. Replace \"%A\" in FORMAT with English day of week of today, \"%a\" with the abbreviated version." (let* ((format (replace-regexp-in-string "%a" (kisaragi/english-dow time zone t) format)) (format (replace-regexp-in-string "%A" (kisaragi/english-dow time zone nil) format))) (funcall func format time zone)))
Then add the advice:
(advice-add 'format-time-string :around #'kisaragi/advice-format-time-string)
(format-time-string "%a") always returns the abbreviated English day-of-week, regardless of system locale, and I don’t have to occasionally run
org-todo twice anymore. Hopefully.