office-gobmx/tools/source/datetime/duration.cxx
Eike Rathke 46e672db80 Resolves: tdf#127334 Increase tools::Duration accuracy epsilon unsharpness
... when converting from double, i.e. to 300 nanoseconds.
Empirically determined..

Change-Id: I92c43b5f244923363af5d44bece9c155126ca343
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155324
Reviewed-by: Eike Rathke <erack@redhat.com>
Tested-by: Jenkins
2023-08-03 21:42:53 +02:00

328 lines
10 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <tools/duration.hxx>
#include <tools/datetime.hxx>
#include <rtl/math.hxx>
#include <o3tl/safeint.hxx>
#include <cmath>
namespace tools
{
Duration::Duration(const ::DateTime& rStart, const ::DateTime& rEnd)
: mnDays(static_cast<const Date&>(rEnd) - static_cast<const Date&>(rStart))
{
SetTimeDiff(rStart, rEnd);
}
Duration::Duration(const Time& rStart, const Time& rEnd)
{
const sal_uInt16 nStartHour = rStart.GetHour();
const sal_uInt16 nEndHour = rEnd.GetHour();
if (nStartHour >= 24 || nEndHour >= 24)
{
Time aEnd(rEnd);
if (nEndHour >= 24)
{
mnDays = (nEndHour / 24) * (aEnd.GetTime() < 0 ? -1 : 1);
aEnd.SetHour(nEndHour % 24);
}
Time aStart(rStart);
if (nStartHour >= 24)
{
mnDays -= (nStartHour / 24) * (aStart.GetTime() < 0 ? -1 : 1);
aStart.SetHour(nStartHour % 24);
}
SetTimeDiff(aStart, aEnd);
}
else
{
SetTimeDiff(rStart, rEnd);
}
}
Duration::Duration(double fTimeInDays, sal_uInt64 nAccuracyEpsilonNanoseconds)
{
assert(nAccuracyEpsilonNanoseconds <= Time::nanoSecPerSec - 1);
double fInt, fFrac;
if (fTimeInDays < 0.0)
{
fInt = ::rtl::math::approxCeil(fTimeInDays);
fFrac = fInt <= fTimeInDays ? 0.0 : fTimeInDays - fInt;
}
else
{
fInt = ::rtl::math::approxFloor(fTimeInDays);
fFrac = fInt >= fTimeInDays ? 0.0 : fTimeInDays - fInt;
}
mnDays = static_cast<sal_Int32>(fInt);
if (fFrac)
{
fFrac *= Time::nanoSecPerDay;
fFrac = ::rtl::math::approxFloor(fFrac);
sal_Int64 nNS = static_cast<sal_Int64>(fFrac);
const sal_Int64 nN = nNS % Time::nanoSecPerSec;
if (nN)
{
const sal_uInt64 nA = std::abs(nN);
if (nA <= nAccuracyEpsilonNanoseconds)
nNS -= (nNS < 0) ? -nN : nN;
else if (nA >= Time::nanoSecPerSec - nAccuracyEpsilonNanoseconds)
{
const sal_Int64 nD = Time::nanoSecPerSec - nA;
nNS += (nNS < 0) ? -nD : nD;
if (std::abs(nNS) >= Time::nanoSecPerDay)
{
mnDays += nNS / Time::nanoSecPerDay;
nNS %= Time::nanoSecPerDay;
}
}
}
maTime.MakeTimeFromNS(nNS);
assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (nNS < 0));
}
}
Duration::Duration(sal_Int32 nDays, const Time& rTime)
: mnDays(nDays)
{
assert(nDays == 0 || rTime.GetTime() == 0 || (nDays < 0) == (rTime.GetTime() < 0));
Normalize(rTime.GetHour(), rTime.GetMin(), rTime.GetSec(), rTime.GetNanoSec(),
((nDays < 0) || (rTime.GetTime() < 0)));
}
Duration::Duration(sal_Int32 nDays, sal_uInt32 nHours, sal_uInt32 nMinutes, sal_uInt32 nSeconds,
sal_uInt64 nNanoseconds)
: mnDays(nDays)
{
Normalize(nHours, nMinutes, nSeconds, nNanoseconds, nDays < 0);
}
Duration::Duration(sal_Int32 nDays, sal_Int64 nTime)
: maTime(nTime)
, mnDays(nDays)
{
}
void Duration::Normalize(sal_uInt64 nHours, sal_uInt64 nMinutes, sal_uInt64 nSeconds,
sal_uInt64 nNanoseconds, bool bNegative)
{
if (nNanoseconds >= Time::nanoSecPerSec)
{
nSeconds += nNanoseconds / Time::nanoSecPerSec;
nNanoseconds %= Time::nanoSecPerSec;
}
if (nSeconds >= Time::secondPerMinute)
{
nMinutes += nSeconds / Time::secondPerMinute;
nSeconds %= Time::secondPerMinute;
}
if (nMinutes >= Time::minutePerHour)
{
nHours += nMinutes / Time::minutePerHour;
nMinutes %= Time::minutePerHour;
}
if (nHours >= Time::hourPerDay)
{
sal_Int64 nDiff = nHours / Time::hourPerDay;
nHours %= Time::hourPerDay;
bool bOverflow = false;
if (bNegative)
{
nDiff = -nDiff;
bOverflow = (nDiff < SAL_MIN_INT32);
bOverflow |= o3tl::checked_add(mnDays, static_cast<sal_Int32>(nDiff), mnDays);
if (bOverflow)
mnDays = SAL_MIN_INT32;
}
else
{
bOverflow = (nDiff > SAL_MAX_INT32);
bOverflow |= o3tl::checked_add(mnDays, static_cast<sal_Int32>(nDiff), mnDays);
if (bOverflow)
mnDays = SAL_MAX_INT32;
}
assert(!bOverflow);
if (bOverflow)
{
nHours = Time::hourPerDay - 1;
nMinutes = Time::minutePerHour - 1;
nSeconds = Time::secondPerMinute - 1;
nNanoseconds = Time::nanoSecPerSec - 1;
}
}
maTime = Time(nHours, nMinutes, nSeconds, nNanoseconds);
if (bNegative)
maTime = -maTime;
assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (maTime.GetTime() < 0));
}
void Duration::ApplyTime(sal_Int64 nNS)
{
if (mnDays > 0 && nNS < 0)
{
--mnDays;
nNS = Time::nanoSecPerDay + nNS;
}
else if (mnDays < 0 && nNS > 0)
{
++mnDays;
nNS = -Time::nanoSecPerDay + nNS;
}
maTime.MakeTimeFromNS(nNS);
assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (maTime.GetTime() < 0));
}
void Duration::SetTimeDiff(const Time& rStart, const Time& rEnd)
{
const sal_Int64 nNS = rEnd.GetNSFromTime() - rStart.GetNSFromTime();
ApplyTime(nNS);
}
Duration Duration::operator-() const
{
Duration aD(-mnDays, -maTime.GetTime());
return aD;
}
Duration& Duration::Add(const Duration& rDuration, bool& rbOverflow)
{
rbOverflow = o3tl::checked_add(mnDays, rDuration.mnDays, mnDays);
// Duration is always normalized, time values >= 24h don't occur.
sal_Int64 nNS = maTime.GetNSFromTime() + rDuration.maTime.GetNSFromTime();
if (nNS < -Time::nanoSecPerDay)
{
rbOverflow |= o3tl::checked_sub(mnDays, sal_Int32(1), mnDays);
nNS += Time::nanoSecPerDay;
}
else if (nNS > Time::nanoSecPerDay)
{
rbOverflow |= o3tl::checked_add(mnDays, sal_Int32(1), mnDays);
nNS -= Time::nanoSecPerDay;
}
ApplyTime(nNS);
return *this;
}
Duration Duration::Mult(sal_Int32 nMult, bool& rbOverflow) const
{
// First try a simple calculation in nanoseconds.
bool bBadNS = false;
sal_Int64 nNS;
sal_Int64 nDays;
if (o3tl::checked_multiply(static_cast<sal_Int64>(mnDays), static_cast<sal_Int64>(nMult), nDays)
|| o3tl::checked_multiply(nDays, Time::nanoSecPerDay, nDays)
|| o3tl::checked_multiply(maTime.GetNSFromTime(), static_cast<sal_Int64>(nMult), nNS)
|| o3tl::checked_add(nDays, nNS, nNS))
{
bBadNS = rbOverflow = true;
}
else
{
const sal_Int64 nD = nNS / Time::nanoSecPerDay;
if (nD < SAL_MIN_INT32 || SAL_MAX_INT32 < nD)
rbOverflow = true;
else
{
rbOverflow = false;
nNS -= nD * Time::nanoSecPerDay;
Duration aD(static_cast<sal_Int32>(nD), 0);
aD.ApplyTime(nNS);
return aD;
}
}
if (bBadNS)
{
// Simple calculation in overall nanoseconds overflowed, try with
// individual components.
const sal_uInt64 nMult64 = (nMult < 0) ? -nMult : nMult;
do
{
sal_uInt64 nN;
if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetNanoSec()), nMult64, nN))
break;
sal_uInt64 nS;
if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetSec()), nMult64, nS))
break;
sal_uInt64 nM;
if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetMin()), nMult64, nM))
break;
sal_uInt64 nH;
if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetHour()), nMult64, nH))
break;
sal_uInt64 nD;
if (o3tl::checked_multiply(
mnDays < 0 ? static_cast<sal_uInt64>(-static_cast<sal_Int64>(mnDays))
: static_cast<sal_uInt64>(mnDays),
nMult64, nD))
break;
if (nN > Time::nanoSecPerSec)
{
const sal_uInt64 nC = nN / Time::nanoSecPerSec;
if (o3tl::checked_add(nS, nC, nS))
break;
nN -= nC * Time::nanoSecPerSec;
}
if (nS > Time::secondPerMinute)
{
const sal_uInt64 nC = nS / Time::secondPerMinute;
if (o3tl::checked_add(nM, nC, nM))
break;
nS -= nC * Time::secondPerMinute;
}
if (nM > Time::minutePerHour)
{
const sal_uInt64 nC = nM / Time::minutePerHour;
if (o3tl::checked_add(nH, nC, nH))
break;
nM -= nC * Time::minutePerHour;
}
if (nH > Time::hourPerDay)
{
const sal_uInt64 nC = nH / Time::hourPerDay;
if (o3tl::checked_add(nD, nC, nD))
break;
nH -= nC * Time::hourPerDay;
}
if (IsNegative() ? (static_cast<sal_uInt64>(SAL_MAX_INT32) + 1) < nD
|| -static_cast<sal_Int64>(nD) < SAL_MIN_INT32
: SAL_MAX_INT32 < nD)
break;
rbOverflow = false;
Time aTime(nH, nM, nS, nN);
if (IsNegative() == (nMult < 0))
{
Duration aD(nD, aTime.GetTime());
return aD;
}
else
{
Duration aD(-static_cast<sal_Int64>(nD), -aTime.GetTime());
return aD;
}
} while (false);
}
assert(rbOverflow);
if (IsNegative() == (nMult < 0))
{
Duration aD(SAL_MAX_INT32, 0);
aD.ApplyTime(Time::nanoSecPerDay - 1);
return aD;
}
else
{
Duration aD(SAL_MIN_INT32, 0);
aD.ApplyTime(-(Time::nanoSecPerDay - 1));
return aD;
}
}
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */