166

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.

Rafael Tavares
  • 3,489
  • 4
  • 25
  • 44
Meow
  • 17,171
  • 50
  • 130
  • 178

15 Answers15

267

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));
Steven Moseley
  • 14,971
  • 4
  • 35
  • 48
  • 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 ? – Meow Jul 02 '13 at 00:40
  • 3
    The sign indicates the offset of the local time from GMT – Steven Moseley Jul 02 '13 at 00:50
  • 1
    @masato-san You have to invert the sign, see the definition at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset – bfavaretto Jul 02 '13 at 00:56
  • @StevenMoseley I know, I was agreeing with your answer. The OP seemed to be confused about that part. – bfavaretto Jul 02 '13 at 02:06
  • 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" – oligofren Nov 26 '13 at 14:54
  • 3
    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 [jsbin](https://jsbin.com/poroqiboqi/edit?js,console,output) – Safique Ahmed Faruque Feb 08 '17 at 10:42
  • 17
    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". – tytk Aug 24 '17 at 14:59
  • 15
    @tytk - Great catch! That is, indeed, subtle. I added your fix to the answer. Thanks for commenting! – Steven Moseley Aug 30 '17 at 15:30
  • 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? – callum Mar 31 '21 at 10:55
  • 1
    Thanks. Reading all the answers, I am still astonished how javascript feels such a "DIY" language for some date-related stuff. – hlobit Jul 15 '21 at 13:02
  • 2
    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 ... – flypenguin Aug 18 '21 at 11:46
  • @flypenguin—yes. ECMAScript **still** doesn't have a sensible way of formatting (or parsing) timestamps. The proposed [*Temporal* object](https://github.com/tc39/proposal-temporal) seeks to address that. – RobG Nov 01 '21 at 12:50
  • 2
    You can be a bit more compact with padStart: `\`${Math.floor(Math.abs(num))}\`.padStart(2, '0')` – fllprbt Nov 29 '21 at 02:50
  • 1
    @fllprbt String interpolation is es6. This needs to be backwards compatible. – Steven Moseley Nov 30 '21 at 03:45
  • 1
    @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. – fllprbt Nov 30 '21 at 23:19
  • @tytk 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? – Raghuram Krishnaswami Jan 22 '22 at 08:51
  • 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)... – Raghuram Krishnaswami Jan 22 '22 at 08:52
  • 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. :-) – RobG Jan 23 '22 at 02:29
  • 3
    @RobG good point, the same could be said for Math.floor. And..... updated! – Steven Moseley Feb 03 '22 at 04:26
75

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.

Matt Johnson-Pint
  • 214,338
  • 71
  • 421
  • 539
  • Using toISOString() won't work. The +01:00 format requires the time part be local time. toISOString() would give a UTC time string. – Austin France Jul 13 '15 at 09:34
  • 1
    @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. – Matt Johnson-Pint Sep 25 '15 at 15:42
38

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.

Tom
  • 16,279
  • 8
  • 64
  • 72
  • 1
    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... – mmey Mar 20 '20 at 21:47
  • plus for toLocaleString with 'sv' format – Pawel Cioch Sep 21 '21 at 04:09
  • This is the sweet potato! – Timbokun Dec 19 '21 at 13:01
  • 1
    Are you sure that `sv` stands for Sweden? When I [search for `"sv" country code`](https://www.google.com/search?q=%22sv%22+country+code), all the top results are about El Salvador. And when I [search `sweden country code two letter`](https://www.google.com/search?q=sweden+country+code+two+letter), Google says it's `se`. – Venryx Jan 15 '22 at 06:07
20

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 Sweden locale uses the ISO 8601 format.

Jonathan Steele
  • 485
  • 6
  • 9
7

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;
  }
Bbb
  • 467
  • 5
  • 20
  • @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. – Andrew Jan 18 '19 at 17:43
6

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'
Nahuel Greco
  • 860
  • 13
  • 12
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
VLAZ
  • 22,934
  • 9
  • 44
  • 60
Arnold Gandarillas
  • 3,639
  • 1
  • 28
  • 36
2

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
2

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.)

Extragorey
  • 1,535
  • 11
  • 28
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();
};
gildniy
  • 2,550
  • 27
  • 19
0
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;
}
Ranch Camal
  • 445
  • 1
  • 4
  • 10
0

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
OhadR
  • 7,578
  • 3
  • 43
  • 48
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://stackoverflow.com/a/29774197/11127383, including timezone offset comment.

Daniel Danielecki
  • 5,838
  • 4
  • 44
  • 72
0

With luxon:

DateTime.now().toISODate() // 2022-05-23
Nelu
  • 13,698
  • 8
  • 71
  • 82
-1

Using just javascript with no libraries is really just two lines:

var dt = new Date();
// Date in UTC and ISO format: "2021-11-30T20:33:32.222Z"
console.log(dt.toISOString());
var dtOffset = new Date(dt.setMinutes(dt.getMinutes() - dt.getTimezoneOffset()));
// Date in EST and ISO format: "2021-11-30T15:33:32.222Z"
console.log(dtOffset.toISOString());

new Date() will default to the current locale but you can also specify it directly if needed:

var dt = new Date(new Date().toLocaleString("en-US", {timeZone: "America/New_York"}));

smoore4
  • 3,838
  • 3
  • 31
  • 49