From 7a8e287ce6afe0be523e096adc9e96a89e291211 Mon Sep 17 00:00:00 2001 From: DTTerastar Date: Tue, 21 Apr 2026 20:43:20 -0400 Subject: [PATCH] fix: resolve dates and display timestamps in local time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bug classes mirrored the fixes in crono-export-cli#5 and liftoff-export-cli#14: 1. parseSince used bare time.Parse("2006-01-02", s), which defaults to UTC. When the parsed time flowed into a Unix-epoch API parameter (measurements startdate, intraday chunk bounds), the epoch was off by the user's UTC offset — so --since 2026-04-15 in EDT actually asked Withings for data starting 2026-04-14 20:00 EDT. Switched to time.ParseInLocation(..., time.Local). 2. Sleep, workout, measurement, and intraday Unix timestamps were converted with .UTC() before RFC3339 formatting, so CSV/JSON output rendered all times as UTC instead of the user's local zone. A 10:30pm PST sleep session showed as 06:30Z the next day. Switched every display-side .UTC() → .Local(). Renamed the intraday CSV column datetime_utc → datetime to match. JSON output is unaffected for struct fields that ride Go's default time.Time marshaler (already RFC3339 with whatever zone the time.Time carries); Unix-epoch fields (int64) were never timezone-dependent. Co-Authored-By: Claude Opus 4.7 (1M context) --- cmd/intraday.go | 4 ++-- cmd/measurements.go | 2 +- cmd/shared.go | 2 +- cmd/sleep.go | 4 ++-- cmd/workouts.go | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/intraday.go b/cmd/intraday.go index 9f29411..fc8fd46 100644 --- a/cmd/intraday.go +++ b/cmd/intraday.go @@ -129,7 +129,7 @@ func writeIntradayCSV(samples []intradaySample) error { w := csv.NewWriter(os.Stdout) defer w.Flush() header := []string{ - "timestamp", "datetime_utc", "duration_sec", + "timestamp", "datetime", "duration_sec", "steps", "distance_m", "elevation_m", "calories", "heart_rate", "hrv_rmssd", "hrv_sdnn1", "hrv_quality", "spo2_auto", "model", "model_id", @@ -140,7 +140,7 @@ func writeIntradayCSV(samples []intradaySample) error { for _, s := range samples { row := []string{ strconv.FormatInt(s.Timestamp, 10), - time.Unix(s.Timestamp, 0).UTC().Format(time.RFC3339), + time.Unix(s.Timestamp, 0).Local().Format(time.RFC3339), strconv.Itoa(s.Duration), strconv.Itoa(s.Steps), strconv.FormatFloat(s.Distance, 'f', -1, 64), diff --git a/cmd/measurements.go b/cmd/measurements.go index 9214aa1..2f8ced7 100644 --- a/cmd/measurements.go +++ b/cmd/measurements.go @@ -101,7 +101,7 @@ var measurementsCmd = &cobra.Command{ return err } for _, g := range resp.Measuregrps { - t := time.Unix(g.Date, 0).UTC() + t := time.Unix(g.Date, 0).Local() for _, m := range g.Measures { value := float64(m.Value) * math.Pow10(m.Unit) name, ok := measureTypeNames[m.Type] diff --git a/cmd/shared.go b/cmd/shared.go index b1fa6a1..3b00a63 100644 --- a/cmd/shared.go +++ b/cmd/shared.go @@ -13,7 +13,7 @@ func parseSince(s string) (time.Time, error) { if s == "" { return time.Time{}, nil } - if t, err := time.Parse("2006-01-02", s); err == nil { + if t, err := time.ParseInLocation("2006-01-02", s, time.Local); err == nil { return t, nil } if len(s) < 2 { diff --git a/cmd/sleep.go b/cmd/sleep.go index bd737f9..66c6f17 100644 --- a/cmd/sleep.go +++ b/cmd/sleep.go @@ -302,8 +302,8 @@ func writeSleepCSV(series []sleepSeries) error { } _ = json.Unmarshal(s.Data, &d) - start := time.Unix(s.StartDate, 0).UTC() - end := time.Unix(s.EndDate, 0).UTC() + start := time.Unix(s.StartDate, 0).Local() + end := time.Unix(s.EndDate, 0).Local() totalSleepMin := (d.Light + d.Deep + d.REM) / 60 row := []string{ diff --git a/cmd/workouts.go b/cmd/workouts.go index 7cbaa51..d9bded3 100644 --- a/cmd/workouts.go +++ b/cmd/workouts.go @@ -163,8 +163,8 @@ func writeWorkoutsCSV(series []workoutSeries) error { } _ = json.Unmarshal(s.Data, &d) - start := time.Unix(s.StartDate, 0).UTC() - end := time.Unix(s.EndDate, 0).UTC() + start := time.Unix(s.StartDate, 0).Local() + end := time.Unix(s.EndDate, 0).Local() category := workoutCategoryNames[s.Category] if category == "" { category = "unknown"