File: System\Windows\DurationConverter.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel.Design.Serialization;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
 
namespace System.Windows
{
    /// <summary>
    /// Provides a type converter to convert from <see cref="Duration"/> to <see cref="string"/> and vice versa.
    /// </summary>
    public class DurationConverter : TypeConverter
    {
        /// <summary>
        /// Returns whether this class can convert specific <see cref="Type"/> into <see cref="Duration"/>.
        /// </summary>
        /// <param name="td">Context information used for conversion.</param>
        /// <param name="t">Type being evaluated for conversion.</param>
        /// <returns><see langword="true"/> if the given <paramref name="t"/> can be converted from, <see langword="false"/> otherwise.</returns>
        public override bool CanConvertFrom(ITypeDescriptorContext td, Type t)
        {
            return t == typeof(string);
        }
 
        /// <summary>
        /// Returns whether this class can convert specified value to <see cref="string"/>.
        /// </summary>
        /// <param name="context">Context information used for conversion.</param>
        /// <param name="destinationType">Type being evaluated for conversion.</param>
        /// <returns><see langword="true"/> if conversion to <see cref="string"/> is possible, <see langword="false"/> otherwise.</returns>
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return destinationType == typeof(InstanceDescriptor) || destinationType == typeof(string);
        }
 
        /// <summary>
        /// Converts <paramref name="value"/> of <see cref="string"/> type to its <see cref="Duration"/> representation.
        /// </summary>
        /// <param name="td">Context information used for conversion.</param>
        /// <param name="cultureInfo">The culture specifier to use.</param>
        /// <param name="value">The string to convert from.</param>
        /// <returns>A <see cref="Duration"/> representing the <see cref="string"/> specified by <paramref name="value"/>.</returns>
        public override object ConvertFrom(ITypeDescriptorContext td, CultureInfo cultureInfo, object value)
        {
            // In case value is not a string, we can try to check InstanceDescriptor or we just throw NotSupportedException
            if (value is not string stringValue)
                return base.ConvertFrom(td, cultureInfo, value);
 
            // Sanitize the input
            ReadOnlySpan<char> valueSpan = stringValue.AsSpan().Trim();
 
            // In case it is not a pre-defined value, we will try to parse the TimeSpan and if it throws,
            // we will catch it as TimeSpanConverter does and rethrow with the inner exception information.
            if (valueSpan.Equals("Automatic", StringComparison.Ordinal))
                return Duration.Automatic;
            else if (valueSpan.Equals("Forever", StringComparison.Ordinal))
                return Duration.Forever;
            else
                return ParseTimeSpan(valueSpan, cultureInfo);
        }
 
        /// <summary>
        /// Facilities parsing from <paramref name="valueSpan"/> to <see cref="TimeSpan"/> and initializes new <see cref="Duration"/> instance.
        /// </summary>
        /// <param name="valueSpan">The string to convert from.</param>
        /// <param name="cultureInfo">The culture specifier to use.</param>
        /// <returns>A newly initialized <see cref="Duration"/> instance from the <paramref name="valueSpan"/> string.</returns>
        /// <remarks>This function is decoupled from the <see cref="ConvertFrom(ITypeDescriptorContext, CultureInfo, object)"/> for performance reasons.</remarks>
        /// <exception cref="FormatException">Thrown when parsing of <paramref name="valueSpan"/> to <see cref="TimeSpan"/> instance fails.</exception>
        private static Duration ParseTimeSpan(ReadOnlySpan<char> valueSpan, CultureInfo cultureInfo)
        {
            try
            {
                return new Duration(TimeSpan.Parse(valueSpan, cultureInfo));
            }
            catch (FormatException e)
            {
                throw new FormatException($"{valueSpan} is not a valid value for {nameof(TimeSpan)}.", e);
            }
        }
 
        /// <summary>
        /// Converts a <paramref name="value"/> of <see cref="Duration"/> to its <see cref="string"/> representation.
        /// </summary>
        /// <param name="context">Context information used for conversion.</param>
        /// <param name="cultureInfo">The culture specifier to use, currently ignored during conversion.</param>
        /// <param name="value">Duration value to convert from.</param>
        /// <param name="destinationType">Type being evaluated for conversion.</param>
        /// <returns>A <see cref="string"/> representing the <see cref="Duration"/> specified by <paramref name="value"/>.</returns>
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cultureInfo, object value, Type destinationType)
        {
            ArgumentNullException.ThrowIfNull(destinationType);
 
            // Check that we actually support the conversion, NULL value results in string.Empty, others throw
            if (value is not Duration duration || (destinationType != typeof(InstanceDescriptor) && destinationType != typeof(string)))
                return base.ConvertTo(context, cultureInfo, value, destinationType);
 
            // The current behaviour produces culture-invariant result in case of a Duration with HasTimeSpan,
            // whereas ConvertFrom accepts localized input, provided the correct cultureInfo was supplied.
            if (destinationType == typeof(string))
                return duration.ToStringInvariant();
 
            // InstanceDescriptor reflection magic
            if (duration.HasTimeSpan)
            {
                MemberInfo mi = typeof(Duration).GetConstructor(new Type[] { typeof(TimeSpan) });
                return new InstanceDescriptor(mi, new object[] { duration.TimeSpan });
            }
            else if (duration == Duration.Forever)
            {
                MemberInfo mi = typeof(Duration).GetProperty("Forever");
                return new InstanceDescriptor(mi, null);
            }
            else
            {
                Debug.Assert(duration == Duration.Automatic); // Only other legal duration type
 
                MemberInfo mi = typeof(Duration).GetProperty("Automatic");
                return new InstanceDescriptor(mi, null);
            }
        }
    }
}