Google_Protobuf_Duration+Extensions.swift 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. // Sources/SwiftProtobuf/Google_Protobuf_Duration+Extensions.swift - Extensions for Duration type
  2. //
  3. // Copyright (c) 2014 - 2016 Apple Inc. and the project authors
  4. // Licensed under Apache License v2.0 with Runtime Library Exception
  5. //
  6. // See LICENSE.txt for license information:
  7. // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
  8. //
  9. // -----------------------------------------------------------------------------
  10. ///
  11. /// Extends the generated Duration struct with various custom behaviors:
  12. /// * JSON coding and decoding
  13. /// * Arithmetic operations
  14. ///
  15. // -----------------------------------------------------------------------------
  16. import Foundation
  17. private let minDurationSeconds: Int64 = -maxDurationSeconds
  18. private let maxDurationSeconds: Int64 = 315_576_000_000
  19. private func parseDuration(text: String) throws -> (Int64, Int32) {
  20. var digits = [Character]()
  21. var digitCount = 0
  22. var total = 0
  23. var chars = text.makeIterator()
  24. var seconds: Int64?
  25. var nanos: Int32 = 0
  26. var isNegative = false
  27. while let c = chars.next() {
  28. switch c {
  29. case "-":
  30. // Only accept '-' as very first character
  31. if total > 0 {
  32. throw JSONDecodingError.malformedDuration
  33. }
  34. digits.append(c)
  35. isNegative = true
  36. case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
  37. digits.append(c)
  38. digitCount += 1
  39. case ".":
  40. if let _ = seconds {
  41. throw JSONDecodingError.malformedDuration
  42. }
  43. let digitString = String(digits)
  44. if let s = Int64(digitString),
  45. s >= minDurationSeconds && s <= maxDurationSeconds
  46. {
  47. seconds = s
  48. } else {
  49. throw JSONDecodingError.malformedDuration
  50. }
  51. digits.removeAll()
  52. digitCount = 0
  53. case "s":
  54. if let _ = seconds {
  55. // Seconds already set, digits holds nanos
  56. while digitCount < 9 {
  57. digits.append(Character("0"))
  58. digitCount += 1
  59. }
  60. while digitCount > 9 {
  61. digits.removeLast()
  62. digitCount -= 1
  63. }
  64. let digitString = String(digits)
  65. if let rawNanos = Int32(digitString) {
  66. if isNegative {
  67. nanos = -rawNanos
  68. } else {
  69. nanos = rawNanos
  70. }
  71. } else {
  72. throw JSONDecodingError.malformedDuration
  73. }
  74. } else {
  75. // No fraction, we just have an integral number of seconds
  76. let digitString = String(digits)
  77. if let s = Int64(digitString),
  78. s >= minDurationSeconds && s <= maxDurationSeconds
  79. {
  80. seconds = s
  81. } else {
  82. throw JSONDecodingError.malformedDuration
  83. }
  84. }
  85. // Fail if there are characters after 's'
  86. if chars.next() != nil {
  87. throw JSONDecodingError.malformedDuration
  88. }
  89. return (seconds!, nanos)
  90. default:
  91. throw JSONDecodingError.malformedDuration
  92. }
  93. total += 1
  94. }
  95. throw JSONDecodingError.malformedDuration
  96. }
  97. private func formatDuration(seconds: Int64, nanos: Int32) -> String? {
  98. let (seconds, nanos) = normalizeForDuration(seconds: seconds, nanos: nanos)
  99. guard seconds >= minDurationSeconds && seconds <= maxDurationSeconds else {
  100. return nil
  101. }
  102. let nanosString = nanosToString(nanos: nanos) // Includes leading '.' if needed
  103. if seconds == 0 && nanos < 0 {
  104. return "-0\(nanosString)s"
  105. }
  106. return "\(seconds)\(nanosString)s"
  107. }
  108. extension Google_Protobuf_Duration {
  109. /// Creates a new `Google_Protobuf_Duration` equal to the given number of
  110. /// seconds and nanoseconds.
  111. ///
  112. /// - Parameter seconds: The number of seconds.
  113. /// - Parameter nanos: The number of nanoseconds.
  114. public init(seconds: Int64 = 0, nanos: Int32 = 0) {
  115. self.init()
  116. self.seconds = seconds
  117. self.nanos = nanos
  118. }
  119. }
  120. extension Google_Protobuf_Duration: _CustomJSONCodable {
  121. mutating func decodeJSON(from decoder: inout JSONDecoder) throws {
  122. let s = try decoder.scanner.nextQuotedString()
  123. (seconds, nanos) = try parseDuration(text: s)
  124. }
  125. func encodedJSONString(options: JSONEncodingOptions) throws -> String {
  126. if let formatted = formatDuration(seconds: seconds, nanos: nanos) {
  127. return "\"\(formatted)\""
  128. } else {
  129. throw JSONEncodingError.durationRange
  130. }
  131. }
  132. }
  133. extension Google_Protobuf_Duration: ExpressibleByFloatLiteral {
  134. public typealias FloatLiteralType = Double
  135. /// Creates a new `Google_Protobuf_Duration` from a floating point literal
  136. /// that is interpreted as a duration in seconds, rounded to the nearest
  137. /// nanosecond.
  138. public init(floatLiteral value: Double) {
  139. let sd = trunc(value)
  140. let nd = round((value - sd) * TimeInterval(nanosPerSecond))
  141. let (s, n) = normalizeForDuration(seconds: Int64(sd), nanos: Int32(nd))
  142. self.init(seconds: s, nanos: n)
  143. }
  144. }
  145. extension Google_Protobuf_Duration {
  146. /// Creates a new `Google_Protobuf_Duration` that is equal to the given
  147. /// `TimeInterval` (measured in seconds), rounded to the nearest nanosecond.
  148. ///
  149. /// - Parameter timeInterval: The `TimeInterval`.
  150. public init(timeInterval: TimeInterval) {
  151. let sd = trunc(timeInterval)
  152. let nd = round((timeInterval - sd) * TimeInterval(nanosPerSecond))
  153. let (s, n) = normalizeForDuration(seconds: Int64(sd), nanos: Int32(nd))
  154. self.init(seconds: s, nanos: n)
  155. }
  156. /// The `TimeInterval` (measured in seconds) equal to this duration.
  157. public var timeInterval: TimeInterval {
  158. TimeInterval(self.seconds) + TimeInterval(self.nanos) / TimeInterval(nanosPerSecond)
  159. }
  160. }
  161. private func normalizeForDuration(
  162. seconds: Int64,
  163. nanos: Int32
  164. ) -> (seconds: Int64, nanos: Int32) {
  165. var s = seconds
  166. var n = nanos
  167. // If the magnitude of n exceeds a second then
  168. // we need to factor it into s instead.
  169. if n >= nanosPerSecond || n <= -nanosPerSecond {
  170. s += Int64(n / nanosPerSecond)
  171. n = n % nanosPerSecond
  172. }
  173. // The Duration spec says that when s != 0, s and
  174. // n must have the same sign.
  175. if s > 0 && n < 0 {
  176. n += nanosPerSecond
  177. s -= 1
  178. } else if s < 0 && n > 0 {
  179. n -= nanosPerSecond
  180. s += 1
  181. }
  182. return (seconds: s, nanos: n)
  183. }
  184. public prefix func - (
  185. operand: Google_Protobuf_Duration
  186. ) -> Google_Protobuf_Duration {
  187. let (s, n) = normalizeForDuration(
  188. seconds: -operand.seconds,
  189. nanos: -operand.nanos
  190. )
  191. return Google_Protobuf_Duration(seconds: s, nanos: n)
  192. }
  193. public func + (
  194. lhs: Google_Protobuf_Duration,
  195. rhs: Google_Protobuf_Duration
  196. ) -> Google_Protobuf_Duration {
  197. let (s, n) = normalizeForDuration(
  198. seconds: lhs.seconds + rhs.seconds,
  199. nanos: lhs.nanos + rhs.nanos
  200. )
  201. return Google_Protobuf_Duration(seconds: s, nanos: n)
  202. }
  203. public func - (
  204. lhs: Google_Protobuf_Duration,
  205. rhs: Google_Protobuf_Duration
  206. ) -> Google_Protobuf_Duration {
  207. let (s, n) = normalizeForDuration(
  208. seconds: lhs.seconds - rhs.seconds,
  209. nanos: lhs.nanos - rhs.nanos
  210. )
  211. return Google_Protobuf_Duration(seconds: s, nanos: n)
  212. }
  213. public func - (
  214. lhs: Google_Protobuf_Timestamp,
  215. rhs: Google_Protobuf_Timestamp
  216. ) -> Google_Protobuf_Duration {
  217. let (s, n) = normalizeForDuration(
  218. seconds: lhs.seconds - rhs.seconds,
  219. nanos: lhs.nanos - rhs.nanos
  220. )
  221. return Google_Protobuf_Duration(seconds: s, nanos: n)
  222. }