bump product version to 4.1.6.2
[LibreOffice.git] / unotools / source / misc / datetime.cxx
blob5710350e3b5a1a4acb8de584a621568006179ccc
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <unotools/datetime.hxx>
21 #include <tools/date.hxx>
22 #include <tools/time.hxx>
23 #include <tools/datetime.hxx>
24 #include <stdexcept>
25 #include <rtl/ustrbuf.hxx>
26 #include <rtl/math.hxx>
29 namespace
31 sal_Int32 impl_pow(sal_Int32 x, sal_Int32 y)
33 if (y == 1)
34 return x;
35 if ( y % 2 == 0)
37 return impl_pow(x*x, y/2);
39 else
41 return x * impl_pow(x*x, y/2);
44 // computes x^y
45 sal_Int32 pow(sal_Int32 x, sal_Int32 y)
47 if (y < 0)
48 throw std::invalid_argument("negative power is not defined in integers");
49 if (y == 0)
50 return 1;
51 return impl_pow(x, y);
54 /** convert string to number with optional min and max values */
55 template <typename T>
56 bool convertNumber( T& rValue,
57 const OUString& rString,
58 T /*nMin*/ = -1, T /*nMax*/ = -1)
60 sal_Bool bNeg = sal_False;
61 rValue = 0;
63 sal_Int32 nPos = 0L;
64 sal_Int32 nLen = rString.getLength();
66 // skip white space
67 while( nPos < nLen && sal_Unicode(' ') == rString[nPos] )
68 nPos++;
70 if( nPos < nLen && sal_Unicode('-') == rString[nPos] )
72 bNeg = sal_True;
73 nPos++;
76 // get number
77 while( nPos < nLen &&
78 sal_Unicode('0') <= rString[nPos] &&
79 sal_Unicode('9') >= rString[nPos] )
81 // TODO: check overflow!
82 rValue *= 10;
83 rValue += (rString[nPos] - sal_Unicode('0'));
84 nPos++;
87 if( bNeg )
88 rValue *= -1;
90 return nPos == nLen;
93 // although the standard calls for fixed-length (zero-padded) tokens
94 // (in their integer part), we are here liberal and allow shorter tokens
95 // (when there are separators, else it is ambiguous).
96 // Note that:
97 // the token separator is OPTIONAL
98 // empty string is a valid token! (to recognise hh or hhmm or hh:mm formats)
99 // returns: success / failure
100 // in case of failure, no reference argument is changed
101 // arguments:
102 // i_str: string to extract token from
103 // index: index in i_str where to start tokenizing
104 // after return, start of *next* token (if any)
105 // if this was the last token, then the value is UNDEFINED
106 // o_strInt: output; integer part of token
107 // o_bFraction: output; was there a fractional part?
108 // o_strFrac: output; fractional part of token
109 bool impl_getISO8601TimeToken(const OUString &i_str, sal_Int32 &nPos, OUString &resInt, bool &bFraction, OUString &resFrac)
111 bFraction = false;
112 // all tokens are of length 2
113 const sal_Int32 nEndPos = nPos + 2;
114 const sal_Unicode c0 = '0';
115 const sal_Unicode c9 = '9';
116 const sal_Unicode sep = ':';
117 for (;nPos < nEndPos && nPos < i_str.getLength(); ++nPos)
119 const sal_Unicode c = i_str[nPos];
120 if (c == sep)
121 return true;
122 if (c < c0 || c > c9)
123 return false;
124 resInt += OUString(c);
126 if (nPos == i_str.getLength() || i_str[nPos] == sep)
127 return true;
128 if (i_str[nPos] == ',' || i_str[nPos] == '.')
130 bFraction = true;
131 ++nPos;
132 for (; nPos < i_str.getLength(); ++nPos)
134 const sal_Unicode c = i_str[nPos];
135 if (c == sep)
136 // fractional part allowed only in *last* token
137 return false;
138 if (c < c0 || c > c9)
139 return false;
140 resFrac += OUString(c);
142 OSL_ENSURE(nPos == i_str.getLength(), "impl_getISO8601TimeToken internal error; expected to be at end of string");
143 return true;
145 else
146 return false;
148 inline bool getISO8601TimeToken(const OUString &i_str, sal_Int32 &io_index, OUString &o_strInt, bool &o_bFraction, OUString &o_strFrac)
150 OUString resInt;
151 OUString resFrac;
152 bool bFraction = false;
153 sal_Int32 index = io_index;
154 if(!impl_getISO8601TimeToken(i_str, index, resInt, bFraction, resFrac))
155 return false;
156 else
158 io_index = index+1;
159 o_strInt = resInt;
160 o_strFrac = resFrac;
161 o_bFraction = bFraction;
162 return true;
167 //.........................................................................
168 namespace utl
170 //------------------------------------------------------------------
171 void typeConvert(const Date& _rDate, starutil::Date& _rOut)
173 _rOut.Day = _rDate.GetDay();
174 _rOut.Month = _rDate.GetMonth();
175 _rOut.Year = _rDate.GetYear();
178 //------------------------------------------------------------------
179 void typeConvert(const starutil::Date& _rDate, Date& _rOut)
181 _rOut = Date(_rDate.Day, _rDate.Month, _rDate.Year);
184 //------------------------------------------------------------------
185 void typeConvert(const DateTime& _rDateTime, starutil::DateTime& _rOut)
187 _rOut.Year = _rDateTime.GetYear();
188 _rOut.Month = _rDateTime.GetMonth();
189 _rOut.Day = _rDateTime.GetDay();
190 _rOut.Hours = _rDateTime.GetHour();
191 _rOut.Minutes = _rDateTime.GetMin();
192 _rOut.Seconds = _rDateTime.GetSec();
193 _rOut.NanoSeconds = _rDateTime.GetNanoSec();
196 //------------------------------------------------------------------
197 void typeConvert(const starutil::DateTime& _rDateTime, DateTime& _rOut)
199 Date aDate(_rDateTime.Day, _rDateTime.Month, _rDateTime.Year);
200 Time aTime(_rDateTime.Hours, _rDateTime.Minutes, _rDateTime.Seconds, _rDateTime.NanoSeconds);
201 _rOut = DateTime(aDate, aTime);
204 // FIXME: these operators should be.... in toplevel namespace? announced in the .hxx file?
205 //-------------------------------------------------------------------------
206 bool operator ==(const starutil::DateTime& _rLeft, const starutil::DateTime& _rRight)
208 return ( _rLeft.NanoSeconds == _rRight.NanoSeconds) &&
209 ( _rLeft.Seconds == _rRight.Seconds) &&
210 ( _rLeft.Minutes == _rRight.Minutes) &&
211 ( _rLeft.Hours == _rRight.Hours) &&
212 ( _rLeft.Day == _rRight.Day) &&
213 ( _rLeft.Month == _rRight.Month) &&
214 ( _rLeft.Year == _rRight.Year) ;
217 //-------------------------------------------------------------------------
218 bool operator ==(const starutil::Date& _rLeft, const starutil::Date& _rRight)
220 return ( _rLeft.Day == _rRight.Day) &&
221 ( _rLeft.Month == _rRight.Month) &&
222 ( _rLeft.Year == _rRight.Year) ;
225 //-------------------------------------------------------------------------
226 bool operator ==(const starutil::Time& _rLeft, const starutil::Time& _rRight)
228 return ( _rLeft.NanoSeconds == _rRight.NanoSeconds) &&
229 ( _rLeft.Seconds == _rRight.Seconds) &&
230 ( _rLeft.Minutes == _rRight.Minutes) &&
231 ( _rLeft.Hours == _rRight.Hours) ;
234 OUString toISO8601(const starutil::DateTime& rDateTime)
236 OUStringBuffer rBuffer;
237 rBuffer.append((sal_Int32) rDateTime.Year);
238 rBuffer.append('-');
239 if( rDateTime.Month < 10 )
240 rBuffer.append('0');
241 rBuffer.append((sal_Int32) rDateTime.Month);
242 rBuffer.append('-');
243 if( rDateTime.Day < 10 )
244 rBuffer.append('0');
245 rBuffer.append((sal_Int32) rDateTime.Day);
247 if( rDateTime.NanoSeconds != 0 ||
248 rDateTime.Seconds != 0 ||
249 rDateTime.Minutes != 0 ||
250 rDateTime.Hours != 0 )
252 rBuffer.append('T');
253 if( rDateTime.Hours < 10 )
254 rBuffer.append('0');
255 rBuffer.append((sal_Int32) rDateTime.Hours);
256 rBuffer.append(':');
257 if( rDateTime.Minutes < 10 )
258 rBuffer.append('0');
259 rBuffer.append((sal_Int32) rDateTime.Minutes);
260 rBuffer.append(':');
261 if( rDateTime.Seconds < 10 )
262 rBuffer.append('0');
263 rBuffer.append((sal_Int32) rDateTime.Seconds);
264 if ( rDateTime.NanoSeconds > 0)
266 OSL_ENSURE(rDateTime.NanoSeconds < 1000000000,"NanoSeconds cannot be more than 999 999 999");
267 rBuffer.append(',');
268 std::ostringstream ostr;
269 ostr.fill('0');
270 ostr.width(9);
271 ostr << rDateTime.NanoSeconds;
272 rBuffer.append(OUString::createFromAscii(ostr.str().c_str()));
275 return rBuffer.makeStringAndClear();
278 OUString toISO8601(const starutil::Time& rTime)
280 OUStringBuffer rBuffer;
281 if( rTime.Hours < 10 )
282 rBuffer.append('0');
283 rBuffer.append((sal_Int32) rTime.Hours);
284 rBuffer.append(':');
285 if( rTime.Minutes < 10 )
286 rBuffer.append('0');
287 rBuffer.append((sal_Int32) rTime.Minutes);
288 rBuffer.append(':');
289 if( rTime.Seconds < 10 )
290 rBuffer.append('0');
291 rBuffer.append((sal_Int32) rTime.Seconds);
292 if ( rTime.NanoSeconds > 0)
294 OSL_ENSURE(rTime.NanoSeconds < 1000000000,"NanoSeconds cannot be more than 999 999 999");
295 rBuffer.append(',');
296 std::ostringstream ostr;
297 ostr.fill('0');
298 ostr.width(9);
299 ostr << rTime.NanoSeconds;
300 rBuffer.append(OUString::createFromAscii(ostr.str().c_str()));
302 return rBuffer.makeStringAndClear();
305 /** convert ISO8601 DateTime String to util::DateTime */
306 bool ISO8601parseDateTime(const OUString &rString, starutil::DateTime& rDateTime)
308 bool bSuccess = true;
310 rtl::OUString aDateStr, aTimeStr;
311 starutil::Date aDate;
312 starutil::Time aTime;
313 sal_Int32 nPos = rString.indexOf( (sal_Unicode) 'T' );
314 if ( nPos >= 0 )
316 aDateStr = rString.copy( 0, nPos );
317 aTimeStr = rString.copy( nPos + 1 );
319 else
320 aDateStr = rString; // no separator: only date part
322 bSuccess = ISO8601parseDate(aDateStr, aDate);
324 if ( bSuccess && !aTimeStr.isEmpty() ) // time is optional
326 bSuccess = ISO8601parseTime(aTimeStr, aTime);
329 if (bSuccess)
331 rDateTime = starutil::DateTime(aTime.NanoSeconds, aTime.Seconds, aTime.Minutes, aTime.Hours,
332 aDate.Day, aDate.Month, aDate.Year, false);
335 return bSuccess;
338 /** convert ISO8601 Date String to util::Date */
339 // TODO: supports only calendar dates YYYY-MM-DD
340 // MISSING: calendar dates YYYYMMDD YYYY-MM
341 // year, week date, ordinal date
342 bool ISO8601parseDate(const OUString &aDateStr, starutil::Date& rDate)
344 bool bSuccess = true;
346 sal_Int32 nYear = 1899;
347 sal_Int32 nMonth = 12;
348 sal_Int32 nDay = 30;
350 const sal_Unicode* pStr = aDateStr.getStr();
351 sal_Int32 nDateTokens = 1;
352 while ( *pStr )
354 if ( *pStr == '-' )
355 nDateTokens++;
356 pStr++;
358 if ( nDateTokens > 3 || aDateStr.isEmpty() )
359 bSuccess = false;
360 else
362 sal_Int32 n = 0;
363 if ( !convertNumber<sal_Int32>( nYear, aDateStr.getToken( 0, '-', n ), 0, 9999 ) )
364 bSuccess = false;
365 if ( nDateTokens >= 2 )
366 if ( !convertNumber<sal_Int32>( nMonth, aDateStr.getToken( 0, '-', n ), 0, 12 ) )
367 bSuccess = false;
368 if ( nDateTokens >= 3 )
369 if ( !convertNumber<sal_Int32>( nDay, aDateStr.getToken( 0, '-', n ), 0, 31 ) )
370 bSuccess = false;
373 if (bSuccess)
375 rDate.Year = (sal_uInt16)nYear;
376 rDate.Month = (sal_uInt16)nMonth;
377 rDate.Day = (sal_uInt16)nDay;
380 return bSuccess;
383 /** convert ISO8601 Time String to util::Time */
384 bool ISO8601parseTime(const OUString &aTimeStr, starutil::Time& rTime)
386 bool bSuccess = true;
388 sal_Int32 nHour = 0;
389 sal_Int32 nMin = 0;
390 sal_Int32 nSec = 0;
391 sal_Int32 nNanoSec = 0;
393 sal_Int32 n = 0;
394 OUString tokInt;
395 OUString tokFrac;
396 bool bFrac = false;
397 // hours
398 if (bSuccess && (bSuccess = getISO8601TimeToken(aTimeStr, n, tokInt, bFrac, tokFrac)))
400 if ( bFrac && n < aTimeStr.getLength())
401 // junk after ISO time
402 bSuccess = false;
403 else if ( (bSuccess = convertNumber<sal_Int32>( nHour, tokInt, 0, 23 )) )
405 if (bFrac)
407 sal_Int64 fracNumerator;
408 if ( (bSuccess = convertNumber(fracNumerator, tokFrac)) )
410 double frac = static_cast<double>(fracNumerator) / static_cast<double>(pow(10, tokFrac.getLength()));
411 // minutes
412 OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac hours (of hours) not between 0 and 1");
413 frac *= 60;
414 nMin = floor(frac);
415 frac -= nMin;
416 // seconds
417 OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac minutes (of hours) not between 0 and 1");
418 frac *= 60;
419 nSec = floor(frac);
420 frac -= nSec;
421 // nanoseconds
422 OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac seconds (of hours) not between 0 and 1");
423 frac *= 1000000000;
424 nNanoSec = ::rtl::math::round(frac);
426 goto end;
428 if(n >= aTimeStr.getLength())
429 goto end;
433 // minutes
434 if (bSuccess && (bSuccess = getISO8601TimeToken(aTimeStr, n, tokInt, bFrac, tokFrac)))
436 if ( bFrac && n < aTimeStr.getLength())
437 // junk after ISO time
438 bSuccess = false;
439 else if ( (bSuccess = convertNumber<sal_Int32>( nMin, tokInt, 0, 59 )) )
441 if (bFrac)
443 sal_Int64 fracNumerator;
444 if ( (bSuccess = convertNumber(fracNumerator, tokFrac)) )
446 double frac = static_cast<double>(fracNumerator) / static_cast<double>(pow(10, tokFrac.getLength()));
447 // seconds
448 OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac minutes (of minutes) not between 0 and 1");
449 frac *= 60;
450 nSec = floor(frac);
451 frac -= nSec;
452 // nanoseconds
453 OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac seconds (of minutes) not between 0 and 1");
454 frac *= 1000000000;
455 nNanoSec = ::rtl::math::round(frac);
457 goto end;
459 if(n >= aTimeStr.getLength())
460 goto end;
463 // seconds
464 if (bSuccess && (bSuccess = getISO8601TimeToken(aTimeStr, n, tokInt, bFrac, tokFrac)))
466 if (n < aTimeStr.getLength())
467 // junk after ISO time
468 bSuccess = false;
469 // max 60 for leap seconds
470 else if ( (bSuccess = convertNumber<sal_Int32>( nSec, tokInt, 0, 60 )) )
472 if (bFrac)
474 sal_Int64 fracNumerator;
475 if ( (bSuccess = convertNumber(fracNumerator, tokFrac)) )
477 double frac = static_cast<double>(fracNumerator) / static_cast<double>(pow(10, tokFrac.getLength()));
478 // nanoseconds
479 OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac seconds (of seconds) not between 0 and 1");
480 frac *= 1000000000;
481 nNanoSec = ::rtl::math::round(frac);
483 goto end;
488 end:
489 if (bSuccess)
491 // normalise time
492 const int secondsOverFlow = (nSec == 60) ? 61 : 60;
493 if (nNanoSec == 1000000000)
495 nNanoSec = 0;
496 ++nSec;
498 if(nSec == secondsOverFlow)
500 nSec = 0;
501 ++nMin;
503 if(nMin == 60)
505 nMin = 0;
506 ++nHour;
509 rTime.Hours = (sal_uInt16)nHour;
510 rTime.Minutes = (sal_uInt16)nMin;
511 rTime.Seconds = (sal_uInt16)nSec;
512 rTime.NanoSeconds = nNanoSec;
515 return bSuccess;
517 //.........................................................................
518 } // namespace utl
519 //.........................................................................
521 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */