github.com/boki/go-xmp@v1.0.1/xmp/units.go (about) 1 // Copyright (c) 2017-2018 Alexander Eichhorn 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 // not use this file except in compliance with the License. You may obtain 5 // 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, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations 13 // under the License. 14 15 // Duration 16 // NullInt 17 // SplitInt (1:2:3:4) 18 // NullString 19 // NullBool 20 // NullFloat 21 // NullFloat64 22 23 package xmp 24 25 import ( 26 "bytes" 27 "encoding/json" 28 "errors" 29 "fmt" 30 "math" 31 "regexp" 32 "strconv" 33 "strings" 34 "time" 35 "unicode" 36 ) 37 38 var EEmptyValue = errors.New("empty value") 39 40 // Int 41 type NullInt int 42 43 func (i NullInt) Int() int { 44 return int(i) 45 } 46 47 func (i NullInt) Value() int { 48 return int(i) 49 } 50 51 func ParseNullInt(d string) (NullInt, error) { 52 switch d { 53 case "", "-", "--", "---", "NaN", "unknown": 54 return 0, EEmptyValue 55 default: 56 // parse integer 57 if i, err := strconv.ParseInt(d, 10, 64); err == nil { 58 return NullInt(i), nil 59 } 60 return 0, fmt.Errorf("xmp: parsing NullInt '%s': invalid syntax", d) 61 } 62 } 63 64 func (i NullInt) MarshalText() ([]byte, error) { 65 return []byte(strconv.Itoa(int(i))), nil 66 } 67 68 func (i *NullInt) UnmarshalText(data []byte) error { 69 v, err := ParseNullInt(string(data)) 70 if err == EEmptyValue { 71 return nil 72 } 73 if err != nil { 74 return err 75 } 76 *i = v 77 return nil 78 } 79 80 func (i NullInt) MarshalJSON() ([]byte, error) { 81 return i.MarshalText() 82 } 83 84 func (i *NullInt) UnmarshalJSON(data []byte) error { 85 if len(data) == 0 { 86 return nil 87 } 88 if data[0] == '"' { 89 return i.UnmarshalText(bytes.Trim(data, "\"")) 90 } 91 return i.UnmarshalText(data) 92 } 93 94 // 32bit Integer split byte-wise (06:13:14:61) 95 type SplitInt uint32 96 97 func (x SplitInt) Uint32() uint32 { 98 return uint32(x) 99 } 100 101 func (x SplitInt) Value() uint32 { 102 return uint32(x) 103 } 104 105 func (x *SplitInt) UnmarshalText(data []byte) error { 106 d := string(data) 107 switch d { 108 case "", "-", "--", "---", "NaN", "unknown": 109 return nil 110 default: 111 // parse integer 112 if i, err := strconv.ParseInt(d, 10, 32); err == nil { 113 *x = SplitInt(i) 114 } else if f := strings.Split(d, ":"); len(f) == 4 { 115 var u uint32 116 for i := 0; i < 4; i++ { 117 if v, err := strconv.ParseInt(f[i], 10, 8); err != nil { 118 return fmt.Errorf("xmp: parsing SplitInt '%s': invalid syntax", d) 119 } else { 120 u += uint32(v) << uint32((3-i)*8) 121 } 122 } 123 *x = SplitInt(u) 124 } else { 125 return fmt.Errorf("xmp: parsing SplitInt '%s': invalid syntax", d) 126 } 127 } 128 return nil 129 } 130 131 // String 132 type NullString string 133 134 func (s NullString) String() string { 135 return string(s) 136 } 137 138 func (s NullString) Value() string { 139 return string(s) 140 } 141 142 func ParseNullString(s string) (NullString, error) { 143 s = strings.TrimSpace(s) 144 switch s { 145 case "", "-", "--", "unknown": 146 return "", EEmptyValue 147 default: 148 return NullString(s), nil 149 } 150 } 151 152 func (s NullString) MarshalText() ([]byte, error) { 153 return []byte(s.String()), nil 154 } 155 156 func (s *NullString) UnmarshalText(data []byte) error { 157 // only overwrite when not set 158 if len(*s) > 0 { 159 return nil 160 } 161 // only overwrite when not empty 162 p, err := ParseNullString(string(data)) 163 if err == EEmptyValue { 164 return nil 165 } 166 if err != nil { 167 return err 168 } 169 *s = p 170 return nil 171 } 172 173 // Bool 174 type NullBool bool 175 176 func (x NullBool) Value() bool { 177 return bool(x) 178 } 179 180 func ParseNullBool(d string) (NullBool, error) { 181 switch strings.ToLower(d) { 182 case "", "-", "--", "---": 183 return NullBool(false), EEmptyValue 184 case "true", "on", "yes", "1", "enabled": 185 return NullBool(true), nil 186 default: 187 return NullBool(false), nil 188 } 189 } 190 191 func (x NullBool) MarshalText() ([]byte, error) { 192 return []byte(strconv.FormatBool(bool(x))), nil 193 } 194 195 func (x *NullBool) UnmarshalText(data []byte) error { 196 v, err := ParseNullBool(string(data)) 197 if err == EEmptyValue { 198 return nil 199 } 200 if err != nil { 201 return err 202 } 203 *x = v 204 return nil 205 } 206 207 func (x NullBool) MarshalJSON() ([]byte, error) { 208 return x.MarshalText() 209 } 210 211 func (x *NullBool) UnmarshalJSON(data []byte) error { 212 if len(data) == 0 { 213 return nil 214 } 215 if data[0] == '"' { 216 return x.UnmarshalText(bytes.Trim(data, "\"")) 217 } 218 return x.UnmarshalText(data) 219 } 220 221 // Duration 222 type Duration time.Duration 223 224 func (d Duration) Duration() time.Duration { 225 return time.Duration(d) 226 } 227 228 func (d Duration) String() string { 229 return time.Duration(d).String() 230 } 231 232 func ParseDuration(d string) (Duration, error) { 233 // parse integer values as seconds 234 if i, err := strconv.ParseInt(d, 10, 64); err == nil { 235 return Duration(time.Duration(i) * time.Second), nil 236 } 237 // parse as duration string (note: no whitespace allowed) 238 if i, err := time.ParseDuration(d); err == nil { 239 return Duration(i), nil 240 } 241 // parse as duration string with whitespace removed 242 d = strings.Map(func(r rune) rune { 243 if unicode.IsSpace(r) { 244 return -1 245 } 246 return r 247 }, d) 248 if i, err := time.ParseDuration(d); err == nil { 249 return Duration(i), nil 250 } 251 return 0, fmt.Errorf("xmp: parsing duration '%s': invalid syntax", d) 252 } 253 254 func (d Duration) MarshalText() ([]byte, error) { 255 return []byte(time.Duration(d).String()), nil 256 } 257 258 func (d *Duration) UnmarshalText(data []byte) error { 259 i, err := ParseDuration(string(data)) 260 if err != nil { 261 return err 262 } 263 *d = i 264 return nil 265 } 266 267 func (d *Duration) UnmarshalJSON(data []byte) error { 268 if len(data) == 0 { 269 return nil 270 } 271 if data[0] == '"' { 272 return d.UnmarshalText(bytes.Trim(data, "\"")) 273 } 274 if i, err := strconv.ParseInt(string(data), 10, 64); err == nil { 275 *d = Duration(time.Duration(i) * time.Second) 276 return nil 277 } 278 return fmt.Errorf("xmp: parsing duration '%s': invalid syntax", string(data)) 279 } 280 281 func (d Duration) Truncate(r time.Duration) Duration { 282 if d > 0 { 283 return Duration(math.Ceil(float64(d)/float64(r))) * Duration(r) 284 } else { 285 return Duration(math.Floor(float64(d)/float64(r))) * Duration(r) 286 } 287 } 288 289 func (d Duration) RoundToDays() int { 290 return int(d.Truncate(time.Hour*24) / Duration(time.Hour*24)) 291 } 292 293 func (d Duration) RoundToHours() int64 { 294 return int64(d.Truncate(time.Hour) / Duration(time.Hour)) 295 } 296 297 func (d Duration) RoundToMinutes() int64 { 298 return int64(d.Truncate(time.Minute) / Duration(time.Minute)) 299 } 300 301 func (d Duration) RoundToSeconds() int64 { 302 return int64(d.Truncate(time.Second) / Duration(time.Second)) 303 } 304 305 func (d Duration) RoundToMillisecond() int64 { 306 return int64(d.Truncate(time.Millisecond) / Duration(time.Millisecond)) 307 } 308 309 // Float32 310 type NullFloat float32 311 312 func (f NullFloat) Float32() float32 { 313 return float32(f) 314 } 315 316 func (f NullFloat) Value() float64 { 317 return float64(f) 318 } 319 320 var cleanFloat = regexp.MustCompile("[^0-9e.+-]") 321 322 func ParseNullFloat(d string) (NullFloat, error) { 323 switch strings.ToLower(d) { 324 case "", "-", "--", "---", "unknown": 325 return 0, EEmptyValue 326 case "nan": 327 return NullFloat(math.NaN()), nil 328 case "-inf": 329 return NullFloat(math.Inf(-1)), nil 330 case "+inf", "inf": 331 return NullFloat(math.Inf(1)), nil 332 default: 333 // parse float 334 c := cleanFloat.ReplaceAllString(d, "") 335 if i, err := strconv.ParseFloat(c, 32); err == nil { 336 return NullFloat(i), nil 337 } 338 return 0, fmt.Errorf("xmp: parsing NullFloat '%s': invalid syntax", d) 339 } 340 } 341 342 func (f NullFloat) MarshalText() ([]byte, error) { 343 return []byte(strconv.FormatFloat(f.Value(), 'f', -1, 32)), nil 344 } 345 346 func (f *NullFloat) UnmarshalText(data []byte) error { 347 v, err := ParseNullFloat(string(data)) 348 if err == EEmptyValue { 349 return nil 350 } 351 if err != nil { 352 return err 353 } 354 *f = v 355 return nil 356 } 357 358 // JSON does not define NaN, +Inf and -Inf 359 func (f NullFloat) MarshalJSON() ([]byte, error) { 360 switch { 361 case math.IsNaN(f.Value()): 362 return []byte("\"NaN\""), nil 363 case math.IsInf(f.Value(), -1): 364 return []byte("\"+Inf\""), nil 365 case math.IsInf(f.Value(), 1): 366 return []byte("\"-Inf\""), nil 367 default: 368 return json.Marshal(f.Value()) 369 } 370 } 371 372 func (f *NullFloat) UnmarshalJSON(data []byte) error { 373 if len(data) == 0 { 374 return nil 375 } 376 if data[0] == '"' { 377 return f.UnmarshalText(bytes.Trim(data, "\"")) 378 } 379 return f.UnmarshalText(data) 380 } 381 382 // Float64 383 type NullFloat64 float64 384 385 func (f NullFloat64) Float64() float64 { 386 return float64(f) 387 } 388 389 func (f NullFloat64) Value() float64 { 390 return float64(f) 391 } 392 393 func ParseNullFloat64(d string) (NullFloat64, error) { 394 switch strings.ToLower(d) { 395 case "", "-", "--", "---", "unknown": 396 return 0, EEmptyValue 397 case "nan": 398 return NullFloat64(math.NaN()), nil 399 case "-inf": 400 return NullFloat64(math.Inf(-1)), nil 401 case "+inf", "inf": 402 return NullFloat64(math.Inf(1)), nil 403 default: 404 // parse float 405 c := cleanFloat.ReplaceAllString(d, "") 406 if i, err := strconv.ParseFloat(c, 64); err == nil { 407 return NullFloat64(i), nil 408 } 409 return 0, fmt.Errorf("xmp: parsing NullFloat64 '%s': invalid syntax", d) 410 } 411 } 412 413 func (f NullFloat64) MarshalText() ([]byte, error) { 414 return []byte(strconv.FormatFloat(f.Value(), 'f', -1, 64)), nil 415 } 416 417 func (f *NullFloat64) UnmarshalText(data []byte) error { 418 v, err := ParseNullFloat64(string(data)) 419 if err == EEmptyValue { 420 return nil 421 } 422 if err != nil { 423 return err 424 } 425 *f = v 426 return nil 427 } 428 429 // JSON does not define NaN, +Inf and -Inf 430 func (f NullFloat64) MarshalJSON() ([]byte, error) { 431 switch { 432 case math.IsNaN(f.Value()): 433 return []byte("\"NaN\""), nil 434 case math.IsInf(f.Value(), -1): 435 return []byte("\"+Inf\""), nil 436 case math.IsInf(f.Value(), 1): 437 return []byte("\"-Inf\""), nil 438 default: 439 return json.Marshal(f.Value()) 440 } 441 } 442 443 func (f *NullFloat64) UnmarshalJSON(data []byte) error { 444 if len(data) == 0 { 445 return nil 446 } 447 if data[0] == '"' { 448 return f.UnmarshalText(bytes.Trim(data, "\"")) 449 } 450 return f.UnmarshalText(data) 451 } 452 453 func Max(x, y int) int { 454 if x < y { 455 return y 456 } else { 457 return x 458 } 459 } 460 461 func Min(x, y int) int { 462 if x > y { 463 return y 464 } else { 465 return x 466 } 467 } 468 469 func Max64(x, y int64) int64 { 470 if x < y { 471 return y 472 } else { 473 return x 474 } 475 } 476 477 func Min64(x, y int64) int64 { 478 if x > y { 479 return y 480 } else { 481 return x 482 } 483 }