将日期转换为毫秒,再将其转换回日期(Swift语言)

92

我正在获取当前时间的UTC值,并将其转换为纳秒,然后需要将纳秒转回本地时间的日期。

我能够将时间转换为纳秒,再将其转换为日期字符串,但是当我从字符串转换为日期时,时间会变得混乱。

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

时间戳是正确的,但返回的日期不正确。


3
将日期转换为字符串再转回日期的目的是什么(在currentTimeInMiliseconds()函数中)? - vadian
我工作的公司将所有日期存储为毫秒。 - user1079052
2
但是双重转换日期->字符串->日期的目的是什么?代码中缺少的“格式”是什么? - vadian
我不理解你想说什么。我使用currentTimeToMilliseconds将UTC日期发送到服务器。然后,当服务器发送时间时,我使用dateFromMilliseconds返回当前日期。 - user1079052
4
你正在将日期转换为字符串,然后再转回相同的日期,这似乎是无用的。请问使用的日期格式是什么?实际上,“Int(Date().timeIntervalSince1970 * 1000)”可以替代整个函数。NSDate只是一个双精度浮点数,不考虑时区。 - vadian
5
在你的问题中,似乎使用了“纳秒”这个词,但实际上你想使用的是“毫秒”。一毫秒等于一百万纳秒,因此这不是一个无关紧要的错误。 - Caleb
10个回答

258

我不明白你为什么要处理字符串...

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)

我正在处理字符串,因为我需要将millisecondsSince1970转换为UTC时间,并使用init(milliseconds:Int)将其转换回本地时间。 - user1079052
3
日期对象始终以UTC时间表示。在我的示例中,这些值是UTC时间(请注意描述方法如何呈现以PDT开始的UTC纪元)。 - Travis Griggs
4
最好使用init(milliseconds: Double)而不是Int,否则在转换回来时会失去毫秒。 - dickyj
3
这个解决方案不会失去毫秒精度吗? - davis
1
如果我理解正确的话,timeIntervalSince1970 是一个双精度浮点数,因此具有毫秒精度。乘法器和转换是为了将其转换为整数。 - Nicolas Guillaume
@davis 它不会失去毫秒级的精度,但会失去亚毫秒级的精度。除非你绝对需要将日期转换为整数,否则请考虑使用我的答案。 - Peter Schorn

56

@Travis的解决方案可以工作,但在某些情况下

var millisecondsSince1970:Int 会导致应用程序崩溃

并显示错误信息:

双精度浮点数无法转换为Int类型,因为如果发生此操作,则结果将大于Int.max 请使用Int64更新您的答案

以下是更新后的答案

extension Date {
 var millisecondsSince1970:Int64 {
        return Int64((self.timeIntervalSince1970 * 1000.0).rounded()) 
        //RESOLVED CRASH HERE
    }

    init(milliseconds:Int) {
        self = Date(timeIntervalSince1970: TimeInterval(milliseconds / 1000))
    }
}

关于 Int 定义。

在 32 位平台上,Int 的大小与 Int32 相同,在 64 位平台上,Int 的大小与 Int64 相同。

通常我会在运行在 32 位环境的 iPhone 5 上遇到这个问题。现在新设备都是 64 位环境。它们的 Int 将是 Int64

希望对那些遇到同样问题的人有所帮助。


3
为什么会发生这个错误?自1970年以来的时间间隔是一个32位数字。至少在我们遇到2038年问题之前是这样的:https://en.wikipedia.org/wiki/Year_2038_problem - DoesData
3
如果你看到我们正在将数字乘以1000,这会超出Int32的范围。 - Prashant Tukadiya
3
一些旧的iOS设备运行在32位环境中。它的“Int”是“Int32”。但是新的iOS设备,例如iPhone 6,它的“Int”是“Int64”。因此,在旧的iOS设备上可能会由于32位问题而导致崩溃。 - AechoLiu
@AechoLiu 谢谢你指出了这个问题,针对这种情况应该采取什么解决方案?你能帮我更新我的答案吗? - Prashant Tukadiya
1
我在32位系统(iPad 2)上遇到了这个问题。这应该是正确的答案。 - doogilasovich

17

@Travis的解决方案是正确的,但是在生成日期时会损失毫秒。我已经添加了一行代码将毫秒包含在日期中:

如果您不需要这种精度,请使用Travis的解决方案,因为它速度更快。

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

}

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

我去掉了似乎无用的将字符串转换和所有那些随机的 !


这很好,但我需要currentTimeInMilliseconds将本地时间转换为UTC,而且我需要dateFromMilliseconds将UTC转换为本地时间。 - user1079052
时间戳已经是UTC时间。从参考日期(1970年1月1日UTC时间00:00:00)开始的秒数。 - user28434'mstep
为什么要 * 1000,我认为我们丢失了毫秒。 - famfamfam
您不会失去毫秒级的精度,但会失去亚毫秒级的精度。除非时间间隔绝对需要表示为整数,否则请考虑下面我的答案。 - Peter Schorn
@PeterSchorn,没错,答案中的代码只是问题中代码的清理版本。因为问题中有Int,所以答案也有Int - user28434'mstep
@user28434 好的,明白了。希望其他人也会发现我的答案有用。 - Peter Schorn

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

2

Prashant Tukadiya的回答是可行的。但是如果你想将值保存在UserDefaults中,然后将其与其他日期进行比较,你会得到被截断的int64,这可能会导致问题。我找到了一个解决方案。

Swift 4:

您可以将int64作为字符串保存在UserDefaults中:

UserDefaults.standard.set("\(yourInt64Value)", forKey: "yourKey")

let value: String(Date().millisecondsSince1970)
let stringValue = String(value)
UserDefaults.standard.set(stringValue, forKey: "int64String")

这样可以避免整数截断。

然后您可以恢复原始值:

最初的回答:

let int64String = UserDefaults.standard.string(forKey: "int64String")
let originalValue = Int64(int64String!)

这可以让您将其与其他日期值进行比较:

最初的回答

let currentTime = Date().millisecondsSince1970
let int64String = UserDefaults.standard.string(forKey: "int64String")
let originalValue = Int64(int64String!) ?? 0 

if currentTime < originalValue {
     return false
} else {
     return true
}

希望这能帮助到遇到同样问题的人。最初的回答:

希望这对有相同问题的人有所帮助。


"Date().millisecondsSince1970" 正在显示为未解决的。 - WestCoastProjects
@javadba 尝试使用 Date().timeIntervalSince1970。返回值是一个 Double,它表示自1970年以来的秒数 - 它精确到“亚毫秒精度”。请参见此处:https://developer.apple.com/documentation/foundation/timeinterval - Jase
@Jase 谢谢,是的,我终于在不久之后遇到了这个问题,并且它已经解决了。 - WestCoastProjects
@javadba Date().millisecondsSince1970 是这个问题中的自定义扩展。它不在 Foundation 中。 - Peter Schorn

2

获取UInt64格式时间戳的简单一行代码

let time = UInt64(Date().timeIntervalSince1970 * 1000)

print(time) <----- prints time in UInt64

附加提示:

如果需要在API调用中使用从1970年起的10位毫秒级时间戳,那么:

let timeStamp = Date().timeIntervalSince1970

print(timeStamp) <-- prints current time stamp

2

这是一个简单的解决方案,适用于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)))
    }
}

这假设您已经计算了UTC时间和本地时间之间的差异,并在毫秒级别上进行了调整和处理。为此,请使用日历

var cal = Calendar.current
cal.timeZone = TimeZone(abbreviation: "UTC")!
let difference = cal.compare(dateGiven, to: date, toGranularity: .nanosecond)

1
除非你必须将日期转换为整数,否则考虑使用Double代替表示时间间隔。毕竟,这是timeIntervalSince1970返回的类型。所有将其转换为整数的答案都会失去亚毫秒精度,但这种解决方案更准确(尽管由于浮点不精确性仍会失去一些精度)。
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)
    }

}

1

如果你要在转换后比较日期,请小心!

例如,我得到了模拟器的资产,其中日期为TimeInterval(366144731.9),将其转换为毫秒Int64(1344451931900),然后再次转换为TimeInterval(366144731.9000001),使用

func convertToMilli(timeIntervalSince1970: TimeInterval) -> Int64 {
    return Int64(timeIntervalSince1970 * 1000)
}

func convertMilliToDate(milliseconds: Int64) -> Date {
    return Date(timeIntervalSince1970: (TimeInterval(milliseconds) / 1000))
}

我尝试按创建日期获取资产,但无法找到该资产,如您所料,数字不同。

我尝试了多种解决方案来减少双精度小数的精度,例如round(interval*1000)/1000、使用NSDecimalNumber等等,但都没有成功。

最终我通过interval-1 < creationDate < interval + 1来获取,而不是creationDate == Interval。

可能有更好的解决方案!?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接