WWDC24 SALE: Save 50% on all my Swift books and bundles! >>

Dates Are Hard 🗓️

Forums > SwiftUI

So I've spent the last three or four days using most of my free time to try and find an answer to this dilema I'm having. Simply put, Dates are hard. I've looked over a lot of Hudson's documentation on dates, and I've referenced Apples documentation as well. I've looked at various YouTube channels, and I've scoured Stack for help.

I need to compare two dates based on a bool, with complexity. If the user is over a certian age, then the length of time between the users input date and the constant date is 24 months. If the user is under a certian age, the constant is 60 months. I need to record the exact day of the users input. This could be the fifth of the month, or the tenth, or the twenty-first. The constant date must always end at the end of the calendar month 24 or 60 months following the users input date. So: The user's certificate was issues on November 1st, 2023. The expiration of the date, if the bool is false, is November 30th, 2025 (the last calendar day of the month).

In similar fashion, the bool is a variable based on the users birthdate. I'm comfortable comparing specific constant dates and variables, but I can't quite figure out how to ensure that the specific constant I'm asking for is always the last day of the month two or five years following the users input. Basically, how do I always ensure that shows the input month, at the last day of that month?

Anybody have any ideas? I hate dates, and am struggling to learn this part specifically.

Thanks, Andy

3      

func addTwentyFourMonths(to date: Date) -> Date? {
    let calendar = Calendar.current
    return calendar.date(byAdding: .month, value: 24, to: date)
}

func lastSecondOfTheMonth(from date: Date) -> Date? {
    var components = DateComponents()
    let calendar = Calendar.current

    // Extract the year and month from the given date
    guard let year = calendar.dateComponents([.year], from: date).year,
          let month = calendar.dateComponents([.month], from: date).month else { return nil }

    // Set the components to the first day of the next month
    components.year = year
    components.month = month + 1
    components.day = 1
    components.second = -1

    // Get the date that's one second before the first second of the next month
    return calendar.date(from: components)
}

4      

Hi @CodingPilot. Yes dates are hard!!!

Make a new swift file (I called mine Date-Extension.swift) then add this ( I have put some comments so you can see what I done)

import Foundation

extension Date {
    /// Calculates a date from a set date plus a number of month that are set by a set age.
    /// - Parameters:
    ///   - DoB: Date of Birth.
    ///   - criteriaAge: Number of years that need to have 60 months to 24 months (default to 18).
    /// - Returns: A end of the subscription month from a set date of 24 or 60 depending on criteria age.
    func subscriptionDate(DoB: Date, criteriaAge: Int = 18) -> Date {
        // Gets current calendar to be able to calculates dates
        let calendar = Calendar.current

        // initial date
        var date: Date? = .now

        // calculates a date from date of birth plus the criteria age (default of 18 years)
        guard let criteriaDate = calendar.date(byAdding: DateComponents(year: criteriaAge), to: DoB) else {
            fatalError("Unable to get criteria date from date of birth")
        }

        // if date from user input date to either 60 months or 24 months depending if date is before the criteriaDate
        if self < criteriaDate {
            date = calendar.date(byAdding: DateComponents(month: 60), to: self)
        } else {
            date = calendar.date(byAdding: DateComponents(month: 24), to: self)
        }

        // makes date non optional
        guard let date = date else {
            fatalError("Unable to calculate date")
        }

        // get the 1st day of month from the subscription date
        guard let startOfSubscriptionMonth = calendar.date(from: calendar.dateComponents([.month, .year], from: date)) else {
            fatalError("Unable to get start date from date")
        }

        // returns the last day of month of the subscription date
        return calendar.date(byAdding: DateComponents(month: 1, day: -1), to: startOfSubscriptionMonth)!
    }
}

Made a small project to show how top use

struct ContentView: View {
    @State private var DoB = Date.now
    @State private var signUpDate = Date.now

    var body: some View {
        Form {
            DatePicker("Date of Birth", selection: $DoB , displayedComponents: .date)

            DatePicker("Sign Up Date", selection: $signUpDate, displayedComponents: .date)

            // default age
            LabeledContent("Subscription Month End") {
                Text(signUpDate.subscriptionDate(DoB: DoB), style: .date)
            }

            // Changed to 21 years old
            LabeledContent("Subscription Month End with 21 years") {
                Text(signUpDate.subscriptionDate(DoB: DoB, criteriaAge: 21), style: .date)
            }
        }
    }
}

If you make the date of birth to be older then 18 (or 21 in second label) then you will see only 24 months added otherwise it 60 months from the Sign up date.

4      

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

50 points to the DoubleDecker team for all the excellent comments! Nice explanations.

-5 points for "inertial date". (initial? initial date?) 😜

PS: Nigel, I like adding the following code comment before extensions. It helps me navigate once my code files get long.

// MARK: - Date Extensions
extension Date {          
     // ------- code here --------

See -> Adding Code Markers

4      

Thank you @Obelix for pointing out typo (now corrected). I would not add a MARK as it in a file called Date-Extension.swift however could add a MARK above the description comment if you have a lot of date extensions.

// MARK: - Subscription Date
/// Calculates a date from a set date plus a number of month that are set by a set age
/// .............

Because I have added the methods comment if you option+click on call site you will get the summary and the file name.

4      

Thank you @eoinnorris and @NigelGee for the great feedback and examples. Unfortunately, in the 100 days courses it seems that dates are not covered too in depth, or I should say in depth enough for what it is I'm trying to learn. On that note, does anyone have any suggestions as to what books would cover dates the best? I'm not in a position to buy any of the bundles, but dates are going to be a pivotal part of my projects going forward and I really need to learn the better.

For another roadblock I'm experiencing: After calculating the dates, I'm looking to save them in UserDefaults. There's no identifying information (no names, no addresses, no phone numbers, just the birthdate for the sake of validating checkride readiness and medical qualification) saved in the app other than the users birthdate so I'm not terribly concerned about safety. I've tried a few methods to save selections out of a date picker, but I can't get the date picker to run a function to convert the date to a string for storage to AppStorage. What am I doing wrong here? .onCommit and .onSubmit don't work here, and attempting to write it like the following yeilds an error:

"Static method 'buildExpression' requires that 'Text' conform to 'TableRowContent'"

DatePicker("What is your birthdate?", selection: $birthdate, displayedComponents: .date) {
                    birthdateToString()
                }
                    .accentColor(Color.teal)

I've also tried using didSet on the variable itself, but can't get anything to print in my console indicating that the value was set...

@State var birthdate: Date = Date() {
        didSet {
            birthdateToString()
            print("The birthdate has now been set to \(birthdateString)")
        }
    }
func birthdateToString() {
        let dateFormatterString = DateFormatter()
        dateFormatterString.dateStyle = .short

        birthdateString = dateFormatterString.string(from: birthdate)

How do you run a function when the user selects a new date in the picker? This seems easy and I feel stupid for having to ask it.

3      

Firstly I do not know anyone that does a book on just dates, however if you search the Forum and type "dates" you will get alot of feed back, there are alot of question (as dates are hard!). Also if you get stuck then there is always here. I tend to uses the extension Date to do all the date calculation as keep the call site clean.

Secondly @AppStorage does NOT store Dates but you can store them in UserDefaults, However Paul in one of HWS+ live streams (Drink up!) has a work around.

@AppStorage("storedDate") var storedDate = Date.now.timeIntervalSinceReferenceDate

However this will not work in DatePicker, so add this

DatePicker("Date of Birth", selection: $DoB , displayedComponents: .date)
    .onChange(of: DoB) { _, newDate in
        storedDate = newDate.timeIntervalSinceReferenceDate
    }

Then add .onAppear on Form to get the saved date to DoB property

.onAppear {
    DoB = Date(timeIntervalSinceReferenceDate: storedDate)
}

Here the final sample project

struct ContentView: View {
    @AppStorage("storedDate") var storedDate = Date.now.timeIntervalSinceReferenceDate
    @State private var DoB = Date.now
    @State private var signUpDate = Date.now

    var body: some View {
        Form {
            DatePicker("Date of Birth", selection: $DoB , displayedComponents: .date)
                .onChange(of: DoB) { _, newDate in
                    storedDate = newDate.timeIntervalSinceReferenceDate
                }

            DatePicker("Sign Up Date", selection: $signUpDate, displayedComponents: .date)

            // default age
            LabeledContent("Subscription Month End") {
                Text(signUpDate.subscriptionDate(DoB: DoB), style: .date)
            }

            // Changed to 21 years old
            LabeledContent("Subscription Month End with 21 years") {
                Text(signUpDate.subscriptionDate(DoB: DoB, criteriaAge: 21), style: .date)
            }
        }
        .onAppear {
            DoB = Date(timeIntervalSinceReferenceDate: storedDate)
        }
    }
}

3      

@NigelGee, thank you very much for taking the time to reply, and the knowledge you've shared. I've poured through the knowledge database and looked back through several projects, but it seems like the dates aspect is sort of piecemealed through the course. I'll take a look at that live stream tomorrow, and I'll resume working on the project tomorrow as well. I have today off with my daughter, and since I travel for work, it's always important to me that I make the most of these days 😊

3      

Dealing with date calculations can indeed be challenging, but with the right approach and libraries, you can simplify the process. It sounds like you're working with a programming language. I'll provide a high-level approach using a hypothetical programming language, but you'll need to adapt this to the specific language you're working with.

3      

Thanks for answering :)

3      

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.