calculator/src/CalcViewModel/DateCalculatorViewModel.cpp
Daniel Belcher f210290ddc - Avoid referencing project headers from precompiled headers.
Before this change, the pchs for CalcViewModel and Calculator project referenced project headers.  If those project headers (or any of their dependencies) were to change, then the pch would be recompiled, slowing local build times.

  By removing references to project headers, the pch will be compiled once and is resilient to changes in the project.  Now that project headers are explicit about their dependencies, when there is a change to a project header only the translation units referencing the modified header will need to be rebuilt.

- Manually tested by ensuring Calculator project builds locally.

@Microsoft/calculator-team
2019-02-14 18:20:25 -08:00

394 lines
13 KiB
C++

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "pch.h"
#include "DateCalculatorViewModel.h"
#include "Common\TraceLogger.h"
#include "Common\LocalizationStringUtil.h"
#include "Common\LocalizationService.h"
#include "Common\LocalizationSettings.h"
#include "Common\CopyPasteManager.h"
using namespace CalculatorApp;
using namespace CalculatorApp::Common;
using namespace CalculatorApp::Common::DateCalculation;
using namespace CalculatorApp::ViewModel;
using namespace Platform;
using namespace Platform::Collections;
using namespace std;
using namespace Windows::ApplicationModel::Resources;
using namespace Windows::Foundation;
using namespace Windows::Globalization;
using namespace Windows::Globalization::DateTimeFormatting;
using namespace Windows::System::UserProfile;
namespace CalculatorApp::ViewModel::DateCalculatorViewModelProperties
{
StringReference StrDateDiffResult(L"StrDateDiffResult");
StringReference StrDateDiffResultAutomationName(L"StrDateDiffResultAutomationName");
StringReference StrDateDiffResultInDays(L"StrDateDiffResultInDays");
StringReference StrDateResult(L"StrDateResult");
StringReference StrDateResultAutomationName(L"StrDateResultAutomationName");
StringReference IsDiffInDays(L"IsDiffInDays");
}
DateCalculatorViewModel::DateCalculatorViewModel() :
m_IsDateDiffMode(true),
m_IsAddMode(true),
m_isOutOfBound(false),
m_DaysOffset(0),
m_MonthsOffset(0),
m_YearsOffset(0),
m_StrDateDiffResult(L""),
m_StrDateDiffResultAutomationName(L""),
m_StrDateDiffResultInDays(L""),
m_StrDateResult(L""),
m_StrDateResultAutomationName(L""),
m_fromDate({ 0 }),
m_toDate({ 0 }),
m_startDate({ 0 }),
m_dateResult({ 0 })
{
const auto& localizationSettings = LocalizationSettings::GetInstance();
// Initialize Date Output format instances
InitializeDateOutputFormats(localizationSettings.GetCalendarIdentifier());
// Initialize Date Calc engine
m_dateCalcEngine = make_shared<DateCalculationEngine>(localizationSettings.GetCalendarIdentifier());
// Initialize dates of DatePicker controls to today's date
auto calendar = ref new Calendar();
auto today = calendar->GetDateTime();
// FromDate and ToDate should be clipped (adjusted to a consistent hour in UTC)
m_fromDate = today;
m_toDate = today;
FromDate = ClipTime(today);
ToDate = ClipTime(today);
// StartDate should not be clipped
StartDate = today;
m_dateResult = today;
// Initialize the list separator delimiter appended with a space at the end, e.g. ", "
// This will be used for date difference formatting: Y years, M months, W weeks, D days
m_listSeparator = ref new String((localizationSettings.GetListSeparator() + L" ").c_str());
// Initialize the output results
UpdateDisplayResult();
m_offsetValues = ref new Vector<String^>();
for (int i = 0; i <= c_maxOffsetValue; i++)
{
wstring numberStr(to_wstring(i));
localizationSettings.LocalizeDisplayValue(&numberStr);
m_offsetValues->Append(ref new String(numberStr.c_str()));
}
/* In the ClipTime function, we used to change timezone to UTC before clipping the time.
The comment from the previous delopers said this was done to eliminate the effects of
Daylight Savings Time. We can't think of a good reason why this change in timezone is
necessary and did find bugs related to the change, therefore, we have removed the
change. Just in case, we will see if the clipped time is ever a different day from the
original day, which would hopefully indicate the change in timezone was actually
necessary. We will collect telemetry if we find this case. If we don't see any
telemetry events after the application has been used for some time, we will feel safe
and can remove this function. */
DayOfWeek trueDayOfWeek = calendar->DayOfWeek;
DateTime clippedTime = ClipTime(today);
calendar->SetDateTime(clippedTime);
if (calendar->DayOfWeek != trueDayOfWeek)
{
calendar->SetDateTime(today);
TraceLogger::GetInstance().LogDateClippedTimeDifferenceFound(
from_cx<winrt::Windows::Globalization::Calendar>(calendar),
winrt::Windows::Foundation::DateTime{ winrt::Windows::Foundation::TimeSpan{ clippedTime.UniversalTime } });
}
}
void DateCalculatorViewModel::OnPropertyChanged(_In_ String^ prop)
{
if (prop == DateCalculatorViewModelProperties::StrDateDiffResult)
{
UpdateStrDateDiffResultAutomationName();
}
else if (prop == DateCalculatorViewModelProperties::StrDateResult)
{
UpdateStrDateResultAutomationName();
}
else if (prop != DateCalculatorViewModelProperties::StrDateDiffResultAutomationName
&& prop != DateCalculatorViewModelProperties::StrDateDiffResultInDays
&& prop != DateCalculatorViewModelProperties::StrDateResultAutomationName
&& prop != DateCalculatorViewModelProperties::IsDiffInDays)
{
OnInputsChanged();
}
}
void DateCalculatorViewModel::OnInputsChanged()
{
DateDifference dateDiff;
if (m_IsDateDiffMode)
{
DateTime clippedFromDate = ClipTime(FromDate);
DateTime clippedToDate = ClipTime(ToDate);
// Calculate difference between two dates
m_dateCalcEngine->GetDateDifference(clippedFromDate, clippedToDate, m_allDateUnitsOutputFormat, &dateDiff);
DateDiffResult = dateDiff;
m_dateCalcEngine->GetDateDifference(clippedFromDate, clippedToDate, m_daysOutputFormat, &dateDiff);
DateDiffResultInDays = dateDiff;
}
else
{
dateDiff.day = DaysOffset;
dateDiff.month = MonthsOffset;
dateDiff.year = YearsOffset;
DateTime dateTimeResult;
if (m_IsAddMode)
{
// Add number of Days, Months and Years to a Date
IsOutOfBound = !m_dateCalcEngine->AddDuration(StartDate, dateDiff, &dateTimeResult);
}
else
{
// Subtract number of Days, Months and Years from a Date
IsOutOfBound = !m_dateCalcEngine->SubtractDuration(StartDate, dateDiff, &dateTimeResult);
}
if (!m_isOutOfBound)
{
DateResult = dateTimeResult;
}
}
}
void DateCalculatorViewModel::UpdateDisplayResult()
{
if (m_IsDateDiffMode)
{
// Are to and from dates the same
if (m_dateDiffResultInDays.day == 0)
{
IsDiffInDays = true;
StrDateDiffResultInDays = L"";
StrDateDiffResult = AppResourceProvider::GetInstance().GetResourceString(L"Date_SameDates");
}
else if ((m_dateDiffResult.year == 0) &&
(m_dateDiffResult.month == 0) &&
(m_dateDiffResult.week == 0))
{
IsDiffInDays = true;
StrDateDiffResultInDays = L"";
// Display result in number of days
StrDateDiffResult = GetDateDiffStringInDays();
}
else
{
IsDiffInDays = false;
// Display result in days, weeks, months and years
StrDateDiffResult = GetDateDiffString();
// Display result in number of days
StrDateDiffResultInDays = GetDateDiffStringInDays();
}
}
else
{
if (m_isOutOfBound)
{
// Display Date out of bound message
StrDateResult = AppResourceProvider::GetInstance().GetResourceString(L"Date_OutOfBoundMessage");
}
else
{
// Display the resulting date in long format
StrDateResult = m_dateTimeFormatter->Format(DateResult);
}
}
}
void DateCalculatorViewModel::UpdateStrDateDiffResultAutomationName()
{
String^ automationFormat = AppResourceProvider::GetInstance().GetResourceString(L"Date_DifferenceResultAutomationName");
wstring localizedAutomationName = LocalizationStringUtil::GetLocalizedString(automationFormat->Data(), StrDateDiffResult->Data());
StrDateDiffResultAutomationName = ref new String(localizedAutomationName.c_str());
}
void DateCalculatorViewModel::UpdateStrDateResultAutomationName()
{
String^ automationFormat = AppResourceProvider::GetInstance().GetResourceString(L"Date_ResultingDateAutomationName");
wstring localizedAutomationName = LocalizationStringUtil::GetLocalizedString(automationFormat->Data(), StrDateResult->Data());
StrDateResultAutomationName = ref new String(localizedAutomationName.c_str());
}
void DateCalculatorViewModel::InitializeDateOutputFormats(_In_ String^ calendarIdentifier)
{
// Format for Add/Subtract days
m_dateTimeFormatter = LocalizationService::GetRegionalSettingsAwareDateTimeFormatter(
L"longdate",
calendarIdentifier,
ClockIdentifiers::TwentyFourHour); // Clock Identifier is not used
// Format for Date Difference
m_allDateUnitsOutputFormat = DateUnit::Year | DateUnit::Month | DateUnit::Week | DateUnit::Day;
m_daysOutputFormat = DateUnit::Day;
}
String^ DateCalculatorViewModel::GetDateDiffString() const
{
String^ result = L"";
bool addDelimiter = false;
AppResourceProvider resourceLoader = AppResourceProvider::GetInstance();
auto yearCount = m_dateDiffResult.year;
if (yearCount > 0)
{
result = String::Concat(GetLocalizedNumberString(yearCount), L" ");
if (yearCount > 1)
{
result = String::Concat(result, resourceLoader.GetResourceString(L"Date_Years"));
}
else
{
result = String::Concat(result, resourceLoader.GetResourceString(L"Date_Year"));
}
// set the flags to add a delimiter whenever the next unit is added
addDelimiter = true;
}
auto monthCount = m_dateDiffResult.month;
if (monthCount > 0)
{
if (addDelimiter)
{
result = String::Concat(result, m_listSeparator);
}
else
{
addDelimiter = true;
}
result = String::Concat(result, String::Concat(GetLocalizedNumberString(monthCount), L" "));
if (monthCount > 1)
{
result = String::Concat(result, resourceLoader.GetResourceString(L"Date_Months"));
}
else
{
result = String::Concat(result, resourceLoader.GetResourceString(L"Date_Month"));
}
}
auto weekCount = m_dateDiffResult.week;
if (weekCount > 0)
{
if (addDelimiter)
{
result = String::Concat(result, m_listSeparator);
}
else
{
addDelimiter = true;
}
result = String::Concat(result, String::Concat(GetLocalizedNumberString(weekCount), L" "));
if (weekCount > 1)
{
result = String::Concat(result, resourceLoader.GetResourceString(L"Date_Weeks"));
}
else
{
result = String::Concat(result, resourceLoader.GetResourceString(L"Date_Week"));
}
}
auto dayCount = m_dateDiffResult.day;
if (dayCount > 0)
{
if (addDelimiter)
{
result = String::Concat(result, m_listSeparator);
}
else
{
addDelimiter = true;
}
result = String::Concat(result, String::Concat(GetLocalizedNumberString(dayCount), L" "));
if (dayCount > 1)
{
result = String::Concat(result, resourceLoader.GetResourceString(L"Date_Days"));
}
else
{
result = String::Concat(result, resourceLoader.GetResourceString(L"Date_Day"));
}
}
return result;
}
String^ DateCalculatorViewModel::GetDateDiffStringInDays() const
{
String^ strDateUnit;
// Display the result as '1 day' or 'N days'
if (m_dateDiffResultInDays.day > 1)
{
strDateUnit = AppResourceProvider::GetInstance().GetResourceString(L"Date_Days");
}
else
{
strDateUnit = AppResourceProvider::GetInstance().GetResourceString(L"Date_Day");
}
return String::Concat(GetLocalizedNumberString(m_dateDiffResultInDays.day), String::Concat(L" ", strDateUnit));
}
void DateCalculatorViewModel::OnCopyCommand(Platform::Object^ parameter)
{
if (m_IsDateDiffMode)
{
CopyPasteManager::CopyToClipboard(m_StrDateDiffResult);
}
else
{
CopyPasteManager::CopyToClipboard(m_StrDateResult);
}
}
String^ DateCalculatorViewModel::GetLocalizedNumberString(int value) const
{
wstring numberStr(to_wstring(value));
LocalizationSettings::GetInstance().LocalizeDisplayValue(&numberStr);
return ref new String(numberStr.c_str());
}
// Adjusts the given DateTime to 12AM of the same day
DateTime DateCalculatorViewModel::ClipTime(DateTime dateTime)
{
auto calendar = ref new Calendar();
calendar->SetDateTime(dateTime);
calendar->Period = 1;
calendar->Hour = 12;
calendar->Minute = 0;
calendar->Second = 0;
calendar->Nanosecond = 0;
return calendar->GetDateTime();
}