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

TSeydel's solution

to Clock in the Delphi Pascal Track

Published at Aug 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.

Testing

In order to run the tests for this track, you will need to install DUnitX. Please see the installation instructions for more information.

Loading Exercises into Delphi

If Delphi is properly installed, and *.dpr file types have been associated with Delphi, then double clicking the supplied *.dpr file will start Delphi and load the exercise/project. control + F9 is the keyboard shortcut to compile the project or pressing F9 will compile and run the project.

Alternatively you may opt to start Delphi and load your project via. the File drop down menu.

When Questions Come Up

We monitor the Pascal-Delphi support room on gitter.im to help you with any questions that might arise.

Submitting Exercises

Note that, when trying to submit an exercise, make sure the exercise file you're submitting is in the exercism/delphi/<exerciseName> directory.

For example, if you're submitting ubob.pas for the Bob exercise, the submit command would be something like exercism submit <path_to_exercism_dir>/delphi/bob/ubob.pas.

Source

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

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you may request help from a mentor.

uClockTests.pas

unit uClockTests;

interface
uses
  DUnitX.TestFramework;

const
  CanonicalVersion = '2.4.0.1';

type

  [TestFixture('Create a new clock with an initial time')]
  TClockTest = class(TObject)
  public
    [Test]
//  [Ignore('Comment the "[Ignore]" statement to run the test')]
    procedure On_the_hour;

    [Test]
    [Ignore]
    procedure Past_the_hour;

    [Test]
    [Ignore]
    procedure Midnight_is_zero_hours;

    [Test]
    [Ignore]
    procedure Hour_rolls_over;

    [Test]
    [Ignore]
    procedure Hour_rolls_over_continuously;

    [Test]
    [Ignore]
    procedure Sixty_minutes_is_next_hour;

    [Test]
    [Ignore]
    procedure Minutes_roll_over;

    [Test]
    [Ignore]
    procedure Minutes_roll_over_continuously;

    [Test]
    [Ignore]
    procedure Hour_and_minutes_roll_over;

    [Test]
    [Ignore]
    procedure Hour_and_minutes_roll_over_continuously;

    [Test]
    [Ignore]
    procedure Hour_and_minutes_roll_over_to_exactly_midnight;

    [Test]
    [Ignore]
    procedure Negative_hour;

    [Test]
    [Ignore]
    procedure Negative_hour_rolls_over;

    [Test]
    [Ignore]
    procedure Negative_hour_rolls_over_continuously;

    [Test]
    [Ignore]
    procedure Negative_minutes;

    [Test]
    [Ignore]
    procedure Negative_minutes_roll_over;

    [Test]
    [Ignore]
    procedure Negative_minutes_roll_over_continuously;

    [Test]
    [Ignore]
    procedure Negative_sixty_minutes_is_previous_hour;

    [Test]
    [Ignore]
    procedure Negative_hour_and_minutes_both_roll_over;

    [Test]
    [Ignore]
    procedure Negative_hour_and_minutes_both_roll_over_continuously;
  end;

  [TestFixture('Add minutes')]
  TAddMinutes = class(TObject)
  public
    [Test]
    [Ignore]
    procedure Add_minutes;

    [Test]
    [Ignore]
    procedure Add_no_minutes;

    [Test]
    [Ignore]
    procedure Add_to_next_hour;

    [Test]
    [Ignore]
    procedure Add_more_than_one_hour;

    [Test]
    [Ignore]
    procedure Add_more_than_two_hours_with_carry;

    [Test]
    [Ignore]
    procedure Add_across_midnight;

    [Test]
    [Ignore]
    procedure Add_more_than_one_day; //1500 min = 25 hrs

    [Test]
    [Ignore]
    procedure Add_more_than_two_days;
  end;

  [TestFixture('Subtract minutes')]
  TSubtractMinutes = class(TObject)
  public
    [Test]
    [Ignore]
    procedure Subtract_minutes;

    [Test]
    [Ignore]
    procedure Subtract_to_previous_hour;

    [Test]
    [Ignore]
    procedure Subtract_more_than_one_hour;

    [Test]
    [Ignore]
    procedure Subtract_across_midnight;

    [Test]
    [Ignore]
    procedure Subtract_more_than_two_hours;

    [Test]
    [Ignore]
    procedure Subtract_more_than_two_hours_with_borrow;

    [Test]
    [Ignore]
    procedure Subtract_more_than_one_day; //(1500 min = 25 hrs)

    [Test]
    [Ignore]
    procedure Subtract_more_than_two_days;
  end;

  [TestFixture('Compare two clocks for equality')]
  TCompareClocks = class(TObject)
  public
    [Test]
    [Ignore]
    procedure Clocks_with_same_time;

    [Test]
    [Ignore]
    procedure Clocks_a_minute_apart;

    [Test]
    [Ignore]
    procedure Clocks_an_hour_apart;

    [Test]
    [Ignore]
    procedure Clocks_with_hour_overflow;

    [Test]
    [Ignore]
    procedure Clocks_with_hour_overflow_by_several_days;

    [Test]
    [Ignore]
    procedure Clocks_with_negative_hour;

    [Test]
    [Ignore]
    procedure Clocks_with_negative_hour_that_wraps;

    [Test]
    [Ignore]
    procedure Clocks_with_negative_hour_that_wraps_multiple_times;

    [Test]
    [Ignore]
    procedure Clocks_with_minute_overflow;

    [Test]
    [Ignore]
    procedure Clocks_with_minute_overflow_by_serveral_days;

    [Test]
    [Ignore]
    procedure Clocks_with_negative_minute;

    [Test]
    [Ignore]
    procedure Clocks_with_negative_minute_that_wraps;

    [Test]
    [Ignore]
    procedure Clocks_with_negative_minute_that_wraps_multiple_times;

    [Test]
    [Ignore]
    procedure Clocks_with_negative_hours_and_minutes;

    [Test]
    [Ignore]
    procedure Clocks_with_negative_hours_and_minutes_that_wrap;

    [Test]
    [Ignore]
    procedure full_clock_and_zeroed_clock;
  end;

implementation
uses uClock;

{$region 'TClockTest'}
procedure TClockTest.On_the_hour;
begin
  Assert.AreEqual('08:00', Clock.SetHands(8).ToString);
end;

procedure TClockTest.Past_the_hour;
begin
  Assert.AreEqual('11:09', Clock.SetHands(11, 9).ToString);
end;

procedure TClockTest.Midnight_is_zero_hours;
begin
  Assert.AreEqual('00:00', Clock.SetHands(24).ToString);
end;

procedure TClockTest.Hour_rolls_over;
begin
  Assert.AreEqual('01:00', Clock.SetHands(25).ToString);
end;

procedure TClockTest.Hour_rolls_over_continuously;
begin
  Assert.AreEqual('04:00', Clock.SetHands(100).ToString);
end;

procedure TClockTest.Sixty_minutes_is_next_hour;
begin
  Assert.AreEqual('02:00', Clock.SetHands(1, 60).ToString);
end;

procedure TClockTest.Minutes_roll_over;
begin
  Assert.AreEqual('02:40', Clock.SetHands(0, 160).ToString);
end;

procedure TClockTest.Minutes_roll_over_continuously;
begin
  Assert.AreEqual('04:43', Clock.SetHands(0, 1723).ToString);
end;

procedure TClockTest.Hour_and_minutes_roll_over;
begin
  Assert.AreEqual('03:40', Clock.SetHands(25, 160).ToString);
end;

procedure TClockTest.Hour_and_minutes_roll_over_continuously;
begin
  Assert.AreEqual('11:01', Clock.SetHands(201, 3001).ToString);
end;

procedure TClockTest.Hour_and_minutes_roll_over_to_exactly_midnight;
begin
  Assert.AreEqual('00:00', Clock.SetHands(72, 8640).ToString);
end;

procedure TClockTest.Negative_hour;
begin
  Assert.AreEqual('23:15', Clock.SetHands(-1, 15).ToString);
end;

procedure TClockTest.Negative_hour_rolls_over;
begin
  Assert.AreEqual('23:00', Clock.SetHands(-25).ToString);
end;

procedure TClockTest.Negative_hour_rolls_over_continuously;
begin
  Assert.AreEqual('05:00', Clock.SetHands(-91).ToString);
end;

procedure TClockTest.Negative_minutes;
begin
  Assert.AreEqual('00:20', Clock.SetHands(1, -40).ToString);
end;

procedure TClockTest.Negative_minutes_roll_over;
begin
  Assert.AreEqual('22:20', Clock.SetHands(1, -160).ToString);
end;

procedure TClockTest.Negative_minutes_roll_over_continuously;
begin
  Assert.AreEqual('16:40', Clock.SetHands(1, -4820).ToString);
end;

procedure TClockTest.Negative_sixty_minutes_is_previous_hour;
begin
  Assert.AreEqual('01:00', Clock.SetHands(2, -60).ToString);
end;

procedure TClockTest.Negative_hour_and_minutes_both_roll_over;
begin
  Assert.AreEqual('20:20', Clock.SetHands(-25, -160).ToString);
end;

procedure TClockTest.Negative_hour_and_minutes_both_roll_over_continuously;
begin
  Assert.AreEqual('22:10', Clock.SetHands(-121, -5810).ToString);
end;
{$endregion}

{$region 'TAddMinutes'}
procedure TAddMinutes.Add_minutes;
begin
  Assert.AreEqual('10:03', Clock.SetHands(10).Add(3).ToString);
end;

procedure TAddMinutes.Add_no_minutes;
begin
  Assert.AreEqual('06:41', Clock.SetHands(6, 41).Add(0).ToString);
end;

procedure TAddMinutes.Add_to_next_hour;
begin
  Assert.AreEqual('01:25', Clock.SetHands(0, 45).Add(40).ToString);
end;

procedure TAddMinutes.Add_more_than_one_hour;
begin
  Assert.AreEqual('11:01', Clock.SetHands(10).Add(61).ToString);
end;

procedure TAddMinutes.Add_more_than_two_hours_with_carry;
begin
  Assert.AreEqual('03:25', Clock.SetHands(0, 45).Add(160).ToString);
end;

procedure TAddMinutes.Add_across_midnight;
begin
  Assert.AreEqual('00:01', Clock.SetHands(23, 59).Add(2).ToString);
end;

procedure TAddMinutes.Add_more_than_one_day;
begin
  Assert.AreEqual('06:32', Clock.SetHands(5, 32).Add(1500).ToString);
end;

procedure TAddMinutes.Add_more_than_two_days;
begin
  Assert.AreEqual('11:21', Clock.SetHands(1, 1).Add(3500).ToString);
end;
{$endregion}

{$region 'TSubtractMinutes'}
procedure TSubtractMinutes.Subtract_minutes;
begin
  Assert.AreEqual('10:00', Clock.SetHands(10, 3).Subtract(3).ToString);
end;

procedure TSubtractMinutes.Subtract_to_previous_hour;
begin
  Assert.AreEqual('09:33', Clock.SetHands(10, 3).Subtract(30).ToString);
end;

procedure TSubtractMinutes.Subtract_more_than_one_hour;
begin
  Assert.AreEqual('08:53', Clock.SetHands(10, 3).Subtract(70).ToString);
end;

procedure TSubtractMinutes.Subtract_across_midnight;
begin
  Assert.AreEqual('23:59', Clock.SetHands(0, 3).Subtract(4).ToString);
end;

procedure TSubtractMinutes.Subtract_more_than_two_hours;
begin
  Assert.AreEqual('21:20', Clock.SetHands(0, 0).Subtract(160).ToString);
end;

procedure TSubtractMinutes.Subtract_more_than_two_hours_with_borrow;
begin
  Assert.AreEqual('03:35', Clock.SetHands(6, 15).Subtract(160).ToString);
end;

procedure TSubtractMinutes.Subtract_more_than_one_day;
begin
  Assert.AreEqual('04:32', Clock.SetHands(5, 32).Subtract(1500).ToString);
end;

procedure TSubtractMinutes.Subtract_more_than_two_days;
begin
  Assert.AreEqual('00:20', Clock.SetHands(2, 20).Subtract(3000).ToString);
end;
{$endregion}

{$region 'TCompareClocks'}
procedure TCompareClocks.Clocks_with_same_time;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(15, 37);
  Clock2 := Clock.SetHands(15, 37);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_a_minute_apart;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(15, 36);
  Clock2 := Clock.SetHands(15, 37);

  Assert.IsFalse(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_an_hour_apart;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(14, 37);
  Clock2 := Clock.SetHands(15, 37);

  Assert.IsFalse(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_with_hour_overflow;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(10, 37);
  Clock2 := Clock.SetHands(34, 37);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_with_hour_overflow_by_several_days;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(3, 11);
  Clock2 := Clock.SetHands(99, 11);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_with_negative_hour;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(22, 40);
  Clock2 := Clock.SetHands(-2, 40);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_with_negative_hour_that_wraps;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(17, 3);
  Clock2 := Clock.SetHands(-31, 3);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_with_negative_hour_that_wraps_multiple_times;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(13, 49);
  Clock2 := Clock.SetHands(-83, 49);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_with_minute_overflow;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(0, 1);
  Clock2 := Clock.SetHands(0, 1441);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_with_minute_overflow_by_serveral_days;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(2, 2);
  Clock2 := Clock.SetHands(2, 4322);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_with_negative_minute;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(2, 40);
  Clock2 := Clock.SetHands(3, -20);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_with_negative_minute_that_wraps;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(4, 10);
  Clock2 := Clock.SetHands(5, -1490);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_with_negative_minute_that_wraps_multiple_times;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(6, 15);
  Clock2 := Clock.SetHands(6, -4305);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_with_negative_hours_and_minutes;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(7, 32);
  Clock2 := Clock.SetHands(-12, -268);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.Clocks_with_negative_hours_and_minutes_that_wrap;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(18, 7);
  Clock2 := Clock.SetHands(-54, -11513);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

procedure TCompareClocks.full_clock_and_zeroed_clock;
var Clock1, Clock2: Clock;
begin
  Clock1 := Clock.SetHands(24, 0);
  Clock2 := Clock.SetHands(0, 0);

  Assert.IsTrue(Clock1.Equal(Clock2));
end;

{$endregion}

initialization
  TDUnitX.RegisterTestFixture(TClockTest);
  TDUnitX.RegisterTestFixture(TAddMinutes);
  TDUnitX.RegisterTestFixture(TSubtractMinutes);
  TDUnitX.RegisterTestFixture(TCompareClocks);
end.
unit uClock;

interface

type
  Clock = class
    private
      FHours: Integer;
      FMinutes: Integer;
    public
      function ToString: string; overload;
      class function SetHands(aHours: Integer = 0; aMinutes: Integer = 0):Clock;
      function Add(aMinutes: Integer): Clock;
      function Subtract(aMinutes: Integer): Clock;
      function Equal(aClock: Clock): Boolean;
      constructor Create(aHours:Integer; aMinutes:Integer);
  end;

implementation

uses
  System.SysUtils;

function Clock.Add(aMinutes: Integer): Clock;
begin
  Result := SetHands(FHours,FMinutes + aMinutes);
end;

constructor Clock.Create(aHours:Integer; aMinutes:Integer);
var
  lTotalMinutes: Integer;
begin
  lTotalMinutes := (aHours * 60 + aMinutes) mod 1440 + 1440; //24 * 60 = 1440
  FHours := Trunc(lTotalMinutes/60) mod 24;
  FMinutes := lTotalMinutes mod 60;
end;

function Clock.Equal(aClock: Clock): Boolean;
begin
  Result := (FHours = aClock.FHours) And (FMinutes = aClock.FMinutes);
end;

class function Clock.SetHands(aHours: Integer = 0; aMinutes: Integer = 0): Clock;
begin
  Result := Clock.Create(aHours,aMinutes);
end;

function Clock.Subtract(aMinutes: Integer): Clock;
begin
  Result := SetHands(FHours,FMinutes - aMinutes);
end;

function Clock.ToString: string;
begin
  Result := Format('%.2d:%.2d', [FHours,FMinutes]);
end;

end.

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?