Cannot group by Year, Month and Date C# - c#

I would like to know how I can group dates by year, month and date respectively.
So far I managed to group by month and date, however I am getting some trouble understanding to group b a year
So, I created several classes that represent Day, Month and Year:
public class Day
{
public DateTime Date { get; set; }
}
public class Month
{
public int MonthNumber { get; set; }
public List<Day> Days { get; set; }
}
public class Year
{
public int YearNumber { get; set; }
public List<Month> Months { get; set; }
}
So first I got a range of dates overall and add them to list of days.
var startDate = DateTime.Parse("12/08/2019");
var endDate = DateTime.Parse("01/03/2020");
var days = new List<Day>();
var months = new List<Month>();
var years = new List<Year>();
if (endDate >= startDate)
{
for (var date = startDate; date <= endDate; date=date.AddDays(1))
{
var dayModel = new Day();
dayModel.Date = date;
days.Add(dayModel);
}
}
Then I tried to group days by months and add them to list of Months:
var grouppedMonths = days.GroupBy(y => y.Date.Month).ToList();
foreach (var month in grouppedMonths)
{
var monthModel = new Month();
monthModel.Days = month.Select(d => d).ToList();
months.Add(monthModel);
}
After groupping by months I want to group actual result by the year.
I tried something like this:
var grouppedYears = months.GroupBy(x => x.Days.Select(y => y.Date.Year));
but it did not work for me.
Overall I want to get
list of years
that contain list of months
that contain list of days.

I guess you want this:
var results = days
.GroupBy(d => d.Date.Year)
.Select(y => new Year
{
YearNumber = y.Key,
Months = y
.GroupBy(d => d.Date.Month)
.Select(m => new Month
{
MonthNumber = m.Key,
Days = m.ToList()
})
.ToList()
})
.ToList();

Related

LINQ Group By Year to Form a Tree View

I am trying to create a tree view that would essentially break down like so:
- Year
- Month
- Related Item
So we might have the Year 2022, that has several related items within the several months.
I have created the following model:
public class TreeYear
{
public string NodeYear { get; set; }
public DateTime CreatedDateTime { get; set; }
public List<TreeMonth> Months { get; set; }
}
public class TreeMonth
{
public int MonthID { get; set; }
public string MonthName { get; set; }
public quoteSummary QuoteSummary{ get; set; }
}
I have written some code in my controller which currently returns every item like so:
var allQuotes = QuoteSummary.ToList();
var tree = new TreeYear();
foreach (var quote in allQuotes)
{
tree.NodeYear= quote.CreatedTime.Year.ToString();
tree.CreatedDateTime = quote.CreatedTime;
tree.Months = new List<TreeMonth>()
{
new TreeMonth() {
MonthID = quote.CreatedTime.Month,
MonthName = getAbbreviatedName(quote.CreatedTime.Month),
QuoteSummary = quote
}
};
}
But obviously over here you can see that it has all 41 records of which none are grouped up by year.
I thought maybe I could write some linq something like but at the moment incorrect:
var groups = TheResponse.Details
.GroupBy(
d => Int32.Parse(d.NodeYear),
(key, g) => g.GroupBy(
d => d.Months.Select(x => x.MonthID)),
(key2, g2) => g2.GroupBy(d => d.CreatedDateTime)
)
);
Or would I need to change the model for this idea to work?
If I understood your question correctly, then you need to flatten the inner list and then group by months again.
var groups = TheResponse.Details
.GroupBy(d => Int32.Parse(d.NodeYear))
.Select(d => new
{
Year = d.Key,
MonthObj = d.SelectMany(m => m.Months)
.GroupBy(m => m.MonthID)
.Select(x => new
{
MonthID = x.Key,
RelatedItem = x.ToList()
})
});
I have simplified it by using anonymous types, but you can obviously tweek it based on your resp. Model.

select a day from a period - reccurrence

I am trying to implement a recurrence pattern for my Calendar Application.
I want it to work the same way Outlook does when you set an appointment with reccurrence.
public async Task<ValidationResponse<ReccurrenceModel>> ApplyReccurrencePeriod (string userName, ReccurrenceModel value)
{
var user = await repository.FindByUserName(userName);
var fromDateUTC = DateTime.SpecifyKind(value.FromDate, DateTimeKind.Utc);
var toDateUTC = DateTime.SpecifyKind(value.ToDate, DateTimeKind.Utc);
var dates = new List<DateTime>();
var weeklyReccurrence = value.weeklyReccurrence;
if (value.IsMonday == true)
{
var fromDate = value.FromDate;
var toDate = value.ToDate;
for (var dt = fromDate; dt < toDate; dt = dt.AddDays(1))
{
dates.Add(dt);
}
var savedDates = dates.Where(x => x.DayOfWeek == DayOfWeek.Monday).Select(x => x.Date);
}
// I do the same code to verify every week day
var test = dates.Where(x => x.DayOfWeek == DayOfWeek.Friday).Select(x => x.Date);
}
foreach (var date in savedDates) {
var x = user.Holidays.FirstOrDefault(kvp => kvp.Key == date
&& kvp.Value.StateVal == value.State.StateVal);
var dateUTC = DateTime.SpecifyKind(date, DateTimeKind.Utc);
user.Holidays[dateUTC] = value.State;
}
// save
var updatedUser = await repository.UpdateEmployee(user);
return await Task.FromResult(new ValidationResponse<HolidayModel>()
{
IsValid = true,
Result = updatedUser.Holidays.ContainsKey(dateUTC) ? new HolidayModel() { Date = dateUTC, State = updatedUser.Holidays[dateUTC] } : null
});
}
}
The problem with my code is that it works only if I have weekly reccurrence. I need to make it work in order to have 2, 3, ... n weeks reccurrence.
How can I make it skip some weeks?
public class ReccurrenceModel
{
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public int WeeklyReccurrence { get; set; }
public State State { get; set; }
public bool IsMonday { get; set; }
public bool IsTuesday { get; set; }
public bool IsWednesday { get; set; }
public bool IsThursday { get; set; }
public bool IsFriday { get; set; }
public DateTime FromDateToReturn { get; set; }
public DateTime ToDateToReturn { get; set; }
}
The code is a bit convoluted, there are a lot of lines that do nothing at all.
Here I provide a sample of code that, albeit not elegant at all, provides you with the behaviour you need, the following code will create a list of days that are recurrent every 2, 3, whatever you need weeks you define in its call.
This method also accepts a list of DayOfWeek for which you want the recurrence to be created
private static void GetRecurrentDays(DateTime fromDate, DateTime untilDate, uint weeklyRecurrence, List<DayOfWeek> recurrenceDays)
{
var recurrenceDates = new List<DateTime>();
for (var dt = fromDate; dt < untilDate; dt = dt.AddDays(1))
{
if (recurrenceDays.Any(day => day.Equals(dt.DayOfWeek)))
{
var lastDate =
recurrenceDates
.LastOrDefault(date => date.DayOfWeek.Equals(dt.DayOfWeek));
// We multiply 7 days (a week) with weeklyRecurrence to
// calculate the appropiate date in which to add another day,
// calling with either 0 or 1 will calculate a weekly
// schedule
if (lastDate.Equals(DateTime.MinValue)
|| weeklyRecurrence.Equals(0)
|| ((dt - lastDate).Days % (7 * weeklyRecurrence)).Equals(0) )
{
recurrenceDates.Add(dt);
}
}
}
}
you can embed this code in yours in order to obtain the days with weekly recurrence and then, consume them further in your code

Linq: Selecting current month Data but checking if anything needs carrying over from previous months

I currently have this linq:
var filterdForecastRevenue = filteredWonSales
.Where(x => x.ProjectStartDate.Month.Equals(month.Month)
&& x.ProjectStartDate.Year.Equals(month.Year));
foreach (var rev in filterdForecastRevenue)
{
if (rev.ProjectDurationMonths > 0)
{
rev.ForecastSell = rev.ForecastSell / rev.ProjectDurationMonths;
}
}
var forecastRevenueTotal = (filterdForecastRevenue.Any())
? filterdForecastRevenue.Sum(x => x.ForecastSell) : 0;
My Class:
public class WonSaleView
{
[Key]
public Guid Id { get; set; }
public string Jobnumber { get; set; }
public double ForecastSell { get; set; }
public DateTime ProjectStartDate { get; set; }
public int ProjectDurationMonths { get; set; }
}
This works, but what i need is:
Values need to carry over from previous months i.e. If ProjectStartDate Is in July but runs for 3 months (ProjectDurationMonths) i need to carry over the calculate ForecastSell in August and September as well.
I tried for 3 hours trying to figure out such a simple task, any help is fantastic.
You are converting a DateTime to intergers (month, year). It is better to use the DateTime Object. See code below :
DateTime now = DateTime.Now;
DateTime firstOfMonth = new DateTime(now.Year, now.Month, 1);
var filterdForecastRevenue = filteredWonSales
.Where(x => (x.ProjectStartDate >= firstOfMonth)
|| ((x.ProjectStartDate.AddMonths(rev.ProjectDurationMonths) >= firstOfMonth));
If understand correctly you need project which are "active" during given month.
Quickly can come up only with two queries
var now = DateTime.Now();
var startMonth = new DateTime(now.Year, now.Month, 1);
var endMonth = startMonth.AddMonths(1).AddSeconds(-1);
var fullyOverlaping =
filteredWonSales.Where(sale => sale.ProjectStartDate < startMonth)
.Where(sale => sale.ProjectStartDate.AddMonths(rev.ProjectDurationMonths) > endMonth);
var withinMonth =
filteredWonSales.Where(sale => (sale.ProjectStartDate >= startMonth && sale.ProjectStartDate <= endMonth) ||
(sale.ProjectStartDate.AddMonths(rev.ProjectDurationMonths) >= startMonth && sale.ProjectStartDate.AddMonths(rev.ProjectDurationMonths) <= endMonth));
var all = withinMonth.Concat(fullyOverlaping);

Group list according to date with multiple dates per item

I have a list like follows :-
List
EDIT (Years can be different fot Birthdays and anniversaries)
- EmpGuid -265d317b-b819-4171-ba12-e64388746d81
Name - abc
Birthday - 15 Aug 2000
Anniversary- 12 july 1989
EmpGuid - 265d317b-b819-4171-ba12-e64388746d82
Name - xyz
Birthday - 24 Jan 2000
Anniversary- 15 Aug 1988
EmpGuid - 265d317b-b819-4171-ba12-e64388746d83
Name - mno
Birthday - 15 aug 2000
Anniversary- 24 Jan 1987
And I want to group the list according to the dates like so :-
12 July - abc anniversary
15 aug - abc Birthday
mno Birthday
xyz Anniversary
24 jan - xyz birthday
mno anniversary
I tried doing this :-
var groupedEmpList = FinalList.GroupBy(u => u.Birthdate)
.Select(grp =>new { GroupID =grp.Key, FinalList = grp.ToList()})
.ToList()
The the above does not give me the desired output. Any help on this would be appreciated
This solution uses SelectMany to get a flattened list of all the dates on which you can group by the items:
var result = FinalList.Select(item => new
{
Date = new [] { item.Birthday.ToString("ddMM"), item.Anniversary.ToString("ddMM") },
Item = item
})
.SelectMany(item => item.Date.Select(date => new { Date = date, Item = item.Item }))
.GroupBy(item => item.Date)
.Select(grouping => new { Date = grouping.Key, Events = grouping.ToList() }).ToList();
One can also perform the first select within the SelectMany - for the purpose of the answer I kept it separately
For adding the type of the event (and on the way removing the first select):
var result = FinalList.SelectMany(item => new List<dynamic>
{
new { Date = item.Birthday.ToString("ddMM"), Event = "Birthday", Item = item },
new { Date = item.Anniversary.ToString("ddMM"), Event = "Anniversary", Item = item }
})
.GroupBy(item => item.Date)
.Select(grouping => new { Date = grouping.Key, Events = grouping.ToList() }).ToList();
For outputing these results you can:
public enum EventTypes
{
Birthday,
Anniversary
}
public class Event
{
public string Date { get; set; }
public EventTypes Type { get; set; }
public IEnumerable<dynamic> Items { get; set; }
}
var result = FinalList.SelectMany(item => new List<dynamic>
{
new { Date = item.Birthday.ToString("ddMM"), Type = EventTypes.Birthday, Item = item },
new { Date = item.Anniversary.ToString("ddMM"), Type = EventTypes.Anniversary, Item = item }
})
.GroupBy(item => new { item.Date, item.Type })
.Select(grouping => new Event { Date = grouping.Key.Date, Type = grouping.Key.Type, Items = grouping.ToList() }).ToList();
Now the result list is List<Event> and inside you have also your oridinal objects in the Items (replace the dynamic of that list to your original class type - I just don't know it)
Since you need to duplicate items (to put same item into up to 2 groups based on different dates) you need to perform that step separately as LINQ does not duplicate items with basic commands.
Simple option - just have 2 lists combined first - one for birthdays and one for anniversary and extract date with its type to wrapping type similar to:
var mergedList =
FinalList.Select(x => new {
Date = x.Birthdate, Type = "Birthday", Value = x})
.Concat(
FinalList
.Where(x => x.Birthday != x.Anniversary) // if needed
.Select(x => new {
Date = x.Anniversary, Type = "Anniversary", Value = x});
// Now list have all unique dates - can group and extract any info
var grouped = mergedList
.GroupBy(u => u.Date)
.Select(grp => new {
Date = grp.Key,
ListOfNames = grp.Select(x => new {x.Value.Name, x.Type}).ToList()
})
.ToList();
Assuming that you have a class:
class Info
{
public Guid EmpGuid { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
public DateTime Anniversary { get; set; }
}
You need to unpivot your class into the following class:
class UnpivotInfo
{
public Guid EmpGuid { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
public string Type { get; set; }
}
where your Type is either "Birthday" or "Anniversary", by doing this:
var unpivoted = list.SelectMany(i => new[]
{
new UnpivotInfo {EmpGuid = i.EmpGuid, Date = i.Birthday, Type = "Birthday", Name = i.Name}
, new UnpivotInfo {EmpGuid = i.EmpGuid, Date = i.Anniversary, Type = "Anniversary", Name = i.Name}
});
You can then group your data by date:
var groups = unpivoted.GroupBy(p => p.Date);

Group and Count in Entity Framework

I have a table with Logs and I am counting the Logs per day as follows:
// Count logs by day
IList<DataModel> models = _context.Logs
.Where(x => x.Created >= dateMinimum && x.Created <= dateMaximum)
.GroupBy(x => new { Year = x.Created.Year, Month = x.Created.Month, Day = x.Created.Day })
.Select(x => new { Year = x.Key.Year, Month = x.Key.Month, Day = x.Key.Day, Count = x.Count() })
.AsEnumerable()
.Select(x => new DataModel { Date = new DateTime(x.Year, x.Month, x.Day), LogsCount = x.Count })
.ToList();
// Fill empty days with dates which contains all days in range
models.AddRange(dates.Where(x => !models.Any(y => y.Date == x.Date)).Select(x => new DataModel { Date = x, LogsCount = 0 }));
This is working if I want to count all logs by day independently of the type.
But I would like to count logs by day and type (Error, Warn, Info, ...).
I tried to add x.Type to group but at the end I get only 3 items.
At the moment my DataModel is the following:
public class DataModel
{
public DateTime Date { get; set; }
public Int32 LogsCount { get; set; }
}
But maybe it should be something like:
public class DataModel
{
public DateTime Date { get; set; }
public KeyValuePair<String, Int32> LogsCount { get; set; }
}
Where LogsCount has a string which holds the Type and Int32 which contains the count.
How can I do this?
Might want to consider using entity functions for grouping by date.
Example:
var results = query.GroupBy(r => new
{
SentDate = System.Data.Objects.EntityFunctions.TruncateTime(r.Launch.EmailDeliveredDate),
EventSubTypeID = r.EmailEventSubtypeID
})
.Select(x => new
{
x.Key.SentDate,
x.Key.EventSubTypeID,
NumResults = x.Count()
})
.ToList();
Did you try something like this?
IList<DataModel> models = Logs
.Where(x => x.Created >= dateMinimum && x.Created <= dateMaximum)
.GroupBy(x => new { Year = x.Created.Year, Month = x.Created.Month, Day = x.Created.Day, Type = x.Type })
.Select(x => new { Year = x.Key.Year, Month = x.Key.Month, Day = x.Key.Day, Count = x.Count(), Type = x.Key.Type })
.AsEnumerable()
.Select(x => new DataModel { Date = new DateTime(x.Year, x.Month, x.Day), LogsCount = x.Count, Type = x.Type })
.ToList()
public class DataModel
{
public DateTime Date { get; set; }
public Int32 LogsCount { get; set; }
public string Type { get; set; }
}

Categories

Resources