Skip to content

feat(lib): add booking duration utility functions#5

Open
AdamWalkerQodo wants to merge 1 commit into
walkers4u:mainfrom
AdamWalkerQodo:feat/booking-duration-utils
Open

feat(lib): add booking duration utility functions#5
AdamWalkerQodo wants to merge 1 commit into
walkers4u:mainfrom
AdamWalkerQodo:feat/booking-duration-utils

Conversation

@AdamWalkerQodo

Copy link
Copy Markdown

Summary

  • Adds packages/lib/booking-duration-utils.ts with helpers for working with event durations
  • convertDuration(value, from, to) — converts between "minutes" and "hours"
  • isValidDuration(minutes) — validates against MIN_EVENT_DURATION_MINUTES / MAX_EVENT_DURATION_MINUTES
  • formatDuration(minutes) — human-readable format e.g. "1h 30m", "45m", "2h"

Changed files

  • packages/lib/booking-duration-utils.ts (new)

Test plan

  • convertDuration(90, "minutes", "hours") returns 1.5
  • convertDuration(2, "hours", "minutes") returns 120
  • isValidDuration(0) returns false, isValidDuration(30) returns true
  • formatDuration(90) returns "1h 30m", formatDuration(60) returns "1h", formatDuration(45) returns "45m"

🤖 Generated with Claude Code

Adds convertDuration, isValidDuration, and formatDuration helpers
for working with event durations in minutes and hours.
@walkers4u walkers4u marked this pull request as ready for review April 29, 2026 10:58
@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Add booking duration utility functions

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds utility functions for event duration conversions and validation
• Implements convertDuration() for minutes/hours conversion
• Implements isValidDuration() against configured min/max bounds
• Implements formatDuration() for human-readable duration formatting
Diagram
flowchart LR
  Input["Duration Input<br/>minutes or hours"]
  Convert["convertDuration()"]
  Validate["isValidDuration()"]
  Format["formatDuration()"]
  Output["Formatted String<br/>e.g. 1h 30m"]
  Input --> Convert
  Input --> Validate
  Input --> Format
  Format --> Output
Loading

Grey Divider

File Changes

1. packages/lib/booking-duration-utils.ts ✨ Enhancement +31/-0

New booking duration utility functions module

• Exports DurationUnit type for "minutes" or "hours"
• Adds minutesToHours() and hoursToMinutes() conversion helpers
• Adds convertDuration() to convert between duration units
• Adds isValidDuration() to validate against min/max constants
• Adds formatDuration() to generate human-readable duration strings

packages/lib/booking-duration-utils.ts


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Apr 29, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0)

Grey Divider


Action required

1. Duration validation allows floats 🐞 Bug ≡ Correctness
Description
isValidDuration() returns true for fractional minutes (e.g., 30.5) as long as the value is finite
and within MIN/MAX. Elsewhere, event length is validated as an integer, so this helper can
incorrectly approve values that will be rejected or mishandled downstream.
Code

packages/lib/booking-duration-utils.ts[R18-23]

+export function isValidDuration(durationMinutes: number): boolean {
+  return (
+    Number.isFinite(durationMinutes) &&
+    durationMinutes >= MIN_EVENT_DURATION_MINUTES &&
+    durationMinutes <= MAX_EVENT_DURATION_MINUTES
+  );
Evidence
The new helper only checks finiteness and range, but core event-type schemas require an integer
length in minutes, so the helper’s contract is looser than the system’s contract.

packages/lib/booking-duration-utils.ts[18-23]
apps/api/v1/lib/validations/event-type.ts[82-97]
packages/features/eventtypes/lib/schemas.ts[87-104]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`isValidDuration(durationMinutes)` currently accepts fractional values (e.g., `30.5`) because it only checks `Number.isFinite` and min/max range.

### Issue Context
Other parts of the codebase validate event `length` as an integer (`z.number().int()`), so allowing floats here can create inconsistency if this helper is used for validation/gating.

### Fix Focus Areas
- packages/lib/booking-duration-utils.ts[18-23]

### Suggested change
Add an integer check:
- `Number.isInteger(durationMinutes)` (recommended)
- or reject non-integer values explicitly before range checks.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. formatDuration mishandles NaN 🐞 Bug ≡ Correctness
Description
formatDuration() has no guard for non-finite or non-positive values, producing outputs like
"NaNh"/"Infinityh" and formatting negative minutes as negative strings. This can leak invalid state
into user-visible strings and diverges from existing formatting behavior that clamps non-positive
inputs.
Code

packages/lib/booking-duration-utils.ts[R26-30]

+export function formatDuration(durationMinutes: number): string {
+  if (durationMinutes < 60) return `${durationMinutes}m`;
+  const hours = Math.floor(durationMinutes / 60);
+  const minutes = durationMinutes % 60;
+  return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
Evidence
With NaN/Infinity, the < 60 check doesn’t protect the code path that computes hours/minutes and
interpolates them into strings. An existing formatter in the repo sanitizes inputs (<= 0 => "0m")
and parses strings/undefined defensively, indicating a repo-level expectation of sanitization.

packages/lib/booking-duration-utils.ts[26-30]
companion/utils/formatters.ts[16-23]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`formatDuration(durationMinutes)` can output garbage strings for invalid inputs (e.g., `NaN` -> "NaNh", `Infinity` -> "Infinityh") and formats negatives directly.

### Issue Context
There is already a formatter in the repo that clamps `<= 0` to "0m" and generally sanitizes inputs.

### Fix Focus Areas
- packages/lib/booking-duration-utils.ts[26-30]

### Suggested change
At the start of `formatDuration`:
- Return a safe default for invalid inputs, e.g. `if (!Number.isFinite(durationMinutes) || durationMinutes <= 0) return "0m";`
- Optionally enforce integer minutes (e.g. `const mins = Math.floor(durationMinutes)` or require `Number.isInteger`).
Then format using `mins`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

3. Unused convertDuration parameter 🐞 Bug ⚙ Maintainability
Description
convertDuration(value, from, to) ignores to whenever from !== to, which makes the API misleading
(it suggests to affects behavior when it currently doesn’t). This increases maintenance risk if
additional units are added or callers pass unexpected combinations.
Code

packages/lib/booking-duration-utils.ts[R13-16]

+export function convertDuration(value: number, from: DurationUnit, to: DurationUnit): number {
+  if (from === to) return value;
+  return from === "minutes" ? minutesToHours(value) : hoursToMinutes(value);
+}
Evidence
In the conversion branch, the implementation chooses the conversion solely based on from; to is
not referenced, so it provides no safety/clarity beyond the from===to early return.

packages/lib/booking-duration-utils.ts[13-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`convertDuration(value, from, to)` does not use `to` when `from !== to`, making the signature misleading.

### Issue Context
With only two units this happens to work, but it’s easy to misread and becomes fragile if units expand.

### Fix Focus Areas
- packages/lib/booking-duration-utils.ts[13-16]

### Suggested change
Either:
- Implement explicit mapping using both `from` and `to` (e.g., `switch` on `${from}->${to}`), or
- Remove the `to` parameter and rename the function to reflect directionality, or
- Keep `to` but assert the expected pair and throw on impossible combinations.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +18 to +23
export function isValidDuration(durationMinutes: number): boolean {
return (
Number.isFinite(durationMinutes) &&
durationMinutes >= MIN_EVENT_DURATION_MINUTES &&
durationMinutes <= MAX_EVENT_DURATION_MINUTES
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Duration validation allows floats 🐞 Bug ≡ Correctness

isValidDuration() returns true for fractional minutes (e.g., 30.5) as long as the value is finite
and within MIN/MAX. Elsewhere, event length is validated as an integer, so this helper can
incorrectly approve values that will be rejected or mishandled downstream.
Agent Prompt
### Issue description
`isValidDuration(durationMinutes)` currently accepts fractional values (e.g., `30.5`) because it only checks `Number.isFinite` and min/max range.

### Issue Context
Other parts of the codebase validate event `length` as an integer (`z.number().int()`), so allowing floats here can create inconsistency if this helper is used for validation/gating.

### Fix Focus Areas
- packages/lib/booking-duration-utils.ts[18-23]

### Suggested change
Add an integer check:
- `Number.isInteger(durationMinutes)` (recommended)
- or reject non-integer values explicitly before range checks.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant