|
dev
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
DateTime Equals method & TicksEquals 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, -- Mike Almond Technology Architect Getronics Mike Almond <AlmondJoy@newsgroups.nospam> wrote:
Show quote > What determines if two DateTime objects compare equal using the DateTime Well, you won't be able to override Equals as DateTime is a value type > 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.. 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. -- Jon Skeet - <sk***@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too 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 Show quote "Jon Skeet [C# MVP]" wrote: > Mike Almond <AlmondJoy@newsgroups.nospam> wrote: > > 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. > > > -- > Jon Skeet - <sk***@pobox.com> > http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet > If replying to the group, please do not mail me too > Mike Almond <AlmondJoy@newsgroups.nospam> wrote:
> Thanks for the quick response. I have located the problem. To do some of There is a constructor which takes the ticks value - it's the one which > 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. taks the long (Int64) value. Does that sort out your problem? -- Jon Skeet - <sk***@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too 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, -- Show quoteMike Almond Technology Architect Getronics "Jon Skeet [C# MVP]" wrote: > Mike Almond <AlmondJoy@newsgroups.nospam> wrote: > > 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? > > -- > Jon Skeet - <sk***@pobox.com> > http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet > If replying to the group, please do not mail me too > 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. 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. -- Show quoteMike Almond Technology Architect Getronics ""Jeffrey Tan[MSFT]"" wrote: > 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. > > 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. 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)); } } } -- Show quoteMike Almond Technology Architect Getronics ""Jeffrey Tan[MSFT]"" wrote: > 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. > > 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. 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. -- Show quoteMike Almond Technology Architect Getronics ""Jeffrey Tan[MSFT]"" wrote: > 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. > > 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. |
|||||||||||||||||||||||