Calculate Working Days without Holidays for specific Country - c#

I've been googling around here to find a solution for my problem, but did not found any so far.
Well, I found some parcial functions that could work, but as I am newbie in C#, I need some help merging all code to work.
So, I need to calculate Working Days between 2 dates, ignoring weekends and Portuguese Holidays.
For the holidays, I have this code which returns all Portuguese Holidays:
public static List<DateTime> GetHolidays(int year)
{
List<DateTime> result = new List<DateTime>();
result.Add(new DateTime(year, 1, 1)); //New Year Day
result.Add(new DateTime(year, 4, 25)); //Dia da Liberdade (PT)
result.Add(new DateTime(year, 5, 1)); //Labour Day
result.Add(new DateTime(year, 6, 10)); //Dia de Portugal (PT)
result.Add(new DateTime(year, 8, 15)); //Assumption of Mary
result.Add(new DateTime(year, 10, 5)); //Implantação da república (PT)
result.Add(new DateTime(year, 11, 1)); //All Saints' Day
result.Add(new DateTime(year, 12, 1)); //Restauração da independência (PT)
result.Add(new DateTime(year, 12, 8)); //Imaculada Conceição (PT?)
result.Add(new DateTime(year, 12, 25)); //Christmas
foreach (DateTime holiday in variable)
{
if (!result.Contains(holiday)) //if the holiday already exists then don't add
{
result.Add(holiday);
}
}
return result;
}
And my function to calculate Working days is this one I found here in StackOverFlow:
public static int BusinessDaysUntil(this DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays)
{
firstDay = firstDay.Date;
lastDay = lastDay.Date;
if (firstDay > lastDay)
throw new ArgumentException("Incorrect last day " + lastDay);
TimeSpan span = lastDay - firstDay;
int businessDays = span.Days + 1;
int fullWeekCount = businessDays / 7;
// find out if there are weekends during the time exceedng the full weeks
if (businessDays > fullWeekCount * 7)
{
// we are here to find out if there is a 1-day or 2-days weekend
// in the time interval remaining after subtracting the complete weeks
int firstDayOfWeek = (int)firstDay.DayOfWeek;
int lastDayOfWeek = (int)lastDay.DayOfWeek;
if (lastDayOfWeek < firstDayOfWeek)
lastDayOfWeek += 7;
if (firstDayOfWeek <= 6)
{
if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval
businessDays -= 2;
else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval
businessDays -= 1;
}
else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval
businessDays -= 1;
}
// subtract the weekends during the full weeks in the interval
businessDays -= fullWeekCount + fullWeekCount;
// subtract the number of bank holidays during the time interval
foreach (DateTime bankHoliday in bankHolidays)
{
DateTime bh = bankHoliday.Date;
if (firstDay <= bh && bh <= lastDay)
--businessDays;
}
return businessDays;
}
But, this code uses the DateTime[] bankHolidays and I need to change it to take my holidays into account (GetHolidays()).
Can you please help me changing the code?
Thanks

As far as I can see, it's a date (e.g. 1st of May) that matter, not year, which we ignore (set to 1):
private static List<DateTime> PortugueseHolidays = new List<DateTime>() {
new DateTime(1, 1, 1)), //New Year Day
new DateTime(1, 4, 25), //Dia da Liberdade (PT)
new DateTime(1, 5, 1), //Labour Day
new DateTime(1, 6, 10), //Dia de Portugal (PT)
new DateTime(1, 8, 15), //Assumption of Mary
new DateTime(1, 10, 5), //Implantação da república (PT)
new DateTime(1, 11, 1), //All Saints' Day
new DateTime(1, 12, 1), //Restauração da independência (PT)
new DateTime(1, 12, 8), //Imaculada Conceição (PT?)
new DateTime(1, 12, 25), //Christmas
};
Test for a single date
private static Boolean IsHoliday(DateTime value, IEnumerable<DateTime> holidays = null) {
if (null == holidays)
holidays = PortugueseHolidays;
return (value.DayOfWeek == DayOfWeek.Sunday) ||
(value.DayOfWeek == DayOfWeek.Saturday) ||
holidays.Any(holiday => holiday.Day == value.Day &&
holiday.Month == value.Month);
}
And for the range (fromDate included, toDate excluded)
public static int BusinessDaysUntil(this DateTime fromDate,
DateTime toDate,
IEnumerable<DateTime> holidays = null) {
int result = 0;
for (DateTime date = fromDate.Date; date < toDate.Date; date = date.AddDays(1))
if (!IsHoliday(date, holidays))
result += 1;
return result;
}

Your goal is converting your List to an Array. Change your GetHolidays function to this:
public static DateTime[] GetHolidays(int year)
{
List<DateTime> result = new List<DateTime>();
result.Add(new DateTime(year, 1, 1)); //New Year Day
result.Add(new DateTime(year, 4, 25)); //Dia da Liberdade (PT)
result.Add(new DateTime(year, 5, 1)); //Labour Day
result.Add(new DateTime(year, 6, 10)); //Dia de Portugal (PT)
result.Add(new DateTime(year, 8, 15)); //Assumption of Mary
result.Add(new DateTime(year, 10, 5)); //Implantação da república (PT)
result.Add(new DateTime(year, 11, 1)); //All Saints' Day
result.Add(new DateTime(year, 12, 1)); //Restauração da independência (PT)
result.Add(new DateTime(year, 12, 8)); //Imaculada Conceição (PT?)
result.Add(new DateTime(year, 12, 25)); //Christmas
foreach (DateTime holiday in variable)
{
if (!result.Contains(holiday)) //if the holiday already exists then don't add
{
result.Add(holiday);
}
}
return result.ToArray<DateTime>();
}
Call the BusinessDaysUntil function using GetHolidays() as last parameter.

Related

C# Working days calculation excluding Bank holidays

I have a calculations for checking working days but it's not giving me the right value that I want. Its for annual leave days calculation. So if I input 27 Feb 2017 as start and 03 Mar 2017 as end date it should give me 5 working days holiday but currently it gives me 3. I'm not sure where I went wrong with my calculation. Help is much appreciated thank you.
DateTime firstDay = Convert.ToDateTime(StartDate);
DateTime lastDay = DateTime.Now;
var bankHolidays = new []
{
new DateTime(2017, 1, 3), // New Year's Day
new DateTime(2017, 1, 2), // New Year's Day
new DateTime(2017, 4, 14), // Good Friday
new DateTime(2017, 4, 17), // Easter Monday
new DateTime(2017, 5, 1), // Early May Bank Holiday
new DateTime(2017, 5, 29), // Spring Bank Holiday
new DateTime(2017, 8, 28), // Summer Bank Holiday
new DateTime(2017, 12, 25), // Christman Day
new DateTime(2017, 12, 26), // Boxing Day
new DateTime(2017, 11, 30), // St Andrews Day
new DateTime(2017, 3, 17), // St Patricks day
new DateTime(2017, 7, 12), // Battle of the Boyne
new DateTime(2016, 1, 1), // New Year's Day
new DateTime(2016, 3, 25), // Good Friday
new DateTime(2016, 3, 28), // Easter Monday
new DateTime(2016, 5, 2), // Early May Bank Holiday
new DateTime(2016, 5, 30), // Spring Bank Holiday
new DateTime(2016, 8, 29), // Summer Bank Holiday,
new DateTime(2016, 12, 26), // Christmas Day
new DateTime(2016, 12, 27), // Boxing Day
new DateTime(2016, 8, 1), // Summer Bank Holiday Scotland,
new DateTime(2016, 11, 30), // St Andrew’s Day
new DateTime(2016, 3, 17), // St Patrick’s Day
new DateTime(2016, 7, 12), // Battle of the Boyne (Orangemen’s Day)
new DateTime(2016, 1, 4), // 2nd January (substitute day)
new DateTime(2015, 12, 25), // Christmas 2015
new DateTime(2015, 12, 28) // Boxing day (substitute day)
}; // I declared all the holidays during year 2017
int Holidays = 0;
if(StartDate == null)
{
return 0;
}
else if(StartDate == EndDate)
{
return 0;
}
else if(!EndDate.HasValue)
{
firstDay = firstDay.AddDays(1).Date;
lastDay = lastDay.AddDays(0).Date;
for (var currentDate = firstDay; currentDate <= lastDay; currentDate = currentDate.AddDays(1)) {
if (currentDate.DayOfWeek != DayOfWeek.Saturday && currentDate.DayOfWeek != DayOfWeek.Sunday && Array.Exists(bankHolidays, o => o.Date == currentDate.Date))
{
Holidays++;
}
}
TimeSpan span = lastDay - firstDay;
int businessDays = span.Days + 1;
int fullWeekCount = businessDays / 7;
if (businessDays > fullWeekCount * 7)
{
int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)lastDay.DayOfWeek;
if (lastDayOfWeek < firstDayOfWeek)
lastDayOfWeek += 7;
if (firstDayOfWeek <= 6)
{
if (lastDayOfWeek >= 7)
businessDays -= 2;
else if (lastDayOfWeek >= 6)
businessDays -= 1;
}
else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)
businessDays -= 1;
}
businessDays = businessDays -1;
businessDays = businessDays - Holidays;
return businessDays -= fullWeekCount + fullWeekCount;
}
else
{
lastDay = Convert.ToDateTime(EndDate);
for (var currentDate = firstDay; currentDate <= lastDay; currentDate = currentDate.AddDays(1)) {
if (currentDate.DayOfWeek != DayOfWeek.Saturday && currentDate.DayOfWeek != DayOfWeek.Sunday && Array.Exists(bankHolidays, o => o.Date == currentDate.Date))
{
Holidays++;
}
}
firstDay = firstDay.AddDays(1).Date;
lastDay = lastDay.AddDays(0).Date;
TimeSpan span = lastDay - firstDay;
int businessDays = span.Days + 1;
int fullWeekCount = businessDays / 7;
if (businessDays > fullWeekCount * 7)
{
int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday
? 7 : (int)lastDay.DayOfWeek;
if (lastDayOfWeek < firstDayOfWeek)
lastDayOfWeek += 7;
if (firstDayOfWeek <= 6)
{
if (lastDayOfWeek >= 7)
businessDays -= 2;
else if (lastDayOfWeek >= 6)
businessDays -= 1;
}
else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)
businessDays -= 1;
}
businessDays = businessDays -1;
businessDays = businessDays - Holidays;
return businessDays -= fullWeekCount + fullWeekCount;
}
Try this approach:
DateTime start = new DateTime(2017, 01, 01);
DateTime end = new DateTime(2017, 01, 10);
TimeSpan ts = end - start;
List<DateTime> workingDays = new List<DateTime>();
for(DateTime dt = start; dt <= end; dt = dt.AddDays(1))
{
if(dt.DayOfWeek != DayOfWeek.Saturday && dt.DayOfWeek != DayOfWeek.Sunday)
workingDays.Add(dt);
}
int holidays = workingDays.Intersect(bankHolidays).Count();
int result = workingDays.Count - holidays;

Count number of including weeks between 2 dates

I need to get the number of weeks between 2 dates.
A week for me is monday to sunday.
So if the first date is on a saturday than this week should be included.
if the second date is on a monday than this week should be included.
What is the most efficient way to do this ?
example :
startdate enddate nbr of weeks
17/09/2016 26/09/2016 3 weeks
17/09/2016 25/09/2016 2 weeks
19/09/2016 26/09/2016 2 weeks
12/09/2016 25/09/2016 2 weeks
I found much answers for this, like this one for example how to calculate number of weeks given 2 dates? but they all end up with dividing the days with 7 and that does not gives the result I need.
The simplest way is probably to write a method to get the start of a week. Then you can subtract one date from another, divide the number of days by 7 and add 1 (to make it inclusive).
Personally I'd use Noda Time for all of this, but using DateTime:
// Always uses Monday-to-Sunday weeks
public static DateTime GetStartOfWeek(DateTime input)
{
// Using +6 here leaves Monday as 0, Tuesday as 1 etc.
int dayOfWeek = (((int) input.DayOfWeek) + 6) % 7;
return input.Date.AddDays(-dayOfWeek);
}
public static int GetWeeks(DateTime start, DateTime end)
{
start = GetStartOfWeek(start);
end = GetStartOfWeek(end);
int days = (int) (end - start).TotalDays;
return (days / 7) + 1; // Adding 1 to be inclusive
}
Complete example:
using System;
class Program
{
static void Main (string[] args)
{
ShowWeeks(new DateTime(2016, 9, 17), new DateTime(2016, 9, 26));
ShowWeeks(new DateTime(2016, 9, 17), new DateTime(2016, 9, 25));
ShowWeeks(new DateTime(2016, 9, 19), new DateTime(2016, 9, 26));
ShowWeeks(new DateTime(2016, 9, 12), new DateTime(2016, 9, 25));
}
static void ShowWeeks(DateTime start, DateTime end)
{
int weeks = GetWeeks(start, end);
Console.WriteLine($"{start:d} {end:d} {weeks}");
}
// Always uses Monday-to-Sunday weeks
public static DateTime GetStartOfWeek(DateTime input)
{
// Using +6 here leaves Monday as 0, Tuesday as 1 etc.
int dayOfWeek = (((int) input.DayOfWeek) + 6) % 7;
return input.Date.AddDays(-dayOfWeek);
}
public static int GetWeeks(DateTime start, DateTime end)
{
start = GetStartOfWeek(start);
end = GetStartOfWeek(end);
int days = (int) (end - start).TotalDays;
return (days / 7) + 1; // Adding 1 to be inclusive
}
}
Output (in my UK locale):
17/09/2016 26/09/2016 3
17/09/2016 25/09/2016 2
19/09/2016 26/09/2016 2
12/09/2016 25/09/2016 2
A bit "simplified", because the Monday is needed just for the start date:
static int weeks(DateTime d1, DateTime d2) {
var daysSinceMonday = ((int)d1.DayOfWeek + 6) % 7;
return ((d2 - d1).Days + daysSinceMonday) / 7 + 1;
}
See my method below, I "strechted" the weeks to monday untill sunday, and then calculated the total days / 7
public static void Main(string[] args)
{
Console.WriteLine(CalculateWeeks(new DateTime(2016, 9, 17), new DateTime(2016, 9, 26)));
Console.WriteLine(CalculateWeeks(new DateTime(2016, 9, 17), new DateTime(2016, 9, 25)));
Console.WriteLine(CalculateWeeks(new DateTime(2016, 9, 19), new DateTime(2016, 9, 26)));
Console.WriteLine(CalculateWeeks(new DateTime(2016, 9, 12), new DateTime(2016, 9, 25)));
}
public static double CalculateWeeks(DateTime from, DateTime to)
{
if (to.DayOfWeek != DayOfWeek.Sunday)
to = to.Add(new TimeSpan(7- (int) to.DayOfWeek, 0, 0, 0)).Date;
return Math.Ceiling((to - from.Subtract(new TimeSpan((int)from.DayOfWeek - 1, 0, 0, 0)).Date).TotalDays / 7);
}

Get x'th last working day (which is not a holiday) in month

How can i get for example 4th last working day (which is not a holiday) from a month ?
In August its 26.8.2015 day (Wednesday)
In September its 25.9.2015 day (Friday)
My actual code is following (i get an inspiration from here)
but i have an recursion and also it has syntax error.
static bool getLastXthWorkingDay(int x)
{
var weekends = new DayOfWeek[] { DayOfWeek.Saturday, DayOfWeek.Sunday };
int month = DateTime.Now.Month;
int year = DateTime.Now.Year;
IFormatProvider culture = new System.Globalization.CultureInfo("cs-CZ", true);
var holidays = new DateTime[] {
Convert.ToDateTime(String.Format("1.1.{0}",year),culture),
Convert.ToDateTime(String.Format("6.4.{0}",year),culture),
Convert.ToDateTime(String.Format("1.5.{0}",year),culture),
Convert.ToDateTime(String.Format("8.5.{0}",year),culture),
Convert.ToDateTime(String.Format("5.7.{0}",year),culture),
Convert.ToDateTime(String.Format("6.7.{0}",year),culture),
Convert.ToDateTime(String.Format("28.9.{0}",year),culture),
Convert.ToDateTime(String.Format("28.10.{0}",year),culture),
Convert.ToDateTime(String.Format("17.11.{0}",year),culture),
Convert.ToDateTime(String.Format("24.12.{0}",year),culture),
Convert.ToDateTime(String.Format("25.12.{0}",year),culture),
Convert.ToDateTime(String.Format("26.12.{0}",year),culture)
};
//Fetch the amount of days in your given month.
int daysInMonth = DateTime.DaysInMonth(year, month);
//Here we create an enumerable from 1 to daysInMonth,
//and ask whether the DateTime object we create belongs to a weekend day,
//if it doesn't, add it to our IEnumerable<int> collection of days.
IEnumerable<int> businessDaysInMonth = Enumerable.Range(1, daysInMonth)
.Where(d => !weekends.Contains(new DateTime(year, month, d).DayOfWeek));
var lastXthWorkingDay = businessDaysInMonth.Skip(Math.Max(0, businessDaysInMonth.Count() - x));
// This code has syntax error ") expected" but i dont see where ?
//if holidays.Contains(Convert.ToDateTime(String.Format("{0},{1},{2}", lastXthWorkingDay, month, year),culture))
//{
// getLastXthWorkingDay(x-1);
//}
//else { return true; }
}
any idea ?
Ok i think i got it. Thank you all
static int GetLastXthWorkingDay(int year, int month, int x)
{
var weekends = new DayOfWeek[] { DayOfWeek.Saturday, DayOfWeek.Sunday };
var holidays = new DateTime[] {
new DateTime(year, 4, 6),
new DateTime(year, 5, 1),
new DateTime(year, 5, 8),
new DateTime(year, 7, 5),
new DateTime(year, 7, 6),
new DateTime(year, 9, 28),
new DateTime(year, 10, 28),
new DateTime(year, 11, 17),
new DateTime(year, 12, 24),
new DateTime(year, 12, 25),
new DateTime(year, 12, 26),
};
//Fetch the amount of days in your given month.
int daysInMonth = DateTime.DaysInMonth(year, month);
//Here we create an enumerable from 1 to daysInMonth,
//and ask whether the DateTime object we create belongs to a weekend day,
//if it doesn't, add it to our IEnumerable<int> collection of days.
IEnumerable<int> businessDaysInMonth = Enumerable.Range(1, daysInMonth)
.Where(d => !weekends.Contains(new DateTime(year, month, d).DayOfWeek));
var lastXthWorkingDay = businessDaysInMonth.Skip(Math.Max(0, businessDaysInMonth.Count() - x));
foreach (var day in lastXthWorkingDay)
{
if ( holidays.Contains(new DateTime(year, month, day)) )
{
return GetLastXthWorkingDay(year, month, x + 1);
//return day-1;
}
else { return day; }
}
return 0;
}

How Can I Only Work Hours DateTime and Time Addition

My code below.I want to do user select 10.06.2015 09:00 - 12.06.2015 13:00 after I will show 2 days 2 hours.
But I want to do Working days and Working Hours beetween 09:00 - 18:00 well users when you 10.06.2015 09:00 - 12.06.2015 13:00 I want to show only 2,5 days.
How can I do?
DateTime t1 = dateTimePicker1.Value.Date;
DateTime t2 = dateTimePicker2.Value.Date;
string s1 = textBox9.Text;
string s2 = textBox10.Text;
DateTime dt1 = t1.AddMinutes(DateTime.Parse(s1).TimeOfDay.TotalMinutes);
DateTime dt2 = t2.AddMinutes(DateTime.Parse(s2).TimeOfDay.TotalMinutes);
var fark = dt2 - dt1;
label1.Text =
String.Format("{0}{1}{2}",
fark.Days > 0 ? string.Format("{0} gün", fark.Days) : "",
fark.Hours > 0 ? string.Format("{0} saat ", fark.Hours) : "",
fark.Minutes > 0 ? string.Format("{0} dakika ", fark.Minutes) : "").Trim();
Well you can assume that any days in the range, except the first and last are full working days. So you need (AllDaysInRange -2) + HoursInfirstDay + HoursInLastDay.
TimeSpan ts = t2 - t1;
ts.Days = ts.Days - 2; //Allow for the 2 end days
int Day1Hours = t1.Hours - 9;//This removes any hours between 00.00 and 09.00
if (day1Hours > 9) //Then the user was working past 18.00
ts.Days = ts.Days+1
else
ts.Hours = ts.Hours + day1Hours;
int Day2Hours = t2.Hours - 9;//This removes any hours between 00.00 and 09.00
if (day2Hours > 9) //Then the user was working past 18.00
ts.Days = ts.Days+1
else
ts.Hours = ts.Hours + day2Hours;
If you can get this to work (I have written it from memory), then I'd wrap the code to convert the hours of the end days into a method rather than repeating it.
According to DateTime Picker In WinForm How To Pick Time? post, you can change your DateTimePicker, to function with times as well.
To limit the range which the users can select, you can modify your ValueChanged event or write your own validation for it.
Probably the simplest is:
private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
{
if (dateTimePicker1.Value.Hour < 10) // the 10 is just a random number, you can change it to your own limit
dateTimePicker1.Value = this.dateTimePicker1.Value.AddHours(10 - dateTimePicker1.Value.Hour);
}
To calculate your 2,5 day according to the working hours, i would write a function to handle this responsibility like:
private int TimeInWorkingHours(DateTime start, DateTime end, int firstHour, int lastHour)
{
int days = Math.Min(end.Subtract(start).Days - 2, 0) ;
int hoursInADay = lastHour - firstHour;
int result = days * hoursInADay;
result += start.Hour - firstHour;
result += lastHour - end.Hour;
return result;
}
This way you call TimeInWorkingHours(...) function with your start date and end date, also providing your first and last working hours.
First you calculate the worked days, than add the border hours. This way you get your worked hours which you can then divide by the work hours to get your working days.
try this
private void GetProperOfficeHours(ref DateTime date)
{
int minHour = 9, maxHour = 17;
if (date.Hour < minHour) //if earlier than office hours - start from 9am
{
date = date + new TimeSpan(9, 0, 0);
}
else if (date.Hour > maxHour) //if later than office hours - go to next day 9am
{
date = date.AddDays(1) + new TimeSpan(9, 0, 0);
}
}
Then ...
//assuming firstDate & lastDate have date and time
int[] weekendDays = new int[2] { 0, 6 }; // Sunday and Saturday
GetProperOfficeHours(ref firstDate);
GetProperOfficeHours(ref lastDate);
while (weekendDays.Contains((int)firstDate.DayOfWeek))
{
//get next date
firstDate = firstDate.AddDays(1);
}
while (weekendDays.Contains((int)lastDate.DayOfWeek))
{
//get prev date
lastDate = lastDate.AddDays(-1);
}
double hourDiff = Math.Abs(firstDate.Hour - lastDate.Hour) / 8.0; //8 office hours
double dayDifference = 0;
while (firstDate.Date <= lastDate.Date) //Loop and skip weekends
{
if (!weekendDays.Contains((int)firstDate.DayOfWeek)) //can also check for holidays here
dayDifference++;
firstDate = firstDate.AddDays(1);
}
dayDifference = dayDifference + hourDiff;
May need some tweaking, hope you find it helpful.
Try this
bool IsWorkingDay(DateTime dt)
{
int year = dt.Year;
Dictionary<DateTime, object> holidays = new Dictionary<DateTime, object>();
holidays.Add(new DateTime(year, 1, 1), null);
holidays.Add(new DateTime(year, 1, 6), null);
holidays.Add(new DateTime(year, 4, 25), null);
holidays.Add(new DateTime(year, 5, 1), null);
holidays.Add(new DateTime(year, 6, 2), null);
holidays.Add(new DateTime(year, 8, 15), null);
holidays.Add(new DateTime(year, 11, 1), null);
holidays.Add(new DateTime(year, 12, 8), null);
holidays.Add(new DateTime(year, 12, 25), null);
holidays.Add(new DateTime(year, 12, 26), null);
DateTime easterMonday = EasterSunday(year).AddDays(1);
if (!holidays.ContainsKey(easterMonday))
holidays.Add(easterMonday, null);
if (!holidays.ContainsKey(dt.Date))
if (dt.DayOfWeek > DayOfWeek.Sunday && dt.DayOfWeek < DayOfWeek.Saturday)
return true;
return false;
}
string WorkingTime(DateTime dt1, DateTime dt2)
{
// Adjust begin datetime
if (IsWorkingDay(dt1))
{
if (dt1.TimeOfDay < TimeSpan.FromHours(9))
dt1 = new DateTime(dt1.Year, dt1.Month, dt1.Day, 9, 0, 0);
else if (dt1.TimeOfDay > TimeSpan.FromHours(13) && dt1.TimeOfDay < TimeSpan.FromHours(14))
dt1 = new DateTime(dt1.Year, dt1.Month, dt1.Day, 14, 0, 0);
else if (dt1.TimeOfDay > TimeSpan.FromHours(18))
dt1 = new DateTime(dt1.Year, dt1.Month, dt1.Day, 9, 0, 0).AddDays(1);
}
else
dt1 = new DateTime(dt1.Year, dt1.Month, dt1.Day, 9, 0, 0).AddDays(1);
// Adjust end datetime
if (IsWorkingDay(dt2))
{
if (dt2.TimeOfDay < TimeSpan.FromHours(9))
dt2 = new DateTime(dt2.Year, dt2.Month, dt2.Day, 18, 0, 0).AddDays(-1);
else if (dt2.TimeOfDay > TimeSpan.FromHours(18))
dt2 = new DateTime(dt2.Year, dt2.Month, dt2.Day, 18, 0, 0);
else if (dt2.TimeOfDay > TimeSpan.FromHours(13) && dt2.TimeOfDay < TimeSpan.FromHours(14))
dt2 = new DateTime(dt2.Year, dt2.Month, dt2.Day, 13, 0, 0);
}
else
dt2 = new DateTime(dt2.Year, dt2.Month, dt2.Day, 18, 0, 0).AddDays(-1);
double days = 0;
double hours = 0;
double minutes = 0;
if (dt2 > dt1)
{
// Move dt1 forward to reach dt2 day chacking for working days
while (dt1.DayOfYear < dt2.DayOfYear)
{
if (IsWorkingDay(dt1))
days++;
dt1 = dt1.AddDays(1);
}
// Now get the worked hours as if were on the same day in the same manner
TimeSpan sdwt = dt2 - dt1;
if (dt1.TimeOfDay < TimeSpan.FromHours(13) && dt2.TimeOfDay > TimeSpan.FromHours(14))
sdwt -= TimeSpan.FromHours(1);
if (sdwt == TimeSpan.FromHours(8))
days++;
else
{
hours = sdwt.Hours;
minutes = sdwt.Minutes;
}
}
// There is a pause in between so adjust if the interval include it
var totalminutes = (days * 8 * 60 + hours * 60 + minutes);
string res = String.Format("{0} days {1} hours {2} minutes",
days,
hours,
minutes);
string totRes = String.Format("{0} days {1} hours {2} minutes",
totalminutes / 8 / 60,
totalminutes / 8,
totalminutes);
return res + "\r\n" + totRes;
}

Get next date within a quarter

I need to create a function to return the next processing date for a given item. An item has a number that represents the month within a quarter that it is processed, as well as a number that represents the week within that month when it is processed. So, given a particular item's create date, I need to get the next processing date for that item, which will be the first day of it's assigned week and month within a quarter.
Note that weeks are broken out by 7 days from the start of the month, regardless of what day of the week. So the first day of the first week could start on Tuesday or any other day for the purposes of this calculation.
Example:
Let's say I have an item with a completed date of 1/8/2010. That item has a monthWithinQuarter value of 2. It has a weekWithinMonth value of 3. So for this item that resolves to the third week of February, so I would want the function to return a date of 2/15/2010.
The function should look something like this:
var nextProcessingDate = GetNextProcessingDate(
itemCompletedDate,
monthWithinQuarter,
weekWithinMonth);
This calculation has to be pretty fast as this calculation is going to be happening a lot, both in real time to display on a web site as well as in batch mode when processing items.
Thanks,
~ Justin
Okay, this should do it for you:
static DateTime GetNextProcessingDate(
DateTime itemCompletedDate,
int monthWithinQuarter,
int weekWithinMonth
) {
if (monthWithinQuarter < 1 || monthWithinQuarter > 3) {
throw new ArgumentOutOfRangeException("monthWithinQuarter");
}
if (weekWithinMonth < 1 || weekWithinMonth > 5) {
throw new ArgumentOutOfRangeException("weekWithinMonth");
}
int year = itemCompletedDate.Year;
DateTime[] startOfQuarters = new[] {
new DateTime(year, 1, 1),
new DateTime(year, 4, 1),
new DateTime(year, 7, 1),
new DateTime(year, 10, 1)
};
DateTime startOfQuarter = startOfQuarters.Where(d => d <= itemCompletedDate)
.OrderBy(d => d)
.Last();
int month = startOfQuarter.Month + monthWithinQuarter - 1;
int day = (weekWithinMonth - 1) * 7 + 1;
if (day > DateTime.DaysInMonth(year, month)) {
throw new ArgumentOutOfRangeException("weekWithinMonth");
}
DateTime candidate = new DateTime(year, month, day);
if (candidate < itemCompletedDate) {
month += 3;
if(month > 12) {
year++;
month -= 12;
}
}
return new DateTime(year, month, day);
}
As far as efficiency, the place where I see the most room for improvement is repeatedly creating the array
DateTime[] startOfQuarters = new[] {
new DateTime(year, 1, 1),
new DateTime(year, 4, 1),
new DateTime(year, 7, 1),
new DateTime(year, 10, 1)
};
So let's offload that to a method and memoize it:
static Dictionary<int, DateTime[]> cache = new Dictionary<int, DateTime[]>();
public static DateTime[] StartOfQuarters(DateTime date) {
int year = date.Year;
DateTime[] startOfQuarters;
if(!cache.TryGetValue(year, out startOfQuarters)) {
startOfQuarters = new[] {
new DateTime(year, 1, 1),
new DateTime(year, 4, 1),
new DateTime(year, 7, 1),
new DateTime(year, 10, 1)
};
cache.Add(year, startOfQuarters);
}
return startOfQuarters;
}
If you don't need the flexibility of quarters possibly starting on unusual days, you could replace
DateTime[] startOfQuarters = new[] {
new DateTime(year, 1, 1),
new DateTime(year, 4, 1),
new DateTime(year, 7, 1),
new DateTime(year, 10, 1)
};
DateTime startOfQuarter = startOfQuarters.Where(d => d <= itemCompletedDate).OrderBy(d => d).Last();
int month = startOfQuarter.Month + monthWithinQuarter - 1;
with
int month = 3 * ((itemCompletedDate.Month - 1) / 3) + monthWithinQuarter;
From what I understand, this should do the job:
public static DateTime GetNextProcessingDate(DateTime itemCreationDate, int monthWithinQuarter,
int weekWithinMonth)
{
var quarter = (itemCreationDate.Month - 1) / 4; // Assumes quarters are divided by calendar year.
var month = quarter * 4 + monthWithinQuarter; // First quarter of month plus month within quarter
var dayInMonth = (weekWithinMonth - 1) * 7 + 1; // Weeks are counted from first day, regardless of day of week (as you mention).
return new DateTime(itemCreationDate.Year, month, dayInMonth);
}
Let me know if any of it isn't clear.
Your calculation, I suppose should be reduced at
DateTime dt;
dt.AddDays(daysToAdd);
dt.AddMonths(monthsToAdd);
dt.AddHours(hoursToAdd);
dt.AddYears(yearsToAdd);

Categories

Resources