1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <unotools/localedatawrapper.hxx>
22 #include <unotools/syslocale.hxx>
23 #include <tools/date.hxx>
24 #include <tools/time.hxx>
25 #include <tools/datetime.hxx>
26 #include <rtl/ustrbuf.hxx>
27 #include <rtl/math.hxx>
28 #include <osl/diagnose.h>
29 #include <comphelper/string.hxx>
30 #include <o3tl/string_view.hxx>
36 bool checkAllNumber(std::u16string_view rString
)
39 sal_Int32 nLen
= rString
.size();
42 while( nPos
< nLen
&& ' ' == rString
[nPos
] )
45 if( nPos
< nLen
&& '-' == rString
[nPos
] )
50 '0' <= rString
[nPos
] &&
51 '9' >= rString
[nPos
] )
59 /** convert string to number with optional min and max values */
60 bool convertNumber32(sal_Int32
& rValue
,
61 std::u16string_view rString
,
62 sal_Int32
/*nMin*/ = -1, sal_Int32
/*nMax*/ = -1)
64 if (!checkAllNumber(rString
))
70 rValue
= o3tl::toInt32(rString
);
74 bool convertNumber64(sal_Int64
& rValue
,
75 std::u16string_view rString
,
76 sal_Int64
/*nMin*/ = -1, sal_Int64
/*nMax*/ = -1)
78 if (!checkAllNumber(rString
))
84 rValue
= o3tl::toInt64(rString
);
88 // although the standard calls for fixed-length (zero-padded) tokens
89 // (in their integer part), we are here liberal and allow shorter tokens
90 // (when there are separators, else it is ambiguous).
92 // the token separator is OPTIONAL
93 // empty string is a valid token! (to recognise hh or hhmm or hh:mm formats)
94 // returns: success / failure
95 // in case of failure, no reference argument is changed
97 // i_str: string to extract token from
98 // index: index in i_str where to start tokenizing
99 // after return, start of *next* token (if any)
100 // if this was the last token, then the value is UNDEFINED
101 // o_strInt: output; integer part of token
102 // o_bFraction: output; was there a fractional part?
103 // o_strFrac: output; fractional part of token
104 bool impl_getISO8601TimeToken(std::u16string_view i_str
, std::size_t &nPos
, OUString
&resInt
, bool &bFraction
, OUString
&resFrac
)
107 // all tokens are of length 2
108 const std::size_t nEndPos
= nPos
+ 2;
109 const sal_Unicode c0
= '0';
110 const sal_Unicode c9
= '9';
111 const sal_Unicode sep
= ':';
112 for (;nPos
< nEndPos
&& nPos
< i_str
.size(); ++nPos
)
114 const sal_Unicode c
= i_str
[nPos
];
117 if (c
< c0
|| c
> c9
)
119 resInt
+= OUStringChar(c
);
123 if (nPos
== i_str
.size() || i_str
[nPos
] == sep
)
125 if (i_str
[nPos
] == ',' || i_str
[nPos
] == '.')
129 for (; nPos
< i_str
.size(); ++nPos
)
131 const sal_Unicode c
= i_str
[nPos
];
132 if (c
== 'Z' || c
== '+' || c
== '-')
134 --nPos
; // we don't want to skip the tz separator
138 // fractional part allowed only in *last* token
140 if (c
< c0
|| c
> c9
)
142 resFrac
+= OUStringChar(c
);
144 OSL_ENSURE(nPos
== i_str
.size(), "impl_getISO8601TimeToken internal error; expected to be at end of string");
147 if (i_str
[nPos
] == 'Z' || i_str
[nPos
] == '+' || i_str
[nPos
] == '-')
149 --nPos
; // we don't want to skip the tz separator
155 bool getISO8601TimeToken(std::u16string_view i_str
, std::size_t &io_index
, OUString
&o_strInt
, bool &o_bFraction
, OUString
&o_strFrac
)
159 bool bFraction
= false;
160 std::size_t index
= io_index
;
161 if(!impl_getISO8601TimeToken(i_str
, index
, resInt
, bFraction
, resFrac
))
168 o_bFraction
= bFraction
;
172 bool getISO8601TimeZoneToken(std::u16string_view i_str
, std::size_t &io_index
, OUString
&o_strInt
)
174 const sal_Unicode c0
= '0';
175 const sal_Unicode c9
= '9';
176 const sal_Unicode sep
= ':';
177 if (i_str
[io_index
] == 'Z') // UTC timezone indicator
183 else if (i_str
[io_index
] == '+' || i_str
[io_index
] == '-') // other timezones indicator
187 for (; io_index
< i_str
.size(); ++io_index
)
189 const sal_Unicode c
= i_str
[io_index
];
190 if ((c
< c0
|| c
> c9
) && c
!= sep
)
192 o_strInt
+= OUStringChar(c
);
203 const LocaleDataWrapper
& GetLocaleData()
205 static SvtSysLocale ourSysLocale
;
206 return ourSysLocale
.GetLocaleData();
209 DateTime
GetDateTime(const css::util::DateTime
& _rDT
) { return DateTime(_rDT
); }
211 OUString
GetDateTimeString(const css::util::DateTime
& _rDT
)
213 // String with date and time information (#i20172#)
214 DateTime
aDT(GetDateTime(_rDT
));
215 const LocaleDataWrapper
& rLoDa
= GetLocaleData();
217 return rLoDa
.getDate(aDT
) + " " + rLoDa
.getTime(aDT
);
220 OUString
GetDateTimeString(sal_Int32 _nDate
, sal_Int32 _nTime
)
222 const LocaleDataWrapper
& rLoDa
= GetLocaleData();
225 tools::Time
aTime(_nTime
* tools::Time::nanoPerCenti
);
226 return rLoDa
.getDate(aDate
) + ", " + rLoDa
.getTime(aTime
);
229 OUString
GetDateString(const css::util::DateTime
& _rDT
)
231 return GetLocaleData().getDate(GetDateTime(_rDT
));
234 void typeConvert(const Date
& _rDate
, css::util::Date
& _rOut
)
236 _rOut
.Day
= _rDate
.GetDay();
237 _rOut
.Month
= _rDate
.GetMonth();
238 _rOut
.Year
= _rDate
.GetYear();
241 void typeConvert(const css::util::Date
& _rDate
, Date
& _rOut
)
243 _rOut
= Date(_rDate
.Day
, _rDate
.Month
, _rDate
.Year
);
246 void typeConvert(const DateTime
& _rDateTime
, css::util::DateTime
& _rOut
)
248 _rOut
.Year
= _rDateTime
.GetYear();
249 _rOut
.Month
= _rDateTime
.GetMonth();
250 _rOut
.Day
= _rDateTime
.GetDay();
251 _rOut
.Hours
= _rDateTime
.GetHour();
252 _rOut
.Minutes
= _rDateTime
.GetMin();
253 _rOut
.Seconds
= _rDateTime
.GetSec();
254 _rOut
.NanoSeconds
= _rDateTime
.GetNanoSec();
257 void typeConvert(const css::util::DateTime
& _rDateTime
, DateTime
& _rOut
)
259 Date
aDate(_rDateTime
.Day
, _rDateTime
.Month
, _rDateTime
.Year
);
260 tools::Time
aTime(_rDateTime
.Hours
, _rDateTime
.Minutes
, _rDateTime
.Seconds
, _rDateTime
.NanoSeconds
);
261 _rOut
= DateTime(aDate
, aTime
);
264 OUString
toISO8601(const css::util::DateTime
& rDateTime
)
266 OUStringBuffer
rBuffer(32);
267 rBuffer
.append(OUString::number(static_cast<sal_Int32
>(rDateTime
.Year
)) + "-");
268 if( rDateTime
.Month
< 10 )
270 rBuffer
.append(OUString::number(static_cast<sal_Int32
>(rDateTime
.Month
)) + "-");
271 if( rDateTime
.Day
< 10 )
273 rBuffer
.append(static_cast<sal_Int32
>(rDateTime
.Day
));
275 if( rDateTime
.NanoSeconds
!= 0 ||
276 rDateTime
.Seconds
!= 0 ||
277 rDateTime
.Minutes
!= 0 ||
278 rDateTime
.Hours
!= 0 )
281 if( rDateTime
.Hours
< 10 )
283 rBuffer
.append(OUString::number(static_cast<sal_Int32
>(rDateTime
.Hours
)) + ":");
284 if( rDateTime
.Minutes
< 10 )
286 rBuffer
.append(OUString::number(static_cast<sal_Int32
>(rDateTime
.Minutes
)) + ":");
287 if( rDateTime
.Seconds
< 10 )
289 rBuffer
.append(static_cast<sal_Int32
>(rDateTime
.Seconds
));
290 if ( rDateTime
.NanoSeconds
> 0)
292 OSL_ENSURE(rDateTime
.NanoSeconds
< 1000000000,"NanoSeconds cannot be more than 999 999 999");
294 std::ostringstream ostr
;
297 ostr
<< rDateTime
.NanoSeconds
;
298 rBuffer
.appendAscii(ostr
.str().c_str());
301 return rBuffer
.makeStringAndClear();
304 /** convert ISO8601 DateTime String to util::DateTime */
305 bool ISO8601parseDateTime(std::u16string_view rString
, css::util::DateTime
& rDateTime
)
307 bool bSuccess
= true;
309 std::u16string_view aDateStr
, aTimeStr
;
310 css::util::Date aDate
;
311 css::util::Time aTime
;
312 size_t nPos
= rString
.find( 'T' );
313 if ( nPos
!= std::u16string_view::npos
)
315 aDateStr
= rString
.substr( 0, nPos
);
316 aTimeStr
= rString
.substr( nPos
+ 1 );
319 aDateStr
= rString
; // no separator: only date part
321 bSuccess
= ISO8601parseDate(aDateStr
, aDate
);
323 if ( bSuccess
&& !aTimeStr
.empty() ) // time is optional
325 bSuccess
= ISO8601parseTime(aTimeStr
, aTime
);
330 rDateTime
= css::util::DateTime(aTime
.NanoSeconds
, aTime
.Seconds
, aTime
.Minutes
, aTime
.Hours
,
331 aDate
.Day
, aDate
.Month
, aDate
.Year
, false);
337 /** convert ISO8601 Date String to util::Date */
338 // TODO: supports only calendar dates YYYY-MM-DD
339 // MISSING: calendar dates YYYYMMDD YYYY-MM
340 // year, week date, ordinal date
341 bool ISO8601parseDate(std::u16string_view aDateStr
, css::util::Date
& rDate
)
343 const sal_Int32 nDateTokens
{comphelper::string::getTokenCount(aDateStr
, '-')};
345 if (nDateTokens
<1 || nDateTokens
>3)
348 sal_Int32 nYear
= 1899;
349 sal_Int32 nMonth
= 12;
353 auto strCurrentToken
= o3tl::getToken(aDateStr
, 0, '-', nIdx
);
354 if ( !convertNumber32( nYear
, strCurrentToken
, 0, 9999 ) )
356 if ( nDateTokens
>= 2 )
358 strCurrentToken
= o3tl::getToken(aDateStr
, 0, '-', nIdx
);
359 if (strCurrentToken
.size() > 2)
361 if ( !convertNumber32( nMonth
, strCurrentToken
, 0, 12 ) )
364 if ( nDateTokens
>= 3 )
366 strCurrentToken
= o3tl::getToken(aDateStr
, 0, '-', nIdx
);
367 if (strCurrentToken
.size() > 2)
369 if ( !convertNumber32( nDay
, strCurrentToken
, 0, 31 ) )
373 rDate
.Year
= static_cast<sal_uInt16
>(nYear
);
374 rDate
.Month
= static_cast<sal_uInt16
>(nMonth
);
375 rDate
.Day
= static_cast<sal_uInt16
>(nDay
);
380 /** convert ISO8601 Time String to util::Time */
381 bool ISO8601parseTime(std::u16string_view aTimeStr
, css::util::Time
& rTime
)
386 sal_Int32 nNanoSec
= 0;
394 bool bSuccess
= getISO8601TimeToken(aTimeStr
, n
, tokInt
, bFrac
, tokFrac
);
398 if ( bFrac
&& n
< aTimeStr
.size())
400 // is it junk or the timezone?
401 bSuccess
= getISO8601TimeZoneToken(aTimeStr
, n
, tokTz
);
405 bSuccess
= convertNumber32( nHour
, tokInt
, 0, 23 );
411 sal_Int64 fracNumerator
;
412 bSuccess
= convertNumber64(fracNumerator
, tokFrac
);
415 double frac
= static_cast<double>(fracNumerator
) / pow(static_cast<double>(10), static_cast<double>(tokFrac
.getLength()));
417 OSL_ENSURE(frac
< 1 && frac
>= 0, "ISO8601parse internal error frac hours (of hours) not between 0 and 1");
422 OSL_ENSURE(frac
< 1 && frac
>= 0, "ISO8601parse internal error frac minutes (of hours) not between 0 and 1");
427 OSL_ENSURE(frac
< 1 && frac
>= 0, "ISO8601parse internal error frac seconds (of hours) not between 0 and 1");
429 nNanoSec
= ::rtl::math::round(frac
);
433 if(n
>= aTimeStr
.size())
437 bSuccess
= getISO8601TimeToken(aTimeStr
, n
, tokInt
, bFrac
, tokFrac
);
440 if ( bFrac
&& n
< aTimeStr
.size())
442 // is it junk or the timezone?
443 bSuccess
= getISO8601TimeZoneToken(aTimeStr
, n
, tokTz
);
447 bSuccess
= convertNumber32( nMin
, tokInt
, 0, 59 );
452 sal_Int64 fracNumerator
;
453 bSuccess
= convertNumber64(fracNumerator
, tokFrac
);
456 double frac
= static_cast<double>(fracNumerator
) / pow(static_cast<double>(10), static_cast<double>(tokFrac
.getLength()));
458 OSL_ENSURE(frac
< 1 && frac
>= 0, "ISO8601parse internal error frac minutes (of minutes) not between 0 and 1");
463 OSL_ENSURE(frac
< 1 && frac
>= 0, "ISO8601parse internal error frac seconds (of minutes) not between 0 and 1");
465 nNanoSec
= ::rtl::math::round(frac
);
469 if(n
>= aTimeStr
.size())
473 bSuccess
= getISO8601TimeToken(aTimeStr
, n
, tokInt
, bFrac
, tokFrac
);
476 if (n
< aTimeStr
.size())
478 // is it junk or the timezone?
479 bSuccess
= getISO8601TimeZoneToken(aTimeStr
, n
, tokTz
);
483 // max 60 for leap seconds
484 bSuccess
= convertNumber32( nSec
, tokInt
, 0, 60 );
489 sal_Int64 fracNumerator
;
490 bSuccess
= convertNumber64(fracNumerator
, tokFrac
);
493 double frac
= static_cast<double>(fracNumerator
) / pow(static_cast<double>(10), static_cast<double>(tokFrac
.getLength()));
495 OSL_ENSURE(frac
< 1 && frac
>= 0, "ISO8601parse internal error frac seconds (of seconds) not between 0 and 1");
497 nNanoSec
= ::rtl::math::round(frac
);
506 const int secondsOverflow
= (nSec
== 60) ? 61 : 60;
507 if (nNanoSec
== 1000000000)
512 if(nSec
== secondsOverflow
)
523 rTime
.IsUTC
= (tokTz
== "Z");
525 rTime
.Hours
= static_cast<sal_uInt16
>(nHour
);
526 rTime
.Minutes
= static_cast<sal_uInt16
>(nMin
);
527 rTime
.Seconds
= static_cast<sal_uInt16
>(nSec
);
528 rTime
.NanoSeconds
= nNanoSec
;
536 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */