fixed timecode issue

This commit is contained in:
2025-09-22 10:53:41 -07:00
parent 4aa72fcd11
commit f2256a9cfe
20 changed files with 173 additions and 12 deletions

View File

@@ -1,10 +1,12 @@
# Garmin Connect Go Client
[![Go Reference](https://pkg.go.dev/badge/github.com/sstent/go-garth/pkg/garmin.svg)](https://pkg.go.dev/github.com/sstent/go-garth/pkg/garmin)
Go port of the Garth Python library for accessing Garmin Connect data. Provides full API coverage with improved performance and type safety.
## Installation
```bash
go get github.com/sstent/garmin-connect/garth
go get github.com/sstent/go-garth/pkg/garmin
```
## Basic Usage
@@ -14,12 +16,12 @@ package main
import (
"fmt"
"time"
"garmin-connect/garth"
"github.com/sstent/go-garth/pkg/garmin"
)
func main() {
// Create client and authenticate
client, err := garth.NewClient("garmin.com")
client, err := garmin.NewClient("garmin.com")
if err != nil {
panic(err)
}
@@ -29,9 +31,9 @@ func main() {
panic(err)
}
// Get yesterday's body battery data
// Get yesterday's body battery data (detailed)
yesterday := time.Now().AddDate(0, 0, -1)
bb, err := garth.BodyBatteryData{}.Get(yesterday, client)
bb, err := client.GetBodyBatteryData(yesterday)
if err != nil {
panic(err)
}
@@ -41,7 +43,7 @@ func main() {
}
// Get weekly steps
steps := garth.NewDailySteps()
steps := garmin.NewDailySteps()
stepData, err := steps.List(time.Now(), 7, client)
if err != nil {
panic(err)
@@ -49,8 +51,8 @@ func main() {
for _, s := range stepData {
fmt.Printf("%s: %d steps\n",
s.(garth.DailySteps).CalendarDate.Format("2006-01-02"),
*s.(garth.DailySteps).TotalSteps)
s.(garmin.DailySteps).CalendarDate.Format("2006-01-02"),
*s.(garmin.DailySteps).TotalSteps)
}
}
```
@@ -102,7 +104,7 @@ BenchmarkSleepList-8 50000 35124 ns/op (7 days)
```
## Documentation
Full API docs: [https://pkg.go.dev/garmin-connect/garth](https://pkg.go.dev/garmin-connect/garth)
Full API docs: [https://pkg.go.dev/github.com/sstent/go-garth/pkg/garmin](https://pkg.go.dev/github.com/sstent/go-garth/pkg/garmin)
## CLI Tool
Includes `cmd/garth` CLI for data export. Supports both daily and weekly stats:

View File

@@ -0,0 +1,6 @@
// Package client implements the low-level Garmin Connect HTTP client.
// It is responsible for authentication (via SSO helpers), request construction,
// header and cookie handling, error mapping, and JSON decoding. Higher-level
// public APIs in pkg/garmin delegate to this package for actual network I/O.
// Note: This is an internal package and not intended for direct external use.
package client

View File

@@ -11,7 +11,7 @@ import (
// LoadEnvCredentials loads credentials from .env file
func LoadEnvCredentials() (email, password, domain string, err error) {
// Determine project root (assuming .env is in the project root)
projectRoot := "/home/sstent/Projects/github.com/sstent/go-garth"
projectRoot := "/home/sstent/Projects/go-garth"
envPath := filepath.Join(projectRoot, ".env")
// Load .env file

View File

@@ -0,0 +1,4 @@
// Package credentials provides helpers for loading user credentials and
// environment configuration used during authentication and local development.
// Note: This is an internal package and not intended for direct external use.
package credentials

View File

@@ -0,0 +1,5 @@
// Package oauth contains low-level OAuth1 and OAuth2 flows used by SSO to
// obtain and exchange tokens. It handles request signing, headers, and response
// parsing to produce strongly-typed token structures for the client.
// Note: This is an internal package and not intended for direct external use.
package oauth

5
internal/auth/sso/doc.go Normal file
View File

@@ -0,0 +1,5 @@
// Package sso implements the Garmin SSO login flow. It orchestrates CSRF,
// ticket exchange, MFA placeholders, and token retrieval, delegating OAuth
// details to internal/auth/oauth. The internal client consumes this package.
// Note: This is an internal package and not intended for direct external use.
package sso

5
internal/config/doc.go Normal file
View File

@@ -0,0 +1,5 @@
// Package config defines the application configuration schema and helpers for
// locating, loading, saving, and initializing configuration files following
// conventional XDG directory layout.
// Note: This is an internal package and not intended for direct external use.
package config

5
internal/data/doc.go Normal file
View File

@@ -0,0 +1,5 @@
// Package data provides helpers and enrichments for Garmin wellness and metric
// data. It includes parsing utilities, convenience wrappers that add methods to
// decoded responses, and transformations used by higher-level APIs.
// Note: This is an internal package and not intended for direct external use.
package data

5
internal/errors/doc.go Normal file
View File

@@ -0,0 +1,5 @@
// Package errors defines structured error types used across the module,
// including APIError, IOError, AuthenticationError, OAuthError, and
// ValidationError. These implement error wrapping and preserve HTTP context.
// Note: This is an internal package and not intended for direct external use.
package errors

View File

@@ -0,0 +1,5 @@
// Package types defines core domain models mapped to Garmin Connect API JSON.
// It includes user profile, wellness metrics, sleep detail, HRV, body battery,
// training status/load, time helpers, and related structures.
// Note: This is an internal package and not intended for direct external use.
package types

View File

@@ -70,6 +70,7 @@ func (gt *GarminTime) UnmarshalJSON(b []byte) (err error) {
// If the input string does not contain 'Z', it will be parsed as local time.
// For consistency, we'll assume UTC if no timezone is specified.
layouts := []string{
"2006-01-02 15:04:05", // Example: 2025-09-21 07:18:03
"2006-01-02T15:04:05.0", // Example: 2018-09-01T00:13:25.0
"2006-01-02T15:04:05", // Example: 2018-09-01T00:13:25
"2006-01-02", // Example: 2018-09-01

View File

@@ -0,0 +1,82 @@
package types
import (
"encoding/json"
"testing"
"time"
)
func TestGarminTime_UnmarshalJSON(t *testing.T) {
tests := []struct {
name string
input string
expected time.Time
wantErr bool
}{
{
name: "space separated format",
input: `"2025-09-21 07:18:03"`,
expected: time.Date(2025, 9, 21, 7, 18, 3, 0, time.UTC),
wantErr: false,
},
{
name: "T separator with milliseconds",
input: `"2018-09-01T00:13:25.0"`,
expected: time.Date(2018, 9, 1, 0, 13, 25, 0, time.UTC),
wantErr: false,
},
{
name: "T separator without milliseconds",
input: `"2018-09-01T00:13:25"`,
expected: time.Date(2018, 9, 1, 0, 13, 25, 0, time.UTC),
wantErr: false,
},
{
name: "date only",
input: `"2018-09-01"`,
expected: time.Date(2018, 9, 1, 0, 0, 0, 0, time.UTC),
wantErr: false,
},
{
name: "invalid format",
input: `"invalid"`,
wantErr: true,
},
{
name: "null value",
input: "null",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var gt GarminTime
err := json.Unmarshal([]byte(tt.input), &gt)
if tt.wantErr {
if err == nil {
t.Errorf("expected error but got none")
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if tt.input == "null" {
// For null values, the time should be zero
if !gt.Time.IsZero() {
t.Errorf("expected zero time for null input, got %v", gt.Time)
}
return
}
if !gt.Time.Equal(tt.expected) {
t.Errorf("expected %v, got %v", tt.expected, gt.Time)
}
})
}
}

5
internal/stats/doc.go Normal file
View File

@@ -0,0 +1,5 @@
// Package stats provides typed accessors for aggregated statistics endpoints,
// including daily and weekly variants for steps, stress, hydration, sleep, HRV,
// and intensity minutes. Pagination and date-window logic live here.
// Note: This is an internal package and not intended for direct external use.
package stats

View File

@@ -0,0 +1,4 @@
// Package testutils contains helpers for tests such as HTTP servers and mock
// clients. It is used by unit and integration tests within this repository.
// Note: This is an internal package and not intended for direct external use.
package testutils

4
internal/users/doc.go Normal file
View File

@@ -0,0 +1,4 @@
// Package users contains structures related to user settings and sleep windows.
// It mirrors selected Garmin user profile payloads used in feature logic.
// Note: This is an internal package and not intended for direct external use.
package users

4
internal/utils/doc.go Normal file
View File

@@ -0,0 +1,4 @@
// Package utils provides general helpers such as OAuth signing utilities,
// time conversions, date range helpers, and string case conversions.
// Note: This is an internal package and not intended for direct external use.
package utils

6
pkg/auth/oauth/doc.go Normal file
View File

@@ -0,0 +1,6 @@
// Package oauth provides public wrappers around the internal OAuth helpers.
// It exposes functions to obtain OAuth1 tokens and exchange them for OAuth2
// tokens compatible with the public garmin package types. External consumers
// should use this package when they need token bootstrapping independent of
// a fully initialized client.
package oauth

View File

@@ -9,6 +9,8 @@ import (
"github.com/sstent/go-garth/internal/models/types"
)
// GetDailyHRVData retrieves comprehensive daily HRV data for the given date.
// It returns nil when no HRV data is available for the specified day.
func (c *Client) GetDailyHRVData(date time.Time) (*types.DailyHRVData, error) {
return getDailyHRVData(date, c.Client)
}
@@ -41,6 +43,9 @@ func getDailyHRVData(day time.Time, client *internalClient.Client) (*types.Daily
return &response.HRVSummary, nil
}
// GetDetailedSleepData retrieves comprehensive sleep data for the given date,
// including sleep stages and movement where available. It returns nil when no
// sleep data is available for the specified day.
func (c *Client) GetDetailedSleepData(date time.Time) (*types.DetailedSleepData, error) {
return getDetailedSleepData(date, c.Client)
}

4
shared/interfaces/doc.go Normal file
View File

@@ -0,0 +1,4 @@
// Package interfaces defines narrow contracts shared across packages.
// Notably, APIClient abstracts HTTP access required by data and stats layers,
// and BaseData/Data define the concurrent day-by-day retrieval pattern.
package interfaces

4
shared/models/doc.go Normal file
View File

@@ -0,0 +1,4 @@
// Package models defines shared data models that are safe to expose to public
// packages, including user settings and related sub-structures consumed by
// both internal client code and public wrappers.
package models