ServerTimestamp.swift 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. /*
  2. * Copyright 2019 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import FirebaseFirestore
  17. #if compiler(>=5.1)
  18. /// A type that can initialize itself from a Firestore Timestamp, which makes
  19. /// it suitable for use with the `@ServerTimestamp` property wrapper.
  20. ///
  21. /// Firestore includes extensions that make `Timestamp`, `Date`, and `NSDate`
  22. /// conform to `ServerTimestampWrappable`.
  23. public protocol ServerTimestampWrappable {
  24. /// Creates a new instance by converting from the given `Timestamp`.
  25. ///
  26. /// - Parameter timestamp: The timestamp from which to convert.
  27. static func wrap(_ timestamp: Timestamp) throws -> Self
  28. /// Converts this value into a Firestore `Timestamp`.
  29. ///
  30. /// - Returns: A `Timestamp` representation of this value.
  31. static func unwrap(_ value: Self) throws -> Timestamp
  32. }
  33. extension Date: ServerTimestampWrappable {
  34. public static func wrap(_ timestamp: Timestamp) throws -> Self {
  35. return timestamp.dateValue()
  36. }
  37. public static func unwrap(_ value: Self) throws -> Timestamp {
  38. return Timestamp(date: value)
  39. }
  40. }
  41. extension NSDate: ServerTimestampWrappable {
  42. public static func wrap(_ timestamp: Timestamp) throws -> Self {
  43. let interval = timestamp.dateValue().timeIntervalSince1970
  44. return NSDate(timeIntervalSince1970: interval) as! Self
  45. }
  46. public static func unwrap(_ value: NSDate) throws -> Timestamp {
  47. return Timestamp(date: value as Date)
  48. }
  49. }
  50. extension Timestamp: ServerTimestampWrappable {
  51. public static func wrap(_ timestamp: Timestamp) throws -> Self {
  52. return timestamp as! Self
  53. }
  54. public static func unwrap(_ value: Timestamp) throws -> Timestamp {
  55. return value
  56. }
  57. }
  58. /// A property wrapper that marks an `Optional<Timestamp>` field to be
  59. /// populated with a server timestamp. If a `Codable` object being written
  60. /// contains a `nil` for an `@ServerTimestamp`-annotated field, it will be
  61. /// replaced with `FieldValue.serverTimestamp()` as it is sent.
  62. ///
  63. /// Example:
  64. /// ```
  65. /// struct CustomModel {
  66. /// @ServerTimestamp var ts: Timestamp?
  67. /// }
  68. /// ```
  69. ///
  70. /// Then writing `CustomModel(ts: nil)` will tell server to fill `ts` with
  71. /// current timestamp.
  72. @propertyWrapper
  73. public struct ServerTimestamp<Value>: Codable, Equatable
  74. where Value: ServerTimestampWrappable & Codable & Equatable {
  75. var value: Value?
  76. public init(wrappedValue value: Value?) {
  77. self.value = value
  78. }
  79. public var wrappedValue: Value? {
  80. get { value }
  81. set { value = newValue }
  82. }
  83. // MARK: Codable
  84. public init(from decoder: Decoder) throws {
  85. let container = try decoder.singleValueContainer()
  86. if container.decodeNil() {
  87. value = nil
  88. } else {
  89. value = try Value.wrap(try container.decode(Timestamp.self))
  90. }
  91. }
  92. public func encode(to encoder: Encoder) throws {
  93. var container = encoder.singleValueContainer()
  94. if let value = value {
  95. try container.encode(Value.unwrap(value))
  96. } else {
  97. try container.encode(FieldValue.serverTimestamp())
  98. }
  99. }
  100. }
  101. #endif // compiler(>=5.1)
  102. /// A compatibility version of `ServerTimestamp` that does not use property
  103. /// wrappers, suitable for use in older versions of Swift.
  104. ///
  105. /// Wraps a `Timestamp` field to mark that it should be populated with a server
  106. /// timestamp. If a `Codable` object being written contains a `.pending` for an
  107. /// `Swift4ServerTimestamp` field, it will be replaced with
  108. /// `FieldValue.serverTimestamp()` as it is sent.
  109. ///
  110. /// Example:
  111. /// ```
  112. /// struct CustomModel {
  113. /// var ts: Swift4ServerTimestamp
  114. /// }
  115. /// ```
  116. ///
  117. /// Then `CustomModel(ts: .pending)` will tell server to fill `ts` with current
  118. /// timestamp.
  119. @available(swift, deprecated: 5.1)
  120. public enum Swift4ServerTimestamp: Codable, Equatable {
  121. /// When being read (decoded) from Firestore, NSNull values will be mapped to
  122. /// `pending`. When being written (encoded) to Firestore, `pending` means
  123. /// requesting server to set timestamp on the field (essentially setting value
  124. /// to FieldValue.serverTimestamp()).
  125. case pending
  126. /// When being read (decoded) from Firestore, non-nil Timestamp will be mapped
  127. /// to `resolved`. When being written (encoded) to Firestore,
  128. /// `resolved(stamp)` will set the field value to `stamp`.
  129. case resolved(Timestamp)
  130. /// Returns this value as an `Optional<Timestamp>`.
  131. ///
  132. /// If the server timestamp is still pending, the returned optional will be
  133. /// `.none`. Once resolved, the returned optional will be `.some` with the
  134. /// resolved timestamp.
  135. public var timestamp: Timestamp? {
  136. switch self {
  137. case .pending:
  138. return .none
  139. case let .resolved(timestamp):
  140. return .some(timestamp)
  141. }
  142. }
  143. public init(from decoder: Decoder) throws {
  144. let container = try decoder.singleValueContainer()
  145. if container.decodeNil() {
  146. self = .pending
  147. } else {
  148. let value = try container.decode(Timestamp.self)
  149. self = .resolved(value)
  150. }
  151. }
  152. public func encode(to encoder: Encoder) throws {
  153. var container = encoder.singleValueContainer()
  154. switch self {
  155. case .pending:
  156. try container.encode(FieldValue.serverTimestamp())
  157. case let .resolved(value: value):
  158. try container.encode(value)
  159. }
  160. }
  161. }