We have created an implementation of it, which also displays years, months and weeks.
A maximum of 2 units are displayed.
Since the number of days in a month are not always the same, there may be inaccuracies.
Examples:
- 1 day
 
- 2 weeks
 
- 1 week 3 days
 
- 1 year 1 month
 
- 1 year 1 week
 
- 1 year 1 day
 
- ...
 
Code:
/// <summary>
///     Format a <see cref="TimeSpan" /> to a human readable string.
/// </summary>
[NoReorder]
public static class TimeSpanHumanReadable
{
#pragma warning disable CS8618
    private static TimeValueClass TimeValue;
    private static StringBuilder  DateStringBuilder;
#pragma warning restore CS8618
    /// <summary>
    ///     Format the given <paramref name="timeSpan" /> to a human readable format.
    /// </summary>
    /// <param name="timeSpan">The value to format</param>
    /// <returns>The formatted value</returns>
    [Pure]
    public static string ToHumanReadableString(this TimeSpan timeSpan)
    {
        TimeValue         = new TimeValueClass(timeSpan);
        DateStringBuilder = new StringBuilder();
        ProcessTimeValue();
        return DateStringBuilder.ToString().Trim();
    }
    // ReSharper disable once CognitiveComplexity
    private static void ProcessTimeValue()
    {
        if (TimeValue.Years is not 0)
        {
            // 1 year
            AddYears();
            AddSpace();
            if (TimeValue.Months is not 0)
            {
                // 1 year 1 month
                AddMonths();
            }
            else if (TimeValue.Weeks is not 0)
            {
                // 1 year 1 week
                AddWeeks();
            }
            else
            {
                // 1 year 1 day
                AddDays();
            }
            return;
        }
        if (TimeValue.Months is not 0)
        {
            // 1 month
            AddMonths();
            AddSpace();
            if (TimeValue.Weeks is not 0)
            {
                // 1 month 1 week
                AddWeeks();
            }
            else if (TimeValue.Days is >= 3 and <= 6)
            {
                // 1 month 1 day
                AddDays();
            }
            return;
        }
        if (TimeValue.Weeks is not 0)
        {
            AddWeeks();
            AddSpace();
            AddDays();
            return;
        }
        if (TimeValue.Days is not 0)
        {
            AddDays();
            AddSpace();
            AddHours();
            return;
        }
        if (TimeValue.Hours is not 0)
        {
            AddHours();
            AddSpace();
            AddMinutes();
            return;
        }
        if (TimeValue.Minutes is not 0)
        {
            AddMinutes();
            AddSpace();
            AddSeconds();
            return;
        }
        if (TimeValue.Seconds is not 0)
        {
            AddSeconds();
            return;
        }
        if (TimeValue.Milliseconds is not 0)
        {
            AddMilliseconds();
            return;
        }
        DateStringBuilder.Append("000 ms");
    }
    private static void AddSpace()
    {
        DateStringBuilder.Append(' ');
    }
    private static void AddYears()
    {
        if (TimeValue.Years is 0)
        {
            return;
        }
        DateStringBuilder.Append(TimeValue.Years);
        DateStringBuilder.Append(TimeValue.Years is 1 ? " year" : " years");
    }
    private static void AddMonths()
    {
        if (TimeValue.Months is 0)
        {
            return;
        }
        DateStringBuilder.Append(TimeValue.Months);
        DateStringBuilder.Append(TimeValue.Months is 1 ? " month" : " months");
    }
    private static void AddWeeks()
    {
        if (TimeValue.Weeks is 0)
        {
            return;
        }
        DateStringBuilder.Append(TimeValue.Weeks);
        DateStringBuilder.Append(TimeValue.Weeks is 1 ? " week" : " weeks");
    }
    private static void AddDays()
    {
        if (TimeValue.Days is 0)
        {
            return;
        }
        DateStringBuilder.Append(TimeValue.Days);
        DateStringBuilder.Append(TimeValue.Days is 1 ? " day" : " days");
    }
    private static void AddHours()
    {
        if (TimeValue.Hours is 0)
        {
            return;
        }
        DateStringBuilder.Append(TimeValue.Hours);
        DateStringBuilder.Append(TimeValue.Hours is 1 ? " hour" : " hours");
    }
    private static void AddMinutes()
    {
        if (TimeValue.Minutes is 0)
        {
            return;
        }
        DateStringBuilder.Append(TimeValue.Minutes);
        DateStringBuilder.Append(" min");
    }
    private static void AddSeconds()
    {
        if (TimeValue.Seconds is 0)
        {
            return;
        }
        DateStringBuilder.Append(TimeValue.Seconds);
        DateStringBuilder.Append(" sec");
    }
    private static void AddMilliseconds()
    {
        if (TimeValue.Milliseconds is 0)
        {
            return;
        }
        DateStringBuilder.Append(TimeValue.Milliseconds.ToString().PadLeft(3, '0'));
        DateStringBuilder.Append(" ms");
    }
    /// <remarks>
    ///     With help from https://stackoverflow.com/a/21260317/1847143
    /// </remarks>
    private class TimeValueClass
    {
        private const double DaysPerMonth = 30.4375;
        private const double DaysPerWeek  = 7;
        private const double DaysPerYear  = 365;
        public int Days         { get; }
        public int Hours        { get; }
        public int Milliseconds { get; }
        public int Minutes      { get; }
        public int Months       { get; }
        public int Seconds      { get; }
        public int Weeks        { get; }
        public int Years { get; }
        public TimeValueClass(TimeSpan timeSpan)
        {
            // Calculate the span in days
            int days = timeSpan.Days;
            // 362 days == 11 months and 4 weeks. 4 weeks => 1 month and 12 months => 1 year. So we have to exclude this value
            bool has362Days = days % 362 == 0;
            // Calculate years
            int years = (int)(days / DaysPerYear);
            // Decrease remaining days
            days -= (int)(years * DaysPerYear);
            // Calculate months
            int months = (int)(days / DaysPerMonth);
            // Decrease remaining days
            days -= (int)(months * DaysPerMonth);
            // Calculate weeks
            int weeks = (int)(days / DaysPerWeek);
            // Decrease remaining days
            days -= (int)(weeks * DaysPerWeek);
            // 4 weeks is 1 month
            if (weeks is 4 && has362Days is false)
            {
                weeks = 0;
                months++;
                days -= (int)(weeks * DaysPerMonth);
            }
            // 12 months is 1 year
            if (months == 12)
            {
                months = 0;
                years++;
                days -= (int)(months * DaysPerMonth);
            }
            Years        = years;
            Months       = months;
            Weeks        = weeks;
            Days         = days;
            Hours        = timeSpan.Hours;
            Minutes      = timeSpan.Minutes;
            Seconds      = timeSpan.Seconds;
            Milliseconds = timeSpan.Milliseconds;
        }
    }
}
Unit Tests:
/// <summary>
///     Test class for <see cref="Utils.Data.TimeSpanHumanReadable.ToHumanReadableString" />
/// </summary>
[NoReorder]
[TestFixture]
public class TimeSpanHumanReadableTests : AbstractTest
{
    [TestCase(1, "1 day")]
    [TestCase(2, "2 days")]
    [TestCase(3, "3 days")]
    [TestCase(4, "4 days")]
    [TestCase(5, "5 days")]
    [TestCase(6, "6 days")]
    [TestCase(7, "1 week")]
    public void ToHumanReadableString_DayValues_ReturnsHumanReadableString(int days, string expected)
    {
        TimeSpan timeSpan = new(days, 0, 0, 0, 0);
        Assert.AreEqual(expected, TimeSpanHumanReadable.ToHumanReadableString(timeSpan), timeSpan.ToString());
    }
    [TestCase(28, "1 month")]
    [TestCase(29, "1 month")]
    [TestCase(30, "1 month")]
    [TestCase(31, "1 month")]
    [TestCase(32, "1 month")]
    public void ToHumanReadableString_DaysFor1Month_ReturnsHumanReadableString(int days, string expected)
    {
        TimeSpan timeSpan = new(days, 0, 0, 0, 0);
        Assert.AreEqual(expected, TimeSpanHumanReadable.ToHumanReadableString(timeSpan), timeSpan.ToString());
    }
    [TestCase(58, "2 months")]
    [TestCase(59, "2 months")]
    [TestCase(60, "2 months")]
    [TestCase(61, "2 months")]
    [TestCase(62, "2 months")]
    public void ToHumanReadableString_DaysFor2Months_ReturnsHumanReadableString(int days, string expected)
    {
        TimeSpan timeSpan = new(days, 0, 0, 0, 0);
        Assert.AreEqual(expected, TimeSpanHumanReadable.ToHumanReadableString(timeSpan), timeSpan.ToString());
    }
    [TestCase(8,  "1 week 1 day")]
    [TestCase(16, "2 weeks 2 days")]
    public void ToHumanReadableString_DaysForWeeks_ReturnsHumanReadableString(int days, string expected)
    {
        TimeSpan timeSpan = new(days, 0, 0, 0, 0);
        Assert.AreEqual(expected, TimeSpanHumanReadable.ToHumanReadableString(timeSpan), timeSpan.ToString());
    }
    [TestCase(30,         "1 month")]
    [TestCase(30 + 1,     "1 month")]
    [TestCase(30 + 2,     "1 month")]
    [TestCase(30 + 3,     "1 month 3 days")]
    [TestCase(30 + 4,     "1 month 4 days")]
    [TestCase(30 + 5,     "1 month 5 days")]
    [TestCase(30 + 6,     "1 month 6 days")]
    [TestCase(30 + 7,     "1 month 1 week")]
    [TestCase(30 + 7 + 1, "1 month 1 week")]
    [TestCase(32 + 7 + 2, "1 month 1 week")]
    [TestCase(32 + 7 + 3, "1 month 1 week")]
    public void ToHumanReadableString_DaysForMonths_ReturnsHumanReadableString(int days, string expected)
    {
        TimeSpan timeSpan = new(days, 0, 0, 0, 0);
        Assert.AreEqual(expected, TimeSpanHumanReadable.ToHumanReadableString(timeSpan), timeSpan.ToString());
    }
    [TestCase(365,         "1 year")]
    [TestCase(365 + 1,     "1 year 1 day")]
    [TestCase(365 + 2,     "1 year 2 days")]
    [TestCase(365 + 3,     "1 year 3 days")]
    [TestCase(365 + 4,     "1 year 4 days")]
    [TestCase(365 + 5,     "1 year 5 days")]
    [TestCase(365 + 6,     "1 year 6 days")]
    [TestCase(365 + 7,     "1 year 1 week")]
    [TestCase(365 + 7 + 1, "1 year 1 week")]
    [TestCase(365 + 7 + 2, "1 year 1 week")]
    [TestCase(365 + 7 + 3, "1 year 1 week")]
    [TestCase(365 + 7 + 4, "1 year 1 week")]
    [TestCase(365 + 7 + 5, "1 year 1 week")]
    [TestCase(365 + 7 + 6, "1 year 1 week")]
    [TestCase(365 + 14,    "1 year 2 weeks")]
    [TestCase(365 + 30,    "1 year 1 month")]
    [TestCase(365 + 60,    "1 year 2 months")]
    public void ToHumanReadableString_DaysForYears_ReturnsHumanReadableString(int days, string expected)
    {
        TimeSpan timeSpan = new(days, 0, 0, 0, 0);
        Assert.AreEqual(expected, TimeSpanHumanReadable.ToHumanReadableString(timeSpan), timeSpan.ToString());
    }
    [TestCase(1, 0,  0,  0,  0,  "1 day")]
    [TestCase(0, 1,  0,  0,  0,  "1 hour")]
    [TestCase(0, 0,  1,  0,  0,  "1 min")]
    [TestCase(0, 0,  0,  1,  0,  "1 sec")]
    [TestCase(0, 0,  0,  0,  1,  "001 ms")]
    [TestCase(0, 15, 0,  0,  0,  "15 hours")]
    [TestCase(0, 0,  15, 0,  0,  "15 min")]
    [TestCase(0, 0,  0,  15, 0,  "15 sec")]
    [TestCase(0, 0,  0,  0,  15, "015 ms")]
    [TestCase(1, 1,  0,  0,  0,  "1 day 1 hour")]
    [TestCase(2, 2,  0,  0,  0,  "2 days 2 hours")]
    [TestCase(5, 5,  5,  5,  5,  "5 days 5 hours")]
    [TestCase(0, 1,  1,  0,  0,  "1 hour 1 min")]
    [TestCase(0, 2,  2,  0,  0,  "2 hours 2 min")]
    [TestCase(0, 0,  1,  1,  0,  "1 min 1 sec")]
    [TestCase(0, 0,  2,  2,  0,  "2 min 2 sec")]
    [TestCase(0, 0,  0,  1,  1,  "1 sec")] // With ms
    [TestCase(0, 0,  0,  2,  2,  "2 sec")] // With ms
    public void ToHumanReadableString_TimeValues_ReturnsHumanReadableString(int days, int hours, int minutes, int seconds, int milliseconds, string expected)
    {
        TimeSpan timeSpan = new(days, hours, minutes, seconds, milliseconds);
        Assert.AreEqual(expected, TimeSpanHumanReadable.ToHumanReadableString(timeSpan), timeSpan.ToString());
    }
}