Range thing.

This commit is contained in:
Burak Kaan Köse
2026-03-21 00:58:01 +01:00
parent 01f7a09cb7
commit 51fef043ee
45 changed files with 1327 additions and 3753 deletions
@@ -0,0 +1,6 @@
using System;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Calendar;
public readonly record struct CalendarDisplayRequest(CalendarDisplayType DisplayType, DateOnly AnchorDate);
@@ -0,0 +1,103 @@
using System;
using System.Linq;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Calendar;
public static class CalendarRangeResolver
{
public static VisibleDateRange Resolve(CalendarDisplayRequest request, CalendarSettings settings, DateOnly today)
{
var startDate = GetStartDate(request.DisplayType, request.AnchorDate, settings);
var endDate = GetEndDate(request.DisplayType, request.AnchorDate, startDate, settings);
var dayCount = endDate.DayNumber - startDate.DayNumber + 1;
var dates = Enumerable.Range(0, dayCount)
.Select(offset => startDate.AddDays(offset))
.ToArray();
return new VisibleDateRange(
request.DisplayType,
request.AnchorDate,
startDate,
endDate,
request.AnchorDate,
dayCount,
today >= startDate && today <= endDate,
startDate.Year == endDate.Year && startDate.Month == endDate.Month,
dates);
}
public static VisibleDateRange ChangeDisplayType(VisibleDateRange currentRange, CalendarDisplayType targetDisplayType, CalendarSettings settings, DateOnly today)
{
if (currentRange.DisplayType == targetDisplayType)
{
return currentRange;
}
var anchorDate = currentRange.AnchorDate;
if (currentRange.DisplayType == CalendarDisplayType.Month)
{
anchorDate = currentRange.Contains(today) ? today : currentRange.StartDate;
}
return Resolve(new CalendarDisplayRequest(targetDisplayType, anchorDate), settings, today);
}
public static VisibleDateRange Navigate(VisibleDateRange currentRange, int direction, CalendarSettings settings, DateOnly today)
{
if (direction == 0)
{
return currentRange;
}
var normalizedDirection = Math.Sign(direction);
var anchorDate = currentRange.DisplayType switch
{
CalendarDisplayType.Day => currentRange.AnchorDate.AddDays(normalizedDirection),
CalendarDisplayType.Week => currentRange.AnchorDate.AddDays(7 * normalizedDirection),
CalendarDisplayType.WorkWeek => currentRange.AnchorDate.AddDays(7 * normalizedDirection),
CalendarDisplayType.Month => currentRange.AnchorDate.AddMonths(normalizedDirection),
_ => currentRange.AnchorDate
};
return Resolve(new CalendarDisplayRequest(currentRange.DisplayType, anchorDate), settings, today);
}
private static DateOnly GetStartDate(CalendarDisplayType displayType, DateOnly anchorDate, CalendarSettings settings)
{
return displayType switch
{
CalendarDisplayType.Day => anchorDate,
CalendarDisplayType.Week => GetStartOfWeek(anchorDate, settings.FirstDayOfWeek),
CalendarDisplayType.WorkWeek => GetStartOfWorkWeek(anchorDate, settings),
CalendarDisplayType.Month => new DateOnly(anchorDate.Year, anchorDate.Month, 1),
_ => anchorDate
};
}
private static DateOnly GetEndDate(CalendarDisplayType displayType, DateOnly anchorDate, DateOnly startDate, CalendarSettings settings)
{
return displayType switch
{
CalendarDisplayType.Day => anchorDate,
CalendarDisplayType.Week => startDate.AddDays(6),
CalendarDisplayType.WorkWeek => startDate.AddDays(settings.WorkWeekDayCount - 1),
CalendarDisplayType.Month => new DateOnly(anchorDate.Year, anchorDate.Month, DateTime.DaysInMonth(anchorDate.Year, anchorDate.Month)),
_ => anchorDate
};
}
private static DateOnly GetStartOfWeek(DateOnly date, DayOfWeek firstDayOfWeek)
{
var offset = ((int)date.DayOfWeek - (int)firstDayOfWeek + 7) % 7;
return date.AddDays(-offset);
}
private static DateOnly GetStartOfWorkWeek(DateOnly anchorDate, CalendarSettings settings)
{
var startOfWeek = GetStartOfWeek(anchorDate, settings.FirstDayOfWeek);
var offsetToWorkWeekStart = settings.GetWeekOffset(settings.WorkWeekStart);
return startOfWeek.AddDays(offsetToWorkWeekStart);
}
}
@@ -0,0 +1,24 @@
using System;
using System.Globalization;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Calendar;
public sealed class CalendarRangeTextFormatter : ICalendarRangeTextFormatter
{
public string Format(VisibleDateRange range, IDateContextProvider dateContextProvider)
{
var culture = dateContextProvider.Culture;
var startText = FormatDate(range.StartDate, culture);
if (range.DisplayType == CalendarDisplayType.Day)
{
return startText;
}
return $"{startText} - {FormatDate(range.EndDate, culture)}";
}
private static string FormatDate(DateOnly date, CultureInfo culture)
=> date.ToString(culture.DateTimeFormat.ShortDatePattern, culture);
}
@@ -7,12 +7,33 @@ namespace Wino.Core.Domain.Models.Calendar;
public record CalendarSettings(DayOfWeek FirstDayOfWeek,
List<DayOfWeek> WorkingDays,
DayOfWeek WorkWeekStart,
DayOfWeek WorkWeekEnd,
TimeSpan WorkingHourStart,
TimeSpan WorkingHourEnd,
double HourHeight,
DayHeaderDisplayType DayHeaderDisplayType,
CultureInfo CultureInfo)
{
public int WorkWeekDayCount
{
get
{
var startOffset = GetWeekOffset(WorkWeekStart);
var endOffset = GetWeekOffset(WorkWeekEnd);
if (endOffset < startOffset)
{
endOffset += 7;
}
return (endOffset - startOffset) + 1;
}
}
public int GetWeekOffset(DayOfWeek dayOfWeek)
=> ((int)dayOfWeek - (int)FirstDayOfWeek + 7) % 7;
public TimeSpan? GetTimeSpan(string selectedTime)
{
// Regardless of the format, we need to parse the time to a TimeSpan.
@@ -0,0 +1,6 @@
namespace Wino.Core.Domain.Models.Calendar;
public interface ICalendarRangeTextFormatter
{
string Format(VisibleDateRange range, IDateContextProvider dateContextProvider);
}
@@ -0,0 +1,11 @@
using System;
using System.Globalization;
namespace Wino.Core.Domain.Models.Calendar;
public interface IDateContextProvider
{
CultureInfo Culture { get; }
TimeZoneInfo TimeZone { get; }
DateOnly GetToday();
}
@@ -0,0 +1,17 @@
using System;
using System.Globalization;
namespace Wino.Core.Domain.Models.Calendar;
public sealed class SystemDateContextProvider : IDateContextProvider
{
public CultureInfo Culture => CultureInfo.CurrentCulture;
public TimeZoneInfo TimeZone => TimeZoneInfo.Local;
public DateOnly GetToday()
{
var localNow = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, TimeZone);
return DateOnly.FromDateTime(localNow.DateTime);
}
}
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Itenso.TimePeriod;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Calendar;
public sealed record VisibleDateRange(
CalendarDisplayType DisplayType,
DateOnly AnchorDate,
DateOnly StartDate,
DateOnly EndDate,
DateOnly PrimaryDate,
int DayCount,
bool ContainsToday,
bool SpansSingleMonth,
IReadOnlyList<DateOnly> Dates)
{
public DateRange ToDateRangeExclusive()
=> new(StartDate.ToDateTime(TimeOnly.MinValue), EndDate.AddDays(1).ToDateTime(TimeOnly.MinValue));
public ITimePeriod ToTimePeriod()
=> new TimeRange(StartDate.ToDateTime(TimeOnly.MinValue), EndDate.AddDays(1).ToDateTime(TimeOnly.MinValue));
public bool Contains(DateOnly date)
=> date >= StartDate && date <= EndDate;
public bool Contains(DateTime date)
=> Contains(DateOnly.FromDateTime(date));
public static VisibleDateRange FromDateRange(CalendarDisplayType displayType, DateRange dateRange, DateOnly anchorDate, DateOnly today)
{
var startDate = DateOnly.FromDateTime(dateRange.StartDate);
var endDate = DateOnly.FromDateTime(dateRange.EndDate.AddDays(-1));
var dayCount = endDate.DayNumber - startDate.DayNumber + 1;
var dates = Enumerable.Range(0, dayCount)
.Select(offset => startDate.AddDays(offset))
.ToArray();
return new VisibleDateRange(
displayType,
anchorDate,
startDate,
endDate,
anchorDate,
dayCount,
today >= startDate && today <= endDate,
startDate.Year == endDate.Year && startDate.Month == endDate.Month,
dates);
}
}
@@ -40,7 +40,7 @@ public static class SettingsNavigationInfoProvider
Translator.WinoAccount_SettingsSection_Title,
Translator.WinoAccount_SettingsSection_Description,
"\uE77B",
searchKeywords: Translator.SettingsSearch_WinoAccount_Keywords),
searchKeywords: string.Empty),
new(null, Translator.SettingsOptions_GeneralSection, string.Empty, "\uE713", isSeparator: true),
new(WinoPage.AppPreferencesPage,
Translator.SettingsAppPreferences_Title,