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 developers 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();
|
|
}
|