Введение: почему ваш код с датами работает "через раз"?
Представьте: вы пишете приложение-календарь или напоминалку. Нужно просто взять текущую дату и добавить к ней один день. Казалось бы, что может пойти не так? Открываете Stack Overflow, находите вопрос с тысячами просмотров "How do I add 1 day to an NSDate?" — и понимаете, что даже такая простая операция может превратиться в кошмар.
В этой статье мы разберем топ ошибок при добавлении дня к NSDate в Swift, научимся их избегать и напишем правильный, надежный код. Вы узнаете, почему старый подход с NSDate и timeInterval — это путь к багам, и как современные API Apple спасают ситуацию.
Погнали разбираться с датами раз и навсегда!
Ошибка №1: Использовать timeInterval для добавления дня
Код-катастрофа
Самый частый "грабли", на которые наступают новички:
let today = NSDate()
let oneDay: TimeInterval = 24 * 60 * 60 // 86,400 секунд
let tomorrow = today.addingTimeInterval(oneDay)
На первый взгляд — логично. В сутках 24 часа, в часе 60 минут, в минуте 60 секунд. Умножаем, прибавляем — профит!
Но нет. Это работает только в 99% случаев, а в 1% — ломает пользователям всё приложение. Почему?
Подводные камни
- Переход на летнее/зимнее время. В день перевода часов сутки могут длиться 23 или 25 часов. Ваши 86,400 секунд превратятся в 83,400 или 90,000.
- Високосные секунды. Хотя это редкий случай, но официально они существуют.
- Часовые пояса. Если пользователь путешествует, его локальное время может сбиться.
- Точность до секунды. Вы не просто прибавляете "следующий день", а прибавляете ровно 24 часа — это разные вещи.
Хотите пример? 28 марта 2021 года в Украине часы перевели на час вперед в 3:00 ночи. Если вы добавили timeInterval к 2:30 ночи, то получите не 3:30, а... 4:30? Или 2:30? Зависит от того, как система обработает переход. Короче, хаос.
Ошибка №2: Смешивать NSDate и Date в одном проекте
В Swift есть два типа дат: старый NSDate (из Objective-C) и современный Date (Swift-native). Многие разработчики, особенно те, кто читает старые туториалы или Stack Overflow ответы 2014 года, продолжают использовать NSDate.
Проблема в том, что NSDate — это класс (reference type), а Date — структура (value type). Смешивание их в коде ведет к:
- Неочевидным багам с мутабельностью
- Лишним преобразованиям (
as Date,as NSDate) - Путанице в API
Правило: в новом Swift-коде используйте только Date. NSDate оставьте для совместимости с Objective-C библиотеками.
Ошибка №3: Игнорировать Calendar и TimeZone
Когда вы добавляете "1 день" к дате, вы делаете это в контексте календаря. Без указания календаря система использует Calendar.current — это календарь пользователя (обычно григорианский).
Но что, если пользователь из Саудовской Аравии использует исламский календарь? Или японский? Без явного указания календаря вы рискуете получить неожиданные результаты.
Пример неправильного кода
var dateComponents = DateComponents()
dateComponents.day = 1
let tomorrow = Calendar.current.date(byAdding: dateComponents, to: Date())
Этот код почти правильный, но он неявно использует часовой пояс устройства. Если вы работаете с серверными датами в UTC, а пользователь находится в Москве (UTC+3), то "завтра" может наступить на 3 часа раньше.
Правильное решение: Calendar + DateComponents
Золотой стандарт добавления дня
Вот как правильно добавить 1 день к дате в Swift:
import Foundation
let calendar = Calendar.current
let today = Date()
let tomorrow = calendar.date(byAdding: .day, value: 1, to: today)
Этот код:
- Использует календарь пользователя (учитывает локальные особенности)
- Правильно обрабатывает переход на летнее время
- Работает с любым календарем (григорианским, исламским и т.д.)
Универсальный вариант с явным указанием TimeZone
Если вы работаете с серверными датами (например, в формате UTC), всегда указывайте часовой пояс:
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(secondsFromGMT: 0)! // UTC
let today = Date()
let tomorrow = calendar.date(byAdding: .day, value: 1, to: today)
Теперь вы точно знаете, что "завтра" наступит через 1 день по UTC, независимо от того, где находится пользователь.
Ошибка №4: Неправильное форматирование после добавления
Вы добавили день, получили новый Date, но при выводе пользователю видите что-то странное. Проблема в DateFormatter.
Типичный баг
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date())!
let dateString = "\(tomorrow)" // "2025-03-15 12:00:00 +0000" — нечитаемо
Пользователь не хочет видеть UTC-строку. Используйте DateFormatter:
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
formatter.locale = Locale.current // Учитывает язык пользователя
let readableDate = formatter.string(from: tomorrow)
print(readableDate) // "15 мар. 2025 г., 12:00" для русского языка
Ошибка №5: Забывать про опциональность
Метод date(byAdding:value:to:) возвращает опциональное значение (Date?). Почему? Потому что не для всех календарей операция "добавить день" имеет смысл. Например, в календаре майя дата может не существовать.
Не используйте force unwrap (!) без проверки!
// ПЛОХО:
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date())!
// ХОРОШО:
guard let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date())