Get total hours that fall between a specific time period - c#

the condition is Time in and Time out (e.g 02/01/2015 02:55 'til 02/02/2015 05:55) that is more than a day. I already computed the total hours of Time in and Time out, and I want to know if the total hours has passed between 23:00(11:00PM ) up to 06:00AM and get the total of it

var hours = (datevalue1 - datevalue2).TotalHours;
or
Timespace ts= (datevalue1 - datevalue2);
var hours = ts.Value.TotalHours;

Try this way.. DateTime.Parse().Subtract()
eg:
string startTime = "11:00 PM";
string endTime = "6:00 AM";
TimeSpan duration = DateTime.Parse(endTime).Subtract(DateTime.Parse(startTime));
Console.WriteLine(duration);
Console.ReadKey();
OR
TimeSpan is the object you need:
TimeSpan span = (DateTime.Now - DateTime.Now);
String.Format("{0} days, {1} hours, {2} minutes, {3} seconds",
span.Days, span.Hours, span.Minutes, span.Seconds);

You can calculate it by passing over time. when its night time add it to TimeSpan.
DateTime timeIn = new DateTime(2015, 09, 29, 10, 11, 3); // 29-09-2015 at 10:11:03
DateTime timeOut = new DateTime(2015, 10, 1, 2, 19, 18); // 01-10-2015 at 02:19:38
TimeSpan nightTime = new TimeSpan(); //total amount of night time
TimeSpan passLength = new TimeSpan(0, 0, 0, 1); // length of time to pass at each iteration (1s)
while (timeIn < timeOut) // do it until timeIn reaches timeOut
{
timeIn = timeIn.Add(passLength); // add 1 second to timeIn
if (timeIn.Hour < 6 || timeIn.Hour == 23) // if we are in range of night time
{
nightTime = nightTime.Add(passLength); // add 1 second to night time
}
}
Console.WriteLine(nightTime);
You can do lot of optimizations. for long times its not good idea to add 1 sec each time. you can add 1 day to TimeIn at each iterate then add only 6 hours to night time. after you get close to Timeout decrease length time
Here is a better way. first get days fast. then get rest of the time.
DateTime timeIn = new DateTime(2015, 09, 29, 10, 11, 3); // 29-09-2015 at 10:11:03
DateTime timeOut = new DateTime(2015, 10, 1, 2, 19, 18); // 01-10-2015 at 02:19:38
// Get days
TimeSpan passLength = new TimeSpan(1, 0, 0, 0); // one day per iterate
while (timeIn + passLength < timeOut)
{
timeIn = timeIn.Add(passLength);
nightTime = nightTime.Add(new TimeSpan(0, 7, 0, 0)); // 7 hours of a day passed is considered night time
}
// Get rest of the time
passLength = new TimeSpan(0, 0, 0, 1); // one second per iterate
while (timeIn < timeOut) // do it until timeIn reaches timeOut
{
timeIn = timeIn.Add(passLength); // add 1 second to timeIn
if (timeIn.Hour < 6 || timeIn.Hour == 23) // if we are in range of night time
{
nightTime = nightTime.Add(passLength); // add 1 second to night time
}
}
Console.WriteLine(nightTime);
You shouldn't be worry about rest of the time calculation performance. since the rest of the time is now less than 1 day which is only 86400 seconds.
Less than 86400 iterates should be fine for today's processors speed. how ever you can still optimize it farther away but you don't get much more performance.

A little bit different and faster approach:
static void Main(string[] args)
{
TimeSpan result = new TimeSpan();
DateTime dt1 = new DateTime(2015, 09, 29, 10, 11, 03);
DateTime dt2 = new DateTime(2015, 10, 01, 02, 19, 38);
DateTime d1 = new DateTime(dt1.Year, dt1.Month, dt1.Day, 0, 0, 0); //Date only
DateTime d2 = new DateTime(dt2.Year, dt2.Month, dt2.Day, 0, 0, 0); //Date only
//Count night time in first day
result += DateTime.Compare(dt1, d1.AddHours(6)) > 0 ? new TimeSpan(6, 0, 0) : new TimeSpan(dt1.Hour, dt1.Minute, dt1.Second);
if (DateTime.Compare(dt1, d1.AddHours(23)) > 0) result += new TimeSpan(dt1.Hour - 23, dt1.Minute, dt1.Second);
//Count night time in last day
result += DateTime.Compare(dt2, d2.AddHours(6)) > 0 ? new TimeSpan(6, 0, 0) : new TimeSpan(dt2.Hour, dt2.Minute, dt2.Second);
if (DateTime.Compare(dt2, d2.AddHours(23)) > 0) result += new TimeSpan(dt1.Hour - 23, dt2.Minute, dt2.Second);
//Count night time in middle days
int daysBetween = (int)(d2 - d1).TotalDays - 1;
result += new TimeSpan(daysBetween * 7, 0, 0);
Console.WriteLine("Night time: " + result);
Console.ReadKey();
}

Compare EndTime with your Range(23:00-06:00)
that is in your Case, check wether EndTime 05:55 < 06:00 and EndTime 05:55 > 23:00

You can subtract the DateTime values to get the TimeSpan in between. Then you can get the TotalHours in that
var hours = timeOut.Subtract(timeIn).TotalHours;
For example
timeIn = 29-09-2015 10:11:03;
timeOut = 01-10-2015 02:19:38;
hours = 52.14303137125;

Related

Trying to set a datetime start time based off datetime.now

I am trying to set my DateTime start variable to change based on what time it is now. my shifts(different start times) are 1: 6am-2pm 2: 2pm-10pm 3: 10pm-6am. I am having trouble writing the if statement to give the correct time.
The short version. I am trying to write C# if statement to figure out which of the the three start it is currently. Below is the code. above is the start times.
The logic I am trying to achieve is to have the DateTime start variable to be set to one of the 3 date time I have listed. Morning, Afternoon, or Evening. The start variable needs to be change values based on DateTime.Now. The View will be refreshing the page every 15 seconds. That will update the DateTime.Now variable.
Thank you for your time.
DateTime MORNING;
DateTime AFTERNOON;
DateTime EVENING;
if (DateTime.Now.Hour > 6 || (DateTime.Now.Hour == 6 && DateTime.Now.Minute >= 00))
MORNING = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 6, 00, 0);
else
MORNING = new DateTime(DateTime.Now.AddDays(-1).Year, DateTime.Now.AddDays(-1).Month, DateTime.Now.AddDays(-1).Day, 6, 30, 0);
if (DateTime.Now.Hour > 14 || (DateTime.Now.Hour == 14 && DateTime.Now.Minute >= 00))
AFTERNOON = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 14, 00, 0);
else
AFTERNOON = new DateTime(DateTime.Now.AddDays(-1).Year, DateTime.Now.AddDays(-1).Month, DateTime.Now.AddDays(-1).Day, 14, 00, 0);
if (DateTime.Now.Hour > 20 || (DateTime.Now.Hour == 20 && DateTime.Now.Minute >= 00))
EVENING = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 20, 00, 0);
else
EVENING = new DateTime(DateTime.Now.AddDays(-1).Year, DateTime.Now.AddDays(-1).Month, DateTime.Now.AddDays(-1).Day, 20, 00, 0);
DateTime start = ((EVENING > MORNING) ? EVENING : MORNING);
DateTime end = start.AddHours(8);
I'm not sure whether you need the MORNING, AFTERNOON, or EVENING variables elsewhere in your code, but they are not needed for this; I would remove them from my sample unless you need them in other locations. If you do need those variables, you can replace my start sets to new DateTime objects with the appropriate variable, but I wanted to demonstrate that the extra variables are not at all required for this. The following should work:
DateTime now = DateTime.Now;
DateTime MORNING = new DateTime(now.Year, now.Month, now.Day, 6, 0, 0);
DateTime AFTERNOON = MORNING.AddHours(8);
DateTime EVENING = AFTERNOON.AddHours(8);
DateTime start;
// Note that hour 22 is 10PM, not hour 20 as in your example
if (now.Hour >= 22 || now.Hour < 6)
{
// Evening
start = new DateTime(now.Year, now.Month, now.Day, 22, 0, 0);
AFTERNOON -= TimeSpan.FromDays(1);
MORNING -= TimeSpan.FromDays(1);
}
else if (now.Hour >= 14)
{
// Afternoon
start = new DateTime(now.Year, now.Month, now.Day, 14, 0, 0);
EVENING -= TimeSpan.FromDays(1);
MORNING -= TimeSpan.FromDays(1);
}
else
{
// Morning
start = new DateTime(now.Year, now.Month, now.Day, 6, 0, 0);
EVENING -= TimeSpan.FromDays(1);
AFTERNOON -= TimeSpan.FromDays(1);
}
DateTime end = start.AddHours(8);

How to get interval between hours and put into list from start into finish?

For example, I will be given a time on hours with type DateTime hours like this
for the starter
my starttime is 00:00
endtime is 02:00
and every time 30 minutes I like to input the value into a List<DateTime>
so, how can I get the value to put into a list that is look like this?
00:00
00:30
01:00
01:30
02:00
My Code
DateTime starTime = new DateTime();
DateTime endTimes = new DateTime();
DateTime interval = new DateTime();
List<DateTime> intervals = new List<DateTime>();
starTime = DateTime.ParseExact(fulldate + "00:00",
"yyyy/MM/dd HH:mm",
CultureInfo.InvariantCulture);
endTimes = DateTime.ParseExact(fulldate + "02:00",
"yyyy/MM/dd HH:mm",
CultureInfo.InvariantCulture); ;
interval = starTime;
for (int i = 0; i < 24; i++)
{
interval.AddHours(0.5);
intervals.Add(interval);
if (interval.ToString("HH:mm") == endTimes.ToString("HH:mm"))
{
break;
}
}
Can anyone help me to solve this?
With some assumption (that end time is on the same day, that your end time is always something that can be devided by 30 mins, ...) this would work.
var start = new TimeSpan(0, 0, 0);
var end = new TimeSpan(2, 0, 0);
var current = start;
List<DateTime> values = new List<DateTime>();
var startDate = DateTime.Now.Date; // editited after #pinkflowydx33's comment
values.Add(startDate + start);
while (current < end)
{
current = current.Add(new TimeSpan(0, 30, 0));
values.Add(startDate + current);
}
foreach (var v in values)
{
Console.WriteLine(v);
}
i prepared that type of solution. - It's loop over number, which represent - times of valueToChange - in this specific case between 30 minutes - and add to the startDate - 30 minutes and also saving to list.
using System;
public class Program
{
public static void Main()
{
List<DateTime> intervals = new List<DateTime>();
var changeValue = 30;
var startDate = new DateTime(2010, 05, 12, 13, 00, 00);
var endDate = new DateTime(2010, 05, 12, 14, 00, 00);
var timeIntervals = System.Math.Abs(startDate.Subtract(endDate).TotalMinutes / changeValue);
for (int i = 0; i < timeIntervals; i++)
{
startDate.AddMinutes(30);
intervals.Add(startDate)
}
}
}
In this case the start and end date are divided by 30 minutes without rest - so if there will be 13:00 and 13:12 - it's doesn't add the value to List - cause the value doesn't > 30.

Counting hours from a valid date

I have a small problem because I do not always understand how to use the lessons of the day, for example, I want the time from the list to have the day time recalculated from that date, but if I have a new time, that conversion counts from the new time. It works fine for me if I only have one time, but if I have two times, foreach the loop calculates me both times for the day.
This is my code:
public TimeSpan GetHoursForDay(DateTime day) {
TimeSpan time = TimeSpan.Zero;
foreach (var times in shouldWorkTime)
{
if (times.Valid_from > day) //here's the real problem for me, do i want the hours to count from that date, for example: for 1.1.2020 it doesn't need to take hours from 1.12.2019
continue;
if (day.DayOfWeek == DayOfWeek.Monday)
{
time += times.ShouldWorkMonday;
}
if (day.DayOfWeek == DayOfWeek.Tuesday)
{
time += times.ShouldWorkTuesday;
}
if (day.DayOfWeek == DayOfWeek.Wednesday)
{
time += times.ShouldWorkWednesday;
}
if (day.DayOfWeek == DayOfWeek.Thursday)
{
time += times.ShouldWorkThursday;
}
if (day.DayOfWeek == DayOfWeek.Friday)
{
time += times.ShouldWorkFriday;
}
if (day.DayOfWeek == DayOfWeek.Saturday)
{
time += times.ShouldWorkSaturday;
}
if (day.DayOfWeek == DayOfWeek.Sunday)
{
time += times.ShouldWorkSunday;
}
}
return time;
}
}
These are the values I get in the list:
var shouldWorkTime = new List<ShouldWorkTime>
{
new ShouldWorkTime
{
Valid_from = new DateTime(2019, 12, 01, 0, 0, 0),
ShouldWorkMonday = new TimeSpan(8,0,0),
ShouldWorkTuesday= new TimeSpan(7,0,0),
ShouldWorkWednesday= new TimeSpan(6,0,0),
ShouldWorkThursday= new TimeSpan(5,0,0),
ShouldWorkFriday= new TimeSpan(8,0,0),
ShouldWorkSaturday = new TimeSpan(0,0,0),
ShouldWorkSunday = new TimeSpan(0,0,0)
},
new ShouldWorkTime
{
Valid_from = new DateTime(2020, 01, 01, 0, 0, 0),
ShouldWorkMonday = new TimeSpan(4,0,0),
ShouldWorkTuesday= new TimeSpan(3,0,0),
ShouldWorkWednesday= new TimeSpan(6,0,0),
ShouldWorkThursday= new TimeSpan(5,0,0),
ShouldWorkFriday= new TimeSpan(9,0,0),
ShouldWorkSaturday = new TimeSpan(0,0,0),
ShouldWorkSunday = new TimeSpan(0,0,0)
}
};
for the day value, I always get for the current day from the calendar, so I want to be counted in this case for the days of 1.1.2020 values for the days that fall in the second count, and until then the values that fall in the first count.
so i need to return how many hours for a particular day a worker needs to make, but valid from the last date from (Valid_From).
How can I correct this? thank you all very much for your help
eg:
input 1.1.2020 output = 6,0,0;
input 1.12.2019 output = 0,0,0;
Here is a little modification of the Data structure.
Instead of a brunch of property The TimeSpan will be mapped to the DayOfWeek using a Dictionary. This will remove the need for a switch-case or a lot of If.
Using DayOfWeek as dictionary key ensure that only one TimeSpan is defined for a day.
public class WorkingTimeScheldure
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public Dictionary<DayOfWeek, TimeSpan> Scheldure { get; set; }
}
That way I can ask for the TimeSpan of a day using : Scheldure[test.DayOfWeek]
Note the addition of an End property. It may be usefull to select the right thing.
eg: 30/01/2020 is superior to 01/12/2019 and 01/01/2020..
So in order to take the most recent, I assume the list in ordered on Start date and pick the last one: .Last(x => x.Start <= day).
Online Demo
public class Program
{
static List<ShouldWork> WorkTimeScheldure;
public static void Main()
{
WorkTimeScheldure = new List<ShouldWork>
{
new ShouldWork
{
Start = new DateTime(2019, 12, 01, 0, 0, 0),
Scheldure= new Dictionary<DayOfWeek, TimeSpan>()
{
{(DayOfWeek)0, new TimeSpan(0,0,0)},
{(DayOfWeek)1, new TimeSpan(8,0,0)},
{(DayOfWeek)2, new TimeSpan(7,0,0)},
{(DayOfWeek)3, new TimeSpan(6,0,0)},
{(DayOfWeek)4, new TimeSpan(5,0,0)},
{(DayOfWeek)5, new TimeSpan(8,0,0)},
{(DayOfWeek)6, new TimeSpan(0,0,0)}
}
},
new ShouldWork
{
Start = new DateTime(2020, 01, 01, 0, 0, 0),
Scheldure = new Dictionary<DayOfWeek, TimeSpan>()
{
{(DayOfWeek)0, new TimeSpan(0,0,0)},
{(DayOfWeek)1, new TimeSpan(4,0,0)},
{(DayOfWeek)2, new TimeSpan(3,0,0)},
{(DayOfWeek)3, new TimeSpan(6,0,0)},
{(DayOfWeek)4, new TimeSpan(5,0,0)},
{(DayOfWeek)5, new TimeSpan(9,0,0)},
{(DayOfWeek)6, new TimeSpan(0,0,0)}
}
}
};
var testValues = new[] {
new DateTime(2019, 12, 01, 0, 0, 0),
new DateTime(2019, 12, 02, 0, 0, 0),
new DateTime(2019, 12, 03, 0, 0, 0),
new DateTime(2019, 12, 04, 0, 0, 0),
new DateTime(2019, 12, 05, 0, 0, 0),
new DateTime(2019, 12, 06, 0, 0, 0),
new DateTime(2019, 12, 07, 0, 0, 0),
new DateTime(2019, 12, 08, 0, 0, 0),
new DateTime(2020, 01, 01, 0, 0, 0),
new DateTime(2020, 01, 02, 0, 0, 0),
new DateTime(2020, 01, 03, 0, 0, 0),
new DateTime(2020, 01, 05, 0, 0, 0),
new DateTime(2020, 01, 05, 0, 0, 0),
new DateTime(2020, 01, 06, 0, 0, 0),
new DateTime(2020, 01, 07, 0, 0, 0),
new DateTime(2020, 01, 08, 0, 0, 0),
};
foreach (var test in testValues) {
// Perhaps there is many possible, so I took the Last.
var workingTime = WorkTimeScheldure.Last(x => x.Start <= day);
//Please handle the case where there is no matching scheludre for this date.
var houtToWork = workingTime.Scheldure[day.DayOfWeek].Hours;
Console.WriteLine(
$"{day.ToShortDateString()} , it's a {day.DayOfWeek}" +
$" I have to work {houtToWork} Hour{(houtToWork>1?"s":"")}!"
);
}
}
}
Result :
12/01/2019 , it's a Sunday I have to work 0 Hour!
12/02/2019 , it's a Monday I have to work 8 Hours!
12/03/2019 , it's a Tuesday I have to work 7 Hours!
12/04/2019 , it's a Wednesday I have to work 6 Hours!
12/05/2019 , it's a Thursday I have to work 5 Hours!
12/06/2019 , it's a Friday I have to work 8 Hours!
12/07/2019 , it's a Saturday I have to work 0 Hour!
12/08/2019 , it's a Sunday I have to work 0 Hour!
01/01/2020 , it's a Wednesday I have to work 6 Hours!
01/02/2020 , it's a Thursday I have to work 5 Hours!
01/03/2020 , it's a Friday I have to work 9 Hours!
01/04/2020 , it's a Saturday I have to work 0 Hour!
01/05/2020 , it's a Sunday I have to work 0 Hour!
01/06/2020 , it's a Monday I have to work 4 Hours!
01/07/2020 , it's a Tuesday I have to work 3 Hours!
01/08/2020 , it's a Wednesday I have to work 6 Hours!
The test times.Valid_from > day is false for all element in shouldWorkTime if the value of day is big enough. That why time can be incremented multiple time.
If you want to increment only once and on the first/last acceptable value of Valid_from, you should ensure that shouldWorkTime is sorted in increasing/decreasing order and ensure that the increment is done only once.
In fact you didn't need increment but just return the corresponding TimeSpan:
public TimeSpan GetHoursForDay(DateTime day) {
// shouldWorkTime should have been sorted once for all at creation.
// This code use the first acceptable Valid_from
// By using OrderByDescending we take the last (in date) entry
var math = shouldWorkTime
.Where(v => day >= v.Valid_from) // We take only valid entry
.OrderByDescending(v => v.Valid_from) // We sort only on valid entry
.FirstOrDefault(); // we take the last (in date) valid entry
if (match == null)
return TimeSpan.Zero;
switch (day.DayOfWeek)
{
case DayOfWeek.Monday:
return match.ShouldWorkMonday;
case DayOfWeek.Tuesday:
return match.ShouldWorkTuesday;
case DayOfWeek.Wednesday:
return match.ShouldWorkWednesday;
case DayOfWeek.Thursday:
return match.ShouldWorkThursday;
case DayOfWeek.Friday:
return match.ShouldWorkFriday;
case DayOfWeek.Saturday:
return match.ShouldWorkSaturday;
case DayOfWeek.Sunday:
return match.ShouldWorkSunday;
}
}
Edit:
To avoid code duplication, the ShouldWorkTime class may provide a GetWorkTimeForDayOfWeek:
public TimeSpan GetWorkTimeForDayOfWeek(DayOfWeek dayOfWeek) {
...
}
And instead of storing seven values in seven fields, you may take a look to collections. I will choose a Dictionary<DayOfWeek, TimeSpan>.

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;
}

Calculating a date around working days/hours?

I am currently working on a website to track projects. In it, it is possible to create Service Level Agreements (SLAs). These are configurable with days of the week that a project can be worked on and also the timespan on each of those days. Eg. on Monday it might be between 08:00 and 16:00 and then on friday from 10:00 to 14:00. They are also configured with a deadline time depending on priority. Eg. a project created with the "Low" priority has a deadline time of two weeks, and a project with "High" priority has a deadline of four hours.
The problem I'm having is calculating the deadline AROUND the hours described earlier. Say I create a project on Monday at 14:00 with a "High" priority. That means I have four hours for this project. But because of the working hours, I have two hours on monday (untill 16:00) and then another two hours on Friday. That means the Deadline must be set for Friday at 12:00.
I've spent quite some time googling this, and I can find quite a few examples of finding out how many working hours there are between a given start end ending date. I just can't figure out how to convert it into FINDING the ending datetime, given a starting time and an amount of time untill the deadline.
The day/timespans are stored in an sql database in the format:
Day(Eg. 1 for Monday) StartHour EndHour
The StartHour/EndHour are saved as DateTimes, but of course only the time part is important.
The way I figure it is, I have to somehow iterate through these times and do some datetime calculations. I just can't quite figure out what those calculations should be, what the best way is.
I found this Question here on the site as I was writing this. It is sort of what I want and I'm playing with it right now, but I'm still lost on how exactly to make it work around my dynamic work days/hours.
Here's some C# code which might help, it could be much cleaner, but it's a quick first draft.
class Program
{
static void Main(string[] args)
{
// Test
DateTime deadline = DeadlineManager.CalculateDeadline(DateTime.Now, new TimeSpan(4, 0, 0));
Console.WriteLine(deadline);
Console.ReadLine();
}
}
static class DeadlineManager
{
public static DateTime CalculateDeadline(DateTime start, TimeSpan workhours)
{
DateTime current = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, 0);
while(workhours.TotalMinutes > 0)
{
DayOfWeek dayOfWeek = current.DayOfWeek;
Workday workday = Workday.GetWorkday(dayOfWeek);
if(workday == null)
{
DayOfWeek original = dayOfWeek;
while (workday == null)
{
current = current.AddDays(1);
dayOfWeek = current.DayOfWeek;
workday = Workday.GetWorkday(dayOfWeek);
if (dayOfWeek == original)
{
throw new InvalidOperationException("no work days");
}
}
current = current.AddHours(workday.startTime.Hour - current.Hour);
current = current.AddMinutes(workday.startTime.Minute - current.Minute);
}
TimeSpan worked = Workday.WorkHours(workday, current);
if (workhours > worked)
{
workhours = workhours - worked;
// Add one day and reset hour/minutes
current = current.Add(new TimeSpan(1, current.Hour * -1, current.Minute * -1, 0));
}
else
{
current.Add(workhours);
return current;
}
}
return DateTime.MinValue;
}
}
class Workday
{
private static readonly Dictionary<DayOfWeek, Workday> Workdays = new Dictionary<DayOfWeek, Workday>(7);
static Workday()
{
Workdays.Add(DayOfWeek.Monday, new Workday(DayOfWeek.Monday, new DateTime(1, 1, 1, 10, 0, 0), new DateTime(1, 1, 1, 16, 0, 0)));
Workdays.Add(DayOfWeek.Tuesday, new Workday(DayOfWeek.Tuesday, new DateTime(1, 1, 1, 10, 0, 0), new DateTime(1, 1, 1, 16, 0, 0)));
Workdays.Add(DayOfWeek.Wednesday, new Workday(DayOfWeek.Wednesday, new DateTime(1, 1, 1, 10, 0, 0), new DateTime(1, 1, 1, 16, 0, 0)));
Workdays.Add(DayOfWeek.Thursday, new Workday(DayOfWeek.Thursday, new DateTime(1, 1, 1, 10, 0, 0), new DateTime(1, 1, 1, 16, 0, 0)));
Workdays.Add(DayOfWeek.Friday, new Workday(DayOfWeek.Friday, new DateTime(1, 1, 1, 10, 0, 0), new DateTime(1, 1, 1, 14, 0, 0)));
}
public static Workday GetWorkday(DayOfWeek dayofWeek)
{
if (Workdays.ContainsKey(dayofWeek))
{
return Workdays[dayofWeek];
}
else return null;
}
public static TimeSpan WorkHours(Workday workday, DateTime time)
{
DateTime sTime = new DateTime(time.Year, time.Month, time.Day,
workday.startTime.Hour, workday.startTime.Millisecond, workday.startTime.Second);
DateTime eTime = new DateTime(time.Year, time.Month, time.Day,
workday.endTime.Hour, workday.endTime.Millisecond, workday.endTime.Second);
if (sTime < time)
{
sTime = time;
}
TimeSpan span = eTime - sTime;
return span;
}
public static DayOfWeek GetNextWeekday(DayOfWeek dayOfWeek)
{
int i = (dayOfWeek == DayOfWeek.Saturday) ? 0 : ((int)dayOfWeek) + 1;
return (DayOfWeek)i;
}
private Workday(DayOfWeek dayOfWeek, DateTime start, DateTime end)
{
this.dayOfWeek = dayOfWeek;
this.startTime = start;
this.endTime = end;
}
public DayOfWeek dayOfWeek;
public DateTime startTime;
public DateTime endTime;
}
There's a recursive solution that could work, try thinking along these lines:
public DateTime getDeadline(SubmitTime, ProjectTimeAllowed)
{
if (SubmitTime+ProjectTimeAllowed >= DayEndTime)
return getDeadline(NextDayStart, ProjectTimeAllowed-DayEndTime-SubmitTime)
else
return SubmitTime + ProjectTimeAllowed
}
Obviously this is quite rough pseudocode. Hopefully it just gives you another way to think about the problem.
Here's how I would do it. The algorithm is to see whether the issue can be closed today and if not, use all of today's time to reduce the issue's remaining time and go to tomorrow.
Find the time you have to close the issue as a TimeSpan (I'm calling this the issue's remaining time)
For each working day, create a DateTime that has only the time of the start and end.
Set the start time to now.
Loop:
Find today's remaining time by subtracting today's end time minus the start time (the result should be a TimeSpan)
If today's remaining time is greater than the issue's remaining time, take today's date and today's starttime + issue remaining time
If the issue's remaining time is greater, set the issue's remaining time to be the issue's remaining time minus today's remaining time, move to tomorrow, and go to the top of the loop.
Using Stu's answer as a starting point, modify the IsInBusinessHours function to look up you business hours for the date parameter. A procedure like the following could be used:
CREATE PROCEDURE [dbo].[IsInBusinessHours]
#MyDate DateTime
AS
BEGIN
SELECT CASE Count(*) WHEN 0 THEN 0 ELSE 1 END AS IsBusinessHour
FROM WorkHours
WHERE (DATEPART(hour, StartHours) <= DATEPART(hour, #MyDate)) AND (DATEPART(hour, EndHours) > DATEPART(hour, #MyDate)) AND (Day = DATEPART(WEEKDAY,
#MyDate))
END

Categories

Resources