Skip to content
Snippets Groups Projects
Commit daab1fd2 authored by Daniel Naude's avatar Daniel Naude
Browse files

Refactor TradingHours struct and validation

Refactor the TradingHours struct to have separate fields for each day of the week and holidays. Also, update the validation logic to validate each day individually.
parent 622734c4
Branches
Tags
1 merge request!51Refactor TradingHours struct and validation
This commit is part of merge request !51. Comments created here will be created in the context of that merge request.
......@@ -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 {
return nil
}
func (day TradingHoursDay) Validate() error {
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")
return errors.Error("start time must be before end time")
}
if len(day.StartTime) != 5 || len(day.EndTime) != 5 {
return errors.Error("Time must be in the format HH:MM")
return errors.Error("time must be in the format HH:MM")
}
startHourMinSlice := strings.Split(day.StartTime, ":")
if len(startHourMinSlice) != 2 {
return errors.Error("Time must be in the format HH:MM")
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")
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")
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")
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")
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 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]
}
// Determine times
var times string
if currentDay.StartTime != "" && currentDay.EndTime != "" {
startTime, err := time.Parse("15:04", currentDay.StartTime)
if err != nil {
return ""
}
var (
result strings.Builder
weekdays = []TradingHoursDay{t.Monday, t.Tuesday, t.Wednesday, t.Thursday, t.Friday, t.Saturday, t.Sunday}
rangeStartWeekday = time.Monday
)
endTime, err := time.Parse("15:04", currentDay.EndTime)
if err != nil {
return ""
}
for i := range weekdays {
currentTradingHoursDay := weekdays[i]
times = startTime.Format("3:04pm") + " – " + endTime.Format("3:04pm")
if currentDay.StartTime == "00:00" && currentDay.EndTime == "23:59" {
times = "All day"
}
// 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 {
times = "Closed"
rangeDescription = fmt.Sprintf("%s – %s", rangeStartWeekday.String()[:3], currentWeekday.String()[:3])
}
// 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]
// 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
}
result.WriteString(fmt.Sprintf("%s – %s: %s", rangeStartDay, rangeEndDay, times))
result.WriteString(fmt.Sprintf("%s: %s, ", rangeDescription, currentTradingHoursDay.String()))
rangeStartWeekday = nextWeekday
}
if i < len(weekdays)-1 {
result.WriteString(", ")
// Public holidays
result.WriteString(fmt.Sprintf("Public holidays: %s", t.Holidays.String()))
return result.String()
}
rangeStartIndex = i + 1
type TradingHoursDay struct {
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
}
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)
startTime, err := time.Parse("15:04", day.StartTime)
if err != nil {
return ""
}
endTime, err := time.Parse("15:04", publicHolidays.EndTime)
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"
if day.StartTime == "00:00" && day.EndTime == "23:59" {
return "All day"
}
} else {
times = "Closed"
}
result.WriteString(fmt.Sprintf(", Public holidays: %s", times))
return result.String()
return startTime.Format("3:04pm") + " – " + endTime.Format("3:04pm")
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment