Birthday.swift 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. /*
  2. * Copyright 2021 Google LLC
  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 Foundation
  17. /// A model type representing the current user's birthday.
  18. struct Birthday: Decodable {
  19. fileprivate let integerDate: Birthday.IntegerDate
  20. /// The birthday as a `Date`.
  21. var date: Date? {
  22. let now = Date()
  23. let currentCalendar = Calendar.autoupdatingCurrent
  24. let currentYear = currentCalendar.dateComponents([.year], from: now)
  25. let comps = DateComponents(calendar: Calendar.autoupdatingCurrent,
  26. timeZone: TimeZone.autoupdatingCurrent,
  27. year: currentYear.year,
  28. month: integerDate.month,
  29. day: integerDate.day)
  30. guard let d = comps.date, comps.isValidDate else { return nil }
  31. if d < now {
  32. var nextYearComponent = DateComponents()
  33. nextYearComponent.year = 1
  34. let bdayNextYear = currentCalendar.date(byAdding: .year, value: 1, to: d)
  35. return bdayNextYear
  36. } else {
  37. return d
  38. }
  39. }
  40. init(from decoder: Decoder) throws {
  41. let container = try decoder.container(keyedBy: CodingKeys.self)
  42. self.integerDate = try container.decode(IntegerDate.self, forKey: .integerDate)
  43. }
  44. init(integerDate: IntegerDate) {
  45. self.integerDate = integerDate
  46. }
  47. static var noBirthday: Birthday? {
  48. return Birthday(integerDate: IntegerDate(month: .min, day: .min))
  49. }
  50. }
  51. extension Birthday {
  52. /// A nested type representing the month and day values of a birthday as integers.
  53. struct IntegerDate: Decodable {
  54. let month: Int
  55. let day: Int
  56. }
  57. }
  58. extension Birthday {
  59. enum CodingKeys: String, CodingKey {
  60. case integerDate = "date"
  61. }
  62. }
  63. extension Birthday: CustomStringConvertible {
  64. /// Converts the instances `date` to a `String`.
  65. var description: String {
  66. return date?.description ?? "No birthday"
  67. }
  68. }
  69. /// A model type representing the response from the request for the current user's birthday.
  70. struct BirthdayResponse: Decodable {
  71. /// The requested user's birthdays.
  72. let birthdays: [Birthday]
  73. /// The first birthday in the returned results.
  74. /// - note: We only care about the birthday's month and day, and so we just use the first
  75. /// birthday in the results.
  76. let firstBirthday: Birthday
  77. init(from decoder: Decoder) throws {
  78. let container = try decoder.container(keyedBy: CodingKeys.self)
  79. self.birthdays = try container.decode([Birthday].self, forKey: .birthdays)
  80. guard let first = birthdays.first else {
  81. throw Error.noBirthdayInResult
  82. }
  83. self.firstBirthday = first
  84. }
  85. }
  86. extension BirthdayResponse {
  87. enum CodingKeys: String, CodingKey {
  88. case birthdays
  89. }
  90. }
  91. extension BirthdayResponse {
  92. /// An error representing what may go wrong in processing the birthday request.
  93. enum Error: Swift.Error {
  94. /// There was no birthday in the returned results.
  95. case noBirthdayInResult
  96. }
  97. }
  98. /*
  99. {
  100. "resourceName": "people/111941908710159755740",
  101. "etag": "%EgQBBy43GgQBAgUHIgxvOUdlOWN5d3lmZz0=",
  102. "birthdays": [
  103. {
  104. "metadata": {
  105. "primary": true,
  106. "source": {
  107. "type": "PROFILE",
  108. "id": "111941908710159755740"
  109. }
  110. },
  111. "date": {
  112. "month": 5,
  113. "day": 31
  114. }
  115. },
  116. {
  117. "metadata": {
  118. "source": {
  119. "type": "ACCOUNT",
  120. "id": "111941908710159755740"
  121. }
  122. },
  123. "date": {
  124. "year": 1982,
  125. "month": 5,
  126. "day": 31
  127. }
  128. }
  129. ]
  130. }
  131. */