CodableTimestamp.swift 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. // Copyright 2024 Google LLC
  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. import FirebaseCore
  15. import FirebaseSharedSwift
  16. import SwiftProtobuf
  17. /**
  18. * A protocol describing the encodable properties of a Timestamp.
  19. *
  20. * Note: this protocol exists as a workaround for the Swift compiler: if the Timestamp class
  21. * was extended directly to conform to Codable, the methods implementing the protocol would be need
  22. * to be marked required but that can't be done in an extension. Declaring the extension on the
  23. * protocol sidesteps this issue.
  24. */
  25. private protocol CodableTimestamp: Codable {
  26. var seconds: Int64 { get }
  27. var nanoseconds: Int32 { get }
  28. init(seconds: Int64, nanoseconds: Int32)
  29. }
  30. /** The keys in a Timestamp. Must match the properties of CodableTimestamp. */
  31. private enum TimestampKeys: String, CodingKey {
  32. case seconds
  33. case nanoseconds
  34. }
  35. /**
  36. * An extension of Timestamp that implements the behavior of the Codable protocol.
  37. *
  38. * Note: this is implemented manually here because the Swift compiler can't synthesize these methods
  39. * when declaring an extension to conform to Codable.
  40. */
  41. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  42. extension CodableTimestamp {
  43. public init(from decoder: any Swift.Decoder) throws {
  44. let container = try decoder.singleValueContainer()
  45. let timestampString = try container.decode(String.self).uppercased()
  46. guard CodableTimestampHelper.regex
  47. .firstMatch(in: timestampString, range: NSRange(location: 0,
  48. length: timestampString.count)) !=
  49. nil else {
  50. FirebaseLogger.dataConnect
  51. .error(
  52. "Timestamp string: \(timestampString) format doesn't support."
  53. )
  54. throw DataConnectError.invalidTimestampFormat
  55. }
  56. let buf: Google_Protobuf_Timestamp =
  57. try! Google_Protobuf_Timestamp(jsonString: "\"\(timestampString)\"")
  58. self.init(seconds: buf.seconds, nanoseconds: buf.nanos)
  59. }
  60. public func encode(to encoder: any Swift.Encoder) throws {
  61. // timestamp to string
  62. var container = encoder.singleValueContainer()
  63. let bufString = try! Google_Protobuf_Timestamp(seconds: seconds, nanos: nanoseconds)
  64. .jsonString()
  65. let timestampString = bufString.trimmingCharacters(in: CharacterSet(charactersIn: "\""))
  66. try container.encode(timestampString)
  67. }
  68. }
  69. /** Extends Timestamp to conform to Codable. */
  70. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  71. extension Timestamp: CodableTimestamp {}
  72. class CodableTimestampHelper {
  73. static let regex =
  74. try! NSRegularExpression(
  75. pattern: #"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{0,9})?(Z|[+-]\d{2}:\d{2})$"#
  76. )
  77. }