mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-08 04:07:40 +02:00
[PT Run > Time and Date plugin] Custom formats and other improvements (#37743)
* add settings definition * fix typos and improve settings * make spell checker happy * new icon type error * first code to handle custom formats * support parsing of new formats * spelling and typos * comment fix * spell check * start implement custom format results * last changes * finish implementation * spell checker * settings name * add missing format * reorder settings * dev docs * change ELF to EAB * update dev docs * last changes * test cases * fix typos * fix typo * port changes * fixes * changes * fixes * leap year support * days in month * tests * comment * fix comment
This commit is contained in:
@@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
{
|
||||
internal class AvailableResult
|
||||
internal sealed class AvailableResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the time/date value
|
||||
@@ -41,6 +41,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
ResultIconType.Time => $"Images\\time.{theme}.png",
|
||||
ResultIconType.Date => $"Images\\calendar.{theme}.png",
|
||||
ResultIconType.DateTime => $"Images\\timeDate.{theme}.png",
|
||||
ResultIconType.Error => $"Images\\Warning.{theme}.png",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
@@ -51,5 +52,6 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
Time,
|
||||
Date,
|
||||
DateTime,
|
||||
Error,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.PowerToys.Run.Plugin.TimeDate.Properties;
|
||||
|
||||
namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
@@ -72,6 +72,86 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
string era = DateTimeFormatInfo.CurrentInfo.GetEraName(calendar.GetEra(dateTimeNow));
|
||||
string eraShort = DateTimeFormatInfo.CurrentInfo.GetAbbreviatedEraName(calendar.GetEra(dateTimeNow));
|
||||
|
||||
// Custom formats
|
||||
foreach (string f in TimeDateSettings.Instance.CustomFormats)
|
||||
{
|
||||
string[] formatParts = f.Split("=", 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
string formatSyntax = formatParts.Length == 2 ? formatParts[1] : string.Empty;
|
||||
string searchTags = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagCustom");
|
||||
DateTime dtObject = dateTimeNow;
|
||||
|
||||
// If Length = 0 then empty string.
|
||||
if (formatParts.Length >= 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Verify and check input and update search tags
|
||||
if (formatParts.Length == 1)
|
||||
{
|
||||
throw new FormatException("Format syntax part after equal sign is missing.");
|
||||
}
|
||||
|
||||
bool containsCustomSyntax = TimeAndDateHelper.StringContainsCustomFormatSyntax(formatSyntax);
|
||||
if (formatSyntax.StartsWith("UTC:", StringComparison.InvariantCulture))
|
||||
{
|
||||
searchTags = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagCustomUtc");
|
||||
dtObject = dateTimeNowUtc;
|
||||
}
|
||||
|
||||
// Get formated date
|
||||
var value = TimeAndDateHelper.ConvertToCustomFormat(dtObject, unixTimestamp, unixTimestampMilliseconds, weekOfYear, eraShort, Regex.Replace(formatSyntax, "^UTC:", string.Empty), firstWeekRule, firstDayOfTheWeek);
|
||||
try
|
||||
{
|
||||
value = dtObject.ToString(value, CultureInfo.CurrentCulture);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!containsCustomSyntax)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do not fail as we have custom format syntax. Instead fix backslashes.
|
||||
value = Regex.Replace(value, @"(?<!\\)\\", string.Empty).Replace("\\\\", "\\");
|
||||
}
|
||||
}
|
||||
|
||||
// Add result
|
||||
results.Add(new AvailableResult()
|
||||
{
|
||||
Value = value,
|
||||
Label = formatParts[0],
|
||||
AlternativeSearchTag = searchTags,
|
||||
IconType = ResultIconType.DateTime,
|
||||
});
|
||||
}
|
||||
catch (ArgumentOutOfRangeException e)
|
||||
{
|
||||
Wox.Plugin.Logger.Log.Exception($"Failed to convert into custom format {formatParts[0]}: {formatSyntax}", e, typeof(AvailableResultsList));
|
||||
results.Add(new AvailableResult()
|
||||
{
|
||||
Value = Resources.Microsoft_plugin_timedate_ErrorConvertCustomFormat + " " + e.Message,
|
||||
Label = formatParts[0],
|
||||
AlternativeSearchTag = searchTags,
|
||||
IconType = ResultIconType.Error,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Wox.Plugin.Logger.Log.Exception($"Failed to convert into custom format {formatParts[0]}: {formatSyntax}", e, typeof(AvailableResultsList));
|
||||
results.Add(new AvailableResult()
|
||||
{
|
||||
Value = Resources.Microsoft_plugin_timedate_InvalidCustomFormat + " " + formatSyntax,
|
||||
Label = formatParts[0],
|
||||
AlternativeSearchTag = searchTags,
|
||||
IconType = ResultIconType.Error,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Predefined formats
|
||||
results.AddRange(new[]
|
||||
{
|
||||
new AvailableResult()
|
||||
@@ -152,6 +232,13 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
IconType = ResultIconType.Date,
|
||||
},
|
||||
new AvailableResult()
|
||||
{
|
||||
Value = DateTime.DaysInMonth(dateTimeNow.Year, dateTimeNow.Month).ToString(CultureInfo.CurrentCulture),
|
||||
Label = Resources.Microsoft_plugin_timedate_DaysInMonth,
|
||||
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,
|
||||
@@ -201,6 +288,13 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
IconType = ResultIconType.Date,
|
||||
},
|
||||
new AvailableResult()
|
||||
{
|
||||
Value = DateTime.IsLeapYear(dateTimeNow.Year) ? Resources.Microsoft_plugin_timedate_LeapYear : Resources.Microsoft_plugin_timedate_NoLeapYear,
|
||||
Label = Resources.Microsoft_plugin_timedate_LeapYear,
|
||||
AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
|
||||
IconType = ResultIconType.Date,
|
||||
},
|
||||
new AvailableResult()
|
||||
{
|
||||
Value = era,
|
||||
Label = Resources.Microsoft_plugin_timedate_Era,
|
||||
@@ -221,13 +315,31 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagDate"),
|
||||
IconType = ResultIconType.Date,
|
||||
},
|
||||
new AvailableResult()
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
results.Add(new AvailableResult()
|
||||
{
|
||||
Value = dateTimeNow.ToFileTime().ToString(CultureInfo.CurrentCulture),
|
||||
Label = Resources.Microsoft_plugin_timedate_WindowsFileTime,
|
||||
AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
|
||||
IconType = ResultIconType.DateTime,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
results.Add(new AvailableResult()
|
||||
{
|
||||
Value = Resources.Microsoft_plugin_timedate_ErrorConvertWft,
|
||||
Label = Resources.Microsoft_plugin_timedate_WindowsFileTime,
|
||||
AlternativeSearchTag = ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFormat"),
|
||||
IconType = ResultIconType.Error,
|
||||
});
|
||||
}
|
||||
|
||||
results.AddRange(new[]
|
||||
{
|
||||
new AvailableResult()
|
||||
{
|
||||
Value = dateTimeNowUtc.ToString("u"),
|
||||
|
||||
@@ -83,10 +83,10 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
/// 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()
|
||||
internal static Result CreateNumberErrorResult(string theme, string title, string subtitle) => new Result()
|
||||
{
|
||||
Title = Resources.Microsoft_plugin_timedate_ErrorResultTitle,
|
||||
SubTitle = Resources.Microsoft_plugin_timedate_ErrorResultSubTitle,
|
||||
Title = title,
|
||||
SubTitle = subtitle,
|
||||
ToolTipData = new ToolTipData(Resources.Microsoft_plugin_timedate_ErrorResultTitle, Resources.Microsoft_plugin_timedate_ErrorResultSubTitle),
|
||||
IcoPath = $"Images\\Warning.{theme}.png",
|
||||
};
|
||||
|
||||
@@ -40,9 +40,12 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
List<AvailableResult> availableFormats = new List<AvailableResult>();
|
||||
List<Result> results = new List<Result>();
|
||||
bool isKeywordSearch = !string.IsNullOrEmpty(query.ActionKeyword);
|
||||
bool isEmptySearchInput = string.IsNullOrEmpty(query.Search);
|
||||
bool isEmptySearchInput = string.IsNullOrWhiteSpace(query.Search);
|
||||
string searchTerm = query.Search;
|
||||
|
||||
// Last input parsing error
|
||||
string lastInputParsingErrorReason = string.Empty;
|
||||
|
||||
// Conjunction search without keyword => return no results
|
||||
// (This improves the results on global queries.)
|
||||
if (!isKeywordSearch && _conjunctionList.Any(x => x.Equals(searchTerm, StringComparison.CurrentCultureIgnoreCase)))
|
||||
@@ -61,13 +64,13 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
{
|
||||
// Search for specified format with specified time/date value
|
||||
var userInput = searchTerm.Split(InputDelimiter);
|
||||
if (TimeAndDateHelper.ParseStringAsDateTime(userInput[1], out DateTime timestamp))
|
||||
if (TimeAndDateHelper.ParseStringAsDateTime(userInput[1], out DateTime timestamp, out lastInputParsingErrorReason))
|
||||
{
|
||||
availableFormats.AddRange(AvailableResultsList.GetList(isKeywordSearch, null, null, timestamp));
|
||||
searchTerm = userInput[0];
|
||||
}
|
||||
}
|
||||
else if (TimeAndDateHelper.ParseStringAsDateTime(searchTerm, out DateTime timestamp))
|
||||
else if (TimeAndDateHelper.ParseStringAsDateTime(searchTerm, out DateTime timestamp, out lastInputParsingErrorReason))
|
||||
{
|
||||
// Return all formats for specified time/date value
|
||||
availableFormats.AddRange(AvailableResultsList.GetList(isKeywordSearch, null, null, timestamp));
|
||||
@@ -122,12 +125,15 @@ 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 && Regex.IsMatch(searchTerm, @"\w+\d+.*$") && !searchTerm.Any(char.IsWhiteSpace) && (TimeAndDateHelper.IsSpecialInputParsing(searchTerm) || !Regex.IsMatch(searchTerm, @"\d+[\.:/]\d+")))
|
||||
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
|
||||
string title = !string.IsNullOrEmpty(lastInputParsingErrorReason) ? Resources.Microsoft_plugin_timedate_ErrorResultValue : Resources.Microsoft_plugin_timedate_ErrorResultTitle;
|
||||
string message = !string.IsNullOrEmpty(lastInputParsingErrorReason) ? lastInputParsingErrorReason : Resources.Microsoft_plugin_timedate_ErrorResultSubTitle;
|
||||
|
||||
// Without plugin key word show only if not hidden by setting
|
||||
if (isKeywordSearch || !TimeDateSettings.Instance.HideNumberMessageOnGlobalQuery)
|
||||
{
|
||||
results.Add(ResultHelper.CreateNumberErrorResult(iconTheme));
|
||||
results.Add(ResultHelper.CreateNumberErrorResult(iconTheme, title, message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.PowerToys.Run.Plugin.TimeDate.Properties;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests")]
|
||||
|
||||
@@ -13,6 +15,33 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
{
|
||||
internal static class TimeAndDateHelper
|
||||
{
|
||||
private static readonly Regex _regexSpecialInputFormats = new Regex(@"^.*(::)?(u|ums|ft|oa|exc|exf)[+-]\d");
|
||||
private static readonly Regex _regexCustomDateTimeFormats = new Regex(@"(?<!\\)(DOW|DIM|WOM|WOY|EAB|WFT|UXT|UMS|OAD|EXC|EXF)");
|
||||
private static readonly Regex _regexCustomDateTimeDow = new Regex(@"(?<!\\)DOW");
|
||||
private static readonly Regex _regexCustomDateTimeDim = new Regex(@"(?<!\\)DIM");
|
||||
private static readonly Regex _regexCustomDateTimeWom = new Regex(@"(?<!\\)WOM");
|
||||
private static readonly Regex _regexCustomDateTimeWoy = new Regex(@"(?<!\\)WOY");
|
||||
private static readonly Regex _regexCustomDateTimeEab = new Regex(@"(?<!\\)EAB");
|
||||
private static readonly Regex _regexCustomDateTimeWft = new Regex(@"(?<!\\)WFT");
|
||||
private static readonly Regex _regexCustomDateTimeUxt = new Regex(@"(?<!\\)UXT");
|
||||
private static readonly Regex _regexCustomDateTimeUms = new Regex(@"(?<!\\)UMS");
|
||||
private static readonly Regex _regexCustomDateTimeOad = new Regex(@"(?<!\\)OAD");
|
||||
private static readonly Regex _regexCustomDateTimeExc = new Regex(@"(?<!\\)EXC");
|
||||
private static readonly Regex _regexCustomDateTimeExf = new Regex(@"(?<!\\)EXF");
|
||||
|
||||
private const long UnixTimeSecondsMin = -62135596800;
|
||||
private const long UnixTimeSecondsMax = 253402300799;
|
||||
private const long UnixTimeMillisecondsMin = -62135596800000;
|
||||
private const long UnixTimeMillisecondsMax = 253402300799999;
|
||||
private const long WindowsFileTimeMin = 0;
|
||||
private const long WindowsFileTimeMax = 2650467707991000000;
|
||||
private const double OADateMin = -657434.99999999;
|
||||
private const double OADateMax = 2958465.99999999;
|
||||
private const double Excel1900DateMin = 1;
|
||||
private const double Excel1900DateMax = 2958465.99998843;
|
||||
private const double Excel1904DateMin = 0;
|
||||
private const double Excel1904DateMax = 2957003.99998843;
|
||||
|
||||
/// <summary>
|
||||
/// Get the format for the time string
|
||||
/// </summary>
|
||||
@@ -56,18 +85,25 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
/// Returns the number week in the month (Used code from 'David Morton' from <see href="https://social.msdn.microsoft.com/Forums/vstudio/bf504bba-85cb-492d-a8f7-4ccabdf882cb/get-week-number-for-month"/>)
|
||||
/// </summary>
|
||||
/// <param name="date">date</param>
|
||||
/// <param name="formatSettingFirstDayOfWeek">Setting for the first day in the week.</param>
|
||||
/// <returns>Number of week in the month</returns>
|
||||
internal static int GetWeekOfMonth(DateTime date, DayOfWeek formatSettingFirstDayOfWeek)
|
||||
{
|
||||
DateTime beginningOfMonth = new DateTime(date.Year, date.Month, 1);
|
||||
int adjustment = 1; // We count from 1 to 7 and not from 0 to 6
|
||||
int weekCount = 1;
|
||||
|
||||
while (date.Date.AddDays(1).DayOfWeek != formatSettingFirstDayOfWeek)
|
||||
for (int i = 1; i <= date.Day; i++)
|
||||
{
|
||||
date = date.AddDays(1);
|
||||
DateTime d = new(date.Year, date.Month, i);
|
||||
|
||||
// Count week number +1 if day is the first day of a week and not day 1 of the month.
|
||||
// (If we count on day one of a month we would start the month with week number 2.)
|
||||
if (i > 1 && d.DayOfWeek == formatSettingFirstDayOfWeek)
|
||||
{
|
||||
weekCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)Math.Truncate((double)date.Subtract(beginningOfMonth).TotalDays / 7f) + adjustment;
|
||||
return weekCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -83,40 +119,170 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
return ((date.DayOfWeek + daysInWeek - formatSettingFirstDayOfWeek) % daysInWeek) + adjustment;
|
||||
}
|
||||
|
||||
internal static double ConvertToOleAutomationFormat(DateTime date, OADateFormats type)
|
||||
{
|
||||
double v = date.ToOADate();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case OADateFormats.Excel1904:
|
||||
// Excel with base 1904: Adjust by -1462
|
||||
v -= 1462;
|
||||
|
||||
// Date starts at 1/1/1904 = 0
|
||||
if (Math.Truncate(v) < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("Not a valid Excel date.", innerException: null);
|
||||
}
|
||||
|
||||
return v;
|
||||
case OADateFormats.Excel1900:
|
||||
// Excel with base 1900: Adjust by -1 if v < 61
|
||||
v = v < 61 ? v - 1 : v;
|
||||
|
||||
// Date starts at 1/1/1900 = 1
|
||||
if (Math.Truncate(v) < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("Not a valid Excel date.", innerException: null);
|
||||
}
|
||||
|
||||
return v;
|
||||
default:
|
||||
// OLE Automation date: Return as is.
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="inputParsingErrorMsg">Error message shown to the user</param>
|
||||
/// <returns>True on success, otherwise false</returns>
|
||||
internal static bool ParseStringAsDateTime(in string input, out DateTime timestamp)
|
||||
internal static bool ParseStringAsDateTime(in string input, out DateTime timestamp, out string inputParsingErrorMsg)
|
||||
{
|
||||
inputParsingErrorMsg = string.Empty;
|
||||
CompositeFormat errorMessage = CompositeFormat.Parse(Resources.Microsoft_plugin_timedate_InvalidInput_SupportedRange);
|
||||
|
||||
if (DateTime.TryParse(input, out timestamp))
|
||||
{
|
||||
// Known date/time format
|
||||
return true;
|
||||
}
|
||||
else if (Regex.IsMatch(input, @"^u[\+-]?\d{1,10}$") && long.TryParse(input.TrimStart('u'), out long secondsU))
|
||||
else if (Regex.IsMatch(input, @"^u[\+-]?\d+$"))
|
||||
{
|
||||
// Unix time stamp
|
||||
// We use long instead of int, because int is too small after 03:14:07 UTC 2038-01-19
|
||||
var canParse = long.TryParse(input.TrimStart('u'), out var secondsU);
|
||||
|
||||
// Value has to be in the range from -62135596800 to 253402300799
|
||||
if (!canParse || secondsU < UnixTimeSecondsMin || secondsU > UnixTimeSecondsMax)
|
||||
{
|
||||
inputParsingErrorMsg = string.Format(CultureInfo.CurrentCulture, errorMessage, Resources.Microsoft_plugin_timedate_Unix, UnixTimeSecondsMin, UnixTimeSecondsMax);
|
||||
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
timestamp = DateTimeOffset.FromUnixTimeSeconds(secondsU).LocalDateTime;
|
||||
return true;
|
||||
}
|
||||
else if (Regex.IsMatch(input, @"^ums[\+-]?\d{1,13}$") && long.TryParse(input.TrimStart("ums".ToCharArray()), out long millisecondsUms))
|
||||
else if (Regex.IsMatch(input, @"^ums[\+-]?\d+$"))
|
||||
{
|
||||
// Unix time stamp in milliseconds
|
||||
// We use long instead of int because int is too small after 03:14:07 UTC 2038-01-19
|
||||
var canParse = long.TryParse(input.TrimStart("ums".ToCharArray()), out var millisecondsUms);
|
||||
|
||||
// Value has to be in the range from -62135596800000 to 253402300799999
|
||||
if (!canParse || millisecondsUms < UnixTimeMillisecondsMin || millisecondsUms > UnixTimeMillisecondsMax)
|
||||
{
|
||||
inputParsingErrorMsg = string.Format(CultureInfo.CurrentCulture, errorMessage, Resources.Microsoft_plugin_timedate_Unix_Milliseconds, UnixTimeMillisecondsMin, UnixTimeMillisecondsMax);
|
||||
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
timestamp = DateTimeOffset.FromUnixTimeMilliseconds(millisecondsUms).LocalDateTime;
|
||||
return true;
|
||||
}
|
||||
else if (Regex.IsMatch(input, @"^ft\d+$") && long.TryParse(input.TrimStart("ft".ToCharArray()), out long secondsFt))
|
||||
else if (Regex.IsMatch(input, @"^ft\d+$"))
|
||||
{
|
||||
var canParse = long.TryParse(input.TrimStart("ft".ToCharArray()), out var secondsFt);
|
||||
|
||||
// Windows file time
|
||||
// Value has to be in the range from 0 to 2650467707991000000
|
||||
if (!canParse || secondsFt < WindowsFileTimeMin || secondsFt > WindowsFileTimeMax)
|
||||
{
|
||||
inputParsingErrorMsg = string.Format(CultureInfo.CurrentCulture, errorMessage, Resources.Microsoft_plugin_timedate_WindowsFileTime, WindowsFileTimeMin, WindowsFileTimeMax);
|
||||
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
// DateTime.FromFileTime returns as local time.
|
||||
timestamp = DateTime.FromFileTime(secondsFt);
|
||||
return true;
|
||||
}
|
||||
else if (Regex.IsMatch(input, @"^oa[+-]?\d+[,.0-9]*$"))
|
||||
{
|
||||
var canParse = double.TryParse(input.TrimStart("oa".ToCharArray()), out var oADate);
|
||||
|
||||
// OLE Automation date
|
||||
// Input has to be in the range from -657434.99999999 to 2958465.99999999
|
||||
// DateTime.FromOADate returns as local time.
|
||||
if (!canParse || oADate < OADateMin || oADate > OADateMax)
|
||||
{
|
||||
inputParsingErrorMsg = string.Format(CultureInfo.CurrentCulture, errorMessage, Resources.Microsoft_plugin_timedate_OADate, OADateMin, OADateMax);
|
||||
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
timestamp = DateTime.FromOADate(oADate);
|
||||
return true;
|
||||
}
|
||||
else if (Regex.IsMatch(input, @"^exc[+-]?\d+[,.0-9]*$"))
|
||||
{
|
||||
var canParse = double.TryParse(input.TrimStart("exc".ToCharArray()), out var excDate);
|
||||
|
||||
// Excel's 1900 date value
|
||||
// Input has to be in the range from 1 (0 = Fake date) to 2958465.99998843 and not 60 whole number
|
||||
// Because of a bug in Excel and the way it behaves before 3/1/1900 we have to adjust all inputs lower than 61 for +1
|
||||
// DateTime.FromOADate returns as local time.
|
||||
if (!canParse || excDate < 0 || excDate > Excel1900DateMax)
|
||||
{
|
||||
// For the if itself we use 0 as min value that we can show a special message if input is 0.
|
||||
inputParsingErrorMsg = string.Format(CultureInfo.CurrentCulture, errorMessage, Resources.Microsoft_plugin_timedate_Excel1900, Excel1900DateMin, Excel1900DateMax);
|
||||
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Math.Truncate(excDate) == 0 || Math.Truncate(excDate) == 60)
|
||||
{
|
||||
inputParsingErrorMsg = Resources.Microsoft_plugin_timedate_InvalidInput_FakeExcel1900;
|
||||
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
excDate = excDate <= 60 ? excDate + 1 : excDate;
|
||||
timestamp = DateTime.FromOADate(excDate);
|
||||
return true;
|
||||
}
|
||||
else if (Regex.IsMatch(input, @"^exf[+-]?\d+[,.0-9]*$"))
|
||||
{
|
||||
var canParse = double.TryParse(input.TrimStart("exf".ToCharArray()), out var exfDate);
|
||||
|
||||
// Excel's 1904 date value
|
||||
// Input has to be in the range from 0 to 2957003.99998843
|
||||
// Because Excel uses 01/01/1904 as base we need to adjust for +1462
|
||||
// DateTime.FromOADate returns as local time.
|
||||
if (!canParse || exfDate < Excel1904DateMin || exfDate > Excel1904DateMax)
|
||||
{
|
||||
inputParsingErrorMsg = string.Format(CultureInfo.CurrentCulture, errorMessage, Resources.Microsoft_plugin_timedate_Excel1904, Excel1904DateMin, Excel1904DateMax);
|
||||
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
timestamp = DateTime.FromOADate(exfDate + 1462);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
timestamp = new DateTime(1, 1, 1, 1, 1, 1);
|
||||
@@ -125,13 +291,85 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if input is special parsing for Unix time, Unix time in milliseconds or File time.
|
||||
/// Test if input is special parsing for Unix time, Unix time in milliseconds, file time, ...
|
||||
/// </summary>
|
||||
/// <param name="input">String with date/time</param>
|
||||
/// <returns>True if yes, otherwise false</returns>
|
||||
internal static bool IsSpecialInputParsing(string input)
|
||||
{
|
||||
return Regex.IsMatch(input, @"^.*(u|ums|ft)\d");
|
||||
return _regexSpecialInputFormats.IsMatch(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a DateTime object based on the format string
|
||||
/// </summary>
|
||||
/// <param name="date">Date/time object.</param>
|
||||
/// <param name="unix">Value for replacing "Unix Time Stamp".</param>
|
||||
/// <param name="unixMilliseconds">Value for replacing "Unix Time Stamp in milliseconds".</param>
|
||||
/// <param name="calWeek">Value for relacing calendar week.</param>
|
||||
/// <param name="eraShortFormat">Era abbreviation.</param>
|
||||
/// <param name="format">Format definition.</param>
|
||||
/// <returns>Formated date/time string.</returns>
|
||||
internal static string ConvertToCustomFormat(DateTime date, long unix, long unixMilliseconds, int calWeek, string eraShortFormat, string format, CalendarWeekRule firstWeekRule, DayOfWeek firstDayOfTheWeek)
|
||||
{
|
||||
string result = format;
|
||||
|
||||
// DOW: Number of day in week
|
||||
result = _regexCustomDateTimeDow.Replace(result, GetNumberOfDayInWeek(date, firstDayOfTheWeek).ToString(CultureInfo.CurrentCulture));
|
||||
|
||||
// DIM: Days in Month
|
||||
result = _regexCustomDateTimeDim.Replace(result, DateTime.DaysInMonth(date.Year, date.Month).ToString(CultureInfo.CurrentCulture));
|
||||
|
||||
// WOM: Week of Month
|
||||
result = _regexCustomDateTimeWom.Replace(result, GetWeekOfMonth(date, firstDayOfTheWeek).ToString(CultureInfo.CurrentCulture));
|
||||
|
||||
// WOY: Week of Year
|
||||
result = _regexCustomDateTimeWoy.Replace(result, calWeek.ToString(CultureInfo.CurrentCulture));
|
||||
|
||||
// EAB: Era abbreviation
|
||||
result = _regexCustomDateTimeEab.Replace(result, eraShortFormat);
|
||||
|
||||
// WFT: Week of Month
|
||||
if (_regexCustomDateTimeWft.IsMatch(result))
|
||||
{
|
||||
// Special handling as very early dates can't convert.
|
||||
result = _regexCustomDateTimeWft.Replace(result, date.ToFileTime().ToString(CultureInfo.CurrentCulture));
|
||||
}
|
||||
|
||||
// UXT: Unix time stamp
|
||||
result = _regexCustomDateTimeUxt.Replace(result, unix.ToString(CultureInfo.CurrentCulture));
|
||||
|
||||
// UMS: Unix time stamp milli seconds
|
||||
result = _regexCustomDateTimeUms.Replace(result, unixMilliseconds.ToString(CultureInfo.CurrentCulture));
|
||||
|
||||
// OAD: OLE Automation date
|
||||
result = _regexCustomDateTimeOad.Replace(result, ConvertToOleAutomationFormat(date, OADateFormats.OLEAutomation).ToString(CultureInfo.CurrentCulture));
|
||||
|
||||
// EXC: Excel date value with base 1900
|
||||
if (_regexCustomDateTimeExc.IsMatch(result))
|
||||
{
|
||||
// Special handling as very early dates can't convert.
|
||||
result = _regexCustomDateTimeExc.Replace(result, ConvertToOleAutomationFormat(date, OADateFormats.Excel1900).ToString(CultureInfo.CurrentCulture));
|
||||
}
|
||||
|
||||
// EXF: Excel date value with base 1904
|
||||
if (_regexCustomDateTimeExf.IsMatch(result))
|
||||
{
|
||||
// Special handling as very early dates can't convert.
|
||||
result = _regexCustomDateTimeExf.Replace(result, ConvertToOleAutomationFormat(date, OADateFormats.Excel1904).ToString(CultureInfo.CurrentCulture));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test a string for our custom date and time format syntax
|
||||
/// </summary>
|
||||
/// <param name="str">String to test.</param>
|
||||
/// <returns>True if yes and otherwise false</returns>
|
||||
internal static bool StringContainsCustomFormatSyntax(string str)
|
||||
{
|
||||
return _regexCustomDateTimeFormats.IsMatch(str);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -190,4 +428,14 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
Date,
|
||||
DateTime,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Different versions of Date formats based on OLE Automation date
|
||||
/// </summary>
|
||||
internal enum OADateFormats
|
||||
{
|
||||
OLEAutomation,
|
||||
Excel1900,
|
||||
Excel1904,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,11 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
/// </summary>
|
||||
internal bool HideNumberMessageOnGlobalQuery { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value containing the custom format definitions
|
||||
/// </summary>
|
||||
internal List<string> CustomFormats { 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
|
||||
@@ -100,29 +105,6 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
{
|
||||
var optionList = new List<PluginAdditionalOption>
|
||||
{
|
||||
new PluginAdditionalOption()
|
||||
{
|
||||
Key = nameof(CalendarFirstWeekRule),
|
||||
DisplayLabel = Resources.Microsoft_plugin_timedate_SettingFirstWeekRule,
|
||||
DisplayDescription = Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_Description,
|
||||
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Combobox,
|
||||
ComboBoxItems = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_Setting_UseSystemSetting, "-1"),
|
||||
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_FirstDay, "0"),
|
||||
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_FirstFullWeek, "1"),
|
||||
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_FirstFourDayWeek, "2"),
|
||||
},
|
||||
ComboBoxValue = -1,
|
||||
},
|
||||
new PluginAdditionalOption()
|
||||
{
|
||||
Key = nameof(FirstDayOfWeek),
|
||||
DisplayLabel = Resources.Microsoft_plugin_timedate_SettingFirstDayOfWeek,
|
||||
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Combobox,
|
||||
ComboBoxItems = GetSortedListForWeekDaySetting(),
|
||||
ComboBoxValue = -1,
|
||||
},
|
||||
new PluginAdditionalOption()
|
||||
{
|
||||
Key = nameof(OnlyDateTimeNowGlobal),
|
||||
@@ -150,6 +132,38 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
DisplayLabel = Resources.Microsoft_plugin_timedate_SettingHideNumberMessageOnGlobalQuery,
|
||||
Value = false,
|
||||
},
|
||||
new PluginAdditionalOption()
|
||||
{
|
||||
Key = nameof(CalendarFirstWeekRule),
|
||||
DisplayLabel = Resources.Microsoft_plugin_timedate_SettingFirstWeekRule,
|
||||
DisplayDescription = Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_Description,
|
||||
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Combobox,
|
||||
ComboBoxItems = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_Setting_UseSystemSetting, "-1"),
|
||||
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_FirstDay, "0"),
|
||||
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_FirstFullWeek, "1"),
|
||||
new KeyValuePair<string, string>(Resources.Microsoft_plugin_timedate_SettingFirstWeekRule_FirstFourDayWeek, "2"),
|
||||
},
|
||||
ComboBoxValue = -1,
|
||||
},
|
||||
new PluginAdditionalOption()
|
||||
{
|
||||
Key = nameof(FirstDayOfWeek),
|
||||
DisplayLabel = Resources.Microsoft_plugin_timedate_SettingFirstDayOfWeek,
|
||||
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Combobox,
|
||||
ComboBoxItems = GetSortedListForWeekDaySetting(),
|
||||
ComboBoxValue = -1,
|
||||
},
|
||||
new PluginAdditionalOption()
|
||||
{
|
||||
Key = nameof(CustomFormats),
|
||||
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.MultilineTextbox,
|
||||
DisplayLabel = Resources.Microsoft_plugin_timedate_Setting_CustomFormats,
|
||||
DisplayDescription = string.Format(CultureInfo.CurrentCulture, Resources.Microsoft_plugin_timedate_Setting_CustomFormatsDescription.ToString(), "DOW", "DIM", "WOM", "WOY", "EAB", "WFT", "UXT", "UMS", "OAD", "EXC", "EXF", "UTC:"),
|
||||
PlaceholderText = "MyFormat=dd-MMM-yyyy\rMySecondFormat=dddd (Da\\y nu\\mber: DOW)\rMyUtcFormat=UTC:hh:mm:ss",
|
||||
TextValue = string.Empty,
|
||||
},
|
||||
};
|
||||
|
||||
return optionList;
|
||||
@@ -172,6 +186,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
TimeWithSeconds = GetSettingOrDefault(settings, nameof(TimeWithSeconds));
|
||||
DateWithWeekday = GetSettingOrDefault(settings, nameof(DateWithWeekday));
|
||||
HideNumberMessageOnGlobalQuery = GetSettingOrDefault(settings, nameof(HideNumberMessageOnGlobalQuery));
|
||||
CustomFormats = GetMultilineTextSettingOrDefault(settings, nameof(CustomFormats));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -204,6 +219,21 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
|
||||
return option?.ComboBoxValue ?? GetAdditionalOptions().First(x => x.Key == name).ComboBoxValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the combobox value 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 List<string> GetMultilineTextSettingOrDefault(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?.TextValueAsMultilineList ?? GetAdditionalOptions().First(x => x.Key == name).TextValueAsMultilineList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a sorted list of values for the combo box of 'first day of week' setting.
|
||||
/// The list is sorted based on the current system culture setting.
|
||||
|
||||
Reference in New Issue
Block a user