Add microsecond precision to RFC 3339 dates
[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})(.([0-9]+))?(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         unsigned us = 0;
123         const string &sec_frac = m[8].str;
124         if(!sec_frac.empty())
125         {
126                 us = lexical_cast<unsigned>(sec_frac);
127                 for(unsigned i=sec_frac.size(); i<6; ++i)
128                         us *= 10;
129                 for(unsigned i=sec_frac.size(); i>6; --i)
130                         us /= 10;
131         }
132
133         DateTime result = DateTime(year, month, mday, hr, minute, second, us);
134
135         int tzoff = 0;
136         if(m[9].str!="Z")
137         {
138                 tzoff = lexical_cast<unsigned>(m[10].str)*60+lexical_cast<unsigned>(m[11].str);
139                 if(m[9].str[0]=='-')
140                         tzoff = -tzoff;
141         }
142
143         result.set_timezone(tzoff);
144
145         return result;
146 }
147
148 void DateTime::add_days(int days)
149 {
150         int new_year = year;
151
152         /* Leap years have a 400 year cycle, so any 400 consecutive years have a
153         constant number of days (400*365+97 = 146097) */
154         new_year += days/146097*400;
155         days %= 146097;
156
157         if(days<0)
158         {
159                 new_year -= 400;
160                 days += 146097;
161         }
162
163         // Fudge factor for leap day
164         int fudge = (month<=2) ? 1 : 0;
165
166         // (Almost) every 4 year cycle has 1 leap year and 3 normal years
167         unsigned cycles = days/1461;
168         days %= 1461;
169
170         new_year += cycles*4;
171
172         // See how many non-leap-years we counted as leap years and reclaim the lost days
173         // XXX This breaks with negative years
174         unsigned missed_leap_days = ((year-fudge)%100+cycles*4)/100;
175         if((year-fudge)%400+cycles*4>=400)
176                 --missed_leap_days;
177
178         days += missed_leap_days;
179
180         // Count single years from the 4 year cycle
181         cycles = days/365;
182         days %= 365;
183
184         new_year += cycles;
185
186         if((year-fudge)%4+cycles>=4 && (new_year%100>=4 || new_year%400<4))
187         {
188                 // We passed a leap year - decrement days
189                 if(days==0)
190                 {
191                         days = is_leap_year(new_year-fudge) ? 365 : 364;
192                         --new_year;
193                 }
194                 else
195                         --days;
196         }
197
198         year = new_year;
199
200         // Step months
201         while(mday+days>month_days(year, month))
202         {
203                 days -= month_days(year, month);
204                 ++month;
205                 if(month>12)
206                 {
207                         ++year;
208                         month = 1;
209                 }
210         }
211
212         mday += days;
213 }
214
215 void DateTime::set_timezone(const TimeZone &tz)
216 {
217         zone = tz;
218 }
219
220 void DateTime::convert_timezone(const TimeZone &tz)
221 {
222         add_raw((tz.get_offset()-zone.get_offset()).raw());
223         zone = tz;
224 }
225
226 DateTime DateTime::operator+(const TimeDelta &td) const
227 {
228         DateTime dt(*this);
229         dt.add_raw(td.raw());
230         return dt;
231 }
232
233 DateTime &DateTime::operator+=(const TimeDelta &td)
234 {
235         add_raw(td.raw());
236         return *this;
237 }
238
239 DateTime DateTime::operator-(const TimeDelta &td) const
240 {
241         DateTime dt(*this);
242         dt.add_raw(-td.raw());
243         return dt;
244 }
245
246 DateTime &DateTime::operator-=(const TimeDelta &td)
247 {
248         add_raw(-td.raw());
249         return *this;
250 }
251
252 int DateTime::cmp(const DateTime &dt) const
253 {
254         if(int c = cmp_(year, dt.year))
255                 return c;
256         if(int c = cmp_(month, dt.month))
257                 return c;
258         if(int c = cmp_(mday, dt.mday))
259                 return c;
260         if(int c = cmp_(hour, dt.hour))
261                 return c;
262         if(int c = cmp_(minute, dt.minute))
263                 return c;
264         if(int c = cmp_(second, dt.second))
265                 return c;
266         if(int c = cmp_(usec, dt.usec))
267                 return c;
268         return 0;
269 }
270
271 TimeStamp DateTime::get_timestamp() const
272 {
273         if(year<-289701 || year>293641)
274                 throw range_error("DateTime::get_timestamp");
275
276         RawTime raw = (((hour*60LL)+minute)*60+second)*1000000+usec;
277         int days = (year-1970)*365;
278         days += (year-1)/4-(year-1)/100+(year-1)/400-477;
279         for(unsigned i=1; i<month; ++i)
280                 days += month_days(year, i);
281         days += mday-1;
282
283         raw += days*86400000000LL;
284
285         return TimeStamp(raw);
286 }
287
288 string DateTime::format(const string &fmt) const
289 {
290         string result;
291         for(string::const_iterator i=fmt.begin(); i!=fmt.end(); ++i)
292         {
293                 if(*i=='%')
294                 {
295                         ++i;
296                         if(i==fmt.end())
297                                 break;
298                         else if(*i=='d')
299                                 result += Msp::format("%02d", mday);
300                         else if(*i=='H')
301                                 result += Msp::format("%02d", hour);
302                         else if(*i=='I')
303                                 result += Msp::format("%02d", hour%12);
304                         else if(*i=='m')
305                                 result += Msp::format("%02d", month);
306                         else if(*i=='M')
307                                 result += Msp::format("%02d", minute);
308                         else if(*i=='p')
309                                 result += ((hour>=12) ? "PM" : "AM");
310                         else if(*i=='S')
311                                 result += Msp::format("%02d", second);
312                         else if(*i=='y')
313                                 result += Msp::format("%02d", year%100);
314                         else if(*i=='Y')
315                                 result += Msp::format("%04d", year);
316                         else if(*i=='%')
317                                 result += '%';
318                 }
319                 else
320                         result += *i;
321         }
322
323         return result;
324 }
325
326 string DateTime::format_rfc3339() const
327 {
328         string result = format("%Y-%m-%dT%H:%M:%S");
329         if(usec)
330                 result += Msp::format(".%06d", usec);
331         if(const TimeDelta &offs = zone.get_offset())
332         {
333                 int m = abs(static_cast<int>(offs/Time::min));
334                 result += Msp::format("%c%02d:%02d", (offs<zero ? '-' : '+'), m/60, m%60);
335         }
336         else
337                 result += 'Z';
338         return result;
339 }
340
341 void DateTime::add_raw(RawTime raw)
342 {
343         int days = static_cast<int>(raw/86400000000LL);
344         raw %= 86400000000LL;
345         if(raw<0)
346         {
347                 --days;
348                 raw += 86400000000LL;
349         }
350
351         usec += raw%1000000; raw /= 1000000;
352         second += raw%60;    raw /= 60;
353         minute += raw%60;    raw /= 60;
354         hour += raw%24;      raw /= 24;
355
356         add_days(days);
357         normalize();
358 }
359
360 void DateTime::normalize()
361 {
362         second += usec/1000000;
363         usec %= 1000000;
364         minute += second/60;
365         second %= 60;
366         hour += minute/60;
367         minute %= 60;
368         mday += hour/24;
369         hour %= 24;
370         while(mday>month_days(year, month))
371         {
372                 mday -= month_days(year, month);
373                 ++month;
374                 if(month>12)
375                 {
376                         ++year;
377                         month = 1;
378                 }
379         }
380 }
381
382 } // namespace Time
383 } // namespace Msp