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 2 : void appendDateStr(rtl::OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter)
60 : {
61 2 : sal_uLong nFormat = pFormatter->GetStandardFormat( NUMBERFORMAT_DATE, ScGlobal::eLnge );
62 2 : rtl::OUString aString;
63 2 : pFormatter->GetInputLineString(fValue, nFormat, aString);
64 2 : rBuffer.append(aString);
65 2 : }
66 :
67 2 : rtl::OUString getSpecialDateName(double fValue, bool bFirst, SvNumberFormatter* pFormatter)
68 : {
69 2 : rtl::OUStringBuffer aBuffer;
70 2 : aBuffer.append(sal_Unicode(bFirst ? '<' : '>'));
71 2 : appendDateStr(aBuffer, fValue, pFormatter);
72 2 : return aBuffer.makeStringAndClear();
73 : }
74 :
75 : }
76 :
77 55 : bool ScDPUtil::isDuplicateDimension(const rtl::OUString& rName)
78 : {
79 55 : if (rName.isEmpty())
80 14 : return false;
81 :
82 41 : sal_Unicode cLast = rName[rName.getLength()-1];
83 41 : return cLast == sal_Unicode('*');
84 : }
85 :
86 145 : rtl::OUString ScDPUtil::getSourceDimensionName(const rtl::OUString& rName)
87 : {
88 145 : return comphelper::string::stripEnd(rName, '*');
89 : }
90 :
91 1 : rtl::OUString ScDPUtil::createDuplicateDimensionName(const rtl::OUString& rOriginal, size_t nDupCount)
92 : {
93 1 : if (!nDupCount)
94 0 : return rOriginal;
95 :
96 1 : rtl::OUStringBuffer aBuf(rOriginal);
97 2 : for (size_t i = 0; i < nDupCount; ++i)
98 1 : aBuf.append(sal_Unicode('*'));
99 :
100 1 : return aBuf.makeStringAndClear();
101 : }
102 :
103 71 : rtl::OUString ScDPUtil::getDateGroupName(
104 : sal_Int32 nDatePart, sal_Int32 nValue, SvNumberFormatter* pFormatter,
105 : double fStart, double fEnd)
106 : {
107 71 : if (nValue == ScDPItemData::DateFirst)
108 1 : return getSpecialDateName(fStart, true, pFormatter);
109 70 : if (nValue == ScDPItemData::DateLast)
110 1 : return getSpecialDateName(fEnd, false, pFormatter);
111 :
112 69 : switch ( nDatePart )
113 : {
114 : case sheet::DataPilotFieldGroupBy::YEARS:
115 21 : return rtl::OUString::valueOf(nValue);
116 : case sheet::DataPilotFieldGroupBy::QUARTERS:
117 22 : 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 26 : 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 0 : return rtl::OUString::createFromAscii("FIXME: unhandled value");
153 : }
154 :
155 18 : double ScDPUtil::getNumGroupStartValue(double fValue, const ScDPNumGroupInfo& rInfo)
156 : {
157 18 : if (fValue < rInfo.mfStart && !rtl::math::approxEqual(fValue, rInfo.mfStart))
158 : {
159 6 : rtl::math::setInf(&fValue, true);
160 6 : return fValue;
161 : }
162 :
163 12 : if (fValue > rInfo.mfEnd && !rtl::math::approxEqual(fValue, rInfo.mfEnd))
164 : {
165 6 : rtl::math::setInf(&fValue, false);
166 6 : return fValue;
167 : }
168 :
169 6 : double fDiff = fValue - rInfo.mfStart;
170 6 : double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
171 6 : double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
172 :
173 6 : 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 6 : 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 2 : 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 2 : rtl::OUStringBuffer aBuffer;
212 2 : aBuffer.append((sal_Unicode)( bFirst ? '<' : '>' ));
213 2 : if ( bDateValues )
214 0 : lcl_AppendDateStr( aBuffer, fValue, pFormatter );
215 : else
216 : rtl::math::doubleToUStringBuffer( aBuffer, fValue, rtl_math_StringFormat_Automatic,
217 2 : rtl_math_DecimalPlaces_Max, cDecSeparator, true );
218 2 : return aBuffer.makeStringAndClear();
219 : }
220 :
221 3 : 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 3 : double fStep = rInfo.mfStep;
228 3 : double fEndValue = fStartValue + fStep;
229 3 : 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 2 : fEndValue -= 1.0;
238 : }
239 :
240 3 : 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 3 : rtl::OUStringBuffer aBuffer;
248 3 : 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 3 : rtl_math_DecimalPlaces_Max, cDecSep, true );
258 3 : aBuffer.append( (sal_Unicode) '-' );
259 : rtl::math::doubleToUStringBuffer( aBuffer, fEndValue, rtl_math_StringFormat_Automatic,
260 3 : rtl_math_DecimalPlaces_Max, cDecSep, true );
261 : }
262 :
263 3 : return aBuffer.makeStringAndClear();
264 : }
265 :
266 : }
267 :
268 5 : rtl::OUString ScDPUtil::getNumGroupName(
269 : double fValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep, SvNumberFormatter* pFormatter)
270 : {
271 5 : if ( fValue < rInfo.mfStart && !rtl::math::approxEqual( fValue, rInfo.mfStart ) )
272 1 : return lcl_GetSpecialNumGroupName( rInfo.mfStart, true, cDecSep, rInfo.mbDateValues, pFormatter );
273 :
274 4 : if ( fValue > rInfo.mfEnd && !rtl::math::approxEqual( fValue, rInfo.mfEnd ) )
275 1 : return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
276 :
277 3 : double fDiff = fValue - rInfo.mfStart;
278 3 : double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
279 3 : double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
280 :
281 3 : 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 3 : return lcl_GetNumGroupName(fGroupStart, rInfo, cDecSep, pFormatter);
293 : }
294 :
295 50 : 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 50 : if (fValue < rInfo.mfStart && !rtl::math::approxEqual(fValue, rInfo.mfStart))
303 0 : return ScDPItemData::DateFirst;
304 50 : if (fValue > rInfo.mfEnd && !rtl::math::approxEqual(fValue, rInfo.mfEnd))
305 0 : return ScDPItemData::DateLast;
306 :
307 50 : sal_Int32 nResult = 0;
308 :
309 50 : 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 50 : Date aDate = *(pFormatter->GetNullDate());
335 50 : aDate += (long)::rtl::math::approxFloor(fValue);
336 :
337 50 : switch ( nDatePart )
338 : {
339 : case com::sun::star::sheet::DataPilotFieldGroupBy::YEARS:
340 18 : nResult = aDate.GetYear();
341 18 : break;
342 : case com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS:
343 16 : nResult = 1 + (aDate.GetMonth() - 1) / 3; // 1..4
344 16 : break;
345 : case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
346 16 : nResult = aDate.GetMonth(); // 1..12
347 16 : 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 0 : break;
359 : default:
360 : OSL_FAIL("invalid date part");
361 : }
362 : }
363 :
364 50 : return nResult;
365 : }
366 :
367 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|