Branch data Line data Source code
1 : : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : : /*
3 : : * Version: MPL 1.1 / GPLv3+ / LGPLv3+
4 : : *
5 : : * The contents of this file are subject to the Mozilla Public License Version
6 : : * 1.1 (the "License"); you may not use this file except in compliance with
7 : : * the License or as specified alternatively below. You may obtain a copy of
8 : : * the License at http://www.mozilla.org/MPL/
9 : : *
10 : : * Software distributed under the License is distributed on an "AS IS" basis,
11 : : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : : * for the specific language governing rights and limitations under the
13 : : * License.
14 : : *
15 : : * Major Contributor(s):
16 : : * Copyright (C) 2012 Kohei Yoshida <kohei.yoshida@suse.com>
17 : : *
18 : : * All Rights Reserved.
19 : : *
20 : : * For minor contributions see the git repository.
21 : : *
22 : : * Alternatively, the contents of this file may be used under the terms of
23 : : * either the GNU General Public License Version 3 or later (the "GPLv3+"), or
24 : : * the GNU Lesser General Public License Version 3 or later (the "LGPLv3+"),
25 : : * in which case the provisions of the GPLv3+ or the LGPLv3+ are applicable
26 : : * instead of those above.
27 : : */
28 : :
29 : : #include "dputil.hxx"
30 : : #include "global.hxx"
31 : : #include "dpitemdata.hxx"
32 : : #include "dpnumgroupinfo.hxx"
33 : :
34 : : #include "comphelper/string.hxx"
35 : : #include "unotools/localedatawrapper.hxx"
36 : : #include "unotools/calendarwrapper.hxx"
37 : : #include "svl/zforlist.hxx"
38 : : #include "rtl/math.hxx"
39 : :
40 : : #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
41 : : #include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
42 : :
43 : : #define D_TIMEFACTOR 86400.0
44 : :
45 : : using namespace com::sun::star;
46 : :
47 : : namespace {
48 : :
49 : : const sal_uInt16 SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations
50 : :
51 : 0 : rtl::OUString getTwoDigitString(sal_Int32 nValue)
52 : : {
53 [ # # ]: 0 : String aRet = String::CreateFromInt32( nValue );
54 [ # # ]: 0 : if ( aRet.Len() < 2 )
55 [ # # ]: 0 : aRet.Insert( (sal_Unicode)'0', 0 );
56 [ # # ][ # # ]: 0 : return aRet;
57 : : }
58 : :
59 : 6 : void appendDateStr(rtl::OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter)
60 : : {
61 [ + - ]: 6 : sal_uLong nFormat = pFormatter->GetStandardFormat( NUMBERFORMAT_DATE, ScGlobal::eLnge );
62 : 6 : rtl::OUString aString;
63 [ + - ]: 6 : pFormatter->GetInputLineString(fValue, nFormat, aString);
64 [ + - ]: 6 : rBuffer.append(aString);
65 : 6 : }
66 : :
67 : 6 : rtl::OUString getSpecialDateName(double fValue, bool bFirst, SvNumberFormatter* pFormatter)
68 : : {
69 : 6 : rtl::OUStringBuffer aBuffer;
70 [ + - ][ + + ]: 6 : aBuffer.append(sal_Unicode(bFirst ? '<' : '>'));
71 [ + - ]: 6 : appendDateStr(aBuffer, fValue, pFormatter);
72 [ + - ]: 6 : return aBuffer.makeStringAndClear();
73 : : }
74 : :
75 : : }
76 : :
77 : 218 : bool ScDPUtil::isDuplicateDimension(const rtl::OUString& rName)
78 : : {
79 [ + + ]: 218 : if (rName.isEmpty())
80 : 42 : return false;
81 : :
82 : 176 : sal_Unicode cLast = rName[rName.getLength()-1];
83 : 218 : return cLast == sal_Unicode('*');
84 : : }
85 : :
86 : 1138 : rtl::OUString ScDPUtil::getSourceDimensionName(const rtl::OUString& rName)
87 : : {
88 : 1138 : return comphelper::string::stripEnd(rName, '*');
89 : : }
90 : :
91 : 3 : rtl::OUString ScDPUtil::createDuplicateDimensionName(const rtl::OUString& rOriginal, size_t nDupCount)
92 : : {
93 [ - + ]: 3 : if (!nDupCount)
94 : 0 : return rOriginal;
95 : :
96 [ + - ]: 3 : rtl::OUStringBuffer aBuf(rOriginal);
97 [ + + ]: 6 : for (size_t i = 0; i < nDupCount; ++i)
98 [ + - ]: 3 : aBuf.append(sal_Unicode('*'));
99 : :
100 [ + - ]: 3 : return aBuf.makeStringAndClear();
101 : : }
102 : :
103 : 213 : rtl::OUString ScDPUtil::getDateGroupName(
104 : : sal_Int32 nDatePart, sal_Int32 nValue, SvNumberFormatter* pFormatter,
105 : : double fStart, double fEnd)
106 : : {
107 [ + + ]: 213 : if (nValue == ScDPItemData::DateFirst)
108 : 3 : return getSpecialDateName(fStart, true, pFormatter);
109 [ + + ]: 210 : if (nValue == ScDPItemData::DateLast)
110 : 3 : return getSpecialDateName(fEnd, false, pFormatter);
111 : :
112 [ + + + - : 207 : switch ( nDatePart )
- - - ]
113 : : {
114 : : case sheet::DataPilotFieldGroupBy::YEARS:
115 : 63 : return rtl::OUString::valueOf(nValue);
116 : : case sheet::DataPilotFieldGroupBy::QUARTERS:
117 : 66 : return ScGlobal::pLocaleData->getQuarterAbbreviation(sal_Int16(nValue-1)); // nValue is 1-based
118 : : case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
119 : : return ScGlobal::GetCalendar()->getDisplayName(
120 [ + - ]: 78 : i18n::CalendarDisplayIndex::MONTH, sal_Int16(nValue-1), 0); // 0-based, get short name
121 : : case sheet::DataPilotFieldGroupBy::DAYS:
122 : : {
123 : 0 : Date aDate(1, 1, SC_DP_LEAPYEAR);
124 [ # # ]: 0 : aDate += (nValue - 1); // nValue is 1-based
125 [ # # ]: 0 : Date aNullDate = *pFormatter->GetNullDate();
126 [ # # ]: 0 : long nDays = aDate - aNullDate;
127 : :
128 [ # # ]: 0 : sal_uLong nFormat = pFormatter->GetFormatIndex(NF_DATE_SYS_DDMMM, ScGlobal::eLnge);
129 : : Color* pColor;
130 [ # # ]: 0 : String aStr;
131 [ # # ]: 0 : pFormatter->GetOutputString(nDays, nFormat, aStr, &pColor);
132 [ # # ][ # # ]: 0 : return aStr;
133 : : }
134 : : case sheet::DataPilotFieldGroupBy::HOURS:
135 : : {
136 : : //! allow am/pm format?
137 : 0 : return getTwoDigitString(nValue);
138 : : }
139 : : break;
140 : : case sheet::DataPilotFieldGroupBy::MINUTES:
141 : : case sheet::DataPilotFieldGroupBy::SECONDS:
142 : : {
143 [ # # ][ # # ]: 0 : rtl::OUStringBuffer aBuf(ScGlobal::pLocaleData->getTimeSep());
144 [ # # ][ # # ]: 0 : aBuf.append(getTwoDigitString(nValue));
145 [ # # ]: 0 : return aBuf.makeStringAndClear();
146 : : }
147 : : break;
148 : : default:
149 : : OSL_FAIL("invalid date part");
150 : : }
151 : :
152 : 213 : return rtl::OUString::createFromAscii("FIXME: unhandled value");
153 : : }
154 : :
155 : 54 : double ScDPUtil::getNumGroupStartValue(double fValue, const ScDPNumGroupInfo& rInfo)
156 : : {
157 [ + + ][ + - ]: 54 : if (fValue < rInfo.mfStart && !rtl::math::approxEqual(fValue, rInfo.mfStart))
[ + + ]
158 : : {
159 : 18 : rtl::math::setInf(&fValue, true);
160 : 18 : return fValue;
161 : : }
162 : :
163 [ + + ][ + - ]: 36 : if (fValue > rInfo.mfEnd && !rtl::math::approxEqual(fValue, rInfo.mfEnd))
[ + + ]
164 : : {
165 : 18 : rtl::math::setInf(&fValue, false);
166 : 18 : return fValue;
167 : : }
168 : :
169 : 18 : double fDiff = fValue - rInfo.mfStart;
170 : 18 : double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
171 : 18 : double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
172 : :
173 [ - + ]: 18 : if (rtl::math::approxEqual(fGroupStart, rInfo.mfEnd) &&
[ - + # # ]
174 : 0 : !rtl::math::approxEqual(fGroupStart, rInfo.mfStart))
175 : : {
176 [ # # ]: 0 : if (!rInfo.mbDateValues)
177 : : {
178 : : // A group that would consist only of the end value is not
179 : : // created, instead the value is included in the last group
180 : : // before. So the previous group is used if the calculated group
181 : : // start value is the selected end value.
182 : :
183 : 0 : fDiv -= 1.0;
184 : 0 : return rInfo.mfStart + fDiv * rInfo.mfStep;
185 : : }
186 : :
187 : : // For date values, the end value is instead treated as above the
188 : : // limit if it would be a group of its own.
189 : :
190 : 0 : return rInfo.mfEnd + rInfo.mfStep;
191 : : }
192 : :
193 : 54 : return fGroupStart;
194 : : }
195 : :
196 : : namespace {
197 : :
198 : 0 : void lcl_AppendDateStr( rtl::OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter )
199 : : {
200 [ # # ]: 0 : sal_uLong nFormat = pFormatter->GetStandardFormat( NUMBERFORMAT_DATE, ScGlobal::eLnge );
201 : 0 : rtl::OUString aString;
202 [ # # ]: 0 : pFormatter->GetInputLineString( fValue, nFormat, aString );
203 [ # # ]: 0 : rBuffer.append( aString );
204 : 0 : }
205 : :
206 : 6 : rtl::OUString lcl_GetSpecialNumGroupName( double fValue, bool bFirst, sal_Unicode cDecSeparator,
207 : : bool bDateValues, SvNumberFormatter* pFormatter )
208 : : {
209 : : OSL_ENSURE( cDecSeparator != 0, "cDecSeparator not initialized" );
210 : :
211 : 6 : rtl::OUStringBuffer aBuffer;
212 [ + - ][ + + ]: 6 : aBuffer.append((sal_Unicode)( bFirst ? '<' : '>' ));
213 [ - + ]: 6 : if ( bDateValues )
214 [ # # ]: 0 : lcl_AppendDateStr( aBuffer, fValue, pFormatter );
215 : : else
216 : : rtl::math::doubleToUStringBuffer( aBuffer, fValue, rtl_math_StringFormat_Automatic,
217 : 6 : rtl_math_DecimalPlaces_Max, cDecSeparator, true );
218 [ + - ]: 6 : return aBuffer.makeStringAndClear();
219 : : }
220 : :
221 : 9 : rtl::OUString lcl_GetNumGroupName(
222 : : double fStartValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep,
223 : : SvNumberFormatter* pFormatter)
224 : : {
225 : : OSL_ENSURE( cDecSep != 0, "cDecSeparator not initialized" );
226 : :
227 : 9 : double fStep = rInfo.mfStep;
228 : 9 : double fEndValue = fStartValue + fStep;
229 [ + - ][ + - ]: 9 : if (rInfo.mbIntegerOnly && (rInfo.mbDateValues || !rtl::math::approxEqual(fEndValue, rInfo.mfEnd)))
[ + + ][ + + ]
230 : : {
231 : : // The second number of the group label is
232 : : // (first number + size - 1) if there are only integer numbers,
233 : : // (first number + size) if any non-integer numbers are involved.
234 : : // Exception: The last group (containing the end value) is always
235 : : // shown as including the end value (but not for dates).
236 : :
237 : 6 : fEndValue -= 1.0;
238 : : }
239 : :
240 [ - + ][ # # ]: 9 : if ( fEndValue > rInfo.mfEnd && !rInfo.mbAutoEnd )
241 : : {
242 : : // limit the last group to the end value
243 : :
244 : 0 : fEndValue = rInfo.mfEnd;
245 : : }
246 : :
247 : 9 : rtl::OUStringBuffer aBuffer;
248 [ - + ]: 9 : if ( rInfo.mbDateValues )
249 : : {
250 [ # # ]: 0 : lcl_AppendDateStr( aBuffer, fStartValue, pFormatter );
251 [ # # ]: 0 : aBuffer.appendAscii( " - " ); // with spaces
252 [ # # ]: 0 : lcl_AppendDateStr( aBuffer, fEndValue, pFormatter );
253 : : }
254 : : else
255 : : {
256 : : rtl::math::doubleToUStringBuffer( aBuffer, fStartValue, rtl_math_StringFormat_Automatic,
257 : 9 : rtl_math_DecimalPlaces_Max, cDecSep, true );
258 [ + - ]: 9 : aBuffer.append( (sal_Unicode) '-' );
259 : : rtl::math::doubleToUStringBuffer( aBuffer, fEndValue, rtl_math_StringFormat_Automatic,
260 : 9 : rtl_math_DecimalPlaces_Max, cDecSep, true );
261 : : }
262 : :
263 [ + - ]: 9 : return aBuffer.makeStringAndClear();
264 : : }
265 : :
266 : : }
267 : :
268 : 15 : rtl::OUString ScDPUtil::getNumGroupName(
269 : : double fValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep, SvNumberFormatter* pFormatter)
270 : : {
271 [ + + ][ + - ]: 15 : if ( fValue < rInfo.mfStart && !rtl::math::approxEqual( fValue, rInfo.mfStart ) )
[ + + ]
272 : 3 : return lcl_GetSpecialNumGroupName( rInfo.mfStart, true, cDecSep, rInfo.mbDateValues, pFormatter );
273 : :
274 [ + + ][ + - ]: 12 : if ( fValue > rInfo.mfEnd && !rtl::math::approxEqual( fValue, rInfo.mfEnd ) )
[ + + ]
275 : 3 : return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
276 : :
277 : 9 : double fDiff = fValue - rInfo.mfStart;
278 : 9 : double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
279 : 9 : double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
280 : :
281 [ - + ]: 9 : if ( rtl::math::approxEqual( fGroupStart, rInfo.mfEnd ) &&
[ - + # # ]
282 : 0 : !rtl::math::approxEqual( fGroupStart, rInfo.mfStart ) )
283 : : {
284 [ # # ]: 0 : if (rInfo.mbDateValues)
285 : : {
286 : : // For date values, the end value is instead treated as above the limit
287 : : // if it would be a group of its own.
288 : 0 : return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
289 : : }
290 : : }
291 : :
292 : 15 : return lcl_GetNumGroupName(fGroupStart, rInfo, cDecSep, pFormatter);
293 : : }
294 : :
295 : 150 : sal_Int32 ScDPUtil::getDatePartValue(
296 : : double fValue, const ScDPNumGroupInfo& rInfo, sal_Int32 nDatePart,
297 : : SvNumberFormatter* pFormatter)
298 : : {
299 : : // Start and end are inclusive
300 : : // (End date without a time value is included, with a time value it's not)
301 : :
302 [ - + ][ # # ]: 150 : if (fValue < rInfo.mfStart && !rtl::math::approxEqual(fValue, rInfo.mfStart))
[ - + ]
303 : 0 : return ScDPItemData::DateFirst;
304 [ - + ][ # # ]: 150 : if (fValue > rInfo.mfEnd && !rtl::math::approxEqual(fValue, rInfo.mfEnd))
[ - + ]
305 : 0 : return ScDPItemData::DateLast;
306 : :
307 : 150 : sal_Int32 nResult = 0;
308 : :
309 [ + - ][ + - ]: 150 : if (nDatePart == sheet::DataPilotFieldGroupBy::HOURS ||
[ - + ]
310 : : nDatePart == sheet::DataPilotFieldGroupBy::MINUTES ||
311 : : nDatePart == sheet::DataPilotFieldGroupBy::SECONDS)
312 : : {
313 : : // handle time
314 : : // (as in the cell functions, ScInterpreter::ScGetHour etc.: seconds are rounded)
315 : :
316 : 0 : double fTime = fValue - rtl::math::approxFloor(fValue);
317 : 0 : long nSeconds = (long)rtl::math::approxFloor(fTime*D_TIMEFACTOR+0.5);
318 : :
319 [ # # # # ]: 0 : switch (nDatePart)
320 : : {
321 : : case sheet::DataPilotFieldGroupBy::HOURS:
322 : 0 : nResult = nSeconds / 3600;
323 : 0 : break;
324 : : case sheet::DataPilotFieldGroupBy::MINUTES:
325 : 0 : nResult = ( nSeconds % 3600 ) / 60;
326 : 0 : break;
327 : : case sheet::DataPilotFieldGroupBy::SECONDS:
328 : 0 : nResult = nSeconds % 60;
329 : 0 : break;
330 : 0 : }
331 : : }
332 : : else
333 : : {
334 [ + - ]: 150 : Date aDate = *(pFormatter->GetNullDate());
335 [ + - ]: 150 : aDate += (long)::rtl::math::approxFloor(fValue);
336 : :
337 [ + + + - : 150 : switch ( nDatePart )
- ]
338 : : {
339 : : case com::sun::star::sheet::DataPilotFieldGroupBy::YEARS:
340 : 54 : nResult = aDate.GetYear();
341 : 54 : break;
342 : : case com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS:
343 : 48 : nResult = 1 + (aDate.GetMonth() - 1) / 3; // 1..4
344 : 48 : break;
345 : : case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
346 : 48 : nResult = aDate.GetMonth(); // 1..12
347 : 48 : break;
348 : : case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS:
349 : : {
350 : 0 : Date aYearStart(1, 1, aDate.GetYear());
351 [ # # ]: 0 : nResult = (aDate - aYearStart) + 1; // Jan 01 has value 1
352 [ # # ][ # # ]: 0 : if (nResult >= 60 && !aDate.IsLeapYear())
[ # # ][ # # ]
353 : : {
354 : : // days are counted from 1 to 366 - if not from a leap year, adjust
355 : 0 : ++nResult;
356 : : }
357 : : }
358 : 150 : break;
359 : : default:
360 : : OSL_FAIL("invalid date part");
361 : : }
362 : : }
363 : :
364 : 150 : return nResult;
365 : : }
366 : :
367 : : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|