async update(status, ping = 0, date) {
        if (!date) {
            date = this.getCurrentDate();
        }

        let flatStatus = this.flatStatus(status);

        if (flatStatus === DOWN && ping > 0) {
            log.debug("uptime-calc", "The ping is not effective when the status is DOWN");
        }

        let divisionKey = this.getMinutelyKey(date);
        let hourlyKey = this.getHourlyKey(date);
        let dailyKey = this.getDailyKey(date);

        let minutelyData = this.minutelyUptimeDataList[divisionKey];
        let hourlyData = this.hourlyUptimeDataList[hourlyKey];
        let dailyData = this.dailyUptimeDataList[dailyKey];

        if (status === MAINTENANCE) {
            minutelyData.maintenance = minutelyData.maintenance ? minutelyData.maintenance + 1 : 1;
            hourlyData.maintenance = hourlyData.maintenance ? hourlyData.maintenance + 1 : 1;
            dailyData.maintenance = dailyData.maintenance ? dailyData.maintenance + 1 : 1;

        } else if (flatStatus === UP) {
            minutelyData.up += 1;
            hourlyData.up += 1;
            dailyData.up += 1;

            // Only UP status can update the ping
            if (!isNaN(ping)) {
                // Add avg ping
                // The first beat of the minute, the ping is the current ping
                if (minutelyData.up === 1) {
                    minutelyData.avgPing = ping;
                    minutelyData.minPing = ping;
                    minutelyData.maxPing = ping;
                } else {
                    minutelyData.avgPing = (minutelyData.avgPing * (minutelyData.up - 1) + ping) / minutelyData.up;
                    minutelyData.minPing = Math.min(minutelyData.minPing, ping);
                    minutelyData.maxPing = Math.max(minutelyData.maxPing, ping);
                }

                // Add avg ping
                // The first beat of the hour, the ping is the current ping
                if (hourlyData.up === 1) {
                    hourlyData.avgPing = ping;
                    hourlyData.minPing = ping;
                    hourlyData.maxPing = ping;
                } else {
                    hourlyData.avgPing = (hourlyData.avgPing * (hourlyData.up - 1) + ping) / hourlyData.up;
                    hourlyData.minPing = Math.min(hourlyData.minPing, ping);
                    hourlyData.maxPing = Math.max(hourlyData.maxPing, ping);
                }

                // Add avg ping (daily)
                // The first beat of the day, the ping is the current ping
                if (dailyData.up === 1) {
                    dailyData.avgPing = ping;
                    dailyData.minPing = ping;
                    dailyData.maxPing = ping;
                } else {
                    dailyData.avgPing = (dailyData.avgPing * (dailyData.up - 1) + ping) / dailyData.up;
                    dailyData.minPing = Math.min(dailyData.minPing, ping);
                    dailyData.maxPing = Math.max(dailyData.maxPing, ping);
                }
            }

        } else if (flatStatus === DOWN) {
            minutelyData.down += 1;
            hourlyData.down += 1;
            dailyData.down += 1;
        }

        if (minutelyData !== this.lastUptimeData) {
            this.lastUptimeData = minutelyData;
        }

        if (hourlyData !== this.lastHourlyUptimeData) {
            this.lastHourlyUptimeData = hourlyData;
        }

        if (dailyData !== this.lastDailyUptimeData) {
            this.lastDailyUptimeData = dailyData;
        }

        // Don't store data in test mode
        if (process.env.TEST_BACKEND) {
            log.debug("uptime-calc", "Skip storing data in test mode");
            return date;
        }

        let dailyStatBean = await this.getDailyStatBean(dailyKey);
        dailyStatBean.up = dailyData.up;
        dailyStatBean.down = dailyData.down;
        dailyStatBean.ping = dailyData.avgPing;
        dailyStatBean.pingMin = dailyData.minPing;
        dailyStatBean.pingMax = dailyData.maxPing;
        {
            // eslint-disable-next-line no-unused-vars
            const { up, down, avgPing, minPing, maxPing, timestamp, ...extras } = dailyData;
            if (Object.keys(extras).length > 0) {
                dailyStatBean.extras = JSON.stringify(extras);
            }
        }
        await R.store(dailyStatBean);

        let currentDate = this.getCurrentDate();

        // For migration mode, we don't need to store old hourly and minutely data, but we need 30-day's hourly data
        // Run anyway for non-migration mode
        if (!this.migrationMode || date.isAfter(currentDate.subtract(this.statHourlyKeepDay, "day"))) {
            let hourlyStatBean = await this.getHourlyStatBean(hourlyKey);
            hourlyStatBean.up = hourlyData.up;
            hourlyStatBean.down = hourlyData.down;
            hourlyStatBean.ping = hourlyData.avgPing;
            hourlyStatBean.pingMin = hourlyData.minPing;
            hourlyStatBean.pingMax = hourlyData.maxPing;
            {
                // eslint-disable-next-line no-unused-vars
                const { up, down, avgPing, minPing, maxPing, timestamp, ...extras } = hourlyData;
                if (Object.keys(extras).length > 0) {
                    hourlyStatBean.extras = JSON.stringify(extras);
                }
            }
            await R.store(hourlyStatBean);
        }

        // For migration mode, we don't need to store old hourly and minutely data, but we need 24-hour's minutely data
        // Run anyway for non-migration mode
        if (!this.migrationMode || date.isAfter(currentDate.subtract(this.statMinutelyKeepHour, "hour"))) {
            let minutelyStatBean = await this.getMinutelyStatBean(divisionKey);
            minutelyStatBean.up = minutelyData.up;
            minutelyStatBean.down = minutelyData.down;
            minutelyStatBean.ping = minutelyData.avgPing;
            minutelyStatBean.pingMin = minutelyData.minPing;
            minutelyStatBean.pingMax = minutelyData.maxPing;
            {
                // eslint-disable-next-line no-unused-vars
                const { up, down, avgPing, minPing, maxPing, timestamp, ...extras } = minutelyData;
                if (Object.keys(extras).length > 0) {
                    minutelyStatBean.extras = JSON.stringify(extras);
                }
            }
            await R.store(minutelyStatBean);
        }

        // No need to remove old data in migration mode
        if (!this.migrationMode) {
            // Remove the old data
            // TODO: Improvement: Convert it to a job?
            log.debug("uptime-calc", "Remove old data");
            await R.exec("DELETE FROM stat_minutely WHERE monitor_id = ? AND timestamp < ?", [
                this.monitorID,
                this.getMinutelyKey(currentDate.subtract(this.statMinutelyKeepHour, "hour")),
            ]);

            await R.exec("DELETE FROM stat_hourly WHERE monitor_id = ? AND timestamp < ?", [
                this.monitorID,
                this.getHourlyKey(currentDate.subtract(this.statHourlyKeepDay, "day")),
            ]);
        }

        return date;
    }
@param {number} status status
@param {number} ping Ping
@param {dayjs.Dayjs} date Date (Only for migration)
@returns {dayjs.Dayjs} date
@throws {Error} Invalid status