diff --git a/README.md b/README.md
index e8f3cf8..67171b6 100644
--- a/README.md
+++ b/README.md
@@ -125,6 +125,7 @@ const count = stringzy.analyze.wordCount('Hello world'); // 2
- [capitalize](#capitalize) - Capitalizes the first letter of each word
- [formatNumber](#formatnumber) - Formats a number string with thousand separators
- [formatPhone](#formatphone) - Formats a phone number string to standard format
+- [formatDuration](#formatduration) - Converts a duration in seconds or milliseconds into a human-readable string
## 📋 API Reference
@@ -1267,6 +1268,27 @@ formatPhone('11234567890', 'international'); // '+1 (123) 456-7890'
| phone | string | required | The phone number string to format |
| format | string | 'us' | Format type: 'us' or 'international' |
+#### `formatDuration(input, options)`
+
+Converts a duration in seconds or milliseconds into a human-readable string.
+
+```javascript
+formatDuration(60); // "1m"
+formatDuration(61); // "1m 1s"
+formatDuration(3661); // "1h 1m 1s"
+formatDuration(7325); // "2h 2m 5s"
+formatDuration(1234567, { unit: 'milliseconds', includeMs: true }); // "20m 34s 567ms"
+```
+
+| Parameter | Type | Default | Description |
+| --------- | ------ | ----------- | ----------------------------------------------------- |
+| input | number | required | The duration in seconds or milliseconds |
+| options | object | `{}` | Optional configuration object |
+| - unit | string | 'seconds' | Input unit: 'seconds' or 'milliseconds' |
+| - format | string | 'short' | Output format: 'short', 'medium', or 'long' |
+| - includeMs | boolean | false | Whether to include milliseconds in output |
+| - delimiter | string | ' ' | The delimiter between time units |
+
## 🔧 Usage Patterns
### Individual Function Imports
diff --git a/src/formatting/duration.ts b/src/formatting/duration.ts
new file mode 100644
index 0000000..22bcdaa
--- /dev/null
+++ b/src/formatting/duration.ts
@@ -0,0 +1,107 @@
+/**
+ * Converts a duration in seconds or milliseconds into a human-readable string.
+ * Intelligently displays hours, minutes, seconds, and optionally milliseconds,
+ * while skipping zero-value units unless the entire duration is zero.
+ *
+ * @param {number} input - The duration in seconds or milliseconds.
+ * @param {object} [options] - Configuration options.
+ * @param {string} [options.unit='seconds'] - The input unit: 'seconds' or 'milliseconds'.
+ * @param {string} [options.format='short'] - Output format: 'short', 'medium', or 'long'.
+ * @param {boolean} [options.includeMs=false] - Whether to include milliseconds in the output.
+ * @param {string} [options.delimiter=' '] - The delimiter between time units.
+ * @returns {string} The formatted duration string.
+ * @throws {TypeError} If input is not a number or is negative.
+ *
+ * @example
+ * formatDuration(60); // "1m"
+ * formatDuration(61); // "1m 1s"
+ * formatDuration(3661); // "1h 1m 1s"
+ * formatDuration(1234567, { unit: 'milliseconds', includeMs: true }); // "20m 34s 567ms"
+ * formatDuration(3600, { format: 'long' }); // "1 hour"
+ */
+export function formatDuration(input: number, options?: {
+ unit?: 'seconds' | 'milliseconds';
+ format?: 'short' | 'medium' | 'long';
+ includeMs?: boolean;
+ delimiter?: string;
+}): string {
+ // Validate input
+ if (typeof input !== 'number' || isNaN(input)) {
+ throw new TypeError('Input must be a number');
+ }
+ if (input < 0) {
+ throw new TypeError('Input must be non-negative');
+ }
+
+ // Default options
+ const opts = {
+ unit: options?.unit || 'seconds',
+ format: options?.format || 'short',
+ includeMs: options?.includeMs || false,
+ delimiter: options?.delimiter || ' '
+ };
+
+ // Convert to milliseconds
+ const totalMs = opts.unit === 'seconds' ? input * 1000 : input;
+
+ // Handle zero case
+ if (totalMs === 0) {
+ return '0s';
+ }
+
+ // Calculate time components
+ let remaining = Math.floor(totalMs);
+ const ms = remaining % 1000;
+ remaining = Math.floor(remaining / 1000);
+ const seconds = remaining % 60;
+ remaining = Math.floor(remaining / 60);
+ const minutes = remaining % 60;
+ const hours = Math.floor(remaining / 60);
+
+ // Build the output parts
+ const parts: string[] = [];
+
+ // Add non-zero units
+ if (hours > 0) {
+ parts.push(formatTimeUnit(hours, 'h', opts.format));
+ }
+
+ if (minutes > 0) {
+ parts.push(formatTimeUnit(minutes, 'm', opts.format));
+ }
+
+ if (seconds > 0 || (parts.length === 0 && !opts.includeMs)) {
+ parts.push(formatTimeUnit(seconds, 's', opts.format));
+ }
+
+ if (opts.includeMs && ms > 0) {
+ parts.push(formatTimeUnit(ms, 'ms', opts.format));
+ }
+
+ return parts.join(opts.delimiter);
+}
+
+/**
+ * Formats a single time unit according to the specified format.
+ * @private
+ */
+function formatTimeUnit(value: number, unit: string, format: string): string {
+ switch (format) {
+ case 'long':
+ const unitNames: Record = {
+ 'h': ['hour', 'hours'],
+ 'm': ['minute', 'minutes'],
+ 's': ['second', 'seconds'],
+ 'ms': ['millisecond', 'milliseconds']
+ };
+ const [singular, plural] = unitNames[unit];
+ return `${value} ${value === 1 ? singular : plural}`;
+
+ case 'medium':
+ const paddedValue = unit === 'h' ? value : value.toString().padStart(2, '0');
+ return `${paddedValue}${unit}`;
+
+ default: // 'short'
+ return `${value}${unit}`;
+ }
+}
\ No newline at end of file
diff --git a/src/formatting/index.ts b/src/formatting/index.ts
index 7b17cd4..5601156 100644
--- a/src/formatting/index.ts
+++ b/src/formatting/index.ts
@@ -1,13 +1,16 @@
export { capitalize } from './capitalize';
export { formatNumber } from './number';
export { formatPhone } from './phone';
+export { formatDuration } from './duration';
import { capitalize } from './capitalize';
import { formatNumber } from './number';
import { formatPhone } from './phone';
+import { formatDuration } from './duration';
export const formatting = {
capitalize,
formatNumber,
formatPhone,
+ formatDuration,
};
diff --git a/src/tests/formatting/duration.test.ts b/src/tests/formatting/duration.test.ts
new file mode 100644
index 0000000..92c9180
--- /dev/null
+++ b/src/tests/formatting/duration.test.ts
@@ -0,0 +1,72 @@
+import { describe, it } from 'node:test';
+import assert from 'node:assert';
+import { formatDuration } from '../../formatting/duration';
+
+describe('formatDuration', () => {
+ describe('basic functionality', () => {
+ it('formats exact minutes', () => {
+ assert.strictEqual(formatDuration(60), '1m');
+ });
+
+ it('formats minutes and seconds', () => {
+ assert.strictEqual(formatDuration(61), '1m 1s');
+ });
+
+ it('formats hours, minutes and seconds', () => {
+ assert.strictEqual(formatDuration(3661), '1h 1m 1s');
+ });
+
+ it('formats exact hours', () => {
+ assert.strictEqual(formatDuration(3600), '1h');
+ });
+
+ it('formats complex durations', () => {
+ assert.strictEqual(formatDuration(7325), '2h 2m 5s');
+ });
+
+ it('formats milliseconds when specified', () => {
+ assert.strictEqual(
+ formatDuration(1234567, { unit: 'milliseconds', includeMs: true }),
+ '20m 34s 567ms'
+ );
+ });
+
+ it('formats zero as zero seconds', () => {
+ assert.strictEqual(formatDuration(0), '0s');
+ });
+
+ it('handles large values', () => {
+ assert.strictEqual(formatDuration(86400), '24h');
+ });
+ });
+
+ describe('format options', () => {
+ it('formats with medium format', () => {
+ assert.strictEqual(formatDuration(3661, { format: 'medium' }), '1h 01m 01s');
+ });
+
+ it('formats with long format', () => {
+ assert.strictEqual(formatDuration(3661, { format: 'long' }), '1 hour 1 minute 1 second');
+ });
+
+ it('formats with custom delimiter', () => {
+ assert.strictEqual(formatDuration(3661, { delimiter: ':' }), '1h:1m:1s');
+ });
+ });
+
+ describe('error handling', () => {
+ it('throws on non-numeric input', () => {
+ assert.throws(() => formatDuration('60' as any), {
+ name: 'TypeError',
+ message: 'Input must be a number'
+ });
+ });
+
+ it('throws on negative input', () => {
+ assert.throws(() => formatDuration(-60), {
+ name: 'TypeError',
+ message: 'Input must be non-negative'
+ });
+ });
+ });
+});
\ No newline at end of file