MATLAB: When converting datenum to datetime, why does the datetime seem to be off by 1 second

datenumdatetimeepsMATLABr2014bround

Before R2014b I used serial date numbers to represent date and time in MATLAB. In R2014b I can use datetime arrays, but when I convert serial date numbers to datetimes, I find that there seems to be a difference of one second between the datetimes and the serial date numbers.
For instance:
>> myDateNum = datenum(2014, 09, 01, 10, 0, 0);
>> datetime(myDateNum, 'ConvertFrom', 'datenum')
ans =
01-Sep-2014 09:59:59
>> datestr(myDateNum)
ans =
01-Sep-2014 10:00:00

Best Answer

This is not a bug in MATLAB's functions for "datetime", "datenum" or "datestr" in R2014b, instead this result is caused by precision limitations of serial date numbers. Serial date numbers contain a number of days, using double precision floating point values. However, 1  hour, 1 minute, and 1 second, i.e. the day fraction 1/24, 1/3600, and 1/86400 cannot be represented exactly in double precision. Therefore, in general, time of day in a serial date number is subject to round-off error. For example, 10 am in the example cannot be represented exactly by a serial date number:
>> format long
>> myDateNum
myDateNum =
7.358434166666666e+05
>> 24*(myDateNum - round(myDateNum))
ans =
   9.999999999068677
As you can see, the hour-part of this serial date number is slightly less than 10. Datetime values have a higher precision, therefore when you convert a datenum value that represents 10 am, it becomes a datetime value just before 10 am. Because the datetime display does not round up, it may seem that the datetime is off by a full second. This is however not the case as can be seen by changing the format of the datetime value:
>> myDateTime = datetime(myDateNum, 'ConvertFrom', 'datenum')
myDateTime =
01-Sep-2014 09:59:59
>> myDateTime.Format = 'dd-MMM-uuuu HH:mm:ss.SSSSS'
myDateTime =
01-Sep-2014 09:59:59.99999
The difference is indeed in the order of the precision of the serial date number value:
>> eps(myDateNum)*24*3600
ans =
1.005828380584717e-05
After converting a datenum to a datetime, you can round the datetime to an exact whole number of hours, minutes or seconds using the "dateshift" function:
>> dateshift(myDateTime, 'start', 'second', 'nearest')
ans =
01-Sep-2014 10:00:00.00000
Or since datenum is basically a double, you could use "eps" function to avoid round off error:
>> myDateNum = datenum(2014, 09, 01, 10, 0, 0)
myDateNum =
   7.3584e+05
>> datetime(myDateNum + eps, 'ConvertFrom', 'datenum')
ans = 
   01-Sep-2014 10:00:00