bb-plane-fork/packages/utils/src/datetime.ts
devin-ai-integration[bot] ed64168ca7
chore(utils): copy helper functions from web/helpers (#6264)
* chore(utils): copy helper functions from web/helpers

Co-Authored-By: sriram@plane.so <sriram@plane.so>

* chore(utils): bump version to 0.24.2

Co-Authored-By: sriram@plane.so <sriram@plane.so>

* chore: bump root package version to 0.24.2

Co-Authored-By: sriram@plane.so <sriram@plane.so>

* fix: remove duplicate function and simplify auth utils

Co-Authored-By: sriram@plane.so <sriram@plane.so>

* fix: improve HTML entity escaping in sanitizeHTML

Co-Authored-By: sriram@plane.so <sriram@plane.so>

* fix: version changes

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: sriram@plane.so <sriram@plane.so>
2024-12-26 15:27:40 +05:30

335 lines
12 KiB
TypeScript

import { differenceInDays, format, formatDistanceToNow, isAfter, isEqual, isValid, parseISO } from "date-fns";
/**
* This method returns a date from string of type yyyy-mm-dd
* This method is recommended to use instead of new Date() as this does not introduce any timezone offsets
* @param date
* @returns date or undefined
*/
export const getDate = (date: string | Date | undefined | null): Date | undefined => {
try {
if (!date || date === "") return;
if (typeof date !== "string" && !(date instanceof String)) return date;
const [yearString, monthString, dayString] = date.substring(0, 10).split("-");
const year = parseInt(yearString);
const month = parseInt(monthString);
const day = parseInt(dayString);
// Using Number.isInteger instead of lodash's isNumber for better specificity and no external dependency
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) return;
return new Date(year, month - 1, day);
} catch (e) {
return undefined;
}
};
/**
* @returns {string | null} formatted date in the format of MMM dd, yyyy
* @description Returns date in the formatted format
* @param {Date | string} date
* @example renderFormattedDate("2024-01-01") // Jan 01, 2024
*/
/**
* @description Returns date in the formatted format
* @param {Date | string} date Date to format
* @param {string} formatToken Format token (optional, default: MMM dd, yyyy)
* @returns {string | undefined} Formatted date in the desired format
* @example
* renderFormattedDate("2024-01-01") // returns "Jan 01, 2024"
* renderFormattedDate("2024-01-01", "MM-DD-YYYY") // returns "01-01-2024"
*/
export const renderFormattedDate = (
date: string | Date | undefined | null,
formatToken: string = "MMM dd, yyyy"
): string | undefined => {
// Parse the date to check if it is valid
const parsedDate = getDate(date);
// return if undefined
if (!parsedDate) return;
// Check if the parsed date is valid before formatting
if (!isValid(parsedDate)) return; // Return undefined for invalid dates
let formattedDate;
try {
// Format the date in the format provided or default format (MMM dd, yyyy)
formattedDate = format(parsedDate, formatToken);
} catch (e) {
// Format the date in format (MMM dd, yyyy) in case of any error
formattedDate = format(parsedDate, "MMM dd, yyyy");
}
return formattedDate;
};
/**
* @description Returns total number of days in range
* @param {string | Date} startDate - Start date
* @param {string | Date} endDate - End date
* @param {boolean} inclusive - Include start and end dates (optional, default: true)
* @returns {number | undefined} Total number of days
* @example
* findTotalDaysInRange("2024-01-01", "2024-01-08") // returns 8
*/
export const findTotalDaysInRange = (
startDate: Date | string | undefined | null,
endDate: Date | string | undefined | null,
inclusive: boolean = true
): number | undefined => {
// Parse the dates to check if they are valid
const parsedStartDate = getDate(startDate);
const parsedEndDate = getDate(endDate);
// return if undefined
if (!parsedStartDate || !parsedEndDate) return;
// Check if the parsed dates are valid before calculating the difference
if (!isValid(parsedStartDate) || !isValid(parsedEndDate)) return 0; // Return 0 for invalid dates
// Calculate the difference in days
const diffInDays = differenceInDays(parsedEndDate, parsedStartDate);
// Return the difference in days based on inclusive flag
return inclusive ? diffInDays + 1 : diffInDays;
};
/**
* @description Add number of days to the provided date
* @param {string | Date} startDate - Start date
* @param {number} numberOfDays - Number of days to add
* @returns {Date | undefined} Resulting date
* @example
* addDaysToDate("2024-01-01", 7) // returns Date(2024-01-08)
*/
export const addDaysToDate = (startDate: Date | string | undefined | null, numberOfDays: number): Date | undefined => {
// Parse the dates to check if they are valid
const parsedStartDate = getDate(startDate);
// return if undefined
if (!parsedStartDate) return;
const newDate = new Date(parsedStartDate);
newDate.setDate(newDate.getDate() + numberOfDays);
return newDate;
};
/**
* @description Returns number of days left from today
* @param {string | Date} date - Target date
* @param {boolean} inclusive - Include today (optional, default: true)
* @returns {number | undefined} Number of days left
* @example
* findHowManyDaysLeft("2024-01-08") // returns days between today and Jan 8, 2024
*/
export const findHowManyDaysLeft = (
date: Date | string | undefined | null,
inclusive: boolean = true
): number | undefined => {
if (!date) return undefined;
return findTotalDaysInRange(new Date(), date, inclusive);
};
/**
* @description Returns time passed since the event happened
* @param {string | number | Date} time - Time to calculate from
* @returns {string} Formatted time ago string
* @example
* calculateTimeAgo("2023-01-01") // returns "1 year ago"
*/
export const calculateTimeAgo = (time: string | number | Date | null): string => {
if (!time) return "";
const parsedTime = typeof time === "string" || typeof time === "number" ? parseISO(String(time)) : time;
if (!parsedTime) return "";
const distance = formatDistanceToNow(parsedTime, { addSuffix: true });
return distance;
};
/**
* @description Returns short form of time passed (e.g., 1y, 2mo, 3d)
* @param {string | number | Date} date - Date to calculate from
* @returns {string} Short form time ago
* @example
* calculateTimeAgoShort("2023-01-01") // returns "1y"
*/
export const calculateTimeAgoShort = (date: string | number | Date | null): string => {
if (!date) return "";
const parsedDate = typeof date === "string" ? parseISO(date) : new Date(date);
const now = new Date();
const diffInSeconds = (now.getTime() - parsedDate.getTime()) / 1000;
if (diffInSeconds < 60) return `${Math.floor(diffInSeconds)}s`;
const diffInMinutes = diffInSeconds / 60;
if (diffInMinutes < 60) return `${Math.floor(diffInMinutes)}m`;
const diffInHours = diffInMinutes / 60;
if (diffInHours < 24) return `${Math.floor(diffInHours)}h`;
const diffInDays = diffInHours / 24;
if (diffInDays < 30) return `${Math.floor(diffInDays)}d`;
const diffInMonths = diffInDays / 30;
if (diffInMonths < 12) return `${Math.floor(diffInMonths)}mo`;
const diffInYears = diffInMonths / 12;
return `${Math.floor(diffInYears)}y`;
};
/**
* @description Checks if a date is greater than today
* @param {string} dateStr - Date string to check
* @returns {boolean} True if date is greater than today
* @example
* isDateGreaterThanToday("2024-12-31") // returns true
*/
export const isDateGreaterThanToday = (dateStr: string): boolean => {
if (!dateStr) return false;
const date = parseISO(dateStr);
const today = new Date();
if (!isValid(date)) return false;
return isAfter(date, today);
};
/**
* @description Returns week number of date
* @param {Date} date - Date to get week number from
* @returns {number} Week number (1-52)
* @example
* getWeekNumberOfDate(new Date("2023-09-01")) // returns 35
*/
export const getWeekNumberOfDate = (date: Date): number => {
const currentDate = date;
const startDate = new Date(currentDate.getFullYear(), 0, 1);
const days = Math.floor((currentDate.getTime() - startDate.getTime()) / (24 * 60 * 60 * 1000));
const weekNumber = Math.ceil((days + 1) / 7);
return weekNumber;
};
/**
* @description Checks if two dates are equal
* @param {Date | string} date1 - First date
* @param {Date | string} date2 - Second date
* @returns {boolean} True if dates are equal
* @example
* checkIfDatesAreEqual("2024-01-01", "2024-01-01") // returns true
*/
export const checkIfDatesAreEqual = (
date1: Date | string | null | undefined,
date2: Date | string | null | undefined
): boolean => {
const parsedDate1 = getDate(date1);
const parsedDate2 = getDate(date2);
if (!parsedDate1 && !parsedDate2) return true;
if (!parsedDate1 || !parsedDate2) return false;
return isEqual(parsedDate1, parsedDate2);
};
/**
* @description Checks if a string matches date format YYYY-MM-DD
* @param {string} date - Date string to check
* @returns {boolean} True if string matches date format
* @example
* isInDateFormat("2024-01-01") // returns true
*/
export const isInDateFormat = (date: string): boolean => {
const datePattern = /^\d{4}-\d{2}-\d{2}$/;
return datePattern.test(date);
};
/**
* @description Converts date string to ISO format
* @param {string} dateString - Date string to convert
* @returns {string | undefined} ISO date string
* @example
* convertToISODateString("2024-01-01") // returns "2024-01-01T00:00:00.000Z"
*/
export const convertToISODateString = (dateString: string | undefined): string | undefined => {
if (!dateString) return dateString;
const date = new Date(dateString);
return date.toISOString();
};
/**
* @description Converts date string to epoch timestamp
* @param {string} dateString - Date string to convert
* @returns {number | undefined} Epoch timestamp
* @example
* convertToEpoch("2024-01-01") // returns 1704067200000
*/
export const convertToEpoch = (dateString: string | undefined): number | undefined => {
if (!dateString) return undefined;
const date = new Date(dateString);
return date.getTime();
};
/**
* @description Gets current date time in ISO format
* @returns {string} Current date time in ISO format
* @example
* getCurrentDateTimeInISO() // returns "2024-01-01T12:00:00.000Z"
*/
export const getCurrentDateTimeInISO = (): string => {
const date = new Date();
return date.toISOString();
};
/**
* @description Converts hours and minutes to total minutes
* @param {number} hours - Number of hours
* @param {number} minutes - Number of minutes
* @returns {number} Total minutes
* @example
* convertHoursMinutesToMinutes(2, 30) // returns 150
*/
export const convertHoursMinutesToMinutes = (hours: number, minutes: number): number => hours * 60 + minutes;
/**
* @description Converts total minutes to hours and minutes
* @param {number} mins - Total minutes
* @returns {{ hours: number; minutes: number }} Hours and minutes
* @example
* convertMinutesToHoursAndMinutes(150) // returns { hours: 2, minutes: 30 }
*/
export const convertMinutesToHoursAndMinutes = (mins: number): { hours: number; minutes: number } => {
const hours = Math.floor(mins / 60);
const minutes = Math.floor(mins % 60);
return { hours, minutes };
};
/**
* @description Converts minutes to hours and minutes string
* @param {number} totalMinutes - Total minutes
* @returns {string} Formatted string (e.g., "2h 30m")
* @example
* convertMinutesToHoursMinutesString(150) // returns "2h 30m"
*/
export const convertMinutesToHoursMinutesString = (totalMinutes: number): string => {
const { hours, minutes } = convertMinutesToHoursAndMinutes(totalMinutes);
return `${hours ? `${hours}h ` : ``}${minutes ? `${minutes}m ` : ``}`;
};
/**
* @description Calculates read time in seconds from word count
* @param {number} wordsCount - Number of words
* @returns {number} Read time in seconds
* @example
* getReadTimeFromWordsCount(400) // returns 120
*/
export const getReadTimeFromWordsCount = (wordsCount: number): number => {
const wordsPerMinute = 200;
const minutes = wordsCount / wordsPerMinute;
return minutes * 60;
};
/**
* @description Generates array of dates between start and end dates
* @param {string | Date} startDate - Start date
* @param {string | Date} endDate - End date
* @returns {Array<{ date: string }>} Array of dates
* @example
* generateDateArray("2024-01-01", "2024-01-03")
* // returns [{ date: "2024-01-02" }, { date: "2024-01-03" }]
*/
export const generateDateArray = (startDate: string | Date, endDate: string | Date): Array<{ date: string }> => {
const start = new Date(startDate);
const end = new Date(endDate);
end.setDate(end.getDate() + 1);
const dateArray = [];
while (start <= end) {
start.setDate(start.getDate() + 1);
dateArray.push({
date: new Date(start).toISOString().split("T")[0],
});
}
return dateArray;
};