Skip to content

Commit

Permalink
fix(module:datepicker): FormatAnalyzer handles also year, week, month…
Browse files Browse the repository at this point in the history
… & quarter picker
  • Loading branch information
anddrzejb committed Apr 23, 2021
1 parent b268394 commit dd21191
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 31 deletions.
13 changes: 2 additions & 11 deletions components/date-picker/DatePicker.Razor.cs
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
Expand Down Expand Up @@ -83,17 +82,9 @@ protected void OnInput(ChangeEventArgs args, int index = 0)
return;
}

if (BindConverter.TryConvertTo(args.Value.ToString(), CultureInfo, out TValue changeValue))
if (FormatAnalyzer.TryPickerStringConvert(args.Value.ToString(), out TValue changeValue, CultureInfo, IsNullable))
{
if (Picker == DatePickerType.Date)
{
if (FormatAnalyzer.IsFullString(args.Value.ToString()))
CurrentValue = changeValue;
else
return;
}
else
CurrentValue = changeValue;
CurrentValue = changeValue;

GetIfNotNull(changeValue, (notNullValue) =>
{
Expand Down
13 changes: 3 additions & 10 deletions components/date-picker/RangePicker.razor.cs
Expand Up @@ -85,17 +85,10 @@ protected void OnInput(ChangeEventArgs args, int index = 0)

var array = Value as Array;

if (BindConverter.TryConvertTo(args.Value.ToString(), CultureInfo, out DateTime changeValue))
if (FormatAnalyzer.TryPickerStringConvert(args.Value.ToString(), out DateTime changeValue, CultureInfo, false))
{
if (Picker == DatePickerType.Date)
{
if (FormatAnalyzer.IsFullString(args.Value.ToString()))
array.SetValue(changeValue, index);
else
return;
}
else
array.SetValue(changeValue, index);
array.SetValue(changeValue, index);

ChangePickerValue(changeValue, index);

if (OnChange.HasDelegate)
Expand Down
6 changes: 3 additions & 3 deletions components/date-picker/internal/DatePickerBase.cs
Expand Up @@ -567,16 +567,16 @@ private string GetTimeFormat()
}

private FormatAnalyzer _formatAnalyzer;
public FormatAnalyzer FormatAnalyzer => _formatAnalyzer ??= new(InternalFormat);
public FormatAnalyzer FormatAnalyzer => _formatAnalyzer ??= new(InternalFormat, Picker, Locale);

public string GetFormatValue(DateTime value, int index)
{
string format;
if (string.IsNullOrEmpty(Format))
format = _pickerStatus[index]._initPicker switch
{
DatePickerType.Week => $"{value.Year}-{DateHelper.GetWeekOfYear(value)}{Locale.Lang.Week}",
DatePickerType.Quarter => $"{value.Year}-{DateHelper.GetDayOfQuarter(value)}",
DatePickerType.Week => $"{Locale.Lang.YearFormat}-{DateHelper.GetWeekOfYear(value)}{Locale.Lang.Week}",
DatePickerType.Quarter => $"{Locale.Lang.YearFormat}-{DateHelper.GetDayOfQuarter(value)}",
_ => InternalFormat,
};
else
Expand Down
140 changes: 138 additions & 2 deletions components/date-picker/locale/FormatAnalyzer.cs
@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using AntDesign.core.Extensions;
using Microsoft.AspNetCore.Components;

namespace AntDesign.Datepicker.Locale
{
Expand All @@ -16,7 +19,8 @@ public class FormatAnalyzer

private List<string> _separators = new();
private List<DateTimePartialType> _partialsOrder = new();

private readonly string _anaylzerType;
private readonly DatePickerLocale _locale;

public enum DateTimePartialType
{
Expand All @@ -29,10 +33,12 @@ public enum DateTimePartialType
Year
}

public FormatAnalyzer(string format)
public FormatAnalyzer(string format, string anaylzerType, DatePickerLocale locale)
{
_formatLength = format.Length;
AnalyzeFormat(format);
_anaylzerType = anaylzerType;
_locale = locale;
}

private void AnalyzeFormat(string format)
Expand Down Expand Up @@ -142,5 +148,135 @@ public bool IsFullString(string forEvaluation)
}
return true;
}

public (bool, DateTime) TryParseQuarterString(string forEvaluation,
string separator = "-", string quarterPrefix = "Q")
{
var arr = forEvaluation.Split(separator);
if (arr.Length != 2)
return (false, default);

if (!(arr[0].Trim().Length >= _locale.Lang.YearFormat.Length
&& arr[0].Trim().Length < 5
&& int.TryParse(arr[0], out int year)))
return (false, default);

if (!arr[1].StartsWith(quarterPrefix.ToUpper()) && !arr[1].StartsWith(quarterPrefix.ToLower()))
return (false, default);

string quarterAsString = arr[1].Substring(quarterPrefix.Length).Trim();
if (quarterAsString.Length == 1
&& int.TryParse(quarterAsString, out int quarter)
&& quarter > 0 && quarter <= 4)
{
//pick first day/month of the quarter
return (true, new DateTime(year, quarter * 3 - 2, 1));
}

return (false, default);
}

public (bool, DateTime) TryParseWeekString(string forEvaluation, string separator = "-")
{
var arr = forEvaluation.Split(separator);
if (arr.Length != 2)
return (false, default);

if (!(arr[0].Trim().Length >= _locale.Lang.YearFormat.Length
&& arr[0].Trim().Length < 5
&& int.TryParse(arr[0], out int year)))
return (false, default);

if (!arr[1].EndsWith(_locale.Lang.Week))
return (false, default);

string weekAsString = arr[1].Substring(0, arr[1].Length - _locale.Lang.Week.Length).Trim();

if (!(weekAsString.Length > 0 && weekAsString.Length <= 2
&& int.TryParse(weekAsString, out int week)
&& week > 0 && week < 55))
return (false, default);

//pick first day of the week
var resultDate = new DateTime(year, 1, 1).AddDays(week * 7 - 7);
if (week > 1)
{
int mondayOffset = (7 + (resultDate.DayOfWeek - DayOfWeek.Monday)) % 7;
resultDate = resultDate.AddDays(-1 * mondayOffset);
}
//cover scenario of 54 weeks when most of times years do not have 54 weeks
if (resultDate.Year == year)
return (true, resultDate);

return (false, default);
}

Func<string, CultureInfo, (bool, DateTime)> _converter;

private Func<string, CultureInfo, (bool, DateTime)> Converter
{
get
{
if (_converter is null)
{
Console.WriteLine("Converter setup");
switch (_anaylzerType)
{
case DatePickerType.Year:
_converter = (pickerString, currentCultureInfo) => TryParseYear(pickerString);
break;
case DatePickerType.Quarter:
_converter = (pickerString, currentCultureInfo) => TryParseQuarterString(pickerString);
break;
case DatePickerType.Week:
_converter = (pickerString, currentCultureInfo) => TryParseWeekString(pickerString);
break;
default:
_converter = (pickerString, currentCultureInfo) => TryParseDate(pickerString, currentCultureInfo);
break;
}
}
return _converter;
}
}

public bool TryPickerStringConvert<TValue>(string pickerString, out TValue changeValue, CultureInfo currentCultureInfo, bool isDateTypeNullable)
{
var resultTuple = Converter(pickerString, currentCultureInfo);
if (resultTuple.Item1)
{
return GetParsedValue(out changeValue, resultTuple.Item2, isDateTypeNullable);
}
changeValue = default;
return false;
}

private (bool, DateTime) TryParseDate(string pickerString, CultureInfo currentCultureInfo)
{
if (IsFullString(pickerString))
{
return (BindConverter.TryConvertTo(pickerString, currentCultureInfo, out DateTime changeValue), changeValue);
}
return (false, default);
}

public (bool, DateTime) TryParseYear(string pickerString)
{
if (IsFullString(pickerString))
{
int value = int.Parse(pickerString);
return (true, new DateTime(value, 1, 1));
}
return (false, default);
}

private bool GetParsedValue<TValue>(out TValue changeValue, DateTime foundDate, bool isDateTypeNullable)
{
if (isDateTypeNullable)
changeValue = DataConvertionExtensions.Convert<DateTime?, TValue>(new DateTime?(foundDate));
else
changeValue = DataConvertionExtensions.Convert<DateTime, TValue>(foundDate);
return true;
}
}
}
111 changes: 106 additions & 5 deletions tests/AntDesign.Tests/DatePicker/locale/FormatAnalyzerTests.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using AntDesign.Datepicker.Locale;
using Xunit;

Expand All @@ -9,17 +10,24 @@ public class FormatAnalyzerTests
{
[Theory]
[MemberData(nameof(FormatAnalyzer_values_seeds))]
public void DateFormatDetails_ShouldReturnCorrectBool_WhenIsDateFullStringCalled(
public void IsFullString_ShouldReturnCorrectBool_WhenIsDateFullStringCalled(
string dateFormat, string possibleDate, bool expectedResult)
{
var locale = new DateLocale() { DateFormat = dateFormat };
var details = new FormatAnalyzer(dateFormat);
Console.WriteLine($"Possible date {possibleDate}");
Assert.Equal(expectedResult, details.IsFullString(possibleDate));
//Arrange
var details = new FormatAnalyzer(dateFormat, DatePickerType.Date, false, new());
//Act
var actual = details.IsFullString(possibleDate);
//Assert
Assert.Equal(expectedResult, actual);
}

public static IEnumerable<object[]> FormatAnalyzer_values_seeds => new List<object[]>
{
new object[] { "yyyy", "2020", true },
new object[] { "yyyy", "20200", false },
new object[] { "yyyy", "202", false },
new object[] { "yyyy", "20 0", false },

new object[] { "yyyy-MM-dd", "2020-01-01", true },
new object[] { "yyyy-MM-dd", "202-01-01", false },
new object[] { "yyyy-MM-dd", "20-01-01", false },
Expand Down Expand Up @@ -132,5 +140,98 @@ public class FormatAnalyzerTests
new object[] { "M/yyyy/d H:mm:s", "01/2020/ 1 10:01:02", false },

};

[Theory]
[MemberData(nameof(TryParseYear_SoulReturnCorrectBool_seed))]
public void TryParseYear_SoulReturnCorrectBool(
string dateFormat, string possibleDate, bool expectedResult, DateTime expectedParsedDate)
{
//Arrange
var details = new FormatAnalyzer(dateFormat, DatePickerType.Year, false, new());
//Act
var actualResult = details.TryPickerStringConvert<DateTime>(possibleDate, out DateTime actualParsedDate, CultureInfo.CurrentCulture);
//Assert
Assert.Equal(expectedResult, actualResult);
Assert.Equal(expectedParsedDate, actualParsedDate);
}

public static IEnumerable<object[]> TryParseYear_SoulReturnCorrectBool_seed => new List<object[]>
{
new object[] { "yyyy", "2020", true, new DateTime(2020, 1, 1) },
new object[] { "yyyy", "202", false, null },
new object[] { "yyyy", "2 20", false, null },
new object[] { "yyyy", "20020", false, null },
};

[Theory]
[MemberData(nameof(TryParseWeek_SoulReturnCorrectBool_seed))]
public void TryParseWeek_SoulReturnCorrectBool(
string possibleDate, bool expectedResult, DateTime expectedParsedDate)
{
//Arrange
var details = new FormatAnalyzer("0000-10Week", DatePickerType.Week, false, new());
//Act
var actualResult = details.TryPickerStringConvert<DateTime>(possibleDate, out DateTime actualParsedDate, CultureInfo.CurrentCulture);
//Assert
Assert.Equal(expectedResult, actualResult);
Assert.Equal(expectedParsedDate, actualParsedDate);
}

public static IEnumerable<object[]> TryParseWeek_SoulReturnCorrectBool_seed => new List<object[]>
{
new object[] { "2020-1Week", true, new DateTime(2020, 1, 1) },
new object[] { "0120-1Week", true, new DateTime(120, 1, 1) },
new object[] { "2020-2Week", true, new DateTime(2020, 1, 6) },
new object[] { "2020- 1Week", true, new DateTime(2020, 1, 1) },
new object[] { "2020-12Week", true, new DateTime(2020,3, 16) },
new object[] { "2020-52Week", true, new DateTime(2020, 12, 21) },
new object[] { "2020-53Week", true, new DateTime(2020, 12, 28) },
new object[] { "2040-54Week", true, new DateTime(2040, 12, 31) }, //extremely rare, only when leap year + 1st of Jan is on Sunday
new object[] { "2020-54Week", false, null },
new object[] { "202-12Week", false, null },
new object[] { "2 20-12Week", false, null },
new object[] { " 020-12Week", false, null },
new object[] { "20201Week", false, null },
new object[] { "2020/1Week", false, null },
new object[] { "2020-1Weeks", false, null },
new object[] { "2020-1week", false, null },
new object[] { "2020-1week", false, null },
new object[] { "2020--1Week", false, null },
new object[] { "2020-0Week", false, null },
new object[] { "2020-AWeek", false, null },

};

[Theory]
[MemberData(nameof(TryParseQuarter_SoulReturnCorrectBool_seed))]
public void TryParseQuarter_SoulReturnCorrectBool(
string possibleDate, bool expectedResult, DateTime expectedParsedDate)
{
//Arrange
var details = new FormatAnalyzer("0000-Q0", DatePickerType.Quarter, false, new());
//Act
var actualResult = details.TryPickerStringConvert<DateTime>(possibleDate, out DateTime actualParsedDate, CultureInfo.CurrentCulture);
//Assert
Assert.Equal(expectedResult, actualResult);
Assert.Equal(expectedParsedDate, actualParsedDate);
}

public static IEnumerable<object[]> TryParseQuarter_SoulReturnCorrectBool_seed => new List<object[]>
{
new object[] { "2020-Q2", true, new DateTime(2020, 4, 1) },
new object[] { "2020-Q1", true, new DateTime(2020, 1, 1) },
new object[] { "2020-Q3", true, new DateTime(2020, 7, 1) },
new object[] { "2020-Q4", true, new DateTime(2020, 10, 1) },
new object[] { "0020-Q4", true, new DateTime(20, 10, 1) },
new object[] { "2020-q4", true, new DateTime(2020, 10, 1) },
new object[] { "2020-Q5", false, null },
new object[] { "2020-Q0", false, null },
new object[] { "2020-1", false, null },
new object[] { "202-Q1", false, null },
new object[] { "20 2-Q1", false, null },
new object[] { " 202-Q1", false, null },
new object[] { "2020/Q1", false, null },
new object[] { "2020 Q1", false, null },
};
}
}

0 comments on commit dd21191

Please sign in to comment.