Skip to content

Latest commit

 

History

History
340 lines (250 loc) · 12.3 KB

16.md

File metadata and controls

340 lines (250 loc) · 12.3 KB

十六、日期和时间

问题

这是本章的解题部分。

39.测量功能执行时间

编写一个函数,该函数可以测量函数在任何所需持续时间(如秒、毫秒、微秒等)内的执行时间(具有任意数量的参数)。

40.两个日期之间的天数

编写一个函数,在给定两个日期的情况下,返回两个日期之间的天数。无论输入日期的顺序如何,该函数都应该工作。

41.一周中的某一天

编写一个函数,在给定日期的情况下,确定一周中的哪一天。这个函数应该返回一个介于 1(星期一)和 7(星期日)之间的值。

42.一年中的日期和星期

编写一个函数,在给定日期的情况下,返回一年中的某一天(闰年从 1 到 365 或 366),并编写另一个函数,对于相同的输入,返回一年中的日历周。

43.多个时区的会议时间

编写一个函数,给出会议参与者及其时区的列表,显示每个参与者的本地会议时间。

44.月历

编写一个函数,在给定年和月的情况下,将月历打印到控制台。预期输出格式如下(示例为 2017 年 12 月):

Mon Tue Wed Thu Fri Sat Sun
                  1   2   3
  4   5   6   7   8   9  10
 11  12  13  14  15  16  17
 18  19  20  21  22  23  24
 25  26  27  28  29  30  31

解决方法

以下是上述问题解决部分的解决方案。

39.测量功能执行时间

要测量函数的执行时间,您应该在函数执行之前检索当前时间,执行函数,然后再次检索当前时间,并确定两个时间点之间经过了多少时间。为了方便起见,这些都可以放在variadic函数模板中,该模板将要执行的函数及其参数作为参数,并且:

  • 默认使用std::high_resolution_clock确定当前时间。
  • 使用std::invoke()执行要测量的函数,带有指定的参数。
  • 返回持续时间,而不是特定持续时间的刻度数。这很重要,这样你就不会失去决心。它使您能够添加各种分辨率的执行持续时间,如秒和毫秒,这是不可能通过返回滴答计数来实现的:
template <typename Time = std::chrono::microseconds,
          typename Clock = std::chrono::high_resolution_clock>
struct perf_timer
{
   template <typename F, typename... Args>
   static Time duration(F&& f, Args... args)
   {
      auto start = Clock::now();
      std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
      auto end = Clock::now();

      return std::chrono::duration_cast<Time>(end - start);
   }
};

该功能模板可以如下使用:

void f() 
{ 
   // simulate work
   std::this_thread::sleep_for(2s); 
}

void g(int const a, int const b) 
{ 
   // simulate work
   std::this_thread::sleep_for(1s); 
}

int main()
{
   auto t1 = perf_timer<std::chrono::microseconds>::duration(f);
   auto t2 = perf_timer<std::chrono::milliseconds>::duration(g, 1, 2);

   auto total = std::chrono::duration<double, std::nano>(t1 + t2).count();
}

40.两个日期之间的天数

从 C++ 17 开始,chrono标准库不支持使用日期、星期、日历、时区和其他有用的相关功能。这将在 C++ 20 中发生变化,因为时区和日历支持已在 2018 年 3 月的杰克逊维尔会议上被添加到标准中。新增加的内容基于一个名为date的开源库,该库建立在chrono之上,由霍华德·欣南特开发,可在 https://github.com/HowardHinnant/date网站上获得。我们将使用这个库来解决本章中的几个问题。虽然在这个实现中,名称空间是date,但是在 C++ 20 中,它将是std::chrono的一部分。但是,您应该能够简单地替换名称空间,而无需任何进一步的代码更改。

要解决这个任务,您可以使用date.h 标题中的date::sys_days类。它代表了自std::system_clock时代以来的天数。这是一个有着一天决心的time_point,并且可以隐式转换为std::system_clock::time_point。基本上,你必须构造两个这种类型的对象并减去它们。结果正好是两个日期之间的天数。下面是这样一个函数的简单实现:

inline int number_of_days(
   int const y1, unsigned int const m1, unsigned int const d1,
   int const y2, unsigned int const m2, unsigned int const d2)
{
   using namespace date;

   return (sys_days{ year{ y1 } / month{ m1 } / day{ d1 } } -
           sys_days{ year{ y2 } / month{ m2 } / day{ d2 } }).count();
}

inline int number_of_days(date::sys_days const & first,
                          date::sys_days const & last)
{
   return (last - first).count();
}

下面是如何使用这些重载函数的几个例子:

int main()
{
   auto diff1 = number_of_days(2016, 9, 23, 2017, 5, 15);

   using namespace date::literals;
   auto diff2 = number_of_days(2016_y/sep/23, 15_d/may/2017);
}

41.一周中的某一天

如果使用date库,解决这个问题也相对简单。但是,这一次,您必须使用以下类型:

  • date::year_month_day,表示一天的结构,包含年、月(1 到 12)和日(1 到 31)字段。
  • date::iso_week::year_weeknum_weekday来自iso_week.h 表头,是一个结构,有年字段、年周数字段和周天数字段(1 到 7)。这个类可以隐式转换到date::sys_days和从date::sys_days转换,这使得它可以显式转换到任何其他可以隐式转换到date::sys_days和从date::year_month_day转换的日历系统。

说了这么多,问题解决了:创建一个year_month_day对象来表示期望的日期,然后从中创建一个year_weeknum_weekday对象,并使用weekday()检索一周中的某一天:

unsigned int week_day(int const y, unsigned int const m, 
                      unsigned int const d)
{
   using namespace date;

   if(m < 1 || m > 12 || d < 1 || d > 31) return 0;

   auto const dt = date::year_month_day{year{ y }, month{ m }, day{ d }};
   auto const tiso = iso_week::year_weeknum_weekday{ dt };

   return (unsigned int)tiso.weekday();
}

int main()
{
   auto wday = week_day(2018, 5, 9);
}

42.一年中的日期和星期

这个由两部分组成的问题的解决方案应该与前面两个简单明了:

  • 要计算一年中的某一天,您要减去两个date::sys_days对象,一个表示给定的一天,另一个表示同一年的 1 月 0 日。或者,您可以从 1 月 1 日开始,并在结果中添加 1。
  • 要确定一年中的周数,构造一个year_weeknum_weekday对象,就像前面的问题一样,并检索weeknum()值:
int day_of_year(int const y, unsigned int const m, 
                unsigned int const d)
{
   using namespace date;

   if(m < 1 || m > 12 || d < 1 || d > 31) return 0;

   return (sys_days{ year{ y } / month{ m } / day{ d } } -
           sys_days{ year{ y } / jan / 0 }).count();
}

unsigned int calendar_week(int const y, unsigned int const m, 
                           unsigned int const d)
{
   using namespace date;

   if(m < 1 || m > 12 || d < 1 || d > 31) return 0;

   auto const dt = date::year_month_day{year{ y }, month{ m }, day{ d }};
   auto const tiso = iso_week::year_weeknum_weekday{ dt };

   return (unsigned int)tiso.weeknum();
}

这些功能可以如下使用:

int main()
{
   int y = 0;
   unsigned int m = 0, d = 0;
   std::cout << "Year:"; std::cin >> y;
   std::cout << "Month:"; std::cin >> m;
   std::cout << "Day:"; std::cin >> d;

   std::cout << "Calendar week:" << calendar_week(y, m, d) << std::endl;
   std::cout << "Day of year:" << day_of_year(y, m, d) << std::endl;
}

43.多个时区的会议时间

要使用时区,您必须使用date库的tz.h标题。但是,这需要在您的机器上下载并解压缩 IANA 时区数据库

以下是如何为日期库准备时区数据库:

  • https://www.iana.org/time-zones下载最新版本的数据库。目前最新的版本叫做tzdata2017c.tar.gz
  • 将它解压缩到你机器上的任何位置,在一个名为tzdata的子目录中。假设父目录是c:\work\challenges\libs\date(在 Windows 机器上);这将有一个名为tzdata的子目录。
  • 对于 Windows,您需要下载一个名为windowsZones.xml的文件,其中包含 Windows 时区到 IANA 时区的映射。这可以在https://unicode . org/repos/cldr/trunk/common/supplicate/windowszones . XML上找到。文件必须存储在之前创建的同一个tzdata子目录中。
  • 在您的项目设置中,定义一个名为INSTALL的预处理器宏,该宏指示tzdata子目录的父目录。这里举的例子,你应该有INSTALL=c:\\work\\challenges\\libs\\date。(请注意,双反斜杠是必要的,因为宏用于使用字符串化和串联创建文件路径,否则会导致路径不正确。)

为了解决这个问题,我们将考虑具有最少信息的用户结构,例如姓名和时区。时区是使用date::locate_zone()功能创建的:

struct user
{
   std::string Name;
   date::time_zone const * Zone;

   explicit user(std::string_view name, std::string_view zone)
      : Name{name.data()}, Zone(date::locate_zone(zone.data()))
   {}
};

显示用户列表及其会议开始的本地时间的函数应该将给定时间从参考区域转换为他们自己区域的时间。为此,我们可以使用date::zoned_time类的转换构造函数:

template <class Duration, class TimeZonePtr>
void print_meeting_times(
   date::zoned_time<Duration, TimeZonePtr> const & time,
   std::vector<user> const & users)
{
   std::cout 
      << std::left << std::setw(15) << std::setfill(' ')
      << "Local time: " 
      << time << std::endl;

   for (auto const & user : users)
   {
      std::cout
         << std::left << std::setw(15) << std::setfill(' ')
         << user.Name
         << date::zoned_time<Duration, TimeZonePtr>(user.Zone, time) 
         << std::endl;
   }
}

该功能可以如下使用,其中给定时间(小时和分钟)以当前时区表示:

int main()
{
   std::vector<user> users{
      user{ "Ildiko", "Europe/Budapest" },
      user{ "Jens", "Europe/Berlin" },
      user{ "Jane", "America/New_York" }
   };

   unsigned int h, m;
   std::cout << "Hour:"; std::cin >> h;
   std::cout << "Minutes:"; std::cin >> m;

   date::year_month_day today = 
      date::floor<date::days>(ch::system_clock::now());

   auto localtime = date::zoned_time<std::chrono::minutes>(
      date::current_zone(), 
      static_cast<date::local_days>(today)+ch::hours{h}+ch::minutes{m});

   print_meeting_times(localtime, users);
}

44.月历

解决这个任务实际上是部分基于前面的任务。为了打印问题中指出的月份,您应该知道:

  • 一个月的第一天是星期几。这可以使用为前一个问题创建的week_day()函数来确定。
  • 一个月的天数。这可以通过使用date::year_month_day_last结构并检索day()的值来确定。

首先确定这些信息后,您应该:

  • 打印第一个工作日前第一周的空值
  • 使用从 1 到每月最后一天的正确格式打印日期
  • 每隔七天换一条新的线(从第一周的第一天开始计算,尽管这可能属于前一个月)

这里显示了所有这些的实现:

unsigned int week_day(int const y, unsigned int const m, 
                      unsigned int const d)
{
   using namespace date;

   if(m < 1 || m > 12 || d < 1 || d > 31) return 0;

   auto const dt = date::year_month_day{year{ y }, month{ m }, day{ d }};
   auto const tiso = iso_week::year_weeknum_weekday{ dt };

   return (unsigned int)tiso.weekday();
}

void print_month_calendar(int const y, unsigned int m)
{
   using namespace date;
   std::cout << "Mon Tue Wed Thu Fri Sat Sun" << std::endl;

   auto first_day_weekday = week_day(y, m, 1);
   auto last_day = (unsigned int)year_month_day_last(
      year{ y }, month_day_last{ month{ m } }).day();

   unsigned int index = 1;
   for (unsigned int day = 1; day < first_day_weekday; ++ day, ++ index)
   {
      std::cout << " ";
   }

   for (unsigned int day = 1; day <= last_day; ++ day)
   {
      std::cout << std::right << std::setfill(' ') << std::setw(3)
                << day << ' ';
      if (index++ % 7 == 0) std::cout << std::endl;
   }

   std::cout << std::endl;
}

int main()
{
   print_month_calendar(2017, 12);
}