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