Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/connectivity/source/commontools/dbconversion.cxx
Line
Count
Source
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
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <connectivity/dbconversion.hxx>
21
#include <com/sun/star/util/Date.hpp>
22
#include <com/sun/star/util/Time.hpp>
23
#include <com/sun/star/util/DateTime.hpp>
24
#include <rtl/character.hxx>
25
#include <rtl/math.hxx>
26
#include <sal/log.hxx>
27
#include <unotools/datetime.hxx>
28
#include <comphelper/date.hxx>
29
#include <o3tl/string_view.hxx>
30
#include <sstream>
31
#include <iomanip>
32
33
namespace
34
{
35
    const sal_Int64 nanoSecInSec = 1000000000;
36
    const sal_Int16 secInMin  = 60;
37
    const sal_Int16 minInHour = 60;
38
39
    const sal_Int64 secMask  = 1000000000;
40
    const sal_Int64 minMask  = 100000000000LL;
41
    const sal_Int64 hourMask = 10000000000000LL;
42
43
    const double fNanoSecondsPerDay = nanoSecInSec * secInMin * minInHour * 24.0;
44
45
    //  32767-12-31 in "(days since 0001-01-01) + 1" format
46
    const sal_Int32 maxDays =  11967896;
47
    // -32768-01-01 in "(days since 0001-01-01) + 1" format
48
    // Yes, I know it is currently unused. Will have to be used
49
    // when we implement negative years. Writing down the correct
50
    // value for future reference.
51
    // *** Please don't remove just because it is unused ***
52
    // Lionel Élie Mamane 2017-08-02
53
    // const sal_Int32 minDays = -11968270;
54
}
55
56
57
namespace dbtools
58
{
59
60
61
    using namespace ::com::sun::star::uno;
62
    using namespace ::com::sun::star::util;
63
    using namespace ::com::sun::star::sdb;
64
    using namespace ::com::sun::star::lang;
65
    using namespace ::com::sun::star::beans;
66
67
68
    css::util::Date const & DBTypeConversion::getStandardDate()
69
86.0k
    {
70
86.0k
        static css::util::Date STANDARD_DB_DATE(30,12,1899);
71
86.0k
        return STANDARD_DB_DATE;
72
86.0k
    }
73
74
    OUString DBTypeConversion::toDateString(const css::util::Date& rDate)
75
28.5k
    {
76
28.5k
        std::ostringstream ostr;
77
28.5k
        using std::setw;
78
28.5k
        ostr.fill('0');
79
28.5k
        ostr << setw(4) << rDate.Year  << "-"
80
28.5k
             << setw(2) << rDate.Month << "-"
81
28.5k
             << setw(2) << rDate.Day;
82
28.5k
        return OUString::createFromAscii(ostr.str());
83
28.5k
    }
84
85
    OUString DBTypeConversion::toTimeStringS(const css::util::Time& rTime)
86
0
    {
87
0
        std::ostringstream ostr;
88
0
        using std::setw;
89
0
        ostr.fill('0');
90
0
        ostr << setw(2) << rTime.Hours   << ":"
91
0
             << setw(2) << rTime.Minutes << ":"
92
0
             << setw(2) << rTime.Seconds;
93
0
        return OUString::createFromAscii(ostr.str());
94
0
    }
95
96
    OUString DBTypeConversion::toTimeString(const css::util::Time& rTime)
97
25.7k
    {
98
25.7k
        std::ostringstream ostr;
99
25.7k
        using std::setw;
100
25.7k
        ostr.fill('0');
101
25.7k
        ostr << setw(2) << rTime.Hours   << ":"
102
25.7k
             << setw(2) << rTime.Minutes << ":"
103
25.7k
             << setw(2) << rTime.Seconds << "."
104
25.7k
             << setw(9) << rTime.NanoSeconds;
105
25.7k
        return OUString::createFromAscii(ostr.str());
106
25.7k
    }
107
108
    OUString DBTypeConversion::toDateTimeString(const css::util::DateTime& _rDateTime)
109
25.7k
    {
110
25.7k
        css::util::Date aDate(_rDateTime.Day,_rDateTime.Month,_rDateTime.Year);
111
25.7k
        css::util::Time const aTime(_rDateTime.NanoSeconds, _rDateTime.Seconds,
112
25.7k
                _rDateTime.Minutes, _rDateTime.Hours, _rDateTime.IsUTC);
113
25.7k
        return toDateString(aDate) + " " + toTimeString(aTime);
114
25.7k
    }
115
116
    css::util::Date DBTypeConversion::toDate(const sal_Int32 _nVal)
117
0
    {
118
0
        css::util::Date aReturn;
119
0
        aReturn.Day = static_cast<sal_uInt16>(_nVal % 100);
120
0
        aReturn.Month = static_cast<sal_uInt16>((_nVal  / 100) % 100);
121
0
        aReturn.Year = static_cast<sal_uInt16>(_nVal  / 10000);
122
0
        return aReturn;
123
0
    }
124
125
126
    css::util::Time DBTypeConversion::toTime(const sal_Int64 _nVal)
127
0
    {
128
0
        css::util::Time aReturn;
129
0
        sal_uInt64 unVal = static_cast<sal_uInt64>(_nVal >= 0 ? _nVal : -_nVal);
130
0
        aReturn.Hours = unVal / hourMask;
131
0
        aReturn.Minutes = (unVal / minMask) % 100;
132
0
        aReturn.Seconds = (unVal / secMask) % 100;
133
0
        aReturn.NanoSeconds = unVal % secMask;
134
0
        return aReturn;
135
0
    }
136
137
    sal_Int64 DBTypeConversion::getNsFromTime(const css::util::Time& rVal)
138
6.55k
    {
139
6.55k
        sal_Int32   nHour     = rVal.Hours;
140
6.55k
        sal_Int32   nMin      = rVal.Minutes;
141
6.55k
        sal_Int32   nSec      = rVal.Seconds;
142
6.55k
        sal_Int32   nNanoSec  = rVal.NanoSeconds;
143
144
6.55k
        return nNanoSec +
145
6.55k
               nSec  * nanoSecInSec +
146
6.55k
               nMin  * (secInMin * nanoSecInSec) +
147
6.55k
               nHour * (minInHour * secInMin * nanoSecInSec);
148
6.55k
    }
149
150
    static sal_Int32 implRelativeToAbsoluteNull(const css::util::Date& _rDate)
151
99.5k
    {
152
99.5k
        if (_rDate.Day == 0 && _rDate.Month == 0 && _rDate.Year == 0)
153
2.99k
        {
154
            // 0000-00-00 is *NOT* a valid date and passing it to the date
155
            // conversion even when normalizing rightly asserts. Whatever we
156
            // return here, it will be wrong. The old before commit
157
            // 52ff16771ac160d27fd7beb78a4cfba22ad84f06 wrong implementation
158
            // calculated -365 for that, effectively that would be a date of
159
            // -0001-01-01 now but it was likely assumed that would be
160
            // 0000-00-01 or even 0000-00-00 instead. Try if we get away with 0
161
            // for -0001-12-31, the same that
162
            // comphelper::date::convertDateToDaysNormalizing()
163
            // would return if comphelper::date::normalize() wouldn't ignore
164
            // such "empty" date.
165
166
2.99k
            return 0;
167
2.99k
        }
168
96.5k
        return comphelper::date::convertDateToDaysNormalizing( _rDate.Day, _rDate.Month, _rDate.Year);
169
99.5k
    }
170
171
    sal_Int32 DBTypeConversion::toDays(const css::util::Date& _rVal, const css::util::Date& _rNullDate)
172
13.8k
    {
173
13.8k
        return implRelativeToAbsoluteNull(_rVal) - implRelativeToAbsoluteNull(_rNullDate);
174
13.8k
    }
175
176
177
    double DBTypeConversion::toDouble(const css::util::Date& rVal, const css::util::Date& _rNullDate)
178
2.76k
    {
179
2.76k
        return static_cast<double>(toDays(rVal, _rNullDate));
180
2.76k
    }
181
182
183
    double DBTypeConversion::toDouble(const css::util::Time& rVal)
184
6.55k
    {
185
6.55k
        return static_cast<double>(getNsFromTime(rVal)) / fNanoSecondsPerDay;
186
6.55k
    }
187
188
189
    double DBTypeConversion::toDouble(const css::util::DateTime& _rVal, const css::util::Date& _rNullDate)
190
6.55k
    {
191
6.55k
        sal_Int64   nTime     = toDays(css::util::Date(_rVal.Day, _rVal.Month, _rVal.Year), _rNullDate);
192
6.55k
        css::util::Time aTimePart;
193
194
6.55k
        aTimePart.Hours             = _rVal.Hours;
195
6.55k
        aTimePart.Minutes           = _rVal.Minutes;
196
6.55k
        aTimePart.Seconds           = _rVal.Seconds;
197
6.55k
        aTimePart.NanoSeconds       = _rVal.NanoSeconds;
198
199
6.55k
        return static_cast<double>(nTime) + toDouble(aTimePart);
200
6.55k
    }
201
202
    static void addDays(const sal_Int32 nDays, css::util::Date& _rDate)
203
68.0k
    {
204
68.0k
        sal_Int64 nTempDays = implRelativeToAbsoluteNull(_rDate);
205
206
68.0k
        nTempDays += nDays;
207
68.0k
        if ( nTempDays > maxDays )
208
3.77k
        {
209
3.77k
            _rDate.Day      = 31;
210
3.77k
            _rDate.Month    = 12;
211
3.77k
            _rDate.Year     = 9999;
212
3.77k
        }
213
        // TODO: can we replace that check by minDays? Would allow dates BCE
214
64.2k
        else if ( nTempDays <= 0 )
215
2.70k
        {
216
2.70k
            _rDate.Day      = 1;
217
2.70k
            _rDate.Month    = 1;
218
2.70k
            _rDate.Year     = 1;
219
2.70k
        }
220
61.5k
        else
221
61.5k
            comphelper::date::convertDaysToDate( nTempDays, _rDate.Day, _rDate.Month, _rDate.Year );
222
68.0k
    }
223
224
    static void subDays(const sal_Int32 nDays, css::util::Date& _rDate )
225
3.86k
    {
226
3.86k
        sal_Int64 nTempDays = implRelativeToAbsoluteNull(_rDate);
227
228
3.86k
        nTempDays -= nDays;
229
3.86k
        if ( nTempDays > maxDays )
230
363
        {
231
363
            _rDate.Day      = 31;
232
363
            _rDate.Month    = 12;
233
363
            _rDate.Year     = 9999;
234
363
        }
235
        // TODO: can we replace that check by minDays? Would allow dates BCE
236
3.50k
        else if ( nTempDays <= 0 )
237
897
        {
238
897
            _rDate.Day      = 1;
239
897
            _rDate.Month    = 1;
240
897
            _rDate.Year     = 1;
241
897
        }
242
2.60k
        else
243
2.60k
            comphelper::date::convertDaysToDate( nTempDays, _rDate.Day, _rDate.Month, _rDate.Year );
244
3.86k
    }
245
246
    css::util::Date DBTypeConversion::toDate(const double dVal, const css::util::Date& _rNullDate)
247
71.8k
    {
248
71.8k
        css::util::Date aRet = _rNullDate;
249
250
71.8k
        if (dVal >= 0)
251
68.0k
            addDays(static_cast<sal_Int32>(dVal),aRet);
252
3.86k
        else
253
3.86k
            subDays(static_cast<sal_uInt32>(-dVal),aRet);
254
            //  x -= (sal_uInt32)(-nDays);
255
256
71.8k
        return aRet;
257
71.8k
    }
258
259
    css::util::Time DBTypeConversion::toTime(const double dVal, short nDigits)
260
24.7k
    {
261
24.7k
        const double nDays = std::trunc(dVal);
262
24.7k
        double fSeconds((dVal - nDays) * (fNanoSecondsPerDay / nanoSecInSec));
263
24.7k
        fSeconds = ::rtl::math::round(fSeconds, nDigits);
264
24.7k
        sal_Int64 nNS = fSeconds * nanoSecInSec;
265
266
24.7k
        sal_Int16 nSign;
267
24.7k
        if ( nNS < 0 )
268
779
        {
269
779
            nNS *= -1;
270
779
            nSign = -1;
271
779
        }
272
23.9k
        else
273
23.9k
            nSign = 1;
274
275
24.7k
        css::util::Time aRet;
276
        // normalize time
277
        // we have to sal_Int32 here because otherwise we get an overflow
278
24.7k
        sal_Int64 nNanoSeconds      = nNS;
279
24.7k
        sal_Int32 nSeconds          = nNanoSeconds / nanoSecInSec;
280
24.7k
        sal_Int32 nMinutes          = nSeconds / secInMin;
281
282
24.7k
        aRet.NanoSeconds            = nNanoSeconds % nanoSecInSec;
283
24.7k
        aRet.Seconds                = nSeconds % secInMin;
284
24.7k
        aRet.Hours                  = nMinutes / minInHour;
285
24.7k
        aRet.Minutes                = nMinutes % minInHour;
286
287
        // assemble time
288
24.7k
        sal_Int64 nTime = nSign *
289
24.7k
                          (aRet.NanoSeconds +
290
24.7k
                           aRet.Seconds * secMask +
291
24.7k
                           aRet.Minutes * minMask +
292
24.7k
                           aRet.Hours   * hourMask);
293
294
24.7k
        if(nTime < 0)
295
779
        {
296
779
            aRet.NanoSeconds  = nanoSecInSec-1;
297
779
            aRet.Seconds      = secInMin-1;
298
779
            aRet.Minutes      = minInHour-1;
299
779
            aRet.Hours        = 23;
300
779
        }
301
24.7k
        return aRet;
302
24.7k
    }
303
304
    css::util::DateTime DBTypeConversion::toDateTime(const double dVal, const css::util::Date& _rNullDate)
305
25.1k
    {
306
25.1k
        css::util::DateTime aRet;
307
308
25.1k
        if (!std::isfinite(dVal))
309
359
        {
310
359
            SAL_WARN("connectivity.commontools", "DateTime has invalid value: " << dVal);
311
359
            return aRet;
312
359
        }
313
314
24.7k
        css::util::Date aDate = toDate(dVal, _rNullDate);
315
        // there is not enough precision in a double to have both a date
316
        // and a time up to nanoseconds -> limit to microseconds to have
317
        // correct rounding, that is e.g. 13:00:00.000000000 instead of
318
        // 12:59:59.999999790
319
24.7k
        css::util::Time aTime = toTime(dVal, 6);
320
321
24.7k
        aRet.Day          = aDate.Day;
322
24.7k
        aRet.Month        = aDate.Month;
323
24.7k
        aRet.Year         = aDate.Year;
324
325
24.7k
        aRet.NanoSeconds  = aTime.NanoSeconds;
326
24.7k
        aRet.Minutes      = aTime.Minutes;
327
24.7k
        aRet.Seconds      = aTime.Seconds;
328
24.7k
        aRet.Hours        = aTime.Hours;
329
330
331
24.7k
        return aRet;
332
25.1k
    }
333
334
    css::util::Date DBTypeConversion::toDate(std::u16string_view _sSQLString)
335
32.2k
    {
336
        // get the token out of a string
337
32.2k
        static const sal_Unicode sDateSep = '-';
338
339
32.2k
        sal_Int32 nIndex    = 0;
340
32.2k
        sal_uInt16  nYear   = 0,
341
32.2k
                    nMonth  = 0,
342
32.2k
                    nDay    = 0;
343
32.2k
        nYear   = static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(_sSQLString, 0,sDateSep,nIndex)));
344
32.2k
        if(nIndex != -1)
345
10.5k
        {
346
10.5k
            nMonth = static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(_sSQLString, 0,sDateSep,nIndex)));
347
10.5k
            if(nIndex != -1)
348
4.10k
                nDay = static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(_sSQLString, 0,sDateSep,nIndex)));
349
10.5k
        }
350
351
32.2k
        return css::util::Date(nDay,nMonth,nYear);
352
32.2k
    }
353
354
355
    css::util::DateTime DBTypeConversion::toDateTime(const OUString& _sSQLString)
356
22.7k
    {
357
        //@see http://java.sun.com/j2se/1.4.2/docs/api/java/sql/Timestamp.html#valueOf(java.lang.String)
358
        //@see http://java.sun.com/j2se/1.4.2/docs/api/java/sql/Date.html#valueOf(java.lang.String)
359
        //@see http://java.sun.com/j2se/1.4.2/docs/api/java/sql/Time.html#valueOf(java.lang.String)
360
361
        // the date part
362
22.7k
        css::util::Date aDate = toDate(_sSQLString);
363
22.7k
        css::util::Time aTime;
364
22.7k
        sal_Int32 nSeparation = _sSQLString.indexOf( ' ' );
365
22.7k
        if ( -1 != nSeparation )
366
12.5k
        {
367
12.5k
            const sal_Unicode *p = _sSQLString.getStr() + nSeparation;
368
12.5k
            const sal_Unicode *const begin = p;
369
42.7k
            while (rtl::isAsciiWhiteSpace(*p)) { ++p; }
370
12.5k
            nSeparation += p - begin;
371
12.5k
            aTime = toTime( _sSQLString.subView( nSeparation ) );
372
12.5k
        }
373
374
22.7k
        return css::util::DateTime(aTime.NanoSeconds, aTime.Seconds, aTime.Minutes, aTime.Hours,
375
22.7k
                        aDate.Day, aDate.Month, aDate.Year, false);
376
22.7k
    }
377
378
379
    css::util::Time DBTypeConversion::toTime(std::u16string_view _sSQLString)
380
12.5k
    {
381
12.5k
        css::util::Time aTime;
382
12.5k
        ::utl::ISO8601parseTime(_sSQLString, aTime);
383
12.5k
        return aTime;
384
12.5k
    }
385
386
387
}   // namespace dbtools
388
389
390
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */