Calculating months between 2 dates, then calculating days in each month - c#

So I'm trying to create a graph by getting the start and end date from the user. I need to count the number of months between the dates, then for each month calculate the number of days and get the name of the month as well.
I've got the number of months between the 2 dates but can't seem to translate the remaining into code. I have a class called MonthRange
public class MonthRange
{
public DateTime startDate { get; set; }
public DateTime endDate { get; set; }
public string monthName { get; set; }
public MonthRange() {
}
public MonthRange(DateTime startDate, DateTime endDate, string monthName) {
this.startDate = startDate;
this.endDate = endDate;
this.monthName = monthName;
}
}
And my method:
private List<MonthRange> GetRangeOfMonthsBetweenDates(DateTime startDate, DateTime endDate) {
List<MonthRange> result = new List<MonthRange>();
int months = (endDate.Year - startDate.Year)*12 + endDate.Month - startDate.month;
foreach(var m in months){
//get the start and end date of that month with the name and add it to the result list.
}
}
The kicker is that if the startDate is midway through the month, then that is what should be saved in the result list, likewise if the endDate is midway of the month, that is what should be saved. I'm a bit lost and would appreciate any help.
Edit: So this is an example of what I'm trying to achieve. I'm trying to create a graph that plots the number of times a person has had food in a given time range.
Edit 2: So i ended up going this way:
private List<MonthRange> GetRangeOfMonthsBetweenDates(DateTime startDate, DateTime endDate) {
List<MonthRange> result = new List<MonthRange>();
DateTime holder = startDate;
var months = (endDate.Year - startDate.Year) * 12 + endDate.Month - startDate.Month;
for (int i = 0; i <= months; i++) {
if (i == 0)
{
result.Add(new MonthRange(startDate, CalculateStartOfMonth(endDate), startDate.ToString("MMM")));
}
else if (i == months)
{
result.Add(new MonthRange(CalculateEndOfMonth(startDate), endDate, endDate.ToString("MMM")));
}
else {
DateTime middleMonth = holder.AddMonths(1);
result.Add(new MonthRange(CalculateStartOfMonth(middleMonth), CalculateEndOfMonth(middleMonth), middleMonth.ToString("MMM")));
}
}
return result;
}
private DateTime CalculateStartOfMonth(DateTime endDate) {
var startOfMonth = new DateTime(endDate.Year, endDate.Month, 1);
return startOfMonth;
}
private DateTime CalculateEndOfMonth(DateTime startDate)
{
var endOfMonth = new DateTime(startDate.Year, startDate.Month, DateTime.DaysInMonth(startDate.Year, startDate.Month));
return endOfMonth;
}

Note: this is not an answer to the question but a suggestion for a different approach. Since the question is about charting number of meals for a person over a period, I think the natural way to approach this (at least for representing the data) is to do it by day, not by month. (Visually, the data may have to be represented by month but it's much easier to first organize the data by days and then render it by month.)
Since the question is rather vague, I made some assumptions about the data. This class could be used to store information about a single day.
class DayEntry
{
public DateTime Date { get; set; }
public string MonthName { get; set; }
public int NumberOfMeals { get; set; }
}
The following function creates a list of DayEntry objects, initialized for each day in the date range:
List<DayEntry> CreateDayEntries(DateTime dateStart, DateTime dateEnd)
{
var dateDiff = dateEnd - dateStart;
var dayCount = (int) Math.Ceiling(dateDiff.TotalDays) + 1;
var dayRange = new List<DayEntry>(dayCount);
for (var i = 0; i < dayCount; i++)
{
var date = dateStart.AddDays(i);
var dayEntry = new DayEntry
{
Date = date,
NumberOfMeals = 0, // TODO
MonthName = date.ToString("MMMM", CultureInfo.CurrentCulture)
};
dayRange.Add(dayEntry);
}
return (dayRange);
}
When the data is prepared for the given date range, it can then be shown in a UI, grouped by month - since each day entry has its own date (with the name of the month it belongs to) and are in order in the list, it's easy to iterate through them and create the UI output.

Based on your MonthRange structure, try this:
private IEnumerable<MonthRange> GetRangeOfMonthsBetweenDates(DateTime startDate, DateTime endDate) {
var start = startDate;
while(start<endDate) {
var end = start.AddMonths(1).AddDays(-start.Day);
yield return new MonthRange(start, end < endDate ? end : endDate, start.ToString("MMMM"));
start = end.AddDays(1);
}
}
For example:
var start = new DateTime(2017,5,16);
var end = new DateTime(2017,7,24);
foreach(var m in GetRangeOfMonthsBetweenDates(start, end)) {
Console.WriteLine("{0}: {1:dd/MM/yyyy}-{2:dd/MM/yyyy}", m.monthName, m.startDate, m.endDate);
}
Should print:
May: 16/05/2017-31/05/2017
June: 01/06/2017-30/06/2017
July: 01/07/2017-24/07/2017

What i would suggest is for you to convert everything into days first.
eg. x= 361 ; // total number of days
then from there you can split them up into months and years .
months = x/30; // find months
days = x%30; // find the remainder of the days from the month
years = months/12; // find years
This way its much easier to calculate.

Related

how to check if dates exist in a list of all the weeks of a year

I've got an ASP.net C# application which creates a list of all the weeks in a given year. e.g. selected year 2019, and will produce 31/12/2019 to 06/01/2019 and so on. see attached image.
To produce this I am borrowing some code from an example I found on Stack Overflow here
Now I also have another list containing dates in the format dd/MM/yyyy, this is generated from an XML file, so I wanted to only show the weeks that match dates in the weeks of the year list and populate the drop down list when a date in my XML generated list is contained within it.
For example if I had a full week or even a day in my XML generated list which fell between the 31/12/2018 to 06/01/2019 I want to show it in the drop down list.
Similarly if the XML generated list doesn't contain at least a day from that week then don't show it.
I've pasted the code I used to get the weeks of a given year below.
I'm not sure of any easy way to compare both lists. Any help would be greatly appreciated.
public List<string> FetchWeeks(int year)
{
List<string> weeks = new List<string>();
DateTime startDate = new DateTime(year, 1, 1);
startDate = startDate.AddDays(1 - (int)startDate.DayOfWeek);
DateTime endDate = startDate.AddDays(6);
while (startDate.Year < 1 + year)
{
weeks.Add(string.Format("{0:dd/MM/yyyy} to {1:dd/MM/yyyy}", startDate, endDate));
startDate = startDate.AddDays(7);
endDate = endDate.AddDays(7);
}
//DropDownList1.Items.Add(weeks);
return weeks;
}
If were trying to compare lists to determine a set of valid weeks, I would try to determine an absolute week index and use that in my work. Since weeks are not impacted by things like leap years or other date oddities, we can just count in 7-day intervals from the beginning of a known of date range. Forgive me if my C# is rusty, but something to the effect of:
public int ToWeekIndex(DateTime date)
{
// Takes any date and maps it to a value that represents the week it resides in.
Timespan ts = date - DateTime.MinValue // Monday, January 1, 0001;
return ts.Days / 7; // Integer divide, drops the remainder.
}
public DateTime FromWeekIndex(int weekIndex)
{
// Takes a week index and returns the Monday from it.
Timespan ts = new Timespan(weekIndex * 7, 0, 0, 0); // Days, hours, minutes, seconds
return DateTime.MinValue + ts;
}
Then to build out your weeks, you could do something to the effect of pseudocode:
all_weeks = []
for date in January 1, 2019 to December 31, 2019 step 7 days:
week_index = ToWeekIndex(date)
week_start = FromWeekIndex(week_index)
week_end = week_start + 7 days - 1 second
all_weeks += [week_start, week_end]
instead of a list of string for dates, use a list of object that contains the week dates and a boolean defaulted to false.
public class WeekObj
{
public string Week { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public bool IsValid { get; set; }
};
List<WeekObj> weeks= new WeekObj();
weeks.add(new WeekObj { "week string", startDate, endDate, false });
Loop through your list of days, and for each day go through the list of weeks and set it to true if the day is between startDate and endDate (which is now in DateTime obj), do that for false dates, no need to recompare true dates.
public static bool Between(DateTime input, DateTime date1, DateTime date2)
{
return (input >= date1 && input <= date2);
}
This is the weeks in the Year data you already have -
class WeekData
{
public DateTime WeekStartDate { get; set; }
public DateTime WeekEndDate { get; set; }
public int WeekStartDay //Gets Day in the year for the Week Start Date
{
get { return WeekStartDate.DayOfYear; }
}
public int WeekEndDay //Gets Day in the year for the Week End Date
{
get { return WeekEndDate.DayOfYear; }
}
}
Dummy WeeksInTheYear data
List<WeekData> weeks = new List<WeekData>
{
new WeekData{WeekStartDate = new DateTime(2019,10,6), WeekEndDate = new DateTime(2019,10,12)},
new WeekData{WeekStartDate = new DateTime(2019,10,13), WeekEndDate = new DateTime(2019,10,19)},
new WeekData{WeekStartDate = new DateTime(2019,10,20), WeekEndDate = new DateTime(2019,10,26)},
new WeekData{WeekStartDate = new DateTime(2019,10,27), WeekEndDate = new DateTime(2019,11,2)}
};
Dummy Dates from the XML feed
List<DateTime> xmlDates = new List<DateTime> { new DateTime(2019, 11, 1), new DateTime(2019, 10, 12), new DateTime(2019, 10, 31) };
Filtering
var weeksINeed = new List<WeekData>();
foreach (var date in xmlDates)
{
var weekINeed = weeks.Where(x => x.WeekStartDay <= date.DayOfYear && x.WeekEndDay >= date.DayOfYear)
.FirstOrDefault();
if (!weeksINeed.Any(x => x.WeekStartDay == weekINeed.WeekStartDay))
{
weeksINeed.Add(weekINeed);
}
}
Output -
foreach (var weekdata in weeksINeed.OrderBy(x=>x.WeekStartDay))
{
Console.WriteLine($"WeekStartDate - {weekdata.WeekStartDate} WeekEndDate - {weekdata.WeekEndDate}");
}
Using some extension functions and LINQ, you can just generate the list directly from the XML Date List<string>.
First, an IEnumerable<> extension to select distinct by a lambda function:
public static class IEnumerableExt {
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> src, Func<T, TKey> keySelector, IEqualityComparer<TKey> comparer = null) {
var seenKeys = new HashSet<TKey>(comparer);
foreach (var e in src)
if (seenKeys.Add(keySelector(e)))
yield return e;
}
}
Then some calendar extensions using the built-in ISOWeek methods to get the week of year (Based on your week date ranges, I assume you are using ISO 8601 Weeks):
public static class CalendarExt {
public static int GetISO8601WeekOfYear(this DateTime aDate) => ISOWeek.GetWeekOfYear(aDate);
public static DateTime FirstDateOfYear(this DateTime d) => new DateTime(d.Year, 1, 1);
public static DateTime FirstDateOfISO8601Week(this DateTime aDate) => aDate.AddDays(-(((int)aDate.DayOfWeek + 6) % 7));
public static DateTime LastDateofISO8601Week(this DateTime aDate) => aDate.FirstDateOfISO8601Week().AddDays(6);
public static DateTime FirstDateOfISO8601Week(int year, int weekNum) => ISOWeek.ToDateTime(year, weekNum, DayOfWeek.Monday);
public static DateTime LastDateofISO8601Week(int year, int weekNum) => FirstDateOfISO8601Week(year, weekNum).AddDays(6);
// for .Net without ISOWeek
//public static DateTime FirstDateOfISO8601Week(this DateTime aDate) => aDate.AddDays(-(((int)aDate.DayOfWeek + 6) % 7));
//public static int GetISO8601WeekOfYear(this DateTime aDate) =>
// CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(aDate.AddDays(DayOfWeek.Monday <= aDate.DayOfWeek && aDate.DayOfWeek <= DayOfWeek.Wednesday ? 3 : 0), CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
}
Finally, given your list of string dates from XML in xmlDateStrings, you can compute the week ranges list:
var currentCulture = CultureInfo.CurrentCulture;
var ans = xmlDateStrings.Select(ds => DateTime.ParseExact(ds, "dd/MM/yyyy", currentCulture))
.DistinctBy(d => d.GetISO8601WeekOfYear())
.OrderBy(d => d) // assume XML is unsorted
.Select(d => $"{d.FirstDateOfISO8601Week().ToString("dd/MM/yyyy")} to {d.LastDateofISO8601Week().ToString("dd/MM/yyyy")}")
.ToList();

Filter or Check Date in A Date Range

It's totally a simple or basic requirement. I am trying to get a date from a list of date using C#. So what I've done, made a function and iterated that with a for loop. I've tried to make the list function into two ranges and passed the value from the DatePicker control as follows:
private void btnClick_Click(object sender, EventArgs e)
{
DateTime theFromDate = dateTimePicker1.Value;
DateTime theToDate = dateTimePicker2.Value;
List<DateRange> lstRange = GetDateRange();
/**Trying To Get The Date From The Range - Starts**/
var dates = new List<DateTime>();
for (var dt = theFromDate; dt <= theToDate; dt = dt.AddDays(1))
{
dates.Add(dt);
//MessageBox.Show(dt.Date.ToString());
}
List<DateRange> lst = GetDateRange();
foreach(var item in lst)
{
if(theFromDate <= item.EndtDate.Date)
{
MessageBox.Show(theFromDate.ToString("dd-MMM-yyyy") + " in the date range!");
}
else
{
MessageBox.Show(theFromDate.ToString("dd-MMM-yyyy") + " not in the date range!");
}
}
/**Trying To Get The Date From The Range - Ends**/
}
public class DateRange
{
public DateTime date { set; get; }
public DateTime EndtDate { set; get; }
}
/**List of Dates Here - Starts**/
public List<DateRange> GetDateRange()
{
List<DateRange> lstDate = new List<DateRange>();
DateRange aDateRange = new DateRange();
aDateRange.StartDate = Convert.ToDateTime("10-Aug-2018");
aDateRange.EndtDate = Convert.ToDateTime("13-Aug-2018");
lstDate.Add(aDateRange);
return lstDate;
}
/**List of Dates Here - Ends**/
Unfortunately this doesn't return the desired output though the list has the specific date.
Update 1:
Expected Output - FromDate and ToDate values are stored in the list.
FromDate ToDate
10-AUG-2018 13-AUG-2018
**in the date range**
FromDate ToDate
13-AUG-2018 16-AUG-2018
**in the date range** //As 13 is the end date in the given list
FromDate ToDate
8-AUG-2018 10-AUG-2018
**in the date range** //As 10 is the start date in the given list
FromDate ToDate
8-AUG-2018 8-AUG-2018
**not in the date range** //As 10 is the start date in the given list
I'm having a bit of trouble figuring out what it is you're trying to do, to be perfectly honest, and I can't help but feel you're "over-engineering" your solution.
First, a "date range" is just two dates - a staring date and an end date, but your GetDateRange method has 4 dates inside it, which it returns as a list. This is incredibly confusing - and I'm not sure if you're trying to get multiple date-ranges (multiple pairs) or a single date-range out of it. Given that all the dates are one after another, I'm going to assume the latter.
public class DateRange
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
public DateRange GetStaticDateRange()
{
//It seems counterproductive to add all 4 dates here,
//given that these are all one after the other
return new DateRange
{
StartDate = new DateTime(2018, 7, 10),
EndDate = new DateTime(2018, 7, 13)
};
//Obviously this can be modified as needed to return whatever combination of
//start-end dates you want, but this method will only ever return ONE range
//However, this method could just as well accept parameters and / or access other resources
}
public bool IsInDateRange(DateTime dateToCheck, DateRange targetRange)
{
//An argument can be made to use non-encompassing comparisons for both checks
//depending on your requirements
return dateToCheck >= targetRange.StartDate && dateToCheck <= targetRange.EndDate;
}
The above has a simple class for storing a "date-range" (aptly called DateRange), and a sample method which checks if a given DateTime is valid inside a specific DateRange.
EDIT:
OK, so from your updated question it seems like you're trying to find if two date-ranges overlap (at all).
In which case, the code below should help.
public static bool DateRangesOverlap(DateRange range1, DateRange range2)
{
return (range1.StartDate >= range2.StartDate && range1.StartDate <= range2.EndDate) ||
(range1.EndDate >= range2.StartDate && range1.EndDate <= range2.EndDate);
}
Here's a working example on .NET Fiddle with your test cases. Note that I'm still using the DateRange class as defined above with a constructor added for brevity.
Also please note that the DateRange class has no sanity-check for the start and end parameters, and it's possible to create a DateRange with the two values reversed (i.e. start > end) which, obviously, would cause errors. But this is just an example so implementations of these things I leave to you. ;)
You have a few bugs in your code.
For example in the GetDateRange(), you are adding only one date to the range, and its date will set to 13-Aug-2018, so that is one thing you need to fix, and if your goal is to find a date in a range of dates, you can use Linq. To compare ranges, I also suggest use DateTime.CompareTo Method. See the code below for corrections of your errors:
public static bool RangeContainsDate(DateTime queriedDateTime)
{
var queriedDateRange = new DateRange { Date = queriedDateTime };
List<DateRange> dates = GetDateRange();
return dates.Where(d => d.CompareTo(queriedDateRange) == 0).Any();
}
/**List of Dates Here - Starts**/
public static List<DateRange> GetDateRange()
{
List<DateRange> lstDate = new List<DateRange>();
DateRange aDateRange1 = new DateRange();
aDateRange1.Date = Convert.ToDateTime("10-Aug-2018");
lstDate.Add(aDateRange1);
DateRange aDateRange2 = new DateRange();
aDateRange2.Date = Convert.ToDateTime("11-Aug-2018");
lstDate.Add(aDateRange2);
DateRange aDateRange3 = new DateRange();
aDateRange3.Date = Convert.ToDateTime("12-Aug-2018");
lstDate.Add(aDateRange3);
DateRange aDateRange4 = new DateRange();
aDateRange4.Date = Convert.ToDateTime("13-Aug-2018");
lstDate.Add(aDateRange4);
return lstDate;
}
}
}
public class DateRange : IComparable<DateRange>
{
public DateTime Date { set; get; }
public int CompareTo(DateRange other)
{
if (ReferenceEquals(other, null))
{
return -1;
}
return DateTime.Compare(Date, other.Date);
}
}
private void btnClick_Click(object sender, EventArgs e)
{
//DateTime theFromDate = dateTimePicker1.Value;
DateTime theToDate = dateTimePicker2.Value;
List<DateRange> lstRange1 = GetDateRange();
List<DateRange> lstRange2 = GetDateRange();
var result = lstRange1.Any(x => x.date >= theToDate && lstRange2.Any(y => y.date < theToDate));
if (result)
{
MessageBox.Show(theToDate.ToString("dd-MMM-yyyy") + " in the date range!");
}
else
{
MessageBox.Show(theToDate.ToString("dd-MMM-yyyy") + " not in the date range!");
}
}
public List<DateRange> GetDateRange()
{
List<DateRange> lstDate = new List<DateRange>();
lstDate.Add(new DateRange { date = Convert.ToDateTime("10-Aug-2018") });
lstDate.Add(new DateRange { date = Convert.ToDateTime("11-Aug-2018") });
lstDate.Add(new DateRange { date = Convert.ToDateTime("12-Aug-2018") });
lstDate.Add(new DateRange { date = Convert.ToDateTime("13-Aug-2018") });
return lstDate;
}

Perform calculations on a set using only LINQ

I'd like to perform the following using only LINQ.
I have a list of time sheet entries with user's in and out times. The class looks like this:
public class TimeSheetLog
{
public Guid EmployeeId { get; set; }
public DateTime ClockInTimeStamp { get; set; }
public DateTime ClockOutTimeStamp { get; set; }
}
I'm passing a List<TimeSheetLog>() which contains all logs from the beginning of the year to date.
I'm trying to calculate the total work time -- regardless of employee -- for the month of January. Please also notice that I have a function named GetTimeDifferenceInMinutes() which calculates the number of minutes between two date/time values.
Here's what I currently have but I feel the whole thing can be done using LINQ only.
public static int GetTotalTimeWorked(List<TimeSheetLog> logs, DateTime startDate, DateTime endDate)
{
// I'm passing 1/1/2018 for startDate and 1/31/2018 for endDate to this function
var totalTimeWorkedInMinutes = 0;
var januaryLogs = logs.Where(x => x.ClockInTimeStamp >= startDate &&
x.ClockOutTimeStamp <= endDate);
foreach(var item in januaryLogs)
{
totalTimeWorkedInMinutes += GetTimeDifferenceInMinutes(item.ClockInTimeStamp, itemClockOutTimeStamp);
}
return totalTimeWorkedInMinutes;
}
var logsFilteredByDate = logs.Where(x => x.ClockInTimeStamp >= startDate &&
x.ClockOutTimeStamp <= endDate);
var totalTimeWorkedInMinutes = logsFilteredByDate.Sum(x =>
GetTimeDifferenceInMinutes(x.ClockInTimeStamp, x.ClockOutTimeStamp));
Or, to combine it all into one query, which is unnecessary and harder to read,
var totalTimeWorkedInMinutes = logs.Where(x => x.ClockInTimeStamp >= startDate &&
x.ClockOutTimeStamp <= endDate)
.Sum(x =>
GetTimeDifferenceInMinutes(x.ClockInTimeStamp, x.ClockOutTimeStamp));
you need sum
var tot = januaryLogs.Sum(item=>GetTimeDifferenceInMinutes(item.ClockInTimeStamp, itemClockOutTimeStamp));
Couldn't you do the Where with a Sum and do DateTime Subtract in the Sum, so
decimal total = logs.Where(x => x.ClockInTimeStamp >= startDate && x.ClockOutTimeStamp <= endDate).Sum(x.ClockOutTimeStamp.Subtract(x.ClockInTimeStamp).TotalMinutes);
The problem seems easy until you realize that a time sheet can span months. So if someone clocked in on January 31st and clocked out on February 1st, you have to count partial timesheets, to do it right.
Here is my solution:
public static class ExtensionMethods
{
static public double TotalMinutes(this IEnumerable<TimeSheetLog> input, DateTime startPeriod, DateTime endPeriod)
{
return TimeSpan.FromTicks
(
input
.Where( a=>
a.ClockOutTimeStamp >= startPeriod &&
a.ClockInTimeStamp <= endPeriod
)
.Select( a=>
Math.Min(a.ClockOutTimeStamp.Ticks, endPeriod.Ticks) -
Math.Max(a.ClockInTimeStamp.Ticks, startPeriod.Ticks)
)
.Sum()
)
.TotalMinutes;
}
}
Logic:
Find all timesheets that overlap at least partially with the period of interest.
Compute the start time as either the clock in time or the period start time, whichever is later.
Compute the end time as either the clock out time or the period end time, whichever is earlier.
Take the difference of the start and end time as ticks. Sum() these.
To do all this math, we convert all the timestamps to Ticks, since you can't take a Max() of two DateTimes. We can add ticks up just fine, then convert the total back into minutes before returning.
Test program (notice the third timesheet spans both January and February):
public class Program
{
static public List<TimeSheetLog> testData = new List<TimeSheetLog>
{
new TimeSheetLog
{
ClockInTimeStamp = DateTime.Parse("1/1/2018 9:00 am"),
ClockOutTimeStamp = DateTime.Parse("1/1/2018 5:00 pm")
},
new TimeSheetLog
{
ClockInTimeStamp = DateTime.Parse("1/2/2018 9:00 am"),
ClockOutTimeStamp = DateTime.Parse("1/2/2018 5:00 pm")
},
new TimeSheetLog
{
ClockInTimeStamp = DateTime.Parse("1/31/2018 6:00 pm"),
ClockOutTimeStamp = DateTime.Parse("2/1/2018 9:00 am")
},
new TimeSheetLog
{
ClockInTimeStamp = DateTime.Parse("2/3/2018 9:00 am"),
ClockOutTimeStamp = DateTime.Parse("2/3/2018 5:00 pm")
}
};
public static void Main()
{
var startPeriod = new DateTime(2018, 1, 1);
var endPeriod = new DateTime(2018, 1, 31, 23, 59, 59, 9999);
Console.WriteLine( testData.TotalMinutes(startPeriod, endPeriod).ToString("0.00") );
}
}
Output:
1320.00
...which is correct.
See my code on DotNetFiddle
Another option is to use .Aggregate function.
public static int GetTotalTimeWorked(List<TimeSheetLog> logs, DateTime startDate, DateTime endDate)
{
var totalTimeWorkedInMinutes = 0;
return logs.Where(x => x.ClockInTimeStamp >= startDate && x.ClockOutTimeStamp <= endDate)
.Aggregate(totalTimeWorkedInMinutes, (total, item) => total + GetTimeDifferenceInMinutes(item.ClockInTimeStamp, item.ClockOutTimeStamp));
}

get monthwise working days between a given start date and end date

I need to write a method where i will pass a start date and a end date . The output should be a list with two parametsrs . One is month name and other is no of working days in that month. (removing sat and sun)
Please advise.
public List<MonthDaysData> GetMonthwiseWorkingdays(DateTime? start, DateTime? end)
{
List<MonthDaysData> monthdays = new List<MonthDaysData>();
// Coding to get the output
return monthdays;
}
public class MonthDaysData
{
public Int32? Month { get; set; }
public Int32? days { get; set; }
}
You could use an Extension Method to get the values like this...
public static class Extensions
{
public static List<MonthDaysData> GetWorkingDaysPerMonthTo(this DateTime from,
DateTime to)
{
var workings = new Dictionary<int, int>();
var currentDateTime = from;
while (currentDateTime <= to)
{
if (currentDateTime.DayOfWeek != DayOfWeek.Saturday
&& currentDateTime.DayOfWeek != DayOfWeek.Sunday
&& !currentDateTime.IsHoliday("CountryCode"))
if (!workings.ContainsKey(currentDateTime.Month))
workings.Add(currentDateTime.Month, 1);
else
{
int curWork;
workings.TryGetValue(currentDateTime.Month, out curWork);
curWork++;
workings.Remove(currentDateTime.Month);
workings.Add(currentDateTime.Month, curWork);
}
currentDateTime = currentDateTime.AddDays(1);
}
return workings.Select(work => new MonthDaysData {Month = work.Key,
days = work.Value}).ToList();
}
public static bool IsHoliday(this DateTime date, string countryCode)
{
// some service that takes a country code and
// returns true/false if its a holiday
return false;
}
}
You could then call it from anywhere like...
var today = new DateTime(2014, 10, 16);
var dates = today.GetWorkingDaysPerMonthTo(new DateTime(2014, 12, 16));
However, this is simply working with weekdays as working days, you would need to check public holidays etc.
This sounds like homework and you don't show what you have tried, so I'm not going to churn out all code for you. There are quite some questions on this matter. See for example Get working days DateTime List of two dates for a simple implementation that returns a list of working day dates:
IEnumerable<DateTime> workingDays = WorkDaysBetween(DateTime start, DateTime end);
You'll then have to group them by month as per your requirement:
var groupedByMonth = workingDays.GroupBy(d => new DateTime(d.Year, d.Month, 1));
From there you must be able to Select() the proper projection.

Getting all DateTimes between two 'DateTime's in C#

I have two DateTimes, and I want to get all DateTimes between these Dates. Such as, if my Dates are like 01.01.2010 - 05.01.2010, my function should return me a list of date (List), and it must contain 01.01.2010, 02.01.2010, 03.01.2010, 04.01.2010, and 05.01.2010.
I wrote a function like this. It works fine, if my dates are in a month. It won't work if my dates are like 01.01.2010 - 05.02.2010. Because the month changed, and my function can't handle it. Is there a function in C# that returns all dates between two dates? Or how can I handle month change?
public void GetAllDatesAndInitializeTickets(DateTime startingDate, DateTime endingDate)
{
List<DateTime> allDates = new List<DateTime>();
int starting = startingDate.Day;
int ending = endingDate.Day;
for (int i = starting; i <= ending; i++)
{
allDates.Add(new DateTime(startingDate.Year, startingDate.Month, i));
}
Question solved, see Tim Robinson's simple answer to use.
You can use DateTime objects directly in the loop, in place of your int. DateTime.AddDays handles month ends correctly.
for (DateTime date = startingDate; date <= endingDate; date = date.AddDays(1))
allDates.Add(date);
How about something like this?
public IEnumerable<DateTime> DateRange(DateTime fromDate, DateTime toDate)
{
return Enumerable.Range(0, toDate.Subtract(fromDate).Days + 1)
.Select(d => fromDate.AddDays(d));
}
Edit: Tested now. :)
public IEnumerable<DateTime> GetAllDatesAndInitializeTickets(DateTime startingDate, DateTime endingDate)
{
if (endingDate < startingDate)
{
throw new ArgumentException("endingDate should be after startingDate");
}
var ts = endingDate - startingDate;
for (int i = 0; i < ts.TotalDays; i++)
{
yield return startingDate.AddDays(i);
}
}
You were so close... just don't use the day, use the whole date.
static IEnumerable<DateTime> GetAllDatesAndInitializeTickets(DateTime startingDate, DateTime endingDate)
{
List<DateTime> allDates = new List<DateTime>();
for (DateTime i = startingDate; i <= endingDate; i = i.AddDays(1))
{
allDates.Add(i);
}
return allDates.AsReadOnly();
}
Given a lowerdate value and higher date value in String and a frequency as the third parameter this method should return a dictionary of dates; where the key is the start value of a date range and the value is the respective range.
This works fine if the frequency is either weekly or monthly- you can customize it as per your need.
The date values passed should be in proper format or you might need to format it using tryParseExact or something like that.
protected static Dictionary<DateTime, String> getDateRange(String lowerDate, String higherDate, String frequency)
{
DateTime startDate, endDate;
startDate = Convert.ToDateTime(lowerDate);
endDate = Convert.ToDateTime(higherDate);
Dictionary<DateTime, String> returnDict = new Dictionary<DateTime, String>();
while (frequency.Equals("weekly") ? (startDate.AddDays(7) <= endDate) : (startDate.AddMonths(1) <= endDate))
{
if (frequency.Equals("weekly"))
{
returnDict.Add(startDate, startDate + "-" + startDate.AddDays(7));
startDate = startDate.AddDays(8);
}
if (frequency.Equals("monthly"))
{
returnDict.Add(startDate, startDate + "-" + startDate.AddMonths(1));
startDate = startDate.AddMonths(1).AddDays(1);
}
}
returnDict.Add(startDate, startDate + "-" + endDate);
return returnDict;
}
The top solutions will fail if the date includes different hours. Here is a solution getting all hours and all days:
All Days:
static public List<string> get_days_between_two_dates(DateTime start_date, DateTime end_date)
{
List<string> days_list = new List<string>();
DateTime temp_start;
DateTime temp_end;
//--Normalize dates by getting rid of minues since they will get in the way when doing the loop
temp_start = new DateTime(start_date.Year, start_date.Month, start_date.Day);
temp_end = new DateTime(end_date.Year, end_date.Month, end_date.Day);
//--Example Should return
//--1-12-2014 5:59AM - 1-13-2014 6:01AM return 12 and 13
for (DateTime date = temp_start; date <= temp_end; date = date.AddDays(1))
{
days_list.Add(date.ToShortDateString());
}
return days_list;
}
All Hours:
static public List<string> get_hours_between_two_dates(DateTime start_date, DateTime end_date)
{
List<string> hours_24_list = new List<string>();
DateTime temp_start;
DateTime temp_end;
//--Normalize dates by getting rid of minutes since they will get in the way when doing the loop
temp_start = new DateTime(start_date.Year, start_date.Month, start_date.Day, start_date.Hour, 0, 0);
temp_end = new DateTime(end_date.Year, end_date.Month, end_date.Day, end_date.Hour, 0, 0);
//--Example Should return
//--5:59AM - 6:01AM return 5am and 6am
for (DateTime date = temp_start; date <= temp_end; date = date.AddHours(1))
{
hours_24_list.Add(date.ToShortTimeString());
}
return hours_24_list;
}
Based on your starting code and using the features available at the time of writing, here is a quick console app to demonstrate how to do it - use AddDays() instead:
class Program
{
static void Main(string[] args)
{
GetDates(new DateTime(2010, 1, 1), new DateTime(2010, 2, 5));
Console.ReadKey();
}
static List<DateTime> GetDates(DateTime startDate, DateTime endDate)
{
List<DateTime> dates = new List<DateTime>();
while ((startDate = startDate.AddDays(1)) < endDate)
dates.Add(startDate);
return dates;
}
}
Although I think the Enumerable.Range() answer from Matt is a nicer solution.
static IEnumerable<DateTime> GetAllDatesAndInitializeTickets(DateTime startingDate, DateTime endingDate)
{
List<DateTime> allDates = new List<DateTime>();
for (DateTime i = startingDate; i <= endingDate; i = i.AddDays(1))
{
allDates.Add(i);
}
return allDates.AsReadOnly();
}

Categories

Resources