How to tell if a business is open after midnight - swift

I've looked at this question, but a lot of it doesn't make sense nor work:
How to determine if a business is open given the hours of operation (Swift-iOS)
Some places on my list open at like 7:30 am and close the next day at 4 am. I have the times on my Parse-server listed as such:
openTime (Number): 7.5 (for 7:30 am)
closeTime (Number): and 4 for (4 am)
However, when I use the logic from the linked questions,
if now.hour! > Int(openTime) && now.hour! < Int(closeTime) {}
it keeps saying that business is closed. How could I adjust the numbers or the logic, in order to make it work for places that close late night / early morning the next day?

You can consider that closeTime must be superior to openTime, otherwise its the day after.
so it will become something like:
let realCloseTime = closeTime < openTime ? closeTime + 24 : closeTime
if now.hour! > Int(openTime) && now.hour! < Int(realCloseTime) {}

You will have a problem with minutes. Stores that open at 7:30 will report being open at 7:00. Stores that close at 11:30 will report being closed at 11:00.
Assuming that open and close are doubles.
var openTime: Double
var closeTime: Double
then
func isOpen(at date: Date = Date()) -> Bool {
guard let openDate = createDate(bySettingHours: openTime, of: date) else { return false }
guard let closeDate = createDate(bySettingHours: closeTime, of: date) else { return false }
guard let adjustedCloseDate = add24Hours(to: closeDate) else { return false }
let realCloseDate = openDate < closeDate ? closeDate : adjustedCloseDate
return openDate <= date && date <= realCloseDate
}
private func createDate(bySettingHours double: Double, of date: Date) -> Date? {
let hour = Int(floor(double)) % 24
let minute = Int(double * 30) % 30
return Calendar.current.date(bySettingHour: hour, minute: minute, second: 0, of: date)
}
private func add24Hours(to date: Date) -> Date? {
return Calendar.current.date(byAdding: .hour, value: 24, to: date)
}
I was assuming you have some kind of model like
class Business {
var openTime: Double
var closeTime: Double
}
My suggestion was to add the isOpen(at:) method here.
class Business {
var openTime: Double
var closeTime: Double
func isOpen(at date: Date = Date()) -> Bool {
// implmentation
}
}
It would be used something like this
var business = Business()
// Setup `business`
business.isOpen()
// or
let now = Date()
business.isOpen(at: now)

Related

Check if between two dates is no longer working under iOS16.3

Using iOS16.3, XCode14.2, Swift5.7.2,
Why is the following method no longer working ?
I call this method by setting date = Date() and maximumDate = Date() as well...
According to this solution, it should work - but it doesn't
public class THManager : ObservableObject {
#Published public var minimumDate: Date = Date()
#Published public var maximumDate: Date = Date()
public func isBetweenMinAndMaxDates(date: Date) -> Bool {
print(min(minimumDate, maximumDate))
print(max(minimumDate, maximumDate))
print(min(minimumDate, maximumDate)...max(minimumDate, maximumDate))
print(date)
print((min(minimumDate, maximumDate)...max(minimumDate, maximumDate)).contains(date))
return (min(minimumDate, maximumDate)...max(minimumDate, maximumDate)).contains(date)
}
}
2022-02-08 19:45:51 +0000
2023-02-03 19:45:51 +0000
2022-02-08 19:45:51 +0000...2023-02-03 19:45:51 +0000
2023-02-03 19:45:51 +0000
false
It supposed to return true ! Why does it return false ???
By the way it works if date = Date() and maximumDate = Date().addingTimeInterval(1)
Very strange, isn't it ?
There is no need for such complexity. Date objects conform to the Comparable and Equatable protocols, so testing for a date being between 2 other dates is one line:
extension Date {
func between(start: Date, end: Date) -> Bool {
return self > start && self < end
}
}
You'd use that like this:
let date = Date()
if date.betweeen(start: someDate, end: someOtherDate) {
// the date is between the start and the end
} else {
// The date is not between the start and end dates
}
The above will only return true if the date in question is not equal to start or end date. You could easily change it to match dates that match the beginning and end dates by using >= and <= instead of > and < in the comparisons.
And as discussed in the comments, Date objects have sub-millisecond precision. Two dates that appear identical may be different by a tiny fraction of a second, the best way to verify your comparisons is to convert your Dates to decimal seconds and log the seconds values. (See the Date property timeIntervalSinceReferenceDate.)
Edit:
Check out this sample code using the above exension:
extension Date {
func between(start: Date, end: Date) -> Bool {
return self > start && self < end
}
var asStringWithDecimal: String {
return DateFormatter.localizedString(from: self, dateStyle: .medium, timeStyle: .medium) + " ( \(self.timeIntervalSinceReferenceDate) seconds)"
}
}
let now = Date()
for _ in (1...5) {
let random = Double.random(in: -1.5...1.5)
let test = now.advanced(by: random)
let start = now.advanced(by: -1)
let end = now.advanced(by: 1)
let isBetween = test.between(start: start, end: end)
let isOrNot = isBetween ? "is" : "is not"
let output = "\(test.asStringWithDecimal) \n \(isOrNot) between \n \(start.asStringWithDecimal) and \n \(end.asStringWithDecimal)"
print(output)
}
That will generate 5 random dates ± 1.5 seconds from the current date, and then test each one to see if it is within 1 second of the current date. It logs the result as both Date strings and Doubles, so you can see what's happening when the seconds match (but the fractions of a second likely don't match.)

Swift Playground : How do I write a while function which doubles each day?

I need to create a loop which will show pennies each day and total for the months end. I couldn't create the loop as the playground keeps on multiplying infinitely.
Great question! There are definitely a lot of ways to solve this problem, but if I understand your question - this is how I would solve it.
Step 1 - Date Extension
First, I would start by making an extension to Date.
extension Date {
/// The month component of the provided date.
var month: Int {
return Calendar.current.component(.month, from: self)
}
/// Exactly one day before the provided date.
var prevDay: Date {
return Calendar.current.date(byAdding: .day, value: -1, to: self)!
}
/// Exactly one day after the provided date.
var nextDay: Date {
return Calendar.current.date(byAdding: .day, value: 1, to: self)!
}
}
These variables will make some of the later work, easier.
Step 2 - Variables
Then we can setup the variables.
First, we get the start date - I am just instantiating a new Date object which defaults to right now.
Along with that, we will define a variable where we can keep track of where we are in the iteration.
Next, we need somewhere to keep track of the values over time.
Lastly, a value to hold the starting value.
let startDate = Date() // Today, now.
var iterDate = startDate
var vals: [Date: Int] = [:]
let startingValue: Int = 1
Step 3 - The Loop
Now, the fun part - the loop. This part will be documented in the code.
// Execute the loop until the end of the start date's month.
while iterDate.month == startDate.month {
// First, check if this is the first iteration -
if vals.count == 0 {
// If so, there is nothing to double, so we just set the starting value.
vals[iterDate] = startingValue
} else {
// If there are already values - get the previous days value, double it, and save.
if let val = vals[iterDate.prevDay] {
vals[iterDate] = val * 2
}
}
// Lastly, move to the next day.
iterDate = iterDate.nextDay
}
Step 4 - Final Value
Now that we have a dictionary of all of the values, as they grow, we can get the month-end value. First, sort the dictionary - then get the value. Getting the value this way means that you don't need to know the date.
let sortedVals = vals.sorted(by: { $0.0 < $1.0 })
if let monthEnd = sortedVals.last {
let monthEndVal = monthEnd.1
// Use the value, here.
}
There it is - hope that solves the problem!
Full Code
import Foundation
extension Date {
/// The month component of the provided date.
var month: Int {
return Calendar.current.component(.month, from: self)
}
/// Exactly one day before the provided date.
var prevDay: Date {
return Calendar.current.date(byAdding: .day, value: -1, to: self)!
}
/// Exactly one day after the provided date.
var nextDay: Date {
return Calendar.current.date(byAdding: .day, value: 1, to: self)!
}
}
let startDate = Date() // Today, now.
var iterDate = startDate
var vals: [Date: Int] = [:]
let startingValue: Int = 1
// Execute the loop until the end of the start date's month.
while iterDate.month == startDate.month {
// First, check if this is the first iteration -
if vals.count == 0 {
// If so, there is nothing to double, so we just set the starting value.
vals[iterDate] = startingValue
} else {
// If there are already values - get the previous days value, double it, and save.
if let val = vals[iterDate.prevDay] {
vals[iterDate] = val * 2
}
}
// Lastly, move to the next day.
iterDate = iterDate.nextDay
}
let sortedVals = vals.sorted(by: { $0.0 < $1.0 })
if let monthEnd = sortedVals.last {
let monthEndVal = monthEnd.1
// Use the value, here.
}

Automatically increase number everyday / Swift

I coded a SwiftUI App, and the User can choose how much days are left. For example he choose 20 days, it should count in a Circle. For example 10 days are done, the circle should be at 50%.
So I thought I create a Int that is everyday increasing by one. When the user choose 20 days, the Int should start to increase and after 10 days for example the Int is at 10 and because the user choose 20 days, the circle should be at 50%.
Days / Int(that is increasing)
But I dont know how to code the Int that is increasing everyday.
Could anyone help me?
Adding to the suggestion of storing the start date and using today's date to know how many days have passed, you can use this extension
extension Date {
func daysSinceDate(_ fromDate: Date = Date()) -> Int {
let earliest = self < fromDate ? self : fromDate
let latest = (earliest == self) ? fromDate : self
let earlierComponents:DateComponents = Calendar.current.dateComponents([.day], from: earliest)
let laterComponents:DateComponents = Calendar.current.dateComponents([.day], from: latest)
guard
let earlierDay = earlierComponents.day,
let laterDay = laterComponents.day,
laterDay >= earlierDay
else {
return 0
}
return laterDay - earlierDay
}
func dateForDaysFromNow(_ days: Int) -> Date? {
var dayComponent = DateComponents()
dayComponent.day = days
return Calendar.current.date(byAdding: dayComponent, to: self)
}
}

How can I structure this code better? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 5 years ago.
Improve this question
I really want to know how much more structure you can do for a task like this in a class cause right now I'm in shock that my answer wasn't enough for this company's interview question. They literally asked me to take a date and see if it is between today and 5 days prior. So let me know how much more needs to be done here.
The date in question is passed to a function. That function takes today's date and then you compare the date in question from 5 days prior to today's date. If the date in question is ascending from the 5-days-prior and then if the date is descending from today's date the function returns true. So someone let me know where I went wrong here.
func compareDate(thisDate:Date,aheadOfDate:Date, beforeThisDate:Date) -> Bool{
if( thisDate < aheadOfDate){
return false
}
if(thisDate > beforeThisDate){
return false
}
return true
}
func daysFromToday(days:Int) -> Date{
let calendar = Calendar.current
let newDate = calendar.date(byAdding: .day, value: days, to: Date.init())
return newDate!
}
let todaysDate = Date.init()
let fiveDaysAgo = daysFromToday(days:-5)
print(compareDate(thisDate: daysFromToday(days: 1), aheadOfDate: fiveDaysAgo, beforeThisDate: todaysDate))
It would be simpler to subtract the given date from today's date and see if the difference in days is between 0 and 5. Also, IDK if they care, but the compareDate function could instead be a one-line boolean interpretation.
First function
I would restructure into an extension on Date:
extension Date {
func isBetween(_ first: Date, and second: Date) -> Bool {
return first < self && self < second
}
}
Then I would replace this a < b && b < c pattern with a range expression a...c ~= b, which creates a date range, and checks if self is in it:
extension Date {
func isBetween(_ first: Date, and second: Date) -> Bool {
return first...second ~= self
}
}
Second function
I would also restructure into an extension on Date, and I would inline calendar and newDate because they add visual clutter, without providing any new information. I would also replace Date.init() with just Date().
extension Date {
func offset(days: Int) -> Date {
return Calendar.current.date(byAdding: .day, value: days, to: self)
}
}
Usage
In the final snippet, I would use these functions as such:
let now = Date()
let fiveDaysAgo = now.offset(days: -5)
print(now.offset(days: +1).isBetween(fiveDaysAgo, and: now))
I think what the current answers are missing is that if you want to compare dates you need to take the time out of the equation (which is not the same as not caring about the time). So for that interview question what you are really saying is 'is the date I am checking >= the start of the day 5 days ago and before the start of tomorrow'. After all if you are checking it at 10pm today then 9am 5 days ago would still be ok but not if you are including time in all the checks.
So before doing any checking you need to calculate the start of day for 5 days ago and tomorrow which is done something like this:
let now = Date()
let cal = Calendar.current
var start = cal.startOfDay(for: Date())
let inYesterday = cal.date(byAdding: .day, value: -5, to: now)!
start = cal.startOfDay(for: inYesterday)
var end = cal.startOfDay(for: Date())
let inTomorrow = cal.date(byAdding: .day, value: 1, to: now)!
end = cal.startOfDay(for: inTomorrow)
(This is more complex than you would think because you have to account for different time zones and formats and things like summer time.)
Then depending on how you want to use it you could do something like this:
extension Date {
func isBetween(date: Date, andDaysAgo daysAgo: Int) -> Bool {
let cal = Calendar.current
var start = cal.startOfDay(for: date)
let inYesterday = cal.date(byAdding: .day, value: -daysAgo, to: date)!
start = cal.startOfDay(for: inYesterday)
var end = cal.startOfDay(for: date)
let inTomorrow = cal.date(byAdding: .day, value: 1, to: date)!
end = cal.startOfDay(for: inTomorrow)
return start..<end ~= self
}
}
Which you would call like this:
var checkDate = Date(timeIntervalSinceNow: 3600 * 24 * 0)
print (checkDate.isBetween(date: Date(), andDaysAgo: 5) // prints true
checkDate = Date(timeIntervalSinceNow: 3600 * 24 * -5)
print (checkDate.isBetween(date: Date(), andDaysAgo: 5) // prints true
checkDate = Date(timeIntervalSinceNow: 3600 * 24 * 1)
print (checkDate.isBetween(date: Date(), andDaysAgo: 5) // prints false
checkDate = Date(timeIntervalSinceNow: 3600 * 24 * -6)
print (checkDate.isBetween(date: Date(), andDaysAgo: 5) // prints flase
(these are just quick hacked examples)
EDIT
As was pointed out to me (quite rightly) if you are comparing dates and don't care about time then you can use noon as your reference point (basically it's always fixed and unaffected by things like daylight saving changes). So this is one way to do that:
func isBetweenAlt(date: Date, andDaysAgo daysAgo: Int) -> Bool {
let cal = Calendar.current
let startCheck = cal.date(bySettingHour: 12, minute: 0, second: 0, of: cal.date(byAdding: .day, value: -daysAgo, to: date)!)!
let endCheck = cal.date(bySettingHour: 12, minute: 0, second: 0, of: date)!
let checkDate = cal.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
return startCheck...endCheck ~= checkDate
}
Also in the spirit of learning here are a couple of other ways to do it:
func isBetweenAlt2(date: Date, andDaysAgo daysAgo: Int) -> Bool {
let cal = Calendar.current
let startCheck = cal.date(byAdding: .day, value: -(daysAgo + 1), to: date)! // You have to offset by one day as the test will be > not >=
let endCheck = cal.date(byAdding: .day, value: 1, to: date)! // You have to offset by one day as the test will be < not <=
return cal.compare(startCheck, to: self, toGranularity: .day) == .orderedAscending && cal.compare(endCheck, to: self, toGranularity: .day) == .orderedDescending
}
func isBetweenAlt3(date: Date, andDaysAgo daysAgo: Int) -> Bool {
let cal = Calendar.current
let startCheck = cal.ordinality(of: .day, in: .era, for: cal.date(byAdding: .day, value: -daysAgo, to: date)!)!
let endCheck = cal.ordinality(of: .day, in: .era, for: date)!
let check = cal.ordinality(of: .day, in: .era, for: self)!
return startCheck...endCheck ~= check
}
All of them do the same job (I think).
EDIT
With reference back to the original question and these kind of tests in interviews. Often there are multiple solutions to a problem that may all be equally valid and the idea of the interview question is not necessarily to get the answer they think is correct but to show that you have thought about the issues that affect the problem. Particularly with a junior developer role it can be less important that an answer is complete in the time but more important that the person understood the issue, the challenges involved and how to go about solving it.
I made your code a bit shorter and a bit more "swift-like"
func compare(date thisDate: Date, aheadOf aheadOfDate: Date, before beforeDate: Date) -> Bool {
return (thisDate > aheadOfDate) && (thisDate < beforeDate)
}
func fromToday(days: Double) -> Date {
let today = Date()
return Date(timeIntervalSince1970: today.timeIntervalSince1970 + (days * 86400.0)) //Todays date in seconds + seconds in a day times desired number of days
}
let today = Date()
let fiveDaysAgo = fromToday(days: -5)
let oneDayFromToday = fromToday(days: 1)
print(compare(date: oneDayFromToday, aheadOf: fiveDaysAgo, before: today))

Round time to nearest thirty seconds

I have an app that presents data that expires every 30 seconds (precisely, at h/m/s 11:30:00, 11:30:30, 11:31:00, etc).
I can get the current time, but I am unsure on how to calculate the time between now and the nearest thirty seconds.
Anything I've found is in Objective-C, and I've been unable to convert it.
Here's what I tried:
func nearestThirtySeconds() -> Date? {
var components = NSCalendar.current.dateComponents([.second], from: self)
let second = components.second ?? 30
components.second = second >= 30 ? 60 - second : -second
return Calendar.current.date(byAdding: components, to: self)
}
But this returns the nearest minute (I think, it always returns a definite minute)
Any ideas?
You can round the seconds to the nearest multiple of 30,
and then add the difference between the rounded and the original
value to the date:
extension Date {
func nearestThirtySeconds() -> Date {
let cal = Calendar.current
let seconds = cal.component(.second, from: self)
// Compute nearest multiple of 30:
let roundedSeconds = lrint(Double(seconds) / 30) * 30
return cal.date(byAdding: .second, value: roundedSeconds - seconds, to: self)!
}
}
That should be good enough to display the rounded time, however it
is not exact: A Date includes also fractional seconds, so
for example "11:30:10.123" would become "11:30:00.123" and not "11:30:00.000". Here is another approach which solves that problem:
extension Date {
func nearestThirtySeconds() -> Date {
let cal = Calendar.current
let startOfMinute = cal.dateInterval(of: .minute, for: self)!.start
var seconds = self.timeIntervalSince(startOfMinute)
seconds = (seconds / 30).rounded() * 30
return startOfMinute.addingTimeInterval(seconds)
}
}
Now seconds is the time interval since the start of the current minute
(including fractional seconds). That interval is rounded to the nearest
multiple of 30 and added to the start of the minute.
I used the answer by Martin R to write a more generic version to round by any time period.
Answer is outdated and only works with time, check gist for the latest version.
https://gist.github.com/casperzandbergenyaacomm/83c6a585073fd7da2e1fbb97c9bcd38a
extension Date {
func rounded(on amount: Int, _ component: Calendar.Component) -> Date {
let cal = Calendar.current
let value = cal.component(component, from: self)
// Compute nearest multiple of amount:
let roundedValue = lrint(Double(value) / Double(amount)) * amount
let newDate = cal.date(byAdding: component, value: roundedValue - value, to: self)!
return newDate.floorAllComponents(before: component)
}
func floorAllComponents(before component: Calendar.Component) -> Date {
// All components to round ordered by length
let components = [Calendar.Component.year, .month, .day, .hour, .minute, .second, .nanosecond]
guard let index = components.index(of: component) else {
fatalError("Wrong component")
}
let cal = Calendar.current
var date = self
components.suffix(from: index + 1).forEach { roundComponent in
let value = cal.component(roundComponent, from: date) * -1
date = cal.date(byAdding: roundComponent, value: value, to: date)!
}
return date
}
}
To round to x minutes you need to also floor the seconds so this also contains the floor method I wrote.
How to use:
let date: Date = Date() // 10:16:34
let roundedDate0 = date.rounded(on: 30, .second) // 10:16:30
let roundedDate1 = date.rounded(on: 15, .minute) // 10:15:00
let roundedDate2 = date.rounded(on: 1, .hour) // 10:00:00
There's a Cocoapod called 'SwiftDate' that is great for date formatting and manipulation that can applied to your Date() instance.
Example of how to use the date rounding method:
let date = Date() // 2018-10-01 23:05:29
date.dateRoundedAt(at: .toFloor5Mins) // 2018-10-01 23:05:00
date.dateRoundedAt(at: .toCeil5Mins) // 2018-10-01 23:10:00
date.dateRoundedAt(at: .toFloorMins(1)) // 2018-10-01 23:05:00
date.dateRoundedAt(at: .toCeilMins(1)) // 2018-10-01 23:06:00
(For reference, check out the documentation at https://cocoapods.org/pods/SwiftDate)
let now = Date()
var timeInterval = now.timeIntervalSinceReferenceDate
timeInterval += 30 - timeInterval.truncatingRemainder(dividingBy: 30)
let rounded = Date(timeIntervalSinceReferenceDate: timeInterval)
print("\(now) rounded to nearest 30 seconds is \(rounded)")