Swift Date literal / Date initialisation from string - swift

I need to initialize a date to a known, fixed, point in time. Swift lacking date literals, I tried :
extension DateFormatter
{
convenience init(_ format: String)
{
self.init()
self.dateFormat = format
}
}
extension Date
{
init?(_ yyyyMMdd: String)
{
let formatter = DateFormatter("yyyyMMdd")
self = formatter.date(from: yyyyMMdd)
}
}
Unfortunately, I can't write self = ... or return formatter.date... as part of the initializer.
However, I would very much like to write :
let date = Date("20120721")
How can I achieve this ?

You can initialize a struct by assigning a value to self.
The problem in your case is that formatter.date(from:)
returns an optional, so you have to unwrap that first:
extension Date {
init?(yyMMdd: String) {
let formatter = DateFormatter("yyyyMMdd")
guard let date = formatter.date(from: yyMMdd) else {
return nil
}
self = date
}
}
You can even initialize a date from a string literal by
adopting the ExpressibleByStringLiteral protocol:
extension Date: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
let formatter = DateFormatter("yyyyMMdd")
self = formatter.date(from: value)!
}
public init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
public init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}
}
let date: Date = "20120721"
Note however that this would crash at runtime for invalid dates.
Two more remarks:
You should set the formatters locale to Locale(identifier: "en_US_POSIX") to allow parsing the date string independent of
the user's locale settings.
The result depends on the current time zone, unless you also
set the formatters time zone to a fixed value (such as TimeZone(secondsFromGMT: 0).

Related

Trying to understand complex enum in Swift 5

I am using a package that has the following in it:
public enum DateValue {
case dateOnly(Date)
case dateAndTime(Date)
}
extension DateValue: Codable {
static let errorMessage = "Date string does not match format expected by formatter."
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let value = try container.decode(String.self)
if let date = DateFormatter.iso8601Full.date(from: value) {
self = .dateAndTime(date)
return
}
if let date = DateFormatter.iso8601DateOnly.date(from: value) {
self = .dateOnly(date)
return
}
throw Swift.DecodingError.dataCorruptedError(
in: container,
debugDescription: Self.errorMessage
)
}
public func encode(to encoder: Encoder) throws {
let value: String
switch self {
case .dateOnly(let date):
value = DateFormatter.iso8601DateOnly.string(from: date)
case .dateAndTime(let date):
value = DateFormatter.iso8601Full.string(from: date)
}
var container = encoder.singleValueContainer()
try container.encode(value)
}
}
In Xcode 13.2.1 I see this in the debugger:
So, I have theStartDate as a DataValue type but want to access it as a Foundation.Date. I would think this is simple, as I am sure it is, but this is over my head in Swift skills.
I am truly at a loss for how to do this and I think my main issue is not understanding how the emun and extension work at all. I have searched around but another problem I have is even defining what I am searching for since I am not sure what I am looking at to begin with. Just trying "theStartDate as Date" doesn't work.
I see I was getting confused by the extra code now and did not realize this is the same problem I have already solved. This code is working now:
func getDateFromKey(thePage: Page, theKey: String) -> Date? {
if let theProperty = thePage.properties[theKey] {
if case .date(let theDateValue) = theProperty.type {
if case .dateOnly(let theDate) = theDateValue!.start {
return theDate
}
}
}
return nil
}

How to create a Swift Regex that outputs a custom type?

In the WWDC videos, it was shown that you can do something like this with Captures/TryCaptures in the Regex Builder:
let regex = Regex {
// ...
TryCapture {
OneOrMore(.digit)
} transform: {
Int($0)
}
// ...
}
And the output of the Regex will be type safe. The Regex will output an Int for that group, instead of a Substring like it normally does.
However, what I would like to do is to change the entire output type of the whole Regex, like applying a transform: at the end of the Regex closure. For example, to parse a line containing the name, age and date of birth of a person:
John (30) 1992-09-22
I would like to do something like:
// this doesn't work and is just for illustration - there is no such Regex.init
let regex = Regex {
Capture(/\w+/)
" ("
TryCapture(/\d+/) { Int($0) }
") "
Capture(.iso8601Date(timeZone: .gmt))
} transform: { (_, name, age, dob) in
Person(name: String(name), age: age, dob: dob)
}
And I would expect regex be of type Regex<Person>, and not Regex<(Substring, Substring, Int, Date)>. That is, someString.wholeMatch(of: regex).output would be a string, not a tuple.
I'm basically just trying to reduce the occurrence of tuples, because I find it very inconvenient to work with them, especially unnamed ones. Since RegexComponent is parameterised by the unconstrained RegexOutput type, and there are built-in types where RegexOutput is Date and Decimal, surely doing this for arbitrary types using regex is not impossible, right?
My attempt was:
struct Person {
let name: String
let age: Int
let dob: Date
}
let line = "John (30) 1992-09-22"
let regex = Regex {
Capture {
Capture(/\w+/)
" ("
TryCapture(/\d+/) { Int($0) }
") "
Capture(.iso8601Date(timeZone: .gmt))
} transform: { (_, name, age, dob) in
Person(name: String(name), age: age, dob: dob)
}
}
line.wholeMatch(of: regex)
but this crashed at runtime, giving the message:
Could not cast value of type 'Swift.Substring' (0x7ff865e3ead8) to '(Swift.Substring, Swift.Substring, Swift.Int, Foundation.Date)' (0x7ff863f2e660).
Another attempt of mine using CustomConsumingRegexComponent is shown here in this answer, but that has quite a large caveat, namely that it doesn't backtrack properly.
How can I create a Regex that outputs my own type?
From what I have read/seen in samples (e.g. swift-regex), it might be a good idea to create a regex component similar to .word, .digit, but nesting captures does not seem to work easily.
Here is an example run in the playground to create a Person struct instance:
public static func regexBuilderMatching(string: String = "John (30) 1992-09-22") {
struct Person: CustomStringConvertible {
let name: String
let age: Int
let dob: Date
public func dobToFormatterString() -> String {
let dateFormatter = DateFormatter()
// 1992-09-22 04:00:00 +0000
dateFormatter.dateFormat = "yyyy-MM-dd"
return dateFormatter.string(from: self.dob)
}
var description: String {
return "\(name), age: \(age), has dob: \(dobToFormatterString())"
}
}
func dateFromString(dateString: String) -> Date? {
let formatter = DateFormatter()
formatter.timeStyle = .none // removes time from date
formatter.dateStyle = .full
formatter.dateFormat = "y-MM-d" // 1992-09-22
return formatter.date(from: dateString)
}
let regexWithBasicCapture = Regex {
/* 1. */ Capture { OneOrMore(.word) }
/* 2. */ " ("
/* 3. */ TryCapture { OneOrMore(.digit) }
transform: { match in
Int(match)
}
/* 4. */ ") "
/* 5. */ TryCapture { OneOrMore(.iso8601Date(timeZone: .gmt)) }
transform: { match in
dateFromString(dateString: String(match))
}
}
let matches = string.matches(of: regexWithBasicCapture)
for match in matches {
// shorthand syntax using match output
// https://developer.apple.com/documentation/swift/regex/match
let (_, name, age, date) = match.output
let person = Person(name: String(name), age: age, dob: date)
print(person)
}
}
The above code will output:
John, age: 30, has dob: 1992-09-22

Swift: Working with Calendar.component causes App to freeze without any Runtime Error?

Context:
I have a custom Day Struct which internally uses a Swift Date Property. I would this Struct be able to check, if another given day is the same weekday as the Day Struct itself. However, every time this Code gets executed, my App freezes without any Runtime Error.
Code
struct Day: Hashable {
private let calendar: Calendar = .current
let date: Date
static func == (lhs: Day, rhs: Day) -> Bool {
return Calendar.current.isDate(lhs.date, inSameDayAs: rhs.date)
}
func isSameDayOfTheWeek(as day: Day) -> Bool {
// Replacing this Line to RETURN TRUE prevents the Crash.
return calendar.component(.weekday, from: self.date) == calendar.component(.weekday, from: day.date)
}
}
enum Time: Int16, Comparable, Identifiable, CaseIterable {
case morning, noon, evening, night
var id: Int16 { self.rawValue }
var name: String { ... }
}
struct ToDo: Hashable, Identifiable {
let id: UUID = UUID()
let block: Block // NSManagedObject
let day: Day
let time: Time
}
struct ToDoView: View {
#FetchRequest(sortDescriptors: [SortDescriptor(\.creationTS)]) private var blocks: FetchedResults<Block>
var body: some View {
ForEach(toDos) { toDo in
Text(toDo.name)
}
}
private var toDos: [ToDo] {
var toDos: [ToDo] = []
for block in blocks {
let startDay: Day = block.safeStartDay
var day: Day = .today
while day >= startDay {
// Removing this Line prevents the Crash.
guard startDay.isSameDayOfTheWeek(as: day) else { continue }
for time in Time.allCases {
toDos.append(ToDo(block: block, day: day, time: time))
}
day = day - 1
}
}
return toDos
}
}
Question
How can I solve this Crash and still be able to check if another given day is the same weekday as the Day Struct itself?
Notice that in the loop, if isSameDayOfTheWeek returns false, then the loop never exits because you don't change day before continue.
A quick fix is to just add day = day - 1 before continue, but a much better solution would be to conform Day to Strideable, so that you could do something like:
for day in stride(from: .today, through: startDay, step: -1) {
...
}
Then just doing continue would work as you had expected.
If I understand correctly what you are trying to do here, you can implement Strideable like this:
func distance(to other: Day) -> Int {
calendar.dateComponents([.day], from: self.date, to: other.date).day!
}
func advanced(by n: Int) -> Day {
guard let date = calendar.date(byAdding: .day, value: n, to: self.date) else {
fatalError("No such date: \(n) days after \(self.date)")
}
return Day(date: date)
}
A note on the calendar: by initialising it to current, two instances of your struct could have end up with different calendars which have different timezones if the user decides to change it at some point.
let day1 = Day(...)
// some time later...
let day2 = Day(...)
Then comparing day1 and day2 could have some unexpected results:
day1.isSameDayOfTheWeek(as: day2) // true, because they are in the same day in day1's timezone
day2.isSameDayOfTheWeek(as: day1) // false, because they are not in the same day in day2's timezone
I would recommend using a fixed, static, calendar.

How could i convert with an extension of NumberFormatter a String to a Float

I have created an extension of NumberFormatter and binaryInteger, to convert Int to String with a space between thousands like thise: 11111 -> 11 111
Now, in another place, i need to reverse the convertion from a specific string to a Float , like this: 11 111 -> 11111.
Here is the first extensions of NumberFormatter and BinaryInteger:
extension Formatter {
static let withSeparator: NumberFormatter = {
let formatter = NumberFormatter()
formatter.groupingSeparator = " "
formatter.allowsFloats = true
formatter.numberStyle = .decimal
return formatter
}()
}
extension BinaryInteger {
var formattedWithSeparator: String {
return Formatter.withSeparator.string(for: self) ?? ""
}
}
So, how could i code an another extension, to make the reverse process?
thank you.
Try this:
extension String {
func backToFloat() -> Float {
// Make a copy of original string
var temp = self
// Remove spaces
temp.removeAll(where: { $0 == " " })
return Float(temp) ?? 0.0
}
}
print("1 234 567.2".backToFloat())
// log: 1234567.2
To enable Float -> String and Double -> String:
extension FloatingPoint {
var formattedWithSeparator: String {
return Formatter.withSeparator.string(for: self) ?? ""
}
}
print(12345678.12.formattedWithSeparator)
// log: 12 345 678.12
You can use the same withSeparator formatter, and add another extension to BinaryInteger:
extension BinaryInteger {
init?(fromStringWithSeparator string: String) {
if let num = NumberFormatter.withSeparator.number(from: string)
.map({ Self.init(truncatingIfNeeded: $0.int64Value) }) {
self = num
} else {
return nil
}
}
}
Here, I basically parsed the number into an NSNumber, and then converted that to an Int64, then converted that to whatever type of BinaryInteger is required. This won't work for the values of UInt64 that are outside of the range of Int64, as the first conversion will convert them to a negative number. So if you want to handle those numbers as well, you should write another UInt64 extension:
extension UInt64 {
init?(fromStringWithSeparator string: String) {
if let num = NumberFormatter.withSeparator.number(from: string)?.uint64Value {
self = num
} else {
return nil
}
}
}

How to handle a mix of standard and customized Swift 4 Decodable properties?

I've been experimenting with customized Decodable properties for handling JSON in Swift 4 and I'm really impressed with the ease of mapping tricky type and format conversions.
However in the JSON data structures that the server exposes to me, only a handful of properties need this treatment. The rest are simple integers and strings. Is there some way to mix the customized decoder with the standard one?
Here's a simplified example showing what I'd like to get rid of:
struct mystruct : Decodable {
var myBool: Bool
var myDecimal: Decimal
var myDate: Date
var myString: String
var myInt: Int
}
extension mystruct {
private struct JSONsource: Decodable {
var my_Bool: Int
var my_Decimal: String
var my_Date: String
// These seem redundant, how can I remove them?
var myString: String
var myInt: Int
}
private enum CodingKeys: String, CodingKey {
case item
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let item = try container.decode(JSONsource.self, forKey: .item)
myBool = item.my_Bool == 1 ? true : false
myDecimal = Decimal(string: item.my_Decimal)!
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZZZZZ"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
myDate = dateFormatter.date(from: item.my_Date)!
// Can I somehow get rid of this redundant-looking code?
myString = item.myString
myInt = item.myInt
}
}
let myJSON = """
{
"item": {
"my_Decimal": "123.456",
"my_Bool" : 1,
"my_Date" : "2019-02-08T11:14:31.4547774-05:00",
"myInt" : 148727,
"myString" : "Hello there!"
}
}
""".data(using: .utf8)
let x = try JSONDecoder().decode(mystruct.self, from: myJSON!)
print("My decimal: \(x.myDecimal)")
print("My bool: \(x.myBool)")
print("My date: \(x.myDate)")
print("My int: \(x.myInt)")
print("My string: \(x.myString)")
JSONDecoder has a dateDecodingStrategy. No need to decode it manually. To simplify decoding your coding keys you can set decoders property .keyDecodingStrategy to . convertFromSnakeCase. You have also some type mismatches you can handle adding computed properties. Btw this might help creating a custom formatter for your ISO8601 date string with fractional seconds. How to create a date time stamp and format as ISO 8601, RFC 3339, UTC time zone? . Last but not least, it is Swift convention to name your structures using UpperCamelCase
struct Root: Codable {
let item: Item
}
struct Item : Codable {
var myBool: Int
var myDecimal: String
var myDate: Date
var myString: String
var myInt: Int
}
extension Item {
var bool: Bool {
return myBool == 1
}
var decimal: Decimal? {
return Decimal(string: myDecimal)
}
}
extension Item: CustomStringConvertible {
var description: String {
return "Iten(bool: \(bool), decimal: \(decimal ?? 0), date: \(myDate), string: \(myString), int: \(myInt))"
}
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZZZZZ"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
decoder.dateDecodingStrategy = .formatted(dateFormatter)
do {
let item = try decoder.decode(Root.self, from: myJSON).item
print(item) // Iten(bool: true, decimal: 123.456, date: 2019-02-08 16:14:31 +0000, string: Hello there!, int: 148727)
}
catch {
print(error)
}