0

I want a TypeScript function that checks if an ISO 8601 datetime string is in UTC. So the output should be:

isUTC('2020-01-01T00:00:00Z')      // true
isUTC('2020-01-01T00:00:00+00')    // true
isUTC('2020-01-01T00:00:00+0000)   // true
isUTC('2020-01-01T00:00:00+00:00') // true
isUTC('2020-01-01T00:00:00+01)     // false
isUTC('2020-01-01T00:00:00+0100)   // false
isUTC('2020-01-01T00:00:00+01:00') // false

If possible I would like to avoid regular expressions and use an existing parser or decomposer instead. Ideally there would be a library that decomposes ISO 8601 strings, so that I can check Iso8601Decomposer(value).timezoneOffset === 0. Does something like this exist in TypeScript/JavaScript?

I found some answers that were checking if value === new Date(Date.parse(value)).toISOString(), but this does not work for lines 2 and 3 above.

Elias Strehle
  • 1,722
  • 1
  • 21
  • 34
  • [ISOString](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) in JS is always UTC time. – Teemu Jul 07 '22 at 10:40
  • 1
    Does [this answer](https://stackoverflow.com/a/52869830/5079258) help? – Alan Friedman Jul 07 '22 at 10:42
  • 1
    Tbh, a regex would the easiest choice. `/^(Z|00(:?00)?)$/.test(input.slice(19))` – Bergi Jul 07 '22 at 13:28
  • @Alan Friedman: I want to check UTC only, not that the string is ISO 8601 (that's already checked by another validator in my code). – Elias Strehle Jul 07 '22 at 13:45
  • 1
    Just to be pedantic, technically only `Z` indicates UTC. `+00:00` indicates that the offset from UTC is zero, but the value is intended to be interpreted as *local* time. For example, London uses `+00:00` in the winter months, and Iceland uses `+00:00` year-round. In both cases, that's the *local* time and is called "GMT" (Greenwich Mean Time). Portugal also uses `+00:00` in the winter months, but calls it "WET" (Western European Time). By contrast "UTC" (Coordinated Universal Time) applies *everywhere* on the planet, at any given time. Yes they are all offset by zero, but context matters. – Matt Johnson-Pint Jul 07 '22 at 16:26
  • Thanks @Matt Johnson-Pint, I didn't know that! – Elias Strehle Jul 08 '22 at 07:05

1 Answers1

1

You can use the luxon library to parse ISO date strings.

One can then get the UTC offset in minutes, if this is 0 we're in UTC (or GMT)

I've wrapped this all up in the required isUTC() function.

let { DateTime } = luxon;

let inputs = [
    '2020-01-01T00:00:00Z',
    '2020-01-01T00:00:00+00',
    '2020-01-01T00:00:00+0000',
    '2020-01-01T00:00:00+00:00',
    '2020-01-01T00:00:00+01',
    '2020-01-01T00:00:00+0100',
    '2020-01-01T00:00:00+01:00'
];


function isUTC(input) {
    const dt = DateTime.fromISO(input, { setZone: true })
    return dt.offset === 0;
}

console.log('Input'.padEnd(30), 'isUTC');
for(let input of inputs) {
    console.log(input.padEnd(30), isUTC(input));
}
.as-console-wrapper { max-height: 100% !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/luxon/2.3.1/luxon.min.js" integrity="sha512-Nw0Abk+Ywwk5FzYTxtB70/xJRiCI0S2ORbXI3VBlFpKJ44LM6cW2WxIIolyKEOxOuMI90GIfXdlZRJepu7cczA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

You can do this with RegEx too:

let inputs = [
    '2020-01-01T00:00:00Z',
    '2020-01-01T00:00:00+00',
    '2020-01-01T00:00:00+0000',
    '2020-01-01T00:00:00+00:00',
    '2020-01-01T00:00:00+01',
    '2020-01-01T00:00:00+0100',
    '2020-01-01T00:00:00+01:00'
];


function isUTC(input) {
    return getUTCOffsetMinutes(input) === 0;
}

function getUTCOffsetMinutes(isoDate) {
    // The pattern will be ±[hh]:[mm], ±[hh][mm], or ±[hh], or 'Z'
    const offsetPattern = /([+-]\d{2}|Z):?(\d{2})?\s*$/;
    if (!offsetPattern.test(isoDate)) {
        throw new Error("Cannot parse UTC offset.")
    }
    const result = offsetPattern.exec(isoDate);
    return (+result[1] || 0) * 60 + (+result[2] || 0);
}

console.log('Input'.padEnd(30), 'isUTC');
for(let input of inputs) {
    console.log(input.padEnd(30), isUTC(input));
}
.as-console-wrapper { max-height: 100% !important; }
Terry Lennox
  • 29,471
  • 5
  • 28
  • 40