ðŸŽ‰ Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io ðŸŽ‰

# glennj's solution

## to Clock in the Kotlin Track

Published at Feb 12 2020 · 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())``````
``````class Clock(hour: Int, minute: Int) {

private var minuteOfDay: Int = 0

init {
minuteOfDay = hour * 60 + minute
normalize()
}

private fun normalize() {
minuteOfDay = minuteOfDay.posMod(24 * 60)
}

override fun toString(): String =
String.format("%02d:%02d", minuteOfDay / 60, minuteOfDay % 60)

override fun equals(other: Any?) = when(other) {
is Clock -> this.minuteOfDay == other.minuteOfDay
else -> false
}

override fun hashCode(): Int = minuteOfDay

fun add(minutes: Int) {
minuteOfDay += minutes
normalize()
}

fun subtract(minutes: Int) {
add(minutes * -1)
}
}

// get (-12 "mod" 10) to report result in `0 until divisor`
// i.e. +8, not -2
fun Int.posMod(divisor: Int): Int {
require(divisor > 0)
return ((this % divisor) + divisor) % divisor
}``````

## 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?