DateTime Equals method & Ticks

G

Guest

What determines if two DateTime objects compare equal using the DateTime
Equals method? I am wrapping DateTime to provide some time-zone awareness
and have run into a problem with the DateTime Equals. A short contrived
example that shows the problem I am having is the following:

DateTime foo1 = DateTime.Now;
DateTime foo2 = foo1.ToUniversalTime();
DateTime foo3 = foo2.ToLocalTime();

After this, foo1, foo2, and foo3 all have identical values for Year, Month,
Day, Hours, Minutes, Seconds, and Millisecond properties, but NOT the Ticks
property which is different between all three. The DateTime equals
comparing any two of the objects returns false....apparently because the
Ticks?

Is this indeed the case, and do all conversions alter the Ticks count - in
effect meaning that converted values are not 'round-trippable' and the
resulting objects not comparable for equality?

I am considering overriding Equals to take care of this -- for this
application we don't need the Ticks for anything but I want to make sure I am
not missing something..

Thank you,
 
J

Jon Skeet [C# MVP]

Mike Almond said:
What determines if two DateTime objects compare equal using the DateTime
Equals method? I am wrapping DateTime to provide some time-zone awareness
and have run into a problem with the DateTime Equals. A short contrived
example that shows the problem I am having is the following:

DateTime foo1 = DateTime.Now;
DateTime foo2 = foo1.ToUniversalTime();
DateTime foo3 = foo2.ToLocalTime();

After this, foo1, foo2, and foo3 all have identical values for Year, Month,
Day, Hours, Minutes, Seconds, and Millisecond properties, but NOT the Ticks
property which is different between all three. The DateTime equals
comparing any two of the objects returns false....apparently because the
Ticks?

Is this indeed the case, and do all conversions alter the Ticks count - in
effect meaning that converted values are not 'round-trippable' and the
resulting objects not comparable for equality?

I am considering overriding Equals to take care of this -- for this
application we don't need the Ticks for anything but I want to make sure I am
not missing something..

Well, you won't be able to override Equals as DateTime is a value type
and thus can't be derived from.

What timezone are you in? I can't currently reproduce the problem, but
then I'm in GMT... changing the date so it's in BST doesn't make any
odds though. Also, which version of the framework are you using?

Could you post a short but complete program which demonstrates the
problem?

See http://www.pobox.com/~skeet/csharp/complete.html for details of
what I mean by that.
 
G

Guest

Jon -

Thanks for the quick response. I have located the problem. To do some of
the timezone-aware operations I am using some Win32APIs to do the work and
the SYSTEMTIME struct has no notion of ticks. By the time I get the results
back and rehydrate it into a DateTime I am faced with the fact that there is
no DateTime constructor that allows you to provide the get-only Ticks
property, so it gets filled in during DateTime construction with a new value
that is unrelated to the original source value.

I already have an Equals in my wrapper class that currently just delegates
to the DateTime.Equals. I am thinking of changing it to check equality
ignoring the Ticks property unless someone has a better idea?? (I know the
2.0 framework has a bit more time zone awareness but this has to be for 1.1
for a variety of reasons).

Thanks,

Mike Almond
Technology Architect
Getronics
 
J

Jon Skeet [C# MVP]

Mike Almond said:
Thanks for the quick response. I have located the problem. To do some of
the timezone-aware operations I am using some Win32APIs to do the work and
the SYSTEMTIME struct has no notion of ticks. By the time I get the results
back and rehydrate it into a DateTime I am faced with the fact that there is
no DateTime constructor that allows you to provide the get-only Ticks
property, so it gets filled in during DateTime construction with a new value
that is unrelated to the original source value.

There is a constructor which takes the ticks value - it's the one which
taks the long (Int64) value.

Does that sort out your problem?
 
G

Guest

Probably not in this particular case. That constructor takes just a ticks
count and assumes the DateTime it is building is for the local machine's time
zone only. The purpose of this wrapper is to extend the behavior to handle
date formatting and calculations across multiple time zones, and so the ticks
property is almost an annoyance (since it messes up the DateTime.Equals) so
I'll probably try the Equals-without-Ticks route.

Thanks again,

--
Mike Almond
Technology Architect
Getronics
 
J

Jeffrey Tan[MSFT]

Hi Mike ,

I am not sure I understand your problem context very well. How and why do
you use Win32 API with DateTime? Can you provide a more clear explanation
regarding your currently problem? Then we may understand it better. Thanks

Normally, DateTime.Equals just checks the tick internally, something like
this:

public bool Equals(DateTime value)
{
return (this.InternalTicks == value.InternalTicks);
}

If you still have any problem, please feel free to feedback. Thanks

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
G

Guest

The root issue I am solving is the lack of any time zone or Daylight Savings
Time awareness in the DateTime struct (in the 1.1 Framework), which always
assumes the local time zone for all instances when doing comparisons, etc.
My wrapper class around DateTime provides this time zone awareness.

I am using a few Win32 APIs (SystemTimeToTzSpecificLocalTime and
TzSpecificLocalTimeToSystemTime in particular) to do the actual conversions
needed in time zone aware date processing. These APIs use the SYSTEMTIME
struct, which has no concept of Ticks in it. A typical scenario is to
convert two (wrapped) DateTimes from different time zones to UTC via the
APIs, perform the logical comparisons and return the result. Another is to
convert one time zone value to another, which requires going through the APIs
to convert to UTC, then to the new time zone. In this latter scenario, the
original 'Ticks' value is lost - actually it gets set when the resultant
SYSTEMTIME struct is 'new' d into a DateTime object representing the result:

DateTime (EST) ---> UTC ---> new DateTime(Korea)

In a one-way scenario like this, there is no problem. However, if a followup
operation attempts to "reverse" the above without altering any of the actual
values:

DateTime(Korea) ---> UTC ---> new DateTime(EST)

When the second DateTime(EST) object is created with the results of the
second UTC conversion there is no Ticks value available from SYSTEMTIME. The
Ticks property (apparently) is initialized during construction of the new
DateTime -- it can't be specified in any constructor (except the one
mentioned by Jon above which doesn't work in this scenario where I need to
specify a particular DateTime value expressed as a SYSTEMTIME). The result
is all of DateTime logical comparision ops will give incorrect reaults when
comparing the first (EST) value to the second (EST) value for example. Even
though both the date part and the time part values in the two are identical,
Equals, >, < etc. all give incorrect results because they include the Ticks
property in the comparison operation, and by this time they are always
different.

My approach has been to provide Equals, >, < etc. operators in my wrapper
class that essentially do the operations but ignore the Ticks property ---
just using the actual date and time portions of the wrapped DateTime object.
Taking ticks out of the equation seems to be working just fine (so far !).
It gives us millisecond accuracy (which is more than we will ever need) and
my wrapper doesn't expose the Ticks property from the inner DateTime so there
is no opportunity for an application caller to get in trouble trying to use
the value.

--
Mike Almond
Technology Architect
Getronics



"Jeffrey Tan[MSFT]" said:
Hi Mike ,

I am not sure I understand your problem context very well. How and why do
you use Win32 API with DateTime? Can you provide a more clear explanation
regarding your currently problem? Then we may understand it better. Thanks

Normally, DateTime.Equals just checks the tick internally, something like
this:

public bool Equals(DateTime value)
{
return (this.InternalTicks == value.InternalTicks);
}

If you still have any problem, please feel free to feedback. Thanks

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
J

Jeffrey Tan[MSFT]

Hi Mike ,

Thanks for your feedback.

Yes, .Net DateTime structure only concerns with current timezone and UTC.

Can you show me how you translate UTC into DateTime object? Which
constructor do you use?

Internally, most constructors of DateTime calls DateToTicks method to
convert year, month and day value into ticks. Something like this:
private static long DateToTicks(int year, int month, int day)
{
if (((year >= 1) && (year <= 0x270f)) && ((month >= 1) && (month <=
12)))
{
int[] numArray1 = DateTime.IsLeapYear(year) ?
DateTime.DaysToMonth366 : DateTime.DaysToMonth365;
if ((day >= 1) && (day <= (numArray1[month] - numArray1[month -
1])))
{
int num1 = year - 1;
int num2 = ((((((num1 * 0x16d) + (num1 / 4)) - (num1 /
100)) + (num1 / 400)) + numArray1[month - 1]) + day) - 1;
return (num2 * 0xc92a69c000);
}
}
throw new ArgumentOutOfRangeException(null,
Environment.GetResourceString("ArgumentOutOfRange_BadYearMonthDay"));
}

So once you passed the correct year, month and day value, you should will
get the same ticks count.

If I misunderstand your point, please feel free to tell me, thanks

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
G

Guest

Jeffrey -

The converted DateTime is constructed as you say -- the individual
SYSTEMTIME fields are individually used in the detailed constructor. Here is
a simple program that I used while investigating this -- I think it
demonstrates the basic problem using only DateTime. Date4 is constructed
from the individual constituent parts of Date1, and thus you would expect
for Date1 and Date4 to be equal -- but they are not, apparently due to either
/ both of the DateTime.Ticks or DateTime.TimeOfDay.Ticks. Running this code
on my system for example gives me:

Date1 Ticks: 632736001277068750 TOD.Ticks: 289277068750
Date2 Ticks: 632736289277068750 TOD.Ticks: 577277068750
Date3 Ticks: 632736001277068750 TOD.Ticks: 289277068750
Date4 Ticks: 632736001277060000 TOD.Ticks: 289277060000
Date1 == Date2: False
Date1 == Date3: True
Date1 == Date4: False
End of run.

The ticks always appear to be 'rounded' down in Date4 as above. I also
notice that in approximately one run in 10 , the situation is reversed -- all
tick values come out the same and Date1 and Date4 ARE Equal. I initially
suspected maybe the VS debugger was getting int the way, but launching the
same code outside of VS gives identical results. The code:

using System;

namespace DateTest
{
class Test
{
DateTime Date1, Date2, Date3, Date4;


[STAThread]
static void Main(string[] args)
{
Test app = new Test();
app.Run();
Console.WriteLine("End of run.");
Console.ReadLine();
}

private void Run()
{
Date1 = DateTime.Now;
Console.WriteLine("Date1 Ticks: {0} TOD.Ticks: {1} ", Date1.Ticks,
Date1.TimeOfDay.Ticks);

Date2 = Date1.ToUniversalTime();
Console.WriteLine("Date2 Ticks: {0} TOD.Ticks: {1} ", Date2.Ticks,
Date2.TimeOfDay.Ticks);

Date3 = Date2.ToLocalTime();
Console.WriteLine("Date3 Ticks: {0} TOD.Ticks: {1} ", Date3.Ticks,
Date3.TimeOfDay.Ticks);

// Create a DateTime equivalent to Date1, using the detailed constructor
Date4 = new DateTime(Date1.Year, Date1.Month, Date1.Day, Date1.Hour,
Date1.Minute, Date1.Second, Date1.Millisecond);
Console.WriteLine("Date4 Ticks: {0} TOD.Ticks: {1} ", Date4.Ticks,
Date4.TimeOfDay.Ticks);

// S/b false -- sanity check.
Console.WriteLine("Date1 == Date2: {0}", DateTime.Equals(Date1, Date2));

// S/b true - .
Console.WriteLine("Date1 == Date3: {0}", DateTime.Equals(Date1, Date3));

// Would expect this to be equal --- but it is false due to Ticks field ???.
Console.WriteLine("Date1 == Date4: {0}", DateTime.Equals(Date1, Date4));

}
}
}


--
Mike Almond
Technology Architect
Getronics



"Jeffrey Tan[MSFT]" said:
Hi Mike ,

Thanks for your feedback.

Yes, .Net DateTime structure only concerns with current timezone and UTC.

Can you show me how you translate UTC into DateTime object? Which
constructor do you use?

Internally, most constructors of DateTime calls DateToTicks method to
convert year, month and day value into ticks. Something like this:
private static long DateToTicks(int year, int month, int day)
{
if (((year >= 1) && (year <= 0x270f)) && ((month >= 1) && (month <=
12)))
{
int[] numArray1 = DateTime.IsLeapYear(year) ?
DateTime.DaysToMonth366 : DateTime.DaysToMonth365;
if ((day >= 1) && (day <= (numArray1[month] - numArray1[month -
1])))
{
int num1 = year - 1;
int num2 = ((((((num1 * 0x16d) + (num1 / 4)) - (num1 /
100)) + (num1 / 400)) + numArray1[month - 1]) + day) - 1;
return (num2 * 0xc92a69c000);
}
}
throw new ArgumentOutOfRangeException(null,
Environment.GetResourceString("ArgumentOutOfRange_BadYearMonthDay"));
}

So once you passed the correct year, month and day value, you should will
get the same ticks count.

If I misunderstand your point, please feel free to tell me, thanks

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
J

Jeffrey Tan[MSFT]

Hi Mike,

Thanks for your feedback.

Yes, with this sample code, I can reproduce out your problem.

This issue is caused by the tick property granularity. A tick is
100-nanosecond interval. Because DateTime.Now is not an integer for
Millisecond(it is hard for us a get an integer Millisecond value from
DateTime.Now), so DateTime.Now is different from DateTime(Date1.Year,
Date1.Month, Date1.Day, Date1.Hour, Date1.Minute, Date1.Second,
Date1.Millisecond).

If we construct Date1 by using an integer Millisecond or Second, like this:
Date1 = new DateTime(2006, 1, 13);

Date1 will equal Date4.

In your scenario, do you really want to compare such accurate granularity?
Your current code assumes the equality of Tick level. If we modify the
implementation to equality level of Millisecond or Second, the translate
will output equal. For example, when constructing DateTime (EST), we can
use

Date1 = new DateTime(DateTime.Now.Year, DateTime.Now.Month,
DateTime.Now.Day, DateTime.Now.Hour,
DateTime.Now.Minute, DateTime.Now.Second, DateTime.Now.Millisecond);
Then translate it into UTC time, and finally translate it into
DateTime(Korea) . DateTime constructor will do the tick calculation
correctly.

Hope this helps

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
G

Guest

Jeffrey -

Thank you -- its good to know where the issue is coming from.

I am already sort of implementing your advice in my wrapper class, which
operates slightly differently from the simple example I posted. For example,
I do not expose Ticks or Milliseconds anywhere for construction or anything
else - Seconds are all that our application needs. For the specific case
where my class would be the only entity constructing DateTimes, your strategy
should work.

Unfortunately I have to support a variety of different constructors on my
class, some of which take a pre-built (elsewhere outside of my class)
DateTime and adds the time zone support. One of the other uses of this class
is in a client-side application so I have a few constructors that handle
string dates of various types using DateTime.Parse and ParseExact under the
covers to construct a DateTime.

So, I don't always have full control over the construction of the DateTimes
I am wrapping. Since the DateTime Equals and other logical operators take
Ticks into account regardless, simply delegating to DateTime for these
operations did not work, and I decided to supply my own methods/operators
(which only assume a granularity of seconds).

Thanks very much for the help and explanation.
--
Mike Almond
Technology Architect
Getronics



"Jeffrey Tan[MSFT]" said:
Hi Mike,

Thanks for your feedback.

Yes, with this sample code, I can reproduce out your problem.

This issue is caused by the tick property granularity. A tick is
100-nanosecond interval. Because DateTime.Now is not an integer for
Millisecond(it is hard for us a get an integer Millisecond value from
DateTime.Now), so DateTime.Now is different from DateTime(Date1.Year,
Date1.Month, Date1.Day, Date1.Hour, Date1.Minute, Date1.Second,
Date1.Millisecond).

If we construct Date1 by using an integer Millisecond or Second, like this:
Date1 = new DateTime(2006, 1, 13);

Date1 will equal Date4.

In your scenario, do you really want to compare such accurate granularity?
Your current code assumes the equality of Tick level. If we modify the
implementation to equality level of Millisecond or Second, the translate
will output equal. For example, when constructing DateTime (EST), we can
use

Date1 = new DateTime(DateTime.Now.Year, DateTime.Now.Month,
DateTime.Now.Day, DateTime.Now.Hour,
DateTime.Now.Minute, DateTime.Now.Second, DateTime.Now.Millisecond);
Then translate it into UTC time, and finally translate it into
DateTime(Korea) . DateTime constructor will do the tick calculation
correctly.

Hope this helps

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
J

Jeffrey Tan[MSFT]

Hi Mike,

Thanks for your feedback.

Yes, because Equals method uses Tick to do the comparison, it introduced
the granularity you do not want. Maybe we can implement a tool method which
determine if 2 DateTime's difference is less than certain granularity(for
example, 1 second). Below sample code demonstrates this:

public bool DateTimeEqual(DateTime dt1, DateTime dt2)
{
TimeSpan ts=dt1.Subtract(dt2);
if(ts.Ticks< TimeSpan.TicksPerSecond)
{
return true;
}
return false;
}

private void Run()
{
Date1 = DateTime.Now;
Console.WriteLine("Date1 Ticks: {0} TOD.Ticks: {1} ", Date1.Ticks,
Date1.TimeOfDay.Ticks);

Date2 = Date1.ToUniversalTime();
Console.WriteLine("Date2 Ticks: {0} TOD.Ticks: {1} ", Date2.Ticks,
Date2.TimeOfDay.Ticks);

Date3 = Date2.ToLocalTime();
Console.WriteLine("Date3 Ticks: {0} TOD.Ticks: {1} ", Date3.Ticks,
Date3.TimeOfDay.Ticks);

// Create a DateTime equivalent to Date1, using the detailed constructor
Date4 = new DateTime(Date1.Year, Date1.Month, Date1.Day, Date1.Hour,
Date1.Minute, Date1.Second, Date1.Millisecond);
Console.WriteLine("Date4 Ticks: {0} TOD.Ticks: {1} ", Date4.Ticks,
Date4.TimeOfDay.Ticks);

// S/b false -- sanity check.
Console.WriteLine("Date1 == Date2: {0}", DateTimeEqual(Date1, Date2));

// S/b true - .
Console.WriteLine("Date1 == Date3: {0}", DateTimeEqual(Date1, Date3));

// Would expect this to be equal --- but it is false due to Ticks field
???.
Console.WriteLine("Date1 == Date4: {0}", DateTimeEqual(Date1, Date4));

}
This workaround may save you from re-writing your own DateTime class. Hope
it helps

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Top