diff --git a/python/clock/.exercism/metadata.json b/python/clock/.exercism/metadata.json new file mode 100644 index 0000000..0a0530c --- /dev/null +++ b/python/clock/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"clock","id":"5ea6c6c4c840489da2850d5d3dacd63f","url":"https://exercism.io/my/solutions/5ea6c6c4c840489da2850d5d3dacd63f","handle":"Xevion","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/python/clock/README.md b/python/clock/README.md new file mode 100644 index 0000000..240ca5c --- /dev/null +++ b/python/clock/README.md @@ -0,0 +1,56 @@ +# Clock + +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. + +## Exception messages + +Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to +indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not +every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include +a message. + +To raise a message with an exception, just write it as an argument to the exception type. For example, instead of +`raise Exception`, you should write: + +```python +raise Exception("Meaningful message indicating the source of the error") +``` + +## Running the tests + +To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)): + +- Python 2.7: `py.test clock_test.py` +- Python 3.4+: `pytest clock_test.py` + +Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version): +`python -m pytest clock_test.py` + +### Common `pytest` options + +- `-v` : enable verbose output +- `-x` : stop running tests on first failure +- `--ff` : run failures from previous test before running other test cases + +For other options, see `python -m pytest -h` + +## Submitting Exercises + +Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/clock` directory. + +You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`. + +For more detailed information about running tests, code style and linting, +please see [Running the Tests](http://exercism.io/tracks/python/tests). + +## Source + +Pairing session with Erin Drummond [https://twitter.com/ebdrummond](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. diff --git a/python/clock/clock.py b/python/clock/clock.py new file mode 100644 index 0000000..4eebf48 --- /dev/null +++ b/python/clock/clock.py @@ -0,0 +1,26 @@ +class Clock(object): + def __init__(self, hour, minute): + self.hour, self.minute = hour, minute + + def __repr__(self): + return ''.format(str(self.hour).zfill(2), str(self.minute).zfill(2)) + + def __eq__(self, other): + return self.hour == other.hour and self.minute == other.minute + + def change(self, minutes): + self.minute += minutes + while self.minute >= 60 or self.minute < 0: + if self.minute >= 60: + self.hour += 1 + self.minute -= 60 + elif self.minute < 0: + self.hour -= 1 + self.minute += 60 + self.hour = self.hour % 24 + + def __add__(self, minutes): + return self.change(minutes) + + def __sub__(self, minutes): + return self.change(-minutes) \ No newline at end of file diff --git a/python/clock/clock_test.py b/python/clock/clock_test.py new file mode 100644 index 0000000..3eed4ee --- /dev/null +++ b/python/clock/clock_test.py @@ -0,0 +1,170 @@ +import unittest + +from clock import Clock + + +# Tests adapted from `problem-specifications//canonical-data.json` @ v2.4.0 + +class ClockTest(unittest.TestCase): + # Test creating a new clock with an initial time. + def test_on_the_hour(self): + self.assertEqual(str(Clock(8, 0)), '08:00') + + def test_past_the_hour(self): + self.assertEqual(str(Clock(11, 9)), '11:09') + + def test_midnight_is_zero_hours(self): + self.assertEqual(str(Clock(24, 0)), '00:00') + + def test_hour_rolls_over(self): + self.assertEqual(str(Clock(25, 0)), '01:00') + + def test_hour_rolls_over_continuously(self): + self.assertEqual(str(Clock(100, 0)), '04:00') + + def test_sixty_minutes_is_next_hour(self): + self.assertEqual(str(Clock(1, 60)), '02:00') + + def test_minutes_roll_over(self): + self.assertEqual(str(Clock(0, 160)), '02:40') + + def test_minutes_roll_over_continuously(self): + self.assertEqual(str(Clock(0, 1723)), '04:43') + + def test_hour_and_minutes_roll_over(self): + self.assertEqual(str(Clock(25, 160)), '03:40') + + def test_hour_and_minutes_roll_over_continuously(self): + self.assertEqual(str(Clock(201, 3001)), '11:01') + + def test_hour_and_minutes_roll_over_to_exactly_midnight(self): + self.assertEqual(str(Clock(72, 8640)), '00:00') + + def test_negative_hour(self): + self.assertEqual(str(Clock(-1, 15)), '23:15') + + def test_negative_hour_rolls_over(self): + self.assertEqual(str(Clock(-25, 0)), '23:00') + + def test_negative_hour_rolls_over_continuously(self): + self.assertEqual(str(Clock(-91, 0)), '05:00') + + def test_negative_minutes(self): + self.assertEqual(str(Clock(1, -40)), '00:20') + + def test_negative_minutes_roll_over(self): + self.assertEqual(str(Clock(1, -160)), '22:20') + + def test_negative_minutes_roll_over_continuously(self): + self.assertEqual(str(Clock(1, -4820)), '16:40') + + def test_negative_sixty_minutes_is_previous_hour(self): + self.assertEqual(str(Clock(2, -60)), '01:00') + + def test_negative_hour_and_minutes_both_roll_over(self): + self.assertEqual(str(Clock(-25, -160)), '20:20') + + def test_negative_hour_and_minutes_both_roll_over_continuously(self): + self.assertEqual(str(Clock(-121, -5810)), '22:10') + + # Test adding and subtracting minutes. + def test_add_minutes(self): + self.assertEqual(str(Clock(10, 0) + 3), '10:03') + + def test_add_no_minutes(self): + self.assertEqual(str(Clock(6, 41) + 0), '06:41') + + def test_add_to_next_hour(self): + self.assertEqual(str(Clock(0, 45) + 40), '01:25') + + def test_add_more_than_one_hour(self): + self.assertEqual(str(Clock(10, 0) + 61), '11:01') + + def test_add_more_than_two_hours_with_carry(self): + self.assertEqual(str(Clock(0, 45) + 160), '03:25') + + def test_add_across_midnight(self): + self.assertEqual(str(Clock(23, 59) + 2), '00:01') + + def test_add_more_than_one_day(self): + self.assertEqual(str(Clock(5, 32) + 1500), '06:32') + + def test_add_more_than_two_days(self): + self.assertEqual(str(Clock(1, 1) + 3500), '11:21') + + def test_subtract_minutes(self): + self.assertEqual(str(Clock(10, 3) - 3), '10:00') + + def test_subtract_to_previous_hour(self): + self.assertEqual(str(Clock(10, 3) - 30), '09:33') + + def test_subtract_more_than_an_hour(self): + self.assertEqual(str(Clock(10, 3) - 70), '08:53') + + def test_subtract_across_midnight(self): + self.assertEqual(str(Clock(0, 3) - 4), '23:59') + + def test_subtract_more_than_two_hours(self): + self.assertEqual(str(Clock(0, 0) - 160), '21:20') + + def test_subtract_more_than_two_hours_with_borrow(self): + self.assertEqual(str(Clock(6, 15) - 160), '03:35') + + def test_subtract_more_than_one_day(self): + self.assertEqual(str(Clock(5, 32) - 1500), '04:32') + + def test_subtract_more_than_two_days(self): + self.assertEqual(str(Clock(2, 20) - 3000), '00:20') + + # Construct two separate clocks, set times, test if they are equal. + def test_clocks_with_same_time(self): + self.assertEqual(Clock(15, 37), Clock(15, 37)) + + def test_clocks_a_minute_apart(self): + self.assertNotEqual(Clock(15, 36), Clock(15, 37)) + + def test_clocks_an_hour_apart(self): + self.assertNotEqual(Clock(14, 37), Clock(15, 37)) + + def test_clocks_with_hour_overflow(self): + self.assertEqual(Clock(10, 37), Clock(34, 37)) + + def test_clocks_with_hour_overflow_by_several_days(self): + self.assertEqual(Clock(3, 11), Clock(99, 11)) + + def test_clocks_with_negative_hour(self): + self.assertEqual(Clock(22, 40), Clock(-2, 40)) + + def test_clocks_with_negative_hour_that_wraps(self): + self.assertEqual(Clock(17, 3), Clock(-31, 3)) + + def test_clocks_with_negative_hour_that_wraps_multiple_times(self): + self.assertEqual(Clock(13, 49), Clock(-83, 49)) + + def test_clocks_with_minute_overflow(self): + self.assertEqual(Clock(0, 1), Clock(0, 1441)) + + def test_clocks_with_minute_overflow_by_several_days(self): + self.assertEqual(Clock(2, 2), Clock(2, 4322)) + + def test_clocks_with_negative_minute(self): + self.assertEqual(Clock(2, 40), Clock(3, -20)) + + def test_clocks_with_negative_minute_that_wraps(self): + self.assertEqual(Clock(4, 10), Clock(5, -1490)) + + def test_clocks_with_negative_minute_that_wraps_multiple_times(self): + self.assertEqual(Clock(6, 15), Clock(6, -4305)) + + def test_clocks_with_negative_hours_and_minutes(self): + self.assertEqual(Clock(7, 32), Clock(-12, -268)) + + def test_clocks_with_negative_hours_and_minutes_that_wrap(self): + self.assertEqual(Clock(18, 7), Clock(-54, -11513)) + + def test_full_clock_and_zeroed_clock(self): + self.assertEqual(Clock(24, 0), Clock(0, 0)) + + +if __name__ == '__main__': + unittest.main()