Resolves: tdf#107012 follow date order of the target locale

... when converting format codes between locales, so en-US MM/DD/YYYY
correctly ends up as de-DE DD.MM.YYYY instead of MM.DD.YYYY

Change-Id: Iccfdd4787fc05462f47266c77cc9e95d14dae60d
diff --git a/svl/source/numbers/zforscan.cxx b/svl/source/numbers/zforscan.cxx
index 788cd42..7ba8659 100644
--- a/svl/source/numbers/zforscan.cxx
+++ b/svl/source/numbers/zforscan.cxx
@@ -1520,6 +1520,17 @@ int ImpSvNumberformatScan::FinalScanGetCalendar( sal_Int32& nPos, sal_uInt16& i,
    return 0;
}

bool ImpSvNumberformatScan::IsDateFragment( size_t nPos1, size_t nPos2 ) const
{
    return nPos2 - nPos1 == 2 && nTypeArray[nPos1+1] == NF_SYMBOLTYPE_DATESEP;
}

void ImpSvNumberformatScan::SwapArrayElements( size_t nPos1, size_t nPos2 )
{
    std::swap( nTypeArray[nPos1], nTypeArray[nPos2]);
    std::swap( sStrArray[nPos1], sStrArray[nPos2]);
}

sal_Int32 ImpSvNumberformatScan::FinalScan( OUString& rString )
{
    const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData();
@@ -1535,6 +1546,9 @@ sal_Int32 ImpSvNumberformatScan::FinalScan( OUString& rString )
    sal_Unicode cOldKeyH    = sKeyword[NF_KEY_H][0];
    sal_Unicode cOldKeyMI   = sKeyword[NF_KEY_MI][0];
    sal_Unicode cOldKeyS    = sKeyword[NF_KEY_S][0];
    DateOrder eOldDateOrder = pLoc->getDateOrder();
    sal_uInt16 nDayPos, nMonthPos, nYearPos;
    nDayPos = nMonthPos = nYearPos = SAL_MAX_UINT16;

    // If the group separator is a No-Break Space (French) continue with a
    // normal space instead so queries on space work correctly.
@@ -1546,6 +1560,7 @@ sal_Int32 ImpSvNumberformatScan::FinalScan( OUString& rString )
    {
        sOldThousandSep = " ";
    }
    bool bNewDateOrder = false;
    // change locale data et al
    if (bConvertMode)
    {
@@ -1554,6 +1569,7 @@ sal_Int32 ImpSvNumberformatScan::FinalScan( OUString& rString )
        pLoc = pFormatter->GetLocaleData();
        //! init new keywords
        InitKeywords();
        bNewDateOrder = (eOldDateOrder != pLoc->getDateOrder());
    }
    const CharClass* pChrCls = pFormatter->GetCharClass();

@@ -2318,6 +2334,37 @@ sal_Int32 ImpSvNumberformatScan::FinalScan( OUString& rString )
            case NF_KEY_RR :                        // RR
                sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
                nPos = nPos + sStrArray[i].getLength();
                if (bNewDateOrder)
                {
                    // For simple numeric date formats record date order and
                    // later rearrange.
                    switch (nTypeArray[i])
                    {
                        case NF_KEY_M:
                        case NF_KEY_MM:
                            if (nMonthPos == SAL_MAX_UINT16)
                                nMonthPos = i;
                            else
                                bNewDateOrder = false;
                        break;
                        case NF_KEY_D:
                        case NF_KEY_DD:
                            if (nDayPos == SAL_MAX_UINT16)
                                nDayPos = i;
                            else
                                bNewDateOrder = false;
                        break;
                        case NF_KEY_YY:
                        case NF_KEY_YYYY:
                            if (nYearPos == SAL_MAX_UINT16)
                                nYearPos = i;
                            else
                                bNewDateOrder = false;
                        break;
                        default:
                            ;   // nothing
                    }
                }
                i++;
                break;
            default: // Other keywords
@@ -2613,6 +2660,37 @@ sal_Int32 ImpSvNumberformatScan::FinalScan( OUString& rString )
                bTimePart = false;
                sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
                nPos = nPos + sStrArray[i].getLength();
                if (bNewDateOrder)
                {
                    // For simple numeric date formats record date order and
                    // later rearrange.
                    switch (nTypeArray[i])
                    {
                        case NF_KEY_M:
                        case NF_KEY_MM:
                            if (nMonthPos == SAL_MAX_UINT16)
                                nMonthPos = i;
                            else
                                bNewDateOrder = false;
                        break;
                        case NF_KEY_D:
                        case NF_KEY_DD:
                            if (nDayPos == SAL_MAX_UINT16)
                                nDayPos = i;
                            else
                                bNewDateOrder = false;
                        break;
                        case NF_KEY_YY:
                        case NF_KEY_YYYY:
                            if (nYearPos == SAL_MAX_UINT16)
                                nYearPos = i;
                            else
                                bNewDateOrder = false;
                        break;
                        default:
                            ;   // nothing
                    }
                }
                i++;
                break;
            case NF_KEY_THAI_T :
@@ -2652,6 +2730,110 @@ sal_Int32 ImpSvNumberformatScan::FinalScan( OUString& rString )
    }
    if ( bConvertMode )
    {
        if (bNewDateOrder && sOldDateSep == "-")
        {
            // Keep ISO formats Y-M-D, Y-M and M-D
            if (IsDateFragment( nYearPos, nMonthPos))
            {
                nTypeArray[nYearPos+1] = NF_SYMBOLTYPE_STRING;
                sStrArray[nYearPos+1] = sOldDateSep;
                bNewDateOrder = false;
            }
            if (IsDateFragment( nMonthPos, nDayPos))
            {
                nTypeArray[nMonthPos+1] = NF_SYMBOLTYPE_STRING;
                sStrArray[nMonthPos+1] = sOldDateSep;
                bNewDateOrder = false;
            }
        }
        if (bNewDateOrder)
        {
            // Rearrange date order to the target locale if the original order
            // includes date separators and is adjacent.
            /* TODO: for incomplete dates trailing separators need to be
             * handled according to the locale's usage, e.g. en-US M/D should
             * be converted to de-DE D.M. and vice versa. As is, it's
             * M/D -> D.M and D.M. -> M/D/ where specifically the latter looks
             * odd. Check accepted date patterns and append/remove? */
            switch (eOldDateOrder)
            {
                case DateOrder::DMY:
                    switch (pLoc->getDateOrder())
                    {
                        case DateOrder::MDY:
                            if (IsDateFragment( nDayPos, nMonthPos))
                                SwapArrayElements( nDayPos, nMonthPos);
                        break;
                        case DateOrder::YMD:
                            if (nYearPos != SAL_MAX_UINT16)
                            {
                                if (IsDateFragment( nDayPos, nMonthPos) && IsDateFragment( nMonthPos, nYearPos))
                                    SwapArrayElements( nDayPos, nYearPos);
                            }
                            else
                            {
                                if (IsDateFragment( nDayPos, nMonthPos))
                                    SwapArrayElements( nDayPos, nMonthPos);
                            }
                        break;
                        default:
                            ;   // nothing
                    }
                break;
                case DateOrder::MDY:
                    switch (pLoc->getDateOrder())
                    {
                        case DateOrder::DMY:
                            if (IsDateFragment( nMonthPos, nDayPos))
                                SwapArrayElements( nMonthPos, nDayPos);
                        break;
                        case DateOrder::YMD:
                            if (nYearPos != SAL_MAX_UINT16)
                            {
                                if (IsDateFragment( nMonthPos, nDayPos) && IsDateFragment( nDayPos, nYearPos))
                                {
                                    SwapArrayElements( nYearPos, nMonthPos);    // YDM
                                    SwapArrayElements( nYearPos, nDayPos);      // YMD
                                }
                            }
                        break;
                        default:
                            ;   // nothing
                    }
                break;
                case DateOrder::YMD:
                    switch (pLoc->getDateOrder())
                    {
                        case DateOrder::DMY:
                            if (nYearPos != SAL_MAX_UINT16)
                            {
                                if (IsDateFragment( nYearPos, nMonthPos) && IsDateFragment( nMonthPos, nDayPos))
                                    SwapArrayElements( nYearPos, nDayPos);
                            }
                            else
                            {
                                if (IsDateFragment( nMonthPos, nDayPos))
                                    SwapArrayElements( nMonthPos, nDayPos);
                            }
                        break;
                        case DateOrder::MDY:
                            if (nYearPos != SAL_MAX_UINT16)
                            {
                                if (IsDateFragment( nYearPos, nMonthPos) && IsDateFragment( nMonthPos, nDayPos))
                                {
                                    SwapArrayElements( nYearPos, nDayPos);      // DMY
                                    SwapArrayElements( nYearPos, nMonthPos);    // MDY
                                }
                            }
                        break;
                        default:
                            ;   // nothing
                    }
                break;
                default:
                    ;   // nothing
            }
        }
        // strings containing keywords of the target locale must be quoted, so
        // the user sees the difference and is able to edit the format string
        for ( i=0; i < nAnzStrings; i++ )
diff --git a/svl/source/numbers/zforscan.hxx b/svl/source/numbers/zforscan.hxx
index 30fc838..13d5b1a 100644
--- a/svl/source/numbers/zforscan.hxx
+++ b/svl/source/numbers/zforscan.hxx
@@ -241,6 +241,15 @@ private: // Private section
        reused instead of shifting all one up and nPos is decremented! */
    bool InsertSymbol( sal_uInt16 & nPos, svt::NfSymbolType eType, const OUString& rStr );

    /** Whether two key symbols are adjacent separated by date separator.
        This can only be used at the end of FinalScan() after
        NF_SYMBOLTYPE_DATESEP has already been set.
     */
    bool IsDateFragment( size_t nPos1, size_t nPos2 ) const;

    /** Swap nTypeArray and sStrArray elements at positions. */
    void SwapArrayElements( size_t nPos1, size_t nPos2 );

    static bool StringEqualsChar( const OUString& rStr, sal_Unicode ch )
        { return rStr.getLength() == 1 && rStr[0] == ch; }