includes complete tests for `date.php`pull/1/head
@ -0,0 +1,2 @@ | |||
vendor/ | |||
.DS_Store |
@ -0,0 +1,8 @@ | |||
language: php | |||
php: | |||
- 5.5 | |||
- 5.6 | |||
- 7.0 | |||
- 7.1 | |||
- nightly | |||
before_script: composer install |
@ -0,0 +1,30 @@ | |||
{ | |||
"name": "p3k/utils", | |||
"type": "library", | |||
"description": "Some helpful functions used by https://p3k.io projects", | |||
"license": "MIT", | |||
"homepage": "https://github.com/aaronpk/p3k-utils", | |||
"authors": [ | |||
{ | |||
"name": "Aaron Parecki", | |||
"homepage": "https://aaronparecki.com" | |||
} | |||
], | |||
"require": { | |||
"php": ">=5.5" | |||
}, | |||
"require-dev": { | |||
"phpunit/phpunit": ">=4.8.13" | |||
}, | |||
"autoload": { | |||
"files": [ | |||
"src/global.php", | |||
"src/url.php", | |||
"src/utils.php", | |||
"src/date.php", | |||
"src/cache.php" | |||
] | |||
}, | |||
"autoload-dev": { | |||
} | |||
} |
@ -0,0 +1,18 @@ | |||
<?xml version="1.0"?> | |||
<phpunit | |||
bootstrap="tests/bootstrap.php" | |||
beStrictAboutTestsThatDoNotTestAnything="true"> | |||
<testsuites> | |||
<testsuite name="comments"> | |||
<directory suffix="Test.php">tests</directory> | |||
</testsuite> | |||
</testsuites> | |||
<filter> | |||
<whitelist processUncoveredFilesFromWhitelist="true"> | |||
<directory suffix=".php">src</directory> | |||
</whitelist> | |||
</filter> | |||
<logging> | |||
<log type="coverage-html" target="./coverage"/> | |||
</logging> | |||
</phpunit> |
@ -0,0 +1,60 @@ | |||
<?php | |||
namespace p3k; | |||
class Cache { | |||
private static $redis; | |||
public static function redis($config=false) { | |||
if(!isset(self::$redis)) { | |||
if($config) { | |||
self::$redis = new \Predis\Client(Config::$redis); | |||
} else { | |||
self::$redis = new \Predis\Client('tcp://127.0.0.1:6379'); | |||
} | |||
} | |||
} | |||
public static function set($key, $value, $exp=600) { | |||
self::redis(); | |||
if($exp) { | |||
self::$redis->setex($key, $exp, json_encode($value)); | |||
} else { | |||
self::$redis->set($key, json_encode($value)); | |||
} | |||
} | |||
public static function get($key) { | |||
self::redis(); | |||
$data = self::$redis->get($key); | |||
if($data) { | |||
return json_decode($data); | |||
} else { | |||
return null; | |||
} | |||
} | |||
public static function delete($key) { | |||
self::redis(); | |||
return self::$redis->del($key); | |||
} | |||
public static function expire($key, $seconds=0) { | |||
self::redis(); | |||
if($seconds) | |||
return self::$redis->expire($key, $seconds); | |||
else | |||
return self::$redis->del($key); | |||
} | |||
public static function incr($key, $value=1) { | |||
self::redis(); | |||
return self::$redis->incrby($key, $value); | |||
} | |||
public static function decr($key, $value=1) { | |||
self::redis(); | |||
return self::$redis->decrby($key, $value); | |||
} | |||
} | |||
@ -0,0 +1,39 @@ | |||
<?php | |||
namespace p3k\date; | |||
use DateTime, DateTimeZone; | |||
// $format - one of the php.net/date format strings | |||
// $date - a string that will be passed to DateTime() | |||
// $offset - integer timezone offset in seconds | |||
function format_local($format, $date, $offset) { | |||
if($offset != 0) | |||
$tz = new DateTimeZone(($offset < 0 ? '-' : '+') . sprintf('%02d:%02d', abs(floor($offset / 60 / 60)), (($offset / 60) % 60))); | |||
else | |||
$tz = new DateTimeZone('UTC'); | |||
$d = new DateTime($date); | |||
$d->setTimeZone($tz); | |||
return $d->format($format); | |||
} | |||
function tz_offset_to_seconds($offset) { | |||
if(preg_match('/([+-])(\d{2}):?(\d{2})/', $offset, $match)) { | |||
$sign = ($match[1] == '-' ? -1 : 1); | |||
return (($match[2] * 60 * 60) + ($match[3] * 60)) * $sign; | |||
} else { | |||
return 0; | |||
} | |||
} | |||
function tz_seconds_to_offset($seconds) { | |||
return ($seconds < 0 ? '-' : '+') . sprintf('%02d:%02d', abs($seconds/60/60), ($seconds/60)%60); | |||
} | |||
function tz_seconds_to_timezone($seconds) { | |||
if($seconds != 0) | |||
$tz = new DateTimeZone(tz_seconds_to_offset($seconds)); | |||
else | |||
$tz = new DateTimeZone('UTC'); | |||
return $tz; | |||
} | |||
@ -0,0 +1,2 @@ | |||
<?php | |||
date_default_timezone_set('UTC'); |
@ -0,0 +1,136 @@ | |||
<?php | |||
namespace p3k\url; | |||
function display_url($url) { | |||
# remove scheme | |||
$url = preg_replace('/^https?:\/\//', '', $url); | |||
# if the remaining string has no path components but has a trailing slash, remove the trailing slash | |||
$url = preg_replace('/^([^\/]+)\/$/', '$1', $url); | |||
return $url; | |||
} | |||
function add_query_params_to_url($url, $add_params) { | |||
$parts = parse_url($url); | |||
if(array_key_exists('query', $parts) && $parts['query']) { | |||
parse_str($parts['query'], $params); | |||
} else { | |||
$params = []; | |||
} | |||
foreach($add_params as $k=>$v) { | |||
$params[$k] = $v; | |||
} | |||
$parts['query'] = http_build_query($params); | |||
return build_url($parts); | |||
} | |||
// Input: Any URL or string like "aaronparecki.com" | |||
// Output: Normlized URL (default to http if no scheme, force "/" path) | |||
// or return false if not a valid URL | |||
function normalize($url) { | |||
$parts = parse_url($url); | |||
if(array_key_exists('path', $parts) && $parts['path'] == '') | |||
return false; | |||
// parse_url returns just "path" for naked domains | |||
if(count($parts) == 1 && array_key_exists('path', $parts)) { | |||
$parts['host'] = $parts['path']; | |||
unset($parts['path']); | |||
} | |||
if(!array_key_exists('scheme', $parts)) | |||
$parts['scheme'] = 'http'; | |||
if(!array_key_exists('path', $parts)) | |||
$parts['path'] = '/'; | |||
// Invalid scheme | |||
if(!in_array($parts['scheme'], array('http','https'))) | |||
return false; | |||
return build_url($parts); | |||
} | |||
// Inverse of parse_url() | |||
// http://php.net/parse_url | |||
function build_url($parsed_url) { | |||
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; | |||
$host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; | |||
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; | |||
$user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; | |||
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; | |||
$pass = ($user || $pass) ? "$pass@" : ''; | |||
$path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; | |||
$query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; | |||
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; | |||
return "$scheme$user$pass$host$port$path$query$fragment"; | |||
} | |||
function parse($url) { | |||
return parse_url($url); | |||
} | |||
function host_matches($a, $b) { | |||
return parse_url($a, PHP_URL_HOST) == parse_url($b, PHP_URL_HOST); | |||
} | |||
function is_url($url) { | |||
return is_string($url) && preg_match('/^https?:\/\/[a-z0-9\.\-]\/?/', $url); | |||
} | |||
function http_header_case($str) { | |||
$str = str_replace('-', ' ', $str); | |||
$str = ucwords($str); | |||
$str = str_replace(' ', '-', $str); | |||
return $str; | |||
} | |||
function is_public_ip($ip) { | |||
// http://stackoverflow.com/a/30143143 | |||
//Private ranges... | |||
//http://www.iana.org/assignments/iana-ipv4-special-registry/ | |||
$networks = array('10.0.0.0' => '255.0.0.0', //LAN. | |||
'172.16.0.0' => '255.240.0.0', //LAN. | |||
'192.168.0.0' => '255.255.0.0', //LAN. | |||
'127.0.0.0' => '255.0.0.0', //Loopback. | |||
'169.254.0.0' => '255.255.0.0', //Link-local. | |||
'100.64.0.0' => '255.192.0.0', //Carrier. | |||
'192.0.2.0' => '255.255.255.0', //Testing. | |||
'198.18.0.0' => '255.254.0.0', //Testing. | |||
'198.51.100.0' => '255.255.255.0', //Testing. | |||
'203.0.113.0' => '255.255.255.0', //Testing. | |||
'192.0.0.0' => '255.255.255.0', //Reserved. | |||
'224.0.0.0' => '224.0.0.0', //Reserved. | |||
'0.0.0.0' => '255.0.0.0'); //Reserved. | |||
$ip = @inet_pton($ip); | |||
if (strlen($ip) !== 4) { return false; } | |||
//Is the IP in a private range? | |||
foreach($networks as $network_address => $network_mask) { | |||
$network_address = inet_pton($network_address); | |||
$network_mask = inet_pton($network_mask); | |||
assert(strlen($network_address) === 4); | |||
assert(strlen($network_mask) === 4); | |||
if (($ip & $network_mask) === $network_address) | |||
return false; | |||
} | |||
return true; | |||
} | |||
function geo_to_latlng($uri) { | |||
if(preg_match('/geo:([\-\+]?[0-9\.]+),([\-\+]?[0-9\.]+)/', $uri, $match)) { | |||
return array( | |||
'latitude' => (double)$match[1], | |||
'longitude' => (double)$match[2], | |||
); | |||
} else { | |||
return false; | |||
} | |||
} | |||
@ -0,0 +1,179 @@ | |||
<?php | |||
namespace p3k; | |||
function redis() { | |||
static $client = false; | |||
if(!$client) | |||
$client = new Predis\Client(Config::$redis); | |||
return $client; | |||
} | |||
function bs() | |||
{ | |||
static $pheanstalk; | |||
if(!isset($pheanstalk)) | |||
$pheanstalk = new Pheanstalk\Pheanstalk(Config::$beanstalkServer, Config::$beanstalkPort); | |||
return $pheanstalk; | |||
} | |||
function initdb() { | |||
ORM::configure('mysql:host=' . Config::$db['host'] . ';dbname=' . Config::$db['database']); | |||
ORM::configure('username', Config::$db['username']); | |||
ORM::configure('password', Config::$db['password']); | |||
} | |||
function e($text) { | |||
return htmlspecialchars($text); | |||
} | |||
function k($a, $k, $default=null) { | |||
if(is_array($k)) { | |||
$result = true; | |||
foreach($k as $key) { | |||
$result = $result && array_key_exists($key, $a); | |||
} | |||
return $result; | |||
} else { | |||
if(is_array($a) && array_key_exists($k, $a)) | |||
return $a[$k]; | |||
elseif(is_object($a) && property_exists($a, $k)) | |||
return $a->$k; | |||
else | |||
return $default; | |||
} | |||
} | |||
function random_string($len) { | |||
$charset='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | |||
$str = ''; | |||
$c = strlen($charset)-1; | |||
for($i=0; $i<$len; $i++) { | |||
$str .= $charset[mt_rand(0, $c)]; | |||
} | |||
return $str; | |||
} | |||
// Returns true if $needle is the end of the $haystack | |||
function str_ends_with($haystack, $needle) { | |||
if($needle == '' || $haystack == '') return false; | |||
return strpos(strrev($haystack), strrev($needle)) === 0; | |||
} | |||
// Sets up the session. | |||
// If create is true, the session will be created even if there is no cookie yet. | |||
// If create is false, the session will only be set up in PHP if they already have a session cookie. | |||
function session_setup($create=false, $lifetime=2592000) { | |||
if($create || isset($_COOKIE[session_name()])) { | |||
session_set_cookie_params($lifetime); | |||
session_start(); | |||
} | |||
} | |||
function session($key) { | |||
if(array_key_exists($key, $_SESSION)) | |||
return $_SESSION[$key]; | |||
else | |||
return null; | |||
} | |||
function flash($key) { | |||
if(isset($_SESSION) && isset($_SESSION[$key])) { | |||
$value = $_SESSION[$key]; | |||
unset($_SESSION[$key]); | |||
return $value; | |||
} | |||
} | |||
function html_to_dom_document($html) { | |||
// Parse the source body as HTML | |||
$doc = new DOMDocument(); | |||
libxml_use_internal_errors(true); # suppress parse errors and warnings | |||
$body = mb_convert_encoding($html, 'HTML-ENTITIES', mb_detect_encoding($html)); | |||
@$doc->loadHTML($body, LIBXML_NOWARNING|LIBXML_NOERROR); | |||
libxml_clear_errors(); | |||
return $doc; | |||
} | |||
function xml_to_dom_document($xml) { | |||
// Parse the source body as XML | |||
$doc = new DOMDocument(); | |||
libxml_use_internal_errors(true); # suppress parse errors and warnings | |||
// $body = mb_convert_encoding($xml, 'HTML-ENTITIES', mb_detect_encoding($xml)); | |||
$body = $xml; | |||
$doc->loadXML($body); | |||
libxml_clear_errors(); | |||
return $doc; | |||
} | |||
// Reads the exif rotation data and actually rotates the photo. | |||
// Only does anything if the exif library is loaded, otherwise is a noop. | |||
function correct_photo_rotation($filename) { | |||
if(class_exists('IMagick')) { | |||
try { | |||
$image = new IMagick($filename); | |||
$orientation = $image->getImageOrientation(); | |||
switch($orientation) { | |||
case IMagick::ORIENTATION_BOTTOMRIGHT: | |||
$image->rotateImage(new ImagickPixel('#00000000'), 180); | |||
break; | |||
case IMagick::ORIENTATION_RIGHTTOP: | |||
$image->rotateImage(new ImagickPixel('#00000000'), 90); | |||
break; | |||
case IMagick::ORIENTATION_LEFTBOTTOM: | |||
$image->rotateImage(new ImagickPixel('#00000000'), -90); | |||
break; | |||
} | |||
$image->setImageOrientation(IMagick::ORIENTATION_TOPLEFT); | |||
$image->writeImage($filename); | |||
} catch(Exception $e){} | |||
} | |||
} | |||
/** | |||
* Converts base 10 to base 60. | |||
* http://tantek.pbworks.com/NewBase60 | |||
* @param int $n | |||
* @return string | |||
*/ | |||
function b10to60($n) | |||
{ | |||
$s = ""; | |||
$m = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz"; | |||
if ($n==0) | |||
return 0; | |||
while ($n>0) | |||
{ | |||
$d = $n % 60; | |||
$s = $m[$d] . $s; | |||
$n = ($n-$d)/60; | |||
} | |||
return $s; | |||
} | |||
/** | |||
* Converts base 60 to base 10, with error checking | |||
* http://tantek.pbworks.com/NewBase60 | |||
* @param string $s | |||
* @return int | |||
*/ | |||
function b60to10($s) | |||
{ | |||
$n = 0; | |||
for($i = 0; $i < strlen($s); $i++) // iterate from first to last char of $s | |||
{ | |||
$c = ord($s[$i]); // put current ASCII of char into $c | |||
if ($c>=48 && $c<=57) { $c=$c-48; } | |||
else if ($c>=65 && $c<=72) { $c-=55; } | |||
else if ($c==73 || $c==108) { $c=1; } // typo capital I, lowercase l to 1 | |||
else if ($c>=74 && $c<=78) { $c-=56; } | |||
else if ($c==79) { $c=0; } // error correct typo capital O to 0 | |||
else if ($c>=80 && $c<=90) { $c-=57; } | |||
else if ($c==95) { $c=34; } // underscore | |||
else if ($c>=97 && $c<=107) { $c-=62; } | |||
else if ($c>=109 && $c<=122) { $c-=63; } | |||
else { $c = 0; } // treat all other noise as 0 | |||
$n = (60 * $n) + $c; | |||
} | |||
return $n; | |||
} |
@ -0,0 +1,78 @@ | |||
<?php | |||
class DateTest extends PHPUnit_Framework_TestCase { | |||
public function testFormatLocalPositiveOffset() { | |||
$local = p3k\date\format_local('c', '2017-05-01T13:30:00+0000', 7200); | |||
$this->assertEquals('2017-05-01T15:30:00+02:00', $local); | |||
} | |||
public function testFormatLocalNegativeOffset() { | |||
$local = p3k\date\format_local('c', '2017-05-01T13:30:00+0000', -25200); | |||
$this->assertEquals('2017-05-01T06:30:00-07:00', $local); | |||
} | |||
public function testFormatLocalZeroOffset() { | |||
$local = p3k\date\format_local('c', '2017-05-01T13:30:00+0200', 0); | |||
$this->assertEquals('2017-05-01T11:30:00+00:00', $local); | |||
} | |||
public function testTZSecondsToTimezonePositive() { | |||
$tz = p3k\date\tz_seconds_to_timezone(7200); | |||
$this->assertInstanceOf(DateTimeZone::class, $tz); | |||
$this->assertEquals('+02:00', $tz->getName()); | |||
} | |||
public function testTZSecondsToTimezoneNegative() { | |||
$tz = p3k\date\tz_seconds_to_timezone(-25200); | |||
$this->assertInstanceOf(DateTimeZone::class, $tz); | |||
$this->assertEquals('-07:00', $tz->getName()); | |||
} | |||
public function testTZSecondsToTimezoneZero() { | |||
$tz = p3k\date\tz_seconds_to_timezone(0); | |||
$this->assertInstanceOf(DateTimeZone::class, $tz); | |||
$this->assertEquals('UTC', $tz->getName()); | |||
} | |||
public function testTZOffsetToSecondsPositive() { | |||
$seconds = p3k\date\tz_offset_to_seconds('+02:00'); | |||
$this->assertEquals(7200, $seconds); | |||
$seconds = p3k\date\tz_offset_to_seconds('+0200'); | |||
$this->assertEquals(7200, $seconds); | |||
} | |||
public function testTZOffsetToSecondsNegative() { | |||
$seconds = p3k\date\tz_offset_to_seconds('-07:00'); | |||
$this->assertEquals(-25200, $seconds); | |||
$seconds = p3k\date\tz_offset_to_seconds('-0700'); | |||
$this->assertEquals(-25200, $seconds); | |||
} | |||
public function testTZOffsetToSecondsZero() { | |||
$seconds = p3k\date\tz_offset_to_seconds('+00:00'); | |||
$this->assertEquals(0, $seconds); | |||
$seconds = p3k\date\tz_offset_to_seconds('+0000'); | |||
$this->assertEquals(0, $seconds); | |||
} | |||
public function testTZOffsetToSecondsInvalid() { | |||
$seconds = p3k\date\tz_offset_to_seconds('foo'); | |||
$this->assertEquals(0, $seconds); | |||
} | |||
public function testTZSecondsToOffsetPositive() { | |||
$offset = p3k\date\tz_seconds_to_offset(7200); | |||
$this->assertEquals('+02:00', $offset); | |||
} | |||
public function testTZSecondsToOffsetNegative() { | |||
$offset = p3k\date\tz_seconds_to_offset(-25200); | |||
$this->assertEquals('-07:00', $offset); | |||
} | |||
public function testTZSecondsToOffsetZero() { | |||
$offset = p3k\date\tz_seconds_to_offset(0); | |||
$this->assertEquals('+00:00', $offset); | |||
} | |||
} |
@ -0,0 +1,2 @@ | |||
<?php | |||
require_once __DIR__ . '/../vendor/autoload.php'; |