[PT Run] TimeDate plugin (#16662)

* create plugin

* Update plugin code

* fix deps

* last changes

* unix

* new results and small changes

* Update settings name

* make spellcheck happy

* new time/date formats

* add comment

* code cleanup, installer, signing pipeline

* fix unix result

* UnitTests

* spell fix

* Update tests, Timestamp query feature

* new formats

* last changes

* last changes

* unit tests and fixes

* cjhanges and fixes

* fix installer

* fix settings class init

* context menu

* fix tests

* add settings tests

* update/fix DateTimeResult tests

* small improvements

* update pipeline

* enable analyzer

* fixes and improvements

* spell fix

* dev docs

* doc fixes

* spell fix

* last changes

* changes and fixes

* fixes and test updates

* improvements

* last changes

* try to fix tests

* remove obsolete code

* add info to test log

* fix search

* tag fix

* tests

* change tests

* update dev docs

* fix spelling

* fix culture for ui strings

* improvements based on feedback

* improve global search

* improve text

* docs improvement

* add settings note

* fix and update tests

* fix spelling
This commit is contained in:
Heiko
2022-03-17 20:33:05 +01:00
committed by GitHub
parent 5914fc1ffd
commit 34e4e7e5bd
36 changed files with 3028 additions and 4 deletions

View File

@@ -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
{
/// <summary>
/// Gets or sets the time/date value
/// </summary>
internal string Value { get; set; }
/// <summary>
/// Gets or sets the text used for the subtitle and as search term
/// </summary>
internal string Label { get; set; }
/// <summary>
/// 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.
/// </summary>
internal string AlternativeSearchTag { get; set; }
/// <summary>
/// Gets or sets a value indicating the type of result
/// </summary>
internal ResultIconType IconType { get; set; }
/// <summary>
/// Returns the path to the icon
/// </summary>
/// <param name="theme">Theme</param>
/// <returns>Path</returns>
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,
}
}

View File

@@ -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
{
/// <summary>
/// Returns a list with all available date time formats
/// </summary>
/// <param name="isKeywordSearch">Is this a search with plugin activation keyword or not</param>
/// <param name="timeLong">Required for UnitTest: Show time in long format</param>
/// <param name="dateLong">Required for UnitTest: Show date in long format</param>
/// <param name="timestamp">Required for UnitTest: Use custom <see cref="DateTime"/> object to calculate results</param>
/// <returns>List of results</returns>
internal static List<AvailableResult> GetList(bool isKeywordSearch, bool? timeLong = null, bool? dateLong = null, DateTime? timestamp = null)
{
List<AvailableResult> results = new List<AvailableResult>();
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();
}
}
}

View File

@@ -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
{
/// <summary>
/// Get the string based on the requested type
/// </summary>
/// <param name="isSystemTimeDate">Does the user search for system date/time?</param>
/// <param name="stringId">Id of the string. (Example: `MyString` for `MyString` and `MyStringNow`)</param>
/// <param name="stringIdNow">Optional string id for now case</param>
/// <returns>The string from the resource file, or <see cref="string.Empty"/> otherwise.</returns>
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;
}
}
/// <summary>
/// Copy the given text to the clipboard
/// </summary>
/// <param name="text">The text to copy to the clipboard</param>
/// <returns><see langword="true"/>The text successful copy to the clipboard, otherwise <see langword="false"/></returns>
/// <remarks>Code copied from TimeZone plugin</remarks>
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;
}
}
/// <summary>
/// Create a tool tip for the alternative search tags
/// </summary>
/// <param name="result">The <see cref="AvailableResult"/>.</param>
/// <returns>New <see cref="ToolTipData"/> object or null if <see cref="AvailableResult.AlternativeSearchTag"/> is empty.</returns>
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);
}
}
/// <summary>
/// Gets a result with an error message that only numbers can't be parsed
/// </summary>
/// <returns>Element of type <see cref="Result"/>.</returns>
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",
};
}
}

View File

@@ -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
{
/// <summary>
/// SearchController: Class tot hold the search method that filter available date time formats
/// Extra class to simplify code in <see cref="Main"/> class
/// </summary>
internal static class SearchController
{
/// <summary>
/// Var that holds the delimiter between format and date
/// </summary>
private const string InputDelimiter = "::";
/// <summary>
/// A list of conjunctions that we ignore on search
/// </summary>
private static readonly string[] _conjunctionList = Resources.Microsoft_plugin_timedate_Search_ConjunctionList.Split("; ");
/// <summary>
/// Searches for results
/// </summary>
/// <param name="query">Search query object</param>
/// <returns>List of Wox <see cref="Result"/>s.</returns>
internal static List<Result> ExecuteSearch(Query query, string iconTheme)
{
List<AvailableResult> availableFormats = new List<AvailableResult>();
List<Result> results = new List<Result>();
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;
}
/// <summary>
/// Checks the format for a match with the user query and returns the score.
/// </summary>
/// <param name="query">The user query.</param>
/// <param name="label">The label of the format.</param>
/// <param name="tags">The search tag list as string.</param>
/// <param name="isGlobalSearch">Is this a global search?</param>
/// <returns>The score for the result.</returns>
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;
}
}
}

View File

@@ -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
{
/// <summary>
/// Get the format for the time string
/// </summary>
/// <param name="targetFormat">Type of format</param>
/// <param name="timeLong">Show date with weekday and name of month (long format)</param>
/// <param name="dateLong">Show time with seconds (long format)</param>
/// <returns>String that identifies the time/date format (<see href="https://docs.microsoft.com/en-us/dotnet/api/system.datetime.tostring"/>)</returns>
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
}
}
/// <summary>
/// Returns the number week in the month (Used code from 'David Morton' from <see href="https://social.msdn.microsoft.com/Forums/vstudio/en-US/bf504bba-85cb-492d-a8f7-4ccabdf882cb/get-week-number-for-month"/>)
/// </summary>
/// <param name="date">date</param>
/// <returns>Number of week in the month</returns>
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;
}
/// <summary>
/// Returns the number of the day in the week
/// </summary>
/// <param name="date">Date</param>
/// <returns>Number of the day in the week</returns>
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;
}
/// <summary>
/// Convert input string to a <see cref="DateTime"/> object in local time
/// </summary>
/// <param name="input">String with date/time</param>
/// <param name="timestamp">The new <see cref="DateTime"/> object</param>
/// <returns>True on success, otherwise false</returns>
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;
}
}
}
/// <summary>
/// Type of time/date format
/// </summary>
internal enum FormatStringType
{
Time,
Date,
DateTime,
}
}

View File

@@ -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
{
/// <summary>
/// Additional settings for the WindowWalker plugin.
/// </summary>
/// <remarks>Some code parts reused from TimeZone plugin.</remarks>
internal sealed class TimeDateSettings
{
/// <summary>
/// Are the class properties initialized with default values
/// </summary>
private readonly bool _initialized;
/// <summary>
/// An instance of the class <see cref="TimeDateSettings"></see>
/// </summary>
private static TimeDateSettings instance;
/// <summary>
/// Gets a value indicating whether to show only the time and date in global results or not
/// </summary>
internal bool OnlyDateTimeNowGlobal { get; private set; }
/// <summary>
/// Gets a value indicating whether to show the time with seconds or not
/// </summary>
internal bool TimeWithSeconds { get; private set; }
/// <summary>
/// Gets a value indicating whether the date with the weekday or not
/// </summary>
internal bool DateWithWeekday { get; private set; }
/// <summary>
/// Gets a value indicating whether to hide the number input error message on global results
/// </summary>
internal bool HideNumberMessageOnGlobalQuery { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="TimeDateSettings"/> class.
/// Private constructor to make sure there is never more than one instance of this class
/// </summary>
private TimeDateSettings()
{
// Init class properties with default values
UpdateSettings(null);
_initialized = true;
}
/// <summary>
/// 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.
/// </summary>
internal static TimeDateSettings Instance
{
get
{
if (instance == null)
{
instance = new TimeDateSettings();
}
return instance;
}
}
/// <summary>
/// Return a list with all additional plugin options.
/// </summary>
/// <returns>A list with all additional plugin options.</returns>
internal static List<PluginAdditionalOption> GetAdditionalOptions()
{
var optionList = new List<PluginAdditionalOption>
{
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;
}
/// <summary>
/// Update this settings.
/// </summary>
/// <param name="settings">The settings for all power launcher plugins.</param>
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));
}
/// <summary>
/// Return one <see cref="bool"/> setting of the given settings list with the given name.
/// </summary>
/// <param name="settings">The object that contain all settings.</param>
/// <param name="name">The name of the setting.</param>
/// <returns>A settings value.</returns>
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;
}
}
}