blocxx
DateTime.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2 * Copyright (C) 2005, Vintela, Inc. All rights reserved.
3 * Copyright (C) 2006, Novell, Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of
14 * Vintela, Inc.,
15 * nor Novell, Inc.,
16 * nor the names of its contributors or employees may be used to
17 * endorse or promote products derived from this software without
18 * specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 *******************************************************************************/
32 
33 
39 #include "blocxx/BLOCXX_config.h"
40 #include "blocxx/DateTime.hpp"
41 #include "blocxx/String.hpp"
42 #include "blocxx/Array.hpp"
43 #include "blocxx/Format.hpp"
44 #include "blocxx/Mutex.hpp"
45 #include "blocxx/MutexLock.hpp"
46 #include "blocxx/ExceptionIds.hpp"
47 
48 #include <time.h>
49 #ifdef BLOCXX_HAVE_SYS_TIME_H
50 #include <sys/time.h>
51 #endif
52 
53 #include <cctype>
54 
55 
56 #ifndef BLOCXX_HAVE_LOCALTIME_R
57 namespace
58 {
59  BLOCXX_NAMESPACE::Mutex localtimeMutex;
60 }
61 struct tm *localtime_r(const time_t *timep, struct tm *result)
62 {
63  BLOCXX_NAMESPACE::MutexLock lock(localtimeMutex);
64  struct tm *p = localtime(timep);
65 
66  if (p)
67  {
68  *(result) = *p;
69  }
70 
71  return p;
72 }
73 #endif
74 
75 #ifndef BLOCXX_HAVE_GMTIME_R
76 namespace
77 {
78  BLOCXX_NAMESPACE::Mutex gmtimeMutex;
79 }
80 struct tm *gmtime_r(const time_t *timep, struct tm *result)
81 {
82  BLOCXX_NAMESPACE::MutexLock lock(gmtimeMutex);
83  struct tm *p = gmtime(timep);
84 
85  if (p)
86  {
87  *(result) = *p;
88  }
89 
90  return p;
91 }
92 #endif
93 
94 #ifndef BLOCXX_HAVE_ASCTIME_R
95 namespace
96 {
97  BLOCXX_NAMESPACE::Mutex asctimeMutex;
98 }
99 char *asctime_r(const struct tm *tm, char *result)
100 {
101  BLOCXX_NAMESPACE::MutexLock lock(asctimeMutex);
102  char *p = asctime(tm);
103 
104  if (p)
105  {
106  //asctime_r requires a buffer to be at least 26 chars in size
107  ::strncpy(result,p,25);
108  result[25] = 0;
109  }
110 
111  return result;
112 }
113 #endif
114 
115 namespace BLOCXX_NAMESPACE
116 {
117 
120 
123  : m_time(0)
124  , m_microseconds(0)
125 
126 {
127 }
129 namespace
130 {
131 
132 inline void badDateTime(const String& str)
133 {
134  BLOCXX_THROW(DateTimeException, Format("Invalid DateTime: %1", str).c_str());
135 }
136 
137 inline void validateRanges(Int32 year, Int32 month, Int32 day, Int32 hour,
138 Int32 minute, Int32 second, Int32 microseconds, const String& str)
139 {
140  if (year < 0 || year > 9999 ||
141  month < 1 || month > 12 ||
142  day < 1 || day > 31 ||
143  hour < 0 || hour > 23 ||
144  minute < 0 || minute > 59 ||
145  second < 0 || second > 60 ||
146  microseconds < 0 || microseconds > 999999)
147  {
148  badDateTime(str);
149  }
150 }
151 
152 inline bool isDOWValid(const char* str)
153 {
154  // a little FSM to validate the day of the week
155  bool good = true;
156  if (str[0] == 'S') // Sun, Sat
157  {
158  if (str[1] == 'u')
159  {
160  if (str[2] != 'n') // Sun
161  {
162  good = false;
163  }
164  }
165  else if (str[1] == 'a')
166  {
167  if (str[2] != 't') // Sat
168  {
169  good = false;
170  }
171  }
172  else
173  {
174  good = false;
175  }
176  }
177  else if (str[0] == 'M') // Mon
178  {
179  if (str[1] == 'o')
180  {
181  if (str[2] != 'n')
182  {
183  good = false;
184  }
185  }
186  else
187  {
188  good = false;
189  }
190  }
191  else if (str[0] == 'T') // Tue, Thu
192  {
193  if (str[1] == 'u')
194  {
195  if (str[2] != 'e') // Tue
196  {
197  good = false;
198  }
199  }
200  else if (str[1] == 'h')
201  {
202  if (str[2] != 'u') // Thu
203  {
204  good = false;
205  }
206  }
207  else
208  {
209  good = false;
210  }
211  }
212  else if (str[0] == 'W') // Wed
213  {
214  if (str[1] == 'e')
215  {
216  if (str[2] != 'd')
217  {
218  good = false;
219  }
220  }
221  else
222  {
223  good = false;
224  }
225  }
226  else if (str[0] == 'F') // Fri
227  {
228  if (str[1] == 'r')
229  {
230  if (str[2] != 'i')
231  {
232  good = false;
233  }
234  }
235  else
236  {
237  good = false;
238  }
239  }
240  else
241  {
242  good = false;
243  }
244 
245  return good;
246 }
247 
248 inline bool isLongDOWValid(const String& s)
249 {
250  if ( (s == "Sunday") ||
251  (s == "Monday") ||
252  (s == "Tuesday") ||
253  (s == "Wednesday") ||
254  (s == "Thursday") ||
255  (s == "Friday") ||
256  (s == "Saturday") )
257  {
258  return true;
259  }
260  return false;
261 }
262 
263 // returns -1 if the month is invalid, 1-12 otherwise
264 inline int decodeShortMonth(const char* str)
265 {
266  // a little FSM to calculate the month
267  if (str[0] == 'J') // Jan, Jun, Jul
268  {
269  if (str[1] == 'a')
270  {
271  if (str[2] == 'n') // Jan
272  {
273  return 1;
274  }
275  }
276  else if (str[1] == 'u')
277  {
278  if (str[2] == 'n') // Jun
279  {
280  return 6;
281  }
282  else if (str[2] == 'l') // Jul
283  {
284  return 7;
285  }
286  }
287  }
288  else if (str[0] == 'F') // Feb
289  {
290  if (str[1] == 'e' && str[2] == 'b')
291  {
292  return 2;
293  }
294  }
295  else if (str[0] == 'M') // Mar, May
296  {
297  if (str[1] == 'a')
298  {
299  if (str[2] == 'r') // Mar
300  {
301  return 3;
302  }
303  else if (str[2] == 'y') // May
304  {
305  return 5;
306  }
307  }
308  }
309  else if (str[0] == 'A') // Apr, Aug
310  {
311  if (str[1] == 'p')
312  {
313  if (str[2] == 'r') // Apr
314  {
315  return 4;
316  }
317  }
318  else if (str[1] == 'u')
319  {
320  if (str[2] == 'g') // Aug
321  {
322  return 8;
323  }
324  }
325  }
326  else if (str[0] == 'S') // Sep
327  {
328  if (str[1] == 'e' && str[2] == 'p')
329  {
330  return 9;
331  }
332  }
333  else if (str[0] == 'O') // Oct
334  {
335  if (str[1] == 'c' && str[2] == 't')
336  {
337  return 10;
338  }
339  }
340  else if (str[0] == 'N') // Nov
341  {
342  if (str[1] == 'o' && str[2] == 'v')
343  {
344  return 11;
345  }
346  }
347  else if (str[0] == 'D') // Dec
348  {
349  if (str[1] == 'e' && str[2] == 'c')
350  {
351  return 12;
352  }
353  }
354 
355  return -1;
356 }
357 
358 // returns -1 if the month is invalid, 1-12 otherwise
359 inline int decodeLongMonth(const String& str)
360 {
361  if ( str.equals("January") )
362  {
363  return 1;
364  }
365  else if ( str.equals("February") )
366  {
367  return 2;
368  }
369  else if ( str.equals("March") )
370  {
371  return 3;
372  }
373  else if ( str.equals("April") )
374  {
375  return 4;
376  }
377  else if ( str.equals("May") )
378  {
379  return 5;
380  }
381  else if ( str.equals("June") )
382  {
383  return 6;
384  }
385  else if ( str.equals("July") )
386  {
387  return 7;
388  }
389  else if ( str.equals("August") )
390  {
391  return 8;
392  }
393  else if ( str.equals("September") )
394  {
395  return 9;
396  }
397  else if ( str.equals("October") )
398  {
399  return 10;
400  }
401  else if ( str.equals("November") )
402  {
403  return 11;
404  }
405  else if ( str.equals("December") )
406  {
407  return 12;
408  }
409  return -1;
410 }
411 
412 // Get the timezone offset (from UTC) for the given timezone. Valid results
413 // are in the range -12 to 12, except for the case where LOCAL_TIME_OFFSET is
414 // returned, in which case UTC should not be used.
415 const int LOCAL_TIME_OFFSET = -24;
416 bool getTimeZoneOffset(const String& timezone, int& offset)
417 {
418  int temp_offset = LOCAL_TIME_OFFSET -1;
419  if ( timezone.length() == 1 )
420  {
421  // Single-letter abbrev.
422  // This could be simplified into a couple of if statements with some
423  // character math, but this should work for now.
424  switch ( timezone[0] )
425  {
426  case 'Y': // Yankee UTC-12
427  temp_offset = -12;
428  break;
429  case 'X': // Xray UTC-11
430  temp_offset = -11;
431  break;
432  case 'W': // Whiskey UTC-10
433  temp_offset = -10;
434  break;
435  case 'V': // Victor UTC-9
436  temp_offset = -9;
437  break;
438  case 'U': // Uniform UTC-8
439  temp_offset = -8;
440  break;
441  case 'T': // Tango UTC-7
442  temp_offset = -7;
443  break;
444  case 'S': // Sierra UTC-6
445  temp_offset = -6;
446  break;
447  case 'R': // Romeo UTC-5
448  temp_offset = -5;
449  break;
450  case 'Q': // Quebec UTC-4
451  temp_offset = -4;
452  break;
453  case 'P': // Papa UTC-3
454  temp_offset = -3;
455  break;
456  case 'O': // Oscar UTC-2
457  temp_offset = -2;
458  break;
459  case 'N': // November UTC-1
460  temp_offset = -1;
461  break;
462  case 'Z': // Zulu UTC
463  temp_offset = 0;
464  break;
465  case 'A': // Aplpha UTC+1
466  temp_offset = 1;
467  break;
468  case 'B': // Bravo UTC+2
469  temp_offset = 2;
470  break;
471  case 'C': // Charlie UTC+3
472  temp_offset = 3;
473  break;
474  case 'D': // Delta UTC+4
475  temp_offset = 4;
476  break;
477  case 'E': // Echo UTC+5
478  temp_offset = 5;
479  break;
480  case 'F': // Foxtrot UTC+6
481  temp_offset = 6;
482  break;
483  case 'G': // Golf UTC+7
484  temp_offset = 7;
485  break;
486  case 'H': // Hotel UTC+8
487  temp_offset = 8;
488  break;
489  case 'I': // India UTC+9
490  temp_offset = 9;
491  break;
492  case 'K': // Kilo UTC+10
493  temp_offset = 10;
494  break;
495  case 'L': // Lima UTC+11
496  temp_offset = 11;
497  break;
498  case 'M': // Mike UTC+12
499  temp_offset = 12;
500  break;
501  case 'J': // Juliet Always local time
502  temp_offset = LOCAL_TIME_OFFSET;
503  break;
504  default:
505  break;
506  }
507  }
508  else if ( timezone == "UTC" ) // Universal Time Coordinated, civil time
509  {
510  temp_offset = 0;
511  }
512  // European timezones
513  else if ( timezone == "GMT" ) // Greenwich Mean Time UTC
514  {
515  temp_offset = 0;
516  }
517  else if ( timezone == "BST" ) // British Summer Time UTC+1
518  {
519  temp_offset = 1;
520  }
521  else if ( timezone == "IST" ) // Irish Summer Time UTC+1
522  {
523  temp_offset = 1;
524  }
525  else if ( timezone == "WET" ) // Western Europe Time UTC
526  {
527  temp_offset = 0;
528  }
529  else if ( timezone == "WEST" ) // Western Europe Summer Time UTC+1
530  {
531  temp_offset = 1;
532  }
533  else if ( timezone == "CET" ) // Central Europe Time UTC+1
534  {
535  temp_offset = 1;
536  }
537  else if ( timezone == "CEST" ) // Central Europe Summer Time UTC+2
538  {
539  temp_offset = 2;
540  }
541  else if ( timezone == "EET" ) // Eastern Europe Time UTC+2
542  {
543  temp_offset = 2;
544  }
545  else if ( timezone == "EEST" ) // Eastern Europe Summer Time UTC+3
546  {
547  temp_offset = 3;
548  }
549  else if ( timezone == "MSK" ) // Moscow Time UTC+3
550  {
551  temp_offset = 3;
552  }
553  else if ( timezone == "MSD" ) // Moscow Summer Time UTC+4
554  {
555  temp_offset = 4;
556  }
557  // US and Canada
558  else if ( timezone == "AST" ) // Atlantic Standard Time UTC-4
559  {
560  temp_offset = -4;
561  }
562  else if ( timezone == "ADT" ) // Atlantic Daylight Saving Time UTC-3
563  {
564  temp_offset = -3;
565  }
566  else if ( timezone == "EST" ) // Eastern Standard Time UTC-5
567  {
568  // CHECKME! This can also be Australian Eastern Standard Time UTC+10
569  // (UTC+11 in Summer)
570  temp_offset = -5;
571  }
572  else if ( timezone == "EDT" ) // Eastern Daylight Saving Time UTC-4
573  {
574  temp_offset = -4;
575  }
576  else if ( timezone == "ET" ) // Eastern Time, either as EST or EDT
577  // depending on place and time of year
578  {
579  // CHECKME! Assuming standard time.
580  temp_offset = -5;
581  }
582  else if ( timezone == "CST" ) // Central Standard Time UTC-6
583  {
584  // CHECKME! This can also be Australian Central Standard Time UTC+9.5
585  temp_offset = -6;
586  }
587  else if ( timezone == "CDT" ) // Central Daylight Saving Time UTC-5
588  {
589  temp_offset = -5;
590  }
591  else if ( timezone == "CT" ) // Central Time, either as CST or CDT
592  // depending on place and time of year
593  {
594  // CHECKME! Assuming standard time.
595  temp_offset = -6;
596  }
597  else if ( timezone == "MST" ) // Mountain Standard Time UTC-7
598  {
599  temp_offset = -7;
600  }
601  else if ( timezone == "MDT" ) // Mountain Daylight Saving Time UTC-6
602  {
603  temp_offset = -6;
604  }
605  else if ( timezone == "MT" ) // Mountain Time, either as MST or MDT
606  // depending on place and time of year
607  {
608  // CHECKME! Assuming standard time.
609  temp_offset = -7;
610  }
611  else if ( timezone == "PST" ) // Pacific Standard Time UTC-8
612  {
613  temp_offset = -8;
614  }
615  else if ( timezone == "PDT" ) // Pacific Daylight Saving Time UTC-7
616  {
617  temp_offset = -7;
618  }
619  else if ( timezone == "PT" ) // Pacific Time, either as PST or PDT
620  // depending on place and time of year
621  {
622  // CHECKME! Assuming standard time.
623  temp_offset = -8;
624  }
625  else if ( timezone == "HST" ) // Hawaiian Standard Time UTC-10
626  {
627  temp_offset = -10;
628  }
629  else if ( timezone == "AKST" ) // Alaska Standard Time UTC-9
630  {
631  temp_offset = -9;
632  }
633  else if ( timezone == "AKDT" ) // Alaska Standard Daylight Saving Time UTC-8
634  {
635  temp_offset = -8;
636  }
637  // Australia
638  else if ( timezone == "WST" ) // Western Standard Time UTC+8
639  {
640  temp_offset = 8;
641  }
642 
643  // Check the results of that huge mess.
644  if ( temp_offset >= LOCAL_TIME_OFFSET )
645  {
646  offset = temp_offset;
647  return true;
648  }
649  return false;
650 }
651 
652 Int32 getDaysPerMonth(Int32 year, Int32 month)
653 {
654  const Int32 normal_days_per_month[12] =
655  { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
656 
657  if ( (month >= 1) && (month <= 12) )
658  {
659  if ( month != 2 )
660  {
661  return normal_days_per_month[month - 1];
662  }
663 
664  int leap_year_adjust = 0;
665 
666  if ( (year % 4) == 0 )
667  {
668  // Possibly a leap year.
669  if ( (year % 100) == 0 )
670  {
671  if ( (year % 400) == 0 )
672  {
673  leap_year_adjust = 1;
674  }
675  }
676  else
677  {
678  leap_year_adjust = 1;
679  }
680  }
681 
682  return normal_days_per_month[month - 1] + leap_year_adjust;
683  // Check to see if it's a leap year.
684  }
685  return 0;
686 }
687 
688 // Adjust the time given (year, month, day, hour) for the given timezone
689 // offset.
690 // Note: this is converting FROM local time to UTC, so the timezone offset is
691 // subtracted instead of added.
692 void adjustTimeForTimeZone(Int32 timezone_offset, Int32& year, Int32& month,
693 Int32& day, Int32& hour)
694 {
695  if ( timezone_offset < 0 )
696  {
697  hour -= timezone_offset;
698 
699  if ( hour > 23 )
700  {
701  ++day;
702  hour -= 24;
703  }
704  // This assumes that the timezone will not shove a date by more than one day.
705  if ( day > getDaysPerMonth(year, month) )
706  {
707  ++month;
708  day = 1;
709  }
710  if ( month > 12 )
711  {
712  month -= 12;
713  ++year;
714  }
715  }
716  else if ( timezone_offset > 0 )
717  {
718  hour -= timezone_offset;
719 
720  if ( hour < 0 )
721  {
722  --day;
723  hour += 24;
724  }
725  // This assumes that the timezone will not shove a date by more than one day.
726  if ( day < 1 )
727  {
728  --month;
729  day += getDaysPerMonth(year, month);
730  }
731  if ( month < 1 )
732  {
733  month += 12;
734  --year;
735  }
736  }
737 }
738 
739 
740 } // end anonymous namespace
741 
744 {
745  // CIM format
746  if ( str.length() == 25 )
747  {
748  // validate required characters
749  if ( !(str[14] != '.' || (str[21] != '+' && str[21] != '-')) )
750  {
751  try
752  {
753  // in CIM, "Fields which are not significant must be
754  // replaced with asterisk characters." We'll convert
755  // asterisks to 0s so we can process them.
756  String strNoAsterisks(str);
757  for (size_t i = 0; i < strNoAsterisks.length(); ++i)
758  {
759  if (strNoAsterisks[i] == '*')
760  {
761  strNoAsterisks[i] = '0';
762  }
763  }
764  Int32 year = strNoAsterisks.substring(0, 4).toInt32();
765  Int32 month = strNoAsterisks.substring(4, 2).toInt32();
766  Int32 day = strNoAsterisks.substring(6, 2).toInt32();
767  Int32 hour = strNoAsterisks.substring(8, 2).toInt32();
768  Int32 minute = strNoAsterisks.substring(10, 2).toInt32();
769  Int32 second = strNoAsterisks.substring(12, 2).toInt32();
770  Int32 microseconds = strNoAsterisks.substring(15, 6).toInt32();
771 
772  validateRanges(year, month, day, hour, minute, second, microseconds, str);
773 
774  Int32 utc = strNoAsterisks.substring(22, 3).toInt32();
775  // adjust the time to utc. According to the CIM spec:
776  // "utc is the offset from UTC in minutes"
777  if (str[21] == '+')
778  {
779  utc = 0 - utc;
780  }
781  minute += utc;
782 
783  set(year, month, day, hour, minute, second,
784  microseconds, E_UTC_TIME);
785  return;
786  }
788  {
789  // Instead of throwing another exception here, we'll try to parse it in
790  // a more general way below.
791  }
792  }
793  }
794 
795  // It didn't return from above, so it's not a CIM datetime. Try to parse
796  // it as a free-form date string.
797  if ( !str.empty() )
798  {
799  // This is a general method of extracting the date.
800  // It still assumes english names for months and days of week.
801 
802  String weekday;
803  String day;
804  String time;
805  int timezone_number = LOCAL_TIME_OFFSET - 1;
806  Int32 month_number = -1;
807  String year;
808 
809  StringArray tokenized_date = str.tokenize();
810 
811  // Attempt to fill in the above list of strings...
812  for ( StringArray::const_iterator date_token = tokenized_date.begin();
813  date_token != tokenized_date.end();
814  ++date_token )
815  {
816  // Check to see if it's a day of the week.
817  if ( isDOWValid( date_token->c_str() ) )
818  {
819  if ( weekday.empty() )
820  {
821  if ( date_token->length() > 3 )
822  {
823  if ( isLongDOWValid( *date_token ) )
824  {
825  weekday = *date_token;
826  }
827  else
828  {
829  // Invalid long day of week
830  badDateTime(str);
831  }
832  }
833  else
834  {
835  weekday = *date_token;
836  }
837  }
838  else
839  {
840  // Multiple weekdays.
841  badDateTime(str);
842  }
843  }
844  // Only do this comparison if a month has not already been found.
845  else if ( (month_number == -1) &&
846  (month_number = decodeShortMonth( date_token->c_str() ) ) != -1 )
847  {
848  if ( date_token->length() > 3 )
849  {
850  month_number = decodeLongMonth( date_token->c_str() );
851 
852  if ( month_number == -1 )
853  {
854  // Invalid characters in the long version of the month.
855  badDateTime(str);
856  }
857  }
858  }
859  // Get the time, if the time wasn't already set.
860  else if ( time.empty() && (date_token->indexOf(":") != String::npos) )
861  {
862  // This will be checked below... Assume it's correct.
863  time = *date_token;
864  }
865  // If a day hasn't been found, and this is a number, assume it's the day.
866  else if ( day.empty() && isdigit((*date_token)[0]) )
867  {
868  day = *date_token;
869  }
870  // If a year hasn't been found, and this is a number, assume it's the year.
871  else if ( year.empty() && isdigit((*date_token)[0]) )
872  {
873  year = *date_token;
874  }
875  else if ( (timezone_number <= LOCAL_TIME_OFFSET) &&
876  (date_token->length() >= 1) &&
877  (date_token->length() <= 4) &&
878  getTimeZoneOffset(*date_token, timezone_number) )
879  {
880  // Matched the timezone (nothing to do, it's already been set).
881  }
882  else
883  {
884  badDateTime(str);
885  }
886 
887  } // for each token.
888 
889 
890  // Done looking at tokens. Verify that all the required fields are present.
891  if ( (month_number >= 1) && !day.empty() && !time.empty() && !year.empty() )
892  {
893  // We've got enough to construct the date.
894 
895  // Parse the time
896  StringArray time_fields = time.tokenize(":");
897 
898  // We need at least the hour and minute, anything other than H:M:S should
899  // be in error.
900  if ( (time_fields.size() < 2) || (time_fields.size() > 3) )
901  {
902  badDateTime(str);
903  }
904 
905  try
906  {
907 
908  Int32 hour;
909  Int32 minute;
910  Int32 second = 0;
911  UInt32 microseconds = 0;
912  Int32 year_number = year.toInt32();
913  Int32 day_number = day.toInt32();
914 
915  hour = time_fields[0].toInt32();
916  minute = time_fields[1].toInt32();
917 
918  if ( time_fields.size() == 3 )
919  {
920  second = time_fields[2].toInt32();
921  }
922 
923  validateRanges(year_number, month_number, day_number,
924  hour, minute, second, microseconds, str);
925 
926  if ( timezone_number <= LOCAL_TIME_OFFSET )
927  {
928  set(year_number, month_number, day_number, hour,
929  minute, second, microseconds, E_LOCAL_TIME);
930  }
931  else
932  {
933  // Adjust the time for the timezone.
934  // The current numbers have already been validated, so any changes
935  // should not do anything unexpected.
936 
937  adjustTimeForTimeZone(timezone_number, year_number, month_number, day_number, hour);
938 
939  // Check again.
940  validateRanges(year_number, month_number, day_number, hour,
941  minute, second, microseconds, str);
942 
943  set(year_number, month_number, day_number, hour,
944  minute, second, microseconds, E_UTC_TIME);
945  }
946  }
947  catch (const StringConversionException&)
948  {
949  badDateTime(str);
950  }
951  }
952  else
953  {
954  // Not all required fields available.
955  badDateTime(str);
956  }
957  }
958  else
959  {
960  // An empty string.
961  badDateTime(str);
962  }
963 }
965 DateTime::DateTime(time_t t, UInt32 microseconds)
966  : m_time(t)
967  , m_microseconds(microseconds)
968 {
969 }
971 DateTime::DateTime(int year, int month, int day, int hour, int minute,
972  int second, UInt32 microseconds, ETimeOffset timeOffset)
973 {
974  set(year, month, day, hour, minute, second, microseconds, timeOffset);
975 }
978 {
979 }
981 inline tm
982 DateTime::getTm(ETimeOffset timeOffset) const
983 {
984  if (timeOffset == E_LOCAL_TIME)
985  {
986  tm theTime;
987  localtime_r(&m_time, &theTime);
988  return theTime;
989  }
990  else // timeOffset == E_UTC_TIME
991  {
992  tm theTime;
993  gmtime_r(&m_time, &theTime);
994  return theTime;
995  }
996 }
997 
999 inline void
1000 DateTime::setTime(tm& tmarg, ETimeOffset timeOffset)
1001 {
1002  if (timeOffset == E_LOCAL_TIME)
1003  {
1004  m_time = ::mktime(&tmarg);
1005  }
1006  else // timeOffset == E_UTC_TIME
1007  {
1008 #ifdef BLOCXX_HAVE_TIMEGM
1009  m_time = ::timegm(&tmarg);
1010 #else
1011  // timezone is a global that is set by mktime() which is "the
1012  // difference, in seconds, between Coordinated Universal Time
1013  // (UTC) and local standard time."
1014 #ifdef BLOCXX_NETWARE
1015  m_time = ::mktime(&tmarg) - _timezone;
1016 #else
1017  m_time = ::mktime(&tmarg) - ::timezone;
1018 #endif
1019 #endif
1020  }
1021  // apparently some implementations of timegm return something other than -1 on error, but still < 0...
1022  if (m_time < 0)
1023  {
1024  char buff[30];
1025  String extraError;
1026 
1027  if( tmarg.tm_wday < 0 || tmarg.tm_wday > 6 )
1028  {
1029  extraError += Format("Invalid weekday: %1. ", tmarg.tm_wday);
1030  tmarg.tm_wday = 0;
1031  }
1032 
1033  if( tmarg.tm_mon < 0 || tmarg.tm_mon > 11 )
1034  {
1035  extraError += Format("Invalid month: %1. ", tmarg.tm_mon);
1036  tmarg.tm_mon = 0;
1037  }
1038 
1039  asctime_r(&tmarg, buff);
1040 
1041  BLOCXX_THROW(DateTimeException, Format("Unable to represent time \"%1\" as a time_t. %2", buff, extraError).toString().rtrim().c_str());
1042  }
1043 }
1045 int
1047 {
1048  return getTm(timeOffset).tm_hour;
1049 }
1051 int
1053 {
1054  return getTm(timeOffset).tm_min;
1055 }
1057 int
1059 {
1060  return getTm(timeOffset).tm_sec;
1061 }
1063 UInt32
1065 {
1066  return m_microseconds;
1067 }
1069 int
1071 {
1072  return getTm(timeOffset).tm_mday;
1073 }
1075 int
1077 {
1078  return getTm(timeOffset).tm_wday;
1079 }
1081 int
1083 {
1084  return getTm(timeOffset).tm_mon+1;
1085 }
1087 int
1089 {
1090  return (getTm(timeOffset).tm_year + 1900);
1091 }
1093 time_t
1095 {
1096  return m_time;
1097 }
1099 void
1100 DateTime::setHour(int hour, ETimeOffset timeOffset)
1101 {
1102  tm theTime = getTm(timeOffset);
1103  theTime.tm_hour = hour;
1104  setTime(theTime, timeOffset);
1105 }
1107 void
1108 DateTime::setMinute(int minute, ETimeOffset timeOffset)
1109 {
1110  tm theTime = getTm(timeOffset);
1111  theTime.tm_min = minute;
1112  setTime(theTime, timeOffset);
1113 }
1115 void
1116 DateTime::setSecond(int second, ETimeOffset timeOffset)
1117 {
1118  tm theTime = getTm(timeOffset);
1119  theTime.tm_sec = second;
1120  setTime(theTime, timeOffset);
1121 }
1123 void
1124 DateTime::setMicrosecond(UInt32 microseconds)
1125 {
1126  if (microseconds > 999999)
1127  {
1128  BLOCXX_THROW(DateTimeException, Format("invalid microseconds: %1", microseconds).c_str());
1129  }
1130  m_microseconds = microseconds;
1131 }
1133 void
1134 DateTime::setTime(int hour, int minute, int second, ETimeOffset timeOffset)
1135 {
1136  tm theTime = getTm(timeOffset);
1137  theTime.tm_hour = hour;
1138  theTime.tm_min = minute;
1139  theTime.tm_sec = second;
1140  setTime(theTime, timeOffset);
1141 }
1143 void
1144 DateTime::setDay(int day, ETimeOffset timeOffset)
1145 {
1146  tm theTime = getTm(timeOffset);
1147  theTime.tm_mday = day;
1148  setTime(theTime, timeOffset);
1149 }
1151 void
1152 DateTime::setMonth(int month, ETimeOffset timeOffset)
1153 {
1154  if (month == 0)
1155  {
1156  BLOCXX_THROW(DateTimeException, "invalid month: 0");
1157  }
1158 
1159  tm theTime = getTm(timeOffset);
1160  theTime.tm_mon = month-1;
1161  setTime(theTime, timeOffset);
1162 }
1164 void
1165 DateTime::setYear(int year, ETimeOffset timeOffset)
1166 {
1167  tm theTime = getTm(timeOffset);
1168  theTime.tm_year = year - 1900;
1169  setTime(theTime, timeOffset);
1170 }
1172 void
1173 DateTime::set(int year, int month, int day, int hour, int minute, int second,
1174  UInt32 microseconds, ETimeOffset timeOffset)
1175 {
1176  tm tmarg;
1177  memset(&tmarg, 0, sizeof(tmarg));
1178  tmarg.tm_year = (year >= 1900) ? year - 1900 : year;
1179  tmarg.tm_mon = month-1;
1180  tmarg.tm_mday = day;
1181  tmarg.tm_hour = hour;
1182  tmarg.tm_min = minute;
1183  tmarg.tm_sec = second;
1184  if (timeOffset == E_UTC_TIME)
1185  {
1186  tmarg.tm_isdst = 0; // don't want dst applied to utc time!
1187  }
1188  else
1189  {
1190  tmarg.tm_isdst = -1; // don't know about daylight savings time
1191  }
1192  setTime(tmarg, timeOffset);
1193  m_microseconds = microseconds;
1194 }
1196 void
1198 {
1199 #ifdef BLOCXX_HAVE_GETTIMEOFDAY
1200  timeval tv;
1201  gettimeofday(&tv, NULL);
1202  m_time = tv.tv_sec;
1203  m_microseconds = tv.tv_usec;
1204 #else
1205  SYSTEMTIME st;
1206  GetSystemTime(&st);
1207  tm theTime;
1208 
1209  theTime.tm_hour = st.wHour;
1210  theTime.tm_min = st.wMinute;
1211  theTime.tm_sec = st.wSecond;
1212  theTime.tm_year = st.wYear - 1900;
1213  theTime.tm_mon = st.wMonth - 1;
1214  theTime.tm_mday = st.wDay;
1215  theTime.tm_wday = st.wDayOfWeek;
1216  theTime.tm_yday = 0;
1217  theTime.tm_isdst = -1;
1218 
1219  m_time = mktime(&theTime);
1220  m_microseconds = st.wMilliseconds*1000;
1221 #endif
1222 }
1224 void
1226 {
1227  tm theTime = getTm(E_UTC_TIME);
1228  theTime.tm_mday += days;
1229  setTime(theTime, E_UTC_TIME);
1230 }
1232 void
1234 {
1235  tm theTime = getTm(E_UTC_TIME);
1236  theTime.tm_year += years;
1237  setTime(theTime, E_UTC_TIME);
1238 }
1240 void
1242 {
1243  tm theTime = getTm(E_UTC_TIME);
1244  theTime.tm_mon += months;
1245  setTime(theTime, E_UTC_TIME);
1246 }
1248 String
1250 {
1251  tm theTime = getTm(timeOffset);
1252  char buff[30];
1253  asctime_r(&theTime, buff);
1254  String s(buff);
1255  return s;
1256 }
1257 
1259 String DateTime::toString(char const * format, ETimeOffset timeOffset) const
1260 {
1261  tm theTime = getTm(timeOffset);
1262  size_t const BUFSZ = 1024;
1263  char buf[BUFSZ];
1264  size_t n = strftime(buf, BUFSZ, format, &theTime);
1265  buf[n >= BUFSZ ? 0 : n] = '\0';
1266  return String(buf);
1267 }
1268 
1270 char const DateTime::DEFAULT_FORMAT[] = "%c";
1271 
1273 String
1275 {
1276  return toString(E_UTC_TIME);
1277 }
1278 
1280 Int16 DateTime::localTimeAndOffset(time_t t, struct tm & t_loc)
1281 {
1282  struct tm t_utc;
1283  struct tm * ptm_utc = ::gmtime_r(&t, &t_utc);
1284  struct tm * ptm_loc = ::localtime_r(&t, &t_loc);
1285  if (!ptm_utc || !ptm_loc)
1286  {
1287  BLOCXX_THROW(DateTimeException, Format("Invalid time_t: %1", t).c_str());
1288  }
1289  int min_diff =
1290  (t_loc.tm_min - t_utc.tm_min) + 60 * (t_loc.tm_hour - t_utc.tm_hour);
1291  // Note: UTC offsets can be greater than 12 hours, but are guaranteed to
1292  // be less than 24 hours.
1293  int day_diff = t_loc.tm_mday - t_utc.tm_mday;
1294  int const one_day = 24 * 60;
1295  if (day_diff == 0)
1296  {
1297  return min_diff;
1298  }
1299  else if (day_diff == 1 || day_diff < -1)
1300  {
1301  // if day_diff < -1, then UTC day is last day of month and local day
1302  // is 1st of next month.
1303  return min_diff + one_day;
1304  }
1305  else /* day_diff == -1 || day_diff > 1 */
1306  {
1307  // if day_diff > 1, then UTC day is 1st of month and local day is last
1308  // day of previous month.
1309  return min_diff - one_day;
1310  }
1311 }
1312 
1314 void
1315 DateTime::set(time_t t, UInt32 microseconds)
1316 {
1317  if (t == static_cast<time_t>(-1) || microseconds > 999999)
1318  {
1319  BLOCXX_THROW(DateTimeException, "Either t == -1 or microseconds > 999999");
1320  }
1321 
1322  m_time = t;
1323  m_microseconds = microseconds;
1324 }
1325 
1327 // static
1328 DateTime
1330 {
1331  DateTime current;
1332  current.setToCurrent();
1333  return current;
1334 }
1335 
1337 DateTime operator-(DateTime const & x, DateTime const & y)
1338 {
1339  time_t diff = x.get() - y.get();
1340  Int32 microdiff = (Int32)x.getMicrosecond() - (Int32)y.getMicrosecond();
1341  if (microdiff < 0)
1342  {
1343  --diff;
1344  microdiff += 1000000;
1345  }
1346  return DateTime(diff, (UInt32)microdiff);
1347 }
1348 
1349 } // end namespace BLOCXX_NAMESPACE
1350