mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
refactor: time zone sensitivity (resolve #184)
This commit is contained in:
@ -7,12 +7,22 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func ParseDate(date string) (time.Time, error) {
|
||||
return time.Parse(config.SimpleDateFormat, date)
|
||||
}
|
||||
|
||||
func ParseDateTime(date string) (time.Time, error) {
|
||||
return time.Parse(config.SimpleDateTimeFormat, date)
|
||||
// ParseDateTimeTZ attempts to parse the given date string from multiple formats.
|
||||
// First, a time-zoned date-time string (e.g. 2006-01-02T15:04:05+02:00) is tried
|
||||
// Second, a non-time-zoned date-time string (e.g. 2006-01-02 15:04:05) is tried at the given zone
|
||||
// Third, a non-time-zoned date string (e.g. 2006-01-02) is tried at the given zone
|
||||
// Example:
|
||||
// - Server runs in CEST (UTC+2), requesting user lives in PDT (UTC-7).
|
||||
// - 2021-04-25T10:30:00Z, 2021-04-25T3:30:00-0100 and 2021-04-25T12:30:00+0200 are equivalent, they represent the same point in time
|
||||
// - When user requests non-time-zoned range (e.g. 2021-04-25T00:00:00), but has their time zone properly configured, this will resolve to 2021-04-25T09:00:00
|
||||
func ParseDateTimeTZ(date string, tz *time.Location) (time.Time, error) {
|
||||
if t, err := time.Parse(time.RFC3339, date); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
if t, err := time.ParseInLocation(config.SimpleDateTimeFormat, date, tz); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
return time.ParseInLocation(config.SimpleDateFormat, date, tz)
|
||||
}
|
||||
|
||||
func FormatDate(date time.Time) string {
|
||||
|
@ -5,33 +5,42 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func StartOfToday() time.Time {
|
||||
return StartOfDay(time.Now())
|
||||
}
|
||||
|
||||
func StartOfDay(date time.Time) time.Time {
|
||||
return FloorDate(date)
|
||||
}
|
||||
|
||||
func StartOfWeek() time.Time {
|
||||
ref := time.Now()
|
||||
year, week := ref.ISOWeek()
|
||||
return firstDayOfISOWeek(year, week, ref.Location())
|
||||
func StartOfToday(tz *time.Location) time.Time {
|
||||
return StartOfDay(FloorDate(time.Now().In(tz)))
|
||||
}
|
||||
|
||||
func StartOfMonth() time.Time {
|
||||
ref := time.Now()
|
||||
return time.Date(ref.Year(), ref.Month(), 1, 0, 0, 0, 0, ref.Location())
|
||||
func StartOfThisWeek(tz *time.Location) time.Time {
|
||||
return StartOfWeek(time.Now().In(tz))
|
||||
}
|
||||
|
||||
func StartOfYear() time.Time {
|
||||
ref := time.Now()
|
||||
return time.Date(ref.Year(), time.January, 1, 0, 0, 0, 0, ref.Location())
|
||||
func StartOfWeek(date time.Time) time.Time {
|
||||
year, week := date.ISOWeek()
|
||||
return firstDayOfISOWeek(year, week, date.Location())
|
||||
}
|
||||
|
||||
// FloorDate rounds date down to the start of the day
|
||||
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 date.Truncate(24 * time.Hour)
|
||||
return time.Date(date.Year(), date.Month(), date.Day(), 0, 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)
|
||||
@ -43,6 +52,20 @@ func CeilDate(date time.Time) time.Time {
|
||||
return floored.Add(24 * time.Hour)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func SplitRangeByDays(from time.Time, to time.Time) [][]time.Time {
|
||||
intervals := make([][]time.Time, 0)
|
||||
|
||||
|
@ -16,51 +16,51 @@ func ParseInterval(interval string) (*models.IntervalKey, error) {
|
||||
return nil, errors.New("not a valid interval")
|
||||
}
|
||||
|
||||
func MustResolveIntervalRaw(interval string) (from, to time.Time) {
|
||||
_, from, to = ResolveIntervalRaw(interval)
|
||||
func MustResolveIntervalRawTZ(interval string, tz *time.Location) (from, to time.Time) {
|
||||
_, from, to = ResolveIntervalRawTZ(interval, tz)
|
||||
return from, to
|
||||
}
|
||||
|
||||
func ResolveIntervalRaw(interval string) (err error, from, to time.Time) {
|
||||
func ResolveIntervalRawTZ(interval string, tz *time.Location) (err error, from, to time.Time) {
|
||||
parsed, err := ParseInterval(interval)
|
||||
if err != nil {
|
||||
return err, time.Time{}, time.Time{}
|
||||
}
|
||||
return ResolveInterval(parsed)
|
||||
return ResolveIntervalTZ(parsed, tz)
|
||||
}
|
||||
|
||||
func ResolveInterval(interval *models.IntervalKey) (err error, from, to time.Time) {
|
||||
to = time.Now()
|
||||
func ResolveIntervalTZ(interval *models.IntervalKey, tz *time.Location) (err error, from, to time.Time) {
|
||||
to = time.Now().In(tz)
|
||||
|
||||
switch interval {
|
||||
case models.IntervalToday:
|
||||
from = StartOfToday()
|
||||
from = StartOfToday(tz)
|
||||
case models.IntervalYesterday:
|
||||
from = StartOfToday().Add(-24 * time.Hour)
|
||||
to = StartOfToday()
|
||||
from = StartOfToday(tz).Add(-24 * time.Hour)
|
||||
to = StartOfToday(tz)
|
||||
case models.IntervalThisWeek:
|
||||
from = StartOfWeek()
|
||||
from = StartOfThisWeek(tz)
|
||||
case models.IntervalLastWeek:
|
||||
from = StartOfWeek().AddDate(0, 0, -7)
|
||||
to = StartOfWeek()
|
||||
from = StartOfThisWeek(tz).AddDate(0, 0, -7)
|
||||
to = StartOfThisWeek(tz)
|
||||
case models.IntervalThisMonth:
|
||||
from = StartOfMonth()
|
||||
from = StartOfThisMonth(tz)
|
||||
case models.IntervalLastMonth:
|
||||
from = StartOfMonth().AddDate(0, -1, 0)
|
||||
to = StartOfMonth()
|
||||
from = StartOfThisMonth(tz).AddDate(0, -1, 0)
|
||||
to = StartOfThisMonth(tz)
|
||||
case models.IntervalThisYear:
|
||||
from = StartOfYear()
|
||||
from = StartOfThisYear(tz)
|
||||
case models.IntervalPast7Days:
|
||||
from = StartOfToday().AddDate(0, 0, -7)
|
||||
from = StartOfToday(tz).AddDate(0, 0, -7)
|
||||
case models.IntervalPast7DaysYesterday:
|
||||
from = StartOfToday().AddDate(0, 0, -1).AddDate(0, 0, -7)
|
||||
to = StartOfToday().AddDate(0, 0, -1)
|
||||
from = StartOfToday(tz).AddDate(0, 0, -1).AddDate(0, 0, -7)
|
||||
to = StartOfToday(tz).AddDate(0, 0, -1)
|
||||
case models.IntervalPast14Days:
|
||||
from = StartOfToday().AddDate(0, 0, -14)
|
||||
from = StartOfToday(tz).AddDate(0, 0, -14)
|
||||
case models.IntervalPast30Days:
|
||||
from = StartOfToday().AddDate(0, 0, -30)
|
||||
from = StartOfToday(tz).AddDate(0, 0, -30)
|
||||
case models.IntervalPast12Months:
|
||||
from = StartOfToday().AddDate(0, -12, 0)
|
||||
from = StartOfToday(tz).AddDate(0, -12, 0)
|
||||
case models.IntervalAny:
|
||||
from = time.Time{}
|
||||
default:
|
||||
@ -78,24 +78,18 @@ func ParseSummaryParams(r *http.Request) (*models.SummaryParams, error) {
|
||||
var from, to time.Time
|
||||
|
||||
if interval := params.Get("interval"); interval != "" {
|
||||
err, from, to = ResolveIntervalRaw(interval)
|
||||
err, from, to = ResolveIntervalRawTZ(interval, user.TZ())
|
||||
} else if start := params.Get("start"); start != "" {
|
||||
err, from, to = ResolveIntervalRaw(start)
|
||||
err, from, to = ResolveIntervalRawTZ(start, user.TZ())
|
||||
} else {
|
||||
from, err = ParseDateTime(params.Get("from"))
|
||||
from, err = ParseDateTimeTZ(params.Get("from"), user.TZ())
|
||||
if err != nil {
|
||||
from, err = ParseDate(params.Get("from"))
|
||||
if err != nil {
|
||||
return nil, errors.New("missing 'from' parameter")
|
||||
}
|
||||
return nil, errors.New("missing or invalid 'from' parameter")
|
||||
}
|
||||
|
||||
to, err = ParseDateTime(params.Get("to"))
|
||||
to, err = ParseDateTimeTZ(params.Get("to"), user.TZ())
|
||||
if err != nil {
|
||||
to, err = ParseDate(params.Get("to"))
|
||||
if err != nil {
|
||||
return nil, errors.New("missing 'to' parameter")
|
||||
}
|
||||
return nil, errors.New("missing or invalid 'to' parameter")
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user