import InBetween from "./BaseDatabase/InBetween";
import DayOfWeek, { DayOfWeekUtil } from "./Day/DayOfWeek";
import BadDataException from "./Exception/BadDataException";
import { ObjectType } from "./JSON";
import PositiveNumber from "./PositiveNumber";
import moment from "moment-timezone";
import Timezone from "./Timezone";
import Zod from "../Utils/Schema/Zod";
export const Moment = moment;
export default class OneUptimeDate {
    // get date time from unix timestamp
    static padDatePart(value) {
        return value.toString().padStart(2, "0");
    }
    static isUtcDateTimeString(value) {
        const utcDateTimePattern = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{1,9})?$/;
        return utcDateTimePattern.test(value.trim());
    }
    static normalizeUtcDateTimeString(value) {
        const trimmedValue = value.trim();
        const [datePart, timePart] = trimmedValue.split(" ");
        if (!datePart || !timePart) {
            return trimmedValue;
        }
        const [baseTime, fractionalPart] = timePart.split(".");
        if (!fractionalPart) {
            return `${datePart}T${baseTime}Z`;
        }
        const milliseconds = fractionalPart.slice(0, 3).padEnd(3, "0");
        return `${datePart}T${baseTime}.${milliseconds}Z`;
    }
    static getLocalShortMonthName(date) {
        return date.toLocaleString("default", {
            month: "short",
        });
    }
    static getSchema() {
        return Zod.object({
            _type: Zod.literal(ObjectType.DateTime),
            value: Zod.string().openapi({
                type: "string",
                example: "2023-10-01T12:00:00Z",
            }),
        }).openapi({
            type: "object",
            description: "A date time object.",
            example: {
                _type: ObjectType.DateTime,
                value: "2023-10-01T12:00:00Z",
            },
        });
    }
    static getHoursAndMinutesFromMinutes(minutes) {
        const hours = Math.floor(minutes / 60);
        const mins = minutes % 60;
        let formattedString = "";
        if (hours > 0) {
            formattedString += hours + " hours ";
        }
        if (mins > 0) {
            formattedString += mins + " minutes";
        }
        return formattedString.trim() || "0 minutes";
    }
    static fromUnixTimestamp(timestamp) {
        return new Date(timestamp * 1000);
    }
    static getStartOfTheWeek(date) {
        date = this.fromString(date);
        return moment(date).startOf("week").toDate();
    }
    static getEndOfTheWeek(date) {
        date = this.fromString(date);
        return moment(date).endOf("week").toDate();
    }
    static getInBetweenDatesAsFormattedString(inBetween) {
        return (this.getDateAsFormattedString(inBetween.startValue) +
            " - " +
            this.getDateAsFormattedString(inBetween.endValue));
    }
    static convertMinutesToMilliseconds(minutes) {
        return minutes * 60 * 1000;
    }
    static getNanoSecondsFromSeconds(seconds) {
        return seconds * 1000 * 1000 * 1000;
    }
    static now() {
        return this.getCurrentDate();
    }
    static getDateFromYYYYMMDD(year, month, day) {
        return moment(`${year}-${month}-${day}`).toDate();
    }
    getMicroSecondsFromSeconds(seconds) {
        return seconds * 1000 * 1000;
    }
    getMilliSecondsFromSeconds(seconds) {
        return seconds * 1000;
    }
    static getCurrentDateAsUnixNano() {
        return this.toUnixNano(this.getCurrentDate());
    }
    static toUnixNano(date) {
        return date.getTime() * 1000000;
    }
    static getLocalHourAndMinuteFromDate(date) {
        return this.getLocalTimeString(date);
    }
    static getLocalShortMonthNameFromDate(date) {
        date = this.fromString(date);
        return this.getLocalShortMonthName(date);
    }
    static getLocalTimeString(date, options) {
        var _a, _b;
        date = this.fromString(date);
        const includeMinutes = (_a = options === null || options === void 0 ? void 0 : options.includeMinutes) !== null && _a !== void 0 ? _a : true;
        const includeSeconds = (_b = options === null || options === void 0 ? void 0 : options.includeSeconds) !== null && _b !== void 0 ? _b : false;
        const hours = this.padDatePart(date.getHours());
        if (!includeMinutes) {
            return hours;
        }
        const minutes = this.padDatePart(date.getMinutes());
        if (!includeSeconds) {
            return `${hours}:${minutes}`;
        }
        const seconds = this.padDatePart(date.getSeconds());
        return `${hours}:${minutes}:${seconds}`;
    }
    static getDateAsLocalDayMonthString(date) {
        date = this.fromString(date);
        const day = this.padDatePart(date.getDate());
        const month = this.getLocalShortMonthName(date);
        return `${day} ${month}`;
    }
    static getDateAsLocalDayMonthHourString(date) {
        date = this.fromString(date);
        return `${this.getDateAsLocalDayMonthString(date)}, ${this.getLocalTimeString(date, { includeMinutes: false })}:00`;
    }
    static getDateAsLocalMonthYearString(date) {
        date = this.fromString(date);
        const month = this.getLocalShortMonthName(date);
        const year = date.getFullYear().toString();
        return `${month} ${year}`;
    }
    static getMillisecondsBetweenTwoUnixNanoDates(startDate, endDate) {
        return endDate - startDate;
    }
    static moveDateToTheDayOfWeek(date, moveToWeek, dayOfWeek) {
        // date will be moved to the week of "moveToWeek" and then to the day of week "dayOfWeek"
        date = this.fromString(date);
        date = this.keepTimeButMoveDay(date, moveToWeek);
        // now move the date to the day of week
        const dateDayOfWeek = this.getDayOfWeek(date);
        if (dateDayOfWeek === dayOfWeek) {
            return date;
        }
        const numberOfDayOfWeek = DayOfWeekUtil.getNumberOfDayOfWeek(dayOfWeek);
        const dateDayOfWeekNumber = DayOfWeekUtil.getNumberOfDayOfWeek(dateDayOfWeek);
        const difference = numberOfDayOfWeek - dateDayOfWeekNumber;
        if (difference === 0) {
            return date;
        }
        return this.addRemoveDays(date, difference);
    }
    static getDayOfTheWeekIndex(date) {
        date = this.fromString(date);
        return moment(date).weekday();
    }
    static isOverlapping(start, end, start1, end1) {
        start = this.fromString(start);
        end = this.fromString(end);
        start1 = this.fromString(start1);
        end1 = this.fromString(end1);
        let isOverlapping = moment(start).isBetween(start1, end1) ||
            moment(end).isBetween(start1, end1) ||
            moment(start).isSame(start1) ||
            moment(end).isSame(end1);
        if (!isOverlapping) {
            // check if the start1 and end1 are in between start and end
            isOverlapping =
                moment(start1).isBetween(start, end) ||
                    moment(end1).isBetween(start, end) ||
                    moment(start1).isSame(start) ||
                    moment(end1).isSame(end);
        }
        return isOverlapping;
    }
    static getCurrentDate() {
        return moment().toDate();
    }
    static fromNow(date) {
        return moment(date).fromNow();
    }
    static differenceBetweenTwoDatesAsFromattedString(date1, date2) {
        const seconds = this.getSecondsBetweenTwoDates(date1, date2);
        return this.secondsToFormattedFriendlyTimeString(seconds);
    }
    static toTimeString(date, use12HourFormat) {
        if (typeof date === "string") {
            date = this.fromString(date);
        }
        const format = use12HourFormat || this.getUserPrefers12HourFormat()
            ? "hh:mm A"
            : "HH:mm";
        return moment(date).format(format);
    }
    static isSame(date1, date2) {
        date1 = this.fromString(date1);
        date2 = this.fromString(date2);
        return moment(date1).isSame(date2);
    }
    static getDaysBetweenTwoDates(startDate, endDate) {
        startDate = this.fromString(startDate);
        endDate = this.fromString(endDate);
        return moment(endDate).diff(moment(startDate), "days");
    }
    static getDaysBetweenTwoDatesInclusive(startDate, endDate) {
        return this.getDaysBetweenTwoDates(startDate, endDate) + 1;
    }
    static getHoursBetweenTwoDates(startDate, endDate) {
        startDate = this.fromString(startDate);
        endDate = this.fromString(endDate);
        return moment(endDate).diff(moment(startDate), "hours");
    }
    static getHoursBetweenTwoDatesInclusive(startDate, endDate) {
        return this.getHoursBetweenTwoDates(startDate, endDate) + 1;
    }
    static getMinutesBetweenTwoDates(startDate, endDate) {
        startDate = this.fromString(startDate);
        endDate = this.fromString(endDate);
        return moment(endDate).diff(moment(startDate), "minutes");
    }
    static getMinutesBetweenTwoDatesInclusive(startDate, endDate) {
        return this.getMinutesBetweenTwoDates(startDate, endDate) + 1;
    }
    static getSecondsBetweenTwoDates(startDate, endDate) {
        startDate = this.fromString(startDate);
        endDate = this.fromString(endDate);
        return moment(endDate).diff(moment(startDate), "seconds");
    }
    static getSecondsBetweenTwoDatesInclusive(startDate, endDate) {
        return this.getSecondsBetweenTwoDates(startDate, endDate) + 1;
    }
    static getWeeksBetweenTwoDates(startDate, endDate) {
        startDate = this.fromString(startDate);
        endDate = this.fromString(endDate);
        return moment(endDate).diff(moment(startDate), "weeks");
    }
    static getWeeksBetweenTwoDatesInclusive(startDate, endDate) {
        return this.getWeeksBetweenTwoDates(startDate, endDate) + 1;
    }
    static getMonthsBetweenTwoDates(startDate, endDate) {
        startDate = this.fromString(startDate);
        endDate = this.fromString(endDate);
        return moment(endDate).diff(moment(startDate), "months");
    }
    static getMonthsBetweenTwoDatesInclusive(startDate, endDate) {
        return this.getMonthsBetweenTwoDates(startDate, endDate) + 1;
    }
    static getYearsBetweenTwoDates(startDate, endDate) {
        startDate = this.fromString(startDate);
        endDate = this.fromString(endDate);
        return moment(endDate).diff(moment(startDate), "years");
    }
    static getYearsBetweenTwoDatesInclusive(startDate, endDate) {
        return this.getYearsBetweenTwoDates(startDate, endDate) + 1;
    }
    static toString(date) {
        if (!date) {
            return "";
        }
        date = this.fromString(date);
        return date.toISOString();
    }
    static getCurrentMomentDate() {
        return moment();
    }
    static keepTimeButMoveDay(keepTimeFor, moveDayTo) {
        keepTimeFor = this.fromString(keepTimeFor);
        moveDayTo = this.fromString(moveDayTo);
        return moment(moveDayTo)
            .set({
            hour: keepTimeFor.getHours(),
            minute: keepTimeFor.getMinutes(),
            second: keepTimeFor.getSeconds(),
            millisecond: keepTimeFor.getMilliseconds(),
        })
            .toDate();
    }
    static getOneMinAgo() {
        return this.getSomeMinutesAgo(new PositiveNumber(1));
    }
    static getOneDayAgo() {
        return this.getSomeDaysAgo(new PositiveNumber(1));
    }
    static fromUnixNano(timestamp) {
        return moment(timestamp / 1000000).toDate();
    }
    static parseRfc5424Timestamp(value) {
        if (!value || typeof value !== "string") {
            return undefined;
        }
        const parsed = Moment(value, Moment.ISO_8601, true);
        if (!parsed.isValid()) {
            return undefined;
        }
        return parsed.toDate();
    }
    static parseRfc3164Timestamp(value) {
        if (!value || typeof value !== "string") {
            return undefined;
        }
        const normalized = value.replace(/\s+/g, " ").trim();
        if (!normalized) {
            return undefined;
        }
        const currentYear = this.getCurrentYear();
        let parsed = Moment(`${normalized} ${currentYear}`, "MMM D HH:mm:ss YYYY", true);
        if (!parsed.isValid()) {
            return undefined;
        }
        const now = Moment();
        if (parsed.isAfter(now.clone().add(1, "months"))) {
            parsed = parsed.subtract(1, "years");
        }
        else if (parsed.isBefore(now.clone().subtract(11, "months"))) {
            parsed = parsed.add(1, "years");
        }
        return parsed.toDate();
    }
    static getSecondsTo(date) {
        date = this.fromString(date);
        const dif = date.getTime() - this.getCurrentDate().getTime();
        const Seconds_from_T1_to_T2 = dif / 1000;
        return Math.abs(Seconds_from_T1_to_T2);
    }
    static getSomeMinutesAgo(minutes) {
        if (!(minutes instanceof PositiveNumber)) {
            minutes = new PositiveNumber(minutes);
        }
        return this.getCurrentMomentDate()
            .add(-1 * minutes.toNumber(), "minutes")
            .toDate();
    }
    static timezoneOffsetDate(date) {
        date = this.fromString(date);
        return this.addRemoveMinutes(date, date.getTimezoneOffset());
    }
    static toDateTimeLocalString(date) {
        date = this.fromString(date);
        const ten = (i) => {
            return (i < 10 ? "0" : "") + i;
        }, YYYY = date.getFullYear(), MM = ten(date.getMonth() + 1), DD = ten(date.getDate()), HH = ten(date.getHours()), II = ten(date.getMinutes()), SS = ten(date.getSeconds());
        return YYYY + "-" + MM + "-" + DD + "T" + HH + ":" + II + ":" + SS;
    }
    static fromJSON(json) {
        if (json["_type"] === ObjectType.DateTime) {
            return OneUptimeDate.fromString(json["value"]);
        }
        throw new BadDataException("Invalid JSON: " + JSON.stringify(json));
    }
    static toJSON(date) {
        return {
            _type: ObjectType.DateTime,
            value: OneUptimeDate.toString(date),
        };
    }
    static areOnTheSameDay(date1, date2) {
        date1 = this.fromString(date1);
        date2 = this.fromString(date2);
        return moment(date1).isSame(date2, "day");
    }
    static areOnTheSameMonth(date1, date2) {
        date1 = this.fromString(date1);
        date2 = this.fromString(date2);
        return moment(date1).isSame(date2, "month");
    }
    static areOnTheSameYear(date1, date2) {
        date1 = this.fromString(date1);
        date2 = this.fromString(date2);
        return moment(date1).isSame(date2, "year");
    }
    static areOnTheSameHour(date1, date2) {
        date1 = this.fromString(date1);
        date2 = this.fromString(date2);
        return moment(date1).isSame(date2, "hour");
    }
    static areOnTheSameMinute(date1, date2) {
        date1 = this.fromString(date1);
        date2 = this.fromString(date2);
        return moment(date1).isSame(date2, "minute");
    }
    static areOnTheSameSecond(date1, date2) {
        date1 = this.fromString(date1);
        date2 = this.fromString(date2);
        return moment(date1).isSame(date2, "second");
    }
    static areOnTheSameWeek(date1, date2) {
        date1 = this.fromString(date1);
        date2 = this.fromString(date2);
        return moment(date1).isSame(date2, "week");
    }
    static addRemoveMinutes(date, minutes) {
        date = this.fromString(date);
        return moment(date).add(minutes, "minutes").toDate();
    }
    static addRemoveDays(date, days) {
        date = this.fromString(date);
        return moment(date).add(days, "days").toDate();
    }
    static addRemoveHours(date, hours) {
        date = this.fromString(date);
        return moment(date).add(hours, "hours").toDate();
    }
    static addRemoveYears(date, years) {
        date = this.fromString(date);
        return moment(date).add(years, "years").toDate();
    }
    static addRemoveMonths(date, months) {
        date = this.fromString(date);
        return moment(date).add(months, "months").toDate();
    }
    static addRemoveWeeks(date, weeks) {
        date = this.fromString(date);
        return moment(date).add(weeks, "weeks").toDate();
    }
    static addRemoveSeconds(date, seconds) {
        date = this.fromString(date);
        return moment(date).add(seconds, "seconds").toDate();
    }
    static getSecondsInDays(days) {
        if (!(days instanceof PositiveNumber)) {
            days = new PositiveNumber(days);
        }
        return days.positiveNumber * 24 * 60 * 60;
    }
    static getMillisecondsInDays(days) {
        return this.getSecondsInDays(days) * 1000;
    }
    static getSomeHoursAgo(hours) {
        if (!(hours instanceof PositiveNumber)) {
            hours = new PositiveNumber(hours);
        }
        return this.getCurrentMomentDate()
            .add(-1 * hours.toNumber(), "hours")
            .toDate();
    }
    static getSomeDaysAgo(days) {
        if (!(days instanceof PositiveNumber)) {
            days = new PositiveNumber(days);
        }
        return this.getCurrentMomentDate()
            .add(-1 * days.toNumber(), "days")
            .toDate();
    }
    static getSomeDaysAgoFromDate(date, days) {
        date = this.fromString(date);
        if (!(days instanceof PositiveNumber)) {
            days = new PositiveNumber(days);
        }
        return moment(date)
            .add(-1 * days.toNumber(), "days")
            .toDate();
    }
    static getSomeDaysAfterFromDate(date, days) {
        date = this.fromString(date);
        if (!(days instanceof PositiveNumber)) {
            days = new PositiveNumber(days);
        }
        return moment(date).add(Number(days.toNumber()), "days").toDate();
    }
    static getSomeDaysBeforeFromDate(date, days) {
        date = this.fromString(date);
        if (!(days instanceof PositiveNumber)) {
            days = new PositiveNumber(days);
        }
        return moment(date)
            .add(-1 * Number(days.toNumber()), "days")
            .toDate();
    }
    static getSomeSecondsAgo(seconds) {
        if (!(seconds instanceof PositiveNumber)) {
            seconds = new PositiveNumber(seconds);
        }
        return this.getCurrentMomentDate()
            .add(-1 * seconds.toNumber(), "seconds")
            .toDate();
    }
    static getOneMinAfter() {
        return this.getSomeMinutesAfter(new PositiveNumber(1));
    }
    static getOneDayAfter() {
        return this.getSomeDaysAfter(new PositiveNumber(1));
    }
    static getSomeMinutesAfter(minutes) {
        if (!(minutes instanceof PositiveNumber)) {
            minutes = new PositiveNumber(minutes);
        }
        return this.getCurrentMomentDate()
            .add(minutes.toNumber(), "minutes")
            .toDate();
    }
    static getSomeHoursAfter(hours) {
        if (!(hours instanceof PositiveNumber)) {
            hours = new PositiveNumber(hours);
        }
        return this.getCurrentMomentDate().add(hours.toNumber(), "hours").toDate();
    }
    static getSomeDaysAfter(days) {
        if (!(days instanceof PositiveNumber)) {
            days = new PositiveNumber(days);
        }
        return this.getCurrentMomentDate().add(days.toNumber(), "days").toDate();
    }
    static secondsToFormattedTimeString(seconds) {
        return moment.utc(seconds * 1000).format("HH:mm:ss");
    }
    static toUnixTimestamp(date) {
        return Math.floor(date.getTime() / 1000);
    }
    static secondsToFormattedFriendlyTimeString(seconds) {
        const startDate = moment.utc(0);
        const date = moment.utc(seconds * 1000);
        // get the difference between the two dates as friendly formatted string
        let formattedString = "";
        // years between two dates
        const years = date.diff(startDate, "years");
        if (years > 0) {
            let text = "years ";
            if (years === 1) {
                text = "year ";
            }
            // add years to start date
            startDate.add(years, "years");
            formattedString += years + " " + text;
        }
        const months = date.diff(startDate, "months");
        if (months > 0) {
            let text = "months ";
            if (months === 1) {
                text = "month ";
            }
            // add months to start date
            startDate.add(months, "months");
            formattedString += months + " " + text;
        }
        const days = date.diff(startDate, "days");
        if (days > 0) {
            let text = "days ";
            if (days === 1) {
                text = "day ";
            }
            // add days to start date
            startDate.add(days, "days");
            formattedString += days + " " + text;
        }
        const hours = date.diff(startDate, "hours");
        if (hours > 0) {
            let text = "hours ";
            if (hours === 1) {
                text = "hour ";
            }
            // add hours to start date
            startDate.add(hours, "hours");
            formattedString += hours + " " + text;
        }
        const minutes = date.diff(startDate, "minutes");
        if (minutes > 0) {
            let text = "mins ";
            if (minutes === 1) {
                text = "min ";
            }
            // add minutes to start date
            startDate.add(minutes, "minutes");
            formattedString += minutes + " " + text;
        }
        const secondsLeft = date.diff(startDate, "seconds");
        if (secondsLeft > 0) {
            let text = "secs ";
            if (secondsLeft === 1) {
                text = "sec ";
            }
            // add seconds to start date
            startDate.add(secondsLeft, "seconds");
            formattedString += secondsLeft + " " + text;
        }
        return formattedString.trim();
    }
    static getGreaterDate(a, b) {
        a = this.fromString(a);
        b = this.fromString(b);
        if (this.isAfter(a, b)) {
            return a;
        }
        return b;
    }
    static getLesserDate(a, b) {
        a = this.fromString(a);
        b = this.fromString(b);
        if (this.isBefore(a, b)) {
            return a;
        }
        return b;
    }
    static getSecondsBetweenDates(start, end) {
        start = this.fromString(start);
        end = this.fromString(end);
        const duration = moment.duration(moment(end).diff(moment(start)));
        return duration.asSeconds();
    }
    static getSomeDaysAfterDate(date, days) {
        if (!(days instanceof PositiveNumber)) {
            days = new PositiveNumber(days);
        }
        return moment(date).add(days.toNumber(), "days").toDate();
    }
    static getSomeSecondsAfter(seconds) {
        if (!(seconds instanceof PositiveNumber)) {
            seconds = new PositiveNumber(seconds);
        }
        return this.getCurrentMomentDate()
            .add(seconds.toNumber(), "seconds")
            .toDate();
    }
    static getNumberOfDaysBetweenDates(startDate, endDate) {
        const a = moment(startDate);
        const b = moment(endDate);
        return b.diff(a, "days");
    }
    static getNumberOfMinutesBetweenDates(startDate, endDate) {
        const a = moment(startDate);
        const b = moment(endDate);
        return b.diff(a, "minutes");
    }
    static getNumberOfDaysBetweenDatesInclusive(startDate, endDate) {
        return this.getNumberOfDaysBetweenDates(startDate, endDate) + 1;
    }
    static momentToDate(moment) {
        return moment.toDate();
    }
    static getCurrentYear() {
        return moment().year();
    }
    static getStartOfDay(date) {
        date = this.fromString(date);
        return moment(date).startOf("day").toDate();
    }
    static getEndOfDay(date) {
        date = this.fromString(date);
        return moment(date).endOf("day").toDate();
    }
    static isBetween(date, startDate, endDate) {
        date = this.fromString(date);
        startDate = this.fromString(startDate);
        endDate = this.fromString(endDate);
        return moment(date).isBetween(startDate, endDate);
    }
    static isAfter(date, startDate) {
        date = this.fromString(date);
        startDate = this.fromString(startDate);
        return moment(date).isAfter(startDate, "seconds");
    }
    static isOnOrAfter(date, startDate) {
        date = this.fromString(date);
        startDate = this.fromString(startDate);
        return moment(date).isSameOrAfter(startDate, "seconds");
    }
    static getDayOfWeek(date) {
        const dayOfWeek = this.geyDayOfWeekAsNumber(date);
        if (dayOfWeek === 1) {
            return DayOfWeek.Monday;
        }
        else if (dayOfWeek === 2) {
            return DayOfWeek.Tuesday;
        }
        else if (dayOfWeek === 3) {
            return DayOfWeek.Wednesday;
        }
        else if (dayOfWeek === 4) {
            return DayOfWeek.Thursday;
        }
        else if (dayOfWeek === 5) {
            return DayOfWeek.Friday;
        }
        else if (dayOfWeek === 6) {
            return DayOfWeek.Saturday;
        }
        else if (dayOfWeek === 7) {
            return DayOfWeek.Sunday;
        }
        throw new BadDataException("Invalid day of week");
    }
    static geyDayOfWeekAsNumber(date) {
        date = this.fromString(date);
        return moment(date).isoWeekday();
    }
    static isOnOrBefore(date, endDate) {
        date = this.fromString(date);
        endDate = this.fromString(endDate);
        return moment(date).isSameOrBefore(endDate, "seconds");
    }
    static isEqualBySeconds(date, startDate) {
        date = this.fromString(date);
        startDate = this.fromString(startDate);
        return moment(date).isSame(startDate, "seconds");
    }
    static hasExpired(expirationDate) {
        expirationDate = this.fromString(expirationDate);
        return !moment(this.getCurrentDate()).isBefore(expirationDate, "seconds");
    }
    static isBefore(date, endDate) {
        date = this.fromString(date);
        endDate = this.fromString(endDate);
        return moment(date).isBefore(endDate, "seconds");
    }
    static getCurrentDateAsFormattedString(options) {
        return this.getDateAsFormattedString(new Date(), options);
    }
    static getUserPrefers12HourFormat() {
        if (typeof window === "undefined") {
            // Server-side: default to 12-hour format for user-friendly display
            return true;
        }
        // Client-side: detect user's preferred time format from browser locale
        const testDate = new Date();
        const timeString = testDate.toLocaleTimeString();
        return (timeString.toLowerCase().includes("am") ||
            timeString.toLowerCase().includes("pm"));
    }
    static getDateAsUserFriendlyFormattedString(date, options) {
        return this.getDateAsFormattedString(date, Object.assign(Object.assign({}, options), { use12HourFormat: this.getUserPrefers12HourFormat() }));
    }
    static getDateAsUserFriendlyLocalFormattedString(date, onlyShowDate, showSeconds) {
        return this.getDateAsLocalFormattedString(date, onlyShowDate, this.getUserPrefers12HourFormat(), showSeconds);
    }
    static getDateAsFormattedString(date, options) {
        date = this.fromString(date);
        let formatstring = "MMM DD YYYY, HH:mm";
        if (options === null || options === void 0 ? void 0 : options.use12HourFormat) {
            formatstring = "MMM DD YYYY, hh:mm A";
        }
        if (options === null || options === void 0 ? void 0 : options.showSeconds) {
            if (options === null || options === void 0 ? void 0 : options.use12HourFormat) {
                formatstring = "MMM DD YYYY, hh:mm:ss A";
            }
            else {
                formatstring = "MMM DD YYYY, HH:mm:ss";
            }
        }
        if (options === null || options === void 0 ? void 0 : options.onlyShowDate) {
            formatstring = "MMM DD, YYYY";
        }
        return (moment(date).format(formatstring) +
            " " +
            ((options === null || options === void 0 ? void 0 : options.onlyShowDate) ? "" : this.getCurrentTimezoneString()));
    }
    static getDifferenceInMinutes(date, date2) {
        date = this.fromString(date);
        date2 = this.fromString(date2);
        const minutes = moment(date).diff(moment(date2), "minutes");
        if (minutes < 0) {
            return minutes * -1;
        }
        return minutes;
    }
    static getDifferenceInSeconds(date, date2) {
        date = this.fromString(date);
        date2 = this.fromString(date2);
        const seconds = moment(date).diff(moment(date2), "seconds");
        if (seconds < 0) {
            return seconds * -1;
        }
        return seconds;
    }
    static getDifferenceInMonths(date, date2) {
        date = this.fromString(date);
        date2 = this.fromString(date2);
        const months = moment(date).diff(moment(date2), "months");
        if (months < 0) {
            return months * -1;
        }
        return months;
    }
    static convertSecondsToDaysHoursMinutesAndSeconds(seconds) {
        // should output 2 days, 3 hours, 4 minutes and 5 seconds. If the days are 0, it should not show the days. If the hours are 0, it should not show the hours. If the minutes are 0, it should not show the minutes. If the seconds are 0, it should not show the seconds.
        const days = Math.floor(seconds / (24 * 60 * 60));
        const hours = Math.floor((seconds % (24 * 60 * 60)) / (60 * 60));
        const minutes = Math.floor((seconds % (60 * 60)) / 60);
        const secs = seconds % 60;
        let formattedString = "";
        if (days > 0) {
            formattedString += days + " days";
        }
        if (hours > 0) {
            if (formattedString.length > 0) {
                formattedString += ", ";
            }
            formattedString += hours + " hours";
        }
        if (minutes > 0) {
            if (formattedString.length > 0) {
                formattedString += ", ";
            }
            formattedString += minutes + " minutes";
        }
        if (secs > 0) {
            if (formattedString.length > 0) {
                formattedString += ", ";
            }
            formattedString += secs + " seconds";
        }
        return formattedString;
    }
    static convertMinutesToDaysHoursAndMinutes(minutes) {
        // should output 2 days, 3 hours and 4 minutes. If the days are 0, it should not show the days. If the hours are 0, it should not show the hours. If the minutes are 0, it should not show the minutes.
        const days = Math.floor(minutes / (24 * 60));
        const hours = Math.floor((minutes % (24 * 60)) / 60);
        const mins = minutes % 60;
        let formattedString = "";
        if (days > 0) {
            formattedString += days + " days";
        }
        if (hours > 0) {
            if (formattedString.length > 0) {
                formattedString += ", ";
            }
            formattedString += hours + " hours";
        }
        if (mins >= 0) {
            if (formattedString.length > 0) {
                formattedString += ", ";
            }
            formattedString += mins + " minutes";
        }
        return formattedString;
    }
    static getHumanizedDurationFromNanoseconds(data) {
        var _a, _b;
        let { nanoseconds } = data;
        const maxParts = (_a = data.maxParts) !== null && _a !== void 0 ? _a : 2;
        if (!Number.isFinite(nanoseconds)) {
            return "-";
        }
        if (nanoseconds === 0) {
            return "0 ms";
        }
        const sign = nanoseconds < 0 ? "-" : "";
        nanoseconds = Math.abs(nanoseconds);
        if (nanoseconds < 1000) {
            return sign + Math.round(nanoseconds).toString() + " ns";
        }
        const microseconds = nanoseconds / 1000;
        if (microseconds < 1000) {
            return sign + OneUptimeDate.formatDurationValue(microseconds) + " μs";
        }
        const milliseconds = nanoseconds / 1000000;
        if (milliseconds < 1000) {
            return sign + OneUptimeDate.formatDurationValue(milliseconds) + " ms";
        }
        const seconds = nanoseconds / 1000000000;
        if (seconds < 60) {
            return sign + OneUptimeDate.formatDurationValue(seconds) + " s";
        }
        const units = [
            { label: "d", seconds: 86400 },
            { label: "h", seconds: 3600 },
            { label: "m", seconds: 60 },
            { label: "s", seconds: 1 },
        ];
        let remainingSeconds = Math.floor(seconds);
        const parts = [];
        for (const unit of units) {
            if (parts.length >= maxParts) {
                break;
            }
            if (remainingSeconds < unit.seconds) {
                continue;
            }
            const value = Math.floor(remainingSeconds / unit.seconds);
            if (value > 0) {
                parts.push(`${value}${unit.label}`);
                remainingSeconds -= value * unit.seconds;
            }
        }
        const fractionalSeconds = seconds - Math.floor(seconds);
        const leftoverSeconds = remainingSeconds + fractionalSeconds;
        if (leftoverSeconds > 0) {
            if (parts.length === 0) {
                parts.push(`${OneUptimeDate.formatDurationValue(leftoverSeconds)}s`);
            }
            else if ((_b = parts[parts.length - 1]) === null || _b === void 0 ? void 0 : _b.endsWith("s")) {
                const numericValue = parseFloat(parts[parts.length - 1].slice(0, -1));
                const updatedSeconds = numericValue + leftoverSeconds;
                parts[parts.length - 1] =
                    `${OneUptimeDate.formatDurationValue(updatedSeconds)}s`;
            }
            else if (parts.length < maxParts) {
                parts.push(`${OneUptimeDate.formatDurationValue(leftoverSeconds)}s`);
            }
        }
        if (parts.length === 0) {
            return sign + OneUptimeDate.formatDurationValue(seconds) + " s";
        }
        const trimmedParts = parts.slice(0, maxParts);
        if (sign) {
            trimmedParts[0] = sign + trimmedParts[0];
        }
        return trimmedParts.join(" ");
    }
    static formatDurationValue(value) {
        const abs = Math.abs(value);
        if (abs >= 100) {
            return Math.round(value).toString();
        }
        const decimalPlaces = 2;
        const fixed = value.toFixed(decimalPlaces);
        return fixed.replace(/\.0+$/, "").replace(/(\.\d*?[1-9])0+$/, "$1");
    }
    static getGmtOffsetByTimezone(timezone) {
        return moment.tz(timezone).utcOffset();
    }
    static getGmtOffsetFriendlyStringByTimezone(timezone) {
        const offset = this.getGmtOffsetByTimezone(timezone);
        return this.getGmtOffsetFriendlyString(offset) + " " + timezone;
    }
    static isValidDateString(date) {
        return moment(date).isValid();
    }
    static getGmtOffsetFriendlyString(offset) {
        const hours = Math.abs(offset) / 60;
        const minutes = Math.abs(offset) % 60;
        let formattedString = "GMT";
        if (offset < 0) {
            formattedString += "-";
        }
        else {
            formattedString += "+";
        }
        // remove decimals from hours
        formattedString += Math.floor(hours);
        if (minutes > 0) {
            formattedString += ":" + minutes;
        }
        return formattedString;
    }
    static getDateAsFormattedArrayInMultipleTimezones(data) {
        var _a;
        let date = data.date;
        const onlyShowDate = data.onlyShowDate;
        let timezones = data.timezones;
        const use12HourFormat = (_a = data.use12HourFormat) !== null && _a !== void 0 ? _a : this.getUserPrefers12HourFormat();
        date = this.fromString(date);
        let formatstring = "MMM DD YYYY, HH:mm";
        if (use12HourFormat) {
            formatstring = "MMM DD YYYY, hh:mm A";
        }
        if (onlyShowDate) {
            formatstring = "MMM DD, YYYY";
        }
        const timezoneDates = [];
        if (!timezones || timezones.length === 0) {
            /*
             * Use IANA region zones (not fixed-offset zones like "EST") so the
             * abbreviation reflects DST at the event's date — e.g. EST/EDT for
             * America/New_York and GMT/BST for Europe/London.
             */
            timezones = [
                Timezone.UTC,
                Timezone.EuropeLondon,
                Timezone.AmericaNew_York,
                Timezone.AmericaLos_Angeles,
                Timezone.AsiaKolkata,
                Timezone.AustraliaSydney,
            ]; // default timezones.
        }
        for (let i = 0; i < timezones.length; i++) {
            timezoneDates.push(moment(date)
                .tz(timezones[i])
                .format(formatstring) +
                " " +
                (onlyShowDate
                    ? ""
                    : this.getZoneAbbrByTimezone(timezones[i], date)));
        }
        return timezoneDates;
    }
    static getDateAsFormattedHTMLInMultipleTimezones(data) {
        const date = data.date;
        const onlyShowDate = data.onlyShowDate;
        const timezones = data.timezones;
        const use12HourFormat = data.use12HourFormat;
        return this.getDateAsFormattedArrayInMultipleTimezones({
            date,
            onlyShowDate,
            timezones,
            use12HourFormat,
        }).join("<br/>");
    }
    static getDateAsFormattedStringInMultipleTimezones(data) {
        const date = data.date;
        const onlyShowDate = data.onlyShowDate;
        const timezones = data.timezones;
        const use12HourFormat = data.use12HourFormat;
        return this.getDateAsFormattedArrayInMultipleTimezones({
            date,
            onlyShowDate,
            timezones,
            use12HourFormat,
        }).join("\n");
    }
    static getDateAsLocalFormattedString(date, onlyShowDate, use12HourFormat, showSeconds) {
        date = this.fromString(date);
        let formatstring = "MMM DD YYYY, HH:mm";
        if (use12HourFormat) {
            formatstring = "MMM DD YYYY, hh:mm A";
        }
        if (showSeconds) {
            if (use12HourFormat) {
                formatstring = "MMM DD YYYY, hh:mm:ss A";
            }
            else {
                formatstring = "MMM DD YYYY, HH:mm:ss";
            }
        }
        if (onlyShowDate) {
            formatstring = "MMM DD, YYYY";
        }
        const momentDate = moment(date).local();
        return (momentDate.format(formatstring) +
            " " +
            (onlyShowDate ? "" : this.getCurrentTimezoneString())).trim();
    }
    static getDayInSeconds(days) {
        if (!days) {
            days = 1;
        }
        return 24 * 60 * 60 * days;
    }
    static getCurrentTimezoneString() {
        return this.getZoneAbbrByTimezone(this.getCurrentTimezone());
    }
    static getZoneAbbrByTimezone(timezone, date) {
        /*
         * Pass the reference date into moment so the abbreviation reflects DST
         * at that date (e.g. EST in winter, EDT in summer) rather than "now".
         */
        let zoneAbbr = date
            ? moment(this.fromString(date)).tz(timezone).zoneAbbr()
            : moment.tz(timezone).zoneAbbr();
        if (zoneAbbr.startsWith("+") || zoneAbbr.startsWith("-")) {
            zoneAbbr = "GMT" + zoneAbbr;
        }
        return zoneAbbr;
    }
    static getCurrentTimezone() {
        return moment.tz.guess();
    }
    static getDateString(date) {
        date = this.fromString(date);
        return this.getDateAsLocalFormattedString(date, true);
    }
    static isInThePast(date) {
        date = this.fromString(date);
        return moment(date).isBefore(new Date(), "seconds");
    }
    static isInTheFuture(date) {
        date = this.fromString(date);
        return moment(date).isAfter(new Date(), "seconds");
    }
    static fromString(date) {
        if (date instanceof Date) {
            return date;
        }
        if (typeof date === "string") {
            const trimmedDate = date.trim();
            if (this.isUtcDateTimeString(trimmedDate)) {
                return moment
                    .utc(this.normalizeUtcDateTimeString(trimmedDate))
                    .toDate();
            }
            /*
             * moment.js throws on non-ISO formats like TLS cert dates ("Mar 31 00:00:00 2026 GMT").
             * Fall back to native Date which handles this format correctly.
             */
            try {
                return moment(trimmedDate).toDate();
            }
            catch (_a) {
                return new Date(trimmedDate);
            }
        }
        if (date &&
            date["value"] &&
            typeof date["value"] === "string" &&
            date["_type"] &&
            (date["_type"] === "Date" || date["_type"] === "DateTime")) {
            return moment(date["value"]).toDate();
        }
        throw new BadDataException("Invalid date: " + (date === null || date === void 0 ? void 0 : date.toString()));
    }
    static asDateForDatabaseQuery(date) {
        date = this.fromString(date);
        const formatstring = "YYYY-MM-DD";
        return moment(date).local().format(formatstring);
    }
    static asFilterDateForDatabaseQuery(date) {
        date = this.fromString(date);
        const formattedDate = moment(date).toDate();
        return new InBetween(OneUptimeDate.getStartOfDay(formattedDate), OneUptimeDate.getEndOfDay(formattedDate));
    }
    static getDateWithCustomTime(data) {
        const hour = data.hours;
        const minutes = data.minutes;
        const seconds = data.seconds;
        //validate the hour
        if (hour < 0 || hour > 23) {
            throw new BadDataException("Invalid hour");
        }
        //validate the minutes
        if (minutes < 0 || minutes > 59) {
            throw new BadDataException("Invalid minutes");
        }
        //validate the seconds
        if (seconds < 0 || seconds > 59) {
            throw new BadDataException("Invalid seconds");
        }
        const date = OneUptimeDate.getCurrentDate();
        date.setHours(hour);
        date.setMinutes(minutes);
        date.setSeconds(seconds);
        return date;
    }
    static toDatabaseDate(date) {
        date = this.fromString(date);
        return moment(date).format("YYYY-MM-DD HH:mm:ss");
    }
    static resetSecondsAndMilliseconds(date) {
        date = this.fromString(date);
        date.setSeconds(0);
        date.setMilliseconds(0);
        return date;
    }
    static toClickhouseDateTime(date) {
        const parsedDate = this.fromString(date);
        return moment(parsedDate).utc().format("YYYY-MM-DD HH:mm:ss");
    }
    static toClickhouseDateTime64(date, nanoTimestamp) {
        const parsedDate = this.fromString(date);
        const base = moment(parsedDate).utc().format("YYYY-MM-DD HH:mm:ss");
        let nanoFraction;
        if (nanoTimestamp !== undefined && nanoTimestamp > 0) {
            // Extract sub-second nanoseconds from the unix nano timestamp
            const subSecondNanos = nanoTimestamp % 1000000000;
            nanoFraction = subSecondNanos.toString().padStart(9, "0");
        }
        else {
            // Fall back to milliseconds from the Date object
            const ms = parsedDate.getMilliseconds();
            nanoFraction = (ms * 1000000).toString().padStart(9, "0");
        }
        return `${base}.${nanoFraction}`;
    }
}
//# sourceMappingURL=Date.js.map