1058 lines
21 KiB
Go

package main
import (
"fmt"
"iter"
"math/bits"
"slices"
"strconv"
"strings"
"time"
"unicode"
)
type Frequency string
const (
Yearly Frequency = "yearly"
Monthly Frequency = "monthly"
Weekly Frequency = "weekly"
Daily Frequency = "daily"
Once Frequency = "once"
)
// Day of the week with week of the month encoded.
// 4 bits 4-7: week of the month 0b00000000_01111111
// 7 bits 8-16: day of the week 0b00001111_00000000
type Day uint16
const (
Sunday Day = 1 << iota
Monday Day = 1 << iota
Tuesday Day = 1 << iota
Wednesday Day = 1 << iota
Thursday Day = 1 << iota
Friday Day = 1 << iota
Saturday Day = 1 << iota
)
func NewDay(d Day, ofMonth int) Day {
return Day(ofMonth&15<<8 | int(d&127))
}
func (d *Day) Set(day Day, weekOfMonth int) {
*d = Day(weekOfMonth&15<<8 | int(day&127))
}
func (d Day) Day() Day {
return Day(int(d) & 127)
}
func (d Day) WeekOfMonth() int {
weekOfMonth := int(d>>8) & 15
if weekOfMonth&0b1000 > 0 {
return -((^weekOfMonth & 15) + 1)
}
return weekOfMonth
}
func (d Day) DayOfWeek() int {
return bits.TrailingZeros16(uint16(d & 127))
}
func (d Day) String() string {
var day string
switch d.Day() {
case Monday:
day = "Mon"
case Tuesday:
day = "Tue"
case Wednesday:
day = "Wed"
case Thursday:
day = "Thu"
case Friday:
day = "Fri"
case Saturday:
day = "Sat"
case Sunday:
day = "Sun"
}
if weekOfMonth := d.WeekOfMonth(); weekOfMonth != 0 {
return fmt.Sprintf("%-d%s", weekOfMonth, day)
}
return day
}
func (d Day) Less(a Day) bool {
return d < a
}
func (d *Day) Parse(text string) (int, error) {
if len(text) == 0 {
return 0, fmt.Errorf("invalid day")
}
var weekOfMonth int
var day string
i := 0
for _, c := range text[i:] {
if unicode.IsDigit(c) || c == '-' {
i++
continue
}
break
}
weekOfMonth, _ = strconv.Atoi(text[:i])
di := 0
for _, c := range text[i:] {
if unicode.IsLetter(c) {
di++
continue
}
break
}
day = text[i : i+di]
i += di
switch strings.ToLower(day) {
case "mon", "monday":
d.Set(Monday, weekOfMonth)
case "tue", "tuesday":
d.Set(Tuesday, weekOfMonth)
case "wed", "wednesday":
d.Set(Wednesday, weekOfMonth)
case "thu", "thursday", "thur", "thr":
d.Set(Thursday, weekOfMonth)
case "fri", "friday":
d.Set(Friday, weekOfMonth)
case "sat", "saturday":
d.Set(Saturday, weekOfMonth)
case "sun", "sunday":
d.Set(Sunday, weekOfMonth)
default:
return 0, fmt.Errorf("invalid day")
}
return i, nil
}
// Month of the year with day of the day of month encoded.
// 5 bits : day of the month 0b00000000_00000000_00011111
// 22 bits : month of the year 0b00001111_11111111_00000000
type Month int32
const (
January Month = 1 << (iota + 8) // 0b0000_00000001_00000000
February Month = 1 << (iota + 8) // 0b0000_00000010_00000000
March Month = 1 << (iota + 8) // 0b0000_00000100_00000000
April Month = 1 << (iota + 8) // 0b0000_00001000_00000000
May Month = 1 << (iota + 8) // 0b0000_00010000_00000000
June Month = 1 << (iota + 8) // 0b0000_00100000_00000000
July Month = 1 << (iota + 8) // 0b0000_01000000_00000000
August Month = 1 << (iota + 8) // 0b0000_10000000_00000000
September Month = 1 << (iota + 8) // 0b0001_00000000_00000000
October Month = 1 << (iota + 8) // 0b0010_00000000_00000000
November Month = 1 << (iota + 8) // 0b0100_00000000_00000000
December Month = 1 << (iota + 8) // 0b1000_00000000_00000000
// day of month -31-31 0b1-0b0011_1111
)
func NewMonth(m Month, dayOfMonth int) Month {
return Month(int(m)&^63 | dayOfMonth&63)
}
func (m Month) Month() Month {
return Month(int(m) & ^int(255))
}
func (m Month) DayOfMonth() int {
dayOfMonth := int(m) & 0b11_1111
if dayOfMonth&0b0010_0000 > 0 {
return -((^dayOfMonth & 0b1_1111) + 1)
}
return dayOfMonth
}
func (m Month) MonthOfYear() time.Month {
return time.Month(bits.TrailingZeros32(uint32(m>>8)) + 1)
}
func (m Month) String() string {
var month string
switch m.Month() {
case January:
month = "Jan"
case February:
month = "Feb"
case March:
month = "Mar"
case April:
month = "Apr"
case May:
month = "May"
case June:
month = "Jun"
case July:
month = "Jul"
case August:
month = "Aug"
case September:
month = "Sep"
case October:
month = "Oct"
case November:
month = "Nov"
case December:
month = "Dec"
}
if dayOfMonth := m.DayOfMonth(); dayOfMonth != 0 {
return fmt.Sprintf("%s%-d", month, dayOfMonth)
}
return month
}
func (m Month) Less(a Month) bool {
return m < a
}
func (w *Month) Parse(text string) (int, error) {
if len(text) == 0 {
return 0, fmt.Errorf("invalid month")
}
mon, day := "", 0
i := 0
for _, c := range text {
if !unicode.IsLetter(c) {
break
}
i++
}
mon = text[:i]
if i < len(text) {
negative := false
if text[i] == '-' {
i++
negative = true
}
di := 0
for _, c := range text[i:] {
if !unicode.IsDigit(c) {
break
}
di++
}
day, _ = strconv.Atoi(text[i : i+di])
i += di
if negative {
day = -day
}
}
switch strings.ToLower(mon) {
case "jan", "january":
*w = NewMonth(January, day)
case "feb", "february":
*w = NewMonth(February, day)
case "mar", "march":
*w = NewMonth(March, day)
case "apr", "april":
*w = NewMonth(April, day)
case "may":
*w = NewMonth(May, day)
case "jun", "june":
*w = NewMonth(June, day)
case "jul", "july":
*w = NewMonth(July, day)
case "aug", "august":
*w = NewMonth(August, day)
case "sep", "sept", "september":
*w = NewMonth(September, day)
case "oct", "october":
*w = NewMonth(October, day)
case "nov", "november":
*w = NewMonth(November, day)
case "dec", "december":
*w = NewMonth(December, day)
default:
return i, fmt.Errorf("unknown month %q", mon)
}
return i, nil
}
type Slate struct {
Date Date
Frequency Frequency
Until time.Time
Interval int
Count int
}
func (s *Slate) Parse(text string) (int, error) {
i, err := s.Date.Parse(text)
if err != nil {
return 0, err
}
return i, nil
}
// String
func (s Slate) String() string {
return s.Date.String()
}
func (s Slate) IsZero() bool {
return s.Date.IsZero() &&
s.Frequency == "" &&
s.Until.IsZero() &&
s.Interval == 0 &&
s.Count == 0
}
func (s Slate) Project(start time.Time) iter.Seq[time.Time] {
if start.IsZero() {
start = time.Now()
}
if !s.Until.IsZero() && s.Until.Before(start) {
return slices.Values[[]time.Time](nil)
}
var increment time.Duration
switch s.Frequency {
case Once:
increment = 0
case Daily:
increment = time.Hour * 24
case Weekly:
increment = time.Hour * 24 * 7
case Monthly:
increment = time.Hour * 24 * 30
case Yearly:
increment = time.Hour * 24 * 365
}
if s.Interval > 0 {
increment *= time.Duration(s.Interval)
}
return func(yield func(time.Time) bool) {
for i := 0; i < s.Count; {
for d := range s.Date.Project(start) {
if !s.Until.IsZero() && s.Until.Before(d) {
return
}
if !yield(d) {
return
}
i++
}
start = start.Add(increment)
}
}
}
type Week int8
func NewWeek(ofYear int) Week {
return Week(ofYear & 63)
}
func (w Week) WeekOfYear() int {
return int(w)
}
func (w Week) String() string {
return fmt.Sprintf("w%02d", int8(w))
}
func (w Week) Less(a Week) bool {
return w < a
}
func (w *Week) Parse(text string) (int, error) {
if len(text) == 0 {
return 0, fmt.Errorf("invalid week")
}
if !strings.HasPrefix(strings.ToLower(text), "w") {
return 0, fmt.Errorf("invalid week")
}
i := 1
for _, c := range text[1:] {
if unicode.IsDigit(c) || c == '-' {
i++
continue
}
break
}
wk, err := strconv.Atoi(text[1:i])
if err != nil {
return 0, err
}
*w = Week(wk)
return i, nil
}
type Date struct {
Year int
Month int
Day int
MonthOfYear []Month
WeekOfYear []Week
DayOfWeek []Day
}
func (d Date) String() string {
v, _ := d.MarshalText()
return string(v)
}
func (d Date) MarshalText() ([]byte, error) {
hasYear := d.Year != 0
hasMonth := d.Month != 0
hasDay := d.Day != 0
onlyYear := hasYear && !hasMonth && !hasDay
yearAndMonth := hasYear && hasMonth && !hasDay
yearMonthDay := hasYear && hasMonth && hasDay
hasMonthOfYear := len(d.MonthOfYear) > 0
hasWeekOfYear := len(d.WeekOfYear) > 0
hasDayOfWeek := len(d.DayOfWeek) > 0
// onlyYear
onlyWeekOfYear := hasWeekOfYear && !hasDayOfWeek && !hasMonthOfYear
onlyMonthOfYear := hasMonthOfYear && !hasWeekOfYear && !hasDayOfWeek
weekOfYearAndDayOfWeek := hasWeekOfYear && !hasMonthOfYear && hasDayOfWeek
monthOfYearAndDayOfWeek := hasMonthOfYear && !hasWeekOfYear && hasDayOfWeek
// yearAndMonth
onlyDayOfWeek := hasDayOfWeek && !hasWeekOfYear && !hasMonthOfYear
switch {
case onlyYear && !hasMonthOfYear && !hasWeekOfYear && !hasDayOfWeek:
return []byte(fmt.Sprintf("%04d", d.Year)), nil
case onlyYear && onlyWeekOfYear:
s := apply(d.WeekOfYear, func(w Week) string {
return w.String()
})
return []byte(fmt.Sprintf("%04d %s", d.Year, strings.Join(s, ", "))), nil
case onlyYear && weekOfYearAndDayOfWeek:
z := zip(d.WeekOfYear, d.DayOfWeek)
s := apply(z, func(w pair[Week, Day]) string {
return fmt.Sprintf("%s %s", w.A.String(), w.B.Day().String())
})
return []byte(fmt.Sprintf("%04d %s", d.Year, strings.Join(s, ", "))), nil
case onlyYear && monthOfYearAndDayOfWeek:
z := zip(d.MonthOfYear, d.DayOfWeek)
s := apply(z, func(w pair[Month, Day]) string {
return fmt.Sprintf("%s %s", w.A.Month().String(), w.B.String())
})
return []byte(fmt.Sprintf("%04d %s", d.Year, strings.Join(s, ", "))), nil
case onlyYear && onlyMonthOfYear:
s := apply(d.MonthOfYear, func(w Month) string {
return w.String()
})
return []byte(fmt.Sprintf("%04d %s", d.Year, strings.Join(s, ", "))), nil
case yearAndMonth && !hasDayOfWeek:
return []byte(fmt.Sprintf("%04d-%02d", d.Year, d.Month)), nil
case yearAndMonth && onlyDayOfWeek:
s := apply(d.DayOfWeek, func(w Day) string {
return w.String()
})
return []byte(fmt.Sprintf("%04d-%02d %s", d.Year, d.Month, strings.Join(s, ", "))), nil
case yearMonthDay:
return []byte(fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day)), nil
}
return nil, nil
}
func (d Date) IsZero() bool {
return d.Year == 0 &&
d.Month == 0 &&
d.Day == 0 &&
len(d.MonthOfYear) == 0 &&
len(d.WeekOfYear) == 0 &&
len(d.DayOfWeek) == 0
}
// ## Date formats ##
// YYYY - year
// 2024 - year 2024
// 2026 - year 2026
// YYYY moy Ndow[, ...] - year moy dow
// 2024 Jan 1Mon, Feb -2Tue - first monday of january 2024 and second from last tuesday of february 2024
// YYYY moyNN[, ...] - year moyNN
// 2024 Jan1, Feb-2 - first day of january 2024 and second last day of february 2024
// YYYY 'w'NN[, ...] - year woy
// 2024 w01, w05 - first week of 2024 and fifth week of 2024
// YYYY 'w'NN dow[, ...] - year woy dow
// 2024 w01 Mon - first monday of 2024
// 2024 w02 Mon, w05 Tue - first monday on second week of 2024 and first tuesday on fifth week of 2024
// YYYY-MM - month
// 2024-01 - month of january 2024
// YYYY-MM Ndow[, ...] month day of week
// 2024-01 1Mon, 2Tue
// YYYY-MM-DD day
// 2024-01-01 - first day of january 2024
func (d *Date) Parse(text string) (int, error) {
i := 0
ds, text, ok := strings.Cut(text, " ")
i += len(ds)
if ok {
i++
}
if t, err := time.Parse("2006-01-02", ds); err == nil {
d.Year = t.Year()
d.Month = int(t.Month())
d.Day = t.Day()
return i, nil
}
if t, err := time.Parse("2006-01", ds); err == nil {
d.Year = t.Year()
d.Month = int(t.Month())
var di int
di, d.DayOfWeek, err = repeatDayOfWeek(text)
if err == nil {
i += di
return i, nil
}
return i, nil
}
if t, err := time.Parse("2006", ds); err == nil {
d.Year = t.Year()
var di int
di, d.WeekOfYear, d.DayOfWeek, err = repeatWeekOfYearAndDayOfWeek(text)
if err == nil {
i += di
return i, nil
}
di, d.MonthOfYear, d.DayOfWeek, err = repeatMonthOfYearAndDayOfWeek(text)
if err == nil {
i += di
return i, nil
}
if di, d.WeekOfYear, err = repeatWeekOfYear(text); err == nil {
i += di
return i, nil
}
if di, d.MonthOfYear, err = repeatMonth(text); err == nil {
i += di
return i, nil
}
if di, d.DayOfWeek, err = repeatDayOfWeek(text); err == nil {
i += di
return i, nil
}
return i, nil
}
return 0, fmt.Errorf("invalid date")
}
func (d Date) Project(start time.Time) iter.Seq[time.Time] {
hasYear := d.Year != 0
hasMonth := d.Month != 0
hasDay := d.Day != 0
onlyYear := hasYear && !hasMonth && !hasDay
yearAndMonth := hasYear && hasMonth && !hasDay
yearMonthDay := hasYear && hasMonth && hasDay
hasMonthOfYear := len(d.MonthOfYear) > 0
hasWeekOfYear := len(d.WeekOfYear) > 0
hasDayOfWeek := len(d.DayOfWeek) > 0
// onlyYear
onlyWeekOfYear := hasWeekOfYear && !hasDayOfWeek && !hasMonthOfYear
onlyMonthOfYear := hasMonthOfYear && !hasWeekOfYear && !hasDayOfWeek
weekOfYearAndDayOfWeek := hasWeekOfYear && !hasMonthOfYear && hasDayOfWeek
monthOfYearAndDayOfWeek := hasMonthOfYear && !hasWeekOfYear && hasDayOfWeek
// yearAndMonth
onlyDayOfWeek := hasDayOfWeek && !hasWeekOfYear && !hasMonthOfYear
switch {
case onlyYear && !hasMonthOfYear && !hasWeekOfYear && !hasDayOfWeek:
year := max(d.Year, start.Year())
month := time.Month(max(1, d.Month))
day := max(1, d.Day)
dt := time.Date(year, month, day, 0, 0, 0, 0, start.Location())
dt = endOfYear(dt)
return func(yield func(time.Time) bool) {
yield(dt)
return
}
case onlyYear && onlyWeekOfYear:
year := max(start.Year(), d.Year)
return func(yield func(time.Time) bool) {
for _, w := range d.WeekOfYear {
if !yield(dayOfWeekNumber(year, w.WeekOfYear(), 6)) {
return
}
}
}
case onlyYear && weekOfYearAndDayOfWeek:
year := max(start.Year(), d.Year)
return func(yield func(time.Time) bool) {
for i, w := range d.WeekOfYear {
dt := dayOfWeekNumber(year, w.WeekOfYear(), int(d.DayOfWeek[i].DayOfWeek()))
if start.After(dt) {
continue
}
if !yield(dt) {
return
}
}
}
case onlyYear && monthOfYearAndDayOfWeek:
year := max(start.Year(), d.Year)
return func(yield func(time.Time) bool) {
for i, m := range d.MonthOfYear {
month := m.MonthOfYear()
dt := time.Date(year, month, 1, 0, 0, 0, 0, start.Location())
dt = monthWithNWeek(d.DayOfWeek[i], dt)
if start.After(dt) {
continue
}
if !yield(dt) {
return
}
}
}
case onlyYear && onlyMonthOfYear:
year := max(start.Year(), d.Year)
return func(yield func(time.Time) bool) {
for _, m := range d.MonthOfYear {
month := time.Month(m.MonthOfYear())
var dt time.Time
if day := m.DayOfMonth(); day == 0 {
dt = time.Date(year, month, 1, 0, 0, 0, 0, start.Location())
dt = endOfMonth(dt)
} else {
dt = time.Date(year, month, day, 0, 0, 0, 0, start.Location())
}
if start.After(dt) {
continue
}
if !yield(dt) {
return
}
}
}
case yearAndMonth && !hasDayOfWeek:
year := d.Year
month := max(start.Month(), time.Month(d.Month))
if start.Year() > d.Year {
year = start.Year()
month = start.Month()
}
return func(yield func(time.Time) bool) {
dt := time.Date(year, month, 1, 0, 0, 0, 0, start.Location())
dt = endOfMonth(dt)
if !yield(dt) {
return
}
}
case yearAndMonth && onlyDayOfWeek:
year := max(start.Year(), d.Year)
month := max(start.Month(), time.Month(d.Month))
day := max(start.Day(), d.Day)
if start.Year() > d.Year {
year = start.Year()
month = start.Month()
day = start.Day()
}
return func(yield func(time.Time) bool) {
for _, d := range d.DayOfWeek {
dt := time.Date(year, month, day, 0, 0, 0, 0, start.Location())
dt = beginningOfWeek(dt).AddDate(0,0, d.DayOfWeek())
if start.After(dt) {
continue
}
if !yield(dt) {
return
}
}
}
case yearMonthDay:
year := max(start.Year(), d.Year)
month := max(start.Month(), time.Month(d.Month))
day := max(start.Day(), d.Day)
d := time.Date(year, month, day, 0, 0, 0, 0, start.Location())
return func(yield func(time.Time) bool) {
yield(d)
return
}
}
return slices.Values[[]time.Time](nil)
}
func (d Date) Less(a Date) bool {
if d.Year < a.Year {
return true
}
if d.Year > a.Year {
return false
}
if d.Month != 0 && d.Month < a.Month {
return true
}
if d.Month != 0 && d.Month > a.Month {
return false
}
if d.Day != 0 && d.Day < a.Day {
return true
}
if d.Day != 0 && d.Day > a.Day {
return false
}
return true
}
func apply[T, F any](arr []T, fn func(T) F) []F {
res := make([]F, len(arr))
for i, v := range arr {
res[i] = fn(v)
}
return res
}
type pair[A, B any] struct {
A A
B B
}
func zip[A, B any](a []A, b []B) []pair[A, B] {
l := min(len(a), len(b))
res := make([]pair[A, B], l)
for i := range a {
res[i] = struct {
A A
B B
}{a[i], b[i]}
}
return res
}
func repeatDayOfWeek(text string) (int, []Day, error) {
var i int
var lis []Day
next := true
for next {
next = false
var dow Day
di, err := dow.Parse(text)
if err != nil {
return 0, nil, err
}
lis = append(lis, dow)
text = text[di:]
i += di
for di, c := range text {
if c == ',' {
next = true
continue
}
if c == ' ' {
continue
}
text = text[di:]
i += di
break
}
}
return i, lis, nil
}
func repeatMonth(text string) (int, []Month, error) {
var i int
var lis []Month
next := true
for next {
next = false
var month Month
di, err := month.Parse(text)
if err != nil {
return 0, nil, err
}
lis = append(lis, month)
text = text[di:]
i += di
for di, c := range text {
if c == ',' {
next = true
continue
}
if c == ' ' {
continue
}
text = text[di:]
i += di
break
}
}
return i, lis, nil
}
func repeatWeekOfYear(text string) (int, []Week, error) {
var i int
var lis []Week
next := true
for next {
next = false
var dow Week
di, err := dow.Parse(text)
if err != nil {
return 0, nil, err
}
lis = append(lis, dow)
text = text[di:]
i += di
for di, c := range text {
if c == ',' {
next = true
continue
}
if c == ' ' {
continue
}
text = text[di:]
i += di
break
}
}
return i, lis, nil
}
func repeatWeekOfYearAndDayOfWeek(text string) (int, []Week, []Day, error) {
var i int
var weeks []Week
var days []Day
next := true
for next {
next = false
var dow Week
di, err := dow.Parse(text)
if err != nil {
return 0, nil, nil, err
}
weeks = append(weeks, dow)
text = text[di:]
i += di
if len(text) == 0 || text[0] != ' ' {
return 0, nil, nil, fmt.Errorf("invalid date")
}
text = text[1:]
i++
var day Day
di, err = day.Parse(text)
if err != nil {
return 0, nil, nil, err
}
days = append(days, day)
text = text[di:]
i += di
for di, c := range text {
if c == ',' {
next = true
continue
}
if c == ' ' {
continue
}
text = text[di:]
i += di
break
}
}
return i, weeks, days, nil
}
func repeatMonthOfYearAndDayOfWeek(text string) (int, []Month, []Day, error) {
var i int
var months []Month
var days []Day
next := true
for next {
next = false
var dow Month
di, err := dow.Parse(text)
if err != nil {
return 0, nil, nil, err
}
months = append(months, dow)
text = text[di:]
i += di
if len(text) == 0 || text[0] != ' ' {
return 0, nil, nil, fmt.Errorf("invalid date")
}
text = text[1:]
i++
var day Day
di, err = day.Parse(text)
if err != nil {
return 0, nil, nil, err
}
days = append(days, day)
text = text[di:]
i += di
for di, c := range text {
if c == ',' {
next = true
continue
}
if c == ' ' {
continue
}
text = text[di:]
i += di
break
}
}
return i, months, days, nil
}
func beginningOfMonth(date time.Time) time.Time {
year, month, _ := date.Date()
return time.Date(year, month, 1, 0,0,0,0, date.Location())
}
func endOfMonth(date time.Time) time.Time {
year, month, _ := date.Date()
date = time.Date(year, month, 1, 0,0,0,0, date.Location())
return date.AddDate(0, 1, -date.Day())
}
func beginningOfYear(date time.Time) time.Time {
year, _, _ := date.Date()
return time.Date(year, 1, 1, 0,0,0,0, date.Location())
}
func beginningOfWeek(date time.Time) time.Time {
year, month, day := date.Date()
return time.Date(year, month, day-int(date.Weekday()), 0,0,0,0, date.Location())
}
func endOfYear(date time.Time) time.Time {
return date.AddDate(1, int(-date.Month()+1), -date.Day())
}
func dayOfWeekNumber(year int, week int, day int) time.Time {
// start of the year
date := time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)
// clamp to max 53
week = min(53, week)
// reverse week starts from the end of year
if week < 0 {
eoy := endOfYear(date)
_, isoWeek := eoy.ISOWeek()
if isoWeek == 1 {
isoWeek = 53
}
week = isoWeek + week + 1
}
// clamp to min 1
week = max(1, week)
// move to the Nth week
date = date.AddDate(0, 0, (7 * (week - 1)))
// move to day
date = date.AddDate(0, 0, -int(date.Weekday())+day)
return date
}
func monthWithNWeek(d Day, dt time.Time) time.Time {
dow := d.DayOfWeek()
if wom := d.WeekOfMonth(); wom >= 0 {
if wom == 0 {
wom = 1
}
day := 7 - int(dt.Weekday()) +
dow +
7*(wom-1)
dt = dt.AddDate(0, 0, day)
} else {
dt = endOfMonth(dt)
day := 7 - int(dt.Weekday())
wk := 7 - dow
shift := 7 * (-wom - 1)
day = wk - day + shift
dt = dt.AddDate(0, 0, -day)
}
return dt
}