Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions CronCraft.Test/CronHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,98 @@ public void Test_Every6MonthsOn27th()
Assert.AreEqual("Every 6 months on the 27th at 04:00 AM", result);
}

[TestMethod]
public void Test_SpecificMonthAndDay()
{
var settings = CreateCustomSettings();

string result = CronHelper.ToHumanReadable("0 9 1 3 *", settings);

Assert.AreEqual("Every year on March 1st at 09:00 AM", result);
}

[TestMethod]
public void Test_SpecificMonthAndDay_December()
{
var settings = CreateCustomSettings();

string result = CronHelper.ToHumanReadable("0 0 25 12 *", settings);

Assert.AreEqual("Every year on December 25th at 12:00 AM", result);
}

[TestMethod]
public void Test_SpecificMonthWithWildcardDay()
{
var settings = CreateCustomSettings();

string result = CronHelper.ToHumanReadable("0 9 * 6 *", settings);

Assert.AreEqual("Every day in June at 09:00 AM", result);
}

[TestMethod]
public void Test_MonthStepWithWildcardDay()
{
var settings = CreateCustomSettings();

string result = CronHelper.ToHumanReadable("0 9 * */3 *", settings);

Assert.AreEqual("Every 3 months at 09:00 AM", result);
}

[TestMethod]
public void Test_SpecificMonthWithDayOfWeek()
{
var settings = new CronSettings { Language = "en", DayNameFormat = "short" };

string result = CronHelper.ToHumanReadable("0 9 * 6 1", settings);

Assert.AreEqual("Every Mon in June at 09:00 AM", result);
}

[DataTestMethod]
[DataRow("en", "Every year on March 1st at 09:00 AM")]
[DataRow("es", "Cada año el día 1st de Marzo a las 09:00 AM")]
[DataRow("fr", "Chaque année le 1st en Mars à 09:00 AM")]
[DataRow("de", "Jedes Jahr am 1. im März um 09:00 Uhr")]
[DataRow("pt", "Todo ano no dia 1º de Março às 09:00")]
[DataRow("it", "Ogni anno il giorno 1 di Marzo alle 09:00")]
[DataRow("nl", "Elk jaar op 1e Maart om 09:00")]
[DataRow("zh", "每年三月第 1 天 09:00")]
[DataRow("ja", "毎年3月1日 09:00に")]
public void Test_SpecificMonthName_IsLocalized(string language, string expected)
{
var settings = new CronSettings { Language = language };

string result = CronHelper.ToHumanReadable("0 9 1 3 *", settings);

Assert.AreEqual(expected, result);
}

[DataTestMethod]
[DataRow("en", "Every Mon in June at 09:00 AM")]
[DataRow("es", "Cada Lun en Junio a las 09:00 AM")]
[DataRow("fr", "Chaque Lun en Juin à 09:00 AM")]
[DataRow("de", "Jeden Mo im Juni um 09:00 Uhr")]
[DataRow("pt", "Em Seg em Junho às 09:00")]
[DataRow("it", "Ogni Lun in Giugno alle 09:00")]
[DataRow("nl", "Elke Ma in Juni om 09:00")]
[DataRow("zh", "六月每周一 09:00")]
[DataRow("ja", "6月の毎週月曜 09:00に")]
public void Test_SpecificMonthWithDayOfWeek_IsLocalized(string language, string expected)
{
var settings = new CronSettings
{
Language = language,
DayNameFormat = "short"
};

string result = CronHelper.ToHumanReadable("0 9 * 6 1", settings);

Assert.AreEqual(expected, result);
}

[TestMethod]
public void Test_SpecificDayAndWeek()
{
Expand Down
111 changes: 111 additions & 0 deletions CronCraft/Extensions/CronHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@ private static string ToHumanReadableEnglish(string cron, CronSettings settings,
["EveryXHours"] = "Every {0} hours",
["EveryXHoursOn"] = "Every {0} hours on {1}",
["EveryMonthOnDay"] = "Every month on the {0}",
["EveryYearOnMonthDay"] = "Every year on {0} {1}",
["EveryDayInMonth"] = "Every day in {0}",
["EveryXMonths"] = "Every {0} months",
["OnDayAndWeek"] = "On {0} and {1}",
["EveryDayOfWeek"] = "Every {0}",
["EveryDayOfWeekInMonth"] = "Every {0} in {1}",
["EveryXMonthsOnDay"] = "Every {0} months on the {1}",
["TimeFormat"] = "12"
});
Expand All @@ -61,8 +65,12 @@ private static string ToHumanReadableSpanish(string cron, CronSettings settings,
["EveryXHours"] = "Cada {0} horas",
["EveryXHoursOn"] = "Cada {0} horas los {1}",
["EveryMonthOnDay"] = "Cada mes el día {0}",
["EveryYearOnMonthDay"] = "Cada año el día {1} de {0}",
["EveryDayInMonth"] = "Cada día en {0}",
["EveryXMonths"] = "Cada {0} meses",
["OnDayAndWeek"] = "El {0} y los {1}",
["EveryDayOfWeek"] = "Cada {0}",
["EveryDayOfWeekInMonth"] = "Cada {0} en {1}",
["EveryXMonthsOnDay"] = "Cada {0} meses el día {1}",
["TimeFormat"] = "12"
});
Expand All @@ -78,8 +86,12 @@ private static string ToHumanReadableFrench(string cron, CronSettings settings,
["EveryXHours"] = "Toutes les {0} heures",
["EveryXHoursOn"] = "Toutes les {0} heures le {1}",
["EveryMonthOnDay"] = "Chaque mois le {0}",
["EveryYearOnMonthDay"] = "Chaque année le {1} en {0}",
["EveryDayInMonth"] = "Chaque jour en {0}",
["EveryXMonths"] = "Tous les {0} mois",
["OnDayAndWeek"] = "Le {0} et le {1}",
["EveryDayOfWeek"] = "Chaque {0}",
["EveryDayOfWeekInMonth"] = "Chaque {0} en {1}",
["EveryXMonthsOnDay"] = "Tous les {0} mois le {1}",
["TimeFormat"] = "12"
});
Expand All @@ -94,8 +106,12 @@ private static string ToHumanReadableGerman(string cron, CronSettings settings,
["EveryXHours"] = "Alle {0} Stunden",
["EveryXHoursOn"] = "Alle {0} Stunden am {1}",
["EveryMonthOnDay"] = "Jeden Monat am {0}",
["EveryYearOnMonthDay"] = "Jedes Jahr am {1} im {0}",
["EveryDayInMonth"] = "Jeden Tag im {0}",
["EveryXMonths"] = "Alle {0} Monate",
["OnDayAndWeek"] = "Am {0} und am {1}",
["EveryDayOfWeek"] = "Jeden {0}",
["EveryDayOfWeekInMonth"] = "Jeden {0} im {1}",
["EveryXMonthsOnDay"] = "Alle {0} Monate am {1}",
["TimeFormat"] = "24"
});
Expand All @@ -109,8 +125,12 @@ private static string ToHumanReadablePortuguese(string cron, CronSettings settin
["EveryXHours"] = "A cada {0} horas",
["EveryXHoursOn"] = "A cada {0} horas em {1}",
["EveryMonthOnDay"] = "Todo mês no dia {0}",
["EveryYearOnMonthDay"] = "Todo ano no dia {1} de {0}",
["EveryDayInMonth"] = "Todos os dias em {0}",
["EveryXMonths"] = "A cada {0} meses",
["OnDayAndWeek"] = "No dia {0} e em {1}",
["EveryDayOfWeek"] = "Em {0}",
["EveryDayOfWeekInMonth"] = "Em {0} em {1}",
["EveryXMonthsOnDay"] = "A cada {0} meses no dia {1}",
["TimeFormat"] = "24"
});
Expand All @@ -124,8 +144,12 @@ private static string ToHumanReadableItalian(string cron, CronSettings settings,
["EveryXHours"] = "Ogni {0} ore",
["EveryXHoursOn"] = "Ogni {0} ore di {1}",
["EveryMonthOnDay"] = "Ogni mese il giorno {0}",
["EveryYearOnMonthDay"] = "Ogni anno il giorno {1} di {0}",
["EveryDayInMonth"] = "Ogni giorno in {0}",
["EveryXMonths"] = "Ogni {0} mesi",
["OnDayAndWeek"] = "Il giorno {0} e di {1}",
["EveryDayOfWeek"] = "Ogni {0}",
["EveryDayOfWeekInMonth"] = "Ogni {0} in {1}",
["EveryXMonthsOnDay"] = "Ogni {0} mesi il giorno {1}",
["TimeFormat"] = "24"
});
Expand All @@ -139,8 +163,12 @@ private static string ToHumanReadableDutch(string cron, CronSettings settings, T
["EveryXHours"] = "Elke {0} uur",
["EveryXHoursOn"] = "Elke {0} uur op {1}",
["EveryMonthOnDay"] = "Elke maand op de {0}",
["EveryYearOnMonthDay"] = "Elk jaar op {1} {0}",
["EveryDayInMonth"] = "Elke dag in {0}",
["EveryXMonths"] = "Elke {0} maanden",
["OnDayAndWeek"] = "Op de {0} en op {1}",
["EveryDayOfWeek"] = "Elke {0}",
["EveryDayOfWeekInMonth"] = "Elke {0} in {1}",
["EveryXMonthsOnDay"] = "Elke {0} maanden op de {1}",
["TimeFormat"] = "24"
});
Expand All @@ -154,8 +182,12 @@ private static string ToHumanReadableChinese(string cron, CronSettings settings,
["EveryXHours"] = "每 {0} 小时",
["EveryXHoursOn"] = "每 {0} 小时({1})",
["EveryMonthOnDay"] = "每月第 {0} 天",
["EveryYearOnMonthDay"] = "每年{0}第 {1} 天",
["EveryDayInMonth"] = "{0}每天",
["EveryXMonths"] = "每 {0} 个月",
["OnDayAndWeek"] = "每月第 {0} 天和{1}",
["EveryDayOfWeek"] = "每{0}",
["EveryDayOfWeekInMonth"] = "{1}每{0}",
["EveryXMonthsOnDay"] = "每 {0} 个月的第 {1} 天",
["TimeFormat"] = "24"
});
Expand All @@ -169,8 +201,12 @@ private static string ToHumanReadableJapanese(string cron, CronSettings settings
["EveryXHours"] = "{0}時間ごと",
["EveryXHoursOn"] = "{1}に{0}時間ごと",
["EveryMonthOnDay"] = "毎月{0}日",
["EveryYearOnMonthDay"] = "毎年{0}{1}日",
["EveryDayInMonth"] = "{0}は毎日",
["EveryXMonths"] = "{0}か月ごと",
["OnDayAndWeek"] = "毎月{0}日と{1}",
["EveryDayOfWeek"] = "毎週{0}",
["EveryDayOfWeekInMonth"] = "{1}の毎週{0}",
["EveryXMonthsOnDay"] = "{0}か月ごとの{1}日",
["TimeFormat"] = "24"
});
Expand Down Expand Up @@ -269,17 +305,32 @@ string Phrase(string key, params object[] args) =>
}

if ((dayOfWeek == "*" || dayOfWeek == "?") && dayOfMonth == "*")
{
if (month.StartsWith("*/"))
return $"{Phrase("EveryXMonths", month.Replace("*/", ""))} {Phrase("AtTime", time)}";

if (TryGetMonthName(month, settings.Language, out var monthName))
return $"{Phrase("EveryDayInMonth", monthName)} {Phrase("AtTime", time)}";

return Phrase("EveryDay") + " " + Phrase("AtTime", time);
}

if (dayOfWeek != "*" && dayOfMonth == "*")
{
if (TryGetMonthName(month, settings.Language, out var monthName))
return $"{Phrase("EveryDayOfWeekInMonth", JoinDays(dayOfWeek, daysMap), monthName)} {Phrase("AtTime", time)}";

return $"{Phrase("EveryDayOfWeek", JoinDays(dayOfWeek, daysMap))} {Phrase("AtTime", time)}";
Comment on lines 318 to 323
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

if (dayOfMonth != "*" && (dayOfWeek == "*" || dayOfWeek == "?"))
{
var ordinal = DescribeOrdinals(dayOfMonth, settings.Language);

return month.StartsWith("*/")
? $"{Phrase("EveryXMonthsOnDay", month.Replace("*/", ""), ordinal)} {Phrase("AtTime", time)}"
: TryGetMonthName(month, settings.Language, out var monthName)
? $"{Phrase("EveryYearOnMonthDay", monthName, ordinal)} {Phrase("AtTime", time)}"
: $"{Phrase("EveryMonthOnDay", ordinal)} {Phrase("AtTime", time)}";
}

Expand Down Expand Up @@ -405,6 +456,66 @@ private static string DescribeOrdinals(string expression, string language = "en"
return JoinDescriptions(descriptions);
}

private static bool TryGetMonthName(string month, string language, out string monthName)
{
monthName = string.Empty;

if (!int.TryParse(month, out var monthNumber) || monthNumber is < 1 or > 12)
return false;

var names = language.ToLowerInvariant() switch
{
"es" => new string[]
{
"Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio",
"Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"
},
"fr" => new string[]
{
"Janvier", "Février", "Mars", "Avril", "Mai", "Juin",
"Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"
},
"de" => new string[]
{
"Januar", "Februar", "März", "April", "Mai", "Juni",
"Juli", "August", "September", "Oktober", "November", "Dezember"
},
"pt" => new string[]
{
"Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho",
"Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"
},
"it" => new string[]
{
"Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno",
"Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"
},
"nl" => new string[]
{
"Januari", "Februari", "Maart", "April", "Mei", "Juni",
"Juli", "Augustus", "September", "Oktober", "November", "December"
},
"zh" => new string[]
{
"一月", "二月", "三月", "四月", "五月", "六月",
"七月", "八月", "九月", "十月", "十一月", "十二月"
},
"ja" => new string[]
{
"1月", "2月", "3月", "4月", "5月", "6月",
"7月", "8月", "9月", "10月", "11月", "12月"
},
_ => new string[]
{
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
}
};

monthName = names[monthNumber - 1];
return true;
}

private static bool TryDescribeTimePattern(
string hour,
string minute,
Expand Down
Loading