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
38
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"
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
57namespace
58{
59 BLOCXX_NAMESPACE::Mutex localtimeMutex;
60}
61struct 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
76namespace
77{
78 BLOCXX_NAMESPACE::Mutex gmtimeMutex;
79}
80struct 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
95namespace
96{
97 BLOCXX_NAMESPACE::Mutex asctimeMutex;
98}
99char *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
115namespace BLOCXX_NAMESPACE
116{
117
120
123 : m_time(0)
124 , m_microseconds(0)
125
126{
127}
128
129namespace
130{
131
132inline void badDateTime(const String& str)
133{
134 BLOCXX_THROW(DateTimeException, Format("Invalid DateTime: %1", str).c_str());
135}
136
137inline void validateRanges(Int32 year, Int32 month, Int32 day, Int32 hour,
138Int32 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
152inline 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
248inline 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
264inline 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
359inline 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.
415const int LOCAL_TIME_OFFSET = -24;
416bool 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
652Int32 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.
692void adjustTimeForTimeZone(Int32 timezone_offset, Int32& year, Int32& month,
693Int32& 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}
964
965DateTime::DateTime(time_t t, UInt32 microseconds)
966 : m_time(t)
967 , m_microseconds(microseconds)
968{
969}
970
971DateTime::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}
976
980
981inline tm
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
999inline void
1000DateTime::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}
1044
1045int
1047{
1048 return getTm(timeOffset).tm_hour;
1049}
1050
1051int
1053{
1054 return getTm(timeOffset).tm_min;
1055}
1056
1057int
1059{
1060 return getTm(timeOffset).tm_sec;
1061}
1062
1063UInt32
1065{
1066 return m_microseconds;
1067}
1068
1069int
1071{
1072 return getTm(timeOffset).tm_mday;
1073}
1074
1075int
1077{
1078 return getTm(timeOffset).tm_wday;
1079}
1080
1081int
1083{
1084 return getTm(timeOffset).tm_mon+1;
1085}
1086
1087int
1089{
1090 return (getTm(timeOffset).tm_year + 1900);
1091}
1092
1093time_t
1095{
1096 return m_time;
1097}
1098
1099void
1100DateTime::setHour(int hour, ETimeOffset timeOffset)
1101{
1102 tm theTime = getTm(timeOffset);
1103 theTime.tm_hour = hour;
1104 setTime(theTime, timeOffset);
1105}
1106
1107void
1108DateTime::setMinute(int minute, ETimeOffset timeOffset)
1109{
1110 tm theTime = getTm(timeOffset);
1111 theTime.tm_min = minute;
1112 setTime(theTime, timeOffset);
1113}
1114
1115void
1116DateTime::setSecond(int second, ETimeOffset timeOffset)
1117{
1118 tm theTime = getTm(timeOffset);
1119 theTime.tm_sec = second;
1120 setTime(theTime, timeOffset);
1121}
1122
1123void
1124DateTime::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}
1132
1133void
1134DateTime::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}
1142
1143void
1144DateTime::setDay(int day, ETimeOffset timeOffset)
1145{
1146 tm theTime = getTm(timeOffset);
1147 theTime.tm_mday = day;
1148 setTime(theTime, timeOffset);
1149}
1150
1151void
1152DateTime::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}
1163
1164void
1165DateTime::setYear(int year, ETimeOffset timeOffset)
1166{
1167 tm theTime = getTm(timeOffset);
1168 theTime.tm_year = year - 1900;
1169 setTime(theTime, timeOffset);
1170}
1171
1172void
1173DateTime::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}
1195
1196void
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}
1223
1224void
1226{
1227 tm theTime = getTm(E_UTC_TIME);
1228 theTime.tm_mday += days;
1229 setTime(theTime, E_UTC_TIME);
1230}
1231
1232void
1234{
1235 tm theTime = getTm(E_UTC_TIME);
1236 theTime.tm_year += years;
1237 setTime(theTime, E_UTC_TIME);
1238}
1239
1240void
1242{
1243 tm theTime = getTm(E_UTC_TIME);
1244 theTime.tm_mon += months;
1245 setTime(theTime, E_UTC_TIME);
1246}
1247
1248String
1250{
1251 tm theTime = getTm(timeOffset);
1252 char buff[30];
1253 asctime_r(&theTime, buff);
1254 String s(buff);
1255 return s;
1256}
1257
1259String 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
1270char const DateTime::DEFAULT_FORMAT[] = "%c";
1271
1273String
1275{
1276 return toString(E_UTC_TIME);
1277}
1278
1280Int16 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
1314void
1315DateTime::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
1330{
1331 DateTime current;
1332 current.setToCurrent();
1333 return current;
1334}
1335
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
char * asctime_r(const struct tm *tm, char *result)
Definition DateTime.cpp:99
struct tm * gmtime_r(const time_t *timep, struct tm *result)
Definition DateTime.cpp:80
struct tm * localtime_r(const time_t *timep, struct tm *result)
Definition DateTime.cpp:61
#define BLOCXX_DEFINE_EXCEPTION_WITH_ID(NAME)
Define a new exception class named <NAME>Exception that derives from Exception.
#define BLOCXX_THROW(exType, msg)
Throw an exception using FILE and LINE.
size_type size() const
V::const_iterator const_iterator
Definition Array.hpp:85
The DateTime class is an abstraction for date time data.
Definition DateTime.hpp:81
ETimeOffset
The values of this enum are passed to various functions to as a flag to indicate the timezone context...
Definition DateTime.hpp:91
static char const DEFAULT_FORMAT[]
A default date/time format to use with toString().
Definition DateTime.hpp:563
void setSecond(int second, ETimeOffset timeOffset=E_LOCAL_TIME)
Set the second component of this DateTime object.
DateTime()
Create a new DateTime object that represents the Epoch (00:00:00 UTC, January 1, 1970)
Definition DateTime.cpp:122
void addMonths(int months)
Add months to the date represented by this object.
int getHour(ETimeOffset timeOffset=E_LOCAL_TIME) const
Get the hour of the day for this DateTime object 0-23.
UInt32 getMicrosecond() const
Get the microsecond of the second for this DateTime object.
void addYears(int years)
Add years to the date represent by this object.
String toStringGMT() const BLOCXX_DEPRECATED
This is the same as toString(E_UTC_TIME).
void setMicrosecond(UInt32 microsecond)
Set the microsecond component of this DateTime object.
int getMinute(ETimeOffset timeOffset=E_LOCAL_TIME) const
Get the minute of the hour for this DateTime object 0-59.
void setHour(int hour, ETimeOffset timeOffset=E_LOCAL_TIME)
Set the hour component of this DateTime object.
void setMonth(int month, ETimeOffset timeOffset=E_LOCAL_TIME)
Set the month component of this DateTime object.
void setTime(int hour, int minute, int second, ETimeOffset timeOffset=E_LOCAL_TIME)
Set the time component of this DateTime object.
static Int16 localTimeAndOffset(time_t t, struct tm &tt)
int getSecond(ETimeOffset timeOffset=E_LOCAL_TIME) const
Get the second of the minute for this DateTime object normally in the range 0-59, but can be up to 60...
void setDay(int day, ETimeOffset timeOffset=E_LOCAL_TIME)
Set the day component of this DateTime object.
void setMinute(int minute, ETimeOffset timeOffset=E_LOCAL_TIME)
Set the minute component of this DateTime object.
int getDow(ETimeOffset timeOffset=E_LOCAL_TIME) const
String toString(ETimeOffset timeOffset=E_LOCAL_TIME) const
int getMonth(ETimeOffset timeOffset=E_LOCAL_TIME) const
void setToCurrent()
Set this DateTime to the current system time.
void setYear(int year, ETimeOffset timeOffset=E_LOCAL_TIME)
Set the year component of this DateTime object.
int getYear(ETimeOffset timeOffset=E_LOCAL_TIME) const
void addDays(int days)
Add days to the date represented by this object.
tm getTm(ETimeOffset timeOffset) const
Definition DateTime.cpp:982
static DateTime getCurrent()
Gets a DateTime instance set to the current system time.
void set(time_t t, UInt32 microseconds=0)
Set this DateTime object with a time_t value.
int getDay(ETimeOffset timeOffset=E_LOCAL_TIME) const
Get the day of the month (1-31)
This String class is an abstract data type that represents as NULL terminated string of characters.
Definition String.hpp:67
size_t length() const
Definition String.cpp:354
String substring(size_t beginIndex, size_t length=npos) const
Create another String object that is comprised of a substring of this String object.
Definition String.cpp:698
StringArray tokenize(const char *delims=" \n\r\t\v", EReturnDelimitersFlag returnDelimitersAsTokens=E_DISCARD_DELIMITERS, EEmptyTokenReturnFlag returnEmptyTokens=E_SKIP_EMPTY_TOKENS) const
Tokenize this String object using the given delimeters.
Definition String.cpp:1147
Int32 toInt32(int base=10) const
Definition String.cpp:1117
static const size_t npos
Definition String.hpp:742
Taken from RFC 1321.
Char16 operator-(const Char16 &arg1, const Char16 &arg2)
Definition Char16.hpp:320
Array< String > StringArray
Definition CommonFwd.hpp:73