Как определить, что две даты выходят на выходные? - PullRequest
3 голосов
/ 21 декабря 2011

Проблема

С учетом двух дат, 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 ));
}

Ответы [ 4 ]

3 голосов
/ 21 декабря 2011

Эта диаграмма может помочь:

  SAT       SUN
|---------|---------|
a---------b
 a---------b
 ...
          a---------b
1 голос
/ 21 декабря 2011

Подход к нему структурированным образом: каковы некоторые из простых / крайних случаев?Каков средний случай?

Простые случаи, которые я могу придумать, вне головы (обратите внимание, поскольку вы уже заставили t0 быть нижним значением, я предполагаю, что ниже):

  1. Если t1 - t0 меньше 1 дня, верните false
  2. Если t1 - t0> = 6 дней, верните true (ВСЕГДА 24 часа в выходные дни в любом заданном6-дневный блок, даже если вы начинаете в воскресенье).

Затем мы берем dayofweek () для t0 и t1 и делаем некоторые проверки (это средний случай).Сейчас мы можем быть очень дешевыми, потому что мы знаем t0 только на 5 дней раньше, чем t1.

Редактировать: Отменены мои условия, потому что тамбыли противные маленькие крайние случаи, которые я не рассматривал.В любом случае, решение, которое я рекомендовал, все еще жизнеспособно, я просто не буду делать это здесь.

1 голос
/ 21 декабря 2011

Самый глупый способ решения этой проблемы: скопировать меньшее значение даты и времени, непрерывно добавлять к нему, пока оно не станет больше, чем другое значение даты и времени (большее), или dayofweek () не будет равно 0 или 7 любым Больше. Затем убедитесь, что общее время, которое вы добавили, составляет менее 24 часов.

Немного менее глупым способом было бы проверить выходные дни, добавить 24 часа времени, а затем проверить один раз, чтобы убедиться, что это выходные, и все же меньше, чем второе время.

Летнее время не должно вступать в игру, если ваша функция заключается в том, чтобы определить, какой сегодня день.

0 голосов
/ 21 декабря 2011

Этот сайт рассчитывает рабочие дни в C #, это помогает?http://www.infopathdev.com/forums/t/7156.aspx

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...