Урок 10.2 — Decimal, DateTime, TimeSpan
Точная десятичная арифметика, работа с датами и временными интервалами.
В этом уроке мы познакомимся с тремя важными типами данных: decimal для точных денежных расчётов, DateTime для работы с датами и TimeSpan для интервалов времени. Эти типы — основа практически любого бизнес-приложения.
Тип decimal — точная десятичная арифметика
Ты уже знаешь тип double для дробных чисел. Но у него есть проблема: он хранит числа в двоичной системе. Это приводит к ошибкам округления в десятичных расчётах:
double a = 0.1;
double b = 0.2;
Console.WriteLine(a + b); // 0.30000000000000004 (не 0.3!)
Console.WriteLine(a + b == 0.3); // False — ошибка округления!
// Это проблема для финансов:
double price = 1.99;
double tax = price * 0.1;
Console.WriteLine(tax); // 0.19900000000000001 вместо 0.199
Тип decimal решает эту проблему. Он хранит числа в десятичной системе (binary-coded decimal) и даёт точные результаты:
decimal a = 0.1m; // суффикс m — значит decimal
decimal b = 0.2m;
Console.WriteLine(a + b); // 0.3 (точно!)
Console.WriteLine(a + b == 0.3m); // True
decimal price = 1.99m;
decimal tax = price * 0.1m;
Console.WriteLine(tax); // 0.199 (точно!)
// Синтаксис: суффикс m или M
decimal salary = 125000.50m;
decimal discount = 0.15m;
decimal total = salary - salary * discount;
Console.WriteLine(total); // 106250.425
Когда использовать decimal vs double:
| Сценарий | Тип | Причина |
|---|---|---|
| Деньги, цены, налоги | decimal | Точность до копейки обязательна |
| Научные расчёты | double | Нужна скорость, погрешность допустима |
| Координаты GPS | double | Большой диапазон значений |
| Бухгалтерия | decimal | Закон требует точности до 0.01 |
| 3D-графика | float/double | Скорость важнее точности |
| Проценты, курсы валют | decimal | Нужна предсказуемая точность |
⚠️ decimal медленнее double (~10-20x), но для финансов это не проблема. Главное — точность. Также у decimal меньше диапазон: ±79 × 10²⁷, против ±5 × 10⁻³²⁴ для double. Но для денег этого хватает с запасом.
DateTime — работа с датами и временем
DateTime — это структура для работы с датой и временем. Она хранит дату от 01.01.0001 до 31.12.9999 и время с точностью до 100 наносекунд (тик).
Создание DateTime
using System;
// Текущая дата и время (локальные)
DateTime now = DateTime.Now;
Console.WriteLine(now); // 23.06.2026 15:45:30
// Текущая дата и время (UTC)
DateTime utcNow = DateTime.UtcNow;
Console.WriteLine(utcNow); // 23.06.2026 12:45:30 (на 3 часа меньше, если MSK)
// Только дата (без времени)
DateTime today = DateTime.Today;
Console.WriteLine(today); // 23.06.2026 0:00:00
// Конкретная дата и время
DateTime specific = new DateTime(2026, 1, 15);
Console.WriteLine(specific); // 15.01.2026 0:00:00
// Дата + время
DateTime specificTime = new DateTime(2026, 1, 15, 14, 30, 0);
Console.WriteLine(specificTime); // 15.01.2026 14:30:00
Форматирование дат
DateTime now = DateTime.Now;
// Стандартные форматы
Console.WriteLine(now.ToString("dd.MM.yyyy")); // 23.06.2026
Console.WriteLine(now.ToString("dd.MM.yyyy HH:mm")); // 23.06.2026 15:45
Console.WriteLine(now.ToString("yyyy-MM-dd")); // 2026-06-23
Console.WriteLine(now.ToString("HH:mm:ss")); // 15:45:30
Console.WriteLine(now.ToString("dddd")); // Tuesday (день недели)
Console.WriteLine(now.ToString("MMMM yyyy")); // June 2026
// Спецификаторы формата:
// dd — день (01-31)
// MM — месяц (01-12)
// yyyy — год (2026)
// HH — часы (00-23)
// mm — минуты (00-59)
// ss — секунды (00-59)
Парсинг дат
string dateStr = "15.01.2026";
// DateTime.Parse() — выбросит исключение при ошибке
DateTime date = DateTime.Parse(dateStr);
Console.WriteLine(date); // 15.01.2026 0:00:00
// DateTime.TryParse() — безопасное преобразование (без исключений)
if (DateTime.TryParse("32.13.2026", out DateTime result))
Console.WriteLine(result);
else
Console.WriteLine("Некорректная дата!"); // сюда
// Парсинг с определённым форматом
string customDate = "2026-01-15 14:30:00";
DateTime parsed = DateTime.ParseExact(
customDate,
"yyyy-MM-dd HH:mm:ss",
null // null = текущая культура
);
Console.WriteLine(parsed);
Арифметика дат
DateTime today = DateTime.Today;
// Добавление дней, месяцев, лет
DateTime tomorrow = today.AddDays(1);
DateTime nextWeek = today.AddDays(7);
DateTime nextMonth = today.AddMonths(1);
DateTime nextYear = today.AddYears(1);
DateTime lastYear = today.AddYears(-1);
Console.WriteLine($"Завтра: {tomorrow:dd.MM.yyyy}");
Console.WriteLine($"Через неделю: {nextWeek:dd.MM.yyyy}");
Console.WriteLine($"Через месяц: {nextMonth:dd.MM.yyyy}");
// Пример: последний день месяца
DateTime firstOfNextMonth = new DateTime(today.Year, today.Month, 1).AddMonths(1);
DateTime lastOfMonth = firstOfNextMonth.AddDays(-1);
Console.WriteLine($"Последний день месяца: {lastOfMonth:dd.MM.yyyy}");
DateTime.MinValue (01.01.0001). Максимальное: DateTime.MaxValue (31.12.9999).TimeSpan — интервалы времени
TimeSpan представляет интервал времени: разница между двумя датами, продолжительность операции, таймаут. В отличие от DateTime, TimeSpan хранит не момент во времени, а промежуток.
Создание TimeSpan
// Конструктор: new TimeSpan(дни, часы, минуты, секунды)
TimeSpan oneHour = new TimeSpan(1, 0, 0); // 1 час
TimeSpan halfHour = new TimeSpan(0, 30, 0); // 30 минут
TimeSpan workDay = new TimeSpan(8, 0, 0); // 8 часов
TimeSpan precise = new TimeSpan(1, 2, 30, 15, 500); // 1д 2ч 30м 15с 500мс
// Статические фабричные методы
TimeSpan twoDays = TimeSpan.FromDays(2);
TimeSpan threeHours = TimeSpan.FromHours(3);
TimeSpan fifteenMinutes = TimeSpan.FromMinutes(15);
TimeSpan tenSeconds = TimeSpan.FromSeconds(10);
TimeSpan fiveMs = TimeSpan.FromMilliseconds(5);
// TimeSpan.Zero — нулевой интервал
TimeSpan zero = TimeSpan.Zero;
// Свойства
Console.WriteLine(precise.Days); // 1
Console.WriteLine(precise.Hours); // 2
Console.WriteLine(precise.Minutes); // 30
Console.WriteLine(precise.TotalHours); // 26.5 (общее количество часов!)
Console.WriteLine(precise.TotalMinutes); // 1590.25
Арифметика дат с TimeSpan
DateTime start = new DateTime(2026, 1, 1, 9, 0, 0); // 01.01.2026 09:00
TimeSpan duration = TimeSpan.FromHours(2.5); // 2 часа 30 минут
DateTime end = start + duration;
Console.WriteLine(end); // 01.01.2026 11:30:00
// Разница между датами — всегда TimeSpan
DateTime deadline = new DateTime(2026, 6, 30);
DateTime today = DateTime.Today;
TimeSpan remaining = deadline - today;
Console.WriteLine($"До дедлайна: {remaining.Days} дней");
Console.WriteLine($"Всего часов: {remaining.TotalHours:F1}");
// Сравнение TimeSpan
TimeSpan task1 = TimeSpan.FromMinutes(25);
TimeSpan task2 = TimeSpan.FromMinutes(40);
if (task1 < task2)
Console.WriteLine("Задача 1 короче");
Console.WriteLine(task1 + task2); // 01:05:00 (сумма интервалов)
Практический пример — сколько тебе дней от роду:
DateTime birthDate = new DateTime(2000, 6, 15);
DateTime today = DateTime.Today;
TimeSpan ageInDays = today - birthDate;
Console.WriteLine($"Тебе {ageInDays.Days} дней от роду!");
// Возраст в годах (приблизительно)
int ageYears = today.Year - birthDate.Year;
if (today < birthDate.AddYears(ageYears))
ageYears--;
Console.WriteLine($"Тебе примерно {ageYears} лет");
Практический пример — расчёт зарплаты
using System;
class Program
{
static void Main()
{
// Ставка в час (используем decimal для денег!)
decimal hourlyRate = 450.50m;
// Рабочее время: 09:00 до 17:30 с перерывом 30 минут
DateTime start = new DateTime(2026, 6, 23, 9, 0, 0);
DateTime end = new DateTime(2026, 6, 23, 17, 30, 0);
TimeSpan breakTime = TimeSpan.FromMinutes(30);
// Чистое рабочее время
TimeSpan workTime = end - start - breakTime;
Console.WriteLine($"Отработано: {workTime.Hours} ч {workTime.Minutes} мин");
// Расчёт зарплаты
decimal salary = hourlyRate * (decimal)workTime.TotalHours;
Console.WriteLine($"Зарплата за день: {salary:F2} руб.");
// Следующая зарплата через 2 недели
DateTime nextPay = DateTime.Today.AddDays(14);
Console.WriteLine($"Следующая зарплата: {nextPay:dd.MM.yyyy}");
// Вычитаем налог 13%
decimal tax = salary * 0.13m;
decimal afterTax = salary - tax;
Console.WriteLine($"После вычета налога: {afterTax:F2} руб.");
}
}
// Вывод:
// Отработано: 8 ч 0 мин
// Зарплата за день: 3604.00 руб.
// Следующая зарплата: 07.07.2026
// После вычета налога: 3135.48 руб.
(decimal)workTime.TotalHours. TotalHours возвращает double, мы преобразуем его в decimal для точного умножения.Мини-тест
Проверь свои знания.
Вопрос 1
Какой суффикс нужно добавить к числу, чтобы это был decimal?
A) d B) m C) dec
Вопрос 2
Что вернёт DateTime.Now?
A) Только дату B) Текущую дату и время в локальном часовом поясе C) UTC время
Вопрос 3
Какой тип получится при вычитании двух DateTime?
A) DateTime B) TimeSpan C) int
Вопрос 4
Какой тип выбрать для хранения цены товара?
A) double B) decimal C) float
Вопрос 5
Что вернёт new DateTime(2026, 6, 15).AddMonths(1)?
A) 15.07.2026 B) 15.07.2027 C) 15.05.2026
Задача
Задание: Календарь платежей
Напиши программу для расчёта графика ежемесячных платежей по кредиту:
- Сумма кредита: 1 000 000 руб. (decimal)
- Процентная ставка: 12% годовых (0.12)
- Срок: 12 месяцев
- Дата выдачи: 15 января 2026
- Рассчитай ежемесячный платёж по формуле аннуитета и выведи график:
- Для каждого месяца выведи: дата платежа, сумма платежа, остаток долга
Формула аннуитетного платежа:
payment = S * (r * (1 + r)^n) / ((1 + r)^n - 1)
где S = сумма, r = месячная ставка (0.12 / 12 = 0.01), n = количество месяцев.
Дополнительно: используй decimal для всех денежных расчётов. Выводи даты в формате «15.02.2026».
Math.Pow(). Она принимает double, поэтому преобразуй результат обратно в decimal.Что важно запомнить
Now (локальное), UtcNow, Today. Методы: AddDays, AddMonths, AddYears.date1 - date2.ToString("dd.MM.yyyy"). Парсинг: TryParse() — безопасно.Тест: 10.2: Decimal, DateTime, TimeSpan
3 вопроса