DayJS isValid behaves differently to Moment - javascript

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 };

Related

How to check if the date is not valid in Typescript

I want to check if the date is not valid in typescript, looking through some of the answers based on javascript. This is the result I came across but unfortunately, it is not working.
...
// Check if the given date is not valid
else if (!(giftCards[i].expiry instanceof Date) || isNaN(Number(giftCards[i].expiry)))
return new Result(true, ErrorCode.BadRequest, 'Expiry Date out of range', {
index: i,
giftCard: giftCards[i]
});
...
How to check if the date is not valid in Typescript
You are trying to check the runtime variable if it's a valid Date object, which is not the best idea. I would just use javascript to check it.
You could either use e.g. isDate function from lodash (https://www.npmjs.com/package/lodash.isdate) or check it manually (and this is what I'm doing with dates in my projects):
const isDate = (value: unknown): value is Date => {
return value instanceof Date && !isNaN(+value);
}
Note: your code sample probably should work fine, but you should replace || with && in your condition.

is it possible to tweak global Date function?

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.

moment-timezone change utc to default timezone

Context:
I need to parse datetimes from UTC into the timezone currently set as default by moment-timezone with moment.tz.setDefault.
Attempt (functional):
const task = {
startDate: '2020-03-24 14:00:00'
};
const startDateMoment = moment.utc(task.startDate).tz(moment().tz());
However, I am creating a new moment object every time I need to set the timezone. Is there a more performant way to converting between utc to the default timezone, or getting the default timezone from moment?
moment.tz.guess() guesses the timezone from the browser, not the one set manually.
console.log(`default timezone: "${moment().tz()}", guessed timezone: "${moment.tz.guess()}"`);
returns
default timezone: "US/Samoa", guessed timezone: "America/Vancouver"
After a bit of digging, I did find the following, undocumented, use-at-your-own-risk, could-disappear-at-any-time property:
moment.defaultZone.name
moment.defaultZone is a Zone Object, of which we really only care about the name property.
Note that it is null if you haven't set a default yet! In the code below, I'm taking advantage of a couple new JavaScript features, optional chaining and the nullish coalescing operator to see if the default zone is set and if not, use the moment.tz.guess() value.
const task = {
startDate: '2020-03-24 14:00:00'
};
console.log('before setting default: ', moment.defaultZone);
moment.tz.setDefault('US/Samoa');
console.log('after setting default: ', moment.defaultZone);
const startDateMoment = moment.utc(task.startDate).tz(moment.defaultZone?.name ?? moment.tz.guess());
console.log(startDateMoment.format());
<script src="https://momentjs.com/downloads/moment.min.js"></script>
<script src="https://momentjs.com/downloads/moment-timezone-with-data.min.js"></script>

Moment js: moment(0) = Wed Dec 31 1969?

I tried to make null date.
so I tried to make them all 0 when I click the clear button.
this.clear = function () {
_this.newQuarters.forEach(function (_value, index) {
_value.startDate = 0;
_value.endDate = 0;
_value.suppressAllocation = false;
_value.quarters.forEach(function (_childValue, index) {
_childValue.startDate = 0;
_childValue.endDate = 0;
_childValue.suppressAllocation = false;
});
});
};
}
After that, I tried to add 0 at moment from other function.
this.newQuarters.forEach(function (_value, index) {
var startDate = moment(_value.startDate);
but, it display startDate = Wed Dec 31 1969.
Please help me to find to make all date are null.
When passing a number to the moment() function, it interpret it as a unix timestamp.
Which is the number of second since EPOCH time, or 01-01-1970.
So passing 0 to that function result in the very first second of jan 1 1970. As Bergi pointed out, you are probably displaying your dates in your local timezone, which could result in a time before 01-01-1970.
If you want to create a null date, you should set your startDate to null and handle it correctly ( with an if statement).
You could also set the date back to the current time by passing no argument to the moment() function.
Dates can be tricky to work with in an application. There is no such thing as time that is not time. At least not in the world of programming. For this reason we have to create our own understanding of what a lack of time will be.
There is no time without time but there is a lack of value.
You obviously understand this, you had an approach, in your case, you seem to be fine with simply having time be zero, it just broke when working with moment as moment is using EPOCH time. Thus, zero is time.
You are going to have to test like the rest of us.
this.newQuarters.forEach(function (_value, index) {
var startDate = _value.startDate ? moment(_value.startDate) : null;
Lack of time could be whatever you want, undefine, null, a beginning date
Be consistent: Database vendors and languages handle things differently, front end is not always the same time zone, etc.
If you are dealing with momentjs library, there is a function: invalid()
You can easily make your variables invalid moment object and control their validity.
let currentDate = moment(); // Valid moment object
console.log(currentDate.format());
// make the moment object invalid
currentDate = moment.invalid();
console.log(currentDate.isValid()); // Check validity
console.log(currentDate.format()); // Invalid date
// make the moment object valid again
currentDate = moment();
console.log(currentDate.isValid()); // Check validity
console.log(currentDate.format()); // A valid date
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>

Detect an invalid timezone with momentjs

Is there a way I can detect an invalid timezone with momentjs?
If I do this:
const m = moment.tz("01/01/2019 5:30pm", "MM/DD/YYYY h:mma", true, "invalid timezone");
m.isValid(); //true
Is there any way to make isValid() return false, or is there some other way to detect that the timezone is invalid?
It logs the following: Moment Timezone has no data for invalid timezone. See http://momentjs.com/timezone/docs/#/data-loading/ but how can I tell programatically?
If you're working with a fully created Moment object, then the answer given by Jaromanda X is the way to go. However, if you're just testing for the time zone name being valid (and loaded into moment), then you don't need to construct a Moment object to do that. Just test the time zone name itself.
if (!moment.tz.zone('FooBarTimeZone')) {
console.log('Invalid time zone');
} else {
console.log('Valid time zone');
}
Note that this works because the moment.tz.zone function returns either a Zone object (which like any object is truthy), or null (which is falsey).
Also note that there was originally a moment.tz.zoneExists function, which still works - but will now give a deprecation message, describing using the above technique instead.
check if m.tz() === undefined
let m = moment.tz("01/01/2019 5:30pm", "MM/DD/YYYY h:mma", true, "invalid timezone");
console.log(`"invalid timezone" results in .tz() == ${m.tz()}`)
m = moment.tz("01/01/2019 5:30pm", "MM/DD/YYYY h:mma", true, "Australia/Melbourne");
console.log(`"Australia/Melbourne" results in .tz() == ${m.tz()}`)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.14/moment-timezone-with-data.min.js"></script>
Of course you can also check if a zone exists
console.log(`"invalid zone name is ${moment.tz.zone("invalid zone name")}`);
console.log(`"australia/nsw" is ${JSON.stringify(moment.tz.zone("australia/nsw"), null, 4)}`);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.14/moment-timezone-with-data.min.js"></script>

Categories

Resources