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