Resolves: tdf#153517 Use tools::Duration for FillSeries with (date+)time

 This is a combination of 3 commits.

Related: tdf#153517 Introduce class tools::Duration

xChange-Id: I73ea6e162c73382ba470a306ce2852dbff3d1314
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/153202
Reviewed-by: Eike Rathke <erack@redhat.com>
Tested-by: Jenkins
(cherry picked from commit eac63ab120a181a1dff6317ee3d223327080e992)

 Conflicts:
	include/tools/datetime.hxx

Resolves: tdf#153517 Use tools::Duration for FillSeries with (date+)time

xChange-Id: I18567fdac512ee786ce4b0785b01b2ae6da7450e
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/153203
Tested-by: Jenkins
Reviewed-by: Eike Rathke <erack@redhat.com>
(cherry picked from commit 30c48379c7b791edc615e7691691e344baa455a3)

Fix TB Jenkins_Linux_Ubsan (duration.cxx)

/tools/source/datetime/duration.cxx:190:77: runtime error: negation of -2147483648 cannot be represented in type 'sal_Int32' (aka 'int'); cast to an unsigned type to negate this value to itself
0 0x7f08bd84936d in tools::Duration::Mult(int, bool&) const /tools/source/datetime/duration.cxx:190:77
1 0x7f08c1c1d27a in tools::DurationTest::testDuration() /tools/qa/cppunit/test_duration.cxx:219:26

Detected thanks to eac63ab120a181a1dff6317ee3d223327080e992
Related: tdf#153517 Introduce class tools::Duration

xChange-Id: I3804cc4ff4011a7f3b393b82b27f0c805682fbb4
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/153254
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
(cherry picked from commit 99d01ff26fe69ccc66f49b3ddd43b258c3687f2c)

Change-Id: I18567fdac512ee786ce4b0785b01b2ae6da7450e
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/153209
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
diff --git a/include/tools/datetime.hxx b/include/tools/datetime.hxx
index 7b78b29..10303b2 100644
--- a/include/tools/datetime.hxx
+++ b/include/tools/datetime.hxx
@@ -26,6 +26,11 @@

#include <iomanip>

namespace tools
{
class Duration;
}

class SAL_WARN_UNUSED TOOLS_DLLPUBLIC DateTime : public Date, public tools::Time
{
public:
@@ -83,6 +88,8 @@ public:
    void            AddTime( double fTimeInDays );
    DateTime&       operator +=( const tools::Time& rTime );
    DateTime&       operator -=( const tools::Time& rTime );
    /** Duration can be negative, so adding it will subtract its value. */
    DateTime&       operator +=( const tools::Duration& rDuration );

    TOOLS_DLLPUBLIC friend DateTime operator +( const DateTime& rDateTime, sal_Int32 nDays );
    TOOLS_DLLPUBLIC friend DateTime operator -( const DateTime& rDateTime, sal_Int32 nDays );
@@ -94,6 +101,8 @@ public:
    TOOLS_DLLPUBLIC friend double   operator -( const DateTime& rDateTime1, const DateTime& rDateTime2 );
    TOOLS_DLLPUBLIC friend sal_Int64 operator -( const DateTime& rDateTime, const Date& rDate )
                        { return static_cast<const Date&>(rDateTime) - rDate; }
    /** Duration can be negative, so adding it will subtract its value. */
    TOOLS_DLLPUBLIC friend DateTime operator +( const DateTime& rDateTime, const tools::Duration& rDuration );

    DateTime&       operator =( const DateTime& rDateTime );
    DateTime&       operator =( const css::util::DateTime& rUDateTime );
diff --git a/include/tools/duration.hxx b/include/tools/duration.hxx
new file mode 100644
index 0000000..83b9d12
--- /dev/null
+++ b/include/tools/duration.hxx
@@ -0,0 +1,72 @@
/* -*- 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/.
 */

#pragma once

#include <tools/time.hxx>

class DateTime;

namespace tools
{
/** Duration in days and time. Can be negative in which case days is 0 and time
    is negative or both days and time are negative.
*/
class SAL_WARN_UNUSED TOOLS_DLLPUBLIC Duration
{
public:
    Duration() {}

    /** Assumes that DateTime are normalized and there are no Time out-of-range
        field values. */
    Duration(const ::DateTime& rStart, const ::DateTime& rEnd);

    /** Time can be a limited duration as well. We don't cater for out-of-range
        minutes and seconds values here though. */
    Duration(const Time& rStart, const Time& rEnd);

    /** Difference in days, like DateTime()-DateTime(). */
    explicit Duration(double fTimeInDays);

    bool IsNegative() const { return mnDays < 0 || maTime.GetTime() < 0; }
    sal_Int32 GetDays() const { return mnDays; }
    const Time& GetTime() const { return maTime; }
    double GetInDays() const { return static_cast<double>(GetDays()) + GetTime().GetTimeInDays(); }

    /** Whether a duration is set. */
    operator bool() const { return maTime.GetTime() != 0 || mnDays != 0; }

    /** Unary minus. */
    Duration operator-() const;

    /** Add a duration to this instance. */
    Duration& Add(const Duration& rDuration, bool& rbOverflow);

    /** Get multiple of duration. */
    Duration Mult(sal_Int32 nMult, bool& rbOverflow) const;

private:
    /** Internal days and Time values. */
    Duration(sal_Int32 nDays, sal_Int64 nTime);

    /** Prerequisite: mnDays is already correctly set and absolute value of
        nanoseconds less than one day. */
    void ApplyTime(sal_Int64 nNS);

    /** Prerequisite: mnDays is already correctly set and Time hour values
        are adjusted. */
    void SetTimeDiff(const Time& rStart, const Time& rEnd);

private:
    Time maTime = Time(0);
    sal_Int32 mnDays = 0;
};
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/sc/inc/table.hxx b/sc/inc/table.hxx
index 851248a..3367af1 100644
--- a/sc/inc/table.hxx
+++ b/sc/inc/table.hxx
@@ -708,7 +708,8 @@ public:
                                 sc::UpdatedRangeNames& rIndexes) const;
    void        Fill( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                        sal_uInt64 nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd,
                        double nStepValue, double nMaxValue, ScProgress* pProgress);
                        double nStepValue, const tools::Duration& rDurationStep,
                        double nMaxValue, ScProgress* pProgress);
    OUString    GetAutoFillPreview( const ScRange& rSource, SCCOL nEndX, SCROW nEndY );

    void UpdateSelectionFunction( ScFunctionData& rData, const ScMarkData& rMark );
@@ -1179,13 +1180,14 @@ private:
    void        FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                                sal_uInt64 nFillCount, FillDir eFillDir, FillCmd eFillCmd,
                                FillDateCmd eFillDateCmd,
                                double nStepValue, double nMaxValue, sal_uInt16 nMinDigits,
                                double nStepValue, const tools::Duration& rDurationStep,
                                double nMaxValue, sal_uInt16 nMinDigits,
                                bool bAttribs, ScProgress* pProgress,
                                bool bSkipOverlappedCells = false,
                                std::vector<sal_Int32>* pNonOverlappedCellIdx = nullptr);
    void        FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                                FillCmd& rCmd, FillDateCmd& rDateCmd,
                                double& rInc, sal_uInt16& rMinDigits,
                                double& rInc, tools::Duration& rDuration, sal_uInt16& rMinDigits,
                                ScUserListData*& rListData, sal_uInt16& rListIndex,
                                bool bHasFiltered, bool& rSkipOverlappedCells,
                                std::vector<sal_Int32>& rNonOverlappedCellIdx );
diff --git a/sc/source/core/data/documen3.cxx b/sc/source/core/data/documen3.cxx
index 2f802a8..600b7be 100644
--- a/sc/source/core/data/documen3.cxx
+++ b/sc/source/core/data/documen3.cxx
@@ -29,6 +29,7 @@
#include <vcl/svapp.hxx>
#include <osl/thread.hxx>
#include <osl/diagnose.h>
#include <tools/duration.hxx>
#include <document.hxx>
#include <attrib.hxx>
#include <table.hxx>
@@ -1175,7 +1176,7 @@ void ScDocument::Fill(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScProg
        {
            maTabs[rTab]->Fill(nCol1, nRow1, nCol2, nRow2,
                            nFillCount, eFillDir, eFillCmd, eFillDateCmd,
                            nStepValue, nMaxValue, pProgress);
                            nStepValue, tools::Duration(), nMaxValue, pProgress);
            RefreshAutoFilter(aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row(), rTab);
        }
    }
diff --git a/sc/source/core/data/table4.cxx b/sc/source/core/data/table4.cxx
index 10c27f8..9c68529 100644
--- a/sc/source/core/data/table4.cxx
+++ b/sc/source/core/data/table4.cxx
@@ -28,6 +28,7 @@
#include <vcl/keycodes.hxx>
#include <rtl/math.hxx>
#include <unotools/charclass.hxx>
#include <tools/duration.hxx>
#include <osl/diagnose.h>

#include <attrib.hxx>
@@ -215,32 +216,20 @@ double approxDiff( double a, double b )
    return rtl::math::round(c, -std::max(nExp, nExpArg));
}

double approxTimeDiff( double a, double b )
double approxTypedDiff( double a, double b, bool bTime, tools::Duration& rDuration )
{
    // Scale to hours, round to "nanohours" (multiple nanoseconds), scale back.
    // Get back 0.0416666666666667 instead of 0.041666666700621136 or
    // 0.041666666664241347 (raw a-b) for one hour, or worse the approxDiff()
    // 0.041666666659999997 value. Though there is no such correct value,
    // IEEE-754 nearest values are
    // 0.041666666666666664353702032030923874117434024810791015625
    // (0x3FA5555555555555) and
    // 0.04166666666666667129259593593815225176513195037841796875
    // (0x3FA5555555555556).
    // This works also for a diff of seconds, unless corner cases would be
    // discovered, which would make it necessary to ditch the floating point
    // and convert to/from time structure values instead.
    return rtl::math::round((a - b) * 24, 9) / 24;
}

double approxTypedDiff( double a, double b, bool bTime )
{
    return bTime ? approxTimeDiff( a, b) : approxDiff( a, b);
    if (bTime)
    {
        rDuration = tools::Duration(a - b);
        return rDuration.GetInDays();
    }
    return approxDiff( a, b);
}
}

void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                            FillCmd& rCmd, FillDateCmd& rDateCmd,
                            double& rInc, sal_uInt16& rMinDigits,
                            double& rInc, tools::Duration& rDuration, sal_uInt16& rMinDigits,
                            ScUserListData*& rListData, sal_uInt16& rListIndex,
                            bool bHasFiltered, bool& rSkipOverlappedCells,
                            std::vector<sal_Int32>& rNonOverlappedCellIdx)
@@ -248,6 +237,7 @@ void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
    OSL_ENSURE( nCol1==nCol2 || nRow1==nRow2, "FillAnalyse: invalid range" );

    rInc = 0.0;
    rDuration = tools::Duration();
    rMinDigits = 0;
    rListData = nullptr;
    rCmd = FILL_SIMPLE;
@@ -413,6 +403,7 @@ void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                }
                else if (nValueCount >= 2)
                {
                    tools::Duration aDuration;
                    for (SCSIZE i = 1; i < nValueCount && bVal; i++)
                    {
                        aPrevCell = aCurrCell;
@@ -421,11 +412,16 @@ void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                        aCurrCell = GetCellValue(nColCurr, nRowCurr);
                        if (aCurrCell.getType() == CELLTYPE_VALUE)
                        {
                            const bool bTime = (nCurrCellFormatType == SvNumFormatType::TIME ||
                                    nCurrCellFormatType == SvNumFormatType::DATETIME);
                            double nDiff = approxTypedDiff(aCurrCell.getDouble(), aPrevCell.getDouble(),
                                    (nCurrCellFormatType == SvNumFormatType::TIME ||
                                     nCurrCellFormatType == SvNumFormatType::DATETIME));
                                    bTime, aDuration);
                            if (i == 1)
                            {
                                rInc = nDiff;
                                if (bTime)
                                    rDuration = aDuration;
                            }
                            if (!::rtl::math::approxEqual(nDiff, rInc, 13))
                                bVal = false;
                            else if ((aCurrCell.getDouble() == 0.0 || aCurrCell.getDouble() == 1.0)
@@ -645,9 +641,10 @@ void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
        {
            if (nCount > 1)
            {
                tools::Duration aDuration;
                double nVal1 = aFirstCell.getDouble();
                double nVal2 = GetValue(nCol+nAddX, nRow+nAddY);
                rInc = approxTypedDiff( nVal2, nVal1, bTime);
                rInc = approxTypedDiff( nVal2, nVal1, bTime, aDuration);
                nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
                nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
                bool bVal = true;
@@ -657,7 +654,7 @@ void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                    if (aCell.getType() == CELLTYPE_VALUE)
                    {
                        nVal2 = aCell.getDouble();
                        double nDiff = approxTypedDiff( nVal2, nVal1, bTime);
                        double nDiff = approxTypedDiff( nVal2, nVal1, bTime, aDuration);
                        if ( !::rtl::math::approxEqual( nDiff, rInc, 13 ) )
                            bVal = false;
                        else if ((nVal2 == 0.0 || nVal2 == 1.0) &&
@@ -670,6 +667,8 @@ void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                        bVal = false;
                    nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
                    nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
                    if (bVal && bTime)
                        rDuration = aDuration;
                }
                if (bVal)
                    rCmd = FILL_LINEAR;
@@ -1092,6 +1091,7 @@ void ScTable::FillAuto( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
        FillCmd eFillCmd;
        FillDateCmd eDateCmd = {};
        double nInc;
        tools::Duration aDurationInc;
        sal_uInt16 nMinDigits;
        ScUserListData* pListData = nullptr;
        sal_uInt16 nListIndex;
@@ -1100,12 +1100,12 @@ void ScTable::FillAuto( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
        if (bVertical)
            FillAnalyse(static_cast<SCCOL>(nCol),nRow1,
                    static_cast<SCCOL>(nCol),nRow2, eFillCmd,eDateCmd,
                    nInc, nMinDigits, pListData, nListIndex,
                    nInc, aDurationInc, nMinDigits, pListData, nListIndex,
                    bHasFiltered, bSkipOverlappedCells, aNonOverlappedCellIdx);
        else
            FillAnalyse(nCol1,static_cast<SCROW>(nRow),
                    nCol2,static_cast<SCROW>(nRow), eFillCmd,eDateCmd,
                    nInc, nMinDigits, pListData, nListIndex,
                    nInc, aDurationInc, nMinDigits, pListData, nListIndex,
                    bHasFiltered, bSkipOverlappedCells, aNonOverlappedCellIdx);

        if (pListData)
@@ -1214,17 +1214,20 @@ void ScTable::FillAuto( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
        else
        {
            if (!bPositive)
            {
                nInc = -nInc;
                aDurationInc = -aDurationInc;
            }
            double nEndVal = (nInc>=0.0) ? MAXDOUBLE : -MAXDOUBLE;
            if (bVertical)
                FillSeries( static_cast<SCCOL>(nCol), nRow1,
                        static_cast<SCCOL>(nCol), nRow2, nFillCount, eFillDir,
                        eFillCmd, eDateCmd, nInc, nEndVal, nMinDigits, false,
                        eFillCmd, eDateCmd, nInc, aDurationInc, nEndVal, nMinDigits, false,
                        pProgress, bSkipOverlappedCells, &aNonOverlappedCellIdx);
            else
                FillSeries( nCol1, static_cast<SCROW>(nRow), nCol2,
                        static_cast<SCROW>(nRow), nFillCount, eFillDir,
                        eFillCmd, eDateCmd, nInc, nEndVal, nMinDigits, false,
                        eFillCmd, eDateCmd, nInc, aDurationInc, nEndVal, nMinDigits, false,
                        pProgress, bSkipOverlappedCells, &aNonOverlappedCellIdx);
            if (pProgress)
                nProgress = pProgress->GetState();
@@ -1375,6 +1378,7 @@ OUString ScTable::GetAutoFillPreview( const ScRange& rSource, SCCOL nEndX, SCROW
        FillCmd eFillCmd;
        FillDateCmd eDateCmd;
        double nInc;
        tools::Duration aDurationInc;
        sal_uInt16 nMinDigits;
        ScUserListData* pListData = nullptr;
        sal_uInt16 nListIndex;
@@ -1385,7 +1389,7 @@ OUString ScTable::GetAutoFillPreview( const ScRange& rSource, SCCOL nEndX, SCROW
        //       after FillAnalyse / FillSeries fully handle them.
        // Now FillAnalyse called as if there are filtered rows, so it will work in the old way.
        FillAnalyse(nCol1, nRow1, nCol2, nRow2, eFillCmd, eDateCmd,
                    nInc, nMinDigits, pListData, nListIndex,
                    nInc, aDurationInc, nMinDigits, pListData, nListIndex,
                    true, bSkipOverlappedCells, aNonOverlappedCellIdx);

        if ( pListData )                            // user defined list
@@ -1530,9 +1534,18 @@ OUString ScTable::GetAutoFillPreview( const ScRange& rSource, SCCOL nEndX, SCROW
                nStart = 0.0;
            if ( eFillCmd == FILL_LINEAR )
            {
                double nAdd = nInc;
                bValueOk = ( SubTotal::SafeMult( nAdd, static_cast<double>(nIndex) ) &&
                             SubTotal::SafePlus( nStart, nAdd ) );
                if (aDurationInc)
                {
                    bool bOverflow;
                    tools::Duration aDuration( aDurationInc.Mult( nIndex, bOverflow));
                    bValueOk = SubTotal::SafePlus( nStart, aDuration.GetInDays()) && !bOverflow;
                }
                else
                {
                    double nAdd = nInc;
                    bValueOk = ( SubTotal::SafeMult( nAdd, static_cast<double>(nIndex) ) &&
                                 SubTotal::SafePlus( nStart, nAdd ) );
                }
            }
            else        // date
            {
@@ -2153,7 +2166,8 @@ inline bool isOverflow( const double& rVal, const double& rMax, const double& rS

void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                    sal_uInt64 nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd,
                    double nStepValue, double nMaxValue, sal_uInt16 nArgMinDigits,
                    double nStepValue, const tools::Duration& rDurationStep,
                    double nMaxValue, sal_uInt16 nArgMinDigits,
                    bool bAttribs, ScProgress* pProgress,
                    bool bSkipOverlappedCells, std::vector<sal_Int32>* pNonOverlappedCellIdx )
{
@@ -2420,10 +2434,18 @@ void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                                        //  use multiplication instead of repeated addition
                                        //  to avoid accumulating rounding errors
                                        nVal = nStartVal;
                                        double nAdd = nStepValue;
                                        if ( !SubTotal::SafeMult( nAdd, static_cast<double>(++nIndex) ) ||
                                                !SubTotal::SafePlus( nVal, nAdd ) )
                                            bError = true;
                                        if (rDurationStep)
                                        {
                                            tools::Duration aDuration( rDurationStep.Mult( ++nIndex, bError));
                                            bError |= !SubTotal::SafePlus( nVal, aDuration.GetInDays());
                                        }
                                        else
                                        {
                                            double nAdd = nStepValue;
                                            if ( !SubTotal::SafeMult( nAdd, static_cast<double>(++nIndex) ) ||
                                                    !SubTotal::SafePlus( nVal, nAdd ) )
                                                bError = true;
                                        }
                                    }
                                    break;
                                case FILL_GROWTH:
@@ -2525,10 +2547,18 @@ void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                                            //  use multiplication instead of repeated addition
                                            //  to avoid accumulating rounding errors
                                            nVal = nStartVal;
                                            double nAdd = nStepValue;
                                            if ( !SubTotal::SafeMult( nAdd, static_cast<double>(++nIndex) ) ||
                                                    !SubTotal::SafePlus( nVal, nAdd ) )
                                                bError = true;
                                            if (rDurationStep)
                                            {
                                                tools::Duration aDuration( rDurationStep.Mult( ++nIndex, bError));
                                                bError |= !SubTotal::SafePlus( nVal, aDuration.GetInDays());
                                            }
                                            else
                                            {
                                                double nAdd = nStepValue;
                                                if ( !SubTotal::SafeMult( nAdd, static_cast<double>(++nIndex) ) ||
                                                        !SubTotal::SafePlus( nVal, nAdd ) )
                                                    bError = true;
                                            }
                                        }
                                        break;
                                    case FILL_GROWTH:
@@ -2598,13 +2628,14 @@ void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,

void ScTable::Fill( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                    sal_uInt64 nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd,
                    double nStepValue, double nMaxValue, ScProgress* pProgress)
                    double nStepValue, const tools::Duration& rDurationStep,
                    double nMaxValue, ScProgress* pProgress)
{
    if (eFillCmd == FILL_AUTO)
        FillAuto(nCol1, nRow1, nCol2, nRow2, nFillCount, eFillDir, pProgress);
    else
        FillSeries(nCol1, nRow1, nCol2, nRow2, nFillCount, eFillDir,
                    eFillCmd, eFillDateCmd, nStepValue, nMaxValue, 0, true, pProgress);
                    eFillCmd, eFillDateCmd, nStepValue, rDurationStep, nMaxValue, 0, true, pProgress);
}

void ScTable::AutoFormatArea(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
diff --git a/tools/CppunitTest_tools_test.mk b/tools/CppunitTest_tools_test.mk
index 9cf6c67..d27f8c3 100644
--- a/tools/CppunitTest_tools_test.mk
+++ b/tools/CppunitTest_tools_test.mk
@@ -17,6 +17,7 @@ $(eval $(call gb_CppunitTest_add_exception_objects,tools_test, \
    tools/qa/cppunit/test_bigint \
    tools/qa/cppunit/test_date \
    tools/qa/cppunit/test_time \
    tools/qa/cppunit/test_duration \
    tools/qa/cppunit/test_fract \
    tools/qa/cppunit/test_inetmime \
    tools/qa/cppunit/test_json_writer \
diff --git a/tools/Library_tl.mk b/tools/Library_tl.mk
index bccbf95..8269e6ae 100644
--- a/tools/Library_tl.mk
+++ b/tools/Library_tl.mk
@@ -45,6 +45,7 @@ $(eval $(call gb_Library_use_libraries,tl,\
$(eval $(call gb_Library_add_exception_objects,tl,\
    tools/source/datetime/datetime \
    tools/source/datetime/datetimeutils \
    tools/source/datetime/duration \
    tools/source/datetime/systemdatetime \
    tools/source/datetime/tdate \
    tools/source/datetime/ttime \
diff --git a/tools/qa/cppunit/test_duration.cxx b/tools/qa/cppunit/test_duration.cxx
new file mode 100644
index 0000000..0f5a4e0
--- /dev/null
+++ b/tools/qa/cppunit/test_duration.cxx
@@ -0,0 +1,278 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * 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 <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>

#include <tools/duration.hxx>
#include <tools/datetime.hxx>

namespace tools
{
class DurationTest : public CppUnit::TestFixture
{
public:
    void testDuration();

    CPPUNIT_TEST_SUITE(DurationTest);
    CPPUNIT_TEST(testDuration);
    CPPUNIT_TEST_SUITE_END();
};

void DurationTest::testDuration()
{
    {
        const Duration aD(Time(0), Time(12, 0, 0));
        CPPUNIT_ASSERT_EQUAL(0.5, aD.GetInDays());
    }
    {
        const Duration aD(Time(24, 0, 0), Time(12, 0, 0));
        CPPUNIT_ASSERT_EQUAL(-0.5, aD.GetInDays());
    }
    {
        const DateTime aS(Date(23, 11, 1999), Time(6, 0, 0));
        const DateTime aE(Date(24, 11, 1999), Time(18, 0, 0));
        const Duration aD(aS, aE);
        CPPUNIT_ASSERT_EQUAL(1.5, aD.GetInDays());
        DateTime aDT1(aS);
        const DateTime aDT2 = aDT1 + aD;
        CPPUNIT_ASSERT_EQUAL(aE, aDT2);
        aDT1 += aD;
        CPPUNIT_ASSERT_EQUAL(aE, aDT1);
        aDT1 += aD;
        CPPUNIT_ASSERT_EQUAL(DateTime(Date(26, 11, 1999), Time(6, 0, 0)), aDT1);
    }
    {
        const DateTime aS(Date(23, 11, 1999), Time(18, 0, 0));
        const DateTime aE(Date(24, 11, 1999), Time(6, 0, 0));
        const Duration aD(aS, aE);
        CPPUNIT_ASSERT_EQUAL(0.5, aD.GetInDays());
        DateTime aDT1(aS);
        const DateTime aDT2 = aDT1 + aD;
        CPPUNIT_ASSERT_EQUAL(aE, aDT2);
        aDT1 += aD;
        CPPUNIT_ASSERT_EQUAL(aE, aDT1);
        aDT1 += aD;
        CPPUNIT_ASSERT_EQUAL(DateTime(Date(24, 11, 1999), Time(18, 0, 0)), aDT1);
    }
    {
        const DateTime aS(Date(24, 11, 1999), Time(18, 0, 0));
        const DateTime aE(Date(23, 11, 1999), Time(6, 0, 0));
        const Duration aD(aS, aE);
        CPPUNIT_ASSERT_EQUAL(-1.5, aD.GetInDays());
        DateTime aDT1(aS);
        const DateTime aDT2 = aDT1 + aD;
        CPPUNIT_ASSERT_EQUAL(aE, aDT2);
        aDT1 += aD;
        CPPUNIT_ASSERT_EQUAL(aE, aDT1);
        aDT1 += aD;
        CPPUNIT_ASSERT_EQUAL(DateTime(Date(21, 11, 1999), Time(18, 0, 0)), aDT1);
    }
    {
        const DateTime aS(Date(24, 11, 1999), Time(6, 0, 0));
        const DateTime aE(Date(23, 11, 1999), Time(18, 0, 0));
        const Duration aD(aS, aE);
        CPPUNIT_ASSERT_EQUAL(-0.5, aD.GetInDays());
        DateTime aDT1(aS);
        const DateTime aDT2 = aDT1 + aD;
        CPPUNIT_ASSERT_EQUAL(aE, aDT2);
        aDT1 += aD;
        CPPUNIT_ASSERT_EQUAL(aE, aDT1);
        aDT1 += aD;
        CPPUNIT_ASSERT_EQUAL(DateTime(Date(23, 11, 1999), Time(6, 0, 0)), aDT1);
    }
    {
        const Duration aD(1.5);
        CPPUNIT_ASSERT_EQUAL(1.5, aD.GetInDays());
        CPPUNIT_ASSERT_EQUAL(DateTime(Date(24, 11, 1999), Time(18, 0, 0)),
                             DateTime(Date(23, 11, 1999), Time(6, 0, 0)) + aD);
    }
    {
        const Duration aD(-1.5);
        CPPUNIT_ASSERT_EQUAL(-1.5, aD.GetInDays());
        CPPUNIT_ASSERT_EQUAL(DateTime(Date(23, 11, 1999), Time(6, 0, 0)),
                             DateTime(Date(24, 11, 1999), Time(18, 0, 0)) + aD);
    }
    {
        const Duration aD(-1.5);
        const Duration aN = -aD;
        CPPUNIT_ASSERT_EQUAL(1.5, aN.GetInDays());
    }
    { // Add()
        const DateTime aS(Date(23, 11, 1999), Time(0, 0, 0));
        const DateTime aE(Date(23, 11, 1999), Time(1, 23, 45));
        const Duration aD(aS, aE);
        Duration aV = aD;
        bool bOverflow = true;
        aV.Add(aD, bOverflow);
        CPPUNIT_ASSERT(!bOverflow);
        CPPUNIT_ASSERT_EQUAL(DateTime(Date(23, 11, 1999), Time(2, 47, 30)),
                             DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV);
        for (int i = 0; i < 20; ++i)
            aV.Add(aD, bOverflow);
        CPPUNIT_ASSERT(!bOverflow);
        CPPUNIT_ASSERT_EQUAL(DateTime(Date(24, 11, 1999), Time(6, 42, 30)),
                             DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV);
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aV.GetDays());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(6), aV.GetTime().GetHour());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(42), aV.GetTime().GetMin());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(30), aV.GetTime().GetSec());
        CPPUNIT_ASSERT(aV.GetTime().GetTime() > 0);
        CPPUNIT_ASSERT_DOUBLES_EQUAL(1.27951388888889, aV.GetInDays(), 1E-14);
        // Negative duration.
        const Duration aN(aE, aS);
        aV = aN;
        aV.Add(aN, bOverflow);
        CPPUNIT_ASSERT(!bOverflow);
        CPPUNIT_ASSERT_EQUAL(DateTime(Date(22, 11, 1999), Time(21, 12, 30)),
                             DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV);
        for (int i = 0; i < 20; ++i)
            aV.Add(aN, bOverflow);
        CPPUNIT_ASSERT(!bOverflow);
        CPPUNIT_ASSERT_EQUAL(DateTime(Date(21, 11, 1999), Time(17, 17, 30)),
                             DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV);
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1), aV.GetDays());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(6), aV.GetTime().GetHour());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(42), aV.GetTime().GetMin());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(30), aV.GetTime().GetSec());
        CPPUNIT_ASSERT(aV.GetTime().GetTime() < 0);
        CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.27951388888889, aV.GetInDays(), 1E-14);
    }
    { // Mult()
        const DateTime aS(Date(23, 11, 1999), Time(0, 0, 0));
        const DateTime aE(Date(23, 11, 1999), Time(1, 23, 45));
        const Duration aD(aS, aE);
        bool bOverflow = true;
        Duration aV = aD.Mult(22, bOverflow);
        CPPUNIT_ASSERT(!bOverflow);
        CPPUNIT_ASSERT_EQUAL(DateTime(Date(24, 11, 1999), Time(6, 42, 30)),
                             DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV);
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aV.GetDays());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(6), aV.GetTime().GetHour());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(42), aV.GetTime().GetMin());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(30), aV.GetTime().GetSec());
        CPPUNIT_ASSERT(aV.GetTime().GetTime() > 0);
        CPPUNIT_ASSERT_DOUBLES_EQUAL(1.27951388888889, aV.GetInDays(), 1E-14);
        // Negative duration.
        const Duration aN(aE, aS);
        bOverflow = true;
        aV = aN.Mult(22, bOverflow);
        CPPUNIT_ASSERT(!bOverflow);
        CPPUNIT_ASSERT_EQUAL(DateTime(Date(21, 11, 1999), Time(17, 17, 30)),
                             DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV);
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1), aV.GetDays());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(6), aV.GetTime().GetHour());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(42), aV.GetTime().GetMin());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(30), aV.GetTime().GetSec());
        CPPUNIT_ASSERT(aV.GetTime().GetTime() < 0);
        CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.27951388888889, aV.GetInDays(), 1E-14);
    }
    { // Mult() including days.
        const Duration aD(1.5);
        bool bOverflow = true;
        Duration aV = aD.Mult(10, bOverflow);
        CPPUNIT_ASSERT(!bOverflow);
        CPPUNIT_ASSERT_DOUBLES_EQUAL(15.0, aV.GetInDays(), 0.0);
    }
    { // Mult() including days.
        const Duration aD(-1.5);
        bool bOverflow = true;
        Duration aV = aD.Mult(10, bOverflow);
        CPPUNIT_ASSERT(!bOverflow);
        CPPUNIT_ASSERT_DOUBLES_EQUAL(-15.0, aV.GetInDays(), 0.0);
    }
    { // Mult() including days.
        const Duration aD(1.5);
        bool bOverflow = true;
        Duration aV = aD.Mult(-10, bOverflow);
        CPPUNIT_ASSERT(!bOverflow);
        CPPUNIT_ASSERT_DOUBLES_EQUAL(-15.0, aV.GetInDays(), 0.0);
    }
    { // Mult() including days.
        const Duration aD(-1.5);
        bool bOverflow = true;
        Duration aV = aD.Mult(-10, bOverflow);
        CPPUNIT_ASSERT(!bOverflow);
        CPPUNIT_ASSERT_DOUBLES_EQUAL(15.0, aV.GetInDays(), 0.0);
    }
    { // Mult() with overflow.
        const Duration aD(SAL_MAX_INT32);
        bool bOverflow = false;
        Duration aV = aD.Mult(2, bOverflow);
        CPPUNIT_ASSERT(bOverflow);
        CPPUNIT_ASSERT_EQUAL(SAL_MAX_INT32, aV.GetDays());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(23), aV.GetTime().GetHour());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetMin());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetSec());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(Time::nanoSecPerSec - 1),
                             aV.GetTime().GetNanoSec());
    }
    { // Mult() with overflow.
        const Duration aD(SAL_MIN_INT32);
        bool bOverflow = false;
        Duration aV = aD.Mult(2, bOverflow);
        CPPUNIT_ASSERT(bOverflow);
        CPPUNIT_ASSERT_EQUAL(SAL_MIN_INT32, aV.GetDays());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(23), aV.GetTime().GetHour());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetMin());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetSec());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(Time::nanoSecPerSec - 1),
                             aV.GetTime().GetNanoSec());
    }
    { // Mult() with overflow.
        const Duration aD(SAL_MAX_INT32);
        bool bOverflow = false;
        Duration aV = aD.Mult(-2, bOverflow);
        CPPUNIT_ASSERT(bOverflow);
        CPPUNIT_ASSERT_EQUAL(SAL_MIN_INT32, aV.GetDays());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(23), aV.GetTime().GetHour());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetMin());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetSec());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(Time::nanoSecPerSec - 1),
                             aV.GetTime().GetNanoSec());
    }
    { // Mult() with overflow.
        const Duration aD(SAL_MIN_INT32);
        bool bOverflow = false;
        Duration aV = aD.Mult(-2, bOverflow);
        CPPUNIT_ASSERT(bOverflow);
        CPPUNIT_ASSERT_EQUAL(SAL_MAX_INT32, aV.GetDays());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(23), aV.GetTime().GetHour());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetMin());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetSec());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(Time::nanoSecPerSec - 1),
                             aV.GetTime().GetNanoSec());
    }
    { // Inaccurate double yielding exact duration.
        const Time aS(15, 0, 0);
        const Time aE(16, 0, 0);
        const Duration aD(aE.GetTimeInDays() - aS.GetTimeInDays());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0), aD.GetDays());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), aD.GetTime().GetMin());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), aD.GetTime().GetSec());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(0), aD.GetTime().GetNanoSec());
    }
    { // Inaccurate double yielding exact duration, negative.
        const Time aS(15, 0, 0);
        const Time aE(16, 0, 0);
        const Duration aD(aS.GetTimeInDays() - aE.GetTimeInDays());
        CPPUNIT_ASSERT(aD.IsNegative());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0), aD.GetDays());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), aD.GetTime().GetMin());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), aD.GetTime().GetSec());
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(0), aD.GetTime().GetNanoSec());
    }
}

CPPUNIT_TEST_SUITE_REGISTRATION(DurationTest);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/tools/source/datetime/datetime.cxx b/tools/source/datetime/datetime.cxx
index 00790ff..9c27347 100644
--- a/tools/source/datetime/datetime.cxx
+++ b/tools/source/datetime/datetime.cxx
@@ -17,6 +17,7 @@
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
#include <tools/datetime.hxx>
#include <tools/duration.hxx>
#include <rtl/math.hxx>
#include <sal/log.hxx>

@@ -152,6 +153,13 @@ DateTime& DateTime::operator -=( const tools::Time& rTime )
    return *this;
}

DateTime& DateTime::operator +=( const tools::Duration& rDuration )
{
    AddDays(rDuration.GetDays());
    operator+=(rDuration.GetTime());
    return *this;
}

DateTime operator +( const DateTime& rDateTime, sal_Int32 nDays )
{
    DateTime aDateTime( rDateTime );
@@ -180,27 +188,19 @@ DateTime operator -( const DateTime& rDateTime, const tools::Time& rTime )
    return aDateTime;
}

DateTime operator +( const DateTime& rDateTime, const tools::Duration& rDuration )
{
    DateTime aDateTime(rDateTime);
    aDateTime.AddDays( rDuration.GetDays());
    aDateTime += rDuration.GetTime();
    return aDateTime;
}

void DateTime::AddTime( double fTimeInDays )
{
    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;
    }
    AddDays( sal_Int32(fInt) );     // full days
    if ( fFrac )
    {
        tools::Time aTime(0);  // default ctor calls system time, we don't need that
        fFrac *= ::tools::Time::nanoSecPerDay;   // time expressed in nanoseconds
        aTime.MakeTimeFromNS( static_cast<sal_Int64>(fFrac) );    // method handles negative ns
        operator+=( aTime );
    }
    // Use Duration to diminish floating point accuracy errors.
    tools::Duration aDuration(fTimeInDays);
    operator+=(aDuration);
}

DateTime operator +( const DateTime& rDateTime, double fTimeInDays )
diff --git a/tools/source/datetime/duration.cxx b/tools/source/datetime/duration.cxx
new file mode 100644
index 0000000..7140e2b
--- /dev/null
+++ b/tools/source/datetime/duration.cxx
@@ -0,0 +1,258 @@
/* -*- 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)
{
    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);
        // Round by 1 nanosecond if it's just 1 off to a second, i.e.
        // 0999999999 or 0000000001. This could be losened to rounding by 2 or
        // such if necessary.
        const sal_Int64 nN = nNS % Time::nanoSecPerSec;
        if (std::abs(nN) == 1)
            nNS -= (nNS < 0) ? -1 : 1;
        else if (std::abs(nN) == Time::nanoSecPerSec - 1)
        {
            nNS += (nNS < 0) ? -1 : 1;
            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, sal_Int64 nTime)
    : maTime(nTime)
    , mnDays(nDays)
{
}

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) == (nNS < 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 overflew, try with
        // individual components.
        const sal_uInt64 nMult64 = (nMult < 0) ? -nMult : nMult;
        do
        {
            rbOverflow = true;
            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: */