├── .github
└── workflows
│ ├── run-tests-L10.yml
│ ├── run-tests-L11.yml
│ ├── run-tests-L7.yml
│ ├── run-tests-L9.yml
│ └── update-changelog.yml
├── .gitignore
├── CHANGELOG.MD
├── LICENSE
├── README.md
├── composer.json
├── composer.lock
├── database
└── migrations
│ └── create_test_models_table.php.stub
├── phpunit.xml.dist
├── src
└── HumanTimestamps.php
└── tests
├── HasHumanTimestampsTest.php
├── ModelBuilder.php
└── TestCase.php
/.github/workflows/run-tests-L10.yml:
--------------------------------------------------------------------------------
1 | name: "Run Tests - 10"
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | php: [8.1,8.2,8.3,8.4]
13 | laravel: [10.*]
14 | dependency-version: [prefer-lowest, prefer-stable]
15 | include:
16 | - laravel: 10.*
17 | testbench: 8.*
18 |
19 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
20 |
21 | steps:
22 | - name: Checkout code
23 | uses: actions/checkout@v3
24 |
25 | # - name: Cache dependencies
26 | # uses: actions/cache@v2
27 | # with:
28 | # path: ~/.composer/cache/files
29 | # key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
30 |
31 | - name: Setup PHP
32 | uses: shivammathur/setup-php@v2
33 | with:
34 | php-version: ${{ matrix.php }}
35 | extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv
36 | coverage: none
37 |
38 | - name: Install dependencies
39 | run: |
40 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" --no-interaction --no-update
41 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
42 | - name: Execute tests
43 | run: vendor/bin/phpunit
44 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests-L11.yml:
--------------------------------------------------------------------------------
1 | name: "Run Tests - 11"
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | php: [8.2,8.3,8.4]
13 | laravel: [11.*]
14 | dependency-version: [prefer-lowest, prefer-stable]
15 | include:
16 | - laravel: 11.*
17 | testbench: 9.*
18 |
19 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
20 |
21 | steps:
22 | - name: Checkout code
23 | uses: actions/checkout@v3
24 |
25 | # - name: Cache dependencies
26 | # uses: actions/cache@v2
27 | # with:
28 | # path: ~/.composer/cache/files
29 | # key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
30 |
31 | - name: Setup PHP
32 | uses: shivammathur/setup-php@v2
33 | with:
34 | php-version: ${{ matrix.php }}
35 | extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv
36 | coverage: none
37 |
38 | - name: Install dependencies
39 | run: |
40 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" --no-interaction --no-update
41 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
42 | - name: Execute tests
43 | run: vendor/bin/phpunit
44 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests-L7.yml:
--------------------------------------------------------------------------------
1 | name: "Run Tests - 7"
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | php: [8.0, 7.4, 7.3]
13 | laravel: [7.*]
14 | dependency-version: [prefer-lowest, prefer-stable]
15 | include:
16 | - laravel: 7.*
17 | testbench: 5.20
18 |
19 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
20 |
21 | steps:
22 | - name: Checkout code
23 | uses: actions/checkout@v3
24 |
25 | # - name: Cache dependencies
26 | # uses: actions/cache@v2
27 | # with:
28 | # path: ~/.composer/cache/files
29 | # key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
30 |
31 | - name: Setup PHP
32 | uses: shivammathur/setup-php@v2
33 | with:
34 | php-version: ${{ matrix.php }}
35 | extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv
36 | coverage: none
37 |
38 | - name: Install dependencies
39 | run: |
40 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" --no-interaction --no-update
41 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
42 | - name: Execute tests
43 | run: vendor/bin/phpunit
44 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests-L9.yml:
--------------------------------------------------------------------------------
1 | name: "Run Tests - 8, 9"
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | php: [8.1, 8.0, 7.4, 7.3]
13 | laravel: [9.*, 8.*]
14 | dependency-version: [prefer-lowest, prefer-stable]
15 | include:
16 | - laravel: 9.*
17 | testbench: 7.*
18 | - laravel: 8.*
19 | testbench: 6.23
20 | exclude:
21 | - laravel: 9.*
22 | php: 7.4
23 | - laravel: 9.*
24 | php: 7.3
25 |
26 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
27 |
28 | steps:
29 | - name: Checkout code
30 | uses: actions/checkout@v3
31 |
32 | # - name: Cache dependencies
33 | # uses: actions/cache@v2
34 | # with:
35 | # path: ~/.composer/cache/files
36 | # key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
37 |
38 | - name: Setup PHP
39 | uses: shivammathur/setup-php@v2
40 | with:
41 | php-version: ${{ matrix.php }}
42 | extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv
43 | coverage: none
44 |
45 | - name: Install dependencies
46 | run: |
47 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" --no-interaction --no-update
48 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
49 | - name: Execute tests
50 | run: vendor/bin/phpunit
51 |
--------------------------------------------------------------------------------
/.github/workflows/update-changelog.yml:
--------------------------------------------------------------------------------
1 | name: "Update Changelog"
2 |
3 | on:
4 | release:
5 | types: [released]
6 |
7 | jobs:
8 | update:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v2
14 | with:
15 | ref: main
16 |
17 | - name: Update Changelog
18 | uses: stefanzweifel/changelog-updater-action@v1
19 | with:
20 | latest-version: ${{ github.event.release.name }}
21 | release-notes: ${{ github.event.release.body }}
22 |
23 | - name: Commit updated CHANGELOG
24 | uses: stefanzweifel/git-auto-commit-action@v4
25 | with:
26 | branch: main
27 | commit_message: Update CHANGELOG
28 | file_pattern: CHANGELOG.md
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | node_modules/
3 | npm-debug.log
4 | yarn-error.log
5 |
6 | # Laravel 4 specific
7 | bootstrap/compiled.php
8 | app/storage/
9 |
10 | # Laravel 5 & Lumen specific
11 | public/storage
12 | public/hot
13 |
14 | # Laravel 5 & Lumen specific with changed public path
15 | public_html/storage
16 | public_html/hot
17 |
18 | storage/*.key
19 | .env
20 | Homestead.yaml
21 | Homestead.json
22 | /.vagrant
23 | .phpunit.result.cache
24 |
--------------------------------------------------------------------------------
/CHANGELOG.MD:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `eloquent-human-timestamps` will be documented in this file
4 |
5 | ## 5.0.0 - 2023-03-13
6 |
7 | - Laravel 10 support
8 | - Use v5 for Laravel 10 due to removal of $dates property.
9 |
10 | ## 4.0.0 - 2022-02-23
11 |
12 | - Laravel 9 Support
13 | - Requires PHP >= 8.0 because of Laravel 9 minimum requirement
14 |
15 | ## 3.0.0 - 2021-12-10
16 |
17 | - Add support for PHP 8.1
18 | - Move support for Laravel 8 to v2.x branch
19 |
20 | ## 2.0.1 - 2021-04-18
21 |
22 | - Cleanup
23 |
24 | ## 2.0.0 - 2021-04-18
25 |
26 | - Update dependencies, add tests.
27 |
28 | ## What's Changed
29 |
30 | - Since I add explicit version dependencies, it's technically a breaking change, hence the major bump.
31 |
32 | ## 1.0.1 - 2020-10-24
33 |
34 | - Fix for bug when attribute is null
35 |
36 | ## 1.0.0 - 2020-10-23
37 |
38 | - Initial release
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Di Carlo Systems Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Automatic human timestamp properties in Laravel
2 |
3 | This package provides a trait you can add to an Eloquent model that will automatically create human-readable timestamp diffs using Carbon.
4 |
5 | [](https://packagist.org/packages/chrisdicarlo/eloquent-human-timestamps)
6 | 
7 | 
8 | 
9 | 
10 | [](https://packagist.org/packages/chrisdicarlo/eloquent-human-timestamps)
11 |
12 | ## Version Compatibility
13 |
14 | | Laravel | PHP | Package Version |
15 | | ------- | --- | --------------- |
16 | | 6 | 8.0, 7.4, 7.3 | 2 |
17 | | 7 | 8.0, 7.4, 7.3 | 2 |
18 | | 8 | 8.1, 8.0, 7.4, 7.3 | 3 |
19 | | 9 | 8.1, 8.0 | 4 |
20 | | 10 | 8.1, 8.2, 8.3, 8.4 | 5 |
21 | | 11 | 8.2, 8.3, 8.4 | 6 |
22 |
23 | ## Installation
24 |
25 | To install the package run:
26 |
27 | ```
28 | composer require chrisdicarlo/eloquent-human-timestamps
29 | ```
30 |
31 | ## Setup
32 |
33 | Add the ChrisDiCarlo\EloquentHumanTimestamps\HumanTimestamps trait to a model that has timestamp columns, e.g.:
34 |
35 | ```
36 |
37 | use ChrisDiCarlo\EloquentHumanTimestamps\HumanTimestamps;
38 |
39 | class Foobar
40 | {
41 |
42 | use HumanTimestamps;
43 |
44 | ...
45 |
46 | }
47 | ```
48 |
49 | ## Usage
50 |
51 | To get the human-readable attribute, simply retrieve the timestamp normally but append **_for_humans** to the name, e.g. created_at_for_humans, updated_at_for_humans.
52 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chrisdicarlo/eloquent-human-timestamps",
3 | "homepage": "https://github.com/chrisdicarlo/eloquent-human-timestamps",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Chris Di Carlo",
8 | "email": "chris@dicarlosystems.ca",
9 | "homepage": "http://dicarlosystems.ca",
10 | "role": "Developer"
11 | }
12 | ],
13 | "require": {
14 | "php": "^7.3|^8.0|^8.1|^8.2|^8.3|^8.4",
15 | "illuminate/support": "^7.0|^8.0|^9.0|^10|^11"
16 | },
17 | "require-dev": {
18 | "orchestra/testbench": "^5.0|^6.0|^7.0|^8.0|^9.0",
19 | "phpunit/phpunit": "^9.4|^9.5.4|^10"
20 | },
21 | "autoload": {
22 | "psr-4": {
23 | "ChrisDiCarlo\\EloquentHumanTimestamps\\": "src"
24 | }
25 | },
26 | "autoload-dev": {
27 | "psr-4": {
28 | "ChrisDiCarlo\\EloquentHumanTimestamps\\Test\\": "tests"
29 | }
30 | },
31 | "config": {
32 | "sort-packages": true
33 | },
34 | "scripts": {
35 | "test": "phpunit"
36 | },
37 | "minimum-stability": "dev",
38 | "prefer-stable": true
39 | }
40 |
--------------------------------------------------------------------------------
/database/migrations/create_test_models_table.php.stub:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->datetime('published_at')->nullable();
19 | $table->timestamps();
20 | });
21 | }
22 |
23 | /**
24 | * Reverse the migrations.
25 | *
26 | * @return void
27 | */
28 | public function down()
29 | {
30 | Schema::drop('test_model');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | src/
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/HumanTimestamps.php:
--------------------------------------------------------------------------------
1 | getAttribute(Str::before($key, '_for_humans')) &&
13 | $this->isDateAttribute(Str::before($key, '_for_humans'))) {
14 | return $this->getAttribute(Str::before($key, '_for_humans'))->diffForHumans();
15 | }
16 |
17 | return parent::__get($key);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/tests/HasHumanTimestampsTest.php:
--------------------------------------------------------------------------------
1 | build();
14 | $model->published_at = now();
15 | $this->assertNull($model->published_at_for_humans);
16 | }
17 |
18 | /** @test */
19 | public function it_returns_a_human_timestamp_if_the_requested_attribute_is_contained_in_the_casts_array_and_casted_to_datetime()
20 | {
21 | $published_at = now()->subDays(5);
22 | $model = (new ModelBuilder)->withTrait()->withCastsProperty('datetime')->build();
23 | $model->published_at = $published_at;
24 |
25 | $this->assertNotNull($model->published_at_for_humans);
26 | $this->assertEquals('5 days ago', $model->published_at_for_humans);
27 | }
28 |
29 | /** @test */
30 | public function it_returns_a_human_timestamp_if_the_requested_attribute_is_contained_in_the_casts_array_and_casted_to_date()
31 | {
32 | $published_at = now()->subDays(5);
33 | $model = (new ModelBuilder)->withTrait()->withCastsProperty('date')->build();
34 | $model->published_at = $published_at;
35 |
36 | $this->assertNotNull($model->published_at_for_humans);
37 | $this->assertEquals('5 days ago', $model->published_at_for_humans);
38 | }
39 |
40 | /** @test */
41 | public function it_returns_a_human_timestamp_if_the_requested_attribute_is_contained_in_the_casts_array_casted_to_datetime_and_the_dates_array()
42 | {
43 | $published_at = now()->subDays(5);
44 | $model = (new ModelBuilder)->withTrait()->withCastsProperty('datetime')->withDatesProperty()->build();
45 | $model->published_at = $published_at;
46 |
47 | $this->assertNotNull($model->published_at_for_humans);
48 | $this->assertEquals('5 days ago', $model->published_at_for_humans);
49 | }
50 |
51 | /** @test */
52 | public function it_returns_a_human_timestamp_if_the_requested_attribute_is_contained_in_the_casts_array_casted_to_date_and_the_dates_array()
53 | {
54 | $published_at = now()->subDays(5);
55 | $model = (new ModelBuilder)->withTrait()->withCastsProperty('date')->withDatesProperty()->build();
56 | $model->published_at = $published_at;
57 |
58 | $this->assertNotNull($model->published_at_for_humans);
59 | $this->assertEquals('5 days ago', $model->published_at_for_humans);
60 | }
61 |
62 | /** @test */
63 | public function it_returns_a_human_timestamp_for_the_updated_at_datetime()
64 | {
65 | $created_at = now()->subDays(5);
66 | $model = (new ModelBuilder)->withTrait()->build();
67 | $model->created_at = $created_at;
68 |
69 | $this->assertNotNull($model->created_at_for_humans);
70 | $this->assertEquals('5 days ago', $model->created_at_for_humans);
71 | }
72 |
73 | /** @test */
74 | public function it_returns_a_human_timestamp_for_the_deleted_at_datetime()
75 | {
76 | $deleted_at = now()->subDays(5);
77 | $model = (new ModelBuilder)->withTrait()->withSoftDeletes()->build();
78 | $model->deleted_at = $deleted_at;
79 |
80 | $this->assertNotNull($model->deleted_at_for_humans);
81 | $this->assertEquals('5 days ago', $model->deleted_at_for_humans);
82 | }
83 |
84 | /** @test */
85 | public function it_returns_a_human_timestamp_for_the_created_at_datetime()
86 | {
87 | $updated_at = now()->subDays(5);
88 | $model = (new ModelBuilder)->withTrait()->withCastsProperty('datetime')->build();
89 | $model->updated_at = $updated_at;
90 |
91 | $this->assertNotNull($model->updated_at_for_humans);
92 | $this->assertEquals('5 days ago', $model->updated_at_for_humans);
93 | }
94 |
95 | /** @test */
96 | public function it_returns_null_if_the_requested_attribute_is_not_contained_in_the_casts_array_or_the_dates_array()
97 | {
98 | $published_at = now()->subDays(5);
99 | $model = (new ModelBuilder)->withTrait()->build();
100 | $model->published_at = $published_at;
101 |
102 | $this->assertNull($model->published_at_for_humans);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/tests/ModelBuilder.php:
--------------------------------------------------------------------------------
1 | useTrait = false;
16 | $this->useSoftDeletes = false;
17 | $this->useCastsProperty = false;
18 | $this->useDatesProperty = false;
19 | $this->castType = '';
20 | }
21 |
22 | public function build()
23 | {
24 | $classDef = <<buildClassDef()}
28 | };
29 | EOT;
30 |
31 | return eval($classDef);
32 | }
33 |
34 | private function buildClassDef()
35 | {
36 | $def = '';
37 |
38 | if ($this->useTrait) {
39 | $def .= "use ChrisDiCarlo\EloquentHumanTimestamps\HumanTimestamps;\n";
40 | }
41 |
42 | if ($this->useSoftDeletes) {
43 | $def .= "use Illuminate\Database\Eloquent\SoftDeletes;\n";
44 | }
45 |
46 | if ($this->useCastsProperty) {
47 | $def .= "protected \$casts = ['published_at' => '{$this->castType}'];\n";
48 | }
49 |
50 | if ($this->useDatesProperty) {
51 | $def .= 'protected $dates = [\'published_at\'];';
52 | }
53 |
54 | return $def;
55 | }
56 |
57 | public function withTrait()
58 | {
59 | $this->useTrait = true;
60 | return $this;
61 | }
62 |
63 | public function withSoftDeletes()
64 | {
65 | $this->useSoftDeletes = true;
66 | return $this;
67 | }
68 |
69 | public function withCastsProperty($castType = 'datetime')
70 | {
71 | $this->castType = $castType;
72 | $this->useCastsProperty = true;
73 | return $this;
74 | }
75 |
76 | public function withDatesProperty()
77 | {
78 | $this->useDatesProperty = true;
79 | return $this;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 |