1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 #include "dpitemdata.hxx"
12 #include "dpnumgroupinfo.hxx"
13 #include "globalnames.hxx"
14 #include "globstr.hrc"
16 #include "comphelper/string.hxx"
17 #include "unotools/localedatawrapper.hxx"
18 #include "unotools/calendarwrapper.hxx"
19 #include "svl/zforlist.hxx"
20 #include "rtl/math.hxx"
22 #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
23 #include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
25 using namespace com::sun::star
;
29 const sal_uInt16 SC_DP_LEAPYEAR
= 1648; // arbitrary leap year for date calculations
31 OUString
getTwoDigitString(sal_Int32 nValue
)
33 OUString aRet
= OUString::number( nValue
);
34 if ( aRet
.getLength() < 2 )
39 void appendDateStr(OUStringBuffer
& rBuffer
, double fValue
, SvNumberFormatter
* pFormatter
)
41 sal_uLong nFormat
= pFormatter
->GetStandardFormat( NUMBERFORMAT_DATE
, ScGlobal::eLnge
);
43 pFormatter
->GetInputLineString(fValue
, nFormat
, aString
);
44 rBuffer
.append(aString
);
47 OUString
getSpecialDateName(double fValue
, bool bFirst
, SvNumberFormatter
* pFormatter
)
49 OUStringBuffer aBuffer
;
50 aBuffer
.append(sal_Unicode(bFirst
? '<' : '>'));
51 appendDateStr(aBuffer
, fValue
, pFormatter
);
52 return aBuffer
.makeStringAndClear();
57 bool ScDPUtil::isDuplicateDimension(const OUString
& rName
)
59 return rName
.endsWith("*");
62 OUString
ScDPUtil::getSourceDimensionName(const OUString
& rName
)
64 return comphelper::string::stripEnd(rName
, '*');
67 sal_uInt8
ScDPUtil::getDuplicateIndex(const OUString
& rName
)
69 // Count all trailing '*'s.
71 sal_Int32 n
= rName
.getLength();
75 sal_uInt8 nDupCount
= 0;
76 const sal_Unicode
* p
= rName
.getStr();
77 const sal_Unicode
* pStart
= p
;
78 p
+= n
-1; // Set it to the last char.
79 for (; p
!= pStart
; --p
, ++nDupCount
)
88 OUString
ScDPUtil::createDuplicateDimensionName(const OUString
& rOriginal
, size_t nDupCount
)
93 OUStringBuffer
aBuf(rOriginal
);
94 for (size_t i
= 0; i
< nDupCount
; ++i
)
97 return aBuf
.makeStringAndClear();
100 OUString
ScDPUtil::getDateGroupName(
101 sal_Int32 nDatePart
, sal_Int32 nValue
, SvNumberFormatter
* pFormatter
,
102 double fStart
, double fEnd
)
104 if (nValue
== ScDPItemData::DateFirst
)
105 return getSpecialDateName(fStart
, true, pFormatter
);
106 if (nValue
== ScDPItemData::DateLast
)
107 return getSpecialDateName(fEnd
, false, pFormatter
);
111 case sheet::DataPilotFieldGroupBy::YEARS
:
112 return OUString::number(nValue
);
113 case sheet::DataPilotFieldGroupBy::QUARTERS
:
114 return ScGlobal::pLocaleData
->getQuarterAbbreviation(sal_Int16(nValue
-1)); // nValue is 1-based
115 case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS
:
116 return ScGlobal::GetCalendar()->getDisplayName(
117 i18n::CalendarDisplayIndex::MONTH
, sal_Int16(nValue
-1), 0); // 0-based, get short name
118 case sheet::DataPilotFieldGroupBy::DAYS
:
120 Date
aDate(1, 1, SC_DP_LEAPYEAR
);
121 aDate
+= (nValue
- 1); // nValue is 1-based
122 Date aNullDate
= *pFormatter
->GetNullDate();
123 long nDays
= aDate
- aNullDate
;
125 sal_uLong nFormat
= pFormatter
->GetFormatIndex(NF_DATE_SYS_DDMMM
, ScGlobal::eLnge
);
128 pFormatter
->GetOutputString(nDays
, nFormat
, aStr
, &pColor
);
131 case sheet::DataPilotFieldGroupBy::HOURS
:
133 //! allow am/pm format?
134 return getTwoDigitString(nValue
);
137 case sheet::DataPilotFieldGroupBy::MINUTES
:
138 case sheet::DataPilotFieldGroupBy::SECONDS
:
140 OUStringBuffer
aBuf(ScGlobal::pLocaleData
->getTimeSep());
141 aBuf
.append(getTwoDigitString(nValue
));
142 return aBuf
.makeStringAndClear();
146 OSL_FAIL("invalid date part");
149 return OUString("FIXME: unhandled value");
152 double ScDPUtil::getNumGroupStartValue(double fValue
, const ScDPNumGroupInfo
& rInfo
)
154 if (fValue
< rInfo
.mfStart
&& !rtl::math::approxEqual(fValue
, rInfo
.mfStart
))
156 rtl::math::setInf(&fValue
, true);
160 if (fValue
> rInfo
.mfEnd
&& !rtl::math::approxEqual(fValue
, rInfo
.mfEnd
))
162 rtl::math::setInf(&fValue
, false);
166 double fDiff
= fValue
- rInfo
.mfStart
;
167 double fDiv
= rtl::math::approxFloor( fDiff
/ rInfo
.mfStep
);
168 double fGroupStart
= rInfo
.mfStart
+ fDiv
* rInfo
.mfStep
;
170 if (rtl::math::approxEqual(fGroupStart
, rInfo
.mfEnd
) &&
171 !rtl::math::approxEqual(fGroupStart
, rInfo
.mfStart
))
173 if (!rInfo
.mbDateValues
)
175 // A group that would consist only of the end value is not
176 // created, instead the value is included in the last group
177 // before. So the previous group is used if the calculated group
178 // start value is the selected end value.
181 return rInfo
.mfStart
+ fDiv
* rInfo
.mfStep
;
184 // For date values, the end value is instead treated as above the
185 // limit if it would be a group of its own.
187 return rInfo
.mfEnd
+ rInfo
.mfStep
;
195 void lcl_AppendDateStr( OUStringBuffer
& rBuffer
, double fValue
, SvNumberFormatter
* pFormatter
)
197 sal_uLong nFormat
= pFormatter
->GetStandardFormat( NUMBERFORMAT_DATE
, ScGlobal::eLnge
);
199 pFormatter
->GetInputLineString( fValue
, nFormat
, aString
);
200 rBuffer
.append( aString
);
203 OUString
lcl_GetSpecialNumGroupName( double fValue
, bool bFirst
, sal_Unicode cDecSeparator
,
204 bool bDateValues
, SvNumberFormatter
* pFormatter
)
206 OSL_ENSURE( cDecSeparator
!= 0, "cDecSeparator not initialized" );
208 OUStringBuffer aBuffer
;
209 aBuffer
.append((sal_Unicode
)( bFirst
? '<' : '>' ));
211 lcl_AppendDateStr( aBuffer
, fValue
, pFormatter
);
213 rtl::math::doubleToUStringBuffer( aBuffer
, fValue
, rtl_math_StringFormat_Automatic
,
214 rtl_math_DecimalPlaces_Max
, cDecSeparator
, true );
215 return aBuffer
.makeStringAndClear();
218 OUString
lcl_GetNumGroupName(
219 double fStartValue
, const ScDPNumGroupInfo
& rInfo
, sal_Unicode cDecSep
,
220 SvNumberFormatter
* pFormatter
)
222 OSL_ENSURE( cDecSep
!= 0, "cDecSeparator not initialized" );
224 double fStep
= rInfo
.mfStep
;
225 double fEndValue
= fStartValue
+ fStep
;
226 if (rInfo
.mbIntegerOnly
&& (rInfo
.mbDateValues
|| !rtl::math::approxEqual(fEndValue
, rInfo
.mfEnd
)))
228 // The second number of the group label is
229 // (first number + size - 1) if there are only integer numbers,
230 // (first number + size) if any non-integer numbers are involved.
231 // Exception: The last group (containing the end value) is always
232 // shown as including the end value (but not for dates).
237 if ( fEndValue
> rInfo
.mfEnd
&& !rInfo
.mbAutoEnd
)
239 // limit the last group to the end value
241 fEndValue
= rInfo
.mfEnd
;
244 OUStringBuffer aBuffer
;
245 if ( rInfo
.mbDateValues
)
247 lcl_AppendDateStr( aBuffer
, fStartValue
, pFormatter
);
248 aBuffer
.appendAscii( " - " ); // with spaces
249 lcl_AppendDateStr( aBuffer
, fEndValue
, pFormatter
);
253 rtl::math::doubleToUStringBuffer( aBuffer
, fStartValue
, rtl_math_StringFormat_Automatic
,
254 rtl_math_DecimalPlaces_Max
, cDecSep
, true );
255 aBuffer
.append( '-' );
256 rtl::math::doubleToUStringBuffer( aBuffer
, fEndValue
, rtl_math_StringFormat_Automatic
,
257 rtl_math_DecimalPlaces_Max
, cDecSep
, true );
260 return aBuffer
.makeStringAndClear();
265 OUString
ScDPUtil::getNumGroupName(
266 double fValue
, const ScDPNumGroupInfo
& rInfo
, sal_Unicode cDecSep
, SvNumberFormatter
* pFormatter
)
268 if ( fValue
< rInfo
.mfStart
&& !rtl::math::approxEqual( fValue
, rInfo
.mfStart
) )
269 return lcl_GetSpecialNumGroupName( rInfo
.mfStart
, true, cDecSep
, rInfo
.mbDateValues
, pFormatter
);
271 if ( fValue
> rInfo
.mfEnd
&& !rtl::math::approxEqual( fValue
, rInfo
.mfEnd
) )
272 return lcl_GetSpecialNumGroupName( rInfo
.mfEnd
, false, cDecSep
, rInfo
.mbDateValues
, pFormatter
);
274 double fDiff
= fValue
- rInfo
.mfStart
;
275 double fDiv
= rtl::math::approxFloor( fDiff
/ rInfo
.mfStep
);
276 double fGroupStart
= rInfo
.mfStart
+ fDiv
* rInfo
.mfStep
;
278 if ( rtl::math::approxEqual( fGroupStart
, rInfo
.mfEnd
) &&
279 !rtl::math::approxEqual( fGroupStart
, rInfo
.mfStart
) )
281 if (rInfo
.mbDateValues
)
283 // For date values, the end value is instead treated as above the limit
284 // if it would be a group of its own.
285 return lcl_GetSpecialNumGroupName( rInfo
.mfEnd
, false, cDecSep
, rInfo
.mbDateValues
, pFormatter
);
289 return lcl_GetNumGroupName(fGroupStart
, rInfo
, cDecSep
, pFormatter
);
292 sal_Int32
ScDPUtil::getDatePartValue(
293 double fValue
, const ScDPNumGroupInfo
& rInfo
, sal_Int32 nDatePart
,
294 SvNumberFormatter
* pFormatter
)
296 // Start and end are inclusive
297 // (End date without a time value is included, with a time value it's not)
299 if (fValue
< rInfo
.mfStart
&& !rtl::math::approxEqual(fValue
, rInfo
.mfStart
))
300 return ScDPItemData::DateFirst
;
301 if (fValue
> rInfo
.mfEnd
&& !rtl::math::approxEqual(fValue
, rInfo
.mfEnd
))
302 return ScDPItemData::DateLast
;
304 sal_Int32 nResult
= 0;
306 if (nDatePart
== sheet::DataPilotFieldGroupBy::HOURS
||
307 nDatePart
== sheet::DataPilotFieldGroupBy::MINUTES
||
308 nDatePart
== sheet::DataPilotFieldGroupBy::SECONDS
)
311 // (as in the cell functions, ScInterpreter::ScGetHour etc.: seconds are rounded)
313 double fTime
= fValue
- rtl::math::approxFloor(fValue
);
314 long nSeconds
= (long)rtl::math::approxFloor(fTime
*DATE_TIME_FACTOR
+0.5);
318 case sheet::DataPilotFieldGroupBy::HOURS
:
319 nResult
= nSeconds
/ 3600;
321 case sheet::DataPilotFieldGroupBy::MINUTES
:
322 nResult
= ( nSeconds
% 3600 ) / 60;
324 case sheet::DataPilotFieldGroupBy::SECONDS
:
325 nResult
= nSeconds
% 60;
331 Date aDate
= *(pFormatter
->GetNullDate());
332 aDate
+= (long)::rtl::math::approxFloor(fValue
);
336 case com::sun::star::sheet::DataPilotFieldGroupBy::YEARS
:
337 nResult
= aDate
.GetYear();
339 case com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS
:
340 nResult
= 1 + (aDate
.GetMonth() - 1) / 3; // 1..4
342 case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS
:
343 nResult
= aDate
.GetMonth(); // 1..12
345 case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS
:
347 Date
aYearStart(1, 1, aDate
.GetYear());
348 nResult
= (aDate
- aYearStart
) + 1; // Jan 01 has value 1
349 if (nResult
>= 60 && !aDate
.IsLeapYear())
351 // days are counted from 1 to 366 - if not from a leap year, adjust
357 OSL_FAIL("invalid date part");
366 sal_uInt16 nFuncStrIds
[12] = {
367 0, // SUBTOTAL_FUNC_NONE
368 STR_FUN_TEXT_AVG
, // SUBTOTAL_FUNC_AVE
369 STR_FUN_TEXT_COUNT
, // SUBTOTAL_FUNC_CNT
370 STR_FUN_TEXT_COUNT
, // SUBTOTAL_FUNC_CNT2
371 STR_FUN_TEXT_MAX
, // SUBTOTAL_FUNC_MAX
372 STR_FUN_TEXT_MIN
, // SUBTOTAL_FUNC_MIN
373 STR_FUN_TEXT_PRODUCT
, // SUBTOTAL_FUNC_PROD
374 STR_FUN_TEXT_STDDEV
, // SUBTOTAL_FUNC_STD
375 STR_FUN_TEXT_STDDEV
, // SUBTOTAL_FUNC_STDP
376 STR_FUN_TEXT_SUM
, // SUBTOTAL_FUNC_SUM
377 STR_FUN_TEXT_VAR
, // SUBTOTAL_FUNC_VAR
378 STR_FUN_TEXT_VAR
// SUBTOTAL_FUNC_VARP
383 OUString
ScDPUtil::getDisplayedMeasureName(const OUString
& rName
, ScSubTotalFunc eFunc
)
386 sal_uInt16 nId
= nFuncStrIds
[eFunc
];
389 aRet
.append(ScGlobal::GetRscString(nId
)); // function name
392 aRet
.append(rName
); // field name
394 return aRet
.makeStringAndClear();
397 ScSubTotalFunc
ScDPUtil::toSubTotalFunc(com::sun::star::sheet::GeneralFunction eGenFunc
)
399 ScSubTotalFunc eSubTotal
;
402 case sheet::GeneralFunction_NONE
: eSubTotal
= SUBTOTAL_FUNC_NONE
; break;
403 case sheet::GeneralFunction_SUM
: eSubTotal
= SUBTOTAL_FUNC_SUM
; break;
404 case sheet::GeneralFunction_COUNT
: eSubTotal
= SUBTOTAL_FUNC_CNT2
; break;
405 case sheet::GeneralFunction_AVERAGE
: eSubTotal
= SUBTOTAL_FUNC_AVE
; break;
406 case sheet::GeneralFunction_MAX
: eSubTotal
= SUBTOTAL_FUNC_MAX
; break;
407 case sheet::GeneralFunction_MIN
: eSubTotal
= SUBTOTAL_FUNC_MIN
; break;
408 case sheet::GeneralFunction_PRODUCT
: eSubTotal
= SUBTOTAL_FUNC_PROD
; break;
409 case sheet::GeneralFunction_COUNTNUMS
: eSubTotal
= SUBTOTAL_FUNC_CNT
; break;
410 case sheet::GeneralFunction_STDEV
: eSubTotal
= SUBTOTAL_FUNC_STD
; break;
411 case sheet::GeneralFunction_STDEVP
: eSubTotal
= SUBTOTAL_FUNC_STDP
; break;
412 case sheet::GeneralFunction_VAR
: eSubTotal
= SUBTOTAL_FUNC_VAR
; break;
413 case sheet::GeneralFunction_VARP
: eSubTotal
= SUBTOTAL_FUNC_VARP
; break;
414 case sheet::GeneralFunction_AUTO
:
416 eSubTotal
= SUBTOTAL_FUNC_NONE
;
421 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */