using System.Collections.Generic;
using System.Globalization;
using System.Collections;
using CSharpFunctionalExtensions;
using System.Text.RegularExpressions;
using System.ComponentModel;
public static void Main()
var culture = SetCulture();
Console.WriteLine($"________________________________________________________________________________________________________");
"04 de Dezembro de 2001",
TestDates(datesToTest, culture);
TestDates(datesToTest, new CultureInfo("en-US"));
Console.WriteLine($"________________________________________________________________________________________________________");
Console.WriteLine($"-----------------------Testing Intervals-------------------------------------------------");
(string date, bool forceBeginDatePreviousYearIfReasonable, bool sameFrameIntervalIsAllowed)[] intervalsToTest = {
(" entre 2022 e 2021", false, false),
(" entre 2013 e 2021", false, false),
(" entre 19092007 e 12092022", false, false),
(" entre 09192007 e 09122022", false, false),
(" entre 19092022 e 12092022", false, false),
(" entre 09192022 e 09122022", false, false),
(" de 092017 até 102018", false, false),
("de 092019 até 102018", false, false),
(" de 2017 a 2018", false, false),
(" de 1017 a 2318", false, false),
("de 2109 até 21102022", false, false),
("de 2111 até 21102022", false, false),
("de 2111 até 21102022", true, false),
("de 21 até 23102022", false, false)
TestInterval(intervalsToTest, culture);
TestInterval(intervalsToTest, new CultureInfo("en-US"));
Console.WriteLine($"-----------------------Testing Dates-------------------------------------------------");
var pattern = @"\b(?<datewithoutday>(?<month>0[1-9]|1[012])(?<year>19|20[0-9]{2}))|(?<datewithday>(?<day>0[1-9]|[12][0-9]|3[01])(?<month>0[1-9]|1[012])(?<year>19[0-9]{2}|20[0-9]{2}|21[0-9]{2}))|(?<dateonlyyear>(?<year>19[0-9]{2}|20[0-9]{2}|21[0-9]{2}))\b";
(string date, string replacePattern, string mask, bool acceptWildCard)[] datesWithMaskToTest = {
("20__", string.Empty, "yyyy", true),
("20__", string.Empty, "yyyy", false),
("2006", string.Empty, "yyyy", false),
("2138", string.Empty, "yyyy", false),
("9238", string.Empty, "yyyy", false),
("092006", @"$2/$3", "MM/yyyy", false),
("122006", @"$2/$3", "MM/yyyy", false),
("129238", @"$2/$3", "MM/yyyy", false),
("122138", @"$2/$3", "MM/yyyy", false),
("12-2138", @"$2$3", "MM-yyyy", false),
("03032020", @"$5/$2/$3", "dd/MM/yyyy", false),
("12092006", @"$5/$2/$3", "dd/MM/yyyy", false),
("12-01-2138", @"$5$2$3", "dd-MM-yyyy", false)
foreach (var d in datesWithMaskToTest)
TestDate(d.date, pattern, d.replacePattern, d.mask, culture, d.acceptWildCard);
var result = DateInSentence.FilterAbsurdDates(Result.Success(d.date), culture, d.acceptWildCard);
Console.WriteLine($"This date is {(result.IsSuccess ? "valid" : "not valid: " + result.Error)}");
Console.WriteLine("#################################################################################");
const string GrabDatesUTC = @"(?<dateUTC>(?<year>-?(?:[1-9][0-9]*)?[0-9]{4})-(?<month>1[0-2]|0[1-9])-(?<day>3[01]|0[1-9]|[12][0-9])T(?<hour>2[0-3]|[01][0-9]):(?<minute>[0-5][0-9]):(?<second>[0-5][0-9])(\\.[0-9]+)?(?<zeroUTCoffse>Z)?|(?<year>(?:[1-9][0-9]*)?[0-9]{4})(?<month>1[0-2]|0[1-9])(?<day>3[01]|0[1-9]|[12][0-9])(?<hour>2[0-3]|[01][0-9])(?<minute>[0-5][0-9])(?<second>[0-5][0-9])(\\.[0-9]+)?(?<zeroUTCoffse>Z)?)";
TestDate("20230605161406", GrabDatesUTC, string.Empty, "yyyyMMddHHmmss", culture, false);
static CultureInfo SetCulture()
CultureInfo culture = CultureInfo.CurrentCulture;
Console.WriteLine($"The current culture DisplayName is {culture.DisplayName}");
Console.WriteLine($"The current culture NativeName is {culture.NativeName}");
Console.WriteLine($"The current culture IsNeutralCulture? {culture.IsNeutralCulture}");
Console.WriteLine($"Before setting, CultureInfo is {culture.Name.ToString()}");
culture = new CultureInfo("pt-BR");
Console.WriteLine($"After setting, CultureInfo is {culture.Name.ToString()}");
Console.WriteLine($"After setting, CultureInfo DisplayName is {culture.DisplayName}");
Console.WriteLine($"After setting, CultureInfo NativeName is {culture.NativeName}");
Console.WriteLine($"The current culture IsNeutralCulture? {culture.IsNeutralCulture}");
CultureInfo.CurrentCulture = culture;
static void TestDates(string[] datesToTest, CultureInfo culture)
DateTime dateToTest = DateTime.MinValue;
Console.WriteLine($"################################################ Dates ({culture.Name.ToString()}) ################################################");
var orderedDates = new List<(DateTime date, string rawDate)>();
foreach (var date in datesToTest)
var isDate = DocNameHelper.DateTryParseExact(date, out dateToTest, culture);
var result = DateInSentence.FilterAbsurdDates(Result.Success(date), culture);
dateToTest = result.IsSuccess ? dateToTest : DateTime.MinValue;
orderedDates.Add((dateToTest, date));
foreach (var(date, rawDate)in orderedDates)
Console.WriteLine($"------------------------------------------------------------------------");
var isDate = date != DateTime.MinValue;
Console.WriteLine(@$"{rawDate, 25} é data? {isDate}{(isDate ? ": " + date.ToString() : "")}");
static void TestInterval((string date, bool forceBeginDatePreviousYearIfReasonable, bool sameFrameIntervalIsAllowed)[] intervalsToTest, CultureInfo culture)
const string GrabDateIntervalFromPartOfFile = @"(?<=\W)(?<interval>(?<starting>[ mçinícandouetr]+?)(?<datebegin>\d+)(?<link>[ eatéfimnldzou]+?)(?<=\W)(?<!\w-)(?<dateend>\d+))";
Console.WriteLine($"################################################ Intervals ({culture.Name.ToString()}) ################################################");
foreach (var interval in intervalsToTest)
var (dateBegin, dateEnd) = IntervalInSentence.GetIntervalDates(interval.date, string.Empty);
var intervalCompared = DocNameHelper.TryCompareIntervalDates(dateBegin.Value, dateEnd.Value, culture, interval.forceBeginDatePreviousYearIfReasonable);
var result = DateInSentence.FilterAbsurdDates(dateBegin, culture);
var beginIsDDMM = DocNameHelper.IsItPossibleToBeDDMMOrMMDD(dateBegin.Value, CultureInfo.CurrentCulture);
var endIsDDMM = DocNameHelper.IsItPossibleToBeDDMMOrMMDD(dateEnd.Value, CultureInfo.CurrentCulture);
var validEndInterval = DocNameHelper.DateTryParseExact(dateEnd.Value, out var endInterval, CultureInfo.CurrentCulture, endIsDDMM);
bool validBeginInterval = false;
DateTime beginInterval = default;
bool intervalInvalidBecauseBeginDateAfterEndDate = false;
var dateParsed = InNameDate.CreateInstance(dateEnd.Value, CultureInfo.CurrentCulture, 2025);
intervalInvalidBecauseBeginDateAfterEndDate =
!DocNameHelper.IsDDMMBeginDateReallyBeforeEndDate(dateBegin.Value, dateEnd.Value,
CultureInfo.CurrentCulture, dateParsed.Mask);
validBeginInterval = DocNameHelper.DateTryParseExact(dateBegin.Value, out beginInterval, CultureInfo.CurrentCulture, beginIsDDMM);
var errorMsgBeginDateAfterEndDate = intervalInvalidBecauseBeginDateAfterEndDate ? " because BeginDate is After EndDate" : string.Empty;
var errorMsgInvalidBeginDateOrEndDate = result.IsFailure ? " because of BeginDate and/or EndDate are not valid" : string.Empty;
Console.WriteLine($"Interval is {(intervalInvalidBecauseBeginDateAfterEndDate || result.IsFailure ? "INVALID" + errorMsgBeginDateAfterEndDate + errorMsgInvalidBeginDateOrEndDate : "Valid")}");
Console.WriteLine($"forceBeginDatePreviousYearIfReasonable is {interval.forceBeginDatePreviousYearIfReasonable}");
var begin = dateBegin.IsSuccess ? dateBegin.Value : "Invalid";
var end = dateEnd.IsSuccess ? dateEnd.Value : "Invalid";
Console.WriteLine($"Begin date ({begin}) is {(result.IsSuccess ? "valid" : "not valid: " + result.Error)}. Mask: {dateParsed.Mask}.");
result = DateInSentence.FilterAbsurdDates(Result.Success(end), culture);
Console.WriteLine($"End date ({end}) is {(result.IsSuccess ? "valid" : "not valid: " + result.Error)}");
Console.WriteLine($"------------------------------------------------------------------------");
intervalCompared.dateBegin.Dump();
var beginInName = intervalCompared.dateBegin.IsSuccess ? intervalCompared.dateBegin.Value.DateText : "Invalid" ;
var beginMask = intervalCompared.dateBegin.IsSuccess ? intervalCompared.dateBegin.Value.Mask.GetDescription() : "No mask" ;
Console.WriteLine($"Begin date ({beginInName}) is {(intervalCompared.dateBegin.IsSuccess ? "valid" : "not valid: " + intervalCompared.dateBegin.Error)}. Mask: {beginMask}.");
intervalCompared.dateEnd.Dump();
var endInName = intervalCompared.dateEnd.IsSuccess ? intervalCompared.dateEnd.Value.DateText : "Invalid" ;
var endMask = intervalCompared.dateEnd.IsSuccess ? intervalCompared.dateEnd.Value.Mask.GetDescription() : "No mask" ;
Console.WriteLine($"End date ({endInName}) is {(intervalCompared.dateEnd.IsSuccess ? "valid" : "not valid: " + intervalCompared.dateEnd.Error)}. Mask: {endMask}.");
Console.WriteLine("#################################################################################");
static (string begin, string end) GetInterval(string rawInterval, string pattern)
var match = DocNameHelper.GetMatch(pattern, rawInterval);
Console.WriteLine($"##interval: {DocNameHelper.GetMatchMainGroup(pattern, rawInterval).Value}-------------------------------------------------");
Console.WriteLine($"starting: {DocNameHelper.GetMatch(pattern, rawInterval, "starting").Value}");
Console.WriteLine($"datebegin: {match.Groups["datebegin"].Value}");
Console.WriteLine($"link: {match.Groups["link"].Value}");
Console.WriteLine($"dateend: {match.Groups["dateend"].Value}");
return (match.Groups["datebegin"].Value, match.Groups["dateend"].Value);
static void TestDate(string rawDate, string pattern, string replacePattern, string toTryParseExact, CultureInfo culture, bool acceptWildCard)
Console.WriteLine($"{rawDate}:");
DateTime date = DateTime.MinValue;
var dateFormatted = string.Empty;
if (string.IsNullOrEmpty(replacePattern))
dateFormatted = Regex.Replace(rawDate, pattern, replacePattern);
Console.WriteLine($" dateFormatted {dateFormatted}:");
Console.WriteLine($" TryParseExact with {toTryParseExact}:");
DateTime.TryParseExact(dateFormatted, toTryParseExact
, null, DateTimeStyles.AssumeLocal, out date);
Console.WriteLine($" TryParse:");
DateTime.TryParse(dateFormatted, null, DateTimeStyles.AssumeLocal, out date);
Console.WriteLine($" FilterAbsurdDates with acceptWildCard = {acceptWildCard}:");
var filter = DateInSentence.FilterAbsurdDates(Result.Success(dateFormatted), culture, acceptWildCard);
[Description("dd/MM/yyyy HH:mm:ss")]
[Description("MM/dd/yyyy HH:mm:ss")]
[Description("yyyyMMddHHmmss")]
[Description("yyyy-MM-dd'T'HH:mm:ss.fffffff'Z'")]
public enum CertaintyAboutElement
public enum ElementRelativePosition
public enum WhichElementItIs
public static class DocNameHelper
public const string GrabNameAndExtension = @"(?<fullname>.+?)(\.(?<extension>[^.]*)$|$)";
public const string GrabPartsOfName = @"([\w _<>+;.,']*)*";
public const string GrabDelimiterWithSpaceAround = @"( - )";
public const string GrabFakeDelimiterWithoutAnySpaceAround = @"(?<before>[\d\w' -.]*)(?<=\w)(?<FakeDelimiter>[-˗–])(?=\w)(?<after>[\d\w -.]*)";
public const string TemporarySubstitutionForFakeDelimiter = @"${before}<>${after}";
public const string GrabTemporarySubstitutionForFakeDelimiter = @"(?<before>[\d\w -.]*)(?<=\w)(?<FakeDelimiter>\<\>)(?=\w)(?<after>[\d\w -.]*)";
public const string RestorePreviousStateBeforeFakeDelimiter = @"${before}-${after}";
#endregion Public Constants
public static string StripOffPart(string partToBeStrippedOff, string partToRemove, string connectorBefore)
if (!string.IsNullOrEmpty(connectorBefore) && !string.IsNullOrWhiteSpace(connectorBefore))
partToRemove = connectorBefore + partToRemove;
if (!string.IsNullOrWhiteSpace(partToRemove))
partToBeStrippedOff = partToBeStrippedOff.Replace(partToRemove.Trim(), string.Empty);
return partToBeStrippedOff = partToBeStrippedOff.Trim();
public static bool DifficultyToTestPart(string partToTest)
return partToTest.Length > 10 && partToTest.DoesNotContainAnySpace();
public static Result<string> ReturnResult(string pattern, string part, string groupName, string errorMsg)
_ = GetMatch(pattern, part, groupName, errorMsg, out var result);
internal static Match GetMatch(string pattern, string part, string groupName, string errorMsg, out Result<string> result)
var rgx = new Regex(pattern);
var match = rgx.Match(" " + part.TrimStart());
result = match.Success ? Result.Success(match.Groups[groupName].Value.Trim()) : Result.Failure<string>(errorMsg);
internal static Match GetMatch(string pattern, string part)
var rgx = new Regex(pattern);
var match = rgx.Match(" " + part);
internal static Group GetMatchMainGroup(string pattern, string part)
var rgx = new Regex(pattern);
var match = rgx.Match(part.EnsureOneSingleSpaceAtBegin());
internal static Group GetMatch(string pattern, string part, string groupName)
var rgx = new Regex(pattern);
var match = rgx.Match(part.EnsureOneSingleSpaceAtBegin());
return match.Groups[groupName];
internal static MatchCollection GetMatches(string pattern, string part)
var rgx = new Regex(pattern);
var matches = rgx.Matches(part.EnsureOneSingleSpaceAtBegin());
internal static bool OutOfForbiddenZoneIfSamePart(string groupName, Match match, int startOfForbiddenZone, int endOfForbiddenZone)
return BeforeStartOfForbiddenZone(groupName, match, startOfForbiddenZone) && AfterEndOfForbiddenZone(groupName, match, endOfForbiddenZone);
private static bool BeforeStartOfForbiddenZone(string groupName, Match match, int startOfForbiddenZone)
return match.Groups[groupName].Index + match.Groups[groupName].Length - 1 < startOfForbiddenZone;
private static bool AfterEndOfForbiddenZone(string groupName, Match match, int endOfForbiddenZone)
return match.Groups[groupName].Index > endOfForbiddenZone;
#region Interval and Date
public static string[] dubiousDDMMOrYYYY() => new string[]{"2001", "2002", "2003", "2004", "2005", "2006", "2007", "2008", "2009", "2010", "2011", "2012"};
public static string[] dubiousMMDDOrYYYY() => new string[]{""};
internal static (Result<InNameDate> dateBegin, Result<InNameDate> dateEnd) TryCompareIntervalDates(string dateBegin, string dateEnd, CultureInfo cultureInfo, bool sameFrameIntervalIsAllowed = false)
var errorMsg = string.Empty;
var isDDMM = cultureInfo.Name == "pt-BR";
if (dateBegin.IsNumber() && dateEnd.IsNumber())
var dateEndIsDDMM = DateTime.TryParseExact(dateEnd, formatEnd.GetDescription(),
cultureInfo, DateTimeStyles.None, out var dateEnded);
if (dateBegin.Length == 8)
var date = InNameDate.CreateInstance(dateBegin, cultureInfo, 2025, false);
var (mask, format) = (date.Mask.GetDescription(), date.Mask);
var validDates = TryToParseDatesAsDDMM_MMDD(dateBegin, dateEnd, cultureInfo, mask);
if (validDates.dateStarted != DateTime.MinValue && validDates.dateEnded != DateTime.MinValue)
var beginDDMMDateIsInPreviousYear = IsDDMMBeginDateReallyBeforeEndDate(dateBegin, dateEnd, cultureInfo, DateFormat.ddMMyyyy,
sameFrameIntervalIsAllowed);
if (beginDDMMDateIsInPreviousYear)
return (Result.Success(InNameDate.CreateInstance(dateBegin, validDates.dateStarted, format))
, Result.Success(InNameDate.CreateInstance(dateEnd, validDates.dateEnded, format)));
errorMsg = "because BeginDate is After EndDate";
else if (dateBegin.Length == 6)
throw new NotImplementedException("I did not implemented 'de MMyyyy até ddMMyyyy'");
else if (dateBegin.Length == 4)
var dateBeginIsDDMM = DateTime.TryParseExact(dateBegin + dateEnded.Year, formatEnd.GetDescription(),
cultureInfo, DateTimeStyles.None, out var ddMMBegin);
if (IsBeginDateReallyBeforeEndDate(ddMMBegin, dateEnded, sameFrameIntervalIsAllowed))
return (Result.Success(InNameDate.CreateInstance(dateBegin, ddMMBegin, formatBegin)),
Result.Success(InNameDate.CreateInstance(dateEnd, dateEnded, formatEnd))
errorMsg = "because BeginDate is After EndDate";
else if (dateBegin.Length == 2)
var dayBegin = int.Parse(dateBegin);
if ((dayBegin > 0 && dayBegin < dateEnded.Day) || ((dayBegin == dateEnded.Day) && sameFrameIntervalIsAllowed))
var dayInterval = Result.Success(InNameDate.CreateInstance(dateBegin,
new DateTime(dateEnded.Year, dateEnded.Month, dayBegin), DateFormat.dd));
Result.Success(InNameDate.CreateInstance(dateEnd, dateEnded, formatEnd))
errorMsg = "because BeginDate is After EndDate";
formatEnd = DateFormat.MMyyyy;
dateEndIsDDMM = DateTime.TryParseExact(dateEnd, formatEnd.GetDescription(),
cultureInfo, DateTimeStyles.None, out dateEnded);
if (dateBegin.Length == 8)
throw new NotImplementedException("I did not implemented 'de ddMMyyyy até MMyyyy'");
else if (dateBegin.Length == 6)
var dateBeginIsDDMM = DateTime.TryParseExact(dateBegin, formatEnd.GetDescription(),
cultureInfo, DateTimeStyles.None, out var ddMMBegin);
if (IsBeginDateReallyBeforeEndDate(ddMMBegin, dateEnded, sameFrameIntervalIsAllowed))
return (Result.Success(InNameDate.CreateInstance(dateBegin, ddMMBegin, formatEnd)),
Result.Success(InNameDate.CreateInstance(dateEnd, dateEnded, formatEnd))
errorMsg = "because BeginDate is After EndDate";
else if (dateBegin.Length == 4)
throw new NotImplementedException("I did not implemented 'de ddMM até MMyyyy'");
else if (dateBegin.Length == 2)
if (dateBegin.Length == 8)
else if (dateBegin.Length == 6)
else if (dateBegin.Length == 4)
var result = DecideIfDateEndIsYYYYOrDDMM(dateBegin, dateEnd, cultureInfo, null, sameFrameIntervalIsAllowed: sameFrameIntervalIsAllowed);
if (result.dateBegin.CertaintyAbout != CertaintyAboutElement.NonExisting)
return (Result.Success(result.dateBegin), Result.Success(result.dateEnd));
errorMsg = "not existing BeginDate or EndDate";
else if (dateBegin.Length == 2)
errorMsg = "No interval date stamp in this file name";
return (Result.Failure<InNameDate>(errorMsg), Result.Failure<InNameDate>(errorMsg));
internal static (InNameDate dateBegin, InNameDate dateEnd) DecideIfDateEndIsYYYYOrDDMM(string dateBeginToTest, string dateEndToTest, CultureInfo cultureInfo, int? maxPossibleYear, int minPossibleYear = 1990, bool sameFrameIntervalIsAllowed = false)
maxPossibleYear ??= InNameDate.GetCurrentYearPlus(3);
var(isDateEndDubious, isDateBeginDubious, certaintyAboutYYYY) = DecideDegreeOfCertaintyAboutDates(dateBeginToTest, dateEndToTest, cultureInfo);
var date = InNameDate.CreateInstance(dateBeginToTest, cultureInfo, 2025);
var(maskForDDMM, formatForDDMM) = (date.Mask.GetDescription(), date.Mask);
var dateBegin = InNameDate.NullDate;
var dateBeginIsYYYY = false;
var dateBeginIsDDMM = false;
var dateEnd = InNameDate.NullDate;
var dateEndIsYYYY = false;
var dateEndIsDDMM = false;
var(dateEnded, endDateMaybeYYYY) = TryToParseDateAsYYYY(dateEndToTest, cultureInfo);
var(dateStarted, beginDateMaybeYYYY) = TryToParseDateAsYYYY(dateBeginToTest, cultureInfo);
endDateMaybeYYYY = IsValidEndDateYYYY(dateStarted, dateEnded, minPossibleYear, maxPossibleYear.Value, sameFrameIntervalIsAllowed);
(dateBeginIsYYYY, dateEndIsYYYY) = DetermineIfIntervalDatesShouldBeYYYY(isDateEndDubious, isDateBeginDubious, endDateMaybeYYYY, beginDateMaybeYYYY, dateEndIsYYYY, dateBeginIsYYYY);
var beginCannotBeDDMMOrMMDD = !IsItPossibleToBeDDMMOrMMDD(dateBeginToTest, cultureInfo);
if (beginCannotBeDDMMOrMMDD && !endDateMaybeYYYY)
if (dateStarted != dateEnded)
return (InNameDate.NullDate, InNameDate.NullDate);
if (dateStarted == dateEnded && !sameFrameIntervalIsAllowed)
return (InNameDate.NullDate, InNameDate.NullDate);
if (dateBeginIsYYYY || beginCannotBeDDMMOrMMDD)
(dateStarted, dateBeginIsYYYY) = TryToParseDateAsYYYY(dateBeginToTest, cultureInfo);
(dateBeginIsDDMM, dateStarted, dateEndIsDDMM, dateEnded) = TryToParseDatesAsDDMM_MMDD(dateBeginToTest, dateEndToTest, cultureInfo, maskForDDMM);
dateEndIsYYYY = DateTime.TryParseExact(dateEndToTest, DateFormat.yyyy.GetDescription(), cultureInfo, DateTimeStyles.None, out dateEnded);
return (dateBegin, dateEnd);
dateBegin = InNameDate.CreateInstance(dateBeginToTest, dateStarted, DateFormat.yyyy, certaintyAboutYYYY);
dateEnd = InNameDate.CreateInstance(dateEndToTest, dateEnded, DateFormat.yyyy, certaintyAboutYYYY);
else if (dateBeginIsDDMM)
if (!dateEndIsDDMM || !IsDDMMBeginDateReallyBeforeEndDate(dateBeginToTest, dateEndToTest, cultureInfo, formatForDDMM, sameFrameIntervalIsAllowed))
return (dateBegin, dateEnd);
dateBegin = InNameDate.CreateInstance(dateBeginToTest, dateStarted, formatForDDMM, certaintyAboutYYYY);
dateEnd = InNameDate.CreateInstance(dateEndToTest, dateEnded, formatForDDMM, certaintyAboutYYYY);
return (dateBegin, dateEnd);
private static (bool isDateEndDubious, bool isDateBeginDubious, CertaintyAboutElement certaintyAboutYYYY) DecideDegreeOfCertaintyAboutDates(string dateBeginToTest, string dateEndToTest, CultureInfo cultureInfo)
var dubiousDate = cultureInfo.Name == "pt-BR" ? dubiousDDMMOrYYYY() : dubiousMMDDOrYYYY();
var isDateEndDubious = dubiousDate.Contains(dateEndToTest);
var isDateBeginDubious = dubiousDate.Contains(dateBeginToTest);
var certaintyAboutYYYY = isDateBeginDubious && isDateEndDubious ? CertaintyAboutElement.InformedGuess : CertaintyAboutElement.AbsolutelyCertain;
return (isDateEndDubious, isDateBeginDubious, certaintyAboutYYYY);
private static (DateTime dateParsed, bool tryParseExact) TryToParseDateAsYYYY(string dateToTest, CultureInfo cultureInfo)
var tryParseExact = DateTime.TryParseExact(dateToTest, DateFormat.yyyy.ToString(), cultureInfo, DateTimeStyles.None, out var dateParsed);
return (dateParsed, tryParseExact);
private static (bool dateBeginIsDDMM, DateTime dateStarted, bool dateEndIsDDMM, DateTime dateEnded) TryToParseDatesAsDDMM_MMDD(string dateBeginToTest, string dateEndToTest, CultureInfo cultureInfo, string maskForDDMM)
var dateBeginIsDDMM = DateTime.TryParseExact(dateBeginToTest, maskForDDMM, cultureInfo, DateTimeStyles.None, out var dateStarted);
var dateEndIsDDMM = DateTime.TryParseExact(dateEndToTest, maskForDDMM, cultureInfo, DateTimeStyles.None, out var dateEnded);
return (dateBeginIsDDMM, dateStarted, dateEndIsDDMM, dateEnded);
private static (bool dateBeginIsYYYY, bool dateEndIsYYYY) DetermineIfIntervalDatesShouldBeYYYY(bool isDateEndDubious, bool isDateBeginDubious, bool endDateMaybeYYYY, bool beginDateMaybeYYYY, bool dateEndIsYYYY, bool dateBeginIsYYYY)
if (IfNoDateIsDubious(isDateEndDubious, isDateBeginDubious))
return (endDateMaybeYYYY, beginDateMaybeYYYY);
if (AtLeastSomeDateIsDubiousAndBothDatesMaybeYYYY(endDateMaybeYYYY, beginDateMaybeYYYY))
return (dateBeginIsYYYY, dateEndIsYYYY);
private static bool AtLeastSomeDateIsDubiousAndBothDatesMaybeYYYY(bool endDateMaybeYYYY, bool beginDateMaybeYYYY)
return endDateMaybeYYYY && beginDateMaybeYYYY;
private static bool IfNoDateIsDubious(bool isDateEndDubious, bool isDateBeginDubious)
return !isDateEndDubious && !isDateBeginDubious;
internal static bool IsItPossibleToBeDDMMOrMMDD(string dateToTest, CultureInfo cultureInfo)
if (dateToTest.Length != 4)
return DateTime.TryParseExact(dateToTest, DateFormat.ddMM.GetDescription(), cultureInfo, DateTimeStyles.None, out _) || DateTime.TryParseExact(dateToTest, DateFormat.MMdd.GetDescription(), cultureInfo, DateTimeStyles.None, out _);
internal static bool IsDDMMBeginDateReallyBeforeEndDate(string dateBegin, string dateEnd, CultureInfo cultureInfo, DateFormat dateFormat, bool sameFrameIntervalIsAllowed = false)
var beginDateValid = DateTime.TryParseExact(dateBegin, dateFormat.GetDescription(), cultureInfo, DateTimeStyles.None, out var beginDate);
var endDateValid = DateTime.TryParseExact(dateEnd, dateFormat.GetDescription(), cultureInfo, DateTimeStyles.None, out var endDate);
if (!beginDateValid || !endDateValid)
return IsBeginDateReallyBeforeEndDate(beginDate, endDate, sameFrameIntervalIsAllowed);
internal static bool IsBeginDateReallyBeforeEndDate(DateTime dateBegin, DateTime dateEnd, bool sameFrameIntervalIsAllowed) => sameFrameIntervalIsAllowed ? dateBegin <= dateEnd : dateBegin < dateEnd;
internal static bool IsValidEndDateYYYY(DateTime dateBegin, DateTime dateEnd, int minPossibleYear, int maxPossibleYear, bool sameYearIntervalIsAllowed)
if (!IsBeginDateReallyBeforeEndDate(dateBegin, dateEnd, sameYearIntervalIsAllowed))
return dateEnd.Year >= minPossibleYear && dateEnd.Year <= maxPossibleYear;
#endregion Interval and Date
#region MedicinesAndDosage
#endregion MedicinesAndDosage
internal static bool DateWithNoYearTryParseExact(string dateWithNoYearAndWithoutSpace, string yearToAssume, out DateTime testDate, CultureInfo cultureInfo)
ArgumentException.ThrowIfNullOrEmpty(dateWithNoYearAndWithoutSpace);
ArgumentException.ThrowIfNullOrEmpty(yearToAssume);
return DateTryParseExact(dateWithNoYearAndWithoutSpace + yearToAssume, out testDate, cultureInfo);
internal static bool DateTryParseExact(string dateWithoutSpace, out DateTime testDate, CultureInfo cultureInfo, bool forceDDMM = false)
ArgumentException.ThrowIfNullOrEmpty(dateWithoutSpace);
var yyyy = forceDDMM ? string.Empty : DateFormat.yyyy.GetDescription();
var isDDMM = cultureInfo.Name == "pt-BR";
var ddMMyyyy = isDDMM ? DateFormat.ddMMyyyy.GetDescription() : DateFormat.MMddyyyy.GetDescription();
var ddMMMyyyy = isDDMM ? DateFormat.ddMMMyyyy.GetDescription() : DateFormat.MMMddyyyy.GetDescription();
var ddMMMMyyyy = isDDMM ? DateFormat.ddMMMMyyyy.GetDescription() : DateFormat.MMMMddyyyy.GetDescription();
var ddMM = isDDMM ? DateFormat.ddMM.GetDescription() : DateFormat.MMdd.GetDescription();
var ddMMM = isDDMM ? DateFormat.ddMMM.GetDescription() : DateFormat.MMMdd.GetDescription();
var ddMMMM = isDDMM ? DateFormat.ddMMMM.GetDescription() : DateFormat.MMMMdd.GetDescription();
var ddMMyyyyHHmmssWithSlash = isDDMM ? DateFormat.completeUpToSeconds.GetDescription() : DateFormat.completeUpToSecondsUS.GetDescription();
return DateTime.TryParse(dateWithoutSpace, cultureInfo, DateTimeStyles.None, out testDate) || DateTime.TryParseExact(dateWithoutSpace, yyyy, cultureInfo, DateTimeStyles.AssumeLocal, out testDate) || DateTime.TryParseExact(dateWithoutSpace, DateFormat.MMMMyyyy.GetDescription(), cultureInfo, DateTimeStyles.AssumeLocal, out testDate) || DateTime.TryParseExact(dateWithoutSpace, DateFormat.MMMyyyy.GetDescription(), cultureInfo, DateTimeStyles.AssumeLocal, out testDate) || DateTime.TryParseExact(dateWithoutSpace, DateFormat.MMyyyy.GetDescription(), cultureInfo, DateTimeStyles.AssumeLocal, out testDate) || DateTime.TryParseExact(dateWithoutSpace, ddMMyyyy, cultureInfo, DateTimeStyles.AssumeLocal, out testDate) || DateTime.TryParseExact(dateWithoutSpace, ddMMMyyyy, cultureInfo, DateTimeStyles.AssumeLocal, out testDate) || DateTime.TryParseExact(dateWithoutSpace, ddMMMMyyyy, cultureInfo, DateTimeStyles.AssumeLocal, out testDate) || DateTime.TryParseExact(dateWithoutSpace, ddMM, cultureInfo, DateTimeStyles.AssumeLocal, out testDate) || DateTime.TryParseExact(dateWithoutSpace, ddMMM, cultureInfo, DateTimeStyles.AssumeLocal, out testDate) || DateTime.TryParseExact(dateWithoutSpace, ddMMMM, cultureInfo, DateTimeStyles.AssumeLocal, out testDate) || DateTime.TryParseExact(dateWithoutSpace, ddMMyyyyHHmmssWithSlash, cultureInfo, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal, out testDate) || DateTime.TryParseExact(dateWithoutSpace, DateFormat.universalSortable.GetDescription(), cultureInfo, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal, out testDate) || DateTime.TryParseExact(dateWithoutSpace, DateFormat.sortable.GetDescription(), cultureInfo, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal, out testDate);
public class InNameDate : ValueObject
public string DateText { get; }
public DateTime DateTime { get; }
public DateFormat Mask { get; }
public CertaintyAboutElement CertaintyAbout { get; }
private InNameDate(string dateText, DateTime dateTime, DateFormat mask, CertaintyAboutElement certaintyAbout)
CertaintyAbout = certaintyAbout;
public static InNameDate NullDate => new(string.Empty, DateTime.MinValue, DateFormat.NoDate, CertaintyAboutElement.NonExisting);
public static InNameDate ChangeMask(InNameDate date, DateFormat newMask)
return CreateInstance(date.DateText, date.DateTime, newMask);
public static InNameDate ChangeToYYYY(InNameDate date, DateFormat newMask)
if (date.DateText.Length != 4)
throw new ArgumentException("Can only change mask of DDMM or MMDD to YYYY");
return CreateInstance(date.DateText, date.DateTime, DateFormat.yyyy);
public static InNameDate ChangeToMMDD(InNameDate date, DateFormat newMask)
if (date.DateText.Length != 4)
throw new ArgumentException("Can only change mask of DDMM or YYYY to MMDD");
return CreateInstance(date.DateText, date.DateTime, DateFormat.MMdd);
public static InNameDate ChangeToDDMM(InNameDate date, DateFormat newMask)
if (date.DateText.Length != 4)
throw new ArgumentException("Can only change mask of MMDD or YYYY to DDMM");
return CreateInstance(date.DateText, date.DateTime, DateFormat.ddMM);
public static InNameDate CreateInstance(string dateText, DateTime dateTime, DateFormat mask, CertaintyAboutElement certaintyAbout = CertaintyAboutElement.AbsolutelyCertain)
return new InNameDate(dateText, dateTime, mask, certaintyAbout);
public static InNameDate CreateInstance(string dateToParse, CultureInfo cultureInfo, int? maxPossibleYear, bool forceDDMMorMMDD = false, int minPossibleYear = 1990)
maxPossibleYear ??= GetCurrentYearPlus(3);
var maskForDateParsing = string.Empty;
DateTime parsedDate = DateTime.MinValue;
var isDDMM = cultureInfo.Name == "pt-BR";
var dateFormatToTest = DateFormat.NoDate;
var degreeOfCertainty = CertaintyAboutElement.AbsolutelyCertain;
if (dateToParse.IsNumber())
if (dateToParse.Length == DateFormat.sortable.GetDescription().Length)
dateFormatToTest = DateFormat.sortable;
else if (dateToParse.Length == 8)
dateFormatToTest = isDDMM ? DateFormat.ddMMyyyy : DateFormat.MMddyyyy;
else if (dateToParse.Length == 6)
dateFormatToTest = DateFormat.MMyyyy;
else if (dateToParse.Length == 4)
var yyyyToTestIfWithinAllowedRange = DateTime.MinValue;
var maybeDDMMorMMDD = DateTime.TryParseExact(dateToParse, DateFormat.ddMM.GetDescription(), cultureInfo, DateTimeStyles.None, out _) || DateTime.TryParseExact(dateToParse, DateFormat.MMdd.GetDescription(), cultureInfo, DateTimeStyles.None, out _);
var maybeYYYY = DateTime.TryParseExact(dateToParse, DateFormat.yyyy.GetDescription(), cultureInfo, DateTimeStyles.None, out yyyyToTestIfWithinAllowedRange);
if ((maybeDDMMorMMDD && !maybeYYYY) || forceDDMMorMMDD)
dateFormatToTest = isDDMM ? DateFormat.ddMM : DateFormat.MMdd;
else if (!maybeDDMMorMMDD && maybeYYYY)
dateFormatToTest = DateFormat.yyyy;
else if (maybeDDMMorMMDD && maybeYYYY)
degreeOfCertainty = CertaintyAboutElement.InformedGuess;
if (yyyyToTestIfWithinAllowedRange >= new DateTime(minPossibleYear, 1, 1) && yyyyToTestIfWithinAllowedRange <= new DateTime(maxPossibleYear.Value, 1, 1))
dateFormatToTest = DateFormat.yyyy;
dateFormatToTest = isDDMM ? DateFormat.ddMM : DateFormat.MMdd;
else if (maybeDDMMorMMDD && maybeYYYY)
dateFormatToTest = DateFormat.NoDate;
degreeOfCertainty = CertaintyAboutElement.NonExisting;
else if (dateToParse.Length == 2)
throw new NotImplementedException("InNameDate CreateInstance (old InferMaskAndFormatForDateParsing): dateToParse.Length == 2");
if (DateTime.TryParseExact(dateToParse, dateFormatToTest.GetDescription(), cultureInfo, DateTimeStyles.None, out parsedDate))
return InNameDate.CreateInstance(dateToParse, parsedDate, dateFormatToTest, degreeOfCertainty);
return InNameDate.CreateInstance(dateToParse, parsedDate, dateFormatToTest, degreeOfCertainty);
internal static int GetCurrentYearPlus(int toAddCurrent) => DateTime.Now.Year + toAddCurrent;
#region Overrides of ValueObject
protected override IEnumerable<object> GetEqualityComponents()
yield return CertaintyAbout;
public static class DateInSentence
public const string GrabDatesWithMonthsInLettersOrPureNumbersFromFile = @"(?<relationandinterval>(?<temporalrelation> em )?(?<=\W)(?<intervalordate>(?:\d{6,8}|\d{2,3}_{1,2})|(?:\b(0?[1-9]|[12][0-9]|3[01])*(?: de )?([JFMASONDjfmasond]([a-z0-9]+)([ç]{0,1})([a-z0-9]+)*)*(?: de )?(19|20)?[0-9]{2}\b)))";
public const string GrabDatesOrPureNumbersFromPartOfFile = @"(?<date>\d{6,8}|\d{2,3}_{1,2})";
public const string GrabDatesUTC = @"(?<dateUTC>(?<year>-?(?:[1-9][0-9]*)?[0-9]{4})-(?<month>1[0-2]|0[1-9])-(?<day>3[01]|0[1-9]|[12][0-9])T(?<hour>2[0-3]|[01][0-9]):(?<minute>[0-5][0-9]):(?<second>[0-5][0-9])(\\.[0-9]+)?(?<zeroUTCoffse>Z)?|(?<year>(?:[1-9][0-9]*)?[0-9]{4})(?<month>1[0-2]|0[1-9])(?<day>3[01]|0[1-9]|[12][0-9])(?<hour>2[0-3]|[01][0-9])(?<minute>[0-5][0-9])(?<second>[0-5][0-9])(\\.[0-9]+)?(?<zeroUTCoffse>Z)?)";
public const string GrabDateIntervalFromPartOfFile = @"(?<=\W)(?<interval>(?<starting>[ mçinícandouetr]+?)(?<datebegin>\d+)(?<link>[ eatéfimnldzou]+?)(?<=\W)(?<!\w-)(?<dateend>\d+))";
public const string GrabDateAndVersionDotAtEnd = @"(?<date>\d{6,8}|\d{2,3}_{1,2}) {1,2}(?<version>[vVersionão]+[\d@]+[\w@]*|[\d@]+[vVersionão]+[\w@]*).|(?<version>[vVersionão]+[\d@]+[\w@]*|[\d@]+[vVersionão]+[\w@]*) {1,2}(?<date>\d{6,8}|\d{2,3}_{1,2})\.|(?<date>\d{6,8}|\d{2,3}_{1,2})\.";
public const string WildCardForDate = "_";
#endregion Public Constants
internal static Result<string> FilterAbsurdDates(Result<string> hasDate, CultureInfo cultureInfo, bool acceptWildCard = true, string errorMsg = "No date found", int numberOfYearsToConsiderAbsurd = 99)
DateTime testDate = DateTime.MinValue;
string dateWithoutSpace = string.Empty;
if (hasDate.Value.DoesNotContainAnySpace())
dateWithoutSpace = hasDate.Value;
var dateCandidates = hasDate.Value.Split(' ');
foreach (var element in dateCandidates)
if (element.Length > 3 && element.Length < 8)
dateWithoutSpace = element;
if (DocNameHelper.DateTryParseExact(dateWithoutSpace, out testDate, cultureInfo))
if (testDate.IsThisDateNullOrDefault())
return Result.Failure<string>(errorMsg);
if (Math.Abs(testDate.YearsBetweenNowAndDate()) >= numberOfYearsToConsiderAbsurd)
return Result.Failure<string>($"Cannot allow set date so many years ({testDate.YearsBetweenNowAndDate()}) far from today");
return Result.Success(dateWithoutSpace);
if (dateWithoutSpace.Contains(WildCardForDate))
return acceptWildCard ? Result.Success(dateWithoutSpace) : Result.Failure<string>($"Because WildCardForDate is not allowed, this is an invalid date resulting in {testDate.ToString()}");
return Result.Failure<string>($"Invalid date resulting in {testDate.ToString()}");
private static bool InTimeRangeAllowed(int numberOfYearsToConsiderAbsurd, DateTime testDate)
var tested = Math.Abs(testDate.YearsBetweenNowAndDate());
return tested < numberOfYearsToConsiderAbsurd;
public static Result<string> GetDate(string partToTest, CultureInfo cultureInfo, (int start, int end) forbiddenZoneCoordinates, bool acceptWildCard = true)
if (string.IsNullOrEmpty(partToTest) || DocNameHelper.DifficultyToTestPart(partToTest))
return Result.Failure<string>("No date stamp in this file name");
var hasDate = ReturnResult(GrabDatesUTC, partToTest, "dateUTC", "No date stamp in this file name", cultureInfo, forbiddenZoneCoordinates);
hasDate = ReturnResult(GrabDatesOrPureNumbersFromPartOfFile, partToTest, "date", "No date stamp in this file name", cultureInfo, forbiddenZoneCoordinates);
hasDate = ReturnResult(GrabDatesWithMonthsInLettersOrPureNumbersFromFile, partToTest, "intervalordate", "No date stamp in this file name", cultureInfo, forbiddenZoneCoordinates);
hasDate = FilterAbsurdDates(hasDate, cultureInfo, acceptWildCard, "No date stamp in this file name");
public static Result<string> ReturnResult(string pattern, string part, string groupName, string errorMsg, CultureInfo cultureInfo, (int start, int end) forbiddenZoneCoordinates)
var match = DocNameHelper.GetMatch(pattern, part, groupName, errorMsg, out result);
if (!result.IsFailure && DocNameHelper.OutOfForbiddenZoneIfSamePart(groupName, match, forbiddenZoneCoordinates.start, forbiddenZoneCoordinates.end))
if (DocNameHelper.DateTryParseExact(part, out var testDate, cultureInfo))
result = !testDate.IsThisDateNullOrDefault() ? Result.Success(part) : Result.Failure<string>(errorMsg);
public static class IntervalInSentence
public const string GrabDatesWithMonthsInLettersOrPureNumbersFromFile = @"(?<relationandinterval>(?<temporalrelation> em )?(?<=\W)(?<intervalordate>(?:\d{6,8}|\d{2,3}_{1,2})|(?:\b(0?[1-9]|[12][0-9]|3[01])*(?: de )?([JFMASONDjfmasond]([a-z0-9]+)([ç]{0,1})([a-z0-9]+)*)*(?: de )?(19|20)?[0-9]{2}\b)))";
public const string GrabDateIntervalFromPartOfFile = @"(?<=\W)(?<interval>(?<starting>[ mçinícandouetr]+?)(?<datebegin>\d+)(?<link>[ eatéfimnldzou]+?)(?<=\W)(?<!\w-)(?<dateend>\d+))";
#endregion Public Constants
public static Result<string> GetInterval(string partToTest, InNameGUID partHasGUID)
var errorMsg = "No interval date stamp in this file name";
if (string.IsNullOrEmpty(partToTest) || DocNameHelper.DifficultyToTestPart(partToTest))
return Result.Failure<string>(errorMsg);
var hasInterval = DocNameHelper.ReturnResult(GrabDateIntervalFromPartOfFile, partToTest, "interval", errorMsg);
if (hasInterval.IsSuccess)
return IsIntervalValid(partToTest, errorMsg) ? hasInterval : Result.Failure<string>(errorMsg);
if (hasInterval.IsFailure)
var match = DocNameHelper.GetMatch(GrabDatesWithMonthsInLettersOrPureNumbersFromFile, partToTest, "relationandinterval", errorMsg, out var result);
hasInterval = DocNameHelper.ReturnResult(GrabDatesWithMonthsInLettersOrPureNumbersFromFile, partToTest, "intervalordate", errorMsg);
if (result.Equals(hasInterval))
return Result.Failure<string>(errorMsg);
internal static bool IsIntervalValid(string partToTest, string errorMsg)
var(dateBegin, dateEnd) = GetIntervalDates(partToTest, errorMsg);
var validBeginInterval = DocNameHelper.DateTryParseExact(dateBegin.Value, out var beginInterval, CultureInfo.CurrentCulture);
var validEndInterval = DocNameHelper.DateTryParseExact(dateEnd.Value, out var endInterval, CultureInfo.CurrentCulture);
var isValid = beginInterval < endInterval;
internal static (Result<string> dateBegin, Result<string> dateEnd) GetIntervalDates(string partToTest, string errorMsg)
var dateEnd = DocNameHelper.ReturnResult(GrabDateIntervalFromPartOfFile, partToTest, "dateend", errorMsg);
var dateBegin = DocNameHelper.ReturnResult(GrabDateIntervalFromPartOfFile, partToTest, "datebegin", errorMsg);
return (dateBegin, dateEnd);
public class InNameGUID : ValueObject
private const string Grab_GUID_treated = @"(?<GUID>[0-9a-f]{8}<>[0-9a-f]{4}<>[0-9a-f]{4}<>[0-9a-f]{4}<>[0-9a-f]{12})";
private InNameGUID(int start, int end, DocNamePart part, string guid)
public static InNameGUID CreateInstance(int start, int end, DocNamePart part, string guid)
return new InNameGUID(start, end, part, guid);
public bool NoGUID => string.IsNullOrEmpty(GUID);
public static InNameGUID NullGUID() => new(-1, -1, DocNamePart.NoPart, string.Empty);
public static InNameGUID GetGUID(string part, DocNamePart namePart)
var rgx = new Regex(Grab_GUID_treated);
var match = rgx.Match(part);
var result = match.Success ? CreateInstance(match.Groups["GUID"].Index, match.Groups["GUID"].Length - 1, namePart, match.Groups["GUID"].Value) : NullGUID();
public int Start { get; private set; }
public int End { get; private set; }
public DocNamePart Part { get; private set; }
public string GUID { get; private set; }
protected override IEnumerable<object> GetEqualityComponents()
public static partial class DateTimeExtensions
public static int YearsBetweenNowAndDate(this DateTime @this) => Math.Abs(DateTime.Now.HowOld(@this, out _, out _));
public static int YearsBetweenDates(this DateTime @this, DateTime that) => Math.Abs(that.HowOld(@this, out _, out _));
public static int HowOld(this DateTime initialDay, DateTime dayToCalculate, out int days, out int months)
months = dayToCalculate.Month - initialDay.Month;
int years = dayToCalculate.Year - initialDay.Year;
if (dayToCalculate.Day < initialDay.Day)
days = (dayToCalculate - initialDay.AddMonths((years * 12) + months)).Days;
public static string HowYearsOldToday(this DateTime birthday, bool allowNegativeAge = false)
var howManyYears = birthday.HowOld(DateTime.Now, out var howManyDays, out var howManyMonths);
if (allowNegativeAge || howManyYears >= 0)
return $"{howManyYears} ano{((howManyYears == 1) ? "" : "s")}";
return "Cannot calculate age";
public static string HowYearsOldAtDate(this DateTime birthday, DateTime atDate, bool allowNegativeAge = false)
var howManyYears = birthday.HowOld(atDate, out var howManyDays, out var howManyMonths);
if (allowNegativeAge || howManyYears >= 0)
return $"{howManyYears} ano{((howManyYears == 1) ? "" : "s")}";
return "Cannot calculate age";
public static string HowYearsMonthsOldToday(this DateTime birthday, bool allowNegativeAge = false)
var howManyYears = birthday.HowOld(DateTime.Now, out var howManyDays, out var howManyMonths);
if (allowNegativeAge || howManyYears >= 0)
return $"{howManyYears} ano{((howManyYears == 1) ? "" : "s")}" + $", {howManyMonths} {((howManyMonths == 1) ? "mês" : "meses")} ";
return "Cannot calculate age";
public static string HowYearsMonthsOldAtDate(this DateTime birthday, DateTime atDate, bool allowNegativeAge = false)
var howManyYears = birthday.HowOld(atDate, out var howManyDays, out var howManyMonths);
if (allowNegativeAge || howManyYears >= 0)
return $"{howManyYears} ano{((howManyYears == 1) ? "" : "s")}" + $", {howManyMonths} {((howManyMonths == 1) ? "mês" : "meses")} ";
return "Cannot calculate age";
public static string HowYearsMonthsDaysOldToday(this DateTime birthday, bool allowNegativeAge = false)
var howManyYears = birthday.HowOld(DateTime.Now, out var howManyDays, out var howManyMonths);
if (allowNegativeAge || howManyYears >= 0)
return $"{howManyYears} ano{((howManyYears == 1) ? "" : "s")}" + $", {howManyMonths} {((howManyMonths == 1) ? "mês" : "meses")} " + $"e {howManyDays} dia{((howManyDays == 1) ? "" : "s")}";
return "Cannot calculate age";
public static string HowYearsMonthsDaysOldAtDatey(this DateTime birthday, DateTime atDate, bool allowNegativeAge = false)
var howManyYears = birthday.HowOld(atDate, out var howManyDays, out var howManyMonths);
if (allowNegativeAge || howManyYears >= 0)
return $"{howManyYears} ano{((howManyYears == 1) ? "" : "s")}" + $", {howManyMonths} {((howManyMonths == 1) ? "mês" : "meses")} " + $"e {howManyDays} dia{((howManyDays == 1) ? "" : "s")}";
return "Cannot calculate age";
public static DateTime MinimumWinFormDateTime(this DateTime @this) => Convert.ToDateTime("1/1/1753 00:00:00");
public static DateTime NullOutlookDateTime(this DateTime @this) => Convert.ToDateTime("1/1/4501 00:00:00");
public static DateTime OutlookDefaultDateForInvalidEntry(this DateTime @this) => Convert.ToDateTime("30/12/1899 00:00:00");
public static DateTime OutlookEarliestDateThatDoesNotProvokeError(this DateTime @this) => Convert.ToDateTime("01/04/1601 00:00:00");
public static DateTime ThisDateOrAppropriateMinimalDateTime(this DateTime @this) => (DateTime)(@this < @this.MinimumWinFormDateTime() || @this == NullOutlookDateTime(@this) ? @this.MinimumWinFormDateTime() : @this);
public static bool IsThisDateNullOrDefault(this DateTime @this) => @this == @this.MinimumWinFormDateTime() || @this == NullOutlookDateTime(@this) || @this == DateTime.MaxValue || @this == DateTime.MinValue || @this == @this.OutlookDefaultDateForInvalidEntry() || @this < @this.OutlookEarliestDateThatDoesNotProvokeError();
public static DateTime ThisDateCandidateOrDateTimeMinimal(this string stringToBeParsed)
DateTime @this = default;
if (stringToBeParsed == null || !DateTime.TryParse(stringToBeParsed, out @this))
return DateTime.MinValue;
public static DateTime ThisDateCandidateOrAppropriateMinimalDateTime(this string stringToBeParsed)
DateTime @this = default;
if (stringToBeParsed == null || !DateTime.TryParse(stringToBeParsed, out @this))
return @this.MinimumWinFormDateTime();
return (DateTime)(@this < @this.MinimumWinFormDateTime() || @this == NullOutlookDateTime(@this) ? @this.MinimumWinFormDateTime() : @this);
public static DateTime ReturnAnteriorBetweenDates(this DateTime d1, DateTime d2) => d1 < d2 ? d1 : d2;
public static DateTime ReturnPosteriorBetweenDates(this DateTime d1, DateTime d2) => d1 < d2 ? d1 : d2;
public static DateTime AllowPastOrNowTimeButNotFuture(this DateTime? ToTest, DateTime? ToCompare = null)
ToCompare = DateTime.Now;
var value when value == null => DateTime.Now,
var value when value < ToCompare => (DateTime)ToTest,
var value when value > ToCompare => throw new ArgumentOutOfRangeException("Date cannot be in the future..."),
public static DateTime AllowOnlyFuture(this DateTime? ToTest, double numberOfHoursToAdd = 1, DateTime? ToCompare = null)
ToCompare = DateTime.Now;
var value when value == null => DateTime.Now.AddHours(numberOfHoursToAdd),
var value when value > ToCompare => (DateTime)ToTest,
var value when value < ToCompare => throw new ArgumentOutOfRangeException("Date cannot be in the past or even now..."),
public static class StringTextExtensions
private const char OneBlankSpace = ' ';
public static string RemoveUnnecessarySpaces(this string textToTrim)
return Regex.Replace(textToTrim, @"\s+", " ").Trim();
public static string RemoveAllSpaces(this string textToTrim)
return textToTrim.Replace(" ", string.Empty);
public static string RemoveAllPunctuation(this string textToTrim)
return new string (textToTrim.Where(c => !char.IsPunctuation(c)).Where(c => !char.IsSymbol(c)).ToArray());
public static string GetRidOfNonLetterCharsInKeyOrPairElements(this string rawString) => rawString.RemoveAllPunctuation().RemoveAllSpaces().RemoveBrackets();
public static string GetNumberOfCharsJustBeforeSubstring(this string text, string beforeThis, int numberOfChars = 1)
var txtBefore = text.GetUntilOrEmpty(beforeThis);
return text.GetUntilOrEmpty(beforeThis).Substring(txtBefore.Length - numberOfChars);
Console.WriteLine(ex.Message);
public static string GetUntilOrEmpty(this string text, string stopAt = "-")
if (!string.IsNullOrWhiteSpace(text))
int charLocation = text.IndexOf(stopAt, StringComparison.Ordinal);
return text.Substring(0, charLocation);
public static string GetAfterOrEmpty(this string text, string stopAt = "-")
if (!string.IsNullOrWhiteSpace(text))
int charLocation = text.IndexOf(stopAt, StringComparison.Ordinal);
return text.Substring(charLocation + stopAt.Length, text.Length - charLocation - stopAt.Length);
public static string GetMiddleString(this string inputString, char separator = OneBlankSpace)
var input = inputString.Split(separator);
var firstToken = input[0];
var lastToken = input[input.Length - 1];
var pos1 = inputString.IndexOf(firstToken) + firstToken.Length;
var pos2 = inputString.IndexOf(lastToken);
var result = inputString.Substring(pos1, pos2 - pos1).Trim() ?? string.Empty;
public static (string textBefore, string textAfter) GetTextBeforeAndAfterSubstring(this string textToExtract, string stringToSearchFor, int desiredLengthToCut, int indexToSearchedString)
var textBefore = textToExtract.Substring(indexToSearchedString - desiredLengthToCut < 0 ? 0 : indexToSearchedString - desiredLengthToCut, indexToSearchedString - desiredLengthToCut > 0 ? desiredLengthToCut : indexToSearchedString);
var textAfter = textToExtract.Substring(indexToSearchedString + stringToSearchFor.Length, DecideHowMuchItCanCutAfter(textToExtract.Length, indexToSearchedString + stringToSearchFor.Length, desiredLengthToCut));
return (textBefore, textAfter);
static int DecideHowMuchItCanCutAfter(int totalLength, int beginCut, int lengthToCut)
if (beginCut + lengthToCut < totalLength)
if (beginCut + lengthToCut >= totalLength)
return totalLength - beginCut;
public static string RemoveDiacritics(this string s)
string normalizedString = s.Normalize(NormalizationForm.FormD);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < normalizedString.Length; i++)
char c = normalizedString[i];
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
return stringBuilder.ToString();
public static string RemoveBrackets(this string s)
return s.Replace("[", string.Empty).Replace("]", string.Empty);
public static string SurroundWithDoubleQuotes(this string text)
return SurroundWith(text, "\"");
public static string SurroundWith(this string text, string ends)
return ends + text + ends;
public static string EscapeSingleQuote(this string text)
return text.Replace("'", @"\'");
public static string AddSingleQuoteAsEscapeCharacter(this string text)
return text.Replace("'", @"''");
public static string EnsureOneSingleSpaceAtBegin(this string text)
return OneBlankSpace + text.TrimStart();
public static string CapitalizeFirstLetter(this string value)
char[] array = value.ToCharArray();
if (char.IsLower(array[0]))
array[0] = char.ToUpper(array[0]);
for (int i = 1; i < array.Length; i++)
if (char.IsLower(array[i]))
array[i] = char.ToUpper(array[i]);
return new string (array);
public static string SplitCapitalizedWords(this string source)
if (String.IsNullOrEmpty(source))
var newText = new StringBuilder(source.Length * 2);
newText.Append(source[0]);
for (int i = 1; i < source.Length; i++)
if (char.IsUpper(source[i]))
newText.Append(source[i]);
return newText.ToString();
public static IEnumerable<int> AllIndexesOf(this string str, string searchString)
int minIndex = str.IndexOf(searchString, StringComparison.Ordinal);
minIndex = str.IndexOf(searchString, minIndex + searchString.Length, StringComparison.Ordinal);
public static bool ContainsAny(this string @this, params string[] values)
foreach (string value in values)
if (@this.IndexOf(value) != -1)
public static bool ContainsAny(this string @this, StringComparison comparisonType, params string[] values)
foreach (string value in values)
if (@this.IndexOf(value, comparisonType) != -1)
public static bool DoesNotContainAnySpace(this string @this) => !@this.Any(char.IsWhiteSpace);
public static bool ContainsOnly(this string @this, string allowedChars) => @this.All(allowedChars.Contains);
public static bool StringHasOnlyNumbers(this string @this) => @this.ReturnOnlyNumbers().Length == @this.Length;
public static List<string> ReturnOnlyInvalidChars(this string @this, string allowedChars) => (
where !allowedChars.Contains(c.ToString())select c.ToString()).ToList();
public static string ReturnOnlyNumbers(this string @this) => new(@this.Where(char.IsDigit).ToArray());
public static int? ReturnOnlyNumbersAsInt(this string @this)
int.TryParse(@this.ReturnOnlyNumbers(), out var result);
public static string ReturnOnlyNonNumericCharacters(this string @this) => Regex.Replace(@this, @"[\d-]", string.Empty);
public static int[] ReturnOnlyNumbersToArray(this string @this) => Regex.Matches(@this, "(-?[0-9]+)").OfType<Match>().Select(m => int.Parse(m.Value)).ToArray();
public static int GetSingleDigitAtPositionInFilteredStrOnlyNumbers(this string @this, int position1Based)
if (position1Based < 1 || position1Based > @this.Length - 1)
throw new ArgumentOutOfRangeException(nameof(position1Based), $"{nameof(position1Based)} cannot be outer of string limits");
return int.Parse(@this.ReturnOnlyNumbers()[position1Based - 1].ToString());
public static string FormatPhoneNumber(this long number, string mask) => number.ToString(mask);
public static string FormatPhoneNumber(this string number)
var mask = number.ReturnOnlyNumbers().Length switch
15 => @"0 000 (00) 00000-0000",
13 => @"+00 (00) 00000-0000",
11 => @"(00) 00000-0000",
_ => throw new ArgumentOutOfRangeException(nameof(FormatPhoneNumber), "FormatPhoneNumber only accepts certain ranges of phone numbers"),
return long.Parse(number.ReturnOnlyNumbers()).FormatPhoneNumber(mask);
public static bool IsDate(this string @this, out DateTime date) => DateTime.TryParse(@this, out date);
public static bool IsDate(this string @this) => DateTime.TryParse(@this, out var date);
public static bool IsNumber(this string @this) => int.TryParse(@this, out var number);
public static bool IsDigit(this string @this) => @this.All(c => c >= '0' && c <= '9');
public static class EasyOfficeFolderExtensions
public static string GetParentFolder(this string pathSource)
return Path.GetFullPath(Path.Combine(pathSource, @"..\"));
public static string CombinePaths(this string pathWithRoot, string pathDistal)
var networkFolder = !pathWithRoot.Contains(":");
var pathOne = pathWithRoot.Split(@"\".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var pathTwo = pathDistal.Split(@"\".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var pathFinal = pathOne.Concat(pathTwo);
var preFinalPath = Path.Combine(pathFinal.ToArray());
return "\\\\" + preFinalPath;
return preFinalPath.Contains(":\\") ? preFinalPath : preFinalPath.Replace(":", ":\\");
public static class ObjectTextExtensions
public static T ObjToEnum<T>(this object o, int unknown = 0)
T enumVal = (T)Enum.Parse(typeof(T), o.ToString());
return (T)Enum.ToObject(typeof(T), unknown);
return (T)Enum.ToObject(typeof(T), unknown);
public static bool IsDefined(this System.Enum value)
return Enum.IsDefined(value.GetType(), value);
public static int EnumToInt<TValue>(this TValue value)
where TValue : System.Enum => (int)(object)value;
public static TValue IntToEnum<TValue>(this int value)
where TValue : System.Enum => (TValue)(object)value;
public static T GetEnumValue<T>(this string value)
throw new Exception("T must be an enum");
if (Enum.IsDefined(t, value))
return (T)Enum.Parse(t, value);
public static T GetValueFromDescription<T>(this string description, bool throwException = true)
foreach (var field in typeof(T).GetFields())
if (Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute))is DescriptionAttribute attribute)
if (attribute.Description == description)
return (T)field.GetValue(null);
if (field.Name == description)
return (T)field.GetValue(null);
throw new ArgumentException("Not found.", nameof(description));
public static string GetDescription(this Enum value)
FieldInfo field = value.GetType().GetField(value.ToString());
DescriptionAttribute attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
return attribute == null ? value.ToString() : attribute.Description;
public static string GetDescription(this object e, Type enumType)
var fieldValue = enumType.GetField(Enum.GetValues(enumType).Cast<object>().Where(o => o.ToString() == e.ToString()).Select(o => o.ToString()).FirstOrDefault()).ToString();
FieldInfo field = enumType.GetField(fieldValue.Contains(" ") ? fieldValue.Split().Last() : fieldValue);
DescriptionAttribute attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
return attribute == null ? e.ToString() : attribute.Description;
public static Dictionary<int, TValue> EnumToDictionary<TValue>(this TValue value)
where TValue : System.Enum
Type enumType = typeof(TValue);
var allEnumValues = enumType.GetEnumValues().Cast<TValue>();
var enumDL = new Dictionary<int, TValue>();
foreach (TValue val in allEnumValues.ToList())
enumDL.Add(val.EnumToInt(), val);
public static Dictionary<int, Enum> EnumToDictionaryNotGeneric(this Enum @enum)
Type enumType = @enum.GetType();
var allEnumValues = Enum.GetValues(@enum.GetType()).Cast<Enum>();
var enumDL = new Dictionary<int, Enum>();
foreach (var val in allEnumValues.ToList())
enumDL.Add(val.EnumToInt(), val);
public static Dictionary<int, T> EnumToDictionary<T>(this Type enumType)
var allEnumValues = enumType.GetEnumValues().Cast<T>();
var converter = new EnumConverter(enumType);
var enumDL = new Dictionary<int, T>();
foreach (T val in allEnumValues.ToList())
enumDL.Add(val.EnumToInt(), val);
public static IReadOnlyList<T> GetValues<T>()
return (T[])Enum.GetValues(typeof(T));