Browse Source

Remove DateTimeFormatter/use normal .net formatting

pull/4108/head
amwx 6 years ago
parent
commit
3c3853dab2
  1. 4
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml
  2. 12
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  3. 22
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  4. 335
      src/Avalonia.Controls/DateTimePickers/DateTimeFormatter.cs
  5. 40
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  6. 22
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

4
samples/ControlCatalog/Pages/DateTimePickerPage.xaml

@ -34,14 +34,14 @@
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15">
<DatePicker x:Name="Control2" DayFormat="{}{day.integer} ({dayofweek.abbreviated})"
<DatePicker x:Name="Control2" DayFormat="d (ddd)"
YearVisible="False" />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
&lt;DatePicker DayFormat="{}{day.integer} ({dayofweek.abbreviated})" YearVisible="False" /&gt;
&lt;DatePicker DayFormat="d (ddd)" YearVisible="False" /&gt;
</x:String>
</TextBlock.Text>
</TextBlock>

12
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@ -447,9 +447,9 @@ namespace Avalonia.Controls
{
PseudoClasses.Set(":hasnodate", false);
var selDate = SelectedDate.Value;
_monthText.Text = new DateTimeFormatter(MonthFormat).Format(selDate);
_yearText.Text = new DateTimeFormatter(YearFormat).Format(selDate);
_dayText.Text = new DateTimeFormatter(DayFormat).Format(selDate);
_monthText.Text = selDate.ToString(MonthFormat);
_yearText.Text = selDate.ToString(YearFormat);
_dayText.Text = selDate.ToString(DayFormat);
}
else
{
@ -504,13 +504,13 @@ namespace Avalonia.Controls
private int _dayIndex;
private int _yearIndex;
private string _dayFormat = "{day.integer}";
private string _dayFormat = "%d";
private bool _dayVisible = true;
private DateTimeOffset _maxYear;
private DateTimeOffset _minYear;
private string _monthFormat = "{month.full}";
private string _monthFormat = "MMMM";
private bool _monthVisible = true;
private string _yearFormat = "{year.full}";
private string _yearFormat = "yyyy";
private bool _yearVisible = true;
private DateTimeOffset? _selectedDate;
}

22
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@ -511,7 +511,6 @@ namespace Avalonia.Controls
_dayItems.Clear();
var format = DayFormat;
DateTimeFormatter formatter = new DateTimeFormatter(format);
var date = Date;
GregorianCalendar gc = new GregorianCalendar();
var daysInMonth = gc.GetDaysInMonth(date.Date.Year, date.Date.Month);
@ -522,9 +521,8 @@ namespace Avalonia.Controls
curDt = new DateTimeOffset(date.Date.Year, date.Date.Month, dayIndex, 0, 0, 0, date.Offset);
DatePickerPresenterItem dppi = new DatePickerPresenterItem(curDt);
dppi.DisplayText = formatter.Format(curDt);
dppi.DisplayText = curDt.ToString(format);
_dayItems.Add(dppi);
dayIndex++;
}
}
@ -541,7 +539,6 @@ namespace Avalonia.Controls
_monthItems.Clear();
var format = MonthFormat;
DateTimeFormatter formatter = new DateTimeFormatter(format);
var date = Date;
int monthIndex = 1;
DateTimeOffset curDt;
@ -550,7 +547,7 @@ namespace Avalonia.Controls
curDt = new DateTimeOffset(date.Date.Year, monthIndex, 1, 0, 0, 0, date.Offset);
DatePickerPresenterItem dppi = new DatePickerPresenterItem(curDt);
dppi.DisplayText = formatter.Format(curDt);
dppi.DisplayText = curDt.ToString(format);
_monthItems.Add(dppi);
monthIndex++;
@ -568,14 +565,13 @@ namespace Avalonia.Controls
_yearItems.Clear();
var format = YearFormat;
DateTimeFormatter formatter = new DateTimeFormatter(format);
var curDt = MinYear.Date;
var max = MaxYear.Date;
while (curDt <= max)
{
DatePickerPresenterItem dppi = new DatePickerPresenterItem(curDt);
dppi.DisplayText = formatter.Format(curDt);
dppi.DisplayText = curDt.ToString(format);
_yearItems.Add(dppi);
curDt = curDt.AddYears(1);
}
@ -594,7 +590,7 @@ namespace Avalonia.Controls
if (!forceRecreate && daysInMonth == _dayItems.Count)
return;
DateTimeFormatter formatter = new DateTimeFormatter(DayFormat);
var format = DayFormat;
if (forceRecreate)
{
//We're not actually going to recreate the entire list, we're just
@ -604,7 +600,7 @@ namespace Avalonia.Controls
DateTimeOffset curDt = new DateTimeOffset(Date.Date.Year, Date.Date.Month, 1, 0, 0, 0, Date.Offset);
for (int i = 0; i < _dayItems.Count; i++)
{
_dayItems[i].UpdateStoredDate(curDt, formatter.Format(curDt));
_dayItems[i].UpdateStoredDate(curDt, curDt.ToString(format));
curDt = curDt.AddDays(1);
}
}
@ -619,7 +615,7 @@ namespace Avalonia.Controls
{
curDt = new DateTimeOffset(date.Date.Year, date.Date.Month, dayIndex, 0, 0, 0, date.Offset);
DatePickerPresenterItem dppi = new DatePickerPresenterItem(curDt);
dppi.DisplayText = formatter.Format(curDt);
dppi.DisplayText = curDt.ToString(format);
_dayItems.Add(dppi);
dayIndex++;
}
@ -1100,13 +1096,13 @@ namespace Avalonia.Controls
private LoopingSelector _daySelector;
private DateTimeOffset _date;
private string _dayFormat = "{day.integer}";
private string _dayFormat = "%d";
private bool _dayVisible = true;
private DateTimeOffset _maxYear;
private DateTimeOffset _minYear;
private string _monthFormat = "{month.full}";
private string _monthFormat = "MMMM";
private bool _monthVisible = true;
private string _yearFormat = "{year.full}";
private string _yearFormat = "yyyy";
private bool _yearVisible = true;
private DateTimeOffset _initDate;

335
src/Avalonia.Controls/DateTimePickers/DateTimeFormatter.cs

@ -1,335 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
namespace Avalonia.Controls
{
/// <summary>
/// Formats a DateTimeOffset by the specified pattern
/// Based on, but not replica, the Windows.Globalization.DateTimeFormatting.DateTimeFormatter
/// https://docs.microsoft.com/en-us/uwp/api/windows.globalization.datetimeformatting.datetimeformatter?view=winrt-19041
/// <para>
/// Currently only the Gregorian Calendar is supported, and regions and languages are not supported.
/// Formatting timezones is also not supported, that's a large task
/// </para>
/// <para>
/// /// Most formats used in UWP/WinUI are compatible here, but not all
/// This DateTimeFormatter will also work with TimeSpans (only patterns though),
/// in addition to the default DateTime/DateTimeOffset
/// </para>
/// <para>
/// Formats are broken down into Patterns and Templates, which are "complete" patterns
/// If a Template is used, only 1 may be provided and cannot be mixed with anything else
/// Multiple patterns can be specified, and can be mixed with other text. All patterns
/// must be enclosed in curly braces {}, e.g. {dayofweek}, or in xaml "{}{dayofweek}"
/// NOTE: Formats and Templates are case sensitive
/// </para>
/// </summary>
public sealed class DateTimeFormatter
{
public DateTimeFormatter(string formatString)
{
_format = formatString;
var reg = new Regex("{([^{}]*)}");
var results = reg.Matches(formatString).Cast<Match>().Select(m => m.Groups[1].Value).Distinct().ToList();
Formats = results;
}
public string Clock
{
get
{
if (_clock == null)
{
var timePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
if (timePattern.IndexOf("H") != -1)
return "24HourClock";
return "12HourClock";
}
return _clock;
}
set
{
_clock = value;
}
}
public List<string> Formats { get; }
/// <summary>
/// Formats a DateTimeOffset object by the format of the <see cref="DateTimeFormatter"/>
/// </summary>
public string Format(DateTimeOffset toFormat)
{
string ret = _format;
if (Formats.Count == 0)
{
return GetFormatTemplate(toFormat);
}
foreach (var item in Formats)
{
var p = $"{{{item}}}";
var ns = Regex.Replace(ret, $"{{{Regex.Escape(item)}}}", GetFormatValue(item, toFormat));
ret = ns;
}
return ret;
}
/// <summary>
/// Formats a TimeSpan object by the format of the <see cref="DateTimeFormatter"/>
/// </summary>
public string Format(TimeSpan toFormat)
{
string ret = _format;
foreach (var item in Formats)
{
var p = $"{{{item}}}";
var ns = Regex.Replace(ret, $"{{{Regex.Escape(item)}}}", GetFormatValue(item, toFormat));
ret = ns;
}
return ret;
}
private string GetFormatValue(string pattern, DateTimeOffset dt)
{
var sp = pattern.Split(new[] { "." }, StringSplitOptions.None);
var type = sp[0].Trim();
var desc = sp[1].Trim();
var len = desc.Contains("(") ? int.Parse(desc.Substring(desc.IndexOf("(") + 1, desc.Length - desc.IndexOf(")"))) : -1;
if (type.Equals("era"))
{
return "";
}
else if (type.Equals("year"))
{
var yr = dt.Year;
if (len == -1)
return yr.ToString();
else if (len <= 2)
return yr.ToString().Substring(2);
else
return yr.ToString();
}
else if (type.Equals("month"))
{
var mon = dt.Month;
var fmt = CultureInfo.CurrentCulture.DateTimeFormat;
if (len == -1)
return desc == "full" ? fmt.GetMonthName(dt.Month) : fmt.GetAbbreviatedMonthName(dt.Month);
var nm = desc == "full" ? fmt.GetMonthName(dt.Month) : fmt.GetAbbreviatedMonthName(dt.Month);
len = Math.Min(nm.Length, Math.Max(0, len));
return nm.Substring(0, len);
}
else if (type.Equals("dayofweek"))
{
var dow = dt.DayOfWeek;
var fmt = CultureInfo.CurrentCulture.DateTimeFormat;
if (len == -1)
return desc == "full" ? fmt.GetDayName(dt.DayOfWeek) : fmt.GetAbbreviatedDayName(dt.DayOfWeek);
var nm = desc == "full" ? fmt.GetDayName(dt.DayOfWeek) : fmt.GetAbbreviatedDayName(dt.DayOfWeek);
len = Math.Min(nm.Length, Math.Max(0, len));
return nm.Substring(0, len);
}
else if (type.Equals("day"))
{
var dy = dt.Day;
if (len == -1)
return dy.ToString();
if (len < 1 || len > 2)
len = 2;
return dy.ToString($"D{len}");
}
else if (type.Equals("period"))
{
if (Clock == "24HourClock")
return "";
var hr = dt.Hour;
if (hr >= 12)
return CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator;
else
return CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator;
}
else if (type.Equals("hour"))
{
var shortTimePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.ToLower();
var resolvedLength = 1;
//valid Hours: h, hh
if (shortTimePattern.Contains("hh"))
{
resolvedLength = 2;
}
var hr = dt.Hour;
if (Clock == "12HourClock")
hr = hr >= 13 ? hr - 12 : hr == 0 ? 12 : hr;
if (len == -1)
return hr.ToString($"D{resolvedLength}");
if (len < 1 || len > 2)
len = resolvedLength;
return hr.ToString($"D{len}");
}
else if (type.Equals("minute"))
{
var shortTimePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.ToLower();
var resolvedLength = 1;
//valid mintues: m, mm
if (shortTimePattern.Contains("hh"))
{
resolvedLength = 2;
}
var dy = dt.Minute;
if (len == -1)
return dy.ToString($"D{resolvedLength}");
if (len < 1 || len > 2)
len = resolvedLength;
return dy.ToString($"D{len}");
}
else if (type.Equals("second"))
{
var shortTimePattern = CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern.ToLower();
var resolvedLength = 1;
//valid seconds: s, ss
if (shortTimePattern.Contains("ss"))
{
resolvedLength = 2;
}
var dy = dt.Second;
if (len == -1)
return dy.ToString($"D{resolvedLength}");
if (len < 1 || len > 2)
len = resolvedLength;
return dy.ToString($"D{len}");
}
else if (type.Equals("timezone"))
{
//Timezones aren't supported yet, that's a HUGE task
//Multiple timezones can exist for a given offset from UTC,
//So info about region is necessary to obtain it
//It'd be nice if MS would move stuff from WinRT globalization namespace to .net
throw new NotSupportedException("Timezones aren't supported yet");
}
throw new ArgumentException("Invalid format");
}
private string GetFormatValue(string pattern, TimeSpan ts)
{
var sp = pattern.Split(new[] { "." }, StringSplitOptions.None);
var type = sp[0].Trim();
var desc = sp.Count() > 1 ? sp[1].Trim() : "";
var len = desc.Contains("(") ? int.Parse(desc.Substring(desc.IndexOf("(") + 1, desc.Length - desc.IndexOf(")"))) : -1;
if (type.Equals("hour"))
{
var shortTimePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.ToLower();
var resolvedLength = 1;
//valid Hours: h, hh
if (shortTimePattern.Contains("hh"))
{
resolvedLength = 2;
}
var hr = ts.Hours;
if (Clock == "12HourClock")
hr = hr >= 13 ? hr - 12 : hr == 0 ? 12 : hr;
if (len == -1)
return hr.ToString($"D{resolvedLength}");
if (len < 1 || len > 2)
len = resolvedLength;
return hr.ToString($"D{len}");
}
else if (type.Equals("minute"))
{
var shortTimePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.ToLower();
var resolvedLength = 1;
//valid mintues: m, mm
if (shortTimePattern.Contains("hh"))
{
resolvedLength = 2;
}
var dy = ts.Minutes;
if (len == -1)
return dy.ToString($"D{resolvedLength}");
if (len < 1 || len > 2)
len = resolvedLength;
return dy.ToString($"D{len}");
}
else if (type.Equals("second"))
{
var shortTimePattern = CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern.ToLower();
var resolvedLength = 1;
//valid seconds: s, ss
if (shortTimePattern.Contains("ss"))
{
resolvedLength = 2;
}
var dy = ts.Seconds;
if (len == -1)
return dy.ToString($"D{resolvedLength}");
if (len < 1 || len > 2)
len = resolvedLength;
return dy.ToString($"D{len}");
}
else if (type.Equals("period"))
{
if (Clock == "24HourClock")
return "";
var hr = ts.Hours;
if (hr >= 12)
return CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator;
else
return CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator;
}
throw new ArgumentException("Invalid format");
}
private string GetFormatTemplate(DateTimeOffset dt)
{
switch (_format)
{
case "longdate":
return dt.ToString(CultureInfo.CurrentCulture.DateTimeFormat.LongDatePattern);
case "shortdate":
return dt.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern);
case "longtime":
return dt.ToString(CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern);
case "shorttime":
return dt.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern);
case "iso8601":
case "sortable":
return dt.ToString(CultureInfo.CurrentCulture.DateTimeFormat.SortableDateTimePattern);
case "universalsortable":
return dt.ToString(CultureInfo.CurrentCulture.DateTimeFormat.UniversalSortableDateTimePattern);
case "rfc1123":
return dt.ToUniversalTime().ToString(CultureInfo.CurrentCulture.DateTimeFormat.RFC1123Pattern);
}
throw new ArgumentException("Invalid template");
}
private string _format;
private string _clock;
}
}

40
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -204,15 +204,9 @@ namespace Avalonia.Controls
var time = SelectedTime;
if (time.HasValue)
{
//DateTimeFormatter will automatically handle the correct format for
//hours/minutes based on culture settings
DateTimeFormatter formatter = new DateTimeFormatter("{hour}");
formatter.Clock = ClockIdentifier;
_hourText.Text = formatter.Format(time.Value);
formatter = new DateTimeFormatter("{minute}");
formatter.Clock = ClockIdentifier;
_minuteText.Text = formatter.Format(time.Value);
_hourText.Text = GetTimeFormat(time.Value, true, ClockIdentifier);
_minuteText.Text = GetTimeFormat(time.Value);
PseudoClasses.Set(":hasnotime", false);
if (time.Value.Hours >= 12)
@ -233,6 +227,34 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Helps formatting timespans for use in TimePicker and TimePickerPresenter
/// </summary>
/// <param name="timeSpan">Timespan to format</param>
/// <param name="forHour">True if formatting hour, false if minute</param>
/// <param name="clockIdentifier"></param>
/// <returns></returns>
internal static string GetTimeFormat(TimeSpan timeSpan, bool forHour = false, string clockIdentifier = "12HourClock")
{
var fmt = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.ToLower();
if (forHour)
{
var hr = timeSpan.Hours;
if (clockIdentifier == "12HourClock" && hr > 12)
hr -= 12;
else if (clockIdentifier == "12HourClock" && hr == 0)
hr = 12;
return fmt.Contains("hh") ? hr.ToString("D2") : hr.ToString();
}
else
{
return timeSpan.ToString("mm");
}
}
private void OnFlyoutButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
_presenter.ClockIdentifier = ClockIdentifier;

22
src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

@ -94,8 +94,6 @@ namespace Avalonia.Controls
{
var old = _Time;
SetAndRaise(TimeProperty, ref _Time, value);
//SelectedTimeChanged?.Invoke(this, new TimePickerSelectedValueChangedEventArgs(old, value));
//SetSelectedTimeText();
}
}
@ -269,16 +267,14 @@ namespace Avalonia.Controls
if (_hourItems == null)
{
_hourItems = new AvaloniaList<TimePickerPresenterItem>();
DateTimeFormatter formatter = new DateTimeFormatter("{hour}");
formatter.Clock = clock;
int numItems = clock == "12HourClock" ? 12 : 24;
for (int i = 0; i < numItems; i++)
{
var hr = clock == "12HourClock" ? TimeSpan.FromHours(i + 1) : TimeSpan.FromHours(i);
TimePickerPresenterItem tppi = new TimePickerPresenterItem(hr);
tppi.DisplayText = formatter.Format(hr);
tppi.DisplayText = TimePicker.GetTimeFormat(hr, true, clock);
_hourItems.Add(tppi);
}
@ -287,8 +283,6 @@ namespace Avalonia.Controls
else if (_hasClockChanged)
{
_hourItems.Clear();
DateTimeFormatter formatter = new DateTimeFormatter("{hour}");
formatter.Clock = clock;
int numItems = clock == "12HourClock" ? 12 : 24;
@ -296,7 +290,7 @@ namespace Avalonia.Controls
{
var hr = clock == "12HourClock" ? TimeSpan.FromHours(i + 1) : TimeSpan.FromHours(i);
TimePickerPresenterItem tppi = new TimePickerPresenterItem(hr);
tppi.DisplayText = formatter.Format(hr);
tppi.DisplayText = TimePicker.GetTimeFormat(hr, true, clock);
_hourItems.Add(tppi);
}
}
@ -320,14 +314,13 @@ namespace Avalonia.Controls
if (_minuteItems == null)
{
_minuteItems = new AvaloniaList<TimePickerPresenterItem>();
DateTimeFormatter formatter = new DateTimeFormatter("{minute}");
var inc = MinuteIncrement;
for (int i = 0; i < 60; i += inc)
{
var min = TimeSpan.FromMinutes(i);
TimePickerPresenterItem tppi = new TimePickerPresenterItem(min);
tppi.DisplayText = formatter.Format(min);
tppi.DisplayText = TimePicker.GetTimeFormat(min);
_minuteItems.Add(tppi);
}
@ -336,14 +329,13 @@ namespace Avalonia.Controls
else if (_hasMinuteIncChanged)
{
_minuteItems.Clear();
DateTimeFormatter formatter = new DateTimeFormatter("{minute}");
var inc = MinuteIncrement;
for (int i = 0; i < 60; i += inc)
{
var min = TimeSpan.FromMinutes(i);
TimePickerPresenterItem tppi = new TimePickerPresenterItem(min);
tppi.DisplayText = formatter.Format(min);
tppi.DisplayText = TimePicker.GetTimeFormat(min);
_minuteItems.Add(tppi);
}
}

Loading…
Cancel
Save