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() } // NextMarketOpen returns the next market open after a given time. func NextMarketOpen(after, openTime time.Time, isHoliday HolidayProvider) time.Time { afterEastern := after.In(Eastern()) todaysOpen := On(openTime, afterEastern) if isHoliday == nil { isHoliday = DefaultHolidayProvider } if afterEastern.Before(todaysOpen) && IsWeekDay(todaysOpen.Weekday()) && !isHoliday(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()) && !isHoliday(afterEastern) { return On(openTime, newDay) } } return Epoch //we should never reach this. } // NextMarketClose returns the next market close after a given time. func NextMarketClose(after, closeTime time.Time, isHoliday HolidayProvider) time.Time { afterEastern := after.In(Eastern()) if isHoliday == nil { isHoliday = DefaultHolidayProvider } todaysClose := On(closeTime, afterEastern) if afterEastern.Before(todaysClose) && IsWeekDay(todaysClose.Weekday()) && !isHoliday(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()) && !isHoliday(newDay) { return On(closeTime, newDay) } } return Epoch //we should never reach this. } // CalculateMarketSecondsBetween calculates the number of seconds the market was open between two dates. func CalculateMarketSecondsBetween(start, end, marketOpen, marketClose time.Time, isHoliday HolidayProvider) (seconds int64) { startEastern := start.In(Eastern()) endEastern := end.In(Eastern()) startMarketOpen := NextMarketOpen(startEastern, marketOpen, isHoliday) startMarketClose := NextMarketClose(startEastern, marketClose, isHoliday) if (startEastern.Equal(startMarketOpen) || startEastern.After(startMarketOpen)) && startEastern.Before(startMarketClose) { seconds += int64(startMarketClose.Sub(startEastern) / time.Second) } cursor := NextMarketOpen(startMarketClose, marketOpen, isHoliday) for BeforeDate(cursor, endEastern) { if IsWeekDay(cursor.Weekday()) && !isHoliday(cursor) { close := NextMarketClose(cursor, marketClose, isHoliday) seconds += int64(close.Sub(cursor) / time.Second) } cursor = cursor.AddDate(0, 0, 1) } finalMarketOpen := NextMarketOpen(cursor, marketOpen, isHoliday) finalMarketClose := NextMarketClose(cursor, marketClose, isHoliday) if endEastern.After(finalMarketOpen) { if endEastern.Before(finalMarketClose) { seconds += int64(endEastern.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 }