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

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.

Related

How to create a formatter for TimeInterval to print minutes, seconds and milliseconds

Is there a way to create a DateComponentFormatter for TimeInterval that outputs minutes, seconds and milliseconds (bonus, if I could specify how many fractional places after seconds).
let t: TimeInterval = 124.344657 // 124 seconds, 345 milliseconds
// output as 2m 4s 345ms
I tried the following:
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .abbreviated
formatter.allowedUnits = [.minute, .second]
formatter.allowsFractionalUnits = true
print("\(formatter.string(from: t)!)") // outputs 2m 4s
I tried playing with more parameters, e.g. like adding .nanosecond, but to no effect.
What's the right approach here?
I think the way to look at this is that it's a misuse of a date components formatter. This isn't a date of any kind. It's a string consisting of a certain number of minutes, seconds, and milliseconds. Unlike date math, that's a calculation you can perform, and then you are free to present the string however you like.
If you want to use a formatter to help you with user locales and so forth, then you are looking for a measurement formatter (for each of the substrings).
Example (using the new Swift 5.5 formatter notation):
let t1 = Measurement<UnitDuration>(value: 2, unit: .minutes)
let t2 = Measurement<UnitDuration>(value: 4, unit: .seconds)
let t3 = Measurement<UnitDuration>(value: 345, unit: .milliseconds)
let s1 = t1.formatted(.measurement(width: .narrow))
let s2 = t2.formatted(.measurement(width: .narrow))
let s3 = t3.formatted(.measurement(width: .narrow))
let result = "\(s1) \(s2) \(s3)" // "2m 4s 345ms"
Addendum: You say in a comment that you're having trouble deriving the number milliseconds. Here's a possible way. Start with seconds and let the Measurement do the conversion. Then format the resulting value in the formatter. Like this:
let t3 = Measurement<UnitDuration>(value: 0.344657, unit: .seconds)
.converted(to: .milliseconds)
// getting the `0.xxx` from `n.xxx` is easy and not shown here
let s3 = t3.formatted(.measurement(
width: .narrow,
numberFormatStyle: .number.precision(.significantDigits(3))))
You might have to play around a little with the number-formatter part of that, but the point is that a measurement formatter lets you dictate the number format and thus get the truncation / rounding behavior you're after.

Is there an elegant way of determining if two dates are separated by a week of year in Swift?

If I have two dates, and I want to know if one of them falls on a week of year (as defined here) just prior to the other, how can I figure this out in Swift?
Assuming I don't care about time, one approach could be:
let calendar = Calendar.current
let startOfDate1 = calendar.startOfDay(for: date1)
let startOfDate2 = calendar.startOfDay(for: date2)
let date1WeekOfYear = calendar.dateComponents([.weekOfYear], from: startOfDate1).weekOfYear!
let date2WeekOfYear = calendar.dateComponents([.weekOfYear], from: startOfDate2).weekOfYear!
if (date1WeekOfYear - date2WeekOfYear) == 1 {
// do some stuff
}
This works except in the case where date1 falls within the first week of the year, and date2 falls within the last week of the prior year. Do I really have to also add in other logic to check the situation where the year components are different, and account for varying lengths of years in weeks (most have 52 weeks, some have 53), or is there a more elegant way to handle this?
Please note that I'm not interested in checking if the two dates are within 7 days of each other. It's possible that the two days are within 2 days of each other, but fall within different weeks of the year.
Thanks in advance.
Here's a way I found:
You do need to get the year, but Calendar can still do the calculation for you. You just need to call a different overload of the method from the one in Rob Napier's answer. You need the overload that accepts DateComponents:
let calendar = Calendar.current
let start = Date(timeIntervalSince1970: 1577375330) // 2019-12-26
let end = Date(timeIntervalSince1970: 1577893730) // 2020-01-01
// remember it's yearForWeekOfYear, not just "year"
let startDateComponents = calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: start)
let endDateComponents = calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: end)
let interval = calendar.dateComponents([.weekOfYear], from: startDateComponents, to: endDateComponents).weekOfYear!
print(interval) // 1
My speculation of why this works but the overload taking Dates doesn't:
The overload that takes Dates will first get the difference between the two dates, which represent instants, and then convert that time interval to the specified set of DateComponents. Note that it's probably converting a TimeInterval to DateComponents, which is why it can't calculate week boundaries and such.
The overload that takes DateComponents can calculate week boundaries because that information is given as its parameters.
You can add 7 days to one date (allowing for year roll-over), and determine the difference in week-numbers between the two dates to be zero weeks rather then one week.
Have a look at the 2019 end of year
> var cal = Calendar.current
> let df = DateFormatter(); df.dateFormat = "yyyy-MM-dd"
> var d1 = df.date(from: "2019-12-28")!;
> var w1=cal.component(.weekOfYear, from:d1)
w1: Int = 52
This tells us Dec 28 still fell in week 52.
> var w2=cal.component(.weekOfYear, from: df.date(from: "2019-12-29")!)
w2: Int = 1
And this tells us Dec 29 fell in week 1 of 2020. These two dates are "1 week apart", as humans can easily tell. Determining this by calculation can be a bit harder, even if you use modulo arithmetic: the wrap-around is irregular, at 52 for some years, and 53 for others, as the OP hinted at. (2006, 2012, 2017 and 2023 are all 53-week years)
To determine that they are 1 week apart by calculation, first move up the earlier date by 7 days, i.e. 2019-12-28 plus 7 days:
> var d1_7 = cal.date(byAdding: .day, value:7, to:d1)!;
d1_7: Date = 2020-01-04 08:00:00 UTC
> var w1_7 = cal.component(.weekOfYear,from:d1_7)
w1_7: Int = 1
Its week number is 1, equalling that of 2019-12-29. We conclude that 2019-12-28 and 2019-12-29 are 1 week apart.

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

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.

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

How to display seconds in year, day format in swift

All i need to do is to change the way that xcode displaying my values in. My code currently displaying the age of something in minutes e.g 4503mint . I want to be able to display these values in the following format 3 days 3 hours 3 mint rather than 4503 mint. Really appreciate your help. Regards
You said:
My code currently displaying the age of something in minutes e.g 4503mint . I want to be able to display these values in the following format 3 days 3 hours 3 mint rather than 4503 mint.
You can format this using NSDateComponentsFormatter. Just multiply the number of minutes by 60 to get the NSTimeInterval, and then you can supply that to the formatter:
let formatter = NSDateComponentsFormatter()
formatter.unitsStyle = .Full
let minutes = 4503
let timeInterval = NSTimeInterval(minutes * 60)
print(formatter.stringFromTimeInterval(timeInterval))
That will produce "3 days, 3 hours, 3 minutes" (or in whatever format appropriate for the locale for that device).
See NSDateComponentsFormatter Reference for more information.
You can obviously calculate it yourself (i.e. days = seconds/(3600*24)etc.), you can also look at NSDateComponentsFormatter, which may be exactly the functionality you are looking for with almost no coding effort.
You could calculate it yourself or do something like this:
let mySeconds = 700000
let date = NSDate(timeIntervalSinceNow: mySeconds) // difference to now
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = NSDateFormatter.dateFormatFromTemplate("yyyy.MM.dd", options: 0, locale: NSLocale.currentLocale())
dateFormatter.stringFromDate(date)