DateTimeOffset Same Day comparison - c#

I ran into interesting issue with the following requirement:
Test if a process had run in the same day, if not run the process. The dates are stored as DataTimeOffset.
My original approach was to:
Convert both values to UTC, because these dates could have been created in different time zones and have different offsets.
View the Date value of each value. This is done after converting to UTC because the Date method ignores the offset.
Most scenarios this worked but I came across one case that the logic would fail. If one of the values had a time that was close to the previous/next day so that the when converting to UTC it would change the date. If the other value didn't have a time that also converted to the previous/next day then the date comparison failed.
So I ended up with the following logic to include that scenario:
public static bool SameDate(DateTimeOffset first, DateTimeOffset second)
{
bool returnValue = false;
DateTime firstAdjusted = first.ToUniversalTime().Date;
DateTime secondAdjusted = second.ToUniversalTime().Date;
// If date is now a day ahead after conversion, than add/deduct a day to other date if that date hasn't advanced
if (first.Date < firstAdjusted.Date && second.Date == secondAdjusted.Date)
secondAdjusted = secondAdjusted.Date.AddDays(1);
if (first.Date > firstAdjusted.Date && second.Date == secondAdjusted.Date)
secondAdjusted = secondAdjusted.Date.AddDays(-1);
if (second.Date < secondAdjusted.Date && first.Date == firstAdjusted.Date)
firstAdjusted = firstAdjusted.Date.AddDays(1);
if (second.Date > secondAdjusted.Date && first.Date == firstAdjusted.Date)
firstAdjusted = firstAdjusted.Date.AddDays(-1);
if (DateTime.Compare(firstAdjusted, secondAdjusted) == 0)
returnValue = true;
return returnValue;
}
Here is the Unit Tests that were failing that now pass:
[TestMethod()]
public void SameDateTest()
{
DateTimeOffset current = DateTimeOffset.Now;
DateTimeOffset first = current;
DateTimeOffset second = current;
// 23 hours later, next day, with negative offset (EST) -- First rolls over
first = new DateTimeOffset(2014, 1, 1, 19, 0, 0, new TimeSpan(-5, 0, 0));
second = new DateTimeOffset(2014, 1, 2, 18, 0, 0, new TimeSpan(-5, 0, 0));
Assert.IsFalse(Common.SameDate(first, second));
// 23 hours earlier, next day, with postive offset -- First rollovers
first = new DateTimeOffset(2014, 1, 1, 4, 0, 0, new TimeSpan(5, 0, 0));
second = new DateTimeOffset(2014, 1, 2, 5, 0, 0, new TimeSpan(5, 0, 0));
Assert.IsFalse(Common.SameDate(first, second));
// 23 hours later, next day, with negative offset (EST) -- Second rolls over
first = new DateTimeOffset(2014, 1, 2, 18, 0, 0, new TimeSpan(-5, 0, 0));
second = new DateTimeOffset(2014, 1, 1, 19, 0, 0, new TimeSpan(-5, 0, 0));
Assert.IsFalse(Common.SameDate(first, second));
// 23 hours earlier, next day, with postive offset -- Second rolls over
first = new DateTimeOffset(2014, 1, 2, 5, 0, 0, new TimeSpan(5, 0, 0));
second = new DateTimeOffset(2014, 1, 1, 4, 0, 0, new TimeSpan(5, 0, 0));
Assert.IsFalse(Common.SameDate(first, second));
}
My gut feeling is that there is a cleaner approach than to increment/decrement based on the other value. Is there a better approach?
The primary criteria:
Adjust the both dates to have the same offset.
Return true only if both first and second dates occur in the same calendar day, not within 24 hours.

Adjust the one of the dates for the difference in both dates:
public static bool SameDate(DateTimeOffset first, DateTimeOffset second)
{
bool returnValue = false;
DateTime firstAdjusted = first.ToUniversalTime().Date;
DateTime secondAdjusted = second.ToUniversalTime().Date;
// calculate the total diference between the dates
int diff = first.Date.CompareTo(firstAdjusted) - second.Date.CompareTo(secondAdjusted);
// the firstAdjusted date is corected for the difference in BOTH dates.
firstAdjusted = firstAdjusted.AddDays(diff);
if (DateTime.Compare(firstAdjusted, secondAdjusted) == 0)
returnValue = true;
return returnValue;
}
In this function I am asuming that the offset will never be more than 24 hours. IE the difference between a date and it's adjusted date will not be two or more days. If this is not the case, then you can use time span comparison.

The general methodology you describe (convert to common time-zone then compare date portion) is reasonable. The problem here is actually one of deciding on the frame of reference. You have arbitrarily chosen UTC as your frame of reference. At first gloss it doesn't matter so long as they are compared in the same time zone, but as you have found this can put them on either side of a day boundary.
I think you need to refine your specification. Ask yourself which of the following you are trying to determine.
Whether the values occur on the same calendar day for a specified time zone.
Whether the values are no more than 12 hours apart (+/- 12hrs is a 24hr period).
Whether the values are no more than 24 hours apart.
It might also be something else. The definition as implemented (but rejected by you) is "Whether the values occur on the same UTC calendar day".

First of all, you need to clear up some confusion what the program should do exactly. For two general timestamps in two general time zones (two DateTimeOffset instances without specific limitations), there is no such concept as “the calendar day”. Each time zone has its own calendar day. For instance, we could have two instances of DateTimeOffset, named first and second, and they have different offsets. Let’s visualize the time axis, and mark the specific time instants to which the DateTimeOffset instances refer with * and the calendar day in the respective time zone (i.e. the interval between 0:00 and 23:59 in the specific timezone) with |__|. It could look like this:
first: ........|___________________*__|.......
second: ...|______*_______________|............
When in the timezone of first, the second event happened during the same calendar day (between 2–3 am). When in the timezone of second, the first event happened during the following calendar day (between 1–2 am).
So it is obvious the question needs clarification and probably a bit of scope limitation. Are those really generic timezones, or are they timezones of the same place, differing potentially only in the daylight saving time? In that case, why don’t you just ignore the timezone? E.g. it does not matter that on November 2nd 2014, between 00:10 and 23:50, the UTC offset has changed (EDT->ET) and the two instants are separated by more than 24 hrs of time: new DateTimeOffset(2014, 11, 02, 00, 10, 00, new TimeSpan(-4, 0, 0)).Date == new DateTimeOffset(2014, 11, 02, 23, 50, 00, new TimeSpan(-5, 0, 0)).Date. Basically, this is what martijn tries to do, but in a very complicated way. When you would try just
public static bool SameDateSimple(DateTimeOffset first, DateTimeOffset second)
{
return first.Date == second.Date;
}
it would work for all your abovementioned unit tests. And, also, this is what most humans would call “the same calendar day” when it is guaranteed the two instances refer to times at a single place.
Or, if you are really comparing two “random” timezones, you have to choose your reference timezone. It could be UTC as you tried initially. Or, it might be more logical from a human standpoint to use the first timezone as the reference (you could also choose the second one, it would give different results, but both variants are “equally good”):
public static bool SameDateGeneral(DateTimeOffset first, DateTimeOffset second)
{
DateTime secondAdjusted = second.ToOffset(first.Offset).Date;
return first.Date == secondAdjusted.Date;
}
This does not work for some of the abovementioned tests, but is more general in the sense it works “correctly” (in some sense) for two random timezones: If you try first = new DateTimeOffset(2014, 1, 2, 0, 30, 0, new TimeSpan(5, 0, 0)), second = new DateTimeOffset(2014, 1, 1, 23, 30, 0, new TimeSpan(4, 0, 0)), the simple SameDateSimple returns false (as does martijn’s), even though these two instances refer to the exact same moment in time (both are 2014-01-01 19:30:00Z). SameDateGeneral returns true here correctly.

First off, you have an error in your UnitTest.
[TestMethod()]
public void SameDateTest()
{
DateTimeOffset current = DateTimeOffset.Now;
DateTimeOffset first = current;
DateTimeOffset second = current;
// 23 hours later, next day, with negative offset (EST) -- First rolls over
first = new DateTimeOffset( 2014, 1, 1, 19, 0, 0, new TimeSpan( -5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 2, 18, 0, 0, new TimeSpan( -5, 0, 0 ) );
Assert.IsTrue( DateTimeComparison.Program.SameDate( first, second ) );
// 23 hours earlier, next day, with positive offset -- First rollovers
first = new DateTimeOffset( 2014, 1, 1, 4, 0, 0, new TimeSpan( 5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 2, 5, 0, 0, new TimeSpan( 5, 0, 0 ) );
Assert.IsFalse( DateTimeComparison.Program.SameDate( first, second ) );
// 23 hours later, next day, with negative offset (EST) -- Second rolls over
first = new DateTimeOffset( 2014, 1, 2, 18, 0, 0, new TimeSpan( -5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 1, 19, 0, 0, new TimeSpan( -5, 0, 0 ) );
Assert.IsTrue( DateTimeComparison.Program.SameDate( first, second ) );
// 23 hours earlier, next day, with positive offset -- Second rolls over
first = new DateTimeOffset( 2014, 1, 2, 5, 0, 0, new TimeSpan( 5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 1, 4, 0, 0, new TimeSpan( 5, 0, 0 ) );
Assert.IsFalse( DateTimeComparison.Program.SameDate( first, second ) );
}
This is the corrected test. Your first test should return "True", as should your third posted tests. Those DateTimeOffsets being compared are on the same UTC date. Only test case two and four should return "False", as those DateTimeOffsets are in fact on 2 different dates.
Second, you can simplify your SameDate() function to this:
public static bool SameDate( DateTimeOffset first, DateTimeOffset second )
{
bool returnValue = false;
DateTime firstAdjusted = first.UtcDateTime;
DateTime secondAdjusted = second.UtcDateTime;
if( firstAdjusted.Date == secondAdjusted.Date )
returnValue = true;
return returnValue;
}
As all you are interested in is if first.Date and second.Date are actually on the same UTC date, this will get the job done without an extra cast/conversion to UTC.
Third, you can test your test cases using this complete program:
using System;
namespace DateTimeComparison
{
public class Program
{
static void Main( string[] args )
{
DateTimeOffset current = DateTimeOffset.Now;
DateTimeOffset first = current;
DateTimeOffset second = current;
// 23 hours later, next day, with negative offset (EST) -- First rolls over
first = new DateTimeOffset( 2014, 1, 1, 19, 0, 0, new TimeSpan( -5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 2, 18, 0, 0, new TimeSpan( -5, 0, 0 ) );
if( false == SameDate( first, second ) ) {
Console.WriteLine( "Different day values!" );
} else {
Console.WriteLine( "Same day value!" );
}
// --Comment is wrong -- 23 hours earlier, next day, with positive offset -- First rollovers
first = new DateTimeOffset( 2014, 1, 1, 4, 0, 0, new TimeSpan( 5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 2, 5, 0, 0, new TimeSpan( 5, 0, 0 ) );
if( false == SameDate( first, second ) ) {
Console.WriteLine( "Different day values!" );
} else {
Console.WriteLine( "Same day value!" );
}
// 23 hours later, next day, with negative offset (EST) -- Second rolls over
first = new DateTimeOffset( 2014, 1, 2, 18, 0, 0, new TimeSpan( -5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 1, 19, 0, 0, new TimeSpan( -5, 0, 0 ) );
if( false == SameDate( first, second ) ) {
Console.WriteLine( "Different day values!" );
} else {
Console.WriteLine( "Same day value!" );
}
// --Comment is wrong -- 23 hours earlier, next day, with positive offset -- Second rolls over
first = new DateTimeOffset( 2014, 1, 2, 5, 0, 0, new TimeSpan( 5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 1, 4, 0, 0, new TimeSpan( 5, 0, 0 ) );
if( false == SameDate( first, second ) ) {
Console.WriteLine( "Different day values!" );
} else {
Console.WriteLine( "Same day value!" );
}
}
public static bool SameDate( DateTimeOffset first, DateTimeOffset second )
{
bool returnValue = false;
DateTime firstAdjusted = first.UtcDateTime;
DateTime secondAdjusted = second.UtcDateTime;
if( firstAdjusted.Date == secondAdjusted.Date )
returnValue = true;
return returnValue;
}
}
}
Set a break point wherever you like and run this short program in the debugger. This will show you that test case 2 and test case 4 are in fact more than 2 days apart by UTC time and therefore should expect false. Furthermore, it will show test case 1 and test case 3 are on the same UTC date and should expect true from a properly functioning SameDate().
If you want your second and fourth test cases to be 23 hours apart on the same date, then for test case two, you should use:
// 23 hours earlier, next day, with positive offset -- First rollovers
first = new DateTimeOffset( 2014, 1, 2, 4, 0, 0, new TimeSpan( 5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 1, 5, 0, 0, new TimeSpan( 5, 0, 0 ) );
Assert.IsTrue( DateTimeComparison.Program.SameDate( first, second ) );
And for test case four, you should use:
// 23 hours earlier, next day, with positive offset -- Second rolls over
first = new DateTimeOffset( 2014, 1, 2, 5, 0, 0, new TimeSpan( 5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 3, 4, 0, 0, new TimeSpan( 5, 0, 0 ) );
Assert.IsTrue( DateTimeComparison.Program.SameDate( first, second ) );

What about this function:
public static bool SameDate(DateTimeOffset first, DateTimeOffset second)
{
return Math.Abs((first - second).TotalDays) < 1;
}
You can substract two dates (DateTimeOffset is smart and knows the timezone) and it will give you a range, a timespan. Then you can check if this range is +- 1 day.

Related

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>.

Time difference from anywhere in the world

On my website i want to ensure if the time gap is under 1 minute of DateTime st of the below function, an action can be taken otherwise it would be declined. Here's the code for that
DateTime st = MyDate.Value; // current value from database: 2019-12-05 13:20:15.478
DateTime now = DateTime.Now;
TimeSpan span = now.Subtract(st);
int expMin = 1;
if (span.Minutes < expMin)
{
// Do something
}
else
{
// Ignore
}
So based on the above date value the service cant be accessed after 2019-12-05 13:21:15.478
This works locally and on my server but I've seen some reports where some users are accessing the service from another country. Is there another way i should be ensuring the time, no matter which country the user is from, can't be accessed after the one minute timespan?
You need to use DateTimeOffset to consider the timezone of your users.
I assume that "MyDate" is passed by the client. See the following example.
var myDate = new DateTimeOffset(2019, 12, 05, 13, 20, 15, 478, new TimeSpan(0, 2, 0, 0, 0));
var now = new DateTimeOffset(2019, 12, 05, 14, 20, 45, 478, new TimeSpan(0, 3, 0, 0, 0));
var span = now.Subtract(myDate);
int expMin = 1;
if (span.Minutes < expMin)
{
Console.WriteLine("Do something");
}
else
{
Console.WriteLine("Ignore");
}

Get total hours that fall between a specific time period

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;

c# time calculations

Does someone knows how to calculate the total hours between 2 times?
For example if a worker clocks in at 8:00 and out at 16:00, I would like to know that in decimal it's 8.0 hours and it's 8:00 hours.
I'm using C# framework 2.0.
The variables that hold the in and out time are of type string.
TY
DateTime start = new DateTime(2010, 8, 25, 8, 0, 0);
DateTime end = new DateTime(2010, 8, 25, 16, 0, 0);
Console.WriteLine((end - start).TotalHours);
for strings:
DateTime start = DateTime.Parse("8:00");
DateTime end = DateTime.Parse("16:00");
Console.WriteLine((end - start).TotalHours);
I came up with this daylight saving time safe method. The function is correct for both UTC and local timezones. If the DateTimeKind is Unspecified on either of the inputs then the return value is undefined (which is a fancy way of saying it could be incorrect).
private double TotalHours(DateTime earliest, DateTime latest)
{
earliest = (earliest.Kind == DateTimeKind.Local) ? earliest.ToUniversalTime() : earliest;
latest = (latest.Kind == DateTimeKind.Local) ? latest.ToUniversalTime() : latest;
return (latest - earliest).TotalHours;
}
System.DateTime punchIn = new System.DateTime(2010, 8, 25, 8, 0, 0);
System.DateTime punchOut = new System.DateTime(2010, 8, 25, 16, 0, 0);
System.TimeSpan diffResult = punchOut.Subtract(punchIn);
Check out TimeSpan.TotalHours:
TimeSpan difference = datetime2 - datetime1;
double totalHours = difference.TotalHours;
You can do it by subtracting two datetimes and using the TotalHours property of the resulting Timespan. Heres an example:
DateTime start = new DateTime(2010, 8, 25, 8, 0, 0);
DateTime end = new DateTime(2010, 8, 25, 16, 0, 0);
int hours = end.Subtract(start).TotalHours;

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