From 9830fd36dbdb72c79703b0c61efc027fba793c5a Mon Sep 17 00:00:00 2001 From: Lionel Elie Mamane Date: Sun, 17 Mar 2013 08:36:26 +0100 Subject: date/time IDL datatypes incompatible change - nanosecond precision - signed (allowed negative) year Also: assorted improvements / bugfixes in date/time handling code. Some factorisation of copy/pasted code. Change-Id: I761a1b0b8731c82f19a0c37acbcf43d3c06d6cd6 --- unotools/inc/unotools/datetime.hxx | 6 + unotools/source/i18n/localedatawrapper.cxx | 32 ++- unotools/source/misc/datetime.cxx | 434 ++++++++++++++++++++++++++++- unotools/source/ucbhelper/ucbhelper.cxx | 2 +- 4 files changed, 459 insertions(+), 15 deletions(-) (limited to 'unotools') diff --git a/unotools/inc/unotools/datetime.hxx b/unotools/inc/unotools/datetime.hxx index 042fa737642b..dfc0ab139825 100644 --- a/unotools/inc/unotools/datetime.hxx +++ b/unotools/inc/unotools/datetime.hxx @@ -44,6 +44,12 @@ namespace utl UNOTOOLS_DLLPUBLIC void typeConvert(const DateTime& _rDateTime, starutil::DateTime& _rOut); UNOTOOLS_DLLPUBLIC void typeConvert(const starutil::DateTime& _rDateTime, DateTime& _rOut); + UNOTOOLS_DLLPUBLIC ::rtl::OUString toISO8601(const starutil::DateTime& _rDateTime); + UNOTOOLS_DLLPUBLIC ::rtl::OUString toISO8601(const starutil::Time& _rTime); + UNOTOOLS_DLLPUBLIC bool ISO8601parseDateTime(const ::rtl::OUString &i_rIn, starutil::DateTime& o_rDateTime); + UNOTOOLS_DLLPUBLIC bool ISO8601parseDate(const ::rtl::OUString &i_rIn, starutil::Date& o_rDate); + UNOTOOLS_DLLPUBLIC bool ISO8601parseTime(const ::rtl::OUString &i_rIn, starutil::Time& o_Time); + //......................................................................... } // namespace utl //......................................................................... diff --git a/unotools/source/i18n/localedatawrapper.cxx b/unotools/source/i18n/localedatawrapper.cxx index 45f24499c70e..493485444dc9 100644 --- a/unotools/source/i18n/localedatawrapper.cxx +++ b/unotools/source/i18n/localedatawrapper.cxx @@ -1140,7 +1140,7 @@ static sal_Unicode* ImplAddUNum( sal_Unicode* pBuf, sal_uInt64 nNumber, int nMin } -static sal_Unicode* ImplAdd2UNum( sal_Unicode* pBuf, sal_uInt16 nNumber, int bLeading ) +static sal_Unicode* ImplAdd2UNum( sal_Unicode* pBuf, sal_uInt16 nNumber, bool bLeading ) { DBG_ASSERT( nNumber < 100, "ImplAdd2UNum() - Number >= 100" ); @@ -1166,6 +1166,24 @@ static sal_Unicode* ImplAdd2UNum( sal_Unicode* pBuf, sal_uInt16 nNumber, int bLe return pBuf; } +static sal_Unicode* ImplAdd9UNum( sal_Unicode* pBuf, sal_uInt32 nNumber, bool bLeading ) +{ + DBG_ASSERT( nNumber < 1000000000, "ImplAdd2UNum() - Number >= 1000000000" ); + + std::ostringstream ostr; + if (bLeading) + { + ostr.fill('0'); + ostr.width(9); + } + ostr << nNumber; + for(const char *pAB=ostr.str().c_str(); *pAB != '\0'; ++pAB, ++pBuf) + { + *pBuf = *pAB; + } + + return pBuf; +} inline sal_Unicode* ImplAddString( sal_Unicode* pBuf, const OUString& rStr ) { @@ -1375,16 +1393,16 @@ OUString LocaleDataWrapper::getTime( const Time& rTime, sal_Bool bSec, sal_Bool pBuf = ImplAdd2UNum( pBuf, nHour, sal_True /* IsTimeLeadingZero() */ ); pBuf = ImplAddString( pBuf, getTimeSep() ); - pBuf = ImplAdd2UNum( pBuf, rTime.GetMin(), sal_True ); + pBuf = ImplAdd2UNum( pBuf, rTime.GetMin(), true ); if ( bSec ) { pBuf = ImplAddString( pBuf, getTimeSep() ); - pBuf = ImplAdd2UNum( pBuf, rTime.GetSec(), sal_True ); + pBuf = ImplAdd2UNum( pBuf, rTime.GetSec(), true ); if ( b100Sec ) { pBuf = ImplAddString( pBuf, getTime100SecSep() ); - pBuf = ImplAdd2UNum( pBuf, rTime.Get100Sec(), sal_True ); + pBuf = ImplAdd9UNum( pBuf, rTime.GetNanoSec(), true ); } } @@ -1473,16 +1491,16 @@ OUString LocaleDataWrapper::getDuration( const Time& rTime, sal_Bool bSec, sal_B else pBuf = ImplAddUNum( pBuf, rTime.GetHour() ); pBuf = ImplAddString( pBuf, getTimeSep() ); - pBuf = ImplAdd2UNum( pBuf, rTime.GetMin(), sal_True ); + pBuf = ImplAdd2UNum( pBuf, rTime.GetMin(), true ); if ( bSec ) { pBuf = ImplAddString( pBuf, getTimeSep() ); - pBuf = ImplAdd2UNum( pBuf, rTime.GetSec(), sal_True ); + pBuf = ImplAdd2UNum( pBuf, rTime.GetSec(), true ); if ( b100Sec ) { pBuf = ImplAddString( pBuf, getTime100SecSep() ); - pBuf = ImplAdd2UNum( pBuf, rTime.Get100Sec(), sal_True ); + pBuf = ImplAdd9UNum( pBuf, rTime.GetNanoSec(), true ); } } diff --git a/unotools/source/misc/datetime.cxx b/unotools/source/misc/datetime.cxx index ff4062523dee..cbfb1694e33d 100644 --- a/unotools/source/misc/datetime.cxx +++ b/unotools/source/misc/datetime.cxx @@ -21,6 +21,147 @@ #include #include #include +#include +#include +#include + +namespace +{ + sal_Int32 impl_pow(sal_Int32 x, sal_Int32 y) + { + if (y == 1) + return x; + if ( y % 2 == 0) + { + return impl_pow(x*x, y/2); + } + else + { + return x * impl_pow(x*x, y/2); + } + } + // computes x^y + sal_Int32 pow(sal_Int32 x, sal_Int32 y) + { + if (y < 0) + throw std::invalid_argument("negative power is not defined in integers"); + if (y == 0) + return 1; + return impl_pow(x, y); + } + + /** convert string to number with optional min and max values */ + template + bool convertNumber( T& rValue, + const OUString& rString, + T /*nMin*/ = -1, T /*nMax*/ = -1) + { + sal_Bool bNeg = sal_False; + rValue = 0; + + sal_Int32 nPos = 0L; + sal_Int32 nLen = rString.getLength(); + + // skip white space + while( nPos < nLen && sal_Unicode(' ') == rString[nPos] ) + nPos++; + + if( nPos < nLen && sal_Unicode('-') == rString[nPos] ) + { + bNeg = sal_True; + nPos++; + } + + // get number + while( nPos < nLen && + sal_Unicode('0') <= rString[nPos] && + sal_Unicode('9') >= rString[nPos] ) + { + // TODO: check overflow! + rValue *= 10; + rValue += (rString[nPos] - sal_Unicode('0')); + nPos++; + } + + if( bNeg ) + rValue *= -1; + + return nPos == nLen; + } + + // although the standard calls for fixed-length (zero-padded) tokens + // (in their integer part), we are here liberal and allow shorter tokens + // (when there are separators, else it is ambiguous). + // Note that: + // the token separator is OPTIONAL + // empty string is a valid token! (to recognise hh or hhmm or hh:mm formats) + // returns: success / failure + // in case of failure, no reference argument is changed + // arguments: + // i_str: string to extract token from + // index: index in i_str where to start tokenizing + // after return, start of *next* token (if any) + // if this was the last token, then the value is UNDEFINED + // o_strInt: output; integer part of token + // o_bFraction: output; was there a fractional part? + // o_strFrac: output; fractional part of token + bool impl_getISO8601TimeToken(const OUString &i_str, sal_Int32 &nPos, OUString &resInt, bool &bFraction, OUString &resFrac) + { + bFraction = false; + // all tokens are of length 2 + const sal_Int32 nEndPos = nPos + 2; + const sal_Unicode c0 = '0'; + const sal_Unicode c9 = '9'; + const sal_Unicode sep = ':'; + for (;nPos < nEndPos && nPos < i_str.getLength(); ++nPos) + { + const sal_Unicode c = i_str[nPos]; + if (c == sep) + return true; + if (c < c0 || c > c9) + return false; + resInt += OUString(c); + } + if (nPos == i_str.getLength() || i_str[nPos] == sep) + return true; + if (i_str[nPos] == ',' || i_str[nPos] == '.') + { + bFraction = true; + ++nPos; + for (; nPos < i_str.getLength(); ++nPos) + { + const sal_Unicode c = i_str[nPos]; + if (c == sep) + // fractional part allowed only in *last* token + return false; + if (c < c0 || c > c9) + return false; + resFrac += OUString(c); + } + OSL_ENSURE(nPos == i_str.getLength(), "impl_getISO8601TimeToken internal error; expected to be at end of string"); + return true; + } + else + return false; + } + inline bool getISO8601TimeToken(const OUString &i_str, sal_Int32 &io_index, OUString &o_strInt, bool &o_bFraction, OUString &o_strFrac) + { + OUString resInt; + OUString resFrac; + bool bFraction = false; + sal_Int32 index = io_index; + if(!impl_getISO8601TimeToken(i_str, index, resInt, bFraction, resFrac)) + return false; + else + { + io_index = index+1; + o_strInt = resInt; + o_strFrac = resFrac; + o_bFraction = bFraction; + return true; + } + } +} //......................................................................... namespace utl @@ -48,21 +189,22 @@ void typeConvert(const DateTime& _rDateTime, starutil::DateTime& _rOut) _rOut.Hours = _rDateTime.GetHour(); _rOut.Minutes = _rDateTime.GetMin(); _rOut.Seconds = _rDateTime.GetSec(); - _rOut.HundredthSeconds = _rDateTime.Get100Sec(); + _rOut.NanoSeconds = _rDateTime.GetNanoSec(); } //------------------------------------------------------------------ void typeConvert(const starutil::DateTime& _rDateTime, DateTime& _rOut) { Date aDate(_rDateTime.Day, _rDateTime.Month, _rDateTime.Year); - Time aTime(_rDateTime.Hours, _rDateTime.Minutes, _rDateTime.Seconds, _rDateTime.HundredthSeconds); + Time aTime(_rDateTime.Hours, _rDateTime.Minutes, _rDateTime.Seconds, _rDateTime.NanoSeconds); _rOut = DateTime(aDate, aTime); } +// FIXME: these operators should be.... in toplevel namespace? announced in the .hxx file? //------------------------------------------------------------------------- -sal_Bool operator ==(const starutil::DateTime& _rLeft, const starutil::DateTime& _rRight) +bool operator ==(const starutil::DateTime& _rLeft, const starutil::DateTime& _rRight) { - return ( _rLeft.HundredthSeconds == _rRight.HundredthSeconds) && + return ( _rLeft.NanoSeconds == _rRight.NanoSeconds) && ( _rLeft.Seconds == _rRight.Seconds) && ( _rLeft.Minutes == _rRight.Minutes) && ( _rLeft.Hours == _rRight.Hours) && @@ -72,7 +214,7 @@ sal_Bool operator ==(const starutil::DateTime& _rLeft, const starutil::DateTi } //------------------------------------------------------------------------- -sal_Bool operator ==(const starutil::Date& _rLeft, const starutil::Date& _rRight) +bool operator ==(const starutil::Date& _rLeft, const starutil::Date& _rRight) { return ( _rLeft.Day == _rRight.Day) && ( _rLeft.Month == _rRight.Month) && @@ -80,14 +222,292 @@ sal_Bool operator ==(const starutil::Date& _rLeft, const starutil::Date& _rRi } //------------------------------------------------------------------------- -sal_Bool operator ==(const starutil::Time& _rLeft, const starutil::Time& _rRight) +bool operator ==(const starutil::Time& _rLeft, const starutil::Time& _rRight) { - return ( _rLeft.HundredthSeconds == _rRight.HundredthSeconds) && + return ( _rLeft.NanoSeconds == _rRight.NanoSeconds) && ( _rLeft.Seconds == _rRight.Seconds) && ( _rLeft.Minutes == _rRight.Minutes) && ( _rLeft.Hours == _rRight.Hours) ; } +OUString toISO8601(const starutil::DateTime& rDateTime) +{ + OUStringBuffer rBuffer; + rBuffer.append((sal_Int32) rDateTime.Year); + rBuffer.append('-'); + if( rDateTime.Month < 10 ) + rBuffer.append('0'); + rBuffer.append((sal_Int32) rDateTime.Month); + rBuffer.append('-'); + if( rDateTime.Day < 10 ) + rBuffer.append('0'); + rBuffer.append((sal_Int32) rDateTime.Day); + + if( rDateTime.NanoSeconds != 0 || + rDateTime.Seconds != 0 || + rDateTime.Minutes != 0 || + rDateTime.Hours != 0 ) + { + rBuffer.append('T'); + if( rDateTime.Hours < 10 ) + rBuffer.append('0'); + rBuffer.append((sal_Int32) rDateTime.Hours); + rBuffer.append(':'); + if( rDateTime.Minutes < 10 ) + rBuffer.append('0'); + rBuffer.append((sal_Int32) rDateTime.Minutes); + rBuffer.append(':'); + if( rDateTime.Seconds < 10 ) + rBuffer.append('0'); + rBuffer.append((sal_Int32) rDateTime.Seconds); + if ( rDateTime.NanoSeconds > 0) + { + OSL_ENSURE(rDateTime.NanoSeconds < 1000000000,"NanoSeconds cannot be more than 999 999 999"); + rBuffer.append(','); + std::ostringstream ostr; + ostr.fill('0'); + ostr.width(9); + ostr << rDateTime.NanoSeconds; + rBuffer.append(OUString::createFromAscii(ostr.str().c_str())); + } + } + return rBuffer.makeStringAndClear(); +} + +OUString toISO8601(const starutil::Time& rTime) +{ + OUStringBuffer rBuffer; + if( rTime.Hours < 10 ) + rBuffer.append('0'); + rBuffer.append((sal_Int32) rTime.Hours); + rBuffer.append(':'); + if( rTime.Minutes < 10 ) + rBuffer.append('0'); + rBuffer.append((sal_Int32) rTime.Minutes); + rBuffer.append(':'); + if( rTime.Seconds < 10 ) + rBuffer.append('0'); + rBuffer.append((sal_Int32) rTime.Seconds); + if ( rTime.NanoSeconds > 0) + { + OSL_ENSURE(rTime.NanoSeconds < 1000000000,"NanoSeconds cannot be more than 999 999 999"); + rBuffer.append(','); + std::ostringstream ostr; + ostr.fill('0'); + ostr.width(9); + ostr << rTime.NanoSeconds; + rBuffer.append(OUString::createFromAscii(ostr.str().c_str())); + } + return rBuffer.makeStringAndClear(); +} + +/** convert ISO8601 DateTime String to util::DateTime */ +bool ISO8601parseDateTime(const OUString &rString, starutil::DateTime& rDateTime) +{ + bool bSuccess = true; + + rtl::OUString aDateStr, aTimeStr; + starutil::Date aDate; + starutil::Time aTime; + sal_Int32 nPos = rString.indexOf( (sal_Unicode) 'T' ); + if ( nPos >= 0 ) + { + aDateStr = rString.copy( 0, nPos ); + aTimeStr = rString.copy( nPos + 1 ); + } + else + aDateStr = rString; // no separator: only date part + + bSuccess = ISO8601parseDate(aDateStr, aDate); + + if ( bSuccess && !aTimeStr.isEmpty() ) // time is optional + { + bSuccess = ISO8601parseTime(aTimeStr, aTime); + } + + if (bSuccess) + { + rDateTime = starutil::DateTime(aTime.NanoSeconds, aTime.Seconds, aTime.Minutes, aTime.Hours, + aDate.Day, aDate.Month, aDate.Year); + } + + return bSuccess; +} + +/** convert ISO8601 Date String to util::Date */ +// TODO: supports only calendar dates YYYY-MM-DD +// MISSING: calendar dates YYYYMMDD YYYY-MM +// year, week date, ordinal date +bool ISO8601parseDate(const OUString &aDateStr, starutil::Date& rDate) +{ + bool bSuccess = true; + + sal_Int32 nYear = 1899; + sal_Int32 nMonth = 12; + sal_Int32 nDay = 30; + + const sal_Unicode* pStr = aDateStr.getStr(); + sal_Int32 nDateTokens = 1; + while ( *pStr ) + { + if ( *pStr == '-' ) + nDateTokens++; + pStr++; + } + if ( nDateTokens > 3 || aDateStr.isEmpty() ) + bSuccess = false; + else + { + sal_Int32 n = 0; + if ( !convertNumber( nYear, aDateStr.getToken( 0, '-', n ), 0, 9999 ) ) + bSuccess = false; + if ( nDateTokens >= 2 ) + if ( !convertNumber( nMonth, aDateStr.getToken( 0, '-', n ), 0, 12 ) ) + bSuccess = false; + if ( nDateTokens >= 3 ) + if ( !convertNumber( nDay, aDateStr.getToken( 0, '-', n ), 0, 31 ) ) + bSuccess = false; + } + + if (bSuccess) + { + rDate.Year = (sal_uInt16)nYear; + rDate.Month = (sal_uInt16)nMonth; + rDate.Day = (sal_uInt16)nDay; + } + + return bSuccess; +} + +/** convert ISO8601 Time String to util::Time */ +bool ISO8601parseTime(const OUString &aTimeStr, starutil::Time& rTime) +{ + bool bSuccess = true; + + sal_Int32 nHour = 0; + sal_Int32 nMin = 0; + sal_Int32 nSec = 0; + sal_Int32 nNanoSec = 0; + + sal_Int32 n = 0; + OUString tokInt; + OUString tokFrac; + bool bFrac; + // hours + if (bSuccess && (bSuccess = getISO8601TimeToken(aTimeStr, n, tokInt, bFrac, tokFrac))) + { + if ( bFrac && n < aTimeStr.getLength()) + // junk after ISO time + bSuccess = false; + else if ( (bSuccess = convertNumber( nHour, tokInt, 0, 23 )) ) + { + if (bFrac) + { + sal_Int64 fracNumerator; + if ( (bSuccess = convertNumber(fracNumerator, tokFrac)) ) + { + double frac = static_cast(fracNumerator) / static_cast(pow(10, tokFrac.getLength())); + // minutes + OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac hours (of hours) not between 0 and 1"); + frac *= 60; + nMin = floor(frac); + frac -= nMin; + // seconds + OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac minutes (of hours) not between 0 and 1"); + frac *= 60; + nSec = floor(frac); + frac -= nSec; + // nanoseconds + OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac seconds (of hours) not between 0 and 1"); + frac *= 1000000000; + nNanoSec = ::rtl::math::round(frac); + } + goto end; + } + } + } + // minutes + if (bSuccess && (bSuccess = getISO8601TimeToken(aTimeStr, n, tokInt, bFrac, tokFrac))) + { + if ( bFrac && n < aTimeStr.getLength()) + // junk after ISO time + bSuccess = false; + else if ( (bSuccess = convertNumber( nMin, tokInt, 0, 59 )) ) + { + if (bFrac) + { + sal_Int64 fracNumerator; + if ( (bSuccess = convertNumber(fracNumerator, tokFrac)) ) + { + double frac = static_cast(fracNumerator) / static_cast(pow(10, tokFrac.getLength())); + // seconds + OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac minutes (of minutes) not between 0 and 1"); + frac *= 60; + nSec = floor(frac); + frac -= nSec; + // nanoseconds + OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac seconds (of minutes) not between 0 and 1"); + frac *= 1000000000; + nNanoSec = ::rtl::math::round(frac); + } + goto end; + } + } + } + // seconds + if (bSuccess && (bSuccess = getISO8601TimeToken(aTimeStr, n, tokInt, bFrac, tokFrac))) + { + if ( bFrac && n < aTimeStr.getLength()) + // junk after ISO time + bSuccess = false; + // max 60 for leap seconds + else if ( (bSuccess = convertNumber( nSec, tokInt, 0, 60 )) ) + { + if (bFrac) + { + sal_Int64 fracNumerator; + if ( (bSuccess = convertNumber(fracNumerator, tokFrac)) ) + { + double frac = static_cast(fracNumerator) / static_cast(pow(10, tokFrac.getLength())); + // nanoseconds + OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac seconds (of seconds) not between 0 and 1"); + frac *= 1000000000; + nNanoSec = ::rtl::math::round(frac); + } + goto end; + } + } + } + + end: + if (bSuccess) + { + // normalise time + const int secondsOverFlow = (nSec == 60) ? 61 : 60; + if (nNanoSec == 1000000000) + { + nNanoSec = 0; + ++nSec; + } + if(nSec == secondsOverFlow) + { + nSec = 0; + ++nMin; + } + if(nMin == 60) + { + nMin = 0; + ++nHour; + } + + rTime.Hours = (sal_uInt16)nHour; + rTime.Minutes = (sal_uInt16)nMin; + rTime.Seconds = (sal_uInt16)nSec; + rTime.NanoSeconds = nNanoSec; + } + + return bSuccess; +} //......................................................................... } // namespace utl //......................................................................... diff --git a/unotools/source/ucbhelper/ucbhelper.cxx b/unotools/source/ucbhelper/ucbhelper.cxx index 9dbd82a9b1cb..82b869f1940b 100644 --- a/unotools/source/ucbhelper/ucbhelper.cxx +++ b/unotools/source/ucbhelper/ucbhelper.cxx @@ -121,7 +121,7 @@ OUString getCasePreservingUrl(INetURLObject url) { DateTime convert(css::util::DateTime const & dt) { return DateTime( Date(dt.Day, dt.Month, dt.Year), - Time(dt.Hours, dt.Minutes, dt.Seconds, dt.HundredthSeconds)); + Time(dt.Hours, dt.Minutes, dt.Seconds, dt.NanoSeconds)); } } -- cgit v1.2.3