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