C# Разбирать переменные даты из строки (например,% сегодня-4 дня%,% сейчас + 15 минут%) - PullRequest
1 голос
/ 07 мая 2020

У меня есть консольное приложение, в которое мне часто нужно передавать переменную DateTime. В настоящее время я использую переключатель для возврата значения, когда переменная соответствует точной строке, и просто добавляю новые по мере необходимости. Я хотел бы спроектировать его так, чтобы он мог анализировать переменную и выполнять правильную операцию на основе строки.

Вот несколько примеров в формате, который я сейчас использую.

%today%
%today-5day%
%today-1sec%
%today+1month%
%now+15min%
%now-30sec%
%now+1week%

Я также хотел бы проанализировать переменную, такую ​​как

%lastmonday%
%nextfriday%
%monthstart%
%yearend% 

et c. но они, казалось бы, лучше всего обрабатываются в переключателе, и если они не совпадают, передаются функции для анализа и вычисления приведенных выше примеров.

Я не уверен в наиболее элегантном методе для достижения этого и не смог найти много в моих поисках.

Ответы [ 2 ]

4 голосов
/ 07 мая 2020

Вы можете попробовать регулярные выражения для синтаксического анализа. Сначала давайте извлечем модель: поскольку команды имеют формат

Start (zero or more Modifiers)

Например,

%today-5day+3hour%

, где today - это Start, а -5day и +3hour - * 1013. * нам нужны две модели

using System.Linq;
using System.Text.RegularExpressions;

...  

//TODO: add more starting points here
private static Dictionary<string, Func<DateTime>> s_Starts =
  new Dictionary<string, Func<DateTime>>(StringComparer.OrdinalIgnoreCase) {
    { "now", () => DateTime.Now},
    { "today", () => DateTime.Today},
    { "yearend", () => new DateTime(DateTime.Today.Year, 12, 31) },
};

//TODO: add more modifiers here 
private static Dictionary<string, Func<DateTime, int, DateTime>> s_Modifiers =
  new Dictionary<string, Func<DateTime, int, DateTime>>(StringComparer.OrdinalIgnoreCase) {
    { "month", (source, x) => source.AddMonths(x)},
    { "week", (source, x) => source.AddDays(x * 7)},
    { "day", (source, x) => source.AddDays(x)},
    { "hour", (source, x) => source.AddHours(x)},
    { "min", (source, x) => source.AddMinutes(x)},
    { "sec", (source, x) => source.AddSeconds(x)},
};

Имея модель (два словаря выше), мы можем реализовать MyParse процедуру:

private static DateTime MyParse(string value) {
  if (null == value)
    throw new ArgumentNullException(nameof(value));

  var match = Regex.Match(value, 
    @"^%(?<start>[a-zA-Z]+)(?<modify>\s*[+-]\s*[0-9]+\s*[a-zA-Z]+)*%$");

  if (!match.Success)
    throw new FormatException("Invalid format");

  string start = match.Groups["start"].Value;

  DateTime result = s_Starts.TryGetValue(start, out var startFunc)
    ? startFunc()
    : throw new FormatException($"Start Date(Time) {start} is unknown.");

  var adds = Regex
    .Matches(match.Groups["modify"].Value, @"([+\-])\s*([0-9]+)\s*([a-zA-Z]+)")
    .Cast<Match>()
    .Select(m => (kind : m.Groups[3].Value, 
                amount : int.Parse(m.Groups[1].Value + m.Groups[2].Value)));

  foreach (var (kind, amount) in adds) 
    if (s_Modifiers.TryGetValue(kind, out var func))
      result = func(result, amount);
    else
      throw new FormatException($"Modification {kind} is unknown.");

  return result;
}

Демо:

  string[] tests = new string[] {
    "%today%",
    "%today-5day%",
    "%today-1sec%",
    "%today+1month%",
    "%now+15min%",
    "%now-30sec%",
    "%now+1week%",
  };

  string report = string.Join(Environment.NewLine, tests
    .Select(test => $"{test,-15} :: {MyParse(test):dd MM yyyy HH:mm:ss}")
  );

  Console.Write(report);

Результат:

%today%         :: 07 05 2020 00:00:00
%today-5day%    :: 02 05 2020 00:00:00
%today-1sec%    :: 06 05 2020 23:59:59
%today+1month%  :: 07 06 2020 00:00:00
%now+15min%     :: 07 05 2020 18:34:55
%now-30sec%     :: 07 05 2020 18:19:25
%now+1week%     :: 14 05 2020 18:19:55
0 голосов
/ 07 мая 2020

Это небольшой пример разбора строки, в которой есть сегодня и день (на примере% today-5day%).

Примечание: это будет работать, только если значение, переданное для добавленные дни находятся в диапазоне от 0 до 9, для обработки больших чисел вам придется l oop над результатом строки.

Вы можете следить за этим блоком кода, чтобы проанализировать и другие результаты.

            switch (date)
            {
                case var a when a.Contains("day") && a.Contains("today"):
                    return DateTime.Today.AddDays(char.GetNumericValue(date[date.Length - 5]));
                default:
                    return DateTime.Today;
            }
...