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

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.)

Related

How to tell if a business is open after midnight

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)

exclude Array of DateInterval from Array of DateInterval

I want to exclude Array of DateInterval from Array of DateInterval. This's my code, but I don't think it will not be helpful.. it goes in infinite loop sometimes and I couldn't solve it.
extension DateInterval {
static func exclude(_ excludedIntervals: [DateInterval], from periods: [DateInterval]) -> [DateInterval] {
if excludedIntervals.isEmpty { return periods }
var resultSlots: [DateInterval] = []
for period in periods {
let results = period.exclude(excludedIntervals)
resultSlots.append(contentsOf: results)
}
return resultSlots
}
func exclude(_ execludedIntervals: [DateInterval]) -> [DateInterval] {
if execludedIntervals.isEmpty { return [self] }
var sortedExecludedIntervals = execludedIntervals.sorted()
var resultSlots: [DateInterval] = []
var execludedInterval = sortedExecludedIntervals.removeFirst()
// remove execludedIntervals from self
if let intersection = self.intersection(with: execludedInterval) {
if self.start == intersection.start && self.end > intersection.end {
let newSlot = DateInterval(start: intersection.end, end: self.end)
resultSlots.append(contentsOf: newSlot.exclude(sortedExecludedIntervals))
} else if self.start < intersection.start && self.end == intersection.end {
let newSlot = DateInterval(start: self.start, end: intersection.start)
resultSlots.append(contentsOf: newSlot.exclude(sortedExecludedIntervals))
} else if self.start < intersection.start && self.end > intersection.end {
let preSlot = DateInterval(start: self.start, end: intersection.start)
resultSlots.append(contentsOf: preSlot.exclude(sortedExecludedIntervals))
let postSlot = DateInterval(start: intersection.end, end: self.end)
resultSlots.append(contentsOf: postSlot.exclude(sortedExecludedIntervals))
} else {
// start = start && end = end
resultSlots = []
return resultSlots
}
}
return resultSlots
}
}
For example, I want to exclude 1 pm- 3pm and 5 pm - 6 pm intervals from an interval 12 pm - 6pm. The function should return 12 pm - 1 pm and 3 pm - 5 pm.
A couple of thoughts:
If I want a method to operate on an array of DateInterval, I’d suggest putting it in an Array (or Sequence) extension, constrained to DateInterval types, rather than a static method on DateInterval:
extension Array where Element == DateInterval {
func exclude(_ excludedIntervals: [DateInterval]) -> [DateInterval] { ... }
}
When considering excluding a DateInterval from another, there are tons of different scenarios:
You could exclude some small window from the middle of the interval;
You could exclude a portion at the start of the interval;
You could exclude a portion at the end of the interval; and
You could exclude the whole interval.
In my mind, it gets too messy to think of all of those scenarios, so I decided to simplify this a bit and decide:
Where exactly does the excluded region intersect with the current interval (and DateInterval supplies a nice method to do that for us);
If I “cut out” that intersection from the date interval, I might end up with two intervals, a before interval and an after interval (e.g. if I cut 2pm-3pm out of noon-6pm, the before interval will be noon-2pm and the after interval will be 3pm-6pm);
The algorithm then distills down to “if the interval is intersected by the excluded region, replace the original interval with the two other intervals, the one before and the one after”; and
Given that I’m mutating the original array of resulting intervals, I’d suggest nested loops, with the outer loop being the intervals to be excluded and the inner loop being the resulting intervals which, because it’s mutating, I’ll iterate through using a while statement, manually checking and adjusting the current index.
That yields:
extension Array where Element == DateInterval {
func exclude(_ excludedIntervals: [DateInterval]) -> [DateInterval] {
var results: [DateInterval] = self
for excludedInterval in excludedIntervals {
var index = results.startIndex
while index < results.endIndex {
let interval = results[index]
if let intersection = interval.intersection(with: excludedInterval) {
var before: DateInterval?
var after: DateInterval?
if intersection.start > interval.start {
before = DateInterval(start: interval.start, end: intersection.start)
}
if intersection.end < interval.end {
after = DateInterval(start: intersection.end, end: interval.end)
}
let replacements = [before, after].compactMap { $0 }
results.replaceSubrange(index...index, with: replacements)
index += replacements.count
} else {
index += 1
}
}
}
return results
}
}
Then we can consider the exclusion applied to a single DateInterval as just a special case of an array with one item:
extension DateInterval {
func exclude(_ excludedIntervals: [DateInterval]) -> [DateInterval] {
return [self].exclude(excludedIntervals)
}
}
So:
let formatter = ISO8601DateFormatter()
let start = formatter.date(from: "2019-02-09T12:00:00Z")!
let end = formatter.date(from: "2019-02-09T18:00:00Z")!
let exclude1Start = formatter.date(from: "2019-02-09T13:00:00Z")!
let exclude1End = formatter.date(from: "2019-02-09T14:00:00Z")!
let exclude2Start = formatter.date(from: "2019-02-09T16:00:00Z")!
let exclude2End = formatter.date(from: "2019-02-09T17:00:00Z")!
let intervals = DateInterval(start: start, end: end)
.exclude([
DateInterval(start: exclude1Start, end: exclude1End),
DateInterval(start: exclude2Start, end: exclude2End)
])
print(intervals)
Will produce:
[
2019-02-09 12:00:00 +0000 to 2019-02-09 13:00:00 +0000,
2019-02-09 14:00:00 +0000 to 2019-02-09 16:00:00 +0000,
2019-02-09 17:00:00 +0000 to 2019-02-09 18:00:00 +0000
]

Swift unix timestamp too short [duplicate]

I am taking the current time, in UTC, and putting it in nanaoseconds and then I need to take the nanoseconds and go back to a date in local time.
I am able to do get the time to nanoseconds and then back to a date string but the time gets convoluted when I go from a string to date.
//Date to milliseconds
func currentTimeInMiliseconds() -> Int! {
let currentDate = NSDate()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
dateFormatter.timeZone = NSTimeZone(name: "UTC") as TimeZone!
let date = dateFormatter.date(from: dateFormatter.string(from: currentDate as Date))
let nowDouble = date!.timeIntervalSince1970
return Int(nowDouble*1000)
}
//Milliseconds to date
extension Int {
func dateFromMilliseconds(format:String) -> Date {
let date : NSDate! = NSDate(timeIntervalSince1970:Double(self) / 1000.0)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
dateFormatter.timeZone = TimeZone.current
let timeStamp = dateFormatter.string(from: date as Date)
let formatter = DateFormatter()
formatter.dateFormat = format
return ( formatter.date( from: timeStamp ) )!
}
}
The timestamp is correct but the date returned isn't.
I don't understand why you're doing anything with strings...
extension Date {
var millisecondsSince1970:Int64 {
Int64((self.timeIntervalSince1970 * 1000.0).rounded())
}
init(milliseconds:Int64) {
self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
}
}
Date().millisecondsSince1970 // 1476889390939
Date(milliseconds: 0) // "Dec 31, 1969, 4:00 PM" (PDT variant of 1970 UTC)
As #Travis Solution works but in some cases
var millisecondsSince1970:Int WILL CAUSE CRASH APPLICATION ,
with error
Double value cannot be converted to Int because the result would be greater than Int.max if it occurs Please update your answer with Int64
Here is Updated Answer
extension Date {
var millisecondsSince1970:Int64 {
return Int64((self.timeIntervalSince1970 * 1000.0).rounded())
//RESOLVED CRASH HERE
}
init(milliseconds:Int) {
self = Date(timeIntervalSince1970: TimeInterval(milliseconds / 1000))
}
}
About Int definitions.
On 32-bit platforms, Int is the same size as Int32, and on 64-bit platforms, Int is the same size as Int64.
Generally, I encounter this problem in iPhone 5, which runs in 32-bit env. New devices run 64-bit env now. Their Int will be Int64.
Hope it is helpful to someone who also has same problem
#Travis solution is right, but it loses milliseconds when a Date is generated. I have added a line to include the milliseconds into the date:
If you don't need this precision, use the Travis solution because it will be faster.
extension Date {
func toMillis() -> Int64! {
return Int64(self.timeIntervalSince1970 * 1000)
}
init(millis: Int64) {
self = Date(timeIntervalSince1970: TimeInterval(millis / 1000))
self.addTimeInterval(TimeInterval(Double(millis % 1000) / 1000 ))
}
}
//Date to milliseconds
func currentTimeInMiliseconds() -> Int {
let currentDate = Date()
let since1970 = currentDate.timeIntervalSince1970
return Int(since1970 * 1000)
}
//Milliseconds to date
extension Int {
func dateFromMilliseconds() -> Date {
return Date(timeIntervalSince1970: TimeInterval(self)/1000)
}
}
I removed seemingly useless conversion via string and all those random !.
let dateTimeStamp = NSDate(timeIntervalSince1970:Double(currentTimeInMiliseconds())/1000) //UTC time //YOUR currentTimeInMiliseconds METHOD
let dateFormatter = NSDateFormatter()
dateFormatter.timeZone = NSTimeZone.localTimeZone()
dateFormatter.dateFormat = "yyyy-MM-dd"
dateFormatter.dateStyle = NSDateFormatterStyle.FullStyle
dateFormatter.timeStyle = NSDateFormatterStyle.ShortStyle
let strDateSelect = dateFormatter.stringFromDate(dateTimeStamp)
print("Local Time", strDateSelect) //Local time
let dateFormatter2 = NSDateFormatter()
dateFormatter2.timeZone = NSTimeZone(name: "UTC") as NSTimeZone!
dateFormatter2.dateFormat = "yyyy-MM-dd"
let date3 = dateFormatter.dateFromString(strDateSelect)
print("DATE",date3)
#Prashant Tukadiya answer works. But if you want to save the value in UserDefaults and then compare it to other date you get yout int64 truncated so it can cause problems. I found a solution.
Swift 4:
You can save int64 as string in UserDefaults:
let value: String(Date().millisecondsSince1970)
let stringValue = String(value)
UserDefaults.standard.set(stringValue, forKey: "int64String")
Like that you avoid Int truncation.
And then you can recover the original value:
let int64String = UserDefaults.standard.string(forKey: "int64String")
let originalValue = Int64(int64String!)
This allow you to compare it with other date values:
let currentTime = Date().millisecondsSince1970
let int64String = UserDefaults.standard.string(forKey: "int64String")
let originalValue = Int64(int64String!) ?? 0
if currentTime < originalValue {
return false
} else {
return true
}
Hope this helps someone who has same problem
Heres a simple solution in Swift 5/iOS 13.
extension Date {
func toMilliseconds() -> Int64 {
Int64(self.timeIntervalSince1970 * 1000)
}
init(milliseconds:Int) {
self = Date().advanced(by: TimeInterval(integerLiteral: Int64(milliseconds / 1000)))
}
}
This however assumes you have calculated the difference between UTF time and local time and adjusted and accounted for in the milliseconds. For that look to calendar
var cal = Calendar.current
cal.timeZone = TimeZone(abbreviation: "UTC")!
let difference = cal.compare(dateGiven, to: date, toGranularity: .nanosecond)
Simple one-line code to get time token in UInt64
let time = UInt64(Date().timeIntervalSince1970 * 1000)
print(time) <----- prints time in UInt64
Additional tip:
For timestamp with 10 Digit milliseconds since 1970 for API call then
let timeStamp = Date().timeIntervalSince1970
print(timeStamp) <-- prints current time stamp
Watch out if you are going to compare dates after the conversion!
For instance, I got simulator's asset with date as TimeInterval(366144731.9), converted to milliseconds Int64(1344451931900) and back to TimeInterval(366144731.9000001), using
func convertToMilli(timeIntervalSince1970: TimeInterval) -> Int64 {
return Int64(timeIntervalSince1970 * 1000)
}
func convertMilliToDate(milliseconds: Int64) -> Date {
return Date(timeIntervalSince1970: (TimeInterval(milliseconds) / 1000))
}
I tried to fetch the asset by creationDate and it doesn't find the asset, as you could figure, the numbers are not the same.
I tried multiple solutions to reduce double's decimal precision, like round(interval*1000)/1000, use NSDecimalNumber, etc... with no success.
I ended up fetching by interval -1 < creationDate < interval + 1, instead of creationDate == Interval.
There may be a better solution!?
Unless you absolutely have to convert the date to an integer, consider using a Double instead to represent the time interval. After all, this is the type that timeIntervalSince1970 returns. All of the answers that convert to integers loose sub-millisecond precision, but this solution is much more accurate (although you will still lose some precision due to floating-point imprecision).
public extension Date {
/// The interval, in milliseconds, between the date value and
/// 00:00:00 UTC on 1 January 1970.
/// Equivalent to `self.timeIntervalSince1970 * 1000`.
var millisecondsSince1970: Double {
return self.timeIntervalSince1970 * 1000
}
/**
Creates a date value initialized relative to 00:00:00 UTC
on 1 January 1970 by a given number of **milliseconds**.
equivalent to
```
self.init(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
```
- Parameter millisecondsSince1970: A time interval in milliseconds.
*/
init(millisecondsSince1970 milliseconds: Double) {
self.init(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
}
}

Trying to get data from a number of consecutive dates

I'm trying to build an array of data from 3000 consecutive days but the code will only extract data from one single date 3000 times.
When I print the date that I keep increasing (theincreasingnumberofdays) I can see that it is adding one day to the date every time it loops, but when I read out the Array after the loop it's all the same data 3000 times.
class day
{
var week: Int?
var date: Int?
var month: Int?
var year: Int?
var weekday: Int?
}
#IBAction func pressbuttontodefinearray(sender: AnyObject) {
print("button has been pressed")
if (theArrayContainingAllTheUserData.count > 1) {
print("array contains something allready and we don't do anything")
return
}
print("array is empty and we're filling it right now")
var ScoopDay = day()
var numberofloops = 0
while theArrayContainingAllTheUserData.count < 3000
{
var theincreasingnumberofdays: NSDate {
return NSCalendar.current.date(byAdding: .day, value: numberofloops, to: NSDate() as Date)! as NSDate
}
print(theincreasingnumberofdays)
let myCalendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)!
// var thenextdate = calendar.date(byAdding: .day, value: numberofloops, to: date as Date)
numberofloops=numberofloops+1
ScoopDay.week=Int(myCalendar.component(.weekday, from: theincreasingnumberofdays as Date!))
ScoopDay.date=Int(myCalendar.component(.day, from: theincreasingnumberofdays as Date!))
ScoopDay.month=Int(myCalendar.component(.month, from: theincreasingnumberofdays as Date!))
ScoopDay.year=Int(myCalendar.component(.year, from: theincreasingnumberofdays as Date!))
ScoopDay.weekday=Int(myCalendar.component(.weekday, from: theincreasingnumberofdays as Date!))
theArrayContainingAllTheUserData.append(ScoopDay)
}
print("we're done with this looping business. Let's print it")
var placeinarray = 0
while placeinarray < 2998
{
print("Here is", placeinarray, theArrayContainingAllTheUserData[placeinarray].date, theArrayContainingAllTheUserData[placeinarray].month)
placeinarray=placeinarray+1
}
return
}
The problem is that there is one day object, named ScoopDay, and you are adding that one object to the array 3000 times. So the array ends up with 3000 references to that one single object, which contains the last values you assigned to it.
You can fix this by moving the line
var ScoopDay = day()
inside the loop. That way you will create 3000 different day objects, each with different contents.
A Swift style tip: capitalize the first letter of class names, and lowercase the first letter of variable names, so:
class Day
and
var scoopDay = Day()

How to compare 2 dates with each other. Dates are converted to String

I want to sort my tableview on date. I know I can do that by using:
array.sort {
$0.date! < $1.date!
}
Problem is that the date is converted to a String. The date is saved in core data and changing the attribute date to NSDate is causing problems. SO how can I compare the dates without changing my Core Data?
Update for swift 3 :
if you want to compare date objects, used below lines of code:
extension Date {
func isGreater(than date: Date) -> Bool {
return self >= date
}
func isSmaller(than date: Date) -> Bool {
return self < date
}
func isEqual(to date: Date) -> Bool {
return self == date
}
}
// how to call it?
let isGreater = end_date.isGreater(than: start_date)
// where end_date and start_date is your date objects.
You should be able to sort by strings, what is your string date format?
Here is an example of sorting dates with strings:
let date1 = Date()
let date2 = Date().addingTimeInterval(TimeInterval.abs(60.0))
let date3 = Date().addingTimeInterval(TimeInterval.abs(60 * 60 * 24))
var arrayOfDates = [date1.description, date2.description, date3.description]
let sorted = arrayOfDates.sorted { $0.0 > $0.1 }
print(sorted.first!)
print(sorted[1])
print(sorted[2])
output:
2016-10-31 18:33:32 +0000
2016-10-30 18:34:32 +0000
2016-10-30 18:33:32 +0000
switching sorted to:
let sorted = arrayOfDates.sorted { $0.0 < $0.1 }
gives the following outside:
2016-10-30 18:35:58 +0000
2016-10-30 18:36:58 +0000
2016-10-31 18:35:58 +0000