Result of adding second to date is one minute off; workaround - swift

I'm adding a second to an instance of Foundation's date, but the result is off by an entire minute.
var calendar = Calendar(identifier: .iso8601)
calendar.locale = Locale(identifier: "en")
calendar.timeZone = TimeZone(identifier: "GMT")!
let date1 = Date(timeIntervalSinceReferenceDate: -62544967141.9)
let date2 = calendar.date(byAdding: DateComponents(second: 1),
to: date1,
wrappingComponents: true)!
ISO8601DateFormatter().string(from: date1) // => 0019-01-11T22:00:58Z
ISO8601DateFormatter().string(from: date2) // => 0019-01-11T21:59:59Z
Interestingly, one of the following makes the error go away:
round time interval since reference date
don't add time zone to calendar
set wrappingComponents to false (even though it shouldn't wrap in this case)
I don't really need sub-second precision in my code, so I created this extension that allows me to discard it.
extension Date {
func roundedToSeconds() -> Date {
return Date(timeIntervalSinceReferenceDate: round(timeIntervalSinceReferenceDate))
}
}
I want to know this:
Why does this error happen?
Am I doing something wrong?
Is there any issue with my workaround?

Why does this error happen?
I would say this is a bug in Core Foundation (CF).
Calendar.date(byAdding:to:wrappingComponents:) calls down to the internal Core Foundation function _CFCalendarAddComponentsV, which in turn uses the ICU Calendar C API. ICU represents a time as an floating-point number of milliseconds since the Unix epoch, while CF uses a floating-point number of seconds since the NeXT reference date. So CF has to convert its representation to ICU's representation before calling into ICU, and convert back to return the result to you.
Here's how it converts from a CF timestamp to an ICU timestamp:
double startingInt;
double startingFrac = modf(*atp, &startingInt);
UDate udate = (startingInt + kCFAbsoluteTimeIntervalSince1970) * 1000.0;
The modf function splits a floating-point number into its integer and fractional parts. Let's plug in your example date:
var startingInt: Double = 0
var startingFrac: Double = modf(date1.timeIntervalSinceReferenceDate, &startingInt)
print(startingInt, startingFrac)
// Output:
-62544967141.0 -0.9000015258789062
Next, CF calls __CFCalendarAdd to add one second to -62544967141. Note that -62544967141 lies in the round one-minute interval -62544967200 ..< -62544967140.0. So when CF adds one second to -62544967141, it gets -62544967140, which would be in the next round one-minute interval. Since you specified wrapping components, CF isn't allowed to change the minute part of the date, so it wraps back to the beginning of the original round one-minute interval, -62544967200.
Finally, CF converts the ICU time back to a CF time, adding in the fractional part of the original time:
*atp = (udate / 1000.0) - kCFAbsoluteTimeIntervalSince1970 + startingFrac + (nanosecond * 1.0e-9);
So it returns -62544967200 + -0.9000015258789062 = -62544967200.9, exactly 59 seconds earlier than the input time.
Am I doing something wrong?
No, the bug is in CF, not in your code.
Is there any issue with my workaround?
If you don't need sub-second precision, your workaround should be fine.
I can reproduce it with more recent dates but so far only with negative reference dates, e.g. Date(timeIntervalSinceReferenceDate: -1008899941.9), which is 1969-01-11T22:00:58Z.
Any negative timeIntervalSinceReferenceDate in the last second of a minute interval should cause the problem. The bug effectively makes the first round whole minute prior to time 0 span from -60.99999999999999 through -1.0, but it should span from -60.0 through -5e324. All more-negative round minute intervals are similarly offset.

Related

What's Swift current time Date object precision when it is initiated using the default constructor?

What's the current time Date object time precision when it is initiated using Date()? Does it capture the time to milliseconds?
let currentTime = Date()
print(currentTime) // 2022-10-09 09:13:39 +0000
When I print the date it only shows 2022-10-09 09:13:39 +0000 so I wonder if its precision is only to the second.
Does it capture the time to milliseconds?
Yes, it does. printing a date shows a fixed string description omitting the fractional seconds. A hint is that TimeInterval is defined as a floating point type (Double).
You can prove it
let interval = Date().timeIntervalSince1970
print(interval)
which shows a real fractional part rather than a Is-floating-point-math-broken value like .00000003

Swift - How to create a date object containing just the time

I trying to create a date object just containing the time of 1 second past midnight.
I believe the following should work but it just keeps returning nil.
let dateTime = Date()
let timeFormatter = DateFormatter()
timeFormatter.dateFormat = "HH:mm:ss"
let time = timeFormatter.date(from: "00:00:01")
print("Time: \(time!)")
Can someone tell me what i'm doing wrong!
Thanks
Let Calendar do the math, this is more reliable, you aren't using the current date (dateTime) anyway.
let midnight = Calendar.current.startOfDay(for: Date())
let oneSecondAfterMidnight = Calendar.current.date(byAdding: .second, value: 1, to: midnight)
This works even if midnight doesn't exist due to daylight saving change.
Date is not a "date" in any meaningful way. It's a specific point in time, independent of any calendar or location. What you want to express is a point on a calendar: "one second" past an arbitrary calendar point we call "midnight." That's done with DateComponents.
var dc = DateComponents()
dc.hour = 0
dc.minute = 0
dc.second = 1
This is the second second of the first minute of the first hour (00:00:01) of an arbitrary day on an arbitrary calendar, which is what you've described.
More precisely, it's "zero hours, zero minutes, and one second," which is only "one second after midnight" if you add it to some "midnight." But beyond that, there is no independent "time" type. Those things only have meaning when applied to a Calendar.
(Keep in mind that due to DST change in some parts of world, such as Iran, there are sometimes two midnights in the same day. So when you ask for this kind of thing, you need to be very clear what you mean. Do you want every second after midnight or just the first one on a given day?)

How to parse date of this format [duplicate]

so in my app, I need to deal with date like \/Date(1440156888750-0700)\/, i think the first part stands for the seconds from Jan 1st, 1970 and the second part is the timezone.
I don't know how to handle data like this and display it the way we all understand in Xcode 7 using Swift 2?
(The previous version of this answer was actually wrong, it did not handle the time zone correctly.)
According to Stand-Alone JSON Serialization:
DateTime values appear as JSON strings in the form of "/Date(700000+0500)/", where the first number (700000 in the example provided) is the number of milliseconds in the GMT time zone, regular (non-daylight savings) time since midnight, January 1, 1970. The number may be negative to represent earlier times. The part that consists of "+0500" in the example is optional and indicates that the time is of the Local kind - that is, should be converted to the local time zone on deserialization. If it is absent, the time is deserialized as Utc. The actual number ("0500" in this example) and its sign (+ or -) are ignored.
and Use JSON.NET to parse json date of format Date(epochTime-offset)
... In this [screwy format][1], the timestamp portion is still based solely on UTC. The offset is extra information. It doesn't change the timestamp. You can give a different offset, or omit it entirely and it's still the same moment in time.
the first number in \/Date(1440156888750-0700)\/ is the number of
milliseconds since the "epoch" January 1, 1970 GMT, and the time
zone part -0700 must simply be ignored.
Here is a Swift 5 extension method for Date which checks
the validity of the string with a regular expression
(accepting both \/Date(...)\/ and /Date(...)/, with or without
a time zone specification) and converts the given number of
milliseconds to a Date:
extension Date {
init?(jsonDate: String) {
let pattern = #"\\?/Date\((\d+)([+-]\d{4})?\)\\?/"#
let regex = try! NSRegularExpression(pattern: pattern)
guard let match = regex.firstMatch(in: jsonDate, range: NSRange(jsonDate.startIndex..., in: jsonDate)) else {
return nil
}
// Extract milliseconds:
let dateString = jsonDate[Range(match.range(at: 1), in: jsonDate)!]
// Convert to UNIX timestamp in seconds:
let timeStamp = Double(dateString)! / 1000.0
// Create Date from timestamp:
self.init(timeIntervalSince1970: timeStamp)
}
}
Example:
let jsonDate = "\\/Date(1440156888750-0700)\\/"
print("JSON Date:", jsonDate)
if let theDate = Date(jsonDate: jsonDate) {
print("Date:", theDate)
} else {
print("wrong format")
}
Output:
JSON Date: \/Date(1440156888750-0700)\/
Date: 2015-08-21 11:34:48 +0000
(Versions for Swift 3 and Swift 4 can be found in the edit history.)
After a bit of experimenting around I came up with the following solution:
let dx = "/Date(1440156888750-0700)/"
let timestamp = (dx as NSString).substringWithRange(NSRange(location: 6,length: 13))
let timezone = (dx as NSString).substringWithRange(NSRange(location: 19,length: 5))
let dateIntermediate = NSDate(timeIntervalSince1970: Double(timestamp)! / 1000)
let outp = NSDateFormatter()
outp.dateFormat = "dd.MM.yyyy hh:mm::ssSSS"
outp.timeZone = NSTimeZone(forSecondsFromGMT: 0)
let input = outp.stringFromDate(dateIntermediate) + " " + timezone
let inp = NSDateFormatter()
inp.dateFormat = "dd.MM.yyyy hh:mm::ssSSS Z"
let finalDate = inp.dateFromString(input)
print(finalDate)
Let me explain:
we extract the millisecond timestamp and the timezone from the original string
we create a date from the timestamp to be able to split it into its different components
we output that date in a more standard way (not as timestamp) and append the previously extracted timezone to that string
we then read that string and parse a date from it again
Note
As #Phoen1xUK mentioned the timestamp might have a different length than 13 digits. You can handle that situation by stripping the /Date( and )/ and then splitting the string before the - (or +).

Picking a date with swift UI automation

In the app I'm testing there is a date picker I'm trying to automate. The wheel defaults to tomorrow and I'm attempting to change it to today's date but 2 minutes from now. Below is the code I'm using to attempt this.
app.pickerWheels.element(boundBy: 0).adjust(toPickerWheelValue: "Today")
app.pickerWheels.element(boundBy: 1).adjust(toPickerWheelValue: "1")
app.pickerWheels.element(boundBy: 2).adjust(toPickerWheelValue: "00")
(In the actual code I'm using variables and not hard coding these string)
This code works for the second and third wheel (hours and minutes) but for the first wheel it won't set the value. The test will fail and not continue past that point.
I have also tried passing today's date instead of just "Today" with the same results.
You can use the DateFormatter class with Date to accomplish this.
// Initialize the date formatter. Set the timeZone and format. I chose hours and minutes.
let dateFormatter: DateFormatter = DateFormatter()
dateFormatter.timeZone = NSTimeZone.local
dateFormatter.dateFormat = "HH:mm"
// Initialize the Date instance using a time interval since now.
let d: Date = Date(timeIntervalSinceNow: 2 * 60)
print("Current Time = \(dateFormatter.string(from: Date())), Two-Minutes-From-Now = \(dateFormatter.string(from: d))")
Output: Current Time = 23:57, Two-Minutes-From-Now = 23:59
A Date is stored as a time interval since January 1st, 1970. You can manipulate the date by adding or subtracting seconds from it. Here, I added 2 * 60 or two 60-second minutes to the current time interval (a large value represented in a double). This points to two minutes in the future.
Now, if you print the date without the formatter, it will just display the current time with no regard to your time zone. So if you want it to be accurate to your time zone, you need to set that in the formatter first. Note that it doesn't change the time, just its representation to you.

How can I get current date and time in swift with out Foundation

I am trying to do the equivalent of NSDate() but with out importing Foundation.
Does the Darwin module have a way to do this?
I was looking at this answer but no dice
How can I get a precise time, for example in milliseconds in Objective-C?
I am not sure if this is what you are looking for, but the BSD library
function
let t = time(nil)
gives the number of seconds since the Unix epoch as an integer, so this is almost the same as
let t = NSDate().timeIntervalSince1970
only that the latter returns the time as a Double with higher
precision. If you need this higher precision then you could use
gettimeofday():
var tv = timeval()
gettimeofday(&tv, nil)
let t = Double(tv.tv_sec) + Double(tv.tv_usec) / Double(USEC_PER_SEC)
If you are looking for the time broken down to years, month, days, hours etc according to your local time zone, then use
var t = time(nil)
var tmValue = tm()
localtime_r(&t, &tmValue)
let year = tmValue.tm_year + 1900
let month = tmValue.tm_mon + 1
let day = tmValue.tm_mday
let hour = tmValue.tm_hour
// ...
tmValue is a struct tm, and the fields are described in
https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/localtime.3.html.