Skip to content

Commit

Permalink
Merge pull request #57 from programmatordev/YAPV-28-create-datetime-rule
Browse files Browse the repository at this point in the history
Create DateTime rule
  • Loading branch information
andrepimpao authored Mar 27, 2024
2 parents 5442cc1 + e03376f commit 7683b6f
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/03-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

## Date Rules

- [DateTime](03-rules_date-time.md)
- [Timezone](03-rules_timezone.md)

## Choice Rules
Expand Down
56 changes: 56 additions & 0 deletions docs/03-rules_date-time.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# DateTime

Validates that a given value is a valid datetime in a specific format.

```php
DateTime(
string $format = 'Y-m-d H:i:s',
?string $message = null
);
```

## Basic Usage

```php
// default "Y-m-d H:i:s"
Validator::dateTime()->validate('2024-01-01 00:00:00'); // true
Validator::dateTime()->validate('2024-01-01'); // false

// validate date
Validator::dateTime(format: 'Y-m-d')->validate('2024-01-01'); // true
Validator::dateTime(format: 'Y-m-d')->validate('2024-01-35'); // false

// validate time
Validator::dateTime(format: 'H:i:s')->validate('21:00:00'); // true
Validator::dateTime(format: 'H:i:s')->validate('35:00:00'); // false
```

> [!NOTE]
> An `UnexpectedValueException` will be thrown when the input value is not a `string` or an object implementing `\Stringable`.
## Options

### `format`

type: `string` default: `Y-m-d H:i:s`

Format of the datetime to be validated.
Check all formatting options [here](https://www.php.net/manual/en/datetimeimmutable.createfromformat.php).

### `message`

type: `?string` default: `The {{ name }} value is not a valid datetime.`

Message that will be shown when the input value is not a valid datetime.

The following parameters are available:

| Parameter | Description |
|----------------|---------------------------|
| `{{ value }}` | The current invalid value |
| `{{ name }}` | Name of the invalid value |
| `{{ format }}` | The datetime format |

## Changelog

- `0.8.0` Created
2 changes: 1 addition & 1 deletion docs/03-rules_regex.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Validator::regex('/[a-z]/', match: false)->validate('123'); // true

### `pattern`

type: `string`
type: `string` `required`

Regular expression pattern to be matched against.

Expand Down
5 changes: 5 additions & 0 deletions src/ChainedValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public function country(
?string $message = null
): ChainedValidatorInterface&Validator;

public function dateTime(
string $format = 'Y-m-d H:i:s',
?string $message = null
): ChainedValidatorInterface&Validator;

public function eachKey(
Validator $validator,
?string $message = null
Expand Down
5 changes: 5 additions & 0 deletions src/Exception/DateTimeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace ProgrammatorDev\Validator\Exception;

class DateTimeException extends ValidationException {}
43 changes: 43 additions & 0 deletions src/Rule/DateTime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace ProgrammatorDev\Validator\Rule;

use ProgrammatorDev\Validator\Exception\DateTimeException;
use ProgrammatorDev\Validator\Exception\UnexpectedTypeException;

class DateTime extends AbstractRule implements RuleInterface
{
private string $message = 'The {{ name }} value is not a valid datetime.';

public function __construct(
private readonly string $format = 'Y-m-d H:i:s',
?string $message = null
)
{
$this->message = $message ?? $this->message;
}

public function assert(mixed $value, ?string $name = null): void
{
if (!\is_scalar($value) && !$value instanceof \Stringable) {
throw new UnexpectedTypeException('string|\Stringable', get_debug_type($value));
}

$value = (string) $value;

\DateTimeImmutable::createFromFormat($this->format, $value);

$errors = \DateTimeImmutable::getLastErrors();

if ($errors !== false && ($errors['error_count'] > 0 || $errors['warning_count'] > 0)) {
throw new DateTimeException(
message: $this->message,
parameters: [
'name' => $name,
'value' => $value,
'format' => $this->format
]
);
}
}
}
2 changes: 1 addition & 1 deletion src/Rule/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function assert(mixed $value, ?string $name = null): void
}

if (!isset(self::TYPE_FUNCTIONS[$constraint]) && !\class_exists($constraint) && !\interface_exists($constraint)) {
throw new UnexpectedOptionException('constraint type', self::TYPE_FUNCTIONS, $constraint);
throw new UnexpectedOptionException('constraint type', \array_keys(self::TYPE_FUNCTIONS), $constraint);
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/StaticValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public static function country(
?string $message = null
): ChainedValidatorInterface&Validator;

public static function dateTime(
string $format = 'Y-m-d H:i:s',
?string $message = null
): ChainedValidatorInterface&Validator;

public static function eachKey(
Validator $validator,
?string $message = null
Expand Down
54 changes: 54 additions & 0 deletions tests/DateTimeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace ProgrammatorDev\Validator\Test;

use ProgrammatorDev\Validator\Exception\DateTimeException;
use ProgrammatorDev\Validator\Rule\DateTime;
use ProgrammatorDev\Validator\Test\Util\TestRuleFailureConditionTrait;
use ProgrammatorDev\Validator\Test\Util\TestRuleMessageOptionTrait;
use ProgrammatorDev\Validator\Test\Util\TestRuleSuccessConditionTrait;
use ProgrammatorDev\Validator\Test\Util\TestRuleUnexpectedValueTrait;

class DateTimeTest extends AbstractTest
{
use TestRuleUnexpectedValueTrait;
use TestRuleFailureConditionTrait;
use TestRuleSuccessConditionTrait;
use TestRuleMessageOptionTrait;

public static function provideRuleUnexpectedValueData(): \Generator
{
$unexpectedTypeMessage = '/Expected value of type "string|\Stringable", "(.*)" given./';

yield 'invalid value type' => [new DateTime(), ['2024-01-01 00:00:00'], $unexpectedTypeMessage];
}

public static function provideRuleFailureConditionData(): \Generator
{
$exception = DateTimeException::class;
$message = '/The (.*) value is not a valid datetime./';

yield 'invalid format' => [new DateTime(format: 'invalid'), '2024-01-01 00:00:00', $exception, $message];
yield 'invalid datetime' => [new DateTime(), '2024-01-01', $exception, $message];
yield 'invalid overflow date' => [new DateTime(format: 'Y-m-d'), '2024-01-35', $exception, $message];
yield 'invalid overflow time' => [new DateTime(format: 'H:i:s'), '35:00:00', $exception, $message];
}

public static function provideRuleSuccessConditionData(): \Generator
{
yield 'datetime' => [new DateTime(), '2024-01-01 00:00:00'];
yield 'date' => [new DateTime(format: 'Y-m-d'), '2024-01-01'];
yield 'time' => [new DateTime(format: 'H:i:s'), '21:00:00'];
}

public static function provideRuleMessageOptionData(): \Generator
{
yield 'message' => [
new DateTime(
message: 'The {{ name }} datetime does not match the format {{ format }}.'
),
'2024-01-01',
'The test datetime does not match the format "Y-m-d H:i:s".'
];
}
}
2 changes: 1 addition & 1 deletion tests/RegexTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class RegexTest extends AbstractTest
public static function provideRuleUnexpectedValueData(): \Generator
{
$unexpectedPatternMessage = '/Invalid regular expression pattern./';
$unexpectedTypeMessage = '/Expected value of type "array|\Stringable", "(.*)" given./';
$unexpectedTypeMessage = '/Expected value of type "string|\Stringable", "(.*)" given./';

yield 'invalid pattern' => [new Regex('invalid'), 'abc', $unexpectedPatternMessage];
yield 'invalid value type' => [new Regex('/[a-z]/'), ['abc'], $unexpectedTypeMessage];
Expand Down

0 comments on commit 7683b6f

Please sign in to comment.