Converting Between Time-Zones using URLSearchParams - javascript

I'm working on a web application that supports link sharing. This link contains the clients selected dates (start and end date) from a date selector react component. As the user changes the selected dates they are accurately represented in the URL ready to be shared with other uses. When another user clicks on this link it should open the same web application except the default selected dates inside the date selector component will be parsed from the URL instead (if they exist otherwise uses the default).
This works exceptionally well when the links are shared between two people in the same time zone. However, if I send my link to someone in a different time-zone the selected dates for them are not the same as mine.
Currently when writing the local dates to the URL I am doing the following steps:
url.set("startDate", dates[0].toISOString());
url.set("endDate", dates[1].toISOString());
where date[0] and date[1] are date objects in the current users local time-zone.
When I parse the URL I do the following:
var startDate = new Date(url.get("startDate") ?? "");
var endDate = new Date(url.get("endDate") ?? "");
if (startDate.toString() === "Invalid Date") {
startDate = defaultStartDate; // user local time ( uses new Date() )
}
if (endDate.toString() === "Invalid Date") {
endDate = defaultEndDate; // user local time ( uses new Date() )
}
For example, I have two brokers running one in Eastern Standard and another in Pacific Standard. If I select 06/01/2021 and 06/05/2021 in Eastern Standard the link that is constructed looks like the following:
startDate=2021-06-01T04%3A00%3A00.000Z&endDate=2021-06-06T03%3A59%3A59.999Z
and when parsed by the user in Pacific Standard the resulting selected dates are:
05/31/2021 and 06/05/2021.
After some further debugging I believe this issue is occurring because the time set by default is 00:00:00 for start date and 23:59:59 for the end date. So when converting from EST -> PST new Date() is subtracting 4 hours from both dates (as it should). However, I need these to be the same dates across multiple time zones.

Given the strings are generated by toISOString, the resulting timestamp will be parsed to exactly the same time value by the built-in parser regardless of the user's system offset or settings.
The difference you see is from generating a local date and time from the time value based on different system settings. Given the same input timestamp, both systems should display exactly the same output timestamp if displayed for the same location, e.g. the following parses the timestamp in the OP, then presents the equivalent time in EDT and PDT respectively.
let s = '2021-06-01T04:00:00.000Z';
let startDate = new Date(s);
console.log(startDate.toLocaleString('en', {
timeZone: 'America/New_York',
timeZoneName: 'short'
}));
console.log(startDate.toLocaleString('en', {
timeZone: 'America/Los_Angeles',
timeZoneName: 'short'
}));
If you want the receiver to see the same date and time as the sender, then include the sender's IANA representative location (such as 'America/New_York' or encoded as 'America%2FNew_York') in the URL. But that doesn't change the generated time value, only the timestamp displayed by the various toString* methods.

Related

How to pass in dynamic value into toLocaleTimeString

So I'd like to pass in a dynamic value into toLocaleTimeString. The way the time is formatted is going to change based on the user location. For example time for a user in the UK should be something like 18:00:00, while for someone in America it would be 6:00:00 PM.
The only problem I'm having is doing a dynamic mapping of timezone to which locale to pass in (i.e. "en-GB", "en-US", etc). How do I do this?
I'm using momenttimezone and tzlookup to get the user timezone:
let timezone = tzlookup(userLatitude, userLongitude)
var date = new Date()
var localHour = date.toLocaleTimeString("xx-XX") // How do I dynamically set xx-XX to what it's supposed to be based on the user location?
Any help is much appreciated!
Time zones and locales are two orthogonal concepts. You should not make any assumptions that bind them together. In many cases, they can be quite different. For example, if I am an English speaking American visiting Japan, my device will likely have a time zone of Asia/Tokyo and a locale of en-US.
As for toLocaleTimeString (and toLocaleDateString, toLocaleString, Intl.DateTimeFormat, etc.) the default value of undefined will automatically use the user's current locale.
In other words:
// this uses the current locale and current time zone
date.toLocaleString()
// this uses a specific locale and current time zone
date.toLocaleString('en-US')
// this uses the current locale and a specific time zone
date.toLocaleString(undefined, {timeZone: 'Asia/Tokyo'})
// this uses a specific locale and a specific time zone
date.toLocaleString('en-US', {timeZone: 'Asia/Tokyo'})
In your example, if you want to display a time in the UK time zone (the mainland, not a dependency or overseas territory), then you can use the time zone Europe/London. Your call to tzlookup should be returning that. You should not choose a locale. Let the user's browser set that according to their preferences.
const timezone = tzlookup(userLatitude, userLongitude)
const date = new Date()
const localHour = date.toLocaleTimeString(undefined, {timeZone: timezone})
You should not need moment or moment-timezone for this operation.

Javascript date conversion and daylight saving time

I am calling an Api that has the dates in the following format: 2020-09-08T00:00:00Z. I want to get the current date of the user and compare it against the API date to see how much time is left. The problem is that users could be in UK or IE and the second problem how can I be sure that there is not a daylight saving time issue here. I would also like to display the dates to the user.
const userDate = new Date() //current date
const apiDate = new Date('2020-09-08T00:00:00Z') //api date
if (userDate.getTime() <= apiDate.getTime())
//do something

utc time according to time zone in javascript

I have a problem in my application. I display date in this format DD/MM/YYYY example 09/07/2020. But the problem is in my form if the user lives in reunion island enters the date 10/07/2020 (the date of today plus one day), the user lives in France sees 09/07/2020.
How can do in javascript to have the same date enters by the user who lives anywhere.
I want to have this : User lives in Reunion island enters 10/07/2020 and the user lives in France sees 10/07/2020
This is a recurrent issue in JS, and the solution is not easy as Date is stored as local time in JS.
You can use an ISO string to input your time: YYYY-MM-DDTHH:mm:ss.sssZ.
Executing this:
new Date('2020-01-01T00:00:00.000Z')
Will give the same results.
If you can, i'll advice you to use Moment.js, it will you save a lot of effort if you plan to do date operations.
If you want to save absolute dates i would recommend you to use a library like moment-timezone and save the dates in utc.
In order to do this you should do two things:
Set the date in utc before sending it to the server:
const parseDateBeforeSend = (selectedDate) => {
const parsedDate = moment(selectedDate).tz('utc', true).startOf('day').toDate()
return parsedDate
}
Transform the date to utc before showing it to the user:
const showDate = (date) => {
const parsedDate = moment(date).tz('utc').format("DD/MM/YYYY")
return parsedDate
}
For your information:
When using tz function in step 1 you can see that it has a second parameter set to true, what this does is to keep the original time and only update the timezone. This would keep the original date you want to show.
In the second step we omit this parameter since we want to show the actual date saved in utc.

How to use moment-timezone for mapping with C# TimeZoneInfo?

I need to make illusion of working in the selected by the user timezone. The problem that server and client code are stick to javascript date. So to achieve the requirement I have made mannualy mapping from utc to the date on the client side:
dateToServer(date) {
const momentDate = moment(date);
let serverDate = null;
if (momentDate.isValid() && date) {
const browserUtcOffset = momentDate.utcOffset();
serverDate = momentDate
.utc()
.subtract(this.clientTimeZoneOffset, 'minutes')
.add(browserUtcOffset, 'minutes')
.toDate();
}
return serverDate;
}
dateToClient(date) {
const momentDate = moment(date);
let uiDate = null;
if (momentDate.isValid() && date) {
const browserUtcOffset = momentDate.utcOffset();
uiDate = momentDate
.utc()
.subtract(browserUtcOffset, 'minutes')
.add(this.clientTimeZoneOffset, 'minutes')
.toDate();
}
return uiDate;
}
I'm adding/subtracting the browserUtcOffset, because it is adding/subtracting automatically by browser when the date is go between server and client.
It was working well, but this solution is missing handling of the DST. I'd like to check is DST active for the date and then add DST offset if need.
Here C# code, that can do this:
string timeZone = "Central Standard Time";
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
DateTime date = new DateTime(2011, 3, 14);
Console.WriteLine(timeZoneInfo.BaseUtcOffset.TotalMinutes); // -360
Console.WriteLine(timeZoneInfo.GetUtcOffset(date).TotalMinutes); // -300
Console.WriteLine(timeZoneInfo.IsDaylightSavingTime(date)); // true
Console.WriteLine(timeZoneInfo.DaylightName);
Console.WriteLine(timeZoneInfo.SupportsDaylightSavingTime);
I have found the isDST in the momentjs, and when I have my windows local timezone to CST and check moment([2011, 2, 14]).isDST(); in browser console I see the true. How you can see the isDST is depends on the browser local time.
Next step try to use moment-timezone to do smth like I have done in C#. Unfortunately I don't understand how to achieve this. The first problem that as start point I have: UTC time, Base offset(-360 in C# sample), timezone name: Central Standard Time, but timezones in the moment-timezone are different.
moment.tz.names().map(name => moment.tz.zone(name)).filter(zone => zone.abbrs.find(abbr => abbr === 'CST') != null) this code returns 68 timezones.
Why there each of them have many abbrs? What mean untils? All I want to check if UTC time in the selected timezone, which is "Central Standard Time", the daylight saving day active. See C# sample one more time :)
string timeZone = "Central Standard Time";
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
DateTime date = new DateTime(2011, 3, 14);
Console.WriteLine(timeZoneInfo.BaseUtcOffset.TotalMinutes); //-360
Console.WriteLine(timeZoneInfo.GetUtcOffset(date).TotalMinutes); //-300
Console.WriteLine(timeZoneInfo.IsDaylightSavingTime(date)); //true, I have checked is daylightsavingtime for the date
Console.WriteLine(timeZoneInfo.DaylightName);
Console.WriteLine(timeZoneInfo.SupportsDaylightSavingTime);
The application has been written on the angularjs 1.6.3.
A few things:
The Date object in JavaScript tracks a specific UTC-based point in time. You can see that timestamp with a call to .valueOf() or .getTime(). It is only certain functions and constructor parameters that work with the local time zone (such as .toString()). That local time zone is applied at the time the function is called. One cannot substitute a different time zone (except in the options object passed to toLocaleString). Thus, the Date object can not be converted from one time zone to another. Because your two functions both take in a Date object and return a Date object, all you are doing in the middle is picking a different point in time. This is also seen inside the function where you use the add and subtract methods of Moment. They manipulate the represented point in time - they do not change the time zone.
By passing this.clientTimeZoneOffset, You appear to have conflated a time zone with a time zone offset. These are separate concepts, as a time zone may go through multiple different offsets, due to DST but also due to changes in standard time, where they have occurred in history. See also "Time Zone != Offset" in the timezone tag wiki. It is not useful to pass just the client's offset, as that offset only applies to a single point in time. You cannot use it for time zone conversions, because it doesn't tell you anything about which offsets are used for other points in time.
Instead, pass a time zone identifier. On Windows in .NET, these look like "Central Standard Time" (representing both standard and daylight time despite the name), and in JavaScript and most other operating systems, IANA time zone names are used. They look like "America/Chicago". This is also covered in the timezone tag wiki.
If your .NET code is using Windows identifiers, you can use my TimeZoneConverter library to convert from Windows to IANA, then send that string to the browser as the time zone.
With Moment-Timezone, you can simply check the DST like so:
moment.tz([2011, 2, 14], 'America/Chicago').isDST()
Again, you should be using IANA time zone IDs, and TimeZoneConverter can provide them if you're using Windows time zones server-side.
You could consider using Noda Time on the server-side, with its TZDB time zone provider. That would allow you to use IANA time zones on both sides. This is also the case with TimeZoneInfo when run on .NET Core on Linux or Mac OSX.
The reason you see so many entries when you searched for abbreviations with CST is that time zone abbreviations are ambiguous. You may have meant US Central Standard Time, but you might also have meant Cuba Standard Time or China Standard Time, or a variety of other places that use that abbreviation.
Regarding the untils array, you generally don't need to concern yourself with that. It is part of the internal data that Moment-Timezone uses to pick the correct point in time for choosing a time zone offset and abbreviation.
You said at the end, something slightly different:
All I want to check if UTC time in the selected timezone, which is "Central Standard Time", the daylight saving day active.
The example I gave earlier assumed you were starting with that time zone's local time. If you're starting with the UTC time, then it's like this:
moment.utc([2011, 2, 14]).tz('America/Chicago').isDST()
Of course, you can pass various other supported inputs where I pass the array. The Moment docs can help you with available options.
Check please the answer of Matt Johnson for this question. It was very usefull for me.
Vocabulary
UI date - the date that user see on the html date input when
selecting the date. UI date is the native js date.
Server date - the UTC date that is stored in the
database.
timezone id - string, that identified the timezone. They are different for windows(.net), IANA(javascript) and rails conventions.
Problem
The main problem f.e. when a man logged in on the PC which is located in the timezone +3, but setting for his account is -6. The angularjs and kendo is using on the project and the kendo time is working only with native js date. And native js date is always in the browser timezone, for this example +3. But I should setup the stuff like it in -6. F.e. example user has selected time 07:00, the UTC will be 04:00 (07:00 - 3), but as for his account the timezone is -6, the utc should be 13:00 (07:00 + 6). And this conversations are applied automatically when we converting native js date(UI date) to UTC and backward. So decision to count the offset on the server and get rid off browser timezone offset: utcTime = UItime + browserOffset - clientTimezoneBaseOffset - daylightSavingTimeOffset.
However there is problem when need to get UI time back, f.e. today 06.06.2014 and the DST is true for this date, but when we are getting 03.03.2014 date from server we don't know if the DST was active for 03.03.2014.
Answer
In project on the server the .net, so in database the window's timezone IDs are stored. I have done it the next way: count on server DST for date ranges in the range from minimum date on the server and save on the client in the local storage. I like that in this case
the server is one source of truth, so the calculations can be performed on any client without manipulating the timezones. And no need to convert between IANA, Windows and rails timezones. But also there is a problem: need to precalculate DST in range from DateTime.MinValue to DateTime.MaxValue, currently to speed up I'm calculating DST for range from 01.01.2000 to DateTime.Now - it is enough when converting value from database to UI, because in database the min date is on the 2008 year, but is not enough for html input, because user can select value greater than DateTime.Now and lower then 01.01.2000. To fix it I'm, planning with use of TimeZoneConverter send to client the IANATimezoneId, and for cases when provided date(UI or server) is not in the range [01.01.2000, DateTime.Now] belong on the moment.utc(date).tz(IANATimezoneId).isDST().
This is new code on the server side
private class DaylightSavingTimeDescriptor
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public bool IsDaylightSavingTime { get; set; }
}
private string GetDaylightSavingTimeDescriptorsJson(string timeZone)
{
string daylightSaveingTimeDescriptorsJson = String.Empty;
if(timeZone != null)
{
List<DaylightSavingTimeDescriptor> dstList = new List<DaylightSavingTimeDescriptor>();
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
DateTime startDate = new DateTime(2000, 1, 1);
DateTime dateIterator = startDate;
bool isDST = timeZoneInfo.IsDaylightSavingTime(startDate);
while (dateIterator < DateTime.Now)
{
bool currDST = timeZoneInfo.IsDaylightSavingTime(dateIterator);
if (isDST != currDST)
{
dstList.Add(new DaylightSavingTimeDescriptor()
{
EndTime = dateIterator.AddDays(-1),
IsDaylightSavingTime = isDST,
StartTime = startDate
});
startDate = dateIterator;
isDST = currDST;
}
dateIterator = dateIterator.AddDays(1);
}
daylightSaveingTimeDescriptorsJson = Newtonsoft.Json.JsonConvert.SerializeObject(dstList);
}
return daylightSaveingTimeDescriptorsJson;
}
And this is modified dateToServer, dateToClient on client side
export default class DateService{
constructor (authService) {
const authData = authService.getAuthData();
this.clientTimeZoneOffset = Number(authData.timeZoneOffset);
this.daylightSavingTimeRanges = authData.daylightSavingTimeRanges ? JSON.parse(authData.daylightSavingTimeRanges) : [];
}
getDaylightSavingTimeMinutesOffset(utcDate) {
const dstRange = this.daylightSavingTimeRanges.find(range => {
const momentStart = moment(range.startTime).utc();
const momentEnd = moment(range.endTime).utc();
const momentDate = moment(utcDate).utc();
return momentStart.isBefore(momentDate) && momentEnd.isAfter(momentDate);
});
const isDaylightSavingTime = dstRange ? dstRange.isDaylightSavingTime : false;
return isDaylightSavingTime ? '60' : 0;
}
dateToClient(date) {
const momentDate = moment(date);
let uiDate = null;
if (momentDate.isValid() && date) {
const browserUtcOffset = momentDate.utcOffset();
uiDate = momentDate
.utc()
.subtract(browserUtcOffset, 'minutes')
.add(this.clientTimeZoneOffset, 'minutes')
.add(this.getDaylightSavingTimeMinutesOffset(momentDate.utc()), 'minutes')
.toDate();
}
return uiDate;
}
dateToServer(date) {
const momentDate = moment(date);
let serverDate = null;
if (momentDate.isValid() && date) {
const browserUtcOffset = momentDate.utcOffset();
serverDate = momentDate
.utc()
.subtract(this.clientTimeZoneOffset, 'minutes')
.add(browserUtcOffset, 'minutes')
.subtract(this.getDaylightSavingTimeMinutesOffset(momentDate.utc()), 'minutes')
.toDate();
}
return serverDate;
}
}
PS
I want to get rid off offsets and use moment-timezone on the client and timezone converter on the server, but it will be later if customer ask, because I have never tried it before and I'm not sure that it will be working well, and current solution is working. Also the offsets anyway will be used for dateinputs components (angularjs), because they are using kendo-dateinput which ng-model is JS Date in browser local time, but I'm providing another timezone so need to transformations inside component.
PPS Best solution from my point of view, if timezone converter and moment-timezone will be working as expected
Anyway the whole app is operating the JS Date, so when user in Moscow with America in profile selects the datetime in kendo-input, or see setup kendo-scheduler, or display the date in the kendo-table, I need to manipulate with offsets. But instead of passing directly the client offset, I'm planning to pass IANA timezone id from the server with help of timezone converter and get the offset I need directly from moment-timezone.

Date Picker for Angular that Ignores Local Timezone

Is there any date picker that just displays what is supplied to it and ignores the local timezone?
We're currently using angular-strap's date picker and the data-min-date changes when the local time zone of the computer is changed. The supplied data-min-date is just a fixed date but somehow the date picker is affected by the local time zone.
Example:
Supplied Date: 2016-12-23T16:33:03+08:00 ISO8601 Format
On UTC-12:00 22-December-2016 is disabled so the minimum is 23-December-2016.
On UTC+08:00 23-December-2016 is disabled so the minimum is 24-December-2016.
Not sure which one is the expected date, but the expected outcome should be that there will be no changes since a fixed date is supplied.
Is there anything I am missing or any other calendar that will just display what is given to it?
EDIT 1: Is it a legit way to test date functionalities by changing the local timezone of a computer? Is it the same or different than when you are locally or naturally situated in that timezone?
EDIT 2: We already generated datetime from our node server using momentjs. var serverTime = moment().utcOffset(8); it's just the the angular-strap handles the date we supplied in an odd way.
EDIT 3: Adding an attribute data-timezone="utc" doesn't help either.
EDIT 4: We've discovered that the plug-in uses date = new Date(value); in which value is the date that we passed. And it returns something like Thu Dec 22 2016 21:29:54 GMT-1200 (Local Standard Time) which clearly uses the local timezone. And there is no condition to check if there is timezone="utc" supplied. We also tried to directly assign date = Thu Dec 22 2016 21:29:54 GMT-1200 (Local Standard Time) but the plug-in breaks.
We did a hack around. We forked the plug in and changed the code that handles the min-date.
Change history can be found here. Snippet we added:
var a = new Date(value); // value - date we are passing
var b = a.getTime() + (a.getTimezoneOffset() * 60000); // no idea what this is
date = new Date(b + (3600000*(8))); // the 8 is the timezone we want
The problem with the new Date(value) is that it is affected by the local timezone.

Categories

Resources