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"