Узнать первый и последний понедельник месяца
Как на С# узнать первый и последний понедельник текущего месяца, а также первую и последнюю субботу текущего месяца. Использую язык Linq. Нужен пример, т.к. сам не могу догадаться в каком направлении двигаться.
Ответы (3 шт):
Давайте напишем метод, который найдёт все понедельники для интервала:
private static IEnumerable<DateTime> SpecificDaysOfWeek(DayOfWeek dayOfWeek, DateTime start, DateTime end)
{
var current = start;
while (current <= end)
{
if (current.DayOfWeek == dayOfWeek)
break;
current = current.AddDays(1);
}
while (current <= end)
{
yield return current;
current = current.AddDays(7);
}
}
Логика следующая. В цикле while находим первый понедельник, потом начинаем прибавлять к найденному значению по 7 дней.
Ну и теперь осталось применить к понедельникам, субботам или нужным дням недели:
var currentDate = DateTime.Today;
var firstDayOfMonth = new DateTime(currentDate.Year, currentDate.Month, 1);
var lastDayOfMonth = firstDayOfMonth.AddMonths(1).AddDays(-1);
var days = SpecificDaysOfWeek(DayOfWeek.Monday, firstDayOfMonth, lastDayOfMonth);
days.Dump();
days.First().Dump();
days.Last().Dump();
Вот вам и результат для ноября 2020:
Также если очень надо именно вот только linq style, то вот то альтернативный вариант этой же функции:
private static IEnumerable<DateTime> SpecificDaysOfWeek(DayOfWeek dayOfWeek, DateTime start, DateTime end)
{
return Enumerable.Range(0, end.Subtract(start).Days)
.Select(x => start.AddDays(x))
.Where(x => x.DayOfWeek == dayOfWeek);
}
Пусть у нас есть какая-то дата и день недели, который мы хотим найти:
var date = new DateTime(2020, 11, 1);
var dayOfWeek = DayOfWeek.Monday;
Вариант с циклом, здесь нам достаточно перебрать не более 7 ближайших дней, включая текущий:
var nextMonday = Enumerable.Range(0, 7)
.Select(x => date.AddDays(x))
.First(d => d.DayOfWeek == dayOfWeek);
var prevMonday = Enumerable.Range(0, 7)
.Select(x => date.AddDays(-x))
.First(d => d.DayOfWeek == dayOfWeek);
Console.WriteLine(nextMonday);
Console.WriteLine(prevMonday);
Вариант без циклов, просто смотрим какой день недели сейчас и высчитываем сколько нам надо прибавить (отнять), чтобы получить нужный день недели:
var nextMonday = date.AddDays((dayOfWeek - date.DayOfWeek + 7) % 7);
var prevMonday = date.AddDays((dayOfWeek - date.DayOfWeek - 7) % 7);
Console.WriteLine(nextMonday);
Console.WriteLine(prevMonday);
Здесь используется мелкая стандартная магия с остатками, прибавляя 7 перед вычислением остатка мы гарантируем, что получим положительное значение или ноль после (0..6). И, наоборот, отнимая 7, гарантируем, что получим отрицательное значение или ноль (-6..0)
В обоих случаях вывод:
02.11.2020 0:00:00
26.10.2020 0:00:00
И да, я здесь смотрю вперед и назад от одной и той же даты, если вам нужно смотреть назад от даты конца месяца, то ее получить легко:
var firstDay = new DateTime(2020, 11, 1);
var lastDay = firstDay.AddMonths(1).AddDays(-1);
Я не очень хорошо помню C#, но постараюсь донести код, который будет одинаково подходить (может быть адаптирован) для всех языков программирования, поддерживающих простые арифметические операции.
На языке JavaScript моё решение выглядело бы так:
const flwom=(y,m,w)=>[
w=7-(y=>~~(23*m/9)+(m<3?y--:y-2)+4+~~(y/4)-~~(y/100)+~~(y/400)-w)(y)%7,
w+7*~~((28+(62648012+16*!(y%4)>>m*2&3)-w)/7)
];
Код создаёт функцию, которая возвращает массив с датами первого и последнего заданного аргументом w дня недели для месяца m года y.
Я не фанат однобуквенных переменных, но за длинными названиями переменных теряется простота формул.
Попробую написать код на C#.
Пусть наши входящие данные будут такими:
int year = 2020;
int month = 12; // Декабрь
int specWeekDay = 2; // Вторник
Сперва определим, является ли год високосным.
bool leap = 0 == year % 4;
Далее нам нужно определить день недели на первое число месяца. /Формула была взята из википедии/.
static int day = 1;
int dayOfWeek = (day += month < 3 ? year-- : year - 2,
23 * (month/9) + day + 4 + year/4 - year/100 + year/400 ) % 7;
Обратите внимание! В этих формулах используется целочисленное деление, то есть деление с отбрасыванием (не округлением!) дробной части.
В переменной dayOfWeek в зависимости от переданных выше параметров будет число от 0 (воскресение) до 6 (Суббота). Не сложно посчитать, например, тот же первый вторник декабря (или любой другой день недели на заданные месяц и год).
int firstWeekday = 1 + specWeekDay - dayOfWeek;
if(dayOfWeek > specWeekDay) firstWeekday += 7;
Дальше для определения последнего дня в месяце, нам нужно получить количество дней в месяце.
static int[] daysInMonth = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int daysInSpecifiedMonth = daysInMonth[month];
if( month == 2 && leap ) daysInSpecifiedMonth ++;
И осталось лишь посчитать последний вторник декабря (или любой другой день недели на заданные месяц и год):
int lastWeekday = daysInSpecifiedMonth - (daysInSpecifiedMonth - firstWeekday) % 7;
Вот и всё, задача решена чистой математикой. Без использования DateTime. Без использования циклов.
PS В коде могут быть очепятки, код не проверялся, если увидели ошибку в коде - сразу правьте мой пост, не обязательно писать в комментариях. Не ругайте сильно, всё таки я больше 11 лет не притрагивался к C#.
Кстати! Есть множество способов как посчитать день недели на заданную дату. Если не нравится формула выше, можно воспользоваться более консервативным методом:
int dayOfWeek(int y, int m, int d)
{
static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
y -= m < 3;
return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
}
А вот этот метод вообще убил:
dow(m,d,y) { y-=m<3; return(y+y/4-y/100+y/400+"-bed=pen+mad."[m]+d)%7; }
В русской википедии тоже есть хорошие статьи:
