3 Commits

Author SHA1 Message Date
70c16e9774 test(commits): add unit tests for commits and cache packages
Add tests for CommitSet filtering (by year, author regex),
ToYearFreq, channel-based frequency functions, cache round-trip
operations, and hash determinism. Also includes goimports formatting
fix in common_test.go.
2026-04-08 09:41:58 +00:00
ef0804254d refactor(server): migrate from gorilla/mux to stdlib net/http
Replace gorilla/mux and gorilla/handlers with Go 1.22+ stdlib
ServeMux method patterns. Fix panic-on-error to return proper HTTP
error responses. Fix Content-Type from text/html to image/svg+xml
for SVG endpoints. Remove gorilla dependencies from go.mod.
2026-04-08 09:41:50 +00:00
028e9a9cac fix(types): correct leap year calculation for century years
The naive year%4==0 check incorrectly treats years like 1900 and 2100
as leap years. Add types.IsLeapYear and types.YearLength helpers using
the full Gregorian calendar rule (divisible by 4, except centuries
unless divisible by 400). Replace all 8 occurrences across the
codebase.
2026-04-08 09:41:44 +00:00
11 changed files with 419 additions and 83 deletions

View File

@@ -2,71 +2,72 @@ package main
import (
"encoding/json"
"log"
"net/http"
"os"
"strconv"
"time"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/taigrr/gico/commits"
"github.com/taigrr/gico/graph/svg"
)
type DayCount [366]int
func main() {
r := mux.NewRouter()
logger := func(h http.Handler) http.Handler {
return handlers.LoggingHandler(os.Stdout, h)
}
r.Use(mux.MiddlewareFunc(logger))
r.HandleFunc("/weekly.svg", func(w http.ResponseWriter, r *http.Request) {
mux := http.NewServeMux()
mux.HandleFunc("GET /weekly.svg", func(w http.ResponseWriter, r *http.Request) {
author := r.URL.Query().Get("author")
highlight := r.URL.Query().Get("highlight")
shouldHighlight := highlight != ""
w.Header().Add("Content-Type", "text/html")
repoPaths, err := commits.GetRepos()
if err != nil {
panic(err)
http.Error(w, "failed to get repos", http.StatusInternalServerError)
log.Printf("error getting repos: %v", err)
return
}
week, err := repoPaths.GetWeekFreq([]string{author})
if err != nil {
panic(err)
http.Error(w, "failed to get weekly frequency", http.StatusInternalServerError)
log.Printf("error getting weekly freq: %v", err)
return
}
svg := svg.GetWeekSVG(week, shouldHighlight)
svg.WriteTo(w)
w.Header().Set("Content-Type", "image/svg+xml")
svgData := svg.GetWeekSVG(week, shouldHighlight)
svgData.WriteTo(w)
})
r.HandleFunc("/stats.json", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("GET /stats.json", func(w http.ResponseWriter, r *http.Request) {
year := time.Now().Year()
yst := r.URL.Query().Get("year")
author := r.URL.Query().Get("author")
y, err := strconv.Atoi(yst)
if err == nil {
if y, err := strconv.Atoi(yst); err == nil {
year = y
}
repoPaths, err := commits.GetRepos()
if err != nil {
panic(err)
http.Error(w, "failed to get repos", http.StatusInternalServerError)
log.Printf("error getting repos: %v", err)
return
}
freq, err := repoPaths.FrequencyChan(year, []string{author})
if err != nil {
panic(err)
http.Error(w, "failed to get frequency", http.StatusInternalServerError)
log.Printf("error getting freq: %v", err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(freq); err != nil {
log.Printf("error encoding response: %v", err)
}
b, _ := json.Marshal(freq)
w.Header().Add("Content-Type", "application/json")
w.Write(b)
})
r.HandleFunc("/yearly.svg", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("GET /yearly.svg", func(w http.ResponseWriter, r *http.Request) {
year := time.Now().Year()
yst := r.URL.Query().Get("year")
author := r.URL.Query().Get("author")
highlight := r.URL.Query().Get("highlight")
shouldHighlight := highlight != ""
y, err := strconv.Atoi(yst)
if err == nil {
if y, err := strconv.Atoi(yst); err == nil {
if year != y {
shouldHighlight = false
}
@@ -74,19 +75,23 @@ func main() {
}
repoPaths, err := commits.GetRepos()
if err != nil {
panic(err)
http.Error(w, "failed to get repos", http.StatusInternalServerError)
log.Printf("error getting repos: %v", err)
return
}
freq, err := repoPaths.FrequencyChan(year, []string{author})
if err != nil {
panic(err)
http.Error(w, "failed to get frequency", http.StatusInternalServerError)
log.Printf("error getting freq: %v", err)
return
}
svg := svg.GetYearSVG(freq, shouldHighlight)
w.Header().Add("Content-Type", "text/html")
svg.WriteTo(w)
w.Header().Set("Content-Type", "image/svg+xml")
svgData := svg.GetYearSVG(freq, shouldHighlight)
svgData.WriteTo(w)
})
err := http.ListenAndServe(":8822", r)
if err != nil {
panic(err)
log.Println("gico-server listening on :8822")
if err := http.ListenAndServe(":8822", mux); err != nil {
log.Fatalf("server error: %v", err)
}
}

99
commits/cache_test.go Normal file
View File

@@ -0,0 +1,99 @@
package commits
import (
"testing"
"time"
"github.com/taigrr/gico/types"
)
func TestHashSliceDeterministic(t *testing.T) {
a := hashSlice([]string{"foo", "bar", "baz"})
b := hashSlice([]string{"baz", "foo", "bar"})
if a != b {
t.Error("hashSlice should be order-independent")
}
}
func TestHashSliceDifferentInputs(t *testing.T) {
a := hashSlice([]string{"foo", "bar"})
b := hashSlice([]string{"foo", "baz"})
if a == b {
t.Error("different inputs should produce different hashes")
}
}
func TestCacheRepoRoundTrip(t *testing.T) {
path := "/test/cache-repo"
head := "deadbeef123"
commits := []types.Commit{
{Hash: head, Author: types.Author{Name: "test"}, TimeStamp: time.Now()},
}
// Should miss before caching
_, ok := GetCachedRepo(path, head)
if ok {
t.Error("expected cache miss before storing")
}
CacheRepo(path, commits)
// Should hit after caching
cached, ok := GetCachedRepo(path, head)
if !ok {
t.Error("expected cache hit after storing")
}
if len(cached) != 1 || cached[0].Hash != head {
t.Error("cached data doesn't match stored data")
}
// Different head should miss
_, ok = GetCachedRepo(path, "differenthead")
if ok {
t.Error("expected cache miss for different head")
}
}
func TestCacheGraphRoundTrip(t *testing.T) {
year := 2025
authors := []string{"alice@example.com"}
paths := []string{"/repo/one", "/repo/two"}
freq := types.Freq{1, 2, 3, 0, 0}
// Should miss before caching
_, ok := GetCachedGraph(year, authors, paths)
if ok {
t.Error("expected cache miss before storing")
}
CacheGraph(year, authors, paths, freq)
// Should hit
cached, ok := GetCachedGraph(year, authors, paths)
if !ok {
t.Error("expected cache hit")
}
if len(cached) != len(freq) {
t.Errorf("expected freq length %d, got %d", len(freq), len(cached))
}
}
func TestCacheReposAuthorsRoundTrip(t *testing.T) {
paths := []string{"/repo/author-test"}
authors := []string{"alice", "bob"}
_, ok := GetCachedReposAuthors(paths)
if ok {
t.Error("expected cache miss")
}
CacheReposAuthors(paths, authors)
cached, ok := GetCachedReposAuthors(paths)
if !ok {
t.Error("expected cache hit")
}
if len(cached) != 2 {
t.Errorf("expected 2 authors, got %d", len(cached))
}
}

View File

@@ -54,10 +54,7 @@ func (paths RepoSet) GetRepoAuthors() ([]string, error) {
}
func (paths RepoSet) GetRepoCommits(year int, authors []string) ([][]types.Commit, error) {
yearLength := 365
if year%4 == 0 {
yearLength++
}
yearLength := types.YearLength(year)
commits := make([][]types.Commit, yearLength)
for i := 0; i < yearLength; i++ {
@@ -109,10 +106,6 @@ func (paths RepoSet) GetRepoCommits(year int, authors []string) ([][]types.Commi
}
func (paths RepoSet) FrequencyChan(year int, authors []string) (types.Freq, error) {
yearLength := 365
if year%4 == 0 {
yearLength++
}
cache, ok := GetCachedGraph(year, authors, paths)
if ok {
return cache, nil
@@ -152,10 +145,7 @@ func (paths RepoSet) FrequencyChan(year int, authors []string) (types.Freq, erro
}
func YearFreqFromChan(cc chan types.Commit, year int) types.Freq {
yearLength := 365
if year%4 == 0 {
yearLength++
}
yearLength := types.YearLength(year)
freq := make([]int, yearLength)
for commit := range cc {
freq[commit.TimeStamp.YearDay()-1]++
@@ -206,10 +196,7 @@ func (repo Repo) GetCommitChan() (chan types.Commit, error) {
}
func FreqFromChan(cc chan types.Commit, year int) types.Freq {
yearLength := 365
if year%4 == 0 {
yearLength++
}
yearLength := types.YearLength(year)
freq := make([]int, yearLength)
for commit := range cc {
if commit.TimeStamp.Year() != year {

View File

@@ -26,10 +26,7 @@ func (paths RepoSet) GetWeekFreq(authors []string) (types.Freq, error) {
return types.Freq{}, err
}
freq = append(curFreq, freq...)
today += 365
if curYear%4 == 0 {
today++
}
today += types.YearLength(curYear)
}
week := freq[today-6 : today+1]
@@ -37,10 +34,7 @@ func (paths RepoSet) GetWeekFreq(authors []string) (types.Freq, error) {
}
func (paths RepoSet) Frequency(year int, authors []string) (types.Freq, error) {
yearLength := 365
if year%4 == 0 {
yearLength++
}
yearLength := types.YearLength(year)
gfreq := make(types.Freq, yearLength)
for _, p := range paths {
repo, err := OpenRepo(p)
@@ -115,11 +109,7 @@ func (repo Repo) GetCommitSet() (CommitSet, error) {
}
func (cs CommitSet) ToYearFreq() types.Freq {
year := cs.Year
yearLength := 365
if year%4 == 0 {
yearLength++
}
yearLength := types.YearLength(cs.Year)
freq := make([]int, yearLength)
for _, v := range cs.Commits {
freq[v.TimeStamp.YearDay()-1]++

212
commits/commits_test.go Normal file
View File

@@ -0,0 +1,212 @@
package commits
import (
"testing"
"time"
"github.com/taigrr/gico/types"
)
func makeCommit(name, email string, ts time.Time) types.Commit {
return types.Commit{
Author: types.Author{Name: name, Email: email},
TimeStamp: ts,
Hash: "abc123",
Repo: "/test/repo",
}
}
func TestCommitSetFilterByYear(t *testing.T) {
cs := CommitSet{
Commits: []types.Commit{
makeCommit("a", "a@x.com", time.Date(2024, 3, 15, 10, 0, 0, 0, time.UTC)),
makeCommit("b", "b@x.com", time.Date(2025, 6, 1, 12, 0, 0, 0, time.UTC)),
makeCommit("c", "c@x.com", time.Date(2024, 12, 31, 23, 59, 0, 0, time.UTC)),
makeCommit("d", "d@x.com", time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)),
},
}
filtered := cs.FilterByYear(2024)
if filtered.Year != 2024 {
t.Errorf("expected Year=2024, got %d", filtered.Year)
}
if len(filtered.Commits) != 2 {
t.Errorf("expected 2 commits for 2024, got %d", len(filtered.Commits))
}
filtered = cs.FilterByYear(2025)
if len(filtered.Commits) != 1 {
t.Errorf("expected 1 commit for 2025, got %d", len(filtered.Commits))
}
filtered = cs.FilterByYear(2000)
if len(filtered.Commits) != 0 {
t.Errorf("expected 0 commits for 2000, got %d", len(filtered.Commits))
}
}
func TestCommitSetFilterByAuthorRegex(t *testing.T) {
cs := CommitSet{
Commits: []types.Commit{
makeCommit("Alice", "alice@example.com", time.Now()),
makeCommit("Bob", "bob@example.com", time.Now()),
makeCommit("Charlie", "charlie@example.com", time.Now()),
},
}
// Filter by name regex
filtered, err := cs.FilterByAuthorRegex([]string{"^Ali"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(filtered.Commits) != 1 {
t.Errorf("expected 1 commit matching ^Ali, got %d", len(filtered.Commits))
}
// Filter by email regex
filtered, err = cs.FilterByAuthorRegex([]string{"bob@"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(filtered.Commits) != 1 {
t.Errorf("expected 1 commit matching bob@, got %d", len(filtered.Commits))
}
// Multiple patterns
filtered, err = cs.FilterByAuthorRegex([]string{"Alice", "Charlie"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(filtered.Commits) != 2 {
t.Errorf("expected 2 commits matching Alice|Charlie, got %d", len(filtered.Commits))
}
// Invalid regex
_, err = cs.FilterByAuthorRegex([]string{"[invalid"})
if err == nil {
t.Error("expected error for invalid regex, got nil")
}
}
func TestCommitSetToYearFreq(t *testing.T) {
// Regular year (2025)
cs := CommitSet{
Year: 2025,
Commits: []types.Commit{
makeCommit("a", "a@x.com", time.Date(2025, 1, 1, 10, 0, 0, 0, time.UTC)),
makeCommit("a", "a@x.com", time.Date(2025, 1, 1, 14, 0, 0, 0, time.UTC)),
makeCommit("b", "b@x.com", time.Date(2025, 3, 15, 12, 0, 0, 0, time.UTC)),
},
}
freq := cs.ToYearFreq()
if len(freq) != 365 {
t.Errorf("expected 365 days for 2025, got %d", len(freq))
}
if freq[0] != 2 {
t.Errorf("expected 2 commits on Jan 1, got %d", freq[0])
}
// March 15 = day 74 (31+28+15=74, index 73)
if freq[73] != 1 {
t.Errorf("expected 1 commit on Mar 15, got %d", freq[73])
}
// Leap year (2024)
csLeap := CommitSet{
Year: 2024,
Commits: []types.Commit{
makeCommit("a", "a@x.com", time.Date(2024, 12, 31, 10, 0, 0, 0, time.UTC)),
},
}
freqLeap := csLeap.ToYearFreq()
if len(freqLeap) != 366 {
t.Errorf("expected 366 days for 2024, got %d", len(freqLeap))
}
}
func TestYearFreqFromChan(t *testing.T) {
cc := make(chan types.Commit, 5)
cc <- makeCommit("a", "a@x.com", time.Date(2025, 1, 1, 10, 0, 0, 0, time.UTC))
cc <- makeCommit("a", "a@x.com", time.Date(2025, 1, 1, 14, 0, 0, 0, time.UTC))
cc <- makeCommit("b", "b@x.com", time.Date(2025, 6, 15, 12, 0, 0, 0, time.UTC))
close(cc)
freq := YearFreqFromChan(cc, 2025)
if len(freq) != 365 {
t.Errorf("expected 365, got %d", len(freq))
}
if freq[0] != 2 {
t.Errorf("expected 2 on Jan 1, got %d", freq[0])
}
}
func TestFreqFromChan(t *testing.T) {
cc := make(chan types.Commit, 5)
cc <- makeCommit("a", "a@x.com", time.Date(2025, 1, 1, 10, 0, 0, 0, time.UTC))
cc <- makeCommit("b", "b@x.com", time.Date(2024, 6, 15, 12, 0, 0, 0, time.UTC)) // wrong year
cc <- makeCommit("c", "c@x.com", time.Date(2025, 12, 31, 23, 0, 0, 0, time.UTC))
close(cc)
freq := FreqFromChan(cc, 2025)
if len(freq) != 365 {
t.Errorf("expected 365, got %d", len(freq))
}
if freq[0] != 1 {
t.Errorf("expected 1 on Jan 1 (skip wrong year), got %d", freq[0])
}
if freq[364] != 1 {
t.Errorf("expected 1 on Dec 31, got %d", freq[364])
}
}
func TestFilterCChanByYear(t *testing.T) {
in := make(chan types.Commit, 5)
in <- makeCommit("a", "a@x.com", time.Date(2025, 3, 1, 10, 0, 0, 0, time.UTC))
in <- makeCommit("b", "b@x.com", time.Date(2024, 3, 1, 10, 0, 0, 0, time.UTC))
in <- makeCommit("c", "c@x.com", time.Date(2025, 7, 4, 10, 0, 0, 0, time.UTC))
close(in)
out := FilterCChanByYear(in, 2025)
count := 0
for range out {
count++
}
if count != 2 {
t.Errorf("expected 2 commits for 2025, got %d", count)
}
}
func TestFilterCChanByAuthor(t *testing.T) {
in := make(chan types.Commit, 5)
in <- makeCommit("Alice", "alice@x.com", time.Now())
in <- makeCommit("Bob", "bob@x.com", time.Now())
in <- makeCommit("Charlie", "charlie@x.com", time.Now())
close(in)
out, err := FilterCChanByAuthor(in, []string{"Alice", "Charlie"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
count := 0
for range out {
count++
}
if count != 2 {
t.Errorf("expected 2 commits, got %d", count)
}
}
func TestOpenRepoNonExistent(t *testing.T) {
_, err := OpenRepo("/nonexistent/path")
if err == nil {
t.Error("expected error for nonexistent path")
}
}
func TestOpenRepoNonDirectory(t *testing.T) {
// Use a known file (not a directory)
_, err := OpenRepo("/dev/null")
if err == nil {
t.Error("expected error for non-directory path")
}
}

3
go.mod
View File

@@ -8,8 +8,6 @@ require (
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/go-git/go-git/v5 v5.16.5
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
github.com/muesli/termenv v0.16.0
github.com/taigrr/mg v0.1.1
github.com/taigrr/simplecolorpalettes v0.9.8
@@ -31,7 +29,6 @@ require (
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.7.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect

6
go.sum
View File

@@ -53,8 +53,6 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@@ -69,10 +67,6 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8J
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=

View File

@@ -8,10 +8,10 @@ import (
func TestMinMax(t *testing.T) {
tests := []struct {
name string
input []int
wantMin int
wantMax int
name string
input []int
wantMin int
wantMax int
}{
{
name: "normal values",

View File

@@ -48,6 +48,19 @@ type (
}
)
// IsLeapYear returns true if year is a leap year per the Gregorian calendar.
func IsLeapYear(year int) bool {
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
}
// YearLength returns 366 for leap years and 365 otherwise.
func YearLength(year int) int {
if IsLeapYear(year) {
return 366
}
return 365
}
func (c Commit) String() string {
return fmt.Sprintf("%s\t%s\t%s\t%s",
c.TimeStamp.Format("0"+time.Kitchen),

View File

@@ -122,6 +122,45 @@ func TestFreqMerge(t *testing.T) {
}
}
func TestIsLeapYear(t *testing.T) {
tests := []struct {
year int
want bool
}{
{2024, true}, // divisible by 4
{2025, false}, // not divisible by 4
{1900, false}, // divisible by 100 but not 400
{2000, true}, // divisible by 400
{2100, false}, // divisible by 100 but not 400
{2400, true}, // divisible by 400
{1996, true}, // divisible by 4
{2023, false}, // not divisible by 4
}
for _, tt := range tests {
t.Run(time.Date(tt.year, 1, 1, 0, 0, 0, 0, time.UTC).Format("2006"), func(t *testing.T) {
got := IsLeapYear(tt.year)
if got != tt.want {
t.Errorf("IsLeapYear(%d) = %v, want %v", tt.year, got, tt.want)
}
})
}
}
func TestYearLength(t *testing.T) {
if YearLength(2024) != 366 {
t.Error("expected 366 for 2024")
}
if YearLength(2025) != 365 {
t.Error("expected 365 for 2025")
}
if YearLength(1900) != 365 {
t.Error("expected 365 for 1900 (century year, not leap)")
}
if YearLength(2000) != 366 {
t.Error("expected 366 for 2000 (divisible by 400)")
}
}
func TestDataSetOperations(t *testing.T) {
ds := NewDataSet()
now := time.Now().Truncate(24 * time.Hour)

View File

@@ -5,6 +5,8 @@ import (
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/taigrr/gico/types"
)
const (
@@ -66,12 +68,10 @@ func (m model) Init() tea.Cmd {
return nil
}
// YearLen returns the number of days in a year.
// Deprecated: Use types.YearLength instead.
func YearLen(year int) int {
yearLen := 365
if year%4 == 0 {
yearLen++
}
return yearLen
return types.YearLength(year)
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {