Learn Courses My Dashboard

List of days in current month

Hi to all of you. I need some help in creating a list of the days of a month in SwiftUI. I tried to have a look online but no success. Is there anything I can try to do to make this thing as simple as possible?
Thanks in advance!

Try this:

func getDaysSimple(for month: Date) -> [Date] {
    
    //get the current Calendar for our calculations
    let cal = Calendar.current
    //get the days in the month as a range, e.g. 1..<32 for March
    let monthRange = cal.range(of: .day, in: .month, for: month)!
    //get first day of the month
    let comps = cal.dateComponents([.year, .month], from: month)
    //start with the first day
    //building a date from just a year and a month gets us day 1
    var date = cal.date(from: comps)!

    //somewhere to store our output
    var dates: [Date] = []
    //loop thru the days of the month
    for _ in monthRange {
        //add to our output array...
        dates.append(date)
        //and increment the day
        date = cal.date(byAdding: .day, value: 1, to: date)!
    }
    return dates
}

Usage:

print(getDaysSimple(for: .now)) 

//running on 2022-03-02 returns the following result:
//[2022-03-01 08:00:00 +0000, 2022-03-02 08:00:00 +0000, 2022-03-03 08:00:00 +0000, 
//2022-03-04 08:00:00 +0000, 2022-03-05 08:00:00 +0000, 2022-03-06 08:00:00 +0000, 
//2022-03-07 08:00:00 +0000, 2022-03-08 08:00:00 +0000, 2022-03-09 08:00:00 +0000,
//2022-03-10 08:00:00 +0000, 2022-03-11 08:00:00 +0000, 2022-03-12 08:00:00 +0000, 
//2022-03-13 08:00:00 +0000, 2022-03-14 07:00:00 +0000, 2022-03-15 07:00:00 +0000,
//2022-03-16 07:00:00 +0000, 2022-03-17 07:00:00 +0000, 2022-03-18 07:00:00 +0000,
//2022-03-19 07:00:00 +0000, 2022-03-20 07:00:00 +0000, 2022-03-21 07:00:00 +0000, 
//2022-03-22 07:00:00 +0000, 2022-03-23 07:00:00 +0000, 2022-03-24 07:00:00 +0000,
//2022-03-25 07:00:00 +0000, 2022-03-26 07:00:00 +0000, 2022-03-27 07:00:00 +0000,
//2022-03-28 07:00:00 +0000, 2022-03-29 07:00:00 +0000, 2022-03-30 07:00:00 +0000,
//2022-03-31 07:00:00 +0000]

Note that the dates are listed for UTC+00:00 adjusted for your locale. I am currently in UTC-08:00, so the start of the day for me is 08:00:00 at the beginning of March, but since Daylight Saving Time starts the second Sunday in March, it switches to 07:00:00 on the 14th since that time zone is UTC-07:00.

With this array of Date objects, you should be able to create a list in SwiftUI or whatever you want to do with it.

1 Like

Thanks for your reply and help.
What I need to do is like the image below, I understand your func but how to implement it in such a view?
It’s not necessary to have that custom Row, I just need a navigation view with all the day cells for every month of the year… :frowning:

This is what I made until now.

DateView File:

struct DateView: View {
    static let dateFormat: DateFormatter = {
        let formatter = DateFormatter()
        formatter.setLocalizedDateFormatFromTemplate("MMMM yyyy")
        return formatter
    }()

    @Binding var date : Date

    var body: some View {

        HStack {

            Image(systemName: "chevron.left")
                .padding()
                .onTapGesture {
                    print("Month -1")
                    self.changeDateBy(-1)
            }

            Spacer()

            Text("\(date, formatter: Self.dateFormat)")

            Spacer()

            Image(systemName: "chevron.right")
                .padding()
                .onTapGesture {
                    print("Month +1")
                    self.changeDateBy(1)
            }

        }
        .padding(EdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10))
        .background(Color.white)
    }

    func changeDateBy(_ months: Int) {
        if let date = Calendar.current.date(byAdding: .month, value: months, to: date) {
            self.date = date
        }
    }
}

ContentView:


struct ContentView: View {
    
    @State var currentDate = Date()
    
    let calendar = Calendar.current
    private var startDateOfMonth: String {
        let components = Calendar.current.dateComponents([.year, .month], from: currentDate)
        let startOfMonth = Calendar.current.date(from: components)!
        return format(date: startOfMonth)
    }

    private var endDateOfMonth: String {
        var components = Calendar.current.dateComponents([.year, .month], from: currentDate)
        components.month = (components.month ?? 0) + 1
        components.hour = (components.hour ?? 0) - 1
        let endOfMonth = Calendar.current.date(from: components)!
        return format(date: endOfMonth)
    }
    
    
    var body: some View {
        NavigationView {
        VStack {
            DateView(date: $currentDate)
                .padding()
            Divider()
            Spacer()
            List {
                
            }
            Text(format(date: currentDate))
            Divider()
            Text(startDateOfMonth)
            Text(endDateOfMonth)
            Divider()
            
    }
        .navigationBarTitle("DAYS")
        }
        
    }

    private func format(date: Date) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .medium
        return dateFormatter.string(from: date)
    }
}

Result in simulator is in the image below.

In the middle space, I need to have the list of days.

First, some convenience methods on Date:

extension Date {
    func startOfMonth() -> Date {
        let cal = Calendar.current
        let comps = cal.dateComponents([.year, .month], from: self)
        return cal.date(from: comps)!
    }
    
    func endOfMonth() -> Date {
        let cal = Calendar.current
        let comps = cal.dateComponents([.year, .month], from: self)
        let date = cal.date(from: comps)!
        let lastDayOfMonth = cal.date(byAdding: DateComponents(month: 1, day: -1), to: date)!
        return lastDayOfMonth
    }
    
    func getDaysOfMonth() -> [Date] {
        
        //get the current Calendar for our calculations
        let cal = Calendar.current
        //get the days in the month as a range, e.g. 1..<32 for March
        let monthRange = cal.range(of: .day, in: .month, for: self)!
        //get first day of the month
        let comps = cal.dateComponents([.year, .month], from: self)
        //start with the first day
        //building a date from just a year and a month gets us day 1
        var date = cal.date(from: comps)!
        
        //somewhere to store our output
        var dates: [Date] = []
        //loop thru the days of the month
        for _ in monthRange {
            //add to our output array...
            dates.append(date)
            //and increment the day
            date = cal.date(byAdding: .day, value: 1, to: date)!
        }
        return dates
    }
}

Now for the main View:

struct CalendricalView: View {
    class ViewModel: ObservableObject {
        @Published var currentDate: Date = .now
        
        let calendar = Calendar.current
        
        func previousMonth() {
            currentDate = calendar.date(byAdding: .month, value: -1, to: currentDate) ?? currentDate
        }
        
        func nextMonth() {
            currentDate = calendar.date(byAdding: .month, value: 1, to: currentDate) ?? currentDate
        }
        
        var startDateOfMonth: Date {
            currentDate.startOfMonth()
        }
        
        var endDateOfMonth: Date {
            currentDate.endOfMonth()
        }
        
        var monthDays: [Date] {
            currentDate.getDaysOfMonth()
        }
    }
    
    @StateObject var viewModel = ViewModel()
    
    var abbreviatedDate: Date.FormatStyle {
        Date.FormatStyle(date: .abbreviated, time: .omitted)
    }
    
    var header: some View {
        VStack {
            HStack {
                Button {
                    viewModel.previousMonth()
                } label: {
                    Image(systemName: "chevron.left")
                }
                .buttonStyle(.plain)
                .padding()

                Spacer()

                Text(viewModel.currentDate, format: .dateTime.month(.wide).year())

                Spacer()

                Button {
                    viewModel.nextMonth()
                } label: {
                    Image(systemName: "chevron.right")
                }
                .buttonStyle(.plain)
                .padding()
            }
            .padding(EdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10))
            .background(Color.white)
            Divider()
        }
    }
    
    var footer: some View {
        VStack {
            Divider()
            Text(viewModel.currentDate, format: abbreviatedDate)
            Divider()
            Text(viewModel.startDateOfMonth, format: abbreviatedDate)
            Text(viewModel.endDateOfMonth, format: abbreviatedDate)
            Divider()
        }
    }

    var body: some View {
        NavigationView {
            VStack {
                header

                DateListView(dates: viewModel.monthDays)

                footer
            }
            .navigationBarTitle("DAYS")
        }
        
    }
}

And the list View:

struct DateListView: View {
    let dates: [Date]
    
    var body: some View {
        List {
            ForEach(dates, id: \.self) { day in
                NavigationLink {
                    Text(day, format: .dateTime.day().month(.abbreviated))
                } label: {
                    Text(day, format: .dateTime.day().month(.abbreviated))
                }
            }
        }
        .listStyle(.plain)
    }
}
  1. There are numerous places here you could customize to your liking.
  2. Obviously the date format will be a little different since our Locales are different.
  3. CalendricalView could have been done with @State for currentDate and some methods on that View but I used a view model instead.

CalendricalView_simulator

Ciao Alessandro,

Grazie per il tuo post! È stato divertente provare a completare questa sfida. Ho fatto questo:

Unfortunately, I could not beat the excellent @roosterboy to the answer, but it seems like my code gets closest to your design. I used your code (thanks for providing all of it!) as well as Patrick’s in order to get here.

Here’s my new DateRowView:

//
//  DateRowView.swift
//  Dates App
//
//  Created by Leone on 3/2/22.
//

import SwiftUI

struct DateRowView: View {
    
    // Pass in a date
    var sideDate: Date
    
    // Return the three-letter date abbreviation, e.g. Monday -> Mon
    var abbreviatedDay: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "EEE"
        // Returns like "Wed"
        return formatter.string(from: sideDate)
    }
    
    var dayNumber: String {
        let formatter = DateFormatter()
        // Formats the Date as a string e.g. 20
        formatter.dateFormat = "d"
        // Returns like 5
        return formatter.string(from: sideDate)
    }
    
    var body: some View {
        
        VStack(alignment: .leading) {
            // MARK: - Date Elements
            // Holds the left date elements
            HStack {
                // Inner VStack holds the date's three-letter day abbreviation as well as number
                VStack {
                    Text(abbreviatedDay)
                        .font(.caption)
                        .foregroundColor(.gray)
                    Text(dayNumber)
                        .foregroundColor(.orange)
                }
                
                // Create a small gray divider 50 pixels high
                Divider()
                    .frame(maxHeight: 40)
            }
            .frame(width: 40)
            
            // MARK: - Bottom Divider
            // Create a Divider under each View
            Divider()
        }
        .padding(.leading)
    }
}

struct DateRowView_Previews: PreviewProvider {
    static var previews: some View {
        DateRowView(sideDate: Date())
    }
}

Meanwhile, I updated the ContentView code to read like this:

//
//  ContentView.swift
//  Dates App
//
//  Created by Leone on 3/2/22.
//

import SwiftUI

struct ContentView: View {
    
    @State var currentDate = Date()
    
    let calendar = Calendar.current
    private var startDateOfMonth: String {
        let components = Calendar.current.dateComponents([.year, .month], from: currentDate)
        let startOfMonth = Calendar.current.date(from: components)!
        return format(date: startOfMonth)
    }
    
    private var endDateOfMonth: String {
        var components = Calendar.current.dateComponents([.year, .month], from: currentDate)
        components.month = (components.month ?? 0) + 1
        components.hour = (components.hour ?? 0) - 1
        let endOfMonth = Calendar.current.date(from: components)!
        return format(date: endOfMonth)
    }
    
    // Get the list of dates for the current month
    private var listOfDates: [Date] {
        let month = Date()
        
        //get the current Calendar for our calculations
        let cal = Calendar.current
        //get the days in the month as a range, e.g. 1..<32 for March
        let monthRange = cal.range(of: .day, in: .month, for: month)!
        //get first day of the month
        let comps = cal.dateComponents([.year, .month], from: month)
        //start with the first day
        //building a date from just a year and a month gets us day 1
        var date = cal.date(from: comps)!

        //somewhere to store our output
        var dates: [Date] = []
        //loop thru the days of the month
        for _ in monthRange {
            //add to our output array...
            dates.append(date)
            //and increment the day
            date = cal.date(byAdding: .day, value: 1, to: date)!
        }
        return dates
    }
    
    var body: some View {
        NavigationView {
            VStack {
                DateView(date: $currentDate)
                    .padding()
                Divider()
                Spacer()
               
                // MARK: - Date Cells
                // Create a nice scroll view for the user
                ScrollView {
                    // Use a LazyVStack in order to not unnecessarilly create elements for things off of screen
                    // This uses less memory
                    LazyVStack {
                        // Loop through each of the dates in the month
                        ForEach(listOfDates, id: \.self) { date in
                            
                            NavigationLink {
                                // Navigate to a new page here
                                Text("Navigated to \(date)")
                            } label: {
                                // Create a DateRow for each date
                                DateRowView(sideDate: date)
                            }
                        }
                    }
                }
                
                Text(format(date: currentDate))
                Divider()
                Text(startDateOfMonth)
                Text(endDateOfMonth)
                Divider()
                
            }
            .navigationBarTitle("DAYS")
        }
        
    }
    
    private func format(date: Date) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .medium
        return dateFormatter.string(from: date)
    }
    
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Cari saluti,
Andrea

P.S. apologies if you don’t speak Italian, but I’m 99% sure you are, because of one screenshot marzo, and that beautiful Italian name

Hi guys,

first of all, thank you very much for all your suggestions, I really appreciate.
I try to “mix” both solutions, in Andrea’s one (Andrea are u Italian? :slight_smile: ) when I change the month in my header, the list of days do not update itself but I will look at this later.
I think “calendar” in Swift needs a lot of teaching, since it’s something really important and hard to manage.

Because it always uses the same date for calculating listOfDates:

let month = Date()

Yes, I made it update correctly using a viewModel.
I am trying now to calculate how many “working” days we have per single month.
Is there any way to “filter” festive days in calendar?

AFAIK there is no built-in way to know which days are holidays and which ones aren’t. Maybe you could do something with EventKit?

Or find a public API you can query to get the list of holidays? Something like Nager.Date, perhaps? For instance, this URL gets you a list of public holidays in Italy in 2022. You would obviously have to set up the data fetching and parsing yourself, but that shouldn’t be too hard. I have no idea regarding the accuracy of this API, though.

Just for giggles, here’s a quick thing I worked up to try out the Nager.Date API. It creates a simple List of holidays for the user’s current Locale and the current year. Hopefully you can extrapolate what you are looking for from this. Again note that I am relying on the accuracy of Nager.Date so no guarantees.

struct Holiday: Identifiable, Codable {
    let date: Date
    let localName: String
    let name: String
    let global: Bool
    
    var id: String {
        date.formatted(.iso8601)
    }
}

extension DateFormatter {
    static let yyyyMMdd: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()
}

@MainActor
class HolidayAPI: ObservableObject {
    @Published var holidays: [Holiday] = []
    
    func fetchHolidays(for locale: Locale, in year: Date) async {
        guard let region = locale.regionCode else { return }
        var apiURL = URL(string: "https://date.nager.at/api/v3/PublicHolidays")!
        apiURL.appendPathComponent(year.formatted(.dateTime.year()))
        apiURL.appendPathComponent(region)
        
        do {
            let (data, _) = try await URLSession.shared.data(from: apiURL)
            let decoder = JSONDecoder()
            decoder.dateDecodingStrategy = .formatted(.yyyyMMdd)
            let result = try decoder.decode([Holiday].self, from: data)
            //filter out non-global holidays
            //global holidays are those observed by the entire country
            holidays = result.filter(\.global)
        } catch {
            print(error)
        }
    }
}

struct Holidays: View {
    @StateObject var api = HolidayAPI()
    
    let locale = Locale.current
    let year = Date.now
    
    var body: some View {
        VStack {
            VStack {
                Text("Holidays").font(.largeTitle).bold()
                Text("for \(locale.identifier) in \(year.formatted(.dateTime.year()))")
            }
            List {
                ForEach(api.holidays) { holiday in
                    HStack {
                        VStack(alignment: .leading) {
                            Text(holiday.name)
                                .font(.headline)
                                .bold()
                            Text(holiday.date, format: .dateTime.year().month(.abbreviated).day())
                                .font(.subheadline)
                                .foregroundColor(.gray)
                        }
                        Spacer()
                        if holiday.date < .now {
                            Image(systemName: "checkmark.circle")
                                .foregroundColor(.green)
                        }
                    }
                }
            }
            .task {
                await api.fetchHolidays(for: locale, in: year)
            }
        }
    }
}

Holidays simulator

1 Like

Guys, thanks to all of you for your fantastic support.
I really appreciate it.
I think that what I try to do is way too much for my Swift understanding, I was “simply” trying to record my working hours day by day, but simple operations like understanding how many official working hours I have month by month, is not at my level.
Anyway, really thank you very very much!

1 Like

Haha no I’m not! Pero parlo l’italiano :hugs:

Hey you did a great job! Between your code and @roosterboy’s I already learned a lot. Keep practicing, watching the videos at CWC+, and come back to it, or the forum with questions!

With Patrick’s answer/ sample code to use the holiday’s API it should not be so hard to figure out either. You could see that there are X number of holidays per month, then you could simply subtract that number of days from the weekdays in the month to get your total number of working days in a month, then do the multiplication to determine how many hours you would have to work.

Cheers,
Andrew