22

TimeSpanクライアントがサーバーに接続されている時間を表す があります。TimeSpanそれをユーザーに表示したい。しかし、その情報を表示するために過度に冗長になりたくありません (例: 2 時間 3 分 32.2345 秒 = 詳細すぎる!)

例: 接続時間が...

> 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日)にしたいと思います。 .

if/else 句の巨大なセットなしでこれを簡単に実装する方法はありますか? これが私が現在行っていることです。

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;
}
4

11 に答える 11

34

複雑な if および switch コンストラクトを取り除くには、TotalSeconds に基づく正しいフォーマット文字列の Dictionary ルックアップと、提供された Timespan を適切にフォーマットする CustomFormatter を使用できます。

public string GetReadableTimespan(TimeSpan ts)
{
     // formats and its cutoffs based on totalseconds
     var cutoff = new SortedList<long, string> { 
       {59, "{3:S}" }, 
       {60, "{2:M}" },
       {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<string, string> timeformats = new Dictionary<string, string> {
        {"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;
   }   
}
于 2014-02-08T17:39:08.070 に答える
15

単純にこのようなものではないのはなぜですか?

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

        return $@"{t:%d} days";
    }
}

2 つの時間単位 (分と秒など) を使用する場合は、簡単に追加できます。

于 2016-03-24T00:55:22.897 に答える
7

私は自分のニーズに合わせて Bjorn の回答に基づいて構築し、他の誰かがこの問題を見た場合に備えて共有したいと考えました。時間を節約できます。受け入れられた答えは、私のニーズに対して少し重いです。

    private static string FormatTimeSpan(TimeSpan timeSpan)
    {
        Func<Tuple<int,string>, string> tupleFormatter = t => $"{t.Item1} {t.Item2}{(t.Item1 == 1 ? string.Empty : "s")}";
        var components = new List<Tuple<int, string>>
        {
            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}";
    }
于 2017-01-31T20:28:55.867 に答える
2

私はこのような、より「読みやすい」ものを好んだと思います:

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;
}
于 2015-12-21T08:30:41.343 に答える
0

私は詳細を破棄することを好みます。たとえば、月数に達したら、秒数は関係ありません。そのため、自然な切り替えポイントを使用して次のレベルに到達し、すべての小数値を破棄します。もちろん、これにより単数/複数の問題も完全に解消されます。

    private static string LastFetched(TimeSpan ago)
    {
        string lastFetched = "last fetched ";
        if (ago.TotalDays >= 90)
            lastFetched += $"{(int)ago.TotalDays / 30} months ago";
        else if (ago.TotalDays >= 14)
            lastFetched += $"{(int)ago.TotalDays / 7} weeks ago";
        else if (ago.TotalDays >= 2)
            lastFetched += $"{(int)ago.TotalDays} days ago";
        else if (ago.TotalHours >= 2)
            lastFetched += $"{(int)ago.TotalHours} hours ago";
        else if (ago.TotalMinutes >= 2)
            lastFetched += $"{(int)ago.TotalMinutes} minutes ago";
        else if (ago.TotalSeconds >= 10)
            lastFetched += $"{(int)ago.TotalSeconds} seconds ago";
        else
            lastFetched += $"just now";

        return lastFetched;
    }
于 2022-01-24T16:21:07.043 に答える