]> git.tdb.fi Git - libs/core.git/blob - source/time/datetime.cpp
Add move semantics to Variant
[libs/core.git] / source / time / datetime.cpp
1 #include <cstdlib>
2 #include <stdexcept>
3 #include <msp/strings/format.h>
4 #include <msp/strings/regex.h>
5 #include "datetime.h"
6 #include "timestamp.h"
7
8 using namespace std;
9
10 namespace {
11
12 inline bool is_leap_year(int y)
13 { return y%4==0 && (y%100 || y%400==0); }
14
15 inline unsigned char month_days(int y, unsigned char m)
16 {
17         switch(m)
18         {
19         case 4:
20         case 6:
21         case 9:
22         case 11:
23                 return 30;
24         case 2:
25                 return is_leap_year(y)?29:28;
26         default:
27                 return 31;
28         }
29 }
30
31 template<typename T>
32 inline int _cmp(T a, T b)
33 {
34         if(a<b)
35                 return -1;
36         if(a>b)
37                 return 1;
38         return 0;
39 }
40
41 }
42
43 namespace Msp {
44 namespace Time {
45
46 DateTime::DateTime(const TimeStamp &ts)
47 {
48         add_raw(ts.raw());
49 }
50
51 DateTime::DateTime(const TimeStamp &ts, const TimeZone &tz):
52         DateTime(ts)
53 {
54         convert_timezone(tz);
55 }
56
57 DateTime::DateTime(int y, unsigned char m, unsigned char d):
58         DateTime(y, m, d, 0, 0, 0, 0)
59 { }
60
61 DateTime::DateTime(int y, unsigned char m, unsigned char d, unsigned char h, unsigned char n, unsigned char s):
62         DateTime(y, m, d, h, n, s, 0)
63 { }
64
65 DateTime::DateTime(int y, unsigned char m, unsigned char d, unsigned char h, unsigned char n, unsigned char s, unsigned u):
66         year(y),
67         month(m),
68         mday(d),
69         hour(h),
70         minute(n),
71         second(s),
72         usec(u)
73 {
74         if(usec>=1000000)
75                 throw out_of_range("DateTime::DateTime usec");
76         if(second>=60)
77                 throw out_of_range("DateTime::DateTime second");
78         if(minute>=60)
79                 throw out_of_range("DateTime::DateTime minute");
80         if(hour>=24)
81                 throw out_of_range("DateTime::DateTime hour");
82         if(month<1 || month>12)
83                 throw out_of_range("DateTime::DateTime month");
84         if(mday<1 || mday>month_days(year, month))
85                 throw out_of_range("DateTime::DateTime mday");
86 }
87
88 DateTime DateTime::parse_rfc3339(const string &str)
89 {
90         static Regex re("^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(.([0-9]+))?(Z|[-+]([0-9]{2}):([0-9]{2}))$");
91
92         RegMatch m = re.match(str);
93         if(!m)
94                 throw invalid_argument("DateTime::parse_rfc3339");
95
96         unsigned year = lexical_cast<unsigned>(m[1].str);
97         unsigned month = lexical_cast<unsigned>(m[2].str);
98         unsigned mday = lexical_cast<unsigned>(m[3].str);
99         unsigned hr = lexical_cast<unsigned>(m[4].str);
100         unsigned minute = lexical_cast<unsigned>(m[5].str);
101         unsigned second = lexical_cast<unsigned>(m[6].str);
102         unsigned us = 0;
103         const string &sec_frac = m[8].str;
104         if(!sec_frac.empty())
105         {
106                 us = lexical_cast<unsigned>(sec_frac);
107                 for(unsigned i=sec_frac.size(); i<6; ++i)
108                         us *= 10;
109                 for(unsigned i=sec_frac.size(); i>6; --i)
110                         us /= 10;
111         }
112
113         DateTime result = DateTime(year, month, mday, hr, minute, second, us);
114
115         int tzoff = 0;
116         if(m[9].str!="Z")
117         {
118                 tzoff = lexical_cast<unsigned>(m[10].str)*60+lexical_cast<unsigned>(m[11].str);
119                 if(m[9].str[0]=='-')
120                         tzoff = -tzoff;
121         }
122
123         result.set_timezone(tzoff);
124
125         return result;
126 }
127
128 void DateTime::add_days(int days)
129 {
130         int new_year = year;
131
132         /* Leap years have a 400 year cycle, so any 400 consecutive years have a
133         constant number of days (400*365+97 = 146097) */
134         new_year += days/146097*400;
135         days %= 146097;
136
137         if(days<0)
138         {
139                 new_year -= 400;
140                 days += 146097;
141         }
142
143         // Fudge factor for leap day
144         int fudge = (month<=2) ? 1 : 0;
145
146         // (Almost) every 4 year cycle has 1 leap year and 3 normal years
147         unsigned cycles = days/1461;
148         days %= 1461;
149
150         new_year += cycles*4;
151
152         // See how many non-leap-years we counted as leap years and reclaim the lost days
153         // XXX This breaks with negative years
154         unsigned missed_leap_days = ((year-fudge)%100+cycles*4)/100;
155         if((year-fudge)%400+cycles*4>=400)
156                 --missed_leap_days;
157
158         days += missed_leap_days;
159
160         // Count single years from the 4 year cycle
161         cycles = days/365;
162         days %= 365;
163
164         new_year += cycles;
165
166         if((year-fudge)%4+cycles>=4 && (new_year%100>=4 || new_year%400<4))
167         {
168                 // We passed a leap year - decrement days
169                 if(days==0)
170                 {
171                         days = is_leap_year(new_year-fudge) ? 365 : 364;
172                         --new_year;
173                 }
174                 else
175                         --days;
176         }
177
178         year = new_year;
179
180         // Step months
181         while(mday+days>month_days(year, month))
182         {
183                 days -= month_days(year, month);
184                 ++month;
185                 if(month>12)
186                 {
187                         ++year;
188                         month = 1;
189                 }
190         }
191
192         mday += days;
193 }
194
195 void DateTime::set_timezone(const TimeZone &tz)
196 {
197         zone = tz;
198 }
199
200 void DateTime::convert_timezone(const TimeZone &tz)
201 {
202         add_raw((tz.get_offset()-zone.get_offset()).raw());
203         zone = tz;
204 }
205
206 DateTime DateTime::operator+(const TimeDelta &td) const
207 {
208         DateTime dt(*this);
209         dt.add_raw(td.raw());
210         return dt;
211 }
212
213 DateTime &DateTime::operator+=(const TimeDelta &td)
214 {
215         add_raw(td.raw());
216         return *this;
217 }
218
219 DateTime DateTime::operator-(const TimeDelta &td) const
220 {
221         DateTime dt(*this);
222         dt.add_raw(-td.raw());
223         return dt;
224 }
225
226 DateTime &DateTime::operator-=(const TimeDelta &td)
227 {
228         add_raw(-td.raw());
229         return *this;
230 }
231
232 int DateTime::cmp(const DateTime &dt) const
233 {
234         if(int c = _cmp(year, dt.year))
235                 return c;
236         if(int c = _cmp(month, dt.month))
237                 return c;
238         if(int c = _cmp(mday, dt.mday))
239                 return c;
240         if(int c = _cmp(hour, dt.hour))
241                 return c;
242         if(int c = _cmp(minute, dt.minute))
243                 return c;
244         if(int c = _cmp(second, dt.second))
245                 return c;
246         if(int c = _cmp(usec, dt.usec))
247                 return c;
248         return 0;
249 }
250
251 TimeStamp DateTime::get_timestamp() const
252 {
253         if(year<-289701 || year>293641)
254                 throw range_error("DateTime::get_timestamp");
255
256         RawTime raw = (((hour*60LL)+minute)*60+second)*1000000+usec;
257         int days = (year-1970)*365;
258         days += (year-1)/4-(year-1)/100+(year-1)/400-477;
259         for(unsigned i=1; i<month; ++i)
260                 days += month_days(year, i);
261         days += mday-1;
262
263         raw += days*86400000000LL;
264
265         return TimeStamp(raw);
266 }
267
268 string DateTime::format(const string &fmt) const
269 {
270         string result;
271         for(auto i=fmt.begin(); i!=fmt.end(); ++i)
272         {
273                 if(*i=='%')
274                 {
275                         ++i;
276                         if(i==fmt.end())
277                                 break;
278                         else if(*i=='d')
279                                 result += Msp::format("%02d", mday);
280                         else if(*i=='H')
281                                 result += Msp::format("%02d", hour);
282                         else if(*i=='I')
283                                 result += Msp::format("%02d", hour%12);
284                         else if(*i=='m')
285                                 result += Msp::format("%02d", month);
286                         else if(*i=='M')
287                                 result += Msp::format("%02d", minute);
288                         else if(*i=='p')
289                                 result += ((hour>=12) ? "PM" : "AM");
290                         else if(*i=='S')
291                                 result += Msp::format("%02d", second);
292                         else if(*i=='y')
293                                 result += Msp::format("%02d", year%100);
294                         else if(*i=='Y')
295                                 result += Msp::format("%04d", year);
296                         else if(*i=='%')
297                                 result += '%';
298                 }
299                 else
300                         result += *i;
301         }
302
303         return result;
304 }
305
306 string DateTime::format_rfc3339() const
307 {
308         string result = format("%Y-%m-%dT%H:%M:%S");
309         if(usec)
310                 result += Msp::format(".%06d", usec);
311         if(const TimeDelta &offs = zone.get_offset())
312         {
313                 int m = abs(static_cast<int>(offs/Time::min));
314                 result += Msp::format("%c%02d:%02d", (offs<zero ? '-' : '+'), m/60, m%60);
315         }
316         else
317                 result += 'Z';
318         return result;
319 }
320
321 void DateTime::add_raw(RawTime raw)
322 {
323         int days = static_cast<int>(raw/86400000000LL);
324         raw %= 86400000000LL;
325         if(raw<0)
326         {
327                 --days;
328                 raw += 86400000000LL;
329         }
330
331         usec += raw%1000000; raw /= 1000000;
332         second += raw%60;    raw /= 60;
333         minute += raw%60;    raw /= 60;
334         hour += raw%24;      raw /= 24;
335
336         add_days(days);
337         normalize();
338 }
339
340 void DateTime::normalize()
341 {
342         second += usec/1000000;
343         usec %= 1000000;
344         minute += second/60;
345         second %= 60;
346         hour += minute/60;
347         minute %= 60;
348         mday += hour/24;
349         hour %= 24;
350         while(mday>month_days(year, month))
351         {
352                 mday -= month_days(year, month);
353                 ++month;
354                 if(month>12)
355                 {
356                         ++year;
357                         month = 1;
358                 }
359         }
360 }
361
362 } // namespace Time
363 } // namespace Msp