Swift DateFormatter 没有根据预设的 Dateformat 转换日期

Swift DateFormatter doesn't convert date according to preset Dateformat

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

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

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

练习说明如下:

Calculate the moment when someone has lived for 10^9 seconds.

A gigasecond is 10^9 (1,000,000,000) seconds.

我写了下面的代码:

    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),
    ])

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

您使用了错误的日期格式 hh 是 01-12 小时。您需要的是 HH 00-23 小时。您的 dateFormat 应该是 "yyyy-MM-dd'T'HH:mm:ss"。请注意,日期描述将 return 一个 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
}

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

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"重新格式化才能获得所需的日期字符串。

再次衷心感谢参与本题解答讨论的各位。没有他们的建议,我不会着手制定解决方案。

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

这是这个问题的另一种解决方案,适用于更广泛的应用场景。此解决方案基于我的导师在 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)
    }
}

如果我能做些什么来改进上面的代码,请告诉我。非常感谢。