kanboard-api/timezone.go
Oliver Jakoubek 1fba43cf90 feat: add timezone support with automatic timestamp conversion
Add GetTimezone() API method and WithTimezone() client option.
When enabled, the client lazily fetches the server timezone on
first API call and converts all Timestamp fields in responses
using reflection-based struct walking.
2026-02-02 12:34:15 +01:00

81 lines
1.8 KiB
Go

package kanboard
import (
"context"
"fmt"
"reflect"
"time"
)
// GetTimezone returns the server's configured timezone string (e.g., "UTC", "Europe/Berlin").
func (c *Client) GetTimezone(ctx context.Context) (string, error) {
var tz string
if err := c.call(ctx, "getTimezone", nil, &tz); err != nil {
return "", fmt.Errorf("getTimezone: %w", err)
}
return tz, nil
}
// loadTimezone fetches and caches the timezone location from the server.
func (c *Client) loadTimezone(ctx context.Context) error {
tz, err := c.GetTimezone(ctx)
if err != nil {
return err
}
loc, err := time.LoadLocation(tz)
if err != nil {
return fmt.Errorf("invalid timezone %q: %w", tz, err)
}
c.timezone = loc
return nil
}
// ensureTimezone loads the timezone if tzEnabled and not yet loaded.
func (c *Client) ensureTimezone(ctx context.Context) error {
if !c.tzEnabled {
return nil
}
var err error
c.tzOnce.Do(func() {
err = c.loadTimezone(ctx)
})
return err
}
// convertTimestamps converts all Timestamp fields in v to the client's timezone.
// v must be a pointer. Handles structs, pointers to structs, and slices of structs.
func (c *Client) convertTimestamps(v any) {
if c.timezone == nil {
return
}
rv := reflect.ValueOf(v)
c.walkAndConvert(rv)
}
var timestampType = reflect.TypeOf(Timestamp{})
func (c *Client) walkAndConvert(rv reflect.Value) {
switch rv.Kind() {
case reflect.Ptr:
if !rv.IsNil() {
c.walkAndConvert(rv.Elem())
}
case reflect.Struct:
if rv.Type() == timestampType {
if rv.CanSet() {
ts := rv.Addr().Interface().(*Timestamp)
if !ts.IsZero() {
ts.Time = ts.Time.In(c.timezone)
}
}
return
}
for i := 0; i < rv.NumField(); i++ {
c.walkAndConvert(rv.Field(i))
}
case reflect.Slice:
for i := 0; i < rv.Len(); i++ {
c.walkAndConvert(rv.Index(i))
}
}
}