LocalDate.swift 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  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 Foundation
  15. /*
  16. LocalDate represents an ISO-8601 formatted date without time components.
  17. Essentially represents: https://the-guild.dev/graphql/scalars/docs/scalars/local-date
  18. */
  19. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  20. public struct LocalDate: Codable, Equatable, CustomStringConvertible {
  21. private var calendar = Calendar(identifier: .gregorian)
  22. private var dateFormatter = DateFormatter()
  23. private var date = Date()
  24. private let components: Set<Calendar.Component> = [.year, .month, .day]
  25. // default initializing here to suppress a false compiler error of "used before initializing"
  26. // the date components will get actually initialized in various initializers.
  27. private var dateComponents: DateComponents = .init()
  28. public init(year: Int, month: Int, day: Int) throws {
  29. dateComponents = DateComponents(year: year, month: month, day: day)
  30. dateComponents.calendar = calendar
  31. guard dateComponents.isValidDate,
  32. let date = dateComponents.date else {
  33. throw DataConnectError.invalidLocalDateFormat
  34. }
  35. self.date = date
  36. setupDateFormat()
  37. }
  38. public init(date: Date) {
  39. dateComponents = calendar.dateComponents(components, from: date)
  40. self.date = calendar.date(from: dateComponents)!
  41. setupDateFormat()
  42. }
  43. // localDateString of format: YYYY-MM-DD
  44. public init(localDateString: String) throws {
  45. setupDateFormat()
  46. date = try convert(dateString: localDateString)
  47. dateComponents = calendar.dateComponents(components, from: date)
  48. }
  49. public var description: String {
  50. return dateFormatter.string(from: date)
  51. }
  52. // MARK: Private
  53. private func setupDateFormat() {
  54. dateFormatter.dateFormat = "yyyy-MM-dd"
  55. }
  56. private func convert(dateString: String) throws -> Date {
  57. guard let date = dateFormatter.date(from: dateString) else {
  58. throw DataConnectError.invalidLocalDateFormat
  59. }
  60. return date
  61. }
  62. }
  63. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  64. extension LocalDate: Codable {
  65. public init(from decoder: any Decoder) throws {
  66. let container = try decoder.singleValueContainer()
  67. let localDateString = try container.decode(String.self)
  68. setupDateFormat()
  69. date = try convert(dateString: localDateString)
  70. dateComponents = calendar.dateComponents(components, from: date)
  71. }
  72. public func encode(to encoder: any Encoder) throws {
  73. var container = encoder.singleValueContainer()
  74. let formattedDate = dateFormatter.string(from: date)
  75. try container.encode(formattedDate)
  76. }
  77. }
  78. // MARK: Equatable, Comparable
  79. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  80. extension LocalDate: Comparable, Equatable {
  81. public static func < (lhs: LocalDate, rhs: LocalDate) -> Bool {
  82. return lhs.date < rhs.date
  83. }
  84. public static func == (lhs: LocalDate, rhs: LocalDate) -> Bool {
  85. return lhs.date == rhs.date
  86. }
  87. }
  88. // MARK: Hashable
  89. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  90. extension LocalDate: Hashable {
  91. public func hash(into hasher: inout Hasher) {
  92. hasher.combine(date)
  93. }
  94. }