datetime sometimes returns NaT and sometimes throws an error - what is the expected behaviour?
Show older comments
Sometimes datetime throws an error if it can't return a valid datetime object from a string input:
dt = datetime(["2026_06" "2026_06_13"], Format = "yyyy_MM_dd")
and sometimes it returns NaT:
dt = datetime(["2026_06_13" "2026_06"], Format = "yyyy_MM_dd")
dt =
2026_06_13 NaT
The second result is what I'd expect from the documentation. I'm having difficulty figuring out what the underlying rule is, since the two examples have the same input apart from the order and the same format. It seems to be to do with whether the first element of the array can be interpreted according to the format.
It looks like I have to put every call to datetime in a try-catch block and also test the result result for NaT, if I'm to detect any non-conforming input. This seems excessive - can anyone suggest a better way, or whether this is actually a bug I should try to report?
23 Comments
Stephen23
on 13 Jun 2026
Report it as a bug. This behavior (giving up on the first element) kinda defeats the point of having NaTs in the first place.
dpb
on 13 Jun 2026
"I'm having difficulty figuring out what the underlying rule is..."
The rule is it's an error if the first element in the input array doesn't match the specified format while the second case does match and a subsequent doesn't.
Now, as @Stephen23 says, that's not particularly user-friendly behavior and while it probably doesn't match the formal definition of a bug, it is, difficult to handle generically and is at least worthy of a strong request for an enhancement for consistent behavior.
The doc notes all elements in the input array must have the same format; for a case where didn't have control of the input I resorted to scanning the input array first to ensure it was consistent so could fix up bum ones before converting. I found that simpler than having to fix up after the fact or having an error handler.
One approach that would make it much more deterministic would be to offer a user-option, e.g. "ParseFailureAction" with possible values of "error" or "NaT". That would let the user decide what response they require from malformed date formats. The current behavior is not predictable or obvious.
dpb
on 13 Jun 2026
Yeah, that would be a relatively easy fix that shouldn't require much, if any, restructuring of the code itself. I guess having that also would end up with one of the two as the default behavior which would also be one or the other for all cases.
There's been a lot of effort towards improving performance of the parsing of string input over last several releases but the above behavior is consistent back at least as far as R2017b although the syntax has to use the name,parameter comma-separated pairs instead of assignment.
The behavior is predictable; just that one has to test first to know which is going to be the result. That doesn't alleviate writing code to deal with the two possibilities, though, granted....
David Young
on 14 Jun 2026
On reflection, one possible issue and peraps why it behaves as it does is, what is it to do in the case if user asked for NaT and it's the first one that fails instead of error? Return an array of NaT the input size instead, I guess?
It would seem it might require a (significant?) change in logic to switch from the present behavior that the first errors to then continue to try all subsequent to only return NaT by position; the presupposition may be that if the first fails it's likely the input format description is simply wrong for the given input array, not that just one or a few may be inconsistent but others may be ok...which condition violates the documented requirement that all be the same format.
When InputFormat is specified, the doc states: "All values in the input argument DateStrings must have the same format." as @dpb noted. It seems to me that neither case in the question meets that requirement, so both should error.
But is that not also the requirement for the case where InputFormat is not specified for which the "same format" requirement is absent? Apparently not (and the doc page does not say that it is):
s1 = "24-Oct-2014 12:45:07";
s2 = "2026-06-13";
datetime([s1,s2])
datetime([s2,s1])
"The datetime function creates a NaT value automatically when it cannot convert text to a datetime value, ..."
By the plain language (or any other frankly) reading of that statement, neither case of the OP should error and both should return NaT for any entry that doesn't match the InputFormat (and if actually implemented would obviate the "same format" requirement in datetime because anything that doesn't match InputFormat would just return NaT anyway).
Seems like the datetime behavior and relevant doc pages are all inconsistent with each other.
dpb
on 14 Jun 2026
@Paul -- True that.
The same format requirement was imposed from the beginning clear back with the introduction in R2014 (I don't recall now if it made a or b was first). I just stumbled over a ten year old Answers of essentially the same issue in which I responded showing how to test and then use @cellfun to process the two or more separate formats.
@Peter Perkins was involved in the thread from Mathworks side; however, the idea of an additional paramater to control whether an error would or would not be generated didn't come up; it concentrated on workarounds to deal with multiple input formats.
"It seems to me that neither case in the question meets that requirement, so both should error."
The function STR2DOUBLE does not throw an error for such cases, it attempts all and returns NaN for those that it cannot convert. So there is precedent for the behavior of DATETIME returning NaT for those elements. Simply throwing an error is obstructive.
Throwing an error might be obstructive, but it's a reasonable outcome given the doc statement "... must have the same format. (emphasis added)"
Perhaps that statement should be removed and datetime should be corrected so that it returns NaT for any entry that doesn't match the required format, whether that format be explicitly specified by InputFormat or implicitly specified by the first entry in an array input. Adding a name-value pair to give the user control over what happens if an input entry is mismatched with the format could be helpful.
Keep in mind that the user can explicitly request a NaT output
[datetime("NaT"),datetime("NaT",'InputFormat','yyyy-MM-dd')]
Is the "NaT" input always to be considered as matching the format or not matching the format?
Like a NaN, the display format is immaterial to the internal representation so asking for a NaT will always produce one. I guess that can be interpreted as "always matches".
nat=[datetime("NaT"),datetime("NaT",'InputFormat','yyyy-MM-dd')]
[nat(1).Format; nat(2).Format]
shows there's only one format property for the array, not by element in the array and that the first kid on the block controls...
nat=[datetime("NaT",'InputFormat','yyyy-MM-dd','Format','yyyy-MM-dd') datetime("NaT")]
nat.Format
While not explicitly documented as is with NaN, the same comparisons hold
[NaT==NaT NaT~=NaT]
Amplifying some on the above example of implicit format string interpretation--
s1 = "24-Oct-2014 12:45:07";
s2 = "2026-06-13";
datetime([s1 s2])
ans.Format
datetime([s2 s1])
ans.Format
illustrates that without the explicit format, datetime tries to figure it out on its own and then sets the 'InputFormat' property to whatever it decides is the one to use -- and then fails it a subsequent string doesn't match. So, the requirement that all are the same format still holds, and the behavior is the same as if the 'InputFormat' string were given and matched the first element correctly. Hence as in @Paul's example, that case is NOT going to error but return the NaT for elements not matching that format. The requirement is there even if not explicitly documented, yes.
The following examples show another wart in that behavior is different for the same possible format ambiguity depending upon which format is given
s = "2026-06-01";
datetime(s)
s = "06/01/2026";
datetime(s)
Both end up presuming the month is June, but both are ambiguous although it's undoubtadly more common to write yyyy/mm/dd than yyyy/dd/mm or mm/dd/yyyy over dd/mm/yyyy, why warn one but not the other?
Paul
on 14 Jun 2026 at 18:50
What should the output be for the following line, assuming the proposed name-value pair is in effect
[datetime("NaT",ParseFailureAction="error"),datetime("NaT",'InputFormat','yyyy-MM-dd',ParseFailureAction="error")]
On the prior assumption that "NaT" is format invariant, there is no error so both are NaT.
One could probably argue for another interpretation, but to me would be inconsistent in expecting a string representation of "NaT" to not match any format because there is no associated format, just a value.
Now, if one were to do something like
%datetime(NaT,'Format','default','InputFormat','yyyy-MM-dd',ParseFailureAction="error")
one might think it should error as the display format isn't the same as the default format --
n=NaT;
n.Format='default';
n.Format
and those don't match and there is an associated format with the object. But, it isn't a string input so
However, that would also be inconsistent with datetime current behavior with datetime variables that; since they're already datetime, get passed through even if their display formats don't match an 'InputFormat' string; only if one converts back to the string representation will it then fail. But, all there is in a string representation of a NaT is "NaT"; there is no other formatting info available once the variable is lost.
Of course, to further complicate things, datetime returns a single array output variable that has one and only one Format property for all its members so it either has to be specified or it will be the current in effect default (and, otomh, I'm not sure one can set a user preference default or not).
Like NaN, a NaT is a peculiar animal and it's easy to dream up strange things with them; this is, I think, especially true with NaT since it's a derived class specific to MATLAB as opposed to an IEEE Standard and there is a defined bit pattern for it.
That's an initial thinking; deeper diving might reveal other issues...
James Tursa
on 15 Jun 2026 at 22:50
Edited: James Tursa
on 15 Jun 2026 at 23:08
Separate from the issue of what datetime( ) should do for invalid/irregular input, note that both datetime and duration class objects store the data as double precision variables, so there is an actual IEEE NaN bit pattern in the background. E.g.,
snan = seconds(nan)
struct(snan)
class(ans.millis)
dnan = datetime(2000,1,1) + snan
struct(dnan)
class(ans.data)
You can see the data areas of both variables contain doubles, so there is in fact an IEEE double defined bit pattern in the background for these NaN and NaT values for what it's worth.
The real difference between datetime and duration class objects is that datetime variables contain an imaginary correction factor to keep sub-nanosecond accuracy, whereas duration class variables do not. A big oversight IMO, so you have to be very careful and write special code to process differences if you want to maintain that accuracy. E.g.,
format longg
dt0 = datetime(1970,1,1)
dt1 = datetime(2000,1,1,1,1,12.1234567891234)
dur = dt1 - dt0
dt2 = dt0 + dur
dt1.Second
dt2.Second
You can see that the seconds precision has been lost in the arithmetic. The reason is the storage scheme is different between datetime and duration. E.g.,
struct(dt1)
struct(dur)
struct(dt2)
That imaginary part of the dt1 data is a correction factor to maintain sub-nanosecond accuracy in storage. But the dur storage doesn't have that, so that level of precision is lost and doesn't appear in the follow on dt2 construction. If you need to have sub millisecond accuracy, you generally need to write your own code for datetime arithmetic. E.g., pick off the Second manually and do your own datetime arithmetic w/o seconds as usual and then do the Seconds arithmetic off to the side and added in after the fact. E.g.,
s0 = dt0.Second; % pick off dt0 seconds
s1 = dt1.Second; % pick off dt1 seconds
temp0 = dt0; temp0.Second = 0; % temp datetime w/o seconds
temp1 = dt1; temp1.Second = 0; % temp datetime w/o seconds
dur = temp1 - temp0; sdur = seconds(s1 - s0); % difference temp datetimes and seconds
dt3 = dt0 + dur + sdur % final result with seconds result added in manually
dt3.Second
Now you get the full precision result, but it is messy to say the least.
(Hey, Mathworks ... it's not too late to enhance duration for this!)
This example also demonstrates that the data value in datetime objects happens to be the number of milliseconds since 1970-01-01.
dpb
on 16 Jun 2026 at 12:48
I should have figured the NaT was NaN in sheep's clothing...good digging, that's good supplementary data for the thread, indeed.
Steven Lord
on 16 Jun 2026 at 16:23
That imaginary part of the dt1 data is a correction factor to maintain sub-nanosecond accuracy in storage.
See this post on Cleve's blog. "I am pleased to reveal some details about the arithmetic in datetime. The object is using a form of quadruple precision known as double-double. In fact, the second half of the 112-bit format is stored as the imaginary part of the data field, as you see in the following."
I recall at least one Answers thread that the poster was bit by the loss of precision of the duration class. Was looking at recordeded timestamps to nanosecond precison but wanted the diff() to see consistency. The simple-minded solution didn't work, leaving him/her(?, I forget) puzzled. Fortunately, it was easy in his case to simply scale up first, but is inconvenient and seems strange to not have the two be consistent in internal precision.
James Tursa
on 16 Jun 2026 at 21:59
Edited: James Tursa
on 16 Jun 2026 at 22:09
Related posts:
James Tursa
on 16 Jun 2026 at 22:14
Edited: James Tursa
on 16 Jun 2026 at 22:21
There is also a discrepancy on string input parsing. E.g.,
format longg
dt = datetime(2000,1,1,1,1,12.1234567891234)
dt.Second
dt = datetime("2000-01-01 01:01:12.1234567891234")
dt.Second
dt = datetime("2000-01-01T01:01:12.1234567891234Z",'TimeZone','UTCLeapSeconds')
dt.Second
Apparently when processing input strings, the datetime parser cuts off the input at nanoseconds (I seem to recall this may be noted in the documentation). If you want to retain full input precision, you have to manually parse at least the seconds part of the string yourself and add it in. E.g.,
dt = datetime("2000-01-01T01:01:00Z",'TimeZone','UTCLeapSeconds') + seconds(12.1234567891234)
dt.Second
datetime does mention that limitation in the input description for infmt for the InputFormat parameter...
"For input text that represents fractional seconds, you can specify infmt with up to nine S characters to indicate fractional second digits."
The Description also states that a datetime provides
- Storage for fractional seconds out to nanosecond precision
so there's no repreesentation of being able to actually use the full available storage even though implemented internally.
It and duration do still seem like a work in progress that has lost attention in the push for fancy gewgaws instead.
Another wart with duration and precision...
s=[0:9]/1E9; % a sequence of nsec values
fmt='HH:mm:ss.SSSSSSSSS'; % show just time fields
dt=datetime(2026,1,1,0,0,s*2,'Format',fmt).'
du=diff(dt);
fmt='hh:mm:ss.SSSSSSSSS'; du.Format=fmt
There's still a FP rounding issue that the difference doesn't show a 2 nsec difference between all elements. I don't know if this is solvable or not?
You'll also notice that the format string for the duration is all lower case except for the capital S for fractional seconds. This is an inconsistency/inconvenience in comparison to the datetime and two conventions for related functions that have to keep in mind.
James Tursa
on 23 Jun 2026 at 2:18
Edited: James Tursa
on 23 Jun 2026 at 2:21
"There's still a FP rounding issue that the difference doesn't show a 2 nsec difference between all elements."
So, this is a display feature. I believe it happens for both datetime and duration objects. The display is truncated instead of rounded, and I am not aware of any way to change that. For this particular example, this is not an issue with the duration class per se. However, it does mean that if you want to output a datetime or duration as a string and be able to read it back in and get the original, you will need to write your own string conversion routine (at least for the sub-seconds part).
E.g.,
s=[0:9]/1E9; % a sequence of nsec values
fmt='HH:mm:ss.SSSSSSSSS'; % show just time fields
dt=datetime(2026,1,1,0,0,s*2,'Format',fmt).';
du=diff(dt);
fmt='hh:mm:ss.SSSSSSSSS'; du.Format=fmt
format longg
for k=1:numel(du)
sprintf('%35.30f',seconds(du(k)))
end
dt = datetime(2000,1,1,1,1,12.6)
Good catch, @James Tursa....was fixated on the display formatting available for the duration being limited to only the nine decimal places which also seems to be an unnecessary limitation.
ADDENDUM
" The display is truncated instead of rounded,..."
That is inconsistent with and in contradiction to the behavior of all other format specifiers for decimal precision. Should that not be considered an implementation shortfall (if not an actual bug since there isn't a comparable standard C library function spec to compare to as with the others)?
s=[0:9]/1E9; % a sequence of nsec values
fmt='HH:mm:ss.SSSSSSSSS'; % show just time fields
dt=datetime(2026,1,1,0,0,s*2,'Format',fmt).';
du=diff(dt);
fmt0='hh:mm:ss.';
nDec=9; % start at nosec
fmt=[fmt0 repmat('S',1,nDec)];
du.Format=fmt;
disp('Nine digit format succeeded')
try
nDec=nDec+1; % ask for an extra digit
fmt=[fmt0 repmat('S',1,10)];
disp('Trying one additional digit precision')
du.Format=fmt;
catch ME
disp(ME.message)
end
disp(num2str(seconds(du),'%0.10f'))
The above produces the expected result; it "just seems wrong" that
- You can't set an arbitrary precision for output on a duration variable (or datetime, either, I presume, although I didn't test it)
- It truncates the displayed value at the number of digits of precision instead of rounding as per C Standard i/o library formatting functions are required to do.
Answers (1)
dpb
on 14 Jun 2026 at 18:46
0 votes
The short anser is "Both!"
Sorry, couldn't help myself <very big grin> )
Categories
Find more on Dates and Time in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!