Як вырабляць «чалавек чытэльнай» радок для прадстаўлення TimeSpan

У мяне ёсць TimeSpan , якое прадстаўляе колькасць часу кліент быў падлучаны да сервера. Я хачу паказаць, што TimeSpan карыстальніку. Але я не хачу быць занадта шматслоўным, каб адлюстроўваць гэтую інфармацыю (напрыклад: 2hr 3мин 32.2345sec = занадта падрабязна)

Напрыклад: Калі час злучэння ...

> 0 seconds and < 1 minute   ----->  0 Seconds
> 1 minute  and < 1 hour     ----->  0 Minutes, 0 Seconds
> 1 hour    and < 1 day      ----->  0 Hours, 0 Minutes
> 1 day                      ----->  0 Days, 0 Hours

І, вядома, у тых выпадках, калі лічба складае 1 (напрыклад: 1 секунда, 1 мін, 1 гадзіну, 1 дзён), я хацеў бы зрабіць тэкст сінгулярнасць (напрыклад: 1 секунда, 1 хвіліна, 1 гадзіну, 1 дзень) ,

Ёсць у любым выпадку лёгка рэалізаваць гэта без гіганцкага набору калі/іншых артыкулаў? Вось тое, што я цяпер раблю.

public string GetReadableTimeSpan(TimeSpan value)
{
    string duration;

    if (value.TotalMinutes < 1)
        duration = value.Seconds + " Seconds";
    else if (value.TotalHours < 1)
        duration = value.Minutes + " Minutes, " + value.Seconds + " Seconds";
    else if (value.TotalDays < 1)
        duration = value.Hours + " Hours, " + value.Minutes + " Minutes";
    else
        duration = value.Days + " Days, " + value.Hours + " Hours";

    if (duration.StartsWith("1 Seconds") || duration.EndsWith(" 1 Seconds"))
        duration = duration.Replace("1 Seconds", "1 Second");

    if (duration.StartsWith("1 Minutes") || duration.EndsWith(" 1 Minutes"))
        duration = duration.Replace("1 Minutes", "1 Minute");

    if (duration.StartsWith("1 Hours") || duration.EndsWith(" 1 Hours"))
        duration = duration.Replace("1 Hours", "1 Hour");

    if (duration.StartsWith("1 Days"))
        duration = duration.Replace("1 Days", "1 Day");

    return duration;
}
14
На жаль, няма. Гэта занадта канкрэтны TAKS быць уключана ў ГКЛЕ
дададзена аўтар Oleksandr Pshenychnyy, крыніца
дададзена аўтар Habib, крыніца
Гэта адзін github.com/MehdiK/Humanizer/blob/master/ SRC/Humanizer/& hellip; мае некаторыя ўмоўнага ладу, але не так шмат. Гэта частка праекта mehdi-khalili.com/humanizer-v1 . Некаторыя карысныя ідэі прыходзяць з таго, хто шукае «чытанага TimeSpan GitHub»
дададзена аўтар Damian Schenkelman, крыніца
Ўзгоднена: Выкарыстоўвайце бібліятэку .NET HUMANIZER, гэта выдатна! github.com/Humanizr/Humanizer
дададзена аўтар James McCormack, крыніца
магчыма дублікат бібліятэк C# для разбору чытэльных пралётаў часу чалавека </а >
дададзена аўтар Moeri, крыніца

8 адказы

Для таго, каб пазбавіцца ад комплексу, калі і перамыкач канструкцыі вы можаце выкарыстоўваць пошук па слоўніку для правільнай радкі фармату на аснове TotalSeconds і CustomFormatter фарматаваць які прыкладаецца TimeSpan адпаведна.

public string GetReadableTimespan(TimeSpan ts)
{
    //formats and its cutoffs based on totalseconds
     var cutoff = new SortedList { 
       {60, "{3:S}" },
       {60*60-1, "{2:M}, {3:S}"},
       {60*60, "{1:H}"},
       {24*60*60-1, "{1:H}, {2:M}"},
       {24*60*60, "{0:D}"},
       {Int64.MaxValue , "{0:D}, {1:H}"}
     };

    //find nearest best match
     var find = cutoff.Keys.ToList()
                   .BinarySearch((long)ts.TotalSeconds);
    //negative values indicate a nearest match
     var near = find<0?Math.Abs(find)-1:find;
    //use custom formatter to get the string
     return String.Format(
         new HMSFormatter(), 
         cutoff[cutoff.Keys[near]], 
         ts.Days, 
         ts.Hours, 
         ts.Minutes, 
         ts.Seconds);
}

// formatter for forms of
// seconds/hours/day
public class HMSFormatter:ICustomFormatter, IFormatProvider
{
   //list of Formats, with a P customformat for pluralization
    static Dictionary timeformats = new Dictionary {
        {"S", "{0:P:Seconds:Second}"},
        {"M", "{0:P:Minutes:Minute}"},
        {"H","{0:P:Hours:Hour}"},
        {"D", "{0:P:Days:Day}"}
    };

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        return String.Format(new PluralFormatter(),timeformats[format], arg);
    }

    public object GetFormat(Type formatType)
    {
        return formatType == typeof(ICustomFormatter)?this:null;
    }   
}

// formats a numeric value based on a format P:Plural:Singular
public class PluralFormatter:ICustomFormatter, IFormatProvider
{

   public string Format(string format, object arg, IFormatProvider formatProvider)
   {
     if (arg !=null)
     {
         var parts = format.Split(':');//["P", "Plural", "Singular"]

         if (parts[0] == "P")//correct format?
         {
           //which index postion to use
            int partIndex = (arg.ToString() == "1")?2:1;
           //pick string (safe guard for array bounds) and format
            return String.Format("{0} {1}", arg, (parts.Length>partIndex?parts[partIndex]:""));               
         }
     }
     return String.Format(format, arg);
   }

   public object GetFormat(Type formatType)
   {
       return formatType == typeof(ICustomFormatter)?this:null;
   }   
}
20
дададзена
Гэта не працуе для timespans як TimeSpan.FromHours (1), што робіць «0 хвілін, 0 секунд»
дададзена аўтар Stephen Drew, крыніца
Гэта, безумоўна, разумны і мне падабаецца. Я прымаю яго, так як ён адказвае на пытанне. Я скажу, што код, які я адправіў мае перавагу быць адразу зразумела і лёгка зразумець. Ваш код патрабуецца некаторы час, каб прытрымлівацца. Гэта адзіная рэч, якую я не закаханы.
дададзена аўтар Michael Mankus, крыніца
Добра, дзякуй. Можа быць, гэта не простая праблема, якую мы спрабуем вырашыць :-)
дададзена аўтар rene, крыніца
@StephenDrew добры ўлоў, дадаўшы дадатковыя запісы ў SortedList павінны вырашыць, што.
дададзена аўтар rene, крыніца
Іншы выпадак TimeSpan.FromMinutes (1) аказвае "0 секунд". Мая прапанова заключаецца ў змене SortedList шляхам выдалення {60, "{3: S}"}, і даданне {59, "{3: S}"}, {60, «{ 2: М} »},
дададзена аўтар Riz, крыніца

Чаму б не проста нешта накшталт гэтага?

public static class TimespanExtensions
{
    public static string ToHumanReadableString (this TimeSpan t)
    {
        if (t.TotalSeconds <= 1) {
            return [email protected]"{t:s\.ff} seconds";
        }
        if (t.TotalMinutes <= 1) {
            return [email protected]"{t:%s} seconds";
        }
        if (t.TotalHours <= 1) {
            return [email protected]"{t:%m} minutes";
        }
        if (t.TotalDays <= 1) {
            return [email protected]"{t:%h} hours";
        }

        return [email protected]"{t:%d} days";
    }
}

Калі вы аддаеце перавагу дзве адзінку часу (напрыклад, хвіліны плюс секунду), што было б вельмі проста дадаць.

6
дададзена
@Kiquenet The @ экрануе змесціва радкі, так што вам не трэба будзе пісаць \\, калі вы хочаце \. $ Даволі новая функцыя і дазваляе {х} радок інтэрпаляцыі.
дададзена аўтар mafu, крыніца

Я палічыў за лепшае б нешта накшталт гэтага, якое больш «чытаны» Я думаю, што:

public string GetReadableTimeSpan(TimeSpan value)
{
 string duration = "";

 var totalDays = (int)value.TotalDays;
 if (totalDays >= 1)
 {
     duration = totalDays + " day" + (totalDays > 1 ? "s" : string.Empty);
     value = value.Add(TimeSpan.FromDays(-1 * totalDays));
 }

 var totalHours = (int)value.TotalHours;
 if (totalHours >= 1)
 {
     if (totalDays >= 1)
     {
         duration += ", ";
     }
     duration += totalHours + " hour" + (totalHours > 1 ? "s" : string.Empty);
     value = value.Add(TimeSpan.FromHours(-1 * totalHours));
 }

 var totalMinutes = (int)value.TotalMinutes;
 if (totalMinutes >= 1)
 {
     if (totalHours >= 1)
     {
         duration += ", ";
     }
     duration += totalMinutes + " minute" + (totalMinutes > 1 ? "s" : string.Empty);
 }

 return duration;
}
1
дададзена
<�Код> вар totalMinutes = (INT) value.TotalHours; <- павінен быць value.TotalMinutes ?
дададзена аўтар wal, крыніца

Іншы падыход (на нямецкай мове)

public static string GetReadableTimeSpan(TimeSpan span)
{
    var formatted = string.Format("{0}{1}{2}{3}",
        span.Duration().Days > 0
            ? $"{span.Days:0} Tag{(span.Days == 1 ? string.Empty : "e")}, "
            : string.Empty,
        span.Duration().Hours > 0
            ? $"{span.Hours:0} Stunde{(span.Hours == 1 ? string.Empty : "n")}, "
            : string.Empty,
        span.Duration().Minutes > 0
            ? $"{span.Minutes:0} Minute{(span.Minutes == 1 ? string.Empty : "n")}, "
            : string.Empty,
        span.Duration().Seconds > 0
            ? $"{span.Seconds:0} Sekunde{(span.Seconds == 1 ? string.Empty : "n")}"
            : string.Empty);

    if (formatted.EndsWith(", ")) formatted = formatted.Substring(0, formatted.Length - 2);

    return string.IsNullOrEmpty(formatted) ? "0 Sekunden" : ReplaceLastOccurrence(formatted, ",", " und ").Replace("  ", " ");
}

private static string ReplaceLastOccurrence(string source, string find, string replace)
{
    var place = source.LastIndexOf(find, StringComparison.Ordinal);

    if (place == -1)
        return source;

    var result = source.Remove(place, find.Length).Insert(place, replace);
    return result;
}
1
дададзена

Я пабудаваў на адказ Bjorn, каб адпавядаць маім патрэбам, хацеў бы падзяліцца ў выпадку, калі хто-то бачыў гэтае пытанне. Можа зэканоміць іх час. Прыняты адказ трохі цяжкавагавы для маіх патрэбаў.

    private static string FormatTimeSpan(TimeSpan timeSpan)
    {
        Func, string> tupleFormatter = t => $"{t.Item1} {t.Item2}{(t.Item1 == 1 ? string.Empty : "s")}";
        var components = new List>
        {
            Tuple.Create((int) timeSpan.TotalDays, "day"),
            Tuple.Create(timeSpan.Hours, "hour"),
            Tuple.Create(timeSpan.Minutes, "minute"),
            Tuple.Create(timeSpan.Seconds, "second"),
        };

        components.RemoveAll(i => i.Item1 == 0);

        string extra = "";

        if (components.Count > 1)
        {
            var finalComponent = components[components.Count - 1];
            components.RemoveAt(components.Count - 1);
            extra = $" and {tupleFormatter(finalComponent)}";
        }

        return $"{string.Join(", ", components.Select(tupleFormatter))}{extra}";
    }
1
дададзена
public string ToHumanDuration(TimeSpan? duration, bool displaySign = true)
    {
        if (duration == null) return null;

        var builder = new StringBuilder();
        if (displaySign)
        {
            builder.Append(duration.Value.TotalMilliseconds < 0 ? "-" : "+");
        }

        duration = duration.Value.Duration();

        if (duration.Value.Days > 0)
        {
            builder.Append($"{duration.Value.Days}d ");
        }

        if (duration.Value.Hours > 0)
        {
            builder.Append($"{duration.Value.Hours}h ");
        }

        if (duration.Value.Minutes > 0)
        {
            builder.Append($"{duration.Value.Minutes}m ");
        }

        if (duration.Value.TotalHours < 1)
        {
            if (duration.Value.Seconds > 0)
            {
                builder.Append(duration.Value.Seconds);
                if (duration.Value.Milliseconds > 0)
                {
                    builder.Append($".{duration.Value.Milliseconds.ToString().PadLeft(3, '0')}");
                }

                builder.Append("s ");
            }
            else
            {
                if (duration.Value.Milliseconds > 0)
                {
                    builder.Append($"{duration.Value.Milliseconds}ms ");
                }
            }
        }

        if (builder.Length <= 1)
        {
            builder.Append(" <1ms ");
        }

        builder.Remove(builder.Length - 1, 1);

        return builder.ToString();
    }

Source: https://github.com/HangfireIO/Hangfire/blob/master/src/Hangfire.Core/Dashboard/HtmlHelper.cs

1
дададзена

Вось маё ўзяцце - трохі прасцей, чым прынята адказваць, вы не думаеце? Акрамя таго, няма радка расшчаплення/сінтаксічнага аналізу.

var components = new List> {
    Tuple.Create((int)span.TotalDays, "day"),
    Tuple.Create(span.Hours, "hour"),
    Tuple.Create(span.Minutes, "minute"),
    Tuple.Create(span.Seconds, "second"),
};

while(components.Any() && components[0].Item1 == 0)
{
    components.RemoveAt(0);
}

var result = string.Join(", ", components.Select(t => t.Item1 + " " + t.Item2 + (t.Item1 != 1 ? "s" : string.Empty)));
0
дададзена

Яшчэ адзін удар у гэтым. Здзелкі з pluralising адзінак (і апускаючы нулявых адзінак) больш паслядоўна:

private string GetValueWithPluralisedUnits(int value, string units, int prefix_value)
    {
        if (value != 0)
        {
            return (prefix_value == 0 ? "" : ", ") + value.ToString() + " " + units + (value == 1 ? "" : "s");
        }

        return "";
    }

    private string GetReadableTimeSpan(TimeSpan value)
    {
        string duration;

        if (value.TotalMinutes < 1)
        {
            if (value.Seconds > 0)
            {
                duration = GetValueWithPluralisedUnits(value.Seconds, "Second", 0);
            }
            else
            {
                duration = "";
            }
        }
        else if (value.TotalHours < 1)
        {
            duration = GetValueWithPluralisedUnits(value.Minutes, "Minute", 0) + GetValueWithPluralisedUnits(value.Seconds, "Second", value.Minutes);
        }
        else if (value.TotalDays < 1)
        {
            duration = GetValueWithPluralisedUnits(value.Hours, "Hour", 0) + GetValueWithPluralisedUnits(value.Minutes, "Minute", value.Hours);
        }
        else
        {
            int days_left = (int)value.TotalDays;
            int years = days_left/365;
            days_left -= years * 365;
            int months = days_left/12;
            days_left -= months * 12;

            duration = GetValueWithPluralisedUnits(years, "Year", 0) + GetValueWithPluralisedUnits(months, "Month", years) + GetValueWithPluralisedUnits(days_left, "Day", years + months);
        }

        return duration;
    }
0
дададзена