dayjs
dayjs copied to clipboard
dayjs(null) crashes when using it with objectSupport plugin
Describe the bug See this codesandbox: dayjs(null) crashes when used with objectSupport plugin
There is a test that runs dayjs(null) (without the plugin)
The parse override of the plugin doesn't handle nullable values at all, it just returns the value as is:
https://github.com/iamkun/dayjs/blob/e70bee7f840c89ec523b9ac997e5ac621a522726/src/plugin/objectSupport/index.js#L12
Whereas the default parser returns new Date(NaN) (to have an invalid date):
https://github.com/iamkun/dayjs/blob/e70bee7f840c89ec523b9ac997e5ac621a522726/src/index.js#L62
I detected this problem using dayjs in Mui (React components library): https://github.com/mui/mui-x/issues/7571
Expected behavior Should not crash
Information
- Day.js Version ^1.11.7
Thank you for isolating this issue! One of our dependencies (@commandbar/foobar) started using the objectSupport plugin in v0.3.31, and I couldn't figure out why our app was suddenly breaking!
I'm using this temporary fix, as we were using our own function for this check anyway:
export function isValidDate(date: Parameters<typeof dayjs>[0]) {
/**
* `dayjs(null)` crashes when the objectSupport plugin is enabled.
* @see https://github.com/iamkun/dayjs/issues/2208
*/
if (date === null) return false;
return dayjs(date).isValid();
}
Hope this helps!
For me, I temporally solved it by copying the code of the object support plugin and then add a small null check.
See this:
const objectSupportDayPlugin = (o, c, dayjs) => {
const proto = c.prototype;
const isObject = (obj) =>
obj !== null && // HACK <-- I added this line
!(obj instanceof Date) &&
!(obj instanceof Array) &&
!proto.$utils().u(obj) &&
obj.constructor.name === "Object";
const prettyUnit = (u) => {
const unit = proto.$utils().p(u);
return unit === "date" ? "day" : unit;
};
const parseDate = (cfg) => {
const { date, utc } = cfg;
const $d = {};
if (isObject(date)) {
if (!Object.keys(date).length) {
return new Date();
}
const now = utc ? dayjs.utc() : dayjs();
Object.keys(date).forEach((k) => {
$d[prettyUnit(k)] = date[k];
});
const d = $d.day || (!$d.year && !($d.month >= 0) ? now.date() : 1);
const y = $d.year || now.year();
const M = $d.month >= 0 ? $d.month : !$d.year && !$d.day ? now.month() : 0; // eslint-disable-line no-nested-ternary,max-len
const h = $d.hour || 0;
const m = $d.minute || 0;
const s = $d.second || 0;
const ms = $d.millisecond || 0;
if (utc) {
return new Date(Date.UTC(y, M, d, h, m, s, ms));
}
return new Date(y, M, d, h, m, s, ms);
}
return date;
};
const oldParse = proto.parse;
proto.parse = function (cfg) {
cfg.date = parseDate.bind(this)(cfg);
oldParse.bind(this)(cfg);
};
const oldSet = proto.set;
const oldAdd = proto.add;
const oldSubtract = proto.subtract;
const callObject = function (call, argument, string, offset = 1) {
const keys = Object.keys(argument);
let chain = this;
keys.forEach((key) => {
chain = call.bind(chain)(argument[key] * offset, key);
});
return chain;
};
proto.set = function (unit, value) {
value = value === undefined ? unit : value;
if (unit.constructor.name === "Object") {
return callObject.bind(this)(
function (i, s) {
return oldSet.bind(this)(s, i);
},
value,
unit
);
}
return oldSet.bind(this)(unit, value);
};
proto.add = function (value, unit) {
if (value.constructor.name === "Object") {
return callObject.bind(this)(oldAdd, value, unit);
}
return oldAdd.bind(this)(value, unit);
};
proto.subtract = function (value, unit) {
if (value.constructor.name === "Object") {
return callObject.bind(this)(oldAdd, value, unit, -1);
}
return oldSubtract.bind(this)(value, unit);
};
};
day.extend(objectSupportDayPlugin);
The workaround I came with is writing an additional small plugin:
// dayjsNullSupport.ts
export default function (options: any, dayjsClass: any): void {
const oldParse = dayjsClass.prototype.parse;
dayjsClass.prototype.parse = function (config: any) {
if (config.date === null) {
config.date = NaN;
}
oldParse.bind(this)(config);
};
}
const objectSupport = require('dayjs/plugin/objectSupport');
const nullSupport = require('./dayjsNullSupport').default;
export const dayjs = require('dayjs');
dayjs.extend(objectSupport);
dayjs.extend(nullSupport);
Can't believe I have to deal with this.