Skip to content
Snippets Groups Projects

Refactor TradingHours struct and validation

Merged Daniel Naude requested to merge trading_hours into main
All threads resolved!
+ 126
114
@@ -2,15 +2,15 @@ package date_utils
import (
"fmt"
"github.com/jinzhu/now"
"reflect"
"strconv"
"strings"
"time"
"github.com/jinzhu/now"
"github.com/araddon/dateparse"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/utils"
)
const TimeZoneString = "Africa/Johannesburg"
@@ -327,144 +327,156 @@ func formatTimestampsWithTimeZoneInSlice(fieldValue reflect.Value, location *tim
// TradingHours represents an array of (StartTime,EndTime) pairs, one for each day of the week.
// The array is 0 indexed, with 0 being Sunday and 6 being Saturday and 7 being public holidays.
type TradingHours []struct {
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
type TradingHours struct {
Monday TradingHoursDay `json:"monday"`
Tuesday TradingHoursDay `json:"tuesday"`
Wednesday TradingHoursDay `json:"wednesday"`
Thursday TradingHoursDay `json:"thursday"`
Friday TradingHoursDay `json:"friday"`
Saturday TradingHoursDay `json:"saturday"`
Sunday TradingHoursDay `json:"sunday"`
Holidays TradingHoursDay `json:"holidays"`
}
func (t TradingHours) Validate() error {
if len(t) != 8 {
return errors.Error("Trading hours must have 8 days, 7 for every day of the week and 1 for public holidays")
if err := t.Monday.Validate(); err != nil {
return errors.Wrapf(err, "Monday failed validation: %v", err.Error())
}
if err := t.Tuesday.Validate(); err != nil {
return errors.Wrapf(err, "Tuesday failed validation: %v", err.Error())
}
if err := t.Wednesday.Validate(); err != nil {
return errors.Wrapf(err, "Wednesday failed validation: %v", err.Error())
}
if err := t.Thursday.Validate(); err != nil {
return errors.Wrapf(err, "Thursday failed validation: %v", err.Error())
}
if err := t.Friday.Validate(); err != nil {
return errors.Wrapf(err, "Friday failed validation: %v", err.Error())
}
if err := t.Saturday.Validate(); err != nil {
return errors.Wrapf(err, "Saturday failed validation: %v", err.Error())
}
if err := t.Sunday.Validate(); err != nil {
return errors.Wrapf(err, "Sunday failed validation: %v", err.Error())
}
if err := t.Holidays.Validate(); err != nil {
return errors.Wrapf(err, "Holidays failed validation: %v", err.Error())
}
for _, day := range t {
if day.StartTime == "" || day.EndTime == "" {
// Allow empty trading hours for a day to represent closed
continue
}
return nil
}
if !TimeBefore(day.StartTime, day.EndTime) {
return errors.Error("Start time must be before end time")
}
func (day TradingHoursDay) Validate() error {
if day.StartTime == "" || day.EndTime == "" {
// Allow empty trading hours for a day to represent closed
return nil
}
if len(day.StartTime) != 5 || len(day.EndTime) != 5 {
return errors.Error("Time must be in the format HH:MM")
}
if !TimeBefore(day.StartTime, day.EndTime) {
return errors.Error("start time must be before end time")
}
startHourMinSlice := strings.Split(day.StartTime, ":")
if len(startHourMinSlice) != 2 {
return errors.Error("Time must be in the format HH:MM")
}
startHour, startMin := startHourMinSlice[0], startHourMinSlice[1]
startHourInt, err := strconv.Atoi(startHour)
if err != nil || startHourInt < 0 || startHourInt > 23 {
return errors.Error("Start hour must be between 0 and 23")
}
startMinInt, err := strconv.Atoi(startMin)
if err != nil || !(startMinInt == 0 || startMinInt == 30) {
return errors.Error("Start minute must be 0 or 30")
}
if len(day.StartTime) != 5 || len(day.EndTime) != 5 {
return errors.Error("time must be in the format HH:MM")
}
endHourMinSlice := strings.Split(day.EndTime, ":")
if len(endHourMinSlice) != 2 {
return errors.Error("Time must be in the format HH:MM")
}
endHour, endMin := endHourMinSlice[0], endHourMinSlice[1]
endHourInt, err := strconv.Atoi(endHour)
if err != nil || endHourInt < 0 || endHourInt > 23 {
return errors.Error("End hour must be between 0 and 23")
}
endMinInt, err := strconv.Atoi(endMin)
if err != nil || !(endMinInt == 0 || endMinInt == 30 || endMinInt == 59) {
return errors.Error("End minute must be 0, 30 or 59")
}
startHourMinSlice := strings.Split(day.StartTime, ":")
if len(startHourMinSlice) != 2 {
return errors.Error("time must be in the format HH:MM")
}
startHour, startMin := startHourMinSlice[0], startHourMinSlice[1]
startHourInt, err := strconv.Atoi(startHour)
if err != nil || startHourInt < 0 || startHourInt > 23 {
return errors.Error("start hour must be between 0 and 23")
}
startMinInt, err := strconv.Atoi(startMin)
if err != nil || !(startMinInt == 0 || startMinInt == 30) {
return errors.Error("start minute must be 0 or 30")
}
endHourMinSlice := strings.Split(day.EndTime, ":")
if len(endHourMinSlice) != 2 {
return errors.Error("time must be in the format HH:MM")
}
endHour, endMin := endHourMinSlice[0], endHourMinSlice[1]
endHourInt, err := strconv.Atoi(endHour)
if err != nil || endHourInt < 0 || endHourInt > 23 {
return errors.Error("end hour must be between 0 and 23")
}
endMinInt, err := strconv.Atoi(endMin)
if err != nil || !(endMinInt == 0 || endMinInt == 30 || endMinInt == 59) {
return errors.Error("end minute must be 0, 30 or 59")
}
return nil
}
func (t TradingHours) String() string {
var result strings.Builder
const numberOfDaysInWeek = 7
copyOfT := utils.DeepCopy(t).(TradingHours)
weekdays, publicHolidays := copyOfT[:numberOfDaysInWeek], copyOfT[numberOfDaysInWeek]
weekdays = append(weekdays, weekdays[0]) // Add the first day (Sunday) to the end because we want Monday to be first in the string
rangeStartIndex := 1
for i := 1; i < len(weekdays); i++ {
currentDay := weekdays[i]
nextDay := currentDay
if i+1 < len(weekdays) {
nextDay = weekdays[i+1]
var (
result strings.Builder
weekdays = []TradingHoursDay{t.Monday, t.Tuesday, t.Wednesday, t.Thursday, t.Friday, t.Saturday, t.Sunday}
rangeStartWeekday = time.Monday
)
for i := range weekdays {
currentTradingHoursDay := weekdays[i]
// Here the index is wrapped so we will never go out of bounds. The next day after Sunday is Monday at index 0 because (6+1)%7 = 0
nextTradingHoursDay := weekdays[(i+1)%len(weekdays)]
// Here we use the same value, as above, but for a different purpose of being compatible with the time package
// This is because the time package uses 0 for Sunday and 1 for Monday. This means when we get to index 6 (Sunday),
// to get the time.Weekday value for Sunday we need to use 0, and as before (6+1)%7 = 0
currentWeekday := time.Weekday((i + 1) % len(weekdays))
nextWeekday := time.Weekday((i + 2) % len(weekdays))
// Determine range description
var rangeDescription string
if currentWeekday == rangeStartWeekday {
rangeDescription = currentWeekday.String()[:3]
} else {
rangeDescription = fmt.Sprintf("%s – %s", rangeStartWeekday.String()[:3], currentWeekday.String()[:3])
}
// Determine times
var times string
if currentDay.StartTime != "" && currentDay.EndTime != "" {
startTime, err := time.Parse("15:04", currentDay.StartTime)
if err != nil {
return ""
}
// If the next day has the same times and we're not at the last element, we continue the current range
if nextTradingHoursDay.StartTime == currentTradingHoursDay.StartTime && nextTradingHoursDay.EndTime == currentTradingHoursDay.EndTime && i < len(weekdays)-1 {
continue
}
endTime, err := time.Parse("15:04", currentDay.EndTime)
if err != nil {
return ""
}
result.WriteString(fmt.Sprintf("%s: %s, ", rangeDescription, currentTradingHoursDay.String()))
rangeStartWeekday = nextWeekday
}
times = startTime.Format("3:04pm") + " – " + endTime.Format("3:04pm")
if currentDay.StartTime == "00:00" && currentDay.EndTime == "23:59" {
times = "All day"
}
} else {
times = "Closed"
}
// Public holidays
result.WriteString(fmt.Sprintf("Public holidays: %s", t.Holidays.String()))
// If we're at the last element or the next day doesn't have the same times, we end the current range
if i == len(weekdays)-1 || currentDay.StartTime != nextDay.StartTime || currentDay.EndTime != nextDay.EndTime {
if rangeStartIndex == i {
day := time.Weekday(rangeStartIndex).String()[:3]
if rangeStartIndex == numberOfDaysInWeek {
day = time.Sunday.String()[:3]
}
result.WriteString(fmt.Sprintf("%s: %s", day, times))
} else {
rangeStartDay := time.Weekday(rangeStartIndex).String()[:3]
rangeEndDay := time.Weekday(i).String()[:3]
if i == numberOfDaysInWeek {
rangeEndDay = time.Sunday.String()[:3]
}
result.WriteString(fmt.Sprintf("%s – %s: %s", rangeStartDay, rangeEndDay, times))
}
return result.String()
}
if i < len(weekdays)-1 {
result.WriteString(", ")
}
type TradingHoursDay struct {
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
}
rangeStartIndex = i + 1
}
func (day TradingHoursDay) String() string {
if day.StartTime == "" && day.EndTime == "" {
return "Closed"
}
// Public holidays
var times string
if publicHolidays.StartTime != "" && publicHolidays.EndTime != "" {
startTime, err := time.Parse("15:04", publicHolidays.StartTime)
if err != nil {
return ""
}
startTime, err := time.Parse("15:04", day.StartTime)
if err != nil {
return ""
}
endTime, err := time.Parse("15:04", publicHolidays.EndTime)
if err != nil {
return ""
}
endTime, err := time.Parse("15:04", day.EndTime)
if err != nil {
return ""
}
times = startTime.Format("3:04pm") + " – " + endTime.Format("3:04pm")
if publicHolidays.StartTime == "00:00" && publicHolidays.EndTime == "23:59" {
times = "All day"
}
} else {
times = "Closed"
if day.StartTime == "00:00" && day.EndTime == "23:59" {
return "All day"
}
result.WriteString(fmt.Sprintf(", Public holidays: %s", times))
return result.String()
return startTime.Format("3:04pm") + " – " + endTime.Format("3:04pm")
}
Loading