blocxx

DateTime.cpp

Go to the documentation of this file.
00001 /*******************************************************************************
00002 * Copyright (C) 2005, Vintela, Inc. All rights reserved.
00003 * Copyright (C) 2006, Novell, Inc. All rights reserved.
00004 * 
00005 * Redistribution and use in source and binary forms, with or without
00006 * modification, are permitted provided that the following conditions are met:
00007 * 
00008 *     * Redistributions of source code must retain the above copyright notice,
00009 *       this list of conditions and the following disclaimer.
00010 *     * Redistributions in binary form must reproduce the above copyright
00011 *       notice, this list of conditions and the following disclaimer in the
00012 *       documentation and/or other materials provided with the distribution.
00013 *     * Neither the name of 
00014 *       Vintela, Inc., 
00015 *       nor Novell, Inc., 
00016 *       nor the names of its contributors or employees may be used to 
00017 *       endorse or promote products derived from this software without 
00018 *       specific prior written permission.
00019 * 
00020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00021 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00022 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00023 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00024 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00025 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00026 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00027 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00028 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00029 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00030 * POSSIBILITY OF SUCH DAMAGE.
00031 *******************************************************************************/
00032 
00033 
00039 #include "blocxx/BLOCXX_config.h"
00040 #include "blocxx/DateTime.hpp"
00041 #include "blocxx/String.hpp"
00042 #include "blocxx/Array.hpp"
00043 #include "blocxx/Format.hpp"
00044 #include "blocxx/Mutex.hpp"
00045 #include "blocxx/MutexLock.hpp"
00046 #include "blocxx/ExceptionIds.hpp"
00047 
00048 #include <time.h>
00049 #ifdef BLOCXX_HAVE_SYS_TIME_H
00050 #include <sys/time.h>
00051 #endif
00052 
00053 #include <cctype>
00054 
00055 
00056 #ifndef BLOCXX_HAVE_LOCALTIME_R
00057 namespace
00058 {
00059    BLOCXX_NAMESPACE::Mutex localtimeMutex;
00060 }
00061 struct tm *localtime_r(const time_t *timep, struct tm *result)
00062 {
00063    BLOCXX_NAMESPACE::MutexLock lock(localtimeMutex);
00064    struct tm *p = localtime(timep);
00065    
00066    if (p)
00067    {
00068       *(result) = *p;
00069    }
00070    
00071    return p;
00072 }
00073 #endif
00074 
00075 #ifndef BLOCXX_HAVE_GMTIME_R
00076 namespace
00077 {
00078    BLOCXX_NAMESPACE::Mutex gmtimeMutex;
00079 }
00080 struct tm *gmtime_r(const time_t *timep, struct tm *result)
00081 {
00082    BLOCXX_NAMESPACE::MutexLock lock(gmtimeMutex);
00083    struct tm *p = gmtime(timep);
00084    
00085    if (p)
00086    {
00087       *(result) = *p;
00088    }
00089    
00090    return p;
00091 }
00092 #endif
00093 
00094 #ifndef BLOCXX_HAVE_ASCTIME_R
00095 namespace
00096 {
00097    BLOCXX_NAMESPACE::Mutex asctimeMutex;
00098 }
00099 char *asctime_r(const struct tm *tm, char *result)
00100 {
00101    BLOCXX_NAMESPACE::MutexLock lock(asctimeMutex);
00102    char *p = asctime(tm);
00103    
00104    if (p)
00105    {
00106       //asctime_r requires a buffer to be at least 26 chars in size
00107       ::strncpy(result,p,25);
00108       result[25] = 0;
00109    }
00110    
00111    return result;
00112 }
00113 #endif
00114 
00115 namespace BLOCXX_NAMESPACE
00116 {
00117 
00119 BLOCXX_DEFINE_EXCEPTION_WITH_ID(DateTime);
00120 
00122 DateTime::DateTime()
00123    : m_time(0)
00124    , m_microseconds(0)
00125 
00126 {
00127 }
00129 namespace
00130 {
00131 
00132 inline void badDateTime(const String& str)
00133 {
00134    BLOCXX_THROW(DateTimeException, Format("Invalid DateTime: %1", str).c_str());
00135 }
00136 
00137 inline void validateRanges(Int32 year, Int32 month, Int32 day, Int32 hour,
00138 Int32 minute, Int32 second, Int32 microseconds, const String& str)
00139 {
00140    if (year < 0 || year > 9999 ||
00141    month < 1 || month > 12 ||
00142    day < 1 || day > 31 ||
00143    hour < 0 || hour > 23 ||
00144    minute < 0 || minute > 59 ||
00145    second < 0 || second > 60 ||
00146    microseconds < 0 || microseconds > 999999)
00147    {
00148       badDateTime(str);
00149    }
00150 }
00151 
00152 inline bool isDOWValid(const char* str)
00153 {
00154    // a little FSM to validate the day of the week
00155    bool good = true;
00156    if (str[0] == 'S') // Sun, Sat
00157    {
00158       if (str[1] == 'u')
00159       {
00160          if (str[2] != 'n') // Sun
00161          {
00162             good = false;
00163          }
00164       }
00165       else if (str[1] ==  'a')
00166       {
00167          if (str[2] != 't') // Sat
00168          {
00169             good = false;
00170          }
00171       }
00172       else
00173       {
00174          good = false;
00175       }
00176    }
00177    else if (str[0] == 'M') // Mon
00178    {
00179       if (str[1] == 'o')
00180       {
00181          if (str[2] != 'n')
00182          {
00183             good = false;
00184          }
00185       }
00186       else
00187       {
00188          good = false;
00189       }
00190    }
00191    else if (str[0] == 'T') // Tue, Thu
00192    {
00193       if (str[1] == 'u')
00194       {
00195          if (str[2] != 'e') // Tue
00196          {
00197             good = false;
00198          }
00199       }
00200       else if (str[1] ==  'h')
00201       {
00202          if (str[2] != 'u') // Thu
00203          {
00204             good = false;
00205          }
00206       }
00207       else
00208       {
00209          good = false;
00210       }
00211    }
00212    else if (str[0] == 'W') // Wed
00213    {
00214       if (str[1] == 'e')
00215       {
00216          if (str[2] != 'd')
00217          {
00218             good = false;
00219          }
00220       }
00221       else
00222       {
00223          good = false;
00224       }
00225    }
00226    else if (str[0] == 'F') // Fri
00227    {
00228       if (str[1] == 'r')
00229       {
00230          if (str[2] != 'i')
00231          {
00232             good = false;
00233          }
00234       }
00235       else
00236       {
00237          good = false;
00238       }
00239    }
00240    else
00241    {
00242       good = false;
00243    }
00244 
00245    return good;
00246 }
00247 
00248 inline bool isLongDOWValid(const String& s)
00249 {
00250    if ( (s == "Sunday") ||
00251    (s == "Monday") ||
00252    (s == "Tuesday") ||
00253    (s == "Wednesday") ||
00254    (s == "Thursday") ||
00255    (s == "Friday") ||
00256    (s == "Saturday") )
00257    {
00258       return true;
00259    }
00260    return false;
00261 }
00262 
00263 // returns -1 if the month is invalid, 1-12 otherwise
00264 inline int decodeShortMonth(const char* str)
00265 {
00266    // a little FSM to calculate the month
00267    if (str[0] == 'J') // Jan, Jun, Jul
00268    {
00269       if (str[1] == 'a')
00270       {
00271          if (str[2] == 'n') // Jan
00272          {
00273             return 1;
00274          }
00275       }
00276       else if (str[1] ==  'u')
00277       {
00278          if (str[2] == 'n') // Jun
00279          {
00280             return 6;
00281          }
00282          else if (str[2] == 'l') // Jul
00283          {
00284             return 7;
00285          }
00286       }
00287    }
00288    else if (str[0] == 'F') // Feb
00289    {
00290       if (str[1] == 'e' && str[2] == 'b')
00291       {
00292          return 2;
00293       }
00294    }
00295    else if (str[0] == 'M') // Mar, May
00296    {
00297       if (str[1] == 'a')
00298       {
00299          if (str[2] == 'r') // Mar
00300          {
00301             return 3;
00302          }
00303          else if (str[2] == 'y') // May
00304          {
00305             return 5;
00306          }
00307       }
00308    }
00309    else if (str[0] == 'A') // Apr, Aug
00310    {
00311       if (str[1] == 'p')
00312       {
00313          if (str[2] == 'r') // Apr
00314          {
00315             return 4;
00316          }
00317       }
00318       else if (str[1] == 'u')
00319       {
00320          if (str[2] == 'g') // Aug
00321          {
00322             return 8;
00323          }
00324       }
00325    }
00326    else if (str[0] == 'S') // Sep
00327    {
00328       if (str[1] == 'e' && str[2] == 'p')
00329       {
00330          return 9;
00331       }
00332    }
00333    else if (str[0] == 'O') // Oct
00334    {
00335       if (str[1] == 'c' && str[2] == 't')
00336       {
00337          return 10;
00338       }
00339    }
00340    else if (str[0] == 'N') // Nov
00341    {
00342       if (str[1] == 'o' && str[2] == 'v')
00343       {
00344          return 11;
00345       }
00346    }
00347    else if (str[0] == 'D') // Dec
00348    {
00349       if (str[1] == 'e' && str[2] == 'c')
00350       {
00351          return 12;
00352       }
00353    }
00354 
00355    return -1;
00356 }
00357 
00358 // returns -1 if the month is invalid, 1-12 otherwise
00359 inline int decodeLongMonth(const String& str)
00360 {
00361    if ( str.equals("January") )
00362    {
00363       return 1;
00364    }
00365    else if ( str.equals("February") )
00366    {
00367       return 2;
00368    }
00369    else if ( str.equals("March") )
00370    {
00371       return 3;
00372    }
00373    else if ( str.equals("April") )
00374    {
00375       return 4;
00376    }
00377    else if ( str.equals("May") )
00378    {
00379       return 5;
00380    }
00381    else if ( str.equals("June") )
00382    {
00383       return 6;
00384    }
00385    else if ( str.equals("July") )
00386    {
00387       return 7;
00388    }
00389    else if ( str.equals("August") )
00390    {
00391       return 8;
00392    }
00393    else if ( str.equals("September") )
00394    {
00395       return 9;
00396    }
00397    else if ( str.equals("October") )
00398    {
00399       return 10;
00400    }
00401    else if ( str.equals("November") )
00402    {
00403       return 11;
00404    }
00405    else if ( str.equals("December") )
00406    {
00407       return 12;
00408    }
00409    return -1;
00410 }
00411 
00412 // Get the timezone offset (from UTC) for the given timezone.  Valid results
00413 // are in the range -12 to 12, except for the case where LOCAL_TIME_OFFSET is
00414 // returned, in which case UTC should not be used.
00415 const int LOCAL_TIME_OFFSET = -24;
00416 bool getTimeZoneOffset(const String& timezone, int& offset)
00417 {
00418    int temp_offset = LOCAL_TIME_OFFSET -1;
00419    if ( timezone.length() == 1 )
00420    {
00421       // Single-letter abbrev.
00422       // This could be simplified into a couple of if statements with some
00423       // character math, but this should work for now.
00424       switch ( timezone[0] )
00425       {
00426          case 'Y': // Yankee   UTC-12
00427             temp_offset = -12;
00428             break;
00429          case 'X': // Xray     UTC-11
00430             temp_offset = -11;
00431             break;
00432          case 'W': // Whiskey  UTC-10
00433             temp_offset = -10;
00434             break;
00435          case 'V': // Victor   UTC-9
00436             temp_offset = -9;
00437             break;
00438          case 'U': // Uniform  UTC-8
00439             temp_offset = -8;
00440             break;
00441          case 'T': // Tango    UTC-7
00442             temp_offset = -7;
00443             break;
00444          case 'S': // Sierra   UTC-6
00445             temp_offset = -6;
00446             break;
00447          case 'R': // Romeo    UTC-5
00448             temp_offset = -5;
00449             break;
00450          case 'Q': // Quebec   UTC-4
00451             temp_offset = -4;
00452             break;
00453          case 'P': // Papa     UTC-3
00454             temp_offset = -3;
00455             break;
00456          case 'O': // Oscar    UTC-2
00457             temp_offset = -2;
00458             break;
00459          case 'N': // November UTC-1
00460             temp_offset = -1;
00461             break;
00462          case 'Z': // Zulu     UTC
00463             temp_offset = 0;
00464             break;
00465          case 'A': // Aplpha   UTC+1
00466             temp_offset = 1;
00467             break;
00468          case 'B': // Bravo    UTC+2
00469             temp_offset = 2;
00470             break;
00471          case 'C': // Charlie  UTC+3
00472             temp_offset = 3;
00473             break;
00474          case 'D': // Delta    UTC+4
00475             temp_offset = 4;
00476             break;
00477          case 'E': // Echo     UTC+5
00478             temp_offset = 5;
00479             break;
00480          case 'F': // Foxtrot  UTC+6
00481             temp_offset = 6;
00482             break;
00483          case 'G': // Golf     UTC+7
00484             temp_offset = 7;
00485             break;
00486          case 'H': // Hotel    UTC+8
00487             temp_offset = 8;
00488             break;
00489          case 'I': // India    UTC+9
00490             temp_offset = 9;
00491             break;
00492          case 'K': // Kilo     UTC+10
00493             temp_offset = 10;
00494             break;
00495          case 'L': // Lima     UTC+11
00496             temp_offset = 11;
00497             break;
00498          case 'M': // Mike     UTC+12
00499             temp_offset = 12;
00500             break;
00501          case 'J': // Juliet   Always local time
00502             temp_offset = LOCAL_TIME_OFFSET;
00503             break;
00504          default:
00505             break;
00506       }
00507    }
00508    else if ( timezone == "UTC" ) // Universal Time Coordinated, civil time
00509    {
00510       temp_offset = 0;
00511    }
00512    // European timezones
00513    else if ( timezone == "GMT" ) // Greenwich Mean Time   UTC
00514    {
00515       temp_offset = 0;
00516    }
00517    else if ( timezone == "BST" ) // British Summer Time   UTC+1
00518    {
00519       temp_offset = 1;
00520    }
00521    else if ( timezone == "IST" ) // Irish Summer Time     UTC+1
00522    {
00523       temp_offset = 1;
00524    }
00525    else if ( timezone == "WET" ) // Western Europe Time   UTC
00526    {
00527       temp_offset = 0;
00528    }
00529    else if ( timezone == "WEST" ) // Western Europe Summer Time   UTC+1
00530    {
00531       temp_offset = 1;
00532    }
00533    else if ( timezone == "CET" ) // Central Europe Time   UTC+1
00534    {
00535       temp_offset = 1;
00536    }
00537    else if ( timezone == "CEST" ) // Central Europe Summer Time   UTC+2
00538    {
00539       temp_offset = 2;
00540    }
00541    else if ( timezone == "EET" ) // Eastern Europe Time   UTC+2
00542    {
00543       temp_offset = 2;
00544    }
00545    else if ( timezone == "EEST" ) // Eastern Europe Summer Time   UTC+3
00546    {
00547       temp_offset = 3;
00548    }
00549    else if ( timezone == "MSK" ) // Moscow Time   UTC+3
00550    {
00551       temp_offset = 3;
00552    }
00553    else if ( timezone == "MSD" ) // Moscow Summer Time    UTC+4
00554    {
00555       temp_offset = 4;
00556    }
00557    // US and Canada
00558    else if ( timezone == "AST" ) // Atlantic Standard Time        UTC-4
00559    {
00560       temp_offset = -4;
00561    }
00562    else if ( timezone == "ADT" ) // Atlantic Daylight Saving Time UTC-3
00563    {
00564       temp_offset = -3;
00565    }
00566    else if ( timezone == "EST" ) // Eastern Standard Time         UTC-5
00567    {
00568       // CHECKME! This can also be Australian Eastern Standard Time UTC+10
00569       // (UTC+11 in Summer)
00570       temp_offset = -5;
00571    }
00572    else if ( timezone == "EDT" ) // Eastern Daylight Saving Time  UTC-4
00573    {
00574       temp_offset = -4;
00575    }
00576    else if ( timezone == "ET" ) // Eastern Time, either as EST or EDT
00577    // depending on place and time of year
00578    {
00579       // CHECKME! Assuming standard time.
00580       temp_offset = -5;
00581    }
00582    else if ( timezone == "CST" ) // Central Standard Time         UTC-6
00583    {
00584       // CHECKME! This can also be Australian Central Standard Time UTC+9.5
00585       temp_offset = -6;
00586    }
00587    else if ( timezone == "CDT" ) // Central Daylight Saving Time  UTC-5
00588    {
00589       temp_offset = -5;
00590    }
00591    else if ( timezone == "CT" ) // Central Time, either as CST or CDT
00592    // depending on place and time of year
00593    {
00594       // CHECKME! Assuming standard time.
00595       temp_offset = -6;
00596    }
00597    else if ( timezone == "MST" ) // Mountain Standard Time        UTC-7
00598    {
00599       temp_offset = -7;
00600    }
00601    else if ( timezone == "MDT" ) // Mountain Daylight Saving Time UTC-6
00602    {
00603       temp_offset = -6;
00604    }
00605    else if ( timezone == "MT" ) // Mountain Time, either as MST or MDT
00606    // depending on place and time of year
00607    {
00608       // CHECKME! Assuming standard time.
00609       temp_offset = -7;
00610    }
00611    else if ( timezone == "PST" ) // Pacific Standard Time         UTC-8
00612    {
00613       temp_offset = -8;
00614    }
00615    else if ( timezone == "PDT" ) // Pacific Daylight Saving Time  UTC-7
00616    {
00617       temp_offset = -7;
00618    }
00619    else if ( timezone == "PT" ) // Pacific Time, either as PST or PDT
00620    // depending on place and time of year
00621    {
00622       // CHECKME! Assuming standard time.
00623       temp_offset = -8;
00624    }
00625    else if ( timezone == "HST" ) // Hawaiian Standard Time        UTC-10
00626    {
00627       temp_offset = -10;
00628    }
00629    else if ( timezone == "AKST" ) // Alaska Standard Time         UTC-9
00630    {
00631       temp_offset = -9;
00632    }
00633    else if ( timezone == "AKDT" ) // Alaska Standard Daylight Saving Time UTC-8
00634    {
00635       temp_offset = -8;
00636    }
00637    // Australia
00638    else if ( timezone == "WST" ) // Western Standard Time         UTC+8
00639    {
00640       temp_offset = 8;
00641    }
00642 
00643    // Check the results of that huge mess.
00644    if ( temp_offset >= LOCAL_TIME_OFFSET )
00645    {
00646       offset = temp_offset;
00647       return true;
00648    }
00649    return false;
00650 }
00651 
00652 Int32 getDaysPerMonth(Int32 year, Int32 month)
00653 {
00654    const Int32 normal_days_per_month[12] =
00655    { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
00656 
00657    if ( (month >= 1) && (month <= 12) )
00658    {
00659       if ( month != 2 )
00660       {
00661          return normal_days_per_month[month - 1];
00662       }
00663 
00664       int leap_year_adjust = 0;
00665 
00666       if ( (year % 4) == 0 )
00667       {
00668          // Possibly a leap year.
00669          if ( (year % 100) == 0 )
00670          {
00671             if ( (year % 400) == 0 )
00672             {
00673                leap_year_adjust = 1;
00674             }
00675          }
00676          else
00677          {
00678             leap_year_adjust = 1;
00679          }
00680       }
00681 
00682       return normal_days_per_month[month - 1] + leap_year_adjust;
00683       // Check to see if it's a leap year.
00684    }
00685    return 0;
00686 }
00687 
00688 // Adjust the time given (year, month, day, hour) for the given timezone
00689 // offset.
00690 // Note: this is converting FROM local time to UTC, so the timezone offset is
00691 // subtracted instead of added.
00692 void adjustTimeForTimeZone(Int32 timezone_offset, Int32& year, Int32& month,
00693 Int32& day, Int32& hour)
00694 {
00695    if ( timezone_offset < 0 )
00696    {
00697       hour -= timezone_offset;
00698 
00699       if ( hour > 23 )
00700       {
00701          ++day;
00702          hour -= 24;
00703       }
00704       // This assumes that the timezone will not shove a date by more than one day.
00705       if ( day > getDaysPerMonth(year, month) )
00706       {
00707          ++month;
00708          day = 1;
00709       }
00710       if ( month > 12 )
00711       {
00712          month -= 12;
00713          ++year;
00714       }
00715    }
00716    else if ( timezone_offset > 0 )
00717    {
00718       hour -= timezone_offset;
00719 
00720       if ( hour < 0 )
00721       {
00722          --day;
00723          hour += 24;
00724       }
00725       // This assumes that the timezone will not shove a date by more than one day.
00726       if ( day < 1 )
00727       {
00728          --month;
00729          day += getDaysPerMonth(year, month);
00730       }
00731       if ( month < 1 )
00732       {
00733          month += 12;
00734          --year;
00735       }
00736    }
00737 }
00738 
00739 
00740 } // end anonymous namespace
00741 
00743 DateTime::DateTime(const String& str)
00744 {
00745    // CIM format
00746    if ( str.length() == 25 )
00747    {
00748       // validate required characters
00749       if (  !(str[14] != '.' || (str[21] != '+' && str[21] != '-')) )
00750       {
00751          try
00752          {
00753             // in CIM, "Fields which are not significant must be
00754             // replaced with asterisk characters."  We'll convert
00755             // asterisks to 0s so we can process them.
00756             String strNoAsterisks(str);
00757             for (size_t i = 0; i < strNoAsterisks.length(); ++i)
00758             {
00759                if (strNoAsterisks[i] == '*')
00760                {
00761                   strNoAsterisks[i] = '0';
00762                }
00763             }
00764             Int32 year = strNoAsterisks.substring(0, 4).toInt32();
00765             Int32 month = strNoAsterisks.substring(4, 2).toInt32();
00766             Int32 day = strNoAsterisks.substring(6, 2).toInt32();
00767             Int32 hour = strNoAsterisks.substring(8, 2).toInt32();
00768             Int32 minute = strNoAsterisks.substring(10, 2).toInt32();
00769             Int32 second = strNoAsterisks.substring(12, 2).toInt32();
00770             Int32 microseconds = strNoAsterisks.substring(15, 6).toInt32();
00771 
00772             validateRanges(year, month, day, hour, minute, second, microseconds, str);
00773 
00774             Int32 utc = strNoAsterisks.substring(22, 3).toInt32();
00775             // adjust the time to utc.  According to the CIM spec:
00776             // "utc is the offset from UTC in minutes"
00777             if (str[21] == '+')
00778             {
00779                utc = 0 - utc;
00780             }
00781             minute += utc;
00782 
00783             set(year, month, day, hour, minute, second,
00784             microseconds, E_UTC_TIME);
00785             return;
00786          }
00787          catch (StringConversionException&)
00788          {
00789             // Instead of throwing another exception here, we'll try to parse it in
00790             // a more general way below.
00791          }
00792       }
00793    }
00794 
00795    // It didn't return from above, so it's not a CIM datetime.  Try to parse
00796    // it as a free-form date string.
00797    if ( !str.empty() )
00798    {
00799       // This is a general method of extracting the date.
00800       // It still assumes english names for months and days of week.
00801 
00802       String weekday;
00803       String day;
00804       String time;
00805       int timezone_number = LOCAL_TIME_OFFSET - 1;
00806       Int32 month_number = -1;
00807       String year;
00808 
00809       StringArray tokenized_date = str.tokenize();
00810 
00811       // Attempt to fill in the above list of strings...
00812       for ( StringArray::const_iterator date_token = tokenized_date.begin();
00813       date_token != tokenized_date.end();
00814       ++date_token )
00815       {
00816          // Check to see if it's a day of the week.
00817          if ( isDOWValid( date_token->c_str() ) )
00818          {
00819             if ( weekday.empty() )
00820             {
00821                if ( date_token->length() > 3 )
00822                {
00823                   if ( isLongDOWValid( *date_token ) )
00824                   {
00825                      weekday = *date_token;
00826                   }
00827                   else
00828                   {
00829                      // Invalid long day of week
00830                      badDateTime(str);
00831                   }
00832                }
00833                else
00834                {
00835                   weekday = *date_token;
00836                }
00837             }
00838             else
00839             {
00840                // Multiple weekdays.
00841                badDateTime(str);
00842             }
00843          }
00844          // Only do this comparison if a month has not already been found.
00845          else if ( (month_number == -1) &&
00846          (month_number = decodeShortMonth( date_token->c_str() ) ) != -1 )
00847          {
00848             if ( date_token->length() > 3 )
00849             {
00850                month_number = decodeLongMonth( date_token->c_str() );
00851 
00852                if ( month_number == -1 )
00853                {
00854                   // Invalid characters in the long version of the month.
00855                   badDateTime(str);
00856                }
00857             }
00858          }
00859          // Get the time, if the time wasn't already set.
00860          else if ( time.empty() && (date_token->indexOf(":") != String::npos) )
00861          {
00862             // This will be checked below... Assume it's correct.
00863             time = *date_token;
00864          }
00865          // If a day hasn't been found, and this is a number, assume it's the day.
00866          else if ( day.empty() && isdigit((*date_token)[0]) )
00867          {
00868             day = *date_token;
00869          }
00870          // If a year hasn't been found, and this is a number, assume it's the year.
00871          else if ( year.empty() && isdigit((*date_token)[0]) )
00872          {
00873             year = *date_token;
00874          }
00875          else if ( (timezone_number <= LOCAL_TIME_OFFSET) &&
00876          (date_token->length() >= 1) &&
00877          (date_token->length() <= 4) &&
00878          getTimeZoneOffset(*date_token, timezone_number) )
00879          {
00880             // Matched the timezone (nothing to do, it's already been set).
00881          }
00882          else
00883          {
00884             badDateTime(str);
00885          }
00886 
00887       } // for each token.
00888 
00889 
00890       // Done looking at tokens.  Verify that all the required fields are present.
00891       if ( (month_number >= 1) && !day.empty() && !time.empty() && !year.empty() )
00892       {
00893          // We've got enough to construct the date.
00894 
00895          // Parse the time
00896          StringArray time_fields = time.tokenize(":");
00897 
00898          // We need at least the hour and minute, anything other than H:M:S should
00899          // be in error.
00900          if ( (time_fields.size() < 2) || (time_fields.size() > 3) )
00901          {
00902             badDateTime(str);
00903          }
00904 
00905          try
00906          {
00907 
00908             Int32 hour;
00909             Int32 minute;
00910             Int32 second = 0;
00911             UInt32 microseconds = 0;
00912             Int32 year_number = year.toInt32();
00913             Int32 day_number = day.toInt32();
00914 
00915             hour = time_fields[0].toInt32();
00916             minute = time_fields[1].toInt32();
00917 
00918             if ( time_fields.size() == 3 )
00919             {
00920                second = time_fields[2].toInt32();
00921             }
00922 
00923             validateRanges(year_number, month_number, day_number,
00924             hour, minute, second, microseconds, str);
00925 
00926             if ( timezone_number <= LOCAL_TIME_OFFSET )
00927             {
00928                set(year_number, month_number, day_number, hour,
00929                minute, second, microseconds, E_LOCAL_TIME);
00930             }
00931             else
00932             {
00933                // Adjust the time for the timezone.
00934                // The current numbers have already been validated, so any changes
00935                // should not do anything unexpected.
00936 
00937                adjustTimeForTimeZone(timezone_number, year_number, month_number, day_number, hour);
00938 
00939                // Check again.
00940                validateRanges(year_number, month_number, day_number, hour,
00941                   minute, second, microseconds, str);
00942 
00943                set(year_number, month_number, day_number, hour,
00944                   minute, second, microseconds, E_UTC_TIME);
00945             }
00946          }
00947          catch (const StringConversionException&)
00948          {
00949             badDateTime(str);
00950          }
00951       }
00952       else
00953       {
00954          // Not all required fields available.
00955          badDateTime(str);
00956       }
00957    }
00958    else
00959    {
00960       // An empty string.
00961       badDateTime(str);
00962    }
00963 }
00965 DateTime::DateTime(time_t t, UInt32 microseconds)
00966    : m_time(t)
00967    , m_microseconds(microseconds)
00968 {
00969 }
00971 DateTime::DateTime(int year, int month, int day, int hour, int minute,
00972    int second, UInt32 microseconds, ETimeOffset timeOffset)
00973 {
00974    set(year, month, day, hour, minute, second, microseconds, timeOffset);
00975 }
00977 DateTime::~DateTime()
00978 {
00979 }
00981 inline tm
00982 DateTime::getTm(ETimeOffset timeOffset) const
00983 {
00984    if (timeOffset == E_LOCAL_TIME)
00985    {
00986       tm theTime;
00987       localtime_r(&m_time, &theTime);
00988       return theTime;
00989    }
00990    else // timeOffset == E_UTC_TIME
00991    {
00992       tm theTime;
00993       gmtime_r(&m_time, &theTime);
00994       return theTime;
00995    }
00996 }
00997 
00999 inline void
01000 DateTime::setTime(tm& tmarg, ETimeOffset timeOffset)
01001 {
01002    if (timeOffset == E_LOCAL_TIME)
01003    {
01004       m_time = ::mktime(&tmarg);
01005    }
01006    else // timeOffset == E_UTC_TIME
01007    {
01008 #ifdef BLOCXX_HAVE_TIMEGM
01009       m_time = ::timegm(&tmarg);
01010 #else
01011       // timezone is a global that is set by mktime() which is "the
01012       // difference, in seconds, between Coordinated Universal Time
01013       // (UTC) and local standard time."
01014 #ifdef BLOCXX_NETWARE
01015       m_time = ::mktime(&tmarg) - _timezone;
01016 #else
01017       m_time = ::mktime(&tmarg) - ::timezone;
01018 #endif
01019 #endif
01020    }
01021    // apparently some implementations of timegm return something other than -1 on error, but still < 0...
01022    if (m_time < 0)
01023    {
01024       char buff[30];
01025       String extraError;
01026 
01027       if( tmarg.tm_wday < 0 || tmarg.tm_wday > 6 )
01028       {
01029          extraError += Format("Invalid weekday: %1. ", tmarg.tm_wday);
01030          tmarg.tm_wday = 0;
01031       }
01032 
01033       if( tmarg.tm_mon < 0 || tmarg.tm_mon > 11 )
01034       {
01035          extraError += Format("Invalid month: %1. ", tmarg.tm_mon);
01036          tmarg.tm_mon = 0;
01037       }
01038 
01039       asctime_r(&tmarg, buff);
01040 
01041       BLOCXX_THROW(DateTimeException, Format("Unable to represent time \"%1\" as a time_t. %2", buff, extraError).toString().rtrim().c_str());
01042    }
01043 }
01045 int
01046 DateTime::getHour(ETimeOffset timeOffset) const
01047 {
01048    return getTm(timeOffset).tm_hour;
01049 }
01051 int
01052 DateTime::getMinute(ETimeOffset timeOffset) const
01053 {
01054    return getTm(timeOffset).tm_min;
01055 }
01057 int
01058 DateTime::getSecond(ETimeOffset timeOffset) const
01059 {
01060    return getTm(timeOffset).tm_sec;
01061 }
01063 UInt32
01064 DateTime::getMicrosecond() const
01065 {
01066    return m_microseconds;
01067 }
01069 int
01070 DateTime::getDay(ETimeOffset timeOffset) const
01071 {
01072    return getTm(timeOffset).tm_mday;
01073 }
01075 int
01076 DateTime::getDow(ETimeOffset timeOffset) const
01077 {
01078    return getTm(timeOffset).tm_wday;
01079 }
01081 int
01082 DateTime::getMonth(ETimeOffset timeOffset) const
01083 {
01084    return getTm(timeOffset).tm_mon+1;
01085 }
01087 int
01088 DateTime::getYear(ETimeOffset timeOffset) const
01089 {
01090    return (getTm(timeOffset).tm_year + 1900);
01091 }
01093 time_t
01094 DateTime::get() const
01095 {
01096    return m_time;
01097 }
01099 void
01100 DateTime::setHour(int hour, ETimeOffset timeOffset)
01101 {
01102    tm theTime = getTm(timeOffset);
01103    theTime.tm_hour = hour;
01104    setTime(theTime, timeOffset);
01105 }
01107 void
01108 DateTime::setMinute(int minute, ETimeOffset timeOffset)
01109 {
01110    tm theTime = getTm(timeOffset);
01111    theTime.tm_min = minute;
01112    setTime(theTime, timeOffset);
01113 }
01115 void
01116 DateTime::setSecond(int second, ETimeOffset timeOffset)
01117 {
01118    tm theTime = getTm(timeOffset);
01119    theTime.tm_sec = second;
01120    setTime(theTime, timeOffset);
01121 }
01123 void
01124 DateTime::setMicrosecond(UInt32 microseconds)
01125 {
01126    if (microseconds > 999999)
01127    {
01128       BLOCXX_THROW(DateTimeException, Format("invalid microseconds: %1", microseconds).c_str());
01129    }
01130    m_microseconds = microseconds;
01131 }
01133 void
01134 DateTime::setTime(int hour, int minute, int second, ETimeOffset timeOffset)
01135 {
01136    tm theTime = getTm(timeOffset);
01137    theTime.tm_hour = hour;
01138    theTime.tm_min = minute;
01139    theTime.tm_sec = second;
01140    setTime(theTime, timeOffset);
01141 }
01143 void
01144 DateTime::setDay(int day, ETimeOffset timeOffset)
01145 {
01146    tm theTime = getTm(timeOffset);
01147    theTime.tm_mday = day;
01148    setTime(theTime, timeOffset);
01149 }
01151 void
01152 DateTime::setMonth(int month, ETimeOffset timeOffset)
01153 {
01154    if (month == 0)
01155    {
01156       BLOCXX_THROW(DateTimeException, "invalid month: 0");
01157    }
01158 
01159    tm theTime = getTm(timeOffset);
01160    theTime.tm_mon = month-1;
01161    setTime(theTime, timeOffset);
01162 }
01164 void
01165 DateTime::setYear(int year, ETimeOffset timeOffset)
01166 {
01167    tm theTime = getTm(timeOffset);
01168    theTime.tm_year = year - 1900;
01169    setTime(theTime, timeOffset);
01170 }
01172 void
01173 DateTime::set(int year, int month, int day, int hour, int minute, int second,
01174    UInt32 microseconds, ETimeOffset timeOffset)
01175 {
01176    tm tmarg;
01177    memset(&tmarg, 0, sizeof(tmarg));
01178    tmarg.tm_year = (year >= 1900) ? year - 1900 : year;
01179    tmarg.tm_mon = month-1;
01180    tmarg.tm_mday = day;
01181    tmarg.tm_hour = hour;
01182    tmarg.tm_min = minute;
01183    tmarg.tm_sec = second;
01184    if (timeOffset == E_UTC_TIME)
01185    {
01186       tmarg.tm_isdst = 0; // don't want dst applied to utc time!
01187    }
01188    else
01189    {
01190       tmarg.tm_isdst = -1; // don't know about daylight savings time
01191    }
01192    setTime(tmarg, timeOffset);
01193    m_microseconds = microseconds;
01194 }
01196 void
01197 DateTime::setToCurrent()
01198 {
01199 #ifdef BLOCXX_HAVE_GETTIMEOFDAY
01200    timeval tv;
01201    gettimeofday(&tv, NULL);
01202    m_time = tv.tv_sec;
01203    m_microseconds = tv.tv_usec;
01204 #else
01205    SYSTEMTIME st; 
01206    GetSystemTime(&st);
01207    tm theTime;
01208    
01209    theTime.tm_hour = st.wHour;
01210    theTime.tm_min = st.wMinute;
01211    theTime.tm_sec = st.wSecond;
01212    theTime.tm_year = st.wYear - 1900;
01213    theTime.tm_mon = st.wMonth - 1;
01214    theTime.tm_mday = st.wDay;
01215    theTime.tm_wday = st.wDayOfWeek;
01216    theTime.tm_yday = 0;
01217    theTime.tm_isdst = -1; 
01218 
01219    m_time = mktime(&theTime);
01220    m_microseconds = st.wMilliseconds*1000;
01221 #endif
01222 }
01224 void
01225 DateTime::addDays(int days)
01226 {
01227    tm theTime = getTm(E_UTC_TIME);
01228    theTime.tm_mday += days;
01229    setTime(theTime, E_UTC_TIME);
01230 }
01232 void
01233 DateTime::addYears(int years)
01234 {
01235    tm theTime = getTm(E_UTC_TIME);
01236    theTime.tm_year += years;
01237    setTime(theTime, E_UTC_TIME);
01238 }
01240 void
01241 DateTime::addMonths(int months)
01242 {
01243    tm theTime = getTm(E_UTC_TIME);
01244    theTime.tm_mon += months;
01245    setTime(theTime, E_UTC_TIME);
01246 }
01248 String
01249 DateTime::toString(ETimeOffset timeOffset) const
01250 {
01251    tm theTime = getTm(timeOffset);
01252    char buff[30];
01253    asctime_r(&theTime, buff);
01254    String s(buff);
01255    return s;
01256 }
01257 
01259 String DateTime::toString(char const * format, ETimeOffset timeOffset) const
01260 {
01261    tm theTime = getTm(timeOffset);
01262    size_t const BUFSZ = 1024;
01263    char buf[BUFSZ];
01264    size_t n = strftime(buf, BUFSZ, format, &theTime);
01265    buf[n >= BUFSZ ? 0 : n] = '\0';
01266    return String(buf);
01267 }
01268 
01270 char const DateTime::DEFAULT_FORMAT[] = "%c";
01271 
01273 String
01274 DateTime::toStringGMT() const
01275 {
01276    return toString(E_UTC_TIME);
01277 }
01278 
01280 Int16 DateTime::localTimeAndOffset(time_t t, struct tm & t_loc)
01281 {
01282    struct tm t_utc;
01283    struct tm * ptm_utc = ::gmtime_r(&t, &t_utc);
01284    struct tm * ptm_loc = ::localtime_r(&t, &t_loc);
01285    if (!ptm_utc || !ptm_loc)
01286    {
01287       BLOCXX_THROW(DateTimeException, Format("Invalid time_t: %1", t).c_str());
01288    }
01289    int min_diff =
01290       (t_loc.tm_min - t_utc.tm_min) + 60 * (t_loc.tm_hour - t_utc.tm_hour);
01291    // Note: UTC offsets can be greater than 12 hours, but are guaranteed to
01292    // be less than 24 hours.
01293    int day_diff = t_loc.tm_mday - t_utc.tm_mday;
01294    int const one_day = 24 * 60;
01295    if (day_diff == 0)
01296    {
01297       return min_diff;
01298    }
01299    else if (day_diff == 1 || day_diff < -1)
01300    {
01301       // if day_diff < -1, then UTC day is last day of month and local day
01302       // is 1st of next month.
01303       return min_diff + one_day;
01304    }
01305    else /* day_diff == -1 || day_diff > 1 */
01306    {
01307       // if day_diff > 1, then UTC day is 1st of month and local day is last
01308       // day of previous month.
01309       return min_diff - one_day;
01310    }
01311 }
01312 
01314 void
01315 DateTime::set(time_t t, UInt32 microseconds)
01316 {
01317    if (t == static_cast<time_t>(-1) || microseconds > 999999)
01318    {
01319       BLOCXX_THROW(DateTimeException, "Either t == -1 or microseconds > 999999");
01320    }
01321 
01322    m_time = t;
01323    m_microseconds = microseconds;
01324 }
01325 
01327 // static
01328 DateTime
01329 DateTime::getCurrent()
01330 {
01331    DateTime current;
01332    current.setToCurrent();
01333    return current;
01334 }
01335 
01337 DateTime operator-(DateTime const & x, DateTime const & y)
01338 {
01339    time_t diff = x.get() - y.get();
01340    Int32 microdiff = (Int32)x.getMicrosecond() - (Int32)y.getMicrosecond();
01341    if (microdiff < 0)
01342    {
01343       --diff;
01344       microdiff += 1000000;
01345    }
01346    return DateTime(diff, (UInt32)microdiff);
01347 }
01348 
01349 } // end namespace BLOCXX_NAMESPACE
01350