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