Datetime conversion to UTC from selected timezone [duplicate] - javascript

This question already has answers here:
How to initialize a JavaScript Date to a particular time zone
(20 answers)
How do I specify the time zone when creating a JavaScript Date?
(1 answer)
Closed 1 year ago.
I have developed a page where users can filter records based on their selections in the filters.
The datetime picker allows users to select a specific datetime and a dropdown allows the user to select the time zone in which they want that selected date to be converted to UTC.
For example, on my computer the time zone set is Asia/Karachi which has an offset of +5 UTC.
But I want to select Europe/Prague from the dropdown and 09/14/2021 3.30pm as the date time. The time should be converted to 1.20 PM as Europe/Prague has a time offset of +2 from UTC using below code.
var ddate = new Date(date.toLocaleString('en-US', {
timeZone: 'Europe/Prague'
}));
However, this yields 12.30 PM as the time. Apparently it is converting from Asia/Karachi(my computer time zone) to Europe/Prague. But I want the selected datetime to be converted to UTC based on the time zone the user selects from the dropdown of available time zones.
Is there any possible solution? I have tried researching and coding a lot but haven't found any yet.

Have you tried moment & moment-timezone?
maybe something like this:
import moment from "moment";
const myDate = "09/14/2021 3:30 PM";
const myTimeZone = "Europe/Prague";
let convertedTime = moment.tz(myDate, time_zone).format();
and you can set your format to be ("HH:mm A")

If you want a date constructor for specific timezones make use of new Date('YYYY-MM-DDTHH:NN:SS.sss±HH:NN').
What you describe and want, is what Date already does if you generate a Date using new Date('YYYY-MM-DDTHH:NN:SS.sss±HH:NN'). You simply have to use it in this format.
±HH:NN is timezone information from your timezone-dropdown. It can either start with a + or a -. (Take a look at the 3rd snippet)
Other formats as the ones i mentioned, are invalid if passed to the Date constructor and you may get unexpected results.
The dropdown (the one about the timezone) requires the information of +hrs:mins and -hrs:mins respective to the timezone selected.
The following describes the Date constructor.
Don't pass a formated string like this to new Date() - it's invalid.
If you use new Date() you either have to pass a time value in milliseconds, a date only or an ISO Timestamp.
new Date(date.valueOf()) results in a copy
new Date(date.toISOString()) results in a copy
new Date(date.toJSON()) results in a copy
new Date(date.toDateString()) results in a copy of the date only => no hour GMT +0 on the current day.
All examples above (except the last one) are a copy of the original date.
For more information on the arguments supported by the Date-constructor take a look at Date.parse()#Date Time String Format
There is no need to change anything. If it's midnight at Asia/Karachi, it will be 9pm (21:00) in Europe/Prague.
As you can see on this example, use GMT+0 or any other accepted Time-Zone String.
Examples of different arguments
1
const date = new Date("2021-02-14T00:00:05.000+00:00"); // 5 seconds after midnight on February 14th, 2021 GMT+0
["GMT+0", "UTC", "America/New_York", "Europe/Madrid", "Europe/Prague", "Europe/Moscow", "Asia/Karachi", "Asia/Tokyo", "PRC"].forEach(timeZone => {
console.log(timeZone, date.toLocaleString('en-US', { timeZone }))
});
2
const date = new Date("2021-09-14");
["GMT+0", "UTC", "America/New_York", "Europe/Madrid", "Europe/Prague", "Europe/Moscow", "Asia/Karachi", "Asia/Tokyo", "PRC"].forEach(timeZone => {
console.log(timeZone, date.toLocaleString('en-US', { timeZone }))
});
3
// 16:00 at timezone +9 is 7 am at gmt
const date = new Date("2021-09-14T16:00:00.000+09:00");
["GMT+0", "UTC", "America/New_York", "Europe/Madrid", "Europe/Prague", "Europe/Moscow", "Asia/Karachi", "Asia/Tokyo", "PRC"].forEach(timeZone => {
console.log(timeZone, date.toLocaleString('en-US', { timeZone }))
});

Related

Using Mongoose and Luxon to try and display date of event, but I'm getting the day before

I am using Mongoose and Luxon to display a date, selected by the user from a form. But the date is console.logging one date, but displaying on the page as the day before.
This is my model:
const mongoose = require("mongoose");
const { DateTime, Settings } = require("luxon");
// Configure time zone
console.log(Settings);
const Schema = mongoose.Schema;
let AccomplishmentSchema = new Schema({
dateInput: {
type: Date,
required: true,
},
textInput: {
type: String,
required: true,
},
});
AccomplishmentSchema.virtual("dateInput_formatted").get(function () {
return DateTime.fromJSDate(this.dateInput).toLocaleString(DateTime.DATE_FULL); // format 'YYYY-MM-DD
});
module.exports = mongoose.model("Accomplishment", AccomplishmentSchema);
And this is my console log vs what is showing up on the page
dateInput: 2023-01-01T00:00:00.000Z,
textInput: 'etst',
December 31, 2022
etst
I can only assume this has to do with some kind of time conversion, but I've tried changing the time zone, and the settings, although I could've read the docs wrong. I cannot find a way to fix this issue.
When working with JS Date, the most important thing to understand is that Date doesn't store or know about a time zone. It simply stores the number of milliseconds since midnight on Jan 1, 1970 (aka UNIX epoch) in the UTC time zone. Then, when you call Date methods, the results are calculated for the current user's time zone (for methods like getMinutes()) or UTC (for methods like getUTCMinutes()). But the underlying data inside the Date is just a timezone-less number of milliseconds.
With that in mind, let's look at a simple example: new Date(0), which is equivalent to new Date('1970-01-01T00:00Z').
If your computer's time zone set to the Europe/Paris time zone, then when you call new Date(0).toLocaleDateString('en-US') then you'll get a result of '1/1/1970'. But if you change your computer's time zone to America/Los_Angeles and run the same code, you'll get a result of '12/31/1969'.
This happens because the Date's stored value corresponds to midnight on Jan 1, 1970 in the UTC time zone. In Paris that instant was 1:00AM on Jan 1, 1970, while in California it was 4:00PM on December 31, 1969. The same exact Date will print a different date depending on the time zone that's active at the time that date is printed.
Where this becomes particularly problematic is in cases where you're trying to express a date without a time. For example, assume you store a Date value of 2023-01-01T00:00:00.000Z in MongoDB.
When you display that Date in a date picker in a browser in California, the date shown will be Dec 31, 2022, because California is 8 hours ahead of UTC.
One way to solve this problem is to tell Date that the date you're using is a UTC date. Like this:
utcDateStoredInMongoDB = new Date ('2023-01-01T00:00:00.000Z');
// Get the year, month, and day in that Date, from the perspective
// of the UTC time zone.
year = utcDateStoredInMongoDB.getUTCFullYear();
month = utcDateStoredInMongoDB.getUTCMonth();
day = utcDateStoredInMongoDB.getUTCDate();
// Use that year, month, day to initialize a new Date in
// the user's time zone.
dateForDatePicker = new Date(year, month, day);
// Use that for your date picker.
dateForDatePicker.toLocaleDateString();
// => '1/1/2023'
// To go in the other direction, pull out the year/month/day
// in the current time zone, and turn that into a UTC date.
dateReturnedByDatePicker = new Date(2023, 02, 15);
year = dateReturnedByDatePicker.getFullYear();
month = dateReturnedByDatePicker.getMonth();
day = dateReturnedByDatePicker.getDate();
dateToStoreInMongoDB = new Date(Date.UTC(year, month, day));
dateToStoreInMongoDB.toISOString();
// => '2023-03-15T00:00:00.000Z'
Note that this all will get a lot easier in a year or so when the new JavaScript built-in date/time API, called Temporal starts shipping in browsers and Node.js. With Temporal, these kinds of problems are much easier because there's a dedicated API, Temporal.PlainDate for dealing with date-only values.
Luxon is working as intended and the display is correct. What is wrong is the data in your MongoDB.
Most libraries consider current time zone when you parse a date. I live in Switzerland.
const moment = require("moment");
console.log( moment('2023-01-01').toDate() )
> 2022-12-31T23:00:00.000Z
const { DateTime } = require("luxon");
console.log( DateTime.fromISO('2023-01-01').toJSDate() )
> 2022-12-31T23:00:00.000Z
const dayjs = require('dayjs')
console.log( dayjs('2023-01-01').toDate() )
> 2022-12-31T23:00:00.000Z
However, the native JavaScript new Date() constructor does not!
console.log( new Date('2023-01-01') )
> 2023-01-01T00:00:00.000Z
Maybe this behavior depends on your environment and scripting engine, I don't know.
You should also use Luxon when you parse the input data - don't forget to use .toJSDate() to convert the DateTime object back to Javascript native Date object.
Luxon uses the system time zone by default. You can change it:
const { DateTime, Settings } = require("luxon");
Settings.defaultZone = 'America/New_York';
If you don't care about the time, you can also use startOf('day')
Settings.defaultZone = 'America/New_York';
DateTime.now().startOf('day').toJSDate()
> 2023-01-13T05:00:00.000Z
DateTime.now().startOf('day').toISO()
> '2023-01-13T00:00:00.000-05:00'

Javascript - force new Date constructor to treat argument as UTC

Have an API end point that accepts a date and does some processing. I do give via postman the date as UTC (denoted by the Z at the end). Sample input sent from Postman.
{
"experimentDate":"2022-01-12T12:30:00.677Z",
}
In the code when I do
let startDate = new Date(experimentDate);
//other calculations e.g get midnight of the startDate
startDate.setHours(0,0,0,0);
The first assignment sets startDate corrected to the current timezone. The rest of my calculations go bad as a result of this. For instance when I use the setHours function setting time to 0, I expect it to be at midnight of the UTC time given but it goes to midnight of my current timezone.
Should new Date not keep the date in UTC given that there is a Z at the end of the date?
Should I reconvert it to UTC like below. Is this not redundant?
Date.UTC(startDate.getUTCFullYear(), startDate.getUTCMonth(),
startDate.getUTCDate(), startDate.getUTCHours(),
startDate.getUTCMinutes(), startDate.getUTCSeconds())
What is the right way to achieve this?
The Date object will be stored as a UTC date, however there are different methods on it that will set/get the date or time for both UTC and local timezones. Try using .setUTCHours(), rather than .setHours().
You can use the Date constructor to parse the timestamp provided.
Most of the methods will treat the date as a local time. For example, the getHours() method returns the hour for the specified date, according to local time.
However you can use the getUTCXXX() methods to get the UTC date components such as year, month, date, hour etc.
You can also use Date.toISOString() to get the date formatted as UTC.
You can use the Date.UTC method to get UTC midnight, passing in the relevant getUTCFullYear(), getUTCMonth(), getUTCDay() etc. from the experiment date.
This can then be passed to the Date constructor.
let timestamp = "2022-01-12T12:30:00.677Z";
const experimentDate = new Date(timestamp);
const midnightUTC = new Date(Date.UTC(experimentDate.getUTCFullYear(), experimentDate.getUTCMonth(), experimentDate.getUTCDate()))
console.log('Experiment date (UTC): ', experimentDate.toISOString());
console.log('Midnight (UTC): ', midnightUTC.toISOString());
You can also use Date.setUTCHours() to do the same thing.
let timestamp = "2022-01-12T12:30:00.677Z";
const experimentDate = new Date(timestamp);
const midnightUTC = new Date(experimentDate);
midnightUTC.setUTCHours(0,0,0,0);
console.log('Experiment date (UTC): ', experimentDate.toISOString());
console.log('Midnight (UTC): ', midnightUTC.toISOString());

convert a string to date object javascript returns an incorrect date

I am trying to convert string to a date object in javascript, however what i day that is minus 1 from day in string. I don't know what is wrong. Here is the method
function formatDate(date_str)
{
console.log(date_str); //input : 2020-03-11
let new_date = new Date(date_str);
console.log(new_date); //output : Tue Mar 10 2020 20:00:00 GMT-0400 (Eastern Daylight Time)
return new_date;
}
The most likely explanation is that parsing the input string "2020-03-11" with no other information equates it to a date of March 11, 2020 at midnight UTC. When you are in a different time zone, then it calculates your time zone offset and gives you a time four hours earlier which would be the day before in local time.
Why such behavior:
The date string(2020-03-11) did not specify any time zone, when you attempt to create a Date object with this string, JavaScript would assume the time zone to be UTC so the date is internally dealt with like as: 2020-03-11T00:00:00Z.
console.log(new_date) would internally call .toString() method on the new_date object and doing that would trigger a date conversion to your local time zone. From the question I believe you(the time on your machine actually) are in GMT-4, this is why 4 hrs is being subtracted from the output of the logs. More details about the date conversion due to time zone here
Possible Fix:
Firstly, we should understand that this is not a bug or an error, it is just how the JavaScript Date object works.
For the scenario described in your question, I'm guessing what you want is to avoid this time zone conversion on the date string. What you can do is add timezone information to the date string before using it to instantiate a date object, with this, javascript wouldn't assume that the date string you are passing into the Date() constructor is in UTC, and when you call Date.toString() or any other similar methods, there won't be any surprises. An implementation for this can be something like this:
// Sorry for the super long function name :)
function add_local_time_zone_to_date_string (date_string) {
// Getting your local time zone
let local_time_zone = (new Date().getTimezoneOffset() * -1) / 60;
// local_time_zone is a number, convert it to a string
local_time_zone = (local_time_zone.toString())
// Padding with 0s if needed to ensure it is of length 2
local_time_zone = local_time_zone.padStart(2, '0');
return `${date_string}T00:00:00+${local_time_zone}`
}
function formatDate(date_str) {
console.log(date_str); //input : 2020-03-11
const date_with_time_zone = add_local_time_zone_to_date_string(date_str);
let new_date = new Date(date_with_time_zone);
console.log(new_date); //output : There should be no surprises here
return new_date;
}

convert date based on timezone user is in

I have a date I want to convert based on their timezone.
For example, I want to set it in EST time (America/New_York)
2019-04-24 12:00:00
and if the user comes across the site from America/Los_Angeles, it will appear:
2019-04-24 09:00:00
I need to be able to return the hour, so in that example: 9.
I tried using https://github.com/iansinnott/jstz to determine their timezone and https://moment.github.io/luxon in hopes of handling the conversion w/o any luck.
I was testing by changing the timezone on my computer w/o any luck.
It sounds like you're asking to convert from a specific time zone to the user's local time zone (whatever it may be). You do not need time zone detection for that, but at present you do need a library. (Answers that suggest using toLocaleString with a time zone parameter are incorrect, as that function converts to a specific time zone, but cannot go the other direction.)
Since you mentioned Luxon, I'll provide a Luxon specific answer:
luxon.DateTime.fromFormat('2019-04-24 12:00:00', // the input string
'yyyy-MM-dd HH:mm:ss', // the format of the input string
{ zone: 'America/New_York'}) // the time zone of the input
.toLocal() // convert to the user's local time
.toFormat('yyyy-MM-dd HH:mm:ss') // return a string in the same format
//=> "2019-04-24 09:00:00"
This capability is also provided by other libraries, such as date-fns-timezone, js-Joda, or Moment-Timezone, but it is not yet something built in to JavaScript.
Converting date based on the time can be done like this. reference convert date to another time zone example snippet is under.
var usaTime = new Date().toLocaleString("en-US", {timeZone: "America/New_York"});
usaTime = new Date(usaTime);
console.log('USA time: '+usaTime.toLocaleString())
var usaTime = new Date().toLocaleString("en-US", {timeZone: "America/Los_Angeles"});
usaTime = new Date(usaTime);
console.log('USA time: '+usaTime.toLocaleString())
You could keep a list of timzeone identifiers and a list of their corresponding +/- number of hours with respect to your local time (which is returned by your time function).
Once you have a user's time zone, and you have extracted the current hour from the local timestamp simply look up the timezone in your list and use it's index to access the second list to find how many hours to add or subtract from the users time.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString
var date = new Date(Date.UTC(2012, 11, 12, 3, 0, 0));
// toLocaleString() without arguments depends on the implementation,
// the default locale, and the default time zone
console.log(date.toLocaleString());
// → "12/11/2012, 7:00:00 PM" if run in en-US locale with time zone America/Los_Angeles
Or you can use getYear, getMonth, getDate, getHours, getMinutes, getSeconds to format your own representation of the date. These methods all return values according to the user's local timezone.
I think the question may need more clarification - my first impression was you refer to a date-time that you already have and serve from the server. Doesn't this problem boil down to the Date object being "user-timezone-aware"? or not? But it is (some methods are, to be exact)
Your date/time is 2019-04-24 12:00:00 EDT (i assume P.M.)
This means the Unix timestamp of this in milliseconds is 1556121600000
(i assume daylight is on for April so not pure EST but EDT and an offset of UTC-4:00)
When you call
console.log(new Date(1556121600000).getHours())
doesn't this return 9 as you suggest, for Javascript executed on a browser from America/Los_Angeles with PDT timezone?
As suggested at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getHours :
The getHours() method returns the hour for the specified date,
according to local time.

Displaying timezone-formatted date as UTC time

on my UI, I try to display a date based on a specific timezone. In this example, I will use Americas/New_York as the timezone. This is how I did it.
$scope.getStartTime = function(){
var date = new Date();
return moment(date).tz("Americas/New_York").format('YYYY-MM-DD HH:mm:ss');
};
Afterwards, I want to send this data and send it to my server. In my server however, I want it so that it is always serialized into UTC time instead of in the New York Timezone (EST).
For example, if the time was 12:00 P.M. in New York, then the time would be serialized to 4:00 P.M. in UTC time before it was sent to the backend. This was my attempt:
var date = getStartTime();
....
// Display the date in the UI
....
$scope.revertStartTime(date);
$scope.revertStartTime = function(startTime) {
console.log("Start time: ", startTime);
console.log("Moment: ", moment(startTime).format());
console.log("Converted to utc time: ", moment().utc(startTime).format());
return moment.utc(startTime).format("YYYY-MM-DD'T'HH:mm:ss.SSSZ");
}
I tried to revert the start time by using the moment().utc() function and hoped that the date would change to a UTC based date but unfortunately it keeps turning my date into the localized date instead of UTC date and I'm not sure why. Any help would be appreciated. Thanks!
Edit:
Tried to follow the below method and here is what I did:
$scope.getStartTime = function(){
var date = new Date();
var startTime = new moment(date).tz($rootScope.userinfo.timeZone).format('YYYY-MM-DD HH:mm:ss');
$rootScope.offset = moment().utcOffset(startTime);
console.log("offset: ", $rootScope.offset);
return startTime;
};
$scope.revertStartTime = function(startTime) {
console.log("User Selected Time: ", moment().utcOffset(startTime).format('YYYY-MM-DD HH:mm:ss'));
return moment().utcOffset(startTime).format('YYYY-MM-DD HH:mm:ss');
}
But all I get is an error saying that revertStartTime returns an Invalid Date.
A few things:
Hoping it's a typo, but just to point out, the zone ID is America/New_York, not Americas/New_York.
You can pass a value as moment.utc(foo), or moment(foo).utc(), but not moment().utc(foo). The difference is that one interprets the input as UTC and stays in UTC mode, while they other just switches to UTC mode. You can also think of this as "converting to UTC", but really the underlying timestamp value doesn't change.
Yes, you can switch to UTC mode and call format, but you can also just call .toISOString() regardless of what mode you're in. That's already in the ISO format you're looking for.
Note that if you start with a unique point in time, and you end with converting to UTC, no amount of switching time zones or offsets in the middle will change the result. In other words, these are all equivalent:
moment().toISOString()
moment.utc().toISOString()
moment(new Date()).toISOString()
moment.utc(new Date()).toISOString()
moment(new Date()).utc().toISOString()
moment().tz('America/New_York').toISOString()
moment.tz('America/New_York').toISOString()
moment().utcOffset(1234).toISOString()
moment.utc().format('YYYY-MM-DD[T]HH:mm:ss.SSS[Z]')
moment().utc().format('YYYY-MM-DD[T]HH:mm:ss.SSS[Z]')
Only the last two even need to be in UTC mode, because the format function would produce different output if in local mode or in a particular time zone.
In order to accomplish this you'd want to use .utcOffset(). It is the preferred method as of Moment 2.9.0. This function uses the real offset from UTC, not the reverse offset (e.g., -240 for New York during DST). Offset strings like "+0400" work the same as before:
// always "2013-05-23 00:55"
moment(1369266934311).utcOffset(60).format('YYYY-MM-DD HH:mm')
moment(1369266934311).utcOffset('+0100').format('YYYY-MM-DD HH:mm')
The older .zone() as a setter was deprecated in Moment.js 2.9.0. It accepted a string containing a timezone identifier (e.g., "-0400" or "-04:00" for -4 hours) or a number representing minutes behind UTC (e.g., 240 for New York during DST).
// always "2013-05-23 00:55"
moment(1369266934311).zone(-60).format('YYYY-MM-DD HH:mm')
moment(1369266934311).zone('+0100').format('YYYY-MM-DD HH:mm')
To work with named timezones instead of numeric offsets, include Moment Timezone and use .tz() instead:
// determines the correct offset for America/Phoenix at the given moment
// always "2013-05-22 16:55"
moment(1369266934311).tz('America/Phoenix').format('YYYY-MM-DD HH:mm')

Categories