ed7c45834f364c31f314f80ed54ae4923a576e03
[feisty_meow.git] / nucleus / library / timely / earth_time.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : earth_time                                                        *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 1999-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 \*****************************************************************************/
14
15 #include "earth_time.h"
16
17 #include <basis/astring.h>
18 #include <basis/utf_conversion.h>
19
20 #include <time.h>
21 #if defined(__WIN32__) || defined(__UNIX__)
22   #include <sys/timeb.h>
23 #endif
24
25 using namespace basis;
26 using namespace structures;
27
28 namespace timely {
29
30 const int days_in_month[12]
31     = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
32
33 const int leap_days_in_month[12]
34     = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
35
36 const int julian_days_in_month[12]
37     = { 31, 29, 31, 30, 31, 30, 31, 30, 31, 30, 31, 30 };
38 //hmmm: is this right?
39
40 const int julian_leap_days_in_month[12]
41     = { 31, 30, 31, 30, 31, 30, 31, 30, 31, 30, 31, 30 };
42
43 //////////////
44
45 void clock_time::pack(byte_array &packed_form) const
46 {
47   attach(packed_form, hour);
48   attach(packed_form, minute);
49   attach(packed_form, second);
50   attach(packed_form, millisecond);
51   attach(packed_form, microsecond);
52 }
53
54 bool clock_time::unpack(byte_array &packed_form)
55 {
56   if (!detach(packed_form, hour)) return false;
57   if (!detach(packed_form, minute)) return false;
58   if (!detach(packed_form, second)) return false;
59   if (!detach(packed_form, millisecond)) return false;
60   if (!detach(packed_form, microsecond)) return false;
61   return true;
62 }
63
64 #define EASY_LT(x, y) \
65   if (x < y) return true; \
66   if (x > y) return false
67
68 bool clock_time::operator < (const clock_time &to_compare) const
69 {
70   EASY_LT(hour, to_compare.hour);
71   EASY_LT(minute, to_compare.minute);
72   EASY_LT(second, to_compare.second);
73   EASY_LT(millisecond, to_compare.millisecond);
74   EASY_LT(microsecond, to_compare.microsecond);
75   return false;
76 }
77
78 bool clock_time::operator == (const clock_time &to_compare) const
79 {
80   return (hour == to_compare.hour) 
81       && (minute == to_compare.minute)
82       && (second == to_compare.second) 
83       && (millisecond == to_compare.millisecond) 
84       && (microsecond == to_compare.microsecond);
85 }
86
87 astring clock_time::text_form(int how) const
88 {
89   astring to_return;
90   text_form(to_return, how);
91   return to_return;
92 }
93
94 void clock_time::text_form(astring &to_return, int how) const
95 {
96   if (!how) return;  // enforce use of the default.
97   if (how & MILITARY)
98     to_return += a_sprintf("%02d:%02d", hour, minute);
99   else {
100     int uhr = hour;
101     if (uhr > 12) uhr -= 12;
102     to_return += a_sprintf("%2d:%02d", uhr, minute);
103   }
104   if ( (how & SECONDS) || (how & MILLISECONDS) )
105     to_return += a_sprintf(":%02d", second);
106   if (how & MILLISECONDS)
107     to_return += a_sprintf(":%03d", millisecond);
108   if (how & MERIDIAN) {
109     if (hour >= 12) to_return += "PM";
110     else to_return += "AM";
111   }
112 }
113
114 // makes sure that "val" is not larger than "max".  if it is, then max is
115 // used as a divisor and stored in "rolls".
116 #define limit_value(val, max) \
117   if (val < 0) { \
118     rolls = val / max; \
119     rolls--; /* subtract an extra one since we definitely roll before -max */ \
120     val += max * -rolls; \
121   } else if (val >= max) { \
122     rolls = val / max; \
123     val -= max * rolls; \
124   } else { rolls = 0; }
125
126 int clock_time::normalize(clock_time &to_fix)
127 {
128   int rolls = 0;  // rollover counter.
129   limit_value(to_fix.microsecond, 1000);
130   to_fix.millisecond += rolls;
131   limit_value(to_fix.millisecond, 1000);
132   to_fix.second += rolls;
133   limit_value(to_fix.second, 60);
134   to_fix.minute += rolls;
135   limit_value(to_fix.minute, 60);
136   to_fix.hour += rolls;
137   limit_value(to_fix.hour, 24);
138   return rolls;
139 }
140
141 //////////////
142
143 void day_in_year::pack(byte_array &packed_form) const
144 {
145   attach(packed_form, day_of_year);
146   attach(packed_form, abyte(day_of_week));
147   attach(packed_form, abyte(month));
148   attach(packed_form, day_in_month);
149   attach(packed_form, abyte(1));
150     // still packing dst chunk; must for backward compatibility.
151 }
152
153 bool day_in_year::unpack(byte_array &packed_form)
154 {
155   if (!detach(packed_form, day_of_year)) return false;
156   abyte temp;
157   if (!detach(packed_form, temp)) return false;
158   day_of_week = days(temp);
159   if (!detach(packed_form, temp)) return false;
160   month = months(temp);
161   if (!detach(packed_form, day_in_month)) return false;
162   if (!detach(packed_form, temp)) return false;  // dst chunk--backward compat.
163   return true;
164 }
165
166 bool day_in_year::operator < (const day_in_year &to_compare) const
167 {
168   EASY_LT(month, to_compare.month);
169   EASY_LT(day_in_month, to_compare.day_in_month);
170   return false;
171 }
172
173 bool day_in_year::operator == (const day_in_year &to_compare) const
174 {
175   return (month == to_compare.month)
176       && (day_in_month == to_compare.day_in_month);
177 }
178
179 astring day_in_year::text_form(int how) const
180 {
181   astring to_return;
182   text_form(to_return, how);
183   return to_return;
184 }
185
186 void day_in_year::text_form(astring &to_stuff, int how) const
187 {
188   if (!how) return;  // enforce use of the default.
189   if (how & INCLUDE_DAY) to_stuff += astring(day_name(day_of_week)) + " ";
190   const char *monat = short_month_name(month);
191   if (how & LONG_MONTH)
192     monat = month_name(month);
193 //hmmm: more formatting, like euro?
194   to_stuff += monat;
195   to_stuff += a_sprintf(" %02d", day_in_month);
196 }
197
198 // note: this only works when adjusting across one month, not multiples.
199 int limit_day_of_month(int &day, int days_in_month, int days_in_prev_month)
200 {
201   if (day > days_in_month) {
202     day -= days_in_month;
203     return 1;  // forward rollover.
204   } else if (day < 1) {
205     day += days_in_prev_month;
206     return -1;
207   }
208   return 0;  // no rolling.
209 }
210
211 int day_in_year::normalize(day_in_year &to_fix, bool leap_year)
212 {
213   int rolls = 0;  // rollover counter.
214   int daysinm = leap_year?
215       leap_days_in_month[to_fix.month] : days_in_month[to_fix.month];
216   int prev_month = to_fix.month - 1;
217   if (prev_month < 0) prev_month = 11;
218   int daysinpm = leap_year?
219       leap_days_in_month[prev_month] : days_in_month[prev_month];
220   rolls = limit_day_of_month(to_fix.day_in_month, daysinm, daysinpm);
221   int monat = to_fix.month + rolls;
222   limit_value(monat, 12);  // months are zero based.
223   to_fix.month = months(monat);
224   return rolls;
225 }
226
227 //////////////
228
229 void time_locus::pack(byte_array &packed_form) const
230 {
231   attach(packed_form, year);
232   clock_time::pack(packed_form);
233   day_in_year::pack(packed_form);
234 }
235
236 bool time_locus::unpack(byte_array &packed_form)
237 {
238   if (!detach(packed_form, year)) return false;
239   if (!clock_time::unpack(packed_form)) return false;
240   if (!day_in_year::unpack(packed_form)) return false;
241   return true;
242 }
243
244 astring time_locus::text_form_long(int t, int d, int y) const
245 {
246   astring to_return;
247   text_form_long(to_return, t, d, y);
248   return to_return;
249 }
250
251 bool time_locus::equal_to(const equalizable &s2) const {
252   const time_locus *s2_cast = dynamic_cast<const time_locus *>(&s2);
253   if (!s2_cast) throw "error: time_locus::==: unknown type";
254   return (year == s2_cast->year)
255       && ( (const day_in_year &) *this == *s2_cast)
256       && ( (const clock_time &) *this == *s2_cast);
257 }
258
259 bool time_locus::less_than(const orderable &s2) const {
260   const time_locus *s2_cast = dynamic_cast<const time_locus *>(&s2);
261   if (!s2_cast) throw "error: time_locus::<: unknown type";
262   EASY_LT(year, s2_cast->year);
263   if (day_in_year::operator < (*s2_cast)) return true;
264   if (!(day_in_year::operator == (*s2_cast))) return false;
265   if (clock_time::operator < (*s2_cast)) return true;
266   return false;
267 }
268
269 void time_locus::text_form_long(astring &to_stuff, int t, int d, int y) const
270 {
271 //hmmm: more formatting desired, like european.
272   if (!y) {
273     text_form_long(to_stuff, t, d);  // enforce use of the default.
274     return;
275   }
276   // add the day.
277   day_in_year::text_form(to_stuff, d);
278   to_stuff += " ";
279   // add the year.
280   if (y & SHORT_YEAR)
281     to_stuff += a_sprintf("%2d", year % 100);
282   else
283     to_stuff += a_sprintf("%4d", year);
284   // add the time.
285   to_stuff += " ";
286   clock_time::text_form(to_stuff, t);
287 }
288
289 int time_locus::normalize(time_locus &to_fix)
290 {
291   int rolls = clock_time::normalize(to_fix);
292   to_fix.day_in_month += rolls;
293
294 //hmmm: this little gem should be abstracted to a method.
295   bool leaping = !(to_fix.year % 4);
296   if (!(to_fix.year % 100)) leaping = false;
297   if (!(to_fix.year % 400)) leaping = true;
298
299   rolls = day_in_year::normalize(to_fix, leaping);
300   to_fix.year += rolls;
301   return 0;
302     // is that always right?  not for underflow.
303 //hmmm: resolve the issue of rollovers here.
304 }
305
306 //////////////
307
308 time_locus convert(const tm &to_convert, int ms)
309 {
310   time_locus r;
311
312   // we lack the resolution for this, currently.
313   r.microsecond = 0;
314
315   r.second = to_convert.tm_sec;
316   r.minute = to_convert.tm_min;
317   r.hour = to_convert.tm_hour;
318   r.day_in_month = to_convert.tm_mday;
319   r.month = months(to_convert.tm_mon);
320   r.year = to_convert.tm_year + 1900;
321   r.day_of_week = days(to_convert.tm_wday);
322   r.day_of_year = to_convert.tm_yday;
323   r.millisecond = ms;
324   return r;
325 }
326
327 time_locus now()
328 {
329   timeb current;
330   ftime(&current);
331   tm split_time(*localtime(&current.time));
332   return convert(split_time, current.millitm);
333 }
334
335 time_locus greenwich_now()
336 {
337   timeb current;
338   ftime(&current);
339   tm split_time(*gmtime(&current.time));
340   return convert(split_time, current.millitm);
341 }
342
343 clock_time time_now() { return now(); }
344
345 days day_now() { return now().day_of_week; }
346
347 months month_now() { return now().month; }
348
349 int year_now() { return now().year; }
350
351 day_in_year date_now() { return now(); }
352
353 const char *day_name(days to_name)
354 {
355   switch (to_name) {
356     case SUNDAY: return "Sunday";
357     case MONDAY: return "Monday";
358     case TUESDAY: return "Tuesday";
359     case WEDNESDAY: return "Wednesday";
360     case THURSDAY: return "Thursday";
361     case FRIDAY: return "Friday";
362     case SATURDAY: return "Saturday";
363     default: return "Not_a_day";
364   }
365 }
366
367 const char *month_name(months to_name)
368 {
369   switch (to_name) {
370     case JANUARY: return "January";
371     case FEBRUARY: return "February";
372     case MARCH: return "March";
373     case APRIL: return "April";
374     case MAY: return "May";
375     case JUNE: return "June";
376     case JULY: return "July";
377     case AUGUST: return "August";
378     case SEPTEMBER: return "September";
379     case OCTOBER: return "October";
380     case NOVEMBER: return "November";
381     case DECEMBER: return "December";
382     default: return "Not_a_month";
383   }
384 }
385
386 const char *short_month_name(months to_name)
387 {
388   switch (to_name) {
389     case JANUARY: return "Jan";
390     case FEBRUARY: return "Feb";
391     case MARCH: return "Mar";
392     case APRIL: return "Apr";
393     case MAY: return "May";
394     case JUNE: return "Jun";
395     case JULY: return "Jul";
396     case AUGUST: return "Aug";
397     case SEPTEMBER: return "Sep";
398     case OCTOBER: return "Oct";
399     case NOVEMBER: return "Nov";
400     case DECEMBER: return "Dec";
401     default: return "Not";
402   }
403 }
404
405 } // namespace.
406