What time is 09:00 IST? Offsets, abbreviations and the choices behind them
15 September 2026
A timestamp in a log file often carries its time zone
as a few characters at the end: Z, +05:30,
GMT-3, CEST, IST. Some of those are
exact. Some are guesses. One of them names three different countries. This
is about how datetime in datatypes 1.4.1 reads them, and about
why it refuses some of them outright.
An offset is a fact, a zone is a rule
An offset such as +05:30 says how far a clock was from UTC
at one moment. A zone such as Asia/Kolkata is a rule: a history
of offsets that a government has set, and may set again. Most of the time
the two agree. They do not have to:
t = datetime (1943, 6, 1, 12, 0, 0, 'TimeZone', 'Asia/Kolkata');
tzoffset (t) % 06:30, India's wartime time
t = datetime (1943, 6, 1, 12, 0, 0, 'TimeZone', '+05:30');
tzoffset (t) % 05:30, whatever the date
Fixed-offset zones are new in 1.4.1. They are the right choice when your data recorded an offset and nothing about a place, which is what most log formats and web APIs give you, and when a timestamp must keep meaning the same instant however the rules for a region change later. If you know where the clock was, use the named zone instead: it knows about daylight saving and about history like the above, and a fixed offset never will.
One spelling deserves a warning. The time zone database's
Etc/GMT-3 means three hours ahead of UTC, not behind.
The sign is reversed, a convention inherited from POSIX, and datatypes reads
the name as the database defines it:
t = datetime (2024, 1, 1, 'TimeZone', 'Etc/GMT-3');
t.TimeZone % +03:00
An offset written in text, like GMT-3 at the end of a log
line, does not share that quirk, which is where the next part starts.
Reading an offset out of text
When the text carries the offset, name its field in
'InputFormat'. The fields x, X and
Z read a numeric offset, a bare Z for UTC, or an
offset after GMT or UTC. The field z
reads all of that and a zone abbreviation as well, which comes up
below.
t = datetime ('2024-03-10 09:00 +05:30', 'InputFormat', 'yyyy-MM-dd HH:mm xxx', ...
'TimeZone', 'UTC')
% 10-Mar-2024 03:30:00
t = datetime ('2024-03-10 09:00 GMT-3', 'InputFormat', 'yyyy-MM-dd HH:mm z', ...
'TimeZone', 'UTC')
% 10-Mar-2024 12:00:00
Here GMT-3 is three hours behind UTC, which is what anyone
writing it means. Only the database's Etc/ names reverse the
sign.
The reader is lenient where real text is untidy and strict where
leniency would mean guessing. It takes a one- or two-digit hour
(-5), minutes and seconds with or without colons
(+0530, +05:30:00), and a GMT or
UTC prefix. It refuses a one-digit minute (-05:3),
because nothing says whether that is three minutes or thirty, and it refuses
an hour of 24 or more. Those are the forms MATLAB accepts too.
ISO 8601 text needs no 'InputFormat' at all, offset
included:
t = datetime ('2024-03-10T09:00:00Z', 'TimeZone', 'Asia/Kolkata', ...
'Format', 'yyyy-MM-dd HH:mm xxx')
% 2024-03-10 14:30 +05:30
Why an offset needs a TimeZone
An offset tells you the instant. It does not tell you how to show it.
09:00+05:30 is 03:30 in London in early March, 05:30 in Athens and
09:00 in Kolkata, and a datetime array displays its times in one
zone. So datatypes asks which:
t = datetime ('2024-03-10T09:00:00+05:30')
% error: datetime: the text '2024-03-10T09:00:00+05:30' carries a time zone
% offset, which requires 'TimeZone' to name a time zone.
The alternative was to assume UTC. Every value would then be the right
instant, shown as UTC wall-clock time, with nothing attached to say so. A
table of readings taken in Kolkata would look exactly like a table of
readings that are five and a half hours wrong. An error you fix once, by
writing 'TimeZone', 'UTC' if UTC is what you want, is cheaper
than a table that is quietly misread.
Three countries called IST
Abbreviations are where text stops being exact. CEST is
+02:00 wherever you read it. IST is not: it is India (+05:30), Ireland (+01:00) and Israel (+02:00).
BST is British Summer Time (+01:00) and Bangladesh Standard
Time (+06:00). ACST is Australian Central Standard Time
(+09:30) under an Australian locale and four hours behind UTC under
Brazilian Portuguese. AMT is Amazon Time, and to some people
Armenia.
Two obvious designs fail. A separate table for each locale reads every
name in the sense of the user's language, which breaks the first time
someone in France reads a log written in Dublin: what the file means does
not depend on who opens it. Refusing every abbreviation is safe and
useless, because real logs are full of EST and
CET.
What datatypes does instead:
- One shared table, for every locale. It holds each
abbreviation that MATLAB reads at a single offset in every locale
that reads it, across the 13 locales we checked, plus
MSK,TRT,AZTandGETfor Europe and West Asia. That is a rule about readings, not about the world:CSTis read as North American Central time, -06:00, although China Standard Time uses the same letters. - The ambiguous names need a locale.
BST,IST,ACSTandAMTare read only under a locale that settles them, at the offset that locale gives them, and refused everywhere else, including when no'Locale'is given. - A name MATLAB reads at two offsets never enters the shared table, and nothing added to it later may change a reading MATLAB already makes.
fmt = 'yyyy-MM-dd HH:mm z';
datetime ('2024-03-10 09:00 IST', 'InputFormat', fmt, 'TimeZone', 'UTC', ...
'Locale', 'en_IN')
% 10-Mar-2024 03:30:00
datetime ('2024-03-10 09:00 IST', 'InputFormat', fmt, 'TimeZone', 'UTC', ...
'Locale', 'en_IE')
% 10-Mar-2024 08:00:00
datetime ('2024-03-10 09:00 IST', 'InputFormat', fmt, 'TimeZone', 'UTC')
% error: datetime: could not parse the date/time string '2024-03-10 09:00 IST'
% with 'InputFormat' 'yyyy-MM-dd HH:mm z'.
The refusal is the ordinary parse error; it does not say that the name was the problem. What it does not do is hand you a time that is right in Delhi and wrong in Dublin.
So much is settled. The shared table may grow, and a name added to it will have one reading. The four locale-bound names stay bound to their locales.
Winter time that counts as summer time
Irish clocks show +01:00 in summer and +00:00 in winter, like British ones. The time zone database records Irish law as it is written, though: +01:00 is Irish Standard Time, and winter is a saving of minus one hour. Read naively, that makes the Irish winter the daylight saving part of the year. Morocco is written the same way: +01:00 is standard time, and Ramadan is a saving of minus one hour that brings clocks to +00:00.
datatypes counts the way the database's own rearguard form does, which is also what MATLAB reports. A negative saving is standard time:
t = datetime ([2024 2024], [1 7], [15 15], 12, 0, 0, 'TimeZone', 'Europe/Dublin');
isdst (t) % 0 1
[off, dst] = tzoffset (t)
% off: 00:00 01:00
% dst: 00:00 01:00
The second output of tzoffset, also new in 1.4.1, is the
daylight saving part of each offset. For Africa/Casablanca,
isdst is true in January, at +01:00, and false on 1 March 2026,
during Ramadan, at +00:00.
Where this matches MATLAB, and where it does not
Every rule above was measured against MATLAB R2024a before it was written: which offset spellings are accepted and refused, which abbreviations are read under which locale, and the daylight saving counts for Dublin and Casablanca. Three differences are deliberate:
- No locale, no ambiguous name. With no
'Locale', MATLAB readsBSTas +06:00 andISTas +05:30. datatypes refuses both, for the reasons above. Where datatypes does read a name, it reads it at the offset MATLAB gives it. - Zone names stay as you wrote them. MATLAB rewrites
some names to other spellings:
Asia/KolkatabecomesAsia/Calcutta, andGMTbecomesUTC. datatypes keeps the name you gave. The exception is a fixed offset, which is stored as one:Etc/GMT-3andUTC+03:00both become+03:00, as they do in MATLAB. - ISO 8601 offsets without a format. MATLAB refuses
ISO 8601 text ending in
Zor an offset unless you give an'InputFormat', even when a'TimeZone'is given. datatypes reads it, because the form cannot mean anything else.
The short version
- You know where the clock was: use a named zone,
Asia/Kolkatarather than+05:30. - You only have an offset: use a fixed-offset zone,
+05:30. - Text carrying an offset: give a
'TimeZone'. It is the zone your times will be shown in. - Text carrying abbreviations: give the
'Locale'the text was written in, and expectIST,BST,ACSTandAMTto be refused without one.
The reference pages for
the datetime constructor
and tzoffset list every
field and option.
The datatypes package for GNU Octave is
developed by Andreas Bertsatos and funded by
NLnet through the NGI Zero Commons Fund.
Questions and corrections:
the issue
tracker.