Files
roast_command_center/shared/time.js
T
snowspeeder 824a964f9d
Test and deploy / test-and-deploy (push) Successful in 2m28s
Rebuild printable roast plans and remove machine-based adjustments
2026-09-05 14:56:11 -04:00

61 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Browser-safe. No node:* imports, no DOM. Imported by both server and browser.
const DASHES = /[‒–—−-]/; // -, –, —, minus sign
// Matches only the non-ASCII members of DASHES — used to fold a leading minus/en/em dash down to
// an ASCII hyphen before sign detection, so a string like "−0:30" (U+2212, the character every
// signed number in this app's own reference data — SYMPTOM_FIXES, cultivar profiles — is written
// with) parses instead of silently returning null.
const NON_ASCII_DASHES = /[‒–—−]/g;
/** "8:45" -> 525, "-0:20" -> -20, "+0:15" -> 15, "0" -> 0, "90" -> 90 (bare seconds). null on junk. */
export function parseDuration(str) {
if (str === null || str === undefined) return null;
const s = String(str).trim().replace(NON_ASCII_DASHES, "-");
if (s === "") return null;
const sign = s.startsWith("-") ? -1 : 1;
const body = s.replace(/^[+-]/, "");
const m = body.match(/^(\d+):(\d{1,2})$/);
if (m) {
const seconds = Number(m[1]) * 60 + Number(m[2]);
return Number(m[2]) < 60 && Number.isSafeInteger(seconds) ? sign * seconds : null;
}
if (/^\d+(\.\d+)?$/.test(body)) {
const seconds = Math.round(Number(body));
return Number.isSafeInteger(seconds) ? sign * seconds : null;
}
return null;
}
/** "7:30-9:00" / "7:30–9:00" -> midpoint seconds. Single value passes through parseDuration. */
export function parseRangeMidpoint(str) {
if (str === null || str === undefined) return null;
const s = String(str).trim();
if (s === "") return null;
const parts = s.split(DASHES).map((p) => p.trim()).filter(Boolean);
if (parts.length >= 2) {
const a = parseDuration(parts[0]);
const b = parseDuration(parts[parts.length - 1]);
if (a === null || b === null) return null;
return Math.round((a + b) / 2);
}
return parseDuration(s);
}
/** 525 -> "8:45". Negative seconds -> "-0:20". null -> "". */
export function formatDuration(totalSeconds) {
if (totalSeconds === null || totalSeconds === undefined || Number.isNaN(totalSeconds)) return "";
const sign = totalSeconds < 0 ? "-" : "";
const abs = Math.abs(Math.round(totalSeconds));
const m = Math.floor(abs / 60);
const s = abs % 60;
return `${sign}${m}:${String(s).padStart(2, "0")}`;
}
/** Same as formatDuration but always carries an explicit sign, and "0" for zero. */
export function formatSigned(totalSeconds) {
if (totalSeconds === null || totalSeconds === undefined || Number.isNaN(totalSeconds)) return "";
if (totalSeconds === 0) return "0";
const out = formatDuration(Math.abs(totalSeconds));
return totalSeconds < 0 ? `-${out}` : `+${out}`;
}