mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
refactor: replace most custom date util functions by lancet ones
refactor: add precision mode to missing intervals function
This commit is contained in:
parent
8a731a252a
commit
5aae18e241
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/duke-git/lancet/v2/datetime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ func (h *HeartbeatHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
timezone := user.TZ()
|
timezone := user.TZ()
|
||||||
rangeFrom, rangeTo := utils.StartOfDay(date.In(timezone)), utils.EndOfDay(date.In(timezone))
|
rangeFrom, rangeTo := datetime.BeginOfDay(date.In(timezone)), datetime.EndOfDay(date.In(timezone))
|
||||||
|
|
||||||
heartbeats, err := h.heartbeatSrvc.GetAllWithin(rangeFrom, rangeTo, user)
|
heartbeats, err := h.heartbeatSrvc.GetAllWithin(rangeFrom, rangeTo, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2,6 +2,7 @@ package v1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/duke-git/lancet/v2/datetime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -120,7 +121,7 @@ func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary
|
|||||||
// i.e. for wakatime, an interval 2021-04-29 - 2021-04-29 is actually 2021-04-29 - 2021-04-30,
|
// i.e. for wakatime, an interval 2021-04-29 - 2021-04-29 is actually 2021-04-29 - 2021-04-30,
|
||||||
// while for wakapi it would be empty
|
// while for wakapi it would be empty
|
||||||
// see https://github.com/muety/wakapi/issues/192
|
// see https://github.com/muety/wakapi/issues/192
|
||||||
end = utils.EndOfDay(end).Add(-1 * time.Second)
|
end = datetime.EndOfDay(end)
|
||||||
|
|
||||||
overallParams := &models.SummaryParams{
|
overallParams := &models.SummaryParams{
|
||||||
From: start,
|
From: start,
|
||||||
|
@ -2,6 +2,7 @@ package routes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/v2/datetime"
|
||||||
"github.com/muety/wakapi/views"
|
"github.com/muety/wakapi/views"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -28,7 +29,7 @@ func DefaultTemplateFuncs() template.FuncMap {
|
|||||||
"simpledate": utils.FormatDate,
|
"simpledate": utils.FormatDate,
|
||||||
"simpledatetime": utils.FormatDateTime,
|
"simpledatetime": utils.FormatDateTime,
|
||||||
"duration": utils.FmtWakatimeDuration,
|
"duration": utils.FmtWakatimeDuration,
|
||||||
"floordate": utils.FloorDate,
|
"floordate": datetime.BeginOfDay,
|
||||||
"ceildate": utils.CeilDate,
|
"ceildate": utils.CeilDate,
|
||||||
"title": strings.Title,
|
"title": strings.Title,
|
||||||
"join": strings.Join,
|
"join": strings.Join,
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/v2/datetime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -13,7 +14,6 @@ import (
|
|||||||
"github.com/muety/wakapi/config"
|
"github.com/muety/wakapi/config"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
wakatime "github.com/muety/wakapi/models/compat/wakatime/v1"
|
wakatime "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
"golang.org/x/sync/semaphore"
|
"golang.org/x/sync/semaphore"
|
||||||
)
|
)
|
||||||
@ -295,8 +295,8 @@ func mapHeartbeat(
|
|||||||
func generateDays(from, to time.Time) []time.Time {
|
func generateDays(from, to time.Time) []time.Time {
|
||||||
days := make([]time.Time, 0)
|
days := make([]time.Time, 0)
|
||||||
|
|
||||||
from = utils.StartOfDay(from)
|
from = datetime.BeginOfDay(from)
|
||||||
to = utils.StartOfDay(to.AddDate(0, 0, 1))
|
to = datetime.BeginOfDay(to.AddDate(0, 0, 1))
|
||||||
|
|
||||||
for d := from; d.Before(to); d = d.AddDate(0, 0, 1) {
|
for d := from; d.Before(to); d = d.AddDate(0, 0, 1) {
|
||||||
days = append(days, d)
|
days = append(days, d)
|
||||||
|
@ -3,6 +3,7 @@ package services
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/v2/datetime"
|
||||||
"github.com/emvi/logbuch"
|
"github.com/emvi/logbuch"
|
||||||
"github.com/leandro-lugaresi/hub"
|
"github.com/leandro-lugaresi/hub"
|
||||||
"github.com/muety/wakapi/config"
|
"github.com/muety/wakapi/config"
|
||||||
@ -113,7 +114,7 @@ func (srv *SummaryService) Retrieve(from, to time.Time, user *models.User, filte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate missing slots (especially before and after existing summaries) from durations (formerly raw heartbeats)
|
// Generate missing slots (especially before and after existing summaries) from durations (formerly raw heartbeats)
|
||||||
missingIntervals := srv.getMissingIntervals(from, to, summaries)
|
missingIntervals := srv.getMissingIntervals(from, to, summaries, false)
|
||||||
for _, interval := range missingIntervals {
|
for _, interval := range missingIntervals {
|
||||||
if s, err := srv.Summarize(interval.Start, interval.End, user, filters); err == nil {
|
if s, err := srv.Summarize(interval.Start, interval.End, user, filters); err == nil {
|
||||||
summaries = append(summaries, s)
|
summaries = append(summaries, s)
|
||||||
@ -368,7 +369,7 @@ func (srv *SummaryService) mergeSummaryItems(existing []*models.SummaryItem, new
|
|||||||
return itemList
|
return itemList
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *SummaryService) getMissingIntervals(from, to time.Time, summaries []*models.Summary) []*models.Interval {
|
func (srv *SummaryService) getMissingIntervals(from, to time.Time, summaries []*models.Summary, precise bool) []*models.Interval {
|
||||||
if len(summaries) == 0 {
|
if len(summaries) == 0 {
|
||||||
return []*models.Interval{{from, to}}
|
return []*models.Interval{{from, to}}
|
||||||
}
|
}
|
||||||
@ -377,37 +378,43 @@ func (srv *SummaryService) getMissingIntervals(from, to time.Time, summaries []*
|
|||||||
|
|
||||||
// Pre
|
// Pre
|
||||||
if from.Before(summaries[0].FromTime.T()) {
|
if from.Before(summaries[0].FromTime.T()) {
|
||||||
intervals = append(intervals, &models.Interval{from, summaries[0].FromTime.T()})
|
intervals = append(intervals, &models.Interval{Start: from, End: summaries[0].FromTime.T()})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Between
|
// Between
|
||||||
for i := 0; i < len(summaries)-1; i++ {
|
for i := 0; i < len(summaries)-1; i++ {
|
||||||
t1, t2 := summaries[i].ToTime.T(), summaries[i+1].FromTime.T()
|
t1, t2 := summaries[i].ToTime.T(), summaries[i+1].FromTime.T()
|
||||||
if t1.Equal(t2) {
|
if t1.Equal(t2) || t1.Equal(to) || t1.After(to) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td1 := t1
|
||||||
|
td2 := t2
|
||||||
|
|
||||||
// round to end of day / start of day, assuming that summaries are always generated on a per-day basis
|
// round to end of day / start of day, assuming that summaries are always generated on a per-day basis
|
||||||
// we assume that, if summary for any time range within a day is present, no further heartbeats exist on that day before 'from' and after 'to' time of that summary
|
// we assume that, if summary for any time range within a day is present, no further heartbeats exist on that day before 'from' and after 'to' time of that summary
|
||||||
// this requires that a summary exists for every single day in a year and none is skipped, which shouldn't ever happen
|
// this requires that a summary exists for every single day in a year and none is skipped, which shouldn't ever happen
|
||||||
td1 := time.Date(t1.Year(), t1.Month(), t1.Day()+1, 0, 0, 0, 0, t1.Location())
|
// non-precise mode is mainly for speed when fetching summaries over large intervals and trades speed for summary accuracy / comprehensiveness
|
||||||
td2 := time.Date(t2.Year(), t2.Month(), t2.Day(), 0, 0, 0, 0, t2.Location())
|
if !precise {
|
||||||
|
td1 = datetime.BeginOfDay(t1).AddDate(0, 0, 1)
|
||||||
|
td2 = datetime.BeginOfDay(t2)
|
||||||
|
|
||||||
// we always want to jump to beginning of next day
|
// we always want to jump to beginning of next day
|
||||||
// however, if left summary ends already at midnight, we would instead jump to beginning of second-next day -> go back again
|
// however, if left summary ends already at midnight, we would instead jump to beginning of second-next day -> go back again
|
||||||
if td1.Sub(t1) == 24*time.Hour {
|
if td1.Sub(t1) == 24*time.Hour {
|
||||||
td1 = td1.Add(-1 * time.Hour)
|
td1 = td1.Add(-1 * time.Hour)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// one or more day missing in between?
|
// one or more day missing in between?
|
||||||
if td1.Before(td2) {
|
if td1.Before(td2) {
|
||||||
intervals = append(intervals, &models.Interval{summaries[i].ToTime.T(), summaries[i+1].FromTime.T()})
|
intervals = append(intervals, &models.Interval{Start: summaries[i].ToTime.T(), End: summaries[i+1].FromTime.T()})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post
|
// Post
|
||||||
if to.After(summaries[len(summaries)-1].ToTime.T()) {
|
if to.After(summaries[len(summaries)-1].ToTime.T()) {
|
||||||
intervals = append(intervals, &models.Interval{summaries[len(summaries)-1].ToTime.T(), to})
|
intervals = append(intervals, &models.Interval{Start: summaries[len(summaries)-1].ToTime.T(), End: to})
|
||||||
}
|
}
|
||||||
|
|
||||||
return intervals
|
return intervals
|
||||||
|
@ -485,6 +485,45 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Filters() {
|
|||||||
assert.Contains(suite.T(), effectiveFilters.Label, TestProjectLabel3)
|
assert.Contains(suite.T(), effectiveFilters.Label, TestProjectLabel3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *SummaryServiceTestSuite) TestSummaryService_getMissingIntervals() {
|
||||||
|
sut := NewSummaryService(suite.SummaryRepository, suite.DurationService, suite.AliasService, suite.ProjectLabelService)
|
||||||
|
|
||||||
|
from1, _ := time.Parse(time.RFC822, "25 Mar 22 11:00 UTC")
|
||||||
|
to1, _ := time.Parse(time.RFC822, "25 Mar 22 13:00 UTC")
|
||||||
|
from2, _ := time.Parse(time.RFC822, "25 Mar 22 15:00 UTC")
|
||||||
|
to2, _ := time.Parse(time.RFC822, "26 Mar 22 00:00 UTC")
|
||||||
|
|
||||||
|
summaries := []*models.Summary{
|
||||||
|
{FromTime: models.CustomTime(from1), ToTime: models.CustomTime(to1)},
|
||||||
|
{FromTime: models.CustomTime(from2), ToTime: models.CustomTime(to2)},
|
||||||
|
}
|
||||||
|
|
||||||
|
r1 := sut.getMissingIntervals(from1, to1, summaries, true)
|
||||||
|
assert.Empty(suite.T(), r1)
|
||||||
|
|
||||||
|
r2 := sut.getMissingIntervals(from1, from1, summaries, true)
|
||||||
|
assert.Empty(suite.T(), r2)
|
||||||
|
|
||||||
|
// non-precise mode will not return intra-day intervals
|
||||||
|
// we might want to change this ...
|
||||||
|
r3 := sut.getMissingIntervals(from1, to2, summaries, false)
|
||||||
|
assert.Len(suite.T(), r3, 0)
|
||||||
|
|
||||||
|
r4 := sut.getMissingIntervals(from1, to2, summaries, true)
|
||||||
|
assert.Len(suite.T(), r4, 1)
|
||||||
|
assert.Equal(suite.T(), to1, r4[0].Start)
|
||||||
|
assert.Equal(suite.T(), from2, r4[0].End)
|
||||||
|
|
||||||
|
r5 := sut.getMissingIntervals(from1.Add(-time.Hour), to2.Add(time.Hour), summaries, true)
|
||||||
|
assert.Len(suite.T(), r5, 3)
|
||||||
|
assert.Equal(suite.T(), from1.Add(-time.Hour), r5[0].Start)
|
||||||
|
assert.Equal(suite.T(), from1, r5[0].End)
|
||||||
|
assert.Equal(suite.T(), to1, r5[1].Start)
|
||||||
|
assert.Equal(suite.T(), from2, r5[1].End)
|
||||||
|
assert.Equal(suite.T(), to2, r5[2].Start)
|
||||||
|
assert.Equal(suite.T(), to2.Add(time.Hour), r5[2].End)
|
||||||
|
}
|
||||||
|
|
||||||
func filterDurations(from, to time.Time, durations models.Durations) models.Durations {
|
func filterDurations(from, to time.Time, durations models.Durations) models.Durations {
|
||||||
filtered := make([]*models.Duration, 0, len(durations))
|
filtered := make([]*models.Duration, 0, len(durations))
|
||||||
for _, d := range durations {
|
for _, d := range durations {
|
||||||
|
@ -2,6 +2,7 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/v2/datetime"
|
||||||
"github.com/emvi/logbuch"
|
"github.com/emvi/logbuch"
|
||||||
"github.com/leandro-lugaresi/hub"
|
"github.com/leandro-lugaresi/hub"
|
||||||
"github.com/muety/wakapi/config"
|
"github.com/muety/wakapi/config"
|
||||||
@ -100,9 +101,9 @@ func (srv *UserService) GetAllByReports(reportsEnabled bool) ([]*models.User, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (srv *UserService) GetActive(exact bool) ([]*models.User, error) {
|
func (srv *UserService) GetActive(exact bool) ([]*models.User, error) {
|
||||||
minDate := time.Now().Add(-24 * time.Hour * time.Duration(srv.config.App.InactiveDays))
|
minDate := time.Now().AddDate(0, 0, -1*srv.config.App.InactiveDays)
|
||||||
if !exact {
|
if !exact {
|
||||||
minDate = utils.FloorDateHour(minDate)
|
minDate = datetime.BeginOfHour(minDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheKey := fmt.Sprintf("%s--active", minDate.String())
|
cacheKey := fmt.Sprintf("%s--active", minDate.String())
|
||||||
|
@ -1033,7 +1033,7 @@
|
|||||||
" pm.expect(jsonData.timezone).to.eql(pm.collectionVariables.get('TZ'));",
|
" pm.expect(jsonData.timezone).to.eql(pm.collectionVariables.get('TZ'));",
|
||||||
" var date = new Date(\"2022-01-01T00:00:00+0100\")",
|
" var date = new Date(\"2022-01-01T00:00:00+0100\")",
|
||||||
" pm.expect(new Date(jsonData.start)).to.eql(date);",
|
" pm.expect(new Date(jsonData.start)).to.eql(date);",
|
||||||
" pm.expect(new Date(jsonData.end)).to.eql(new Date(date.getTime() + 3600 * 1000 * 24));",
|
" pm.expect(new Date(jsonData.end)).to.eql(new Date(date.getTime() + 3600 * 1000 * 24 - 1000));",
|
||||||
" pm.expect(jsonData.data.length).to.eql(2);",
|
" pm.expect(jsonData.data.length).to.eql(2);",
|
||||||
"});"
|
"});"
|
||||||
],
|
],
|
||||||
|
@ -2,99 +2,41 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/v2/datetime"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: replace these functions by github.com/duke-git/lancet/v2/datetime
|
func BeginOfToday(tz *time.Location) time.Time {
|
||||||
// needs additional thoughts, though, as for "EndOfX" functions, we currently return the discrete next day,
|
return datetime.BeginOfDay(time.Now().In(tz))
|
||||||
// while the above lib returns the very last nanosecond of the current day, i.e.
|
|
||||||
// 2022-02-15 23:59:59.999 +0800 CST vs. 2022-02-16 00:00:00.000 +0800 CST
|
|
||||||
// -> need to revisit comparison logic, etc.
|
|
||||||
|
|
||||||
func StartOfDay(date time.Time) time.Time {
|
|
||||||
return FloorDate(date)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartOfToday(tz *time.Location) time.Time {
|
func BeginOfThisWeek(tz *time.Location) time.Time {
|
||||||
return StartOfDay(FloorDate(time.Now().In(tz)))
|
return datetime.BeginOfWeek(time.Now().In(tz))
|
||||||
}
|
}
|
||||||
|
|
||||||
func EndOfDay(date time.Time) time.Time {
|
func BeginOfThisMonth(tz *time.Location) time.Time {
|
||||||
floored := FloorDate(date)
|
return datetime.BeginOfMonth(time.Now().In(tz))
|
||||||
if floored == date {
|
|
||||||
date = date.Add(1 * time.Second)
|
|
||||||
}
|
|
||||||
return CeilDate(date)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func EndOfToday(tz *time.Location) time.Time {
|
func BeginOfThisYear(tz *time.Location) time.Time {
|
||||||
return EndOfDay(time.Now().In(tz))
|
return datetime.BeginOfYear(time.Now().In(tz))
|
||||||
}
|
|
||||||
|
|
||||||
func StartOfThisWeek(tz *time.Location) time.Time {
|
|
||||||
return StartOfWeek(time.Now().In(tz))
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartOfWeek(date time.Time) time.Time {
|
|
||||||
year, week := date.ISOWeek()
|
|
||||||
return firstDayOfISOWeek(year, week, date.Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartOfThisMonth(tz *time.Location) time.Time {
|
|
||||||
return StartOfMonth(time.Now().In(tz))
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartOfMonth(date time.Time) time.Time {
|
|
||||||
return time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, date.Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartOfThisYear(tz *time.Location) time.Time {
|
|
||||||
return StartOfYear(time.Now().In(tz))
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartOfYear(date time.Time) time.Time {
|
|
||||||
return time.Date(date.Year(), time.January, 1, 0, 0, 0, 0, date.Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
// FloorDate rounds date down to the start of the day and keeps the time zone
|
|
||||||
func FloorDate(date time.Time) time.Time {
|
|
||||||
return time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
// FloorDateHour rounds date down to the start of the current hour and keeps the time zone
|
|
||||||
func FloorDateHour(date time.Time) time.Time {
|
|
||||||
return time.Date(date.Year(), date.Month(), date.Day(), date.Hour(), 0, 0, 0, date.Location())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CeilDate rounds date up to the start of next day if date is not already a start (00:00:00)
|
// CeilDate rounds date up to the start of next day if date is not already a start (00:00:00)
|
||||||
func CeilDate(date time.Time) time.Time {
|
func CeilDate(date time.Time) time.Time {
|
||||||
floored := FloorDate(date)
|
floored := datetime.BeginOfDay(date)
|
||||||
if floored == date {
|
if floored == date {
|
||||||
return floored
|
return floored
|
||||||
}
|
}
|
||||||
return floored.AddDate(0, 0, 1)
|
return floored.AddDate(0, 0, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLocation resets the time zone information of a date without converting it, i.e. 19:00 UTC will result in 19:00 CET, for instance
|
|
||||||
func SetLocation(date time.Time, tz *time.Location) time.Time {
|
|
||||||
return time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, tz)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithOffset adds the time zone difference between Local and tz to a date, i.e. 19:00 UTC will result in 21:00 CET (or 22:00 CEST), for instance
|
|
||||||
func WithOffset(date time.Time, tz *time.Location) time.Time {
|
|
||||||
now := time.Now()
|
|
||||||
_, localOffset := now.Zone()
|
|
||||||
_, targetOffset := now.In(tz).Zone()
|
|
||||||
dateTz := date.Add(time.Duration((targetOffset - localOffset) * int(time.Second)))
|
|
||||||
return time.Date(dateTz.Year(), dateTz.Month(), dateTz.Day(), dateTz.Hour(), dateTz.Minute(), dateTz.Second(), dateTz.Nanosecond(), dateTz.Location()).In(tz)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitRangeByDays creates a slice of intervals between from and to, each of which is at max of 24 hours length and has its split at midnight
|
// SplitRangeByDays creates a slice of intervals between from and to, each of which is at max of 24 hours length and has its split at midnight
|
||||||
func SplitRangeByDays(from time.Time, to time.Time) [][]time.Time {
|
func SplitRangeByDays(from time.Time, to time.Time) [][]time.Time {
|
||||||
intervals := make([][]time.Time, 0)
|
intervals := make([][]time.Time, 0)
|
||||||
|
|
||||||
for t1 := from; t1.Before(to); {
|
for t1 := from; t1.Before(to); {
|
||||||
t2 := StartOfDay(t1).AddDate(0, 0, 1)
|
t2 := datetime.BeginOfDay(t1).AddDate(0, 0, 1)
|
||||||
if t2.After(to) {
|
if t2.After(to) {
|
||||||
t2 = to
|
t2 = to
|
||||||
}
|
}
|
||||||
@ -118,22 +60,3 @@ func LocalTZOffset() time.Duration {
|
|||||||
_, offset := time.Now().Zone()
|
_, offset := time.Now().Zone()
|
||||||
return time.Duration(offset * int(time.Second))
|
return time.Duration(offset * int(time.Second))
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/18632496
|
|
||||||
func firstDayOfISOWeek(year int, week int, timezone *time.Location) time.Time {
|
|
||||||
date := time.Date(year, 0, 0, 0, 0, 0, 0, timezone)
|
|
||||||
isoYear, isoWeek := date.ISOWeek()
|
|
||||||
for date.Weekday() != time.Monday { // iterate back to Monday
|
|
||||||
date = date.AddDate(0, 0, -1)
|
|
||||||
isoYear, isoWeek = date.ISOWeek()
|
|
||||||
}
|
|
||||||
for isoYear < year { // iterate forward to the first day of the first week
|
|
||||||
date = date.AddDate(0, 0, 1)
|
|
||||||
isoYear, isoWeek = date.ISOWeek()
|
|
||||||
}
|
|
||||||
for isoWeek < week { // iterate forward to the first day of the given week
|
|
||||||
date = date.AddDate(0, 0, 1)
|
|
||||||
isoYear, isoWeek = date.ISOWeek()
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/duke-git/lancet/v2/datetime"
|
||||||
"github.com/muety/wakapi/config"
|
"github.com/muety/wakapi/config"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
@ -21,100 +22,11 @@ func init() {
|
|||||||
tzPst, _ = time.LoadLocation("America/Los_Angeles")
|
tzPst, _ = time.LoadLocation("America/Los_Angeles")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDate_Ceil(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
in string
|
|
||||||
out string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"02 Jan 06 15:04 MST",
|
|
||||||
"03 Jan 06 00:00 MST",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"03 Jan 06 00:00 MST",
|
|
||||||
"03 Jan 06 00:00 MST",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
inDate, _ := time.Parse(time.RFC822, test.in)
|
|
||||||
outDate, _ := time.Parse(time.RFC822, test.out)
|
|
||||||
out := CeilDate(inDate)
|
|
||||||
assert.Equal(t, outDate, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDate_StartOfDay(t *testing.T) {
|
|
||||||
d1, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 20:25:00", tzLocal)
|
|
||||||
d2, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 20:25:00", tzUtc)
|
|
||||||
d3, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 20:25:00", tzPst)
|
|
||||||
d4, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 20:25:00", tzCet)
|
|
||||||
|
|
||||||
t1, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 00:00:00", tzLocal)
|
|
||||||
t2, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 00:00:00", tzUtc)
|
|
||||||
t3, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 00:00:00", tzPst)
|
|
||||||
t4, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 00:00:00", tzCet)
|
|
||||||
|
|
||||||
assert.Equal(t, t1, StartOfDay(d1))
|
|
||||||
assert.Equal(t, t2, StartOfDay(d2))
|
|
||||||
assert.Equal(t, t3, StartOfDay(d3))
|
|
||||||
assert.Equal(t, t4, StartOfDay(d4))
|
|
||||||
|
|
||||||
assert.Equal(t, tzLocal, StartOfDay(d1).Location())
|
|
||||||
assert.Equal(t, tzUtc, StartOfDay(d2).Location())
|
|
||||||
assert.Equal(t, tzPst, StartOfDay(d3).Location())
|
|
||||||
assert.Equal(t, tzCet, StartOfDay(d4).Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDate_EndOfDay(t *testing.T) {
|
|
||||||
d1, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 20:25:00", tzLocal)
|
|
||||||
d2, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 20:25:00", tzUtc)
|
|
||||||
d3, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 20:25:00", tzPst)
|
|
||||||
d4, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 20:25:00", tzCet)
|
|
||||||
|
|
||||||
t1, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-26 00:00:00", tzLocal)
|
|
||||||
t2, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-26 00:00:00", tzUtc)
|
|
||||||
t3, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-26 00:00:00", tzPst)
|
|
||||||
t4, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-26 00:00:00", tzCet)
|
|
||||||
|
|
||||||
assert.Equal(t, t1, EndOfDay(d1))
|
|
||||||
assert.Equal(t, t2, EndOfDay(d2))
|
|
||||||
assert.Equal(t, t3, EndOfDay(d3))
|
|
||||||
assert.Equal(t, t4, EndOfDay(d4))
|
|
||||||
|
|
||||||
assert.Equal(t, tzLocal, EndOfDay(d1).Location())
|
|
||||||
assert.Equal(t, tzUtc, EndOfDay(d2).Location())
|
|
||||||
assert.Equal(t, tzPst, EndOfDay(d3).Location())
|
|
||||||
assert.Equal(t, tzCet, EndOfDay(d4).Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDate_StartOfWeek(t *testing.T) {
|
|
||||||
d1, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 20:25:00", tzLocal)
|
|
||||||
d2, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 20:25:00", tzUtc)
|
|
||||||
d3, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 20:25:00", tzPst)
|
|
||||||
d4, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-25 20:25:00", tzCet)
|
|
||||||
|
|
||||||
t1, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-19 00:00:00", tzLocal)
|
|
||||||
t2, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-19 00:00:00", tzUtc)
|
|
||||||
t3, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-19 00:00:00", tzPst)
|
|
||||||
t4, _ := time.ParseInLocation(config.SimpleDateTimeFormat, "2021-04-19 00:00:00", tzCet)
|
|
||||||
|
|
||||||
assert.Equal(t, t1, StartOfWeek(d1))
|
|
||||||
assert.Equal(t, t2, StartOfWeek(d2))
|
|
||||||
assert.Equal(t, t3, StartOfWeek(d3))
|
|
||||||
assert.Equal(t, t4, StartOfWeek(d4))
|
|
||||||
|
|
||||||
assert.Equal(t, tzLocal, StartOfWeek(d1).Location())
|
|
||||||
assert.Equal(t, tzUtc, StartOfWeek(d2).Location())
|
|
||||||
assert.Equal(t, tzPst, StartOfWeek(d3).Location())
|
|
||||||
assert.Equal(t, tzCet, StartOfWeek(d4).Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDate_SplitRangeByDays(t *testing.T) {
|
func TestDate_SplitRangeByDays(t *testing.T) {
|
||||||
df1, _ := time.Parse(config.SimpleDateTimeFormat, "2021-04-25 20:25:00")
|
df1, _ := time.Parse(config.SimpleDateTimeFormat, "2021-04-25 20:25:00")
|
||||||
dt1, _ := time.Parse(config.SimpleDateTimeFormat, "2021-04-28 06:45:00")
|
dt1, _ := time.Parse(config.SimpleDateTimeFormat, "2021-04-28 06:45:00")
|
||||||
df2 := df1
|
df2 := df1
|
||||||
dt2 := CeilDate(df1)
|
dt2 := datetime.EndOfDay(df1)
|
||||||
df3 := df1
|
df3 := df1
|
||||||
dt3 := df1.Add(10 * time.Second)
|
dt3 := df1.Add(10 * time.Second)
|
||||||
df4 := df1
|
df4 := df1
|
||||||
|
@ -35,27 +35,27 @@ func ResolveIntervalTZ(interval *models.IntervalKey, tz *time.Location) (err err
|
|||||||
|
|
||||||
switch interval {
|
switch interval {
|
||||||
case models.IntervalToday:
|
case models.IntervalToday:
|
||||||
from = StartOfToday(tz)
|
from = BeginOfToday(tz)
|
||||||
case models.IntervalYesterday:
|
case models.IntervalYesterday:
|
||||||
from = StartOfToday(tz).Add(-24 * time.Hour)
|
from = BeginOfToday(tz).Add(-24 * time.Hour)
|
||||||
to = StartOfToday(tz)
|
to = BeginOfToday(tz)
|
||||||
case models.IntervalThisWeek:
|
case models.IntervalThisWeek:
|
||||||
from = StartOfThisWeek(tz)
|
from = BeginOfThisWeek(tz)
|
||||||
case models.IntervalLastWeek:
|
case models.IntervalLastWeek:
|
||||||
from = StartOfThisWeek(tz).AddDate(0, 0, -7)
|
from = BeginOfThisWeek(tz).AddDate(0, 0, -7)
|
||||||
to = StartOfThisWeek(tz)
|
to = BeginOfThisWeek(tz)
|
||||||
case models.IntervalThisMonth:
|
case models.IntervalThisMonth:
|
||||||
from = StartOfThisMonth(tz)
|
from = BeginOfThisMonth(tz)
|
||||||
case models.IntervalLastMonth:
|
case models.IntervalLastMonth:
|
||||||
from = StartOfThisMonth(tz).AddDate(0, -1, 0)
|
from = BeginOfThisMonth(tz).AddDate(0, -1, 0)
|
||||||
to = StartOfThisMonth(tz)
|
to = BeginOfThisMonth(tz)
|
||||||
case models.IntervalThisYear:
|
case models.IntervalThisYear:
|
||||||
from = StartOfThisYear(tz)
|
from = BeginOfThisYear(tz)
|
||||||
case models.IntervalPast7Days:
|
case models.IntervalPast7Days:
|
||||||
from = now.AddDate(0, 0, -7)
|
from = now.AddDate(0, 0, -7)
|
||||||
case models.IntervalPast7DaysYesterday:
|
case models.IntervalPast7DaysYesterday:
|
||||||
from = StartOfToday(tz).AddDate(0, 0, -1).AddDate(0, 0, -7)
|
from = BeginOfToday(tz).AddDate(0, 0, -1).AddDate(0, 0, -7)
|
||||||
to = StartOfToday(tz).AddDate(0, 0, -1)
|
to = BeginOfToday(tz).AddDate(0, 0, -1)
|
||||||
case models.IntervalPast14Days:
|
case models.IntervalPast14Days:
|
||||||
from = now.AddDate(0, 0, -14)
|
from = now.AddDate(0, 0, -14)
|
||||||
case models.IntervalPast30Days:
|
case models.IntervalPast30Days:
|
||||||
|
Loading…
Reference in New Issue
Block a user