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