go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/internal/utils/time.go (about) 1 // Copyright 2019 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package utils 16 17 import ( 18 "fmt" 19 "time" 20 21 "github.com/golang/protobuf/ptypes" 22 "google.golang.org/protobuf/types/known/timestamppb" 23 ) 24 25 type Interval struct { 26 Start time.Time `json:"start,omitempty"` 27 End time.Time `json:"end,omitempty"` 28 Now time.Time `json:"now,omitempty"` 29 } 30 31 func (in Interval) Started() bool { 32 return !in.Start.IsZero() 33 } 34 35 func (in Interval) Ended() bool { 36 return !in.End.IsZero() 37 } 38 39 func (in Interval) Duration() time.Duration { 40 // An interval that hasn't started has no duration. 41 if !in.Started() { 42 return 0 43 } 44 45 end := in.End 46 if end.IsZero() { 47 // Open Interval 48 end = in.Now 49 } 50 51 // Don't return non-sensical values. 52 if d := end.Sub(in.Start); d > 0 { 53 return d 54 } 55 return 0 56 } 57 58 func ToInterval(start, end, now *timestamppb.Timestamp) (result Interval) { 59 if t, err := ptypes.Timestamp(start); err == nil { 60 result.Start = t 61 } 62 if t, err := ptypes.Timestamp(end); err == nil { 63 result.End = t 64 } 65 if t, err := ptypes.Timestamp(now); err == nil { 66 result.Now = t 67 } 68 return 69 } 70 71 // Duration returns duration between start and the earliest of ends. 72 // Ignores nil ends. 73 // Fallbacks to "N/A" on insufficient data. 74 func Duration(start *timestamppb.Timestamp, ends ...*timestamppb.Timestamp) string { 75 if start != nil { 76 startTime := start.AsTime() 77 78 var earliestEnd time.Time 79 for _, ts := range ends { 80 if ts == nil { 81 continue 82 } 83 t := ts.AsTime() 84 if earliestEnd.IsZero() || t.Before(earliestEnd) { 85 earliestEnd = t 86 } 87 } 88 89 if startTime.Before(earliestEnd) { 90 return HumanDuration(earliestEnd.Sub(startTime)) 91 } 92 } 93 94 return "N/A" 95 } 96 97 // HumanDuration translates d into a human readable string of x units y units, 98 // where x and y could be in days, hours, minutes, or seconds, whichever is the 99 // largest. 100 func HumanDuration(d time.Duration) string { 101 t := int64(d.Seconds()) 102 day := t / 86400 103 hr := (t % 86400) / 3600 104 105 if day > 0 { 106 if hr != 0 { 107 return fmt.Sprintf("%d days %d hrs", day, hr) 108 } 109 return fmt.Sprintf("%d days", day) 110 } 111 112 min := (t % 3600) / 60 113 if hr > 0 { 114 if min != 0 { 115 return fmt.Sprintf("%d hrs %d mins", hr, min) 116 } 117 return fmt.Sprintf("%d hrs", hr) 118 } 119 120 sec := t % 60 121 if min > 0 { 122 if sec != 0 { 123 return fmt.Sprintf("%d mins %d secs", min, sec) 124 } 125 return fmt.Sprintf("%d mins", min) 126 } 127 128 if sec != 0 { 129 return fmt.Sprintf("%d secs", sec) 130 } 131 132 if d > time.Millisecond { 133 return fmt.Sprintf("%d ms", d/time.Millisecond) 134 } 135 136 return "0" 137 }