using System.Linq.Expressions; namespace PARR.API.Helpers { /// /// Хелпер для работы с датами в разных часовых поясах (UTC - пользователь). Используется для формирования СТАТИСТИКИ /// public static class TimeZoneDateHelper { /// /// Создаёт начало дня (00:00:00) в часовом поясе пользователя и конвертирует в UTC /// /// /// /// public static DateTime GetUtcSartOfDay(DateOnly userDate, TimeSpan userOffset) { var userStart = new DateTimeOffset(userDate.Year, userDate.Month, userDate.Day, 0, 0, 0, userOffset); return userStart.UtcDateTime; } /// /// Создаёт полуоткрытый интервал [start; end) в UTC для фильтрации за период (end не включается) /// /// /// /// /// public static (DateTime StartUtc, DateTime EndUtc) GetUtcDateRange(DateOnly userStartDate, DateOnly userEndDateExclusive, TimeSpan userOffset) { var start = new DateTimeOffset( userStartDate.Year, userStartDate.Month, userStartDate.Day, 0, 0, 0, userOffset).UtcDateTime; var end = new DateTimeOffset( userEndDateExclusive.Year, userEndDateExclusive.Month, userEndDateExclusive.Day, 0, 0, 0, userOffset).UtcDateTime; return (start, end); } /// /// Применяет фильтр по дате к IQueryable /// /// /// /// /// /// /// public static IQueryable FilterByDateRangeUtc(this IQueryable query, Expression> dateSelector, DateTime startUtc, DateTime endUtc) { // Строим выражение: t => t.DateCreated.UtcDateTime >= start && < end var param = dateSelector.Parameters[0]; var body = Expression.AndAlso( // Строим левую часть: t.DateCreated.UtcDateTime >= startUtc Expression.GreaterThanOrEqual( Expression.Property(dateSelector.Body, nameof(DateTimeOffset.UtcDateTime)), Expression.Constant(startUtc, typeof(DateTime)) ), // Строим правую часть: t.DateCreated.UtcDateTime < endUtc Expression.LessThan( Expression.Property(dateSelector.Body, nameof(DateTimeOffset.UtcDateTime)), Expression.Constant(endUtc, typeof(DateTime)) ) ); // Собираем финальную лямбду: t => (t.DateCreated.UtcDateTime >= start && < end) var lambda = Expression.Lambda>(body, param); // Применяем к запросу return query.Where(lambda); } /// /// Группирует записи по дате в часовом поясе пользователя (для загруженных записей) /// /// /// /// /// /// public static Dictionary GroupByUserDate(this IEnumerable source, Func dateSelector, TimeSpan userOffset) { return source.GroupBy(t => DateOnly.FromDateTime(dateSelector(t).ToOffset(userOffset).DateTime)) .ToDictionary(g => g.Key, g => g.Count()); } } }