├── .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 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/chrisdicarlo/eloquent-human-timestamps.svg?style=flat-square)](https://packagist.org/packages/chrisdicarlo/eloquent-human-timestamps) 6 | ![Laravel 7/8 Tests](https://github.com/chrisdicarlo/eloquent-human-timestamps/actions/workflows/run-tests-L7.yml/badge.svg) 7 | ![Laravel 9 Tests](https://github.com/chrisdicarlo/eloquent-human-timestamps/actions/workflows/run-tests-L9.yml/badge.svg) 8 | ![Laravel 10 Tests](https://github.com/chrisdicarlo/eloquent-human-timestamps/actions/workflows/run-tests-L10.yml/badge.svg) 9 | ![Laravel 11 Tests](https://github.com/chrisdicarlo/eloquent-human-timestamps/actions/workflows/run-tests-L11.yml/badge.svg) 10 | [![Total Downloads](https://img.shields.io/packagist/dt/chrisdicarlo/eloquent-human-timestamps.svg?style=flat-square)](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 |