/*
* ptime.cxx
*
* Time and date classes.
*
* Portable Windows Library
*
* Copyright (c) 1993-1998 Equivalence Pty. Ltd.
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is Portable Windows Library.
*
* The Initial Developer of the Original Code is Equivalence Pty. Ltd.
*
* Portions are Copyright (C) 1993 Free Software Foundation, Inc.
* All Rights Reserved.
*
* Contributor(s): ______________________________________.
*
* $Log: ptime.cxx,v $
* Revision 1.47 2004/06/06 08:50:08 rjongbloed
* Fixed rounding error in microsoeconds stream output, was not properly cascaded
* along other time elements (hour min etc) so as doing rounding properly is
* hideously difficult, truncated instead, thanks Marco Turconi
*
* Revision 1.46 2004/04/03 08:22:22 csoutheren
* Remove pseudo-RTTI and replaced with real RTTI
*
* Revision 1.45 2003/08/14 02:07:11 dereksmithies
* Fix bug in AsString handling. Thanks Diego Tartara for pointing it out.
*
* Revision 1.44 2002/12/17 04:41:40 robertj
* Added ability to escape special characters.
*
* Revision 1.43 2002/12/10 05:22:16 robertj
* Fixed GNU warning
*
* Revision 1.42 2002/12/10 04:45:41 robertj
* Added support in PTime for ISO 8601 format.
*
* Revision 1.41 2002/02/15 03:56:09 yurik
* Warnings removed during compilation, patch courtesy of Jehan Bing, jehan@bravobrava.com
*
* Revision 1.40 2001/10/16 07:44:06 robertj
* Added AsString() function to PTimeInterval.
*
* Revision 1.39 2001/09/28 10:05:57 robertj
* Added check for "scientific" mode in PTimeInterval so does not print
* out hours and minutes, but just rounded seconds.
*
* Revision 1.38 2001/04/02 01:44:15 robertj
* Fixed PTime::operator-, was adding usecs instead of subtracting.
*
* Revision 1.37 2001/03/19 05:37:29 robertj
* Fixed problem with reading a time if there is leading white space.
*
* Revision 1.36 2001/01/02 06:06:07 robertj
* Fixed inclusion of microseconds in arithmetic functions.
*
* Revision 1.35 2000/04/29 08:11:06 robertj
* Fixed problem with stream output width in PTimeInterval
*
* Revision 1.34 2000/04/29 04:50:16 robertj
* Added microseconds to string output.
* Fixed uninitialised microseconds on some constructors.
*
* Revision 1.33 2000/04/28 13:23:25 robertj
* Fixed printing of negative time intervals.
*
* Revision 1.32 2000/04/18 06:01:01 robertj
* Fixed integer overflow bug in PTime addition functions, thanks Ian MacDonald
*
* Revision 1.31 2000/04/05 02:50:17 robertj
* Added microseconds to PTime class.
*
* Revision 1.30 2000/03/08 17:56:33 rogerh
* Fix error in creation of a PStringStream from a PString
*
* Revision 1.29 2000/03/06 04:09:23 robertj
* Added constructor to do PString conversion to PTimeInterval
*
* Revision 1.28 1999/10/14 08:23:20 robertj
* Fixed display of decimals in milliseconds when precision other than 3.
*
* Revision 1.27 1999/08/08 12:39:24 robertj
* Fixed bug in display of PTimeIntervals larger than 24 hours.
*
* Revision 1.26 1999/06/14 07:58:39 robertj
* Fixed bug in PTimeInteerval output, left stream fill char as '0'.
* Added ability to set the width of the PTimeInterval stream output.
*
* Revision 1.25 1999/03/29 03:38:58 robertj
* Improved time & date parser.
*
* Revision 1.24 1998/11/30 12:31:46 robertj
* Removed redundent conditionals.
*
* Revision 1.23 1998/11/14 01:11:45 robertj
* PPC linux GNU compatibility.
*
* Revision 1.22 1998/09/23 06:22:36 robertj
* Added open source copyright license.
*
* Revision 1.21 1998/03/05 12:49:53 robertj
* MemCheck fixes.
*
* Revision 1.20 1998/01/26 00:48:30 robertj
* Removed days from PTimeInterval PrintOn(), can get it back by using negative precision.
* Fixed Y2K problem in parsing dates.
*
* Revision 1.19 1998/01/04 07:24:32 robertj
* Added operating system specific versions of gmtime and localtime.
*
* Revision 1.18 1997/07/08 13:05:21 robertj
* Fixed bug in parsing time zone incorrectly.
*
* Revision 1.17 1997/05/16 12:05:57 robertj
* Changed PTimeInterval to guarentee no overflow in millisecond calculations.
*
* Revision 1.16 1997/03/18 21:24:19 robertj
* Fixed parsing of time putting back token after time zone.
*
* Revision 1.15 1997/01/03 04:40:03 robertj
* Changed default time so if no year goes to last 12 months rather than current year.
*
* Revision 1.14 1996/10/26 01:40:12 robertj
* Fixed bug in time parser that caused endless looping.
*
* Revision 1.13 1996/08/20 12:07:29 robertj
* Fixed volatile milliseconds member of PTimeInterval for printing.
*
* Revision 1.12 1996/07/27 04:11:28 robertj
* Added bullet proofing for invlid times - especially in Western time zones.
*
* Revision 1.11 1996/07/15 12:43:01 robertj
* Fixed MSVC 4.1 compiler bug.
*
* Revision 1.10 1996/06/17 11:34:48 robertj
* Fixed bug in NOT localising RFC1123 time.
*
* Revision 1.9 1996/05/09 12:18:16 robertj
* Fixed syntax error found by Mac platform.
* Resolved C++ problems with 64 bit PTimeInterval for Mac platform.
*
* Revision 1.8 1996/03/17 05:43:42 robertj
* Changed PTimeInterval to 64 bit integer.
*
* Revision 1.7 1996/03/05 14:09:20 robertj
* Fixed bugs in PTimerInterval stream output.
*
* Revision 1.6 1996/03/04 12:21:42 robertj
* Fixed output of leading zeros in PTimeInterval stream output.
*
* Revision 1.5 1996/02/25 11:22:13 robertj
* Added check for precision field in stream when outputting time interval..
*
* Revision 1.4 1996/02/25 03:07:47 robertj
* Changed PrintOn and ReadFrom on PTimeInterval to use dd:hh:mm:ss.mmm format.
*
* Revision 1.3 1996/02/15 14:47:35 robertj
* Fixed bugs in time zone compensation (some in the C library).
*
* Revision 1.2 1996/02/13 12:59:32 robertj
* Changed GetTimeZone() so can specify standard/daylight time.
* Split PTime into separate module after major change to ReadFrom().
*
* Revision 1.1 1996/02/13 10:11:52 robertj
* Initial revision
*
*/
#include <ptlib.h>
#include <ctype.h>
///////////////////////////////////////////////////////////////////////////////
// PTimeInterval
PTimeInterval::PTimeInterval(long millisecs,
long seconds,
long minutes,
long hours,
int days)
{
SetInterval(millisecs, seconds, minutes, hours, days);
}
PTimeInterval::PTimeInterval(const PString & str)
{
PStringStream strm(str);
ReadFrom(strm);
}
PObject::Comparison PTimeInterval::Compare(const PObject & obj) const
{
PAssert(PIsDescendant(&obj, PTimeInterval), PInvalidCast);
const PTimeInterval & other = (const PTimeInterval &)obj;
return milliseconds < other.milliseconds ? LessThan :
milliseconds > other.milliseconds ? GreaterThan : EqualTo;
}
void PTimeInterval::PrintOn(ostream & stream) const
{
int precision = stream.precision();
Formats fmt = NormalFormat;
if ((stream.flags()&ios::scientific) != 0)
fmt = SecondsOnly;
else if (precision < 0) {
fmt = IncludeDays;
precision = -precision;
}
stream << AsString(precision, fmt, stream.width());
}
void PTimeInterval::ReadFrom(istream &strm)
{
long day = 0;
long hour = 0;
long min = 0;
float sec;
strm >> sec;
while (strm.peek() == ':') {
day = hour;
hour = min;
min = (long)sec;
strm.get();
strm >> sec;
}
SetInterval(((long)(sec*1000))%1000, (int)sec, min, hour, day);
}
PString PTimeInterval::AsString(int precision, Formats format, int width) const
{
PStringStream str;
if (precision > 3)
precision = 3;
else if (precision < 0)
precision = 0;
PInt64 ms = milliseconds;
if (ms < 0) {
str << '-';
ms = -ms;
}
if (format == SecondsOnly) {
switch (precision) {
case 1 :
str << ms/1000 << '.' << (int)(ms%1000+50)/100;
break;
case 2 :
str << ms/1000 << '.' << setw(2) << (int)(ms%1000+5)/10;
break;
case 3 :
str << ms/1000 << '.' << setw(3) << (int)(ms%1000);
break;
default :
str << (ms+500)/1000;
}
return str;
}
BOOL hadPrevious = FALSE;
long tmp;
str.fill('0');
if (format == IncludeDays) {
tmp = (long)(ms/86400000);
if (tmp > 0 || width > (precision+10)) {
str << tmp << 'd';
hadPrevious = TRUE;
}
tmp = (long)(ms%86400000)/3600000;
}
else
tmp = (long)(ms/3600000);
if (hadPrevious || tmp > 0 || width > (precision+7)) {
if (hadPrevious)
str.width(2);
str << tmp << ':';
hadPrevious = TRUE;
}
tmp = (long)(ms%3600000)/60000;
if (hadPrevious || tmp > 0 || width > (precision+4)) {
if (hadPrevious)
str.width(2);
str << tmp << ':';
hadPrevious = TRUE;
}
if (hadPrevious)
str.width(2);
str << (long)(ms%60000)/1000;
switch (precision) {
case 1 :
str << '.' << (int)(ms%1000)/100;
break;
case 2 :
str << '.' << setw(2) << (int)(ms%1000)/10;
break;
case 3 :
str << '.' << setw(3) << (int)(ms%1000);
}
return str;
}
void PTimeInterval::SetInterval(PInt64 millisecs,
long seconds,
long minutes,
long hours,
int days)
{
milliseconds = days;
milliseconds *= 24;
milliseconds += hours;
milliseconds *= 60;
milliseconds += minutes;
milliseconds *= 60;
milliseconds += seconds;
milliseconds *= 1000;
milliseconds += millisecs;
}
///////////////////////////////////////////////////////////////////////////////
// PTime
static time_t p_mktime(struct tm * t, int zone)
{
// mktime returns GMT, calculated using input_time - timezone. However, this
// assumes that the input time was a local time. If the input time wasn't a
// local time, then we have have to add the local timezone (without daylight
// savings) and subtract the specified zone offset to get GMT
// and then subtract
t->tm_isdst = PTime::IsDaylightSavings() ? 1 : 0;
time_t theTime = mktime(t);
if (theTime == (time_t)-1)
theTime = 0;
else if (zone != PTime::Local) {
theTime += PTime::GetTimeZone()*60; // convert to local time
if (theTime > (time_t) zone*60)
theTime -= zone*60; // and then to GMT
}
return theTime;
}
PTime::PTime(const PString & str)
{
PStringStream s(str);
ReadFrom(s);
}
PTime::PTime(int second, int minute, int hour,
int day, int month, int year,
int zone)
{
microseconds = 0;
struct tm t;
PAssert(second >= 0 && second <= 59, PInvalidParameter);
t.tm_sec = second;
PAssert(minute >= 0 && minute <= 59, PInvalidParameter);
t.tm_min = minute;
PAssert(hour >= 0 && hour <= 23, PInvalidParameter);
t.tm_hour = hour;
PAssert(day >= 1 && day <= 31, PInvalidParameter);
t.tm_mday = day;
PAssert(month >= 1 && month <= 12, PInvalidParameter);
t.tm_mon = month-1;
PAssert(year >= 1970 && year <= 2038, PInvalidParameter);
t.tm_year = year-1900;
theTime = p_mktime(&t, zone);
}
PObject::Comparison PTime::Compare(const PObject & obj) const
{
PAssert(PIsDescendant(&obj, PTime), PInvalidCast);
const PTime & other = (const PTime &)obj;
if (theTime < other.theTime)
return LessThan;
if (theTime > other.theTime)
return GreaterThan;
if (microseconds < other.microseconds)
return LessThan;
if (microseconds > other.microseconds)
return GreaterThan;
return EqualTo;
}
PString PTime::AsString(TimeFormat format, int zone) const
{
if (format >= NumTimeStrings)
return "Invalid format : " + AsString("yyyy-MM-dd T hh:mm:ss Z");
switch (format) {
case RFC1123 :
return AsString("wwwe, dd MMME yyyy hh:mm:ss z", zone);
case ShortISO8601 :
return AsString("yyyyMMddThhmmssZ");
case LongISO8601 :
return AsString("yyyy-MM-dd T hh:mm:ss Z");
default :
break;
}
PString fmt, dsep;
PString tsep = GetTimeSeparator();
BOOL is12hour = GetTimeAMPM();
switch (format ) {
case LongDateTime :
case LongTime :
case MediumDateTime :
case ShortDateTime :
case ShortTime :
if (!is12hour)
fmt = "h";
fmt += "h" + tsep + "mm";
switch (format) {
case LongDateTime :
case LongTime :
fmt += tsep + "ss";
default :
break;
}
if (is12hour)
fmt += "a";
break;
default :
break;
}
switch (format ) {
case LongDateTime :
case MediumDateTime :
case ShortDateTime :
fmt += ' ';
break;
default :
break;
}
switch (format ) {
case LongDateTime :
case LongDate :
fmt += "wwww ";
switch (GetDateOrder()) {
case MonthDayYear :
fmt += "MMMM d, yyyy";
break;
case DayMonthYear :
fmt += "d MMMM yyyy";
break;
case YearMonthDay :
fmt += "yyyy MMMM d";
}
break;
case MediumDateTime :
case MediumDate :
fmt += "www ";
switch (GetDateOrder()) {
case MonthDayYear :
fmt += "MMM d, yy";
break;
case DayMonthYear :
fmt += "d MMM yy";
break;
case YearMonthDay :
fmt += "yy MMM d";
}
break;
case ShortDateTime :
case ShortDate :
dsep = GetDateSeparator();
switch (GetDateOrder()) {
case MonthDayYear :
fmt += "MM" + dsep + "dd" + dsep + "yy";
break;
case DayMonthYear :
fmt += "dd" + dsep + "MM" + dsep + "yy";
break;
case YearMonthDay :
fmt += "yy" + dsep + "MM" + dsep + "dd";
}
break;
default :
break;
}
if (zone != Local)
fmt += " z";
return AsString(fmt, zone);
}
PString PTime::AsString(const char * format, int zone) const
{
PAssert(format != NULL, PInvalidParameter);
BOOL is12hour = strchr(format, 'a') != NULL;
PStringStream str;
str.fill('0');
// the localtime call automatically adjusts for daylight savings time
// so take this into account when converting non-local times
if (zone == Local)
zone = GetTimeZone(); // includes daylight savings time
time_t realTime = theTime + zone*60; // to correct timezone
struct tm ts;
struct tm * t = os_gmtime(&realTime, &ts);
PINDEX repeatCount;
while (*format != '\0') {
repeatCount = 1;
switch (*format) {
case 'a' :
while (*++format == 'a')
;
if (t->tm_hour < 12)
str << GetTimeAM();
else
str << GetTimePM();
break;
case 'h' :
while (*++format == 'h')
repeatCount++;
str << setw(repeatCount) << (is12hour ? (t->tm_hour+11)%12+1 : t->tm_hour);
break;
case 'm' :
while (*++format == 'm')
repeatCount++;
str << setw(repeatCount) << t->tm_min;
break;
case 's' :
while (*++format == 's')
repeatCount++;
str << setw(repeatCount) << t->tm_sec;
break;
case 'w' :
while (*++format == 'w')
repeatCount++;
if (repeatCount != 3 || *format != 'e')
str << GetDayName((Weekdays)t->tm_wday, repeatCount <= 3 ? Abbreviated : FullName);
else {
static const char * const EnglishDayName[] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
str << EnglishDayName[t->tm_wday];
format++;
}
break;
case 'M' :
while (*++format == 'M')
repeatCount++;
if (repeatCount < 3)
str << setw(repeatCount) << (t->tm_mon+1);
else if (repeatCount > 3 || *format != 'E')
str << GetMonthName((Months)(t->tm_mon+1),
repeatCount == 3 ? Abbreviated : FullName);
else {
static const char * const EnglishMonthName[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
str << EnglishMonthName[t->tm_mon];
format++;
}
break;
case 'd' :
while (*++format == 'd')
repeatCount++;
str << setw(repeatCount) << t->tm_mday;
break;
case 'y' :
while (*++format == 'y')
repeatCount++;
if (repeatCount < 3)
str << setw(2) << (t->tm_year%100);
else
str << setw(4) << (t->tm_year+1900);
break;
case 'z' :
case 'Z' :
if (zone == 0) {
if (*format == 'Z')
str << 'Z';
else
str << "GMT";
}
else {
str << (zone < 0 ? '-' : '+');
zone = PABS(zone);
str << setw(2) << (zone/60) << setw(2) << (zone%60);
}
while (toupper(*++format) == 'z')
;
break;
case 'u' :
while (*++format == 'u')
repeatCount++;
switch (repeatCount) {
case 1 :
str << (microseconds/100000);
break;
case 2 :
str << setw(2) << (microseconds/10000);
break;
case 3 :
str << setw(3) << (microseconds/1000);
break;
default :
str << setw(6) << microseconds;
break;
}
break;
case '\\' :
format++;
// Escaped character, put straight through to output string
default :
str << *format++;
}
}
return str;
}
///////////////////////////////////////////////////////////
//
// Time parser
//
extern "C" {
#ifndef STDAPICALLTYPE
#define STDAPICALLTYPE
#endif
time_t STDAPICALLTYPE PTimeParse(void *, struct tm *, int);
int STDAPICALLTYPE PTimeGetChar(void * stream)
{
return ((istream *)stream)->get();
}
void STDAPICALLTYPE PTimeUngetChar(void * stream, int c)
{
((istream *)stream)->putback((char)c);
}
int STDAPICALLTYPE PTimeGetDateOrder()
{
return PTime::GetDateOrder();
}
int STDAPICALLTYPE PTimeIsMonthName(const char * str, int month, int abbrev)
{
return PTime::GetMonthName((PTime::Months)month,
abbrev ? PTime::Abbreviated : PTime::FullName) *= str;
}
int STDAPICALLTYPE PTimeIsDayName(const char * str, int day, int abbrev)
{
return PTime::GetDayName((PTime::Weekdays)day,
abbrev ? PTime::Abbreviated : PTime::FullName) *= str;
}
};
void PTime::ReadFrom(istream & strm)
{
time_t now;
struct tm timeBuf;
time(&now);
microseconds = 0;
strm >> ws;
theTime = PTimeParse(&strm, os_localtime(&now, &timeBuf), GetTimeZone());
}
PTime PTime::operator+(const PTimeInterval & t) const
{
time_t secs = theTime + t.GetSeconds();
long usecs = (long)(microseconds + (t.GetMilliSeconds()%1000)*1000);
if (usecs < 0) {
usecs += 1000000;
secs--;
}
else if (usecs >= 1000000) {
usecs -= 1000000;
secs++;
}
return PTime(secs, usecs);
}
PTime & PTime::operator+=(const PTimeInterval & t)
{
theTime += t.GetSeconds();
microseconds += (long)(t.GetMilliSeconds()%1000)*1000;
if (microseconds < 0) {
microseconds += 1000000;
theTime--;
}
else if (microseconds >= 1000000) {
microseconds -= 1000000;
theTime++;
}
return *this;
}
PTimeInterval PTime::operator-(const PTime & t) const
{
time_t secs = theTime - t.theTime;
long usecs = microseconds - t.microseconds;
if (usecs < 0) {
usecs += 1000000;
secs--;
}
else if (usecs >= 1000000) {
usecs -= 1000000;
secs++;
}
return PTimeInterval(usecs/1000, secs);
}
PTime PTime::operator-(const PTimeInterval & t) const
{
time_t secs = theTime - t.GetSeconds();
long usecs = (long)(microseconds - (t.GetMilliSeconds()%1000)*1000);
if (usecs < 0) {
usecs += 1000000;
secs--;
}
else if (usecs >= 1000000) {
usecs -= 1000000;
secs++;
}
return PTime(secs, usecs);
}
PTime & PTime::operator-=(const PTimeInterval & t)
{
theTime -= t.GetSeconds();
microseconds -= (long)(t.GetMilliSeconds()%1000)*1000;
if (microseconds < 0) {
microseconds += 1000000;
theTime--;
}
else if (microseconds >= 1000000) {
microseconds -= 1000000;
theTime++;
}
return *this;
}
// End Of File ///////////////////////////////////////////////////////////////
syntax highlighted by Code2HTML, v. 0.9.1