package date import ( "sync" "time" ) const ( // AllDaysMask is a bitmask of all the days of the week. AllDaysMask = 1<friday. func IsWeekDay(day time.Weekday) bool { return !IsWeekendDay(day) } // IsWeekendDay returns if the day is a monday->friday. func IsWeekendDay(day time.Weekday) bool { return day == time.Saturday || day == time.Sunday } // BeforeDate returns if a timestamp is strictly before another date (ignoring hours, minutes etc.) func BeforeDate(before, reference time.Time) bool { if before.Year() < reference.Year() { return true } if before.Month() < reference.Month() { return true } return before.Year() == reference.Year() && before.Month() == reference.Month() && before.Day() < reference.Day() } // IsNYSEHoliday returns if a date was/is on a nyse holiday day. func IsNYSEHoliday(t time.Time) bool { te := t.In(Eastern()) if te.Year() == 2013 { if te.Month() == 1 { return te.Day() == 1 || te.Day() == 21 } else if te.Month() == 2 { return te.Day() == 18 } else if te.Month() == 3 { return te.Day() == 29 } else if te.Month() == 5 { return te.Day() == 27 } else if te.Month() == 7 { return te.Day() == 4 } else if te.Month() == 9 { return te.Day() == 2 } else if te.Month() == 11 { return te.Day() == 28 } else if te.Month() == 12 { return te.Day() == 25 } } else if te.Year() == 2014 { if te.Month() == 1 { return te.Day() == 1 || te.Day() == 20 } else if te.Month() == 2 { return te.Day() == 17 } else if te.Month() == 4 { return te.Day() == 18 } else if te.Month() == 5 { return te.Day() == 26 } else if te.Month() == 7 { return te.Day() == 4 } else if te.Month() == 9 { return te.Day() == 1 } else if te.Month() == 11 { return te.Day() == 27 } else if te.Month() == 12 { return te.Day() == 25 } } else if te.Year() == 2015 { if te.Month() == 1 { return te.Day() == 1 || te.Day() == 19 } else if te.Month() == 2 { return te.Day() == 16 } else if te.Month() == 4 { return te.Day() == 3 } else if te.Month() == 5 { return te.Day() == 25 } else if te.Month() == 7 { return te.Day() == 3 } else if te.Month() == 9 { return te.Day() == 7 } else if te.Month() == 11 { return te.Day() == 26 } else if te.Month() == 12 { return te.Day() == 25 } } else if te.Year() == 2016 { if te.Month() == 1 { return te.Day() == 1 || te.Day() == 18 } else if te.Month() == 2 { return te.Day() == 15 } else if te.Month() == 3 { return te.Day() == 25 } else if te.Month() == 5 { return te.Day() == 30 } else if te.Month() == 7 { return te.Day() == 4 } else if te.Month() == 9 { return te.Day() == 5 } else if te.Month() == 11 { return te.Day() == 24 || te.Day() == 25 } else if te.Month() == 12 { return te.Day() == 26 } } else if te.Year() == 2017 { if te.Month() == 1 { return te.Day() == 1 || te.Day() == 16 } else if te.Month() == 2 { return te.Day() == 20 } else if te.Month() == 4 { return te.Day() == 15 } else if te.Month() == 5 { return te.Day() == 29 } else if te.Month() == 7 { return te.Day() == 4 } else if te.Month() == 9 { return te.Day() == 4 } else if te.Month() == 11 { return te.Day() == 23 } else if te.Month() == 12 { return te.Day() == 25 } } else if te.Year() == 2018 { if te.Month() == 1 { return te.Day() == 1 || te.Day() == 15 } else if te.Month() == 2 { return te.Day() == 19 } else if te.Month() == 3 { return te.Day() == 30 } else if te.Month() == 5 { return te.Day() == 28 } else if te.Month() == 7 { return te.Day() == 4 } else if te.Month() == 9 { return te.Day() == 3 } else if te.Month() == 11 { return te.Day() == 22 } else if te.Month() == 12 { return te.Day() == 25 } } return false } // MarketOpen returns 0930 on a given day. func MarketOpen(on time.Time) time.Time { onEastern := on.In(Eastern()) return time.Date(onEastern.Year(), onEastern.Month(), onEastern.Day(), 9, 30, 0, 0, Eastern()) } // MarketClose returns 1600 on a given day. func MarketClose(on time.Time) time.Time { onEastern := on.In(Eastern()) return time.Date(onEastern.Year(), onEastern.Month(), onEastern.Day(), 16, 0, 0, 0, Eastern()) } // NextMarketOpen returns the next market open after a given time. func NextMarketOpen(after time.Time) time.Time { afterEastern := after.In(Eastern()) todaysOpen := MarketOpen(afterEastern) if afterEastern.Before(todaysOpen) && IsWeekDay(todaysOpen.Weekday()) && !IsNYSEHoliday(todaysOpen) { return todaysOpen } if afterEastern.Equal(todaysOpen) { //rare but it might happen. return todaysOpen } for cursorDay := 1; cursorDay < 6; cursorDay++ { newDay := todaysOpen.AddDate(0, 0, cursorDay) if IsWeekDay(newDay.Weekday()) && !IsNYSEHoliday(afterEastern) { return time.Date(newDay.Year(), newDay.Month(), newDay.Day(), 9, 30, 0, 0, Eastern()) } } return Epoch //we should never reach this. } // NextMarketClose returns the next market close after a given time. func NextMarketClose(after time.Time) time.Time { afterEastern := after.In(Eastern()) todaysClose := MarketClose(afterEastern) if afterEastern.Before(todaysClose) && IsWeekDay(todaysClose.Weekday()) && !IsNYSEHoliday(todaysClose) { return todaysClose } if afterEastern.Equal(todaysClose) { //rare but it might happen. return todaysClose } for cursorDay := 1; cursorDay < 6; cursorDay++ { newDay := todaysClose.AddDate(0, 0, cursorDay) if IsWeekDay(newDay.Weekday()) && !IsNYSEHoliday(newDay) { return time.Date(newDay.Year(), newDay.Month(), newDay.Day(), 16, 0, 0, 0, Eastern()) } } return Epoch //we should never reach this. } // CalculateMarketSecondsBetween calculates the number of seconds the market was open between two dates. func CalculateMarketSecondsBetween(start, end time.Time) (seconds int64) { se := start.In(Eastern()) ee := end.In(Eastern()) startMarketOpen := NextMarketOpen(se) startMarketClose := NextMarketClose(se) if (se.Equal(startMarketOpen) || se.After(startMarketOpen)) && se.Before(startMarketClose) { seconds += int64(startMarketClose.Sub(se) / time.Second) } cursor := NextMarketOpen(startMarketClose) for BeforeDate(cursor, ee) { if IsWeekDay(cursor.Weekday()) && !IsNYSEHoliday(cursor) { close := NextMarketClose(cursor) seconds += int64(close.Sub(cursor) / time.Second) } cursor = cursor.AddDate(0, 0, 1) } finalMarketOpen := NextMarketOpen(cursor) finalMarketClose := NextMarketClose(cursor) if end.After(finalMarketOpen) { if end.Before(finalMarketClose) { seconds += int64(end.Sub(finalMarketOpen) / time.Second) } else { seconds += int64(finalMarketClose.Sub(finalMarketOpen) / time.Second) } } return } // Format returns a string representation of a date. func format(t time.Time) string { return t.Format("2006-01-02") } // Parse parses a date from a string. func parse(str string) time.Time { res, _ := time.Parse("2006-01-02", str) return res }