Programming Problems & Solutions : “Simplifying Time: Humanizing Duration in Programming”. The introduction to this series is here and includes all links to every post in the series. If you’d like to watch the video (see just below this), or the AI code up (it’s at the bottom of the post) they’re available! But if you just want to work through the problem keep reading, I cover most of what is in the video plus a slightly different path down below.
In software development, seemingly simple tasks can unfold into complex challenges, especially when it involves outputs that must be human-centric, such as formatting time durations into a readable format. This is the case with the task of converting seconds into an easily digestible format for users.
The Challenge
The objective is straightforward: write a function that converts a given number of seconds into a format easy for humans to read. If it’s zero seconds, the function should return “now”. Otherwise, it should represent the duration as a combination of years, days, hours, minutes, and seconds, following specific formatting rules.
Why This Matters
Presenting users with raw seconds in applications where time durations are critical (like project timelines or cooking timers) is not helpful. Human-readable formats allow users to quickly and intuitively understand the information presented.
Testing and Validation
As usual, I like to write out some tests to get my thinking sorted out and do initial class and structure design this way. I start off here with now, because it’s always now.
using NUnit.Framework;
[TestFixture]
public class Tests {
[Test]
public void basicTests() {
Assert.AreEqual("now", HumanTimeFormat.formatDuration(0));
Assert.AreEqual("1 second", HumanTimeFormat.formatDuration(1));
Assert.AreEqual("1 minute and 2 seconds", HumanTimeFormat.formatDuration(62));
Assert.AreEqual("2 minutes", HumanTimeFormat.formatDuration(120));
Assert.AreEqual("1 hour, 1 minute and 2 seconds", HumanTimeFormat.formatDuration(3662));
Assert.AreEqual("182 days, 1 hour, 44 minutes and 40 seconds", HumanTimeFormat.formatDuration(15731080));
Assert.AreEqual("4 years, 68 days, 3 hours and 4 minutes", HumanTimeFormat.formatDuration(132030240));
Assert.AreEqual("6 years, 192 days, 13 hours, 3 minutes and 54 seconds", HumanTimeFormat.formatDuration(205851834));
Assert.AreEqual("8 years, 12 days, 13 hours, 41 minutes and 1 second", HumanTimeFormat.formatDuration(253374061));
Assert.AreEqual("7 years, 246 days, 15 hours, 32 minutes and 54 seconds", HumanTimeFormat.formatDuration(242062374));
Assert.AreEqual("3 years, 85 days, 1 hour, 9 minutes and 26 seconds", HumanTimeFormat.formatDuration(101956166));
Assert.AreEqual("1 year, 19 days, 18 hours, 19 minutes and 46 seconds", HumanTimeFormat.formatDuration(33243586));
}
}
Starting Code
The task begins with a simple C# class structure:
public class HumanTimeFormat{
public static string formatDuration(int seconds){
// Enter Code here
}
}
Implementing the Solution
My first draft function, formatDuration, handles the conversion of seconds into a structured and readable string.
public class HumanTimeFormat
{
public static string formatDuration(int seconds)
{
if (seconds == 0) return "now";
int secondsPerMinute = 60;
int secondsPerHour = 60 * secondsPerMinute;
int secondsPerDay = 24 * secondsPerHour;
int secondsPerYear = 365 * secondsPerDay;
int years = seconds / secondsPerYear;
seconds %= secondsPerYear;
int days = seconds / secondsPerDay;
seconds %= secondsPerDay;
int hours = seconds / secondsPerHour;
seconds %= secondsPerHour;
int minutes = seconds / secondsPerMinute;
seconds %= secondsPerMinute;
List<string> parts = new List<string>();
if (years > 0) parts.Add($"{years} year{(years > 1 ? "s" : "")}");
if (days > 0) parts.Add($"{days} day{(days > 1 ? "s" : "")}");
if (hours > 0) parts.Add($"{hours} hour{(hours > 1 ? "s" : "")}");
if (minutes > 0) parts.Add($"{minutes} minute{(minutes > 1 ? "s" : "")}");
if (seconds > 0) parts.Add($"{seconds} second{(seconds > 1 ? "s" : "")}");
return parts.Count > 1
? string.Join(", ", parts.Take(parts.Count - 1)) + " and " + parts.Last()
: parts.FirstOrDefault();
}
}
The formatDuration function not only meets the practical needs of converting seconds into a user-friendly format but also illustrates the broader challenge in software development of enhancing user interaction through thoughtful, well-tested code. The solution, specific to C#, demonstrates a methodology that can be adapted across various programming languages, embodying a universal challenge and its resolution.
Refactoring Time (No Pun’ Intended)
Immediately I stepped into a few key changes.
- Introduced constants
TimeUnitsandTimeUnitValuesto store the time unit names and their corresponding values in seconds. This makes the code more readable and avoids hard-coding the values multiple times. - Renamed the method to
FormatDurationto follow the C# naming convention. - Used a
forloop to iterate over the time units and their values. This eliminates the repetitive code for each time unit. - Extracted the logic for formatting a single part (e.g., “1 year” or “2 days”) into a separate method
FormatPart. This improves readability and reusability. - Extracted the logic for combining the parts into a separate method
CombineParts. This makes the main methodFormatDurationmore focused and easier to understand.
public class HumanTimeFormat
{
private static readonly string[] TimeUnits = { "year", "day", "hour", "minute", "second" };
private static readonly int[] TimeUnitValues = { 365 * 24 * 60 * 60, 24 * 60 * 60, 60 * 60, 60, 1 };
public static string FormatDuration(int seconds)
{
if (seconds == 0)
return "now";
var parts = new List<string>();
for (int i = 0; i < TimeUnitValues.Length; i++)
{
int count = seconds / TimeUnitValues[i];
if (count > 0)
{
parts.Add(FormatPart(count, TimeUnits[i]));
seconds %= TimeUnitValues[i];
}
}
return CombineParts(parts);
}
private static string FormatPart(int count, string unit)
{
return $"{count} {unit}{(count > 1 ? "s" : "")}";
}
private static string CombineParts(List<string> parts)
{
return parts.Count > 1
? string.Join(", ", parts.Take(parts.Count - 1)) + " and " + parts.Last()
: parts.FirstOrDefault();
}
}
Ran the tests and they passed. All looked good, and I’m happy with this string of refactors, so I’m going to call this one a day! Happy thrashing coding. 🤘🏻
Reference