diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 6ec373861c..bfffbbe886 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -373,6 +373,7 @@ DARKYELLOW
datareader
Datavalue
DATAW
+David
davidegiacometti
Dayof
Dbg
@@ -1214,6 +1215,7 @@ MONITORINFOEXW
monitorinfof
Monthand
Moq
+Morton
MOUSEACTIVATE
MOUSEHWHEEL
MOUSEINPUT
@@ -1925,6 +1927,7 @@ subquery
substr
Sul
Superbar
+supressions
Suri
sut
SVE
@@ -1992,6 +1995,7 @@ testzones
TEXCOORD
textblock
TEXTINCLUDE
+THH
THICKFRAME
THISCOMPONENT
THotkey
diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json
index f4f9ddb5f2..37eda37735 100644
--- a/.pipelines/ESRPSigning_core.json
+++ b/.pipelines/ESRPSigning_core.json
@@ -94,6 +94,7 @@
"modules\\launcher\\Plugins\\VSCodeWorkspaces\\Community.PowerToys.Run.Plugin.VSCodeWorkspaces.dll",
"modules\\launcher\\Plugins\\Service\\Microsoft.PowerToys.Run.Plugin.Service.dll",
"modules\\launcher\\Plugins\\System\\Microsoft.PowerToys.Run.Plugin.System.dll",
+ "modules\\launcher\\Plugins\\TimeDate\\Microsoft.PowerToys.Run.Plugin.TimeDate.dll",
"modules\\launcher\\Plugins\\TimeZone\\Microsoft.PowerToys.Run.Plugin.TimeZone.dll",
"modules\\launcher\\Plugins\\WebSearch\\Community.PowerToys.Run.Plugin.WebSearch.dll",
"modules\\launcher\\Plugins\\WindowsTerminal\\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.dll",
diff --git a/.pipelines/ci/templates/build-powertoys-steps.yml b/.pipelines/ci/templates/build-powertoys-steps.yml
index 3c9d828741..78f216a6ab 100644
--- a/.pipelines/ci/templates/build-powertoys-steps.yml
+++ b/.pipelines/ci/templates/build-powertoys-steps.yml
@@ -130,7 +130,8 @@ steps:
**\Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest.dll
**\Microsoft.Plugin.Uri.UnitTests.dll
**\Wox.Test.dll
- **\Microsoft.PowerToys.Run.Plugin.System.UnitTests.dll
+ **\Microsoft.PowerToys.Run.Plugin.System.UnitTests.dll
+ **\Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests.dll
**\Microsoft.Plugin.WindowsTerminal.UnitTests.dll
**\Microsoft.Plugin.WindowWalker.UnitTests.dll
!**\obj\**
diff --git a/PowerToys.sln b/PowerToys.sln
index b19e84e078..f56d438622 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -136,6 +136,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher", "src\module
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}
{0351ADA4-0C32-4652-9BA0-41F7B602372B} = {0351ADA4-0C32-4652-9BA0-41F7B602372B}
{787B8AA6-CA93-4C84-96FE-DF31110AD1C4} = {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}
+ {F44934A8-36F3-49B0-9465-3831BE041CDE} = {F44934A8-36F3-49B0-9465-3831BE041CDE}
+ {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF} = {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}
{5043CECE-E6A7-4867-9CBE-02D27D83747A} = {5043CECE-E6A7-4867-9CBE-02D27D83747A}
{F8B870EB-D5F5-45BA-9CF7-A5C459818820} = {F8B870EB-D5F5-45BA-9CF7-A5C459818820}
{74F1B9ED-F59C-4FE7-B473-7B453E30837E} = {74F1B9ED-F59C-4FE7-B473-7B453E30837E}
@@ -399,6 +401,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonacoPreviewHandler", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.TimeZone", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.TimeZone\Microsoft.PowerToys.Run.Plugin.TimeZone.csproj", "{F44934A8-36F3-49B0-9465-3831BE041CDE}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.TimeDate", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.TimeDate\Microsoft.PowerToys.Run.Plugin.TimeDate.csproj", "{5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests\Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests.csproj", "{FD464B4C-2F68-4D06-91E7-4208146C41F5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.WindowWalker.UnitTests", "src\modules\launcher\Plugins\Microsoft.Plugin.WindowWalker.UnitTests\Microsoft.Plugin.WindowWalker.UnitTests.csproj", "{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}"
EndProject
Global
@@ -1087,11 +1092,25 @@ Global
{F44934A8-36F3-49B0-9465-3831BE041CDE}.Debug|x64.ActiveCfg = Debug|x64
{F44934A8-36F3-49B0-9465-3831BE041CDE}.Debug|x64.Build.0 = Debug|x64
{F44934A8-36F3-49B0-9465-3831BE041CDE}.Debug|x86.ActiveCfg = Debug|x64
- {F44934A8-36F3-49B0-9465-3831BE041CDE}.Debug|x86.Build.0 = Debug|x64
{F44934A8-36F3-49B0-9465-3831BE041CDE}.Release|x64.ActiveCfg = Release|x64
{F44934A8-36F3-49B0-9465-3831BE041CDE}.Release|x64.Build.0 = Release|x64
{F44934A8-36F3-49B0-9465-3831BE041CDE}.Release|x86.ActiveCfg = Release|x64
{F44934A8-36F3-49B0-9465-3831BE041CDE}.Release|x86.Build.0 = Release|x64
+ {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Debug|x64.ActiveCfg = Debug|x64
+ {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Debug|x64.Build.0 = Debug|x64
+ {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Debug|x86.ActiveCfg = Debug|x64
+ {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Release|x64.ActiveCfg = Release|x64
+ {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Release|x64.Build.0 = Release|x64
+ {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Release|x86.ActiveCfg = Release|x64
+ {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Release|x86.Build.0 = Release|x64
+ {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Debug|x64.ActiveCfg = Debug|x64
+ {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Debug|x64.Build.0 = Debug|x64
+ {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Debug|x86.ActiveCfg = Debug|x64
+ {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Debug|x86.Build.0 = Debug|x64
+ {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Release|x64.ActiveCfg = Release|x64
+ {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Release|x64.Build.0 = Release|x64
+ {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Release|x86.ActiveCfg = Release|x64
+ {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Release|x86.Build.0 = Release|x64
{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Debug|x64.ActiveCfg = Debug|x64
{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Debug|x64.Build.0 = Debug|x64
{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Debug|x86.ActiveCfg = Debug|x64
@@ -1232,6 +1251,8 @@ Global
{F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC} = {2F305555-C296-497E-AC20-5FA1B237996A}
{04B193D7-3E21-46B8-A958-89B63A8A69DE} = {2F305555-C296-497E-AC20-5FA1B237996A}
{F44934A8-36F3-49B0-9465-3831BE041CDE} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
+ {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
+ {FD464B4C-2F68-4D06-91E7-4208146C41F5} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/doc/devdocs/modules/launcher/plugins/timedate.md b/doc/devdocs/modules/launcher/plugins/timedate.md
new file mode 100644
index 0000000000..4103beeaca
--- /dev/null
+++ b/doc/devdocs/modules/launcher/plugins/timedate.md
@@ -0,0 +1,148 @@
+# 'Time and Date' plugin
+The 'Time and Date' plugin shows the date and time in different formats. For the date and time formats the plugin uses the culture setting in Windows, if the format is not commonly defined. The user can search for the system date/time or a custom date/time. The value of each result can be copied to clipboard.
+
+**Query examples:**
+- Format: `time`
+- Date/time: `10:30 AM`
+- Format and date/time: `Week number::10/10/2022`
+
+
+
+
+
+
+## Formats
+
+### Available formats
+
+**Remarks**
+- The following formats requires a prefix in the query:
+ - Unix Timestamp: `u`
+ - Windows file time: `ft`
+- On invalid number inputs we show a warning that tells the user which prefixes are allowed/required.
+
+**List of available formats**
+
+The following formats are currently available:
+
+| Format | Example (Based on default settings) | As result | As input |
+|--------------|-----------|------------|------------|
+| Time | 5:10 PM | x | x |
+| Date | 3/5/2022 | x | x |
+| Now | 3/5/2022 5:10 PM | x | x |
+| Time UTC | 4:10 PM | x | x |
+| Now UTC | 3/5/2022 4:10 PM | x | x |
+| Unix Timestamp | 1646496622 | x | x |
+| Hour | 10 | x | |
+| Minute | 30 | x | |
+| Second | 45 | x | |
+| Millisecond | 678 | x | |
+| Day (Week day) | Saturday | x | |
+| Day of the week | 6 | x | |
+| Day of the month | 5 | x | |
+| Day of the year | 64 | x | |
+| Week of the month | 1 | x | |
+| Week of the year (Calendar week, Week number) | 10 | x | |
+| Month | March | x | |
+| Month of the year | 3 | x | |
+| Month and day | March 7 | x | x |
+| Year | 2022 | x | |
+| Era | AD | x | |
+| Era abbreviation | A | x | |
+| Month and year | March 2022 | x | x |
+| Windows file time (Int64 number) | 637820976123938199 | x | x |
+| Universal time format: YYYY-MM-DD hh:mm:ss| 2022-03-05 16:20:12Z | x | x |
+| ISO 8601 | 2022-03-05T17:23:04 | x | x |
+| ISO 8601 UTC | 2022-03-05T16:23:04 | x | x |
+| ISO 8601 with time zone | 2022-03-05T17:23:04+01:00 | x | x |
+| ISO 8601 UTC with time zone | 2022-03-05T16:23:04Z | x | x |
+| RFC1123 | Sat, 05 Mar 2022 16:23:04 GMT | x | x |
+
+### Add new formats
+- To add a new formats you have to add them to the method `GetList()` of the [`AvailableResultsList`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/AvailableResultsList.cs) class.
+ - Please add the new formats in the second range. The first one is reserved for the three main formats (Time, Date, Now).
+- After adding the new formats you have to update the Unit Tests!
+
+
+## Optional plugin settings
+- The optional plugin settings are implemented via the [`ISettingProvider`](/src/modules/launcher/Wox.Plugin/ISettingProvider.cs) interface from `Wox.Plugin` project.
+- All available settings for the plugin are defined in the [`TimeDateSettings`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeDateSettings.cs) class of the plugin. The settings can be accessed everywhere in the plugin code via the static class instance `TimeDateSettings.Instance`.
+- We have the following settings that the user can configure to change the behavior of the plugin:
+
+ | Key | Default value | Name/Description |
+ |--------------|-----------|------------|
+ | `OnlyDateTimeNowGlobal` | `true` | Show only 'Time', 'Date', and 'Now' result on global queries |
+ | `TimeWithSeconds` | `false` | Show time with seconds (Applies to 'Time' and 'Now' result) |
+ | `DateWithWeekday` | `false` | Show date with weekday and name of month (Applies to 'Date' and 'Now' result) |
+ | `HideNumberMessageOnGlobalQuery` | `false` | Hide 'Invalid number input' error message on global queries |
+
+
+## Classes
+
+### [`AvailableResult.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/AvailableResult.cs)
+- Each instance of the [`AvailableResult`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/AvailableResult.cs) class represents a time/date result/format that the user can search for.
+- The results/formats are defined in the `AvailableResultsList` class.
+
+### [`AvailableResultsList.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/AvailableResultsList.cs)
+- The [`AvailableResultsList`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/AvailableResultsList.cs) class contains the list of available formats/results in its method `GetList()`.
+
+### [`ResultHelper.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/ResultHelper.cs)
+- The [`ResultHelper`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/ResultHelper.cs) class contains methods for some of the result features (tool tip, copy to clipboard) and the error result on incorrect number input.
+- And it contains the `SelectStringFromResources()` method for getting the resource strings based on the user input.
+ - The method has a parameter for the `stringId` which is the name of the string in the resource file. By default the word `Now` is automatically added at the end to get the string for a system time/date search.
+ - If a different/custom string is needed for a system time/date search the parameter `stringIdNow` can be used to override the default behavior of the method.
+ - If only a string for the system time/date search is required, you can set `stringId` to `string.Empty` and only `stringIdNow` to a valid string id.
+
+### [`TimeAndDateHelper.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeAndDateHelper.cs)
+- The [`TimeAndDateHelper`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeAndDateHelper.cs) class contains methods to format/convert date and time formats/strings.
+
+### [`TimeDateSettings.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeDateSettings.cs)
+- The [`TimeDateSettings`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeDateSettings.cs) class provides access to all optional plugin settings.
+- The class has a static property called `Instance` that holds an instance of the class itself. This allows us to access the settings from everywhere in the plugin code without having additional parameters in our methods.
+
+### [`SearchController.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/SearchController.cs)
+- The [`SearchController`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/SearchController.cs) encapsulates the methods needed to search and find matches.
+
+
+## Search
+
+### Tags
+- We compare the user input with the label of each results. If it doesn't match we search the tags of the result too.
+- For each result two tag strings are defined. One for a search with system time/date and one for a search with a custom time/date. Most of the results (except the era results) are using one of the generic tag lists: Date, Time or Format
+- The selection of the tag (for "system time/date" or "custom time/date") is happening at search time in the `AvailableResultsList.cs` class.
+- The different tags in a list are split by the `;` character.
+
+### Score
+- The plugin uses `FuzzyMatching` to get the matching formats, if the user searches for a specific format. The score is set based on the `FuzzySearch` result.
+- To achieve a better balance between sub title matches and tag matches the score of tag matches is divided by two.
+
+### Match requirements for global queries
+On global queries the high score returned by `FuzzySearch` has negative impacts on the user experience and the search results priority/order of other plugins. To mitigate this we defined some matching requirements:
+- If the query is a word of the following conjunction list, we don't return any results: for, and, nor, but, or, so
+ - We don't have 'yet' (synonym of 'now') on the list, because this could block results in some languages.
+- The first word of the query has to be a full match with a word in the label or tag list.
+- For both requirements we compare case-insensitive.
+
+
+## [Unit Tests](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests)
+We have a [Unit Test project](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests) that executes various test to ensure that the plugin works as expected.
+
+### [`TimeDateResultTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/TimeDateResultTests.cs)
+- The [`TimeDateResultTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/TimeDateResultTests.cs) class contains tests to validate that the time and date values are correctly formatted/calculated.
+- That we can execute the tests at any time on any machine, we use a specified date/time value and set the thread culture always to `en-us` while executing the tests.
+- Some tests contain checks that calculate the expected result at runtime instead of using an expected value written fix in the code. This is done to get valid results on every machine at any time.
+
+### [`ImageTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/ImageTests.cs)
+- The [`ImageTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/ImageTests.cs) class contains tests to validate that each result shows the expected and correct image.
+- That we can execute the tests at any time on any machine, we set the thread culture always to `en-us` while executing the tests.
+
+### [`PluginSettingsTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/PluginSettingsTests.cs)
+- The [`PluginSettingsTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/PluginSettingsTests.cs) class contains tests to validate that all settings exist and that they have the correct default values.
+
+### [`QueryTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/QueryTests.cs)
+- The [`QueryTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/QueryTests.cs) class contains tests to validate that the user gets the correct results when searching.,
+- That we can execute the tests at any time on any machine, we set the thread culture always to `en-us` while executing the tests.
+
+### [`StringParserTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/StringParserTests.cs)
+- The [`StringParserTests.cs`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/StringParserTests.cs) class contains tests to validate that the typed string gets converted correctly into a `DateTime` object.
+- That we can execute the tests at any time on any machine, we set the thread culture always to `en-us` while executing the tests.
\ No newline at end of file
diff --git a/doc/devdocs/modules/launcher/readme.md b/doc/devdocs/modules/launcher/readme.md
index 6d22cc6ed9..b217507ba5 100644
--- a/doc/devdocs/modules/launcher/readme.md
+++ b/doc/devdocs/modules/launcher/readme.md
@@ -11,6 +11,7 @@
- [Program](/doc/devdocs/modules/launcher/plugins/program.md)
- [Registry](/doc/devdocs/modules/launcher/plugins/registry.md)
- [Shell](/doc/devdocs/modules/launcher/plugins/shell.md)
+ - [Time and Date](/doc/devdocs/modules/launcher/plugins/timedate.md)
- [Windows System Commands](/doc/devdocs/modules/launcher/plugins/system.md)
- [Uri](/doc/devdocs/modules/launcher/plugins/uri.md)
- [Window Walker](/doc/devdocs/modules/launcher/plugins/windowwalker.md)
diff --git a/doc/images/launcher/plugins/timedate.png b/doc/images/launcher/plugins/timedate.png
new file mode 100644
index 0000000000..41fa86b369
Binary files /dev/null and b/doc/images/launcher/plugins/timedate.png differ
diff --git a/doc/images/launcher/plugins/timedate2.png b/doc/images/launcher/plugins/timedate2.png
new file mode 100644
index 0000000000..251f4cd9d6
Binary files /dev/null and b/doc/images/launcher/plugins/timedate2.png differ
diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs
index 24adac58a6..dac3cda854 100644
--- a/installer/PowerToysSetup/Product.wxs
+++ b/installer/PowerToysSetup/Product.wxs
@@ -90,7 +90,11 @@
-
+
+
+
+
+
@@ -481,6 +485,10 @@
+
+
+
+
@@ -1094,7 +1102,7 @@
-
+
@@ -1367,6 +1375,12 @@
Guid="$(var.CompGUIDPrefix)17"
Directory="Resource$(var.IdSafeLanguage)UnitConverterPluginFolder">
+
+
+
@@ -1573,6 +1587,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/codeAnalysis/GlobalSuppressions.cs b/src/codeAnalysis/GlobalSuppressions.cs
index 5645159cb3..fc6291173c 100644
--- a/src/codeAnalysis/GlobalSuppressions.cs
+++ b/src/codeAnalysis/GlobalSuppressions.cs
@@ -49,6 +49,10 @@ using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#InternalGetTarget(Microsoft.Win32.SafeHandles.SafeFileHandle)", Justification = "Only used for local generation")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#OpenReparsePoint(System.String,Microsoft.Templates.Core.Locations.JunctionNativeMethods+EFileAccess)", Justification = "Only used for local generation")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Windows.Documents.InlineCollection.Add(System.String)", Scope = "member", Target = "Microsoft.Templates.UI.Extensions.TextBlockExtensions.#OnSequentialFlowStepChanged(System.Windows.DependencyObject,System.Windows.DependencyPropertyChangedEventArgs)", Justification = "No text here")]
+[assembly: SuppressMessage("Globalization", "CA1309:Use ordinal string comparison", Justification = "The user's search term should be compared with culture based rules.", Scope = "type", Target = "~T:Microsoft.PowerToys.Run.Plugin.TimeDate.Components.SearchController")]
// FxCop warning suppression for uninstantiated TestFixture classes
[assembly: SuppressMessage("Microsoft.Performance", "CA1812: Avoid uninstantiated internal classes", Scope = "module", Justification = "CA1812 will be thrown for every file in the test project. This is mentioned here: dotnet/roslyn-analyzers#1830")]
+
+// Code quality
+[assembly: SuppressMessage("CodeQuality", "IDE0076:Invalid global 'SuppressMessageAttribute'", Justification = "Affect predefined supressions.")]
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/ImageTests.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/ImageTests.cs
new file mode 100644
index 0000000000..156bfd9541
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/ImageTests.cs
@@ -0,0 +1,131 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Wox.Infrastructure;
+using Wox.Plugin;
+
+namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
+{
+ [TestClass]
+ public class ImageTests
+ {
+ private CultureInfo originalCulture;
+ private CultureInfo originalUiCulture;
+
+ [TestInitialize]
+ public void Setup()
+ {
+ StringMatcher.Instance = new StringMatcher();
+
+ // Set culture to 'en-us'
+ originalCulture = CultureInfo.CurrentCulture;
+ CultureInfo.CurrentCulture = new CultureInfo("en-us");
+ originalUiCulture = CultureInfo.CurrentUICulture;
+ CultureInfo.CurrentUICulture = new CultureInfo("en-us");
+ }
+
+ [DataTestMethod]
+ [DataRow("time", "Time -", "Images\\time.dark.png")]
+ [DataRow("time u", "Time UTC -", "Images\\time.dark.png")]
+ [DataRow("date", "Date -", "Images\\calendar.dark.png")]
+ [DataRow("now", "Now -", "Images\\timeDate.dark.png")]
+ [DataRow("now u", "Now UTC -", "Images\\timeDate.dark.png")]
+ [DataRow("unix", "Unix epoch time -", "Images\\timeDate.dark.png")]
+ [DataRow("hour", "Hour -", "Images\\time.dark.png")]
+ [DataRow("minute", "Minute -", "Images\\time.dark.png")]
+ [DataRow("second", "Second -", "Images\\time.dark.png")]
+ [DataRow("millisecond", "Millisecond -", "Images\\time.dark.png")]
+ [DataRow("file", "Windows file time (Int64 number) -", "Images\\timeDate.dark.png")]
+ [DataRow("day", "Day (Week day) -", "Images\\calendar.dark.png")]
+ [DataRow("day of week", "Day of the week (Week day) -", "Images\\calendar.dark.png")]
+ [DataRow("day of month", "Day of the month -", "Images\\calendar.dark.png")]
+ [DataRow("day of year", "Day of the year -", "Images\\calendar.dark.png")]
+ [DataRow("week of month", "Week of the month -", "Images\\calendar.dark.png")]
+ [DataRow("week of year", "Week of the year (Calendar week, Week number) -", "Images\\calendar.dark.png")]
+ [DataRow("month", "Month -", "Images\\calendar.dark.png")]
+ [DataRow("month of year", "Month of the year -", "Images\\calendar.dark.png")]
+ [DataRow("month and", "Month and day -", "Images\\calendar.dark.png")]
+ [DataRow("year", "Year -", "Images\\calendar.dark.png")]
+ [DataRow("era", "Era -", "Images\\calendar.dark.png")]
+ [DataRow("era abb", "Era abbreviation -", "Images\\calendar.dark.png")]
+ [DataRow("month and", "Month and year -", "Images\\calendar.dark.png")]
+ [DataRow("universal", "Universal time format: YYYY-MM-DD hh:mm:ss -", "Images\\timeDate.dark.png")]
+ [DataRow("iso", "ISO 8601 -", "Images\\timeDate.dark.png")]
+ [DataRow("iso utc", "ISO 8601 UTC - ", "Images\\timeDate.dark.png")]
+ [DataRow("iso zone", "ISO 8601 with time zone - ", "Images\\timeDate.dark.png")]
+ [DataRow("iso utc zone", "ISO 8601 UTC with time zone - ", "Images\\timeDate.dark.png")]
+ [DataRow("rfc", "RFC1123 -", "Images\\timeDate.dark.png")]
+ public void IconThemeDarkTest(string typedString, string subTitleMatch, string expectedResult)
+ {
+ // Setup
+ Mock main = new ();
+ main.Object.IconTheme = "dark";
+ Query expectedQuery = new ("(" + typedString, "(");
+
+ // Act
+ string result = main.Object.Query(expectedQuery).FirstOrDefault(predicate: x => x.SubTitle.StartsWith(subTitleMatch, System.StringComparison.CurrentCulture)).IcoPath;
+
+ // Assert
+ Assert.AreEqual(expectedResult, result);
+ }
+
+ [DataTestMethod]
+ [DataRow("time", "Time -", "Images\\time.light.png")]
+ [DataRow("time u", "Time UTC -", "Images\\time.light.png")]
+ [DataRow("date", "Date -", "Images\\calendar.light.png")]
+ [DataRow("now", "Now -", "Images\\timeDate.light.png")]
+ [DataRow("now u", "Now UTC -", "Images\\timeDate.light.png")]
+ [DataRow("unix", "Unix epoch time -", "Images\\timeDate.light.png")]
+ [DataRow("hour", "Hour -", "Images\\time.light.png")]
+ [DataRow("minute", "Minute -", "Images\\time.light.png")]
+ [DataRow("second", "Second -", "Images\\time.light.png")]
+ [DataRow("millisecond", "Millisecond -", "Images\\time.light.png")]
+ [DataRow("file", "Windows file time (Int64 number) -", "Images\\timeDate.light.png")]
+ [DataRow("day", "Day (Week day) -", "Images\\calendar.light.png")]
+ [DataRow("day of week", "Day of the week (Week day) -", "Images\\calendar.light.png")]
+ [DataRow("day of month", "Day of the month -", "Images\\calendar.light.png")]
+ [DataRow("day of year", "Day of the year -", "Images\\calendar.light.png")]
+ [DataRow("week of month", "Week of the month -", "Images\\calendar.light.png")]
+ [DataRow("week of year", "Week of the year (Calendar week, Week number) -", "Images\\calendar.light.png")]
+ [DataRow("month", "Month -", "Images\\calendar.light.png")]
+ [DataRow("month of year", "Month of the year -", "Images\\calendar.light.png")]
+ [DataRow("month and", "Month and day -", "Images\\calendar.light.png")]
+ [DataRow("year", "Year -", "Images\\calendar.light.png")]
+ [DataRow("era", "Era -", "Images\\calendar.light.png")]
+ [DataRow("era abb", "Era abbreviation -", "Images\\calendar.light.png")]
+ [DataRow("Month and", "Month and year -", "Images\\calendar.light.png")]
+ [DataRow("universal", "Universal time format: YYYY-MM-DD hh:mm:ss -", "Images\\timeDate.light.png")]
+ [DataRow("iso", "ISO 8601 -", "Images\\timeDate.light.png")]
+ [DataRow("iso utc", "ISO 8601 UTC - ", "Images\\timeDate.light.png")]
+ [DataRow("iso zone", "ISO 8601 with time zone - ", "Images\\timeDate.light.png")]
+ [DataRow("iso utc zone", "ISO 8601 UTC with time zone - ", "Images\\timeDate.light.png")]
+ [DataRow("rfc", "RFC1123 -", "Images\\timeDate.light.png")]
+ public void IconThemeLightTest(string typedString, string subTitleMatch, string expectedResult)
+ {
+ // Setup
+ Mock main = new ();
+ main.Object.IconTheme = "light";
+ Query expectedQuery = new ("(" + typedString, "(");
+
+ // Act
+ var result = main.Object.Query(expectedQuery).FirstOrDefault(x => x.SubTitle.StartsWith(subTitleMatch, System.StringComparison.CurrentCulture)).IcoPath;
+
+ // Assert
+ Assert.AreEqual(expectedResult, result);
+ }
+
+ [TestCleanup]
+ public void CleanUp()
+ {
+ // Set culture to original value
+ CultureInfo.CurrentCulture = originalCulture;
+ CultureInfo.CurrentUICulture = originalUiCulture;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests.csproj b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests.csproj
new file mode 100644
index 0000000000..1930fb1d31
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests.csproj
@@ -0,0 +1,46 @@
+
+
+
+ net6.0-windows
+ false
+ x64
+ Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
+ true
+ true
+ Recommended
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+ GlobalSuppressions.cs
+
+
+ StyleCop.json
+
+
+
+
+ 1.1.118
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/PluginSettingsTests.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/PluginSettingsTests.cs
new file mode 100644
index 0000000000..d87bf92f0a
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/PluginSettingsTests.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Reflection;
+using Microsoft.PowerToys.Run.Plugin.TimeDate.Components;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
+{
+ [TestClass]
+ public class PluginSettingsTests
+ {
+ [TestMethod]
+ public void SettingsCount()
+ {
+ // Setup
+ PropertyInfo[] settings = TimeDateSettings.Instance?.GetType()?.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance);
+
+ // Act
+ var result = settings?.Length;
+
+ // Assert
+ Assert.AreEqual(4, result);
+ }
+
+ [DataTestMethod]
+ [DataRow("OnlyDateTimeNowGlobal")]
+ [DataRow("TimeWithSeconds")]
+ [DataRow("DateWithWeekday")]
+ [DataRow("HideNumberMessageOnGlobalQuery")]
+ public void DoesSettingExist(string name)
+ {
+ // Setup
+ Type settings = TimeDateSettings.Instance?.GetType();
+
+ // Act
+ var result = settings?.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance);
+
+ // Assert
+ Assert.IsNotNull(result);
+ }
+
+ [DataTestMethod]
+ [DataRow("OnlyDateTimeNowGlobal", true)]
+ [DataRow("TimeWithSeconds", false)]
+ [DataRow("DateWithWeekday", false)]
+ [DataRow("HideNumberMessageOnGlobalQuery", false)]
+ public void DefaultValues(string name, bool valueExpected)
+ {
+ // Setup
+ TimeDateSettings setting = TimeDateSettings.Instance;
+
+ // Act
+ PropertyInfo propertyInfo = setting?.GetType()?.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance);
+ var result = propertyInfo?.GetValue(setting);
+
+ // Assert
+ Assert.AreEqual(valueExpected, result);
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/QueryTests.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/QueryTests.cs
new file mode 100644
index 0000000000..98606a638b
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/QueryTests.cs
@@ -0,0 +1,243 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Wox.Infrastructure;
+using Wox.Plugin;
+
+namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
+{
+ [TestClass]
+ public class QueryTests
+ {
+ private CultureInfo originalCulture;
+ private CultureInfo originalUiCulture;
+
+ [TestInitialize]
+ public void Setup()
+ {
+ StringMatcher.Instance = new StringMatcher();
+
+ // Set culture to 'en-us'
+ originalCulture = CultureInfo.CurrentCulture;
+ CultureInfo.CurrentCulture = new CultureInfo("en-us");
+ originalUiCulture = CultureInfo.CurrentUICulture;
+ CultureInfo.CurrentUICulture = new CultureInfo("en-us");
+ }
+
+ [DataTestMethod]
+ [DataRow("time", 2)]
+ [DataRow("date", 2)]
+ [DataRow("now", 3)]
+ [DataRow("current", 3)]
+ [DataRow("year", 0)]
+ [DataRow("", 0)]
+ [DataRow("time::10:10:10", 2)]
+ [DataRow("date::10/10/10", 2)]
+ public void CountWithoutPluginKeyword(string typedString, int expectedResultCount)
+ {
+ // Setup
+ Mock main = new ();
+ Query expectedQuery = new (typedString);
+
+ // Act
+ var result = main.Object.Query(expectedQuery).Count;
+
+ // Assert
+ Assert.AreEqual(expectedResultCount, result, "Result depends on default plugin settings!");
+ }
+
+ [DataTestMethod]
+ [DataRow("(time", 16)]
+ [DataRow("(date", 24)]
+ [DataRow("(year", 7)]
+ [DataRow("(now", 30)]
+ [DataRow("(current", 30)]
+ [DataRow("(", 30)]
+ [DataRow("(now::10:10:10", 1)] // Windows file time
+ [DataRow("(current::10:10:10", 0)]
+ public void CountWithPluginKeyword(string typedString, int expectedResultCount)
+ {
+ // Setup
+ Mock main = new ();
+ Query expectedQuery = new (typedString, "(");
+
+ // Act
+ var result = main.Object.Query(expectedQuery);
+
+ // Assert
+ Assert.AreEqual(expectedResultCount, result.Count, result.ToString());
+ }
+
+ [DataTestMethod]
+ [DataRow("time", 2)] // Match if first word is a full word match
+ [DataRow("ime", 0)] // Don't match if first word is not a full match
+ [DataRow("and", 0)] // Don't match for only conjunctions
+ [DataRow("and time", 1)] // match if term is conjunction and other words
+ [DataRow("date and time", 1)] // Match if first word is a full word match
+ [DataRow("ate and time", 0)] // Don't match if first word is not a full word match
+ public void ValidateBehaviorOnGlobalQueries(string typedString, int expectedResultCount)
+ {
+ // Setup
+ Mock main = new ();
+ Query expectedQuery = new (typedString);
+
+ // Act
+ var result = main.Object.Query(expectedQuery);
+
+ // Assert
+ Assert.AreEqual(expectedResultCount, result.Count, result.ToString());
+ }
+
+ [DataTestMethod]
+ [DataRow("(time", "Time -")]
+ [DataRow("(time u", "Time UTC -")]
+ [DataRow("(date", "Date -")]
+ [DataRow("(now", "Now -")]
+ [DataRow("(now u", "Now UTC -")]
+ [DataRow("(unix", "Unix epoch time -")]
+ [DataRow("(file", "Windows file time (Int64 number) ")]
+ [DataRow("(hour", "Hour -")]
+ [DataRow("(minute", "Minute -")]
+ [DataRow("(second", "Second -")]
+ [DataRow("(millisecond", "Millisecond -")]
+ [DataRow("(day", "Day (Week day) -")]
+ [DataRow("(day of week", "Day of the week (Week day) -")]
+ [DataRow("(day of month", "Day of the month -")]
+ [DataRow("(day of year", "Day of the year -")]
+ [DataRow("(week of month", "Week of the month -")]
+ [DataRow("(week of year", "Week of the year (Calendar week, Week number) -")]
+ [DataRow("(month", "Month -")]
+ [DataRow("(month of year", "Month of the year -")]
+ [DataRow("(month and d", "Month and day -")]
+ [DataRow("(month and y", "Month and year -")]
+ [DataRow("(year", "Year -")]
+ [DataRow("(era", "Era -")]
+ [DataRow("(era a", "Era abbreviation -")]
+ [DataRow("(universal", "Universal time format: YYYY-MM-DD hh:mm:ss -")]
+ [DataRow("(iso", "ISO 8601 -")]
+ [DataRow("(iso utc", "ISO 8601 UTC -")]
+ [DataRow("(iso zone", "ISO 8601 with time zone - ")]
+ [DataRow("(iso utc zone", "ISO 8601 UTC with time zone -")]
+ [DataRow("(rfc", "RFC1123 -")]
+ [DataRow("(time::12:30", "Time -")]
+ [DataRow("(date::10.10.2022", "Date -")]
+ [DataRow("(time::u1646408119", "Time -")]
+ [DataRow("(time::ft637820085517321977", "Time -")]
+ [DataRow("(year", "Era -")]
+ [DataRow("(date", "Era -")]
+ [DataRow("(week day", "Day (Week day) -")]
+ [DataRow("(week day", "Day of the week (Week day) -")]
+ [DataRow("(cal week", "Week of the year (Calendar week, Week number) -")]
+ [DataRow("(week num", "Week of the year (Calendar week, Week number) -")]
+ public void CanFindFormatResult(string typedString, string expectedResult)
+ {
+ // Setup
+ Mock main = new ();
+ Query expectedQuery = new (typedString, "(");
+
+ // Act
+ var result = main.Object.Query(expectedQuery).FirstOrDefault(x => x.SubTitle.StartsWith(expectedResult, StringComparison.CurrentCulture));
+
+ // Assert
+ Assert.IsNotNull(result);
+ Assert.IsTrue(result?.Score >= 1, $"Score: {result?.Score}");
+ }
+
+ [DataTestMethod]
+ [DataRow("(12:30", "Time -")]
+ [DataRow("(10.10.2022", "Date -")]
+ [DataRow("(u1646408119", "Date and time -")]
+ [DataRow("(ft637820085517321977", "Date and time -")]
+ public void DateTimeNumberOnlyInput(string typedString, string expectedResult)
+ {
+ // Setup
+ Mock main = new ();
+ Query expectedQuery = new (typedString, "(");
+
+ // Act
+ var result = main.Object.Query(expectedQuery).FirstOrDefault(x => x.SubTitle.StartsWith(expectedResult, StringComparison.CurrentCulture));
+
+ // Assert
+ Assert.IsNotNull(result);
+ }
+
+ // [DataRow("(::")] -> Behaves different to PT Run user interface
+ // [DataRow("(time::")] -> Behaves different to PT Run user interface
+ // [DataRow("(::time")] -> Behaves different to PT Run user interface
+ [DataTestMethod]
+ [DataRow("(abcdefg")]
+ [DataRow("(timmmmeeee")]
+ [DataRow("(10.10.20.aa.22")]
+ [DataRow("(12::55")]
+ [DataRow("(12:aa:55")]
+ [DataRow("(timtaaaetetaae::u1646408119")]
+ [DataRow("(time:eeee")]
+ [DataRow("(time::eeee")]
+ [DataRow("(time//eeee")]
+ [DataRow("(date::12::55")]
+ [DataRow("(date::12:aa:55")]
+ public void InvalidInputNotShowsResults(string typedString)
+ {
+ // Setup
+ Mock main = new ();
+ Query expectedQuery = new (typedString, "(");
+
+ // Act
+ var result = main.Object.Query(expectedQuery).FirstOrDefault();
+
+ // Assert
+ Assert.IsNull(result, result?.ToString());
+ }
+
+ [DataTestMethod]
+ [DataRow("(ug1646408119")] // Invalid prefix
+ [DataRow("(u9999999999999")] // Unix number + prefix is longer than 12 characters
+ [DataRow("(0123456")] // Missing prefix
+ [DataRow("(ft63782008ab55173dasdas21977")] // Number contains letters
+ public void InvalidNumberInputShowsErrorMessage(string typedString)
+ {
+ // Setup
+ Mock main = new ();
+ Query expectedQuery = new (typedString, "(");
+
+ // Act
+ var result = main.Object.Query(expectedQuery).FirstOrDefault().Title;
+
+ // Assert
+ Assert.IsTrue(result.StartsWith("Error:", StringComparison.CurrentCulture));
+ }
+
+ [DataTestMethod]
+ [DataRow("(ft12..548")] // Number contains punctuation
+ [DataRow("(ft12..54//8")] // Number contains punctuation and other characters
+ [DataRow("(12..54//8")] // Number contains punctuation and other characters
+ [DataRow("(ft::1288gg8888")] // Number contains delimiter and other characters
+ public void InvalidInputNotShowsErrorMessage(string typedString)
+ {
+ // Setup
+ Mock main = new ();
+ Query expectedQuery = new (typedString, "(");
+
+ // Act
+ var result = main.Object.Query(expectedQuery).FirstOrDefault();
+
+ // Assert
+ Assert.IsNull(result);
+ }
+
+ [TestCleanup]
+ public void CleanUp()
+ {
+ // Set culture to original value
+ CultureInfo.CurrentCulture = originalCulture;
+ CultureInfo.CurrentUICulture = originalUiCulture;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/StringParserTests.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/StringParserTests.cs
new file mode 100644
index 0000000000..a31b45c560
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/StringParserTests.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+using System.Threading;
+using Microsoft.PowerToys.Run.Plugin.TimeDate.Components;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
+{
+ [TestClass]
+ public class StringParserTests
+ {
+ private CultureInfo originalCulture;
+ private CultureInfo originalUiCulture;
+
+ [TestInitialize]
+ public void Setup()
+ {
+ // Set culture to 'en-us'
+ originalCulture = CultureInfo.CurrentCulture;
+ CultureInfo.CurrentCulture = new CultureInfo("en-us");
+ originalUiCulture = CultureInfo.CurrentUICulture;
+ CultureInfo.CurrentUICulture = new CultureInfo("en-us");
+ }
+
+ [DataTestMethod]
+ [DataRow("10/29/2022 17:05:10", true, "G", "10/29/2022 5:05:10 PM")]
+ [DataRow("Saturday, October 29, 2022 5:05:10 PM", true, "G", "10/29/2022 5:05:10 PM")]
+ [DataRow("10/29/2022", true, "d", "10/29/2022")]
+ [DataRow("Saturday, October 29, 2022", true, "d", "10/29/2022")]
+ [DataRow("17:05:10", true, "T", "5:05:10 PM")]
+ [DataRow("5:05:10 PM", true, "T", "5:05:10 PM")]
+ [DataRow("10456", false, "", "")]
+ [DataRow("u10456", true, "", "")] // Value is UTC and can be different based on system
+ [DataRow("ft10456", true, "", "")] // Value is UTC and can be different based on system
+ public void ConvertStringToDateTime(string typedString, bool expectedBool, string stringType, string expectedString)
+ {
+ // Act
+ bool boolResult = TimeAndDateHelper.ParseStringAsDateTime(in typedString, out DateTime result);
+
+ // Assert
+ Assert.AreEqual(expectedBool, boolResult);
+ if (!string.IsNullOrEmpty(expectedString))
+ {
+ Assert.AreEqual(expectedString, result.ToString(stringType, CultureInfo.CurrentCulture));
+ }
+ }
+
+ [TestCleanup]
+ public void CleanUp()
+ {
+ // Set culture to original value
+ CultureInfo.CurrentCulture = originalCulture;
+ CultureInfo.CurrentUICulture = originalUiCulture;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/TimeDateResultTests.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/TimeDateResultTests.cs
new file mode 100644
index 0000000000..741a14fe6c
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/TimeDateResultTests.cs
@@ -0,0 +1,313 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+using System.Linq;
+using Microsoft.PowerToys.Run.Plugin.TimeDate.Components;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests
+{
+ [TestClass]
+ public class TimeDateResultTests
+ {
+ private CultureInfo originalCulture;
+ private CultureInfo originalUiCulture;
+
+ [TestInitialize]
+ public void Setup()
+ {
+ // Set culture to 'en-us'
+ originalCulture = CultureInfo.CurrentCulture;
+ CultureInfo.CurrentCulture = new CultureInfo("en-us");
+ originalUiCulture = CultureInfo.CurrentUICulture;
+ CultureInfo.CurrentUICulture = new CultureInfo("en-us");
+ }
+
+ [DataTestMethod]
+ [DataRow("time", "10:30 AM")]
+ [DataRow("date", "3/2/2022")]
+ [DataRow("date and time", "3/2/2022 10:30 AM")]
+ [DataRow("hour", "10")]
+ [DataRow("minute", "30")]
+ [DataRow("second", "45")]
+ [DataRow("millisecond", "0")]
+ [DataRow("day (week day)", "Wednesday")]
+ [DataRow("day of the week (week day)", "4")]
+ [DataRow("day of the month", "2")]
+ [DataRow("day of the year", "61")]
+ [DataRow("week of the month", "1")]
+ [DataRow("week of the year (calendar week, week number)", "10")]
+ [DataRow("month", "March")]
+ [DataRow("month of the year", "3")]
+ [DataRow("month and day", "March 2")]
+ [DataRow("year", "2022")]
+ [DataRow("month and year", "March 2022")]
+ [DataRow("ISO 8601", "2022-03-02T10:30:45")]
+ [DataRow("ISO 8601 with time zone", "2022-03-02T10:30:45")]
+ [DataRow("RFC1123", "Wed, 02 Mar 2022 10:30:45 GMT")]
+ public void LocalFormatsWithShortTimeAndShortDate(string formatLabel, string expectedResult)
+ {
+ // Setup
+ var helperResults = AvailableResultsList.GetList(true, false, false, new DateTime(2022, 03, 02, 10, 30, 45));
+
+ // Act
+ var result = helperResults.FirstOrDefault(x => x.Label.Equals(formatLabel, StringComparison.OrdinalIgnoreCase));
+
+ // Assert
+ Assert.AreEqual(expectedResult, result?.Value, $"Culture {CultureInfo.CurrentCulture.Name}, Culture UI: {CultureInfo.CurrentUICulture.Name}, Calendar: {CultureInfo.CurrentCulture.Calendar}, Region: {RegionInfo.CurrentRegion.Name}");
+ }
+
+ [DataTestMethod]
+ [DataRow("time", "10:30 AM")]
+ [DataRow("date", "Wednesday, March 2, 2022")]
+ [DataRow("date and time", "Wednesday, March 2, 2022 10:30 AM")]
+ [DataRow("hour", "10")]
+ [DataRow("minute", "30")]
+ [DataRow("second", "45")]
+ [DataRow("millisecond", "0")]
+ [DataRow("day (week day)", "Wednesday")]
+ [DataRow("day of the week (week day)", "4")]
+ [DataRow("day of the month", "2")]
+ [DataRow("day of the year", "61")]
+ [DataRow("week of the month", "1")]
+ [DataRow("week of the year (calendar week, week number)", "10")]
+ [DataRow("month", "March")]
+ [DataRow("month of the year", "3")]
+ [DataRow("month and day", "March 2")]
+ [DataRow("year", "2022")]
+ [DataRow("month and year", "March 2022")]
+ [DataRow("ISO 8601", "2022-03-02T10:30:45")]
+ [DataRow("ISO 8601 with time zone", "2022-03-02T10:30:45")]
+ [DataRow("RFC1123", "Wed, 02 Mar 2022 10:30:45 GMT")]
+ public void LocalFormatsWithShortTimeAndLongDate(string formatLabel, string expectedResult)
+ {
+ // Setup
+ var helperResults = AvailableResultsList.GetList(true, false, true, new DateTime(2022, 03, 02, 10, 30, 45));
+
+ // Act
+ var result = helperResults.FirstOrDefault(x => x.Label.Equals(formatLabel, StringComparison.OrdinalIgnoreCase));
+
+ // Assert
+ Assert.AreEqual(expectedResult, result?.Value);
+ }
+
+ [DataTestMethod]
+ [DataRow("time", "10:30:45 AM")]
+ [DataRow("date", "3/2/2022")]
+ [DataRow("date and time", "3/2/2022 10:30:45 AM")]
+ [DataRow("hour", "10")]
+ [DataRow("minute", "30")]
+ [DataRow("second", "45")]
+ [DataRow("millisecond", "0")]
+ [DataRow("day (week day)", "Wednesday")]
+ [DataRow("day of the week (week day)", "4")]
+ [DataRow("day of the month", "2")]
+ [DataRow("day of the year", "61")]
+ [DataRow("week of the month", "1")]
+ [DataRow("week of the year (calendar week, week number)", "10")]
+ [DataRow("month", "March")]
+ [DataRow("month of the year", "3")]
+ [DataRow("month and day", "March 2")]
+ [DataRow("year", "2022")]
+ [DataRow("month and year", "March 2022")]
+ [DataRow("ISO 8601", "2022-03-02T10:30:45")]
+ [DataRow("ISO 8601 with time zone", "2022-03-02T10:30:45")]
+ [DataRow("RFC1123", "Wed, 02 Mar 2022 10:30:45 GMT")]
+ public void LocalFormatsWithLongTimeAndShortDate(string formatLabel, string expectedResult)
+ {
+ // Setup
+ var helperResults = AvailableResultsList.GetList(true, true, false, new DateTime(2022, 03, 02, 10, 30, 45));
+
+ // Act
+ var result = helperResults.FirstOrDefault(x => x.Label.Equals(formatLabel, StringComparison.OrdinalIgnoreCase));
+
+ // Assert
+ Assert.AreEqual(expectedResult, result?.Value);
+ }
+
+ [DataTestMethod]
+ [DataRow("time", "10:30:45 AM")]
+ [DataRow("date", "Wednesday, March 2, 2022")]
+ [DataRow("date and time", "Wednesday, March 2, 2022 10:30:45 AM")]
+ [DataRow("hour", "10")]
+ [DataRow("minute", "30")]
+ [DataRow("second", "45")]
+ [DataRow("millisecond", "0")]
+ [DataRow("day (week day)", "Wednesday")]
+ [DataRow("day of the week (week day)", "4")]
+ [DataRow("day of the month", "2")]
+ [DataRow("day of the year", "61")]
+ [DataRow("week of the month", "1")]
+ [DataRow("week of the year (calendar week, week number)", "10")]
+ [DataRow("month", "March")]
+ [DataRow("month of the year", "3")]
+ [DataRow("month and day", "March 2")]
+ [DataRow("year", "2022")]
+ [DataRow("month and year", "March 2022")]
+ [DataRow("ISO 8601", "2022-03-02T10:30:45")]
+ [DataRow("ISO 8601 with time zone", "2022-03-02T10:30:45")]
+ [DataRow("RFC1123", "Wed, 02 Mar 2022 10:30:45 GMT")]
+ public void LocalFormatsWithLongTimeAndLongDate(string formatLabel, string expectedResult)
+ {
+ // Setup
+ var helperResults = AvailableResultsList.GetList(true, true, true, new DateTime(2022, 03, 02, 10, 30, 45));
+
+ // Act
+ var result = helperResults.FirstOrDefault(x => x.Label.Equals(formatLabel, StringComparison.OrdinalIgnoreCase));
+
+ // Assert
+ Assert.AreEqual(expectedResult, result?.Value);
+ }
+
+ [DataTestMethod]
+ [DataRow("time utc", "t")]
+ [DataRow("date and time utc", "g")]
+ [DataRow("ISO 8601 UTC", "yyyy-MM-ddTHH:mm:ss")]
+ [DataRow("ISO 8601 UTC with time zone", "yyyy-MM-ddTHH:mm:ss'Z'")]
+ [DataRow("Universal time format: YYYY-MM-DD hh:mm:ss", "u")]
+ public void UtcFormatsWithShortTimeAndShortDate(string formatLabel, string expectedFormat)
+ {
+ // Setup
+ var helperResults = AvailableResultsList.GetList(true, false, false, new DateTime(2022, 03, 02, 10, 30, 45));
+ var expectedResult = new DateTime(2022, 03, 02, 10, 30, 45).ToUniversalTime().ToString(expectedFormat, CultureInfo.CurrentCulture);
+
+ // Act
+ var result = helperResults.FirstOrDefault(x => x.Label.Equals(formatLabel, StringComparison.OrdinalIgnoreCase));
+
+ // Assert
+ Assert.AreEqual(expectedResult, result?.Value);
+ }
+
+ [DataTestMethod]
+ [DataRow("time utc", "t")]
+ [DataRow("date and time utc", "f")]
+ [DataRow("ISO 8601 UTC", "yyyy-MM-ddTHH:mm:ss")]
+ [DataRow("ISO 8601 UTC with time zone", "yyyy-MM-ddTHH:mm:ss'Z'")]
+ [DataRow("Universal time format: YYYY-MM-DD hh:mm:ss", "u")]
+ public void UtcFormatsWithShortTimeAndLongDate(string formatLabel, string expectedFormat)
+ {
+ // Setup
+ var helperResults = AvailableResultsList.GetList(true, false, true, new DateTime(2022, 03, 02, 10, 30, 45));
+ var expectedResult = new DateTime(2022, 03, 02, 10, 30, 45).ToUniversalTime().ToString(expectedFormat, CultureInfo.CurrentCulture);
+
+ // Act
+ var result = helperResults.FirstOrDefault(x => x.Label.Equals(formatLabel, StringComparison.OrdinalIgnoreCase));
+
+ // Assert
+ Assert.AreEqual(expectedResult, result?.Value);
+ }
+
+ [DataTestMethod]
+ [DataRow("time utc", "T")]
+ [DataRow("date and time utc", "G")]
+ [DataRow("ISO 8601 UTC", "yyyy-MM-ddTHH:mm:ss")]
+ [DataRow("ISO 8601 UTC with time zone", "yyyy-MM-ddTHH:mm:ss'Z'")]
+ [DataRow("Universal time format: YYYY-MM-DD hh:mm:ss", "u")]
+ public void UtcFormatsWithLongTimeAndShortDate(string formatLabel, string expectedFormat)
+ {
+ // Setup
+ var helperResults = AvailableResultsList.GetList(true, true, false, new DateTime(2022, 03, 02, 10, 30, 45));
+ var expectedResult = new DateTime(2022, 03, 02, 10, 30, 45).ToUniversalTime().ToString(expectedFormat, CultureInfo.CurrentCulture);
+
+ // Act
+ var result = helperResults.FirstOrDefault(x => x.Label.Equals(formatLabel, StringComparison.OrdinalIgnoreCase));
+
+ // Assert
+ Assert.AreEqual(expectedResult, result?.Value);
+ }
+
+ [DataTestMethod]
+ [DataRow("time utc", "T")]
+ [DataRow("date and time utc", "F")]
+ [DataRow("ISO 8601 UTC", "yyyy-MM-ddTHH:mm:ss")]
+ [DataRow("ISO 8601 UTC with time zone", "yyyy-MM-ddTHH:mm:ss'Z'")]
+ [DataRow("Universal time format: YYYY-MM-DD hh:mm:ss", "u")]
+ public void UtcFormatsWithLongTimeAndLongDate(string formatLabel, string expectedFormat)
+ {
+ // Setup
+ var helperResults = AvailableResultsList.GetList(true, true, true, new DateTime(2022, 03, 02, 10, 30, 45));
+ var expectedResult = new DateTime(2022, 03, 02, 10, 30, 45).ToUniversalTime().ToString(expectedFormat, CultureInfo.CurrentCulture);
+
+ // Act
+ var result = helperResults.FirstOrDefault(x => x.Label.Equals(formatLabel, StringComparison.OrdinalIgnoreCase));
+
+ // Assert
+ Assert.AreEqual(expectedResult, result?.Value);
+ }
+
+ [TestMethod]
+ public void UnixTimestampFormat()
+ {
+ // Setup
+ string formatLabel = "Unix epoch time";
+ DateTime timeValue = DateTime.Now.ToUniversalTime();
+ var helperResults = AvailableResultsList.GetList(true, false, false, timeValue);
+ var expectedResult = (long)timeValue.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
+
+ // Act
+ var result = helperResults.FirstOrDefault(x => x.Label.Equals(formatLabel, StringComparison.OrdinalIgnoreCase));
+
+ // Assert
+ Assert.AreEqual(expectedResult.ToString(CultureInfo.CurrentCulture), result?.Value);
+ }
+
+ [TestMethod]
+ public void WindowsFileTimeFormat()
+ {
+ // Setup
+ string formatLabel = "Windows file time (Int64 number)";
+ DateTime timeValue = DateTime.Now;
+ var helperResults = AvailableResultsList.GetList(true, false, false, timeValue);
+ var expectedResult = timeValue.Ticks.ToString(CultureInfo.CurrentCulture);
+
+ // Act
+ var result = helperResults.FirstOrDefault(x => x.Label.Equals(formatLabel, StringComparison.OrdinalIgnoreCase));
+
+ // Assert
+ Assert.AreEqual(expectedResult, result?.Value);
+ }
+
+ [TestMethod]
+ public void ValidateEraResult()
+ {
+ // Setup
+ string formatLabel = "Era";
+ DateTime timeValue = DateTime.Now;
+ var helperResults = AvailableResultsList.GetList(true, false, false, timeValue);
+ var expectedResult = DateTimeFormatInfo.CurrentInfo.GetEraName(CultureInfo.CurrentCulture.Calendar.GetEra(timeValue));
+
+ // Act
+ var result = helperResults.FirstOrDefault(x => x.Label.Equals(formatLabel, StringComparison.OrdinalIgnoreCase));
+
+ // Assert
+ Assert.AreEqual(expectedResult, result?.Value);
+ }
+
+ [TestMethod]
+ public void ValidateEraAbbreviationResult()
+ {
+ // Setup
+ string formatLabel = "Era abbreviation";
+ DateTime timeValue = DateTime.Now;
+ var helperResults = AvailableResultsList.GetList(true, false, false, timeValue);
+ var expectedResult = DateTimeFormatInfo.CurrentInfo.GetAbbreviatedEraName(CultureInfo.CurrentCulture.Calendar.GetEra(timeValue));
+
+ // Act
+ var result = helperResults.FirstOrDefault(x => x.Label.Equals(formatLabel, StringComparison.OrdinalIgnoreCase));
+
+ // Assert
+ Assert.AreEqual(expectedResult, result?.Value);
+ }
+
+ [TestCleanup]
+ public void CleanUp()
+ {
+ // Set culture to original value
+ CultureInfo.CurrentCulture = originalCulture;
+ CultureInfo.CurrentUICulture = originalUiCulture;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/AvailableResult.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/AvailableResult.cs
new file mode 100644
index 0000000000..2741de6e51
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/AvailableResult.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests")]
+
+namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
+{
+ internal class AvailableResult
+ {
+ ///
+ /// Gets or sets the time/date value
+ ///
+ internal string Value { get; set; }
+
+ ///
+ /// Gets or sets the text used for the subtitle and as search term
+ ///
+ internal string Label { get; set; }
+
+ ///
+ /// Gets or sets an an alternative search tag that will be evaluated if label doesn't match. For example we like to show the era on searches for 'year' too.
+ ///
+ internal string AlternativeSearchTag { get; set; }
+
+ ///
+ /// Gets or sets a value indicating the type of result
+ ///
+ internal ResultIconType IconType { get; set; }
+
+ ///
+ /// Returns the path to the icon
+ ///
+ /// Theme
+ /// Path
+ internal string GetIconPath(string theme)
+ {
+ return IconType switch
+ {
+ ResultIconType.Time => $"Images\\time.{theme}.png",
+ ResultIconType.Date => $"Images\\calendar.{theme}.png",
+ ResultIconType.DateTime => $"Images\\timeDate.{theme}.png",
+ _ => string.Empty,
+ };
+ }
+ }
+
+ internal enum ResultIconType
+ {
+ Time,
+ Date,
+ DateTime,
+ }
+}
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/AvailableResultsList.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/AvailableResultsList.cs
new file mode 100644
index 0000000000..2dfc374485
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/AvailableResultsList.cs
@@ -0,0 +1,267 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using Microsoft.PowerToys.Run.Plugin.TimeDate.Properties;
+
+namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
+{
+ internal class AvailableResultsList
+ {
+ ///
+ /// Returns a list with all available date time formats
+ ///
+ /// Is this a search with plugin activation keyword or not
+ /// Required for UnitTest: Show time in long format
+ /// Required for UnitTest: Show date in long format
+ /// Required for UnitTest: Use custom object to calculate results
+ /// List of results
+ internal static List GetList(bool isKeywordSearch, bool? timeLong = null, bool? dateLong = null, DateTime? timestamp = null)
+ {
+ List results = new List();
+ bool timeExtended = timeLong ?? TimeDateSettings.Instance.TimeWithSeconds;
+ bool dateExtended = dateLong ?? TimeDateSettings.Instance.DateWithWeekday;
+ bool isSystemDateTime = timestamp == null;
+ Calendar calendar = CultureInfo.CurrentCulture.Calendar;
+ DateTime dateTimeNow = timestamp ?? DateTime.Now;
+ DateTime dateTimeNowUtc = dateTimeNow.ToUniversalTime();
+
+ results.AddRange(new[]
+ {
+ // This range is reserved for the following three results: Time, Date, Now
+ // Don't add any new result in this range! For new results, please use the next range.
+ new AvailableResult()
+ {
+ Value = dateTimeNow.ToString(TimeAndDateHelper.GetStringFormat(FormatStringType.Time, timeExtended, dateExtended), CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_Time,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, string.Empty, "Microsoft_plugin_timedate_SearchTagTimeNow"),
+ IconType = ResultIconType.Time,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.ToString(TimeAndDateHelper.GetStringFormat(FormatStringType.Date, timeExtended, dateExtended), CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_Date,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, string.Empty, "Microsoft_plugin_timedate_SearchTagDateNow"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.ToString(TimeAndDateHelper.GetStringFormat(FormatStringType.DateTime, timeExtended, dateExtended), CultureInfo.CurrentCulture),
+ Label = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_DateAndTime", "Microsoft_plugin_timedate_Now"),
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
+ IconType = ResultIconType.DateTime,
+ },
+ });
+
+ if (isKeywordSearch || !TimeDateSettings.Instance.OnlyDateTimeNowGlobal)
+ {
+ // We use long instead of int for unix time stamp because int ist to small after 03:14:07 UTC 2038-01-19
+ long unixTimestamp = (long)dateTimeNowUtc.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
+ int weekOfYear = calendar.GetWeekOfYear(dateTimeNow, DateTimeFormatInfo.CurrentInfo.CalendarWeekRule, DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek);
+ string era = DateTimeFormatInfo.CurrentInfo.GetEraName(calendar.GetEra(dateTimeNow));
+ string eraShort = DateTimeFormatInfo.CurrentInfo.GetAbbreviatedEraName(calendar.GetEra(dateTimeNow));
+
+ results.AddRange(new[]
+ {
+ new AvailableResult()
+ {
+ Value = dateTimeNowUtc.ToString(TimeAndDateHelper.GetStringFormat(FormatStringType.Time, timeExtended, dateExtended), CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_TimeUtc,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, string.Empty, "Microsoft_plugin_timedate_SearchTagTimeNow"),
+ IconType = ResultIconType.Time,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNowUtc.ToString(TimeAndDateHelper.GetStringFormat(FormatStringType.DateTime, timeExtended, dateExtended), CultureInfo.CurrentCulture),
+ Label = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_DateAndTimeUtc", "Microsoft_plugin_timedate_NowUtc"),
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
+ IconType = ResultIconType.DateTime,
+ },
+ new AvailableResult()
+ {
+ Value = unixTimestamp.ToString(CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_Unix,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
+ IconType = ResultIconType.DateTime,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.Hour.ToString(CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_Hour,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagTime"),
+ IconType = ResultIconType.Time,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.Minute.ToString(CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_Minute,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagTime"),
+ IconType = ResultIconType.Time,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.Second.ToString(CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_Second,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagTime"),
+ IconType = ResultIconType.Time,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.Millisecond.ToString(CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_Millisecond,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagTime"),
+ IconType = ResultIconType.Time,
+ },
+ new AvailableResult()
+ {
+ Value = DateTimeFormatInfo.CurrentInfo.GetDayName(dateTimeNow.DayOfWeek),
+ Label = Resources.Microsoft_plugin_timedate_Day,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = TimeAndDateHelper.GetNumberOfDayInWeek(dateTimeNow).ToString(CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_DayOfWeek,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.Day.ToString(CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_DayOfMonth,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.DayOfYear.ToString(CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_DayOfYear,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = TimeAndDateHelper.GetWeekOfMonth(dateTimeNow).ToString(CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_WeekOfMonth,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = weekOfYear.ToString(CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_WeekOfYear,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = DateTimeFormatInfo.CurrentInfo.GetMonthName(dateTimeNow.Month),
+ Label = Resources.Microsoft_plugin_timedate_Month,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.Month.ToString(CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_MonthOfYear,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.ToString("M", CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_DayMonth,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = calendar.GetYear(dateTimeNow).ToString(CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_Year,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = era,
+ Label = Resources.Microsoft_plugin_timedate_Era,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagEra"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = era != eraShort ? eraShort : string.Empty, // Setting value to empty string if 'era == eraShort'. This result will be filtered later.
+ Label = Resources.Microsoft_plugin_timedate_EraAbbreviation,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagEra"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.ToString("Y", CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_MonthYear,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
+ IconType = ResultIconType.Date,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.Ticks.ToString(CultureInfo.CurrentCulture),
+ Label = Resources.Microsoft_plugin_timedate_WindowsFileTime,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
+ IconType = ResultIconType.DateTime,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNowUtc.ToString("u"),
+ Label = Resources.Microsoft_plugin_timedate_UniversalTime,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
+ IconType = ResultIconType.DateTime,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.ToString("s"),
+ Label = Resources.Microsoft_plugin_timedate_Iso8601,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
+ IconType = ResultIconType.DateTime,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNowUtc.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture),
+ Label = Resources.Microsoft_plugin_timedate_Iso8601Utc,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
+ IconType = ResultIconType.DateTime,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.ToString("yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture),
+ Label = Resources.Microsoft_plugin_timedate_Iso8601Zone,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
+ IconType = ResultIconType.DateTime,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNowUtc.ToString("yyyy-MM-ddTHH:mm:ss'Z'", CultureInfo.InvariantCulture),
+ Label = Resources.Microsoft_plugin_timedate_Iso8601ZoneUtc,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
+ IconType = ResultIconType.DateTime,
+ },
+ new AvailableResult()
+ {
+ Value = dateTimeNow.ToString("R"),
+ Label = Resources.Microsoft_plugin_timedate_Rfc1123,
+ AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
+ IconType = ResultIconType.DateTime,
+ },
+ });
+ }
+
+ // Return only results where value is not empty
+ // This can happen, for example, when we can't read the 'era' or when 'era == era abbreviation' and we set value explicitly to an empty string.
+ return results.Where(x => !string.IsNullOrEmpty(x.Value)).ToList();
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/ResultHelper.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/ResultHelper.cs
new file mode 100644
index 0000000000..a2f3dfe6af
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/ResultHelper.cs
@@ -0,0 +1,92 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Windows;
+using Microsoft.PowerToys.Run.Plugin.TimeDate.Properties;
+using Wox.Plugin;
+using Wox.Plugin.Logger;
+
+[assembly: InternalsVisibleTo("Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests")]
+
+namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
+{
+ internal static class ResultHelper
+ {
+ ///
+ /// Get the string based on the requested type
+ ///
+ /// Does the user search for system date/time?
+ /// Id of the string. (Example: `MyString` for `MyString` and `MyStringNow`)
+ /// Optional string id for now case
+ /// The string from the resource file, or otherwise.
+ internal static string SelectStringFromResources(bool isSystemTimeDate, string stringId, string stringIdNow = default)
+ {
+ if (!isSystemTimeDate)
+ {
+ return Resources.ResourceManager.GetString(stringId, CultureInfo.CurrentUICulture) ?? string.Empty;
+ }
+ else if (!string.IsNullOrEmpty(stringIdNow))
+ {
+ return Resources.ResourceManager.GetString(stringIdNow, CultureInfo.CurrentUICulture) ?? string.Empty;
+ }
+ else
+ {
+ return Resources.ResourceManager.GetString(stringId + "Now", CultureInfo.CurrentUICulture) ?? string.Empty;
+ }
+ }
+
+ ///
+ /// Copy the given text to the clipboard
+ ///
+ /// The text to copy to the clipboard
+ /// The text successful copy to the clipboard, otherwise
+ /// Code copied from TimeZone plugin
+ internal static bool CopyToClipBoard(in string text)
+ {
+ try
+ {
+ Clipboard.Clear();
+ Clipboard.SetText(text);
+ return true;
+ }
+ catch (Exception exception)
+ {
+ Log.Exception("Can't copy to clipboard", exception, typeof(ResultHelper));
+ return false;
+ }
+ }
+
+ ///
+ /// Create a tool tip for the alternative search tags
+ ///
+ /// The .
+ /// New object or null if is empty.
+ internal static ToolTipData GetSearchTagToolTip(AvailableResult result, out Visibility visibility)
+ {
+ switch (string.IsNullOrEmpty(result.AlternativeSearchTag))
+ {
+ case true:
+ visibility = Visibility.Hidden;
+ return null;
+ default:
+ visibility = Visibility.Visible;
+ return new ToolTipData(Resources.Microsoft_plugin_timedate_ToolTipAlternativeSearchTag, result.AlternativeSearchTag);
+ }
+ }
+
+ ///
+ /// Gets a result with an error message that only numbers can't be parsed
+ ///
+ /// Element of type .
+ internal static Result CreateNumberErrorResult(string theme) => new Result()
+ {
+ Title = Resources.Microsoft_plugin_timedate_ErrorResultTitle,
+ SubTitle = Resources.Microsoft_plugin_timedate_ErrorResultSubTitle,
+ IcoPath = $"Images\\Warning.{theme}.png",
+ };
+ }
+}
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/SearchController.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/SearchController.cs
new file mode 100644
index 0000000000..fef883818f
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/SearchController.cs
@@ -0,0 +1,182 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Windows;
+using Microsoft.PowerToys.Run.Plugin.TimeDate.Properties;
+using Wox.Infrastructure;
+using Wox.Plugin;
+
+namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
+{
+ ///
+ /// SearchController: Class tot hold the search method that filter available date time formats
+ /// Extra class to simplify code in class
+ ///
+ internal static class SearchController
+ {
+ ///
+ /// Var that holds the delimiter between format and date
+ ///
+ private const string InputDelimiter = "::";
+
+ ///
+ /// A list of conjunctions that we ignore on search
+ ///
+ private static readonly string[] _conjunctionList = Resources.Microsoft_plugin_timedate_Search_ConjunctionList.Split("; ");
+
+ ///
+ /// Searches for results
+ ///
+ /// Search query object
+ /// List of Wox s.
+ internal static List ExecuteSearch(Query query, string iconTheme)
+ {
+ List availableFormats = new List();
+ List results = new List();
+ bool isKeywordSearch = !string.IsNullOrEmpty(query.ActionKeyword);
+ bool isEmptySearchInput = string.IsNullOrEmpty(query.Search);
+ string searchTerm = query.Search;
+
+ // Empty search without keyword => return no results
+ if (!isKeywordSearch && isEmptySearchInput)
+ {
+ return results;
+ }
+
+ // Conjunction search without keyword => return no results
+ // (This improves the results on global queries.)
+ if (!isKeywordSearch && _conjunctionList.Any(x => x.Equals(searchTerm, StringComparison.CurrentCultureIgnoreCase)))
+ {
+ return results;
+ }
+
+ // Switch search type
+ if (isEmptySearchInput)
+ {
+ // Return all results for system time/date on empty keyword search
+ availableFormats.AddRange(AvailableResultsList.GetList(isKeywordSearch));
+ }
+ else if (Regex.IsMatch(searchTerm, @".+" + Regex.Escape(InputDelimiter) + @".+"))
+ {
+ // Search for specified format with specified time/date value
+ var userInput = searchTerm.Split(InputDelimiter);
+ if (TimeAndDateHelper.ParseStringAsDateTime(userInput[1], out DateTime timestamp))
+ {
+ availableFormats.AddRange(AvailableResultsList.GetList(isKeywordSearch, null, null, timestamp));
+ searchTerm = userInput[0];
+ }
+ }
+ else if (TimeAndDateHelper.ParseStringAsDateTime(searchTerm, out DateTime timestamp))
+ {
+ // Return all formats for specified time/date value
+ availableFormats.AddRange(AvailableResultsList.GetList(isKeywordSearch, null, null, timestamp));
+ searchTerm = string.Empty;
+ }
+ else
+ {
+ // Search for specified format with system time/date
+ availableFormats.AddRange(AvailableResultsList.GetList(isKeywordSearch));
+ }
+
+ // Check searchTerm after getting results to select type of result list
+ if (string.IsNullOrEmpty(searchTerm))
+ {
+ // Generate list with all results
+ foreach (var f in availableFormats)
+ {
+ results.Add(new Result
+ {
+ Title = f.Value,
+ SubTitle = $"{f.Label} - {Resources.Microsoft_plugin_timedate_SubTitleNote}",
+ ToolTipData = ResultHelper.GetSearchTagToolTip(f, out Visibility v),
+ ToolTipVisibility = v,
+ IcoPath = f.GetIconPath(iconTheme),
+ Action = _ => ResultHelper.CopyToClipBoard(f.Value),
+ ContextData = f,
+ });
+ }
+ }
+ else
+ {
+ // Generate filtered list of results
+ foreach (var f in availableFormats)
+ {
+ var resultMatchScore = GetMatchScore(searchTerm, f.Label, f.AlternativeSearchTag, !isKeywordSearch);
+
+ if (resultMatchScore > 0)
+ {
+ results.Add(new Result
+ {
+ Title = f.Value,
+ SubTitle = $"{f.Label} - {Resources.Microsoft_plugin_timedate_SubTitleNote}",
+ ToolTipData = ResultHelper.GetSearchTagToolTip(f, out Visibility v),
+ ToolTipVisibility = v,
+ IcoPath = f.GetIconPath(iconTheme),
+ Action = _ => ResultHelper.CopyToClipBoard(f.Value),
+ Score = resultMatchScore,
+ ContextData = f,
+ });
+ }
+ }
+ }
+
+ // If search term is only a number that can't be parsed return an error message
+ if (!isEmptySearchInput && results.Count == 0 && searchTerm.Any(char.IsNumber) && Regex.IsMatch(searchTerm, @"\w+\d+$") &&
+ !searchTerm.Contains(InputDelimiter) && !searchTerm.Any(char.IsWhiteSpace) && !searchTerm.Any(char.IsPunctuation))
+ {
+ // Without plugin key word show only if message is not hidden by setting
+ if (isKeywordSearch || !TimeDateSettings.Instance.HideNumberMessageOnGlobalQuery)
+ {
+ results.Add(ResultHelper.CreateNumberErrorResult(iconTheme));
+ }
+ }
+
+ return results;
+ }
+
+ ///
+ /// Checks the format for a match with the user query and returns the score.
+ ///
+ /// The user query.
+ /// The label of the format.
+ /// The search tag list as string.
+ /// Is this a global search?
+ /// The score for the result.
+ private static int GetMatchScore(string query, string label, string tags, bool isGlobalSearch)
+ {
+ // The query is global and the first word don't match any word in the label or tags => Return score of zero
+ if (isGlobalSearch)
+ {
+ char[] chars = new char[] { ' ', ',', ';', '(', ')' };
+ string queryFirstWord = query.Split(chars)[0];
+ string[] words = $"{label} {tags}".Split(chars);
+
+ if (!words.Any(x => x.Trim().Equals(queryFirstWord, StringComparison.CurrentCultureIgnoreCase)))
+ {
+ return 0;
+ }
+ }
+
+ // Get match for label (or for tags if label score is <1)
+ int score = StringMatcher.FuzzySearch(query, label).Score;
+ if (score < 1)
+ {
+ foreach (string t in tags.Split(";"))
+ {
+ var tagScore = StringMatcher.FuzzySearch(query, t.Trim()).Score / 2;
+ if (tagScore > score)
+ {
+ score = tagScore / 2;
+ }
+ }
+ }
+
+ return score;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeAndDateHelper.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeAndDateHelper.cs
new file mode 100644
index 0000000000..a066331455
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeAndDateHelper.cs
@@ -0,0 +1,129 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Text.RegularExpressions;
+
+[assembly: InternalsVisibleTo("Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests")]
+
+namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
+{
+ internal static class TimeAndDateHelper
+ {
+ ///
+ /// Get the format for the time string
+ ///
+ /// Type of format
+ /// Show date with weekday and name of month (long format)
+ /// Show time with seconds (long format)
+ /// String that identifies the time/date format ()
+ internal static string GetStringFormat(FormatStringType targetFormat, bool timeLong, bool dateLong)
+ {
+ switch (targetFormat)
+ {
+ case FormatStringType.Time:
+ return timeLong ? "T" : "t";
+ case FormatStringType.Date:
+ return dateLong ? "D" : "d";
+ case FormatStringType.DateTime:
+ if (timeLong & dateLong)
+ {
+ return "F"; // Friday, October 31, 2008 5:04:32 PM
+ }
+ else if (timeLong & !dateLong)
+ {
+ return "G"; // 10/31/2008 5:04:32 PM
+ }
+ else if (!timeLong & dateLong)
+ {
+ return "f"; // Friday, October 31, 2008 5:04 PM
+ }
+ else
+ {
+ // (!timeLong & !dateLong)
+ return "g"; // 10/31/2008 5:04 PM
+ }
+
+ default:
+ return string.Empty; // Windows default based on current culture settings
+ }
+ }
+
+ ///
+ /// Returns the number week in the month (Used code from 'David Morton' from )
+ ///
+ /// date
+ /// Number of week in the month
+ internal static int GetWeekOfMonth(DateTime date)
+ {
+ DateTime beginningOfMonth = new DateTime(date.Year, date.Month, 1);
+
+ while (date.Date.AddDays(1).DayOfWeek != CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek)
+ {
+ date = date.AddDays(1);
+ }
+
+ return (int)Math.Truncate((double)date.Subtract(beginningOfMonth).TotalDays / 7f) + 1;
+ }
+
+ ///
+ /// Returns the number of the day in the week
+ ///
+ /// Date
+ /// Number of the day in the week
+ internal static int GetNumberOfDayInWeek(DateTime date)
+ {
+ int daysInWeek = 7;
+ int adjustment = 1; // We count from 1 to 7 and not from 0 to 6
+ int formatSettingFirstDayOfWeek = (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
+
+ return ((int)(date.DayOfWeek + daysInWeek - formatSettingFirstDayOfWeek) % daysInWeek) + adjustment;
+ }
+
+ ///
+ /// Convert input string to a object in local time
+ ///
+ /// String with date/time
+ /// The new object
+ /// True on success, otherwise false
+ internal static bool ParseStringAsDateTime(in string input, out DateTime timestamp)
+ {
+ if (DateTime.TryParse(input, out timestamp))
+ {
+ // Known date/time format
+ return true;
+ }
+ else if (Regex.IsMatch(input, @"^u\d+") && input.Length <= 12 && long.TryParse(input.TrimStart('u'), out long secondsInt))
+ {
+ // unix time stamp
+ // we use long instead of int because int ist to small after 03:14:07 UTC 2038-01-19
+ timestamp = new DateTime(1970, 1, 1).AddSeconds(secondsInt).ToLocalTime();
+ return true;
+ }
+ else if (Regex.IsMatch(input, @"^ft\d+") && long.TryParse(input.TrimStart("ft".ToCharArray()), out long secondsLong))
+ {
+ // windows file time
+ timestamp = new DateTime(secondsLong);
+ return true;
+ }
+ else
+ {
+ timestamp = new DateTime(1, 1, 1, 1, 1, 1);
+ return false;
+ }
+ }
+ }
+
+ ///
+ /// Type of time/date format
+ ///
+ internal enum FormatStringType
+ {
+ Time,
+ Date,
+ DateTime,
+ }
+}
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeDateSettings.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeDateSettings.cs
new file mode 100644
index 0000000000..a554caac95
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeDateSettings.cs
@@ -0,0 +1,154 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using Microsoft.PowerToys.Run.Plugin.TimeDate.Properties;
+using Microsoft.PowerToys.Settings.UI.Library;
+
+[assembly: InternalsVisibleTo("Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests")]
+
+namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
+{
+ ///
+ /// Additional settings for the WindowWalker plugin.
+ ///
+ /// Some code parts reused from TimeZone plugin.
+ internal sealed class TimeDateSettings
+ {
+ ///
+ /// Are the class properties initialized with default values
+ ///
+ private readonly bool _initialized;
+
+ ///
+ /// An instance of the class
+ ///
+ private static TimeDateSettings instance;
+
+ ///
+ /// Gets a value indicating whether to show only the time and date in global results or not
+ ///
+ internal bool OnlyDateTimeNowGlobal { get; private set; }
+
+ ///
+ /// Gets a value indicating whether to show the time with seconds or not
+ ///
+ internal bool TimeWithSeconds { get; private set; }
+
+ ///
+ /// Gets a value indicating whether the date with the weekday or not
+ ///
+ internal bool DateWithWeekday { get; private set; }
+
+ ///
+ /// Gets a value indicating whether to hide the number input error message on global results
+ ///
+ internal bool HideNumberMessageOnGlobalQuery { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Private constructor to make sure there is never more than one instance of this class
+ ///
+ private TimeDateSettings()
+ {
+ // Init class properties with default values
+ UpdateSettings(null);
+ _initialized = true;
+ }
+
+ ///
+ /// Gets an instance property of this class that makes sure that the first instance gets created
+ /// and that all the requests end up at that one instance.
+ /// The benefit of this is that we don't need additional variables/parameters
+ /// to communicate the settings between plugin's classes/methods.
+ /// We can simply access this one instance, whenever we need the actual settings.
+ ///
+ internal static TimeDateSettings Instance
+ {
+ get
+ {
+ if (instance == null)
+ {
+ instance = new TimeDateSettings();
+ }
+
+ return instance;
+ }
+ }
+
+ ///
+ /// Return a list with all additional plugin options.
+ ///
+ /// A list with all additional plugin options.
+ internal static List GetAdditionalOptions()
+ {
+ var optionList = new List
+ {
+ new PluginAdditionalOption()
+ {
+ // ToDo: When description property is implemented (#15853), move the note in brackets to description.
+ Key = nameof(OnlyDateTimeNowGlobal),
+ DisplayLabel = Resources.Microsoft_plugin_timedate_SettingOnlyDateTimeNowGlobal,
+ Value = true,
+ },
+ new PluginAdditionalOption()
+ {
+ // ToDo: When description property is implemented (#15853), move the note in brackets to description.
+ Key = nameof(TimeWithSeconds),
+ DisplayLabel = Resources.Microsoft_plugin_timedate_SettingTimeWithSeconds,
+ Value = false,
+ },
+ new PluginAdditionalOption()
+ {
+ // ToDo: When description property is implemented (#15853), move the note in brackets to description.
+ Key = nameof(DateWithWeekday),
+ DisplayLabel = Resources.Microsoft_plugin_timedate_SettingDateWithWeekday,
+ Value = false,
+ },
+ new PluginAdditionalOption()
+ {
+ Key = nameof(HideNumberMessageOnGlobalQuery),
+ DisplayLabel = Resources.Microsoft_plugin_timedate_SettingHideNumberMessageOnGlobalQuery,
+ Value = false,
+ },
+ };
+
+ return optionList;
+ }
+
+ ///
+ /// Update this settings.
+ ///
+ /// The settings for all power launcher plugins.
+ internal void UpdateSettings(PowerLauncherPluginSettings settings)
+ {
+ if ((settings is null || settings.AdditionalOptions is null) & _initialized)
+ {
+ return;
+ }
+
+ OnlyDateTimeNowGlobal = GetSettingOrDefault(settings, nameof(OnlyDateTimeNowGlobal));
+ TimeWithSeconds = GetSettingOrDefault(settings, nameof(TimeWithSeconds));
+ DateWithWeekday = GetSettingOrDefault(settings, nameof(DateWithWeekday));
+ HideNumberMessageOnGlobalQuery = GetSettingOrDefault(settings, nameof(HideNumberMessageOnGlobalQuery));
+ }
+
+ ///
+ /// Return one setting of the given settings list with the given name.
+ ///
+ /// The object that contain all settings.
+ /// The name of the setting.
+ /// A settings value.
+ private static bool GetSettingOrDefault(PowerLauncherPluginSettings settings, string name)
+ {
+ var option = settings?.AdditionalOptions?.FirstOrDefault(x => x.Key == name);
+
+ // If a setting isn't available, we use the value defined in the method GetAdditionalOptions() as fallback.
+ // We can use First() instead of FirstOrDefault() because the values must exist. Otherwise, we made a mistake when defining the settings.
+ return option?.Value ?? GetAdditionalOptions().First(x => x.Key == name).Value;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/Warning.dark.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/Warning.dark.png
new file mode 100644
index 0000000000..9ae30c0548
Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/Warning.dark.png differ
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/Warning.light.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/Warning.light.png
new file mode 100644
index 0000000000..30e0d9737e
Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/Warning.light.png differ
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/calendar.dark.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/calendar.dark.png
new file mode 100644
index 0000000000..8119c811e1
Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/calendar.dark.png differ
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/calendar.light.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/calendar.light.png
new file mode 100644
index 0000000000..7f3ba03780
Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/calendar.light.png differ
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/time.dark.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/time.dark.png
new file mode 100644
index 0000000000..8470208cf7
Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/time.dark.png differ
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/time.light.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/time.light.png
new file mode 100644
index 0000000000..3e39467760
Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/time.light.png differ
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/timeDate.dark.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/timeDate.dark.png
new file mode 100644
index 0000000000..299302d2a3
Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/timeDate.dark.png differ
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/timeDate.light.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/timeDate.light.png
new file mode 100644
index 0000000000..a639fa4bec
Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Images/timeDate.light.png differ
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Main.cs
new file mode 100644
index 0000000000..3a7347a61f
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Main.cs
@@ -0,0 +1,115 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Threading;
+using System.Windows.Controls;
+using System.Windows.Input;
+using ManagedCommon;
+using Microsoft.PowerToys.Run.Plugin.TimeDate.Components;
+using Microsoft.PowerToys.Run.Plugin.TimeDate.Properties;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Wox.Plugin;
+
+namespace Microsoft.PowerToys.Run.Plugin.TimeDate
+{
+ public class Main : IPlugin, IPluginI18n, ISettingProvider, IContextMenu
+ {
+ private PluginInitContext _context;
+
+ public string IconTheme { get; set; }
+
+ public string Name => Resources.Microsoft_plugin_timedate_plugin_name;
+
+ public string Description => GetTranslatedPluginDescription();
+
+ public IEnumerable AdditionalOptions
+ {
+ get
+ {
+ return TimeDateSettings.GetAdditionalOptions();
+ }
+ }
+
+ public List LoadContextMenus(Result selectedResult)
+ {
+ if (!(selectedResult?.ContextData is AvailableResult data))
+ {
+ return new List(0);
+ }
+
+ return new List()
+ {
+ new ContextMenuResult
+ {
+ AcceleratorKey = Key.C,
+ AcceleratorModifiers = ModifierKeys.Control,
+ FontFamily = "Segoe MDL2 Assets",
+ Glyph = "\xE8C8", // E8C8 => Symbol: Copy
+ Title = Resources.Microsoft_plugin_timedate_CopyToClipboard,
+ Action = _ => ResultHelper.CopyToClipBoard(data.Value),
+ },
+ };
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ _context = context;
+ _context.API.ThemeChanged += OnThemeChanged;
+ UpdateIconTheme(_context.API.GetCurrentTheme());
+ }
+
+ public List Query(Query query)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException(paramName: nameof(query));
+ }
+
+ return SearchController.ExecuteSearch(query, IconTheme);
+ }
+
+ private void UpdateIconTheme(Theme theme)
+ {
+ if (theme == Theme.Light || theme == Theme.HighContrastWhite)
+ {
+ IconTheme = "light";
+ }
+ else
+ {
+ IconTheme = "dark";
+ }
+ }
+
+ private void OnThemeChanged(Theme currentTheme, Theme newTheme)
+ {
+ UpdateIconTheme(newTheme);
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ string timeExample = DateTime.Now.ToString("T", CultureInfo.CurrentCulture);
+ string dateExample = DateTime.Now.ToString("d", CultureInfo.CurrentCulture);
+ return string.Format(CultureInfo.CurrentCulture, Resources.Microsoft_plugin_timedate_plugin_description, dateExample, timeExample);
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return Resources.Microsoft_plugin_timedate_plugin_name;
+ }
+
+ public Control CreateSettingPanel()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void UpdateSettings(PowerLauncherPluginSettings settings)
+ {
+ TimeDateSettings.Instance.UpdateSettings(settings);
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Microsoft.PowerToys.Run.Plugin.TimeDate.csproj b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Microsoft.PowerToys.Run.Plugin.TimeDate.csproj
new file mode 100644
index 0000000000..0cd4d83e73
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Microsoft.PowerToys.Run.Plugin.TimeDate.csproj
@@ -0,0 +1,128 @@
+
+
+
+
+ Library
+ net6.0-windows
+ Properties
+ Microsoft.PowerToys.Run.Plugin.TimeDate
+ Microsoft.PowerToys.Run.Plugin.TimeDate
+ $(Version).0
+ true
+ false
+ false
+ x64
+ en-US
+ true
+ Recommended
+
+
+
+ true
+ ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\TimeDate\
+ DEBUG;TRACE
+ full
+ x64
+ 8.0
+ prompt
+ MinimumRecommendedRules.ruleset
+ 4
+ false
+
+
+
+ ..\..\..\..\..\x64\Release\modules\launcher\Plugins\TimeDate\
+ TRACE
+ true
+ pdbonly
+ x64
+ 8.0
+ prompt
+ MinimumRecommendedRules.ruleset
+ 4
+
+
+ true
+
+
+
+ true
+
+
+
+
+
+
+
+
+ GlobalSuppressions.cs
+
+
+ StyleCop.json
+
+
+
+
+ 1.1.118
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..33195f13ce
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.Designer.cs
@@ -0,0 +1,532 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.PowerToys.Run.Plugin.TimeDate.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy value (Ctrl+C).
+ ///
+ internal static string Microsoft_plugin_timedate_CopyToClipboard {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_CopyToClipboard", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Date.
+ ///
+ internal static string Microsoft_plugin_timedate_Date {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Date", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Date and time.
+ ///
+ internal static string Microsoft_plugin_timedate_DateAndTime {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_DateAndTime", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Date and time UTC.
+ ///
+ internal static string Microsoft_plugin_timedate_DateAndTimeUtc {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_DateAndTimeUtc", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Day (Week day).
+ ///
+ internal static string Microsoft_plugin_timedate_Day {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Day", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Month and day.
+ ///
+ internal static string Microsoft_plugin_timedate_DayMonth {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_DayMonth", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Day of the month.
+ ///
+ internal static string Microsoft_plugin_timedate_DayOfMonth {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_DayOfMonth", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Day of the week (Week day).
+ ///
+ internal static string Microsoft_plugin_timedate_DayOfWeek {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_DayOfWeek", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Day of the year.
+ ///
+ internal static string Microsoft_plugin_timedate_DayOfYear {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_DayOfYear", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Era.
+ ///
+ internal static string Microsoft_plugin_timedate_Era {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Era", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Era abbreviation.
+ ///
+ internal static string Microsoft_plugin_timedate_EraAbbreviation {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_EraAbbreviation", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Valid prefixes: 'u' for Unix Timestamp, 'ft' for Windows file time.
+ ///
+ internal static string Microsoft_plugin_timedate_ErrorResultSubTitle {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_ErrorResultSubTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Error: Invalid number input.
+ ///
+ internal static string Microsoft_plugin_timedate_ErrorResultTitle {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_ErrorResultTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Hour.
+ ///
+ internal static string Microsoft_plugin_timedate_Hour {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Hour", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ISO 8601.
+ ///
+ internal static string Microsoft_plugin_timedate_Iso8601 {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Iso8601", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ISO 8601 UTC.
+ ///
+ internal static string Microsoft_plugin_timedate_Iso8601Utc {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Iso8601Utc", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ISO 8601 with time zone.
+ ///
+ internal static string Microsoft_plugin_timedate_Iso8601Zone {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Iso8601Zone", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ISO 8601 UTC with time zone.
+ ///
+ internal static string Microsoft_plugin_timedate_Iso8601ZoneUtc {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Iso8601ZoneUtc", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Millisecond.
+ ///
+ internal static string Microsoft_plugin_timedate_Millisecond {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Millisecond", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Minute.
+ ///
+ internal static string Microsoft_plugin_timedate_Minute {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Minute", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Month.
+ ///
+ internal static string Microsoft_plugin_timedate_Month {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Month", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Month of the year.
+ ///
+ internal static string Microsoft_plugin_timedate_MonthOfYear {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_MonthOfYear", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Month and year.
+ ///
+ internal static string Microsoft_plugin_timedate_MonthYear {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_MonthYear", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Now.
+ ///
+ internal static string Microsoft_plugin_timedate_Now {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Now", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Now UTC.
+ ///
+ internal static string Microsoft_plugin_timedate_NowUtc {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_NowUtc", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Provides time and date values for the system time or a custom time stamp (Examples: 'day', 'day::{0}', 'time::{1}', 'calendar week::{0}').
+ ///
+ internal static string Microsoft_plugin_timedate_plugin_description {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_plugin_description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Time and Date.
+ ///
+ internal static string Microsoft_plugin_timedate_plugin_name {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_plugin_name", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to RFC1123.
+ ///
+ internal static string Microsoft_plugin_timedate_Rfc1123 {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Rfc1123", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to for; and; nor; but; or; so.
+ ///
+ internal static string Microsoft_plugin_timedate_Search_ConjunctionList {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Search_ConjunctionList", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Date.
+ ///
+ internal static string Microsoft_plugin_timedate_SearchTagDate {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagDate", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Current date; Now.
+ ///
+ internal static string Microsoft_plugin_timedate_SearchTagDateNow {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagDateNow", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Year; Calendar era; Date.
+ ///
+ internal static string Microsoft_plugin_timedate_SearchTagEra {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagEra", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Current year; Current calendar era; Current date; Now.
+ ///
+ internal static string Microsoft_plugin_timedate_SearchTagEraNow {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagEraNow", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Date and time; Time and Date.
+ ///
+ internal static string Microsoft_plugin_timedate_SearchTagFormat {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Current date and time; Current time and date; Now.
+ ///
+ internal static string Microsoft_plugin_timedate_SearchTagFormatNow {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagFormatNow", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Time.
+ ///
+ internal static string Microsoft_plugin_timedate_SearchTagTime {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagTime", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Current Time; Now.
+ ///
+ internal static string Microsoft_plugin_timedate_SearchTagTimeNow {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagTimeNow", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Second.
+ ///
+ internal static string Microsoft_plugin_timedate_Second {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Second", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Show date with weekday and name of month (Applies to 'Date' and 'Now' result).
+ ///
+ internal static string Microsoft_plugin_timedate_SettingDateWithWeekday {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_SettingDateWithWeekday", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Hide 'Invalid number input' error message on global queries.
+ ///
+ internal static string Microsoft_plugin_timedate_SettingHideNumberMessageOnGlobalQuery {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_SettingHideNumberMessageOnGlobalQuery", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Show only 'Time', 'Date' and 'Now' result on global queries
+ ///(Regardless of this setting, on global queries the first search word has to be a full match.).
+ ///
+ internal static string Microsoft_plugin_timedate_SettingOnlyDateTimeNowGlobal {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_SettingOnlyDateTimeNowGlobal", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Show time with seconds (Applies to 'Time' and 'Now' result).
+ ///
+ internal static string Microsoft_plugin_timedate_SettingTimeWithSeconds {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_SettingTimeWithSeconds", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Select or press Ctrl+C to copy.
+ ///
+ internal static string Microsoft_plugin_timedate_SubTitleNote {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_SubTitleNote", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Time.
+ ///
+ internal static string Microsoft_plugin_timedate_Time {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Time", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Time UTC.
+ ///
+ internal static string Microsoft_plugin_timedate_TimeUtc {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_TimeUtc", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Alternative search tags:.
+ ///
+ internal static string Microsoft_plugin_timedate_ToolTipAlternativeSearchTag {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_ToolTipAlternativeSearchTag", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Universal time format: YYYY-MM-DD hh:mm:ss.
+ ///
+ internal static string Microsoft_plugin_timedate_UniversalTime {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_UniversalTime", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Unix epoch time.
+ ///
+ internal static string Microsoft_plugin_timedate_Unix {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Unix", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Week of the month.
+ ///
+ internal static string Microsoft_plugin_timedate_WeekOfMonth {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_WeekOfMonth", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Week of the year (Calendar week, Week number).
+ ///
+ internal static string Microsoft_plugin_timedate_WeekOfYear {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_WeekOfYear", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Windows file time (Int64 number).
+ ///
+ internal static string Microsoft_plugin_timedate_WindowsFileTime {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_WindowsFileTime", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Year.
+ ///
+ internal static string Microsoft_plugin_timedate_Year {
+ get {
+ return ResourceManager.GetString("Microsoft_plugin_timedate_Year", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.resx
new file mode 100644
index 0000000000..a3081c320f
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.resx
@@ -0,0 +1,294 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Copy value (Ctrl+C)
+ 'Ctrl+C' is a shortcut
+
+
+ Date
+
+
+ Date and time
+
+
+ Date and time UTC
+ 'UTC' means here 'Universal Time Convention'
+
+
+ Day (Week day)
+
+
+ Month and day
+
+
+ Day of the month
+
+
+ Day of the week (Week day)
+
+
+ Day of the year
+
+
+ Era
+
+
+ Era abbreviation
+
+
+ Valid prefixes: 'u' for Unix Timestamp, 'ft' for Windows file time
+
+
+ Error: Invalid number input
+
+
+ Hour
+
+
+ ISO 8601
+
+
+ ISO 8601 UTC
+ 'UTC' means here 'Universal Time Convention'
+
+
+ ISO 8601 with time zone
+
+
+ ISO 8601 UTC with time zone
+ 'UTC' means here 'Universal Time Convention'
+
+
+ Millisecond
+
+
+ Minute
+
+
+ Month
+
+
+ Month of the year
+
+
+ Month and year
+
+
+ Now
+
+
+ Now UTC
+ 'UTC' means here 'Universal Time Convention'
+
+
+ Provides time and date values for the system time or a custom time stamp (Examples: 'day', 'day::{0}', 'time::{1}', 'calendar week::{0}')
+ The character sequence '::' is a fixed delimiter in plugin code. Do not translate the placeholders '{0}' and '{1}' because it will be replaced in code.
+
+
+ Time and Date
+
+
+ RFC1123
+
+
+ Date
+ Don't change order
+
+
+ Current date; Now
+ Don't change order
+
+
+ Year; Calendar era; Date
+ Don't change order
+
+
+ Current year; Current calendar era; Current date; Now
+ Don't change order
+
+
+ Date and time; Time and Date
+ Don't change order
+
+
+ Current date and time; Current time and date; Now
+ Don't change order
+
+
+ Time
+ Don't change order
+
+
+ Current Time; Now
+ Don't change order
+
+
+ for; and; nor; but; or; so
+ List of conjunctions. We don't add 'yet' because this can be a synonym of 'now' which might be problematic on localized searches.
+
+
+ Second
+
+
+ Show date with weekday and name of month (Applies to 'Date' and 'Now' result)
+
+
+ Hide 'Invalid number input' error message on global queries
+
+
+ Show only 'Time', 'Date' and 'Now' result on global queries
+(Regardless of this setting, on global queries the first search word has to be a full match.)
+
+
+ Show time with seconds (Applies to 'Time' and 'Now' result)
+
+
+ Select or press Ctrl+C to copy
+ 'Ctrl+C' is a shortcut
+
+
+ Time
+
+
+ Time UTC
+ 'UTC' means here 'Universal Time Convention'
+
+
+ Alternative search tags:
+
+
+ Universal time format: YYYY-MM-DD hh:mm:ss
+
+
+ Unix epoch time
+
+
+ Week of the month
+
+
+ Week of the year (Calendar week, Week number)
+
+
+ Windows file time (Int64 number)
+
+
+ Year
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/plugin.json b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/plugin.json
new file mode 100644
index 0000000000..55dfcd511a
--- /dev/null
+++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/plugin.json
@@ -0,0 +1,13 @@
+{
+ "ID": "5D69806A5A474115821C3E4C56B9C793",
+ "ActionKeyword": "(",
+ "IsGlobal": true,
+ "Name": "Time and date",
+ "Author": "htcfreek",
+ "Version": "1.0.0",
+ "Language": "csharp",
+ "Website": "https://aka.ms/powertoys",
+ "ExecuteFileName": "Microsoft.PowerToys.Run.Plugin.TimeDate.dll",
+ "IcoPathDark": "Images\\timeDate.dark.png",
+ "IcoPathLight": "Images\\timeDate.light.png"
+}
diff --git a/src/modules/launcher/Wox.Infrastructure/StringMatcher.cs b/src/modules/launcher/Wox.Infrastructure/StringMatcher.cs
index 8390c8a20d..ad4e3378ac 100644
--- a/src/modules/launcher/Wox.Infrastructure/StringMatcher.cs
+++ b/src/modules/launcher/Wox.Infrastructure/StringMatcher.cs
@@ -10,6 +10,7 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Plugin.Program.UnitTests")]
[assembly: InternalsVisibleTo("Microsoft.PowerToys.Run.Plugin.System.UnitTests")]
+[assembly: InternalsVisibleTo("Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests")]
namespace Wox.Infrastructure
{