Add Workdays Using Go
Go time package has extensive support for manipulating times and durations. For example, we can use the time package to find the next business days. Let’s create a small case for this.
Study Case
We want to create a Gantt chart data for our to-do app project. We will break down the tasks into a sequential task.
- UI/UX design (5 days)
- Implementing the frontend (3 days)
- Implementing the backend (2 days)
Let’s assume that this project will start on 9 August 2021. So this project will end on 20 August 2021.
Data Structure
Each task will have these types
package main
type Task struct {
// Name of the task e.g. UI/UX design, FE, BE, etc
Name string
// Duration of the task
Duration int
}
and let’s mix them up into an array of Task
package main
var tasks = []Task{
{Name: "UI/UX Design", Duration: 5},
{Name: "FE Implementation", Duration: 3},
{Name: "BE Implementation", Duration: 2},
}
Don’t forget to create the Gantt chart data structure too
package main
type Data struct {
Name string
StartDate time.Time
EndDate time.Time
}
var data []Data
Implementation
There are lots of solutions out there and recursive is the neat solution to handle this case. Let’s start with the function param signature first
func AddWorkdays(date time.Time, days int) time.Time {
...
}
we have date
as time.Time
which we will use as our start date. And we have days
as int
which we will use as how many days we want to add.
Since we’re doing recursive, we will add a do...while
loop and add a stopping point to it. So we won’t have an infinite loop. The idea is we will decrement the days
on each iteration and we will stop if the days
value is zero.
func AddWorkdays(date time.Time, days int) time.Time {
for {
if (days == 0) {
return date
}
...
days--
}
}
Now come to the fun parts. The main idea of this function is we keep adding more days until the days
are zero. And it will skip the weekend days (Saturday and Sunday) to Monday.
func AddWorkdays(date time.Time, days int) time.Time {
for {
if (days == 0) {
return date
}
date = date.AddDate(0, 0, 1)
if date.Weekday() == time.Saturday {
date = date.AddDate(0, 0, 2)
return AddWorkdays(date, days-1)
} else if date.Weekday() == time.Sunday {
date = date.AddDate(0, 0, 1)
return AddWorkdays(date, days-1)
}
days--
}
}
The logic is quite simple
- Add one day on each iteration
- If the current date (after addition) is Saturday, we will add two more days. So it will skip to Monday
- It’s like the logic above. But, we only add one more day if the current date is Sunday
- Before return back to the same function, we decrement the
days
by one - Once it comes to the next iteration, the
date
already has the addition to it. And thedays
already had the subtraction - And the function will continue until the
days
is zero
Please note that the function will always end up with one more day than intended. For example, if we add 4 days from 9 August 2021, it will end up at 13 August 2021.
The Calculation
Now let’s do the calculation for adding workdays. Let’s begin with iterate the tasks
and put the calculated date to our data []Data
. Don’t forget that our start date is from 9 August 2021.
...
firstDate, _ := time.Parse("2006-01-02 00:00:00", "2021-08-09 00:00:00")
var data []Data
for k, v := range tasks {
d:= Data{}
// let's assume that the first index is the start date
if k == 0 {
d.StartDate = firstDate
d.EndDate = AddWorkdays(firstDate , v.Duration-1)
} else {
start := data[k-1].EndDate
startDate := AddWorkdays(start, 1)
d.StartDate = startDate
d.EndDate = AddWorkdays(starDate, v.Duration-1)
}
data = append(data, d)
}
...
The logic is like this
- We iterate the
tasks
- We will assume that the first index is always the start date of the project
- If it’s the first day, we will assign the
StartDate
with thefirstDate
. For theEndDate
, we will use theAddWorkdays
function to add days based on duration (v.Duration
). Don’t forget to subtract by one day since the function has a gotcha - If it’s the rest of the days, we will use the
EndDate
from the last element as the start date. And we will add one day so the start date won’t clash with the end date from the previous element. TheEndDate
use the same logic as above - Append the calculated data to the
[]Data
If we want to print the result, we can use a JSON pretty print (json.MarshalIndent
).
Result
- go
- json
1 |
|