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() } // MarketOpen returns 0930 on a given day. func MarketOpen(on, openTime time.Time) time.Time { onEastern := on.In(Eastern()) return On(openTime, onEastern) } // MarketClose returns 1600 on a given day. func MarketClose(on, closeTime 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, openTime time.Time, isHoliday HolidayChecker) time.Time { afterEastern := after.In(Eastern()) todaysOpen := MarketOpen(afterEastern, openTime) 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 HolidayChecker) time.Time { afterEastern := after.In(Eastern()) todaysClose := MarketClose(afterEastern, closeTime) 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 HolidayChecker) (seconds int64) { se := start.In(Eastern()) ee := end.In(Eastern()) startMarketOpen := NextMarketOpen(se, marketOpen, isHoliday) startMarketClose := NextMarketClose(se, marketClose, isHoliday) if (se.Equal(startMarketOpen) || se.After(startMarketOpen)) && se.Before(startMarketClose) { seconds += int64(startMarketClose.Sub(se) / time.Second) } cursor := NextMarketOpen(startMarketClose, marketClose, isHoliday) for BeforeDate(cursor, ee) { 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 end.After(finalMarketOpen) { if end.Before(finalMarketClose) { seconds += int64(end.Sub(finalMarketOpen) / time.Second) } else { seconds += int64(finalMarketClose.Sub(finalMarketOpen) / time.Second) } } return } // ClockTime returns a new time.Time for the given clock components. func ClockTime(hour, min, sec, nsec int, loc *time.Location) time.Time { return time.Date(0, 0, 0, hour, min, sec, nsec, loc) } // On returns the clock components of clock (hour,minute,second) on the date components of d. func On(clock, d time.Time) time.Time { return time.Date(d.Year(), d.Month(), d.Day(), clock.Hour(), clock.Minute(), clock.Second(), clock.Nanosecond(), clock.Location()) } // 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 }