golib/time/calendar/weekly.go

148 lines
3.3 KiB
Go

package calendar
import (
"time"
)
func GetSuffixEnglish(n int) string {
if n%100 >= 11 && n%100 <= 13 {
return "th"
}
switch n % 10 {
case 1:
return "st"
case 2:
return "nd"
case 3:
return "rd"
default:
return "th"
}
}
// Returns which nth weekday of the month the target date is (e.g. 2nd Tuesday)
func NthWeekday(t time.Time) int {
_, _, date := t.Date()
nth := date / 7
nthMod := date % 7
if nthMod != 0 {
nth += 1
}
return nth
}
func NthWeekdayFromEnd(t time.Time) int {
_, _, day := t.Date()
lastDay := time.Date(t.Year(), t.Month()+1, 0, 0, 0, 0, 0, t.Location()).Day()
return (lastDay-day)/7 + 1
}
// GetNthWeekday can find the 1st, 2nd, 3rd, 4th (and sometimes 5th) Monday, etc of the given month
func GetNthWeekday(year int, month time.Month, weekday time.Weekday, n int) (time.Time, bool) {
var mOffset time.Month
nOffset := 1
if n < 0 {
mOffset = 1
nOffset = 0
}
// First day of month
first := time.Date(year, mOffset+month, 1, 0, 0, 0, 0, time.UTC)
wd := first.Weekday()
// Days to first target weekday
daysToAdd := int(weekday - wd)
if daysToAdd < 0 {
daysToAdd += 7
}
// First occurrence
firstOcc := first.AddDate(0, 0, daysToAdd)
// nth occurrence
target := firstOcc.AddDate(0, 0, (n-nOffset)*7)
// Check if still in same month
if target.Month() != month {
return time.Time{}, false
}
return target, true
}
type Year = int
type YearlyDate struct {
Month time.Month
Day int
}
type YearlyCalendar map[YearlyDate]struct{}
type MultiYearCalendar map[Year]YearlyCalendar
func NewMultiYearCalendar(startYear, endYear int, fixed []FixedDate, floating []FloatingDate) MultiYearCalendar {
var mcal = make(MultiYearCalendar)
for year := startYear; year <= endYear; year++ {
mcal[year] = make(YearlyCalendar)
for _, fixedDate := range fixed {
date := YearlyDate{fixedDate.Month, fixedDate.Day}
mcal[year][date] = struct{}{}
}
for _, floatingDate := range floating {
date := floatingDate.ToDate(year)
mcal[year][date] = struct{}{}
}
}
return mcal
}
type FixedDate struct {
Month time.Month
Day int
Name string
}
type FloatingDate struct {
Nth int
Weekday time.Weekday
Month time.Month
Name string
}
func (d FloatingDate) ToDate(year int) YearlyDate {
t, _ := GetNthWeekday(year, d.Month, d.Weekday, d.Nth)
_, month, day := t.Date()
return YearlyDate{month, day}
}
// Reserved. DO NOT USE. For the time being, Easter, Moon cycles, moveable feasts and such must be entered manually
type LunisolarHoliday struct{}
func (h MultiYearCalendar) IsBusinessDay(t time.Time) bool {
if wd := t.Weekday(); wd == time.Saturday || wd == time.Sunday {
return false
}
_, ok := h[t.Year()][YearlyDate{t.Month(), t.Day()}]
return !ok
}
// GetBankDaysBefore calculates business days is useful for calculating transactions that must occur such
// that they will complete by the target date. For example, if you want money to be in
// your account by the 15th each month, and the transfer takes 3 business days, and this
// month's 15th is a Monday that happens to be a holiday, this would return the previous
// Tuesday.
func (h MultiYearCalendar) GetNthBankDayBefore(t time.Time, n int) time.Time {
if !h.IsBusinessDay(t) {
n++
}
for n > 0 {
t = t.AddDate(0, 0, -1)
if h.IsBusinessDay(t) {
n--
}
}
return t
}