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, AZT and GET for Europe and West Asia. That is a rule about readings, not about the world: CST is read as North American Central time, -06:00, although China Standard Time uses the same letters.
  • The ambiguous names need a locale. BST, IST, ACST and AMT are 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 reads BST as +06:00 and IST as +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/Kolkata becomes Asia/Calcutta, and GMT becomes UTC. datatypes keeps the name you gave. The exception is a fixed offset, which is stored as one: Etc/GMT-3 and UTC+03:00 both become +03:00, as they do in MATLAB.
  • ISO 8601 offsets without a format. MATLAB refuses ISO 8601 text ending in Z or 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/Kolkata rather 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 expect IST, BST, ACST and AMT to 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.