Koito/internal/db/period.go
Gabe Farrell d4ac96f780
feat: Rewind (#116)
* wip

* chore: update counts to allow unix timeframe

* feat: add db functions for counting new items

* wip: endpoint working

* wip

* wip: initial ui done

* add header, adjust ui

* add time listened toggle

* fix layout, year param

* param fixes
2025-12-31 18:44:55 -05:00

125 lines
3.5 KiB
Go

package db
import (
"time"
)
// should this be in db package ???
type Timeframe struct {
Period Period
T1u int64
T2u int64
}
func TimeframeToTimeRange(timeframe Timeframe) (t1, t2 time.Time) {
if timeframe.T1u == 0 && timeframe.T2u == 0 {
t2 = time.Now()
t1 = StartTimeFromPeriod(timeframe.Period)
} else {
t1 = time.Unix(timeframe.T1u, 0)
t2 = time.Unix(timeframe.T2u, 0)
}
return
}
type Period string
const (
PeriodDay Period = "day"
PeriodWeek Period = "week"
PeriodMonth Period = "month"
PeriodYear Period = "year"
PeriodAllTime Period = "all_time"
PeriodDefault Period = "day"
)
func StartTimeFromPeriod(p Period) time.Time {
now := time.Now()
switch p {
case "day":
return now.AddDate(0, 0, -1)
case "week":
return now.AddDate(0, 0, -7)
case "month":
return now.AddDate(0, -1, 0)
case "year":
return now.AddDate(-1, 0, 0)
case "all_time":
return time.Time{}
default:
// default 1 day
return now.AddDate(0, 0, -1)
}
}
type StepInterval string
const (
StepDay StepInterval = "day"
StepWeek StepInterval = "week"
StepMonth StepInterval = "month"
StepYear StepInterval = "year"
StepDefault StepInterval = "day"
DefaultRange int = 12
)
// start is the time of 00:00 at the beginning of opts.Range opts.Steps ago,
// end is the end time of the current opts.Step.
// E.g. if step is StepWeek and range is 4, start will be the time 00:00 on Sunday on the 4th week ago,
// and end will be 23:59:59 on Saturday at the end of the current week.
// If opts.Year (or opts.Year + opts.Month) is provided, start and end will simply by the start and end times of that year/month.
func ListenActivityOptsToTimes(opts ListenActivityOpts) (start, end time.Time) {
now := time.Now()
// If Year (and optionally Month) are specified, use calendar boundaries
if opts.Year != 0 {
if opts.Month != 0 {
// Specific month of a specific year
start = time.Date(opts.Year, time.Month(opts.Month), 1, 0, 0, 0, 0, now.Location())
end = start.AddDate(0, 1, 0).Add(-time.Nanosecond)
} else {
// Whole year
start = time.Date(opts.Year, 1, 1, 0, 0, 0, 0, now.Location())
end = start.AddDate(1, 0, 0).Add(-time.Nanosecond)
}
return start, end
}
// X days ago + today = range
opts.Range = opts.Range - 1
// Determine step and align accordingly
switch opts.Step {
case StepDay:
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
start = today.AddDate(0, 0, -opts.Range)
end = today.AddDate(0, 0, 1).Add(-time.Nanosecond)
case StepWeek:
// Align to most recent Sunday
weekday := int(now.Weekday()) // Sunday = 0
startOfThisWeek := time.Date(now.Year(), now.Month(), now.Day()-weekday, 0, 0, 0, 0, now.Location())
start = startOfThisWeek.AddDate(0, 0, -7*opts.Range)
end = startOfThisWeek.AddDate(0, 0, 7).Add(-time.Nanosecond)
case StepMonth:
firstOfThisMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
start = firstOfThisMonth.AddDate(0, -opts.Range, 0)
end = firstOfThisMonth.AddDate(0, 1, 0).Add(-time.Nanosecond)
case StepYear:
firstOfThisYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
start = firstOfThisYear.AddDate(-opts.Range, 0, 0)
end = firstOfThisYear.AddDate(1, 0, 0).Add(-time.Nanosecond)
default:
// Default to daily
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
start = today.AddDate(0, 0, -opts.Range)
end = today.AddDate(0, 0, 1).Add(-time.Nanosecond)
}
return start, end
}