How to turn off local time (late timezone warnings) in direct messages?

It appears that there is a new, very annoying, feature in Mattermost that seems to want to be helpful with “late timezone warnings”, but this is bad when the majority of users in a Mattermost actually work nights.

Where can this be turned off?

You aren’t the first to notice this :slight_smile: I believe it’ll be customizable in a future release, hopefully soon!

1 Like

Thanks, definitely getting a lot of complaints here after I updated from MM 9 to 10… :melting_face:

Has this received any attention as of yet?

EDIT:
Disappointing to see that it would be fixed soon and now is stuck in this absolutely ridiculous quagmire of bureaucracy: allow the Late Time Warning to be disabled or modified – Mattermost Feature Proposal Forum

1 Like

Sadly since there was the feeling of assurance that this would be resolved quickly, and yet it was not, I’ve implemented a workaround for now that will patch the client on the server:

Before:

After:

Execution example:

# STATUS CHECK BEFORE INSTALL:

$ mm-remotehour.sh status
Client dir:  /opt/mattermost/client
Entry HTML:  /opt/mattermost/client/root.html
Static dir:  /opt/mattermost/client
Prefix:      custom
CSS path:    /opt/mattermost/client/custom-hide-remote-hour.css
Linked:      no
CSS exists:  no
Dir owner:   mattermost:mattermost
HTML owner:  mattermost:mattermost
Last backup: (none)

# INSTALL:

$ mm-remotehour.sh install
Client dir: /opt/mattermost/client
Entry HTML: /opt/mattermost/client/root.html
Static dir: /opt/mattermost/client
CSS file:   /opt/mattermost/client/custom-hide-remote-hour.css
Backup: /username/.mm-backups/mattermost-remotehour/20250821T030546Z
Injected link block.
Done.

# STATUS CHECK AFTER INSTALL:

$ mm-remotehour.sh status
Client dir:  /opt/mattermost/client
Entry HTML:  /opt/mattermost/client/root.html
Static dir:  /opt/mattermost/client
Prefix:      custom
CSS path:    /opt/mattermost/client/custom-hide-remote-hour.css
Linked:      yes
CSS exists:  yes
Dir owner:   mattermost:mattermost
HTML owner:  mattermost:mattermost
Last backup: /username/.mm-backups/mattermost-remotehour/latest

Script $HOME/bin/mm-remotehour.sh:

#!/usr/bin/env sh
# Mattermost: hide the "Remote/Late time" chip via CSS injection.
# Generic, ownership-safe, idempotent, reversible, with backups.
# Usage: ./mm-remotehour.sh [install|uninstall|status|reinstall|repair]
# Requires: POSIX sh, awk, sed, grep, cp, mv, chmod, chown (if root), systemctl (optional)

set -eu
umask 022

# --- configurable (env overrides allowed) ------------------------------------
CLIENT_DIR="${CLIENT_DIR:-/opt/mattermost/client}"   # adjust if needed
PREFIX="${PREFIX:-custom}"                           # only affects filename
CSS_NAME="${CSS_NAME:-${PREFIX}-hide-remote-hour.css}"
SERVICE_NAME="${SERVICE_NAME:-mattermost}"           # systemd unit (optional)
BACKUP_BASE="${BACKUP_BASE:-$HOME/.mm-backups/mattermost-remotehour}"
MARK_BEGIN="<!-- REMOTE_HOUR_INJECT_START -->"
MARK_END="<!-- REMOTE_HOUR_INJECT_END -->"
# -----------------------------------------------------------------------------

CSS_PATH=""
ENTRY_HTML=""
STATIC_DIR=""
TARGET_OWN_GRP=""
CACHE_BUSTER=""

die() { echo "$@" >&2; exit 1; }
need_dir() { [ -d "$1" ] || die "Directory not found: $1"; }

get_owner_group() {
  if owner=$(stat -c '%U' "$1" 2>/dev/null) && group=$(stat -c '%G' "$1" 2>/dev/null); then
    printf '%s:%s\n' "$owner" "$group"; return 0
  fi
  set -- $(ls -ld "$1")
  printf '%s:%s\n' "$3" "$4"
}

can_write() {
  [ -w "$1" ] && return 0
  [ "$(id -u)" -eq 0 ] && return 0
  return 1
}

ensure_backup_dir() { mkdir -p "$BACKUP_BASE"; }
timestamp() { date -u +%Y%m%dT%H%M%SZ; }

backup_now() {
  ensure_backup_dir
  ts="$(timestamp)"
  dest="$BACKUP_BASE/$ts"
  mkdir -p "$dest"
  cp -p "$ENTRY_HTML" "$dest/$(basename "$ENTRY_HTML")" 2>/dev/null || true
  [ -n "$CSS_PATH" ] && [ -f "$CSS_PATH" ] && cp -p "$CSS_PATH" "$dest/$(basename "$CSS_PATH")"
  ln -sfn "$dest" "$BACKUP_BASE/latest"
  printf 'Backup: %s\n' "$dest"
}

detect_entry_html() {
  if [ -f "$CLIENT_DIR/root.html" ]; then ENTRY_HTML="$CLIENT_DIR/root.html"; return; fi
  if [ -f "$CLIENT_DIR/index.html" ]; then ENTRY_HTML="$CLIENT_DIR/index.html"; return; fi
  ent=$(grep -l '</head>' "$CLIENT_DIR"/*.html 2>/dev/null | head -n1 || true)
  [ -n "${ent:-}" ] && ENTRY_HTML="$ent"
}

detect_static_dir() {
  ref=$(grep -o '/static/[^"]*' "$ENTRY_HTML" | head -n1 || true)
  if [ -n "$ref" ]; then
    rel=${ref#/static/}
    if [ -f "$CLIENT_DIR/$rel" ]; then STATIC_DIR="$CLIENT_DIR"; return; fi
    if [ -f "$CLIENT_DIR/static/$rel" ]; then STATIC_DIR="$CLIENT_DIR/static"; return; fi
  fi
  if [ -d "$CLIENT_DIR/static" ]; then STATIC_DIR="$CLIENT_DIR/static"; else STATIC_DIR="$CLIENT_DIR"; fi
}

write_css_tmp() {
  tmp_css="$(mktemp)"
  # Key idea: target the stable wrapper (.postBoxIndicator) and the chip (.RemoteUserHour).
  # 1) Hide the whole row when the chip is present (via :has()).
  # 2) Fallback: hide the chip + icon; if the row becomes empty, collapse it.
  cat >"$tmp_css" <<'EOF'
/* Primary: remove the entire banner row if it contains the RemoteUserHour chip */
.postBoxIndicator:has(.RemoteUserHour) {
  display: none !important;
}

/* Fallback for browsers without :has(): hide the chip itself (and its icon) */
.postBoxIndicator .RemoteUserHour,
.postBoxIndicator [class*="RemoteUserHour"],
.postBoxIndicator .moonIcon {
  display: none !important;
}

/* If the row becomes empty after hiding the chip, collapse it fully */
.postBoxIndicator:empty {
  display: none !important;
  margin: 0 !important;
  padding: 0 !important;
  min-height: 0 !important;
  height: 0 !important;
}
EOF
  printf '%s\n' "$tmp_css"
}

safe_overwrite() {
  src="$1"; dest="$2"
  if [ ! -e "$dest" ]; then mv "$src" "$dest"; return 0; fi
  can_write "$dest" || { echo "No write permission on $dest (try root)"; rm -f "$src"; exit 1; }
  cat "$src" >"$dest"; rm -f "$src"
}

fix_owner_mode() {
  file="$1"
  if [ "$(id -u)" -eq 0 ]; then chown "$TARGET_OWN_GRP" "$file" 2>/dev/null || true; fi
  chmod 0644 "$file" 2>/dev/null || true
}

link_present() { grep -Fq "$MARK_BEGIN" "$ENTRY_HTML"; }

# Single-line-safe insertion INSIDE <head> (before </head> if present; else after <head ...>)
insert_link_block() {
  tmp="$(mktemp)"
  awk -v begin="$MARK_BEGIN" -v end="$MARK_END" -v css="$CSS_NAME" -v buster="$CACHE_BUSTER" '
    BEGIN { inserted=0 }
    {
      if (!inserted) {
        low = tolower($0)
        p = index(low, "</head>")
        if (p > 0) {
          pre  = substr($0, 1, p-1)
          post = substr($0, p)
          print pre
          print "  " begin
          print "  <link rel=\"stylesheet\" href=\"/static/" css "?v=" buster "\"/>"
          print "  " end
          print post
          inserted = 1
          next
        }
        if (match(low, /<head[^>]*>/)) {
          head_close = RSTART + RLENGTH - 1
          print substr($0, 1, head_close)
          print "  " begin
          print "  <link rel=\"stylesheet\" href=\"/static/" css "?v=" buster "\"/>"
          print "  " end
          print substr($0, head_close+1)
          inserted = 1
          next
        }
      }
      print
    }
    END {
      if (!inserted) {
        print "  " begin
        print "  <link rel=\"stylesheet\" href=\"/static/" css "?v=" buster "\"/>"
        print "  " end
      }
    }
  ' "$ENTRY_HTML" >"$tmp"
  safe_overwrite "$tmp" "$ENTRY_HTML"
  fix_owner_mode "$ENTRY_HTML"
}

remove_link_block() {
  tmp="$(mktemp)"
  awk -v begin="$MARK_BEGIN" -v end="$MARK_END" '
    BEGIN { skip=0 }
    index($0, begin) { skip=1; next }
    index($0, end)   { skip=0; next }
    !skip { print }
  ' "$ENTRY_HTML" >"$tmp"
  safe_overwrite "$tmp" "$ENTRY_HTML"
  fix_owner_mode "$ENTRY_HTML"
}

remove_direct_link_line_for_our_css() {
  tmp="$(mktemp)"
  esc="$(printf '%s\n' "/static/$CSS_NAME" | sed 's/[.[\*^$(){}?+|/]/\\&/g')"
  awk -v pat="$esc" 'index($0, pat) { next } { print }' "$ENTRY_HTML" >"$tmp"
  safe_overwrite "$tmp" "$ENTRY_HTML"
  fix_owner_mode "$ENTRY_HTML"
}

restart_service_if_any() {
  if command -v systemctl >/dev/null 2>&1; then
    systemctl try-restart "$SERVICE_NAME" 2>/dev/null || true
  fi
}

ensure_entry_writable_or_root() {
  if ! can_write "$ENTRY_HTML"; then
    echo "Cannot write $ENTRY_HTML; run as root or as its owner ($(get_owner_group "$ENTRY_HTML"))."
    exit 1
  fi
}

ensure_entry_owner_matches_dir_if_root() {
  if [ "$(id -u)" -eq 0 ]; then
    current="$(get_owner_group "$ENTRY_HTML")"
    if [ "$current" != "$TARGET_OWN_GRP" ]; then
      chown "$TARGET_OWN_GRP" "$ENTRY_HTML" 2>/dev/null || true
      chmod a+r "$ENTRY_HTML" 2>/dev/null || true
    fi
  fi
}

init_env() {
  need_dir "$CLIENT_DIR"
  detect_entry_html
  [ -n "$ENTRY_HTML" ] || die "Could not find entry HTML in $CLIENT_DIR"
  detect_static_dir
  CSS_PATH="$STATIC_DIR/$CSS_NAME"
  TARGET_OWN_GRP="$(get_owner_group "$CLIENT_DIR")"
}

do_install() {
  init_env
  echo "Client dir: $CLIENT_DIR"
  echo "Entry HTML: $ENTRY_HTML"
  echo "Static dir: $STATIC_DIR"
  echo "CSS file:   $CSS_PATH"
  ensure_entry_writable_or_root
  backup_now

  # 1) Write/refresh CSS
  tmp_css="$(write_css_tmp)"
  if [ -f "$CSS_PATH" ]; then
    safe_overwrite "$tmp_css" "$CSS_PATH"
  else
    mv "$tmp_css" "$CSS_PATH"
  fi
  fix_owner_mode "$CSS_PATH"

  # Cache buster from mtime (fallback to current UTC seconds)
  CACHE_BUSTER="$(stat -c %Y "$CSS_PATH" 2>/dev/null || date -u +%s)"

  # 2) Inject link block inside <head>
  if link_present; then
    echo "CSS link block already present; refreshed CSS only."
  else
    insert_link_block
    echo "Injected link block."
  fi

  ensure_entry_owner_matches_dir_if_root
  restart_service_if_any
  echo "Done."
}

do_uninstall() {
  init_env
  echo "Client dir: $CLIENT_DIR"
  echo "Entry HTML: $ENTRY_HTML"
  echo "Static dir: $STATIC_DIR"
  echo "CSS file:   $CSS_PATH"
  ensure_entry_writable_or_root
  backup_now

  if link_present; then
    remove_link_block
    echo "Removed link block."
  else
    echo "No markers found; removing any direct link to this CSS if present."
    remove_direct_link_line_for_our_css
  fi

  [ -f "$CSS_PATH" ] && { rm -f "$CSS_PATH"; echo "Removed $(basename "$CSS_PATH")."; }

  ensure_entry_owner_matches_dir_if_root
  restart_service_if_any
  echo "Reverted."
}

do_status() {
  init_env
  echo "Client dir:  $CLIENT_DIR"
  echo "Entry HTML:  $ENTRY_HTML"
  echo "Static dir:  $STATIC_DIR"
  echo "Prefix:      $PREFIX"
  echo "CSS path:    $CSS_PATH"
  printf "Linked:      "; if link_present; then echo "yes"; else echo "no"; fi
  printf "CSS exists:  "; if [ -f "$CSS_PATH" ]; then echo "yes"; else echo "no"; fi
  echo "Dir owner:   $TARGET_OWN_GRP"
  echo "HTML owner:  $(get_owner_group "$ENTRY_HTML")"
  if [ -e "$BACKUP_BASE/latest" ]; then
    echo "Last backup: $BACKUP_BASE/latest"
  else
    echo "Last backup: (none)"
  fi
}

do_repair() {
  init_env
  echo "Repairing ownership and perms to match $CLIENT_DIR -> $TARGET_OWN_GRP"
  if [ "$(id -u)" -ne 0 ]; then echo "Repair needs root to chown files. Abort."; exit 1; fi
  backup_now
  chown "$TARGET_OWN_GRP" "$ENTRY_HTML" 2>/dev/null || true
  chmod a+r "$ENTRY_HTML" 2>/dev/null || true
  [ -f "$CSS_PATH" ] && chown "$TARGET_OWN_GRP" "$CSS_PATH" 2>/dev/null || true
  [ -f "$CSS_PATH" ] && chmod 0644 "$CSS_PATH" 2>/dev/null || true
  echo "Repair complete."
}

cmd="${1:-install}"
case "$cmd" in
  install)   do_install ;;
  uninstall) do_uninstall ;;
  status)    do_status ;;
  reinstall) do_uninstall; do_install ;;
  repair)    do_repair ;;
  *) echo "Usage: $0 [install|uninstall|status|reinstall|repair]"; exit 2 ;;
esac

Thanks so much for sharing that workaround, @ylluminate!