Как я могу рассчитать количество секунд до следующей субботы в C ++? - PullRequest
2 голосов
/ 11 апреля 2019

Я пытаюсь сделать калькулятор, который будет определять количество секунд до начала определенного дня.

В настоящее время я могу определить текущий день и время, но я не уверен, как определить желаемое количество секунд до начала будущего дня (скажем, в субботу, например).

Я думаю, я мог бы подсчитать, сколько дней, кроме субботы, мы находим, а затем преобразовать это в секунды, но проблема в том, что может быть добавлено дополнительное время (я хочу знать время в полночь, начало дня ).

Кто-нибудь знает, как это возможно?

#pragma once
#include <string>
#include <ctime>

class DateTime
{
public:
   DateTime();

   DateTime(
      const time_t& specific_datetime);

   const std::string GetWeekDay() const;

   const std::string GetMonth() const;

   int GetDate() const;

   const int GetSecondsUntilNextWeekDay(
      const std::string& day_name) const;

private:

   const int GetWeekDayNumberFromName(
      const std::string& name) const;

   tm m_time;
   const time_t m_datetime;

   DateTime(const DateTime &rhs);
   DateTime &operator=(const DateTime &rhs);
};
#include "DateTime.h"

DateTime::DateTime() :
   m_datetime(std::time(NULL))
{
   localtime_s(&m_time, &m_datetime);
}

DateTime::DateTime(
   const time_t& specific_datetime) :
   m_datetime(specific_datetime)
{
   localtime_s(&m_time, &m_datetime);
}

const std::string DateTime::GetWeekDay() const
{
   switch (m_time.tm_wday)
   {
   case 0:
      return "Sunday";
   case 1:
      return "Monday";
   case 2:
      return "Tuesday";
   case 3:
      return "Wednesday";
   case 4:
      return "Thursday";
   case 5:
      return "Friday";
   case 6:
      return "Saturday";
   default:
      return "Sunday";
   }
}

const std::string DateTime::GetMonth() const
{
   switch (m_time.tm_mon)
   {
   case 0:
      return "January";
   case 1:
      return "February";
   case 2:
      return "March";
   case 3:
      return "April";
   case 4:
      return "May";
   case 5:
      return "June";
   case 6:
      return "July";
   case 7:
      return "August";
   case 8:
      return "September";
   case 9:
      return "October";
   case 10:
      return "November";
   case 11:
      return "December";
   default:
      return "January";
   }
}

int DateTime::GetDate() const
{
   return m_time.tm_mday;
}

int DateTime::GetYear() const
{
   return 1900 + m_time.tm_year;
}

const int DateTime::GetSecondsUntilNextWeekDay(
   const std::string& day_name) const
{
   // Calculate how many seconds left for today...
   const int todays_hours_left = 23 - m_time.tm_hour;
   const int todays_minutes_left = (todays_hours_left * 60) + (59 - m_time.tm_min);
   const int todays_seconds_left = (todays_minutes_left * 60) + (59 - m_time.tm_sec);
   int overlap_seconds = 0;

   // Calculate how many days until the desired date.
   int current_day_number = m_time.tm_wday;
   const int desired_day_number = GetWeekDayNumberFromName(day_name);

   if (desired_day_number >= 0)
   {
      if (desired_day_number <= current_day_number)
      {
         // Find out how many days left in the week, add them to how many days until the today.
         const int max_day = 6;
         const int days_remaining = max_day - current_day_number;
         overlap_seconds = (days_remaining * 24 * 60 * 60);
         current_day_number = 0;
      }

      const int days_left = desired_day_number - current_day_number;
      const int hours_left = days_left * 24;
      const int minutes_left = hours_left * 60;
      const int seconds_left = (minutes_left * 60) - (86400 - todays_seconds_left) + overlap_seconds;

      return seconds_left;
   }

   return -1;
}

const int DateTime::GetWeekDayNumberFromName(
   const std::string& name) const
{
   if (name == "Sunday")
   {
      return 0;
   }
   else if (name == "Monday")
   {
      return 1;
   }
   else if (name == "Tuesday")
   {
      return 2;
   }
   else if (name == "Wednesday")
   {
      return 3;
   }
   else if (name == "Thursday")
   {
      return 4;
   }
   else if (name == "Friday")
   {
      return 5;
   }
   else if (name == "Saturday")
   {
      return 6;
   }

   return -1;
}
#include <iostream>

#include "DateTime.h"

int main()
{
   DateTime date_time;

   const int seconds = date_time.GetSecondsUntilNextWeekDay("Monday");
   std::cout << "Seconds Till Date: " << seconds;
}

Я ожидал бы, что если бы я хотел знать, как долго до начала четверга от текущего времени (21:00 в среду), он вернется через 3 часа или 10800 секунд (либо работает, либо).

Ответы [ 3 ]

1 голос
/ 11 апреля 2019

У этого вопроса есть пара тонких вопросов:

  • Находится ли «начало дня» в определенном часовом поясе? В UTC?

При чтении кода в вопросе выясняется, что «начало дня» определяется настройкой локального часового пояса компьютера.

  • Поскольку мы учитываем часовые пояса, я полагаю, что нам также необходимо учитывать летнее время. Например, если в 2:00 в определенный день происходит переход в течение 1 часа в течение 1 часа, то в этот день прошедшее время с 1:00 до 3:00 составляет 1 час, тогда как в «нормальные» дни это 2 часа. , Если только мы не собираемся считать «местные секунды», а не «физические секунды». Вопрос не совсем ясен в этом вопросе ...

Я протестировал код, приведенный в вопросе, используя «America / New_York» в качестве местного часового пояса с 3 входными парами:

  1. Суббота 2019-03-02 22:02:05 EST до понедельника 2019-03-04 00:00:00 EST
  2. Суббота 2019-03-09 22:02:05 EST до понедельника 2019-03-11 00:00:00 ПО ВОСТОЧНОМУ ВРЕМЕНИ
  3. Суббота 2019-03-16 22:02:05 ПО ВОСТОЧНОМУ ВРЕМЕНИ до понедельника 2019-03-18 00:00:00 ПО ВОСТОЧНОМУ ВТОРОМУ

Это вычисление времени между поздним субботним вечером и началом понедельника в течение 3 недель подряд, когда средняя неделя колеблется в изменении смещения UTC из-за перехода на летнее время.

Код в вопросе дает 7074 для всех входных пар. В формате чч: мм: сс, то есть 01:57:54. Это примерно 1 день от правильного ответа, который для 1-й и 3-й пар ввода составляет 93475 с, или 1 день и 7075 с, или 1 день и 01: 57: 55.

Правильный ответ для второго случая, потому что воскресенье длится всего 23 часа, на 1 час меньше, или 89875 с.

Я не отследил все проблемы в коде в вопросе, но, кажется, есть по крайней мере эти 3:

  1. Непостоянная ошибка в подсчете количества дней.
  2. Непостоянная ошибка в подсчете количества секунд.
  3. Пренебрегать изменениями смещения UTC, которые могут произойти в местном часовом поясе.

Бесплатная библиотека с открытым исходным кодом Говарда Хиннанта для часовых поясов 1 может быть использована для исправления следующих проблем:

#include "date/tz.h"
#include <chrono>
#include <iostream>

std::chrono::seconds
GetSecondsUntilNextWeekDay(date::weekday wd,
                           date::sys_seconds tp = 
                      date::floor<std::chrono::seconds>(std::chrono::system_clock::now()))
{
    using namespace std::chrono;
    using namespace date;
    auto zone = current_zone();
    zoned_seconds now = {zone, tp};
    auto today_local = floor<days>(now.get_local_time());
    weekday today_wd{today_local};
    auto delta_days = wd - today_wd;
    if (delta_days == days{0} && now.get_local_time() != today_local)
        delta_days += weeks{1};
    zoned_seconds target_date = {zone, today_local + delta_days};
    return target_date.get_sys_time() - now.get_sys_time();
}

int
main()
{
    using namespace date;
    using namespace std::chrono;
    zoned_seconds zt{current_zone(), local_days{March/9/2019} + 22h + 2min + 5s};
    std::cout << GetSecondsUntilNextWeekDay(Monday, zt.get_sys_time()) << '\n';
}

Этот код работает путем создания zoned_seconds как для времени ввода, так и для целевого времени (начало целевого дня недели, определенного местным часовым поясом). zoned_seconds - это специализация zoned_time<Duration> с точностью до секунды.

A zoned_time - это пара точек местного времени и time_zone. Это можно эквивалентно представить как соединение момента времени UTC и time_zone. В любом случае можно использовать либо local_time, либо sys_time (sys_time - это UTC), и можно извлечь как local_time, так и sys_time, которые связаны через time_zone.

В этом примере time_zone находится с current_zone(), который определяет текущую настройку местного часового пояса компьютера.

now представляет собой пару входных данных sys_time (sys_seconds - псевдоним для секундной точности sys_time) и текущего местного часового пояса компьютера.

today_local - это дневная точность local_time, полученная путем получения now s local_time и получения дневной точности.

Местный weekday может быть построен непосредственно из today_local.

delta_days - количество дней, которое нужно добавить к today_wd, чтобы достичь цели weekday из wd. Вычитание weekday имеет циклический характер: оно всегда приводит к числу дней в диапазоне [0, 6] и не зависит от основного кодирования дней недели. Например, воскресенье всегда 1 день после субботы, даже если суббота кодируется как 6, а воскресенье как 0.

Если текущее значение weekday совпадает с целевым значением weekday, и если это не первая секунда целевого значения weekday, то мы хотим вычислить начало этого же дня на следующей неделе. В противном случае now является целевой датой, и эта функция вернет 0 секунд.

Учитывая количество дней (delta_days) и Текущий день (today_local), целевую дату можно вычислить с помощью today_local + delta_days. Это точность дней local_time, которая неявно преобразуется в точность секунд и указывает на начало дня. Это в паре с местным часовым поясом для построения target_date.

Теперь, когда у нас есть target_date и now, нам просто нужно вычесть их, чтобы получить желаемое количество секунд. Есть два способа сделать это вычитание:

  1. В local_time. Это будет измерять время в «календарных секундах», как определено местным time_zone. В этом календаре все дни 24 часа.

  2. В sys_time. Это преобразуется в UTC до вычитания и, таким образом, измеряет «физические секунды». Это обнаруживает 23-е воскресенье в случае 2 выше.

Примечания:

  • Код не содержит явных констант преобразования, таких как 60, 24 или 86400.
  • Код не беспокоит вычисление истекших секунд текущего дня.
  • Код не зависит от кодировки weekday и поэтому значительно упрощен.
  • Код полностью избегает устаревшего API синхронизации C и поэтому значительно упрощен.

Как я пишу это:

std::cout << GetSecondsUntilNextWeekDay(Saturday) << '\n';

Выходы:

128459s

1 Библиотека часовых поясов Говарда Хиннанта теперь является частью черновой спецификации C ++ 20 с небольшими изменениями, такими как добавление в namespace std::chrono.

0 голосов
/ 11 апреля 2019

Попробуйте это,

#include <ctime>
#include <chrono>
#include <iostream>
using std::chrono::system_clock;

int main()
{
    std::time_t current = std::time(nullptr);
    tm* timeinfo = localtime(&current);                                      
    int wday=timeinfo->tm_wday;                                             
    int hour=timeinfo->tm_hour;                                             
    int min=timeinfo->tm_min;                                               
    int sec=timeinfo->tm_sec;  

    int seconds_passed = sec + (60 * min) + (60 * 60 * hour);  // get the time in seconds passed after 12 AM  

    // days till next saturday.
    switch (wday)
    {       
        case 1: 
            wday = 5;
            break;
        case 2: 
            wday = 4;
            break;
        case 3:  
            wday = 3;
            break;
        case 4: 
            wday = 2;
            break;
        case 5:
            wday = 1;
            break;
        case 6:
            wday = 7;
            break;
        case 0:
            wday = 6;
        break;
    }

    std::chrono::system_clock::time_point t = std::chrono::system_clock::now();
    std::chrono::system_clock::time_point new_t = t + std::chrono::hours(wday * 24);  // get saturdays timestamp

    std::time_t time = system_clock::to_time_t(new_t) - seconds_passed; // subtract seconds_passed from saturdays timestamp

    int seconds = time - current ;  //seconds until next saturday

    std::cout <<"Number of seconds until next saturday "<< seconds <<"\n";

}
0 голосов
/ 11 апреля 2019

Я обновил исходный вопрос этим решением, я не уверен, есть ли более простой способ сделать это ... поэтому я открыт для предложений.

Вот добавленный код ...

const int DateTime::GetSecondsUntilNextWeekDay(
   const std::string& day_name) const
{
   // Calculate how many seconds left for today...
   const int todays_hours_left = 23 - m_time.tm_hour;
   const int todays_minutes_left = (todays_hours_left * 60) + (59 - m_time.tm_min);
   const int todays_seconds_left = (todays_minutes_left * 60) + (59 - m_time.tm_sec);
   int overlap_seconds = 0;

   // Calculate how many days until the desired date.
   int current_day_number = m_time.tm_wday;
   const int desired_day_number = GetWeekDayNumberFromName(day_name);

   if (desired_day_number >= 0)
   {
      if (desired_day_number <= current_day_number)
      {
         // Find out how many days left in the week, add them to how many days until the today.
         const int max_day = 6;
         const int days_remaining = max_day - current_day_number;
         overlap_seconds = (days_remaining * 24 * 60 * 60);
         current_day_number = 0;
      }

      const int days_left = desired_day_number - current_day_number;
      const int hours_left = days_left * 24;
      const int minutes_left = hours_left * 60;
      const int seconds_left = (minutes_left * 60) - (86400 - todays_seconds_left) + overlap_seconds;

      return seconds_left;
   }

   return -1;
}

const int DateTime::GetWeekDayNumberFromName(
   const std::string& name) const
{
   if (name == "Sunday")
   {
      return 0;
   }
   else if (name == "Monday")
   {
      return 1;
   }
   else if (name == "Tuesday")
   {
      return 2;
   }
   else if (name == "Wednesday")
   {
      return 3;
   }
   else if (name == "Thursday")
   {
      return 4;
   }
   else if (name == "Friday")
   {
      return 5;
   }
   else if (name == "Saturday")
   {
      return 6;
   }

   return -1;
}
...