Merge branch '1.0' of git.carlh.net:git/libdcp into 1.0
[libdcp.git] / src / local_time.cc
1 /*
2     Copyright (C) 2014 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 /** @file  src/local_time.cc
35  *  @brief LocalTime class.
36  */
37
38 #include "local_time.h"
39 #include "exceptions.h"
40 #include "dcp_assert.h"
41 #include <boost/lexical_cast.hpp>
42 #include <boost/date_time/c_local_time_adjustor.hpp>
43 #include <cstdio>
44
45 using std::string;
46 using std::ostream;
47 using boost::lexical_cast;
48 using namespace dcp;
49
50 /** Construct a LocalTime from the current time */
51 LocalTime::LocalTime ()
52 {
53         time_t now = time (0);
54         struct tm* tm = localtime (&now);
55
56         _year = tm->tm_year + 1900;
57         _month = tm->tm_mon + 1;
58         _day = tm->tm_mday;
59         _hour = tm->tm_hour;
60         _minute = tm->tm_min;
61         _second = tm->tm_sec;
62         _millisecond = 0;
63
64         set_local_time_zone ();
65 }
66
67 /** Construct a LocalTime from a boost::posix_time::ptime using the local
68  *  time zone.
69  */
70 LocalTime::LocalTime (boost::posix_time::ptime t)
71 {
72         _year = t.date().year ();
73         _month = t.date().month ();
74         _day = t.date().day ();
75         _hour = t.time_of_day().hours ();
76         _minute = t.time_of_day().minutes ();
77         _second = t.time_of_day().seconds ();
78         _millisecond = t.time_of_day().fractional_seconds () / 1000;
79         DCP_ASSERT (_millisecond < 1000);
80
81         set_local_time_zone ();
82 }
83
84 /** Construct a LocalTime from a boost::posix_time::ptime and a time zone offset */
85 LocalTime::LocalTime (boost::posix_time::ptime t, int tz_hour, int tz_minute)
86 {
87         _year = t.date().year ();
88         _month = t.date().month ();
89         _day = t.date().day ();
90         _hour = t.time_of_day().hours ();
91         _minute = t.time_of_day().minutes ();
92         _second = t.time_of_day().seconds ();
93         _millisecond = t.time_of_day().fractional_seconds () / 1000;
94         DCP_ASSERT (_millisecond < 1000);
95
96         _tz_hour = tz_hour;
97         _tz_minute = tz_minute;
98 }
99
100 /** Set our UTC offset to be according to the local time zone */
101 void
102 LocalTime::set_local_time_zone ()
103 {
104         boost::posix_time::ptime const utc_now = boost::posix_time::second_clock::universal_time ();
105         boost::posix_time::ptime const now = boost::date_time::c_local_adjustor<boost::posix_time::ptime>::utc_to_local (utc_now);
106         boost::posix_time::time_duration offset = now - utc_now;
107
108         _tz_hour = offset.hours ();
109         _tz_minute = offset.minutes ();
110 }
111
112 /** @param s A string of the form 2013-01-05T18:06:59[.123]+04:00 */
113 LocalTime::LocalTime (string s)
114 {
115         /* 2013-01-05T18:06:59+04:00 or 2013-01-05T18:06:59.123+04:00 */
116         /* 0123456789012345678901234 or 01234567890123456789012345678 */
117
118         if (s.length() < 25) {
119                 throw TimeFormatError (s);
120         }
121
122         /* Check incidental characters */
123         bool const common = s[4] == '-' && s[7] == '-' && s[10] == 'T' && s[13] == ':' && s[16] == ':';
124         bool const without_millisecond = common && s[22] == ':';
125         bool const with_millisecond = common && s[19] == '.' && s[26] == ':';
126
127         if (!with_millisecond && !without_millisecond) {
128                 throw TimeFormatError (s);
129         }
130
131         _year = lexical_cast<int> (s.substr (0, 4));
132         _month = lexical_cast<int> (s.substr (5, 2));
133         _day = lexical_cast<int> (s.substr (8, 2));
134         _hour = lexical_cast<int> (s.substr (11, 2));
135         _minute = lexical_cast<int> (s.substr (14, 2));
136         _second = lexical_cast<int> (s.substr (17, 2));
137         if (without_millisecond) {
138                 _millisecond = 0;
139                 _tz_hour = lexical_cast<int> (s.substr (20, 2));
140                 _tz_minute = lexical_cast<int> (s.substr (23, 2));
141         } else {
142                 _millisecond = lexical_cast<int> (s.substr (20, 3));
143                 _tz_hour = lexical_cast<int> (s.substr (24, 2));
144                 _tz_minute = lexical_cast<int> (s.substr (27, 2));
145         }
146
147         int const plus_minus_position = with_millisecond ? 23 : 19;
148
149         if (s[plus_minus_position] == '-') {
150                 _tz_hour = -_tz_hour;
151         } else if (s[plus_minus_position] != '+') {
152                 throw TimeFormatError (s);
153         }
154 }
155
156 /** @return A string of the form 2013-01-05T18:06:59+04:00 or 2013-01-05T18:06:59.123+04:00 */
157 string
158 LocalTime::as_string (bool with_millisecond) const
159 {
160         char buffer[32];
161         snprintf (
162                 buffer, sizeof (buffer),
163                 "%sT%s%s%02d:%02d",
164                 date().c_str(), time_of_day(with_millisecond).c_str(), (_tz_hour >= 0 ? "+" : "-"), abs (_tz_hour), _tz_minute
165                 );
166         return buffer;
167 }
168
169 /** @return The date in the form YYYY-MM-DD */
170 string
171 LocalTime::date () const
172 {
173         char buffer[32];
174         snprintf (buffer, sizeof (buffer), "%04d-%02d-%02d", _year, _month, _day);
175         return buffer;
176 }
177
178 /** @return The time in the form HH:MM:SS or HH:MM:SS.mmm */
179 string
180 LocalTime::time_of_day (bool with_millisecond) const
181 {
182         char buffer[32];
183         if (with_millisecond) {
184                 snprintf (buffer, sizeof (buffer), "%02d:%02d:%02d.%03d", _hour, _minute, _second, _millisecond);
185         } else {
186                 snprintf (buffer, sizeof (buffer), "%02d:%02d:%02d", _hour, _minute, _second);
187         }
188         return buffer;
189 }
190
191 bool
192 LocalTime::operator== (LocalTime const & other) const
193 {
194         return _year == other._year && _month == other._month && _day == other._day &&
195                 _hour == other._hour && _second == other._second && _millisecond == other._millisecond &&
196                 _tz_hour == other._tz_hour && _tz_minute == other._tz_minute;
197 }
198
199 bool
200 LocalTime::operator!= (LocalTime const & other) const
201 {
202         return !(*this == other);
203 }
204
205 ostream&
206 dcp::operator<< (ostream& s, LocalTime const & t)
207 {
208         s << t.as_string ();
209         return s;
210 }