// Copyright (c) MudBlazor 2021 // MudBlazor licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace Connected; public class DateMask : PatternMask { public DateMask(string mask, char year = 'y', char month = 'M', char day = 'd') : base(mask) { _y = year; _M = month; _d = day; _maskChars = _maskChars.Concat(new[] { MaskChar.Digit(year), MaskChar.Digit(month), MaskChar.Digit(day), }) .ToArray(); } private char _y; private char _M; private char _d; private int _year = 0; private int _month = 0; private int _day = 0; protected override void ModifyPartiallyAlignedMask(string mask, string text, int maskOffset, ref int textIndex, ref int maskIndex, ref string alignedText) { if (alignedText.IsEmpty()) return; var y = ExtractYear(mask, alignedText, maskOffset); if (y >= 0) _year = y; MonthLogic(mask, text, maskOffset, ref textIndex, ref maskIndex, ref alignedText); DayLogic(mask, text, maskOffset, ref textIndex, ref maskIndex, ref alignedText); } private int ExtractYear(string mask, string alignedText, int maskOffset) { var yyyy = new string(_y, 4); var yy = new string(_y, 2); if (mask.Contains(yyyy)) { var (yearString, _) = Extract(yyyy, mask, maskOffset, alignedText); if (yearString == null || yearString.Length < 4) return -1; if (int.TryParse(yearString, out var year)) return year; } else if (mask.Contains(yy)) { var (yearString, _) = Extract(yy, mask, maskOffset, alignedText); if (yearString == null || yearString.Length < 2) return -1; if (int.TryParse(yearString, out var y)) return (DateTime.Today.Year / 100 * 100) + y; // this code will still work in 2100 until 2900. I guess in a thousand years we'll have to update this line ;) } return -1; } private void MonthLogic(string mask, string text, int maskOffset, ref int textIndex, ref int maskIndex, ref string alignedText) { var MM = new string(_M, 2); var (monthString, index) = Extract(MM, mask, maskOffset, alignedText); if (monthString == null) return; if (!int.TryParse(monthString, out var month)) return; if (monthString.Length == 1) { // we are at the first digit of MM, only 0 and 1 are allowed if (month > 1) { alignedText = alignedText.Insert(index, "0"); maskIndex++; } } else if (monthString.Length == 2) { var fixedMonth = FixMonth(month); _month = fixedMonth; if (fixedMonth != month) alignedText = alignedText.Remove(index, 2).Insert(index, $"{fixedMonth:D2}"); } } private void DayLogic(string mask, string text, int maskOffset, ref int textIndex, ref int maskIndex, ref string alignedText) { var dd = new string(_d, 2); var (dayString, index) = Extract(dd, mask, maskOffset, alignedText); if (dayString == null) return; if (!int.TryParse(dayString, out var day)) return; if (dayString.Length == 1) { // we are at the first digit of dd, only 0..3 are allowed except if month is February. if (day > 3 || day == 3 && _month == 2) { // by inserting a 0 we make 09 out of 9 alignedText = alignedText.Insert(index, "0"); maskIndex++; _day = day; } } else if (dayString.Length == 2) { var fixedDay = FixDay(_year, _month, day); if (fixedDay != day) alignedText = alignedText.Remove(index, 2).Insert(index, $"{fixedDay:D2}"); _day = fixedDay; } } /// /// Note: this is required for special cases where the date becomes invalid after the last character has been input /// For instance: MM/dd/yyyy from 02/29/000| to 02/29/0001| /// The year 0001 is not a leap year so the day needs to be corrected to 28 /// Or this 0[3]/31/2000 input "4" => 04/31/2000 /// But we do this only for a complete date! /// protected override string ModifyFinalText(string text) { try { var yyyy = new string(_y, 4); var yy = new string(_y, 2); var dd = new string(_d, 2); var MM = new string(_M, 2); var maskHasDay = Mask.Contains(dd); var maskHasMonth = Mask.Contains(MM); var maskHasYear = Mask.Contains(yy) || Mask.Contains(yyyy); var (dayString, dayIndex) = Extract(dd, Mask, 0, text); var (monthString, monthIndex) = Extract(MM, Mask, 0, text); var dayFound = dayIndex >= 0; var dayComplete = dayString?.Length == 2; var monthFound = monthIndex >= 0; var monthComplete = monthString?.Length == 2; var y = ExtractYear(Mask, text, 0); //if (maskHasYear && y < 0 || maskHasMonth && (!monthFound || !monthComplete) || maskHasDay && (!dayFound || !dayComplete)) // return text; // we have incomplete input, no final check necessary/possible int.TryParse(dayString ?? "", out var d); int.TryParse(monthString ?? "", out var m); if (!maskHasYear) y = 0; if (y < 0) y = _year; if (maskHasMonth && (monthFound || monthComplete)) { var m1 = FixMonth(m); if (m1 != m) text = text.Remove(monthIndex, 2).Insert(monthIndex, $"{m1:D2}"); } if (maskHasDay && (dayFound || dayComplete)) { var d1 = FixDay(y, m, d); text = text.Remove(dayIndex, 2).Insert(dayIndex, $"{d1:D2}"); } } catch (Exception e) { Console.WriteLine("Error in ModifyFinalText: " +e.Message); return text; } return text; } private int FixDay(int year, int month, int day) { if (day == 0) return 1; if (day > 28) { var daysInMonth = GetDaysInMonth(year, month); if (day > daysInMonth) return daysInMonth; } return day; } private int FixMonth(int month) { if (month == 0) return 1; if (month > 12) return 12; return month; } private int GetDaysInMonth(int year, int month) { if (month <= 0 || month > 12) // we don't know yet which month the user means, so assume 31 return 31; if (year == 0) // DateTime.DaysInMonth does not support year 0 but we just use 4 instead because it was a leap year too year = 4; return DateTime.DaysInMonth(year, Math.Min(12, Math.Max(1, month))); } private (string, int) Extract(string maskPart, string mask, int maskOffset, string alignedText) { var maskIndex = mask.IndexOf(maskPart); var index = maskIndex - maskOffset; if (index < 0 || index >= alignedText.Length) return (null, -1); var subString = alignedText.Substring(index, Math.Min(maskPart.Length, alignedText.Length - index)); if (!Regex.IsMatch(subString, @"^\d+$")) return (null, -1); return (subString, index); } public override void UpdateFrom(IMask other) { base.UpdateFrom(other); var o = other as DateMask; if (o == null) return; _y = o._y; _M = o._M; _d = o._d; } }