diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index aff7937142..425ec9bbd0 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1983,6 +1983,7 @@ uipi UIs ULARGE ULONGLONG +ums unapply unassign uncompilable diff --git a/doc/devdocs/modules/launcher/plugins/timedate.md b/doc/devdocs/modules/launcher/plugins/timedate.md index 6ad856657a..ca04b53293 100644 --- a/doc/devdocs/modules/launcher/plugins/timedate.md +++ b/doc/devdocs/modules/launcher/plugins/timedate.md @@ -18,6 +18,7 @@ The 'Time and Date' plugin shows the date and time in different formats. For the **Remarks** - The following formats requires a prefix in the query: - Unix Timestamp: `u` + - Unix Timestamp in milliseconds: `ums` - Windows file time: `ft` - On invalid number inputs we show a warning that tells the user which prefixes are allowed/required. @@ -33,6 +34,7 @@ The following formats are currently available: | Time UTC | 4:10 PM | x | x | | Now UTC | 3/5/2022 4:10 PM | x | x | | Unix Timestamp | 1646496622 | x | x | +| Unix Timestamp in milliseconds | 1646496622500 | x | x | | Hour | 10 | x | | | Minute | 30 | x | | | Second | 45 | x | | 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 index 094e525852..a51e2c4540 100644 --- 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 @@ -36,6 +36,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests [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("unix epoch time in", "Unix epoch time in milliseconds -", "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")] @@ -82,6 +83,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests [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("unix epoch time in", "Unix epoch time in milliseconds -", "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")] 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 index 8fbcaf41e7..68d962d102 100644 --- 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 @@ -31,13 +31,13 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests } [DataTestMethod] - [DataRow("time", 2)] - [DataRow("date", 2)] - [DataRow("now", 3)] - [DataRow("current", 3)] - [DataRow("year", 0)] - [DataRow("time::10:10:10", 0)] - [DataRow("date::10/10/10", 0)] + [DataRow("time", 2)] // Setting 'Only Date, Time, Now on global results' is default on + [DataRow("date", 2)] // Setting 'Only Date, Time, Now on global results' is default on + [DataRow("now", 3)] // Setting 'Only Date, Time, Now on global results' is default on + [DataRow("current", 3)] // Setting 'Only Date, Time, Now on global results' is default on + [DataRow("year", 0)] // Setting 'Only Date, Time, Now on global results' is default on + [DataRow("time::10:10:10", 0)] // Setting 'Only Date, Time, Now on global results' is default on + [DataRow("date::10/10/10", 0)] // Setting 'Only Date, Time, Now on global results' is default on public void CountWithoutPluginKeyword(string typedString, int expectedResultCount) { // Setup @@ -52,12 +52,12 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests } [DataTestMethod] - [DataRow("(time", 17)] - [DataRow("(date", 25)] + [DataRow("(time", 18)] + [DataRow("(date", 26)] [DataRow("(year", 7)] - [DataRow("(now", 31)] - [DataRow("(current", 31)] - [DataRow("(", 31)] + [DataRow("(now", 32)] + [DataRow("(current", 32)] + [DataRow("(", 32)] [DataRow("(now::10:10:10", 1)] // Windows file time [DataRow("(current::10:10:10", 0)] public void CountWithPluginKeyword(string typedString, int expectedResultCount) @@ -104,6 +104,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests [DataRow("(now", "Now -")] [DataRow("(now u", "Now UTC -")] [DataRow("(unix", "Unix epoch time -")] + [DataRow("(unix epoch time in milli", "Unix epoch time in milliseconds -")] [DataRow("(file", "Windows file time (Int64 number) ")] [DataRow("(hour", "Hour -")] [DataRow("(minute", "Minute -")] @@ -156,6 +157,11 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests [DataRow("(12:30", "Time -")] [DataRow("(10.10.2022", "Date -")] [DataRow("(u1646408119", "Date and time -")] + [DataRow("(u+1646408119", "Date and time -")] + [DataRow("(u-1646408119", "Date and time -")] + [DataRow("(ums1646408119", "Date and time -")] + [DataRow("(ums+1646408119", "Date and time -")] + [DataRow("(ums-1646408119", "Date and time -")] [DataRow("(ft637820085517321977", "Date and time -")] public void DateTimeNumberOnlyInput(string typedString, string expectedResult) { @@ -176,15 +182,10 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests [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 @@ -201,8 +202,23 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests [DataTestMethod] [DataRow("(ug1646408119")] // Invalid prefix [DataRow("(u9999999999999")] // Unix number + prefix is longer than 12 characters + [DataRow("(ums999999999999999")] // Unix number in milliseconds + prefix is longer than 17 characters + [DataRow("(-u99999999999")] // Unix number with wrong placement of - sign + [DataRow("(+ums9999999999")] // Unix number in milliseconds with wrong placement of + sign [DataRow("(0123456")] // Missing prefix [DataRow("(ft63782008ab55173dasdas21977")] // Number contains letters + [DataRow("(ft63782008ab55173dasdas")] // Number contains letters at the end + [DataRow("(ft12..548")] // Number contains wrong punctuation + [DataRow("(ft12..54//8")] // Number contains wrong punctuation and other characters + [DataRow("(time::ft12..54//8")] // Number contains wrong punctuation and other characters + [DataRow("(ut2ed.5555")] // Number contains letters + [DataRow("(12..54//8")] // Number contains punctuation and other characters, but no special prefix + [DataRow("(ft::1288gg8888")] // Number contains delimiter and letters, but no special prefix + [DataRow("(date::12::55")] + [DataRow("(date::12:aa:55")] + [DataRow("(10.aa.22")] + [DataRow("(12::55")] + [DataRow("(12:aa:55")] public void InvalidNumberInputShowsErrorMessage(string typedString) { // Setup @@ -217,10 +233,12 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests } [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 + [DataRow("(ft1 2..548")] // Input contains space + [DataRow("(ft12..54 //8")] // Input contains space + [DataRow("(time::ft12..54 //8")] // Input contains space + [DataRow("(10.10aa")] // Input contains . (Can be part of a date.) + [DataRow("(10:10aa")] // Input contains : (Can be part of a time.) + [DataRow("(10/10aa")] // Input contains / (Can be part of a date.) public void InvalidInputNotShowsErrorMessage(string typedString) { // Setup 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 index 322592a457..1a868c0f8f 100644 --- 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 @@ -34,6 +34,11 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests [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("u-10456", true, "", "")] // Value is UTC and can be different based on system + [DataRow("u+10456", true, "", "")] // Value is UTC and can be different based on system + [DataRow("ums10456", true, "", "")] // Value is UTC and can be different based on system + [DataRow("ums-10456", true, "", "")] // Value is UTC and can be different based on system + [DataRow("ums+10456", 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) { 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 index 2fc553f822..ab6c77f1ea 100644 --- 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 @@ -61,6 +61,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components { // We use long instead of int for unix time stamp because int is too small after 03:14:07 UTC 2038-01-19 long unixTimestamp = (long)dateTimeNowUtc.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; + long unixTimestampMilliseconds = (long)dateTimeNowUtc.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds; 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)); @@ -89,6 +90,13 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components IconType = ResultIconType.DateTime, }, new AvailableResult() + { + Value = unixTimestampMilliseconds.ToString(CultureInfo.CurrentCulture), + Label = Resources.Microsoft_plugin_timedate_Unix_Milliseconds, + 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, 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 index a2f3dfe6af..5d4e3eb8f4 100644 --- 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 @@ -86,6 +86,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components { Title = Resources.Microsoft_plugin_timedate_ErrorResultTitle, SubTitle = Resources.Microsoft_plugin_timedate_ErrorResultSubTitle, + ToolTipData = new ToolTipData(Resources.Microsoft_plugin_timedate_ErrorResultTitle, 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 index 708dc7e39b..219f2c5556 100644 --- 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 @@ -121,8 +121,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components } // 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)) + if (!isEmptySearchInput && results.Count == 0 && Regex.IsMatch(searchTerm, @"\w+\d+.*$") && !searchTerm.Any(char.IsWhiteSpace) && (TimeAndDateHelper.IsSpecialInputParsing(searchTerm) || !Regex.IsMatch(searchTerm, @"\d+[\.:/]\d+"))) { // Without plugin key word show only if message is not hidden by setting if (isKeywordSearch || !TimeDateSettings.Instance.HideNumberMessageOnGlobalQuery) 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 index 86294474c6..ed1f5097b8 100644 --- 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 @@ -96,17 +96,24 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components // Known date/time format return true; } - else if (Regex.IsMatch(input, @"^u\d+") && input.Length <= 12 && long.TryParse(input.TrimStart('u'), out long secondsInt)) + else if (Regex.IsMatch(input, @"^u[\+-]?\d{1,10}$") && long.TryParse(input.TrimStart('u'), out long secondsU)) { // unix time stamp // we use long instead of int because int is too small after 03:14:07 UTC 2038-01-19 - timestamp = new DateTime(1970, 1, 1).AddSeconds(secondsInt).ToLocalTime(); + timestamp = new DateTime(1970, 1, 1).AddSeconds(secondsU).ToLocalTime(); return true; } - else if (Regex.IsMatch(input, @"^ft\d+") && long.TryParse(input.TrimStart("ft".ToCharArray()), out long secondsLong)) + else if (Regex.IsMatch(input, @"^ums[\+-]?\d{1,13}$") && long.TryParse(input.TrimStart("ums".ToCharArray()), out long millisecondsUms)) + { + // unix time stamp in milliseconds + // we use long instead of int because int is too small after 03:14:07 UTC 2038-01-19 + timestamp = new DateTime(1970, 1, 1).AddMilliseconds(millisecondsUms).ToLocalTime(); + return true; + } + else if (Regex.IsMatch(input, @"^ft\d+$") && long.TryParse(input.TrimStart("ft".ToCharArray()), out long secondsFt)) { // windows file time - timestamp = new DateTime(secondsLong); + timestamp = new DateTime(secondsFt); return true; } else @@ -115,6 +122,16 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components return false; } } + + /// + /// Test if input is special parsing for Unix time, Unix time in milliseconds or File time. + /// + /// String with date/time + /// True if yes, otherwise false + internal static bool IsSpecialInputParsing(string input) + { + return Regex.IsMatch(input, @"^.*(u|ums|ft)\d"); + } } /// 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 index 41c0fc0398..69689b7980 100644 --- 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 @@ -160,7 +160,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties { } /// - /// Looks up a localized string similar to Valid prefixes: 'u' for Unix Timestamp, 'ft' for Windows file time. + /// Looks up a localized string similar to Valid prefixes: 'u' for Unix Timestamp, 'ums' for Unix Timestamp in milliseconds, 'ft' for Windows file time. /// internal static string Microsoft_plugin_timedate_ErrorResultSubTitle { get { @@ -555,6 +555,15 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties { } } + /// + /// Looks up a localized string similar to Unix epoch time in milliseconds. + /// + internal static string Microsoft_plugin_timedate_Unix_Milliseconds { + get { + return ResourceManager.GetString("Microsoft_plugin_timedate_Unix_Milliseconds", resourceCulture); + } + } + /// /// Looks up a localized string similar to Week of the month. /// 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 index c5cd40c0a3..acdb4aa3f4 100644 --- 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 @@ -153,7 +153,7 @@ Era abbreviation - Valid prefixes: 'u' for Unix Timestamp, 'ft' for Windows file time + Valid prefixes: 'u' for Unix Timestamp, 'ums' for Unix Timestamp in milliseconds, 'ft' for Windows file time Error: Invalid number input @@ -312,4 +312,7 @@ Year + + Unix epoch time in milliseconds + \ No newline at end of file