0

I inserted data into core-data with a type of Date but when I try to fetch data's from the database I couldn't group them by month. You can find the code that I tried for a solution but it didn't work.

let groupedDict = Dictionary(grouping: self.lessons) { (month) -> Date in
        return month.lessonDate!
    }
    var groupedMonth = [[Lessons]]()
    let keys = groupedDict.keys.sorted()
    keys.forEach { (key) in
        groupedMonth.append(groupedDict[key]!)
    }
    groupedMonth.forEach ({
        $0.forEach({print($0)})
        print("--------")
    })

If I should give you an example of what I want to accomplish. Here is the example:

Sample Dates:

  • 04.07.2017

  • 01.08.2018

  • 02.08.2018

  • 05.08.2018

  • 19.09.2018

  • 07.09.2018

I wanted to group these data like

  • June 2017
  • August 2018
  • September 2018

Thank you for helping me

Esat kemal Ekren
  • 516
  • 2
  • 8
  • 28
  • Show your `Lessons` structure declaration – Leo Dabus Aug 27 '18 at 16:17
  • I'll put the "dates", but you want at the end `[[04.07.2017], [01.08.2018, 02.08.2018, 05.08.2018], [19.09.2018, 07.09.2018]]` Or you want the "MonthName + Year", as a Group Name (key ?) So maybe [[ "name": "June 2018", "lessons": [[04.07.2017]], etc.]? – Larme Aug 27 '18 at 16:28
  • @LeoDabus structure is in core data lessons is the core data object which is included date element – Esat kemal Ekren Aug 27 '18 at 16:29
  • @Larme no i only need grouped names datas is not necessary at the moment i just need to tied all data and make it clean – Esat kemal Ekren Aug 27 '18 at 16:31
  • I guess they are sorted at first? You can use `reduce(into:)` or just `reduce()`. See my answer there https://stackoverflow.com/questions/52019449/best-way-to-loop-through-array-and-group-consecutive-numbers-in-another-array-sw/52019769#52019769 or @LeoDabus one's (which is more concise, but maybe more difficult to read if you don't know how it works, mine is more explicit/verbose). Change the types (`Int` => `Lessons`) and instead of `next-last == 1` which doesn't interest your, compare `next` & `last` if they are of the same month & same year (`DateComponents` or `Calendar` should help you). – Larme Aug 27 '18 at 16:36
  • Possibly helpful (if you plan to display the grouped data in a table view): https://stackoverflow.com/questions/30543064/sectionname-in-tableview-with-date – Martin R Aug 27 '18 at 16:41

2 Answers2

2

You have to use Dictionary(grouping:by:) differently. You have to return the value you want to group by, not for every lesson a different group.

Helpers

struct Lesson {
    let lessonDate: Date
}

extension Date {
    func addDays(_ days: Int) -> Date {
        return Calendar.current.date(byAdding: .day, value: days, to: self)!
    }

    func addMonths(_ months: Int) -> Date {
        return Calendar.current.date(byAdding: .month, value: months, to: self)!
    }

    var month: Date {
        let calendar = Calendar.current
        return calendar.date(from: calendar.dateComponents([.month, .year], from: self))!
    }

    var prettyMonth: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMMM yyyy"
        formatter.locale = Calendar.current.locale!

        return formatter.string(from: self)
    }

    var prettyDate: String {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formatter.timeStyle = .medium
        formatter.locale = Calendar.current.locale!

        return formatter.string(from: self)
    }
}

Example

func testing() {
    let lessons: [Lesson] = [
        .init(lessonDate: Date().addDays(2)),
        .init(lessonDate: Date().addDays(3)),
        .init(lessonDate: Date().addMonths(1).addDays(1)),
        .init(lessonDate: Date().addMonths(1).addDays(2)),
        .init(lessonDate: Date().addMonths(1).addDays(3)),
        .init(lessonDate: Date().addMonths(3)),
    ]

    let groupedDict = Dictionary(grouping: lessons, by: { $0.lessonDate.month })

    groupedDict
        .sorted(by: { a, b in a.key < b.key })
        .forEach {
            print("----- \($0.key.prettyMonth) -----")
            $0.value.forEach {
                print("\($0.lessonDate.prettyDate)")
            }
        }
}

Produces

----- August 2018 -----
29. Aug 2018 at 19:38:13
30. Aug 2018 at 19:38:13
----- September 2018 -----
28. Sep 2018 at 19:38:13
29. Sep 2018 at 19:38:13
30. Sep 2018 at 19:38:13
----- November 2018 -----
27. Nov 2018 at 19:38:13
Fabian
  • 5,040
  • 2
  • 23
  • 35
  • meanwhile you adopted all my suggestions step by step – vadian Aug 27 '18 at 17:17
  • `"MMMM YYYY"` is wrong. Y is used only for WeekOfYear. Btw note that every time you call this property you create a new DateFormatter – Leo Dabus Aug 27 '18 at 17:20
  • month property should return an Int instead of a Date. Btw why are you adding one day to it? – Leo Dabus Aug 27 '18 at 17:24
  • @LeoDabus I'm in UTC+2 and directly taking `dateComponents` from the prior date while creating an UTC+0-date makes for the end of the last month instead of the correct one. That's my guess anyway why it otherwise shows "correct month - 2 hours". – Fabian Aug 27 '18 at 17:26
  • You shouldn't mess with it. Just prin the date.description(with.current) to check if your date using the current locale/timezone – Leo Dabus Aug 27 '18 at 17:28
  • `Dictionary(grouping: lessons, by: { $0.lessonDate.month })` – Leo Dabus Aug 27 '18 at 17:28
  • 1
    @LeoDabus Taking only `month and year` from an UTC+2 and then printing as UTC+0 always has the problem. Or was it the other way around? I'm confused doesn't matter. Formatting with the correct locale worked to fix it. Thank you for your improvements! – Fabian Aug 27 '18 at 17:43
  • @vadian I thought that's the point of comments in the first place – Fabian Aug 27 '18 at 17:46
  • Thank you for showing me right path I used some parts of both answers and i got the solution – Esat kemal Ekren Aug 28 '18 at 17:10
1

This is a starting point to group the array with Dictionary(grouping:by:) by month and year using DateComponents.

The dictionary keys are dates. You can sort them and convert them to string with a DateFormatter and format "MMMM yyyy"

let grouped = Dictionary(grouping: lessons) { lesson -> Date in
    let calendar = Calendar.current
    let components = calendar.dateComponents([.year, .month], from: lesson.lessonDate!)
    return calendar.date(from: components) ?? Date(timeIntervalSince1970: 0)
}

If a date can't ever be created from the components, 1970-1-1 is used instead


Side note: As you force unwrap lessonDate anyway why is it optional at all?

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Thank you for showing me to the right path I used your grouped function and used other answer's extension. So I couldn't decide to which one of you are the correct ones because of 2 answers is the correct in somehow – Esat kemal Ekren Aug 28 '18 at 17:11