Convert Date to Integer in Swift - 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)

Related

Adding Time Interval. e.g hours, minutes and seconds

I am getting time from the response in hours, minutes and seconds like "01:32:34" of multiple objects. I have saved it in a custom date object, where i am saving the date value and the string value, there are a large number of records so i am saving it in my local db and upon retrieving i get the date value in the format 1999-12-31 19:01:04 +0000 whereas my string value which i am getting from response as well is 19:01:04. now i would like to add all of these values to return a string e.g 1:15:16, 00:15:02, 00:45:27 should return 2 hours 15 minutes 45 seconds. I have explored Calendar.Components.byAdding but the methods there only let me add one single component, it doesn't receive an array and return Date. Is there any swifty way to achieve this, I want to achieve this via an elegant and proper method, i could think of a few fixes but they don't seem appropriate.
I’m going to assume that “01:32:34” represents an elapsed time of 5,554 seconds, not 1:32am. So, I’d convert it to a TimeInterval, not a Date:
func timeInterval(from string: String) -> TimeInterval? {
let components = string.components(separatedBy: ":").map { Double($0) }
guard
components.count == 3,
let hours = components[0],
let minutes = components[1],
let seconds = components[2]
else { return nil }
return ((hours * 60) + minutes) * 60 + seconds
}
You can chose to store either the original “01:32:34” string or this value, 5,554.0.
Anyway, then adding these numeric time intervals is trivial addition. And to display a resulting TimeInterval, you’d use a DateComponentsFormatter, e.g.
let timeIntervalFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second]
return formatter
}()
func totalElapsed(_ strings: [String]) -> String? {
let total = strings.reduce(TimeInterval.zero) { sum, string in
sum + (timeInterval(from: string) ?? 0)
}
return timeIntervalFormatter.string(from: total)
}
let strings = ["1:15:16", "00:15:02", "00:45:27"]
let result = totalElapsed(strings)
02:15:45
Or if you want more of a (localized) natural language representation, use unitsStyle of .full:
2 hours, 15 minutes, 45 seconds
This approach (using TimeInterval, not Date) has the virtue that it also can represent intervals that exceed 24 hours.

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

Turning an Int into a String

I'm pretty new to swift (and programming altogether). I'm trying to convert an Int into a String. I've tried using switch statements but every time I use them, it never changes to the String (AKA it prints the number 4) An example of what I'm trying to do is as follows:
class Birthday(_ month: Int, _ day:Int, _ year:Int) -> String{
//Here is where I'd like to turn my month into April
Return (month)
}
let example = Birthday()
example(4,15,1988)
If you really just want to get a month name from a month number, you can do the following:
let formatter = DateFormatter()
let monthName = formatter.monthSymbols[month - 1] // Assuming 1 means January
But since you are passing in a month, day, and year, you presumably want to create a Date and then you want to format that Date into a `String.
Create a Date using Calendar and DateComponents.
let date = Calendar.current.date(from: DateComponents(year: year, month: month, day: day))
Then you format the Date into a String using DateFormatter.
let formatter = DateFormatter()
formatter.dateStyle = .long // choose a desired style
formatter.timeStyle = .none
let string = formatter.string(from: date)
You can use a dictionary which maps objects to each other. For example, a months dictionary could look like:
let months: [Int:String] = [1:"January", 2:"February",...]
return months[4] // returns "April"
Simple solution to get you started would be a method that takes an integer and return your month string.
func numberToMonth(number: Int) -> String {
guard number > 0, number < 13 else { return "" }
return DateFormatter().monthSymbols[number-1]
}

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

How to pass date information to AppleScript?

I am making one app that can run AppleScript via NSAppleScript.
Everything had been fine but I haven't been able to figure out how to pass date information from my app to AppleScript. (Since AppleScript has date type, I suppose this is possible)
The way I pass parameters to AppleScript is through NSAppleEventDescriptor. I learned from Google that I could pass it as typeLongDateTime type:
- (id)initWithDate:(NSDate *)date {
LongDateTime ldt;
UCConvertCFAbsoluteTimeToLongDateTime(CFDateGetAbsoluteTime((CFDateRef)date), &ldt);
return [self initWithDescriptorType:typeLongDateTime
bytes:&ldt
length:sizeof(ldt)];
}
Unfortunately, the type LongDateTime had long gone, because I am using Swift and under OS X 10.10. Even the Core Services function UCConvertCFAbsoluteTimeToLongDateTime has already been removed from 10.10.3.
Now I am stuck.
Do you have any ideas that inspire?
Is seems that LongDateTime is a signed 64-bit integer which represents
a date d as the number of seconds since January 1, 1904, GMT, adjusted by the time-zone offset
for d in the local time zone (including daylight-savings time
offset if DST is active at d).
The following code gives the same result as your Objective-C code for all dates that I tested (winter time and summer time).
class DateEventDescriptor : NSAppleEventDescriptor {
convenience init?(date : NSDate) {
let secondsSince2001 = Int64(date.timeIntervalSinceReferenceDate)
var secondsSince1904 = secondsSince2001 + 3061152000
secondsSince1904 += Int64(NSTimeZone.localTimeZone().secondsFromGMTForDate(date))
self.init(descriptorType: DescType(typeLongDateTime),
bytes: &secondsSince1904, length: sizeofValue(secondsSince1904))
}
}
Update for Swift 3:
class DateEventDescriptor : NSAppleEventDescriptor {
convenience init?(date: Date) {
let secondsSince2001 = Int64(date.timeIntervalSinceReferenceDate)
var secondsSince1904 = secondsSince2001 + 3061152000
secondsSince1904 += Int64(NSTimeZone.local.secondsFromGMT(for: date))
self.init(descriptorType: DescType(typeLongDateTime),
bytes: &secondsSince1904, length: MemoryLayout.size(ofValue: secondsSince1904))
}
}
Update for macOS 10.11:
As of macOS 10.11 there is a
NSAppleEventDescriptor(date: Date)
initializer, so that the above workaround is no longer necessary.
(Thanks to #Wevah for this information.)
Inspired by Martin, I got to know that the LongDateTime type is just something that records time interval since the date 1904-01-01 midnight. And AppleScript utilizes it to represent dates. However, one weird thing in AppleScript is that there is no time zone concept for date type. So, simply passing the time interval since 1904-01-01 00:00:00 +0000, would only make the resulted date in AppleScript show the time in GMT. That was why I tried Martin's suggestion but got wrong time shown from the AppleScript. Since it is a data involving time difference, I got the following way works for me:
convenience init?(date: NSDate) {
struct StaticWrapper {
static var longDateTimeReferenceDate: NSDate!
}
if StaticWrapper.longDateTimeReferenceDate == nil {
let formatter = NSDateFormatter()
let c = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
formatter.calendar = c
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
StaticWrapper.longDateTimeReferenceDate = formatter.dateFromString("1904-01-01 00:00:00")
}
var secondsSince1904 = Int64(date.timeIntervalSinceDate(StaticWrapper.longDateTimeReferenceDate))
self.init(descriptorType: DescType(typeLongDateTime), bytes: &secondsSince1904, length: sizeofValue(secondsSince1904))
}
The time zone information is not given in the date formatter, which implicitly includes the current time zone. Therefore, the resulted time interval will make the AppleScript to show the time in local time zone. Which behaves like the AppleScript command "current date".
There is a little-known CoreFoundation constant kCFAbsoluteTimeIntervalSince1904 representing the difference between 1904 and 2001. This NSDate extension converts NSDate to NSAppleEventDescriptor and vice versa
extension NSDate {
func appleScriptDate() -> NSAppleEventDescriptor
{
var secondsSince1904 = Int64(self.timeIntervalSinceReferenceDate + kCFAbsoluteTimeIntervalSince1904)
return NSAppleEventDescriptor(descriptorType: DescType(typeLongDateTime), bytes: &secondsSince1904, length: sizeofValue(secondsSince1904))!
}
convenience init(appleScriptDate : NSAppleEventDescriptor)
{
var secondsSince1904 : Int64 = 0
let data = appleScriptDate.data
data.getBytes(&secondsSince1904, length: data.length)
self.init(timeIntervalSinceReferenceDate:NSTimeInterval(secondsSince1904) - kCFAbsoluteTimeIntervalSince1904)
}
}
If you need to adjust the time zone information (converting to AppleScript date does not preserve the time zone) add NSTimeZone.systemTimeZone().secondsFromGMT in Swift or time to GMT in AppleScript
I updated vadian's extension for Swift 3:
extension NSDate {
func appleScriptDate() -> NSAppleEventDescriptor
{
var secondsSince1904 = Int64(self.timeIntervalSinceReferenceDate + kCFAbsoluteTimeIntervalSince1904)
return NSAppleEventDescriptor(descriptorType: DescType(typeLongDateTime), bytes: &secondsSince1904, length: MemoryLayout.size(ofValue: secondsSince1904))!
}
convenience init(appleScriptDate : NSAppleEventDescriptor)
{
var secondsSince1904 : Int64 = 0
withUnsafeMutablePointer(to: &secondsSince1904) {
_ = appleScriptDate.data.copyBytes(
to: UnsafeMutableBufferPointer(start: $0, count: 4),
from: 0..<4)
}
self.init(timeIntervalSinceReferenceDate:TimeInterval(secondsSince1904) - kCFAbsoluteTimeIntervalSince1904)
}
}