From 32aaa0d7e1c63431f30d0239b06aeefcc3765633 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Apr 2019 12:26:05 -0700 Subject: [PATCH] fixes #471 (#481) Expand fix for ja era to handle months and days --- src/CalcViewModel/Common/DateCalculator.cpp | 76 ++++++++-------- .../DateCalculatorUnitTests.cpp | 86 +++++++++++++++---- 2 files changed, 105 insertions(+), 57 deletions(-) diff --git a/src/CalcViewModel/Common/DateCalculator.cpp b/src/CalcViewModel/Common/DateCalculator.cpp index 4ccba38c..b18dde37 100644 --- a/src/CalcViewModel/Common/DateCalculator.cpp +++ b/src/CalcViewModel/Common/DateCalculator.cpp @@ -26,21 +26,19 @@ bool DateCalculationEngine::AddDuration(_In_ DateTime startDate, _In_ const Date { m_calendar->SetDateTime(startDate); + // The Japanese Era system can have multiple year partitions within the same year. + // For example, April 30, 2019 is denoted April 30, Heisei 31; May 1, 2019 is denoted as May 1, Reiwa 1. + // The Calendar treats Heisei 31 and Reiwa 1 as separate years, which results in some unexpected behaviors where subtracting a year from Reiwa 1 results in a date in Heisei 31. + // To provide the expected result across era boundaries, we first convert the Japanese era system to a Gregorian system, do date math, and then convert back to the Japanese era system. + // This works because the Japanese era system maintains the same year/month boundaries and durations as the Gregorian system and is only different in display value. + if (currentCalendarSystem == CalendarIdentifiers::Japanese) + { + m_calendar->ChangeCalendarSystem(CalendarIdentifiers::Gregorian); + } + if (duration.year != 0) { - // The Japanese Era system can have multiple year partitions within the same year. - // For example, April 30, 2019 is denoted April 30, Heisei 31; May 1, 2019 is denoted as May 1, Reiwa 1. - // The Calendar treats Heisei 31 and Reiwa 1 as separate years, which results in some unexpected behaviors where subtracting a year from Reiwa 1 results in a date in Heisei 31. - // To provide the expected result across era boundaries, we first convert the Japanese era system to a Gregorian system, do date math, and then convert back to the Japanese era system. - // This works because the Japanese era system maintains the same year/month boundaries and durations as the Gregorian system and is only different in display value. - if (currentCalendarSystem == CalendarIdentifiers::Japanese) - { - m_calendar->ChangeCalendarSystem(CalendarIdentifiers::Gregorian); - } - m_calendar->AddYears(duration.year); - - m_calendar->ChangeCalendarSystem(currentCalendarSystem); } if (duration.month != 0) { @@ -62,6 +60,8 @@ bool DateCalculationEngine::AddDuration(_In_ DateTime startDate, _In_ const Date return false; } + m_calendar->ChangeCalendarSystem(currentCalendarSystem); + return true; } @@ -77,6 +77,16 @@ bool DateCalculationEngine::SubtractDuration(_In_ DateTime startDate, _In_ const { m_calendar->SetDateTime(startDate); + // The Japanese Era system can have multiple year partitions within the same year. + // For example, April 30, 2019 is denoted April 30, Heisei 31; May 1, 2019 is denoted as May 1, Reiwa 1. + // The Calendar treats Heisei 31 and Reiwa 1 as separate years, which results in some unexpected behaviors where subtracting a year from Reiwa 1 results in a date in Heisei 31. + // To provide the expected result across era boundaries, we first convert the Japanese era system to a Gregorian system, do date math, and then convert back to the Japanese era system. + // This works because the Japanese era system maintains the same year/month boundaries and durations as the Gregorian system and is only different in display value. + if (currentCalendarSystem == CalendarIdentifiers::Japanese) + { + m_calendar->ChangeCalendarSystem(CalendarIdentifiers::Gregorian); + } + if (duration.day != 0) { m_calendar->AddDays(-duration.day); @@ -87,21 +97,8 @@ bool DateCalculationEngine::SubtractDuration(_In_ DateTime startDate, _In_ const } if (duration.year != 0) { - // The Japanese Era system can have multiple year partitions within the same year. - // For example, April 30, 2019 is denoted April 30, Heisei 31; May 1, 2019 is denoted as May 1, Reiwa 1. - // The Calendar treats Heisei 31 and Reiwa 1 as separate years, which results in some unexpected behaviors where subtracting a year from Reiwa 1 results in a date in Heisei 31. - // To provide the expected result across era boundaries, we first convert the Japanese era system to a Gregorian system, do date math, and then convert back to the Japanese era system. - // This works because the Japanese era system maintains the same year/month boundaries and durations as the Gregorian system and is only different in display value. - if (currentCalendarSystem == CalendarIdentifiers::Japanese) - { - m_calendar->ChangeCalendarSystem(CalendarIdentifiers::Gregorian); - } - m_calendar->AddYears(-duration.year); - - m_calendar->ChangeCalendarSystem(currentCalendarSystem); } - *endDate = m_calendar->GetDateTime(); } catch (Platform::InvalidArgumentException^ ex) @@ -113,6 +110,8 @@ bool DateCalculationEngine::SubtractDuration(_In_ DateTime startDate, _In_ const return false; } + m_calendar->ChangeCalendarSystem(currentCalendarSystem); + // Check that the UniversalTime value is not negative return (endDate->UniversalTime >= 0); } @@ -308,27 +307,24 @@ bool DateCalculationEngine::TryGetCalendarDaysInYear(_In_ DateTime date, _Out_ U // Adds/Subtracts certain value for a particular date unit DateTime DateCalculationEngine::AdjustCalendarDate(Windows::Foundation::DateTime date, DateUnit dateUnit, int difference) { - m_calendar->SetDateTime(date); + // The Japanese Era system can have multiple year partitions within the same year. + // For example, April 30, 2019 is denoted April 30, Heisei 31; May 1, 2019 is denoted as May 1, Reiwa 1. + // The Calendar treats Heisei 31 and Reiwa 1 as separate years, which results in some unexpected behaviors where subtracting a year from Reiwa 1 results in a date in Heisei 31. + // To provide the expected result across era boundaries, we first convert the Japanese era system to a Gregorian system, do date math, and then convert back to the Japanese era system. + // This works because the Japanese era system maintains the same year/month boundaries and durations as the Gregorian system and is only different in display value. + auto currentCalendarSystem = m_calendar->GetCalendarSystem(); + if (currentCalendarSystem == CalendarIdentifiers::Japanese) + { + m_calendar->ChangeCalendarSystem(CalendarIdentifiers::Gregorian); + } + switch (dateUnit) { case DateUnit::Year: - { - // In the Japanese calendar, transition years have 2 partial years. - // It is not guaranteed that adding 1 year will always add 365 days in the Japanese Calendar. - // To work around this quirk, we will change the calendar system to Gregorian before adding 1 year in the Japanese Calendar case only. - // We will then return the calendar system back to the Japanese Calendar. - auto currentCalendarSystem = m_calendar->GetCalendarSystem(); - if (currentCalendarSystem == CalendarIdentifiers::Japanese) - { - m_calendar->ChangeCalendarSystem(CalendarIdentifiers::Gregorian); - } - m_calendar->AddYears(difference); - m_calendar->ChangeCalendarSystem(currentCalendarSystem); break; - } case DateUnit::Month: m_calendar->AddMonths(difference); break; @@ -337,5 +333,7 @@ DateTime DateCalculationEngine::AdjustCalendarDate(Windows::Foundation::DateTime break; } + m_calendar->ChangeCalendarSystem(currentCalendarSystem); + return m_calendar->GetDateTime(); } diff --git a/src/CalculatorUnitTests/DateCalculatorUnitTests.cpp b/src/CalculatorUnitTests/DateCalculatorUnitTests.cpp index 0318f152..8a38eab7 100644 --- a/src/CalculatorUnitTests/DateCalculatorUnitTests.cpp +++ b/src/CalculatorUnitTests/DateCalculatorUnitTests.cpp @@ -670,7 +670,7 @@ namespace DateCalculationUnitTests auto viewModel = make_unique(CalendarIdentifiers::Japanese); auto cal = ref new Calendar(); - // Showa period ended in Jan 1989. + // The Showa period ended in Jan 1989. cal->Year = 1989; cal->Month = 1; cal->Day = 1; @@ -681,14 +681,28 @@ namespace DateCalculationUnitTests cal->Day = 1; // Expect that adding a year across boundaries adds the equivalent in the Gregorian calendar. - auto expectedResult = cal->GetDateTime(); - DateDifference duration; - duration.year = 1; + auto expectedYearResult = cal->GetDateTime(); + DateDifference yearDuration; + yearDuration.year = 1; - DateTime actualResult; - viewModel->AddDuration(startTime, duration, &actualResult); + DateTime actualYearResult; + viewModel->AddDuration(startTime, yearDuration, &actualYearResult); - VERIFY_ARE_EQUAL(expectedResult.UniversalTime, actualResult.UniversalTime); + VERIFY_ARE_EQUAL(expectedYearResult.UniversalTime, actualYearResult.UniversalTime); + + cal->Year = 1989; + cal->Month = 2; + cal->Day = 1; + + // Expect that adding a month across boundaries adds the equivalent in the Gregorian calendar. + auto expectedMonthResult = cal->GetDateTime(); + DateDifference monthDuration; + monthDuration.month = 1; + + DateTime actualMonthResult; + viewModel->AddDuration(startTime, monthDuration, &actualMonthResult); + + VERIFY_ARE_EQUAL(expectedMonthResult.UniversalTime, actualMonthResult.UniversalTime); } TEST_METHOD(JaEraTransitionSubtraction) @@ -696,25 +710,61 @@ namespace DateCalculationUnitTests auto viewModel = make_unique(CalendarIdentifiers::Japanese); auto cal = ref new Calendar(); - // Showa period ended in Jan 1989. - cal->Year = 1990; + // The Showa period ended in Jan 1989. + cal->Year = 1989; + cal->Month = 2; + cal->Day = 1; + auto startTime = cal->GetDateTime(); + + cal->Year = 1988; + cal->Month = 2; + cal->Day = 1; + + // Expect that adding a year across boundaries adds the equivalent in the Gregorian calendar. + auto expectedYearResult = cal->GetDateTime(); + DateDifference yearDuration; + yearDuration.year = 1; + + DateTime actualYearResult; + viewModel->SubtractDuration(startTime, yearDuration, &actualYearResult); + + VERIFY_ARE_EQUAL(expectedYearResult.UniversalTime, actualYearResult.UniversalTime); + + cal->Year = 1989; + cal->Month = 1; + cal->Day = 1; + + // Expect that adding a month across boundaries adds the equivalent in the Gregorian calendar. + auto expectedMonthResult = cal->GetDateTime(); + DateDifference monthDuration; + monthDuration.month = 1; + + DateTime actualMonthResult; + viewModel->SubtractDuration(startTime, monthDuration, &actualMonthResult); + + VERIFY_ARE_EQUAL(expectedMonthResult.UniversalTime, actualMonthResult.UniversalTime); + } + + TEST_METHOD(JaEraTransitionDifference) + { + auto viewModel = make_unique(CalendarIdentifiers::Japanese); + auto cal = ref new Calendar(); + + // The Showa period ended in Jan 8, 1989. Pick 2 days across that boundary + cal->Year = 1989; cal->Month = 1; cal->Day = 1; auto startTime = cal->GetDateTime(); cal->Year = 1989; cal->Month = 1; - cal->Day = 1; + cal->Day = 20; + auto endTime = cal->GetDateTime(); - // Expect that adding a year across boundaries adds the equivalent in the Gregorian calendar. - auto expectedResult = cal->GetDateTime(); - DateDifference duration; - duration.year = 1; + DateDifference diff; + viewModel->GetDateDifference(startTime, endTime, DateUnit::Day, &diff); - DateTime actualResult; - viewModel->SubtractDuration(startTime, duration, &actualResult); - - VERIFY_ARE_EQUAL(expectedResult.UniversalTime, actualResult.UniversalTime); + VERIFY_ARE_EQUAL(diff.day, 19); } }; }