Skip to main content
Glama
pshempel

MCP Time Server Node

by pshempel
calculateDuration.ts8.1 kB
import { differenceInMilliseconds } from 'date-fns'; import { ValidationError, TimezoneError, DateParsingError } from '../adapters/mcp-sdk'; import { CacheTTL } from '../cache/timeCache'; import type { CalculateDurationParams, CalculateDurationResult } from '../types'; import { getConfig } from '../utils/config'; import { debug } from '../utils/debug'; import { parseTimeInput } from '../utils/parseTimeInput'; import { resolveTimezone as resolveTimezoneUtil } from '../utils/timezoneUtils'; import { validateTimezone, validateDateString } from '../utils/validation'; import { withCache } from '../utils/withCache'; const validUnits = ['auto', 'milliseconds', 'seconds', 'minutes', 'hours', 'days']; export function calculateDuration(params: CalculateDurationParams): CalculateDurationResult { debug.timing('calculateDuration called with params: %O', params); const { start_time, end_time } = params; // Validate string lengths first if (typeof start_time === 'string') { validateDateString(start_time, 'start_time'); } if (typeof end_time === 'string') { validateDateString(end_time, 'end_time'); } const config = getConfig(); const unit = validateUnit(params.unit); const timezone = resolveTimezoneUtil(params.timezone, config.defaultTimezone); // Use withCache wrapper instead of manual cache management return withCache( `duration_${start_time}_${end_time}_${unit}_${timezone}`, CacheTTL.CALCULATIONS, () => { // Validate timezone if (!validateTimezone(timezone)) { debug.error('Invalid timezone: %s', timezone); throw new TimezoneError(`Invalid timezone: ${timezone}`, timezone); } // Parse dates with proper error context const startDate = parseDateWithContext(start_time, timezone, 'start_time'); debug.timing('Parsed start date: %s', startDate.toISOString()); const endDate = parseDateWithContext(end_time, timezone, 'end_time'); debug.timing('Parsed end date: %s', endDate.toISOString()); // Calculate all duration values const values = calculateDurationValues(startDate, endDate); debug.timing('Duration in milliseconds: %d', values.milliseconds); // Format the result const formatted = formatDurationResult(values, unit); debug.timing('Formatted duration: %s', formatted); const result: CalculateDurationResult = { ...values, formatted, }; debug.timing('Returning result: %O', result); return result; } ); } /** * Time components for duration formatting */ interface TimeComponents { days: number; hours: number; minutes: number; seconds: number; } /** * Pluralize a time unit based on value */ export function pluralize(value: number, singular: string): string { return `${value} ${value === 1 ? singular : singular + 's'}`; } /** * Add a time unit to the parts array if value > 0 */ export function addTimeUnit(parts: string[], value: number, unit: string): void { if (value > 0) { parts.push(pluralize(value, unit)); } } /** * Calculate time components from total seconds */ export function calculateTimeComponents(totalSeconds: number): TimeComponents { const days = Math.floor(totalSeconds / 86400); const hours = Math.floor((totalSeconds % 86400) / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; return { days, hours, minutes, seconds }; } /** * Validate and resolve unit parameter */ export function validateUnit(unit: string | undefined): string { debug.validation('validateUnit called with: %s', unit); const resolved = unit ?? 'auto'; if (!validUnits.includes(resolved)) { debug.validation('Invalid unit detected: %s', resolved); debug.error('Invalid unit: %s', resolved); throw new ValidationError( `Invalid unit: ${resolved}. Must be one of: ${validUnits.join(', ')}`, { unit: resolved } ); } debug.validation('Resolved unit: %s', resolved); return resolved; } /** * Parse date with proper error context */ function parseDateWithContext(time: string, timezone: string, paramName: string): Date { debug.parse( 'parseDateWithContext called for %s: time=%s, timezone=%s', paramName, time, timezone ); try { const result = parseTimeParameter(time, timezone); debug.parse('%s parsed successfully: %s', paramName, result.toISOString()); return result; } catch (error) { debug.parse('Failed to parse %s: %s', paramName, error); debug.error('Failed to parse %s: %s', paramName, error); throw new DateParsingError(`Invalid ${paramName} format: ${time}`, { [paramName]: time }); } } /** * Parse time parameter with timezone awareness using the centralized parser */ export function parseTimeParameter(time: string, timezone: string): Date { debug.parse('parseTimeParameter called with: time=%s, timezone=%s', time, timezone); try { const result = parseTimeInput(time, timezone); debug.parse('parseTimeParameter returning: %s', result.date.toISOString()); return result.date; } catch (error) { debug.parse('Parse error: %s', error); debug.error('Parse error: %s', error); throw new DateParsingError(`Invalid date format: ${time}`, { time, timezone }); } } /** * Duration values interface */ interface DurationValues { milliseconds: number; seconds: number; minutes: number; hours: number; days: number; is_negative: boolean; } /** * Calculate all duration values from start and end dates */ export function calculateDurationValues(startDate: Date, endDate: Date): DurationValues { debug.timing( 'calculateDurationValues called with: start=%s, end=%s', startDate.toISOString(), endDate.toISOString() ); const milliseconds = differenceInMilliseconds(endDate, startDate); const seconds = milliseconds / 1000; const minutes = seconds / 60; const hours = minutes / 60; const days = hours / 24; const is_negative = milliseconds < 0; const result = { milliseconds, seconds, minutes, hours, days, is_negative, }; debug.timing('Calculated duration values: %O', result); return result; } /** * Format duration result based on unit parameter */ export function formatDurationResult(values: DurationValues, unit: string): string { debug.timing('formatDurationResult called with: unit=%s, values=%O', unit, values); let formatted: string; if (unit !== 'auto' && unit !== 'milliseconds') { // Specific unit requested const value = unit === 'seconds' ? values.seconds : unit === 'minutes' ? values.minutes : unit === 'hours' ? values.hours : values.days; formatted = `${value} ${unit}`; } else if (unit === 'milliseconds') { formatted = `${values.milliseconds} milliseconds`; } else { // Auto format - use existing formatDuration helper formatted = formatDuration(values.milliseconds); } debug.timing('Formatted result: %s', formatted); return formatted; } /** * Format duration in human-readable format */ function formatDuration(milliseconds: number): string { debug.timing('formatDuration called with: %d ms', milliseconds); const abs = Math.abs(milliseconds); const negative = milliseconds < 0; if (abs === 0) { debug.timing('Zero duration, returning "0 seconds"'); return '0 seconds'; } const totalSeconds = Math.floor(abs / 1000); const components = calculateTimeComponents(totalSeconds); debug.timing('Time components: %O', components); const parts: string[] = []; addTimeUnit(parts, components.days, 'day'); addTimeUnit(parts, components.hours, 'hour'); addTimeUnit(parts, components.minutes, 'minute'); // Always include seconds if no other units if (components.seconds > 0 || parts.length === 0) { parts.push(pluralize(components.seconds, 'second')); } const formatted = parts.join(' '); const result = negative ? `-${formatted}` : formatted; debug.timing('Formatted duration: %s', result); return result; }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pshempel/mcp-time-server-node'

If you have feedback or need assistance with the MCP directory API, please join our Discord server