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