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