From 81c0bc396ab65e4f18f6912c13f3dc727b316f39 Mon Sep 17 00:00:00 2001 From: melyux <10296053+melyux@users.noreply.github.com> Date: Wed, 22 Feb 2023 11:38:57 -0800 Subject: [PATCH] Duration format: Add spaces, remove lead zeros for Letters (#10141) * Add thinspaces between d/h/m/s * Remove lead zeros * Make letter `d` for days translatable * Make thinspace into hairspace when compact * Adjust and add unit tests --- frontend/datetime.lua | 35 ++++++---- .../elements/common_settings_menu_table.lua | 2 +- spec/unit/datetime_spec.lua | 64 ++++++++++++++++--- 3 files changed, 79 insertions(+), 22 deletions(-) diff --git a/frontend/datetime.lua b/frontend/datetime.lua index d5faff7ea..2852401d9 100644 --- a/frontend/datetime.lua +++ b/frontend/datetime.lua @@ -100,10 +100,10 @@ function datetime.secondsToClock(seconds, withoutSeconds, withDays) mins = string.format("%02d", 0) hours = string.format("%02d", hours + 1) end - return (days ~= "0" and (days .. "d") or "") .. hours .. ":" .. mins + return (days ~= "0" and (days .. C_("Time", "d")) or "") .. hours .. ":" .. mins else local secs = string.format("%02d", seconds % 60) - return (days ~= "0" and (days .. "d") or "") .. hours .. ":" .. mins .. ":" .. secs + return (days ~= "0" and (days .. C_("Time", "d")) or "") .. hours .. ":" .. mins .. ":" .. secs end end end @@ -111,10 +111,10 @@ end --- Converts seconds to a period of time string. ---- @int seconds number of seconds ---- @bool withoutSeconds if true 1h30', if false 1h30'10" ----- @bool hmsFormat, if true format 1h30m10s ----- @bool withDays, if true format 1d12h30m10s ----- @bool compact, if set removes all leading zeros (incl. units if necessary) ----- @treturn string clock string in the form of 1h30'10" or 1h30m10s +---- @bool hmsFormat, if true format 1h 30m 10s +---- @bool withDays, if true format 1d12h30'10" or 1d 12h 30m 10s +---- @bool compact, if set removes all leading zeros (incl. units if necessary) and turns thinspaces into hairspaces (if present) +---- @treturn string clock string in the form of 1h30'10" or 1h 30m 10s function datetime.secondsToHClock(seconds, withoutSeconds, hmsFormat, withDays, compact) local SECONDS_SYMBOL = "\"" seconds = tonumber(seconds) @@ -150,7 +150,7 @@ function datetime.secondsToHClock(seconds, withoutSeconds, hmsFormat, withDays, if compact then return T(C_("Time", "%1s"), string.format("%d", seconds)) else - return T(C_("Time", "%1m%2s"), "0", string.format("%02d", seconds)) + return T(C_("Time", "%1m\xE2\x80\x89%2s"), "0", string.format("%d", seconds)) end else if compact then @@ -177,7 +177,16 @@ function datetime.secondsToHClock(seconds, withoutSeconds, hmsFormat, withDays, end if hmsFormat then - return withoutSeconds and time_string or (time_string .. C_("Time", "s")) + time_string = time_string:gsub("0(%d)", "%1") -- delete all leading "0"s + time_string = time_string:gsub(C_("Time", "d"), C_("Time", "d") .. "\xE2\x80\x89") -- add thin space after "d" + time_string = time_string:gsub(C_("Time", "h"), C_("Time", "h") .. "\xE2\x80\x89") -- add thin space after "h" + if not withoutSeconds then + time_string = time_string:gsub(C_("Time", "m"), C_("Time", "m") .. "\xE2\x80\x89") .. C_("Time", "s") -- add thin space after "m" + end + if compact then + time_string = time_string:gsub("\xE2\x80\x89", "\xE2\x80\x8A") -- replace thin space with hair space + end + return time_string else time_string = time_string:gsub(C_("Time", "m"), "'") -- replace m with ' return withoutSeconds and time_string or (time_string .. SECONDS_SYMBOL) @@ -187,11 +196,11 @@ end --- Converts seconds to a clock type (classic or modern), based on the given format preference --- "Classic" format calls secondsToClock, "Modern" and "Letters" formats call secondsToHClock ----- @string Either "modern" for 1h30'10", "letters" for 1h30m10s, or "classic" for 1:30:10 ----- @bool withoutSeconds if true 1h30' or 1h30m, if false 1h30'10" or 1h30m10s ----- @bool withDays, if hours>=24 include days in clock string 1d12h10m10s ----- @bool compact, if set removes all leading zeros (incl. units if necessary) ----- @treturn string clock string in the specific format of 1h30', 1h30'10" resp. 1h30m, 1h30m10s +---- @string Either "modern" for 1h30'10", "letters" for 1h 30m 10s, or "classic" for 1:30:10 +---- @bool withoutSeconds if true 1h30' or 1h 30m, if false 1h30'10" or 1h 30m 10s +---- @bool withDays, if hours>=24 include days in clock string 1d12h10'10" or 1d 12h 10m 10s +---- @bool compact, if set removes all leading zeros (incl. units if necessary) and turns thinspaces into hairspaces (if present) +---- @treturn string clock string in the specific format of 1h30', 1h30'10" resp. 1h 30m, 1h 30m 10s function datetime.secondsToClockDuration(format, seconds, withoutSeconds, withDays, compact) if format == "modern" then return datetime.secondsToHClock(seconds, withoutSeconds, false, withDays, compact) diff --git a/frontend/ui/elements/common_settings_menu_table.lua b/frontend/ui/elements/common_settings_menu_table.lua index 3c3d83b5f..995988529 100644 --- a/frontend/ui/elements/common_settings_menu_table.lua +++ b/frontend/ui/elements/common_settings_menu_table.lua @@ -128,7 +128,7 @@ common_settings.time = { { text_func = function() local datetime = require("datetime") - -- sample text shows 1h23m45s + -- sample text shows 1h 23m 45s local duration_format_str = datetime.secondsToClockDuration("letters", 5025, false) return T(C_("Time", "Letters (%1)"), duration_format_str) end, diff --git a/spec/unit/datetime_spec.lua b/spec/unit/datetime_spec.lua index ff87cb5ee..7328c3291 100644 --- a/spec/unit/datetime_spec.lua +++ b/spec/unit/datetime_spec.lua @@ -44,10 +44,28 @@ describe("datetime module", function() assert.is_equal("00:02:00", datetime.secondsToClock(120)) end) + it("should convert seconds to 0d00:00:00 format", function() + assert.is_equal("00:00:00", + datetime.secondsToClock(0, false, true)) + assert.is_equal("00:02:00", + datetime.secondsToClock(120, false, true)) + assert.is_equal("5d00:02:00", + datetime.secondsToClock(432120, false, true)) + end) + it("should convert seconds to 0d00:00 format", function() + assert.is_equal("00:00", + datetime.secondsToClock(0, true, true)) + assert.is_equal("00:02", + datetime.secondsToClock(120, true, true)) + assert.is_equal("5d00:02", + datetime.secondsToClock(432110, true, true)) + assert.is_equal("5d00:02", + datetime.secondsToClock(432120, true, true)) + end) end) describe("secondsToHClock()", function() - it("should convert seconds to 0'00'' format", function() + it("should convert seconds to 0' format", function() assert.is_equal("0'", datetime.secondsToHClock(0, true)) assert.is_equal("0'", @@ -75,22 +93,38 @@ describe("datetime module", function() assert.is_equal("10h01'", datetime.secondsToHClock(36060, true)) end) - it("should round seconds to minutes in 0h00m format", function() + it("should round seconds to minutes in 0h 0m (thinspace) format", function() assert.is_equal("1m", datetime.secondsToHClock(89, true, true)) assert.is_equal("2m", datetime.secondsToHClock(90, true, true)) assert.is_equal("2m", datetime.secondsToHClock(110, true, true)) - assert.is_equal("1h00m", + assert.is_equal("1h\xE2\x80\x890m", datetime.secondsToHClock(3600, true, true)) - assert.is_equal("1h00m", + assert.is_equal("1h\xE2\x80\x890m", datetime.secondsToHClock(3599, true, true)) assert.is_equal("59m", datetime.secondsToHClock(3569, true, true)) - assert.is_equal("10h01m", + assert.is_equal("10h\xE2\x80\x891m", datetime.secondsToHClock(36060, true, true)) end) + it("should round seconds to minutes in 0h 0m (hairspace) format", function() + assert.is_equal("1m", + datetime.secondsToHClock(89, true, true, false, true)) + assert.is_equal("2m", + datetime.secondsToHClock(90, true, true, false, true)) + assert.is_equal("2m", + datetime.secondsToHClock(110, true, true, false, true)) + assert.is_equal("1h\xE2\x80\x8A0m", + datetime.secondsToHClock(3600, true, true, false, true)) + assert.is_equal("1h\xE2\x80\x8A0m", + datetime.secondsToHClock(3599, true, true, false, true)) + assert.is_equal("59m", + datetime.secondsToHClock(3569, true, true, false, true)) + assert.is_equal("10h\xE2\x80\x8A1m", + datetime.secondsToHClock(36060, true, true, false, true)) + end) it("should convert seconds to 0h00'00'' format", function() assert.is_equal("0\"", datetime.secondsToHClock(0)) @@ -111,7 +145,7 @@ describe("datetime module", function() it("should change type based on format", function() assert.is_equal("10h01'30\"", datetime.secondsToClockDuration("modern", 36090, false)) - assert.is_equal("10h01m30s", + assert.is_equal("10h\xE2\x80\x891m\xE2\x80\x8930s", datetime.secondsToClockDuration("letters", 36090, false)) assert.is_equal("10:01:30", datetime.secondsToClockDuration("classic", 36090, false)) @@ -125,15 +159,29 @@ describe("datetime module", function() datetime.secondsToClockDuration("modern", 36090, false)) assert.is_equal("10h02'", datetime.secondsToClockDuration("modern", 36090, true)) - assert.is_equal("10h01m30s", + assert.is_equal("10h\xE2\x80\x891m\xE2\x80\x8930s", datetime.secondsToClockDuration("letters", 36090, false)) - assert.is_equal("10h02m", + assert.is_equal("10h\xE2\x80\x892m", datetime.secondsToClockDuration("letters", 36090, true)) assert.is_equal("10:01:30", datetime.secondsToClockDuration("classic", 36090, false)) assert.is_equal("10:02", datetime.secondsToClockDuration("classic", 36090, true)) end) + it("should pass along withDays", function() + assert.is_equal("58h01'30\"", + datetime.secondsToClockDuration("modern", 208890, false, false)) + assert.is_equal("2d10h01'30\"", + datetime.secondsToClockDuration("modern", 208890, false, true)) + assert.is_equal("58h\xE2\x80\x891m\xE2\x80\x8930s", + datetime.secondsToClockDuration("letters", 208890, false, false)) + assert.is_equal("2d\xE2\x80\x8910h\xE2\x80\x891m\xE2\x80\x8930s", + datetime.secondsToClockDuration("letters", 208890, false, true)) + assert.is_equal("58:01:30", + datetime.secondsToClockDuration("classic", 208890, false, false)) + assert.is_equal("2d10:01:30", + datetime.secondsToClockDuration("classic", 208890, false, true)) + end) end) describe("secondsToDate()", function()