DateTime.Compare doesn't work as expected - c#

I'm trying to compare two DateTime objects ignoring the seconds with the below function, but it gives the wrong result even though both of the DateTime objects are have the same values. Can't figure out how to exactly get it working, any help would be highly appreciated.
public static int CompareDateTime(DateTime d1, DateTime d2)
{
d1 = d1.AddSeconds(-1 * d1.Second);
d2 = d2.AddSeconds(-1 * d2.Second);
int result = DateTime.Compare(d1, d2);
string relationship;
if (result < 0)
relationship = "is earlier than";
else if (result == 0)
relationship = "is the same time as";
else
relationship = "is later than";
Console.WriteLine("{0} {1} {2}", d1, relationship, d2);
return result;
}
Result:
3/7/2017 2:54:00 PM is later than 3/7/2017 2:54:00 PM

The problem here is that you aren't truncating as you expect to. The best way to truncate is to create an entirely new DateTime object from the constructor by doing the following:
d1 = new DateTime(d1.Year, d1.Month, d1.Day, d1.Hour, d1.Minute, 0);
d2 = new DateTime(d2.Year, d2.Month, d2.Day, d2.Hour, d2.Minute, 0);
This will ensure you are only comparing the data you want.
In this specific case it is the milliseconds that may have been part of the datetime that were being left by your attempted truncation.

You just need to ignore the milliseconds as well. You could add this to your code, or go for the more 'elegant' solution suggested by #Chris
d1 = d1.AddMilliseconds(-1 * d1.Millisecond);
d2 = d2.AddMilliseconds(-1 * d2.Millisecond);

Everyone is giving a solution for truncating time... So I'll give the one that I normally use... A DateTime is the number of ticks starting from 0:00:00 on January 1, 0001... so if you know how many ticks there are in a minute (there is a readonly field for that: TimeSpan.TicksPerMinute) you can count the number of minutes from January 1, 0001...
long minutes1 = d1.Ticks / TimeSpan.TicksPerMinute;
long minutes2 = d2.Ticks / TimeSpan.TicksPerMinute;
int result = minutes1.CompareTo(minutes2);
(note that there are various "useful" values in TimeSpan: TicksPerDay, TicksPerHour, TicksPerMinute, TicksPerSecond, TicksPerMillisecond)

Related

DateTime.Compare(start, end) resulting weired in my system

In the above picture you can see that the start and end value is same. but the compare method is returning -1, which means start time is less than end time. How is this possible?
I have tried sample values in a console application to test comapre method, & its working fine. I think here may be some internal value of datetime object is not matching. But couldn't find.
Here is the code.
DateTime start = Convert.ToDateTime(pi.StartTime), end = Convert.ToDateTime(pi.EndTime);
int t1 = DateTime.Compare(start, end);
if (t1 == 0)
{
MessageBox.Show("Start Time and End Time are same.");
return;
}
else if (t1 == 1)
{
MessageBox.Show("Start Time is greater then end time.");
return;
}
I suggest comparison with tolerance, e.g. trimming off milliseconds:
int t1 = DateTime.Compare(
new DateTime(start.Ticks - (start.Ticks % TimeSpan.TicksPerSecond), start.Kind),
new DateTime(end.Ticks - (end.Ticks % TimeSpan.TicksPerSecond), end.Kind));
Just after the posting of Question, I checked each properties and found that there are difference of 127 miliseconds. WOW! And then I convert my system datetime to string and then again converting to datetime (as the milisecond become 0). so everything works fine now. So is it ok what I am doing?
No. By doing a conversion you do not communicate intent. The one reading your code will not understand why you did like that.
A much better way would be:
var difference = date1.Substract(date2).TotalSeconds;
return Math.Abs(difference) < 1;
Because then you show in the code that you accept a small difference (and how large difference you allow).
From MSDN
Compares two instances of DateTime and returns an integer that
indicates whether the first instance is earlier than, the same as, or
later than the second instance.
Then it says :
To determine the relationship of t1 to t2, the Compare method compares
the Ticks property of t1 and t2 but ignores their Kind property.
Before comparing DateTime objects, ensure that the objects represent
times in the same time zone.
And that s why you're having that result : here's an example :
Edit :
using System;
public class Example
{
public static void Main()
{
DateTime date1 = new DateTime(2009, 8, 1, 0, 0, 0);
DateTime date2 = new DateTime(2009, 8, 1, 0, 0, 0);
date2 = date2.AddMilliseconds(2);
int result = DateTime.Compare(date1, date2);
string relationship;
if (result < 0)
relationship = "is earlier than";
else if (result == 0)
relationship = "is the same time as";
else
relationship = "is later than";
Console.WriteLine("{0} {1} {2}", date1, relationship, date2);
}
}
// The example displays the following output:
// 8/1/2009 12:00:00 AM is earlier than 8/1/2009 12:00:00 AM
Hope that helped.
You can compare
DateTime.Compare(
start.AddMilliseconds(-start.Millisecond),
end.AddMilliseconds(-end.Millisecond)
);
or even better with an extension method
DateTime.Compare(start.TrimMilliseconds(), stop.TrimMilliseconds())
public static class DateTimeExtensions
{
public static DateTime TrimMilliseconds(this DateTime date)
{
return date.AddMilliseconds(-date.Millisecond);
}
}
please not that DateTime values are immutable so you are comparing two different DateTime values. start and end are not modified and are still differnt. you can avoid that with trimming the milliseconds during assignment
var start = DateTime.Now.TrimMilliseconds();

How can I segregate properties of DateTime?

I would like to write: if the result of the difference of 2 DateTimes is longer than 3 hours then.... stuff in the if statement happens. But I only need properties in seconds or minutes, can I extract just that from the DateTime object?
if(diffResult > DateTime.Hour(3))
{
}
I also want to know if its possible to divide DateTime by periods. Say I want to split my diffResult (which is the difference between 2 DateTimes) into 3 periods or perhaps for every 3 seconds my counter gets one added to it.
For the first part:
You can subtract two DateTimes to get a TimeSpan there you can get the total of various units - for example:
if ( (secondTime - firstTime).TotalMinutes > 180.0) ...
or you could use TimeSpan directly:
if (secondTime - firstTime > TimeSpan.FromHours(3)) ...
for the secondpart you have to do some calculation yourself:
var diff = secondTime - firstTime;
var period = TimeSpan.FromSeconds(diff.TotalSeconds / 3.0);
for (var time = firstTime; time < secondTime; time += period)
{ /* do your stuff */ }
U can compare using the follow code:
DateTime dt = new DateTime();
dt = DateTime.Now;
dt.AddHours(3);
int h = (int)DateTime.Now.Hour;
if (dt.Hour == h )
//Do something
else
//do otherthing
You can do this:
TimeSpan time = new TimeSpan(3, 0, 0);
if (date1.Subtract(date2) > time)
{
//YourCode
}
For the second, this article should be useful:
http://www.blackwasp.co.uk/TimespanMultiplication.aspx
The methods your asking about return integer results. What exactly is your question? DateTime.Hour(3) would not even compile.
I think you are looking for DateTime.Now.AddHours(3.0)
I should be clear, the only reason this answer is this sparse, is because of the invalid code in the author's question which. Since I don't attempt to guess at what people actually want, its up to the author, to clarify what he wants exactly.
All he has to do is subtract two DateTime values and compare it to a TimeSpan

When are two Datetime variables equal?

I try to compare Datetime.Now with a Datetime variable I set, using the Datetime.CompareTo() method. I use a timer to compare these every second and display the result, but as the current time approaches the time I set, the result changes from 1 to -1, but never 0, which means these two are never equal. I'm suspecting the Datetime structure contains milliseconds?
You're suspecting correctly. It goes further than milliseconds though. The maximum resolution is the "tick", which is equal to 100 nanoseconds.
As other have mentioned here, the resolution is 100ns.
The easiest approach would be to take your DateTime and subtract DateTime.Now. You then end up with a TimeSpan. If the TimeSpan's TotalSeconds property is 0, the difference between them is less than a second.
You are correct in your suspicion. The DateTime struct smallest unit is the "Tick" which is measured in units of 100ns. (One tick is 100ns)
What you more likely want to do is check if everything down to the seconds is equal and you can do that like this by first comparing the Date property and then compare the hour, minute and second properties individually
DateTime comparison is more exact than comparing with seconds. In your scenario, you can define an "error range", e.g. if the gap between two DateTime is less than 1 second, they are considered to be the same(in your program).
Try this... (but change the test date, of course)
DateTime d1 = new DateTime(2011, 12, 27, 4, 37, 17);
DateTime d2 = DateTime.Now;
if (d1.Subtract(d2).Seconds <= 1)
{
//consider these DateTimes equal... continue
}
I prefer to compare Datetime (as well as double) not with exact values but with value ranges, because it is quite unlikely that you have the exact value.
DateTime d1 = new DateTime(2011, 12, 27, 4, 37, 17);
DateTime d2 = DateTime.Now;
if ((d2 >= d1) && (d2 <= d1.AddMinutes(1)))
....
'simulate comparison of two datetimes
d1 = DateTime.Now
Threading.Thread.Sleep(250)
d2 = DateTime.Now
'see if two dates are within a second of each other
Dim ts As Double = ((d2 - d1).TotalSeconds)
If ts < 1 Then
'equal
Debug.WriteLine("EQ " & ts.ToString("n4"))
Else
Debug.WriteLine("neq " & ts.ToString("n4"))
End If

How to round-off hours based on Minutes(hours+0 if min<30, hours+1 otherwise)?

I need to round-off the hours based on the minutes in a DateTime variable. The condition is: if minutes are less than 30, then minutes must be set to zero and no changes to hours, else if minutes >=30, then hours must be set to hours+1 and minutes are again set to zero. Seconds are ignored.
example:
11/08/2008 04:30:49 should become 11/08/2008 05:00:00
and 11/08/2008 04:29:49 should become 11/08/2008 04:00:00
I have written code which works perfectly fine, but just wanted to know a better method if could be written and also would appreciate alternative method(s).
string date1 = "11/08/2008 04:30:49";
DateTime startTime;
DateTime.TryParseExact(date1, "MM/dd/yyyy HH:mm:ss", null,
System.Globalization.DateTimeStyles.None, out startTime);
if (Convert.ToInt32((startTime.Minute.ToString())) > 29)
{
startTime = DateTime.Parse(string.Format("{0}/{1}/{2} {3}:{4}:{5}",
startTime.Month.ToString(), startTime.Day.ToString(),
startTime.Year.ToString(), startTime.Hour.ToString(), "00", "00"));
startTime = startTime.Add(TimeSpan.Parse("01:00:00"));
Console.WriteLine("startTime is :: {0}",
startTime.ToString("MM/dd/yyyy HH:mm:ss"));
}
else
{
startTime = DateTime.Parse(string.Format("{0}/{1}/{2} {3}:{4}:{5}",
startTime.Month.ToString(),
startTime.Day.ToString(), startTime.Year.ToString(),
startTime.Hour.ToString(), "00", "00"));
Console.WriteLine("startTime is :: {0}",
startTime.ToString("MM/dd/yyyy HH:mm:ss"));
}
Just as an alternative:
public static DateTime Round( DateTime dateTime )
{
var updated = dateTime.AddMinutes( 30 );
return new DateTime( updated.Year, updated.Month, updated.Day,
updated.Hour, 0, 0, dateTime.Kind );
}
If speed is an issue, the following should be the fastest way:
static DateTime RoundToHour(DateTime dt){
long ticks = dt.Ticks + 18000000000;
return new DateTime(ticks - ticks % 36000000000, dt.Kind);
}
It's also a pretty straight-forward and simple way to do it.
To explain, a DateTime structure doesn't actually have fields that store the year, month, day, hour, minute, etc. It stores one single long value, the number of "ticks" since a certain epoch (Jan 1, 1 AD). A tick is 100 nanoseconds, or one 10,000,000th of a second.
Any time you use any of the date/time properties, it divides by the proper constant.
So here, we add a constant equal to 30 minutes (30 * 60 * 1e7 = 18000000000 ticks), then subtract the remainder after dividing by a constant equal to one hour (60 * 60 * 1e7 = 36000000000 ticks).
What about:
public static DateTime RoundToHours(DateTime input)
{
DateTime dt = new DateTime(input.Year, input.Month, input.Day, input.Hour, 0, 0);
if (input.Minute > 29)
return dt.AddHours(1);
else
return dt;
}
No need to convert to string and back again!
EDIT:
Using a input.Hour+1 in the constructor will fail if the Hour is 23. The .AddHours(1) will correctly result in '0:00' the next day.
Here goes!
var rounded = date.AddMinutes(30).Date.AddHours(date.AddMinutes(30).Hour);
And for those that want it floored
var floored = date.Date.AddHours(date.Hours)
DateTime s = DateTime.Now;
if (s.Minute > 30) s = s.AddHours(1); //only add hours if > 30
if (s.Minute == 30 && s.Second > 0) s = s.AddHours(1); //add precision as needed
s = new DateTime(s.Year, s.Month, s.Day, s.Hour, 0, 0);
Extending Hans Kestings good Answer:
public DateTime RoundToHours(DateTime input)
{
DateTime dt = new DateTime(input.Year, input.Month, input.Day, input.Hour, 0, 0);
return dt.AddHours((int)(input.Minutes / 30));
}
The (int) Cast might not be required.
EDIT: Adapted the corrections Hans Kesting made in his Answer.
To improve upon some of the other methods, here is a method that will also preserve the DateTime Kind:
/// <summary>
/// Rounds a DateTime to the nearest hour.
/// </summary>
/// <param name="dateTime">DateTime to Round</param>
/// <returns>DateTime rounded to nearest hour</returns>
public static DateTime RoundToNearestHour(this DateTime dateTime)
{
dateTime += TimeSpan.FromMinutes(30);
return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, 0, 0, dateTime.Kind);
}
Based on P Daddy's solution, I propose to not hardcode that big number of ticks to one hour. Hardcoding is evil, isn't it? With this modified solution, you can now round any given time to any number of minutes:
public DateTime RoundToMinutes(DateTime dt, int NrMinutes)
{
long TicksInNrMinutes = (long)NrMinutes * 60 * 10000000;//1 tick per 100 nanosecond
long ticks = dt.Ticks + TicksInNrMinutes / 2;
return new DateTime(ticks - ticks % TicksInNrMinutes, dt.Kind);
}
I use this for rounding to the nearest 5 minutes, e.g. 22:23 becomes 22:25.
Years ago I used the same method to round amounts of money to the nearest 25 cent, e.g. $ 22.23 becomes $ 22.25. But the project manager sometimes changed his mind, but changing the rounding to the nearest 10 or 5 cent would be trivial. So now I similarly do not have to get nervous when my project mgr wants rounding times to another round nr of minutes.
So this rounding method is both fast, and flexible.
My method was already found and published in this 2008 SO solution
DateTime dtm = DateTime.Now;
if (dtm.Minute < 30)
{
dtm = dtm.AddMinutes(dtm.Minute * -1);
}
else
{
dtm = dtm.AddMinutes(60 - dtm.Minute);
}
dtm = dtm.AddSeconds(dtm.Second * -1);

How can I get correct payperiod from date?

I feel like this is math problem more than anything. My company has employees all over the country. Some parts of the company are on an "odd" pay cycle and some are on "even". I call the starting date of a given pay period a "payperiod". I need to do two things:
1) determine the payperiod in which a given date falls
//Something like this:
public static DateTime getPayPeriodStartDate(DateTime givenDate, string EvenOrOdd)
{ .. }
2) get a list of payperiods between two dates:
//Something like this:
public static List<DateTime> getPayPeriodsBetween(DateTime start, DateTime end, string EvenOrOdd)
{ .. }
I'm using a couple dates as fixed standards on which to base any future pay period dates. The fixed standard dates for even and odd are as follows:
Even - 01/04/09
Odd - 01/11/09
Each pay period starts on the sunday of the week and goes for two weeks. For instance, using the standard dates above, the first even pay period starts on 01/04/09 and ends on 01/17/09. The first odd pay period starts on 01/11/09 and ends on 01/24/09. As you can see, there is some overlap. We have thousands of employees so it's necessary to split them up a bit.
I have a solution that is based on week numbers but it's clunky and has to be "fixed" every new year. I'm wondering how you would handle this.
Not fully optimized or tested, but this is what I came up with:
const int DaysInPeriod = 14;
static IEnumerable<DateTime> GetPayPeriodsInRange(DateTime start, DateTime end, bool isOdd)
{
var epoch = isOdd ? new DateTime(2009, 11, 1) : new DateTime(2009, 4, 1);
var periodsTilStart = Math.Floor(((start - epoch).TotalDays) / DaysInPeriod);
var next = epoch.AddDays(periodsTilStart * DaysInPeriod);
if (next < start) next = next.AddDays(DaysInPeriod);
while (next <= end)
{
yield return next;
next = next.AddDays(DaysInPeriod);
}
yield break;
}
static DateTime GetPayPeriodStartDate(DateTime givenDate, bool isOdd)
{
var candidatePeriods = GetPayPeriodsInRange(givenDate.AddDays(-DaysInPeriod), givenDate.AddDays(DaysInPeriod), isOdd);
var period = from p in candidatePeriods where (p <= givenDate) && (givenDate < p.AddDays(DaysInPeriod)) select p;
return period.First();
}
I haven't tested for many test cases, but I think this fits the bill:
public static DateTime getPayPeriodStartDate(DateTime givenDate, string EvenOrOdd)
{
DateTime newYearsDay = new DateTime(DateTime.Today.Year, 1, 1);
DateTime firstEvenMonday = newYearsDay.AddDays((8 - (int)newYearsDay.DayOfWeek) % 7);
DateTime firstOddMonday = firstEvenMonday.AddDays(7);
TimeSpan span = givenDate - (EvenOrOdd.Equals("Even") ? firstEvenMonday : firstOddMonday);
int numberOfPayPeriodsPast = span.Days / 14;
return (EvenOrOdd.Equals("Even") ? firstEvenMonday : firstOddMonday).AddDays(14 * numberOfPayPeriodsPast);
}
public static List<DateTime> getPayPeriodsBetween(DateTime start, DateTime end, string EvenOrOdd)
{
DateTime currentPayPeriod = getPayPeriodStartDate(start, EvenOrOdd);
if (currentPayPeriod < start) currentPayPeriod = currentPayPeriod.AddDays(14);
List<DateTime> dtList = new List<DateTime>();
while (currentPayPeriod <= end)
{
dtList.Add(currentPayPeriod);
currentPayPeriod = currentPayPeriod.AddDays(14);
}
return dtList;
}
I am sure it can be improved.
I had a need to do something similar and was able to do it very easily using LINQ. Simply build up a List for even and odd and then query between dates from the odd/even as necessary. Also, I recommend you move to an emum for parameters like EvenOrOdd where you have fixed values.
I had a similar problem a few months ago, and I ended up writing a quick script to create entries in a database for each pay period so I never had to actually do the math. This way, The system works the same speed, and doesn't have to do any slow iterations every time a period is requested.
That being said, you can always take the starting date, and add two weeks (or however long your periods are) over and over until you reach the dates you specify in the function call. This is a bit ugly, and the longer it sits in production, the slower it gets (since the dates are getting further and further apart).
Both ways are trivial to implement, it's just a matter of what kind of resources you have at hand to tackle the issue.
So, for number 1: Start with either 1/4/2009 or 1/11/2009 (depending on even/odd pay week) and add 2 weeks until the givenDate is less than the date you're testing + 2 weeks. That's the start of the period.
For number 2: Same thing, start at the date and add 2 weeks until you're within the date range. While you're there, add each item to a list. As soon as you're past the last date, break out of your loop and return your shiny new list.
If you used my method and went with a database to house all this info, it turns into 2 simple queries:
1)SELECT * FROM payperiods WHERE startdate<=givenDate ORDER BY startdate LIMIT 1
2) SELECT * FROM payperiods WHERE startdate>=givenDate AND enddate<=givenDate ORDER BY startdate
It works perfectly. I have tested.
public static DateTime GetFirstDayOfWeek(DateTime dayInWeek)
{
CultureInfo _culture = (CultureInfo)CultureInfo.CurrentCulture.Clone();
CultureInfo _uiculture = (CultureInfo)CultureInfo.CurrentUICulture.Clone();
_culture.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Monday;
_uiculture.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Monday;
System.Threading.Thread.CurrentThread.CurrentCulture = _culture;
System.Threading.Thread.CurrentThread.CurrentUICulture = _uiculture;
// CultureInfo defaultCultureInfo = CultureInfo.CurrentCulture;
DayOfWeek firstDay = _culture.DateTimeFormat.FirstDayOfWeek;
DateTime firstDayInWeek = dayInWeek.Date;
// Logic Of getting pay period Monday(Odd monday)
int i = Convert.ToInt32(firstDay);
while (firstDayInWeek.DayOfWeek != firstDay)
if (i % 2 != 0)
{ firstDayInWeek = firstDayInWeek.AddDays(-1); }
else
{
firstDayInWeek = firstDayInWeek.AddDays(-2);
}
return firstDayInWeek;
}

Categories