🎉 Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io 🎉
Avatar of akiraly

akiraly's solution

to Clock in the Kotlin Track

Published at Feb 01 2021 · 0 comments
Instructions
Test suite
Solution

Implement a clock that handles times without dates.

You should be able to add and subtract minutes to it.

Two clocks that represent the same time should be equal to each other.

Setup

Go through the setup instructions for Kotlin to install the necessary dependencies:

https://exercism.io/tracks/kotlin/installation

Making the test suite pass

Execute the tests with:

$ gradlew test

Use gradlew.bat if you're on Windows

In the test suites all tests but the first have been skipped.

Once you get a test passing, you can enable the next one by removing the @Ignore annotation.

Source

Pairing session with Erin Drummond https://twitter.com/ebdrummond

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

ClockAddTest.kt

import org.junit.Ignore
import org.junit.Test
import kotlin.test.assertEquals

class ClockAddTest {

    @Ignore
    @Test
    fun `add minutes`() =
        Clock(10, 0)
            .plusMinutes(3)
            .shouldBe("10:03")

    @Ignore
    @Test
    fun `add no minutes`() =
        Clock(6, 41)
            .plusMinutes(0)
            .shouldBe("06:41")

    @Ignore
    @Test
    fun `add to next hour`() =
        Clock(0, 45)
            .plusMinutes(40)
            .shouldBe("01:25")


    @Ignore
    @Test
    fun `add more than one hour`() =
        Clock(10, 0)
            .plusMinutes(61)
            .shouldBe("11:01")


    @Ignore
    @Test
    fun `add more than two hours with carry`() =
        Clock(0, 45)
            .plusMinutes(160)
            .shouldBe("03:25")


    @Ignore
    @Test
    fun `add across midnight`() =
        Clock(23, 59)
            .plusMinutes(2)
            .shouldBe("00:01")


    @Ignore
    @Test
    fun `add more than one day`() =
        Clock(5, 32)
            .plusMinutes(1500)
            .shouldBe("06:32")


    @Ignore
    @Test
    fun `add more than two days`() =
        Clock(1, 1)
            .plusMinutes(3500)
            .shouldBe("11:21")

}

private fun Clock.plusMinutes(minutes: Int): Clock {
    add(minutes)
    return this
}

private fun Clock.shouldBe(expectation: String) = assertEquals(expectation, toString())

ClockCreationTest.kt

import org.junit.Ignore
import org.junit.Test
import kotlin.test.assertEquals

class ClockCreationTest {

    @Test
    fun `on the hour`() = assertEquals("08:00", Clock(8, 0).toString())

    @Ignore
    @Test
    fun `past the hour`() = assertEquals("11:09", Clock(11, 9).toString())

    @Ignore
    @Test
    fun `midnight is zero hours`() = assertEquals("00:00", Clock(24, 0).toString())

    @Ignore
    @Test
    fun `hour rolls over`() = assertEquals("01:00", Clock(25, 0).toString())

    @Ignore
    @Test
    fun `hour rolls over continuously`() = assertEquals("04:00", Clock(100, 0).toString())

    @Ignore
    @Test
    fun `sixty minutes is next hour`() = assertEquals("02:00", Clock(1, 60).toString())

    @Ignore
    @Test
    fun `minutes roll over`() = assertEquals("02:40", Clock(0, 160).toString())

    @Ignore
    @Test
    fun `minutes roll over continuously`() = assertEquals("04:43", Clock(0, 1723).toString())

    @Ignore
    @Test
    fun `hour and minutes roll over`() = assertEquals("03:40", Clock(25, 160).toString())

    @Ignore
    @Test
    fun `hour and minutes roll over continuously`() = assertEquals("11:01", Clock(201, 3001).toString())

    @Ignore
    @Test
    fun `hour and minutes roll over to exactly midnight`() = assertEquals("00:00", Clock(72, 8640).toString())

    @Ignore
    @Test
    fun `negative hour`() = assertEquals("23:15", Clock(-1, 15).toString())

    @Ignore
    @Test
    fun `negative hour rolls over`() = assertEquals("23:00", Clock(-25, 0).toString())

    @Ignore
    @Test
    fun `negative hour rolls over continuously`() = assertEquals("05:00", Clock(-91, 0).toString())

    @Ignore
    @Test
    fun `negative minutes`() = assertEquals("00:20", Clock(1, -40).toString())

    @Ignore
    @Test
    fun `negative minutes roll over`() = assertEquals("22:20", Clock(1, -160).toString())

    @Ignore
    @Test
    fun `negative minutes roll over continuously`() = assertEquals("16:40", Clock(1, -4820).toString())

    @Ignore
    @Test
    fun `negative sixty minutes is previous hour`() = assertEquals("01:00", Clock(2, -60).toString())

    @Ignore
    @Test
    fun `negative hour and minutes both roll over`() = assertEquals("20:20", Clock(-25, -160).toString())

    @Ignore
    @Test
    fun `negative hour and minutes both roll over continuously`() =
        assertEquals("22:10", Clock(-121, -5810).toString())

}

ClockEqualTest.kt

import org.junit.Ignore
import org.junit.Test
import kotlin.test.assertNotEquals
import kotlin.test.assertEquals

class ClockEqualTest {

    @Ignore
    @Test
    fun `same time`() = assertEquals(Clock(15, 37), Clock(15, 37))

    @Ignore
    @Test
    fun `clocks a minute apart`() = assertNotEquals(Clock(15, 36), Clock(15, 37))

    @Ignore
    @Test
    fun `clocks an hour apart`() = assertNotEquals(Clock(14, 37), Clock(15, 37))

    @Ignore
    @Test
    fun `hour overflow`() = assertEquals(Clock(10, 37), Clock(34, 37))

    @Ignore
    @Test
    fun `hour overflow by several days`() = assertEquals(Clock(3, 11), Clock(99, 11))

    @Ignore
    @Test
    fun `negative hour`() = assertEquals(Clock(22, 40), Clock(-2, 40))

    @Ignore
    @Test
    fun `negative hour that wraps`() = assertEquals(Clock(17, 3), Clock(-31, 3))

    @Ignore
    @Test
    fun `negative hour that wraps multiple times`() = assertEquals(Clock(13, 49), Clock(-83, 49))

    @Ignore
    @Test
    fun `minute overflow`() = assertEquals(Clock(0, 1), Clock(0, 1441))

    @Ignore
    @Test
    fun `minute overflow by several days`() = assertEquals(Clock(2, 2), Clock(2, 4322))

    @Ignore
    @Test
    fun `negative minute`() = assertEquals(Clock(2, 40), Clock(3, -20))

    @Ignore
    @Test
    fun `negative minute that wraps`() = assertEquals(Clock(4, 10), Clock(5, -1490))

    @Ignore
    @Test
    fun `negative minute that wraps multiple times`() = assertEquals(Clock(6, 15), Clock(6, -4305))

    @Ignore
    @Test
    fun `negative hours and minutes`() = assertEquals(Clock(7, 32), Clock(-12, -268))

    @Ignore
    @Test
    fun `negative hours and minutes that wrap`() = assertEquals(Clock(18, 7), Clock(-54, -11513))

    @Ignore
    @Test
    fun `full clock and zeroed clock`() = assertEquals(Clock(24, 0), Clock(0, 0))

}

ClockSubtractTest.kt

import org.junit.Ignore
import org.junit.Test
import kotlin.test.assertEquals

class ClockSubtractTest {

    @Ignore
    @Test
    fun `subtract minutes`() =
        Clock(10, 3)
            .minusMinutes(3)
            .shouldBe("10:00")

    @Ignore
    @Test
    fun `subtract to previous hour`() =
        Clock(10, 3)
            .minusMinutes(30)
            .shouldBe("09:33")

    @Ignore
    @Test
    fun `subtract more than an hour`() =
        Clock(10, 3)
            .minusMinutes(70)
            .shouldBe("08:53")

    @Ignore
    @Test
    fun `subtract across midnight`() =
        Clock(0, 3)
            .minusMinutes(4)
            .shouldBe("23:59")

    @Ignore
    @Test
    fun `subtract more than two hours`() =
        Clock(0, 0)
            .minusMinutes(160)
            .shouldBe("21:20")

    @Ignore
    @Test
    fun `subtract more than two hours with borrow`() =
        Clock(6, 15)
            .minusMinutes(160)
            .shouldBe("03:35")

    @Ignore
    @Test
    fun `subtract more than one day`() =
        Clock(5, 32)
            .minusMinutes(1500)
            .shouldBe("04:32")

    @Ignore
    @Test
    fun `subtract more than two days`() =
        Clock(2, 20)
            .minusMinutes(3000)
            .shouldBe("00:20")

}

private fun Clock.minusMinutes(minutes: Int): Clock {
    subtract(minutes)
    return this
}

private fun Clock.shouldBe(expectation: String) = assertEquals(expectation, toString())
data class Clock constructor(var minutes: Int) {
    constructor(hour: Int, minutes: Int) : this(hour * 60 + minutes)

    init {
        minutes = minutes.normalizeMinutes()
    }

    override fun toString(): String {
        return "${(minutes / 60).toZeroPad()}:${(minutes % 60).toZeroPad()}"
    }

    fun subtract(minutes: Int) {
        this.minutes = (this.minutes - minutes).normalizeMinutes()
    }

    fun add(minutes: Int) {
        this.minutes = (this.minutes + minutes).normalizeMinutes()
    }
}

private const val minutesPerDay = 24 * 60

private fun Int.toZeroPad() = toString().padStart(2, '0')

private fun Int.normalizeMinutes() =
    if (this >= 0) this % minutesPerDay
    else minutesPerDay - (-this % (minutesPerDay))

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?