Tidying.
[libdcp.git] / src / local_time.cc
1 /*
2     Copyright (C) 2014-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libdcp is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34
35 /** @file  src/local_time.cc
36  *  @brief LocalTime class
37  */
38
39
40 #include "local_time.h"
41 #include "exceptions.h"
42 #include "dcp_assert.h"
43 #include <boost/lexical_cast.hpp>
44 #include <boost/date_time/posix_time/posix_time.hpp>
45 #include <boost/date_time/c_local_time_adjustor.hpp>
46 #include <boost/date_time/gregorian/gregorian.hpp>
47 #include <cstdio>
48
49
50 using std::string;
51 using std::ostream;
52 using boost::lexical_cast;
53 using namespace dcp;
54
55
56 LocalTime::LocalTime ()
57 {
58         auto now = time (0);
59         auto tm = localtime (&now);
60         set (tm);
61         set_local_time_zone ();
62 }
63
64
65 LocalTime::LocalTime (struct tm t)
66 {
67         set (&t);
68         set_local_time_zone ();
69 }
70
71
72 void
73 LocalTime::set (struct tm const * tm)
74 {
75         _year = tm->tm_year + 1900;
76         _month = tm->tm_mon + 1;
77         _day = tm->tm_mday;
78         _hour = tm->tm_hour;
79         _minute = tm->tm_min;
80         _second = tm->tm_sec;
81         _millisecond = 0;
82 }
83
84
85 LocalTime::LocalTime (boost::posix_time::ptime t)
86 {
87         set (t);
88         set_local_time_zone ();
89 }
90
91
92 void
93 LocalTime::set (boost::posix_time::ptime t)
94 {
95         _year = t.date().year ();
96         _month = t.date().month ();
97         _day = t.date().day ();
98         _hour = t.time_of_day().hours ();
99         _minute = t.time_of_day().minutes ();
100         _second = t.time_of_day().seconds ();
101         _millisecond = t.time_of_day().fractional_seconds () / 1000;
102         DCP_ASSERT (_millisecond < 1000);
103 }
104
105
106 LocalTime::LocalTime (boost::posix_time::ptime t, int tz_hour, int tz_minute)
107 {
108         set (t);
109         _tz_hour = tz_hour;
110         _tz_minute = tz_minute;
111 }
112
113
114 /** Set our UTC offset to be according to the local time zone */
115 void
116 LocalTime::set_local_time_zone ()
117 {
118         auto const utc_now = boost::posix_time::second_clock::universal_time ();
119         auto const now = boost::date_time::c_local_adjustor<boost::posix_time::ptime>::utc_to_local (utc_now);
120         auto offset = now - utc_now;
121
122         _tz_hour = offset.hours ();
123         _tz_minute = offset.minutes ();
124 }
125
126
127 LocalTime::LocalTime (string s)
128 {
129         /* 2013-01-05T18:06:59 or 2013-01-05T18:06:59.123 or 2013-01-05T18:06:59+04:00 or 2013-01-05T18:06:59.123+04:00 */
130         /* 0123456789012345678 or 01234567890123456789012 or 0123456789012345678901234 or 01234567890123456789012345678 */
131
132         if (s.length() < 19) {
133                 throw TimeFormatError (s);
134         }
135
136         bool with_millisecond = false;
137         bool with_tz = false;
138
139         switch (s.length ()) {
140         case 19:
141                 break;
142         case 23:
143                 with_millisecond = true;
144                 break;
145         case 25:
146                 with_tz = true;
147                 break;
148         case 29:
149                 with_millisecond = with_tz = true;
150                 break;
151         default:
152                 throw TimeFormatError (s);
153         }
154
155         int const tz_pos = with_millisecond ? 23 : 19;
156
157         /* Check incidental characters */
158         if (s[4] != '-' || s[7] != '-' || s[10] != 'T' || s[13] != ':' || s[16] != ':') {
159                 throw TimeFormatError (s);
160         }
161         if (with_millisecond && s[19] != '.') {
162                 throw TimeFormatError (s);
163         }
164         if (with_tz && s[tz_pos] != '+' && s[tz_pos] != '-') {
165                 throw TimeFormatError (s);
166         }
167
168         _year = lexical_cast<int>(s.substr(0, 4));
169         _month = lexical_cast<int>(s.substr(5, 2));
170         _day = lexical_cast<int>(s.substr(8, 2));
171         _hour = lexical_cast<int>(s.substr(11, 2));
172         _minute = lexical_cast<int>(s.substr(14, 2));
173         _second = lexical_cast<int>(s.substr(17, 2));
174         _millisecond = with_millisecond ? lexical_cast<int>(s.substr(20, 3)) : 0;
175         _tz_hour = with_tz ? lexical_cast<int>(s.substr(tz_pos + 1, 2)) : 0;
176         _tz_minute = with_tz ? lexical_cast<int>(s.substr(tz_pos + 4, 2)) : 0;
177
178         if (with_tz && s[tz_pos] == '-') {
179                 _tz_hour = -_tz_hour;
180                 _tz_minute = -_tz_minute;
181         }
182 }
183
184
185 string
186 LocalTime::as_string (bool with_millisecond) const
187 {
188         char buffer[32];
189         snprintf (
190                 buffer, sizeof (buffer),
191                 "%sT%s%s%02d:%02d",
192                 date().c_str(), time_of_day(true, with_millisecond).c_str(), (_tz_hour >= 0 ? "+" : "-"), abs (_tz_hour), abs(_tz_minute)
193                 );
194         return buffer;
195 }
196
197
198 string
199 LocalTime::date () const
200 {
201         char buffer[32];
202         snprintf (buffer, sizeof (buffer), "%04d-%02d-%02d", _year, _month, _day);
203         return buffer;
204 }
205
206
207 string
208 LocalTime::time_of_day (bool with_second, bool with_millisecond) const
209 {
210         char buffer[32];
211         DCP_ASSERT(!(with_millisecond && !with_second));
212         if (with_millisecond) {
213                 snprintf (buffer, sizeof (buffer), "%02d:%02d:%02d.%03d", _hour, _minute, _second, _millisecond);
214         } else if (with_second) {
215                 snprintf (buffer, sizeof (buffer), "%02d:%02d:%02d", _hour, _minute, _second);
216         } else {
217                 snprintf (buffer, sizeof (buffer), "%02d:%02d", _hour, _minute);
218         }
219         return buffer;
220 }
221
222
223 void
224 LocalTime::add_days (int days)
225 {
226         using namespace boost;
227
228         gregorian::date d (_year, _month, _day);
229         if (days > 0) {
230                 d += gregorian::days (days);
231         } else {
232                 d -= gregorian::days (-days);
233         }
234
235         set (posix_time::ptime(d, posix_time::time_duration(_hour, _minute, _second, _millisecond * 1000)));
236 }
237
238
239 void
240 LocalTime::add_months (int m)
241 {
242         using namespace boost;
243
244         gregorian::date d (_year, _month, _day);
245         if (m > 0) {
246                 d += gregorian::months (m);
247         } else {
248                 d -= gregorian::months (-m);
249         }
250
251         set (posix_time::ptime(d, posix_time::time_duration(_hour, _minute, _second, _millisecond * 1000)));
252 }
253
254
255 void
256 LocalTime::add_minutes (int m)
257 {
258         using namespace boost;
259
260         posix_time::ptime t(gregorian::date(_year, _month, _day), posix_time::time_duration(_hour, _minute, _second, _millisecond * 1000));
261         t += posix_time::time_duration(0, m, 0);
262         set (t);
263 }
264
265
266 bool
267 LocalTime::operator== (LocalTime const & other) const
268 {
269         return _year == other._year && _month == other._month && _day == other._day &&
270                 _hour == other._hour && _second == other._second && _millisecond == other._millisecond &&
271                 _tz_hour == other._tz_hour && _tz_minute == other._tz_minute;
272 }
273
274
275 bool
276 LocalTime::operator< (LocalTime const & other) const
277 {
278         if (_year != other._year) {
279                 return _year < other._year;
280         }
281         if (_month != other._month) {
282                 return _month < other._month;
283         }
284         if (_day != other._day) {
285                 return _day < other._day;
286         }
287         if (_hour != other._hour) {
288                 return _hour < other._hour;
289         }
290         if (_second != other._second) {
291                 return _second < other._second;
292         }
293         return _millisecond < other._millisecond;
294 }
295
296
297 bool
298 LocalTime::operator!= (LocalTime const & other) const
299 {
300         return !(*this == other);
301 }
302
303
304 ostream&
305 dcp::operator<< (ostream& s, LocalTime const & t)
306 {
307         s << t.as_string ();
308         return s;
309 }