Users can sell items and add an expiration date that varies from 1-3 weeks out from current date.
This is how I store the expiration date as a Double
. Do I need to be explicit about other date components like year, month, etc?
enum Weeks: Int {
case one
case two
case three
}
extension Weeks {
var timeInterval: Double? {
let currentDate = Date()
let calendar = Calendar.current
let calendarComponents: Set<Calendar.Component> = Set(arrayLiteral: Calendar.Component.year, Calendar.Component.month, Calendar.Component.day, Calendar.Component.hour, Calendar.Component.minute, Calendar.Component.second)
var components = calendar.dateComponents(calendarComponents, from: currentDate)
switch self {
case .one: components.weekOfMonth = 1
case .two: components.weekOfMonth = 2
case .three: components.weekOfMonth = 3
}
if let expirationDate = calendar.date(from: components) {
return expirationDate.timeIntervalSince1970 as Double
} else {
return nil
}
}
}
This is how I calculate the countdown in Date
extension:
func countdown(to date: Date) -> CountdownResponse {
let difference = Calendar.current.dateComponents([.day, .hour, .minute, .second], from: self, to: date)
guard let day = difference.day, let hour = difference.hour, let minute = difference.minute, let second = difference.second else {
return .error(message: "One or more date components are nil.")
}
if day <= 0 && hour <= 0 && minute <= 0 && second <= 0 {
return .isFinished
}
let days = displayableText(from: difference.day)
let hours = displayableText(from: difference.hour)
let minutes = displayableText(from: difference.minute)
let seconds = displayableText(from: difference.second)
let timeRemaining = "\(days) : \(hours) : \(minutes) : \(seconds)"
return .result(time: timeRemaining)
}
This is how I'd like to get the countdown, and then I check the response enum for error, or result. But this gives me incorrect time.
let expirationDate = Date(timeIntervalSince1970: self.item.expirationDate)
let response = currentDate.countdown(to: expirationDate)
When I create date manually and test like below, it works as expected.
var comp = calendar.dateComponents(calendarComponents, from: Date())
comp.year = 2017
comp.month = 8
comp.day = 24
comp.minute = 50
comp.second = 30
What am I doing wrong? Am I persisting date as time interval incorrectly, or creating date from timeInterval incorrectly?
As I look over the code I realize that I may be calculating the week wrong. If it's the second week of the month and a user chooses for item to expire in 3 weeks, week of month shouldn't be 3, it needs to be 3 weeks from current week of month. Any guidance on how to fix this logic is appreciated.
Why did you make your code so complicated? The Foundation framework provide everything you need, from calendrical calculation (add and subtract dates) to output formatting (x days, y hours, z minutes, etc.).
Here's an example of how you can shorten it:
enum Week: Int {
case one = 7, two = 14, three = 21
}
enum CountdownResponse {
case isFinished
case result(time: String)
}
struct ProductListing {
// Customize this if you want to change timeRemaining's format
// It automatically take care of singular vs. plural, i.e. 1 hr and 2 hrs
private static let dateComponentFormatter: DateComponentsFormatter = {
var formatter = DateComponentsFormatter()
formatter.allowedUnits = [.day, .hour, .minute, .second]
formatter.unitsStyle = .short
return formatter
}()
var listingDate: Date
var duration: Week
var expirationDate: Date {
return Calendar.current.date(byAdding: .day, value: duration.rawValue, to: listingDate)!
}
var timeRemaining: CountdownResponse {
let now = Date()
if expirationDate <= now {
return .isFinished
} else {
let timeRemaining = ProductListing.dateComponentFormatter.string(from: now, to: expirationDate)!
return .result(time: timeRemaining)
}
}
}
// Usage
let august01 = DateComponents(calendar: .current, year: 2017, month: 8, day: 1).date!
let august19 = DateComponents(calendar: .current, year: 2017, month: 8, day: 19).date!
let listing1 = ProductListing(listingDate: august01, duration: .three)
let listing2 = ProductListing(listingDate: august19, duration: .one)
print(listing1.timeRemaining) // .isFinished
print(listing2.timeRemaining) // .result("2 days, 4 hrs, 9 min, 23 secs")
Note though, the calculation becomes real hairy when it crosses over the clock change days as the time shifts back and forth. I haven't test these edge cases with the code above.