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