I am working on a scheduling app. I want all the dates of the given months I am not able to group dates by months that is what I tried but I want a different expected result
extension Date {
static func dates(from fromDate: Date, to toDate: Date) -> [Date] {
var dates: [Date] = []
var date = fromDate
while date <= toDate {
dates.append(date)
guard let newDate = Calendar.current.date(byAdding: .day, value: 1, to: date) else { break }
date = newDate
}
return dates
}
var month: Int {
return Calendar.current.component(.month, from: self)
}
}
let fromDate = Calendar.current.date(byAdding: .day, value: 30, to: Date())
let datesBetweenArray = Date.dates(from: Date(), to: fromDate!)
var sortedDatesByMonth: [[Date]] = []
let filterDatesByMonth = { month in datesBetweenArray.filter { $0.month == month } }
(1...12).forEach { sortedDatesByMonth.append(filterDatesByMonth($0)) }
The result is in this format [[], [], [], [], [], [], [2019-07-31
03:51:19 +0000],……., [], [], [], []]
This kinda result I want expecting
struct ScheduleDates {
var month: String
var dates: [Date]
init(month: String, dates: [Date]) {
self.month = month
self.dates = dates
}
}
var sections = [ScheduleDates]()
If you want to group your dates by month you can create a Dictionary like this:
Dictionary(grouping: datesBetweenArray, by: { $0.month })
This results in the following output of the format [Int: [Date]]
The key of the dictionary will be your month.
Now you can initialize your scheduleDates struct by looping through this dictionary in this way:
var sections = Dictionary(grouping: datesBetweenArray,
by: ({$0.month}))
.map { tuple in
ScheduleDates(month: String(tuple.0), dates: tuple.1)
}
Here's the code for a Playground
I think your structs should probably be Int values for the months, as when you go to populate something like a tableview, it'll be a PITA to re-order months if you've got them as Strings.
struct ScheduleDates {
var month: String
var dates: [Date]
}
Anyway, here's the extension I wrote based on what you provided. I frankly think you should return a dictionary with an Int as the key and an array of Dates as the value, but here's what you wanted...
I used Dictionary(grouping:by:) to construct a dictionary from an array of dates.
extension Date {
static func dateDictionary(from arrayOfDates: [Date]) -> [String: [Date]] {
// declare a dictionary
return Dictionary(grouping: arrayOfDates) { date -> String in
// get the month as an int
let monthAsInt = Calendar.current.dateComponents([.month], from: date).month
// convert the int to a string...i think you probably want to return an int value and do the month conversion in your tableview or collection view
let monthName = DateFormatter().monthSymbols[(monthAsInt ?? 0) - 1]
// return the month string
return monthName
}
}
}
Here's a utility method I wrote to generate data while I figured out how to do it. If you're going to be in production, don't force unwrap stuff as I did here.
// Utility method to generate dates
func createDate(month: Int, day: Int, year: Int) -> Date? {
var components = DateComponents()
components.month = month
components.day = day
components.year = year
return Calendar.current.date(from: components)!
}
Below is how I generated an array of sample dates to experiment.
// generate array of sample dates
let dateArray: [Date] = {
let months = Array(1...12)
let days = Array(1...31)
let years = [2019]
var dateArray: [Date] = []
while dateArray.count < 100 {
let randomMonth = months.randomElement()
let randomDay = days.randomElement()
let randomYear = years.randomElement()
if let month = randomMonth,
let day = randomDay,
let year = randomYear,
let date = createDate(month: month,
day: day,
year: year) {
dateArray.append(date)
}
}
return dateArray
}()
let monthDictionary = Date.dateDictionary(from: dateArray)
var arrayOfStructs: [ScheduleDates] = []
monthDictionary.keys.forEach { key in
let scheduleDate = ScheduleDates(month: key,
dates: monthDictionary[key] ?? [])
arrayOfStruct.append(scheduleDate)
}
print(arrayOfStructs)
You can use my code. Which i write to adapt your case.
let fromDate = Calendar.current.date(byAdding: .day, value: 30, to: Date())
let datesBetweenArray = Date.dates(from: Date(), to: fromDate!)
if datesBetweenArray.count <= 1 {
print(datesBetweenArray)
}
var sortedDatesByMonth: [[Date]] = []
var tempMonth = datesBetweenArray[0].month
var dates: [Date] = []
for i in 0..<datesBetweenArray.count {
if tempMonth == datesBetweenArray[i].month {
dates.append(datesBetweenArray[i])
if i == datesBetweenArray.count - 1 {
sortedDatesByMonth.append(dates)
}
} else {
sortedDatesByMonth.append(dates)
tempMonth = datesBetweenArray[i].month
dates.removeAll()
dates.append(datesBetweenArray[i])
}
}
print(sortedDatesByMonth.count)
print(sortedDatesByMonth)
Related
I have an array of posts which I currently group by day. I use the below functions to do so;
private func splitDay(from date: Date) -> Date {
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month, .day], from: date)
return calendar.date(from: components)!
}
private func sectionPosts(posts: [Post]) {
let groups = Dictionary(grouping: posts) { (posts) in
return splitDay(from: Date(timeIntervalSince1970: posts.createdOn))
}
self.sections = groups.map { (date, posts) in
return PostSection(date: date, posts: posts)
}
}
However, i'd like to implement a custom grouping which would be the following;
Today
Yesterday
This Week
This Month
Older
How would I build this into my grouping function? My section struct is like such;
struct PostSection {
var date: Date
var posts: [Post]
}
Since you are trying to group your data into groups such as "today", "yesterday", "this week" and "this month", you should first create a type to represent these groups:
enum PostGroup {
case today
case yesterday
case thisWeek
case thisMonth
case older
case coming // added "coming" group so that the groups cover all possible dates
}
Then your PostSection struct would have a PostGroup property, rather than a Date property:
struct PostSection {
let group: PostGroup
let posts: [Post]
}
Now we just need a function that goes (Post) -> PostGroup that we can pass to Dictionary(grouping:by:). This can be implemented just by comparing the date components of the post date with that of today:
func group(for post: Post) -> PostGroup {
let today = Date()
let calendar = Calendar.current
let postDateComponents = calendar.dateComponents([.year, .month, .day], from: post.createdOn)
let todayDateComponents = calendar.dateComponents([.year, .month, .day], from: today)
if postDateComponents == todayDateComponents {
return .today
}
let daysDifference = calendar.dateComponents([.day], from: postDateComponents, to: todayDateComponents)
if daysDifference.day == 1 {
return .yesterday
}
let postWeekComponents = calendar.dateComponents([.weekOfYear, .yearForWeekOfYear], from: post.createdOn)
let todayWeekComponents = calendar.dateComponents([.weekOfYear, .yearForWeekOfYear], from: today)
if postWeekComponents == todayWeekComponents {
return .thisWeek
}
if postDateComponents.year == todayDateComponents.year &&
postDateComponents.month == todayDateComponents.month {
return .thisMonth
}
if post.createdOn < today {
return .older
} else {
return .coming
}
}
To finish it off:
private func sectionPosts(posts: [Post]) {
let groups = Dictionary(grouping: posts, by: group(for:))
self.sections = groups.map { (group, posts) in
return PostSection(group: group, posts: posts)
}
}
This is not an elegant solution, but it solves the problem, please feel free to optimize this function, specially since date calculations can be slow.
private func preSplitProcess(from date: Date) -> Date {
let calendar = Calendar.current
let isToday = calendar.isDateInToday(date)
if isToday {
return date
} else {
let wasYesterday = calendar.isDateInYesterday(date)
if wasYesterday {
return date
} else {
let bowComponents = calendar.dateComponents([.weekOfYear], from: date)
let beginningOfWeek = calendar.date(from: bowComponents)! // Handle errors, please
if beginningOfWeek < date {
return date
} else {
let bomComponents = calendar.dateComponents([.month], from: date)
let beginningsOfMonth = calendar.date(from: bomComponents)! // Handle errors, please
if beginningsOfMonth < date {
return date
} else {
var components = DateComponents()
components.year = 1970
components.month = 01
components.day = 01
let oldDate = calendar.date(from: components)!
return oldDate
}
}
}
}
}
Add this before you call your splitDay function, since this doesn't reduce the return dates to the same date.
I have an array of objects that contain Date property. I want to distribute them in UITableView sections according to their month by month order. How I can do that?
My model:
class Birthday: Object {
#objc dynamic var name: String = ""
#objc dynamic var date: Date = Date()
#objc dynamic var dayLeft: Int = 0
#objc dynamic var userImageData: Data?
}
First I use the next code with DateFormatter:
var grouped: [String: [Birthday]] = [:]
grouped = Dictionary(grouping: birthdaysList) { item -> String in
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month], from: item.date)
let date = calendar.date(from: components) ?? Date(timeIntervalSince1970: 0)
return date.monthAsString()
}
extension Date {
func monthAsString() -> String {
let df = DateFormatter()
df.setLocalizedDateFormatFromTemplate("MMM")
return df.string(from: self)
}
}
Then I use:
struct Section {
let month: String
let birthdays: [Birthday]
}
var sections: [Section] = []
let keys = Array(grouped.keys)
sections = keys.map({Section(month: $0, birthdays: grouped[$0]!)})
But I need to sort month names in order. January, february etc. How this can be done?
I try to return tuple from my code, but get error
Declared closure result '(String, Int)' is incompatible with contextual type 'String'
grouped = Dictionary(grouping: birthdaysList) { item -> (String, Int) in
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month], from: item.date)
let date = calendar.date(from: components) ?? Date(timeIntervalSince1970: 0)
return (date.monthAsString(), components.month!)
}
Create a wrapper struct
struct Section {
let month : String
let birthdays : [Birthday]
}
then map the grouped dictionary to an array of Section
let keys = Array(grouped.keys)
let sections = keys.map({Section(month: $0, birthdays: grouped[$0]!)})
sections represent the sections, birthdays the rows, month the section header titles.
Edit:
To be able to sort the months by their ordinal number return the number in front of the month name
grouped = Dictionary(grouping: birthdaysList) { item -> String in
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month], from: item.date)
let date = calendar.date(from: components) ?? Date(timeIntervalSince1970: 0)
return "\(components.month!)_" + date.monthAsString()
}
And modify the mapping code
let keys = grouped.keys.sorted{$0.localizedStandardCompare($1) == .orderedAscending}
let sections = keys.map({Section(month: $0.components(separatedBy:"_").last!, birthdays: grouped[$0]!)})
In my application i have an option to enter the data for every 15days.I have to maintain this for an current year.Please help me to figure out this problem.
For ex: [
"1-1-2018 to 15-1-2018", "16-1-2018 to 31-1-2018",
"1-2-2018 to 15-2-2018", "16-2-2018 to 28-2-2018",
"1-3-2018 to 15-3-2018", "16-3-2018 to 31-3-2018",
"1-4-2018 to 15-4-2018", "16-4-2018 to 30-4-2018",
"1-5-2018 to 15-5-2018", "16-5-2018 to 31-5-2018",
"1-6-2018 to 15-6-2018", "16-6-2018 to 30-6-2018",
"1-7-2018 to 15-7-2018", "16-7-2018 to 31-7-2018",
"1-8-2018 to 15-8-2018", "16-8-2018 to 31-8-2018",
"1-9-2018 to 15-9-2018", "16-9-2018 to 30-9-2018",
"1-10-2018 to 15-10-2018", "16-10-2018 to 31-10-2018",
"1-11-2018 to 15-11-2018", "16-11-2018 to 30-11-2018",
"1-12-2018 to 15-12-2018", "16-12-2018 to 31-12-2018"
]
From Calendar API you can get total number of days for any month in the given date and also first day of the month like below,
extension Calendar {
public func firstDayOfMonth(date: Date) -> Date {
let components = self.dateComponents([.year, .month], from: date)
return self.date(from: components) ?? date
}
public func numberOfDaysInMonthFor(date: Date) -> Int {
let range = self.range(of: .day, in: .month, for: date)
return range?.count ?? 0
}
public func lowerHalfOfMonthFor(date: Date) -> (Date, Date) {
let startDate = self.firstDayOfMonth(date: date)
let endDate = startDate.dateByAppending(day: 14)
return (startDate, endDate)
}
public func upperHalfOfMonthFor(date: Date) -> (Date, Date) {
let firstDayOfMonthDate = self.firstDayOfMonth(date: date)
let totalNoOfDaysInMonth = self.numberOfDaysInMonthFor(date: firstDayOfMonthDate)
let startDate = firstDayOfMonthDate.dateByAppending(day: 15)
let endDate = firstDayOfMonthDate.dateByAppending(day: totalNoOfDaysInMonth - 1)
return (startDate, endDate)
}
}
you can also extend Date to get new date by appending any number of days,
extension Date {
public func dateByAppending(day: Int) -> Date {
let newDate = Calendar.current.date(byAdding: .day, value: day, to: self)
return newDate ?? self
}
public func daysDifference(_ date: Date?) -> Int? {
guard let date = date else { return nil }
return Calendar.current.dateComponents([.day], from: self, to: date).day
}
With the mix of above helper methods, you should be able to achieve the required result like below,
let date = Date()
let lowerHalf = Calendar.current.lowerHalfOfMonthFor(date: date)
let uppperHalf = Calendar.current.upperHalfOfMonthFor(date: date)
Below code will calculate the 15th day if you give the input,
static func getFortnightly(selectedDate : String) -> String?{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yy" //Your date format
if let dateSelected = dateFormatter.date(from: selectedDate) {//according to d
let newDate = Calendar.current.date(byAdding: .weekOfYear, value: 2, to: dateSelected)
let convertedDateToString = dateFormatter.string(from: newDate!)
return convertedDateToString
}
return nil
}
}
Im trying to build a weekly calendar for IOS.
The problem with that sample is code is: the usage of an dates array
let dates = ["7/10/2017", "7/11/2017", "7/12/2017", "7/13/2017", "7/14/2017", "7/15/2017", "7/16/2017"]
func spreadsheetView(_ spreadsheetView: SpreadsheetView, cellForItemAt indexPath: IndexPath) -> Cell? {
if case (1...(dates.count + 1), 0) = (indexPath.column, indexPath.row) {
let cell = spreadsheetView.dequeueReusableCell(withReuseIdentifier: String(describing: DateCell.self), for: indexPath) as! DateCell
cell.label.text = dates[indexPath.column - 1]
return cell
Filling that array with real dates from 01.01.2000 - 31.12.2099 e.g. leads to really bad performance of the app.
Does anyone know how to display the current Dates in an more elegant way?
You could do so by using the following extension:
See here : Swift: Print all dates between two NSDate()
extension Date{
func generateDatesArrayBetweenTwoDates(startDate: Date , endDate:Date) ->[Date]
{
var datesArray: [Date] = [Date]()
var startDate = startDate
let calendar = Calendar.current
let fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd"
while startDate <= endDate {
datesArray.append(startDate)
startDate = calendar.date(byAdding: .day, value: 1, to: startDate)!
}
return datesArray
}
}
Usage:
let dates = Date().generateDatesArrayBetweenTwoDates(startDate: Your Start Date Object , endDate: Your End Date Object)
Here's one solution using Calendar enumerateDates:
// 01.01.2000
let startComps = DateComponents(year: 2000, month: 1, day: 1)
let startDate = Calendar.current.date(from: startComps)!
// 31.12.2099
let endComps = DateComponents(year: 2099, month: 12, day: 31)
let endDate = Calendar.current.date(from: endComps)!
let components = DateComponents(hour: 0, minute: 0, second: 0) // midnight
var dates = [startDate]
Calendar.current.enumerateDates(startingAfter: startDate, matching: components, matchingPolicy: .nextTime) { (date, strict, stop) in
if let date = date {
if date <= endDate {
dates.append(date)
} else {
stop = true
}
}
}
How to get previous 7 days of the month I know how to get if today is 18, but what if today id 3rd November? How to get the last 4 days from the previous month(October) in Int?
Use NSCalendar and NSDateComponents:
let cal = NSCalendar.currentCalendar()
// start with today
var date = cal.startOfDayForDate(NSDate())
var days = [Int]()
for i in 1 ... 7 {
// get day component:
let day = cal.component(.DayCalendarUnit, fromDate: date)
days.append(day)
// move back in time by one day:
date = cal.dateByAddingUnit(.DayCalendarUnit, value: -1, toDate: date, options: nil)!
}
println(days)
Update for Swift 2.2 (Xcode 7.3.1):
let cal = NSCalendar.currentCalendar()
var date = cal.startOfDayForDate(NSDate())
var days = [Int]()
for i in 1 ... 7 {
let day = cal.component(.Day, fromDate: date)
days.append(day)
date = cal.dateByAddingUnit(.Day, value: -1, toDate: date, options: [])!
}
print(days)
Update for Swift 3 (Xcode 8 beta 2):
let cal = Calendar.current
var date = cal.startOfDay(for: Date())
var days = [Int]()
for i in 1 ... 7 {
let day = cal.component(.day, from: date)
days.append(day)
date = cal.date(byAdding: .day, value: -1, to: date)!
}
print(days)
Inspired by this answer,
Get list of previous N days as an array of strings.
extension Date {
static func getDates(forLastNDays nDays: Int) -> [String] {
let cal = NSCalendar.current
// start with today
var date = cal.startOfDay(for: Date())
var arrDates = [String]()
for _ in 1 ... nDays {
// move back in time by one day:
date = cal.date(byAdding: Calendar.Component.day, value: -1, to: date)!
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let dateString = dateFormatter.string(from: date)
arrDates.append(dateString)
}
print(arrDates)
return arrDates
}
}
Usage:
let last7Days = Date.getDates(forLastNDays: 7)
debugPrint(last7Days)
//Today(2017-11-19) it prints: ["2017-11-18", "2017-11-17", "2017-11-16", "2017-11-15", "2017-11-14", "2017-11-13", "2017-11-12"]
You can create an extension using Calendar to help you with your calendrical calculations:
Swift 3 or later
extension Date {
var day: Int {
return Calendar.current.component(.day, from: self)
}
func adding(days: Int) -> Date {
return Calendar.current.date(byAdding: .day, value: days, to: self)!
}
var last7days: [Int] {
return (1...7).map {
adding(days: -$0).day
}
}
func near(days: Int) -> [Int] {
return days == 0 ? [day] : (1...abs(days)).map {
adding(days: $0 * (days < 0 ? -1 : 1) ).day
}
}
}
usage:
let last7Days = Date().last7days // [29, 28, 27, 26, 25, 24, 23]
let last7Days2 = Date().near(days: -7) // [29, 28, 27, 26, 25, 24, 23]
let next7Days = Date().near(days: 7) // [27, 28, 29, 30, 31, 1, 2]
Same strategy as #rintaro answer, as short as possible ;) (added support to negative values and made it as a Date extension to be able to use any date as input)
extension Date {
func closest(days: Int) -> [Int] {
return days == 0 ? [] : (1...abs(days)).map { delta -> Int in Calendar.current.component(.day, from: Calendar.current.date(byAdding: .day, value: delta * (days >= 0 ? 1 : -1), to: self)!) }
}
}
usage:
let next7Days2 = Date().closest(days: 7) // [27, 28, 29, 30, 31, 1, 2]
let last7Days3 = Date().closest(days: -7) // [25, 24, 23, 22, 21, 20, 19]
let next2Days = Date().closest(days: 2) // [27, 28]
let last2Days = Date().closest(days: -2) // [25, 24]
Same strategy as #MartinR answer, as short as possible:
let cal = NSCalendar.currentCalendar()
let date = NSDate()
var result = map(-6...0) { delta -> Int in
cal.component(.DayCalendarUnit, fromDate: cal.dateByAddingUnit(.DayCalendarUnit, value: delta, toDate: date, options: nil)!)
}
It's might not be the fastest code. But you get the idea :)
import Foundation
let lastSevenDay: [Int] = {
var days = [Int]()
let secondsInADay: NSTimeInterval = 24 * 60 * 60
let now = NSDate()
let calendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)!
for i in 1...7 {
let theDate = now.dateByAddingTimeInterval(-secondsInADay * NSTimeInterval(7 - i))
let dateComponent = calendar.components(NSCalendarUnit.CalendarUnitDay, fromDate: theDate)
let dayOfMonth = dateComponent.day
days.append(dayOfMonth)
}
return days
}()
I modified the rintaro answer a bit to get past dates in a NSArray of NSDictionaries to get all values including past days, months and years. You only need to call this snippet like this
println(getPastDates(2))
to get a full list with past dates like that
(
{
day = 30;
month = 11;
year = 2014;
},
{
day = 29;
month = 11;
year = 2014;
}
)
func getPastDates(days: Int) -> NSArray {
var dates = NSMutableArray()
let cal = NSCalendar.currentCalendar()
var today = cal.startOfDayForDate(NSDate())
for i in 1 ... days {
let day = cal.component(.DayCalendarUnit, fromDate: today)
let month = cal.component(.MonthCalendarUnit, fromDate: today)
let year = cal.component(.YearCalendarUnit, fromDate: today)
var date = NSMutableDictionary()
date.setValue(day, forKey: "day")
date.setValue(month, forKey: "month")
date.setValue(year, forKey: "year")
dates.addObject(date)
// move back in time by one day:
today = cal.dateByAddingUnit(.DayCalendarUnit, value: -1, toDate: today, options: nil)!
}
return dates
}
I update
Alejandro Luengo's code. It works Swift4
func getPastDates(days: Int) -> NSMutableArray {
let dates = NSMutableArray()
let calendar = Calendar.current
var today = calendar.startOfDay(for: Date())
for _ in 1 ... days {
let day = calendar.component(.day, from: today)
let month = calendar.component(.month, from: today)
let year = calendar.component(.year, from: today)
let date = NSMutableDictionary()
date.setValue(day, forKey: "day")
date.setValue(month, forKey: "month")
date.setValue(year, forKey: "year")
dates.add(date)
today = calendar.date(byAdding: .day, value: -1, to: today)!
}
return dates
}
Swift4
Same strategy as #MartinR, #rintaro answer, as short as possible:
let calendar = Calendar.current
let date = Date()
let result: [Int] = (-6...0).map { delta -> Int in
calendar.component(.day, from: calendar.date(byAdding: .day, value: delta, to: date)!)
}
You want to use NSCalendar to get the last 7 days. Here's a code snippet to demonstrate it in action for 3rd November:
let dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = .ShortStyle
dateFormatter.timeStyle = .NoStyle
dateFormatter.locale = NSLocale(localeIdentifier: "en_GB")
let date = dateFormatter.dateFromString("03/11/14")!
println("date = \(dateFormatter.stringFromDate(date))")
let calendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)!
var previous7days: Array<Int> = []
for i in 0...6 {
let prevDate = calendar.dateByAddingUnit(.CalendarUnitDay, value: -i, toDate: date, options: nil)
previous7days.insert(calendar.component(.CalendarUnitDay, fromDate: prevDate!), atIndex: 0)
}
println("last 7 days: \(previous7days)")