Проблема
С учетом двух дат, dt0
и dt1
(может быть не в порядке), каков алгоритм, который может определить, существует ли выходной хотя бы за 24 часа (SAT, SUN) между двумя датами?
Предположим, что есть функция dayofweek()
, которая возвращает 0 для SUN, 1 для MON и т. Д ...
Примечание:проблему легко визуализировать геометрически в терминах отрезков, но на данный момент мне не хватает вычислений.
Решение
Решение, приведенное ниже, будет работать для UTC, но для DST не получится.
weekdayno()
реализация не включена: SUN == 0, MON == 1 и т. Д. isWeekday()
также не отображается, но ее тривиально реализовать после того, как выреализация dayofweek()
- бинарная
operator-()
также не показана, но мы просто конвертируем оба экземпляра в UNIX-время (количество секунд с начала эпохи) и принимаем разницу, чтобы получить количество секунд между двумя DateTime
s hh()
mm()
ss()
- это просто константные средства для возврата hнаши, минуты и секунды, соответственно
Джеймс МакНеллис прямо на отметке о летнем времени.
Как заставить этот код работать для общегоСлучай DST нетривиален: нужно добавить tz, и где бы вы ни делали, арифметика любых дат требует тщательного рассмотрения.Потребуются дополнительные модульные тесты.
Извлеченные уроки
- Запрос переполнения стека для различных способов решения проблемы.
- Вы никогда не можете иметь слишком многомодульные тесты: нужно, чтобы они избавились от странных крайних случаев
- Используйте визуализацию, если возможно, чтобы посмотреть на проблему
- То, что кажется тривиальной проблемой, на самом деле может быть немного сложнее, когда вы смотритев деталях (например, DST).
- Сохраняйте решение как можно более простым , потому что ваш код, скорее всего, изменится: для исправления ошибок / новых тестов или для добавленияновые функции (например, заставить его работать на летнее время).Сохраняйте его читабельным и легким для понимания, насколько это возможно: предпочитайте алгоритмы переключателям / кейсам.
- Будьте смелее и попробуйте: продолжайте вникать в решение до тех пор, пока что-то не сработает.Используйте юнит-тесты, чтобы вы могли непрерывно проводить рефакторинг.Для написания простого кода требуется много работы, но, в конце концов, оно того стоит.
Заключение
Текущее решение достаточно для моих целей (я буду использовать UTC дляизбежать проблем с летним периодом).Я выберу ответ holygeek за его предложение нарисовать немного ASCII-произведений.В этом случае это помогло мне придумать алгоритм, который прост для понимания и действительно настолько прост, насколько я могу это сделать.Спасибо всем за участие в анализе этой проблемы.
static const size_t ONEDAYINSECS = (24 * 60 * 60);
DateTime
DateTime::nextSatMorning() const
{
// 0 is SUN, 6 is SAT
return *this + (6 - weekdayno()) * ONEDAYINSECS -
(((hh() * 60) + mm())*60 + ss());
}
DateTime
DateTime::previousSunNight() const
{
return *this - ((weekdayno() - 1 + 7)%7) * ONEDAYINSECS -
(((hh() * 60) + mm())*60 + ss());
}
bool
DateTime::straddles_24HofWeekend_OrMore( const DateTime& newDt ) const
{
const DateTime& t0 = min( *this, newDt );
const DateTime& t1 = max( *this, newDt );
// ------------------------------------
//
// <--+--F--+--S--+--S--+--M--+-->
// t0 ^ ^ t1
// +---->+ +<----|
// | |
// +<--nSecs-->+
// edge0 edge1
//
// ------------------------------------
DateTime edge0 = t0.isWeekday() ? t0.nextSatMorning() : t0;
DateTime edge1 = t1.isWeekday() ? t1.previousSunNight() : t1;
return (edge1 - edge0) > ONEDAYINSECS;
}
Джон Лейдегрен попросил мои модульные тесты, так что здесь есть (с помощью googletest) Обратите внимание, что они проходят для неСлучаи DST выше (работает для UTC) - я ожидаю, что текущая реализация потерпит неудачу для случаев DST (еще не добавил их в тестовые примеры ниже).
TEST( Test_DateTime, tryNextSatMorning )
{
DateTime mon{ 20010108, 81315 };
DateTime exp_sat{ 20010113, 0ul };
EXPECT_EQ( exp_sat, mon.nextSatMorning() );
}
TEST( Test_DateTime, tryPrevSunNight )
{
DateTime tue{ 20010109, 81315 };
DateTime exp_sun1{ 20010108, 0ul };
EXPECT_EQ( exp_sun1, tue.previousSunNight() );
DateTime sun{ 20010107, 81315 };
DateTime exp_sun2{ 20010101, 0ul };
EXPECT_EQ( exp_sun2, sun.previousSunNight() );
}
TEST( Test_DateTime, straddlesWeekend )
{
DateTime fri{ 20010105, 163125 };
DateTime sat{ 20010106, 101515 };
DateTime sun{ 20010107, 201521 };
DateTime mon{ 20010108, 81315 };
DateTime tue{ 20010109, 81315 };
EXPECT_FALSE( fri.straddles_24HofWeekend_OrMore( sat ));
EXPECT_TRUE( fri.straddles_24HofWeekend_OrMore( sun ));
EXPECT_TRUE( fri.straddles_24HofWeekend_OrMore( mon ));
EXPECT_TRUE( sat.straddles_24HofWeekend_OrMore( sun ));
EXPECT_TRUE( sat.straddles_24HofWeekend_OrMore( mon ));
EXPECT_FALSE( sun.straddles_24HofWeekend_OrMore( mon ));
EXPECT_TRUE( fri.straddles_24HofWeekend_OrMore( tue ));
EXPECT_FALSE( sun.straddles_24HofWeekend_OrMore( tue ));
}