Skip to content

Conversation

AdamGrzybkowski
Copy link
Contributor

WOOMOB-1515

Description

This PR updates the booking duration formatting to follow the same logic as the web.

Steps to reproduce

  1. Make sure you have bookings with different durations
  2. To test the normalization logic, you can create a room rental booking that lasts full day
  3. Launch the booking tab and open different bookings
  4. Verify the duration

Testing information

The tests that have been performed

The above.

Images/gif

Without the normalization, this would show 1 day 23 hours 59 minutes
Screenshot_20251015_151451

  • I have considered if this change warrants release notes and have added them to RELEASE-NOTES.txt if necessary. Use the "[Internal]" label for non-user-facing changes.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Updates booking duration formatting in the mobile app to match the web application's logic, implementing duration normalization and human-readable formatting.

  • Replaced simple "X min" formatting with comprehensive duration display (days, hours, minutes, seconds)
  • Added duration normalization to handle precision issues by rounding up durations within one minute of common boundaries
  • Introduced localized string resources for proper pluralization of time units

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
BookingMapper.kt Implements new duration formatting logic with normalization and human-readable output
strings.xml Adds localized string resources for time unit pluralization
BookingMapperTest.kt Adds comprehensive test coverage for duration formatting scenarios
BookingListViewModelTest.kt Updates test setup to inject ResourceProvider dependency
BookingDetailsViewModelTest.kt Updates test setup and adds duration string stubbing for exact days

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +141 to +163
@Suppress("LongMethod")
private fun Duration.toHumanReadableFormat(): String {
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

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

[nitpick] The toHumanReadableFormat() method is quite long and handles multiple formatting scenarios. Consider breaking it down into smaller, more focused methods for each time unit range (formatDaysHoursMinutes, formatHoursMinutes, formatMinutes, formatSeconds) to improve readability and maintainability.

Copilot uses AI. Check for mistakes.

Copy link
Member

@hichamboushaba hichamboushaba Oct 17, 2025

Choose a reason for hiding this comment

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

I think AI has a point here, the method is quite long, and the multiple branches complicate the code a bit.

I'm suggesting a simpler version below, please check it out:

    private fun Duration.toHumanReadableFormat(): String {
        if (this < Duration.ofMinutes(1)) {
            return resourceProvider.getQuantityString(
                quantity = seconds.toInt(),
                default = R.string.booking_duration_seconds,
                one = R.string.booking_duration_second
            )
        }

        val days = toDays()
        val hours = minusDays(days).toHours()
        val minutes = minusDays(days).minusHours(hours).toMinutes()

        return buildString {
            if (days > 0) {
                append(
                    resourceProvider.getQuantityString(
                        quantity = days.toInt(),
                        default = R.string.booking_duration_days,
                        one = R.string.booking_duration_day
                    )
                )
            }
            if (hours > 0) {
                append(" ")
                append(
                    resourceProvider.getQuantityString(
                        quantity = hours.toInt(),
                        default = R.string.booking_duration_hours,
                        one = R.string.booking_duration_hour
                    )
                )
            }
            if (minutes > 0) {
                append(" ")
                append(
                    resourceProvider.getQuantityString(
                        quantity = minutes.toInt(),
                        default = R.string.booking_duration_minutes,
                        one = R.string.booking_duration_minute
                    )
                )
            }
        }.trim()
    }

This gives the same results, and the tests are green.

But please feel free to ignore the suggestion if you prefer the other version 🙂

Comment on lines +132 to +161
val remainder = durationInSeconds % boundary
val difference = if (remainder == 0L) 0L else boundary - remainder
if (difference > 0 && difference <= minuteInSeconds) {
durationInSeconds += difference
}
}
return Duration.ofSeconds(durationInSeconds)
}

Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

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

[nitpick] The normalization logic could be simplified by extracting the boundary checking into a separate function. This would make the algorithm clearer and more testable.

Suggested change
val remainder = durationInSeconds % boundary
val difference = if (remainder == 0L) 0L else boundary - remainder
if (difference > 0 && difference <= minuteInSeconds) {
durationInSeconds += difference
}
}
return Duration.ofSeconds(durationInSeconds)
}
durationInSeconds = adjustToBoundary(durationInSeconds, boundary, minuteInSeconds)
}
return Duration.ofSeconds(durationInSeconds)
}
private fun adjustToBoundary(durationInSeconds: Long, boundary: Long, minuteInSeconds: Long): Long {
val remainder = durationInSeconds % boundary
val difference = if (remainder == 0L) 0L else boundary - remainder
return if (difference > 0 && difference <= minuteInSeconds) {
durationInSeconds + difference
} else {
durationInSeconds
}
}

Copilot uses AI. Check for mistakes.

Comment on lines +187 to +212
val hoursPart = resourceProvider.getQuantityString(
quantity = hours,
default = R.string.booking_duration_hours,
one = R.string.booking_duration_hour
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure this is enough for other languages that we support, but from what I've checked, this is how we do plurals here.

For example, that wouldn't work for Polish, but we don't support Polish, so maybe that's fine.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, our plurals support is quite limited, a limitation of Glotpress, this won't work well for Arabic too, but I don't think we have much to do here.

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Oct 15, 2025

📲 You can test the changes from this Pull Request in WooCommerce-Wear Android by scanning the QR code below to install the corresponding build.
App NameWooCommerce-Wear Android
Platform⌚️ Wear OS
FlavorJalapeno
Build TypeDebug
Commit8645155
Direct Downloadwoocommerce-wear-prototype-build-pr14760-8645155.apk

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Oct 15, 2025

📲 You can test the changes from this Pull Request in WooCommerce Android by scanning the QR code below to install the corresponding build.

App NameWooCommerce Android
Platform📱 Mobile
FlavorJalapeno
Build TypeDebug
Commit8645155
Direct Downloadwoocommerce-prototype-build-pr14760-8645155.apk

@codecov-commenter
Copy link

codecov-commenter commented Oct 15, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 38.03%. Comparing base (6da2cb0) to head (8645155).
⚠️ Report is 61 commits behind head on trunk.

Additional details and impacted files
@@             Coverage Diff              @@
##              trunk   #14760      +/-   ##
============================================
+ Coverage     38.00%   38.03%   +0.03%     
- Complexity     9960     9972      +12     
============================================
  Files          2118     2118              
  Lines        119121   119188      +67     
  Branches      16224    16233       +9     
============================================
+ Hits          45267    45334      +67     
  Misses        69287    69287              
  Partials       4567     4567              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@AdamGrzybkowski AdamGrzybkowski force-pushed the issue/WOOMOB-1515_booking_duration branch from f8ef79f to 8645155 Compare October 17, 2025 08:35
@wpmobilebot wpmobilebot modified the milestones: 23.5, 23.6 Oct 17, 2025
@wpmobilebot
Copy link
Collaborator

Version 23.5 has now entered code-freeze, so the milestone of this PR has been updated to 23.6.

Copy link
Member

@hichamboushaba hichamboushaba left a comment

Choose a reason for hiding this comment

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

Really nice work @AdamGrzybkowski.

I left some comments but nothing is blocking.

And just a question: how do you feel about moving the formatting logic to a separate class? I feel like it's something that can be used elsewhere if we ever use Duration.

Comment on lines +187 to +212
val hoursPart = resourceProvider.getQuantityString(
quantity = hours,
default = R.string.booking_duration_hours,
one = R.string.booking_duration_hour
)
Copy link
Member

Choose a reason for hiding this comment

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

Yes, our plurals support is quite limited, a limitation of Glotpress, this won't work well for Arabic too, but I don't think we have much to do here.

val hours = ((totalSeconds % dayInSeconds) / hourInSeconds).toInt()
val minutes = ((totalSeconds % hourInSeconds) / minuteInSeconds).toInt()

val parts = mutableListOf<String>()
Copy link
Member

Choose a reason for hiding this comment

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

np, we can probably use buildString here instead of a mutableListOf<String>, for example for this first formatting logic:

                buildString {
                    append(
                        resourceProvider.getQuantityString(
                            quantity = days,
                            default = R.string.booking_duration_days,
                            one = R.string.booking_duration_day
                        )
                    )
                    if (hours > 0) {
                        append(" ")
                        append(
                            resourceProvider.getQuantityString(
                                quantity = hours,
                                default = R.string.booking_duration_hours,
                                one = R.string.booking_duration_hour
                            )
                        )
                    }
                    if (minutes > 0) {
                        append(" ")
                        append(
                            resourceProvider.getQuantityString(
                                quantity = minutes,
                                default = R.string.booking_duration_minutes,
                                one = R.string.booking_duration_minute
                            )
                        )
                    }
                }

Comment on lines +141 to +163
@Suppress("LongMethod")
private fun Duration.toHumanReadableFormat(): String {
Copy link
Member

@hichamboushaba hichamboushaba Oct 17, 2025

Choose a reason for hiding this comment

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

I think AI has a point here, the method is quite long, and the multiple branches complicate the code a bit.

I'm suggesting a simpler version below, please check it out:

    private fun Duration.toHumanReadableFormat(): String {
        if (this < Duration.ofMinutes(1)) {
            return resourceProvider.getQuantityString(
                quantity = seconds.toInt(),
                default = R.string.booking_duration_seconds,
                one = R.string.booking_duration_second
            )
        }

        val days = toDays()
        val hours = minusDays(days).toHours()
        val minutes = minusDays(days).minusHours(hours).toMinutes()

        return buildString {
            if (days > 0) {
                append(
                    resourceProvider.getQuantityString(
                        quantity = days.toInt(),
                        default = R.string.booking_duration_days,
                        one = R.string.booking_duration_day
                    )
                )
            }
            if (hours > 0) {
                append(" ")
                append(
                    resourceProvider.getQuantityString(
                        quantity = hours.toInt(),
                        default = R.string.booking_duration_hours,
                        one = R.string.booking_duration_hour
                    )
                )
            }
            if (minutes > 0) {
                append(" ")
                append(
                    resourceProvider.getQuantityString(
                        quantity = minutes.toInt(),
                        default = R.string.booking_duration_minutes,
                        one = R.string.booking_duration_minute
                    )
                )
            }
        }.trim()
    }

This gives the same results, and the tests are green.

But please feel free to ignore the suggestion if you prefer the other version 🙂

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants