Exercism v3 launches on Sept 1st 2021. Learn more! ๐Ÿš€๐Ÿš€๐Ÿš€
Avatar of rootulp

rootulp's solution

to Clock in the TypeScript Track

Published at Jun 05 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 TypeScript to install the necessary dependencies:

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

Requirements

Install assignment dependencies:

$ yarn install

Making the test suite pass

Execute the tests with:

$ yarn test

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 changing xit to it.

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.

clock.test.ts

import Clock from './clock'

describe('Clock', () => {
  describe('Creating a new clock with an initial time', () => {
    test('on the hour', () => {
      expect(new Clock(8).toString()).toEqual('08:00')
    })

    test('past the hour', () => {
      expect(new Clock(11, 9).toString()).toEqual('11:09')
    })

    test('midnight is zero hours', () => {
      expect(new Clock(24, 0).toString()).toEqual('00:00')
    })

    test('hour rolls over', () => {
      expect(new Clock(25, 0).toString()).toEqual('01:00')
    })

    test('hour rolls over continuously', () => {
      expect(new Clock(100, 0).toString()).toEqual('04:00')
    })

    test('sixty minutes is next hour', () => {
      expect(new Clock(1, 60).toString()).toEqual('02:00')
    })

    test('minutes roll over', () => {
      expect(new Clock(0, 160).toString()).toEqual('02:40')
    })

    test('minutes roll over continuously', () => {
      expect(new Clock(0, 1723).toString()).toEqual('04:43')
    })

    test('hour and minutes roll over', () => {
      expect(new Clock(25, 160).toString()).toEqual('03:40')
    })

    test('hour and minutes roll over continuously', () => {
      expect(new Clock(201, 3001).toString()).toEqual('11:01')
    })

    test('hour and minutes roll over to exactly midnight', () => {
      expect(new Clock(72, 8640).toString()).toEqual('00:00')
    })

    test('negative hour', () => {
      expect(new Clock(-1, 15).toString()).toEqual('23:15')
    })

    test('negative hour rolls over', () => {
      expect(new Clock(-25, 0).toString()).toEqual('23:00')
    })

    test('negative hour rolls over continuously', () => {
      expect(new Clock(-91, 0).toString()).toEqual('05:00')
    })

    test('negative minutes', () => {
      expect(new Clock(1, -40).toString()).toEqual('00:20')
    })

    test('negative minutes rolls over', () => {
      expect(new Clock(1, -160).toString()).toEqual('22:20')
    })

    test('negative minutes rolls over continuously', () => {
      expect(new Clock(1, -4820).toString()).toEqual('16:40')
    })

    test('negative hour and minutes both roll over', () => {
      expect(new Clock(-25, -160).toString()).toEqual('20:20')
    })

    test('negative hour and minutes both roll over continuously', () => {
      expect(new Clock(-121, -5810).toString()).toEqual('22:10')
    })

    describe('Adding and subtracting minutes', () => {
      test('add minutes', () => {
        expect(new Clock(10, 0).plus(3).toString()).toEqual('10:03')
      })

      test('add no minutes', () => {
        expect(new Clock(6, 41).plus(0).toString()).toEqual('06:41')
      })

      test('add to next hour', () => {
        expect(new Clock(0, 45).plus(40).toString()).toEqual('01:25')
      })

      test('add more than one hour', () => {
        expect(new Clock(10, 0).plus(61).toString()).toEqual('11:01')
      })

      test('add more than two hours with carry', () => {
        expect(new Clock(0, 45).plus(160).toString()).toEqual('03:25')
      })

      test('add across midnight', () => {
        expect(new Clock(23, 59).plus(2).toString()).toEqual('00:01')
      })

      test('add more than one day (1500 min = 25 hrs)', () => {
        expect(new Clock(5, 32).plus(1500).toString()).toEqual('06:32')
      })

      test('add more than two days', () => {
        expect(new Clock(1, 1).plus(3500).toString()).toEqual('11:21')
      })

      test('subtract minutes', () => {
        expect(new Clock(10, 3).minus(3).toString()).toEqual('10:00')
      })

      test('subtract to previous hour', () => {
        expect(new Clock(10, 3).minus(30).toString()).toEqual('09:33')
      })

      test('subtract more than an hour', () => {
        expect(new Clock(10, 3).minus(70).toString()).toEqual('08:53')
      })

      test('subtract across midnight', () => {
        expect(new Clock(0, 3).minus(4).toString()).toEqual('23:59')
      })

      test('subtract more than two hours', () => {
        expect(new Clock(0, 0).minus(160).toString()).toEqual('21:20')
      })

      test('subtract more than two hours with borrow', () => {
        expect(new Clock(6, 15).minus(160).toString()).toEqual('03:35')
      })

      test('subtract more than one day (1500 min = 25 hrs)', () => {
        expect(new Clock(5, 32).minus(1500).toString()).toEqual('04:32')
      })

      test('subtract more than two days', () => {
        expect(new Clock(2, 20).minus(3000).toString()).toEqual('00:20')
      })
    })

    describe('Construct two separate clocks, set times, test if they are equal', () => {
      test('clocks with same time', () => {
        expect(new Clock(15, 37).equals(new Clock(15, 37))).toBeTruthy()
      })

      test('clocks a minute apart', () => {
        expect(new Clock(15, 36).equals(new Clock(15, 37))).toBeFalsy()
      })

      test('clocks an hour apart', () => {
        expect(new Clock(14, 37).equals(new Clock(15, 37))).toBeFalsy()
      })

      test('clocks with hour overflow', () => {
        expect(new Clock(10, 37).equals(new Clock(34, 37))).toBeTruthy()
      })

      test('clocks with hour overflow by several days', () => {
        expect(new Clock(3, 11).equals(new Clock(99, 11))).toBeTruthy()
      })

      test('clocks with negative hour', () => {
        expect(new Clock(22, 40).equals(new Clock(-2, 40))).toBeTruthy()
      })

      test('clocks with negative hour that wraps', () => {
        expect(new Clock(17, 3).equals(new Clock(-31, 3))).toBeTruthy()
      })

      test('clocks with negative hour that wraps multiple times', () => {
        expect(new Clock(13, 49).equals(new Clock(-83, 49))).toBeTruthy()
      })

      test('clocks with minute overflow', () => {
        expect(new Clock(0, 1).equals(new Clock(0, 1441))).toBeTruthy()
      })

      test('clocks with minute overflow by several days', () => {
        expect(new Clock(2, 2).equals(new Clock(2, 4322))).toBeTruthy()
      })

      test('clocks with negative minute', () => {
        expect(new Clock(2, 40).equals(new Clock(3, -20))).toBeTruthy()
      })

      test('clocks with negative minute that wraps', () => {
        expect(new Clock(4, 10).equals(new Clock(5, -1490))).toBeTruthy()
      })

      test('clocks with negative minute that wraps multiple times', () => {
        expect(new Clock(6, 15).equals(new Clock(6, -4305))).toBeTruthy()
      })

      test('clocks with negative hours and minutes', () => {
        expect(new Clock(7, 32).equals(new Clock(-12, -268))).toBeTruthy()
      })

      test('clocks with negative hours and minutes that wrap', () => {
        expect(new Clock(18, 7).equals(new Clock(-54, -11513))).toBeTruthy()
      })
    })
  })
})
export default class Clock {
    private hours: number;
    private minutes: number;

    constructor(hours: number, minutes?: number) {
        this.minutes = minutes ? Clock.mod(minutes, 60) : 0;
        const additionalHours = minutes ? Math.floor(minutes / 60) : 0;
        this.hours = Clock.mod((hours + additionalHours), 24);
    }

    public toString() {
        const formattedHours = this.hours.toString().padStart(2, '0')
        const formattedMinutes = this.minutes ? this.minutes.toString().padStart(2, '0') : '00'
        return [formattedHours, formattedMinutes].join(':')
    }

    public plus(minutes: number): Clock {
        const newMins = this.minutes + minutes;
        this.minutes = Clock.mod(newMins, 60);
        const additionalHours =  Math.floor(newMins / 60);
        this.hours = Clock.mod(this.hours + additionalHours, 24);
        return this;
    }

    public minus(minutes: number): Clock {
        const newMins = this.minutes - minutes;
        this.minutes = Clock.mod(newMins, 60);
        const removedHours = Math.floor(newMins / 60);
        this.hours = Clock.mod(this.hours + removedHours, 24);
        return this;
    }

    public equals(c: Clock): boolean {
        return this.hours === c.hours &&
               this.minutes === c.minutes;
    }

    // Can't use % because JavaScript module bug
    // https://web.archive.org/web/20090717035140if_/javascript.about.com/od/problemsolving/a/modulobug.htm
    private static mod(x: number, y: number): number {
        return ((x % y) + y) % y;
    }
}

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?