aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/utils/calendar.cpp
blob: 73602d6349080c0ecb7e11d36f05bd28630070e4 (plain)
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
/*
* Calendar Functions
* (C) 1999-2010 Jack Lloyd
* (C) 2015 Simon Warta (Kullo GmbH)
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/calendar.h>
#include <botan/exceptn.h>
#include <ctime>
#include <sstream>
#include <iomanip>
#include <mutex>

#if defined(BOTAN_HAS_BOOST_DATETIME)
#include <boost/date_time/posix_time/posix_time_types.hpp>
#endif

namespace Botan {

namespace {

std::tm do_gmtime(std::time_t time_val)
   {
   std::tm tm;

#if defined(BOTAN_TARGET_OS_HAS_GMTIME_S)
   gmtime_s(&tm, &time_val); // Windows
#elif defined(BOTAN_TARGET_OS_HAS_GMTIME_R)
   gmtime_r(&time_val, &tm); // Unix/SUSv2
#else
   std::tm* tm_p = std::gmtime(&time_val);
   if (tm_p == nullptr)
      throw Encoding_Error("time_t_to_tm could not convert");
   tm = *tm_p;
#endif

   return tm;
   }

#if !defined(BOTAN_TARGET_OS_HAS_TIMEGM) && !defined(BOTAN_TARGET_OS_HAS_MKGMTIME)

#if defined(BOTAN_HAS_BOOST_DATETIME)

std::time_t boost_timegm(std::tm *tm)
   {
   const int sec  = tm->tm_sec;
   const int min  = tm->tm_min;
   const int hour = tm->tm_hour;
   const int day  = tm->tm_mday;
   const int mon  = tm->tm_mon + 1;
   const int year = tm->tm_year + 1900;

   std::time_t out;

      {
      using namespace boost::posix_time;
      using namespace boost::gregorian;
      const auto epoch = ptime(date(1970, 01, 01));
      const auto time = ptime(date(year, mon, day),
                              hours(hour) + minutes(min) + seconds(sec));
      const time_duration diff(time - epoch);
      out = diff.ticks() / diff.ticks_per_second();
      }

   return out;
   }

#else

#pragma message "Caution! A fallback version of timegm() is used which is not thread-safe"

std::mutex ENV_TZ;

std::time_t fallback_timegm(std::tm *tm)
   {
   std::time_t out;
   std::string tz_backup;

   ENV_TZ.lock();

   // Store current value of env variable TZ
   const char* tz_env_pointer = ::getenv("TZ");
   if (tz_env_pointer != nullptr)
      tz_backup = std::string(tz_env_pointer);

   // Clear value of TZ
   ::setenv("TZ", "", 1);
   ::tzset();

   out = ::mktime(tm);

   // Restore TZ
   if (!tz_backup.empty())
      {
      // setenv makes a copy of the second argument
      ::setenv("TZ", tz_backup.data(), 1);
      }
   else
      {
      ::unsetenv("TZ");
      }
   ::tzset();

   ENV_TZ.unlock();

   return out;
}
#endif // BOTAN_HAS_BOOST_DATETIME

#endif

}

std::chrono::system_clock::time_point calendar_point::to_std_timepoint() const
   {
   if (year < 1970)
      throw Invalid_Argument("calendar_point::to_std_timepoint() does not support years before 1970.");

   // 32 bit time_t ends at January 19, 2038
   // https://msdn.microsoft.com/en-us/library/2093ets1.aspx
   // For consistency reasons, throw after 2037 as long as
   // no other implementation is available.
   if (year > 2037)
      throw Invalid_Argument("calendar_point::to_std_timepoint() does not support years after 2037.");

   // std::tm: struct without any timezone information
   std::tm tm;
   tm.tm_isdst = -1; // i.e. no DST information available
   tm.tm_sec   = seconds;
   tm.tm_min   = minutes;
   tm.tm_hour  = hour;
   tm.tm_mday  = day;
   tm.tm_mon   = month - 1;
   tm.tm_year  = year - 1900;

   // Define a function alias `botan_timegm`
   #if defined(BOTAN_TARGET_OS_HAS_TIMEGM)
   std::time_t (&botan_timegm)(std::tm *tm) = timegm;
   #elif defined(BOTAN_TARGET_OS_HAS_MKGMTIME)
   // http://stackoverflow.com/questions/16647819/timegm-cross-platform
   std::time_t (&botan_timegm)(std::tm *tm) = _mkgmtime;
   #elif defined(BOTAN_HAS_BOOST_DATETIME)
   std::time_t (&botan_timegm)(std::tm *tm) = boost_timegm;
   #else
   std::time_t (&botan_timegm)(std::tm *tm) = fallback_timegm;
   #endif

   // Convert std::tm to std::time_t
   std::time_t tt = botan_timegm(&tm);
   if (tt == -1)
      throw Invalid_Argument("calendar_point couldn't be converted: " + to_string());

   return std::chrono::system_clock::from_time_t(tt);
   }

std::string calendar_point::to_string() const
   {
   // desired format: <YYYY>-<MM>-<dd>T<HH>:<mm>:<ss>
   std::stringstream output;
      {
      using namespace std;
      output << setfill('0')
             << setw(4) << year << "-" << setw(2) << month << "-" << setw(2) << day
             << "T"
             << setw(2) << hour << ":" << setw(2) << minutes << ":" << setw(2) << seconds;
      }
   return output.str();
   }


calendar_point calendar_value(
   const std::chrono::system_clock::time_point& time_point)
   {
   std::tm tm = do_gmtime(std::chrono::system_clock::to_time_t(time_point));

   return calendar_point(tm.tm_year + 1900,
                         tm.tm_mon + 1,
                         tm.tm_mday,
                         tm.tm_hour,
                         tm.tm_min,
                         tm.tm_sec);
   }

}