@airia-in/run-app-data-format
Version:
Shared data formatting library for Airia fitness platform
334 lines (333 loc) • 11.1 kB
JavaScript
;
/**
* Data Format Library
* Centralized formatting functions for time, distance, pace, HR, and other fitness metrics
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.msToMinPerKm = msToMinPerKm;
exports.mToKm = mToKm;
exports.formatDuration = formatDuration;
exports.formatHeartRate = formatHeartRate;
exports.getHeartRateZone = getHeartRateZone;
exports.formatSpeed = formatSpeed;
exports.formatElevation = formatElevation;
exports.formatPower = formatPower;
exports.formatCadence = formatCadence;
exports.formatTemperature = formatTemperature;
exports.formatDate = formatDate;
exports.formatCalories = formatCalories;
exports.convertPace = convertPace;
/**
* Converts speed from meters/second to minutes/km.
* @param ms Speed in meters/second
* @returns Pace in minutes/km (as a string "mm:ss")
*/
function msToMinPerKm(ms) {
if (typeof ms === "string") {
ms = parseFloat(ms);
}
if (ms <= 0 || isNaN(ms))
return "-";
const pace = 1000 / (60 * ms); // minutes per km
const minutes = Math.floor(pace);
const seconds = Math.round((pace - minutes) * 60);
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
}
/**
* Converts distance from meters to kilometers or meters based on value.
* @param meters Distance in meters
* @returns Formatted distance string with unit
*/
function mToKm(meters) {
if (typeof meters === "string") {
meters = parseFloat(meters);
}
if (meters <= 0 || isNaN(meters))
return "-";
if (meters >= 1000) {
return `${(meters / 1000).toFixed(1)} km`;
}
return `${Math.round(meters)} m`;
}
/**
* Formats duration from seconds to various time formats.
* @param seconds Duration in seconds
* @param format Format type: 'hh:mm:ss', 'mm:ss', 'humanReadable'
* @returns Formatted duration string
*/
function formatDuration(seconds, format = 'hh:mm:ss') {
if (typeof seconds === "string") {
seconds = parseFloat(seconds);
}
if (seconds <= 0 || isNaN(seconds))
return "-";
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.round(seconds % 60);
switch (format) {
case 'mm:ss':
const totalMinutes = Math.floor(seconds / 60);
return `${totalMinutes}:${secs.toString().padStart(2, '0')}`;
case 'humanReadable':
const parts = [];
if (hours > 0)
parts.push(`${hours}h`);
if (minutes > 0)
parts.push(`${minutes}m`);
if (secs > 0 || parts.length === 0)
parts.push(`${secs}s`);
return parts.join(' ');
case 'hh:mm:ss':
default:
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
}
/**
* Formats heart rate value.
* @param hr Heart rate value
* @param includeUnit Whether to include 'bpm' unit
* @returns Formatted heart rate string
*/
function formatHeartRate(hr, includeUnit = true) {
if (typeof hr === "string") {
hr = parseFloat(hr);
}
if (hr <= 0 || isNaN(hr))
return "-";
const rounded = Math.round(hr);
return includeUnit ? `${rounded} bpm` : `${rounded}`;
}
/**
* Calculates and formats heart rate zones based on max HR.
* @param currentHR Current heart rate
* @param maxHR Maximum heart rate (if not provided, uses 220 - age)
* @param age User's age (used if maxHR not provided)
* @returns Object with zone information
*/
function getHeartRateZone(currentHR, maxHR, age) {
// Calculate max HR if not provided
if (!maxHR && age) {
maxHR = 220 - age;
}
if (!maxHR || currentHR <= 0) {
return {
zone: 0,
zoneName: 'Invalid',
percentage: 0,
description: 'Invalid heart rate data'
};
}
const percentage = (currentHR / maxHR) * 100;
if (percentage < 50) {
return { zone: 0, zoneName: 'Rest', percentage, description: 'Very light activity' };
}
else if (percentage < 60) {
return { zone: 1, zoneName: 'Warm-up', percentage, description: 'Light activity' };
}
else if (percentage < 70) {
return { zone: 2, zoneName: 'Fat Burn', percentage, description: 'Moderate activity' };
}
else if (percentage < 80) {
return { zone: 3, zoneName: 'Cardio', percentage, description: 'Hard activity' };
}
else if (percentage < 90) {
return { zone: 4, zoneName: 'Peak', percentage, description: 'Very hard activity' };
}
else {
return { zone: 5, zoneName: 'Maximum', percentage, description: 'Maximum effort' };
}
}
/**
* Formats speed value to km/h or m/s.
* @param metersPerSecond Speed in m/s
* @param unit Output unit: 'kmh' or 'ms'
* @returns Formatted speed string with unit
*/
function formatSpeed(metersPerSecond, unit = 'kmh') {
if (typeof metersPerSecond === "string") {
metersPerSecond = parseFloat(metersPerSecond);
}
if (metersPerSecond <= 0 || isNaN(metersPerSecond))
return "-";
if (unit === 'kmh') {
const kmh = metersPerSecond * 3.6;
return `${kmh.toFixed(1)} km/h`;
}
return `${metersPerSecond.toFixed(1)} m/s`;
}
/**
* Formats elevation/altitude values.
* @param meters Elevation in meters
* @param unit Output unit: 'm' or 'ft'
* @returns Formatted elevation string with unit
*/
function formatElevation(meters, unit = 'm') {
if (typeof meters === "string") {
meters = parseFloat(meters);
}
if (isNaN(meters))
return "-";
if (unit === 'ft') {
const feet = meters * 3.28084;
return `${Math.round(feet)} ft`;
}
return `${Math.round(meters)} m`;
}
/**
* Formats power output (for cycling).
* @param watts Power in watts
* @param includeUnit Whether to include 'W' unit
* @returns Formatted power string
*/
function formatPower(watts, includeUnit = true) {
if (typeof watts === "string") {
watts = parseFloat(watts);
}
if (watts < 0 || isNaN(watts))
return "-";
const rounded = Math.round(watts);
return includeUnit ? `${rounded} W` : `${rounded}`;
}
/**
* Formats cadence (steps/min for running, rpm for cycling).
* @param cadence Cadence value
* @param type Activity type: 'running' or 'cycling'
* @returns Formatted cadence string with appropriate unit
*/
function formatCadence(cadence, type = 'running') {
if (typeof cadence === "string") {
cadence = parseFloat(cadence);
}
if (cadence <= 0 || isNaN(cadence))
return "-";
const rounded = Math.round(cadence);
const unit = type === 'running' ? 'spm' : 'rpm';
return `${rounded} ${unit}`;
}
/**
* Formats temperature values.
* @param celsius Temperature in Celsius
* @param unit Output unit: 'C' or 'F'
* @returns Formatted temperature string with unit
*/
function formatTemperature(celsius, unit = 'C') {
if (typeof celsius === "string") {
celsius = parseFloat(celsius);
}
if (isNaN(celsius))
return "-";
if (unit === 'F') {
const fahrenheit = (celsius * 9 / 5) + 32;
return `${Math.round(fahrenheit)}°F`;
}
return `${Math.round(celsius)}°C`;
}
/**
* Formats date to various formats.
* @param date Date string or Date object
* @param format Format type: 'short', 'long', 'iso', 'relative'
* @returns Formatted date string
*/
function formatDate(date, format = 'short') {
if (!date)
return '-';
try {
const dateObj = typeof date === 'string' ? new Date(date) : date;
if (isNaN(dateObj.getTime()))
return '-';
switch (format) {
case 'short':
return dateObj.toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: '2-digit'
});
case 'long':
return dateObj.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
case 'iso':
return dateObj.toISOString();
case 'relative':
const now = new Date();
const diffMs = now.getTime() - dateObj.getTime();
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffDays === 0)
return 'Today';
if (diffDays === 1)
return 'Yesterday';
if (diffDays < 7)
return `${diffDays} days ago`;
if (diffDays < 30)
return `${Math.floor(diffDays / 7)} weeks ago`;
if (diffDays < 365)
return `${Math.floor(diffDays / 30)} months ago`;
return `${Math.floor(diffDays / 365)} years ago`;
default:
return dateObj.toLocaleDateString();
}
}
catch (error) {
return '-';
}
}
/**
* Formats calories burned.
* @param calories Calories value
* @param includeUnit Whether to include 'cal' unit
* @returns Formatted calories string
*/
function formatCalories(calories, includeUnit = true) {
if (typeof calories === "string") {
calories = parseFloat(calories);
}
if (calories < 0 || isNaN(calories))
return "-";
const rounded = Math.round(calories);
return includeUnit ? `${rounded} cal` : `${rounded}`;
}
/**
* Converts and formats pace between different units.
* @param value Pace value
* @param fromUnit Input unit: 'min/km', 'min/mi', 's/m'
* @param toUnit Output unit: 'min/km', 'min/mi', 's/m'
* @returns Formatted pace string
*/
function convertPace(value, fromUnit, toUnit) {
// First convert to seconds per meter (common unit)
let secondsPerMeter;
switch (fromUnit) {
case 'min/km':
secondsPerMeter = (value * 60) / 1000;
break;
case 'min/mi':
secondsPerMeter = (value * 60) / 1609.344;
break;
case 's/m':
secondsPerMeter = value;
break;
}
// Then convert to target unit
switch (toUnit) {
case "min/km": {
const minPerKm = (secondsPerMeter * 1000) / 60;
const minutes = Math.floor(minPerKm);
const seconds = Math.round((minPerKm - minutes) * 60);
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
}
case "min/mi": {
const minPerMi = (secondsPerMeter * 1609.344) / 60;
const minutes = Math.floor(minPerMi);
const seconds = Math.round((minPerMi - minutes) * 60);
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
}
case "s/m":
return secondsPerMeter.toFixed(2);
}
}