Locales, Org timestamps, and format-time-string

The problem

Org mode's timestamps include a day-of-week part (like this: <2019-10-09 Wed> ). That day-of-week is localized.

When 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.

Setting system-time-locale

Before I found out this only works when I'm not using the Emacs daemon, I used this in my init file:

1 2 ( 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 /usr/share .

$HOME/.config/systemd/user/emacs.service :

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.

Advising format-time-string

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 and %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:

1 ( advice-add 'format-time-string :around #' kisaragi/advice-format-time-string )

Now (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.

File in my .emacs.d.