Skip to content

Commit

Permalink
panics
Browse files Browse the repository at this point in the history
  • Loading branch information
pchavanne committed Oct 17, 2024
1 parent 882d49e commit 1a3dcd0
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 32 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ import (
)

func main() {
// Load the New York Stock Exchange (XNYS) calendar
nyse := calendar.XNYS()
// Create a calendar with default years (current year ±5 years)
nyse := calendar.XNYS()

today := time.Now()
// Or create a calendar with a custom start and end year
customCal := calendar.XNYS(2010, 2035) // From 2010 to 2035

// Check if today is a business day
today := time.Now()
if nyse.IsBusinessDay(today) {
fmt.Println("Today is a business day.")
} else {
Expand All @@ -51,7 +53,10 @@ func main() {
// Get the next business day
nextBusinessDay := nyse.NextBusinessDay(today)
fmt.Printf("Next business day: %v\n", nextBusinessDay)
fmt.

// Check if Black Friday 2030 is an early close
blackFriday := time.Date(2030, time.November, 29, 0, 0, 0, 0, calendar.NewYork)
fmt.Println(customCal.IsEarlyClose(blackFriday)) // Outputs: true
}
```

Expand Down
41 changes: 39 additions & 2 deletions calendar.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
const YearsAhead = 5
const YearsPast = 5

// Predefined time locations for various cities.
var (
Mexico, _ = time.LoadLocation("America/Mexico_City")
Chicago, _ = time.LoadLocation("America/Chicago")
Expand Down Expand Up @@ -39,6 +40,7 @@ var (
Sydney, _ = time.LoadLocation("Australia/Sydney")
)

// Session defines the operating hours and breaks for the calendar.
type Session struct {
EarlyOpen time.Duration
Open time.Duration
Expand All @@ -49,14 +51,17 @@ type Session struct {
LateClose time.Duration
}

// HasBreak checks if the session has a break defined.
func (s *Session) HasBreak() bool {
return s.BreakStart != 0
}

// IsZero checks if the Session is empty.
func (s Session) IsZero() bool {
return s == Session{}
}

// Calendar represents a calendar with holidays and sessions.
type Calendar struct {
Name string
Loc *time.Location // NewYork or time.LoadLocation("America/New_York")
Expand All @@ -82,6 +87,11 @@ func newCalendar(name string, loc *time.Location, start, end int) *Calendar {
}
}

// NewCalendar creates a new Calendar instance based on the provided parameters.
// It accepts a name for the calendar, a location, and an optional list of years.
// If no years are specified, it defaults to a range of 5 years before and after the current year.
// If one year is provided, it sets that year as the start and calculates the end as 10 years later.
// If two years are provided, it calculates the end year based on the first year and checks if the second is less than 100.
func NewCalendar(name string, loc *time.Location, years ...int) *Calendar {
var start, end int
switch len(years) {
Expand Down Expand Up @@ -109,20 +119,22 @@ func (c *Calendar) reset() {
c.ecmap = make(map[int64]*Holiday)
}

// early, core and late Sessions
// Session returns the current session details.
func (c *Calendar) Session() *Session {
return c.session
}

// early, core and late Sessions
// SetSession updates the session details for the calendar.
func (c *Calendar) SetSession(s *Session) {
c.session = s
}

// Years returns the start and end years of the calendar.
func (c *Calendar) Years() (start, end int) {
return c.startYear, c.endYear
}

// SetYears updates the start and end years for the calendar and resets holidays.
func (c *Calendar) SetYears(start, end int) {
c.startYear, c.endYear = start, end
c.reset()
Expand All @@ -131,6 +143,7 @@ func (c *Calendar) SetYears(start, end int) {
c.addHoliday(h)
}
}

func (c *Calendar) addHoliday(h *Holiday) {
for y := c.startYear; y <= c.endYear; y++ {
t := h.Calc(y, c.Loc)
Expand All @@ -144,6 +157,7 @@ func (c *Calendar) addHoliday(h *Holiday) {
})
}

// AddHolidays appends holidays to the calendar and adds them to the holiday list.
func (c *Calendar) AddHolidays(h ...*Holiday) {
for _, ho := range h {
c.h = append(c.h, ho)
Expand All @@ -165,13 +179,15 @@ func (c *Calendar) addEarlyClosingDay(h *Holiday) {
})
}

// AddEarlyClosingDays appends early closing holidays to the calendar.
func (c *Calendar) AddEarlyClosingDays(h ...*Holiday) {
for _, ho := range h {
c.addEarlyClosingDay(ho)
c.h = append(c.h, ho)
}
}

// HasHoliday checks if a specific holiday is present in the calendar.
func (c *Calendar) HasHoliday(h *Holiday) bool {
for _, ho := range c.h {
if h == ho {
Expand All @@ -181,7 +197,16 @@ func (c *Calendar) HasHoliday(h *Holiday) bool {
return false
}

func (c *Calendar) ensureInRange(t time.Time) {
year := t.Year()
if year < c.startYear || year > c.endYear {
panic(fmt.Sprintf("provided time %v is outside the calendar range (%d - %d)", t, c.startYear, c.endYear))
}
}

// IsBusinessDay checks if a specific Tims is a business day for this calendar.
func (c *Calendar) IsBusinessDay(t time.Time) bool {
c.ensureInRange(t)
if IsWeekend(t) {
return false
}
Expand All @@ -191,17 +216,23 @@ func (c *Calendar) IsBusinessDay(t time.Time) bool {
return true
}

// IsHoliday checks if a specific Tims is a holiday day for this calendar.
func (c *Calendar) IsHoliday(t time.Time) bool {
c.ensureInRange(t)
_, ok := c.hmap[BOD(t).Unix()]
return ok
}

// IsHoliday checks if a specific Tims is a early close for this calendar.
func (c *Calendar) IsEarlyClose(t time.Time) bool {
c.ensureInRange(t)
_, ok := c.ecmap[BOD(t).Unix()]
return ok
}

// IsOpen checks if a specific Tims is in a business session for this calendar.
func (c *Calendar) IsOpen(t time.Time) bool {
c.ensureInRange(t)
if c.session.IsZero() {
panic(errNoSession)
}
Expand All @@ -223,15 +254,19 @@ func (c *Calendar) IsOpen(t time.Time) bool {
return true
}

// NextBusinessDay returns the business day following the provided Time.
func (c *Calendar) NextBusinessDay(t time.Time) time.Time {
c.ensureInRange(t)
t = t.AddDate(0, 0, 1)
for !c.IsBusinessDay(t) {
t = t.AddDate(0, 0, 1)
}
return t
}

// NextHoliday returns the following holiday time and Holiday for the provided Time.
func (c *Calendar) NextHoliday(t time.Time) (time.Time, *Holiday) {
c.ensureInRange(t)
for _, ts := range c.hts {
if t.Unix() < ts {
return time.Unix(ts, 0).In(c.Loc), c.hmap[ts]
Expand All @@ -240,7 +275,9 @@ func (c *Calendar) NextHoliday(t time.Time) (time.Time, *Holiday) {
return time.Time{}, nil
}

// NextClose return the next closing time
func (c *Calendar) NextClose(t time.Time) time.Time {
c.ensureInRange(t)
if c.session.IsZero() {
panic(errNoSession)
}
Expand Down
19 changes: 8 additions & 11 deletions calendar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,14 @@ func TestCalendarYears(t *testing.T) {
assert.Equal(time.Now().Year()+YearsAhead, end)
c = NewCalendar("Calendar", Chicago, 2015)
c.AddHolidays(NewYear)
ti, ho := c.NextHoliday(time.Time{})
assert.Equal(time.Date(2015, 1, 1, 0, 0, 0, 0, Chicago), ti)
ti, ho := c.NextHoliday(time.Date(time.Now().Year(), 1, 1, 0, 0, 0, 0, Chicago))
assert.Equal(time.Date(time.Now().Year()+1, 1, 1, 0, 0, 0, 0, Chicago), ti)
assert.Equal(NewYear, ho)
c.SetYears(2018, 2020)
assert.Equal(2018, c.startYear)
assert.Equal(2020, c.endYear)
ti, ho = c.NextHoliday(time.Time{})
assert.Equal(time.Date(2018, 1, 1, 0, 0, 0, 0, Chicago), ti)
assert.Equal(NewYear, ho)
assert.Panics(func() { c.NextHoliday(time.Time{}) })

}

func TestAddHoliday(t *testing.T) {
Expand Down Expand Up @@ -145,7 +144,7 @@ func TestCalendarBusinessDay(t *testing.T) {
c.AddHolidays(NewYear) // 1/1/2013 is a tuesday
assert.False(c.IsBusinessDay(time.Date(2013, 1, 1, 0, 0, 0, 0, Chicago)))
assert.True(c.IsBusinessDay(time.Date(2013, 1, 2, 0, 0, 0, 0, Chicago)))
assert.Equal(time.Date(2013, 1, 2, 0, 0, 0, 0, Chicago), c.NextBusinessDay(time.Date(2012, 12, 31, 0, 0, 0, 0, Chicago)))
assert.Panics(func() { c.NextBusinessDay(time.Date(2012, 12, 31, 0, 0, 0, 0, Chicago)) })
assert.Equal(time.Date(2013, 1, 2, 0, 0, 0, 0, Chicago), c.NextBusinessDay(time.Date(2013, 1, 1, 0, 0, 0, 0, Chicago)))
assert.Equal(time.Date(2013, 1, 3, 0, 0, 0, 0, Chicago), c.NextBusinessDay(time.Date(2013, 1, 2, 0, 0, 0, 0, Chicago)))
assert.Equal(time.Date(2013, 1, 4, 0, 0, 0, 0, Chicago), c.NextBusinessDay(time.Date(2013, 1, 3, 0, 0, 0, 0, Chicago)))
Expand All @@ -160,10 +159,8 @@ func TestNextBusinessDay(t *testing.T) {
assert := assert.New(t)
c := NewCalendar("Calendar", Chicago, 2014, 2015)
c.AddHolidays(NewYear)
ti, ho := c.NextHoliday(time.Date(2013, 1, 1, 0, 0, 0, 0, Chicago))
assert.Equal(time.Date(2014, 1, 1, 0, 0, 0, 0, Chicago), ti)
assert.Equal(NewYear, ho)
ti, ho = c.NextHoliday(time.Date(2014, 1, 1, 0, 0, 0, 0, Chicago))
assert.Panics(func() { c.NextHoliday(time.Date(2013, 1, 1, 0, 0, 0, 0, Chicago)) })
ti, ho := c.NextHoliday(time.Date(2014, 1, 1, 0, 0, 0, 0, Chicago))
assert.Equal(time.Date(2015, 1, 1, 0, 0, 0, 0, Chicago), ti)
assert.Equal(NewYear, ho)
ti, ho = c.NextHoliday(time.Date(2014, 2, 1, 0, 0, 0, 0, Chicago))
Expand All @@ -172,7 +169,7 @@ func TestNextBusinessDay(t *testing.T) {
ti, ho = c.NextHoliday(time.Date(2015, 2, 1, 0, 0, 0, 0, Chicago))
assert.Equal(time.Time{}, ti)
assert.Nil(ho)

assert.Panics(func() { c.NextHoliday(time.Date(2016, 1, 1, 0, 0, 0, 0, Chicago)) })
}
func TestIsOpen(t *testing.T) {
assert := assert.New(t)
Expand Down
22 changes: 11 additions & 11 deletions holiday.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Holiday struct {
Observance observance
}

// Copy holiday. Always set observance on a copy.
// Copy returns a copy of holiday. Always set observance on a copy.
func (h Holiday) Copy(name ...string) *Holiday {
if len(name) > 0 {
h.Name = name[0]
Expand Down Expand Up @@ -58,7 +58,7 @@ func (h *Holiday) SetObservance(o observance) *Holiday {
return h
}

// Calculate the holiday time.Time for a given year
// Calculate the holiday time.Time for a given year.
func (h *Holiday) Calc(year int, loc *time.Location) time.Time {
if h.OnYear > 0 && year != h.OnYear {
return time.Time{}
Expand Down Expand Up @@ -91,31 +91,31 @@ func (h *Holiday) Calc(year int, loc *time.Location) time.Time {
return t
}

// Day of month in Gregorian Calendar, -1 means last day of the month
// Day of month in Gregorian Calendar, -1 means last day of the month.
func CalcDayOfMonth(h *Holiday, year int, loc *time.Location) time.Time {
if h.Day < 0 {
return time.Date(year, h.Month, 1, 0, 0, 0, 0, loc).AddDate(0, 1, h.Day)
}
return time.Date(year, h.Month, h.Day, 0, 0, 0, 0, loc)
}

// Day of month in Lunisolar Calendar, -1 means last day of the month
// Day of month in Lunisolar Calendar, -1 means last day of the month.
func CalcLunisolarDayOfMonth(h *Holiday, year int, loc *time.Location) time.Time {
if h.Day < 0 {
return LunisolarToGregorian(time.Date(year, h.Month, 1, 0, 0, 0, 0, loc).AddDate(0, 1, 0), false).AddDate(0, 0, h.Day)
}
return LunisolarToGregorian(time.Date(year, h.Month, h.Day, 0, 0, 0, 0, loc), false)
}

// Day of month in Hijri Calendar, -1 means last day of the month
// Day of month in Hijri Calendar, -1 means last day of the month.
func CalcHijriDayOfMonth(h *Holiday, year int, loc *time.Location) time.Time {
if h.Day < 0 {
return HijriToGregorian(time.Date(HijriYear(year), h.Month, 1, 0, 0, 0, 0, loc).AddDate(0, 1, 0)).AddDate(0, 0, h.Day)
}
return HijriToGregorian(time.Date(HijriYear(year), h.Month, h.Day, 0, 0, 0, 0, loc))
}

// Nth occurrence of a weekday like 3rd monday, -1 means last monday of the month
// Nth occurrence of a weekday like 3rd monday, -1 means last monday of the month.
func CalcNthWeekday(h *Holiday, year int, loc *time.Location) time.Time {
month := h.Month
if h.NthWeekday < 0 {
Expand All @@ -124,31 +124,31 @@ func CalcNthWeekday(h *Holiday, year int, loc *time.Location) time.Time {
return NthWeekday(year, month, h.Weekday, h.NthWeekday, loc)
}

// Easter sunday
// Easter sunday.
func CalcEasterOffset(h *Holiday, year int, loc *time.Location) time.Time {
day, month := Easter(year)
return time.Date(year, month, day, 0, 0, 0, 0, loc)
}

// March Equinox
// March Equinox.
func CalcNorthwardEquinox(h *Holiday, year int, loc *time.Location) time.Time {
c := northwardEquinox(year).In(loc)
return time.Date(year, time.March, c.Day(), 0, 0, 0, 0, loc)
}

// June Solstice
// June Solstice.
func CalcNorthernSolstice(h *Holiday, year int, loc *time.Location) time.Time {
c := northernSolstice(year).In(loc)
return time.Date(year, time.June, c.Day(), 0, 0, 0, 0, loc)
}

// September Equinox
// September Equinox.
func CalcSouthwardEquinox(h *Holiday, year int, loc *time.Location) time.Time {
c := southwardEquinox(year).In(loc)
return time.Date(year, time.September, c.Day(), 0, 0, 0, 0, loc)
}

// December Solstice
// December Solstice.
func CalcSouthernSolstice(h *Holiday, year int, loc *time.Location) time.Time {
c := southernSolstice(year).In(loc)
return time.Date(year, time.December, c.Day(), 0, 0, 0, 0, loc)
Expand Down
8 changes: 4 additions & 4 deletions lunar.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ var yearSum = map[int]int{
2090: 384, 2091: 354, 2092: 355, 2093: 384, 2094: 355, 2095: 354, 2096: 384, 2097: 354, 2098: 354, 2099: 384,
}

// The leap month of a year (0 if none)
// GetLeapMonth returns the leap month of a year (0 if none).
func GetLeapMonth(year int) int {
return lunarInfomation[year-1900] & 0xf
}

// Gregorian calendar -> Lunisolar calendar
// GregorianToLunisolar returns a Lunisolar calendar time from a Gregorian calendar time.
func GregorianToLunisolar(t time.Time) (time.Time, bool) {

// 1900-1-31 timestamp
Expand Down Expand Up @@ -124,7 +124,7 @@ func GregorianToLunisolar(t time.Time) (time.Time, bool) {
return time.Date(lunarYear, time.Month(month), day, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location()), leapMonth != 0
}

// Lunisolar calendar -> Gregorian calendar
// LunisolarToGregorian returns a Gregorian calendar time from a Lunisolar calendar time.
func LunisolarToGregorian(t time.Time, leap bool) time.Time {
var sum int = 0
for i := 1900; i < t.Year(); i++ {
Expand Down Expand Up @@ -186,7 +186,7 @@ func LunisolarToGregorian(t time.Time, leap bool) time.Time {
return time.Date(solarYear, time.Month(solarMonth), solarDay, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
}

// Get the number of days in a lunar month
// GetLunisolarMonthDays gets the number of days in a lunar month.
func GetLunisolarMonthDays(year int, month time.Month, leapMonth bool) int {
hex := lunarInfomation[year-1900]
if leapMonth {
Expand Down

0 comments on commit 1a3dcd0

Please sign in to comment.