Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions src/main/java/com/thealgorithms/conversions/TimeConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.thealgorithms.conversions;

import java.util.Locale;
import java.util.Map;

/**
* A utility class to convert between different units of time.
*
* <p>This class supports conversions between the following units:
* <ul>
* <li>seconds</li>
* <li>minutes</li>
* <li>hours</li>
* <li>days</li>
* <li>weeks</li>
* <li>months (approximated as 30.44 days)</li>
* <li>years (approximated as 365.25 days)</li>
* </ul>
*
* <p>The conversion is based on predefined constants in seconds.
* Results are rounded to three decimal places for consistency.
*
* <p>This class is final and cannot be instantiated.
*
* @see <a href="https://en.wikipedia.org/wiki/Unit_of_time">Wikipedia: Unit of time</a>
*/
public final class TimeConverter {

private TimeConverter() {
// Prevent instantiation
}

/**
* Supported time units with their equivalent in seconds.
*/
private enum TimeUnit {
SECONDS(1.0),
MINUTES(60.0),
HOURS(3600.0),
DAYS(86400.0),
WEEKS(604800.0),
MONTHS(2629800.0), // 30.44 days
YEARS(31557600.0); // 365.25 days

private final double seconds;

TimeUnit(double seconds) {
this.seconds = seconds;
}

public double toSeconds(double value) {
return value * seconds;
}

public double fromSeconds(double secondsValue) {
return secondsValue / seconds;
}
}

private static final Map<String, TimeUnit> UNIT_LOOKUP
= Map.ofEntries(Map.entry("seconds", TimeUnit.SECONDS), Map.entry("minutes", TimeUnit.MINUTES), Map.entry("hours", TimeUnit.HOURS), Map.entry("days", TimeUnit.DAYS), Map.entry("weeks", TimeUnit.WEEKS), Map.entry("months", TimeUnit.MONTHS), Map.entry("years", TimeUnit.YEARS));

/**
* Converts a time value from one unit to another.
*
* @param timeValue the numeric value of time to convert; must be non-negative
* @param unitFrom the unit of the input value (e.g., "minutes", "hours")
* @param unitTo the unit to convert into (e.g., "seconds", "days")
* @return the converted value in the target unit, rounded to three decimals
* @throws IllegalArgumentException if {@code timeValue} is negative
* @throws IllegalArgumentException if either {@code unitFrom} or {@code unitTo} is not supported
*/
public static double convertTime(double timeValue, String unitFrom, String unitTo) {
if (timeValue < 0) {
throw new IllegalArgumentException("timeValue must be a non-negative number.");
}

TimeUnit from = resolveUnit(unitFrom);
TimeUnit to = resolveUnit(unitTo);

double secondsValue = from.toSeconds(timeValue);
double converted = to.fromSeconds(secondsValue);

return Math.round(converted * 1000.0) / 1000.0;
}

private static TimeUnit resolveUnit(String unit) {
if (unit == null) {
throw new IllegalArgumentException("Unit cannot be null.");
}
TimeUnit resolved = UNIT_LOOKUP.get(unit.toLowerCase(Locale.ROOT));
if (resolved == null) {
throw new IllegalArgumentException("Invalid unit '" + unit + "'. Supported units are: " + UNIT_LOOKUP.keySet());
}
return resolved;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.thealgorithms.conversions;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;

class TimeConverterTest {

@ParameterizedTest(name = "{0} {1} -> {2} {3}")
@CsvSource({"60, seconds, minutes, 1", "120, seconds, minutes, 2", "2, minutes, seconds, 120", "2, hours, minutes, 120", "1, days, hours, 24", "1, weeks, days, 7", "1, months, days, 30.438", "1, years, days, 365.25", "3600, seconds, hours, 1", "86400, seconds, days, 1",
"604800, seconds, weeks, 1", "31557600, seconds, years, 1"})
void
testValidConversions(double value, String from, String to, double expected) {
assertEquals(expected, TimeConverter.convertTime(value, from, to));
}

@Test
@DisplayName("Zero conversion returns zero")
void testZeroValue() {
assertEquals(0.0, TimeConverter.convertTime(0, "seconds", "hours"));
}

@Test
@DisplayName("Same-unit conversion returns original value")
void testSameUnitConversion() {
assertEquals(123.456, TimeConverter.convertTime(123.456, "minutes", "minutes"));
}

@Test
@DisplayName("Negative value throws exception")
void testNegativeValue() {
assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(-5, "seconds", "minutes"));
}

@ParameterizedTest
@CsvSource({"lightyears, seconds", "minutes, centuries"})
void testInvalidUnits(String from, String to) {
assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(10, from, to));
}

@Test
@DisplayName("Null unit throws exception")
void testNullUnit() {
assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(10, null, "seconds"));

assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(10, "minutes", null));

assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(10, null, null));
}

static Stream<org.junit.jupiter.params.provider.Arguments> roundTripCases() {
return Stream.of(org.junit.jupiter.params.provider.Arguments.of(1.0, "hours", "minutes"), org.junit.jupiter.params.provider.Arguments.of(2.5, "days", "hours"), org.junit.jupiter.params.provider.Arguments.of(1000, "seconds", "minutes"));
}

@ParameterizedTest
@MethodSource("roundTripCases")
@DisplayName("Round-trip conversion returns original value")
void testRoundTripConversion(double value, String from, String to) {
double converted = TimeConverter.convertTime(value, from, to);
double roundTrip = TimeConverter.convertTime(converted, to, from);
assertEquals(Math.round(value * 1000.0) / 1000.0, Math.round(roundTrip * 1000.0) / 1000.0, 0.05);
}
}