2

When I convert Unix epoch time to milliseconds, it's getting rounded.

Code:

import Foundation

extension Date {
    public func getMilliSeconds()-> TimeInterval {
        return TimeInterval(self.timeIntervalSince1970)
    }
}

let epochTime: TimeInterval = 1597269862.9328
print("\(epochTime) Precise Epoch Time /// (notice the 8 is the last digit)")

let convertedTime = Date(timeIntervalSince1970: epochTime)
print("\(convertedTime) Time converted To Swift Date")

let df = DateFormatter()
df.dateFormat = "y-MM-dd H:m:ss.SSSS"
let withMilli = df.string(from: convertedTime)
print("\(withMilli) Time with milliseconds using Date Formatter /// (notice 8 is missing and this was rounded to .9330")

if let convertedBackToEpochTime = df.date(from: withMilli) {
    print("\(convertedBackToEpochTime.getMilliSeconds()) Time converted back to Unix epoch time")
}

Log:

1597269862.9328 Precise Epoch Time /// (notice the 8 is the last digit)
2020-08-12 22:04:22 +0000 Time converted To Swift Date
2020-08-12 15:4:22.9330 Time with milliseconds using Date Formatter /// (notice 8 is missing and this was rounded to .9330
1597269862.933 Time converted back to Unix epoch time

I started with 1597269862.9328 and ended with 1597269862.933. How can I get the date back to exactly what I started with: 1597269862.9328?

user1107173
  • 10,334
  • 16
  • 72
  • 117

2 Answers2

3

The issue there is that DateFormatter is limited to milliseconds. There is no way to display more than 3 fraction digits using it.

extension Formatter {
    static let iso8601withFractionalSeconds: DateFormatter = {
        let formatter = DateFormatter()
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXXXX"
        return formatter
    }()
}

Formatter.iso8601withFractionalSeconds.string(from: Date())  // "2020-10-29T16:12:27.111000000Z"

If you would like to get the initial value back you just need top get the timeIntervalSince1970 from your convertedTime date:

let epochTime: TimeInterval = 1597269862.9328
print("\(epochTime) Precise Epoch Time")

let convertedTime = Date(timeIntervalSince1970: epochTime)
print("\(convertedTime) Time converted To Swift Date")

print(convertedTime.timeIntervalSince1970)   // "1597269862.9328\n"

Note that Swift Date is stored as the time interval since reference date. If you would like to preserve the date accuracy you should use timeIntervalSinceReferenceDate instead of timeIntervalSince1970 when archiving it. Check this post.

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • 1
    @user1107173 there is no standard way to convert the date to string and back to date preserving the fraction digits after the third digit using `DateFormatter` and/or `ISO8601DateFormatter` – Leo Dabus Oct 29 '20 at 17:10
2

All components (year, month, day, hour, minute, second, etc.) of a Date are integers. And millisecond, true to its name, is only accurate to one-thousandth of a second (i.e.: 3 decimal digits).

What you want to get is the nanosecond property. Here's an example using custom interpolation feature added in Swift 5.0:

import Foundation

extension String.StringInterpolation {
    /// Our custom interpolator for Date
    mutating func appendInterpolation(custom date: Date) {
        let components: Set<Calendar.Component> = [
            .year, .month, .day, .hour, .minute, .second, .nanosecond, .timeZone
        ]
        let values = Calendar.current.dateComponents(components, from: date)
        appendLiteral(values.description)
    }
}

let epochTime: TimeInterval = 1597269862.9328
print("\(epochTime) Precise Epoch Time (notice the 8 is the last digit)")

let convertedTime = Date(timeIntervalSince1970: epochTime)
print("\(custom: convertedTime)")

Result:

1597269862.9328 Precise Epoch Time (notice the 8 is the last digit)
timeZone: America/Toronto (current) year: 2020 month: 8 day: 12 hour: 18 minute: 4 second: 22 nanosecond: 932800054 isLeapMonth: false

Notice nanosecond: 932800054. The difference between this value and the original input is due to floating point precision, which is unavoidable.

Code Different
  • 90,614
  • 16
  • 144
  • 163
  • Thanks. How do I extract the final value 1597269862.9328 or 1597269862.932800054.? – user1107173 Oct 29 '20 at 16:17
  • There is no millisecond component in DateComponents. The date property timeIntervalSince1970 is not limited to milliseconds. It has the same nanosecond precision. The only precision that is lost (not related to this question) is due to the fact under the hood date is stored as the time interval since reference date. https://stackoverflow.com/a/47502712/2303865 – Leo Dabus Oct 29 '20 at 16:33