diff --git a/src/Calculator.Shared/Calculator.Shared.projitems b/src/Calculator.Shared/Calculator.Shared.projitems index 24599449..6004fdf4 100644 --- a/src/Calculator.Shared/Calculator.Shared.projitems +++ b/src/Calculator.Shared/Calculator.Shared.projitems @@ -39,6 +39,7 @@ + @@ -48,6 +49,7 @@ + diff --git a/src/Calculator.Shared/Common/DateCalculator.cs b/src/Calculator.Shared/Common/DateCalculator.cs new file mode 100644 index 00000000..ab4433a5 --- /dev/null +++ b/src/Calculator.Shared/Common/DateCalculator.cs @@ -0,0 +1,377 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text; +using Windows.Globalization; + +namespace CalculatorApp.Common.DateCalculation +{ + [Flags] + public enum DateUnit + { + Year = 0x01, + Month = 0x02, + Week = 0x04, + Day = 0x08 + }; + + // Struct to store the difference between two Dates in the form of Years, Months , Weeks + public struct DateDifference + { + public int year; + public int month; + public int week; + public int day; + }; + + public class DateCalculationEngine + { + const ulong c_millisecond = 10000; + const ulong c_second = 1000 * c_millisecond; + const ulong c_minute = 60 * c_second; + const ulong c_hour = 60 * c_minute; + const ulong c_day = 24 * c_hour; + + const int c_unitsOfDate = 4; // Units Year,Month,Week,Day + const int c_unitsGreaterThanDays = 3; // Units Greater than Days (Year/Month/Week) 3 + const int c_daysInWeek = 7; + + private Calendar m_calendar; + + public DateCalculationEngine(string calendarIdentifier) + { + m_calendar = new Calendar(); + m_calendar.ChangeTimeZone("UTC"); + m_calendar.ChangeCalendarSystem(calendarIdentifier); + } + + // Adding Duration to a Date + // Returns: True if function succeeds to calculate the date else returns False + public bool AddDuration( DateTime startDate, DateDifference duration, out DateTime endDate) + { + var currentCalendarSystem = m_calendar.GetCalendarSystem(); + + try + { + 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) + { + m_calendar.AddYears(duration.year); + } + if (duration.month != 0) + { + m_calendar.AddMonths(duration.month); + } + if (duration.day != 0) + { + m_calendar.AddDays(duration.day); + } + + endDate = m_calendar.GetDateTime().DateTime; + } + catch (ArgumentException ex) + { + // ensure that we revert to the correct calendar system + m_calendar.ChangeCalendarSystem(currentCalendarSystem); + + // Do nothing + endDate = default(DateTime); + return false; + } + + m_calendar.ChangeCalendarSystem(currentCalendarSystem); + + return true; + } + + // Subtracting Duration from a Date + // Returns: True if function succeeds to calculate the date else returns False + public bool SubtractDuration( DateTime startDate, DateDifference duration, out DateTime endDate) + { + var currentCalendarSystem = m_calendar.GetCalendarSystem(); + + // For Subtract the Algorithm is different than Add. Here the smaller units are subtracted first + // and then the larger units. + try + { + 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); + } + if (duration.month != 0) + { + m_calendar.AddMonths(-duration.month); + } + if (duration.year != 0) + { + m_calendar.AddYears(-duration.year); + } + endDate = m_calendar.GetDateTime().DateTime; + } + catch (ArgumentException ex) + { + // ensure that we revert to the correct calendar system + m_calendar.ChangeCalendarSystem(currentCalendarSystem); + + // Do nothing + endDate = default(DateTime); + return false; + } + + m_calendar.ChangeCalendarSystem(currentCalendarSystem); + + // Check that the UniversalTime value is not negative + return (endDate.ToUniversalTime().Ticks >= 0); + } + + // Calculate the difference between two dates + public void GetDateDifference( DateTime date1, DateTime date2, DateUnit outputFormat, out DateDifference difference) + { + DateTime startDate; + DateTime endDate; + DateTime pivotDate; + DateTime tempPivotDate; + uint daysDiff = 0; + uint[] differenceInDates = new uint[c_unitsOfDate]; + + if (date1.ToUniversalTime() < date2.ToUniversalTime()) + { + startDate = date1; + endDate = date2; + } + else + { + startDate = date2; + endDate = date1; + } + + pivotDate = startDate; + + daysDiff = (uint)GetDifferenceInDays(startDate, endDate); + + // If output has units other than days + // 0th bit: Year, 1st bit: Month, 2nd bit: Week, 3rd bit: Day + if (((int)(outputFormat) & 7) != 0) + { + uint daysInMonth; + uint approximateDaysInYear; + + // If we're unable to calculate the days-in-month or days-in-year, we'll leave the values at 0. + if (TryGetCalendarDaysInMonth(startDate, out daysInMonth) && TryGetCalendarDaysInYear(endDate, out approximateDaysInYear)) + { + uint[] daysIn = new uint[c_unitsOfDate] { approximateDaysInYear, daysInMonth, c_daysInWeek, 1 }; + + for (int unitIndex = 0; unitIndex < c_unitsGreaterThanDays; unitIndex++) + { + tempPivotDate = pivotDate; + + // Check if the bit flag is set for the date unit + DateUnit dateUnit = (DateUnit)(1 << unitIndex); + + if ((int)(outputFormat & dateUnit) != 0) + { + bool isEndDateHit = false; + differenceInDates[unitIndex] = (daysDiff / daysIn[unitIndex]); + + if (differenceInDates[unitIndex] != 0) + { + try + { + pivotDate = AdjustCalendarDate(pivotDate, dateUnit, (int)(differenceInDates[unitIndex])); + } + catch (ArgumentException) + { + // Operation failed due to out of bound result + // Do nothing + differenceInDates[unitIndex] = 0; + } + } + + int tempDaysDiff; + + do + { + tempDaysDiff = GetDifferenceInDays(pivotDate, endDate); + + if (tempDaysDiff < 0) + { + // pivotDate has gone over the end date; start from the beginning of this unit + differenceInDates[unitIndex] -= 1; + pivotDate = tempPivotDate; + pivotDate = AdjustCalendarDate(pivotDate, dateUnit, (int)(differenceInDates[unitIndex])); + isEndDateHit = true; + } + else if (tempDaysDiff > 0) + { + if (isEndDateHit) + { + // This is the closest the pivot can get to the end date for this unit + break; + } + + // pivotDate is still below the end date + try + { + pivotDate = AdjustCalendarDate(pivotDate, dateUnit, 1); + differenceInDates[unitIndex] += 1; + } + catch (ArgumentException) + { + // handling for 31st Dec, 9999 last valid date + // Do nothing - break out + break; + } + } + } while (tempDaysDiff != 0); // dates are the same - exit the loop + + tempPivotDate = AdjustCalendarDate(tempPivotDate, dateUnit, (int)(differenceInDates[unitIndex])); + pivotDate = tempPivotDate; + daysDiff = (uint)GetDifferenceInDays(pivotDate, endDate); + } + } + } + } + + differenceInDates[3] = daysDiff; + + difference.year = (int)differenceInDates[0]; + difference.month = (int)differenceInDates[1]; + difference.week = (int)differenceInDates[2]; + difference.day = (int)differenceInDates[3]; + } + + // Private Methods + + // Gets number of days between the two date time values + int GetDifferenceInDays(DateTime date1, DateTime date2) + { + // A tick is defined as the number of 100 nanoseconds + long ticksDifference = date2.ToUniversalTime().Ticks - date1.ToUniversalTime().Ticks; + return (int)(ticksDifference / (long)(c_day)); + } + + // Gets number of Calendar days in the month in which this date falls. + // Returns true if successful, false otherwise. + bool TryGetCalendarDaysInMonth( DateTime date, out uint daysInMonth) + { + daysInMonth = 0; + bool result = false; + m_calendar.SetDateTime(date); + + // NumberOfDaysInThisMonth returns -1 if unknown. + int daysInThisMonth = m_calendar.NumberOfDaysInThisMonth; + if (daysInThisMonth != -1) + { + daysInMonth = (uint)(daysInThisMonth); + result = true; + } + + return result; + } + + // Gets number of Calendar days in the year in which this date falls. + // Returns true if successful, false otherwise. + bool TryGetCalendarDaysInYear( DateTime date, out uint daysInYear) + { + daysInYear = 0; + bool result = false; + uint days = 0; + + m_calendar.SetDateTime(date); + + // NumberOfMonthsInThisYear returns -1 if unknown. + int monthsInYear = m_calendar.NumberOfMonthsInThisYear; + if (monthsInYear != -1) + { + bool monthResult = true; + + // Not all years begin with Month 1. + int firstMonthThisYear = m_calendar.FirstMonthInThisYear; + for (int month = 0; month < monthsInYear; month++) + { + m_calendar.Month = firstMonthThisYear + month; + + // NumberOfDaysInThisMonth returns -1 if unknown. + int daysInMonth = m_calendar.NumberOfDaysInThisMonth; + if (daysInMonth == -1) + { + monthResult = false; + break; + } + + days += (uint)daysInMonth; + } + + if (monthResult) + { + daysInYear = days; + result = true; + } + } + + return result; + } + + // Adds/Subtracts certain value for a particular date unit + DateTime AdjustCalendarDate(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. + var currentCalendarSystem = m_calendar.GetCalendarSystem(); + if (currentCalendarSystem == CalendarIdentifiers.Japanese) + { + m_calendar.ChangeCalendarSystem(CalendarIdentifiers.Gregorian); + } + + switch (dateUnit) + { + case DateUnit.Year: + m_calendar.AddYears(difference); + break; + case DateUnit.Month: + m_calendar.AddMonths(difference); + break; + case DateUnit.Week: + m_calendar.AddWeeks(difference); + break; + } + + m_calendar.ChangeCalendarSystem(currentCalendarSystem); + + return m_calendar.GetDateTime().DateTime; + } + } +} diff --git a/src/Calculator.Shared/Common/LocalizationService.cs b/src/Calculator.Shared/Common/LocalizationService.cs index 49ebcd44..e715fd9d 100644 --- a/src/Calculator.Shared/Common/LocalizationService.cs +++ b/src/Calculator.Shared/Common/LocalizationService.cs @@ -379,7 +379,7 @@ namespace CalculatorApp.Common // as configured by running intl.cpl. // // This helper function creates a DateTimeFormatter with a TwentyFour hour clock - DateTimeFormatter GetRegionalSettingsAwareDateTimeFormatter( string format) + public static DateTimeFormatter GetRegionalSettingsAwareDateTimeFormatter( string format) { IEnumerable languageIdentifiers = GetLanguageIdentifiers(); if (languageIdentifiers == null) @@ -392,75 +392,39 @@ namespace CalculatorApp.Common // If successful, returns a formatter that respects the user's regional format settings, // as configured by running intl.cpl. - DateTimeFormatter GetRegionalSettingsAwareDateTimeFormatter( string format, string calendarIdentifier, string clockIdentifier) + public static DateTimeFormatter GetRegionalSettingsAwareDateTimeFormatter( string format, string calendarIdentifier, string clockIdentifier) { - // UNO TODO - //IIterable languageIdentifiers = GetLanguageIdentifiers(); - //if (languageIdentifiers == null) - //{ - // languageIdentifiers = ApplicationLanguages.Languages; - //} + IEnumerable languageIdentifiers = GetLanguageIdentifiers(); + if (languageIdentifiers == null) + { + languageIdentifiers = ApplicationLanguages.Languages; + } - //return new DateTimeFormatter(format, languageIdentifiers, GlobalizationPerences.HomeGeographicRegion, calendarIdentifier, clockIdentifier); - - throw new NotImplementedException(); + return new DateTimeFormatter(format, languageIdentifiers, GlobalizationPreferences.HomeGeographicRegion, calendarIdentifier, clockIdentifier); } - public CurrencyFormatter GetRegionalSettingsAwareCurrencyFormatter() + public static CurrencyFormatter GetRegionalSettingsAwareCurrencyFormatter() { - // UNO TODO - //string userCurrency = - // (GlobalizationPreferences.Currencies.Size > 0) ? GlobalizationPreferences.Currencies.GetAt(0) : string(DefaultCurrencyCode.data()); + string userCurrency = + (GlobalizationPreferences.Currencies.Count > 0) ? GlobalizationPreferences.Currencies[0] : DefaultCurrencyCode; - //IIterable languageIdentifiers = GetLanguageIdentifiers(); - //if (languageIdentifiers == null) - //{ - // languageIdentifiers = ApplicationLanguages.Languages; - //} + IEnumerable languageIdentifiers = GetLanguageIdentifiers(); + if (languageIdentifiers == null) + { + languageIdentifiers = ApplicationLanguages.Languages; + } - //var currencyFormatter = new CurrencyFormatter(userCurrency, languageIdentifiers, GlobalizationPerences.HomeGeographicRegion); + var currencyFormatter = new CurrencyFormatter(userCurrency, languageIdentifiers, GlobalizationPreferences.HomeGeographicRegion); - //int fractionDigits = LocalizationSettings.GetInstance().GetCurrencyTrailingDigits(); - //currencyFormatter.FractionDigits = fractionDigits; + int fractionDigits = LocalizationSettings.GetInstance().GetCurrencyTrailingDigits(); + currencyFormatter.FractionDigits = fractionDigits; - //return currencyFormatter; - - throw new NotImplementedException(); + return currencyFormatter; } static IEnumerable GetLanguageIdentifiers() - { -#if !HAS_UNO - //char currentLocale[LOCALE_NAME_MAX_LENGTH] = {}; - //int result = GetUserDefaultLocaleName(currentLocale, LOCALE_NAME_MAX_LENGTH); - //if (result != 0) - //{ - // // GetUserDefaultLocaleName may return an invalid bcp47 language tag with trailing non-BCP47 friendly characters, - // // which if present would start with an underscore, for example sort order - // // (see https://msdn.microsoft.com/en-us/library/windows/desktop/dd373814(v=vs.85).aspx). - // // Therefore, if there is an underscore in the locale name, trim all characters from the underscore onwards. - // WCHAR* underscore = wcschr(currentLocale, '_'); - // if (underscore != null) - // { - // *underscore = '\0'; - // } - - // string localestring = new String(currentLocale); - // // validate if the locale we have is valid - // // otherwise we fallback to the default. - // if (Language.IsWellFormed(localeString)) - // { - // var languageList = new Vector(); - // languageList.Append(localeString); - // return languageList; - // } - //} - - //return null; - yield break; -#else - yield break; -#endif + { + return GlobalizationPreferences.Languages; } // Resources for the engine use numbers as keys. It's inconvenient, but also difficult to diff --git a/src/Calculator.Shared/Common/LocalizationSettings.cs b/src/Calculator.Shared/Common/LocalizationSettings.cs index 5fe8f6b4..902d00e8 100644 --- a/src/Calculator.Shared/Common/LocalizationSettings.cs +++ b/src/Calculator.Shared/Common/LocalizationSettings.cs @@ -128,17 +128,16 @@ namespace CalculatorApp m_digitSymbols[i] = i.ToString(NumberFormatInfo.CurrentInfo)[0]; } - // As CalcEngine only supports the first character of the decimal separator, - // Only first character of the decimal separator string is supported. - m_decimalSeparator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator[0]; + // As CalcEngine only supports the first character of the decimal separator, + // Only first character of the decimal separator string is supported. + m_decimalSeparator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator[0]; m_numberGroupSeparator = NumberFormatInfo.CurrentInfo.NumberGroupSeparator[0]; m_numberGrouping = ""; // UNO TODO https://docs.microsoft.com/en-us/windows/desktop/Intl/locale-sgrouping m_listSeparator = CultureInfo.CurrentCulture.TextInfo.ListSeparator; m_currencyTrailingDigits = NumberFormatInfo.CurrentInfo.CurrencyDecimalDigits; m_currencySymbolPrecedence = ~(NumberFormatInfo.CurrentInfo.CurrencyPositivePattern) & 1; - - m_calendarIdentifier = "";//CultureInfo.CurrentCulture.DateTimeFormat.Calendar.ToString(); - + m_resolvedName = CultureInfo.CurrentCulture.Name; + m_calendarIdentifier = new Windows.Globalization.Calendar().GetCalendarSystem(); m_firstDayOfWeek = (Windows.Globalization.DayOfWeek)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek; #endif } @@ -155,7 +154,7 @@ namespace CalculatorApp return m_localizationSettings; } - string GetLocaleName() + public string GetLocaleName() { return m_resolvedName; } @@ -326,7 +325,7 @@ namespace CalculatorApp return m_listSeparator; } - Windows.Globalization.DayOfWeek GetFirstDayOfWeek() + public Windows.Globalization.DayOfWeek GetFirstDayOfWeek() { return m_firstDayOfWeek; } diff --git a/src/Calculator.Shared/Common/TraceLogger.cs b/src/Calculator.Shared/Common/TraceLogger.cs new file mode 100644 index 00000000..8b769748 --- /dev/null +++ b/src/Calculator.Shared/Common/TraceLogger.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Windows.Globalization; +using CalculatorApp.Common; + +namespace CalculatorApp +{ + public class TraceLogger + { + private static TraceLogger _instance = new TraceLogger(); + public static TraceLogger GetInstance() => _instance; + + + public bool GetTraceLoggingProviderEnabled() => false; + + public void LogAppLaunchStart() {} + public void LogAppLaunchComplete() {} + public void LogAppResumeComplete() {} + //public void LogOnAppLaunch(std::wstring_view previousExecutionState) {} + public void LogMemoryClearAll(int i) {} + public void LogBitFlipPaneClicked() {} + public void LogBitFlipUsed() {} + public void LogHistoryBodyOpened() {} + public void LogHistoryItemLoadBegin() {} + public void LogHistoryItemLoadEnd(uint i) {} + public void LogHistoryFlyoutOpenBegin(uint i) {} + public void LogHistoryFlyoutOpenEnd(int i) {} + public void LogCalculatorModeViewed(ViewMode mode, int i) {} + public void LogDateCalculatorModeViewed(ViewMode mode, int i) {} + public void LogConverterModeViewed(ViewMode mode, int i) {} + public void LogModeChangeBegin(ViewMode oldMode, ViewMode newMode, int i) {} + public void LogModeChangeEnd(ViewMode mode, int i) {} + public void LogClearHistory() {} + public void InsertintoMemoryMap(int i, bool b1, bool b2, bool b3) {} + public void UpdateMemoryMap(int i1, int i2, bool b1, bool b2, bool b3) {} + public void DeleteFromMemoryMap(int i1, int i2) {} + public void LogMemoryUsed(int i1, uint i2, bool b1, bool b2, bool b3, uint i4) {} + public void LogMultipleMemoryUsed(uint i1, uint i2) {} + public void LogSingleMemoryUsed(uint i) {} + //public void LogSharedMemoryUsed(std::wstring_view, std::wstring_view, uint i) {} + public void LogMemoryBodyOpened() {} + public void LogMemoryFlyoutOpenBegin(uint i) {} + //public void LogDebug(std::wstring_view debugData) {} + public void LogMemoryFlyoutOpenEnd(uint i) {} + //public void LogInvalidPastedInputOccurred(std::wstring_view reason, ViewMode mode, int ProgrammerNumberBase, int bitLengthType) {} + public void LogValidInputPasted(ViewMode mode) {} + public void UpdateFunctionUsage(int func) {} + public void LogFunctionUsage(int i) {} + public void InitFunctionLogArray() {} + public void LogBitLengthButtonUsed(int windowId) {} + public void LogRadixButtonUsed(int windowId) {} + public void LogAngleButtonUsed(int windowId) {} + public void LogHypButtonUsed(int windowId) {} + public void LogNewWindowCreationBegin(int windowId) {} + public void LogNewWindowCreationEnd(int windowId) {} + //public void LogError(std::wstring_view errorString) {} + public void LogPrelaunchedAppActivatedByUser() {} + public void LogAppPrelaunchedBySystem() {} + //public void UpdateWindowCount(size_t windowCount) {} + public bool UpdateWindowIdLog(int windowId) => true; + public void LogMaxWindowCount() {} + public void LogWindowActivated() {} + public void LogWindowLaunched() {} + public void LogUserRequestedRefreshFailed() {} + //public void LogConversionResult(std::wstring_view fromValue, std::wstring_view fromUnit, std::wstring_view toValue, std::wstring_view toUnit) {} + public void LogAboutFlyoutOpened() {} + public void LogNavBarOpened() {} + public void LogViewClosingTelemetry(int i) {} + public void LogCoreWindowWasNull() {} + + // Trace methods for Date Calculator usage + public void LogDateDifferenceModeUsed(int windowId) {} + public void LogDateAddSubtractModeUsed(int windowId, bool isAddMode) {} + public void LogDateClippedTimeDifferenceFound(Calendar today, DateTime clippedTime) {} + + //public void LogStandardException(std::wstring_view functionName, _In_ const std::exception& e) {} + //public void LogWinRTException(std::wstring_view functionName, _In_ winrt::hresult_error const& e) {} + //public void LogPlatformException(std::wstring_view functionName, _In_ Platform::Exception ^ e) {} + } +} diff --git a/src/Calculator.Shared/ViewModels/ApplicationViewModel.cs b/src/Calculator.Shared/ViewModels/ApplicationViewModel.cs index 2069defe..b2b609d4 100644 --- a/src/Calculator.Shared/ViewModels/ApplicationViewModel.cs +++ b/src/Calculator.Shared/ViewModels/ApplicationViewModel.cs @@ -35,9 +35,8 @@ namespace CalculatorApp.ViewModel private StandardCalculatorViewModel m_CalculatorViewModel; public StandardCalculatorViewModel CalculatorViewModel { get => m_CalculatorViewModel; set { m_CalculatorViewModel = value; RaisePropertyChanged("CalculatorViewModel"); } } - // UNO TODO - //private DateCalculatorViewModel m_DateCalcViewModel; - //public DateCalculatorViewModel DateCalcViewModel { get => m_DateCalcViewModel; set { m_DateCalcViewModel = value; RaisePropertyChanged("DateCalcViewModel"); } } + private DateCalculatorViewModel m_DateCalcViewModel; + public DateCalculatorViewModel DateCalcViewModel { get => m_DateCalcViewModel; set { m_DateCalcViewModel = value; RaisePropertyChanged("DateCalcViewModel"); } } // UNO TODO @@ -160,12 +159,11 @@ namespace CalculatorApp.ViewModel else if (NavCategory.IsDateCalculatorViewMode(m_mode)) { // TraceLogger.GetInstance().LogDateCalculatorModeViewed(m_mode, ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread())); - // UNO TODO - //if (m_DateCalcViewModel == null) - //{ - // m_DateCalcViewModel = new DateCalculatorViewModel(); - //} - } + if (m_DateCalcViewModel == null) + { + m_DateCalcViewModel = new DateCalculatorViewModel(); + } + } else if (NavCategory.IsConverterViewMode(m_mode)) { // UNO TODO @@ -200,12 +198,13 @@ namespace CalculatorApp.ViewModel //{ // ConverterViewModel.OnCopyCommand(parameter); //} - //else if (NavCategory.IsDateCalculatorViewMode(m_mode)) - //{ - // DateCalcViewModel.OnCopyCommand(parameter); - //} //else - { + if (NavCategory.IsDateCalculatorViewMode(m_mode)) + { + DateCalcViewModel.OnCopyCommand(parameter); + } + else + { CalculatorViewModel.OnCopyCommand(parameter); } } diff --git a/src/Calculator.Shared/ViewModels/DateCalculatorViewModel.cs b/src/Calculator.Shared/ViewModels/DateCalculatorViewModel.cs index c7778222..11c385a2 100644 --- a/src/Calculator.Shared/ViewModels/DateCalculatorViewModel.cs +++ b/src/Calculator.Shared/ViewModels/DateCalculatorViewModel.cs @@ -1,531 +1,651 @@ -//using System; -//using System.Collections.Generic; -//using System.ComponentModel; -//using System.Text; -//using Windows.UI.Xaml.Data; -//using Windows.Globalization; -//using CalculatorApp.Common; -//using Windows.Globalization.DateTimeFormatting; -//using CalculatorApp.Common.DateCalculation; -//using Windows.Foundation; -//using Platform.Collections; -//using System.Numerics; - -//namespace WindowsCalculator.Shared.ViewModels -//{ -// /* MAX? -// { -// StringReference StrDateDiffResult(L"StrDateDiffResult"); -// StringReference StrDateDiffResultAutomationName(L"StrDateDiffResultAutomationName"); -// StringReference StrDateDiffResultInDays(L"StrDateDiffResultInDays"); -// StringReference StrDateResult(L"StrDateResult"); -// StringReference StrDateResultAutomationName(L"StrDateResultAutomationName"); -// StringReference IsDiffInDays(L"IsDiffInDays"); -// } -// */ - -// [Bindable] -// public sealed class DateCalculatorViewModel : INotifyPropertyChanged -// { -// // Private members -// private DateCalculationEngine m_dateCalcEngine; -// private DateUnit m_daysOutputFormat; -// private DateUnit m_allDateUnitsOutputFormat; -// private DateTimeFormatter m_dateTimeFormatter; -// private string m_listSeparator; - -// private bool m_isOutOfBound; -// private Vector m_offsetValues; -// private DateTime m_fromDate; -// private DateTime m_toDate; -// private DateTime m_startDate; -// private DateTime m_dateResult; -// private DateDifference m_dateDiffResult; -// private DateDifference m_dateDiffResultInDays; - -// public DateCalculatorViewModel() -// { -// m_IsDateDiffMode = true; -// m_IsAddMode = true; -// m_isOutOfBound = false; -// m_DaysOffset = 0; -// m_MonthsOffset = 0; -// m_YearsOffset = 0; -// m_StrDateDiffResult = ""; -// m_StrDateDiffResultAutomationName = ""; -// m_StrDateDiffResultInDays = ""; -// m_StrDateResult = ""; -// m_StrDateResultAutomationName = ""; -// m_fromDate = 0; -// m_toDate = 0; -// m_startDate = 0; -// m_dateResult = 0; - -// var localizationSettings = LocalizationSettings.GetInstance(); - -// // Initialize Date Output format instances -// InitializeDateOutputFormats(localizationSettings.GetCalendarIdentifier()); - -// // Initialize Date Calc engine -// m_dateCalcEngine = make_shared(localizationSettings.GetCalendarIdentifier()); - -// // Initialize dates of DatePicker controls to today's date -// var calendar = new Calendar(); -// var 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 = new String((localizationSettings.GetListSeparator() + " ").ToString()); - -// // Initialize the output results -// UpdateDisplayResult(); - -// m_offsetValues = new Vector (); - -// for (int i = 0; i <= c_maxOffsetValue; i++) -// { -// // MAX? -// string numberStr = i.ToString(); -// localizationSettings.LocalizeDisplayValue(numberStr); -// m_offsetValues.Append(numberStr.ToString()); -// } - -// /* 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) -// { -// // MAX? -// calendar.SetDateTime(today); -// TraceLogger.GetInstance().LogDateClippedTimeDifferenceFound( -// from_cx(calendar), -// DateTime{ TimeSpan{ clippedTime.UniversalTime } }); -// } -// } - -// static void CheckClipTimeSameDay(Calendar reference) -// { - -// } - -// private bool IsOutOfBound -// { -// get { return m_isOutOfBound; } -// set -// { -// m_isOutOfBound = value; -// UpdateDisplayResult(); -// } -// } - -// private DateDifference DateDiffResult -// { -// get { return m_dateDiffResult; } -// set { -// m_dateDiffResult = value; -// UpdateDisplayResult(); -// } -// } - -// private DateDifference DateDiffResultInDays -// { -// get { return m_dateDiffResultInDays; } -// set -// { -// m_dateDiffResultInDays = value; -// UpdateDisplayResult(); -// } -// } - -// private DateTime DateResult -// { -// get { return m_dateResult; } -// set { -// m_dateResult = value; -// UpdateDisplayResult(); -// } -// } - -// // PUBLIC - -// // Input Properties -// public string IsDateDiffMode { get; set; } -// public string IsAddMode { get; set; } -// public string IsDiffInDays { get; set; } // If diff is only in days or the dates are the same, -// // then show only one result and avoid redundancy - -// public string DaysOffset { get; set; } -// public string MonthsOffset { get; set; } -// public string YearsOffset { get; set; } - -// // Read only property for offset values -// public IVector OffsetValues -// { -// get { return m_offsetValues; } -// } - -// // From date for Date Diff -// public DateTime FromDate -// { -// DateTime get() { return m_fromDate; } -// set -// { -// if (m_fromDate.UniversalTime != value.UniversalTime) -// { -// m_fromDate = value; -// RaisePropertyChanged("FromDate"); -// } -// } -// } - -// // To date for Date Diff -// public DateTime ToDate -// { -// get { return m_toDate; } -// set -// { -// if (m_toDate.UniversalTime != value.UniversalTime) -// { -// m_toDate = value; -// RaisePropertyChanged("ToDate"); -// } -// } -// } - -// // Start date for Add/Subtract date -// public DateTime StartDate -// { -// get { return m_startDate; } -// set -// { -// if (m_startDate.UniversalTime != value.UniversalTime) -// { -// m_startDate = value; -// RaisePropertyChanged("StartDate"); -// } -// } -// } - -// // Output Properties -// public string StrDateDiffResult { get; set; } - -// public string StrDateDiffResultAutomationName { get; set; } - -// public string StrDateDiffResultInDays { get; set; } - -// public string StrDateResult { get; set; } - -// public string StrDateResultAutomationName { get; set; } - -// public void OnCopyCommand(Object parameter) -// { - -// } - -// // PRIVATE -// private void OnPropertyChanged(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(); -// } -// } - -// private void 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; -// } -// } -// } - -// private void UpdateDisplayResult() -// { -// if (m_IsDateDiffMode) -// { -// // Are to and from dates the same -// if (m_dateDiffResultInDays.day == 0) -// { -// IsDiffInDays = true; -// StrDateDiffResultInDays = ""; -// StrDateDiffResult = AppResourceProvider.GetInstance().GetResourceString("Date_SameDates"); -// } -// else if ((m_dateDiffResult.year == 0) && -// (m_dateDiffResult.month == 0) && -// (m_dateDiffResult.week == 0)) -// { -// IsDiffInDays = true; -// StrDateDiffResultInDays = ""; - -// // 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("Date_OutOfBoundMessage"); -// } -// else -// { -// // Display the resulting date in long format -// StrDateResult = m_dateTimeFormatter.Format(DateResult); -// } -// } -// } - -// private void UpdateStrDateDiffResultAutomationName() -// { -// String automationFormat = AppResourceProvider.GetInstance().GetResourceString("Date_DifferenceResultAutomationName"); -// String localizedAutomationName = LocalizationStringUtil.GetLocalizedString(automationFormat->Data(), StrDateDiffResult.Data()); - -// StrDateDiffResultAutomationName = new String(localizedAutomationName.ToString()); -// } - -// private void UpdateStrDateResultAutomationName() -// { -// String automationFormat = AppResourceProvider.GetInstance().GetResourceString("Date_ResultingDateAutomationName"); -// String localizedAutomationName = LocalizationStringUtil.GetLocalizedString(automationFormat.Data(), StrDateResult.Data()); - -// StrDateResultAutomationName = new String(localizedAutomationName.ToString()); -// } - -// private void InitializeDateOutputFormats(string calendarIdentifer) -// { -// // Format for Add/Subtract days -// m_dateTimeFormatter = LocalizationService.GetRegionalSettingsAwareDateTimeFormatter( -// "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; -// } - -// private string GetDateDiffString() -// { -// String result = ""; -// bool addDelimiter = false; -// AppResourceProvider resourceLoader = AppResourceProvider.GetInstance(); - -// var yearCount = m_dateDiffResult.year; - -// if (yearCount > 0) -// { -// result = String.Concat(GetLocalizedNumberString(yearCount), " "); - -// if (yearCount > 1) -// { -// result = String.Concat(result, resourceLoader.GetResourceString("Date_Years")); -// } -// else -// { -// result = String.Concat(result, resourceLoader.GetResourceString("Date_Year")); -// } - -// // set the flags to add a delimiter whenever the next unit is added -// addDelimiter = true; -// } - -// var 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), " ")); - -// if (monthCount > 1) -// { -// result = String.Concat(result, resourceLoader.GetResourceString("Date_Months")); -// } -// else -// { -// result = String.Concat(result, resourceLoader.GetResourceString("Date_Month")); -// } -// } - -// var 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), " ")); - -// if (weekCount > 1) -// { -// result = String.Concat(result, resourceLoader.GetResourceString("Date_Weeks")); -// } -// else -// { -// result = String.Concat(result, resourceLoader.GetResourceString("Date_Week")); -// } -// } - -// var 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), " ")); - -// if (dayCount > 1) -// { -// result = String.Concat(result, resourceLoader.GetResourceString("Date_Days")); -// } -// else -// { -// result = String.Concat(result, resourceLoader.GetResourceString("Date_Day")); -// } -// } - -// return result; -// } - -// private string GetDateDiffStringInDays() -// { -// String strDateUnit; - -// // Display the result as '1 day' or 'N days' -// if (m_dateDiffResultInDays.day > 1) -// { -// strDateUnit = AppResourceProvider.GetInstance().GetResourceString("Date_Days"); -// } -// else -// { -// strDateUnit = AppResourceProvider.GetInstance().GetResourceString("Date_Day"); -// } - -// return String.Concat(GetLocalizedNumberString(m_dateDiffResultInDays.day), String.Concat(" ", strDateUnit)); -// } - -// private string GetLocalizedNumberString(int value) -// { -// string numberStr = value.ToString(); -// LocalizationSettings.GetInstance().LocalizeDisplayValue(numberStr); - -// return numberStr.ToString(); -// } - -// private static DateTime ClipTime(DateTime dateTime) -// { -// var calendar = new Calendar(); -// calendar.SetDateTime(dateTime); -// calendar.Period = 1; -// calendar.Hour = 12; -// calendar.Minute = 0; -// calendar.Second = 0; -// calendar.Nanosecond = 0; - -// return calendar.GetDateTime(); -// } - -// // MAX? -// private static void CheckClipTimeSameDay(Calendar reference) -// { - -// } -// } -//} \ No newline at end of file +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using Windows.UI.Xaml.Data; +using Windows.Globalization; +using CalculatorApp.Common; +using Windows.Globalization.DateTimeFormatting; +using CalculatorApp.Common.DateCalculation; +using Windows.Foundation; +using System.Numerics; +using CalculatorApp; + +namespace CalculatorApp.ViewModel +{ + [Windows.UI.Xaml.Data.Bindable] + public sealed class DateCalculatorViewModel : INotifyPropertyChanged + { + const int c_maxOffsetValue = 999; + + const string StrDateDiffResultPropertyName="StrDateDiffResult"; + const string StrDateDiffResultAutomationNamePropertyName="StrDateDiffResultAutomationName"; + const string StrDateDiffResultInDaysPropertyName="StrDateDiffResultInDays"; + const string StrDateResultPropertyName="StrDateResult"; + const string StrDateResultAutomationNamePropertyName="StrDateResultAutomationName"; + const string IsDiffInDaysPropertyName="IsDiffInDays"; + + public event PropertyChangedEventHandler PropertyChanged; + private void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName]string p = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p)); + + // Private members + private DateCalculationEngine m_dateCalcEngine; + private DateUnit m_daysOutputFormat; + private DateUnit m_allDateUnitsOutputFormat; + private DateTimeFormatter m_dateTimeFormatter; + private string m_listSeparator; + + private CalculatorList m_offsetValues; + private DateTime m_fromDate; + private DateTime m_toDate; + private DateTime m_startDate; + private DateTime m_dateResult; + private DateDifference m_dateDiffResult; + private DateDifference m_dateDiffResultInDays; + + private bool m_IsDateDiffMode = true; + private bool m_IsAddMode = true; + private bool m_IsDiffInDays = true; + private bool m_isOutOfBound = false; + private int m_DaysOffset = 0; + private int m_MonthsOffset = 0; + private int m_YearsOffset = 0; + private string m_StrDateDiffResult = ""; + private string m_StrDateDiffResultAutomationName = ""; + private string m_StrDateDiffResultInDays = ""; + private string m_StrDateResult = ""; + private string m_StrDateResultAutomationName = ""; + + public DateCalculatorViewModel() + { + var localizationSettings = LocalizationSettings.GetInstance(); + + // Initialize Date Output format instances + InitializeDateOutputFormats(localizationSettings.GetCalendarIdentifier()); + + // Initialize Date Calc engine + m_dateCalcEngine = new DateCalculationEngine(localizationSettings.GetCalendarIdentifier()); + + // Initialize dates of DatePicker controls to today's date + var calendar = new Calendar(); + var today = calendar.GetDateTime().DateTime; + + // 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 = localizationSettings.GetListSeparator() + " "; + + // Initialize the output results + UpdateDisplayResult(); + + m_offsetValues = new CalculatorList(); + + for (int i = 0; i <= c_maxOffsetValue; i++) + { + // MAX? + string numberStr = i.ToString(); + localizationSettings.LocalizeDisplayValue(ref numberStr); + m_offsetValues.Append(numberStr.ToString()); + } + + /* 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. */ + Windows.Globalization.DayOfWeek trueDayOfWeek = calendar.DayOfWeek; + + DateTime clippedTime = ClipTime(today); + calendar.SetDateTime(clippedTime); + + if (calendar.DayOfWeek != trueDayOfWeek) + { + // MAX? + calendar.SetDateTime(today); + TraceLogger.GetInstance().LogDateClippedTimeDifferenceFound( + calendar, + clippedTime.ToUniversalTime()); + } + + PropertyChanged += (snd, e) => ((DateCalculatorViewModel)snd).OnPropertyChanged(e.PropertyName); + } + + static void CheckClipTimeSameDay(Calendar reference) + { + + } + + private bool IsOutOfBound + { + get { return m_isOutOfBound; } + set + { + m_isOutOfBound = value; + UpdateDisplayResult(); + } + } + + private DateDifference DateDiffResult + { + get { return m_dateDiffResult; } + set { + m_dateDiffResult = value; + UpdateDisplayResult(); + } + } + + private DateDifference DateDiffResultInDays + { + get { return m_dateDiffResultInDays; } + set + { + m_dateDiffResultInDays = value; + UpdateDisplayResult(); + } + } + + private DateTime DateResult + { + get { return m_dateResult; } + set { + m_dateResult = value; + UpdateDisplayResult(); + } + } + + // PUBLIC + + // Input Properties + public bool IsDateDiffMode + { + get => m_IsDateDiffMode; + set + { + if (m_IsDateDiffMode != value) + { + m_IsDateDiffMode = value; + RaisePropertyChanged(); + } + } + } + public bool IsAddMode + { + get => m_IsAddMode; + set + { + if (m_IsAddMode != value) + { + m_IsAddMode = value; + RaisePropertyChanged(); + } + } + } + public bool IsDiffInDays + { + get => m_IsDiffInDays; + private set + { + if (m_IsDiffInDays != value) + { + m_IsDiffInDays = value; + RaisePropertyChanged(); + } + } + } // If diff is only in days or the dates are the same, + // then show only one result and avoid redundancy + + public int DaysOffset + { + get => m_DaysOffset; + set + { + if (m_DaysOffset != value) + { + m_DaysOffset = value; + RaisePropertyChanged(); + } + } + } + + public int MonthsOffset + { + get => m_MonthsOffset; + set + { + if (m_MonthsOffset != value) + { + m_MonthsOffset = value; + RaisePropertyChanged(); + } + } + } + + public int YearsOffset + { + get => m_YearsOffset; + set + { + if (m_YearsOffset != value) + { + m_YearsOffset = value; + RaisePropertyChanged(); + } + } + } + + // Read only property for offset values + public CalculatorList OffsetValues + { + get { return m_offsetValues; } + } + + // From date for Date Diff + public DateTime FromDate + { + get { return m_fromDate; } + set + { + if (m_fromDate.ToUniversalTime() != value.ToUniversalTime()) + { + m_fromDate = value; + RaisePropertyChanged("FromDate"); + } + } + } + + // To date for Date Diff + public DateTime ToDate + { + get { return m_toDate; } + set + { + if (m_toDate.ToUniversalTime() != value.ToUniversalTime()) + { + m_toDate = value; + RaisePropertyChanged("ToDate"); + } + } + } + + // Start date for Add/Subtract date + public DateTime StartDate + { + get { return m_startDate; } + set + { + if (m_startDate.ToUniversalTime() != value.ToUniversalTime()) + { + m_startDate = value; + RaisePropertyChanged("StartDate"); + } + } + } + + // Output Properties + public string StrDateDiffResult + { + get => m_StrDateDiffResult; + private set + { + if (m_StrDateDiffResult != value) + { + m_StrDateDiffResult = value; + RaisePropertyChanged(); + } + } + } + + public string StrDateDiffResultAutomationName + { + get => m_StrDateDiffResultAutomationName; + private set + { + if (m_StrDateDiffResultAutomationName != value) + { + m_StrDateDiffResultAutomationName = value; + RaisePropertyChanged(); + } + } + } + + public string StrDateDiffResultInDays + { + get => m_StrDateDiffResultInDays; + private set + { + if (m_StrDateDiffResultInDays != value) + { + m_StrDateDiffResultInDays = value; + RaisePropertyChanged(); + } + } + } + + public string StrDateResult + { + get => m_StrDateResult; + private set + { + if (m_StrDateResult != value) + { + m_StrDateResult = value; + RaisePropertyChanged(); + } + } + } + + public string StrDateResultAutomationName + { + get => m_StrDateResultAutomationName; + private set + { + if (m_StrDateResultAutomationName != value) + { + m_StrDateResultAutomationName = value; + RaisePropertyChanged(); + } + } + } + + public void OnCopyCommand(Object parameter) + { + + } + + // PRIVATE + private void OnPropertyChanged(string prop) + { + if (prop == StrDateDiffResultPropertyName) + { + UpdateStrDateDiffResultAutomationName(); + } + else if (prop == StrDateResultPropertyName) + { + UpdateStrDateResultAutomationName(); + } + else if ( + prop != StrDateDiffResultAutomationNamePropertyName + && prop != StrDateDiffResultInDaysPropertyName + && prop != StrDateResultAutomationNamePropertyName + && prop != IsDiffInDaysPropertyName) + { + OnInputsChanged(); + } + } + + private void OnInputsChanged() + { + DateDifference dateDiff = new DateDifference(); + + if (m_IsDateDiffMode) + { + DateTime clippedFromDate = ClipTime(FromDate); + DateTime clippedToDate = ClipTime(ToDate); + + // Calculate difference between two dates + m_dateCalcEngine.GetDateDifference(clippedFromDate, clippedToDate, m_allDateUnitsOutputFormat, out dateDiff); + DateDiffResult = dateDiff; + + m_dateCalcEngine.GetDateDifference(clippedFromDate, clippedToDate, m_daysOutputFormat, out 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, out dateTimeResult); + } + else + { + // Subtract number of Days, Months and Years from a Date + IsOutOfBound = !m_dateCalcEngine.SubtractDuration(StartDate, dateDiff, out dateTimeResult); + } + + if (!m_isOutOfBound) + { + DateResult = dateTimeResult; + } + } + } + + private void UpdateDisplayResult() + { + if (m_IsDateDiffMode) + { + // Are to and from dates the same + if (m_dateDiffResultInDays.day == 0) + { + IsDiffInDays = true; + StrDateDiffResultInDays = ""; + StrDateDiffResult = AppResourceProvider.GetInstance().GetResourceString("Date_SameDates"); + } + else if ((m_dateDiffResult.year == 0) && + (m_dateDiffResult.month == 0) && + (m_dateDiffResult.week == 0)) + { + IsDiffInDays = true; + StrDateDiffResultInDays = ""; + + // 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("Date_OutOfBoundMessage"); + } + else + { + // Display the resulting date in long format + StrDateResult = m_dateTimeFormatter.Format(DateResult); + } + } + } + + private void UpdateStrDateDiffResultAutomationName() + { + String automationFormat = AppResourceProvider.GetInstance().GetResourceString("Date_DifferenceResultAutomationName"); + String localizedAutomationName = LocalizationStringUtil.GetLocalizedString(automationFormat, StrDateDiffResult); + + StrDateDiffResultAutomationName = localizedAutomationName.ToString(); + } + + private void UpdateStrDateResultAutomationName() + { + String automationFormat = AppResourceProvider.GetInstance().GetResourceString("Date_ResultingDateAutomationName"); + String localizedAutomationName = LocalizationStringUtil.GetLocalizedString(automationFormat, StrDateResult); + + StrDateResultAutomationName = localizedAutomationName.ToString(); + } + + private void InitializeDateOutputFormats(string calendarIdentifier) + { + // Format for Add/Subtract days + m_dateTimeFormatter = LocalizationService.GetRegionalSettingsAwareDateTimeFormatter( + "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; + } + + private string GetDateDiffString() + { + String result = ""; + bool addDelimiter = false; + AppResourceProvider resourceLoader = AppResourceProvider.GetInstance(); + + var yearCount = m_dateDiffResult.year; + + if (yearCount > 0) + { + result = String.Concat(GetLocalizedNumberString(yearCount), " "); + + if (yearCount > 1) + { + result = String.Concat(result, resourceLoader.GetResourceString("Date_Years")); + } + else + { + result = String.Concat(result, resourceLoader.GetResourceString("Date_Year")); + } + + // set the flags to add a delimiter whenever the next unit is added + addDelimiter = true; + } + + var 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), " ")); + + if (monthCount > 1) + { + result = String.Concat(result, resourceLoader.GetResourceString("Date_Months")); + } + else + { + result = String.Concat(result, resourceLoader.GetResourceString("Date_Month")); + } + } + + var 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), " ")); + + if (weekCount > 1) + { + result = String.Concat(result, resourceLoader.GetResourceString("Date_Weeks")); + } + else + { + result = String.Concat(result, resourceLoader.GetResourceString("Date_Week")); + } + } + + var 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), " ")); + + if (dayCount > 1) + { + result = String.Concat(result, resourceLoader.GetResourceString("Date_Days")); + } + else + { + result = String.Concat(result, resourceLoader.GetResourceString("Date_Day")); + } + } + + return result; + } + + private string GetDateDiffStringInDays() + { + String strDateUnit; + + // Display the result as '1 day' or 'N days' + if (m_dateDiffResultInDays.day > 1) + { + strDateUnit = AppResourceProvider.GetInstance().GetResourceString("Date_Days"); + } + else + { + strDateUnit = AppResourceProvider.GetInstance().GetResourceString("Date_Day"); + } + + return String.Concat(GetLocalizedNumberString(m_dateDiffResultInDays.day), String.Concat(" ", strDateUnit)); + } + + private string GetLocalizedNumberString(int value) + { + string numberStr = value.ToString(); + LocalizationSettings.GetInstance().LocalizeDisplayValue(ref numberStr); + + return numberStr.ToString(); + } + + private static DateTime ClipTime(DateTime dateTime) + { + var calendar = new Calendar(); + calendar.SetDateTime(dateTime); + calendar.Period = 1; + calendar.Hour = 12; + calendar.Minute = 0; + calendar.Second = 0; + calendar.Nanosecond = 0; + + return calendar.GetDateTime().DateTime; + } + } +} diff --git a/src/Calculator.Shared/Views/Calculator.xaml.cs b/src/Calculator.Shared/Views/Calculator.xaml.cs index 422c3775..d774e013 100644 --- a/src/Calculator.Shared/Views/Calculator.xaml.cs +++ b/src/Calculator.Shared/Views/Calculator.xaml.cs @@ -553,7 +553,7 @@ namespace CalculatorApp FullscreenFlyoutClosed(); } - void CloseHistoryFlyout() + public void CloseHistoryFlyout() { if (m_fIsHistoryFlyoutOpen) { @@ -561,7 +561,7 @@ namespace CalculatorApp } } - void CloseMemoryFlyout() + public void CloseMemoryFlyout() { if (m_fIsMemoryFlyoutOpen) { diff --git a/src/Calculator.Shared/Views/DateCalculator.xaml b/src/Calculator.Shared/Views/DateCalculator.xaml index 31d9b822..4f513fde 100644 --- a/src/Calculator.Shared/Views/DateCalculator.xaml +++ b/src/Calculator.Shared/Views/DateCalculator.xaml @@ -5,11 +5,10 @@ xmlns:converters="using:CalculatorApp.Converters" xmlns:local="using:CalculatorApp.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + Loaded="OnLoaded" mc:Ignorable=""> - - - - + - + - --> - + - + - --> - + @@ -543,14 +542,14 @@ - --> - + - --> - + - --> - + - + - --> - --> + - + - + - --> + @@ -688,7 +689,7 @@ Margin="0,0,0,6" Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}" TextWrapping="Wrap" /> - + - + - + - + - + - --> + @@ -748,5 +750,5 @@ ContextFlyout="{StaticResource ResultsContextMenu}" Text="{Binding StrDateResult}" /> - --> + diff --git a/src/Calculator.Shared/Views/DateCalculator.xaml.cs b/src/Calculator.Shared/Views/DateCalculator.xaml.cs index c8fa6365..272ced89 100644 --- a/src/Calculator.Shared/Views/DateCalculator.xaml.cs +++ b/src/Calculator.Shared/Views/DateCalculator.xaml.cs @@ -1,17 +1,19 @@ -using System; -using System.Collections.Generic; -using System.IO; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Foundation; -using Windows.Foundation.Collections; +using Windows.Globalization; +using Windows.Globalization.DateTimeFormatting; +using Windows.UI.Core; +using Windows.UI.ViewManagement; using Windows.UI.Xaml; +using Windows.UI.Xaml.Automation; +using Windows.UI.Xaml.Automation.Peers; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Navigation; +using CalculatorApp.Common; +using CalculatorApp.ViewModel; // The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 @@ -19,9 +21,207 @@ namespace CalculatorApp { public sealed partial class DateCalculator : UserControl { + // We choose 2550 as the max year because CalendarDatePicker experiences clipping + // issues just after 2558. We would like 9999 but will need to wait for a platform + // fix before we use a higher max year. This platform issue is tracked by + // TODO: MSFT-9273247 + const int c_maxYear = 2550; + const int c_minYear = 1601; + public DateCalculator() { this.InitializeComponent(); + + var localizationSettings = LocalizationSettings.GetInstance(); + + // Set Calendar Identifier + DateDiff_FromDate.CalendarIdentifier = localizationSettings.GetCalendarIdentifier(); + DateDiff_ToDate.CalendarIdentifier = localizationSettings.GetCalendarIdentifier(); + + // Setting the FirstDayofWeek + DateDiff_FromDate.FirstDayOfWeek = localizationSettings.GetFirstDayOfWeek(); + DateDiff_ToDate.FirstDayOfWeek = localizationSettings.GetFirstDayOfWeek(); + + // Setting the Language explicitly is not required, + // this is a workaround for the bug in the control due to which + // the displayed date is incorrect for non Gregorian Calendar Systems + // The displayed date doesn't honor the shortdate format, on setting the Language the format is refreshed + DateDiff_FromDate.Language = localizationSettings.GetLocaleName(); + DateDiff_ToDate.Language = localizationSettings.GetLocaleName(); + + // Set Min and Max Dates according to the Gregorian Calendar(1601 & 9999) + var calendar = new Calendar(); + var today = calendar.GetDateTime(); + + calendar.ChangeCalendarSystem(CalendarIdentifiers.Gregorian); + calendar.Day = 1; + calendar.Month = 1; + calendar.Year = c_minYear; + var minYear = calendar.GetDateTime(); // 1st January, 1601 + DateDiff_FromDate.MinDate = minYear; + DateDiff_ToDate.MinDate = minYear; + + calendar.Day = 31; + calendar.Month = 12; + calendar.Year = c_maxYear; + var maxYear = calendar.GetDateTime(); // 31st December, 9878 + DateDiff_FromDate.MaxDate = maxYear; + DateDiff_ToDate.MaxDate = maxYear; + + // Set the PlaceHolderText for CalendarDatePicker + DateTimeFormatter dateTimeFormatter = LocalizationService.GetRegionalSettingsAwareDateTimeFormatter( + "day month year", + localizationSettings.GetCalendarIdentifier(), + ClockIdentifiers.TwentyFourHour); // Clock Identifier is not used + + DateDiff_FromDate.DateFormat = "day month year"; + DateDiff_ToDate.DateFormat = "day month year"; + + var placeholderText = dateTimeFormatter.Format(today); + + DateDiff_FromDate.PlaceholderText = placeholderText; + DateDiff_ToDate.PlaceholderText = placeholderText; + + // TODO UNO + // CopyMenuItem.Text = AppResourceProvider.GetInstance().GetResourceString("copyMenuItem"); + DateCalculationOption.SelectionChanged += new SelectionChangedEventHandler(DateCalcOption_Changed); + } + + void FromDate_DateChanged( CalendarDatePicker sender, CalendarDatePickerDateChangedEventArgs e) + { + if (e.NewDate != null) + { + var dateCalcViewModel = (DateCalculatorViewModel)this.DataContext; + dateCalcViewModel.FromDate = e.NewDate.Value.DateTime; + TraceLogger.GetInstance().LogDateDifferenceModeUsed(ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread())); + } + else + { + ReselectCalendarDate(sender, e.OldDate.Value.DateTime); + } + } + + void ToDate_DateChanged( CalendarDatePicker sender, CalendarDatePickerDateChangedEventArgs e) + { + if (e.NewDate != null) + { + var dateCalcViewModel = (DateCalculatorViewModel)this.DataContext; + dateCalcViewModel.ToDate = e.NewDate.Value.DateTime; + TraceLogger.GetInstance().LogDateDifferenceModeUsed(ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread())); + } + else + { + ReselectCalendarDate(sender, e.OldDate.Value.DateTime); + } + } + + void AddSubtract_DateChanged( CalendarDatePicker sender, CalendarDatePickerDateChangedEventArgs e) + { + if (e.NewDate != null) + { + var dateCalcViewModel = (DateCalculatorViewModel)this.DataContext; + dateCalcViewModel.StartDate = e.NewDate.Value.DateTime; + TraceLogger.GetInstance().LogDateAddSubtractModeUsed( + ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread()), dateCalcViewModel.IsAddMode); + } + else + { + ReselectCalendarDate(sender, e.OldDate.Value.DateTime); + } + } + + void OffsetValue_Changed( object sender, SelectionChangedEventArgs e) + { + var dateCalcViewModel = (DateCalculatorViewModel)this.DataContext; + TraceLogger.GetInstance().LogDateAddSubtractModeUsed( + ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread()), dateCalcViewModel.IsAddMode); + } + + void OnCopyMenuItemClicked( object sender, RoutedEventArgs e) + { + // TODO UNO + //var calcResult = (TextBlock)ResultsContextMenu.Target; + + //CopyPasteManager.CopyToClipboard(calcResult.Text); + } + + void OnLoaded( object sender, Windows.UI.Xaml.RoutedEventArgs e) + { + } + + public void CloseCalendarFlyout() + { + if (DateDiff_FromDate.IsCalendarOpen) + { + DateDiff_FromDate.IsCalendarOpen = false; + } + + if (DateDiff_ToDate.IsCalendarOpen) + { + DateDiff_ToDate.IsCalendarOpen = false; + } + + if ((AddSubtract_FromDate != null) && (AddSubtract_FromDate.IsCalendarOpen)) + { + AddSubtract_FromDate.IsCalendarOpen = false; + } + } + + public void SetDefaultFocus() + { + DateCalculationOption.Focus(FocusState.Programmatic); + } + + void DateCalcOption_Changed( object sender, Windows.UI.Xaml.Controls.SelectionChangedEventArgs e) + { + FindName("AddSubtractDateGrid"); + DateCalculationOption.SelectionChanged -= DateCalcOption_Changed; + } + + void AddSubtractDateGrid_Loaded( object sender, Windows.UI.Xaml.RoutedEventArgs e) + { + var localizationSettings = LocalizationSettings.GetInstance(); + + AddSubtract_FromDate.PlaceholderText = DateDiff_FromDate.PlaceholderText; + AddSubtract_FromDate.CalendarIdentifier = localizationSettings.GetCalendarIdentifier(); + AddSubtract_FromDate.FirstDayOfWeek = localizationSettings.GetFirstDayOfWeek(); + AddSubtract_FromDate.Language = localizationSettings.GetLocaleName(); + + AddSubtract_FromDate.MinDate = DateDiff_FromDate.MinDate; + AddSubtract_FromDate.MaxDate = DateDiff_FromDate.MaxDate; + AddSubtract_FromDate.DateFormat = "day month year"; + } + + void ReselectCalendarDate( Windows.UI.Xaml.Controls.CalendarDatePicker calendarDatePicker, DateTime dateTime) + { + // Reselect the unselected Date + calendarDatePicker.Date = dateTime; + + // Dismiss the Calendar flyout + calendarDatePicker.IsCalendarOpen = false; + } + + void OffsetDropDownClosed( object sender, object e) + { + RaiseLiveRegionChangedAutomationEvent(/* DateDiff mode */ false); + } + + void CalendarFlyoutClosed( object sender, object e) + { + var dateCalcViewModel = (DateCalculatorViewModel)this.DataContext; + RaiseLiveRegionChangedAutomationEvent(dateCalcViewModel.IsDateDiffMode); + } + + void RaiseLiveRegionChangedAutomationEvent( bool isDateDiffMode) + { + TextBlock resultTextBlock = (isDateDiffMode ? DateDiffAllUnitsResultLabel : DateResultLabel); + String automationName = AutomationProperties.GetName(resultTextBlock); + TextBlockAutomationPeer.FromElement(resultTextBlock).RaiseAutomationEvent(AutomationEvents.LiveRegionChanged); + } + + void AddSubtractOption_Checked( object sender, RoutedEventArgs e) + { + RaiseLiveRegionChangedAutomationEvent(/* DateDiff mode */ false); } } } diff --git a/src/Calculator.Shared/Views/MainPage.xaml.cs b/src/Calculator.Shared/Views/MainPage.xaml.cs index c40dbd8d..f10658e0 100644 --- a/src/Calculator.Shared/Views/MainPage.xaml.cs +++ b/src/Calculator.Shared/Views/MainPage.xaml.cs @@ -284,19 +284,18 @@ namespace CalculatorApp { m_calculator.SetDefaultFocus(); } + if (m_dateCalculator != null && m_dateCalculator.Visibility == Visibility.Visible) + { + m_dateCalculator.SetDefaultFocus(); + } + // UNO TODO + //if (m_converter != null && m_converter.Visibility == Visibility.Visible) + //{ + // m_converter.SetDefaultFocus(); + //} + } - // UNO TODO - //if (m_dateCalculator != null && m_dateCalculator.Visibility == Visibility.Visible) - //{ - // m_dateCalculator.SetDefaultFocus(); - //} - //if (m_converter != null && m_converter.Visibility == Visibility.Visible) - //{ - // m_converter.SetDefaultFocus(); - //} - } - - void EnsureCalculator() + void EnsureCalculator() { if (m_calculator == null) { @@ -336,23 +335,22 @@ namespace CalculatorApp void EnsureDateCalculator() { - // UNO TODO - //if (m_dateCalculator == null) - //{ - // // delay loading converter - // m_dateCalculator = new DateCalculator(); - // m_dateCalculator.Name = "dateCalculator"; - // m_dateCalculator.DataContext = m_model.DateCalcViewModel; + if (m_dateCalculator == null) + { + // delay loading converter + m_dateCalculator = new DateCalculator(); + m_dateCalculator.Name = "dateCalculator"; + m_dateCalculator.DataContext = m_model.DateCalcViewModel; - // DateCalcHolder.Child = m_dateCalculator; - //} + DateCalcHolder.Child = m_dateCalculator; + } - //if (m_calculator != null) - //{ - // m_calculator.CloseHistoryFlyout(); - // m_calculator.CloseMemoryFlyout(); - //} - } + if (m_calculator != null) + { + m_calculator.CloseHistoryFlyout(); + m_calculator.CloseMemoryFlyout(); + } + } void EnsureConverter() {