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
394 lines
13 KiB
C++
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();
|
|
}
|