Swift unix timestamp too short [duplicate] - swift

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

Related

Calculate Asleep time in HealthKit using Swift

I have the following code that gets the sum of sleep hours. However, it is summing inbed and asleep together. What I am trying to get is a sum for just the asleep time.
func readSleepAnalysis(date: Date) {
if let sleepType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis) {
let startDate = convertSleepStartDate(StartDate: date)
let endDate = convertSleepEndDate(EndDate: date)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let query = HKSampleQuery(sampleType: sleepType, predicate: predicate, limit: 30, sortDescriptors: [sortDescriptor]) {
(query, samples, error) in
guard
error == nil,
samples == samples as? [HKCategorySample] else {
print("Something went wrong getting sleep analysis: \(String(describing: error))")
return
}
let total = samples?.map(self.calculateSleepHours).reduce(0, {$0 + $1}) ?? 0
DispatchQueue.main.async {
self.userSleepMinutes = total
print("userSleepHours = \(self.userSleepMinutes)")
}
}
healthKit.execute(query)
}
}
func calculateSleepHours(sample: HKSample) -> TimeInterval {
let hours = sample.endDate.timeIntervalSince(sample.startDate) / 60 / 60
return hours
}
I previously discovered that Apple records all data based on UTC. Make sense! However, this may work for active energy and other data like that but total sleep time can't be calculated like this. I am calculating the total time from 6 PM the night prior to 05:59 AM that day. Here is how I am doing that (there may be a better way but it's beyond me at this moment).
func convertSleepStartDate(StartDate: Date) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyy-MM-dd '18':'00':'01' +0000"
let dateString = dateFormatter.string(from: StartDate)
dateFormatter.dateFormat = "yyy-MM-dd HH:mm:ss +0000"
let date = dateFormatter.date(from: dateString)
let datePrior = Calendar.current.date(byAdding: .hour, value: -24, to: date!)
print(datePrior as Any)
return datePrior!
}
func convertSleepEndDate(EndDate: Date) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyy-MM-dd '17':'59':'59' +0000"
let dateString = dateFormatter.string(from: EndDate)
dateFormatter.dateFormat = "yyy-MM-dd HH:mm:ss +0000"
let date = dateFormatter.date(from: dateString)
print(date as Any)
return date!
}
Any ideas?
Better late than never: In your calculateSleepHours function, you should check sample.value against the HKCategoryValueSleepAnalysis constants (inBed, asleep, awake). That will tell you the interval's type. It will also teach you how Apple measures and returns this data. Keep an eye on the intervals, because when you hit the end of the planned sleep time on the watch, it will give you a summary of inBed time.
let value1 = (sample.value == HKCategoryValueSleepAnalysis.inBed.rawValue) ? "InBed" : "Asleep"
let value2 = (sample.value == HKCategoryValueSleepAnalysis.asleep.rawValue) ? "Asleep" : "Awake"
let value3 = (sample.value == HKCategoryValueSleepAnalysis.awake.rawValue) ? "Awake" : "Asleep"
The 3 values contain the type of constant that each interval is measuring. It is almost always asleep, but may differ depending on the Watch OS versions.
You can print the values and see, on a per interval basis, exactly what is being measured. Be sure to print the sample.startDate and sample.endDate too. That will teach you all you need to know to get actual sleep time between two Dates.

How to calculate the correct time interval between two times that span two days in swift?

I'm trying to get the correct time interval between two times that span two days (Overnight). Here is my code successfully printing out the difference between two times - however for my use case I need the ability to span overnight, how might I do this?
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
//This time represents (23:00, Aug 07) for example
let date1 = dateFormatter.date(from: "23:00")!
//This time represents (06:00, Aug 08) for example
let date2 = dateFormatter.date(from: "06:00")!
let elapsedTime = date2.timeIntervalSince(date1)
print(abs(elapsedTime)/60/60)
//prints 17.0
My desired result is a print out of 7, as that is the amount of hours between 23:00, Aug 7 and 06:00, Aug 8 - My current code is correctly showing me the interval between those two times (as if they were from the same day) but I am trying to work out how to account for when those times overlap two days. Any help would be much appreciated.
UPDATE:
To give a more complete picture I have an object that has a start and and end date represented by a string:
Activity(startTime: "23:00", endTime: "06:00")
I use some functions to turn those strings into dates:
func startDate(startTime: String) -> Date {
let currentDate = Date().string(format: "dd-MM-yyyy")
let myStartingDate = "\(currentDate) \(startTime)"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy HH:mm"
let startDate = dateFormatter.date(from: myStartingDate)
return startDate!
}
func endDate(endTime: String) -> Date {
let currentDate = Date().string(format: "dd-MM-yyyy")
let myEndingDate = "\(currentDate) \(endTime)"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy HH:mm"
let endDate = dateFormatter.date(from: myEndingDate)
return endDate!
}
So my more complete workings look more like this:
func calculateTimeInterval(activity: Activity) {
let startHourDate = self.startDate(startTime: activity.startTime)
let endHourDate = self.endDate(endTime: activity.endTime)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
//This time represents (23:00, Aug 07) for example
let date1 = startHourDate!
//This time represents (06:00, Aug 08) for example
let date2 = endHourDate!
let elapsedTime = date2.timeIntervalSince(date1)
print(abs(elapsedTime)/60/60)
}
//prints 17.0
Without a date part the only way to determine if the end time is past midnight is if the end time is less than the start time. If so your code can be changed to
var elapsedTime = date2.timeIntervalSince(date1)
if elapsedTime < 0 {
let date3 = date2 + 60 * 60 * 24
elapsedTime = date3.timeIntervalSince(date1)
}
print(elapsedTime/60/60)
You can write an Extension to Date like this:
extension Date {
func hours(from date: Date) -> Int {
return Calendar.current.dateComponents([.hour], from: date).hour ?? 0
}
}
And just use it on any Date directly. This way you don't need DateFormatter at all. Hope this helps!

Find difference between just the time of two dates in seconds

I have 2 dates. I don't care about the date portion, just the time.
How can I compare 2 dates and get the timeinterval between 2 dates?
Should I set the dates to 01-01-2000 and leave the time alone to compare?
Use DateComponents and get the hour, minute, and second of the two dates. At this point you have to assume a full 24 hour, 86400 seconds per day. There's no need to worry about daylight saving or leap seconds or anything since you are doing date independent calculations.
Convert the hours, minutes, and seconds into total seconds of the day for the two dates. Then simply subtract the two totals and you have the difference.
Here's a helpful Date extension:
extension Date {
func secondsSinceMidnight() -> TimeInterval {
let comps = Calendar.current.dateComponents([.hour,.minute,.second], from: self)
return TimeInterval(comps.hour! * 3600 + comps.minute! * 60 + comps.second!)
}
func timeDifference(to date: Date) -> TimeInterval {
return date.secondsSinceMidnight() - self.secondsSinceMidnight()
}
}
Call timeDifference(to:) using your two dates and you will get the difference in seconds ignoring the date portion of the dates.
A negative result means that the to date is closer to midnight.
This is an alternative to rmaddy's solution completely based on DateComponents
extension Date {
func timeComponents() -> DateComponents {
return Calendar.current.dateComponents([.hour,.minute,.second], from: self)
}
func timeDifference(to date: Date) -> Int {
return Calendar.current.dateComponents([.second], from: date.timeComponents(), to: self.timeComponents()).second!
}
}
If you have two dates you can use the method timeIntervalSince(Date).
For instance:
func calculateElapsedTime(from someTime: Date) -> TimeInterval {
let currentTime = Date()
var elapsedTime = currentTime.timeIntervalSince(someTime)
return elapsedTime
}
If you only want to consider the time difference between the two dates, you first have to normalize the date. This can be done in the following cumbersome way:
let currentDate = Date()
let anotherDate = Date(timeInterval: 60, since: currentDate)
let formatter = DateFormatter()
formatter.timeStyle = .short
let currentTime = formatter.string(from: currentDate)
let anotherTime = formatter.string(from: anotherDate)
let currentIntervalTime = formatter.date(from: currentTime)
let anotherIntervalTime = formatter.date(from: anotherTime)
let elapsedTime = anotherIntervalTime?.timeIntervalSince(currentIntervalTime!)

How to extract TimeZone from Date object?

I have an ISO-8601 date string like this: "2017-02-07T00:00:00-08:00".
How can I extract the TimeZone object from this date?
Unfortunately, DateFormatter is no help since you don't want a Date nor does it provide any information about any timezone info about a parsed date string. And TimeZone doesn't have any initializer that can parse a timezone offset string.
So you will have to do the work yourself. Since you have a fixed format date string, you know the timezone offset is always going to be the last 6 characters of the string. The last 2 of those are the number of minutes and the first 3 of those are the number of hours (including the sign).
Extract these two substrings (hours and minutes) from the date string. Convert them both to Int. Then do some simple math to calculate an offset in seconds (hours * 3600 + minutes * 60).
Once you have that offset in seconds, you can create a TimeZone instance using the init(secondsFromGMT:) initializer.
Using rmaddys proposed solution, I wrote an extension for TimeZone which should do the job.
extension TimeZone {
init?(iso8601String: String) {
let timeZoneString = String(iso8601String.suffix(6))
let sign = String(timeZoneString.prefix(1))
guard sign == "+" || sign == "-" else {
return nil
}
let fullTimeString = timeZoneString.filter("0123456789".contains)
guard fullTimeString.count == 4 else {
return nil
}
guard let hours = Int(sign+fullTimeString.prefix(2)), let minutes = Int(sign+fullTimeString.suffix(2)) else {
return nil
}
let secondsFromGMT = hours * 3600 + minutes * 60
self.init(secondsFromGMT: secondsFromGMT)
}
}
You could create a date formatter that only returns the time zone such as below. Change the abbreviation to whichever time zone you are looking for.
let timeZoneOnlyDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.timeZone = TimeZone(abbreviation: "UTC")
formatter.dateStyle = .none
formatter.timeStyle = .none
return formatter
}()
And use these functions to convert it to a string or convert your string to a date.
func formatDateIntoString(date: Date, dateFormatter: DateFormatter) -> String {
return dateFormatter.string(from: date)
}
func formatStringIntoDate(string: String, dateFormatter: DateFormatter) -> Date! {
return dateFormatter.date(from: string)
}

Convert Date to Integer in Swift

I am updating some of my old Swift 2 answers to Swift 3. My answer to this question, though, is not easy to update since the question specifically asks for NSDate and not Date. So I am creating a new version of that question that I can update my answer for.
Question
If I start with a Date instance like this
let someDate = Date()
how would I convert that to an integer?
Related but different
These questions are asking different things:
Swift convert unix time to date and time
Converting Date Components (Integer) to String
Convert Date String to Int Swift
Date to Int
// using current date and time as an example
let someDate = Date()
// convert Date to TimeInterval (typealias for Double)
let timeInterval = someDate.timeIntervalSince1970
// convert to Integer
let myInt = Int(timeInterval)
Doing the Double to Int conversion causes the milliseconds to be lost. If you need the milliseconds then multiply by 1000 before converting to Int.
Int to Date
Including the reverse for completeness.
// convert Int to TimeInterval (typealias for Double)
let timeInterval = TimeInterval(myInt)
// create NSDate from Double (NSTimeInterval)
let myNSDate = Date(timeIntervalSince1970: timeInterval)
I could have also used `timeIntervalSinceReferenceDate` instead of `timeIntervalSince1970` as long as I was consistent. This is assuming that the time interval is in seconds. Note that Java uses milliseconds.
Note
For the old Swift 2 syntax with NSDate, see this answer.
If you are looking for timestamp with 10 Digit seconds since 1970 for API call then, below is code:
Just 1 line code for Swift 4/ Swift 5
let timeStamp = UInt64(Date().timeIntervalSince1970)
print(timeStamp) <-- prints current time stamp
1587473264
let timeStamp = UInt64((Date().timeIntervalSince1970) * 1000) // will give 13 digit timestamp in milli seconds
timeIntervalSince1970 is a relevant start time, convenient and provided by Apple.
If u want the int value to be smaller, u could choose the relevant start time you like
extension Date{
var intVal: Int?{
if let d = Date.coordinate{
let inteval = Date().timeIntervalSince(d)
return Int(inteval)
}
return nil
}
// today's time is close to `2020-04-17 05:06:06`
static let coordinate: Date? = {
let dateFormatCoordinate = DateFormatter()
dateFormatCoordinate.dateFormat = "yyyy-MM-dd HH:mm:ss"
if let d = dateFormatCoordinate.date(from: "2020-04-17 05:06:06") {
return d
}
return nil
}()
}
extension Int{
var dateVal: Date?{
// convert Int to Double
let interval = Double(self)
if let d = Date.coordinate{
return Date(timeInterval: interval, since: d)
}
return nil
}
}
Use like this:
let d = Date()
print(d)
// date to integer, you need to unwrap the optional
print(d.intVal)
// integer to date
print(d.intVal?.dateVal)