Updated license
[anna.git] / source / time / Date.cpp
1 // ANNA - Anna is Not Nothingness Anymore
2 //
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
4 //
5 // https://bitbucket.org/testillano/anna
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 //
11 //     * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 //     * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 //     * Neither the name of Google Inc. nor the names of its
18 // contributors may be used to endorse or promote products derived from
19 // this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 // Authors: eduardo.ramos.testillano@gmail.com
34 //          cisco.tierra@gmail.com
35
36
37 #include <anna/core/tracing/Logger.hpp>
38 #include <anna/core/tracing/TraceWriter.hpp>
39 #include <anna/config/defines.hpp>
40 #include <anna/core/functions.hpp>
41 #include <anna/core/mt/Guard.hpp>
42 #include <anna/xml/xml.hpp>
43
44 // Local
45 #include <anna/time/Date.hpp>
46 #include <anna/time/functions.hpp>
47 #include <anna/time/internal/sccs.hpp>
48
49 // Standard
50 #include <stdlib.h> // putenv
51 #include <iostream>
52
53
54 using namespace anna;
55 using namespace anna::time;
56
57
58
59 //******************************************************************************
60 //------------------------------------------------------------------------- Date
61 //******************************************************************************
62
63 //------------------------------------------------------------------------------
64 //----------------------------------------------------------------- Date::Date()
65 //------------------------------------------------------------------------------
66 Date::Date(const char * TzContext) {
67   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "Default Constructor", ANNA_FILE_LOCATION));
68   time::sccs::activate();
69
70   if(!functions::initialized()) {
71     //anna::Logger::error("You should firstly invoke anna::time::functions::initialize() before using this module", ANNA_FILE_LOCATION);
72     //Perhaps former couldn't be written (no Logger initialize):
73     std::cout << std::endl << "Develop ERROR: you should firstly invoke anna::time::functions::initialize() before using time module" << std::endl;
74   }
75
76   a_local_tz = functions::getLocalTz();
77   a_work_tz = a_local_tz;
78   initialize(TzContext);
79 };
80
81
82 //------------------------------------------------------------------------------
83 //----------------------------------------------------------------- Date::Date()
84 //------------------------------------------------------------------------------
85 Date::Date(const Date & d) {
86   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "Copy constructor", ANNA_FILE_LOCATION));
87   time::sccs::activate();
88   *this = d;
89   a_local_tz = functions::getLocalTz();
90   a_work_tz = d.a_work_tz;
91 };
92
93
94 //------------------------------------------------------------------------------
95 //---------------------------------------------------------------- Date::~Date()
96 //------------------------------------------------------------------------------
97 Date::~Date() {
98   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "~Date", ANNA_FILE_LOCATION));
99 }
100
101 // private:
102
103
104 //------------------------------------------------------------------------------
105 //-------------------------------------------------------------- Date::_putenv()
106 //------------------------------------------------------------------------------
107 void Date::_putenv(const char * Tz) throw() {
108   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "_putenv", ANNA_FILE_LOCATION));
109   TZ providedTz;
110
111   if(Tz)
112     providedTz.set(Tz);
113   else
114     providedTz = a_local_tz;
115
116   if(providedTz != a_work_tz) {
117     a_work_tz = providedTz;
118 // Solaris no tiene el unsetenv, actualizamos directamente con putenv: "unset TZ", que es totalmente valido (*)
119 //      if (a_work_tz.isUnset())
120 //         unsetenv ("TZ");
121 //      else
122     putenv((char *)a_work_tz.getShellAssignment().c_str());
123 // (*)
124 //      if (a_work_tz.isUnset())
125 //         std::cout << "Unset" << std::endl;
126 //      else
127 //      {
128 //         if (a_work_tz.isEmpty())
129 //            std::cout << "Empty" << std::endl;
130 //         else
131 //            std::cout << a_work_tz.getValue() << std::endl;
132 //      }
133 //         std::cout << "Salida putenv poniendo " << a_work_tz.getAssignment().c_str() << ": " << putenv((char *)a_work_tz.getAssignment().c_str()) << std::endl;
134 //         const char *kk = getenv("TZ");
135 //         if (kk) std::cout << "getenv: " << kk << std::endl;
136 //         else std::cout << "getenv: NULL" << std::endl;
137   }
138 }
139
140
141 //------------------------------------------------------------------------------
142 //------------------------------------------------------- Date::set_tz_context()
143 //------------------------------------------------------------------------------
144 void Date::set_tz_context(const char * TzContext) throw() {
145   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "set_tz_context", ANNA_FILE_LOCATION));
146   if(TzContext)
147     a_TZ_context.set(TzContext);
148   else
149     a_TZ_context = a_local_tz;
150
151   refresh_regarding_unix_timestamp(); // borns with timestamp and is based on new TZ value
152 }
153
154
155 //------------------------------------------------------------------------------
156 //------------------------------------------------------- Date::get_TZ_context()
157 //------------------------------------------------------------------------------
158 const TZ & Date::get_TZ_context(void) const throw() {
159   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "get_TZ_context", ANNA_FILE_LOCATION));
160   return (a_TZ_context);
161 }
162
163
164 //------------------------------------------------------------------------------
165 //---------------------------------------------------------- Date::_initialize()
166 //------------------------------------------------------------------------------
167 void Date::_initialize(const char * TzContext) throw() {
168   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "_initialize", ANNA_FILE_LOCATION));
169   a_unix_timestamp = ::time(NULL);
170   set_tz_context(TzContext);  // This refresh all data with the asigned TZ
171 }
172
173
174 //------------------------------------------------------------------------------
175 //------------------------------------------------- Date::check_yyyymmddHHmmss()
176 //------------------------------------------------------------------------------
177 void Date::check_yyyymmddHHmmss(const std::string & yyyymmddHHmmss)
178
179 throw(anna::RuntimeException)
180
181 {
182   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "check_yyyymmddHHmmss", ANNA_FILE_LOCATION));
183   // check 2038 effect: restrictive limit
184   if(yyyymmddHHmmss > _2K38_EFFECT_LIMIT) {  // ESTO NO LO COMPRUEBA mktime !!
185     throw anna::RuntimeException("Provided time/date is over 2K38 effect (19 January 2038, 03:14:07) !!", ANNA_FILE_LOCATION);
186   }
187
188   // check 14-digit length (i.e. "19741219100000")
189   int longitud = yyyymmddHHmmss.length();
190
191   if(longitud != 14) {
192     throw anna::RuntimeException("Provided time/date has not 14-digit length !!. Remember: 'yyyymmddHHmmss'", ANNA_FILE_LOCATION);
193   }
194
195   // All digits:
196   const char * ptr_cad = yyyymmddHHmmss.c_str();
197
198   for(register int k = 0; k < longitud; k++) {
199     if(!isdigit(ptr_cad[k])) {
200       throw anna::RuntimeException("Provided time/date only can contain digits !!. Remember: 'yyyymmddHHmmss'", ANNA_FILE_LOCATION);
201     }
202   }
203
204 //   // Data range:
205 //   int year, mon, mday, hour, min, sec;
206 //   sscanf(ptr_cad, STRING_FORMAT_yyyymmddHHmmss, &year, &mon, &mday, &hour, &min, &sec);
207 //   //year 2038 already checked
208 //   if (mon < 1 || mon > 12)
209 //      throw anna::RuntimeException("Month out of range (1-12) !!", ANNA_FILE_LOCATION);
210 //   top_day = 31;
211 //   if (mon == 2) top_day = 29/*28*/;
212 //   if (mon == 4 || mon == 6 || mon == 9 || mon == 11) top_day = 30;
213 //
214 //   if (mday < 1 || mday > top_day)
215 //      throw anna::RuntimeException(anna::functions::asString("Day out of range (1-%d) !!", top_day), ANNA_FILE_LOCATION);
216 //   if (hour > 23)
217 //      throw anna::RuntimeException("Hour out of range (0-23) !!", ANNA_FILE_LOCATION);
218 //   if (min < 0 || min > 59)
219 //      throw anna::RuntimeException("Minute out of range (0-59) !!", ANNA_FILE_LOCATION);
220 //   if (sec < 0 || sec > 59)
221 //      throw anna::RuntimeException("Second out of range (0-59) !!", ANNA_FILE_LOCATION);
222 }
223
224
225 //------------------------------------------------------------------------------
226 //------------------------------------- Date::refresh_regarding_unix_timestamp()
227 //------------------------------------------------------------------------------
228 void Date::refresh_regarding_unix_timestamp(void) throw() {
229   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "refresh_regarding_unix_timestamp", ANNA_FILE_LOCATION));
230   anna::Guard guard(a_mutex);
231   //anna::Guard<anna::Mutex> guard(a_mutex);
232   _putenv(get_TZ_context().getValue().c_str());
233   struct tm *ptrTm;
234   struct tm resTm;
235   //ptrTm = localtime_r(&a_unix_timestamp, &resTm); NO FUNCIONA BIEN EN LINUX!!
236   ptrTm = localtime(&a_unix_timestamp);
237   // OJO:
238   //   ctime() -> localtime() -> __tz_convert() -> __libc_lock_lock()
239   //   So, glibc does not guarantee the sane behavior when one uses ctime() in
240   //   signal handler.
241   //
242   //   Este problema puede ser de algún bug de la librería libc.so.6 de linux.
243   //   Recomiendan poner LD_ASSUME_KERNEL=2.4.1
244   // Otra solucion sera hacerlo MT-Safe con guardas
245   a_tm_struct = *ptrTm;
246   // Returns begining
247   _putenv(a_local_tz.getValue().c_str());
248   static char cad_aux[32];
249   sprintf(cad_aux, STRING_FORMAT_yyyymmddHHmmss,   1900 + a_tm_struct.tm_year, 1 + (a_tm_struct.tm_mon), a_tm_struct.tm_mday,
250           a_tm_struct.tm_hour, a_tm_struct.tm_min, a_tm_struct.tm_sec);
251   // Assignment:
252   _yyyymmddHHmmss = cad_aux;
253 }
254
255 // public
256
257 // sets
258
259
260 //------------------------------------------------------------------------------
261 //----------------------------------------------------------- Date::initialize()
262 //------------------------------------------------------------------------------
263 void Date::initialize(const char * TzContext) throw() {
264   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "initialize", ANNA_FILE_LOCATION));
265   _initialize(TzContext);
266 }
267
268
269 //------------------------------------------------------------------------------
270 //--------------------------------------------------------- Date::setTzContext()
271 //------------------------------------------------------------------------------
272 void Date::setTzContext(const char * TzContext) throw() {
273   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "setTzContext", ANNA_FILE_LOCATION));
274   set_tz_context(TzContext);
275 }
276
277
278
279 //------------------------------------------------------------------------------
280 //---------------------------------------------------------------- Date::store()
281 //------------------------------------------------------------------------------
282 void Date::store(const char* dateTimeAsStringFormat, const std::string & dateTimeAsString, const char * OriginTz)
283
284 throw(anna::RuntimeException)
285
286 {
287   // Get equivalent 'tm':
288   struct tm TmOrigin;
289   memset(&TmOrigin, 0, sizeof(TmOrigin));
290
291   if(strptime(dateTimeAsString.c_str(), dateTimeAsStringFormat, &TmOrigin) == NULL) {
292     std::string msg("Error during strptime() conversion: '");
293     msg += dateTimeAsString;
294     msg += "' can't be interpreted as '";
295     msg += dateTimeAsStringFormat;
296     msg += "'";
297     throw anna::RuntimeException(msg, ANNA_FILE_LOCATION);
298   }
299
300   store(TmOrigin, OriginTz);
301 }
302
303
304 void Date::store(const std::string & yyyymmddHHmmss, const char * OriginTz)
305
306 throw(anna::RuntimeException)
307
308 {
309   check_yyyymmddHHmmss(yyyymmddHHmmss);  // launch exception when format error (14 digits)
310   // Could be good, but not assing (perhaps 'strptime' could fail)
311 // Limitaciones del 'strptime' usado por 'store(format, cadena)':
312 // El formato directamente sacado de este interfaz ("%Y%m%d%H%M%S") no valida las fechas (meses 13, dias 32, etc),
313 //  para ello hay que poner separadores en el formato (mucho más robusto). Aun asi, 'strptime' sigue "tragandose cosas"
314 //  como por ejemplo segundos=60 (07:54:60 lo convierte a 07:55:00), y no comprueba los años bisiestos (de febrero traga
315 //  29 dias siempre, aunque no traga 30)
316 // Solucion, convertimos la cadena a otra con separadores para dar mayor robustez (aunque no es perfecto como se ha dicho):
317   int tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec;
318   sscanf(yyyymmddHHmmss.c_str(), STRING_FORMAT_yyyymmddHHmmss, &tm_year, &tm_mon, &tm_mday, &tm_hour, &tm_min, &tm_sec);
319   static char cad_aux[64];
320   sprintf(cad_aux, "%04d-%02d-%02d-%02d-%02d-%02d", tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec);
321   store(STRPTIME_FORMAT_yyyy_mm_dd_HH_mm_ss, cad_aux, OriginTz);
322 }
323
324
325 //------------------------------------------------------------------------------
326 //---------------------------------------------------------------- Date::store()
327 //------------------------------------------------------------------------------
328 void Date::store(const struct tm & TmOrigen, const char * OriginTz)
329
330 throw(anna::RuntimeException)
331
332 {
333   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "store", ANNA_FILE_LOCATION));
334   _putenv(OriginTz);
335   // Set '-1' on 'tm_isdst', or it could be an ambiguity on tranformation
336   //  returning inexact timestamp. This setting force system to check the correct flag:
337   struct tm TmOrigen_isdst_menos1 = TmOrigen;
338   TmOrigen_isdst_menos1.tm_isdst = -1;
339   time_t unixTimestamp = mktime((struct tm*) & TmOrigen_isdst_menos1);
340
341   if(unixTimestamp == (time_t) - 1)
342     throw anna::RuntimeException("Error during mktime() conversion !!", ANNA_FILE_LOCATION);
343
344   a_unix_timestamp = unixTimestamp;
345   // Refresh the othr data:
346   refresh_regarding_unix_timestamp();
347 }
348
349
350 //------------------------------------------------------------------------------
351 //---------------------------------------------------------------- Date::store()
352 //------------------------------------------------------------------------------
353 void Date::store(const time_t & unixTimestamp) throw() {
354   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "store unix timestamp", ANNA_FILE_LOCATION));
355   a_unix_timestamp = unixTimestamp;
356   // Refresh the other data:
357   refresh_regarding_unix_timestamp();
358 }
359
360
361 //------------------------------------------------------------------------------
362 //------------------------------------------------------------- Date::storeNtp()
363 //------------------------------------------------------------------------------
364 void Date::storeNtp(const unsigned int & ntpTimestamp) throw() {
365   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "store ntp timestamp", ANNA_FILE_LOCATION));
366   a_unix_timestamp = ntpTimestamp - TIMESTAMP_OFFSET_NTP1900_OVER_UNIX1970;
367   // Refresh the other data:
368   refresh_regarding_unix_timestamp();
369 }
370
371
372 //------------------------------------------------------------------------------
373 //-------------------------------------------------------------- Date::operator=
374 //------------------------------------------------------------------------------
375 Date & Date::operator = (const Date & d) {
376   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "operator=", ANNA_FILE_LOCATION));
377   // avoid itself copy: i.e., Date a,b; a=&b; a=b; b=a;
378   if(this == &d) return (*this);
379
380   a_TZ_context = d.get_TZ_context();
381   a_unix_timestamp = d.getUnixTimestamp();
382   a_tm_struct = d.getTm();
383   _yyyymmddHHmmss = d.yyyymmddHHmmss();
384   return (*this);
385 }
386
387
388 // gets
389
390 //------------------------------------------------------------------------------
391 //--------------------------------------------------------- Date::getTzContext()
392 //------------------------------------------------------------------------------
393 const std::string & Date::getTzContext(void) const throw() {
394   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "getTzContext", ANNA_FILE_LOCATION));
395   return (a_TZ_context.getValue());
396 }
397
398
399 //------------------------------------------------------------------------------
400 //------------------------------------------------------- Date::yyyymmddHHmmss()
401 //------------------------------------------------------------------------------
402 const std::string & Date::yyyymmddHHmmss(void) const throw() {
403   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "yyyymmddHHmmss", ANNA_FILE_LOCATION));
404   return (_yyyymmddHHmmss);
405 }
406
407
408 //------------------------------------------------------------------------------
409 //----------------------------------------------------- Date::getUnixTimestamp()
410 //------------------------------------------------------------------------------
411 const time_t & Date::getUnixTimestamp(void) const throw() {
412   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "getUnixTimestamp", ANNA_FILE_LOCATION));
413   return (a_unix_timestamp);
414 }
415
416
417 //------------------------------------------------------------------------------
418 //------------------------------------------------------ Date::getNtpTimestamp()
419 //------------------------------------------------------------------------------
420 unsigned int Date::getNtpTimestamp(void) const throw() {
421   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "getNtpTimestamp", ANNA_FILE_LOCATION));
422   unsigned int ntp_timestamp = a_unix_timestamp + TIMESTAMP_OFFSET_NTP1900_OVER_UNIX1970;
423   return (ntp_timestamp);
424 }
425
426
427 //------------------------------------------------------------------------------
428 //---------------------------------------------------------------- Date::getTm()
429 //------------------------------------------------------------------------------
430 const struct tm & Date::getTm(void) const throw() {
431   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "getTm", ANNA_FILE_LOCATION));
432
433
434   return (a_tm_struct);
435 }
436
437
438 //------------------------------------------------------------------------------
439 //------------------------------------------------------------- Date::asString()
440 //------------------------------------------------------------------------------
441 std::string Date::asString(void) const throw() {
442   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "asString", ANNA_FILE_LOCATION));
443   std::string trace;
444   static char cad_aux[256];
445   //bool differentContextAndLocalTZ = getTzContext() != a_local_tz.getValue();
446   sprintf(cad_aux, "%02d/%02d/%04d %02d:%02d:%02d",
447           //          yyyymmddHHmmss().c_str(),
448           getTm().tm_mday, 1 + (getTm().tm_mon), 1900 + getTm().tm_year,
449           getTm().tm_hour, getTm().tm_min, getTm().tm_sec);
450   trace = cad_aux;
451
452 //   if (!(get_TZ_context().isUnset()))
453 //      if (!(get_TZ_context().isEmpty()))
454 //         { trace += " "; trace += get_TZ_context().getValue(); }
455   if(getTzContext() != ""/* && differentContextAndLocalTZ*/) { trace += " "; trace += getTzContext(); }
456
457   sprintf(cad_aux, ", isdst = %d [Unix Timestamp: %ld, Ntp Timestamp: %u]", getTm().tm_isdst, getUnixTimestamp(), getNtpTimestamp());
458   trace += cad_aux;
459
460 //   if (!(a_local_tz.isUnset()))
461 //      if (!(a_local_tz.isEmpty()))
462 //         { trace += ", Local TZ = "; trace += a_local_tz.getValue(); }
463   if(a_local_tz.getValue() != "") {
464     //trace += differentContextAndLocalTZ ? ", Local TZ = ":", TZ Context = Local TZ = ";
465     trace += ", Local TZ = ";
466     trace += a_local_tz.getValue();
467   }
468
469   return (trace);
470 }
471
472
473 //------------------------------------------------------------------------------
474 //---------------------------------------------------------------- Date::asXML()
475 //------------------------------------------------------------------------------
476 anna::xml::Node* Date::asXML(anna::xml::Node* parent) const throw() {
477   //LOGMETHOD (TraceMethod tm ("anna::time::Date", "asXML", ANNA_FILE_LOCATION));
478   //anna::xml::Node* result = parent->createChild("anna.time.Date");
479   //bool differentContextAndLocalTZ = getTzContext() != a_local_tz.getValue();
480   parent->createAttribute("Date", anna::functions::asString("%02d/%02d/%04d", getTm().tm_mday, 1 + (getTm().tm_mon), 1900 + getTm().tm_year));
481   parent->createAttribute("Time", anna::functions::asString("%02d:%02d:%02d", getTm().tm_hour, getTm().tm_min, getTm().tm_sec));
482
483   if(getTzContext() != ""/* && differentContextAndLocalTZ*/) parent->createAttribute("TZContext", getTzContext());
484
485   parent->createAttribute("Isdst", (getTm().tm_isdst) ? "yes" : "no");
486 //   parent->createAttribute("UnixTimestamp", anna::functions::asString("%lu seconds", getUnixTimestamp())); // unsigned long -> %lu
487 //   parent->createAttribute("NtpTimestamp", anna::functions::asString("%u seconds", getNtpTimestamp())); // unsigned int -> %u
488   parent->createAttribute("UnixTimestamp", anna::functions::asString((const int)getUnixTimestamp()));
489   parent->createAttribute("NtpTimestamp", anna::functions::asString(getNtpTimestamp()));
490
491   if(a_local_tz.getValue() != "") parent->createAttribute(/*differentContextAndLocalTZ ? "LocalTZ":"TZContext=LocalTZ"*/"LocalTZ", a_local_tz.getValue());
492
493   return parent;
494 }
495