[PowerRename] Add 12-hour time format patterns with AM/PM support (#30703) (#38723)

* [PowerRename][Feature] Add new date/time formatting patterns to GetDatedFileName

* [PowerRename][UI] Add date/time shortcut patterns to cheat sheet

* [PowerRename][Tests] Add tests for new date/time formatting patterns

* [PowerRename] [Refactor] Simplify AM/PM string handling in time patterns
This commit is contained in:
Abhyudit
2025-04-16 07:45:03 +05:30
committed by GitHub
parent 0e98cbd57e
commit 4e0db267dc
5 changed files with 152 additions and 7 deletions

View File

@@ -195,8 +195,15 @@ namespace winrt::PowerRenameUI::implementation
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$DDD", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_DayNameAbbr").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$DD", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_DayDigitLZero").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$D", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_DayDigit").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$hh", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_HoursLZero").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$h", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_Hours").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$HH", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_Hours12LZero").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$H", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_Hours12").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$TT", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_AMPMUpperCase").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$tt", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_AMPMLowerCase").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$hh", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_Hours24LZero").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$h", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_Hours24").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$mm", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_MinutesLZero").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$m", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_Minutes").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$ss", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_SecondsLZero").ValueAsString()));

View File

@@ -219,11 +219,23 @@
<data name="DateTimeCheatSheet_DayDigit" xml:space="preserve">
<value>Day of the month as digits without leading zeros for single-digit days.</value>
</data>
<data name="DateTimeCheatSheet_HoursLZero" xml:space="preserve">
<value>Hours with leading zeros for single-digit hours.</value>
<data name="DateTimeCheatSheet_Hours12LZero" xml:space="preserve">
<value>Hours in 12-hour format (01-12) with leading zero.</value>
</data>
<data name="DateTimeCheatSheet_Hours" xml:space="preserve">
<value>Hours without leading zeros for single-digit hours.</value>
<data name="DateTimeCheatSheet_Hours12" xml:space="preserve">
<value>Hours in 12-hour format (1-12) without leading zero.</value>
</data>
<data name="DateTimeCheatSheet_AMPMUpperCase" xml:space="preserve">
<value>AM/PM indicator in uppercase (AM or PM).</value>
</data>
<data name="DateTimeCheatSheet_AMPMLowerCase" xml:space="preserve">
<value>AM/PM indicator in lowercase (am or pm).</value>
</data>
<data name="DateTimeCheatSheet_Hours24LZero" xml:space="preserve">
<value>Hours in 24-hour format (00-23) with leading zero.</value>
</data>
<data name="DateTimeCheatSheet_Hours24" xml:space="preserve">
<value>Hours in 24-hour format (0-23) without leading zero.</value>
</data>
<data name="DateTimeCheatSheet_MinutesLZero" xml:space="preserve">
<value>Minutes with leading zeros for single-digit minutes.</value>

View File

@@ -248,7 +248,19 @@ HRESULT GetTransformedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR sour
bool isFileTimeUsed(_In_ PCWSTR source)
{
bool used = false;
static const std::array patterns = { std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$Y" }, std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$M" }, std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$D" }, std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$h" }, std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$m" }, std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$s" }, std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$f" } };
static const std::array patterns = {
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$Y" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$M" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$D" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$h" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$m" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$s" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$f" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$H" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$T" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$t" }
};
for (size_t i = 0; !used && i < patterns.size(); i++)
{
if (std::regex_search(source, patterns[i]))
@@ -275,6 +287,12 @@ HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SY
StringCchCopy(localeName, LOCALE_NAME_MAX_LENGTH, L"en_US");
}
int hour12 = (fileTime.wHour % 12);
if (hour12 == 0)
{
hour12 = 12;
}
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%04d"), L"$01", fileTime.wYear);
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$YYYY"), replaceTerm);
@@ -316,6 +334,18 @@ HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SY
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wDay);
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$D"), replaceTerm);
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", hour12);
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$HH"), replaceTerm);
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", hour12);
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$H"), replaceTerm);
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", (fileTime.wHour < 12) ? L"AM" : L"PM");
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$TT"), replaceTerm);
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", (fileTime.wHour < 12) ? L"am" : L"pm");
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$tt"), replaceTerm);
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wHour);
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$hh"), replaceTerm);

View File

@@ -127,5 +127,53 @@ TEST_METHOD(VerifyLookbehind)
CoTaskMemFree(result);
}
}
TEST_METHOD (Verify12and24HourTimeFormats)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = MatchAllOccurrences | UseRegularExpressions;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
struct TimeTestCase {
SYSTEMTIME time; // Input time
PCWSTR formatString; // Format pattern
PCWSTR expectedResult; // Expected output
PCWSTR description; // Description of what we're testing
};
struct TimeTestCase testCases[] = {
// Midnight (00:00 / 12:00 AM)
{ { 2025, 4, 4, 10, 0, 0, 0, 0 }, L"[$hh:$mm] [$H:$mm $tt]", L"[00:00] [12:00 am]", L"Midnight formatting" },
// Noon (12:00 / 12:00 PM)
{ { 2025, 4, 4, 10, 12, 0, 0, 0 }, L"[$hh:$mm] [$H:$mm $tt]", L"[12:00] [12:00 pm]", L"Noon formatting" },
// 1:05 AM
{ { 2025, 4, 4, 10, 1, 5, 0, 0 }, L"[$h:$m] [$H:$m $tt] [$hh:$mm] [$HH:$mm $TT]",
L"[1:5] [1:5 am] [01:05] [01:05 AM]", L"1 AM with various formats" },
// 11 PM
{ { 2025, 4, 4, 10, 23, 45, 0, 0 }, L"[$h:$m] [$H:$m $tt] [$hh:$mm] [$HH:$mm $TT]",
L"[23:45] [11:45 pm] [23:45] [11:45 PM]", L"11 PM with various formats" },
// Mixed formats in complex pattern
{ { 2025, 4, 4, 10, 14, 30, 0, 0 }, L"Date: $YYYY-$MM-$DD Time: $hh:$mm (24h) / $H:$mm $tt (12h)",
L"Date: 2025-04-10 Time: 14:30 (24h) / 2:30 pm (12h)", L"Complex combined format" },
};
for (int i = 0; i < ARRAYSIZE(testCases); i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(L"test") == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(testCases[i].formatString) == S_OK);
Assert::IsTrue(renameRegEx->PutFileTime(testCases[i].time) == S_OK);
unsigned long index = {};
Assert::IsTrue(renameRegEx->Replace(L"test", &result, index) == S_OK);
Assert::IsTrue(wcscmp(result, testCases[i].expectedResult) == 0,
(std::wstring(L"Failed test case: ") + testCases[i].description).c_str());
CoTaskMemFree(result);
}
}
};
}

View File

@@ -207,5 +207,53 @@ TEST_METHOD(VerifyLookbehindFails)
}
}
TEST_METHOD (Verify12and24HourTimeFormats)
{
CComPtr<IPowerRenameRegEx> renameRegEx;
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
DWORD flags = MatchAllOccurrences | UseRegularExpressions;
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
struct TimeTestCase {
SYSTEMTIME time; // Input time
PCWSTR formatString; // Format pattern
PCWSTR expectedResult; // Expected output
PCWSTR description; // Description of what we're testing
};
struct TimeTestCase testCases[] = {
// Midnight (00:00 / 12:00 AM)
{ { 2025, 4, 4, 10, 0, 0, 0, 0 }, L"[$hh:$mm] [$H:$mm $tt]", L"[00:00] [12:00 am]", L"Midnight formatting" },
// Noon (12:00 / 12:00 PM)
{ { 2025, 4, 4, 10, 12, 0, 0, 0 }, L"[$hh:$mm] [$H:$mm $tt]", L"[12:00] [12:00 pm]", L"Noon formatting" },
// 1:05 AM
{ { 2025, 4, 4, 10, 1, 5, 0, 0 }, L"[$h:$m] [$H:$m $tt] [$hh:$mm] [$HH:$mm $TT]",
L"[1:5] [1:5 am] [01:05] [01:05 AM]", L"1 AM with various formats" },
// 11 PM
{ { 2025, 4, 4, 10, 23, 45, 0, 0 }, L"[$h:$m] [$H:$m $tt] [$hh:$mm] [$HH:$mm $TT]",
L"[23:45] [11:45 pm] [23:45] [11:45 PM]", L"11 PM with various formats" },
// Mixed formats in complex pattern
{ { 2025, 4, 4, 10, 14, 30, 0, 0 }, L"Date: $YYYY-$MM-$DD Time: $hh:$mm (24h) / $H:$mm $tt (12h)",
L"Date: 2025-04-10 Time: 14:30 (24h) / 2:30 pm (12h)", L"Complex combined format" },
};
for (int i = 0; i < ARRAYSIZE(testCases); i++)
{
PWSTR result = nullptr;
Assert::IsTrue(renameRegEx->PutSearchTerm(L"test") == S_OK);
Assert::IsTrue(renameRegEx->PutReplaceTerm(testCases[i].formatString) == S_OK);
Assert::IsTrue(renameRegEx->PutFileTime(testCases[i].time) == S_OK);
unsigned long index = {};
Assert::IsTrue(renameRegEx->Replace(L"test", &result, index) == S_OK);
Assert::IsTrue(wcscmp(result, testCases[i].expectedResult) == 0,
(std::wstring(L"Failed test case: ") + testCases[i].description).c_str());
CoTaskMemFree(result);
}
}
};
}