Bob
Bob

Reputation: 99794

Converting Days into Human Readable Duration Text

Given an amount of days, say 25, convert it into a duration text such as "3 Weeks, 4 Days"

C# and F# solutions would both be great if the F# variation offers any improvement over C#.

Edit: The solution should expand past weeks to include months and years. Bonus points for including centuries and so on. Extra bonus if it is somewhat configurable, meaning you can tell the method to exclude the normalization of weeks.

Upvotes: 3

Views: 1504

Answers (4)

simonuk
simonuk

Reputation: 391

This is a recursive solution. Note that a duration only really makes sense measured against a particular point in time on a given calendar because month and year lengths vary. But here is a simple solution assuming fixed lengths:

let divmod n m = n / m, n % m    

let units = [
    ("Centuries", TimeSpan.TicksPerDay * 365L * 100L );
    ("Years", TimeSpan.TicksPerDay * 365L);
    ("Weeks", TimeSpan.TicksPerDay * 7L);
    ("Days", TimeSpan.TicksPerDay)
]

let duration days =
    let rec duration' ticks units acc =
        match units with
        | [] -> acc
        | (u::us) ->
            let (wholeUnits, ticksRemaining) = divmod ticks (snd u)
            duration' ticksRemaining us (((fst u), wholeUnits) :: acc)
    duration' (TimeSpan.FromDays(float days).Ticks) units []

Upvotes: 2

Brian
Brian

Reputation: 118895

Here's an F# version based on the previously posted C# version. The main difference is it's applicative rather than imperative (no mutable variables).

#light
let conversions = [|
    365, "Year", "Years"
    30, "Month", "Months"
    7, "Week", "Weeks"
    1, "Day", "Days" |]
let ToDuration numDays =
    conversions
    |> Array.fold_left (fun (remainDays,results) (n,sing,plur) ->
        let count = remainDays / n
        if count >= 1 then 
            remainDays - (count * n),
              (sprintf "%d %s" count (if count=1 then sing else plur)) :: results
        else
            remainDays, results
    ) (numDays,[])
    |> snd |> (fun rs -> System.String.Join(", ", List.rev rs |> List.to_array))
printfn "%s" (ToDuration 1008)    

Upvotes: 0

Bob
Bob

Reputation: 99794

    public class UnitOfMeasure {
        public UnitOfMeasure(string name, int value) {
            Name = name;
            Value = value;
        }

        public string Name { get; set; }
        public int Value { get; set; }

        public static UnitOfMeasure[] All = new UnitOfMeasure[] {
            new UnitOfMeasure("Year", 356),
            new UnitOfMeasure("Month", 30),
            new UnitOfMeasure("Week", 7),
            new UnitOfMeasure("Day", 1)
        };

        public static string ConvertToDuration(int days) {
            List<string> results = new List<string>();

            for (int i = 0; i < All.Length; i++) {
                int count = days / All[i].Value;

                if (count >= 1) {
                    results.Add((count + " " + All[i].Name) + (count == 1 ? string.Empty : "s"));
                    days -= count * All[i].Value;
                }
            }

            return string.Join(", ", results.ToArray());
        }
    }

Upvotes: 0

Gavin Miller
Gavin Miller

Reputation: 43845

String.Format("{0} Weeks, {1} days", days / 7, days % 7);

Upvotes: 5

Related Questions