backend requirements onthe xustomer app
This commit is contained in:
103
utils/epoch.go
Normal file
103
utils/epoch.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Timestamps on the customer surface.
|
||||
//
|
||||
// The requirement is epoch milliseconds, UTC, integer — a real instant, not a
|
||||
// wall clock. That is not what this database hands back. The DSN sets
|
||||
// TimeZone=Asia/Kolkata and the timestamp columns hold IST wall-clock digits
|
||||
// (see DBNow), so a stored value read into a time.Time carries the right
|
||||
// digits with the wrong (or no) zone. Calling UnixMilli on it directly is off
|
||||
// by 5h30m — the same class of defect as sending a naive local string with a Z
|
||||
// on it, which has already produced "yesterday's work shown as today" on the
|
||||
// miler app. So every timestamp leaving /customer/* goes through here.
|
||||
//
|
||||
// Reinterpreting the wall clock in IST is correct for both shapes this
|
||||
// database produces: a value tagged UTC that actually holds IST digits, and a
|
||||
// value correctly tagged +05:30. Both name the same instant afterwards.
|
||||
|
||||
// istLocation is Asia/Kolkata, with an exact fixed-offset fallback for
|
||||
// containers shipped without tzdata. IST observes no DST, so +05:30 is not an
|
||||
// approximation.
|
||||
var istLocation = func() *time.Location {
|
||||
if loc, err := time.LoadLocation("Asia/Kolkata"); err == nil {
|
||||
return loc
|
||||
}
|
||||
return time.FixedZone("IST", 5*3600+30*60)
|
||||
}()
|
||||
|
||||
// IST reinterprets a database timestamp's wall clock as Indian Standard Time,
|
||||
// yielding the instant it actually names.
|
||||
func IST(t time.Time) time.Time {
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), istLocation)
|
||||
}
|
||||
|
||||
// EpochMillis converts a database timestamp to UTC epoch milliseconds.
|
||||
func EpochMillis(t time.Time) int64 {
|
||||
return IST(t).UnixMilli()
|
||||
}
|
||||
|
||||
// EpochMillisPtr is EpochMillis over a nullable column. A nil timestamp stays
|
||||
// null in JSON rather than becoming the epoch, which the client would render
|
||||
// as 1 Jan 1970.
|
||||
func EpochMillisPtr(t *time.Time) *int64 {
|
||||
if t == nil || t.IsZero() {
|
||||
return nil
|
||||
}
|
||||
ms := EpochMillis(*t)
|
||||
return &ms
|
||||
}
|
||||
|
||||
// ISTNow is the current moment in IST, for formatting and for comparing
|
||||
// against a value already put through IST().
|
||||
func ISTNow() time.Time {
|
||||
return time.Now().In(istLocation)
|
||||
}
|
||||
|
||||
// ISTLocation exposes the zone for callers building their own times.
|
||||
func ISTLocation() *time.Location { return istLocation }
|
||||
|
||||
// FormatISTDate renders a date the way the customer app shows it: "Thu, 12 Sep".
|
||||
// Display strings are formatted server-side, in IST, so the client never has to
|
||||
// know the operating timezone.
|
||||
func FormatISTDate(t time.Time) string {
|
||||
return IST(t).Format("Mon, 2 Jan")
|
||||
}
|
||||
|
||||
// FormatISTDay renders a date relative to today where that reads better —
|
||||
// "Today", "Tomorrow", otherwise "Mon, 8 Sep". Used for pickup slot days.
|
||||
func FormatISTDay(t time.Time) string {
|
||||
d := IST(t)
|
||||
today := ISTNow()
|
||||
y1, m1, d1 := d.Date()
|
||||
y2, m2, d2 := today.Date()
|
||||
switch {
|
||||
case y1 == y2 && m1 == m2 && d1 == d2:
|
||||
return "Today"
|
||||
case y1 == y2 && m1 == m2 && d1 == d2+1:
|
||||
return "Tomorrow"
|
||||
default:
|
||||
tomorrow := today.AddDate(0, 0, 1)
|
||||
y3, m3, d3 := tomorrow.Date()
|
||||
if y1 == y3 && m1 == m3 && d1 == d3 {
|
||||
return "Tomorrow"
|
||||
}
|
||||
return d.Format("Mon, 2 Jan")
|
||||
}
|
||||
}
|
||||
|
||||
// FormatISTWindow renders a pickup window as the design writes it:
|
||||
// "2:00 – 4:00 PM" — en dash, spaced, and the meridiem stated once when both
|
||||
// ends share it.
|
||||
func FormatISTWindow(from, to time.Time) string {
|
||||
f, t := IST(from), IST(to)
|
||||
fMer, tMer := f.Format("PM"), t.Format("PM")
|
||||
if fMer == tMer {
|
||||
return fmt.Sprintf("%s – %s", f.Format("3:04"), t.Format("3:04 PM"))
|
||||
}
|
||||
return fmt.Sprintf("%s – %s", f.Format("3:04 PM"), t.Format("3:04 PM"))
|
||||
}
|
||||
Reference in New Issue
Block a user