1 /*
2  * Copyright (C) 2013 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 
7 #include "Wt/WApplication.h"
8 #include "Wt/WEnvironment.h"
9 #include "Wt/WStringStream.h"
10 #include "Wt/WLocalDateTime.h"
11 #include "Wt/WLogger.h"
12 #include "Wt/WDateTime.h"
13 #include "Wt/WDate.h"
14 #include "Wt/WTime.h"
15 
16 #include "Wt/cpp20/date.hpp"
17 #include "Wt/cpp20/tz.hpp"
18 
19 #ifndef WT_WIN32
20 #include <ctime>
21 #else
22 #include <windows.h>
23 #endif
24 
25 #include <chrono>
26 #include <type_traits>
27 
28 namespace Wt {
29 
30   namespace {
31     cpp20::date::local_time<std::chrono::system_clock::time_point::duration>
asLocalTime(const std::chrono::system_clock::time_point & dt)32     asLocalTime(const std::chrono::system_clock::time_point &dt)
33     {
34       return cpp20::date::local_time<std::chrono::system_clock::time_point::duration>(dt.time_since_epoch());
35     }
36   }
37 
38 LOGGER("WDateTime");
39 
40 class WLocalDateTime::OffsetZone {
41   std::chrono::minutes offset_;
42   std::string name_;
43 
44 public:
OffsetZone(std::chrono::minutes offset)45   explicit OffsetZone(std::chrono::minutes offset)
46     : offset_{offset}
47   {
48     Wt::WStringStream ss;
49     ss << "<custom zone, offset ";
50     ss << std::string((offset < std::chrono::minutes{0}) ? "-" : "+");
51     ss << static_cast<long long>(std::abs(offset.count()));
52     ss << " minutes>";
53     name_ = ss.str();
54   }
55 
56   template<class Duration>
57   cpp20::date::local_time<typename std::common_type<Duration, std::chrono::minutes>::type>
to_local(cpp20::date::sys_time<Duration> tp)58   to_local(cpp20::date::sys_time<Duration> tp) const
59   {
60     using LT = cpp20::date::local_time<typename std::common_type<Duration, std::chrono::minutes>::type>;
61     return LT{(tp + offset_).time_since_epoch()};
62   }
63 
64   template<class Duration>
65   cpp20::date::sys_time<typename std::common_type<Duration, std::chrono::minutes>::type>
to_sys(cpp20::date::local_time<Duration> tp)66   to_sys(cpp20::date::local_time<Duration> tp) const
67   {
68     using ST = cpp20::date::sys_time<typename std::common_type<Duration, std::chrono::minutes>::type>;
69     return ST{(tp - offset_).time_since_epoch()};
70   }
71 
name()72   const std::string &name() const
73   {
74     return name_;
75   }
76 
offset()77   std::chrono::minutes offset() const
78   {
79     return offset_;
80   }
81 };
82 
WLocalDateTime(const std::chrono::system_clock::time_point & dt,const cpp20::date::time_zone * zone,const WT_USTRING & format)83 WLocalDateTime::WLocalDateTime(const std::chrono::system_clock::time_point& dt,
84 			       const cpp20::date::time_zone *zone, const WT_USTRING& format)
85   : datetime_(dt),
86     format_(format),
87     zone_(zone),
88     valid_(false),
89     null_(false)
90 {
91   if (!zone_)
92     LOG_WARN("Invalid local date time: <no zone>");
93   else
94     valid_ = WDateTime(dt).isValid();
95 }
96 
WLocalDateTime(const std::chrono::system_clock::time_point & dt,const std::shared_ptr<OffsetZone> & zone,const WT_USTRING & format)97 WLocalDateTime::WLocalDateTime(const std::chrono::system_clock::time_point& dt,
98                                const std::shared_ptr<OffsetZone>& zone,
99 			       const WT_USTRING& format)
100   : datetime_(dt),
101     format_(format),
102     zone_(nullptr),
103     customZone_(zone),
104     valid_(false),
105     null_(false)
106 {
107     valid_ = WDateTime(dt).isValid();
108 }
109 
WLocalDateTime(const WLocale & locale)110 WLocalDateTime::WLocalDateTime(const WLocale& locale)
111   : datetime_(std::chrono::system_clock::time_point()),
112     format_(locale.dateTimeFormat()),
113     zone_(locale.timeZone()),
114     valid_(false),
115     null_(true)
116 { }
117 
118 /*
119  * Todo, add overload which indicates DST
120  */
WLocalDateTime(const WDate & date,const WTime & time,const WLocale & locale)121 WLocalDateTime::WLocalDateTime(const WDate& date, const WTime& time,
122 			       const WLocale& locale)
123   : datetime_(std::chrono::system_clock::time_point()),
124     format_(locale.dateTimeFormat()),
125     zone_(locale.timeZone()),
126     valid_(false),
127     null_(false)
128 {
129   setDateTime(date, time);
130 }
131 
offsetDateTime(const std::chrono::system_clock::time_point & dt,std::chrono::minutes offset,const WT_USTRING & format)132 WLocalDateTime WLocalDateTime::offsetDateTime(const std::chrono::system_clock::time_point& dt,
133                                               std::chrono::minutes offset, const WT_USTRING& format)
134 {
135   return WLocalDateTime(dt, std::make_shared<OffsetZone>(offset), format);
136 }
137 
isNull()138 bool WLocalDateTime::isNull() const
139 {
140   return null_;
141 }
142 
isValid()143 bool WLocalDateTime::isValid() const
144 {
145   return valid_;
146 }
147 
setDateTime(const WDate & date,const WTime & time)148 void WLocalDateTime::setDateTime(const WDate& date, const WTime& time)
149 {
150   null_ = false;
151   valid_ = true;
152   if (date.isValid() && time.isValid()) {
153     if (zone_ || customZone_) {
154       try {
155         if (zone_)
156           datetime_ = zone_->to_sys(asLocalTime(WDateTime(date, time).toTimePoint()));
157         else
158           datetime_ = customZone_->to_sys(asLocalTime(WDateTime(date, time).toTimePoint()));
159       } catch(std::exception& e){
160         LOG_WARN("Invalid local date time: " << e.what());
161         setInvalid();
162       }
163     } else{
164       LOG_WARN("Invalid local date time ("
165                << date.toString() << " "
166                << time.toString() << ") in zone "
167                << "<no zone>");
168       setInvalid();
169     }
170     if(isNull()){
171       LOG_WARN("Invalid local date time ("
172                << date.toString() << " "
173                << time.toString() << ") in zone "
174                << (zone_ ? std::string(zone_->name()) : (customZone_ ? customZone_->name() : "<no zone>")));
175       setInvalid();
176     }
177 
178   } else
179     setInvalid();
180 }
181 
setInvalid()182 void WLocalDateTime::setInvalid()
183 {
184   valid_ = false;
185 }
186 
setDateTime(const WDate & date,const WTime & time,bool dst)187 void WLocalDateTime::setDateTime(const WDate& date, const WTime& time,
188 				 bool dst)
189 {
190   null_ = false;
191   valid_ = true;
192   if (date.isValid() && time.isValid()) {
193     try {
194       if (zone_) {
195         if (dst)
196           datetime_ = zone_->to_sys(asLocalTime(WDateTime(date, time).toTimePoint()), cpp20::date::choose::latest);
197         else
198           datetime_ = zone_->to_sys(asLocalTime(WDateTime(date, time).toTimePoint()), cpp20::date::choose::earliest);
199         if (isNull()) {
200           LOG_WARN("Invalid local date time ("
201                    << date.toString() << " "
202                    << time.toString() << " "
203                    << "dst=" << dst << ") in zone "
204                    << std::string(zone_->name()));
205           setInvalid();
206         }
207       } else if (customZone_) {
208         datetime_ = customZone_->to_sys(asLocalTime(WDateTime(date, time).toTimePoint()));
209         if (isNull()) {
210           LOG_WARN("Invalid local date time ("
211                    << date.toString() << " "
212                    << time.toString() << " "
213                    << "dst=" << dst << ") in zone "
214                    << customZone_->name());
215           setInvalid();
216         }
217       } else{
218         LOG_WARN("Invalid local date time ("
219                  << date.toString() << " "
220                  << time.toString() << " "
221                  << "dst=" << dst << ") in zone "
222                  << "<no zone>");
223         setInvalid();
224       }
225     } catch(std::exception& e) {
226       LOG_WARN("Invalid local date time " << e.what());
227       setInvalid();
228     }
229   } else
230     setInvalid();
231 }
232 
setDate(const WDate & date)233 void WLocalDateTime::setDate(const WDate& date)
234 {
235   if (isValid())
236     setDateTime(date, time());
237   else
238     setDateTime(date, WTime(0, 0));
239 }
240 
date()241 WDate WLocalDateTime::date() const
242 {
243   if (isValid()){
244     auto d = zone_ ? cpp20::date::floor<cpp20::date::days>(zone_->to_local(datetime_)) :
245                      cpp20::date::floor<cpp20::date::days>(customZone_->to_local(datetime_));
246     auto ymd = cpp20::date::year_month_day(d);
247     return WDate(int(ymd.year()), unsigned(ymd.month()), unsigned(ymd.day()));
248   }
249   return WDate();
250 }
251 
setTime(const WTime & time)252 void WLocalDateTime::setTime(const WTime& time)
253 {
254   if (isValid())
255     setDateTime(date(), time);
256 }
257 
time()258 WTime WLocalDateTime::time() const
259 {
260   if (isValid()){
261     auto dt = zone_ ? zone_->to_local(datetime_) : customZone_->to_local(datetime_);
262     auto dp = cpp20::date::local_days(cpp20::date::floor<cpp20::date::days>(dt.time_since_epoch()));
263 #ifdef WT_DATE_TZ_USE_DATE
264     auto time = ::date::make_time(dt - dp);
265 #else
266     auto time = std::chrono::hh_mm_ss(dt - dp);
267 #endif
268     std::chrono::duration<int, std::milli> ms = std::chrono::duration_cast<std::chrono::milliseconds>(time.subseconds());
269     return WTime(time.hours().count(), time.minutes().count(), time.seconds().count(), ms.count());
270   }
271   return WTime();
272 }
273 
toUTC()274 WDateTime WLocalDateTime::toUTC() const
275 {
276   if (isValid()){
277     return WDateTime(datetime_);
278   }
279   else
280     return WDateTime();
281 }
282 
toString()283 WT_USTRING WLocalDateTime::toString() const
284 {
285   return toString(format_);
286 }
287 
timeZoneOffset()288 int WLocalDateTime::timeZoneOffset() const
289 {
290   if (zone_) {
291     auto info = zone_->get_info(datetime_);
292     return info.offset.count() / 60;
293   } else if (customZone_) {
294     return customZone_->offset().count();
295   } else {
296     throw WException("WLocalDateTime: timezone is null");
297   }
298 }
299 
timeZone()300 const cpp20::date::time_zone* WLocalDateTime::timeZone() const
301 {
302   return zone_;
303 }
304 
toString(const WT_USTRING & format)305 WT_USTRING WLocalDateTime::toString(const WT_USTRING& format) const
306 {
307   WDate d = date();
308   WTime t = time();
309   return WDateTime::toString(&d, &t, format, true, timeZoneOffset());
310 }
311 
fromString(const WT_USTRING & s,const WLocale & locale)312 WLocalDateTime WLocalDateTime::fromString(const WT_USTRING& s,
313 					  const WLocale& locale)
314 {
315   WDateTime t = WDateTime::fromString(s, locale.dateTimeFormat());
316 
317   return WLocalDateTime(t.date(), t.time(), locale);
318 }
319 
currentDateTime(const WLocale & locale)320 WLocalDateTime WLocalDateTime::currentDateTime(const WLocale& locale)
321 {
322   WApplication *app = WApplication::instance();
323 
324   if (!locale.timeZone() && app)
325     return currentTime(app->environment().timeZoneOffset(), locale.dateTimeFormat());
326   else
327     return WDateTime::currentDateTime().toLocalTime(locale);
328 }
329 
currentTime(std::chrono::minutes offset,const WT_USTRING & format)330 WLocalDateTime WLocalDateTime::currentTime(std::chrono::minutes offset, const WT_USTRING& format)
331 {
332   auto z = std::make_shared<OffsetZone>(offset);
333   return WLocalDateTime(std::chrono::system_clock::now(), z, format);
334 }
335 
currentServerDateTime()336 WLocalDateTime WLocalDateTime::currentServerDateTime()
337 {
338 #ifndef WT_WIN32
339   std::time_t t = std::time(nullptr);
340   std::tm tm;
341   ::localtime_r(&t, &tm);
342   // tm_gmtoff is not part of the POSIX standard, but Linux, Mac OS X and the BSDs provide it
343   return currentTime(date::floor<std::chrono::minutes>(std::chrono::seconds{tm.tm_gmtoff}),
344 		     WLocale::currentLocale().dateTimeFormat());
345 #else
346   TIME_ZONE_INFORMATION tzi{};
347   DWORD tz_result = ::GetTimeZoneInformation(&tzi);
348   if (tz_result == TIME_ZONE_ID_INVALID)
349   {
350     return currentTime(std::chrono::minutes{0}, WLocale::currentLocale().dateTimeFormat());
351   }
352   bool dst = tz_result == TIME_ZONE_ID_DAYLIGHT;
353   return currentTime(std::chrono::minutes{- tzi.Bias - (dst ? tzi.DaylightBias : 0)},
354 		     WLocale::currentLocale().dateTimeFormat());
355 #endif
356 }
357 
358 bool WLocalDateTime::operator==(const WLocalDateTime& other) const
359 {
360   return datetime_ == other.datetime_;
361 }
362 
363 bool WLocalDateTime::operator!=(const WLocalDateTime& other) const
364 {
365   return datetime_ != other.datetime_;
366 }
367 
368 bool WLocalDateTime::operator<(const WLocalDateTime& other) const
369 {
370   return datetime_ < other.datetime_;
371 }
372 
373 }
374