github.com/go-chrono/chrono@v0.0.0-20240102183611-532f0d0d7c34/duration.go (about) 1 package chrono 2 3 import ( 4 "fmt" 5 "math/big" 6 "strconv" 7 ) 8 9 // Duration represents a period of time with nanosecond precision, 10 // with a range of approximately ±292,300,000,000 years. 11 type Duration struct { 12 v big.Int 13 } 14 15 // DurationOf creates a new duration from the supplied extent. 16 // Durations and extents are semantically equivalent, except that durations exceed, 17 // and can therefore not be converted to, Go's basic types. Extents are represented as a single integer. 18 func DurationOf(v Extent) Duration { 19 return durationOf(int64(v)) 20 } 21 22 func durationOf(v int64) Duration { 23 return Duration{v: *big.NewInt(v)} 24 } 25 26 // Compare compares d with d2. If d is less than d2, it returns -1; 27 // if d is greater than d2, it returns 1; if they're equal, it returns 0. 28 func (d Duration) Compare(d2 Duration) int { 29 return d.v.Cmp(&d2.v) 30 } 31 32 // Add returns the duration d+d2. 33 // If the operation would overflow the maximum duration, or underflow the minimum duration, it panics. 34 // Use CanAdd to test whether aa panic would occur. 35 func (d Duration) Add(d2 Duration) Duration { 36 out, err := d.add(d2) 37 if err != nil { 38 panic(err.Error()) 39 } 40 return out 41 } 42 43 // CanAdd returns false if Add would panic if passed the same argument. 44 func (d Duration) CanAdd(d2 Duration) bool { 45 _, err := d.add(d2) 46 return err == nil 47 } 48 49 func (d Duration) add(d2 Duration) (Duration, error) { 50 out := new(big.Int).Set(&d.v) 51 out.Add(out, &d2.v) 52 53 if out.Cmp(bigIntMinInt64) == -1 || out.Cmp(bigIntMaxInt64) == 1 { 54 return Duration{}, fmt.Errorf("duration out of range") 55 } 56 return Duration{v: *out}, nil 57 } 58 59 func (d Duration) mul(v int64) (Duration, error) { 60 out := new(big.Int).Set(&d.v) 61 out.Mul(out, big.NewInt(v)) 62 63 if out.Cmp(bigIntMinInt64) == -1 || out.Cmp(bigIntMaxInt64) == 1 { 64 return Duration{}, fmt.Errorf("duration out of range") 65 } 66 return Duration{v: *out}, nil 67 } 68 69 // Nanoseconds returns the duration as a floating point number of nanoseconds. 70 func (d Duration) Nanoseconds() float64 { 71 out, _ := new(big.Float).SetInt(&d.v).Float64() 72 return out 73 } 74 75 // Microseconds returns the duration as a floating point number of microseconds. 76 func (d Duration) Microseconds() float64 { 77 out, _ := new(big.Float).Quo(new(big.Float).SetInt(&d.v), bigFloatMicrosecondExtent).Float64() 78 return out 79 } 80 81 // Milliseconds returns the duration as a floating point number of milliseconds. 82 func (d Duration) Milliseconds() float64 { 83 out, _ := new(big.Float).Quo(new(big.Float).SetInt(&d.v), bigFloatMillisecondExtent).Float64() 84 return out 85 } 86 87 // Seconds returns the duration as a floating point number of seconds. 88 func (d Duration) Seconds() float64 { 89 out, _ := new(big.Float).Quo(new(big.Float).SetInt(&d.v), bigFloatSecondExtent).Float64() 90 return out 91 } 92 93 // Minutes returns the duration as a floating point number of minutes. 94 func (d Duration) Minutes() float64 { 95 out, _ := new(big.Float).Quo(new(big.Float).SetInt(&d.v), bigFloatMinuteExtent).Float64() 96 return out 97 } 98 99 // Hours returns the duration as a floating point number of hours. 100 func (d Duration) Hours() float64 { 101 out, _ := new(big.Float).Quo(new(big.Float).SetInt(&d.v), bigFloatHourExtent).Float64() 102 return out 103 } 104 105 // String returns a string formatted according to ISO 8601. 106 // It is equivalent to calling [Format] with no arguments. 107 func (d Duration) String() string { 108 return d.Format() 109 } 110 111 // Units returns the whole numbers of hours, minutes, seconds, and nanosecond offset represented by d. 112 func (d Duration) Units() (hours, mins, secs, nsec int) { 113 _secs, _nsec, _ := d.integers() 114 hours = int(_secs / 3600) 115 mins = int((_secs / 60) % 60) 116 secs = int(_secs % 60) 117 nsec = int(_nsec) 118 return 119 } 120 121 // Designator of date and time elements present in ISO 8601. 122 type Designator rune 123 124 // Designators. 125 const ( 126 Hours Designator = 'H' 127 Minutes Designator = 'M' 128 Seconds Designator = 'S' 129 ) 130 131 // Format the duration according to ISO 8601. 132 // The output consists of only the time component - the period component is never included. 133 // Thus the output always consists of "PT" followed by at least one unit of the time component (hours, minutes, seconds). 134 // 135 // The default format, obtained by calling the function with no arguments, consists of the most significant non-zero units, 136 // presented non-breaking but trimmed. 137 // 5 minutes is formatted as PT5M, trimming 0-value hours and seconds. 138 // 1 hour and 5 seconds is formatted at PT1H0M5S, ensuring the sequence of units is not broken. 139 // 140 // A list of designators can be optionally passed to the function in order to control which units are included. 141 // When passed, only those specified units are included in the formatted string, and are present regardless of whether their value is 0. 142 // 143 // Fractional values are automatically applied to the least significant unit, if applicable. 144 // In order to format only integers, the round functions should be used before calling this function. 145 func (d Duration) Format(exclusive ...Designator) string { 146 out, neg := d.format(exclusive...) 147 out = "P" + out 148 if neg { 149 out = "-" + out 150 } 151 return out 152 } 153 154 func (d Duration) format(exclusive ...Designator) (_ string, neg bool) { 155 secs, nsec, neg := d.integers() 156 return formatDuration(secs, nsec, neg, exclusive...) 157 } 158 159 func formatDuration(secs int64, nsec uint32, neg bool, exclusive ...Designator) (_ string, isNeg bool) { 160 values := make(map[Designator]float64, 3) 161 if len(exclusive) >= 1 { 162 for _, d := range exclusive { 163 values[d] = 0 164 } 165 } 166 167 _, h := values[Hours] 168 _, m := values[Minutes] 169 _, s := values[Seconds] 170 171 switch { 172 case len(exclusive) == 0: 173 if v := float64(secs / 3600); v != 0 { 174 values[Hours] = v 175 h = true 176 } 177 case h && (m || s): 178 values[Hours] = float64(secs / 3600) 179 case h: 180 values[Hours] = (float64(secs) / 3600) + (float64(nsec) / 3.6e12) 181 } 182 183 switch { 184 case len(exclusive) == 0: 185 if v := float64((secs % 3600) / 60); v != 0 { 186 values[Minutes] = v 187 m = true 188 } 189 case m && s && h: 190 values[Minutes] = float64((secs % 3600) / 60) 191 case m && s: 192 values[Minutes] = float64(secs / 60) 193 case m && h: 194 values[Minutes] = (float64(secs%3600) / 60) + (float64(nsec) / 6e10) 195 case m: 196 values[Minutes] = (float64(secs) / 60) + (float64(nsec) / 6e10) 197 } 198 199 switch { 200 case len(exclusive) == 0: 201 if v := float64(secs%60) + (float64(nsec) / 1e9); v != 0 { 202 values[Seconds] = v 203 if h && !m { 204 values[Minutes] = 0 205 } 206 } else if !h && !m { 207 values[Seconds] = 0 208 } 209 case s && m: 210 values[Seconds] = float64(secs%60) + (float64(nsec) / 1e9) 211 case s && h: 212 values[Seconds] = float64(secs%3600) + (float64(nsec) / 1e9) 213 case s: 214 values[Seconds] = float64(secs) + (float64(nsec) / 1e9) 215 } 216 217 out := "T" 218 if v, ok := values[Hours]; ok { 219 out += strconv.FormatFloat(v, 'f', -1, 64) + "H" 220 } 221 222 if v, ok := values[Minutes]; ok { 223 out += strconv.FormatFloat(v, 'f', -1, 64) + "M" 224 } 225 226 if v, ok := values[Seconds]; ok { 227 out += strconv.FormatFloat(v, 'f', -1, 64) + "S" 228 } 229 return out, neg 230 } 231 232 func (d Duration) integers() (secs int64, nsec uint32, neg bool) { 233 v := new(big.Int).Abs(&d.v) 234 var _nsec big.Int 235 _secs, _ := new(big.Int).DivMod(v, bigIntSecondExtent, &_nsec) 236 return _secs.Int64(), uint32(_nsec.Uint64()), d.v.Cmp(bigIntNegOne) == -1 237 } 238 239 // Parse the time portion of an ISO 8601 duration. 240 func (d *Duration) Parse(s string) error { 241 _, _, _, _, secs, nsec, neg, err := parseDuration(s, false, true) 242 if err != nil { 243 return err 244 } 245 246 *d = makeDuration(secs, nsec, neg) 247 return nil 248 } 249 250 // MinDuration returns the minimum supported duration. 251 func MinDuration() Duration { 252 return Duration{v: *bigIntMinInt64} 253 } 254 255 // MaxDuration returns the maximum supported duration. 256 func MaxDuration() Duration { 257 return Duration{v: *bigIntMaxInt64} 258 } 259 260 func makeDuration(secs int64, nsec uint32, neg bool) Duration { 261 out := new(big.Int).Mul(big.NewInt(secs), bigIntSecondExtent) 262 out.Add(out, big.NewInt(int64(nsec))) 263 if neg { 264 out.Neg(out) 265 } 266 return Duration{v: *out} 267 } 268 269 var ( 270 bigIntNegOne = big.NewInt(-1) 271 272 bigIntMinInt64 = new(big.Int).Lsh(big.NewInt(int64(-Second)), 63) 273 bigIntMaxInt64 = new(big.Int).Add(new(big.Int).Lsh(big.NewInt(int64(Second)), 63), big.NewInt(-1)) 274 275 bigIntSecondExtent = big.NewInt(int64(Second)) 276 277 bigFloatMicrosecondExtent = big.NewFloat(float64(Microsecond)) 278 bigFloatMillisecondExtent = big.NewFloat(float64(Millisecond)) 279 bigFloatSecondExtent = big.NewFloat(float64(Second)) 280 bigFloatMinuteExtent = big.NewFloat(float64(Minute)) 281 bigFloatHourExtent = big.NewFloat(float64(Hour)) 282 )