duration.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. /*
  2. *
  3. * Copyright 2023 gRPC authors.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. package serviceconfig
  19. import (
  20. "encoding/json"
  21. "fmt"
  22. "math"
  23. "strconv"
  24. "strings"
  25. "time"
  26. )
  27. // Duration defines JSON marshal and unmarshal methods to conform to the
  28. // protobuf JSON spec defined [here].
  29. //
  30. // [here]: https://protobuf.dev/reference/protobuf/google.protobuf/#duration
  31. type Duration time.Duration
  32. func (d Duration) String() string {
  33. return fmt.Sprint(time.Duration(d))
  34. }
  35. // MarshalJSON converts from d to a JSON string output.
  36. func (d Duration) MarshalJSON() ([]byte, error) {
  37. ns := time.Duration(d).Nanoseconds()
  38. sec := ns / int64(time.Second)
  39. ns = ns % int64(time.Second)
  40. var sign string
  41. if sec < 0 || ns < 0 {
  42. sign, sec, ns = "-", -1*sec, -1*ns
  43. }
  44. // Generated output always contains 0, 3, 6, or 9 fractional digits,
  45. // depending on required precision.
  46. str := fmt.Sprintf("%s%d.%09d", sign, sec, ns)
  47. str = strings.TrimSuffix(str, "000")
  48. str = strings.TrimSuffix(str, "000")
  49. str = strings.TrimSuffix(str, ".000")
  50. return []byte(fmt.Sprintf("\"%ss\"", str)), nil
  51. }
  52. // UnmarshalJSON unmarshals b as a duration JSON string into d.
  53. func (d *Duration) UnmarshalJSON(b []byte) error {
  54. var s string
  55. if err := json.Unmarshal(b, &s); err != nil {
  56. return err
  57. }
  58. if !strings.HasSuffix(s, "s") {
  59. return fmt.Errorf("malformed duration %q: missing seconds unit", s)
  60. }
  61. neg := false
  62. if s[0] == '-' {
  63. neg = true
  64. s = s[1:]
  65. }
  66. ss := strings.SplitN(s[:len(s)-1], ".", 3)
  67. if len(ss) > 2 {
  68. return fmt.Errorf("malformed duration %q: too many decimals", s)
  69. }
  70. // hasDigits is set if either the whole or fractional part of the number is
  71. // present, since both are optional but one is required.
  72. hasDigits := false
  73. var sec, ns int64
  74. if len(ss[0]) > 0 {
  75. var err error
  76. if sec, err = strconv.ParseInt(ss[0], 10, 64); err != nil {
  77. return fmt.Errorf("malformed duration %q: %v", s, err)
  78. }
  79. // Maximum seconds value per the durationpb spec.
  80. const maxProtoSeconds = 315_576_000_000
  81. if sec > maxProtoSeconds {
  82. return fmt.Errorf("out of range: %q", s)
  83. }
  84. hasDigits = true
  85. }
  86. if len(ss) == 2 && len(ss[1]) > 0 {
  87. if len(ss[1]) > 9 {
  88. return fmt.Errorf("malformed duration %q: too many digits after decimal", s)
  89. }
  90. var err error
  91. if ns, err = strconv.ParseInt(ss[1], 10, 64); err != nil {
  92. return fmt.Errorf("malformed duration %q: %v", s, err)
  93. }
  94. for i := 9; i > len(ss[1]); i-- {
  95. ns *= 10
  96. }
  97. hasDigits = true
  98. }
  99. if !hasDigits {
  100. return fmt.Errorf("malformed duration %q: contains no numbers", s)
  101. }
  102. if neg {
  103. sec *= -1
  104. ns *= -1
  105. }
  106. // Maximum/minimum seconds/nanoseconds representable by Go's time.Duration.
  107. const maxSeconds = math.MaxInt64 / int64(time.Second)
  108. const maxNanosAtMaxSeconds = math.MaxInt64 % int64(time.Second)
  109. const minSeconds = math.MinInt64 / int64(time.Second)
  110. const minNanosAtMinSeconds = math.MinInt64 % int64(time.Second)
  111. if sec > maxSeconds || (sec == maxSeconds && ns >= maxNanosAtMaxSeconds) {
  112. *d = Duration(math.MaxInt64)
  113. } else if sec < minSeconds || (sec == minSeconds && ns <= minNanosAtMinSeconds) {
  114. *d = Duration(math.MinInt64)
  115. } else {
  116. *d = Duration(sec*int64(time.Second) + ns)
  117. }
  118. return nil
  119. }