Skip to content

Commit

Permalink
Removed force unwrap from string initializers and added tests. isoDat…
Browse files Browse the repository at this point in the history
…eTime now includes seconds, Removed isoDateTimeSec. Renamed isoDateTimeMilliSec isoDateTimeFull.
  • Loading branch information
melvitax committed Dec 8, 2021
1 parent 41ee31d commit e909947
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 65 deletions.
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ Initializes a date from a string using a strict or custom format that is cached,
Date(fromString: "2009", format: .isoYear)
Date(fromString: "2009-08", format: .isoYearMonth)
Date(fromString: "2009-08-11", format: .isoDate)
Date(fromString: "2009-08-11T06:00-07:00", format: .isoDateTime)
Date(fromString: "2009-08-11T06:00:00-07:00", format: .isoDateTimeSec)
Date(fromString: "2009-08-11T06:00:00.000-07:00", format: .isoDateTimeMilliSec)
Date(fromString: "2009-08-11T06:00:00-07:00", format: .isoDateTime)
Date(fromString: "2009-08-11T06:00:00.000-07:00", format: .isoDateTimeFull)
Date(fromString: "/Date(1260123281843)/", format: .dotNet)
Date(fromString: "Fri, 09 Sep 2011 15:26:08 +0200", format: .rss)
Date(fromString: "09 Sep 2011 15:26:08 +0200", format: .altRSS)
Expand Down Expand Up @@ -78,8 +77,7 @@ Date().toString(format: .isoYear)
Date().toString(format: .isoYearMonth)
Date().toString(format: .isoDate)
Date().toString(format: .isoDateTime)
Date().toString(format: .isoDateTimeSec)
Date().toString(format: .isoDateTimeMilliSec)
Date().toString(format: .isoDateTimeFull)
Date().toString(format: .dotNet)
Date().toString(format: .rss)
Date().toString(format: .altRSS)
Expand Down
91 changes: 42 additions & 49 deletions Sources/DateHelper/DateHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,50 +18,49 @@ public extension Date {
Creates a new Date based on a string of a specified format. Supports optional timezone and locale.
*/
init?(fromString string: String, format:DateFormatType, timeZone: TimeZoneType = .local, locale: Locale = Foundation.Locale.current, isLenient: Bool = true) {
guard !string.isEmpty else {
return nil
}

guard !string.isEmpty else { return nil }

var string = string

switch format {
case .dotNet:
let pattern = "\\\\?/Date\\((\\d+)(([+-]\\d{2})(\\d{2}))?\\)\\\\?/"
let regex = try! NSRegularExpression(pattern: pattern)
guard let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) else {
return nil
}
#if swift(>=4.0)
case .dotNet:
let pattern = "\\\\?/Date\\((\\d+)(([+-]\\d{2})(\\d{2}))?\\)\\\\?/"
let regex = try? NSRegularExpression(pattern: pattern)
let range = NSRange(location: 0, length: string.utf16.count)
if let match = regex?.firstMatch(in: string, range: range) {
let dateString = (string as NSString).substring(with: match.range(at: 1))
#else
let dateString = (string as NSString).substring(with: match.rangeAt(1))
#endif
let interval = Double(dateString)! / 1000.0
self.init(timeIntervalSince1970: interval)
return
case .rss, .altRSS:
if string.hasSuffix("Z") {
string = string[..<string.index(string.endIndex, offsetBy: -1)].appending("GMT")
}
case .isoDateTimeMilliSec, .isoDateTimeSec, .isoDateTime, .isoYear,. isoDate, .isoYearMonth:
if #available(iOS 10.0, watchOS 4, tvOS 10, macOS 11, *) {
let formatter = Date.cachedDateFormatters.cachedISOFormatter(format, timeZone: timeZone, locale: locale)
guard let date = formatter.date(from: string) else {
return nil
}
self.init(timeInterval: 0, since: date)
return
}
default:
break
} else {
return nil
}
case .rss, .altRSS:
if string.hasSuffix("Z") {
string = string[..<string.index(string.endIndex, offsetBy: -1)].appending("GMT")
}
case .isoDateTimeFull, .isoDateTime, .isoYear, . isoDate, .isoYearMonth:
let formatter = Date.cachedDateFormatters.cachedISOFormatter(format, timeZone: timeZone, locale: locale)
if let date = formatter.date(from: string) {
self.init(timeInterval: 0, since: date)
return
} else {
return nil
}
default:
break
}
let formatter = Date.cachedDateFormatters.cachedFormatter(
format.stringFormat,
timeZone: timeZone.timeZone,
locale: locale,
isLenient: isLenient)
guard let date = formatter.date(from: string) else {
if let date = formatter.date(from: string) {
self.init(timeInterval: 0, since: date)
} else {
return nil
}
self.init(timeInterval: 0, since: date)
}

/*
Expand Down Expand Up @@ -128,8 +127,7 @@ public extension Date {
let offset = Foundation.NSTimeZone.default.secondsFromGMT() / 3600
let nowMillis = 1000 * self.timeIntervalSince1970
return String(format: format.stringFormat, nowMillis, offset)
case .isoDateTimeMilliSec, .isoDateTimeSec, .isoDateTime,
.isoYear,. isoDate, .isoYearMonth:
case .isoDateTimeFull, .isoDateTime, .isoYear,. isoDate, .isoYearMonth:
if #available(iOS 10.0, watchOS 4, tvOS 10, macOS 11, *) {
let formatter = Date.cachedDateFormatters.cachedISOFormatter(format, timeZone: timeZone, locale: useLocale)
return formatter.string(from: self)
Expand Down Expand Up @@ -683,15 +681,15 @@ public extension Date {

var options: ISO8601DateFormatter.Options = []
switch format {
case .isoDate:
options = [.withFullDate]
case .isoYearMonth:
options = [.withYear, .withMonth]
case .isoYear:
options = [.withYear, .withFractionalSeconds]
case .isoDateTimeSec, .isoDateTime:
case .isoYearMonth:
options = [.withYear, .withMonth, .withDashSeparatorInDate]
case .isoDate:
options = [.withFullDate]
case .isoDateTime:
options = [.withInternetDateTime]
case .isoDateTimeMilliSec:
case .isoDateTimeFull:
options = [.withInternetDateTime, .withFractionalSeconds]
default:
fatalError("Unimplemented format \(format)")
Expand Down Expand Up @@ -765,9 +763,8 @@ public extension Date {
case isoYear: i.e. 1997
case isoYearMonth: i.e. 1997-07
case isoDate: i.e. 1997-07-16
case isoDateTime: i.e. 1997-07-16T19:20+01:00
case isoDateTimeSec: i.e. 1997-07-16T19:20:30+01:00
case isoDateTimeMilliSec: i.e. 1997-07-16T19:20:30.45+01:00
case isoDateTime: i.e. 1997-07-16T19:20:30+01:00
case isoDateTimeFull: i.e. 1997-07-16T19:20:30.45+01:00
case dotNet: i.e. "/Date(1268123281843)/"
case rss: i.e. "Fri, 09 Sep 2011 15:26:08 +0200"
case altRSS: i.e. "09 Sep 2011 15:26:08 +0200"
Expand All @@ -788,14 +785,11 @@ public enum DateFormatType {
/// The ISO8601 formatted date "yyyy-MM-dd" i.e. 1997-07-16
case isoDate

/// The ISO8601 formatted date and time "yyyy-MM-dd'T'HH:mmZ" i.e. 1997-07-16T19:20+01:00
case isoDateTime

/// The ISO8601 formatted date, time and sec "yyyy-MM-dd'T'HH:mm:ssZ" i.e. 1997-07-16T19:20:30+01:00
case isoDateTimeSec
case isoDateTime

/// The ISO8601 formatted date, time and millisec "yyyy-MM-dd'T'HH:mm:ss.SSSZ" i.e. 1997-07-16T19:20:30.45+01:00
case isoDateTimeMilliSec
case isoDateTimeFull

/// The dotNet formatted date "/Date(%d%d)/" i.e. "/Date(1268123281843)/"
case dotNet
Expand All @@ -820,9 +814,8 @@ public enum DateFormatType {
case .isoYear: return "yyyy"
case .isoYearMonth: return "yyyy-MM"
case .isoDate: return "yyyy-MM-dd"
case .isoDateTime: return "yyyy-MM-dd'T'HH:mmZ"
case .isoDateTimeSec: return "yyyy-MM-dd'T'HH:mm:ssZ"
case .isoDateTimeMilliSec: return "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
case .isoDateTime: return "yyyy-MM-dd'T'HH:mm:ssZ"
case .isoDateTimeFull: return "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
case .dotNet: return "/Date(%d%f)/"
case .rss: return "EEE, d MMM yyyy HH:mm:ss ZZZ"
case .altRSS: return "d MMM yyyy HH:mm:ss ZZZ"
Expand Down
81 changes: 81 additions & 0 deletions Tests/DateHelperTests/DateFromStringTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import XCTest
@testable import DateHelper

final class DateHelperTests: XCTestCase {

// date from isoYear string
func testDateFromIsoYearString() throws {
let date = Date(fromString: "2009", format: .isoYear)
let comparison = Calendar.current.date(from: DateComponents(year: 2009))
XCTAssertEqual(date, comparison)
}

// date from isoYearMonth string
func testDateFromIsoYearMonthString() throws {
let date = Date(fromString: "2009-08", format: .isoYearMonth)
let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 08))
XCTAssertEqual(date, comparison)
}

// date from isoYearMonth string
func testDateFromIsoDateString() throws {
let date = Date(fromString: "2009-08-11", format: .isoDate)
let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 08, day: 11))
XCTAssertEqual(date, comparison)
}

// date from isoDateTime string
func testDateFromIsoDateTimeString() throws {
let date = Date(fromString: "2009-08-11T06:30:03-05:00", format: .isoDateTime)
let offset = -5
let timeZone = TimeZone(secondsFromGMT: 60 * 60 * offset)
let comparison = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2009, month: 08, day: 11, hour: 06, minute: 30, second: 03))
XCTAssertEqual(date, comparison)
}

// date from isoDateTimeFull string
func testDateFromIsoDateTimeFullString() throws {
let date = Date(fromString: "2009-08-11T06:30:03.161-05:00", format: .isoDateTimeFull)
let offset = -5
let nanoseconds = 161*1000000
let timeZone = TimeZone(secondsFromGMT: 60 * 60 * offset)
let comparison = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2009, month: 08, day: 11, hour: 06, minute: 30, second: 03, nanosecond: nanoseconds))
XCTAssertEqual(date, comparison)
}

// date from dotNet string
func testDateFromDotNetString() throws {
let date = Date(fromString: "/Date(1260123281843)/", format: .dotNet)
let timeZone = TimeZone(secondsFromGMT: 0)
let nanoseconds = 843*1000000
let comparison = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 41, nanosecond: nanoseconds))
XCTAssertEqual(date?.timeIntervalSince1970, comparison?.timeIntervalSince1970)
}

// date from rss string
func testDateFromRSSString() throws {
let date = Date(fromString: "Fri, 09 Sep 2011 15:26:08 +0200", format: .rss)
let offset = 2
let timeZone = TimeZone(secondsFromGMT: 60 * 60 * offset)
let comparison = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2011, month: 09, day: 09, hour: 15, minute: 26, second: 08))
XCTAssertEqual(date, comparison)
}

// date from altRSS string
func testDateFromAltRSSString() throws {
let date = Date(fromString: "09 Sep 2011 15:26:08 +0200", format: .altRSS)
let offset = 2
let timeZone = TimeZone(secondsFromGMT: 60 * 60 * offset)
let comparison = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2011, month: 09, day: 09, hour: 15, minute: 26, second: 08))
XCTAssertEqual(date, comparison)
}

// date from httpHeader string
func testDateFromHttpHeaderString() throws {
let date = Date(fromString: "Wed, 01 03 2017 06:43:19 -0500", format: .httpHeader)
let offset = -5
let timeZone = TimeZone(secondsFromGMT: 60 * 60 * offset)
let comparison = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2017, month: 03, day: 01, hour: 06, minute: 43, second: 19))
XCTAssertEqual(date, comparison)
}
}
11 changes: 0 additions & 11 deletions Tests/DateHelperTests/DateHelperTests.swift

This file was deleted.

0 comments on commit e909947

Please sign in to comment.