Swift DateFormatter 不会根据预设的 Dateformat 转换日期

Posted

技术标签:

【中文标题】Swift DateFormatter 不会根据预设的 Dateformat 转换日期【英文标题】:Swift DateFormatter doesn't convert date according to preset Dateformat 【发布时间】:2020-07-14 04:40:11 【问题描述】:

我在Exercism 上为 Swfit 进行在线练习时遇到了一个问题。但是,我写的代码一直无法通过网站提供的测试套件。问题似乎在于包含在可选中的结果日期对象的日期格式。

我能够获得传入dateString 的格式以符合测试套件日期格式。但是,我无法使 destinationDate 也符合测试套件日期格式。

我尝试使用ISO8601DateFormatter,但我的旧 Mac 上的编译器不支持此类。我在在线 Swift 编译器上尝试了我的代码,但到目前为止结果也不令人满意。

练习描述如下:

计算某人活了 10^9 秒的时刻。

千兆秒是 10^9 (1,000,000,000) 秒。

我写了以下代码:

    import Foundation
    
    func Gigasecond(from dateString: String) -> Date? 
        let GIGASECOND: Double = 1_000_000_000
        let RFC3339DateFormatter = DateFormatter()
    
        RFC3339DateFormatter.locale = Locale(identifier: "en_US_POSIX")
        RFC3339DateFormatter.dateFormat = "yyyy-MM-dd'T'hh:mm:ss"
    
        let sourceDate = RFC3339DateFormatter.date(from: dateString)
        var destinationDate: Date? = Date(timeInterval: GIGASECOND, since: sourceDate ?? Date())
        let destDateString = RFC3339DateFormatter.string(from: destinationDate ?? Date())
        destinationDate = RFC3339DateFormatter.date(from: destDateString)
      
        return destinationDate
    

网站提供的本次练习的测试套件如下:

//GigasecondTests.swift

import XCTest
@testable import Gigasecond

class GigasecondTests: XCTestCase 

    func test1 () 
        let gs = Gigasecond(from: "2011-04-25T00:00:00")?.description
        XCTAssertEqual("2043-01-01T01:46:40", gs)
    

    func test2 () 
        let gs = Gigasecond(from: "1977-06-13T00:00:00")?.description
        XCTAssertEqual("2009-02-19T01:46:40", gs)
    

    func test3 () 
        let gs = Gigasecond(from: "1959-07-19T00:00:00")?.description
        XCTAssertEqual("1991-03-27T01:46:40", gs)
    

    func testTimeWithSeconds () 
        let gs = Gigasecond(from: "1959-07-19T23:59:59")?.description
        XCTAssertEqual("1991-03-28T01:46:39", gs)
    

    func testFullTimeSpecified () 
        let gs = Gigasecond(from: "2015-01-24T22:00:00")?.description
        XCTAssertEqual("2046-10-02T23:46:40", gs)
    

    func testFullTimeWithDayRollOver () 
        let gs = Gigasecond(from: "2015-01-24T23:59:59")?.description
        XCTAssertEqual("2046-10-03T01:46:39", gs)
    

    static var allTests: [(String, (GigasecondTests) -> () throws -> Void)] 
        return [
            ("test1 ", test1 ),
            ("test2 ", test2 ),
            ("test3 ", test3 ),
            ("testTimeWithSeconds ", testTimeWithSeconds ),
            ("testFullTimeSpecified ", testFullTimeSpecified ),
            ("testFullTimeWithDayRollOver ", testFullTimeWithDayRollOver ),
        ]
    
 

// LinuxMain.swift
import XCTest
@testable import GigasecondTests

XCTMain([
    testCase(GigasecondTests.allTests),
    ])

请帮忙看看我的代码有什么问题。非常感谢!

【问题讨论】:

您使用了错误的 dateFormat hh 是 01-12 您需要的是 HH 00-23。 dateFormat = "yyyy-MM-dd'T'HH:mm:ss"。请注意,描述将导致 UTC 日期描述(不是当前时区) 您的方法必须返回Date吗?从测试用例来看,它应该返回一个String... @Sweeper 如果 OP 使用日期,则测试用例将永远不值得推力。它总是会失去精度。他需要使用日期格式化程序从解析的日期生成字符串以测试是否相等 @Sweeper ***.com/questions/60136982/… @LeoDabus 是的,这就是我的意思...... 【参考方案1】:

您使用了错误的 dateFormat hh 是 01-12 小时。您需要的是HH 00-23 小时。你的dateFormat 应该是"yyyy-MM-dd'T'HH:mm:ss"。请注意,日期描述将返回 UTC 日期描述(不是当前时区)。你 需要使用相同的日期格式化程序从解析的日期生成字符串以测试是否相等:

extension Formatter 
    static let rfc3339: DateFormatter = 
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
        return dateFormatter
    ()

你的千兆秒方法可以简化为:

func gigasecond(from dateString: String) -> Date? 
    return Formatter.rfc3339.date(from: dateString)?
        .addingTimeInterval(1_000_000_000)


测试:

if let gs = gigasecond(from: "2011-04-25T00:00:00") 
    "2043-01-01T01:46:40" == Formatter.rfc3339.string(from: gs)  // true

【讨论】:

感谢@LeoDabus,您的解决方案。我通过测试套件运行它并且测试失败了。测试报告太长,不能在这里粘贴。总之,您的代码生成的日期与测试套件提供的日期不匹配。无论如何,再次感谢您的帮助。 我已经测试了 test1 并且它有效。随意编辑您的问题【参考方案2】:

我对我的代码进行了一些调整,并幸运地通过了测试套件。我修改后的代码如下:

import Foundation

func Gigasecond(from dateString: String) -> String? 
    let GIGASECOND: Double = 1_000_000_000
    let RFC3339DateFormatter = DateFormatter()

    RFC3339DateFormatter.locale = Locale(identifier: "en_US_POSIX")
    RFC3339DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
    RFC3339DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)

    let sourceDate = RFC3339DateFormatter.date(from: dateString)
    var destinationDate: Date? = Date(timeInterval: GIGASECOND, since: sourceDate ?? Date())
    var destDateString = RFC3339DateFormatter.string(from: destinationDate ?? Date())
    destinationDate = RFC3339DateFormatter.date(from: destDateString)
    destDateString = RFC3339DateFormatter.string(from: destinationDate ?? Date())
  
    return destDateString

原来生成的destinationDate需要用日期格式"yyyy-MM-dd'T'HH:mm:ss"重新格式化,才能得到需要的日期字符串。

再次衷心感谢所有参与讨论此问题解决方案的人。如果没有他们的建议,我不会着手制定解决方案。

我期待进一步讨论解决问题的更多更好的方法。谢谢。

【讨论】:

您的字符串中没有时区信息。通常,如果字符串没有时区,您将无法真正知道它是代表本地时间还是 UTC 时间。通常,当您将日期存储在服务器中时,您使用 UTC 时区并将Z、“+0000”或“+00:00”添加到您的日期字符串以避免这种情况。我想知道为什么你会用当前本地时间和 UTC/GMT 时间进行一些测试。 感谢@LeoDabus 的评论。你是对的。我应该在日期格式化程序中添加一个TimeZone(identifier: "UTC")。这也是我的导师在 Exercism 网站上给出的修改建议之一。根据导师的建议,我重写了代码,以适应更广泛的应用环境。我提出修改后的代码作为该线程的替代解决方案。【参考方案3】:

这是此问题的另一种解决方案,适用于更广泛的应用环境。这个解决方案是基于我导师在Exercism网站上的建议。

我还要感谢 Leo Dabus,他建议我在日期格式化程序中添加时区信息以避免隐式采用的默认本地时区的影响。

代码如下:

import Foundation

struct Gigasecond 
    public let gigasecond : Double = 1_000_000_000
    private let rfc3339DateFormatter : DateFormatter =  
            let myFormatter = DateFormatter()
            myFormatter.timeZone = TimeZone(identifier: "UTC")
            myFormatter.locale = Locale(identifier: "en_US_POSIX")
            myFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"

            return myFormatter 
    ()
    let description : String

    init?(from dateString: String) 

        let sourceDate = rfc3339DateFormatter.date(from: dateString)
        guard let srcDate = sourceDate else  
            description = "" 
            return 
        
        let destinationDate = Date(timeInterval: gigasecond, since: srcDate)
        description = rfc3339DateFormatter.string(from: destinationDate)
    

如果我可以做些什么来改进上述代码,请告诉我。非常感谢。

【讨论】:

无需为您的问题添加多个答案。您可以编辑原始帖子或其他答案 请注意,您应该将 DateFormatter 语言环境设置为“en_US_POSIX”,正如我在帖子中已经提到的那样 Swift 命名约定以小写字母开头的属性命名 如果您想了解如何创建正确的 RFC3339 日期格式化程序,请查看此帖子 ***.com/questions/28016578/… 只需确保在设置 dateFormat 之前始终设置您的语言环境。它通常应该是要设置的第一个属性。

以上是关于Swift DateFormatter 不会根据预设的 Dateformat 转换日期的主要内容,如果未能解决你的问题,请参考以下文章

DateFormatter 不转换特定日期 (Swift)

在swift中使用DateFormatter时发生错误[重复]

Swift:DateFormatter 返回 nil

Swift DateFormatter 提取时间

DateFormatter 中的 Swift 语言错误? [复制]

在 Swift 中使用 DateFormatter 和 TimeZone 来格式化日期