Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.GetTimeOffsMapped(): function to get time-offs with accurate times #49

Merged
merged 1 commit into from Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add `v1.GetTimeOffsMapped()` function

## [0.3.0] - 2023-07-05

### Added
Expand Down
46 changes: 46 additions & 0 deletions v1/personio.go
Expand Up @@ -11,6 +11,8 @@ import (
"strconv"
"strings"
"time"

util "github.com/giantswarm/personio-go"
)

const DefaultBaseUrl = "https://api.personio.de/v1"
Expand Down Expand Up @@ -530,3 +532,47 @@ func (personio *Client) GetTimeOffs(start *time.Time, end *time.Time, offset int

return timeOffs, nil
}

// GetTimeOffsMapped returns a slice of timeOffs with times mapped from HalfDayStart/HalfDayEnd/DaysCount
func (personio *Client) GetTimeOffsMapped(start time.Time, end time.Time) ([]*TimeOff, error) {

timeOffs, err := personio.GetTimeOffs(&start, &end, 0, 2147483647)
if err != nil {
return nil, err
}

var matchedTimeOffs []*TimeOff
for _, timeOff := range timeOffs {

// we need to adjust the wall-clock time according to other fields
if timeOff.DaysCount > 1 {
if timeOff.HalfDayStart {
timeOff.StartDate = timeOff.StartDate.Add(time.Hour * 12)
}
if timeOff.HalfDayEnd {
timeOff.EndDate = timeOff.EndDate.Add(time.Hour * 12)
} else {
timeOff.EndDate = timeOff.EndDate.Add(time.Hour * 24)
}
} else {
if timeOff.HalfDayStart && !timeOff.HalfDayEnd {
timeOff.EndDate = timeOff.EndDate.Add(time.Hour * 12)
} else if !timeOff.HalfDayStart && timeOff.HalfDayEnd {
timeOff.StartDate = timeOff.StartDate.Add(time.Hour * 12)
timeOff.EndDate = timeOff.EndDate.Add(time.Hour * 24)
} else {
timeOff.EndDate = timeOff.EndDate.Add(time.Hour * 24)
}
}

overlap := util.GetTimeIntersection(timeOff.StartDate, timeOff.EndDate, start, end)
if overlap <= 0 {
// timeOff and queried region doesn't overlap
continue
}

matchedTimeOffs = append(matchedTimeOffs, timeOff)
}

return matchedTimeOffs, nil
}
76 changes: 73 additions & 3 deletions v1/personio_test.go
Expand Up @@ -101,12 +101,17 @@ func (p *PersonioMock) PersonioMockHandler(w http.ResponseWriter, req *http.Requ
var errEnd error
if startArg != "" {
start, errStart = time.Parse(queryDateFormat, startArg)
// Personio seems to report more events around the selected start date
// try to match that behavior
start = start.Add(time.Second*24*60*60*-1 - 1)
} else {
start = time.Time{}
}

if endArg != "" {
end, errEnd = time.Parse(queryDateFormat, endArg)
// Personio seems to report more events around the selected end date
end = end.Add(time.Second*24*60*60 - 1)
} else {
end = util.PersonioDateMax
}
Expand Down Expand Up @@ -466,7 +471,7 @@ type timeOffTestCase struct {
func TestClient_GetTimeOffs(t *testing.T) {

tsTooEarly := makeTime("1971-01-01T00:00:00Z")
tsEarly := makeTime("2022-09-06T05:00:00Z")
tsEarly := makeTime("2022-09-05T05:00:00Z")
tsMiddle := makeTime("2022-09-08T06:00:00Z")
tsMiddlePlus6h := tsMiddle.Add(makeDuration("6h"))
tsLate := makeTime("2022-09-10T05:00:00Z")
Expand All @@ -475,9 +480,9 @@ func TestClient_GetTimeOffs(t *testing.T) {
{start: nil, end: nil, wantIds: []int64{125814620, 125682392}},
{start: nil, end: &tsTooEarly, wantIds: []int64{}},
{start: nil, end: &tsEarly, wantIds: []int64{125814620}},
{start: &tsLate, end: &util.PersonioDateMax, wantIds: []int64{125682392}},
{start: &tsLate, end: &util.PersonioDateMax, wantIds: []int64{125682392, 125682393}},
{start: &tsMiddle, end: &tsMiddlePlus6h, wantIds: []int64{125682392, 125814620}},
{start: &tsTooLate, end: nil, wantIds: []int64{}},
{start: &tsTooLate, end: nil, wantIds: []int64{125682393}},
}

server, err := newTestServer()
Expand Down Expand Up @@ -547,3 +552,68 @@ func TestClient_GetTimeOffs(t *testing.T) {
}
}
}

func TestClient_GetTimeOffsMapped(t *testing.T) {

tsStart := makeTime("2022-09-10T00:00:00+02:00")
tsEnd := makeTime("2022-12-01T00:00:00+02:00")
tsStart2 := makeTime("2022-12-01T00:00:00+02:00")
tsEnd2 := makeTime("2022-12-01T12:01:00+02:00")
tsStart3 := makeTime("2022-12-01T00:00:00+02:00")
tsEnd3 := makeTime("2022-12-01T12:00:00+02:00")
timeOffCases := []timeOffTestCase{
{start: &tsStart, end: &tsEnd, wantIds: []int64{125682392}},
{start: &tsStart2, end: &tsEnd2, wantIds: []int64{125682393}},
{start: &tsStart3, end: &tsEnd3, wantIds: []int64{}},
}

server, err := newTestServer()
if err != nil {
t.Errorf("Failed to setup mock Personio server: failed to listen: %s", err)
return
}

defer func() {
_ = server.Close()
}()

personioCredentials := Credentials{ClientId: "abc", ClientSecret: "def"}
personio, err := NewClient(context.TODO(), fmt.Sprintf("http://localhost:%d", server.port), personioCredentials)
if err != nil {
t.Errorf("Failed to create Personio API v1 client: %s", err)
return
}

for testNumber, testCase := range timeOffCases {
timeOffs, err := personio.GetTimeOffsMapped(*testCase.start, *testCase.end)
if err != nil {
t.Errorf("[%d] Failed to query time-offs: %s", testNumber, err)
continue
}

if len(testCase.wantIds) != len(timeOffs) {
t.Errorf("[%d] Expected %d time-offs, got %d", testNumber, len(testCase.wantIds), len(timeOffs))
continue
}

for _, timeOff := range timeOffs {
if len(timeOff.Comment) <= 0 {
t.Errorf("[%d] Time-off with ID %d has empty comment", testNumber, timeOff.Id)
}
}

for _, id := range testCase.wantIds {
found := false
for i := range timeOffs {
if timeOffs[i].Id == id {
found = true
break
}
}
if !found {
t.Errorf("[%d] Time-off with ID %d not found in time-offs", testNumber, id)
break
}
}
}
}
56 changes: 56 additions & 0 deletions v1/testdata/time-offs-body.json
Expand Up @@ -112,6 +112,62 @@
"created_at": "2022-07-26T11:52:55+02:00",
"updated_at": "2022-10-02T19:13:09+02:00"
}
},
{
"type": "TimeOffPeriod",
"attributes": {
"id": 125682393,
"status": "approved",
"comment": "later-this-year",
"start_date": "2022-12-01T00:00:00+02:00",
"end_date": "2022-12-01T00:00:00+02:00",
"days_count": 0.5,
"half_day_start": 0,
"half_day_end": 1,
"time_off_type": {
"type": "TimeOffType",
"attributes": {
"id": 155627,
"name": "Vacation",
"category": "paid_vacation"
}
},
"employee": {
"type": "Employee",
"attributes": {
"id": {
"label": "ID",
"value": 6205887,
"type": "integer",
"universal_id": "id"
},
"first_name": {
"label": "First name",
"value": "El",
"type": "standard",
"universal_id": "first_name"
},
"last_name": {
"label": "Last name",
"value": "Gonzo",
"type": "standard",
"universal_id": "last_name"
},
"email": {
"label": "Email",
"value": "gonzo@giantswarm.io",
"type": "standard",
"universal_id": "email"
}
}
},
"created_by": "El Gonzo",
"certificate": {
"status": "not-required"
},
"created_at": "2022-07-26T11:52:55+02:00",
"updated_at": "2022-10-02T19:13:09+02:00"
}
}
]
}