diff --git a/concepts/time-duration/.meta/config.json b/concepts/time-duration/.meta/config.json new file mode 100644 index 000000000..f4fe5ba8d --- /dev/null +++ b/concepts/time-duration/.meta/config.json @@ -0,0 +1,7 @@ +{ + "blurb": "Learn how concept time and duration is implemented in Go.", + "authors": [ + "thibault2705" + ], + "contributors": [] +} diff --git a/concepts/time-duration/about.md b/concepts/time-duration/about.md new file mode 100644 index 000000000..389acae1d --- /dev/null +++ b/concepts/time-duration/about.md @@ -0,0 +1,94 @@ +# About + +In Go, time-based calculations are handled using the [`time`][time-package] package. +Two key types used in this exercise are: + +- [`time.Time`][time]: represents a specific moment in time (a timestamp) +- [`time.Duration`][duration]: represents the amount of time elapsed between two moments + +--- + +## `time.Time` vs `time.Duration` + +A `time.Time` represents an absolute point in time, such as: + +```go +t := time.Now() +``` + +A `time.Duration` represents the difference between two points in time: +```go +start := time.Now() +end := start.Add(5 * time.Minute) + +duration := end.Sub(start) // => 5m0s +``` + +In short: +- `time.Time` → “when something happens” +- `time.Duration` → “how long it lasts” + +## Measuring Time with `time.Since` +To measure how long something takes (e.g., execution time), you can use `time.Since`[since] +```go +start := time.Now() + +// some work here + +elapsed := time.Since(start) +fmt.Println(elapsed) +``` + +## Adding and Subtracting Time +You can manipulate timestamps using: +- [`Time.Add()`][add] → add or subtract a duration +- [`Time.Sub()`][sub] → calculate the difference between two times + +## Time Units +Go provides built-in constants for time units: + +| Unit | Constant | +| ----------- |----------------------| +|Nanosecond | `time.Nanosecond` | +|Microsecond | `time.Microsecond ` | +|Millisecond | `time.Millisecond` | +|Second | `time.Second` | +|Minute | `time.Minute` | +|Hour | `time.Hour` | + +You can combine them to build durations: +```go +d := 1*time.Hour + 5*time.Minute + 19*time.Second + 234*time.Millisecond +``` + +You can also perform calculations: +```go +seconds := d.Seconds() // float64 +minutes := d.Minutes() +``` + +## Working with Durations +Durations can be: +- Added to a `time.Time` +- Subtracted from a `time.Time` +- Compared or converted to different units + +```go +start := time.Now() +finish := start.Add(30 * time.Second) + +duration := finish.Sub(start) // => 30s +``` + +## Other resources +- [Go by Example: Time](https://gobyexample.com/time) +- [Go by Example: Time Formatting / Parsing](https://gobyexample.com/time-formatting-parsing) + +[time-package]: https://pkg.go.dev/time +[time]: https://pkg.go.dev/time#Time +[duration]: https://pkg.go.dev/time#Duration +[since]: https://pkg.go.dev/time#Since +[add]: https://pkg.go.dev/time#Time.Add +[sub]: https://pkg.go.dev/time#Time.Sub +[now]: https://pkg.go.dev/time#Now + diff --git a/concepts/time-duration/introduction.md b/concepts/time-duration/introduction.md new file mode 100644 index 000000000..8f9881e6e --- /dev/null +++ b/concepts/time-duration/introduction.md @@ -0,0 +1,94 @@ +# Introduction + +In Go, time-based calculations are handled using the [`time`][time-package] package. +Two key types used in this exercise are: + +- [`time.Time`][time]: represents a specific moment in time (a timestamp) +- [`time.Duration`][duration]: represents the amount of time elapsed between two moments + +--- + +## `time.Time` vs `time.Duration` + +A `time.Time` represents an absolute point in time, such as: + +```go +t := time.Now() +``` + +A `time.Duration` represents the difference between two points in time: +```go +start := time.Now() +end := start.Add(5 * time.Minute) + +duration := end.Sub(start) // => 5m0s +``` + +In short: +- `time.Time` → “when something happens” +- `time.Duration` → “how long it lasts” + +## Measuring Time with `time.Since` +To measure how long something takes (e.g., execution time), you can use `time.Since`[since] +```go +start := time.Now() + +// some work here + +elapsed := time.Since(start) +fmt.Println(elapsed) +``` + +## Adding and Subtracting Time +You can manipulate timestamps using: +- [`Time.Add()`][add] → add or subtract a duration +- [`Time.Sub()`][sub] → calculate the difference between two times + +## Time Units +Go provides built-in constants for time units: + +| Unit | Constant | +| ----------- |----------------------| +|Nanosecond | `time.Nanosecond` | +|Microsecond | `time.Microsecond ` | +|Millisecond | `time.Millisecond` | +|Second | `time.Second` | +|Minute | `time.Minute` | +|Hour | `time.Hour` | + +You can combine them to build durations: +```go +d := 1*time.Hour + 5*time.Minute + 19*time.Second + 234*time.Millisecond +``` + +You can also perform calculations: +```go +seconds := d.Seconds() // float64 +minutes := d.Minutes() +``` + +## Working with Durations +Durations can be: +- Added to a `time.Time` +- Subtracted from a `time.Time` +- Compared or converted to different units + +```go +start := time.Now() +finish := start.Add(30 * time.Second) + +duration := finish.Sub(start) // => 30s +``` + +## Other resources +- [Go by Example: Time](https://gobyexample.com/time) +- [Go by Example: Time Formatting / Parsing](https://gobyexample.com/time-formatting-parsing) + +[time-package]: https://pkg.go.dev/time +[time]: https://pkg.go.dev/time#Time +[duration]: https://pkg.go.dev/time#Duration +[since]: https://pkg.go.dev/time#Since +[add]: https://pkg.go.dev/time#Time.Add +[sub]: https://pkg.go.dev/time#Time.Sub +[now]: https://pkg.go.dev/time#Now + diff --git a/concepts/time-duration/links.json b/concepts/time-duration/links.json new file mode 100644 index 000000000..f97efbf3b --- /dev/null +++ b/concepts/time-duration/links.json @@ -0,0 +1,38 @@ +[ + { + "url": "https://pkg.go.dev/time", + "description": "Go packages: time package" + }, + { + "url": "https://pkg.go.dev/time#Time", + "description": "Go packages: time.Time type" + }, + { + "url": "https://pkg.go.dev/time#Duration", + "description": "Go packages: time.Duration type" + }, + { + "url": "https://pkg.go.dev/time#Since", + "description": "Go packages: time.Since function" + }, + { + "url": "https://pkg.go.dev/time#Time.Add", + "description": "Go packages: time.Time.Add method" + }, + { + "url": "https://pkg.go.dev/time#Time.Sub", + "description": "Go packages: time.Time.Sub method" + }, + { + "url": "https://pkg.go.dev/time#Now", + "description": "Go packages: time.Now function" + }, + { + "url": "https://gobyexample.com/time", + "description": "Go by Example: Time" + }, + { + "url": "https://gobyexample.com/time-formatting-parsing", + "description": "Go by Example: Time Formatting / Parsing" + } +] \ No newline at end of file diff --git a/config.json b/config.json index 274837577..7793d3dc7 100644 --- a/config.json +++ b/config.json @@ -428,6 +428,17 @@ "methods", "structs" ] + }, + { + "slug": "time-keeper", + "name": "Time Keeper", + "uuid": "82abf57d-891b-484f-b4d8-309e1486a6f5", + "concepts": [ + "time-duration" + ], + "prerequisites": [ + "time" + ] } ], "practice": [ @@ -2530,6 +2541,11 @@ "uuid": "f22b0e85-4d86-4ecf-9415-ea27c93ebcd6", "slug": "error-wrapping", "name": "Error Wrapping" + }, + { + "uuid": "32ee0ee7-7191-4d21-aa8b-f912cae21ca7", + "slug": "time-duration", + "name": "Time Duration" } ], "key_features": [ diff --git a/exercises/concept/time-keeper/.docs/hints.md b/exercises/concept/time-keeper/.docs/hints.md new file mode 100644 index 000000000..6d074ed6b --- /dev/null +++ b/exercises/concept/time-keeper/.docs/hints.md @@ -0,0 +1,54 @@ +# Hints + +## 1. Calculate Duration + +Think about how to measure the time between two moments. +Go provides a built-in way to compare timestamps directly. + +--- + +## 2. Handle Different Time Zones + +You don’t need to manually adjust for time zones. +Focus on comparing the two timestamps as they are. + +--- + +## 3. Parse Recorded Times + +The input follows a structured format with hours, minutes, and seconds. +Look for a standard library function that can interpret duration strings. + +--- + +## 4. Correct Start Time + +You need to adjust a timestamp by moving it backward in time. +Consider how to shift a time value using a duration. + +--- + +## 5. Filter Valid Detections + +First determine how much time has passed since the start. +Then check whether that elapsed time falls within a given range. + +--- + +## 6. Format Leaderboard Output + +You need to display: +- the runner’s total time +- the difference compared to the leader + +Think about: +- how to convert a duration to a readable string +- how to express the difference in seconds with precision + +--- + +## General Advice + +- Prefer built-in time utilities instead of manual calculations +- Be careful with boundaries when checking ranges +- Keep your solution simple and precise — timing systems must be reliable \ No newline at end of file diff --git a/exercises/concept/time-keeper/.docs/instructions.md b/exercises/concept/time-keeper/.docs/instructions.md new file mode 100644 index 000000000..40b8d7bd5 --- /dev/null +++ b/exercises/concept/time-keeper/.docs/instructions.md @@ -0,0 +1,84 @@ +# Instructions + +Sarah is a professional time-keeper who travels around the world to measure athletes' performance at sporting events. + +In this exercise, you will implement several helper functions for her timing software. +Accuracy is critical — even small mistakes could invalidate a world record. +--- + +## 1. Calculate the race duration + +Given a start time and a finish time, calculate how long the race lasted. + +Implement the function `GetDuration(start, finish)` that returns the duration between the two timestamps. + +```go +duration := GetDuration(start, finish) +// => 1h5m19.234s +``` + +## 2. Handle timestamps from different time zones + +Timing devices may be located in different time zones. +You still need to calculate the correct duration between two timestamps. + +Implement the function `GetDurationInDifferentTimezones(start, finish)` that returns the correct duration regardless of time zone differences. +```go +duration := GetDurationInDifferentTimezones(start, finish) +// => correct duration +``` + +## 3. Parse recorded race times +Some race times are recorded manually in a string format such as: +```go +"10h5m19.234s" +``` + +Implement the function `ParseRun(input)` that converts this string into a duration. +```go +duration, _ := ParseRun("10h5m19.234s") +// => 10h5m19.234s +``` + +### 4. Correct the start time +A backup system shows that the race actually started earlier than recorded. +You need to adjust the start time accordingly. + +Implement the function `FixStartTime(incorrectStart, offset)` that returns the corrected start time by subtracting the given duration. +```go +newStart := FixStartTime(incorrectStart, offset) +// => corrected start time +``` + +## 5. Filter valid detection times + +During the race, many timestamps are recorded, including unwanted ones. +Only detections within a specific time window after the start should be considered valid. + +Implement the function `IsValidDetection(start, from, to, detection)` that returns whether a detection is within the allowed range. +```go +valid := IsValidDetection(start, 10*time.Minute, 30*time.Minute, detection) +// => true or false +``` +## 6. Format leaderboard results +Race results should be displayed in a clear format, including the time difference from the leader. + +Implement the function FormatResult(result, leader) that returns a string formatted like: +```go +"1h5m19.234s +4.843" +``` + +Where: +- the first part is the athlete’s time +- the second part is the difference (in seconds) compared to the leader +- +```go +output := FormatResult(result, leader) +// => "1h5m19.234s +4.843" +``` + +## Notes +- Use Go’s time package for all time-related operations +- Focus on correctness — small mistakes can lead to invalid race results +- Keep your implementation simple and readable + diff --git a/exercises/concept/time-keeper/.docs/introduction.md b/exercises/concept/time-keeper/.docs/introduction.md new file mode 100644 index 000000000..24091f804 --- /dev/null +++ b/exercises/concept/time-keeper/.docs/introduction.md @@ -0,0 +1,93 @@ +# Introduction + +In Go, time-based calculations are handled using the [`time`][time-package] package. +Two key types used in this exercise are: + +- [`time.Time`][time]: represents a specific moment in time (a timestamp) +- [`time.Duration`][duration]: represents the amount of time elapsed between two moments + +--- + +## `time.Time` vs `time.Duration` + +A `time.Time` represents an absolute point in time, such as: + +```go +t := time.Now() +``` + +A `time.Duration` represents the difference between two points in time: +```go +start := time.Now() +end := start.Add(5 * time.Minute) + +duration := end.Sub(start) // => 5m0s +``` + +In short: +- `time.Time` → “when something happens” +- `time.Duration` → “how long it lasts” + +## Measuring Time with `time.Since` +To measure how long something takes (e.g., execution time), you can use `time.Since`[since] +```go +start := time.Now() + +// some work here + +elapsed := time.Since(start) +fmt.Println(elapsed) +``` + +## Adding and Subtracting Time +You can manipulate timestamps using: +- [`Time.Add()`][add] → add or subtract a duration +- [`Time.Sub()`][sub] → calculate the difference between two times + +## Time Units +Go provides built-in constants for time units: + +| Unit | Constant | +| ----------- |----------------------| +|Nanosecond | `time.Nanosecond` | +|Microsecond | `time.Microsecond ` | +|Millisecond | `time.Millisecond` | +|Second | `time.Second` | +|Minute | `time.Minute` | +|Hour | `time.Hour` | + +You can combine them to build durations: +```go +d := 1*time.Hour + 5*time.Minute + 19*time.Second + 234*time.Millisecond +``` + +You can also perform calculations: +```go +seconds := d.Seconds() // float64 +minutes := d.Minutes() +``` + +## Working with Durations +Durations can be: +- Added to a `time.Time` +- Subtracted from a `time.Time` +- Compared or converted to different units + +```go +start := time.Now() +finish := start.Add(30 * time.Second) + +duration := finish.Sub(start) // => 30s +``` + +## Other resources +- [Go by Example: Time](https://gobyexample.com/time) +- [Go by Example: Time Formatting / Parsing](https://gobyexample.com/time-formatting-parsing) + +[time-package]: https://pkg.go.dev/time +[time]: https://pkg.go.dev/time#Time +[duration]: https://pkg.go.dev/time#Duration +[since]: https://pkg.go.dev/time#Since +[add]: https://pkg.go.dev/time#Time.Add +[sub]: https://pkg.go.dev/time#Time.Sub +[now]: https://pkg.go.dev/time#Now diff --git a/exercises/concept/time-keeper/.meta/config.json b/exercises/concept/time-keeper/.meta/config.json new file mode 100644 index 000000000..252bdbf18 --- /dev/null +++ b/exercises/concept/time-keeper/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "thibault2705" + ], + "files": { + "solution": [ + "time_keeper.go" + ], + "test": [ + "time_keeper_test.go" + ], + "exemplar": [ + ".meta/exemplar.go" + ] + }, + "blurb": "Learn how concept time and duration is implemented in Go." +} diff --git a/exercises/concept/time-keeper/.meta/exemplar.go b/exercises/concept/time-keeper/.meta/exemplar.go new file mode 100644 index 000000000..ec2aec21d --- /dev/null +++ b/exercises/concept/time-keeper/.meta/exemplar.go @@ -0,0 +1,41 @@ +package time_keeper + +import ( + "fmt" + "time" +) + +// GetDuration - Get the difference between start and finish. +func GetDuration(start, finish time.Time) time.Duration { + return finish.Sub(start) +} + +// GetDurationInDifferentTimezones - Get the difference between times from different time zones. +func GetDurationInDifferentTimezones(start, finish time.Time) time.Duration { + return finish.Sub(start) +} + +// ParseRun - Parse a duration like "10h5m19.234s". +func ParseRun(input string) (time.Duration, error) { + return time.ParseDuration(input) +} + +// FixStartTime - Recalculate correct start time. +func FixStartTime(incorrectStart time.Time, offset time.Duration) time.Time { + return incorrectStart.Add(-offset) +} + +// IsValidDetection - Check whether a detection is within an allowed time window after start. +func IsValidDetection(start time.Time, from, to time.Duration, detection time.Time) bool { + elapsed := detection.Sub(start) + return elapsed >= from && elapsed <= to +} + +// FormatResult - Format leaderboard output. +func FormatResult(result, leader time.Duration) string { + diff := result - leader + if diff < 0 { + diff = 0 + } + return fmt.Sprintf("%s +%.3f", result.String(), diff.Seconds()) +} diff --git a/exercises/concept/time-keeper/go.mod b/exercises/concept/time-keeper/go.mod new file mode 100644 index 000000000..951ef0c65 --- /dev/null +++ b/exercises/concept/time-keeper/go.mod @@ -0,0 +1,4 @@ +module time_duration + +go 1.26 + diff --git a/exercises/concept/time-keeper/time_keeper.go b/exercises/concept/time-keeper/time_keeper.go new file mode 100644 index 000000000..1d1630a4c --- /dev/null +++ b/exercises/concept/time-keeper/time_keeper.go @@ -0,0 +1,35 @@ +package time_keeper + +import ( + "time" +) + +// GetDuration - Get the difference between start and finish. +func GetDuration(start, finish time.Time) time.Duration { + panic("Please implement GetDuration") +} + +// GetDurationInDifferentTimezones - Get the difference between times from different time zones. +func GetDurationInDifferentTimezones(start, finish time.Time) time.Duration { + panic("Please implement GetDurationInDifferentTimezones") +} + +// ParseRun - Parse a duration like "10h5m19.234s". +func ParseRun(input string) (time.Duration, error) { + panic("Please implement ParseRun") +} + +// FixStartTime - Recalculate correct start time. +func FixStartTime(incorrectStart time.Time, offset time.Duration) time.Time { + panic("Please implement FixStartTime") +} + +// IsValidDetection - Check whether a detection is within an allowed time window after start. +func IsValidDetection(start time.Time, from, to time.Duration, detection time.Time) bool { + panic("Please implement IsValidDetection") +} + +// FormatResult - Format leaderboard output. +func FormatResult(result, leader time.Duration) string { + panic("Please implement FormatResult") +} diff --git a/exercises/concept/time-keeper/time_keeper_test.go b/exercises/concept/time-keeper/time_keeper_test.go new file mode 100644 index 000000000..b61897140 --- /dev/null +++ b/exercises/concept/time-keeper/time_keeper_test.go @@ -0,0 +1,145 @@ +package time_keeper + +import ( + "testing" + "time" +) + +func TestGetDuration(t *testing.T) { + start := time.Date(2026, 4, 20, 9, 0, 0, 0, time.UTC) + finish := time.Date(2026, 4, 20, 10, 5, 19, 234000000, time.UTC) + + got := GetDuration(start, finish) + want := 1*time.Hour + 5*time.Minute + 19*time.Second + 234*time.Millisecond + + if got != want { + t.Errorf("GetDuration() = %v, want %v", got, want) + } +} + +func TestGetDurationInDifferentTimezones(t *testing.T) { + newYork, err := time.LoadLocation("America/New_York") + if err != nil { + t.Fatalf("failed to load New York location: %v", err) + } + + regina, err := time.LoadLocation("America/Regina") + if err != nil { + t.Fatalf("failed to load Regina location: %v", err) + } + + start := time.Date(2026, 4, 20, 8, 0, 0, 0, newYork) + finish := time.Date(2026, 4, 20, 8, 30, 0, 0, regina) + + got := GetDurationInDifferentTimezones(start, finish) + + startUTC := start.UTC() + finishUTC := finish.UTC() + want := finishUTC.Sub(startUTC) + + if got != want { + t.Errorf("GetDurationInDifferentTimezones() = %v, want %v", got, want) + } +} + +func TestParseRun(t *testing.T) { + got, err := ParseRun("10h5m19.234s") + if err != nil { + t.Fatalf("ParseRun() returned error: %v", err) + } + + want := 10*time.Hour + 5*time.Minute + 19*time.Second + 234*time.Millisecond + + if got != want { + t.Errorf("ParseRun() = %v, want %v", got, want) + } +} + +func TestParseRunInvalid(t *testing.T) { + _, err := ParseRun("10hours5minutes") + + if err == nil { + t.Error("ParseRun() expected error for invalid input, got nil") + } +} + +func TestFixStartTime(t *testing.T) { + incorrectStart := time.Date(2026, 4, 20, 9, 0, 15, 0, time.UTC) + offset := 15 * time.Second + + got := FixStartTime(incorrectStart, offset) + want := time.Date(2026, 4, 20, 9, 0, 0, 0, time.UTC) + + if !got.Equal(want) { + t.Errorf("FixStartTime() = %v, want %v", got, want) + } +} + +func TestIsValidDetection(t *testing.T) { + start := time.Date(2026, 4, 20, 9, 0, 0, 0, time.UTC) + + tests := []struct { + name string + detection time.Time + want bool + }{ + { + name: "before lower bound", + detection: start.Add(9 * time.Minute), + want: false, + }, + { + name: "exactly at lower bound", + detection: start.Add(10 * time.Minute), + want: true, + }, + { + name: "inside range", + detection: start.Add(20 * time.Minute), + want: true, + }, + { + name: "exactly at upper bound", + detection: start.Add(30 * time.Minute), + want: true, + }, + { + name: "after upper bound", + detection: start.Add(31 * time.Minute), + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := IsValidDetection(start, 10*time.Minute, 30*time.Minute, tt.detection) + if got != tt.want { + t.Errorf("IsValidDetection() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFormatResult(t *testing.T) { + result := 1*time.Hour + 5*time.Minute + 19*time.Second + 234*time.Millisecond + leader := 1*time.Hour + 5*time.Minute + 14*time.Second + 391*time.Millisecond + + got := FormatResult(result, leader) + want := "1h5m19.234s +4.843" + + if got != want { + t.Errorf("FormatResult() = %q, want %q", got, want) + } +} + +func TestFormatResultLeaderHasSameTime(t *testing.T) { + result := 42*time.Minute + 12*time.Second + leader := 42*time.Minute + 12*time.Second + + got := FormatResult(result, leader) + want := "42m12s +0.000" + + if got != want { + t.Errorf("FormatResult() = %q, want %q", got, want) + } +}