is it possible to tweak global Date function? - javascript

I'm trying to tweak the global Date function by preserving all functionality of Date but add some parameter checking inside the constructor
because I want to throw an error when calling Date function like this new Date('2021-01-01'), which will return Invalid Date in safari.
(function () {
let OldDate = window.Date;
window.Date = class Date extends (
OldDate
) {
constructor(...args) {
if (/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/.test(args[0])) {
throw Error('wrong date format');
}
super(...args);
}
};
})();
but this approach has a pitfall, I can't call Date without new keyword
// before override
new Date('2021/02/01'); // OK
Date('2021/02/01'); // OK
// after override
new Date('2021/02/01'); // OK
Date('2021/02/01'); // Uncaught TypeError: Class constructor Date cannot be invoked without 'new'
How do I fix it?
the motivation of tweaking the global Date function
most newbies will tend to call Date function with a format like YYYY-MM-DD HH:mm:ss, this works inside his development environment (latest version of Chrome), and he doesn't konw this won't work on safari until he tests his project on safari, so I have to tell him to change the Date format string he uses every time he falls into this problem.
Writing a code style guide won't always work, a newbie is a newbie because he always forgets things, so I have to tell him to read the style guide document again.
Instead, I want to throw an error every time the newbie use Date with the wrong date string format
new Date('2021-01-02') // Error: 'YYYY-MM-DD is wrong format', try use 'YYYY/MM/DD'

No matter why you want to override Date. My solution is
var originalDate = Date; // backup
function _date(str) {
// your implementation here
if (/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/.test(str)) {
throw Error('wrong date format');
}
return new originalDate(str);
}
window.Date = _date; // override
Object.assign(Date.prototype, originalDate.prototype); // I suppose you still want to have properties of the original Date.

Related

Replace builtin Date without being able to recover original constructor

I want to replace Date inside a JavaScript VM (V8 but this is not specific to V8), and make it impossible to access the original Date constructor. This is meant to be one part of a defence against timing attacks like Spectre with multi-tenant JavaScript running in the same process (but different JavaScript VM.) The idea is to deny access to high resolution timers. Cloudflare does this in their Workers, for the same reason. Time is advanced only when IO happens, not during computation. I know there are other ways to construct a timer, but I'm just focused on Date for this question.
I think this can be done in pure JavaScript, and this is my attempt. Is there a way to get the original Date constructor back after running this? Is there a way this differs functionally from the builtin Date - something that could break backwards compatibility?
// Current UNIX timestamp in milliseconds, but that we set manually on each call to runTasks.
// not advancing time during computation is one of our defenses against Spectre attacks.
let NOW = Date.now();
export function setDate(unixTimestampMillis) {
NOW = unixTimestampMillis;
}
Date = function (BuiltinDate) {
function Date(...args) {
if (new.target === undefined) {
// This is the deprecated naked Date() call which returns a string
return (new BuiltinDate(NOW)).toString();
}
// Otherwise it was the constructor called with new
if (args.length === 0) {
// Return the "current" time.
return new BuiltinDate(NOW);
}
// Build a Date with the specified datetime
return new BuiltinDate(...args);
}
// Make a copy of the BuiltinDate "class" and replace the constructor,
// It needs to be impossible for the user to grab an reference to BuiltinDate.
Date.prototype = BuiltinDate.prototype;
BuiltinDate.prototype.constructor = Date;
// Add the static methods now(), UTC(), and parse() - all of which return a numeric timestamp
function now() {
return NOW;
}
Date.now = now;
Date.parse = BuiltinDate.parse; // returns a number
Date.UTC = BuiltinDate.UTC; // returns a number
return Date;
}(Date);

DayJS isValid behaves differently to Moment

I was trying to validate birthday using dayjs but its isValid is returning true for a date that doesn't exist. Funnily enough, the isValid by moment is working just fine.
dayjs('2019/02/31', 'YYYY/MM/DD').isValid() // true
moment('2019/02/31', 'YYYY/MM/DD').isValid() // false
I can't switch to moment because of the lightweightness of dayjs
Any idea how to tackle this?
August 2021: It's now possible to detect with .isValid() method these invalid dates passing a third parameter as true to dayjs constructor:
dayjs('2019/02/31', 'YYYY/MM/DD').isValid() // true
dayjs('2019/02/31', 'YYYY/MM/DD',true).isValid() // false
More information on this strict parsing argument can be found now at official doc.
NOTE: In Node.js I had to load first customParseFormat plugin before calling isValid() to receive expected validity results:
const dayjs = require("dayjs");
var customParseFormat = require("dayjs/plugin/customParseFormat");
dayjs.extend(customParseFormat);
Please look at this thread. Basically isValid doesn't validate if passed date is exists, it just validates that date was parsed correctly.
I'm not exactly sure if this works in all scenarios (especially if you have localization), but you could try something like:
function validate(date, format) {
return dayjs(date, format).format(format) === date;
}
validate('2019/02/31', 'YYYY/MM/DD') // false
Reason for this kind of check is that
dayjs('2019/02/31', 'YYYY/MM/DD').format('YYYY/MM/DD')
returns 2019/03/03. Then when you compare it to your initial date (you should be able because formatting is the same) you should get the same value - and in this case you don't.
How moment.isValid() works (overflow checks)
If the moment that results from the parsed input does not exist, moment#isValid will return false.
How dayjs.isValid() works
If you look at the source code:
src/index.js
this.$d = parseDate(cfg) // Line 75
// ...
isValid() { // Line 96
return !(this.$d.toString() === C.INVALID_DATE_STRING)
}
src/constant.js
export const INVALID_DATE_STRING = 'Invalid Date'
If you look to the source parse is not a straighforward Date.parse() because it take in consideration locale, date format and so on, but it seems that every valid date passed to Date() or Date.parse() are valid.
Date.parse('2019/02/31') // 1551567600000
new Date(2019/02/31) // Thu Jan 01 1970 01:00:00 GMT+0100
You can use customParseFormat plugin from day.js
I do this on my react project and this is work perfectly fine.
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
dayjs.extend(customParseFormat);
dayjs('30-02-2022', 'DD-MM-YYYY', true).isValid() // false
use strict mode
dayjs('2019/02/31', 'YYYY/MM/DD', true).isValid() //false
When strict mode is not enforced
Note: you have to extend customParseFormat plugin as a prerequisite
Unfortunately, we can't rely on isValid() function that is provided in dayjs.
What I did, was to create a file utils called date-utils.ts. This file is a wrapper around the lib and add more validations on top of it.
Essentially, this statement will do the work we need:
// it validates the string date properly
if (Number.isNaN(new Date(date).getTime())) {
return false;
}
Then, you could create a file like the typescript example (feel free to make it js if you want).
// date-utils.ts
import dayjs from 'dayjs';
const isValid = (date: string) => {
if (!date) {
return false;
}
// it validates the string date properly
if (Number.isNaN(new Date(date).getTime())) {
return false;
}
return dayjs(date).isValid();
};
const getDate = (date: string) => {
if (!date) {
throw Error('The date is required.');
}
if (!isValid(date)) {
throw Error(`The date "${date}" is not valid.`);
}
return dayjs(date);
};
export default { isValid, getDate };

'new Date()' in IE11 does not accept string in ISO 8701 date format with a timezone

The following call to the Date constructor call works in Chrome, Mozilla, and Edge, but not in IE11:
new Date("2018-11-01T04:00:00.000+1000");
Note: I receive the date string from server response and therefore, I have no control over it
In IE11 only, I get Invalid Date as a returned value. I figured out that it was because of the format of the timezone marker (+ onward) because the following call works as intended:
new Date("2018-11-01T04:00:00.000"); // No timezone
As well as this one:
new Date("2018-11-01T04:00:00.000+10:00"); // Formatted timezone
What ways are there to get a Date object from the string "2018-11-01T04:00:00.000+1000" in IE11?
Splicing the : in the appropriate place seems to do the job, but I am not sure it is the best solution.
Thank you!
The solution to my problem ended up being, as I described, splicing : in the correct spot it didn't already exist:
// Call this after making sure the date doesn't contain the : at the appropriate position
function formatExtendedTimezoneIn(originalDate) {
const timezoneDivisorIndex = originalDate.length - 4;
const arr = originalDate.split('');
arr.splice(timezoneDivisorIndex+2, 0, ':');
return arr.join('');
}

HTML Comparing 2 Dates with Javascript

I'm trying to compare 2 dates using Javascript. If "myDateL" is after "mydateR" display a message box when the button is clicked.
What is wrong with my code?
I know I've seen a similar thread to this but I couldn't understand it. I hope someone can help me with this please.
<input type="Button" value="TwoDates" onClick="twoDates()">
<script>
function twoDates() {
var firstdate = new date(document.getElementById("mydateL").value);
var seconddate = new date(document.getElementById("mydateR").value);
if(firstdate > seconddate) {
alert('Please change your return date.');
}
}
</script>
It's new Date(...), not new date(...). Date is the global object that holds dates and times, date would be a function you've declared called date. If you look at the console when you run this, you should see something like:
ReferenceError: date is not defined

Javascript Date decorator for accurate client side time

I'm currently working on some Javascript to synchronize the client side clock with our servers. I'm doing this by looking at the 'Date' header of any Ajax responses (it only has to be accurate to a few seconds).
To make the rest of the code work seamlessly with this Date function I want to override the native Date function in a decorator style so I can just enhance it with the calculated clock skew.
So far I have this (which seems to work):
var SystemDate = Date;
var ServerDate = function() {
var a = arguments;
switch(a.length){
case 0:
var system_date = new SystemDate();
return new SystemDate(system_date - ServerDate.skew);
case 1:
return new SystemDate(a[0]);
case 7:
return new SystemDate(a[0],a[1],a[2],a[3],a[4],a[5],a[6]);
}
};
ServerDate.parse = Date.parse;
ServerDate.UTC = Date.UTC;
ServerDate.skew = 0;
var Date = ServerDate;
Now whenever I get an Ajax respose I can adjust the skew property and all new instances of Date will have the adjusted value.
I'm really inviting criticism. There are a few things I'm not sure on:
Is this a good idea? - I'm not a Javascript programmer so I've no idea what sins I have just commited
How can I handle the different argument lengths more neatly - it seems very hacky
Copying the class methods UTC and parse seems very brittle
The new Date function now returns a date object on the console rather than the string representation.

Categories

Resources