From 4f38532d7f52942753fd009f2ea4e8270eafbb63 Mon Sep 17 00:00:00 2001 From: Marek Goc Date: Mon, 30 Sep 2024 22:11:45 +0200 Subject: [PATCH] init --- Readme.md | 13 ++++ go.mod | 3 + holidays.go | 170 +++++++++++++++++++++++++++++++++++++++++++++++ holidays_test.go | 69 +++++++++++++++++++ models.go | 24 +++++++ 5 files changed, 279 insertions(+) create mode 100644 Readme.md create mode 100644 go.mod create mode 100644 holidays.go create mode 100644 holidays_test.go create mode 100644 models.go diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..d0e3276 --- /dev/null +++ b/Readme.md @@ -0,0 +1,13 @@ +This is little library to operate with bank holidays in Poland. + +```go +holidays := holidays_pl.New() + +// to get list of all holidays +holidays.GetHolidaysList(d.Year()) + +// get holiday by date or false as second returned value +hh, ok := holidays.GetByDate(time.Date(2024, time.March, 31, 0, 0, 0, 0, time.UTC)) + +``` +For more info just look at th tests file. \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..99dbd33 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.ma-al.com/goc_marek/holidayspl + +go 1.23.0 diff --git a/holidays.go b/holidays.go new file mode 100644 index 0000000..2a2e101 --- /dev/null +++ b/holidays.go @@ -0,0 +1,170 @@ +package holidayspl + +import ( + "time" +) + +type Holidays struct { + list []Holiday + cache map[int][]Holiday +} + +func New() *Holidays { + return &Holidays{ + list: createList(), + cache: make(map[int][]Holiday), + } +} + +func (hol *Holidays) GetEasterDate(year int) time.Time { + a := year % 19 + b := year / 100 + c := year % 100 + d := b / 4 + e := b % 4 + f := (b + 8) / 25 + g := (b - f + 1) / 3 + h := (19*a + b - d - g + 15) % 30 + i := c / 4 + k := c % 4 + l := (32 + 2*e + 2*i - h - k) % 7 + m := (a + 11*h + 22*l) / 451 + n := (h + l - 7*m + 114) / 31 + o := (h + l - 7*m + 114) % 31 + return time.Date(year, time.Month(n), o+1, 0, 0, 0, 0, time.UTC) +} + +func (hol *Holidays) GetHolidaysList(year int) []Holiday { + // Check if the holiday list for this year is already cached + if holidays, ok := hol.cache[year]; ok { + return holidays + } + + // Calculate and store holidays in the cache + easter := hol.GetEasterDate(year) + for i, h := range hol.list { + if h.HolidayType == FIXED { + hol.list[i].Date = time.Date(year, h.Month, h.Day, 0, 0, 0, 0, time.UTC) + } else if h.HolidayType == MOVABLE { + hol.list[i].Date = easter.AddDate(0, 0, h.PlusEaster) + } + } + + // Cache the computed holiday list + hol.cache[year] = hol.list + return hol.list +} + +func (hol *Holidays) GetByDate(d time.Time) (Holiday, bool) { + for _, h := range hol.GetHolidaysList(d.Year()) { + if h.Date.Equal(d) { + return h, true + } + } + return Holiday{}, false +} + +func (hol *Holidays) GetCache() map[int][]Holiday { + return hol.cache +} + +func createList() []Holiday { + + return []Holiday{ + { + Name: "New Year", + NamePL: "Nowy Rok", + Month: time.Month(1), + Day: 1, + HolidayType: FIXED, + }, + { + Name: "Three Kings' Day", + NamePL: "Święto Trzech Króli", + Month: time.Month(1), + Day: 6, + HolidayType: FIXED, + }, + { + Name: "Labour Day", + NamePL: "Święto Pracy", + Month: time.Month(5), + Day: 1, + HolidayType: FIXED, + }, + { + Name: "3 May Constitution Day", + NamePL: "Narodowe Święto Konstytucji Trzeciego Maja", + Month: time.Month(5), + Day: 3, + HolidayType: FIXED, + }, + { + Name: "Assumption of Mary", + NamePL: "Wniebowzięcie Najświętszej Maryi Panny", + Month: time.Month(8), + Day: 15, + HolidayType: FIXED, + }, + { + Name: "All Saints' Day", + NamePL: "Wszystkich Świętych", + Month: time.Month(11), + Day: 1, + HolidayType: FIXED, + }, + { + Name: "National Independence Day", + NamePL: "Narodowe Święto Niepodległości", + Month: time.Month(11), + Day: 11, + HolidayType: FIXED, + }, + { + Name: "Christmas", + NamePL: "Boże Narodzenie", + Month: time.Month(12), + Day: 25, + HolidayType: FIXED, + }, + { + Name: "Second Day of Christmas", + NamePL: "Boże Narodzenie - drugi dzień", + Month: time.Month(12), + Day: 26, + HolidayType: FIXED, + }, + { + Name: "Easter Sunday", + NamePL: "Niedziela Wielkanocna", + Month: time.Month(5), + Day: 3, + HolidayType: MOVABLE, + PlusEaster: 0, + }, + { + Name: "Easter Monday", + NamePL: "Poniedziałek Wielkanocny", + Month: time.Month(5), + Day: 3, + HolidayType: MOVABLE, + PlusEaster: 1, + }, + { + Name: "Green Week", + NamePL: "Zielone Świątki", + Month: time.Month(5), + Day: 3, + HolidayType: MOVABLE, + PlusEaster: 49, + }, + { + Name: "Feast of Corpus Christi", + NamePL: "Boże Ciało", + Month: time.Month(5), + Day: 3, + HolidayType: MOVABLE, + PlusEaster: 60, + }, + } +} diff --git a/holidays_test.go b/holidays_test.go new file mode 100644 index 0000000..3387a40 --- /dev/null +++ b/holidays_test.go @@ -0,0 +1,69 @@ +package holidayspl_test + +import ( + "testing" + "time" + + "git.ma-al.com/goc_marek/holidayspl" +) + +func TestGetEasterDate(t *testing.T) { + holidays := holidayspl.New() + + tests := []struct { + year int + expected time.Time + }{ + {2024, time.Date(2024, time.March, 31, 0, 0, 0, 0, time.UTC)}, + {2025, time.Date(2025, time.April, 20, 0, 0, 0, 0, time.UTC)}, + {2026, time.Date(2026, time.April, 5, 0, 0, 0, 0, time.UTC)}, + } + + for _, test := range tests { + result := holidays.GetEasterDate(test.year) + if !result.Equal(test.expected) { + t.Errorf("GetEasterDate(%d) = %v; want %v", test.year, result, test.expected) + } + } +} + +func TestGetByDate(t *testing.T) { + holidays := holidayspl.New() + + tests := []struct { + date time.Time + expected string + found bool + }{ + {time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC), "New Year", true}, + {time.Date(2024, time.March, 31, 0, 0, 0, 0, time.UTC), "Easter Sunday", true}, + {time.Date(2024, time.December, 25, 0, 0, 0, 0, time.UTC), "Christmas", true}, + {time.Date(2024, time.August, 15, 0, 0, 0, 0, time.UTC), "Assumption of Mary", true}, + {time.Date(2024, time.July, 4, 0, 0, 0, 0, time.UTC), "", false}, // No holiday on this date + } + + for _, test := range tests { + result, found := holidays.GetByDate(test.date) + if found != test.found { + t.Errorf("GetByDate(%v) = %v; want %v", test.date, found, test.found) + } + if found && result.Name != test.expected { + t.Errorf("GetByDate(%v) = %v; want %v", test.date, result.Name, test.expected) + } + } +} + +func TestCaching(t *testing.T) { + holidays := holidayspl.New() + + year := 2024 + // First call should calculate and cache holidays for the year + holidays.GetHolidaysList(year) + // Modify the cache and see if the function returns the cached value + holidays.GetCache()[year][0].Name = "Modified New Year" + + result := holidays.GetHolidaysList(year) + if result[0].Name != "Modified New Year" { + t.Errorf("Caching not working properly. Got %v, expected %v", result[0].Name, "Modified New Year") + } +} diff --git a/models.go b/models.go new file mode 100644 index 0000000..082b1a9 --- /dev/null +++ b/models.go @@ -0,0 +1,24 @@ +package holidayspl + +import "time" + +type HolidayType int + +const ( + FIXED HolidayType = iota + MOVABLE +) + +func (d HolidayType) String() string { + return [...]string{"fixed", "movable"}[d] +} + +type Holiday struct { + Name string + NamePL string + Month time.Month + Day int + HolidayType HolidayType + PlusEaster int + Date time.Time +}