I'm trying to create a Date in NodeJS with zero time i.e. something like 2016-08-23T00:00:00.000Z. I tried the following code:
var dateOnly = new Date(2016, 7, 23, 0, 0, 0, 0);
console.log(dateOnly);
While I expected the output to be as mentioned above, I got this:
2016-08-22T18:30:00.000Z
How do I create a Date object like I wanted?
The key thing about JavaScript's Date type is that it gives you two different views of the same information: One in local time, and the other in UTC (loosely, GMT).
What's going on in your code is that new Date interprets its arguments as local time (in your timezone), but then the console displayed it in UTC (the Z suffix tells us that). Your timezone is apparently GMT+05:30, which is why the UTC version is five and a half hours earlier than the date/time you specified to new Date.
If you'd output that date as a string in your local timezone (e.g., from toString, or using getHours and such), you would have gotten all zeros for hours, minutes, seconds, and milliseconds. It's the same information (the date is the same point in time), just two different views of it.
So the key thing is to make sure you stick with just the one view of the date, both on input and output. So you can either:
Create it like you did and output it using the local timezone functions (toString, getHours, etc.), or
Created it via Date.UTC so it interprets your arguments in UTC, and then use UTC/GMT methods when displaying it such as toISOString, getUTCHours, etc.:
var dateOnlyInUTC = new Date(Date.UTC(2016, 7, 23));
console.log(dateOnlyInUTC.toISOString()); // "2016-08-23T00:00:00.000Z"
Side note: The hours, minutes, seconds, and milliseconds arguments of both new Date and Date.UTC default to 0, you don't need to specify them if you want zeroes there.
You could always just initialize the Date object with your desired date, then use the Date objects .setHours() method to set it to midnight.
See also:
What is the best way to initialize a JavaScript Date to midnight?
Related
I've been looking for a way to keep my dates in UTC in my JS application; however, in every case the Date's getTimezoneOffset() does not return a 0 which, I would imagine, should be the case -- and which would be seemingly important in casting dates between UTC and the local TZ.
See below examples of what I've tried, thanks in advance for any insight!
var TheDate = new Date( Date.UTC(2012, 10, 5) );
console.log(TheDate.getTimezoneOffset()) // => 300 (for me)
console.log(moment().utc().toDate().getTimezoneOffset()) // => 240 (for me)
ECAMScript Dates are inherently UTC and are just an offset from the ECMAScript epoch (a time value in milliseconds from 1970-01-01T00:00:00Z), nothing more. They have no associated timezone.
The host timezone offset is used:
When creating a Date to determine the equivalent UTC time and calculate the time value
In various methods that use local dates and times, such as getHours (vs getUTCHours)
The date itself does not know anything about timezones.
The value returned by getTimezoneOffset is based on the host system settings, the only relationship it has to the Date it's called on is that the offset is calculated for that Date. The method might be called getHostSystemTimezoneOffset, because that's what it returns.
In your code:
new Date(Date.UTC(2012, 10, 5))
creates a Date for 2012-11-05T00:00:00Z. Calling getTimezoneOffset on that date returns the host system offset for the equivalent local date and time, not the offset that was used in creating the Date.
There is no way to associate a timezone with a date without using a library (either one you write or one of the many existing libraries).
In your second example:
moment().utc()
just sets a flag to tell moment.js methods to use UTC for everything. Then using:
....toDate().getTimezoneOffset()
returns a Date object, then gets the host system timezone offset for that date as if you'd done:
new Date().getTimezoneOffset()
If you want to only use UTC, then use UTC methods for everything and ignore timezones completely (which I think is what you want to do).
Lets say I have the following timezone aware Date objects in Javascript:
var date1 = new Date("2019-07-02T07:30:00-05:00");
var date2 = new Date("2020-08-05T00:00:00-05:00");
What is the best way to merge these two, such that I keep the time from date1 and the date from date2, yielding:
new Date("2020-08-05T07:30:00-05:00");
I've tried:
date1.setDate(date2.getDate());
date1.setMonth(date2.getMonth());
date1.setYear(date2.getYear());
which set the day and month correctly for date1, but the year is incorrectly set to "120" with the example inputs above.
A few things:
Date objects cannot be time zone aware. When passed a string with a time zone offset like the ones you showed here, they use that offset to determine the equivalent UTC time. Ultimately the only thing stored within the Date object is the numeric Unix timestamp that corresponds to that UTC time. You can see this with .getTime() or .valueOf().
getDay/setDay are for the day of the week, Saturday (0) through Sunday (6). Use getDate/setDate for the day of the month.
getYear/setYear are for two-digit years (or rather the current year minus 1900), and should not be used ever. Use getFullYear/SetFullYear instead.
Because of the first point, what you ask is not possible. Or at least, not possible with the full range of values that might be encountered in such strings. Instead, you can manipulate the strings directly, or you can use a library such as Luxon or Moment.
I'd also think a bit harder about what you're actually trying to accomplish. Where do these two values come from? Why are portions of the data important and others to be discarded? What will you do if the offsets are different between the two values? Only you can answer these points.
Manipulate the ISO strings directly:
const date1 = new Date("2019-07-02T07:30:00-05:00");
const date2 = new Date("2020-08-05T00:00:00-05:00");
const date3 = new Date([
date2.toISOString().split('T')[0],
'T',
date1.toISOString().split('T')[1],
].join(''));
console.log({date1, date2, date3});
By the way, there is no "timezone-aware" date objects. Once Date(...) is fired, time is UTC. getTimezoneOffset gets the difference from the local timezone to UTC.
I wish to create a new Date in JS, but have it be cast as UTC time. For example, suppose castAsUTC() produces the following desired effect:
var x = new Date('2019-01-01T00:00:00') // In local time (PST)
castAsUTC(x).toISOString(); // => '2019-01-01T00:00:00Z'
// x.toISOString() gives us '2019-01-01T08:00:00Z', which is undesired
Currently, my function looks like this:
function castAsUTC(date) {
return new Date(x.toLocaleString() + '+00:00');
}
Is there a cleaner/nicer way of producing the same effect? Thanks in advance!
EDIT: To be more specific, I'm interested in transforming the date's timezone, without changing its actual value with as little arithmetic as possible. So calling .toISOString() will produce the same date as it is in local time.
I am currently using the moment-timezone library, but I can't seem to get the desired effect using that, either. I would definitely accept an answer that uses Moment.js
You can switch a Moment instance to UTC using the utc function. Then just use format to get whatever the specific output you want from it.
If indeed the string you have is like the one shown, then the easiest thing to do would be to append a Z to indicate UTC.
var input = '2019-01-01T00:00:00';
var date = new Date(input + 'Z');
var output = date.toISOString();
Or, if you would like to use Moment.js, then do this:
var input = '2019-01-01T00:00:00';
var m = moment.utc(input);
var output = m.format();
You do not need moment-timezone for this.
tl;dr;
You formatted the date wrong. Add the letter "Z" to the end of your date string and it will be treated as UTC.
var x = new Date('2019-01-01T00:00:00Z') // Jan 1, 2019 12 AM UTC
These formatting issues are easier to manage with a library like momentjs (utc and format functions) as described in other answers. If you want to use vanilla javascript, you'll need to subtract out the timezone offset before calling toISOString (see warnings in the longer answer below).
Details
Date in javascript deals with timezones in a somewhat counter intuitive way. Internally, the date is stored as the number of milliseconds since the Unix epoch (Jan 1, 1970). That's the number you get when you call getTime() and it's the number that's used for math and comparisons.
However - when you use the standard string formatting functions (toString, toTimeString, toDateString, etc) javascript automatically applies the timezone offset for the local computers timezone before formatting. In a browser, that means it will apply the offset for the end users computer, not the server. The toISOString and toUTCString functions will not apply the offset - they print the actual UTC value stored in the Date. This will probably still look "wrong" to you because it won't match the value you see in the console or when calling toString.
Here's where things really get interesting. You can create Date's in javascript by specifying the number of milliseconds since the Unix epoch using new Date(milliseconds) or by using a parser with either new Date(dateString). With the milliseconds method, there's no timezone to worry about - it's defined as UTC. The question is, with the parse method, how does javascript determine which timezone you intended? Before ES5 (released 2009) the answer was different depending on the browser! Post ES5, the answer depends on how you format the string! If you use a simplified version of ISO 8601 (with only the date, no time), javascript considers the date to be UTC. Otherwise, if you specify the time in ISO 8601 format, or you use a "human readable" format, it considers the date to be local timezone. Check out MDN for more.
Some examples. I've indicated for each if javascript treats it as a UTC or a local date. In UTC, the value would be Jan 1, 1970 at midnight. In local it depends on the timezone. For OP in pacfic time (UTC-8), the UTC value would be Jan 1, 1970 at 8 AM.
new Date(0) // UTC (milliseconds is always UTC)
new Date("1/1/1970"); // Local - (human readable string)
new Date("1970-1-1"); // Local (invalid ISO 8601 - missing leading zeros on the month and day)
new Date("1970-01-01"); // UTC (valid simplified ISO 8601)
new Date("1970-01-01T00:00"); // Local (valid ISO 8601 with time and no timezone)
new Date("1970-01-01T00:00Z"); // UTC (valid ISO 8601 with UTC specified)
You cannot change this behavior - but you can be pedantic about the formats you use to parse dates. In your case, the problem was you provided an ISO-8601 string with the time component but no timezone. Adding the letter "Z" to the end of your string, or removing the time would both work for you.
Or, always use a library like momentjs to avoid these complexities.
Vanilla JS Workaround
As discussed, the real issue here is knowing whether a date will be treated as local or UTC. You can't "cast" from local to UTC because all Date's are UTC already - it's just formatting. However, if you're sure a date was parsed as local and it should really be UTC, you can work around it by manually adjusting the timezone offset. This is referred to as "epoch shifting" (thanks #MattJohnson for the term!) and it's dangerous. You actually create a brand new Date that refers to a different point in time! If you use it in other parts of your code, you can end up with incorrect values!
Here's a sample epoch shift method (renamed from castAsUtc for clarity). First get the timezone offset from the object, then subtract it and create a new date with the new value. If you combine this with toISOString you'll get a date formatted as you wanted.
function epochShiftToUtc(date) {
var timezoneOffsetMinutes = date.getTimezoneOffset();
var timezoneOffsetMill = timezoneOffsetMinutes * 1000 * 60;
var buffer = new Date(date.getTime() - timezoneOffsetMill);
return buffer;
}
epochShiftToUtc(date).toUTCString();
I have the following data structure. The first column is intervals. The first row of the interval datum is a unix time and the subsequent data are intervals (i.e. 300*1, 300*2, ect). The other column is the data values. Here is the head of the data:
a1521207300,555.45
1,554.53
2,554.07
3,553.9
4,552.67
And here I went about converting the unix time to a Date object. The a here is ornamental, so I slice() at 1 like so:
var rawTime = data[0].interval;
var timeValue = Math.round(rawTime.slice(1));
console.log(timeValue)
console.log(new Date(timeValue))
I also tried using parseInt() instead of round(). The console shows that this unix time is equivalent to: Jan 18 1970 which I had quite the guffaw at. Then I got to thinking, maybe I did something wrong. It's supposed to be a very recent date -- March 16th 2018. This is strange because my understanding is that javascript can be passed a unix date directly as per this answer.
I also checked the unix time at a conversion site: www.onlineconversion.com/unix_time.htm
Which confirmed that it's indeed a March 16th 2018 timestamp.
Question: Why is this unix date for my March 2018 data being treated like a 1970's date? Maybe the a is actually doing something after all... Anyway, what is the correct way to handle this time stamp? It's only 10 numerical digits, it does not seem to be a precision problem. Date can handle unix times up to 13 digits I believe.
As per the documentation, when you invoke new Date(value) with an integer value, it is used as the number of milliseconds since January 1, 1970. To get the date you want, the value 1521207300 appears to be number of seconds instead of milliseconds. That is, you missed a factor of 1000. new Date(1521207300000) gives Fri Mar 16 2018.
When I take away new from new Date it seems to be ok. Not sure why though.
The documentation mentions the different behavior:
Note: JavaScript Date objects can only be instantiated by calling JavaScript Date as a constructor: calling it as a regular function (i.e. without the new operator) will return a string rather than a Date object; unlike other JavaScript object types, JavaScript Date objects have no literal syntax.
It seems when called as a function Date(value), it treats the value as the number of seconds, instead of milliseconds. I didn't dig deep enough to confirm this, because it doesn't matter: the documentation says to not use it this way (and since it gives a string instead of a date object, it's not very useful anyway).
I've read this question:
How do you convert a JavaScript date to UTC?
and based on this I implemented this conversion in a dateTools module as follows:
[Update]
var dt, utcTime;
dt = new Date();
utcTime = new Date(Date.UTC(dt.getFullYear(),
dt.getMonth(),
dt.getDate(),
dt.getHours(),
dt.getMinutes(),
dt.getSeconds(),
dt.getMilliseconds()));
Now I'd like to write unit tests. My idea was to check whether the result is actually in UTC, but I don't know how.
All the toString, toUTCString and similar methods seem to be identical for the input (non UTC) and output (UTC) date.
Only the result of the getTime method differs.
Is there a way to check wheter a date is UTC in javascript? If not, is there a better idea to unit test this?
To give more context:
Only converting the it to a UTC string is not that helpful, because in the next step the date is sent to an Asp.net service and therefore converted to a string like:
'/Date([time])/'
with this code
var aspDate = '/Date(' + date.getTime() + ')/';
var aspDate = '/Date(' + date.getTime() + ')/';
This outputs the internal UNIX epoch value (UTC), which represents a timestamp. You can use the various toString methods to get a more verbose string representation of that timestamp:
.toString() uses the users timezone, result is something like "Fri Jan 25 2013 15:20:14 GMT+0100" (for me, at least, you might live in a different timezone)
.toUTCString() uses UTC, and the result will look like "Fri, 25 Jan 2013 14:20:15 GMT"
.toISOString() uses UTC, and formats the datestring according to ISO: "2013-01-25T14:20:20.061Z"
So how do we construct the time value that we want?
new Date() or Date.now() result in the current datetime. No matter what the user's timezone is, the timestamp is just the current moment.
new Date(year, month, …) uses the users timezone for constructing a timestamp from the single values. If you expect this to be the same across your user community, you are screwed. Even when not using time values but only dates it can lead to odd off-by-one errors.
You can use the setYear, setMonth, … and getYear, getMonth … methods to set/get single values on existing dates according to the users timezone. This is appropriate for input from/output to the user.
getTimezoneOffset() allows you to query the timezone that will be used for all these
new Date(timestring) and Date.parse cannot be trusted. If you feed them a string without explicit timezone denotation, the UA can act random. And if you want to feed a string with a proper format, you will be able to find a browser that does not accept it (old IEs, especially).
Date.UTC(year, month, …) allows you to construct a timestamp from values in the UTC timezone. This comes in handy for input/output of UTC strings.
Every get/set method has a UTC equivalent which you can also use for these things.
You can see now that your approach to get the user-timezone values and use them as if they were in UTC must be flawed. It means either dt or utcTime has the wrong value, although using the wrong output method may let it appear correct.
getTimezoneOffset
Syntax: object.getTimezoneOffset( ) This method
returns the difference in minutes between local time and Greenwich
Mean Time. This value is not a constant, as you might think, because
of the practice of using Daylight Saving Time.
i.e.
var myDate = new Date;
var myUTCDate = new Date(myDate - myDate.getTimezoneOffset() * 60000);
alert(myUTCDate);
note: 60000 is the number of milliseconds in a minute;