Last active
July 3, 2023 17:05
-
-
Save paviad/0d1f668764195c42d26d1582769419ee to your computer and use it in GitHub Desktop.
Translation of https://github.com/jakubroztocil/rrule/blob/master/src/nlp/totext.ts to C# (English only)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Ical.Net; | |
using Ical.Net.DataTypes; | |
public class ToText { | |
private static readonly string[] MonthNames = { | |
"January", | |
"February", | |
"March", | |
"April", | |
"May", | |
"June", | |
"July", | |
"August", | |
"September", | |
"October", | |
"November", | |
"December", | |
}; | |
private readonly List<int>? _byMonthDay; | |
private readonly ByWeekDayRec? _byWeekDay; | |
private readonly Func<DateTime, string> _dateFormatter; | |
private readonly RecurrencePattern _recurrencePattern; | |
private readonly List<string> _text = new(); | |
public ToText(RecurrencePattern recurrencePattern, Func<DateTime, string>? dateFormatter = null) { | |
_recurrencePattern = recurrencePattern; | |
_dateFormatter = dateFormatter ?? DefaultDateFormatter; | |
DayOfWeek[] weekdays = { | |
DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, | |
}; | |
DayOfWeek[] weekend = { DayOfWeek.Friday, DayOfWeek.Saturday }; | |
bool Contains(DayOfWeek d) => _recurrencePattern.ByDay.Any(z => z.DayOfWeek == d); | |
bool NContains(DayOfWeek d) => _recurrencePattern.ByDay.All(z => z.DayOfWeek != d); | |
if (_recurrencePattern.ByDay.Count > 0) { | |
var allWeeks = _recurrencePattern.ByDay.Where(r => r.Offset == 0).ToList(); | |
var someWeeks = _recurrencePattern.ByDay.Where(r => r.Offset != 0).ToList(); | |
var isWeekDays = weekdays.All(Contains) && weekend.All(NContains); | |
var isEveryDay = weekdays.All(Contains) && weekend.All(Contains); | |
_byWeekDay = new ByWeekDayRec(allWeeks, someWeeks, isWeekDays, isEveryDay); | |
} | |
if (_recurrencePattern.ByMonthDay.Count > 0) { | |
var positive = _recurrencePattern.ByMonthDay.Where(r => r > 0).Order().ToList(); | |
var negative = _recurrencePattern.ByMonthDay.Where(r => r < 0).Order().ToList(); | |
_byMonthDay = positive.Concat(negative).ToList(); | |
} | |
} | |
public string ToFriendlyName() { | |
_text.Add("Every"); | |
switch (_recurrencePattern.Frequency) { | |
case FrequencyType.Minutely: | |
Minutely(); | |
break; | |
case FrequencyType.Hourly: | |
Hourly(); | |
break; | |
case FrequencyType.Daily: | |
Daily(); | |
break; | |
case FrequencyType.Weekly: | |
Weekly(); | |
break; | |
case FrequencyType.Monthly: | |
Monthly(); | |
break; | |
case FrequencyType.Yearly: | |
Yearly(); | |
break; | |
case FrequencyType.Secondly: | |
case FrequencyType.None: | |
default: | |
throw new ArgumentOutOfRangeException(); | |
} | |
if (_recurrencePattern.Until != DateTime.MinValue) { | |
_text.Add("until"); | |
var until = _recurrencePattern.Until; | |
_text.Add(_dateFormatter(until)); | |
} | |
else if (_recurrencePattern.Count > 0) { | |
_text.Add("for"); | |
_text.Add(_recurrencePattern.Count.ToString()); | |
_text.Add(Plural(_recurrencePattern.Count) ? "times" : "time"); | |
} | |
if (!IsFullyConvertible()) { | |
_text.Add("(~ approximate)"); | |
} | |
return string.Join(' ', _text); | |
} | |
private static string DefaultDateFormatter(DateTime d) { | |
var utc = d.ToUniversalTime(); | |
var year = utc.Year; | |
var month = MonthNames[utc.Month]; | |
var day = utc.Day; | |
return $"{month} ${day}, ${year}"; | |
} | |
private static string List<T>(IEnumerable<T> arr, Func<T, string> getText, string? finalDelim = null, | |
string delim = ",") { | |
static string DelimJoin(IReadOnlyList<string> array, string delimiter, string finalDelimiter) { | |
var list = ""; | |
for (var i = 0; i < array.Count; i++) { | |
if (i != 0) { | |
if (i == array.Count - 1) { | |
list += $" {finalDelimiter} "; | |
} | |
else { | |
list += $"{delimiter} "; | |
} | |
} | |
list += array[i]; | |
} | |
return list; | |
} | |
var strarr = arr.Select(getText).DefaultIfEmpty("").ToList(); | |
if (finalDelim is not null) { | |
return DelimJoin(strarr, delim, finalDelim); | |
} | |
return strarr.Aggregate((x, y) => $"{x}{delim} {y}"); | |
} | |
private static string MonthText(int m) { | |
return MonthNames[m - 1]; | |
} | |
private static string Nth(int n) { | |
if (n == -1) { | |
return "last"; | |
} | |
var npos = Math.Abs(n); | |
var nth = npos switch { | |
1 => npos + "st", | |
2 => npos + "nd", | |
3 => npos + "rd", | |
21 => npos + "st", | |
22 => npos + "nd", | |
23 => npos + "rd", | |
31 => npos + "st", | |
_ => npos + "th", | |
}; | |
return n < 0 ? nth + " " + "last" : nth; | |
} | |
private static bool Plural(int n) => n % 100 != 1; | |
private static string WeekDayText(WeekDay r) => r.DayOfWeek.ToString(); | |
private void ByHour() { | |
_text.Add("at"); | |
_text.Add(List(_recurrencePattern.ByHour, x => x.ToString(), "and")); | |
} | |
private void ByMonth() { | |
_text.Add(List(_recurrencePattern.ByMonth, MonthText, "and")); | |
} | |
private void ByMonthDay() { | |
if (_byWeekDay is { AllWeeks.Count: > 0 }) { | |
_text.Add("on"); | |
_text.Add(List(_byWeekDay.AllWeeks, WeekDayText, "or")); | |
_text.Add("the"); | |
_text.Add(List(_byMonthDay!, Nth, "or")); | |
} | |
else { | |
_text.Add("on the"); | |
_text.Add(List(_byMonthDay!, Nth, "and")); | |
} | |
} | |
private void ByWeekDay() { | |
if (_byWeekDay is { AllWeeks.Count: > 0, IsWeekDays: false }) { | |
_text.Add("on"); | |
_text.Add(List(_byWeekDay.AllWeeks, WeekDayText)); | |
} | |
if (_byWeekDay is { SomeWeeks.Count: > 0 }) { | |
if (_byWeekDay is { AllWeeks.Count: > 0 }) { | |
_text.Add("and"); | |
} | |
_text.Add("on the"); | |
List(_byWeekDay.SomeWeeks, WeekDayText, "and"); | |
} | |
} | |
private void Daily() { | |
if (_recurrencePattern.Interval != 1) { | |
_text.Add(_recurrencePattern.Interval.ToString()); | |
} | |
if (_byWeekDay is { IsWeekDays: true }) { | |
_text.Add(Plural(_recurrencePattern.Interval) ? "weekdays" : "weekday"); | |
} | |
else { | |
_text.Add(Plural(_recurrencePattern.Interval) ? "days" : "day"); | |
} | |
if (_recurrencePattern.ByMonth.Count > 0) { | |
_text.Add("in"); | |
ByMonth(); | |
} | |
if (_byMonthDay is not null) { | |
ByMonthDay(); | |
} | |
else if (_byWeekDay is not null) { | |
ByWeekDay(); | |
} | |
else if (_recurrencePattern.ByHour.Count > 0) { | |
ByHour(); | |
} | |
} | |
private void Hourly() { | |
if (_recurrencePattern.Interval != 1) { | |
_text.Add(_recurrencePattern.Interval.ToString()); | |
} | |
_text.Add(Plural(_recurrencePattern.Interval) ? "hours" : "hour"); | |
} | |
private bool IsFullyConvertible() { | |
if (_recurrencePattern.Frequency switch { | |
FrequencyType.None => true, | |
FrequencyType.Secondly => true, | |
_ => false, | |
}) { | |
return false; | |
} | |
if (_recurrencePattern.Until != DateTime.MinValue && _recurrencePattern.Count > 0) { | |
return false; | |
} | |
return true; | |
} | |
private void Minutely() { | |
if (_recurrencePattern.Interval != 1) { | |
_text.Add(_recurrencePattern.Interval.ToString()); | |
} | |
_text.Add(Plural(_recurrencePattern.Interval) ? "minutes" : "minute"); | |
} | |
private void Monthly() { | |
if (_recurrencePattern.ByMonth.Count > 0) { | |
if (_recurrencePattern.Interval != 1) { | |
_text.Add(_recurrencePattern.Interval.ToString()); | |
_text.Add("months"); | |
if (Plural(_recurrencePattern.Interval)) { | |
_text.Add("in"); | |
} | |
} | |
ByMonth(); | |
} | |
else { | |
if (_recurrencePattern.Interval != 1) { | |
_text.Add(_recurrencePattern.Interval.ToString()); | |
} | |
_text.Add(Plural(_recurrencePattern.Interval) ? "months" : "month"); | |
} | |
if (_byMonthDay is not null) { | |
ByMonthDay(); | |
} | |
else if (_byWeekDay is { IsWeekDays: true }) { | |
_text.Add("on"); | |
_text.Add("weekdays"); | |
} | |
else { | |
ByWeekDay(); | |
} | |
} | |
private void Weekly() { | |
if (_recurrencePattern.Interval != 1) { | |
_text.Add(_recurrencePattern.Interval.ToString()); | |
_text.Add(Plural(_recurrencePattern.Interval) ? "weeks" : "week"); | |
} | |
if (_byWeekDay is { IsWeekDays: true }) { | |
if (_recurrencePattern.Interval == 1) { | |
_text.Add(Plural(_recurrencePattern.Interval) ? "weekdays" : "weekday"); | |
} | |
else { | |
_text.Add("on"); | |
_text.Add("weekdays"); | |
} | |
} | |
else if (_byWeekDay is { IsEveryDay: true }) { | |
_text.Add(Plural(_recurrencePattern.Interval) ? "days" : "day"); | |
} | |
else { | |
if (_recurrencePattern.Interval == 1) { | |
_text.Add("week"); | |
} | |
if (_recurrencePattern.ByMonth.Count > 0) { | |
_text.Add("in"); | |
ByMonth(); | |
} | |
if (_byMonthDay is not null) { | |
ByMonthDay(); | |
} | |
else if (_byWeekDay is not null) { | |
ByWeekDay(); | |
} | |
} | |
} | |
private void Yearly() { | |
if (_recurrencePattern.ByMonth.Count > 0) { | |
if (_recurrencePattern.Interval != 1) { | |
_text.Add(_recurrencePattern.Interval.ToString()); | |
_text.Add("years"); | |
} | |
ByMonth(); | |
} | |
else { | |
if (_recurrencePattern.Interval != 1) { | |
_text.Add(_recurrencePattern.Interval.ToString()); | |
} | |
_text.Add(Plural(_recurrencePattern.Interval) ? "years" : "year"); | |
} | |
if (_byMonthDay is not null) { | |
ByMonthDay(); | |
} | |
else if (_byWeekDay is not null) { | |
ByWeekDay(); | |
} | |
if (_recurrencePattern.ByYearDay.Count > 0) { | |
_text.Add("on the"); | |
_text.Add(List(_recurrencePattern.ByYearDay, Nth, "and")); | |
_text.Add("day"); | |
} | |
if (_recurrencePattern.ByWeekNo.Count > 0) { | |
_text.Add("in"); | |
_text.Add(Plural(_recurrencePattern.ByWeekNo.Count) ? "weeks" : "week"); | |
_text.Add(List(_recurrencePattern.ByWeekNo, x => x.ToString(), "and")); | |
} | |
} | |
private record ByWeekDayRec(List<WeekDay> AllWeeks, List<WeekDay> SomeWeeks, bool IsWeekDays, bool IsEveryDay); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment