155 lines
5.4 KiB
TypeScript
155 lines
5.4 KiB
TypeScript
/**
|
|
* Format the date/time.
|
|
* If the date is today, show the time. Otherwise, show the date in "day month year" format.
|
|
* @param dateString The date string to format
|
|
* @returns Formatted date/time string
|
|
*/
|
|
export const utilityFormatDate = (dateString: string): string => {
|
|
const date = new Date(dateString);
|
|
const today = new Date();
|
|
|
|
// save today date at midnight for comparison
|
|
const todayMidnight = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
|
|
// check if the date is today
|
|
if (
|
|
date >= todayMidnight &&
|
|
date < new Date(todayMidnight.getTime() + 86400000 /* milliseconds in a day sheeesh */)
|
|
) {
|
|
// show time if today
|
|
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
} else {
|
|
// "day month year" format
|
|
return date.toLocaleDateString(undefined, { day: "numeric", month: "long", year: "numeric" });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Format bytes into human-readable size
|
|
* @param bytes Number of bytes to format
|
|
* @returns Formatted size string
|
|
*/
|
|
export const utilityFormatSize = (bytes: number): string => {
|
|
if (bytes === 0) return "0 Bytes";
|
|
|
|
const k = 1024;
|
|
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
|
// tf is pow
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
};
|
|
|
|
/**
|
|
* Convert a formatted size string back to bytes
|
|
* @param formattedSize Formatted size string (must be in the same format produced by `utilityFormatSize()`)
|
|
* @returns Number of bytes
|
|
*/
|
|
export const utilityUnformatSize = (formattedSize: string): number => {
|
|
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
const [value, unit] = formattedSize.split(" ");
|
|
const index = sizes.indexOf(unit);
|
|
return parseFloat(value) * Math.pow(1024, index);
|
|
};
|
|
|
|
/**
|
|
* Extract filename stem and extension from a file path
|
|
* @param filePath The file path to parse
|
|
* @returns Object containing `fileStem` and `fileExtension`. `fileStem` is the filename without the extension, and `fileExtension` is the extension in lowercase (or an empty string if there is no extension).
|
|
* @throws Error if the file path is invalid
|
|
*/
|
|
export const utilityExtractFilename = (filePath: string): { fileStem: string; fileExtension: string } | null => {
|
|
const lastSlashIndex = filePath.lastIndexOf("/");
|
|
const lastDotIndex = filePath.lastIndexOf(".");
|
|
|
|
// check if the last slash comes before the last dot
|
|
if (lastDotIndex !== -1 && lastSlashIndex > lastDotIndex) {
|
|
console.error("Invalid file path: the last slash comes before the last dot");
|
|
throw new Error("Invalid file path: the last slash comes before the last dot");
|
|
}
|
|
|
|
// check if the file path ends with a slash
|
|
if (lastSlashIndex === filePath.length - 1) {
|
|
console.error("Invalid file path: the file path ends with a slash");
|
|
throw new Error("Invalid file path: the file path ends with a slash");
|
|
}
|
|
|
|
let fileStem: string, fileExtension: string;
|
|
|
|
if (lastDotIndex === -1 || lastDotIndex < lastSlashIndex) {
|
|
// if there's no dot after the last slash, the whole name is the fileStem
|
|
fileStem = filePath.substring(lastSlashIndex + 1);
|
|
fileExtension = "";
|
|
} else {
|
|
fileStem = filePath.substring(lastSlashIndex + 1, lastDotIndex);
|
|
fileExtension = filePath.substring(lastDotIndex + 1).toLowerCase();
|
|
}
|
|
|
|
return { fileStem: fileStem, fileExtension: fileExtension };
|
|
};
|
|
|
|
/**
|
|
* Clamps a value to a lower and upper bound
|
|
* @param value Value to clamp
|
|
* @param min Lower bound, defaults to negative `Number.MAX_VALUE`
|
|
* @param max Upper bound, defaults to `Number.MAX_VALUE`
|
|
* @returns Clamped value
|
|
*/
|
|
export function clampNumber(value: number, min: number = -Number.MAX_VALUE, max: number = Number.MAX_VALUE): number {
|
|
if (isNaN(value)) {
|
|
// if (CONSTANTS.isDevBranch) throw new Error("NaN passed into clampNumber()");
|
|
return min;
|
|
}
|
|
return Math.max(Math.min(value, max), min);
|
|
}
|
|
|
|
/**
|
|
* Clamps a value to an integer within a lower and upper bound
|
|
* @param value Value to clamp
|
|
* @param min Lower bound, defaults to negative `Number.MAX_SAFE_INTEGER`
|
|
* @param max Upper bound, defaults to `Number.MAX_SAFE_INTEGER`
|
|
* @returns Clamped integer value
|
|
*/
|
|
export function clampInteger(
|
|
value: number,
|
|
min: number = -Number.MAX_SAFE_INTEGER,
|
|
max: number = Number.MAX_SAFE_INTEGER,
|
|
): number {
|
|
if (isNaN(value)) {
|
|
// if (CONSTANTS.isDevBranch) throw new Error("NaN passed into clampInteger()");
|
|
return min;
|
|
}
|
|
return Math.round(Math.max(Math.min(value, max), min));
|
|
}
|
|
|
|
/**
|
|
* Checks that a variable is a valid number
|
|
* A valid number must be of type "number" and cannot be NaN
|
|
* @param n The number to check
|
|
* @returns True if n is a valid number, false otherwise
|
|
*/
|
|
export function isValidNumber(n: number): boolean {
|
|
return typeof n === "number" && !isNaN(n);
|
|
}
|
|
|
|
/**
|
|
* Generate a random number between min and max (inclusive)
|
|
* @param min Minimum value (inclusive)
|
|
* @param max Maximum value (inclusive)
|
|
* @returns Random number between min and max
|
|
*/
|
|
export function randomNumber(min: number, max: number): number {
|
|
if (min > max) throw new Error("min cannot be greater than max");
|
|
return Math.random() * (max - min) + min;
|
|
}
|
|
|
|
/**
|
|
* Generate a random integer between min and max (inclusive)
|
|
* @param min Minimum value (inclusive)
|
|
* @param max Maximum value (inclusive)
|
|
* @returns Random integer between min and max
|
|
*/
|
|
export function randomInteger(min: number, max: number): number {
|
|
return Math.floor(randomNumber(min, max + 1));
|
|
}
|