summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorEike Rathke <erack@redhat.com>2016-07-08 17:08:47 +0200
committerEike Rathke <erack@redhat.com>2016-07-08 20:41:02 +0000
commit6d4f2dcc7cbba771e9d9b00de50368db4a88ef1b (patch)
tree0301896941b955ffa79ef3e96b874ebdad78662c /tools
parent06287b9c348281612854d67c4eb2e7a38dc722ca (diff)
Resolves: tdf#100452 class Date full (BCE,CE) proleptic Gregorian calendar
... implementing signed years with year 0 gap. Date(31,12,-1) last day BCE Date(1,1,1) first day CE New class Date member functions: * AddYears(sal_Int16) to be used instead of aDate.SetYear(aDate.GetYear()+sal_Int16) to handle year 0 gap. * convenience GetNextYear() to be used insted of GetYear()+1 * convenience GetPrevYear() to be used insted of GetYear()-1 * AddMonths(sal_Int32) * operator=(const css::util::Date&) New class DateTime member functions: * operator=(const css::util::DateTime&) Made some conversion ctors explicit, specifically Date(sal_Int32) Adapted hopefully all places that used a sal_uInt16 year to use sal_Int16 where appropriate. Eliminated some quirks in date handling found on the fly. Added era handling to i18npool icu calendar setting interface, which missing was responsible for 0001-01-01 entered in Calc being set as -0001-01-01, hence subtracting one day resulted in -0002-12-31. Change-Id: I77b39fba9599ebd5067d7864f6c9ebe01f6f578f Reviewed-on: https://gerrit.libreoffice.org/27049 Reviewed-by: Eike Rathke <erack@redhat.com> Tested-by: Jenkins <ci@libreoffice.org>
Diffstat (limited to 'tools')
-rw-r--r--tools/CppunitTest_tools_test.mk1
-rw-r--r--tools/qa/cppunit/test_date.cxx76
-rw-r--r--tools/source/datetime/datetime.cxx7
-rw-r--r--tools/source/datetime/tdate.cxx261
4 files changed, 280 insertions, 65 deletions
diff --git a/tools/CppunitTest_tools_test.mk b/tools/CppunitTest_tools_test.mk
index e90b52457244..aced4bf6698a 100644
--- a/tools/CppunitTest_tools_test.mk
+++ b/tools/CppunitTest_tools_test.mk
@@ -15,6 +15,7 @@ $(eval $(call gb_CppunitTest_use_external,tools_test,boost_headers))
$(eval $(call gb_CppunitTest_add_exception_objects,tools_test, \
tools/qa/cppunit/test_bigint \
+ tools/qa/cppunit/test_date \
tools/qa/cppunit/test_fract \
tools/qa/cppunit/test_inetmime \
tools/qa/cppunit/test_pathutils \
diff --git a/tools/qa/cppunit/test_date.cxx b/tools/qa/cppunit/test_date.cxx
new file mode 100644
index 000000000000..0ee30089b7b5
--- /dev/null
+++ b/tools/qa/cppunit/test_date.cxx
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <tools/date.hxx>
+
+namespace tools
+{
+
+class DateTest : public CppUnit::TestFixture
+{
+public:
+ void testDate();
+
+ CPPUNIT_TEST_SUITE(DateTest);
+ CPPUNIT_TEST(testDate);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void DateTest::testDate()
+{
+ const Date aCE(1,1,1); // first day CE
+ const Date aBCE(31,12,-1); // last day BCE
+ const Date aMin(1,1,-32768); // minimum date
+ const Date aMax(31,12,32767); // maximum date
+ Date aDate(Date::EMPTY);
+ const long kMinDays = -11968265;
+ const long kMaxDays = 11967900;
+
+ // Last day BCE to first day CE is 1 day difference.
+ CPPUNIT_ASSERT_EQUAL( static_cast<long>(1), aCE - aBCE);
+ CPPUNIT_ASSERT_EQUAL( static_cast<long>(-1), aBCE - aCE);
+ aDate = aBCE;
+ CPPUNIT_ASSERT_EQUAL( aCE.GetDate(), (aDate += 1).GetDate());
+ aDate = aCE;
+ CPPUNIT_ASSERT_EQUAL( aBCE.GetDate(), (aDate -= 1).GetDate());
+
+ // The entire BCE and CE ranges cover that many days. Day 0 is -0001-12-31
+ CPPUNIT_ASSERT_EQUAL( kMaxDays, aMax - aBCE);
+ CPPUNIT_ASSERT_EQUAL( kMinDays, aMin - aBCE);
+
+ // Truncate at limits, not under-/overflow or wrap.
+ aDate = aMin;
+ CPPUNIT_ASSERT_EQUAL( aMin.GetDate(), (aDate -= 1).GetDate());
+ aDate = aMax;
+ CPPUNIT_ASSERT_EQUAL( aMax.GetDate(), (aDate += 1).GetDate());
+ aDate = aBCE;
+ CPPUNIT_ASSERT_EQUAL( aMin.GetDate(), (aDate += (kMinDays-10)).GetDate());
+ aDate = aBCE;
+ CPPUNIT_ASSERT_EQUAL( aMax.GetDate(), (aDate += (kMaxDays+10)).GetDate());
+
+ // Year -1 is a leap year.
+ aDate = Date(28,2,-1);
+ CPPUNIT_ASSERT_EQUAL( Date(29,2,-1).GetDate(), (aDate += 1).GetDate());
+ aDate = Date(1,3,-1);
+ CPPUNIT_ASSERT_EQUAL( Date(29,2,-1).GetDate(), (aDate -= 1).GetDate());
+ // Year -5 is a leap year.
+ aDate = Date(28,2,-5);
+ CPPUNIT_ASSERT_EQUAL( Date(29,2,-5).GetDate(), (aDate += 1).GetDate());
+ aDate = Date(1,3,-5);
+ CPPUNIT_ASSERT_EQUAL( Date(29,2,-5).GetDate(), (aDate -= 1).GetDate());
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(DateTest);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/tools/source/datetime/datetime.cxx b/tools/source/datetime/datetime.cxx
index cc0fc13a9ae6..d24f6f818184 100644
--- a/tools/source/datetime/datetime.cxx
+++ b/tools/source/datetime/datetime.cxx
@@ -25,6 +25,13 @@ DateTime::DateTime( const css::util::DateTime& rDateTime )
{
}
+DateTime& DateTime::operator =( const css::util::DateTime& rUDateTime )
+{
+ Date::operator=( Date( rUDateTime.Day, rUDateTime.Month, rUDateTime.Year));
+ Time::operator=( Time( rUDateTime));
+ return *this;
+}
+
bool DateTime::IsBetween( const DateTime& rFrom, const DateTime& rTo ) const
{
if ( (*this >= rFrom) && (*this <= rTo) )
diff --git a/tools/source/datetime/tdate.cxx b/tools/source/datetime/tdate.cxx
index 78b1ec63ac70..e4556be8c1c0 100644
--- a/tools/source/datetime/tdate.cxx
+++ b/tools/source/datetime/tdate.cxx
@@ -29,36 +29,64 @@
static const sal_uInt16 aDaysInMonth[12] = { 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31 };
-// The number of days we internally handle.
+// Once upon a time the number of days we internally handled was limited to
+// MAX_DAYS 3636532. That changed with a full 16-bit year.
// Assuming the first valid positive date in a proleptic Gregorian calendar is
-// 0001-01-01, this results in an end date of 9957-06-26.
+// 0001-01-01, this resulted in an end date of 9957-06-26.
// Hence we documented that years up to and including 9956 are handled.
/* XXX: it is unclear history why this value was chosen, the representable
* 9999-12-31 would be 3652060 days from 0001-01-01. Even 9998-12-31 to
- * distinguish from a maximum possible date would be 3651695. Better keep the
- * value in case something somewhere relies on it or there is another reason
- * beyond..
- * At least connectivity/source/commontools/dbconversion.cxx has the same
- * value to calculate with css::util::Date */
-#define MAX_DAYS 3636532
+ * distinguish from a maximum possible date would be 3651695.
+ * There is connectivity/source/commontools/dbconversion.cxx that still has the
+ * same value to calculate with css::util::Date */
+/* XXX can that dbconversion cope with years > 9999 or negative years at all?
+ * Database fields may be limited to positive 4 digits. */
+
+static const long MIN_DAYS = -11968265; // -32768-01-01
+static const long MAX_DAYS = 11967900; // 32767-12-31
namespace
{
-inline long ImpYearToDays( sal_uInt16 nYear )
+const sal_Int16 kYearMax = SAL_MAX_INT16;
+const sal_Int16 kYearMin = SAL_MIN_INT16;
+
+// Days until start of year from zero, so month and day of month can be added.
+// year 1 => 0 days, year 2 => 365 days, ...
+// year -1 => -366 days, year -2 => -731 days, ...
+inline long ImpYearToDays( sal_Int16 nYear )
{
- const long nYr(static_cast<long>(nYear) - 1);
- return nYr*365 + nYr/4 - nYr/100 + nYr/400;
+ assert( nYear != 0 );
+ long nOffset;
+ long nYr;
+ if (nYear < 0)
+ {
+ nOffset = -366;
+ nYr = nYear + 1;
+ }
+ else
+ {
+ nOffset = 0;
+ nYr = nYear - 1;
+ }
+ return nOffset + nYr*365 + nYr/4 - nYr/100 + nYr/400;
}
-inline bool ImpIsLeapYear( sal_uInt16 nYear )
+inline bool ImpIsLeapYear( sal_Int16 nYear )
{
+ // Leap years BCE are -1, -5, -9, ...
+ // See
+ // https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar#Usage
+ // https://en.wikipedia.org/wiki/0_(year)#History_of_astronomical_usage
+ assert( nYear != 0 );
+ if (nYear < 0)
+ nYear = -nYear - 1;
return ( ( ((nYear % 4) == 0) && ((nYear % 100) != 0) ) ||
( (nYear % 400) == 0 ) );
}
// All callers must have sanitized or normalized month and year values!
-inline sal_uInt16 ImplDaysInMonth( sal_uInt16 nMonth, sal_uInt16 nYear )
+inline sal_uInt16 ImplDaysInMonth( sal_uInt16 nMonth, sal_Int16 nYear )
{
if ( nMonth != 2 )
return aDaysInMonth[nMonth-1];
@@ -73,8 +101,29 @@ inline sal_uInt16 ImplDaysInMonth( sal_uInt16 nMonth, sal_uInt16 nYear )
}
+void Date::setDateFromDMY( sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear )
+{
+ SAL_WARN_IF( nYear == 0, "tools","Date::setDateFromDMY - sure about 0 year? It's not in the calendar.");
+ if (nYear < 0)
+ mnDate =
+ (static_cast<sal_Int32>( nYear ) * 10000) -
+ (static_cast<sal_Int32>( nMonth % 100 ) * 100) -
+ (static_cast<sal_Int32>( nDay % 100 ));
+ else
+ mnDate =
+ (static_cast<sal_Int32>( nYear ) * 10000) +
+ (static_cast<sal_Int32>( nMonth % 100 ) * 100) +
+ (static_cast<sal_Int32>( nDay % 100 ));
+}
+
+void Date::SetDate( sal_Int32 nNewDate )
+{
+ assert( ((nNewDate / 10000) != 0) && "you don't want to set a 0 year, do you?" );
+ mnDate = nNewDate;
+}
+
// static
-sal_uInt16 Date::GetDaysInMonth( sal_uInt16 nMonth, sal_uInt16 nYear )
+sal_uInt16 Date::GetDaysInMonth( sal_uInt16 nMonth, sal_Int16 nYear )
{
SAL_WARN_IF( nMonth < 1 || 12 < nMonth, "tools", "Date::GetDaysInMonth - nMonth out of bounds " << nMonth);
if (nMonth < 1)
@@ -87,7 +136,7 @@ sal_uInt16 Date::GetDaysInMonth( sal_uInt16 nMonth, sal_uInt16 nYear )
long Date::GetAsNormalizedDays() const
{
// This is a very common datum we often calculate from.
- if (nDate == 18991230) // 1899-12-30
+ if (mnDate == 18991230) // 1899-12-30
{
assert(DateToDays( GetDay(), GetMonth(), GetYear() ) == 693594);
return 693594;
@@ -95,7 +144,7 @@ long Date::GetAsNormalizedDays() const
return DateToDays( GetDay(), GetMonth(), GetYear() );
}
-long Date::DateToDays( sal_uInt16 nDay, sal_uInt16 nMonth, sal_uInt16 nYear )
+long Date::DateToDays( sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear )
{
Normalize( nDay, nMonth, nYear);
@@ -108,25 +157,28 @@ long Date::DateToDays( sal_uInt16 nDay, sal_uInt16 nMonth, sal_uInt16 nYear )
static Date lcl_DaysToDate( long nDays )
{
+ if ( nDays <= MIN_DAYS )
+ return Date( 1, 1, kYearMin );
if ( nDays >= MAX_DAYS )
- return Date( 31, 12, 9999 );
-
- if ( nDays <= 0 )
- return Date( 1, 0, 0 );
+ return Date( 31, 12, kYearMax );
- long nTempDays;
- long i = 0;
- bool bCalc;
+ // Day 0 is -0001-12-31, day 1 is 0001-01-01
+ const sal_Int16 nSign = (nDays <= 0 ? -1 : 1);
+ long nTempDays;
+ long i = 0;
+ bool bCalc;
- sal_uInt16 nYear;
+ sal_Int16 nYear;
do
{
- nYear = (sal_uInt16)((nDays / 365) - i);
+ nYear = static_cast<sal_Int16>((nDays / 365) - (i * nSign));
+ if (nYear == 0)
+ nYear = nSign;
nTempDays = nDays - ImpYearToDays(nYear);
bCalc = false;
if ( nTempDays < 1 )
{
- i++;
+ i += nSign;
bCalc = true;
}
else
@@ -135,7 +187,7 @@ static Date lcl_DaysToDate( long nDays )
{
if ( (nTempDays != 366) || !ImpIsLeapYear( nYear ) )
{
- i--;
+ i -= nSign;
bCalc = true;
}
}
@@ -147,7 +199,7 @@ static Date lcl_DaysToDate( long nDays )
while ( nTempDays > static_cast<long>(ImplDaysInMonth( nMonth, nYear )) )
{
nTempDays -= ImplDaysInMonth( nMonth, nYear );
- nMonth++;
+ ++nMonth;
}
return Date( static_cast<sal_uInt16>(nTempDays), nMonth, nYear );
@@ -195,11 +247,73 @@ void Date::SetMonth( sal_uInt16 nNewMonth )
setDateFromDMY( GetDay(), nNewMonth, GetYear() );
}
-void Date::SetYear( sal_uInt16 nNewYear )
+void Date::SetYear( sal_Int16 nNewYear )
{
+ assert( nNewYear != 0 );
setDateFromDMY( GetDay(), GetMonth(), nNewYear );
}
+void Date::AddYears( sal_Int16 nAddYears )
+{
+ sal_Int16 nYear = GetYear();
+ if (nYear < 0)
+ {
+ if (nAddYears < 0)
+ {
+ if (nYear < kYearMin - nAddYears)
+ nYear = kYearMin;
+ else
+ nYear += nAddYears;
+ }
+ else
+ {
+ nYear += nAddYears;
+ if (nYear == 0)
+ nYear = 1;
+ }
+ }
+ else
+ {
+ if (nAddYears > 0)
+ {
+ if (kYearMax - nAddYears < nYear)
+ nYear = kYearMax;
+ else
+ nYear += nAddYears;
+ }
+ else
+ {
+ nYear += nAddYears;
+ if (nYear == 0)
+ nYear = -1;
+ }
+ }
+
+ SetYear( nYear );
+ if (GetMonth() == 2 && GetDay() == 29 && !ImpIsLeapYear( nYear))
+ SetDay(28);
+}
+
+void Date::AddMonths( sal_Int32 nAddMonths )
+{
+ long nMonths = GetMonth() + nAddMonths;
+ long nNewMonth = nMonths % 12;
+ long nYear = GetYear() + nMonths / 12;
+ if( nMonths <= 0 || nNewMonth == 0 )
+ --nYear;
+ if( nNewMonth <= 0 )
+ nNewMonth += 12;
+ if (nYear == 0)
+ nYear = (nAddMonths < 0 ? -1 : 1);
+ else if (nYear < kYearMin)
+ nYear = kYearMin;
+ else if (nYear > kYearMax)
+ nYear = kYearMax;
+ SetMonth( static_cast<sal_uInt16>(nNewMonth) );
+ SetYear( static_cast<sal_Int16>(nYear) );
+ Normalize();
+}
+
DayOfWeek Date::GetDayOfWeek() const
{
return static_cast<DayOfWeek>((GetAsNormalizedDays()-1) % 7);
@@ -209,7 +323,7 @@ sal_uInt16 Date::GetDayOfYear() const
{
sal_uInt16 nDay = GetDay();
sal_uInt16 nMonth = GetMonth();
- sal_uInt16 nYear = GetYear();
+ sal_Int16 nYear = GetYear();
Normalize( nDay, nMonth, nYear);
for( sal_uInt16 i = 1; i < nMonth; i++ )
@@ -245,7 +359,7 @@ sal_uInt16 Date::GetWeekOfYear( DayOfWeek eStartDay,
else if ( nWeek == 53 )
{
short nDaysInYear = (short)GetDaysInYear();
- short nDaysNextYear = (short)Date( 1, 1, GetYear()+1 ).GetDayOfWeek();
+ short nDaysNextYear = (short)Date( 1, 1, GetNextYear() ).GetDayOfWeek();
nDaysNextYear = (nDaysNextYear+(7-(short)eStartDay)) % 7;
if ( nDayOfYear > (nDaysInYear-nDaysNextYear-1) )
nWeek = 1;
@@ -257,7 +371,7 @@ sal_uInt16 Date::GetWeekOfYear( DayOfWeek eStartDay,
// First week of a year is equal to the last week of the previous year
if ( nWeek == 0 )
{
- Date aLastDatePrevYear( 31, 12, GetYear()-1 );
+ Date aLastDatePrevYear( 31, 12, GetPrevYear() );
nWeek = aLastDatePrevYear.GetWeekOfYear( eStartDay, nMinimumNumberOfDaysInWeek );
}
}
@@ -273,7 +387,7 @@ sal_uInt16 Date::GetWeekOfYear( DayOfWeek eStartDay,
else if ( n1WDay == nMinimumNumberOfDaysInWeek + 1 )
{
// Year after leapyear
- if ( Date( 1, 1, GetYear()-1 ).IsLeapYear() )
+ if ( Date( 1, 1, GetPrevYear() ).IsLeapYear() )
nWeek = 53;
else
nWeek = 52;
@@ -307,7 +421,7 @@ sal_uInt16 Date::GetDaysInMonth() const
{
sal_uInt16 nDay = GetDay();
sal_uInt16 nMonth = GetMonth();
- sal_uInt16 nYear = GetYear();
+ sal_Int16 nYear = GetYear();
Normalize( nDay, nMonth, nYear);
return ImplDaysInMonth( nMonth, nYear );
@@ -315,7 +429,7 @@ sal_uInt16 Date::GetDaysInMonth() const
bool Date::IsLeapYear() const
{
- sal_uInt16 nYear = GetYear();
+ sal_Int16 nYear = GetYear();
return ImpIsLeapYear( nYear );
}
@@ -323,7 +437,7 @@ bool Date::IsValidAndGregorian() const
{
sal_uInt16 nDay = GetDay();
sal_uInt16 nMonth = GetMonth();
- sal_uInt16 nYear = GetYear();
+ sal_Int16 nYear = GetYear();
if ( !nMonth || (nMonth > 12) )
return false;
@@ -348,7 +462,7 @@ bool Date::IsValidDate() const
}
//static
-bool Date::IsValidDate( sal_uInt16 nDay, sal_uInt16 nMonth, sal_uInt16 nYear )
+bool Date::IsValidDate( sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear )
{
if ( !nMonth || (nMonth > 12) )
return false;
@@ -361,7 +475,7 @@ bool Date::Normalize()
{
sal_uInt16 nDay = GetDay();
sal_uInt16 nMonth = GetMonth();
- sal_uInt16 nYear = GetYear();
+ sal_Int16 nYear = GetYear();
if (!Normalize( nDay, nMonth, nYear))
return false;
@@ -372,7 +486,7 @@ bool Date::Normalize()
}
//static
-bool Date::Normalize( sal_uInt16 & rDay, sal_uInt16 & rMonth, sal_uInt16 & rYear )
+bool Date::Normalize( sal_uInt16 & rDay, sal_uInt16 & rMonth, sal_Int16 & rYear )
{
if (IsValidDate( rDay, rMonth, rYear))
return false;
@@ -381,42 +495,59 @@ bool Date::Normalize( sal_uInt16 & rDay, sal_uInt16 & rMonth, sal_uInt16 & rYear
{
rYear += rMonth / 12;
rMonth = rMonth % 12;
+ if (rYear == 0)
+ rYear = 1;
}
- if (!rMonth)
+ if (rMonth == 0)
{
- if (!rYear)
+ --rYear;
+ if (rYear == 0)
+ rYear = -1;
+ rMonth = 12;
+ }
+
+ if (rYear < 0)
+ {
+ sal_uInt16 nDays;
+ while (rDay > (nDays = ImplDaysInMonth( rMonth, rYear)))
{
- rYear = 0;
- rMonth = 1;
- if (rDay > 31)
- rDay -= 31;
+ rDay -= nDays;
+ if (rMonth > 1)
+ --rMonth;
else
- rDay = 1;
- }
- else
- {
- --rYear;
- rMonth = 12;
+ {
+ if (rYear == kYearMin)
+ {
+ rDay = 1;
+ rMonth = 1;
+ return true;
+ }
+ --rYear;
+ rMonth = 12;
+ }
}
}
- sal_uInt16 nDays;
- while (rDay > (nDays = ImplDaysInMonth( rMonth, rYear)))
+ else
{
- rDay -= nDays;
- if (rMonth < 12)
- ++rMonth;
- else
+ sal_uInt16 nDays;
+ while (rDay > (nDays = ImplDaysInMonth( rMonth, rYear)))
{
- ++rYear;
- rMonth = 1;
+ rDay -= nDays;
+ if (rMonth < 12)
+ ++rMonth;
+ else
+ {
+ if (rYear == kYearMax)
+ {
+ rDay = 31;
+ rMonth = 12;
+ return true;
+ }
+ ++rYear;
+ rMonth = 1;
+ }
}
}
- if (rYear > 9999)
- {
- rDay = 31;
- rMonth = 12;
- rYear = 9999;
- }
return true;
}