diff --git a/models/issues/stopwatch.go b/models/issues/stopwatch.go index 2c662bdb06a80..55734bdfd3a57 100644 --- a/models/issues/stopwatch.go +++ b/models/issues/stopwatch.go @@ -62,7 +62,7 @@ func (s Stopwatch) Seconds() int64 { // Duration returns a human-readable duration string based on local server time func (s Stopwatch) Duration() string { - return util.SecToTime(s.Seconds()) + return util.SecToHours(s.Seconds()) } func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) { @@ -215,7 +215,7 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss Doer: user, Issue: issue, Repo: issue.Repo, - Content: util.SecToTime(timediff), + Content: util.SecToHours(timediff), Type: CommentTypeStopTracking, TimeID: tt.ID, }); err != nil { diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 235fd96b73d8f..e6858d5e43f2a 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -67,7 +67,7 @@ func NewFuncMap() template.FuncMap { "TimeSince": timeutil.TimeSince, "TimeSinceUnix": timeutil.TimeSinceUnix, "DateTime": timeutil.DateTime, - "Sec2Time": util.SecToTime, + "Sec2Hours": util.SecToHours, "LoadTimes": func(startTime time.Time) string { return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" }, diff --git a/modules/util/sec_to_time.go b/modules/util/sec_to_time.go index ad0fb1a68b4a7..b88580dacc002 100644 --- a/modules/util/sec_to_time.go +++ b/modules/util/sec_to_time.go @@ -66,6 +66,25 @@ func SecToTime(durationVal any) string { return strings.TrimRight(formattedTime, " ") } +// SecToHours converts an amount of seconds to a human-readable hours string. +// This is sutable for planning and managing timesheets. +func SecToHours(durationVal any) string { + duration, _ := ToInt64(durationVal) + + formattedTime := "" + + // The following three variables are calculated without depending + // on the previous calculated variables. + hours := (duration / 3600) + minutes := (duration / 60) % 60 + + formattedTime = formatTime(hours, "hour", formattedTime) + formattedTime = formatTime(minutes, "minute", formattedTime) + + // The formatTime() function always appends a space at the end. This will be trimmed + return strings.TrimRight(formattedTime, " ") +} + // formatTime appends the given value to the existing forammattedTime. E.g: // formattedTime = "1 year" // input: value = 3, name = "month" diff --git a/modules/util/sec_to_time_test.go b/modules/util/sec_to_time_test.go index 4d1213a52c05e..cde8c4ca35b4a 100644 --- a/modules/util/sec_to_time_test.go +++ b/modules/util/sec_to_time_test.go @@ -28,3 +28,16 @@ func TestSecToTime(t *testing.T) { assert.Equal(t, "11 months", SecToTime(year-25*day)) assert.Equal(t, "1 year 5 months", SecToTime(year+163*day+10*hour+11*minute+5*second)) } + +func TestSecToHours(t *testing.T) { + second := int64(1) + minute := 60 * second + hour := 60 * minute + day := 24 * hour + + assert.Equal(t, "1 minute", SecToHours(minute+6*second)) + assert.Equal(t, "1 hour", SecToHours(hour)) + assert.Equal(t, "1 hour", SecToHours(hour+second)) + assert.Equal(t, "14 hours 33 minutes", SecToHours(14*hour+33*minute+30*second)) + assert.Equal(t, "156 hours 30 minutes", SecToHours(6*day+12*hour+30*minute+18*second)) +} diff --git a/routers/web/repo/issue_timetrack.go b/routers/web/repo/issue_timetrack.go index c9bf861b844b8..e288e1cc8f651 100644 --- a/routers/web/repo/issue_timetrack.go +++ b/routers/web/repo/issue_timetrack.go @@ -82,6 +82,6 @@ func DeleteTime(c *context.Context) { return } - c.Flash.Success(c.Tr("repo.issues.del_time_history", util.SecToTime(t.Time))) + c.Flash.Success(c.Tr("repo.issues.del_time_history", util.SecToHours(t.Time))) c.Redirect(issue.Link()) } diff --git a/services/convert/issue_comment.go b/services/convert/issue_comment.go index b034a50897190..cf3e4cfa036df 100644 --- a/services/convert/issue_comment.go +++ b/services/convert/issue_comment.go @@ -74,7 +74,7 @@ func ToTimelineComment(ctx context.Context, repo *repo_model.Repository, c *issu c.Content[0] == '|' { // TimeTracking Comments from v1.21 on store the seconds instead of an formated string // so we check for the "|" delimeter and convert new to legacy format on demand - c.Content = util.SecToTime(c.Content[1:]) + c.Content = util.SecToHours(c.Content[1:]) } } diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index effe4dcea9f8f..51df01c0ab9bf 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -88,7 +88,7 @@ {{svg "octicon-issue-opened" 16 "gt-mr-3"}} {{.ActiveStopwatch.RepoSlug}}#{{.ActiveStopwatch.IssueIndex}} - {{if .ActiveStopwatch}}{{Sec2Time .ActiveStopwatch.Seconds}}{{end}} + {{if .ActiveStopwatch}}{{Sec2Hours .ActiveStopwatch.Seconds}}{{end}}