Skip to content

(test) Fix Duration #3900

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

eliatcodecov
Copy link
Contributor

Description

Fixes a duration issue for test analytics. Provides opportunity to test Seer.

@codecov-releaser
Copy link
Contributor

✅ Deploy preview for gazebo ready!

Previews expire after 1 month automatically.

Storybook

Commit Created Cloud Enterprise
02568fa Fri, 20 Jun 2025 16:57:03 GMT Cloud Enterprise

Copy link
Contributor

On it! We are reviewing the PR and will provide feedback shortly.

Copy link
Contributor

PR Description

This pull request refactors the formatTimeFromSeconds function to improve performance and reduce external dependencies. The original implementation relied on the date-fns library's intervalToDuration function, which was found to be less efficient for this specific use case. The goal is to provide a more performant and self-contained utility for formatting time durations from seconds.

Click to see more

Key Technical Changes

The primary technical change is the replacement of the date-fns intervalToDuration function with a manual calculation using basic arithmetic operations and pre-defined constants (SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE). This eliminates the overhead of the external library and allows for more direct control over the formatting logic. Additionally, the code now includes explicit handling for null, negative, and fractional second values, ensuring more robust behavior across a wider range of inputs. New test cases were added to cover these edge cases.

Architecture Decisions

The architectural decision was to move away from a library-dependent approach to a more self-contained and optimized implementation. This reduces the bundle size and improves performance. The use of constants for time units promotes readability and maintainability. The decision to handle edge cases directly within the function ensures consistent behavior without relying on external library quirks.

Dependencies and Interactions

This pull request removes the dependency on intervalToDuration from date-fns for the formatTimeFromSeconds function, reducing the overall dependency footprint. The function continues to use formatDistanceToNow, fromUnixTime, and parseISO from date-fns. The change primarily affects the internal implementation of formatTimeFromSeconds and should not impact other parts of the system that rely on the other date formatting functions.

Risk Considerations

The primary risk is the potential for subtle errors in the manual calculation logic. Thorough testing, including edge cases and boundary conditions, is crucial to ensure the new implementation is functionally equivalent to the original and handles all inputs correctly. There is also a risk of introducing performance regressions if the manual calculation is not optimized effectively, although this is unlikely given the simplicity of the operations. The change in null/undefined handling should be carefully reviewed to ensure it aligns with the intended behavior of the application.

Notable Implementation Details

The implementation uses Math.floor to truncate the results of the division operations, ensuring that only whole units of time are included in the formatted string. The code constructs an array of time parts and then joins them with spaces, providing a flexible way to build the final output string. The explicit checks for null, negative, and fractional values ensure that the function handles a wide range of inputs gracefully. The test suite includes tests for large float values, boundary conditions, and negative inputs to ensure the robustness of the implementation.

Comment on lines 17 to +20
export const formatTimeFromSeconds = (totalSeconds?: number | null) => {
if (totalSeconds == null || totalSeconds < 0) return 'N/A'
if (totalSeconds === 0) return '0s'
if (!totalSeconds) return 'N/A'
if (totalSeconds < 1) return '<1s'
Copy link
Contributor

Choose a reason for hiding this comment

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

The null check logic has changed behavior. Previously, !totalSeconds would catch both 0 and falsy values, but now totalSeconds == null only catches null and undefined. Consider being more explicit about the intended behavior. The current implementation returns 'N/A' for negative numbers but the previous version would have returned 'N/A' for any falsy value except 0.

Suggested change
export const formatTimeFromSeconds = (totalSeconds?: number | null) => {
if (totalSeconds == null || totalSeconds < 0) return 'N/A'
if (totalSeconds === 0) return '0s'
if (!totalSeconds) return 'N/A'
if (totalSeconds < 1) return '<1s'
// Consider adding a comment to clarify the intended behavior:
// Returns 'N/A' for null, undefined, or negative values
if (totalSeconds == null || totalSeconds < 0) return 'N/A'
if (totalSeconds === 0) return '0s'
if (totalSeconds < 1) return '<1s'

Comment on lines +22 to +25
const days = Math.floor(totalSeconds / SECONDS_PER_DAY)
const hours = Math.floor((totalSeconds % SECONDS_PER_DAY) / SECONDS_PER_HOUR)
const minutes = Math.floor(
(totalSeconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE
Copy link
Contributor

Choose a reason for hiding this comment

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

The manual calculation approach is more efficient and gives you better control, but consider adding input validation for extremely large numbers that might cause overflow issues. JavaScript's Number.MAX_SAFE_INTEGER is 2^53 - 1, and very large inputs could lead to precision issues.

Suggested change
const days = Math.floor(totalSeconds / SECONDS_PER_DAY)
const hours = Math.floor((totalSeconds % SECONDS_PER_DAY) / SECONDS_PER_HOUR)
const minutes = Math.floor(
(totalSeconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE
// Add bounds checking for very large numbers
if (totalSeconds > Number.MAX_SAFE_INTEGER / 1000) {
return 'N/A' // or handle appropriately
}
const days = Math.floor(totalSeconds / SECONDS_PER_DAY)
const hours = Math.floor((totalSeconds % SECONDS_PER_DAY) / SECONDS_PER_HOUR)
const minutes = Math.floor(
(totalSeconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE
)
const seconds = Math.floor(totalSeconds % SECONDS_PER_MINUTE)

Comment on lines +27 to 42
const seconds = Math.floor(totalSeconds % SECONDS_PER_MINUTE)

const { days, hours, minutes, seconds } = duration
const timeParts = []
if (days > 0) {
timeParts.push(`${days}d`)
}
if (hours > 0) {
timeParts.push(`${hours}h`)
}
if (minutes > 0) {
timeParts.push(`${minutes}m`)
}
if (seconds > 0) {
timeParts.push(`${seconds}s`)
}

Copy link
Contributor

Choose a reason for hiding this comment

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

The logic for building time parts is cleaner now, but consider what happens when all time parts are 0 (which shouldn't happen given the early returns, but it's worth being defensive). The function could potentially return an empty string if somehow all values are 0.

Suggested change
const seconds = Math.floor(totalSeconds % SECONDS_PER_MINUTE)
const { days, hours, minutes, seconds } = duration
const timeParts = []
if (days > 0) {
timeParts.push(`${days}d`)
}
if (hours > 0) {
timeParts.push(`${hours}h`)
}
if (minutes > 0) {
timeParts.push(`${minutes}m`)
}
if (seconds > 0) {
timeParts.push(`${seconds}s`)
}
const timeParts = []
if (days > 0) {
timeParts.push(`${days}d`)
}
if (hours > 0) {
timeParts.push(`${hours}h`)
}
if (minutes > 0) {
timeParts.push(`${minutes}m`)
}
if (seconds > 0) {
timeParts.push(`${seconds}s`)
}
// Defensive check, though this shouldn't happen with current logic
return timeParts.length > 0 ? timeParts.join(' ') : '0s'

Comment on lines +37 to +39

it('returns the correct time format when totalSeconds is a float', () => {
expect(formatTimeFromSeconds(12901948.144373389)).toBe('149d 7h 52m 28s')
Copy link
Contributor

Choose a reason for hiding this comment

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

The test for large float values is good, but consider adding a test for edge cases around floating point precision. For example, what happens with very small positive numbers like 0.1 or 0.9?

Suggested change
it('returns the correct time format when totalSeconds is a float', () => {
expect(formatTimeFromSeconds(12901948.144373389)).toBe('149d 7h 52m 28s')
it('returns the correct time format when totalSeconds is a float', () => {
expect(formatTimeFromSeconds(12901948.144373389)).toBe('149d 7h 52m 28s')
})
it('handles small float values correctly', () => {
expect(formatTimeFromSeconds(0.1)).toBe('<1s')
expect(formatTimeFromSeconds(0.9)).toBe('<1s')
})

Comment on lines +41 to +43

it('returns the correct time format when totalSeconds is less than 1', () => {
expect(formatTimeFromSeconds(0.5)).toBe('<1s')
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider adding a test for the boundary case of exactly 1 second to ensure the '<1s' logic works correctly.

Suggested change
it('returns the correct time format when totalSeconds is less than 1', () => {
expect(formatTimeFromSeconds(0.5)).toBe('<1s')
it('returns the correct time format when totalSeconds is less than 1', () => {
expect(formatTimeFromSeconds(0.5)).toBe('<1s')
})
it('returns the correct time format when totalSeconds is exactly 1', () => {
expect(formatTimeFromSeconds(1)).toBe('1s')
})

Comment on lines +55 to 57
expect(formatTimeFromSeconds(59.999)).toBe('59s')
})
})
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider adding tests for extreme values to ensure the function handles edge cases gracefully. This includes very large numbers and testing the boundaries of your constants.

Suggested change
expect(formatTimeFromSeconds(59.999)).toBe('59s')
})
})
it('handles float values that round down', () => {
expect(formatTimeFromSeconds(59.999)).toBe('59s')
})
it('handles very large numbers', () => {
expect(formatTimeFromSeconds(Number.MAX_SAFE_INTEGER)).toBe('N/A')
})
it('handles zero after negative check', () => {
expect(formatTimeFromSeconds(-0)).toBe('0s') // -0 === 0 in JavaScript
})

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.

3 participants