How to ISO 8601 format a Date with Timezone Offset in JavaScript?
Asked Answered
G

22

222

Goal: Find the local time and UTC time offset then construct the URL in following format.

Example URL: /Actions/Sleep?duration=2002-10-10T12:00:00−05:00

The format is based on the W3C recommendation. The documentation says:

For example, 2002-10-10T12:00:00−05:00 (noon on 10 October 2002, Central Daylight Savings Time as well as Eastern Standard Time in the U.S.) is equal to 2002-10-10T17:00:00Z, five hours later than 2002-10-10T12:00:00Z.

So based on my understanding, I need to find my local time by new Date() then use getTimezoneOffset() function to compute the difference then attach it to the end of string.

  1. Get local time with format

    var local = new Date().format("yyyy-MM-ddThh:mm:ss"); // 2013-07-02T09:00:00
    
  2. Get UTC time offset by hour

    var offset = local.getTimezoneOffset() / 60; // 7
    
  3. Construct URL (time part only)

    var duration = local + "-" + offset + ":00"; // 2013-07-02T09:00:00-7:00
    

The above output means my local time is 2013/07/02 9am and difference from UTC is 7 hours (UTC is 7 hours ahead of local time)

So far it seems to work but what if getTimezoneOffset() returns negative value like -120?

I'm wondering how the format should look like in such case because I cannot figure out from W3C documentation.

Gulgee answered 2/7, 2013 at 0:19 Comment(1)
I'm seeing a lot of answers that use string splitting (which has its place) but be aware that dates can be different in different timezones even if it is the same point in time (in fact it can vary up to 2 days)Izawa
H
318

Here's a simple helper function that will format JS dates for you.

function toIsoString(date) {
  var tzo = -date.getTimezoneOffset(),
      dif = tzo >= 0 ? '+' : '-',
      pad = function(num) {
          return (num < 10 ? '0' : '') + num;
      };

  return date.getFullYear() +
      '-' + pad(date.getMonth() + 1) +
      '-' + pad(date.getDate()) +
      'T' + pad(date.getHours()) +
      ':' + pad(date.getMinutes()) +
      ':' + pad(date.getSeconds()) +
      dif + pad(Math.floor(Math.abs(tzo) / 60)) +
      ':' + pad(Math.abs(tzo) % 60);
}

var dt = new Date();
console.log(toIsoString(dt));
Homogamy answered 2/7, 2013 at 0:35 Comment(19)
Are you saying that I can use plus sign if negative value (-120) is returned from getTimezoneOffset() such as 2013-07-02T09:00:00+2:00 ?Gulgee
The sign indicates the offset of the local time from GMTHomogamy
@masato-san You have to invert the sign, see the definition at developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Stipitate
@StevenMoseley I know, I was agreeing with your answer. The OP seemed to be confused about that part.Stipitate
Just realized your original non-IE 8 answer is wrong! You don't adjust the time, you only append the offset. That makes your two answers differ. Only your IE8 proof version is correct. The last would create "2013-11-26T14:52:26+01:00" and the first would create "2013-11-26T15:52:30+01:00"Psychologize
Here pad function returns proper string for each section of date except milliseconds. For instance, for 5ms as an input will return 05, which supposed to be 005. Here is a link with minimum modified version of the same function jsbinStepha
Please note: there is a subtle bug in this answer. Timezone offset will not be calculated correctly if the zone had offset minutes and was negative (eg. -09:30), such as certain locations in France and Canada. Mod returns a negative number, so doing floor() before abs() inadvertently made the offset MORE negative. To correct this bug, so abs() and THEN floor(). Tried to edit the answer, but apparently "This edit was intended to address the author of the post and makes no sense as an edit".Moorland
@Moorland - Great catch! That is, indeed, subtle. I added your fix to the answer. Thanks for commenting!Homogamy
It's generally considered bad practice to modify the prototype of a global like this. Why not just define a function that returns the date, and leave it up to the user whether they want to modify their environment globals?Yearling
Thanks. Reading all the answers, I am still astonished how javascript feels such a "DIY" language for some date-related stuff.Bibi
is that still the best answer? javascript has no method of doing this? ... asked by someone who just has to do this for the very first time ...Spook
@flypenguin—yes. ECMAScript still doesn't have a sensible way of formatting (or parsing) timestamps. The proposed Temporal object seeks to address that.Revis
You can be a bit more compact with padStart: `${Math.floor(Math.abs(num))}`.padStart(2, '0')Fathead
@Fathead String interpolation is es6. This needs to be backwards compatible.Homogamy
@StevenMoseley you are absolutely correct. In our FE builds we transpile everything (we dropped also IE11 support recently, yay) but this often makes me neglect that not everyone might be doing the same. Had also missed the parts were compatibility was discussed, thanks for pointing out :). Also, thanks for the contribution, it works better than my previous logic.Fathead
@Moorland I'm having difficulty understanding your comment. Yes, Mod returns negative number, but it will still be an integer, so floor() will not make the offset "more negative", right?Cerelia
NVM, I got it... What you meant to say was that it will make the hours part (division) more negative, not the minutes part (mod)...Cerelia
The use of Math.abs in the pad function is not logical and only needed beause the offset might be negative. Better to Math.abs the offset first (after storing the sign of course) before getting the hr and min and padding. :-)Revis
@Revis good point, the same could be said for Math.floor. And..... updated!Homogamy
N
84

getTimezoneOffset() returns the opposite sign of the format required by the spec that you referenced.

This format is also known as ISO8601, or more precisely as RFC3339.

In this format, UTC is represented with a Z while all other formats are represented by an offset from UTC. The meaning is the same as JavaScript's, but the order of subtraction is inverted, so the result carries the opposite sign.

Also, there is no method on the native Date object called format, so your function in #1 will fail unless you are using a library to achieve this. Refer to this documentation.

If you are seeking a library that can work with this format directly, I recommend trying moment.js. In fact, this is the default format, so you can simply do this:

var m = moment();    // get "now" as a moment
var s = m.format();  // the ISO format is the default so no parameters are needed

// sample output:   2013-07-01T17:55:13-07:00

This is a well-tested, cross-browser solution, and has many other useful features.

Nicollenicolson answered 2/7, 2013 at 0:58 Comment(2)
Using toISOString() won't work. The +01:00 format requires the time part be local time. toISOString() would give a UTC time string.Solve
@AustinFrance - You're right! I'm surprised I made that mistake at the time, as I correct others on this point often. Sorry I didn't see your comment two years ago! Answer edited.Nicollenicolson
E
62

I think it is worth considering that you can get the requested info with just a single API call to the standard library...

new Date().toLocaleString( 'sv', { timeZoneName: 'short' } );

// produces "2019-10-30 15:33:47 GMT−4"

You would have to do text swapping if you want to add the 'T' delimiter, remove the 'GMT-', or append the ':00' to the end.

But then you can easily play with the other options if you want to eg. use 12h time or omit the seconds etc.

Note that I'm using Sweden as locale because it is one of the countries that uses ISO 8601 format. I think most of the ISO countries use this 'GMT-4' format for the timezone offset other then Canada which uses the time zone abbreviation eg. "EDT" for eastern-daylight-time.

You can get the same thing from the newer standard i18n function "Intl.DateTimeFormat()" but you have to tell it to include the time via the options or it will just give date.

Explicable answered 30/10, 2019 at 21:6 Comment(6)
Hm, for sv, I actually get "CET" and for a summer time date, "CEST"... But thanks for the input, the standard API is sufficient for me (need to prefix log messages with a date). If I wanted to get serious about time and timezones, I guess I'd go for moment library...Lxx
plus for toLocaleString with 'sv' formatFrumpy
Are you sure that sv stands for Sweden? When I search for "sv" country code, all the top results are about El Salvador. And when I search sweden country code two letter, Google says it's se.Taal
@Taal According to ISO 639-1 en.wikipedia.org/wiki/List_of_ISO_639-1_codes, sv stands for Swedish. I'm willing to bet it's because they call their language SvenskaFaintheart
Please note that locales may change their preferred date format at any time.Blanchblancha
This output is not valid ISO 8601 with the space instead of T and the named offset.Seriema
B
27

My answer is a slight variation for those who just want today's date in the local timezone in the YYYY-MM-DD format.

Let me be clear:

My Goal: get today's date in the user's timezone but formatted as ISO8601 (YYYY-MM-DD)

Here is the code:

new Date().toLocaleDateString("sv") // "2020-02-23" // 

This works because the Swedish language locale (svenska) uses the ISO 8601 format.

Bancroft answered 24/2, 2020 at 1:22 Comment(6)
Invalid answer, this does answer, but other question... This answer can be easily be found on SO.Leafstalk
I haven't read the question in full, so I can't speak to whether this answers the question, but this is exactly what I was looking for (and it's super short). Thank you!Jankell
sv is not Sweden. Google says it's El Salvador. Swedens shortcode is 'se'Hairpiece
@Hairpiece According to ISO 639-1 (en.wikipedia.org/wiki/List_of_ISO_639-1_codes), sv stands for Swedish. I'm willing to bet it's because they call their language Svenska.Faintheart
@KnechtRootrecht: It's the language code, not country code.Denial
As best I can work out, the ECMAScript standard tc39.es/ecma402/#datetimeformat-objects refers to unicode.org/reports/tr35/#Unicode_locale_identifier which refers to BCP 47 [ and also tells us “for historical reasons, this is called a Unicode locale identifier. However, it really functions (with few exceptions) as a language identifier, and accesses language-based data.” ] , which gets us to IETF tags as per en.wikipedia.org/wiki/IETF_language_tag which tells us that sv is for Swedish. Getting this far has been painful.Gerber
G
12

Check this:

function dateToLocalISO(date) {
    const off    = date.getTimezoneOffset()
    const absoff = Math.abs(off)
    return (new Date(date.getTime() - off*60*1000).toISOString().substr(0,23) +
            (off > 0 ? '-' : '+') + 
            Math.floor(absoff / 60).toFixed(0).padStart(2,'0') + ':' + 
            (absoff % 60).toString().padStart(2,'0'))
}

// Test it:
d = new Date()

dateToLocalISO(d)
// ==> '2019-06-21T16:07:22.181-03:00'

// Is similar to:

moment = require('moment')
moment(d).format('YYYY-MM-DDTHH:mm:ss.SSSZ') 
// ==> '2019-06-21T16:07:22.181-03:00'
Gelatin answered 21/6, 2019 at 19:13 Comment(0)
D
9

This is my function for the clients timezone, it's lite weight and simple

  function getCurrentDateTimeMySql() {        
      var tzoffset = (new Date()).getTimezoneOffset() * 60000; //offset in milliseconds
      var localISOTime = (new Date(Date.now() - tzoffset)).toISOString().slice(0, 19).replace('T', ' ');
      var mySqlDT = localISOTime;
      return mySqlDT;
  }
Decameter answered 25/5, 2018 at 22:8 Comment(1)
@tsh, are you sure? You are getting the current time offset, which is later used to simulate the local time. Maybe at that point the offset was different, but it doesn't matter because you just care about now.Sholem
C
6

You can achieve this with a few simple extension methods. The following Date extension method returns just the timezone component in ISO format, then you can define another for the date/time part and combine them for a complete date-time-offset string.

Date.prototype.getISOTimezoneOffset = function () {
    const offset = this.getTimezoneOffset();
    return (offset < 0 ? "+" : "-") + Math.floor(Math.abs(offset / 60)).leftPad(2) + ":" + (Math.abs(offset % 60)).leftPad(2);
}

Date.prototype.toISOLocaleString = function () {
    return this.getFullYear() + "-" + (this.getMonth() + 1).leftPad(2) + "-" +
        this.getDate().leftPad(2) + "T" + this.getHours().leftPad(2) + ":" +
        this.getMinutes().leftPad(2) + ":" + this.getSeconds().leftPad(2) + "." +
        this.getMilliseconds().leftPad(3);
}

Number.prototype.leftPad = function (size) {
    var s = String(this);
    while (s.length < (size || 2)) {
        s = "0" + s;
    }
    return s;
}

Example usage:

var date = new Date();
console.log(date.toISOLocaleString() + date.getISOTimezoneOffset());
// Prints "2020-08-05T16:15:46.525+10:00"

I know it's 2020 and most people are probably using Moment.js by now, but a simple copy & pastable solution is still sometimes handy to have.

(The reason I split the date/time and offset methods is because I'm using an old Datejs library which already provides a flexible toString method with custom format specifiers, but just doesn't include the timezone offset. Hence, I added toISOLocaleString for anyone without said library.)

Claresta answered 5/8, 2020 at 6:38 Comment(0)
H
3

Just my two cents here

I was facing this issue with datetimes so what I did is this:

const moment = require('moment-timezone')

const date = moment.tz('America/Bogota').format()

Then save date to db to be able to compare it from some query.


To install moment-timezone

npm i moment-timezone
Henryhenryetta answered 21/2, 2019 at 23:48 Comment(0)
H
3

No moment.js needed: Here's a full round trip answer, from an input type of "datetime-local" which outputs an ISOLocal string to UTCseconds at GMT and back:

<input type="datetime-local" value="2020-02-16T19:30">

isoLocal="2020-02-16T19:30"
utcSeconds=new Date(isoLocal).getTime()/1000

//here you have 1581899400 for utcSeconds

let isoLocal=new Date(utcSeconds*1000-new Date().getTimezoneOffset()*60000).toISOString().substring(0,16)
2020-02-16T19:30
Hackman answered 15/1, 2020 at 3:49 Comment(0)
D
3
  • date to ISO string,
  • with local(computer) time zone,
  • with or without milliseconds

ISO ref: https://en.wikipedia.org/wiki/ISO_8601

how to use: toIsoLocalTime(new Date())

function toIsoLocalTime(value) {
    if (value instanceof Date === false)
        value = new Date();    
    const off = value.getTimezoneOffset() * -1;
    const del = value.getMilliseconds() ? 'Z' : '.'; // have milliseconds ?
    value = new Date(value.getTime() + off * 60000); // add or subtract time zone
    return value
        .toISOString()
        .split(del)[0]
        + (off < 0 ? '-' : '+')
        + ('0' + Math.abs(Math.floor(off / 60))).substr(-2)
        + ':'
        + ('0' + Math.abs(off % 60)).substr(-2);
}

function test(value) {
    const event = new Date(value);
    console.info(value + ' -> ' + toIsoLocalTime(event) + ', test = ' + (event.getTime() === (new Date(toIsoLocalTime(event))).getTime() ));
}

test('2017-06-14T10:00:00+03:00'); // test with timezone
test('2017-06-14T10:00:00'); // test with local timezone
test('2017-06-14T10:00:00Z'); // test with UTC format
test('2099-12-31T23:59:59.999Z'); // date with milliseconds
test((new Date()).toString()); // now
Deshawndesi answered 30/6, 2022 at 1:32 Comment(0)
B
3

Use Temporal.

Temporal.Now.zonedDateTimeISO().toString()
// '2022-08-09T14:16:47.762797591-07:00[America/Los_Angeles]'

To omit the fractional seconds and IANA time zone:

Temporal.Now.zonedDateTimeISO().toString({
  timeZoneName: "never",
  fractionalSecondDigits: 0
})
// '2022-08-09T14:18:34-07:00'

Note: Temporal is currently (2022) available as a polyfill, but will soon be available in major browsers.

Blanchblancha answered 9/8, 2022 at 21:19 Comment(0)
T
3

If you are looking for an easy one-liner to get the current date in ISO format here it is.

const currentIsoDateString = new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString()

You can change this to get the ISO date/time string for other dates by replacing the Date.now() with your epoch date integer.

Here is how it works from the inside out. Date.now() returns the current time in milliseconds since the epoch Jan. 1st 1970. For me the current local date/time is 2023-07-05 09:57:32 (ex. 1688565167374) but Date.now does not care about the local time, it just gives the time from the epoch regardless of local time, so we need to adjust.

new Date().getTimezonOffset() returns the local timezone offset from UTC. In my case it is 240 or 4 hours. We multiply these minutes by 60000 to convert it to milliseconds and subtract the result from the UTC.

Then we can use the handy .toISOString() native function to get a nicely formatted ISO string. '2023-07-05T09:58:18.119Z'

No libraries required :-)

Tuyettv answered 5/7, 2023 at 14:3 Comment(4)
Why no upvotes?Guesswarp
His post is pretty recent that's maybe why; +1 by the way.Fulgurant
It has no upvotes because it is completely wrong. Z means Zulu time, or UTC, which is what .toISOString() is outputting in your example. Thus by changing the time (which you don't even do correctly in the final part of your answer) and not the timezone, you have messed up twice. The correct ISO compliant output from your example should be 2023-07-05T09:52:47-04:00Undershrub
The Z on the end is an important part of the process. When date functions in javascript create a new date from a string with a Z on the end javascript with automatically convert the date to the browsers local time zone. By using the -04:00 timezone offset in your example you assuming EST during daylight savings time. It can be dangerous to hardcode offsets. To understand the effect of the Z better open the console in your dev tools, get a new date with the Z new Date('2024-01-13T10:04:11.146Z') now enter without the Z new Date('2024-01-13T10:04:11.146') notice the time difference?Tuyettv
T
2

consider using moment (like Matt's answer).

From version 2.20.0, you may call .toISOString(true) to prevent UTC conversion:

console.log(moment().toISOString(true));

// sample output:   2022-04-06T16:26:36.758+03:00
Thick answered 6/4, 2022 at 13:36 Comment(0)
C
1

Here are the functions I used for this end:

function localToGMTStingTime(localTime = null) {
    var date = localTime ? new Date(localTime) : new Date();
    return new Date(date.getTime() + (date.getTimezoneOffset() * 60000)).toISOString();
};

function GMTToLocalStingTime(GMTTime = null) {
    var date = GMTTime ? new Date(GMTTime) : new Date();;
    return new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString();
};
Cryobiology answered 22/7, 2020 at 13:59 Comment(0)
U
1

With luxon:

DateTime.now().toISODate() // 2022-05-23
Ultra answered 23/5, 2022 at 13:0 Comment(0)
F
1

a simple way to get:

//using a sample date
let iso_str = '2022-06-11T01:51:59.618Z';
let d = new Date(iso_str);

let tz = 'America/Santiago'
let options = {
    timeZone:tz ,
    timeZoneName:'longOffset',
    year: 'numeric',
    month: 'numeric',
    day: 'numeric',
    hour: 'numeric',
    minute: 'numeric',
    second: 'numeric',
    fractionalSecondDigits: 3
}


str_locale = d.toLocaleString("sv-SE",options);
iso_str_tz = str_locale
             .replace(/(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2}),(\d+)\s+/,'$1-$2-$3T$4:$5:$6.$7')
             .replace('GMT−', '-' )
             .replace('GMT+','+')


console.log('iso_str               : ',iso_str);
console.log('str_locale            : ',str_locale);
console.log('iso_str_tz            : ',iso_str_tz);
console.log('iso_str_tz --> date   : ',new Date(iso_str_tz));
console.log('iso_str_tz --> iso_str: ',new Date(iso_str_tz).toISOString());
Femininity answered 11/6, 2022 at 1:59 Comment(0)
A
0
let myDate = new Date(dateToBeFormatted * 1000); // depends if you have milliseconds, or seconds, then the * 1000 might be not, or required.
timeOffset = myDate.getTimezoneOffset();
myDate = new Date(myDate.getTime() - (timeOffset * 60 * 1000));

console.log(myDate.toISOString().split('T')[0]);

Inspired by https://mcmap.net/q/37097/-format-javascript-date-as-yyyy-mm-dd, including timezone offset comment.

Anagnos answered 15/4, 2022 at 8:50 Comment(0)
M
0

Using moment.js, you can use keepOffset parameter of toISOString:

toISOString(keepOffset?: boolean): string;

moment().toISOString(true)

Mephistopheles answered 6/11, 2022 at 14:1 Comment(0)
C
0

Alternative approach with dayjs

import dayjs from "dayjs"

const formattedDateTime = dayjs(new Date()).format()

console.log(formattedDateTime) // Prints 2022-11-09T07:49:29+03:00
Clausen answered 9/11, 2022 at 1:53 Comment(0)
C
0

Here's another way a convert your date with an offset.

function toCustomDateString(date, offset) {
  function pad(number) {
    if (number < 10) {
      return "0" + number;
    }
    return number;
  }

  var offsetHours = offset / 60;
  var offsetMinutes = offset % 60;

  var sign = (offset > 0) ? "+" : "-";
  offsetHours = pad(Math.floor(Math.abs(offsetHours)));
  offsetMinutes = pad(Math.abs(offsetMinutes));

  return date.getFullYear() +
    "-" + pad(date.getMonth() + 1) +
    "-" + pad(date.getDate()) +
    "T" + pad(date.getHours()) +
    ":" + pad(date.getMinutes()) +
    ":" + pad(date.getSeconds()) +
    sign + offsetHours +
    ":" + offsetMinutes;
}

Then you can use it like this:

var date = new Date();
var offset = 330; // offset in minutes from UTC, for India it is 330 minutes ahead of UTC
var customDateString = toCustomDateString(date, offset);
console.log(customDateString);
// Output: "2023-02-09T10:29:31+05:30"
Coleslaw answered 9/2, 2023 at 5:29 Comment(0)
L
-1
function setDate(){
    var now = new Date();
    now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
    var timeToSet = now.toISOString().slice(0,16);

    /*
        If you have an element called "eventDate" like the following:

        <input type="datetime-local" name="eventdate" id="eventdate" />

        and you would like to  set the current and minimum time then use the following:
    */

    var elem = document.getElementById("eventDate");
    elem.value = timeToSet;
    elem.min = timeToSet;
}
Liew answered 26/10, 2020 at 7:3 Comment(0)
C
-1

I found another more easy solution:

let now = new Date();
// correct time zone offset for generating iso string
now.setMinutes(now.getMinutes() - now.getTimezoneOffset())
now = now.toISOString();

I undo the timezone offset by substracting it from the current date object. The UTC time from the date object is now pointing to the local time. That gives you the possibility to get the iso date for the local time.

Constrained answered 22/11, 2022 at 8:56 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.