Line data Source code
1 : /********************************************************************\
2 : * gnc-datetime.cpp -- Date and Time classes for GnuCash *
3 : * *
4 : * Copyright 2015 John Ralls <jralls@ceridwen.us> *
5 : * *
6 : * This program is free software; you can redistribute it and/or *
7 : * modify it under the terms of the GNU General Public License as *
8 : * published by the Free Software Foundation; either version 2 of *
9 : * the License, or (at your option) any later version. *
10 : * *
11 : * This program 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 this program; if not, contact: *
18 : * *
19 : * Free Software Foundation Voice: +1-617-542-5942 *
20 : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21 : * Boston, MA 02110-1301, USA gnu@gnu.org *
22 : * *
23 : \********************************************************************/
24 :
25 : #ifndef __GNC_DATETIME_HPP__
26 : #define __GNC_DATETIME_HPP__
27 :
28 : #include <cstdint>
29 : #include <memory>
30 : #include <string>
31 : #include <vector>
32 : #include <functional>
33 : #include <optional>
34 :
35 : #include <boost/date_time/gregorian/gregorian.hpp>
36 : #include <boost/regex.hpp>
37 :
38 : typedef struct
39 : {
40 : int year; //1400-9999
41 : int month; //1-12
42 : int day; //1-31
43 : } gnc_ymd;
44 :
45 : enum class DayPart {
46 : start, // 00:00 local
47 : neutral, // 10:59 UTC
48 : end, // 23:59 local
49 : };
50 :
51 : class GncDateTimeImpl;
52 : class GncDateImpl;
53 : class GncDate;
54 : using time64 = int64_t;
55 : constexpr const time64 MINTIME = -17987443200;
56 : constexpr const time64 MAXTIME = 253402214400;
57 :
58 : /** GnuCash DateTime class
59 : *
60 : * Represents local time in the current timezone.
61 : * As with GncDate, the represented time is limited to the period
62 : * between 1400 and 9999 CE.
63 : *
64 : * Be careful when using times: A particular time is represented
65 : * differently depending on the timezone, which can shift the displayed
66 : * date. Accounting is generally not sensitive to the time of day, but
67 : * is sensitive to the recorded day. Since GncDates are not timezone
68 : * dependent they should be preferred for accounting entries.
69 : */
70 :
71 : class GncDateTime
72 : {
73 : public:
74 : /** Construct a GncDateTime representing the current time in the
75 : * current timezone.
76 : */
77 : GncDateTime();
78 : /** Construct a GncDateTime in the current timezone representing the
79 : * timestamp as seconds from the POSIX epoch (1970-01-01T00:00:00UTC).
80 : * @param time Seconds from the POSIX epoch.
81 : * @exception std::invalid_argument if the year is outside the constraints.
82 : */
83 : GncDateTime(const time64 time);
84 : /** Construct a GncDateTime in the current timezone representing the
85 : * standard struct tm provided.
86 : * @param tm A C-standard struct tm representing the date and
87 : * time. Note that the timezone and offset are ignored on those
88 : * systems which include them in struct tm.
89 : * @exception std::invalid_argument if the year is outside the constraints.
90 : */
91 : GncDateTime(const struct tm tm);
92 : /** Construct a GncDateTime from a GncDate. As a GncDate doesn't contain time
93 : * information, the time will be set depending on the second parameter
94 : * to start of day, neutral or end of day.
95 : * @param date A GncDate representing a date.
96 : * @param part An optinoal DayPart indicating which time to use in the conversion.
97 : * This can be "DayPart::start" for start of day (00:00 local time),
98 : * "DayPart::neutral" for a neutral time (10:59 UTC, chosen to have the
99 : * least chance of date changes when crossing timezone borders),
100 : * "DayPart::end" for end of day (23:59 local time).
101 : * If omitted part defaults to DayPart::neutral.
102 : * Note the different timezone used for DayPart::neutral compared to the other
103 : * two options!
104 : * @exception std::invalid_argument if the year is outside the constraints.
105 : */
106 : GncDateTime(const GncDate& date, DayPart part = DayPart::neutral);
107 : /** Construct a GncDateTime
108 : * @param str A string representing the date and time in some
109 : * recognizable format. Note that if a timezone is not specified the
110 : * default is UTC, not the local one.
111 : * @exception std::invalid_argument if the year is outside the constraints.
112 : */
113 : GncDateTime(const std::string& str);
114 : GncDateTime(const char* str);
115 : ~GncDateTime();
116 : /** Set the GncDateTime to the date and time indicated in the computer's clock.
117 : */
118 : void now();
119 : /** Cast the GncDateTime to a time64, seconds from the POSIX epoch. */
120 : explicit operator time64() const;
121 : /** Cast the GncDateTime to a struct tm. Timezone field isn't filled.
122 : */
123 : explicit operator struct tm() const;
124 : /** Obtain the UTC offset in seconds
125 : * @return seconds difference between this local time and UTC. West
126 : * is negative.
127 : */
128 : long offset()const;
129 : /** Obtain a struct tm representing the time in UTC.
130 : * @return struct tm
131 : */
132 : struct tm utc_tm() const;
133 : /** Obtain the date from the time, as a GncDate, in the current timezone.
134 : * @return GncDate represented by the GncDateTime.
135 : */
136 : GncDate date() const;
137 : /** Test if the GncDateTime has a member pointer. Testing only. */
138 : bool isnull (void) { return m_impl == nullptr; }
139 : /** Format the GncDateTime into a std::string
140 : * @param format A cstr describing the way the date and time are
141 : * presented. Code letters preceded with % stand in for arguments;
142 : * most are the same as described in strftime(3), but there are a few
143 : * differences. Consult the boost::date_time documentation.
144 : * @return a std::string containing a representation of the date and time in
145 : * the locale's time zone according to the format.
146 : */
147 : std::string format(const char* format) const;
148 : /** Format the GncDateTime into a std::string in GMT
149 : * @param format A cstr describing the way the date and time are
150 : * presented. Code letters preceded with % stand in for arguments;
151 : * most are the same as described in strftime(3), but there are a few
152 : * differences. Consult the boost::date_time documentation.
153 : * @return a std::string containing a representation of the date and time in
154 : * GMT (timezone Z) according to the format.
155 : */
156 : std::string format_zulu(const char* format) const;
157 : /** Format the GncDateTime into a gnucash-style iso8601 string in UTC.
158 : * @return a std::string in the format YYYY-MM-DD HH:MM:SS.
159 : */
160 : std::string format_iso8601() const;
161 : /** Get an undelimited string representing the current date and time.
162 : * @return a std::string in the format YYYYMMDDHHMMSS.
163 : */
164 : static std::string timestamp();
165 :
166 : private:
167 : std::unique_ptr<GncDateTimeImpl> m_impl;
168 : };
169 :
170 : /** GnuCash DateFormat class
171 : *
172 : * A helper class to represent a date format understood
173 : * by the GncDate string/format constructor. Consumers
174 : * of this header file are not supposed to create
175 : * objects of this class themselves. Instead they
176 : * can get a list of the understood formats from the
177 : * GncDate::c_formats class variable and work with those.
178 : */
179 :
180 : using StringToDate = std::function<boost::gregorian::date(const std::string&)>;
181 :
182 : class GncDateFormat
183 : {
184 : public:
185 : /** Construct a GncDateFormat with a given format and corresponding
186 : * regular expression. This should only be used internally by the
187 : * GncDate implementation. Consumers should never construct a GncDateFormat
188 : * themselves!
189 : */
190 238 : GncDateFormat (const char* fmt, const char* re) :
191 714 : m_fmt(fmt), m_re(re) {}
192 357 : GncDateFormat (const char* fmt, StringToDate str_to_date, const char* re) :
193 1071 : m_fmt(fmt), m_re(re), m_str_to_date(str_to_date) {}
194 119 : GncDateFormat (const char* fmt, StringToDate str_to_date) :
195 357 : m_fmt(fmt), m_str_to_date(str_to_date) {}
196 : /** A string representing the format. */
197 : const std::string m_fmt;
198 : private:
199 : /** Regular expression associated with the format string. This is to and
200 : * only be used internally by the gnc-datetime code.
201 : */
202 : boost::regex m_re;
203 : std::optional<StringToDate> m_str_to_date;
204 :
205 : friend class GncDateImpl;
206 : };
207 :
208 : /** GnuCash Date class
209 : *
210 : * The represented date is limited to the period
211 : * between 1400 and 9999 CE.
212 : */
213 :
214 : class GncDate
215 : {
216 : public:
217 : /** A vector with all the date formats supported by the string constructor.
218 : * The currently supported formats are:
219 : * "y-m-d" (including yyyymmdd)
220 : * "d-m-y" (including ddmmyyyy)
221 : * "m-d-y" (including mmddyyyy)
222 : * "d-m" (including ddmm)
223 : * "m-d" (including mmdd)
224 : *
225 : * Notes:
226 : * - while the format names are using a "-" as separator, the
227 : * regexes will accept any of "-/.' " and will also work for dates
228 : * without separators.
229 : * - the format strings are marked for translation so it is possible
230 : * to use a localized version of a format string using gettext. Example:
231 : * gettext(GncDate::c_formats[0])
232 : */
233 : static const std::vector<GncDateFormat> c_formats;
234 : /** Construct a GncDate representing the current day.
235 : */
236 : GncDate();
237 : /** Construct a GncDate representing the given year, month, and day in
238 : * the proleptic Gregorian calendar.
239 : *
240 : * Years are constrained to be from 1400 - 9999 CE inclusive. Dates
241 : * will be normalized if the day or month values are outside of the
242 : * normal ranges. e.g. 1994, -3, 47 will be normalized to 1993-10-17.
243 : *
244 : * @param year The year in the Common Era.
245 : * @param month The month, where 1 is January and 12 is December.
246 : * @param day The day of the month, beginning with 1.
247 : * @exception std::invalid_argument if the calculated year is outside
248 : * of the constrained range.
249 : */
250 : GncDate(int year, int month, int day);
251 : /** Construct a GncDate by parsing a string assumed to be in the format
252 : * passed in.
253 : *
254 : * The currently recognized formats are d-m-y, m-d-y, y-m-d, m-d, d-m.
255 : * Note while the format descriptions use "-" as separator any of
256 : * "-" (hyphen), "/" (slash), "'" (single quote), " " (space) or
257 : * "." will be accepted.
258 : *
259 : * @param str The string to be interpreted.
260 : * @param fmt The expected date format of the string passed in.
261 : * @exception std::invalid_argument if
262 : * - the string couldn't be parsed using the provided format
263 : * - any of the date components is outside of its limit
264 : * (like month being 13, or day being 31 in February)
265 : * - fmt doesn't specify a year, yet a year was found in the string
266 : */
267 : GncDate(const std::string str, const std::string fmt);
268 : /** Construct a GncDate from a GncDateImpl.
269 : */
270 : GncDate(std::unique_ptr<GncDateImpl> impl);
271 : /** Copy constructor.
272 : */
273 : GncDate(const GncDate&);
274 : /** Move constructor.
275 : */
276 : GncDate(GncDate&&);
277 : /** Default destructor.
278 : */
279 : ~GncDate();
280 : /** Copy assignment operator.
281 : */
282 : GncDate& operator=(const GncDate&);
283 : /** Move assignment operator.
284 : */
285 : GncDate& operator=(GncDate&&);
286 : /** Set the date object to the computer clock's current day. */
287 : void today();
288 : /** Get the year, month, and day from the date as a gnc_ymd.
289 : * @return gnc_ymd struct
290 : */
291 : gnc_ymd year_month_day() const;
292 : /** Format the GncDate into a std::string
293 : * @param format A cstr describing the way the date and time are
294 : * presented. Code letters preceded with % stand in for arguments;
295 : * most are the same as described in strftime(3), but there are a few
296 : * differences. Consult the boost::date_time documentation.
297 : * @return a std::string containing a representation of the date
298 : * according to the format.
299 : */
300 : std::string format(const char* format);
301 : /** Test that the Date has an implementation. */
302 : bool isnull (void) { return m_impl == nullptr; }
303 :
304 : private:
305 : std::unique_ptr<GncDateImpl> m_impl;
306 :
307 : friend GncDateTime::GncDateTime(const GncDate&, DayPart);
308 : friend bool operator<(const GncDate&, const GncDate&);
309 : friend bool operator>(const GncDate&, const GncDate&);
310 : friend bool operator==(const GncDate&, const GncDate&);
311 : friend bool operator<=(const GncDate&, const GncDate&);
312 : friend bool operator>=(const GncDate&, const GncDate&);
313 : friend bool operator!=(const GncDate&, const GncDate&);
314 : };
315 :
316 : /**@{
317 : * Standard comparison operators working on GncDate objects.
318 : */
319 : bool operator<(const GncDate& a, const GncDate& b);
320 : bool operator>(const GncDate& a, const GncDate& b);
321 : bool operator==(const GncDate& a, const GncDate& b);
322 : bool operator<=(const GncDate& a, const GncDate& b);
323 : bool operator>=(const GncDate& a, const GncDate& b);
324 : bool operator!=(const GncDate& a, const GncDate& b);
325 : /**@}*/
326 :
327 : #endif // __GNC_DATETIME_HPP__
|