Dates and Times

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

Written against MATLAB R2026b. datetime, duration and calendarDuration are not in GNU Octave 11.3.0, so the code that uses them is not run here and is checked against the R2026b documentation; where a result is shown for such code it is the display the documentation describes for the same call. The legacy datenum/datestr snippets in the last section were run in Octave 11.3.0 (MATLAB code that Octave also accepts) and show Octave’s output. All the types on this page are in base MATLAB; no toolbox is needed.

MATLAB has a separate data type for each way of talking about time:

Type Represents Unit of a difference

datetime

A point in time, such as 22 October 2026, 09:00

Subtracting two points gives a duration

duration

A length of time in fixed-length units: 1 day is always 24 hours and 1 year is always 365.2425 days

Exact and uniform, displayed as hh:mm:ss or in a chosen unit

calendarDuration

A length of time in calendar units of variable length: a month may be 28, 29, 30 or 31 days, a day may be 23 or 25 hours across a daylight-saving change

Adding it to a datetime gives another datetime

The first two are introduced with the examples below; calendarDuration has its own section. Tables whose rows are indexed by time are on Timetables, and a missing time is the NaT value described here and on Missing Data.

Creating a datetime

datetime has existed since R2014b. A datetime array stores the year, month, day, hour, minute and second of each point in the proleptic ISO calendar, with fractional seconds out to nanosecond precision. The main syntaxes are:

t = datetime                              % the current date and time
t = datetime(relativeDay)                 % "today", "tomorrow", "yesterday", "now"
t = datetime(DateStrings)                 % text that datetime can recognise
t = datetime(DateStrings,InputFormat=infmt)
t = datetime(Y,M,D)                       % numeric year, month, day
t = datetime(Y,M,D,H,MI,S)                % ... plus hour, minute, second
t = datetime(Y,M,D,H,MI,S,MS)             % ... plus milliseconds
t = datetime(DateVectors)                 % rows of [Y M D H MI S]
t = datetime(X,ConvertFrom=dateType)      % from numbers: "datenum", "excel", "posixtime", ...

The numeric inputs may be arrays of the same size, or a scalar combined with arrays; the documentation shows an example with year and day arrays and a scalar month. Name-value arguments must come after the other arguments, and the Name=Value form used here is available from R2021a (before that, write 'Name',Value with the name quoted).

% Not run here (no datetime in Octave).
t0  = datetime(2026,10,22,9,0,0);                       % 22 Oct 2026, 09:00:00
ts  = datetime(2026,10,22,9,0,0:30:90);                 % three readings, 30 s apart
ti  = datetime("2026-10-22 09:00:00");                  % text in a recognised format
tn  = datetime("22/10/2026",InputFormat="dd/MM/yyyy");  % text that needs a format
tnow = datetime("now");                                 % current time

Text input: InputFormat versus Format

When the text is in a format datetime cannot recognise unambiguously, such as 22/10/2026 versus 10/22/2026, give the format of the input with InputFormat. Two rules from the documentation matter:

  • The InputFormat value must be a character vector or string scalar even when the input is a string array, and all the values in one call must share that format.

  • InputFormat applies only to the conversion from text. The result is displayed with the default format (or the one in the Format name-value argument) and not with the input format.

Format is a separate setting, a property of every datetime array, that controls only how values are displayed and converted to text. Changing it never changes the stored values.

% Not run here.
t = datetime(2026,10,22,9,0,0:30:90);
t.Format = "yyyy-MM-dd HH:mm:ss.SSS";   % display with milliseconds
t.Format = "eeee, MMMM d, y";           % Thursday, October 22, 2026
str = string(t)                         % text, using the current Format

The letters of the format follow the Unicode CLDR conventions and differ from the legacy datestr letters:

Letter Meaning in a datetime format

yyyy, uuuu

Year (uuuu is the extended year)

MM, MMM, MMMM

Month as 10, Oct, October

dd, d

Day of the month

eeee, eee

Day of the week, full or abbreviated name

HH, hh, a

Hour (24-hour, 12-hour) and the AM/PM marker

mm

Minute (lowercase)

ss, SSS

Seconds and fractional-second digits

Z, XXX, B

Time-zone offset in two forms (Z gives offsets such as -0400) and the time-standard label

The classic bug is the case of M: in datetime formats MM is the month and mm is the minute, while the legacy datestr uses mm for the month and MM for the minute. A format copied from old datenum/datestr code swaps the two without an error. Since R2020a the InputFormat identifiers e, ee, M, MM, Q and QQ do not recognise names, while eee, MMM and QQQ do not recognise full names, so a format that used to read Oct as a month name with MM now needs MMM.

ISO 8601 text with a literal T and a time-zone offset needs the T in single quotes inside the format, written twice in a MATLAB character vector, and the target time zone:

% Not run here; this is the call from the documentation.
DateStrings = {'2014-05-26T13:30-05:00';'2014-08-26T13:30-04:00';'2014-09-26T13:30Z'};
t = datetime(DateStrings,'InputFormat','uuuu-MM-dd''T''HH:mmXXX','TimeZone','UTC')

The three offsets are converted to UTC and displayed (per the documentation) as 26-May-2014 18:30:00, 26-Aug-2014 17:30:00 and 26-Sep-2014 13:30:00. Text in another language needs Locale, for example Locale="fr_FR" for French month names. PivotYear sets the start of the 100-year range used for two-digit years and defaults to 50 years before the current year.

Components and assignment

The components are properties and are also read with the functions year, quarter, month, day, hour, minute and second. Assigning to a property changes the value.

% Not run here.
t = datetime(2026,10,22,9,0,0:30:90);
t.Year, t.Month, t.Day, t.Hour, t.Minute, t.Second   % properties
month(t)                                             % the same component by function
t.Day = t.Day + 1;                                   % move each reading one day later
[y,m,d] = ymd(t);                                    % several components at once
t.Month = 13                                         % overflow rolls into the next year

Out-of-range numbers assigned to a component, as with t.Month = 13, are normalised into the next larger unit in the usual calendar way (not run here, so check the result in your release). Component comparisons such as t.Month == 10 compare numbers, not points in time; compare whole values with the relational operators below.

duration: fixed-length time

Subtracting two datetime arrays gives a duration, displayed by default as hh:mm:ss. The functions seconds, minutes, hours, days and years create a duration and set its display unit to match; the same functions applied to a duration give the length as a plain number.

% Not run here.
t1 = datetime(2026,10,22,9,0,0);
t2 = datetime(2026,10,22,9,45,30);
d  = t2 - t1                 % duration, displayed 00:45:30
d.Format = "s";              % show it in seconds: 2730 sec
secs = seconds(d)            % 2730, a plain double
len  = hours(72) + minutes(10)   % 72 hours and 10 minutes
span = days(2.5)             % 2.5 days, each day exactly 24 hours

A duration of days(1) always equals 24 hours and a years(1) is always 365.2425 days, so it is the right type for elapsed-time arithmetic, sensor intervals and time steps. duration text such as duration("08:17:43") is converted directly, without a datetime. Note that seconds(d) returns a number but a bare d keeps the type; computations that need plain numbers, such as the heat-equation time step, call seconds.

calendarDuration and calendar arithmetic

A month is not a fixed number of days, a day is not always 24 hours (daylight-saving changes) and a year is not always 365 days. For arithmetic in calendar units, create a calendarDuration with caldays, calweeks, calmonths, calquarters and calyears. Components of different units stay separate until the value is added to a datetime, because only then do the lengths of the actual months matter.

% Not run here.
L  = calmonths(2) + caldays(35)   % 2mo 35d  (shown by the documentation as "2mo 35d")
t2 = datetime(2014,6,28,6:7,0:1,0);
t3 = t2 + L                       % each point moved two months and 35 days later

The documentation shows the result for the two points 28 June 2014 06:00:00 and 07:01:00 as 02-Oct-2014 06:00:00 and 02-Oct-2014 07:01:00. In R2026b, a calendarDuration created with calmonths or calquarters can also preserve end-of-month or end-of-quarter dates in arithmetic through the ArithmeticMethod name-value argument, so adding one month to 30 June can give 31 July instead of 30 July. By default the arithmetic preserves the day of the month when possible. A calendarDuration does not support the relational operators (<, ==), because calendar units are not fixed lengths.

Want Use

The exact elapsed time between two instants

t2 - t1 (a duration)

The difference in calendar terms, such as "1 month 3 days"

between(t1,t2) (a calendarDuration such that t2 = t1 + dt)

The same day next month, or the same clock time across a daylight-saving change

t + calmonths(1) or t + caldays(1)

Exactly 24 hours later

t + hours(24) or t + days(1)

between(t1,t2) takes leap years, daylight-saving shifts and the different lengths of months into account. between(t1,t2,components) expresses the difference in the chosen components only, for example the whole months with "months" (the replacement the documentation gives for months). caldiff computes the successive calendar differences within an array. The reference pages for between and caldiff load at the URLs listed in the references.

Moving to boundaries: dateshift

dateshift moves each value to the start or end of a unit, or to a named weekday, and keeps the array size:

t2 = dateshift(t,"start",unit)           % unit: "year" "quarter" "month" "week" "day" "hour" "minute" "second"
t2 = dateshift(t,"end",unit)
t2 = dateshift(t,"dayofweek",dow)        % the given day of week on or after each value
t2 = dateshift(t,"dayofweek","weekend")  % the next Saturday or Sunday
t2 = dateshift(t,"dayofweek","weekday")  % the next Monday-Friday

For example, shifting 9:14 a.m. on 27 June 2021 to the start of the hour gives 9:00 a.m., and to the start of the month gives midnight on 1 June 2021. The end of a day, hour, minute or second is the beginning of the next one, while the documentation states that the end of a year, quarter, month or week is midnight at the beginning of the last day of that unit (so the end of June 2021 is 12:00 a.m. on 30 June). That surprising definition makes dateshift(t,"end","month") a date-only tool; to get the first instant of the next month, add caldays(1) or shift "start" of the following month.

The documentation uses dateshift as the replacement for the legacy eomdate, lweekdate and nweekdate. The last Tuesday of a month, for instance, is the end of the month shifted to the previous Tuesday:

% Not run here; from the documentation.
october = datetime(2021,10,1);
endOfOctober = dateshift(october,"end","month");
lastTuesday = dateshift(endOfOctober,"dayofweek","Tuesday","previous")

Time zones, daylight saving and leap seconds

A datetime is unzoned (floating) by default: its TimeZone property is empty, daylight saving is not applied and no offset is attached. Set TimeZone to an IANA name such as "Europe/Madrid" or to "UTC", or to "local" for the system time zone, to make the value zoned; timezones lists the names, and setting the TimeZone property of a zoned array converts its display, while the TimeZone name-value argument labels the values created from text or numbers. Values keep their absolute instant when you change the property of a zoned array, which changes only the display. SystemTimeZone is a read-only property with the system setting.

% Not run here.
t = datetime("now",TimeZone="Europe/Madrid",Format="d-MMM-y HH:mm:ss Z");
u = t;  u.TimeZone = "UTC";                % same instant, shown in UTC
posix = posixtime(t);                      % seconds since 1970-01-01 UTC
t2 = datetime(posix,ConvertFrom="posixtime",TimeZone="UTC");

Two practical rules follow:

  • Without a TimeZone, a POSIX time or Julian date passed to ConvertFrom is treated as UTC, not as local time, so state the zone when it matters.

  • Comparing or subtracting zoned and unzoned arrays is an error (the identifier and wording vary by release; not run here), so make both arrays zoned or both unzoned. A sensor log that comes from a datalogger without a zone is best kept unzoned or tagged "UTC" once and converted only for display.

A zoned datetime follows daylight-saving rules, so adding caldays(1) on the spring-forward day moves to the same wall-clock time, 23 hours later, while adding days(1) moves exactly 24 hours.

R2026b adds the TimeStandard name-value argument and property. Its values are "none" (the default), "simplifiedUTC", "UTC", "TAI" and "TT". International Atomic Time (TAI) and Terrestrial Time (TT) are useful for aerospace, astronomy and telecommunications, where the simplified UTC that ignores leap seconds is not enough. The documentation’s example creates 00:00:37 on 1 January 2025 in TAI and converts it to TT by assigning the property; the displayed result is 00:01:09.184 TT (the offset between the two scales is 32.184 s). The leapseconds function lists the leap seconds that datetime supports. Programs that must run on earlier releases should not set TimeStandard: code that uses ordinary civil time with TimeZone="UTC" does not need it.

NaT: the missing datetime

NaT (Not-a-Time) is the missing value of a datetime array. It is created automatically when datetime cannot convert a piece of text (per the NaT reference), when a component is set to NaN, or by assignment; NaT(3,4) makes a 3-by-4 array of them, and the NaT function with a Format name-value argument initialises an array that will hold values in that display format. NaT always displays as NaT, and isnat and ismissing find it.

Like NaN, it compares as not equal to everything, including itself, and it propagates through arithmetic:

% Not run here.
t = datetime(2026,10,22,9,0:2,0);
t(2) = NaT;          % assign a missing time
isnat(t)             % 0 1 0
t(2) == t(2)         % 0 (NaT is not equal to itself)
t(2) - t(1)          % NaN of type duration (displayed NaN)

Gaps in a time series such as the heatflow sensor log, where some rows have no usable timestamp, are filled and removed with the missing-data functions (Missing Data). The conversion of a string array or cell array of text to datetime is a place where NaT can appear instead of an error, so check any(isnat(t)) after parsing.

Comparison and ranges

datetime and duration arrays use the ordinary relational operators <, <=, == and ~=. Their sizes must be compatible, and text and numbers are converted before the comparison:

  • A datetime can be compared with text that datetime recognises: d2 >= "2022-06-01". A text value with a date only is read as midnight.

  • A duration can be compared with numbers or with text, but a datetime cannot be compared with a duration; compare components such as hour(t) with numbers or duration values instead.

  • calendarDuration does not support the relational operators.

isbetween(A,lower,upper) returns a logical array that tells which values are within a closed interval by default, and takes an interval type such as "open" as the fourth argument. The result of a comparison is a logical array used to select values (d2(d2 > d1)), as on Logical Type and Conditions.

% Not run here.
d1 = datetime("2022-06-05 11:37:05");
d2 = datetime(2022,2:4:10,15,12,0,0);   % 15 Feb, 15 Jun, 15 Oct 2022 at 12:00
tf = d2 > d1                            % 0 1 1
afterD1 = d2(tf)
june = isbetween(d2,datetime(2022,6,1),datetime(2022,6,30))   % 0 1 0

For a series whose rows are the times, a timerange subscript on a timetable is usually clearer than a mask (see Timetables).

Sequences

t1:caldays(1):t2 and t1:hours(6):t2 make a sequence between two points: the step may be a duration or a calendarDuration, and both give a datetime row vector. dateshift with "start" or "end" aligns an existing sequence to bin boundaries. Adding an array of durations to one start point is just as direct:

% Not run here.
t0 = datetime(2026,10,22,9,0,0);
minutesOfLog = t0 + minutes(0:5);          % six readings, one minute apart
monthly      = datetime(2026,1,31) + calmonths(0:3);   % month arithmetic from a month end

Before datetime existed, MATLAB used two representations: a serial date number, a double that counts days (and fractions of a day) from a fixed origin, made by datenum, and a date string, formatted text made by datestr. As of R2022b both are documented as not recommended for specifying dates and times, and the functions are kept: the documentation states there are no plans to remove the discouraged functions or their serial-number/string syntaxes. The reasons it gives for datetime are flexible input and display formats, storage of fractional seconds to nanoseconds, and properties for time zones, daylight saving and leap seconds.

A serial date number has no unit label, no time zone and a limited resolution, since it is a double near 7.4e5 days. The snippets below were run in Octave 11.3.0, which implements datenum, datestr, datevec and addtodate.

d = datenum(2022,6,28,12,34,56);
fprintf("%.5f\n", d);
disp(datestr(d))
disp(datestr(d, "yyyy-mm-dd HH:MM:SS"))
t1 = datenum(2026,10,22,9,0,0);
t2 = datenum(2026,10,22,9,45,30);
fprintf("%.0f seconds\n", (t2-t1)*86400);
dv = datevec(t2)
d0 = datenum(2022,1,31);
disp(datestr(addtodate(d0,1,"month"), "yyyy-mm-dd"))
fprintf("%d\n", datenum(2022,13,1) == datenum(2023,1,1));

Octave 11.3.0 output:

738700.52426
28-Jun-2022 12:34:56
2022-06-28 12:34:56
2730 seconds
dv =

   2026     10     22      9     45     30

2022-02-28
1

The number 738700.52426 is the serial date number that the MATLAB documentation also uses for 28 June 2022, 12:34:56. The datestr format uses mm for the month and MM for the minute (the reverse of datetime), and the month overflow in datenum(2022,13,1) is accepted silently. The elapsed-time arithmetic (t2-t1)*86400 has to multiply by hand because a serial number counts days; a second run shows the resolution:

d = datenum(2026,10,22,9,0,0);
fprintf("%.6g days spacing = %.2e seconds\n", eps(d), eps(d)*86400);
class(d)
x = d + 1/24;     % one hour later, by convention only
disp(datestr(x))

Octave 11.3.0 output:

1.16415e-10 days spacing = 1.01e-05 seconds
ans = double
22-Oct-2026 10:00:00

Neighbouring serial numbers are about ten microseconds apart at this date, so a time stamp cannot be stored to nanosecond precision, and class(d) is plain double, so nothing in the type stops code from adding a number of hours to a number of days. This is the reason to convert at the boundary and keep datetime inside new code.

Replacing the legacy functions

Convert serial numbers and strings once, on input, and then work with datetime:

% Not run here; from the documentation.
d = datetime(738522,ConvertFrom="datenum");            % serial number -> datetime
d = datetime("28 June 2022",InputFormat="dd MMMM yyyy");   % text -> datetime
x = datetime(44481,ConvertFrom="excel");               % Excel serial number
s = string(datetime(2022,6,28,12,34,56));              % datetime -> text (replaces datestr)
n = convertTo(d,"datenum");                            % datetime -> serial number

The documentation gives these replacements:

Legacy function Replacement

datenum

datetime values (convert from numbers with ConvertFrom set to datenum)

datestr

string or char of a datetime (the Format property sets the layout)

now, today, date, clock

datetime("now"), datetime("today"), string(datetime("today")), and datevec(datetime) for a date vector

addtodate

Add duration or calendarDuration values to a datetime

etime

Subtract the datetime values, or use between for a calendar difference

eomdate, lweekdate, nweekdate

dateshift (and datetime for the starting month)

months

between(d1,d2,"months")

weeknum

week(d,"weekofyear")

m2xdate, x2mdate

exceltime(d), datetime(x,ConvertFrom="excel")

The functions year, month, day, hour, minute, second, quarter and datevec still accept serial numbers and date strings, but those syntaxes are not recommended either; pass a datetime and read the property (d.Year). When you maintain a library that has to accept both, convert at the top of the function: if isdatetime(d), d = convertTo(d,"datenum"); end keeps the old body unchanged, though the documentation notes that this loses information such as the time zone, so rewriting in terms of datetime is the long-term fix. For old date strings that must be read the way datenum read them ("01/02/22"), the compatibility function matlab.datetime.compatibility.convertDatenum reproduces the legacy interpretation.

The heatflow sensor log

The thermocouple log data/sensors.csv starts with a timestamp column. Reading it with a format avoids the slowest and least predictable path (guessing) and gives a column of datetime that can serve as the row times of a timetable:

% Not run here; the column name Time is assumed for this illustration.
opts = detectImportOptions("data/sensors.csv");
opts = setvaropts(opts,"Time",InputFormat="yyyy-MM-dd HH:mm:ss",TimeZone="UTC");
T = readtable("data/sensors.csv",opts);
T.Time.Format = "yyyy-MM-dd HH:mm:ss";
elapsed = T.Time - T.Time(1);              % duration column
T.Seconds = seconds(elapsed);              % plain numbers, for the numerical code
gaps = isnat(T.Time);                      % rows without a usable stamp

The duration column keeps units, while seconds turns it into the numbers the time-stepping code expects.

Pitfalls

  • MM versus mm: month versus minute in datetime formats, the opposite of datestr.

  • duration is not calendar time: days(1) is 24 hours even across a daylight-saving change, and years(1) is 365.2425 days; use caldays and calyears for calendar arithmetic.

  • calendarDuration has no < or ==, and its month component is resolved only when added to a datetime.

  • Unzoned versus zoned arrays cannot be mixed in comparisons or subtraction.

  • Text that cannot be converted can become NaT; check with isnat after parsing.

  • isnat on a serial date number: isnat is defined for datetime arrays, so calling it on a datenum value (a double) fails with the error identifier MATLAB:UndefinedFunction (not run here). Test a double with isnan, or convert it first with datetime(d,ConvertFrom="datenum").

  • datenum is a plain double, so unit mistakes (days versus seconds) are not caught.

  • Display hides precision: fractional seconds are stored but not shown until Format includes SSS.

  • dateshift(t,"end","month") returns midnight at the beginning of the last day of the month, not the end of that day.

Where older books differ

Older books, written for MATLAB before R2014b, use datenum, datestr, datevec, clock, now, tic/toc and etime for all date work. Those functions still run and are not going to be removed, but since R2022b the documentation recommends datetime, duration and calendarDuration; the books' format strings (yyyy-mm-dd HH:MM:SS) have different letters in datetime. tic and toc remain the way to time code (see Profiling and Timing).