├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src └── PrettyDateTime.php └── tests └── PrettyDateTimeTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | .DS_Store 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 5.5 6 | - 5.4 7 | - 5.3 8 | - hhvm 9 | 10 | matrix: 11 | fast_finish: true 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 Daniel St. Jules 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | php-pretty-datetime 2 | =================== 3 | 4 | Generates human-readable strings for PHP DateTime objects. It handles dates in the past and future. For future dates, it uses the format 'In x unit', ie: 'In 1 minute'. For dates in the past, it uses 'x unit ago', ie: '2 years ago'. 5 | 6 | Note: Comparison of dates, for those beyond a day apart, uses the difference between their Unix timestamps. 7 | 8 | ## Installation 9 | 10 | If you're using Composer to manage dependencies, you can include the following 11 | in your composer.json file: 12 | 13 | ``` 14 | "require": { 15 | "danielstjules/php-pretty-datetime": "~1.0.0" 16 | } 17 | ``` 18 | 19 | Otherwise, you can simply require the file directly: 20 | 21 | ```php 22 | require_once 'path/to/php-pretty-datetime/src/PrettyDateTime.php'; 23 | ``` 24 | 25 | ## Usage 26 | 27 | ```php 28 | use PrettyDateTime\PrettyDateTime; 29 | 30 | PrettyDateTime::parse(new DateTime('now')); // Moments ago 31 | PrettyDateTime::parse(new DateTime('+ 59 second')); // Seconds from now 32 | PrettyDateTime::parse(new DateTime('+ 1 minute')); // In 1 minute 33 | PrettyDateTime::parse(new DateTime('- 59 minute')); // 59 minutes ago 34 | 35 | // You can supply a secondary argument to provide an alternate reference 36 | // DateTime. The default is the current DateTime, ie: DateTime('now'). In 37 | // addition, it takes into account the day of each DateTime. So in the next 38 | // two examples, even though they're only a second apart, 'Yesterday' and 39 | // 'Tomorrow' will be displayed 40 | 41 | $now = new DateTime('1991-05-18 00:00:00 UTC'); 42 | $dateTime = new DateTime('1991-05-17 23:59:59 UTC'); 43 | PrettyDateTime::parse($dateTime, $now); // Yesterday 44 | 45 | $now = new DateTime('1991-05-17 23:59:59 UTC'); 46 | $dateTime = new DateTime('1991-05-18 00:00:00 UTC'); 47 | PrettyDateTime::parse($dateTime, $now) // Tomorrow 48 | ``` 49 | 50 | ## Tests 51 | 52 | [![Build Status](https://travis-ci.org/danielstjules/php-pretty-datetime.png)](https://travis-ci.org/danielstjules/php-pretty-datetime) 53 | 54 | From the project directory, tests can be ran using `phpunit` 55 | 56 | ## License 57 | 58 | Released under the MIT License - see `LICENSE.txt` for details. 59 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "danielstjules/php-pretty-datetime", 3 | "description": "Generates human-readable strings for PHP DateTime objects", 4 | "keywords": ["human", "readable", "pretty", "datetime", "date", "time", "string"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Daniel St. Jules", 9 | "email": "danielst.jules@gmail.com", 10 | "homepage": "http://www.danielstjules.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.3.0" 15 | }, 16 | "support": { 17 | "issues": "https://github.com/danielstjules/php-pretty-datetime/issues", 18 | "source": "https://github.com/danielstjules/php-pretty-datetime" 19 | }, 20 | "autoload": { 21 | "psr-4": { "PrettyDateTime\\": "src/" } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | tests 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/PrettyDateTime.php: -------------------------------------------------------------------------------- 1 | 0) ? ' ago' : ''; 32 | 33 | $difference = floor(abs($difference)); 34 | 35 | // If difference is plural, add an 's' to $unit 36 | if ($difference > 1) { 37 | $unit = $unit . 's'; 38 | } 39 | 40 | return sprintf('%s%d %s%s', $prepend, $difference, $unit, $append); 41 | } 42 | 43 | /** 44 | * Returns a pretty, or human readable string corresponding to the supplied 45 | * $dateTime. If an optional secondary DateTime object is provided, it is 46 | * used as the reference - otherwise the current time and date is used. 47 | * 48 | * Examples: 'Moments ago', 'Yesterday', 'In 2 years' 49 | * 50 | * @param DateTime $dateTime The DateTime to parse 51 | * @param DateTime $reference (Optional) Defaults to the DateTime('now') 52 | * @return string The date in human readable format 53 | */ 54 | public static function parse(\DateTime $dateTime, \DateTime $reference = null) 55 | { 56 | // If not provided, set $reference to the current DateTime 57 | if (!$reference) { 58 | $reference = new \DateTime(NULL, new \DateTimeZone($dateTime->getTimezone()->getName())); 59 | } 60 | 61 | // Get the difference between the current date and the supplied $dateTime 62 | $difference = $reference->format('U') - $dateTime->format('U'); 63 | $absDiff = abs($difference); 64 | 65 | // Get the date corresponding to the $dateTime 66 | $date = $dateTime->format('Y/m/d'); 67 | 68 | // Throw exception if the difference is NaN 69 | if (is_nan($difference)) { 70 | throw new Exception('The difference between the DateTimes is NaN.'); 71 | } 72 | 73 | // Today 74 | if ($reference->format('Y/m/d') == $date) { 75 | if (0 <= $difference && $absDiff < self::MINUTE) { 76 | return 'Moments ago'; 77 | } elseif ($difference < 0 && $absDiff < self::MINUTE) { 78 | return 'Seconds from now'; 79 | } elseif ($absDiff < self::HOUR) { 80 | return self::prettyFormat($difference / self::MINUTE, 'minute'); 81 | } else { 82 | return self::prettyFormat($difference / self::HOUR, 'hour'); 83 | } 84 | } 85 | 86 | $yesterday = clone $reference; 87 | $yesterday->modify('- 1 day'); 88 | 89 | $tomorrow = clone $reference; 90 | $tomorrow->modify('+ 1 day'); 91 | 92 | if ($yesterday->format('Y/m/d') == $date) { 93 | return 'Yesterday'; 94 | } else if ($tomorrow->format('Y/m/d') == $date) { 95 | return 'Tomorrow'; 96 | } else if ($absDiff / self::DAY <= 7) { 97 | return self::prettyFormat($difference / self::DAY, 'day'); 98 | } else if ($absDiff / self::WEEK <= 5) { 99 | return self::prettyFormat($difference / self::WEEK, 'week'); 100 | } else if ($absDiff / self::MONTH < 12) { 101 | return self::prettyFormat($difference / self::MONTH, 'month'); 102 | } 103 | 104 | // Over a year ago 105 | return self::prettyFormat($difference / self::YEAR, 'year'); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tests/PrettyDateTimeTest.php: -------------------------------------------------------------------------------- 1 | midnight = new DateTime('1991-05-18 00:00:00 UTC'); 14 | $this->beforeMidnight = new DateTime('1991-05-18 23:59:59 UTC'); 15 | } 16 | 17 | public function testSingleDateTime() 18 | { 19 | $now = new DateTime('now'); 20 | $this->assertEquals('Moments ago', PrettyDateTime::parse($now)); 21 | } 22 | 23 | public function testSameDateTime() 24 | { 25 | $now = new DateTime('now'); 26 | $this->assertEquals('Moments ago', PrettyDateTime::parse($now, $now)); 27 | } 28 | 29 | // Testing DateTimes that occurred in the past 30 | 31 | /** 32 | * @dataProvider pastDateTimesAndStrings 33 | */ 34 | public function testDateTimesInThePast($timeAgo, $prettyString) 35 | { 36 | $dateTime = clone $this->beforeMidnight; 37 | $dateTime->modify($timeAgo); 38 | $prettyDateTime = PrettyDateTime::parse($dateTime, $this->beforeMidnight); 39 | $this->assertEquals($prettyString, $prettyDateTime); 40 | } 41 | 42 | public function pastDateTimesAndStrings() 43 | { 44 | $testData = array( 45 | array('- 59 second', 'Moments ago'), 46 | array('- 1 minute', '1 minute ago'), 47 | array('- 1 hour', '1 hour ago'), 48 | array(sprintf('- %d second', PrettyDateTime::YEAR), '1 year ago') 49 | ); 50 | 51 | // Test that DateTimes 2..59 minutes prior all say 'x minutes ago' 52 | for ($i = 2; $i < 60; $i++) { 53 | array_push($testData, array("- $i minute", "$i minutes ago")); 54 | } 55 | 56 | // Test that DateTimes 2..23 hours earlier all say 'In x hours' 57 | for ($i = 2; $i < 24; $i++) { 58 | array_push($testData, array("- $i hour", "$i hours ago")); 59 | } 60 | 61 | // Within the past 2..7 days 62 | for ($i = 2; $i <= 7; $i++) { 63 | array_push($testData, array("- $i day", "$i days ago")); 64 | } 65 | 66 | // Within the past 2..5 weeks 67 | for ($i = 2; $i <= 5; $i++) { 68 | $days = 7 * $i; 69 | array_push($testData, array("- $days day", "$i weeks ago")); 70 | } 71 | 72 | // Within the past 2..11 months 73 | for ($i = 2; $i <= 11; $i++) { 74 | $seconds = PrettyDateTime::MONTH * $i + PrettyDateTime::HOUR; 75 | array_push($testData, array("- $seconds second", "$i months ago")); 76 | } 77 | 78 | // Within the past 2..20 years 79 | for ($i = 2; $i <= 20; $i++) { 80 | $seconds = PrettyDateTime::YEAR * $i; 81 | array_push($testData, array("- $seconds second", "$i years ago")); 82 | } 83 | 84 | return $testData; 85 | } 86 | 87 | public function testYesterday() 88 | { 89 | $dateTime = clone $this->midnight; 90 | $dateTime->modify('- 1 second'); 91 | $prettyDateTime = PrettyDateTime::parse($dateTime, $this->midnight); 92 | $this->assertEquals('Yesterday', $prettyDateTime); 93 | } 94 | 95 | // Testing DateTimes that will occur in the future 96 | 97 | /** 98 | * @dataProvider futureDateTimesAndStrings 99 | */ 100 | public function testDateTimesInTheFuture($timeFromNow, $prettyString) 101 | { 102 | $dateTime = clone $this->midnight; 103 | $dateTime->modify($timeFromNow); 104 | $prettyDateTime = PrettyDateTime::parse($dateTime, $this->midnight); 105 | $this->assertEquals($prettyString, $prettyDateTime); 106 | } 107 | 108 | public function futureDateTimesAndStrings() 109 | { 110 | $testData = array( 111 | array('+ 59 second', 'Seconds from now'), 112 | array('+ 1 minute', 'In 1 minute'), 113 | array('+ 1 hour', 'In 1 hour'), 114 | array(sprintf('+ %d second', PrettyDateTime::YEAR), 'In 1 year') 115 | ); 116 | 117 | // Test that DateTimes 2..59 minutes later all say 'In x minutes' 118 | for ($i = 2; $i < 60; $i++) { 119 | array_push($testData, array("+ $i minute", "In $i minutes")); 120 | } 121 | 122 | // Test that DateTimes 2..23 hours earlier all say 'In x hours' 123 | for ($i = 2; $i < 24; $i++) { 124 | array_push($testData, array("+ $i hour", "In $i hours")); 125 | } 126 | 127 | // In the next 2..7 days 128 | for ($i = 2; $i <= 7; $i++) { 129 | array_push($testData, array("+ $i day", "In $i days")); 130 | } 131 | 132 | // In the next 2..5 weeks 133 | for ($i = 2; $i <= 5; $i++) { 134 | $days = 7 * $i; 135 | array_push($testData, array("+ $days day", "In $i weeks")); 136 | } 137 | 138 | // In the next 2..11 months 139 | for ($i = 2; $i <= 11; $i++) { 140 | $seconds = PrettyDateTime::MONTH * $i; 141 | array_push($testData, array("+ $seconds second", "In $i months")); 142 | } 143 | 144 | // In the next 2..20 years 145 | for ($i = 2; $i <= 20; $i++) { 146 | $seconds = PrettyDateTime::YEAR * $i; 147 | array_push($testData, array("+ $seconds second", "In $i years")); 148 | } 149 | 150 | return $testData; 151 | } 152 | 153 | public function testTomorrow() 154 | { 155 | $dateTime = clone $this->beforeMidnight; 156 | $dateTime->modify('+ 1 second'); 157 | $prettyDateTime = PrettyDateTime::parse($dateTime, $this->beforeMidnight); 158 | $this->assertEquals('Tomorrow', $prettyDateTime); 159 | } 160 | } 161 | --------------------------------------------------------------------------------