Get the real time of a user - javascript

I'm facing a problem to get the real 'trusted' user time, preventing them to cheat if they change their computer's time.
Weather I use a plain date object, or moment timezone or even google timezone api I just can't get the 'real' time of a user if I try manipulating the current time.
If we are at 20:00 (no matther the location) and the user tempers with the time to set it as 11:00 then I always end up with that time and not the real one, either by
const time = new Date();
const timestamp = (time.getTime() / 1000 + time.getTimezoneOffset() * 60);
const url = 'https://maps.googleapis.com/maps/api/timezone/json?location=-31.369926900000003,-64.2218601&timestamp=1568396292&key=MY_API_KEY';
this.httpDuplicate.get(url ).subscribe((res: any) => {
if (res) {
const dst = res.dstOffset;
const raw = res.rawOffset;
const fixed = (timestamp + raw + dst) * 1000;
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const currentTime = momenttz().tz(timezone).format();
console.warn('initial time ', new Date(time),
' - google time ', new Date(fixed),
' - moment timezone ', currentTime);
// all these values are wrong and point to 11:00 rather than 20:00
}
});
Is there a way to achieve this ? What I want in the end is to get the right time for a certain position... clearly not trusting the user's system time, but do trust its location (though there are ways to alter this as well)

The reason it fails is because you reference the Date object you made based on the user's client time ('time' variable).
If you don't trust the system time, you'd have to get the time from somewhere else, like an external server that you do trust, or the backend of your application if you have one (server time).
For example: http://worldtimeapi.org/
Of course, you'd also need the user's location in that case. I can imagine you don't trust that either since you also don't trust the time, so in that scenario there's no way to do it.

Related

How to convert any timezone to local timezone in javascript?

The below code converts the date to local timezone:
function convertDateToServerDate(incomingDate) {
var serverOffset = new Date();
serverOffset = serverOffset.getTimezoneOffset();
var outgoingServerDate = new Date((incomingDate.getTime() + Math.abs(serverOffset * 60 * 1000)));
return outgoingServerDate;
}
I have a date in the IST timezone. I'm also in the IST timezone the above function changes the time whereas it should just return it. I also tried converting to UTC then back to the local but the same result. How to get a local timezone and do nothing if the date is already in local time zone?
You can't do this with vanilla Javascript without a library, because the Date class only comprehends two timezones: GMT, and local. (Source]
Libraries like momentjs and date-fns do not merely provide convenient functions, they very importantly also include hard-coded datasets that specify current real-world facts about time zone adjustments, including things like the dates where DST switches. This is vital, because if you look at the map of timezones, you'll see that the boundaries of the different zones are not straight lines. That's because they are determined by humans who made interesting compromises which are then enshrined in custom and law.
Many of those compromises are there so that people who share a jurisdiction can also share a clock. It would be enormously inconvenient for them otherwise, and many people would be adversely impacted every single day.
There is a proposal for a successor to Date, called Temporal, that would remedy this.
Best to use moment library. https://momentjs.com/timezone/docs/
moment().tz(String)
var a = moment.utc("2013-11-18 11:55").tz("Asia/Taipei");
var b = moment.utc("2013-11-18 11:55").tz("America/Toronto");
a.format(); // 2013-11-18T19:55:00+08:00
b.format(); // 2013-11-18T06:55:00-05:00
a.utc().format(); // 2013-11-18T11:55Z
b.utc().format(); // 2013-11-18T11:55Z
The server offset has to be set using INTL, hardcoded or come from the server
So something like this
const serverOffset = -240; // this is static
function convertDateToServerDate(incomingDate) {
const incomingOffset = incomingDate.getTimezoneOffset();
if (serverOffset === incomingOffset) return incomingDate;
console.log(serverOffset-incomingOffset,"difference")
const outGoingDate = new Date(incomingDate.getTime())
outGoingDate.setTime(incomingDate.getTime() + ((serverOffset-incomingOffset) * 60 * 1000));
return outGoingDate;
}
console.log(convertDateToServerDate(new Date()))

Is it safe to use performance.now() to calibrate time?

I'm wondering if I'm not foreseeing something that might be a problem with my approach.
class RDate {
constructor(serverIsoDate) {
this.serverDate = new Date(serverIsoDate);
this.localDate = new Date();
this.localTick = performance.now();
}
getCompensatedDate() {
return this.serverDate.getTime() + (performance.now()-this.localTick);
}
}
I get serverDate periodically, but If the user leaves the webapp running for a long time I can guarantee correct time even if the user spoofs his clock.
Does performance.now overflows or get paused when the tab gets suspended?
I can detect how much the clock drifted or got spoofed by calculating the difference between localDate and localTick
_spoofed() {
return Math.abs(this.localDate.getTime() - this.localTick - (Date.now() - performance.now())) > 1000 * 60 * 60;
}
The MDN documentation on the Performance API says:
Since a platform's system clock is subject to various skews (such as NTP adjustments), the interfaces support a monotonic clock i.e. a clock that is always increasing.
This seems to say that the clock used for performance.now() should be independent of the system clock.
I don't think its clock can be paused. The description of performance.now() says:
The returned value represents the time elapsed since the time origin.
The time origin doesn't change when a tab is suspended, so it should include the suspension time.

Get Navigation Timing backward/forward compatible - Convert from epoch to HR time

Let's introduce by a note from www.w3.org including two important links to compare.
The PerformanceTiming interface was defined in [NAVIGATION-TIMING] and
is now considered obsolete. The use of names from the
PerformanceTiming interface is supported to remain backwards
compatible, but there are no plans to extend this functionality to
names in the PerformanceNavigationTiming interface defined in
[NAVIGATION-TIMING-2] (or other interfaces) in the future.
I have made a function to get a Navigation Time that should be both backward and forward compatible, because we are in the middle era of transforming to level 2. So this function to get a time from an event name works in Chrome but not Firefox:
function nav(eventName) {
var lev1 = performance.timing; //deprecated unix epoch time in ms since 1970
var lev2 = performance.getEntriesByType("navigation")[0]; //ms since page started to load. (since performance.timing.navigationStart)
var nav = lev2 || lev1; //if lev2 is undefined then use lev1
return nav[eventName]
}
Explanation: When there is no "navigation" entry this falls back to the deprecated way to do navigation timing based on Unix epoch time time in milliseconds since 1970 (lev1), while the new way (lev2) is HR time in milliseconds since the current document navigation started to load, that is useful together with User Timing that always have had the HR time format.
How can we get the function return HR time in all cases?
When I see a number with more than 10 digits without a period I know it is a time got from the deprecated Navigation Timing level 1. All other test cases give decimal point numbers meaning it is HR times with higher precision. The biggest issue is that they have different time origin.
I have gone through confusion, trial errors and frustrated serching (MDN has not updated to level 2) to confirm and state that:
Navigation Timing Level 1 use unix epoch time and the rest...
Navigation Timing Level 2 use HR time
User Timing Level 1 use HR time
User Timing Level 2 use HR time
Also performance.now() has HR time both in Chrome and Firefox.
How to convert unix epoch time to HR time?
SOLVED .:
The code is corrected by help from Amadan.
See comments in tha accepted answer.
function nav(eventName, fallback) {
var lev1 = performance.timing; //deprecated unix epoch time in ms since 1970
var lev2 = performance.getEntriesByType("navigation")[0]; //ms since page started to load
var nav = lev2 || lev1; //if lev2 is undefined then use lev1
if (!nav[eventName] && fallback) eventName = fallback
// approximate t microseconds it takes to execute performance.now()
var i = 10000, t = performance.now()
while(--i) performance.now()
t = (performance.now() - t)/10000 // < 10 microseconds
var oldTime = new Date().getTime(),
newTime = performance.now(),
timeOrigin = performance.timeOrigin?
performance.timeOrigin:
oldTime - newTime - t; // approximate
return nav[eventName] - (lev2? 0: timeOrigin);
// return nav[eventName] - (lev2? 0: lev1.navigationStart); //alternative?
}
The performance.timeOrigin is reduced in the case where old timing lev1 is used.
If browser does not have it then approximate timeOrigin by reducing performance.now() the time since timeOrigin, from (new Date().getTime()) the time since Unix Epoch to result in the time to timeOrigin since Unix Epoch. Apparently it is the definition though the link was a bit vague about it. I confirmed by testing and I trust the answer. Hopefully w3c have a better definition of timeOrigin than: the high resolution timestamp of the start time of the performance measurement.
The functions returned value represents the time elapsed since the time origin.
It may be insignificant in most cases, but the measured time t it took to execute performance.now() is removed to approximate simultaneous execution.
I measured t to almost 10 microseconds on my Raspberry Pi that was fairly stable with various loop sizes. But my Lenovo was not as precise rounding off decimals and getting shorter times on t when tested bigger loop sizes.
An alternative solution is commented away in the last line of code.
The deprecated performance.timing.navigationStart:
representing the moment, in miliseconds since the UNIX epoch, right
after the prompt for unload terminates on the previous document in the
same browsing context. If there is no previous document, this value
will be the same as PerformanceTiming.fetchStart
So, to check current document (ignoring any previous) then use the deprecated performance.timing.fetchStart:
representing the moment, in miliseconds since the UNIX epoch, the
browser is ready to fetch the document using an HTTP request. This
moment is before the check to any application cache.
It is of course correct to use a deprecated property if it is the only one the browser understand. It is used when "navigation" is not defined in the getEntriesByType otherwise having good browser support.
A quick check confirmed each other by this line just before return:
console.log(performance.timeOrigin + '\n' + lev1.navigationStart + '\n' + lev1.fetchStart)
With a result that looks like this in my Chrome
1560807558225.1611
1560807558225
1560807558241
It is only possible if the browser supports HR time 2:
let unixTime = hrTime + performance.timeOrigin;
let hrTime = unixTime - performance.timeOrigin;
However, performance is generally used for time diffs, which do not care what the origin of absolute timestamps is.
For the browsers that do not support HR time 2, or those that "support" it half-heartedly, you can fake it this way:
const hrSyncPoint = performance.now();
const unixSyncPoint = new Date().getTime();
const timeOrigin = unixSyncPoint - hrSyncPoint;
It's not super-exact, but should be good enough for most purposes (on my system, performance.timeOrigin - timeOrigin is sub-millisecond).

Guesstimate time zone from time offset with JavaScript and Moment.js

I know this is not going to be foolproof because an offset isn't specific to a timezone, but with location data it seems like it would be possible to make an educated guess.
Basically, I would like to be able to take an object similar to this:
{
offset: -5,
location: 'America'
}
...and get back either a single or multiple matching time zones, which would be:
['America/Montreal', 'America/New_York', ...]
One solution I can think of is iterating through the zone data provided by moment-timezone, but that just doesn't seem like an elegant way to get this info.
Any ideas?
It's not really elegant but iterating through the time zone database allows to get all timezones associated with a given offset.
Note that the timezone database stores the variation of offset due to the daylight saving rules and also the historical evolution of time zones.
Given an offset in minutes, this function returns, according to the iana timezone database, the list of all timezones that used this offset once in the history or that will use this offset once in the futur.
function getZonesByOffset(offset){
//offset in minutes
results = [];
var tzNames = moment.tz.names();
for(var i in tzNames){
var zone = moment.tz.zone(tzNames[i]);
for(var j in zone.offsets){
if(zone.offsets[j] === offset){
//Add the new timezone only if not already present
var inside = false;
for(var k in results){
if(results[k] === tzNames[i]){
inside = true;
}
}
if(!inside){
results.push(tzNames[i]);
}
}
}
}
moment-timezone#0.5.0 added moment.tz.guess() which attempts to guess the user's most likely timezone by looking at Date#getTimezoneOffset and Date#toString. It then picks the zone with the largest population. It's not perfect, but it's close enough! Data is pulled from this table and Intl is used when available.
Fiddle
moment.tz.guess()
//= America/New_York (I'm in America/Montreal, but this works too)

Javascript countdown and timezone and daylight saving time issues

Our team are having big issues with the JQuery countdown and we really need some help.
Initially, we had some ScriptSharp code that does this
JQueryCountdownOptions opts = new JQueryCountdownOptions();
opts.Layout = "<ul class=\"group\"> <li>{dn} <span>{dl}</span></li> <li>{hn} <span>{hl}</span></li> <li>{mn} <span>{ml}</span></li> <li>{sn} <span>{sl}</span></li> </ul>";
opts.Until = Number.ParseInt(timeLeft);
jQuery.Select("#countdownclock").Plugin<JQueryCountdown>().Countdown(opts);
jQuery.Select("#countdownclock").Show();
jQuery.Select("#bidBox").RemoveAttr("disabled");
What we noticed is that this uses the client's clock to countdown from. So, if the client decided to change his time to 5 hours ahead then the countdown would be 5 hours off.
To fix this we introduced some more code
In the view:
$(function () {
var expires = new Date(#year, #month, #day, #hours, #minutes, #seconds);
$('#clockDiv').countdown({ until: expires, timeZone: null, serverSync: serverTime, onTick: serverTime, tickInterval: 60 });
function serverTime() {
var time = null;
$.ajax({ url: '/Auction/SyncServerTime',
async: false, dataType: 'json',
success: function (result) {
time = new Date(result.serverTime);
}, error: function (http, message, exc) {
time = new Date();
}
});
return time;
}
});
In the controller
public JsonResult SyncServerTime()
{
var result = new JsonResult
{
Data = new
{
serverTime = DateTime.Now.ToString("MMM dd, yyyy HH:mm:ss zz")
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
return result;
}
This code ensures that no matter what the user sets his clock to the countdown timer will periodically sync to the server's time. Problem solved.
The only issue is that we have come up with other issues.
The problem is that when users are in different timezones then the countdowns of those users are different depending on the timezone offset that their timezone has. We have tried changing all sorts of parameters and still are having issues. To make matters worse if my timespan straddles a date when daylight saving time is applied then things go awry again, both for those in the same timezone and those in different ones. We have experimented with different code and parameters so the above is just what I did and is different from what my esteemed colleagues tried. What I am asking is surely, someone somewhere out there must have had a requirement to
Write a countdown that is independent of client time and based on server time.
Shows the same number of days, hours, minutes, seconds remaining no matter what timezone a user is in
Shows the same number of days, hours, minutes, seconds remaining for a user whose time will change in this period because of DST to user whose time will not change in this period because of DST
Shows the actual number of days, hours, minutes and seconds remaining for a user whose time will change in this period because of DST.
We cannot be the only people who have ever had this issue, surely. It cannot be this hard. Does anyone know a solution?
Thanks,
Sachin
I haven't dealt with the same scenarios personally, but seeing Date, timezone issues etc. pop up automatically triggers thoughts about some potential issues stemming from the use of local date objects as opposed to UTC date objects.
IMO, things are simply better off if all computation, serialization of dates only worked in the UTC space, and finally when it comes to present a date from a user, it is converted to local or appropriate time zone depending on the scenario. On the flip-side, the user enters local or some time zone relative entry, and immediately that is converted to UTC as the internal representation. This avoids all sorts of confusion across different layers/tiers of the app.
Its not really a solution to your specific problem, but perhaps something to consider that could lead to one.

Categories

Resources