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