Skip to content

Commit 1972ac7

Browse files
authored
Adjust timezone for ZoneHistory in UI (#201)
1 parent 6bf8d20 commit 1972ac7

File tree

5 files changed

+74
-21
lines changed

5 files changed

+74
-21
lines changed

garden-app/server/garden_responses.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,7 @@ func (g *GardenResponse) Render(w http.ResponseWriter, r *http.Request) error {
145145
State: nextLightState,
146146
}
147147

148-
var loc *time.Location
149-
tzHeader := r.Header.Get("X-TZ-Offset")
150-
if tzHeader != "" {
151-
loc, err = pkg.TimeLocationFromOffset(tzHeader)
152-
if err != nil {
153-
return fmt.Errorf("error parsing timezone from header: %w", err)
154-
}
155-
}
148+
loc := getLocationFromRequest(r)
156149
if loc == nil {
157150
loc = g.LightSchedule.StartTime.Time.Location()
158151
}

garden-app/server/water_schedule_responses.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,7 @@ func GetNextWaterDetails(r *http.Request, ws *pkg.WaterSchedule, worker *worker.
4242
return result
4343
}
4444

45-
var loc *time.Location
46-
tzHeader := r.Header.Get("X-TZ-Offset")
47-
if tzHeader != "" {
48-
var err error
49-
loc, err = pkg.TimeLocationFromOffset(tzHeader)
50-
if err != nil {
51-
result.Message = fmt.Sprintf("error parsing timezone from header: %v", err)
52-
}
53-
}
45+
loc := getLocationFromRequest(r)
5446
if loc == nil {
5547
loc = ws.StartTime.Time.Location()
5648
}

garden-app/server/zone.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ func (api *ZonesAPI) waterHistory(_ http.ResponseWriter, r *http.Request, zone *
295295
return nil, apiErr
296296
}
297297

298-
return NewZoneWaterHistoryResponse(history), nil
298+
return NewZoneWaterHistoryResponse(history, getLocationFromRequest(r)), nil
299299
}
300300

301301
func (api *ZonesAPI) getWaterHistoryFromRequest(r *http.Request, zone *pkg.Zone, logger *slog.Logger) ([]pkg.WaterHistory, *babyapi.ErrResponse) {

garden-app/server/zone_responses.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func (zr *ZoneResponse) Render(w http.ResponseWriter, r *http.Request) error {
117117
logger.Error("error getting water history", "error", apiErr)
118118
zr.HistoryError = apiErr.ErrorText
119119
}
120-
zr.History = NewZoneWaterHistoryResponse(history)
120+
zr.History = NewZoneWaterHistoryResponse(history, getLocationFromRequest(r))
121121

122122
// Reverse history for better presentation in UI
123123
slices.Reverse(zr.History.History)
@@ -168,6 +168,18 @@ func (azr AllZonesResponse) Render(w http.ResponseWriter, r *http.Request) error
168168
return azr.ResourceList.Render(w, r)
169169
}
170170

171+
// getLocationFromRequest reads the X-TZ-Offset header and returns the corresponding time.Location
172+
func getLocationFromRequest(r *http.Request) *time.Location {
173+
tzHeader := r.Header.Get("X-TZ-Offset")
174+
if tzHeader != "" {
175+
loc, err := pkg.TimeLocationFromOffset(tzHeader)
176+
if err == nil {
177+
return loc
178+
}
179+
}
180+
return nil
181+
}
182+
171183
func (azr AllZonesResponse) HTML(_ http.ResponseWriter, r *http.Request) string {
172184
slices.SortFunc(azr.Items, func(z *ZoneResponse, zz *ZoneResponse) int {
173185
return strings.Compare(z.Name, zz.Name)
@@ -199,14 +211,21 @@ type ZoneWaterHistoryResponse struct {
199211
}
200212

201213
// NewZoneWaterHistoryResponse creates a response by creating some basic statistics about a list of history events
202-
func NewZoneWaterHistoryResponse(history []pkg.WaterHistory) ZoneWaterHistoryResponse {
214+
// and converting times to the specified location
215+
func NewZoneWaterHistoryResponse(history []pkg.WaterHistory, loc *time.Location) ZoneWaterHistoryResponse {
203216
total := time.Duration(0)
204217
count := 0
205-
for _, h := range history {
218+
for i, h := range history {
206219
if h.Status == pkg.WaterStatusCompleted {
207220
total += h.Duration.Duration
208221
count++
209222
}
223+
// Convert times to the target timezone
224+
if loc != nil {
225+
history[i].SentAt = h.SentAt.In(loc)
226+
history[i].StartedAt = h.StartedAt.In(loc)
227+
history[i].CompletedAt = h.CompletedAt.In(loc)
228+
}
210229
}
211230

212231
average := time.Duration(0)

garden-app/server/zone_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,55 @@ func TestWaterHistory(t *testing.T) {
11051105
},
11061106
}
11071107

1108+
// Test timezone conversion separately since it requires different setup
1109+
t.Run("SuccessfulWaterHistoryWithTimezoneConversion", func(t *testing.T) {
1110+
// Use UTC time in mock data
1111+
utcTime, _ := time.Parse(time.RFC3339Nano, "2021-10-03T18:24:52.891386Z")
1112+
1113+
influxdbClient := new(influxdb.MockClient)
1114+
influxdbClient.On("GetWaterHistory", mock.Anything, id.String(), "test-garden", time.Hour*72, uint64(5)).
1115+
Return([]pkg.WaterHistory{
1116+
{
1117+
Duration: pkg.Duration{Duration: 3 * time.Second},
1118+
Status: pkg.WaterStatusCompleted,
1119+
Source: string(action.SourceCommand),
1120+
SentAt: utcTime,
1121+
StartedAt: utcTime,
1122+
CompletedAt: utcTime,
1123+
EventID: "00000000000000000000",
1124+
},
1125+
}, nil)
1126+
influxdbClient.On("Close")
1127+
1128+
storageClient, err := storage.NewClient(storage.Config{
1129+
ConnectionString: ":memory:",
1130+
})
1131+
assert.NoError(t, err)
1132+
1133+
zr := NewZonesAPI()
1134+
zr.setup(storageClient, influxdbClient, worker.NewWorker(storageClient, influxdbClient, nil, slog.Default()))
1135+
1136+
garden := createExampleGarden()
1137+
zone := createExampleZone()
1138+
1139+
err = storageClient.Gardens.Set(context.Background(), garden)
1140+
assert.NoError(t, err)
1141+
err = storageClient.Zones.Set(context.Background(), zone)
1142+
assert.NoError(t, err)
1143+
1144+
// Use X-TZ-Offset: 420 (UTC-7 / America/Phoenix) to convert from UTC to Phoenix time
1145+
r := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/gardens/%s/zones/%s/history", garden.ID, zone.ID), http.NoBody)
1146+
r.Header.Set("X-TZ-Offset", "420")
1147+
w := babytest.TestWithParentRoute(t, zr.API, garden, "Gardens", "/gardens", r)
1148+
1149+
assert.Equal(t, http.StatusOK, w.Code)
1150+
// The UTC time should be converted to -07:00 offset (18:24:52 - 7 hours = 11:24:52 local time)
1151+
expected := `{"history":[{"duration":"3s","event_id":"00000000000000000000","status":"complete","source":"command","sent_at":"2021-10-03T11:24:52.891386-07:00","started_at":"2021-10-03T11:24:52.891386-07:00","completed_at":"2021-10-03T11:24:52.891386-07:00"}],"count":1,"average":"3s","total":"3s"}`
1152+
assert.Equal(t, expected, strings.TrimSpace(w.Body.String()))
1153+
1154+
influxdbClient.AssertExpectations(t)
1155+
})
1156+
11081157
for _, tt := range tests {
11091158
t.Run(tt.name, func(t *testing.T) {
11101159
influxdbClient := new(influxdb.MockClient)

0 commit comments

Comments
 (0)