locked
[Tip]How NOT to use DateTime.Now RRS feed

  • 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 calls

    although (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 or  DateTime.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