From 8282fad7dc1047a32e546435d4cafefcb907e0eb Mon Sep 17 00:00:00 2001
From: Andrey Petrov <andrey.petrov@shazow.net>
Date: Sat, 16 Mar 2019 12:20:43 -0400
Subject: [PATCH] chat: Add timezone support to /timestamp

---
 chat/command.go       | 27 +++++++++++++++++++++++----
 chat/message/theme.go |  4 +++-
 chat/message/user.go  | 10 ++++++++--
 3 files changed, 34 insertions(+), 7 deletions(-)

diff --git a/chat/command.go b/chat/command.go
index cba9bbc..f4bc903 100644
--- a/chat/command.go
+++ b/chat/command.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"strings"
+	"time"
 
 	"github.com/shazow/ssh-chat/chat/message"
 	"github.com/shazow/ssh-chat/internal/sanitize"
@@ -283,16 +284,34 @@ func InitCommands(c *Commands) {
 	})
 
 	c.Add(Command{
-		Prefix: "/timestamp",
-		Help:   "Prefix messages with a timestamp.",
+		Prefix:     "/timestamp",
+		PrefixHelp: "[TZINFO]",
+		Help:       "Prefix messages with a timestamp. (Example: America/Toronto)",
 		Handler: func(room *Room, msg message.CommandMsg) error {
 			u := msg.From()
 			cfg := u.Config()
-			cfg.Timestamp = !cfg.Timestamp
+
+			args := msg.Args()
+			if len(args) >= 1 {
+				// FIXME: This is an annoying format to demand from users, but
+				// hopefully we can make it a non-primary flow if we add GeoIP
+				// someday.
+				timeLoc, err := time.LoadLocation(args[0])
+				if err != nil {
+					err = fmt.Errorf("%s: Use a location name such as \"America/Toronto\" or refer to the IANA Time Zone database for the full list of names: https://wikipedia.org/wiki/List_of_tz_database_time_zones", err)
+					return err
+				}
+				cfg.Timezone = timeLoc
+				cfg.Timestamp = true
+			} else {
+				cfg.Timestamp = !cfg.Timestamp
+			}
 			u.SetConfig(cfg)
 
 			var body string
-			if cfg.Timestamp {
+			if cfg.Timestamp && cfg.Timezone != nil {
+				body = fmt.Sprintf("Timestamp is toggled ON with timezone %q", cfg.Timezone)
+			} else if cfg.Timestamp {
 				body = "Timestamp is toggled ON"
 			} else {
 				body = "Timestamp is toggled OFF"
diff --git a/chat/message/theme.go b/chat/message/theme.go
index b3bfde2..bc79f41 100644
--- a/chat/message/theme.go
+++ b/chat/message/theme.go
@@ -5,6 +5,8 @@ import (
 	"time"
 )
 
+const timestampLayout = "2006-01-02 15:04:05 MST"
+
 const (
 	// Reset resets the color
 	Reset = "\033[0m"
@@ -167,7 +169,7 @@ func (theme Theme) Highlight(s string) string {
 // Timestamp formats and colorizes the timestamp.
 func (theme Theme) Timestamp(t time.Time) string {
 	// TODO: Change this per-theme? Or config?
-	return theme.sys.Format(t.Format("2006-01-02 15:04:05 UTC"))
+	return theme.sys.Format(t.Format(timestampLayout))
 }
 
 // List of initialzied themes
diff --git a/chat/message/user.go b/chat/message/user.go
index e825ae1..7635161 100644
--- a/chat/message/user.go
+++ b/chat/message/user.go
@@ -16,7 +16,6 @@ const messageBuffer = 5
 const messageTimeout = 5 * time.Second
 const reHighlight = `\b(%s)\b`
 const timestampTimeout = 30 * time.Minute
-const timestampLayout = "2006-01-02 15:04:05 UTC"
 
 var ErrUserClosed = errors.New("user closed")
 
@@ -175,7 +174,13 @@ func (u *User) render(m Message) string {
 		out += m.Render(cfg.Theme)
 	}
 	if cfg.Timestamp {
-		return cfg.Theme.Timestamp(m.Timestamp()) + "  " + out + Newline
+		ts := m.Timestamp()
+		if cfg.Timezone != nil {
+			ts = ts.In(cfg.Timezone)
+		} else {
+			ts = ts.UTC()
+		}
+		return cfg.Theme.Timestamp(ts) + "  " + out + Newline
 	}
 	return out + Newline
 }
@@ -220,6 +225,7 @@ type UserConfig struct {
 	Bell      bool
 	Quiet     bool
 	Timestamp bool
+	Timezone  *time.Location
 	Theme     *Theme
 }