Asked by:
[Tip]How NOT to use DateTime.Now

Question
-
User1428246847 posted
I often see constructions like
DateTime dt = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
The problem is that DateTime.Now can change between the successive 'calls'. If you execute the above just before midnight at the end of the month, you might get 2014-08-01 (instead of 2014/08/31) because DateTime.Now has rolled over to the first day of the next month before retrieving DateTime.Now.Day. You might think that your chances are slim that this happens, but that's actually not the case; it's also beside the point.
The better construction is to store DateTime.Now and use that.
DateTime dt = DateTime.Now; dt = new DateTime(dt.Year, dt.Month, dt.Day);
Note that the described problem applies to milliseconds, seconds, minutes, hours, days, months and years, not just day as in the example.
Sunday, August 24, 2014 5:33 AM
All replies
-
User-1806150748 posted
Thanks for information!!
Sunday, August 24, 2014 11:44 AM -
User-434868552 posted
@wim sturkenb... thank you for sharing. Learning only has true value when it’s shared.
i've analyzed your code for efficiency and practicality.
/* a */ DateTime dt = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day); /* b */ DateTime dt = DateTime.Now; dt = new DateTime(dt.Year, dt.Month, dt.Day);
looking at the IL:
=================== a ========================= ================== b ========================== IL_0001: ldloca.s 00 // dt IL_0003: call System.DateTime.get_Now ..... IL_0001: call System.DateTime.get_Now IL_0008: stloc.1 // CS$0$0000 ..... IL_0006: stloc.0 // dt IL_0009: ldloca.s 01 // CS$0$0000 ..... IL_0007: ldloca.s 00 // dt ..... IL_0009: ldloca.s 00 // dt IL_000B: call System.DateTime.get_Year ..... IL_000B: call System.DateTime.get_Year IL_0010: call System.DateTime.get_Now IL_0015: stloc.1 // CS$0$0000 IL_0016: ldloca.s 01 // CS$0$0000 ..... IL_0010: ldloca.s 00 // dt IL_0018: call System.DateTime.get_Month ..... IL_0012: call System.DateTime.get_Month IL_001D: call System.DateTime.get_Now IL_0022: stloc.1 // CS$0$0000 IL_0023: ldloca.s 01 // CS$0$0000 ..... IL_0017: ldloca.s 00 // dt IL_0025: call System.DateTime.get_Day ..... IL_0019: call System.DateTime.get_Day IL_002A: call System.DateTime..ctor ..... IL_001E: call System.DateTime..ctor
on an ACER Aspire 7745 (i3 intel M350 2.27 GHz, 4GB, 64-bit, win7 SP1), for one million iterations,
(a) is almost 2.6 times slower than (b).
(a) =~ 3.78 seconds per million calls
(b) =~ 1.48seconds per million callsalthough (a) is shorter to code, more IL code gets generated.
The problem is that DateTime.Now can change between the successive 'calls'.if you execute code variation (a) above just before midnight at the end of the month, you might get 2014-09-01 (instead of 2014/08/31) because DateTime.Now has rolled over to the first day of the next month before retrieving DateTime.Now.Day.
You might think that your chances are slim that this happens, but that's actually not the case;
actually, the chances are very slim:
Year would be wrong if the code crossed the boundary between December 31st and January 1st ... less than one in c. 31.5 million.
Month would be wrong if the code crossed the boundary between the end of the month and the first of the following month ... less than one in c. 2.6 million.
Day would be wrong if the code crossed the midnight boundary ... less than 1 in 86400.
Given that the code compiles and runs in under 3 milliseconds and runs in neglible time, the above odds should be multiplied by at least 1000.
Wim, there is ultra low risk that predictable failure would occur, whether it was significant would matter only in some applications and even then the effect of being off by a day might be insignificant.
CONCLUSION
my preference is Wim's example (b) which imho for performance is for all intents and purposes only slighty better than (a) except where the code is executed a substantial number of times; for robustness, Win's example (b) never fails versus example (a)'s negligible chance of failure. Most of all is that imho it is important to code as per Wim's example (b) because that helps us write clearer code (no hidden glitches--not even neglible glitches, plus greater efficiency).
Saturday, October 11, 2014 1:26 PM -
User1428246847 posted
Hi Gerry, thanks for the response and I'm glad that you agree on using option (b) :-)
actually, the chances are very slim:I'm not a statistics or mathematics person, but my approach to this is a bit different. If the millisecond component of DateTime.Now rolls over, there is a chance that any of the other components has an unexpected value. I wrote the below console application to test this out.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DateTimeNow { class Program { static void Main(string[] args) { DateTime dtStart = DateTime.Now; DateTime dt1 = dtStart; DateTime dt2; long cnt = 0; int cntSeconds = 0; int cntMinutes = 0; int cntHours = 0; int cntDays = 0; int duration=120; Console.WriteLine(String.Format("Looping for {0} minute(s)", duration)); Console.WriteLine(String.Format("Start: {0}", dtStart.ToString("HH:mm:ss"))); while (dt1 < dtStart.AddMinutes(duration)) { cnt++; dt1 = DateTime.Now; dt2 = DateTime.Now; if (dt1.Millisecond > dt2.Millisecond) cntSeconds++; if (dt1.Second > dt2.Second) cntMinutes++; if (dt1.Minute > dt2.Minute) cntHours++; if (dt1.Hour > dt2.Hour) cntDays++; } Console.WriteLine(String.Format("End: {0}", DateTime.Now.ToString("HH:mm:ss"))); Console.WriteLine(String.Format("Iterations: {0}", cnt)); Console.WriteLine(String.Format("Missed seconds: {0}", cntSeconds)); Console.WriteLine(String.Format("Missed minutes: {0}", cntMinutes)); Console.WriteLine(String.Format("Missed hours: {0}", cntHours)); Console.WriteLine(String.Format("Missed days: {0}", cntDays)); Console.ReadLine(); } } }
It loops for a number of minutes and counts where a DateTime component overflows. The results are quite shocking on my Core2Duo (2.1GHz and 4GB, Win7 Pro SP1 64bit); I've ran it with varying durations (up till 12 hours). There is roughly 20% chance that the millisecond component rolls over (indicated by the missed seconds). Minutes are in the same magnitude, hours are currently a little lagging (around 15% (updated after another run to 17%)) and days are in the same magnitude (only encountered 4 day boundaries till now, so not really representative).
I understand that statistics are only valid over large numbers of iterations, but there is no doubt in my mind (I know, not a scientific approach) that if I run this over a couple of years (and add missed months as well), all components will show a roughly 20% chance of incorrect dates on my system. So at the end of this month, you actually have a 20% chance that the date will be 2014/10/01 instead of 2014/10/31 or 2014/11/01.
start end duration iterations seconds minutes hours days seconds % minutes % hours % days % 13:25:09 14:25:09 3600 863 10 0 23.97 16.67 0.00 18:33:37 21:33:37 10800 2532 39 0 23.44 21.67 0.00 10:02:54 13:02:54 10800 2637 43 0 24.42 23.89 0.00 13:11:19 16:11:19 10800 2565 40 1 23.75 22.22 33.33 16:13:43 19:13:43 10800 2708 37 1 25.07 20.56 33.33 19:16:39 01:16:39 21600 4451 81 0 0x 20.61 22.50 0.00 0 06:24:46 07:24:46 3600 748 8 0 0 20.78 13.33 0.00 16:13:35 04:13:35 43200 9466 158 3 0x 21.91 21.94 25.00 0 04:18:43 16:18:43 43200 8967 171 3 0 20.76 23.75 25.00 20:12:02 04:12:02 28800 5905 98 2 0x 20.50 20.42 25.00 0 11:32:34 19:32:34 28800 5962 100 1 0 20.70 20.83 12.50 19:35:03 03:35:03 28800 6189 89 2 1x 21.49 18.54 25.00 100 07:47:57 09:47:57 7200 1545 29 1 0 21.46 24.17 50.00 Average 22.22 20.81 17.63 25.00
Note: the 'x' next to days indicate that a day boundary was crossed during the test. Also, the first couple of runs do not have a days value as I did not implement that yet.
The number of iterations depends on how busy the system is doing other things. Not filled in in above table, but one iteration takes on average around 600-700 nanoseconds when the system is relatively quiet.
Wim
Thursday, October 16, 2014 4:34 AM -
User-1631058530 posted
Why just not use
DateTime dt = DateTime.Today;
?
Tuesday, January 13, 2015 7:54 AM -
User-991316286 posted
I often see constructions like
DateTime dt = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
Not sure why someone use such code, you can use either
DateTime.Today
orDateTime.Now.Date
(both are equivalent)but better to read http://stackoverflow.com/questions/6545254/difference-between-system-datetime-now-and-system-datetime-today
Tuesday, January 13, 2015 8:11 AM -
User-434868552 posted
@Egor Witkovs... and damithSL
You are both correct:
DateTime dt = DateTime.Now; dt = new DateTime(dt.Year, dt.Month, dt.Day); Console.WriteLine (dt); Console.WriteLine (DateTime.Today); Console.WriteLine (DateTime.Now.Date);
output:
2015-01-13 00:00:00 2015-01-13 00:00:00 2015-01-13 00:00:00
FWIW, i suspect Wim chose the example to demonstrate a different principle; when all one wants is the year, month, and day, the i'd use DateTime.Today for my local time, or DateTime.UtcNow.Date for UTC.
if we compare IL:
Console.WriteLine (DateTime.Today); // shortest IL
IL_0001: call System.DateTime.get_Today IL_0006: box System.DateTime IL_000B: call System.Console.WriteLine
Longer:
Console.WriteLine (DateTime.Now.Date); // note the extra call to System.DateTime.get_Date
IL_0001: call System.DateTime.get_Now IL_0006: stloc.0 // CS$0$0000 IL_0007: ldloca.s 00 // CS$0$0000 IL_0009: call System.DateTime.get_Date IL_000E: box System.DateTime IL_0013: call System.Console.WriteLine
even longer:
DateTime dt = DateTime.Now; dt = new DateTime(dt.Year, dt.Month, dt.Day); Console.WriteLine (dt);
IL_0001: call System.DateTime.get_Now IL_0006: stloc.0 // dt IL_0007: ldloca.s 00 // dt IL_0009: ldloca.s 00 // dt IL_000B: call System.DateTime.get_Year IL_0010: ldloca.s 00 // dt IL_0012: call System.DateTime.get_Month IL_0017: ldloca.s 00 // dt IL_0019: call System.DateTime.get_Day IL_001E: call System.DateTime..ctor IL_0023: nop IL_0024: ldloc.0 // dt IL_0025: box System.DateTime IL_002A: call System.Console.WriteLine
BOTTOM LINE: code that may look identical is not necessarily equivalent even though the results may be the same.
Tuesday, January 13, 2015 12:21 PM