Repository: mgechev/revive

Issue:
report time.Date used with leading zeroes (so the octal notation)
**Is your feature request related to a problem? Please describe.**

[time.Date](https://pkg.go.dev/time#Date) is often used with leading zeroes

```go
time.Date(2023, 01, 02, 03, 04, 05, 06, time.UTC)
```

[This pattern is pretty common](https://github.com/search?q=language%3Ago%20%2Ftime.Date%5C(%5B0-9%5D%2B%2C%20%5B%5E%5C)%5D*0o%3F%5B0-7%5D%2F&type=code) as it replicates the natural way a human would write dates.

But the leading zeros that seem OK, forces the value to be octal literals.

This would be the way to define the date,

```go
time.Date(2023, 1, 2, 3, 4, 5, 6, time.UTC)
```

It doesn't seem to be a real problem, but the following code cannot compile
```go
time.Date(2023, 01, 02, 03, 08, 00, 00, time.UTC)
```

Here someone wanted to use 08:00:00 UTC time, but 08 is not a valid octal.

So some PR requires to change from octal notation to decimal one to be able to use 08 and 09

There is fortunately no conversion issues for number under 7 (octal range is 0-7)

Also, there is a real concern when [gofumpt](https://github.com/mvdan/gofumpt) faces octal notation, it will transform it to this.

```go
time.Date(2023, 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, time.UTC)
```
[
This pattern is not that common](https://github.com/search?q=language%3Ago%20%2Ftime.Date%5C(%5B0-9%5D%2B%2C%20%5B%5E%5C)%5D*0o%5B0-7%5D%2F&type=code), but pretty ugly.

**Describe the solution you'd like**

A new rule to detect this.

**Describe alternatives you've considered**

- updating the code of gofumpt, it's strange to add exception for this in [gofumpt code that is very simple](https://github.com/mvdan/gofumpt/blob/72ae882b16759122e10c954ce3ebb807b6d7365e/format/format.go#L685-L692)
- adding exception to go-critic [octalLiteral](https://go-critic.com/overview.html#octalliteral), the code is very simple, and the linter is not aware of how an octal is used, so it would require a lot of code change to check if we are in time.Date method


**Additional context**

I faced this when:
- I tried to use the time 08:00:00 and wrote `time.Date(2023, 01, 02, 03, 08, 00, 00, time.UTC)`
- gofumpt reformated some files


Hints from issue comments:
@ccoVeille: A real life example of this could be [this](https://github.com/getyoti/yoti-go-sdk/blob/54e6d51b1c1cd3e538c7616eea459ed07f3db72d/extension/third_party_attribute_extension_test.go#L103)

```go
time.Date(2026, 01, 31, 22, 04, 05, 001777777, time.UTC)
time.Date(2026, 07, 31, 22, 04, 05, 000777777, time.UTC)
```

Here the leading zeroes implies a strange behavior

https://go.dev/play/p/RQk4PtV66nm

You thought you asked for 777777 nanoseconds.

So you expected this

2026-07-31 22:04:05.001777777 +0000 UTC
2026-07-31 22:04:05.000777777 +0000 UTC

But you got this
2026-07-31 22:04:05.001777777 +0000 UTC
2026-07-31 22:04:05.000262143 +0000 UTC
@alexandear: Thanks for the detailed report — the rationale and examples are very clear.

Could we report only values with zeros that are greater than `09`?

`00`, `01`, `02`, `03`, `04`, `05`, `06`, `07`, `08`, `09` - fine.

`001777777` - not fine.@ccoVeille: Thanks you @alexandear about your feedback.

I wouldikd to thank @denisvmedia about the feedbacks he made on the PR

> > If a person deliberately set it to octal, then I think in this case it's a style issue, not a real problem. So, I'm not sure we should do it in this linter rule.

- https://github.com/mgechev/revive/pull/1327#discussion_r2065848484
- https://github.com/mgechev/revive/pull/1327#discussion_r2065850772

You are both right. I might go too far with this style issue.

Let's consider the following statements:

About time.Date
- there is indeed no problem with having octal notation 01-07 with time.Date
- reporting the usage of 00-07 with time.Date doesn't make sense.
- reporting them would have impact on a lot of valid code, they would be considered as false-negative and opionated
- 08 and 09 are forbidden by go compiler, so there is nothing to do about them
- if people want to use unusual integer literal with time.Date is stylistic opinion
- the only one that could be considered is the fact 0o1-0o7 are odd to use with time.Date

About gofumpt
- having gofumpt that transform "old octal notation" to "new octal notation" is only a stylistic issue, as it looks odd to see 0o0 - 0o7 used with time.Date once gofumpt format them.
- opening an issue on gofumpt about this should be considered about this.

About leading octal with multiple leading 00
- having an octal written with at least two leading zero bigger than 7 is a source of bug, and here it's not about time.Date only.
- a new linter could be created about it.

Depending on your feedbacks about these, I would do changes to my code@denisvmedia: To summarize: we should distinguish between a potential bug and a style issue. While I do support checking for both, I'm not sure it should belong to one and the same linter. At the same time, building different linters for this is overkill. In fact, the style issue can be a part of go-critic maybe. But the error case can be done in revive. I don't have a strong opinion though.@ccoVeille: I'm facing a strange situation then.

Because the linter in the way I thought initially was doing a lot of things and would now be limited to use only possible bug detection with leading zero for the nanoseconds.

I feel like it would report very few things. But they are likely to be error I think.

So is there a need for such linter?@ccoVeille: I looked at go-critic again and I think it's a good candidate for the 0o0-0o7 and maybe the 00-07 one too.

I will open a PR to octal notation.

But I also feel like I may have a PR to open in gofumpt@chavacava: >there is indeed no problem with having octal notation 01-07 with time.Date

There is no problem but, as you mention in your first post, most of the time it's an "accidental" use of octal notation; i.e., we write `01`-`07` thinking on them as decimal notation.

> reporting the usage of 00-07 with time.Date doesn't make sense.

Personally, I'll found useful if a tool tells me "hey, Do you known that these are octal and not decimal?"
(and 99% of the times I'll say "oh, thanks, it was not my intent")

> reporting them would have impact on a lot of valid code, they would be considered as false-negative and opionated

we can use `confidence` to handle these cases. "hey, I might be wrong (low confidence) but that smells your are using octal notation by accident" 

> if people want to use unusual integer literal with time.Date is stylistic opinion

I think 99.99% of the people uses decimal notation. I don't see a problem if false positives affect the other 0.01%.  

To resume: I think the rule has its place in `revive`. (Potentially) stylistic errors could be reported with low confidence and actual errors (like `000777777`) with high confidence.  @ccoVeille: > About gofumpt\n\nhaving gofumpt that transform \"old octal notation\" to \"new octal notation\" is only a stylistic issue, as it looks odd to see 0o0 - 0o7 used with time.Date once gofumpt format them.\nopening an issue on gofumpt about this should be considered about this.

gofumpt maintainer is not interested by doing extra work to fix octal usage with time.Date 

- https://github.com/mvdan/gofumpt/issues/323#issuecomment-2868905806

Solution patch:
diff --git a/README.md b/README.md
index c6dccbb..b55119b 100644
--- a/README.md
+++ b/README.md
@@ -530,6 +530,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a
 | [`string-of-int`](./RULES_DESCRIPTIONS.md#string-of-int)          |  n/a   | Warns on suspicious casts from int to string            |    no    |  yes   |
 | [`struct-tag`](./RULES_DESCRIPTIONS.md#struct-tag)          |  []string   | Checks common struct tags like `json`, `xml`, `yaml`               |    no    |  no   |
 | [`superfluous-else`](./RULES_DESCRIPTIONS.md#superfluous-else)    |  []string   | Prevents redundant else statements (extends [`indent-error-flow`](./RULES_DESCRIPTIONS.md#indent-error-flow)) |    no    |  no   |
+| [`time-date`](./RULES_DESCRIPTIONS.md#time-date)         |  n/a   | Reports bad usage of `time.Date`.                 |   no    |  yes  |
 | [`time-equal`](./RULES_DESCRIPTIONS.md#time-equal)         |  n/a   | Suggests to use `time.Time.Equal` instead of `==` and `!=` for equality check time.                 |   no    |  yes  |
 | [`time-naming`](./RULES_DESCRIPTIONS.md#time-naming)         |  n/a   | Conventions around the naming of time variables.                 |   yes    |  yes  |
 | [`unchecked-type-assertions`](./RULES_DESCRIPTIONS.md#unchecked-type-assertions)         |  n/a   | Disallows type assertions without checking the result.                 |   no    |  yes  |
diff --git a/RULES_DESCRIPTIONS.md b/RULES_DESCRIPTIONS.md
index 2ac0c1f..183b080 100644
--- a/RULES_DESCRIPTIONS.md
+++ b/RULES_DESCRIPTIONS.md
@@ -70,6 +70,7 @@ List of all available rules.
   - [string-of-int](#string-of-int)
   - [struct-tag](#struct-tag)
   - [superfluous-else](#superfluous-else)
+  - [time-date](#time-date)
   - [time-equal](#time-equal)
   - [time-naming](#time-naming)
   - [unchecked-type-assertion](#unchecked-type-assertion)
@@ -1013,6 +1014,55 @@ Examples:
   arguments = ["preserve-scope"]
 ```
 
+## time-date
+
+_Description_: Reports bad usage of `time.Date`.
+
+_Configuration_: N/A
+
+_Example_:
+
+Here the leading zeros are defining integers with octal notation
+
+```go
+var (
+  // here we can imagine zeroes were used for padding purpose
+  a = time.Date(2023, 1, 2, 3, 4, 0, 00000000, time.UTC) // 00000000 is octal and equals 0 in decimal
+  b = time.Date(2023, 1, 2, 3, 4, 0, 00000006, time.UTC) // 00000006 is octal and equals 6 in decimal
+  c = time.Date(2023, 1, 2, 3, 4, 0, 00123456, time.UTC) // 00123456 is octal and equals 42798 in decimal
+)
+```
+
+But here, what was expected 123456 or 42798 ? It's a source of bugs.
+
+So the rule will report this
+
+> octal notation with padding zeroes found: choose between 123456 and 42798 (decimal value of 123456 octal value)
+
+This rule also reports strange notations used with time.Date.
+
+Example:
+
+```go
+var _ = time.Date(
+		0x7e7,    // hexadecimal notation: use 2023 instead of 0x7e7/
+		0b1,      // binary notation: use 1 instead of 0b1/
+		0x_2,     // hexadecimal notation: use 2 instead of 0x_2/
+		1_3,      // alternative notation: use 13 instead of 1_3/
+		1e1,      // exponential notation: use 10 instead of 1e1/
+		0.,       // float literal: use 0 instead of 0./
+		0x1.Fp+6, // float literal: use 124 instead of 0x1.Fp+6/
+		time.UTC)
+```
+
+All these are considered to be an uncommon usage of time.Date, are reported with a 0.8 confidence.
+
+Note: even if 00, 01, 02, 03, 04, 05, 06, 07 are octal numbers, they can be considered as valid, and reported with 0.5 confidence.
+
+```go
+var _ = time.Date(2023, 01, 02, 03, 04, 00, 0, time.UTC)
+```
+
 ## time-equal
 
 _Description_: This rule warns when using `==` and `!=` for equality check `time.Time` and suggest to `time.time.Equal` method,
diff --git a/config/config.go b/config/config.go
index 34340b7..0f9ee9e 100644
--- a/config/config.go
+++ b/config/config.go
@@ -83,6 +83,7 @@ var allRules = append([]lint.Rule{
 	&rule.UselessBreak{},
 	&rule.UncheckedTypeAssertionRule{},
 	&rule.TimeEqualRule{},
+	&rule.TimeDateRule{},
 	&rule.BannedCharsRule{},
 	&rule.OptimizeOperandsOrderRule{},
 	&rule.UseAnyRule{},
diff --git a/rule/time_date.go b/rule/time_date.go
new file mode 100644
index 0000000..c8ba841
--- /dev/null
+++ b/rule/time_date.go
@@ -0,0 +1,237 @@
+package rule
+
+import (
+	"errors"
+	"fmt"
+	"go/ast"
+	"go/token"
+	"strconv"
+	"strings"
+
+	"github.com/mgechev/revive/lint"
+	"github.com/mgechev/revive/logging"
+)
+
+// TimeDateRule lints the way time.Date is used.
+type TimeDateRule struct{}
+
+// Apply applies the rule to given file.
+func (*TimeDateRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
+	var failures []lint.Failure
+
+	onFailure := func(failure lint.Failure) {
+		failures = append(failures, failure)
+	}
+
+	w := &lintTimeDate{file, onFailure}
+
+	ast.Walk(w, file.AST)
+	return failures
+}
+
+// Name returns the rule name.
+func (*TimeDateRule) Name() string {
+	return "time-date"
+}
+
+type lintTimeDate struct {
+	file      *lint.File
+	onFailure func(lint.Failure)
+}
+
+var (
+	// timeDateArgumentNames are the names of the arguments of time.Date
+	timeDateArgumentNames = []string{
+		"year",
+		"month",
+		"day",
+		"hour",
+		"minute",
+		"second",
+		"nanosecond",
+		"timezone",
+	}
+
+	// timeDateArity is the number of arguments of time.Date
+	timeDateArity = len(timeDateArgumentNames)
+)
+
+func (w lintTimeDate) Visit(n ast.Node) ast.Visitor {
+	ce, ok := n.(*ast.CallExpr)
+	if !ok || len(ce.Args) != timeDateArity {
+		return w
+	}
+	if !isPkgDot(ce.Fun, "time", "Date") {
+		return w
+	}
+
+	// The last argument is a timezone, there is no need to check it, also it has a different type
+	for pos, arg := range ce.Args[:timeDateArity-1] {
+		bl, ok := arg.(*ast.BasicLit)
+		if !ok {
+			continue
+		}
+
+		replacedValue, err := parseDecimalInteger(bl)
+		if err == nil {
+			continue
+		}
+
+		if errors.Is(err, errParsedInvalid) {
+			// This is not supposed to happen, let's be defensive
+			// log the error, but continue
+
+			logger, errLogger := logging.GetLogger()
+			if errLogger != nil {
+				// This is not supposed to happen, discard both errors
+				continue
+			}
+			logger.With(
+				"value", bl.Value,
+				"kind", bl.Kind,
+				"error", err.Error(),
+			).Error("failed to parse time.Date argument")
+
+			continue
+		}
+
+		confidence := 0.8 // default confidence
+		errMessage := err.Error()
+		instructions := fmt.Sprintf("use %s instead of %s", replacedValue, bl.Value)
+		switch {
+		case errors.Is(err, errParsedOctalWithZero):
+			// people can use 00, 01, 02, 03, 04, 05, 06, and 07 if they want.
+			confidence = 0.5
+
+		case errors.Is(err, errParsedOctalWithPaddingZeroes):
+			// This is a clear mistake.
+			// example with 000123456 (octal) is about 123456 or 42798 ?
+			confidence = 1
+
+			strippedValue := strings.TrimLeft(bl.Value, "0")
+			if strippedValue == "" {
+				// avoid issue with 00000000
+				strippedValue = "0"
+			}
+
+			if strippedValue != replacedValue {
+				instructions = fmt.Sprintf(
+					"choose between %s and %s (decimal value of %s octal value)",
+					strippedValue, replacedValue, strippedValue,
+				)
+			}
+		}
+
+		w.onFailure(lint.Failure{
+			Category:   "time",
+			Node:       bl,
+			Confidence: confidence,
+			Failure: fmt.Sprintf(
+				"use decimal digits for time.Date %s argument: %s found: %s",
+				timeDateArgumentNames[pos], errMessage, instructions),
+		})
+	}
+
+	return w
+}
+
+var (
+	errParsedOctal                  = errors.New("octal notation")
+	errParsedOctalWithZero          = errors.New("octal notation with leading zero")
+	errParsedOctalWithPaddingZeroes = errors.New("octal notation with padding zeroes")
+	errParsedHexadecimal            = errors.New("hexadecimal notation")
+	errParseBinary                  = errors.New("binary notation")
+	errParsedFloat                  = errors.New("float literal")
+	errParsedExponential            = errors.New("exponential notation")
+	errParsedAlternative            = errors.New("alternative notation")
+	errParsedInvalid                = errors.New("invalid notation")
+)
+
+func parseDecimalInteger(bl *ast.BasicLit) (string, error) {
+	currentValue := strings.ToLower(bl.Value)
+
+	switch currentValue {
+	case "0":
+		// skip 0 as it is a valid value for all the arguments
+		return bl.Value, nil
+	case "00", "01", "02", "03", "04", "05", "06", "07":
+		// people can use 00, 01, 02, 03, 04, 05, 06, 07, if they want
+		return bl.Value[1:], errParsedOctalWithZero
+	}
+
+	switch bl.Kind {
+	case token.FLOAT:
+		// someone used a float literal, while they should have used an integer literal.
+		parsedValue, err := strconv.ParseFloat(currentValue, 64)
+		if err != nil {
+			// This is not supposed to happen
+			return bl.Value, fmt.Errorf(
+				"%w: %s: %w",
+				errParsedInvalid,
+				"failed to parse number as float",
+				err,
+			)
+		}
+
+		// this will convert back the number to a string
+		cleanedValue := strconv.FormatFloat(parsedValue, 'f', -1, 64)
+		if strings.Contains(currentValue, "e") {
+			return cleanedValue, errParsedExponential
+		}
+
+		return cleanedValue, errParsedFloat
+
+	case token.INT:
+		// we expect this format
+
+	default:
+		// This is not supposed to happen
+		return bl.Value, fmt.Errorf(
+			"%w: %s",
+			errParsedInvalid,
+			"unexpected kind of literal",
+		)
+	}
+
+	// Parse the number with base=0 that allows to accept all number formats and base
+	parsedValue, err := strconv.ParseInt(currentValue, 0, 64)
+	if err != nil {
+		// This is not supposed to happen
+		return bl.Value, fmt.Errorf(
+			"%w: %s: %w",
+			errParsedInvalid,
+			"failed to parse number as integer",
+			err,
+		)
+	}
+
+	cleanedValue := strconv.FormatInt(parsedValue, 10)
+
+	// Let's figure out the notation to return an error
+	switch {
+	case strings.HasPrefix(currentValue, "0b"):
+		return cleanedValue, errParseBinary
+	case strings.HasPrefix(currentValue, "0x"):
+		return cleanedValue, errParsedHexadecimal
+	case strings.HasPrefix(currentValue, "0"):
+		// this matches both "0" and "0o" octal notation.
+
+		if strings.HasPrefix(currentValue, "00") {
+			// 00123456 (octal) is about 123456 or 42798 ?
+			return cleanedValue, errParsedOctalWithPaddingZeroes
+		}
+
+		// 0006 is a valid octal notation, but we can use 6 instead.
+		return cleanedValue, errParsedOctal
+	}
+
+	// Convert back the number to a string, and compare it with the original one
+	formattedValue := strconv.FormatInt(parsedValue, 10)
+	if formattedValue != currentValue {
+		// This can catch some edge cases like: 1_0 ...
+		return cleanedValue, errParsedAlternative
+	}
+
+	// The number is a decimal integer, we can use it as is.
+	return bl.Value, nil
+}



Test patch:
diff --git a/test/time_date_test.go b/test/time_date_test.go
new file mode 100644
index 0000000..6cb98db
--- /dev/null
+++ b/test/time_date_test.go
@@ -0,0 +1,11 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/mgechev/revive/rule"
+)
+
+func TestTimeDate(t *testing.T) {
+	testRule(t, "time_date_decimal_literal", &rule.TimeDateRule{})
+}
diff --git a/testdata/time_date_decimal_literal.go b/testdata/time_date_decimal_literal.go
new file mode 100644
index 0000000..d2b9526
--- /dev/null
+++ b/testdata/time_date_decimal_literal.go
@@ -0,0 +1,108 @@
+package pkg
+
+import "time"
+
+// these should match and be reported as invalid usage of time.Date
+// the rule should suggest to use a decimal number
+var (
+	// All these examples refers to the same date
+	// 2023-01-02 03:04:05.000000006 +0000 UTC
+
+	// this is an invalid usage
+	// people are using this format because a date is easier to read
+	// but here it leads to use octal numbers0
+	_ = time.Date(
+		2023,
+		01,        // MATCH /use decimal digits for time.Date month argument: octal notation with leading zero found: use 1 instead of 01/
+		02,        // MATCH /use decimal digits for time.Date day argument: octal notation with leading zero found: use 2 instead of 02/
+		03,        // MATCH /use decimal digits for time.Date hour argument: octal notation with leading zero found: use 3 instead of 03/
+		04,        // MATCH /use decimal digits for time.Date minute argument: octal notation with leading zero found: use 4 instead of 04/
+		05,        // MATCH /use decimal digits for time.Date second argument: octal notation with leading zero found: use 5 instead of 05/
+		000000006, // MATCH /use decimal digits for time.Date nanosecond argument: octal notation with padding zeroes found: use 6 instead of 000000006/
+		time.UTC)
+
+	// the following one could have been written by someone who is not aware of the issue
+	// Please note, there are multiple issues on the same line
+	//
+	// use special syntax to match multiple issues being reported on the same line
+	// MATCH:34 /use decimal digits for time.Date month argument: octal notation with leading zero found: use 1 instead of 01/
+	// MATCH:34 /use decimal digits for time.Date day argument: octal notation with leading zero found: use 2 instead of 02/
+	// MATCH:34 /use decimal digits for time.Date hour argument: octal notation with leading zero found: use 3 instead of 03/
+	// MATCH:34 /use decimal digits for time.Date minute argument: octal notation with leading zero found: use 4 instead of 04/
+	// MATCH:34 /use decimal digits for time.Date second argument: octal notation with leading zero found: use 5 instead of 05/
+	// MATCH:34 /use decimal digits for time.Date nanosecond argument: octal notation with padding zeroes found: use 6 instead of 000000006/
+	_ = time.Date(2023, 01, 02, 03, 04, 05, 000000006, time.UTC)
+)
+
+// gofumpt formats legacy non-decimal notation to new one non-decimal notation
+// it transforms 01, 02, 03, 04, 05, 06, and 07 to 0o1, 0o2, 0o3, 0o4, 0o5, 0o6, and 0o7
+// but here with time.Date it doesn't make sense. This is the main reason why the rule was created.
+var (
+	_ = time.Date(2023, 0o1, 2, 3, 4, 5, 6, time.UTC) // MATCH /use decimal digits for time.Date month argument: octal notation found: use 1 instead of 0o1/
+	_ = time.Date(2023, 1, 0o2, 3, 4, 5, 6, time.UTC) // MATCH /use decimal digits for time.Date day argument: octal notation found: use 2 instead of 0o2/
+	_ = time.Date(2023, 1, 2, 0o3, 4, 5, 6, time.UTC) // MATCH /use decimal digits for time.Date hour argument: octal notation found: use 3 instead of 0o3/
+	_ = time.Date(2023, 1, 2, 3, 0o4, 5, 6, time.UTC) // MATCH /use decimal digits for time.Date minute argument: octal notation found: use 4 instead of 0o4/
+	_ = time.Date(2023, 1, 2, 3, 4, 0o5, 6, time.UTC) // MATCH /use decimal digits for time.Date second argument: octal notation found: use 5 instead of 0o5/
+	_ = time.Date(2023, 1, 2, 3, 4, 5, 0o6, time.UTC) // MATCH /use decimal digits for time.Date nanosecond argument: octal notation found: use 6 instead of 0o6/
+)
+
+// padding with zeroes can lead to errors
+var (
+	_ = time.Date(2023, 1, 2, 3, 4, 5, 00, time.UTC) // MATCH /use decimal digits for time.Date nanosecond argument: octal notation with leading zero found: use 0 instead of 00/
+	_ = time.Date(2023, 1, 2, 3, 4, 5, 01, time.UTC) // MATCH /use decimal digits for time.Date nanosecond argument: octal notation with leading zero found: use 1 instead of 01/
+
+	_ = time.Date(2023, 1, 2, 3, 4, 5, 00000000, time.UTC) // MATCH /use decimal digits for time.Date nanosecond argument: octal notation with padding zeroes found: use 0 instead of 00000000/
+	_ = time.Date(2023, 1, 2, 3, 4, 5, 00000006, time.UTC) // MATCH /use decimal digits for time.Date nanosecond argument: octal notation with padding zeroes found: use 6 instead of 00000006/
+	_ = time.Date(2023, 1, 2, 3, 4, 5, 00123456, time.UTC) // MATCH /use decimal digits for time.Date nanosecond argument: octal notation with padding zeroes found: choose between 123456 and 42798 (decimal value of 123456 octal value)/
+)
+
+// hypothetical examples based on other number notations
+// https://go.dev/ref/spec#Integer_literals
+// these should match and be reported as invalid usage of time.Date
+var (
+	_ = time.Date(
+		0x7e7,    // MATCH /use decimal digits for time.Date year argument: hexadecimal notation found: use 2023 instead of 0x7e7/
+		0b1,      // MATCH /use decimal digits for time.Date month argument: binary notation found: use 1 instead of 0b1/
+		0x_2,     // MATCH /use decimal digits for time.Date day argument: hexadecimal notation found: use 2 instead of 0x_2/
+		1_3,      // MATCH /use decimal digits for time.Date hour argument: alternative notation found: use 13 instead of 1_3/
+		1e1,      // MATCH /use decimal digits for time.Date minute argument: exponential notation found: use 10 instead of 1e1/
+		0.,       // MATCH /use decimal digits for time.Date second argument: float literal found: use 0 instead of 0./
+		0x1.Fp+6, // MATCH /use decimal digits for time.Date nanosecond argument: float literal found: use 124 instead of 0x1.Fp+6/
+		time.UTC)
+)
+
+// these should never match
+var (
+	_ = time.Date(2023, 1, 2, 3, 4, 5, 1234567, time.UTC)
+	_ = time.Date(2023, time.January, 2, 3, 4, 5, 1234567, time.UTC)
+	_ = time.Date(2023, 10, 10, 10, 10, 10, 100, time.UTC)
+	_ = time.Date(2023, 1, 2, 3, 4, 5, 0, time.UTC)
+	_ = time.Date(2023, 1, 2, 3, 4, 5, 6, time.UTC)
+)
+
+// uncommon notations
+// these notations are supported by Go, the rule should not report it
+// it's out of the scope of the rule
+var (
+	// negative values can be used
+	// Go will compute 2022-10-28 20:55:54.999999994 +0000 UTC
+	_ = time.Date(2023, -1, -2, -3, -4, -5, -6, time.UTC)
+
+	// value out of the expected scale for a field, are OK.
+	// Go will compute 2024-02-02 04:11:10 +0000 UTC
+	_ = time.Date(2023, 13, 32, 27, 70, 70, 0, time.UTC)
+
+	// using gigantic nanoseconds to move into the future
+	// Go will compute 2054-09-10 04:50:45 +0000 UTC
+	_ = time.Date(2023, 1, 2, 3, 4, 5, 1000000000000000000, time.UTC)
+)
+
+// Common user errors with dates
+// it's out of the scope of the rule
+var (
+	// there is no February 29th in 2023, Go will compute 2023-03-01 03:04:05 +0000 UTC
+	_ = time.Date(2023, 2, 29, 3, 4, 5, 0, time.UTC)
+
+	// June has only 30 days, Go will compute 2023-07-01 03:04:05 +0000 UTC
+	_ = time.Date(2023, 6, 31, 3, 4, 5, 0, time.UTC)
+)