bug#81582: 32.0.50; gnus-search: with-locale-environment for a month name breaks tty display

Colin McLear <[email protected]>
Newsgroups gmane.emacs.bugs
Message-ID <[email protected]>
This may be a very small edge case, but I ran into it while working on a
port of emacs to ipadOS. Specifically, as part of a process in which I
was getting email up and working in Gnus.

`gnus-search-imap-handle-date` wraps a single `format-time-string` call in
`with-locale-environment "C"`, purely to obtain an English month abbreviation
for an IMAP SEARCH date string. Changing the locale environment resets the
terminal's coding system, so on a text terminal this leaves the display
unable to show any non-ASCII character: every one renders as a literal
`\uXXXX` escape.

On a system whose locale is already UTF-8 the damage is transient,
because `with-locale-environment` restores the previous locale on the
way out and that restore re-derives a UTF-8 terminal coding system. On a
system whose locale is C, POSIX, or unavailable (like my ipadOS port),
there is nothing to restore to and the terminal stays broken for the
rest of the session.

My port is a form of tty Emacs where the platform provides no locale at all.
You can see this in the report below: `locale-coding-system: nil` under
Important settings, which is exactly the condition that turns the transient
damage into permanent damage. Every IMAP search left the frame full of
escapes: mode-line ellipses, tab-bar circled digits, en dashes in message
bodies, and the Gnus summary marks.

Reproduction:

I hit this on the 32.0.50 ipadOS build described below, but you don't need
any of that to see it. I also reproduced it on a stock 31.0.90 on macOS.
Put this in /tmp/repro.el:

    ;;; repro.el -*- lexical-binding: t; -*-
    (set-terminal-coding-system 'utf-8-unix)
    (princ (format "before: %S\n" (terminal-coding-system)))
    (with-locale-environment "C" (format-time-string "%b"))
    (princ (format "after:  %S\n" (terminal-coding-system)))

Then:

    $ LANG=C LC_ALL=C emacs -Q --batch -l /tmp/repro.el
    before: utf-8-unix
    after:  nil

With a UTF-8 locale the same script prints `utf-8-unix` twice, because the
restore masks it. The call in gnus-search is reached by any IMAP search with
a date component, for example a saved search using `since:7d`.

Mechanism:

`with-locale-environment` calls `set-locale-environment` twice, once to enter
and once to restore inside its `unwind-protect` (mule-cmds.el). Each call
reaches `set-display-table-and-terminal-coding-system`, whose last form is

    (set-terminal-coding-system (or coding-system coding) display inhibit-refresh)

`set-language-environment` and `set-locale-environment` pass no coding system
for a language environment with no `unibyte-display` property, so this hands
through nil. `set-terminal-coding-system` passes nil to
`set-terminal-coding-system-internal`, `setup_coding_system` maps nil to
`undecided`, and because that requires no encoding the terminal's
`charset_list` is narrowed to `(ascii)` (coding.c, Fset_terminal_coding_system_internal).

Redisplay then fails `char_charset` in `tty_produce_glyphs` for every
non-ASCII character and formats the codepoint through
`produce_glyphless_glyph` (term.c), which is the `\uXXXX` output.

Worth noting that a terminal whose coding system was never set does NOT
render this way: its `charset_list` is nil, and `char_charset` with a nil
list falls back to `Vcharset_ordered_list` and succeeds for any Unicode
character. The escapes require the list to have been actively narrowed, which
is why this presents as a regression triggered by searching rather than as a
terminal that never worked. That took me a while to figure out, because the
obvious first guess is that my terminal was simply never set up right.

`with-locale-environment`'s own docstring documents the hazard:

    Note that changing the locale modifies settings that affect
    the display, such as `terminal-coding-system' and `standard-display-table',
    but this macro does not by itself perform redisplay.

So the side effect is known (and I should have paid attention to the
docstring earlier and saved myself some time lol). But we don't need to
have the bad effect.

Suggested fix:

`format-time-string` consults `system-time-locale`, which sets LC_TIME for
that one call and touches no display state. In `gnus-search-imap-handle-date`:

    (let ((system-time-locale "C"))
      (format-time-string "%e-%b-%Y" (encode-time 0 0 0 dday dmonth dyear)))

in place of the `with-locale-environment` wrapper.

I compared the two forms on three dates, including the `%e` space padding for
single-digit days. Output is identical, leading space included:

    (1 6 2026)    both => " 1-Jun-2026"
    (15 12 2025)  both => "15-Dec-2025"
    (3 3 2026)    both => " 3-Mar-2026"

gnus-search.el is the one caller of `with-locale-environment` I am sure of.
I definitely have not audited the tree for others, so there may well be more, and the
same reasoning would apply anywhere the macro is used only to influence
time formatting.

I may be missing important context as to why the macro was chosen here rather than
`system-time-locale`, so if there's a reason it needs the full locale
environment, I'd be glad to hear it.

---
In GNU Emacs 32.0.50 (build 1, aarch64-apple-darwin) of 2026-08-08 built
 on localhost
Configured using:
 'configure --host=aarch64-apple-darwin --without-x --without-xpm
 --without-jpeg --without-png --without-gif --without-tiff
 --without-rsvg --without-imagemagick --without-xft --without-cairo
 --without-native-compilation --without-dbus --without-gpm
 --without-mailutils --without-pop --with-modules --without-tree-sitter
 --with-gnutls --with-sqlite3 --without-ns --without-lcms2
 --without-libsystemd --without-libotf --without-m17n-flt --without-json
 --with-xml2 --without-zlib --without-jansson --without-webp
 --without-harfbuzz --without-compress-install --without-sound
 --without-selinux
 'CC=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
 --target=arm64-apple-ios26.0 -isysroot
 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS26.2.sdk
 -DEMACS_IOS_LIBRARY'
 'CFLAGS=-I/Users/roambot/code/emacs/emacs-ipad/build/stubs
 -I/Users/roambot/code/emacs/emacs-ipad/build/vendor/gnutls-device/gnutls/include
 -include
 /Users/roambot/code/emacs/emacs-ipad/build/stubs/availability-shim.h -g
 -O2 -Wno-error=unguarded-availability-new
 -Wno-error=unavailable-declarations -Wno-unavailable-declarations'
 CXX=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++
 AR=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ar
 RANLIB=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib
 STRIP=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/strip
 IPHONEOS_DEPLOYMENT_TARGET=26.0 emacs_use_mailutils=no'

Configured features:
ACL GNUTLS LIBXML2 MODULES NOTIFY KQUEUE PDUMPER SQLITE3 THREADS

Important settings:
  value of $EMACSDATA: /private/var/containers/Bundle/Application/F6ED1081-0EF2-46D7-AA82-1DB74B28697C/EmacsIPad.app/etc
  value of $EMACSDOC: /private/var/containers/Bundle/Application/F6ED1081-0EF2-46D7-AA82-1DB74B28697C/EmacsIPad.app/etc
  value of $EMACSLOADPATH: /private/var/containers/Bundle/Application/F6ED1081-0EF2-46D7-AA82-1DB74B28697C/EmacsIPad.app/lisp
  locale-coding-system: nil

Major mode: Lisp Interaction

Minor modes in effect:
  popper-echo-mode: t
  popper-mode: t
  winner-mode: t
  which-key-mode: t
  nerd-icons-completion-mode: t
  marginalia-mode: t
  vertico-buffer-mode: t
  vertico-mode: t
  lambda-line-mode: t
  xterm-mouse-mode: t
  recentf-mode: t
  save-place-mode: t
  savehist-mode: t
  global-auto-revert-mode: t
  global-hl-line-mode: t
  delete-selection-mode: t
  electric-pair-mode: t
  global-so-long-mode: t
  global-subword-mode: t
  subword-mode: t
  override-global-mode: t
  tooltip-mode: t
  global-eldoc-mode: t
  eldoc-mode: t
  show-paren-mode: t
  electric-indent-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  minibuffer-nonselected-mode: t
  minibuffer-regexp-mode: t
  column-number-mode: t
  line-number-mode: t
  global-visual-line-mode: t
  visual-line-mode: t
  transient-mark-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t
  auto-save-visited-mode: t

Load-path shadows:
/private/var/containers/Bundle/Application/F6ED1081-0EF2-46D7-AA82-1DB74B28697C/EmacsIPad.app/lisp/compat hides /private/var/containers/Bundle/Application/F6ED1081-0EF2-46D7-AA82-1DB74B28697C/EmacsIPad.app/lisp/emacs-lisp/compat

Features:
(shadow sort hl-todo cond-let ws-butler goggles pulse color jinx
mail-extr emacsbug lisp-mnt message yank-media dired desktop frameset
files-x dnd dired-loaddefs rfc822 mml mailabbrev nnheader range
gmm-utils mailheader sendmail mail-utils vertico-sort cursor-sensor
ipad-splash display-line-numbers ipad-org ipad-setup-org ipad-build url
url-proxy url-privacy url-expand url-methods url-history url-cookie
generate-lisp-file url-domsuf url-util url-parse auth-source url-vars
mm-view mml-smime mml-sec epa epg rfc6068 epg-config gnus-util
text-property-search smime password-cache gnutls puny dig mm-decode
mm-bodies mm-encode mail-parse rfc2231 rfc2047 rfc2045 mm-util
ietf-drums mail-prsvr mailcap json map ipad-mail ipad-spell ipad-editing
popper-echo popper cpm-paragraph-formats ipad-keybindings winner
cus-edit icons ring cl-extra help-mode tool-bar transient cond-star
format-spec eieio eieio-core ipad-functions ipad-completion which-key
nerd-icons-completion nerd-icons nerd-icons-faces nerd-icons-data
nerd-icons-data-mdicon nerd-icons-data-flicon nerd-icons-data-codicon
nerd-icons-data-devicon nerd-icons-data-sucicon nerd-icons-data-wicon
nerd-icons-data-faicon nerd-icons-data-powerline nerd-icons-data-octicon
nerd-icons-data-pomicon nerd-icons-data-ipsicon marginalia
vertico-repeat vertico-directory vertico-buffer vertico orderless compat
ipad-ui lambda-line let-alist face-remap lambda-dark-theme lambda-themes
ipad-core byte-opt pp project generator vc-libgit libgit edmacro kmacro
xt-mouse recentf tree-widget wid-edit saveplace savehist autorevert
filenotify hl-line delsel elec-pair so-long cap-words superword subword
use-package use-package-ensure use-package-delight use-package-diminish
use-package-bind-key bind-key use-package-core derived bytecomp
byte-compile yaml-mode-autoloads ws-butler-autoloads
writeroom-mode-autoloads visual-regexp-autoloads
visual-fill-column-autoloads vertico-autoloads tabspaces-autoloads
string-inflection-autoloads s-autoloads rainbow-identifiers-autoloads
rainbow-delimiters-autoloads queue-autoloads puni-autoloads
popper-autoloads parsebib-autoloads org-modern-autoloads
org-autolist-autoloads org-appear-autoloads orderless-autoloads
nerd-icons-dired-autoloads nerd-icons-completion-autoloads
nerd-icons-autoloads md-mode-autoloads markdown-toc-autoloads
markdown-mode-autoloads marginalia-autoloads lambda-themes-autoloads
imenu-list-autoloads iedit-autoloads hl-todo-autoloads
hide-mode-line-autoloads helpful-autoloads goggles-autoloads f-autoloads
expand-region-autoloads embrace-autoloads embark-consult-autoloads
embark-autoloads elisp-refs-autoloads elgrep-autoloads diredfl-autoloads
dired-sidebar-autoloads dired-narrow-autoloads
dired-hacks-utils-autoloads denote-autoloads dash-autoloads
crux-autoloads consult-notes-autoloads consult-dir-autoloads
consult-autoloads cond-let-autoloads citeproc-autoloads
citar-denote-autoloads citar-autoloads cape-autoloads avy-autoloads
async-autoloads ace-window-autoloads jinx-ios-setup time-date subr-x
mule-util term/xterm xterm easy-mmode cl-macs cl-seq inline cl-loaddefs
cl-lib gv regexp-opt rx pcase disp-table rmc iso-transl tooltip
cus-start cconv eldoc paren electric uniquify ediff-hook vc-hooks
lisp-float-type elisp-mode tabulated-list replace newcomment text-mode
lisp-mode prog-mode register page tab-bar menu-bar rfn-eshadow isearch
easymenu timer select mouse jit-lock font-lock syntax font-core
term/tty-colors frame minibuffer nadvice seq simple cl-generic
indonesian philippine cham georgian utf-8-lang misc-lang vietnamese
tibetan thai tai-viet lao korean japanese eucjp-ms cp51932 hebrew greek
romanian slovak czech european ethiopic indian cyrillic chinese
composite emoji-zwj charscript charprop case-table epa-hook
jka-cmpr-hook help abbrev obarray oclosure cl-preloaded button loaddefs
theme-loaddefs faces cus-face macroexp files window text-properties
overlay sha1 md5 base64 format env code-pages mule custom widget keymap
hashtable-print-readable backquote threads kqueue multi-tty
make-network-process tty-child-frames emacs)

Memory information:
((conses 16 786704 140647) (symbols 48 38513 20) (strings 32 109018 4473)
 (string-bytes 1 3270897) (vectors 16 37024) (vector-slots 8 1243685 126545)
 (floats 8 564 219) (intervals 56 618 22) (buffers 984 10))
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.