Przejdź do treści
School IT / Przestrzeń do nauki

DatePicker i Calendar

W lekcji 22 datę urodzenia wpisywało się w pole tekstowe i sprawdzało przez DateTime.TryParse. Wygodniej i bezpieczniej jest dać użytkownikowi kalendarz: nie pomyli formatu, nie wybierze 31 lutego, a program może z góry zablokować daty z przeszłości albo zajęte terminy. W tej lekcji poznasz pole daty z rozwijanym kalendarzem i kalendarz wyświetlany na stałe oraz policzysz noce, dni robocze i ceny.

XAML C# DatePicker Calendar DateTime? BlackoutDates 90 min
CEL LEKCJI

Czego się dziś nauczysz

  • Dodasz pole wyboru daty i odczytasz wybraną datę typu DateTime?.
  • Ograniczysz wybór do zakresu dat i zablokujesz wybrane dni.
  • Wyświetlisz kalendarz na stałe i odczytasz zaznaczony zakres dni.
  • Policzysz liczbę dni między datami i przejdziesz pętlą po kolejnych dniach.
  • Rozpoznasz weekend i sformatujesz datę z nazwą dnia tygodnia.
TEORIA

DatePicker

MainWindow.xaml
<StackPanel Margin="15" Width="220" HorizontalAlignment="Left">
    <Label Content="_Data sprawdzianu:" Target="{Binding ElementName=Data}"/>
    <DatePicker x:Name="Data" SelectedDateFormat="Long"
                SelectedDateChanged="Data_SelectedDateChanged"/>
    <TextBlock x:Name="Info" Margin="0,8,0,0"/>
</StackPanel>
Sprawdzian–□✕
Data sprawdzianu:
czwartek, 15 października 2026📅
◀październik 2026▶
pnwtśrczptsond 1234 567891011 12131415161718 1920…

rozwinięty kalendarz pola daty — obwódka to dzisiejszy dzień, wypełnienie — wybrany

Użytkownik może wybrać datę z kalendarza albo wpisać ją w pole. Wybraną datę zawiera SelectedDate typu DateTime? — tak jak IsChecked w lekcji 14, wartość może być pusta (null), gdy nic nie wybrano:

MainWindow.xaml.cs
private void Data_SelectedDateChanged(object? sender, SelectionChangedEventArgs e)
{
    if (!Data.SelectedDate.HasValue)         // nic nie wybrano
    {
        Info.Text = "";
        return;
    }

    DateTime d = Data.SelectedDate.Value;    // zwykły DateTime
    int dni = (d - DateTime.Today).Days;
    Info.Text = $"{d:dddd} — za {dni} dni";
}

Data.SelectedDate = DateTime.Today.AddDays(7);   // ustawienie z kodu
Data.SelectedDate = null;                        // wyczyszczenie

SelectedDateFormat="Long" wyświetla datę z nazwą dnia i miesiąca, Short (domyślny) — jako „15.10.2026”. Format zależy od ustawień regionalnych Windows.

TEORIA

Zakres i zablokowane dni

MainWindow.xaml.cs
// tylko od dziś do końca roku szkolnego
Data.DisplayDateStart = DateTime.Today;
Data.DisplayDateEnd = new DateTime(2027, 6, 25);

// zablokowane dni (przekreślone, nie da się ich wybrać)
Data.BlackoutDates.Add(new CalendarDateRange(new DateTime(2026, 12, 23), new DateTime(2026, 12, 31)));
Data.BlackoutDates.Add(new CalendarDateRange(new DateTime(2026, 11, 11)));   // jeden dzień

// czy dzień jest zablokowany?
bool zajety = Data.BlackoutDates.Contains(new DateTime(2026, 12, 24));

Dni spoza zakresu DisplayDateStart–DisplayDateEnd nie są pokazywane, a dni z BlackoutDates są przekreślone. Data wpisana ręcznie w zablokowany dzień zostanie odrzucona. Uwaga: dnia, który jest aktualnie wybrany, nie wolno dodać do BlackoutDates — program zakończy się wyjątkiem. Najpierw wyczyść wybór.

TEORIA

Calendar

Calendar to ten sam kalendarz, ale widoczny na stałe. Może zaznaczać więcej niż jeden dzień:

SelectionModeZaznaczanie
SingleDate (domyślny)jeden dzień; odczyt z SelectedDate
SingleRangejeden ciągły zakres — przeciągnięcie myszą lub Shift + klik
MultipleRangewiele zakresów — z Ctrl
Nonetylko podgląd, bez zaznaczania
MainWindow.xaml.cs
// w XAML: <Calendar x:Name="Kalendarz" SelectionMode="SingleRange"
//                   SelectedDatesChanged="Kalendarz_SelectedDatesChanged"/>
int ile = Kalendarz.SelectedDates.Count;           // liczba zaznaczonych dni
DateTime pierwszy = Kalendarz.SelectedDates[0];    // kolejność = kolejność zaznaczania

Kalendarz.SelectedDates.Clear();
Kalendarz.SelectedDates.AddRange(od, doDnia);      // zaznaczenie zakresu z kodu

Przy zakresie zaznaczonym „od tyłu” (od późniejszej daty do wcześniejszej) pierwszy element SelectedDates jest późniejszy — dlatego w przykładzie daty są porównywane i w razie potrzeby zamieniane.

TEORIA

Liczenie na datach

MainWindow.xaml.cs
DateTime dzis = DateTime.Today;                 // dziś, godzina 00:00
DateTime termin = dzis.AddDays(14);             // za dwa tygodnie
int dni = (termin - dzis).Days;                 // 14

bool weekend = termin.DayOfWeek == DayOfWeek.Saturday || termin.DayOfWeek == DayOfWeek.Sunday;

// pętla po kolejnych dniach (od włącznie, do — bez)
int robocze = 0;
for (DateTime d = dzis; d < termin; d = d.AddDays(1))
{
    if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
    {
        robocze++;
    }
}

Info.Text = $"{termin:dddd, d MMMM yyyy}";       // „czwartek, 22 października 2026”

Do porównań dat używaj DateTime.Today, a nie DateTime.Now — Now zawiera godzinę, więc „dziś o 14:30” jest późniejsze niż data z kalendarza „dziś” (godzina 00:00).

PRZYKŁAD

Rezerwacja noclegu w schronisku

Gość wybiera daty przyjazdu i wyjazdu w polach daty albo zaznacza pobyt w kalendarzu — oba sposoby są ze sobą zsynchronizowane. Zajęte noce są przekreślone. Program liczy noce, rozróżnia noce weekendowe (droższe) i blokuje rezerwację przy złych datach. Po rezerwacji wybrane noce stają się zajęte.

Schronisko „Pod Śnieżką” — rezerwacja–✕
Przyjazd:
15.10.2026📅
Wyjazd:
19.10.2026📅
Pt i sob: 160 zł za noc, pozostałe noce: 120 zł. Najwyżej 14 nocy.
czw 15.10 → pon 19.10
Noce: 4 (w tym weekendowe: 2)
Do zapłaty: 560,00 zł
Rezerwuj
Albo zaznacz pobyt w kalendarzu:
◀październik 2026▶
pnwtśrczptsond 1234 567891011 12131415161718 19202122232425 262728293031
Daty przekreślone są już zajęte.

pobyt czw–pon: 2 noce zwykłe (czw, nd) i 2 weekendowe (pt, sob)

MainWindow.xaml — pełny plik
<Window x:Class="Lekcja32.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Schronisko „Pod Śnieżką” — rezerwacja" SizeToContent="WidthAndHeight"
        ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen">

    <Grid Margin="15">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="260"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Column="0" Margin="0,0,15,0">
            <Label Content="_Przyjazd:" Padding="0,0,0,2" Target="{Binding ElementName=Przyjazd}"/>
            <DatePicker x:Name="Przyjazd" SelectedDateChanged="Data_Zmiana"/>

            <Label Content="_Wyjazd:" Padding="0,8,0,2" Target="{Binding ElementName=Wyjazd}"/>
            <DatePicker x:Name="Wyjazd" SelectedDateChanged="Data_Zmiana"/>

            <TextBlock Text="Pt i sob: 160 zł za noc, pozostałe noce: 120 zł. Najwyżej 14 nocy."
                       TextWrapping="Wrap" Foreground="Gray" FontSize="11" Margin="0,8,0,0"/>

            <Border x:Name="Wynik" Margin="0,12,0,0" Padding="10" CornerRadius="6"
                    Background="#FFF3F4F6">
                <TextBlock x:Name="WynikTekst" TextWrapping="Wrap" Text="Wybierz daty pobytu."/>
            </Border>

            <Button x:Name="Rezerwuj" Content="Rezerwuj" Height="30" Margin="0,12,0,0"
                    IsEnabled="False" Click="Rezerwuj_Click"/>
        </StackPanel>

        <!-- kalendarz: można w nim zaznaczyć zakres przeciągnięciem myszy -->
        <StackPanel Grid.Column="1">
            <TextBlock Text="Albo zaznacz pobyt w kalendarzu:" Margin="0,0,0,4"/>
            <Calendar x:Name="Kalendarz" SelectionMode="SingleRange"
                      SelectedDatesChanged="Kalendarz_SelectedDatesChanged"/>
            <TextBlock Text="Daty przekreślone są już zajęte." Foreground="Gray" FontSize="11"/>
        </StackPanel>
    </Grid>
</Window>
MainWindow.xaml.cs — pełny plik
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Lekcja32;

public partial class MainWindow : Window
{
    private const decimal CenaZwykla = 120m;
    private const decimal CenaWeekend = 160m;
    private const int MaksNocy = 14;

    private bool synchronizacja = false;      // true, gdy kod sam zmienia daty

    public MainWindow()
    {
        InitializeComponent();

        DateTime dzis = DateTime.Today;

        // wybór tylko od dziś do pół roku naprzód
        Przyjazd.DisplayDateStart = dzis;
        Przyjazd.DisplayDateEnd = dzis.AddMonths(6);
        Wyjazd.DisplayDateStart = dzis.AddDays(1);
        Wyjazd.DisplayDateEnd = dzis.AddMonths(6);
        Kalendarz.DisplayDateStart = dzis;
        Kalendarz.DisplayDateEnd = dzis.AddMonths(6);

        // noce już zarezerwowane przez innych gości
        DodajZajete(dzis.AddDays(5), dzis.AddDays(6));
        DodajZajete(dzis.AddDays(18), dzis.AddDays(21));
    }

    private void DodajZajete(DateTime od, DateTime doDnia)
    {
        // każda kontrolka dostaje własny obiekt zakresu
        Kalendarz.BlackoutDates.Add(new CalendarDateRange(od, doDnia));
        Przyjazd.BlackoutDates.Add(new CalendarDateRange(od, doDnia));
    }

    // Zmiana w którymkolwiek polu daty
    private void Data_Zmiana(object? sender, SelectionChangedEventArgs e)
    {
        if (synchronizacja)
        {
            return;
        }

        // pokaż wybrany pobyt także w kalendarzu
        synchronizacja = true;
        Kalendarz.SelectedDates.Clear();
        if (Przyjazd.SelectedDate.HasValue && Wyjazd.SelectedDate.HasValue)
        {
            DateTime od = Przyjazd.SelectedDate.Value;
            DateTime ostatniaNoc = Wyjazd.SelectedDate.Value.AddDays(-1);

            // kalendarz nie przyjmie zakresu z zajętymi dniami — zaznaczamy tylko wolny
            if (ostatniaNoc >= od && WolneNoce(od, ostatniaNoc))
            {
                Kalendarz.SelectedDates.AddRange(od, ostatniaNoc);
            }
        }
        synchronizacja = false;

        Przelicz();
    }

    // Zaznaczenie zakresu w kalendarzu ustawia oba pola daty
    private void Kalendarz_SelectedDatesChanged(object? sender, SelectionChangedEventArgs e)
    {
        if (synchronizacja || Kalendarz.SelectedDates.Count == 0)
        {
            return;
        }

        DateTime pierwsza = Kalendarz.SelectedDates[0];
        DateTime ostatnia = Kalendarz.SelectedDates[Kalendarz.SelectedDates.Count - 1];
        if (ostatnia < pierwsza)                          // zaznaczono „od tyłu”
        {
            DateTime pom = pierwsza;
            pierwsza = ostatnia;
            ostatnia = pom;
        }

        synchronizacja = true;
        Przyjazd.SelectedDate = pierwsza;
        Wyjazd.SelectedDate = ostatnia.AddDays(1);       // wyjazd rano po ostatniej nocy
        synchronizacja = false;

        Przelicz();
    }

    private void Rezerwuj_Click(object sender, RoutedEventArgs e)
    {
        if (!Przyjazd.SelectedDate.HasValue || !Wyjazd.SelectedDate.HasValue)
        {
            return;
        }
        DateTime od = Przyjazd.SelectedDate.Value;
        DateTime doDnia = Wyjazd.SelectedDate.Value;

        MessageBox.Show($"Zarezerwowano pobyt {od:dd.MM.yyyy} – {doDnia:dd.MM.yyyy}.\n{WynikTekst.Text}",
                        "Rezerwacja", MessageBoxButton.OK, MessageBoxImage.Information);

        // najpierw czyścimy wybór — zaznaczonej daty nie można oznaczyć jako zajętej
        synchronizacja = true;
        Przyjazd.SelectedDate = null;
        Wyjazd.SelectedDate = null;
        Kalendarz.SelectedDates.Clear();
        synchronizacja = false;

        DodajZajete(od, doDnia.AddDays(-1));               // te noce są teraz zajęte
        Przelicz();
    }

    // Czy żadna noc z zakresu nie jest zajęta
    private bool WolneNoce(DateTime od, DateTime ostatniaNoc)
    {
        for (DateTime noc = od; noc <= ostatniaNoc; noc = noc.AddDays(1))
        {
            if (Kalendarz.BlackoutDates.Contains(noc))
            {
                return false;
            }
        }
        return true;
    }

    private void Przelicz()
    {
        Rezerwuj.IsEnabled = false;
        Wynik.Background = Brushes.MistyRose;

        if (!Przyjazd.SelectedDate.HasValue || !Wyjazd.SelectedDate.HasValue)
        {
            WynikTekst.Text = "Wybierz daty pobytu.";
            Wynik.Background = new SolidColorBrush(Color.FromRgb(243, 244, 246));
            return;
        }

        DateTime od = Przyjazd.SelectedDate.Value;
        DateTime doDnia = Wyjazd.SelectedDate.Value;
        int noce = (doDnia - od).Days;

        if (noce < 1)
        {
            WynikTekst.Text = "Wyjazd musi być co najmniej dzień po przyjeździe.";
            return;
        }
        if (noce > MaksNocy)
        {
            WynikTekst.Text = $"Najdłuższy pobyt to {MaksNocy} nocy (wybrano {noce}).";
            return;
        }

        // każda noc: sprawdzamy, czy wolna, i doliczamy cenę
        decimal razem = 0;
        int weekendowe = 0;
        for (DateTime noc = od; noc < doDnia; noc = noc.AddDays(1))
        {
            if (Kalendarz.BlackoutDates.Contains(noc))
            {
                WynikTekst.Text = $"Noc {noc:dd.MM} jest już zajęta. Wybierz inny termin.";
                return;
            }

            if (noc.DayOfWeek == DayOfWeek.Friday || noc.DayOfWeek == DayOfWeek.Saturday)
            {
                razem += CenaWeekend;
                weekendowe++;
            }
            else
            {
                razem += CenaZwykla;
            }
        }

        WynikTekst.Text = $"{od:ddd dd.MM} → {doDnia:ddd dd.MM}\n" +
                          $"Noce: {noce} (w tym weekendowe: {weekendowe})\n" +
                          $"Do zapłaty: {razem:N2} zł";
        Wynik.Background = Brushes.Honeydew;
        Rezerwuj.IsEnabled = true;
    }
}

Co dzieje się po kolei

  • Konstruktor ogranicza wybór do najbliższych sześciu miesięcy i oznacza dwa zajęte terminy, liczone od dzisiejszej daty — przykład działa niezależnie od tego, kiedy go uruchomisz.
  • Zajęte noce są zablokowane w kalendarzu i w polu przyjazdu, ale nie w polu wyjazdu: można wyjechać rano w dniu, w którym wieczorem przyjeżdża następny gość.
  • Pobyt to noce od dnia przyjazdu do dnia przed wyjazdem. Dlatego w kalendarzu zaznaczamy zakres „przyjazd … wyjazd − 1 dzień”, a z zakresu w kalendarzu wyliczamy wyjazd jako „ostatnia noc + 1 dzień”.
  • Pola daty i kalendarz zmieniają się nawzajem. Pole synchronizacja zapobiega nieskończonemu „odbijaniu” zmian: gdy kod sam ustawia daty, zdarzenia wychodzą od razu.
  • WolneNoce sprawdza dzień po dniu, czy zakres nie zawiera zablokowanych dni — kalendarz nie przyjąłby takiego zaznaczenia.
  • Przelicz sprawdza kolejno: czy obie daty są wybrane, czy wyjazd jest po przyjeździe, czy pobyt nie jest za długi, a potem w pętli po nocach dolicza ceny i wykrywa zajęte noce.
  • Po rezerwacji najpierw czyścimy wybór, a dopiero potem dodajemy zarezerwowane noce do zablokowanych.

Sprawdź trzy rzeczy

  1. Przeciągnij myszą w kalendarzu przez zajęte dni. Zaznaczenie zatrzyma się na wolnych dniach.
  2. Wpisz w pole przyjazdu ręcznie datę z przeszłości i naciśnij Tab. Pole ją odrzuci.
  3. Zamień w Rezerwuj_Click kolejność: najpierw DodajZajete, potem czyszczenie. Rezerwacja zakończy się wyjątkiem.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementTypCo robi
DatePicker.SelectedDateDateTime?Wybrana data albo null.
HasValue, Valuebool, DateTimeCzy wartość jest; sama wartość.
SelectedDateChangedzdarzenieZmiana daty w polu; parametr SelectionChangedEventArgs.
SelectedDateFormatShort, LongSposób wyświetlania daty.
DisplayDateStart, DisplayDateEndDateTime?Zakres dostępnych dat.
BlackoutDateskolekcjaZablokowane dni: Add(new CalendarDateRange(od, do)), Contains(d).
Calendar.SelectionModewyliczenieSingleDate, SingleRange, MultipleRange, None.
SelectedDateskolekcjaZaznaczone dni; AddRange, Clear, Count.
SelectedDatesChangedzdarzenieZmiana zaznaczenia w kalendarzu.
DayOfWeekwyliczenieDzień tygodnia daty.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
DateTime d = Data.SelectedDate;CS0266 — DateTime? to nie DateTime. Sprawdź HasValue i użyj Value.
Data.SelectedDate.Value bez sprawdzeniaInvalidOperationException, gdy nic nie wybrano.
dodanie wybranego dnia do BlackoutDatesArgumentOutOfRangeException. Najpierw wyczyść wybór.
porównanie z DateTime.Now„Dziś” wybrane w kalendarzu jest wcześniejsze niż „teraz”. Używaj DateTime.Today.
(do - od).Days + 1 jako liczba nocyNoce to różnica dni bez dodawania 1; dni pobytu — z dodaniem.
synchronizacja dwóch kontrolek bez flagiZdarzenia wywołują się nawzajem — dziwne skoki albo przepełnienie stosu.
ZADANIA

Zadania

Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Daty wybieraj kontrolkami z tej lekcji, nie wpisuj w pola tekstowe. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.

ZAD 1Kalkulator wieku★☆☆

Pole daty urodzenia (wybór tylko do dzisiaj, od 1900 roku). Po wyborze program pokazuje: wiek w latach (z poprawką na urodziny w tym roku), liczbę przeżytych dni, dzień tygodnia urodzenia („urodziłeś się w środę”) i liczbę dni do najbliższych urodzin (albo „Wszystkiego najlepszego!” w dniu urodzin).

pokaż wskazówkę
  • Najbliższe urodziny: data urodzin w tym roku; jeśli już minęła — w przyszłym. Uwaga na osoby urodzone 29 lutego.
ZAD 2Ile do…?★☆☆

Lista rozwijana ważnych dat roku szkolnego (ferie zimowe, egzamin zawodowy, zakończenie roku — wpisz daty sam) i pozycja „inna data”, która odblokowuje pole daty. Program pokazuje: liczbę dni, pełnych tygodni i dni roboczych (bez sobót i niedziel) do wybranej daty oraz dzień tygodnia tej daty. Dla daty z przeszłości: „To już było 12 dni temu”.

pokaż wskazówkę
  • Dni robocze policzysz pętlą po dniach jak w teorii.
ZAD 3Planer sprawdzianów klasy★★☆

Kalendarz z wyborem jednego dnia i formularz: przedmiot (lista) + przycisk Dodaj sprawdzian. Sprawdziany zapisuj w liście obiektów klasy Sprawdzian (data, przedmiot) i pokazuj na liście posortowanej według daty. Reguły szkolne: najwyżej jeden sprawdzian dziennie i najwyżej trzy w tygodniu (pn–pt); weekendy i przerwa świąteczna są zablokowane w kalendarzu. Złamanie reguły — komunikat z wyjaśnieniem.

pokaż wskazówkę
  • Poniedziałek tygodnia daty d: d.AddDays(-(((int)d.DayOfWeek + 6) % 7)). Sprawdzian jest w tym samym tygodniu, gdy ma ten sam poniedziałek.
  • Weekendy w BlackoutDates dodaj w pętli po dniach widocznego zakresu.
ZAD 4Dni robocze i święta★★☆

Kalkulator dla sekretariatu: kalendarz z wyborem zakresu (SingleRange). Program liczy w zaznaczonym zakresie: wszystkie dni, soboty i niedziele, święta ustawowe przypadające w dni robocze (zapisz w tablicy daty świąt w bieżącym roku) i wynikowe dni robocze. Pod spodem lista świąt wpadających w zakres z nazwami. Święta są w kalendarzu przekreślone, ale wliczają się do zakresu.

pokaż wskazówkę
  • Zablokowanych dni nie da się zaznaczyć w SingleRange — więc świąt nie blokuj, tylko pokaż je w liście obok.
ZAD 5Termin zwrotu i kara★★☆

Biblioteka: data wypożyczenia (pole daty, domyślnie dziś), liczba dni wypożyczenia (suwak 7–60) i wyliczony termin zwrotu. Jeśli termin wypada w niedzielę lub święto z tablicy, przesuwa się na najbliższy dzień roboczy biblioteki (pn–sob). Drugie pole daty: faktyczny zwrot. Za każdy dzień spóźnienia 0,50 zł, najwyżej 30 zł. Wynik: „Zwrot 4 dni po terminie — kara 2,00 zł”.

pokaż wskazówkę
  • Przesunięcie terminu: pętla while, dopóki dzień jest niedzielą albo świętem.
ZAD 6Grafik dyżurów na korytarzu★★★

Lista nauczycieli i kalendarz z wyborem wielu dni (MultipleRange). Po wybraniu nauczyciela kalendarz pokazuje zaznaczone jego dni dyżurów; zmiana zaznaczenia zapisuje je. Każdego dnia roboczego dyżur może mieć najwyżej dwóch nauczycieli — przy próbie dodania trzeciego program cofa zaznaczenie tego dnia i pokazuje, kto już ma dyżur. Pod spodem tabela-podsumowanie: nauczyciel i liczba dyżurów w miesiącu, a dni robocze bez żadnego dyżuru są wypisane na czerwono.

pokaż wskazówkę
  • Klasa Nauczyciel z listą List<DateTime> Dyzury. Zmieniając nauczyciela, odtwórz zaznaczenie kalendarza z jego listy — z flagą blokującą zapis w tym czasie.
PODSUMOWANIE

Co trzeba zapamiętać

  • DatePicker — pole z rozwijanym kalendarzem; SelectedDate to DateTime?: HasValue, Value.
  • DisplayDateStart/End ograniczają zakres, BlackoutDates blokują dni.
  • Calendar — kalendarz na stałe; SelectionMode i SelectedDates dla zakresów.
  • Różnica dat to TimeSpan (.Days); pętla for z AddDays(1) przechodzi po dniach.
  • Porównuj z DateTime.Today; dwie zsynchronizowane kontrolki potrzebują flagi blokującej.

Dokumentacja: Microsoft Learn — DatePicker, Calendar.

Postęp zapisuje się w tej przeglądarce.