5
\$\begingroup\$

I'm trying to convert TimeSpan object to text that can be read like a sentence. e.g.

TimeSpan(2, 1, 0, 0) --> "2 days and an hour"

TimeSpan(1, 2, 1, 0) --> "A day, 2 hours and a minute"

Some more samples for conversion are in the 'TestCases' object under 'TimeSpanPrettyFormatterTests' class.

Is there any better way to do it than the one i suggested here?

Thanks

using System;
using JetBrains.Annotations;
using NUnit.Framework;

namespace Client.Tests
{

   [TestFixture]
   public class TimeSpanPrettyFormatterTests
   {
    [UsedImplicitly]
    static object[] TestCases =
        {
            new object[] { new TimeSpan(0, 0, 0, 0), string.Empty },

            new object[] { new TimeSpan(1, 0, 0, 0), "A day" },
            new object[] { new TimeSpan(2, 0, 0, 0), "2 days" },
            new object[] { new TimeSpan(0, 1, 0, 0), "An hour" },
            new object[] { new TimeSpan(1, 1, 0, 0), "A day and an hour" },
            new object[] { new TimeSpan(2, 1, 0, 0), "2 days and an hour" },
            new object[] { new TimeSpan(0, 2, 0, 0), "2 hours" },
            new object[] { new TimeSpan(1, 2, 0, 0), "A day and 2 hours" },
            new object[] { new TimeSpan(2, 2, 0, 0), "2 days and 2 hours" },

            new object[] { new TimeSpan(0, 0, 1, 0), "A minute" },
            new object[] { new TimeSpan(1, 0, 1, 0), "A day and a minute" },
            new object[] { new TimeSpan(2, 0, 1, 0), "2 days and a minute" },
            new object[] { new TimeSpan(0, 1, 1, 0), "An hour and a minute" },
            new object[] { new TimeSpan(1, 1, 1, 0), "A day, an hour and a minute" },
            new object[] { new TimeSpan(2, 1, 1, 0), "2 days, an hour and a minute" },
            new object[] { new TimeSpan(0, 2, 1, 0), "2 hours and a minute" },
            new object[] { new TimeSpan(1, 2, 1, 0), "A day, 2 hours and a minute" },
            new object[] { new TimeSpan(2, 2, 1, 0), "2 days, 2 hours and a minute" },

            new object[] { new TimeSpan(0, 0, 2, 0), "2 minutes" },
            new object[] { new TimeSpan(1, 0, 2, 0), "A day and 2 minutes" },
            new object[] { new TimeSpan(2, 0, 2, 0), "2 days and 2 minutes" },
            new object[] { new TimeSpan(0, 1, 2, 0), "An hour and 2 minutes" },
            new object[] { new TimeSpan(1, 1, 2, 0), "A day, an hour and 2 minutes" },
            new object[] { new TimeSpan(2, 1, 2, 0), "2 days, an hour and 2 minutes" },
            new object[] { new TimeSpan(0, 2, 2, 0), "2 hours and 2 minutes" },
            new object[] { new TimeSpan(1, 2, 2, 0), "A day, 2 hours and 2 minutes" },
            new object[] { new TimeSpan(2, 2, 2, 0), "2 days, 2 hours and 2 minutes" }
        };

        [Test, TestCaseSource("TestCases")]
        public void ParseTimespan_ShouldReturn(TimeSpan timeSpan, string expectedResult)
        {
            string formatedText = timeSpan.ToPrettyFormat();
            Assert.AreEqual(expectedResult, formatedText);
        }
    }

    public static class StringExtensions
    {
        public static string UppercaseFirst(this string s)
        {
            if (string.IsNullOrEmpty(s))
            {
                return string.Empty;
            }

            return char.ToUpper(s[0]) + s.Substring(1);
        }
    }

    public static class TimeSpanExtensions
    {
        public static bool HasDays(this TimeSpan timeSpan)
        {
            return timeSpan.Days != 0;
        }

        public static bool HasMinutes(this TimeSpan timeSpan)
        {
            return timeSpan.Minutes != 0;
        }

        public static bool HasHours(this TimeSpan timeSpan)
        {
            return timeSpan.Hours != 0;
        }

        public static string ToPrettyFormat(this TimeSpan timeSpan)
        {
            string result;

            if (timeSpan.HasDays())
            {
                if (timeSpan.HasHours())
                {
                    if (timeSpan.HasMinutes())
                    {
                        result = GetDays(timeSpan) + ", " + GetHours(timeSpan) + " and " + GetMinutes(timeSpan);
                    }
                    else
                    {
                        result = GetDays(timeSpan) + " and " + GetHours(timeSpan);
                    }
                }
                else
                {
                    result = GetDays(timeSpan) + (timeSpan.HasMinutes() ? " and " + GetMinutes(timeSpan) : string.Empty);
                }
            }
            else
            {
                if (timeSpan.HasHours())
                {
                    result = GetHours(timeSpan) + (timeSpan.HasMinutes() ? " and " + GetMinutes(timeSpan) : string.Empty);
                }
                else
                {
                    result = GetMinutes(timeSpan);
                }
            }
            return result.UppercaseFirst();
        }

        private static string GetMinutes(TimeSpan timeSpan)
        {
            if (timeSpan.Minutes == 0) return string.Empty;
            if (timeSpan.Minutes == 1) return "a minute";
            return timeSpan.Minutes + " minutes";
        }

        private static string GetHours(TimeSpan timeSpan)
        {
            if (timeSpan.Hours == 0) return string.Empty;
            if (timeSpan.Hours == 1) return "an hour";
            return timeSpan.Hours + " hours";
        }

        private static string GetDays(TimeSpan timeSpan)
        {
            if (timeSpan.Days == 0) return string.Empty;
            if (timeSpan.Days == 1) return "a day";
            return timeSpan.Days + " days";
        }
    }
}
\$\endgroup\$
6
\$\begingroup\$

ToPrettyFormat method can definitely be improved. Instead writing the conditional statement for all possible permutations of HasDays, HasHours and HasMinutes it's better to step back and define rules for resulting string:

  • string may consist of 3 parts (days, hours and minutes)
  • if there is 0 or one non-empty part - it is a final result
  • if there are 2 non-empty parts - there should be "and" between them
  • if there are 3 non-empty parts - first 2 should be separated by comma + "and" before last part. Note that we can actually generalize last two rules for N parts (months, weeks, etc) - instead of "3" we can say combine first N-1 parts with comma and add last one with "and"

As a result you can get a bit simplified code:

public static string ToPrettyFormat(this TimeSpan timeSpan)
{
    var dayParts = new[] { GetDays(timeSpan), GetHours(timeSpan), GetMinutes(timeSpan) }
        .Where(s => !string.IsNullOrEmpty(s))
        .ToArray();

    var numberOfParts = dayParts.Length;

    string result;
    if (numberOfParts < 2)
        result = dayParts.FirstOrDefault() ?? string.Empty;
    else
        result = string.Join(", ", dayParts, 0, numberOfParts - 1) + " and " + dayParts[numberOfParts - 1];

    return result.UppercaseFirst();
}
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.