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