2020-12-23 01:44:45 +00:00
< ? php
namespace FWIF ;
2020-12-26 04:12:26 +00:00
use DateInterval ;
2020-12-24 03:25:10 +00:00
use DateTime ;
use DateTimeInterface ;
2020-12-26 04:12:26 +00:00
use DateTimeImmutable ;
2020-12-24 03:25:10 +00:00
use DateTimeZone ;
2020-12-26 04:12:26 +00:00
use InvalidArgumentException ;
2020-12-24 03:25:10 +00:00
2020-12-23 01:44:45 +00:00
class FWIF {
2020-12-28 01:09:41 +00:00
public const CONTENT_TYPE = 'application/x.fwif' ;
2020-12-23 01:44:45 +00:00
2020-12-27 04:10:44 +00:00
public const DEFAULT = 0 ; // Default behaviour
2020-12-24 03:25:10 +00:00
public const DISCARD_MILLISECONDS = 0x01 ; // Always exclude the millisecond component from DateTime
2020-12-27 04:10:44 +00:00
public const EXCLUDE_VERSION = 0x02 ; // Exclude version byte at the start of the stream
2020-12-24 03:25:10 +00:00
2020-12-23 01:44:45 +00:00
public const TYPE_NULL = 0 ; // NULL, no data
public const TYPE_INTEGER = 0x01 ; // LEB128, implicit length
public const TYPE_FLOAT = 0x02 ; // double precision IEEE 754, fixed length of 8 bytes
2020-12-24 03:25:10 +00:00
public const TYPE_STRING = 0x03 ; // UTF-8 string, terminated with TRAILER
public const TYPE_BUFFER = 0x04 ; // Buffer with binary data, prefixed with a LEB128 length
public const TYPE_ARRAY = 0x05 ; // List of values, terminated with TRAILER
public const TYPE_OBJECT = 0x06 ; // List of values with ASCII names, terminated with TRAILER
2020-12-26 04:12:26 +00:00
public const TYPE_DATETIME = 0x07 ; // A gregorian year, month and day as well as an hour, minute, seconds and millisecond component, variable ranging from 4 to 7 bytes
2020-12-27 04:10:44 +00:00
public const TYPE_TIMESPAN = 0x08 ; // A period of time, follows the same format DATETIME but uses an additional flag to indicate negative periods
2020-12-24 03:25:10 +00:00
public const TRAILER = 0xFF ; // Termination byte
2020-12-23 01:44:45 +00:00
2020-12-26 04:12:26 +00:00
public const VERSION = 0x01 ; // min 1, max 254
2020-12-23 01:44:45 +00:00
private const CODECS = [
2020-12-27 04:10:44 +00:00
self :: TYPE_NULL => 'Null' ,
self :: TYPE_INTEGER => 'Integer' ,
self :: TYPE_FLOAT => 'Float' ,
self :: TYPE_STRING => 'String' ,
self :: TYPE_ARRAY => 'Array' ,
self :: TYPE_OBJECT => 'Object' ,
self :: TYPE_BUFFER => 'Buffer' ,
2020-12-24 03:25:10 +00:00
self :: TYPE_DATETIME => 'DateTime' ,
2020-12-27 04:10:44 +00:00
self :: TYPE_TIMESPAN => 'TimeSpan' ,
2020-12-23 01:44:45 +00:00
];
2020-12-24 03:25:10 +00:00
private const UTF8 = '%^(?:' // https://www.w3.org/International/questions/qa-forms-utf-8.en
. '[\x09\x0A\x0D\x20-\x7E]' // ASCII
. '|[\xC2-\xDF][\x80-\xBF]' // non-overlong 2-byte
. '|\xE0[\xA0-\xBF][\x80-\xBF]' // excluding overlongs
. '|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}' // straight 3-byte
. '|\xED[\x80-\x9F][\x80-\xBF]' // excluding surrogates
. '|\xF0[\x90-\xBF][\x80-\xBF]{2}' // planes 1-3
. '|[\xF1-\xF3][\x80-\xBF]{3}' // planes 4-15
. '|\xF4[\x80-\x8F][\x80-\xBF]{2}' // plane 16
. ')*$%xs' ;
2020-12-23 01:44:45 +00:00
private static function isAssocArray ( $array ) : bool {
if ( ! is_array ( $array ) || $array === [])
return false ;
return array_keys ( $array ) !== range ( 0 , count ( $array ) - 1 );
}
2020-12-24 03:25:10 +00:00
// apparently this is faster than mb_check_encoding($string, 'utf-8');
// on PHP 7.1 on Windows at least, perhaps investigate this later
2020-12-26 04:12:26 +00:00
// UPDATE TODO: does this even make any sense with other internal encodings?
2020-12-24 03:25:10 +00:00
private static function isUTF8String ( string $string ) : bool {
return preg_match ( self :: UTF8 , $string ) === 1 ;
}
private static function detectType ( $data , int $flags ) : int {
2020-12-23 01:44:45 +00:00
if ( is_null ( $data ))
return self :: TYPE_NULL ;
if ( is_int ( $data ))
return self :: TYPE_INTEGER ;
if ( is_float ( $data ))
return self :: TYPE_FLOAT ;
2020-12-24 03:25:10 +00:00
if ( is_string ( $data ))
return self :: isUTF8String ( $data ) ? self :: TYPE_STRING : self :: TYPE_BUFFER ;
if ( is_object ( $data ) || self :: isAssocArray ( $data )) {
if ( $data instanceof DateTimeInterface )
return self :: TYPE_DATETIME ;
2020-12-27 04:10:44 +00:00
if ( $data instanceof DateInterval )
return self :: TYPE_TIMESPAN ;
2020-12-23 01:44:45 +00:00
return self :: TYPE_OBJECT ;
2020-12-24 03:25:10 +00:00
}
2020-12-23 01:44:45 +00:00
if ( is_array ( $data ))
return self :: TYPE_ARRAY ;
throw new FWIFUnsupportedTypeException ( gettype ( $data ));
}
2020-12-24 03:25:10 +00:00
public static function encode ( $data , int $flags = self :: DEFAULT ) : string {
2020-12-26 04:12:26 +00:00
$encoded = self :: encodeInternal ( $data , $flags );
if ( ! ( $flags & self :: EXCLUDE_VERSION ))
$encoded = chr ( self :: VERSION ) . $encoded ;
return $encoded ;
}
private static function encodeInternal ( $data , int $flags ) : string {
2020-12-23 01:44:45 +00:00
if ( $data instanceof FWIFSerializable )
$data = $data -> fwifSerialize ();
2020-12-24 03:25:10 +00:00
$type = self :: detectType ( $data , $flags );
return chr ( $type ) . self :: { 'encode' . self :: CODECS [ $type ]}( $data , $flags );
2020-12-23 01:44:45 +00:00
}
2020-12-26 04:12:26 +00:00
public static function decode ( $data , int $flags = self :: DEFAULT ) {
if ( is_string ( $data )) {
$fd = fopen ( 'php://memory' , 'rb+' );
fwrite ( $fd , $data );
fseek ( $fd , 0 );
$data = $fd ;
}
if ( ! is_resource ( $data ))
throw new InvalidArgumentException ( '$data must be either a string or a file handle.' );
if ( ! ( $flags & self :: EXCLUDE_VERSION )) {
$version = ord ( fgetc ( $data ));
if ( $version < 1 || $version > 254 )
throw new InvalidArgumentException ( '$data is not a valid FWIF serialized stream.' );
if ( $version > self :: VERSION )
throw new FWIFUnsupportedVersionException ;
}
$decoded = self :: decodeInternal ( $data , $flags );
if ( isset ( $fd ))
fclose ( $fd );
return $decoded ;
2020-12-23 01:44:45 +00:00
}
2020-12-26 04:12:26 +00:00
private static function decodeInternal ( $data , int $flags ) {
$type = ord ( fgetc ( $data ));
2020-12-23 01:44:45 +00:00
if ( ! array_key_exists ( $type , self :: CODECS )) {
2020-12-26 04:12:26 +00:00
$hexType = dechex ( $type ); $pos = ftell ( $data ); $hexPos = dechex ( $pos );
throw new FWIFUnsupportedTypeException ( " Unsupported type { $type } (0x { $hexType } ) at position { $pos } (0x { $hexPos } ) " );
2020-12-23 01:44:45 +00:00
}
2020-12-24 03:25:10 +00:00
return self :: { 'decode' . self :: CODECS [ $type ]}( $data , $flags );
2020-12-23 01:44:45 +00:00
}
2020-12-24 03:25:10 +00:00
private static function encodeNull ( $data , int $flags ) : string { return '' ; }
2020-12-26 04:12:26 +00:00
private static function decodeNull ( $data , int $flags ) { return null ; }
2020-12-23 01:44:45 +00:00
2020-12-24 03:25:10 +00:00
private static function encodeInteger ( int $number , int $flags ) : string {
2020-12-23 01:44:45 +00:00
$packed = '' ; $more = 1 ; $negative = $number < 0 ; $size = PHP_INT_SIZE * 8 ;
while ( $more ) {
$byte = $number & 0x7F ;
$number >>= 7 ;
if ( $negative )
$number |= ( ~ 0 << ( $size - 7 ));
if (( ! $number && ! ( $byte & 0x40 )) || ( $number === - 1 && ( $byte & 0x40 )))
$more = 0 ;
else
$byte |= 0x80 ;
$packed .= chr ( $byte );
}
return $packed ;
}
2020-12-26 04:12:26 +00:00
private static function decodeInteger ( $data , int $flags ) : int {
2020-12-28 01:09:41 +00:00
$number = 0 ; $shift = 0 ; $size = PHP_INT_SIZE * 8 ;
2020-12-23 01:44:45 +00:00
do {
2020-12-26 04:12:26 +00:00
$byte = ord ( fgetc ( $data ));
2020-12-23 01:44:45 +00:00
$number |= ( $byte & 0x7F ) << $shift ;
$shift += 7 ;
} while ( $byte & 0x80 );
if (( $shift < $size ) && ( $byte & 0x40 ))
$number |= ( ~ 0 << $shift );
return $number ;
}
2020-12-27 04:10:44 +00:00
private const FLOAT_MZ = 0x04 ;
private const FLOAT_EZ = 0x08 ;
private const FLOAT_ZERO = self :: FLOAT_MZ | self :: FLOAT_EZ ;
private const FLOAT_NAN = 0x20 ;
private const FLOAT_INF = 0x40 ;
private const FLOAT_SIGN = 0x80 ;
private const FLOAT_NINF = self :: FLOAT_INF | self :: FLOAT_SIGN ;
private const FLOAT_DIV = 0x7FFFFFFF ;
2020-12-24 03:25:10 +00:00
private static function encodeFloat ( float $number , int $flags ) : string {
2020-12-27 04:10:44 +00:00
if ( is_nan ( $number ))
$packed = chr ( self :: FLOAT_NAN );
elseif ( $number === INF )
$packed = chr ( self :: FLOAT_INF );
elseif ( $number === - INF )
$packed = chr ( self :: FLOAT_NINF );
else {
$byte1 = 0 ; $packed = '' ;
if ( $number < 0.0 || ( $number ** - 1 === - INF ))
$byte1 |= self :: FLOAT_SIGN ;
if ( $number === 0.0 )
$byte1 |= self :: FLOAT_ZERO ;
else {
$numAbs = abs ( $number );
$exp = ( int )( floor ( log ( $numAbs , 2 )) + 1 );
$man = ( int )(( $numAbs * ( 2 ** - $exp )) * self :: FLOAT_DIV );
if ( $exp != 0 )
$packed .= self :: encodeInteger ( $exp , $flags );
else
$byte1 |= self :: FLOAT_EZ ;
if ( $man > 0 )
$packed .= pack ( 'N' , $man );
else
$byte1 |= self :: FLOAT_MZ ;
}
$packed = chr ( $byte1 ) . $packed ;
}
return $packed ;
2020-12-23 01:44:45 +00:00
}
2020-12-26 04:12:26 +00:00
private static function decodeFloat ( $data , int $flags ) : float {
2020-12-27 04:10:44 +00:00
$byte1 = ord ( fgetc ( $data ));
if ( $byte1 & self :: FLOAT_NAN )
return NAN ;
if ( $byte1 & self :: FLOAT_INF )
return ( $byte1 & self :: FLOAT_SIGN ) ? - INF : INF ;
if (( $byte1 & self :: FLOAT_ZERO ) === self :: FLOAT_ZERO )
return ( $byte1 & self :: FLOAT_SIGN ) ? - 0.0 : 0.0 ;
$exp = 0 ; $man = 0 ;
if ( ! ( $byte1 & self :: FLOAT_EZ ))
$exp = self :: decodeInteger ( $data , $flags );
if ( ! ( $byte1 & self :: FLOAT_MZ ))
$man = (( float ) unpack ( 'N' , fread ( $data , 4 ))[ 1 ]) / self :: FLOAT_DIV ;
$number = $man * ( 2 ** $exp );
if ( $byte1 & self :: FLOAT_SIGN )
$number *= - 1 ;
return $number ;
2020-12-23 01:44:45 +00:00
}
2020-12-24 03:25:10 +00:00
private static function encodeString ( string $string , int $flags ) : string {
2020-12-26 04:12:26 +00:00
$packed = '' ;
$string = unpack ( 'C*' , mb_convert_encoding ( $string , 'utf-8' , mb_internal_encoding ()));
2020-12-23 01:44:45 +00:00
foreach ( $string as $char )
$packed .= chr ( $char );
2020-12-24 03:25:10 +00:00
return $packed . chr ( self :: TRAILER );
2020-12-23 01:44:45 +00:00
}
2020-12-26 04:12:26 +00:00
private static function decodeString ( $data , int $flags ) : string {
$packed = '' ;
2020-12-23 01:44:45 +00:00
for (;;) {
2020-12-26 04:12:26 +00:00
$char = fgetc ( $data ); $byte = ord ( $char );
if ( $byte == self :: TRAILER )
2020-12-23 01:44:45 +00:00
break ;
2020-12-26 04:12:26 +00:00
$packed .= $char ;
if (( $byte & 0xF8 ) == 0xF0 )
$packed .= fread ( $data , 3 );
elseif (( $byte & 0xF0 ) == 0xE0 )
$packed .= fread ( $data , 2 );
elseif (( $byte & 0xE0 ) == 0xC0 )
$packed .= fgetc ( $data );
2020-12-23 01:44:45 +00:00
}
2020-12-26 04:12:26 +00:00
return mb_convert_encoding ( $packed , mb_internal_encoding (), 'utf-8' );
2020-12-23 01:44:45 +00:00
}
2020-12-24 03:25:10 +00:00
private static function encodeArray ( array $array , int $flags ) : string {
2020-12-23 01:44:45 +00:00
$packed = '' ;
foreach ( $array as $value )
2020-12-26 04:12:26 +00:00
$packed .= self :: encodeInternal ( $value , $flags );
2020-12-24 03:25:10 +00:00
return $packed . chr ( self :: TRAILER );
2020-12-23 01:44:45 +00:00
}
2020-12-26 04:12:26 +00:00
private static function decodeArray ( $data , int $flags ) : array {
2020-12-23 01:44:45 +00:00
$array = [];
for (;;) {
2020-12-26 04:12:26 +00:00
if ( ord ( fgetc ( $data )) === self :: TRAILER )
2020-12-23 01:44:45 +00:00
break ;
2020-12-26 04:12:26 +00:00
fseek ( $data , - 1 , SEEK_CUR );
2020-12-24 03:25:10 +00:00
$array [] = self :: decodeInternal ( $data , $flags );
2020-12-23 01:44:45 +00:00
}
return $array ;
}
2020-12-24 03:25:10 +00:00
private static function encodeObject ( $object , int $flags ) : string {
2020-12-23 01:44:45 +00:00
$packed = '' ; $array = ( array ) $object ;
foreach ( $array as $name => $value )
2020-12-26 04:12:26 +00:00
$packed .= mb_convert_encoding ( $name , 'us-ascii' , mb_internal_encoding ()) . chr ( self :: TRAILER ) . self :: encodeInternal ( $value , $flags );
2020-12-24 03:25:10 +00:00
return $packed . chr ( self :: TRAILER );
2020-12-23 01:44:45 +00:00
}
2020-12-26 04:12:26 +00:00
private static function decodeObjectKey ( $data , int $flags ) : string {
$packed = '' ;
for (;;) {
$char = fgetc ( $data );
if ( ord ( $char ) === self :: TRAILER )
break ;
$packed .= $char ;
}
return mb_convert_encoding ( $packed , mb_internal_encoding (), 'us-ascii' );
}
private static function decodeObject ( $data , int $flags ) : object {
2020-12-23 01:44:45 +00:00
$array = [];
for (;;) {
2020-12-26 04:12:26 +00:00
if ( ord ( fgetc ( $data )) === self :: TRAILER )
2020-12-23 01:44:45 +00:00
break ;
2020-12-26 04:12:26 +00:00
fseek ( $data , - 1 , SEEK_CUR );
$array [ self :: decodeObjectKey ( $data , $flags )] = self :: decodeInternal ( $data , $flags );
2020-12-23 01:44:45 +00:00
}
return ( object ) $array ;
}
2020-12-24 03:25:10 +00:00
private static function encodeBuffer ( string $buffer , int $flags ) : string {
return self :: encodeInteger ( strlen ( $buffer ), $flags ) . $buffer ;
}
2020-12-26 04:12:26 +00:00
private static function decodeBuffer ( $data , int $flags ) : string {
return fread ( $data , self :: decodeInteger ( $data , $flags ));
2020-12-24 03:25:10 +00:00
}
2020-12-27 04:10:44 +00:00
private const DATETIME_FLAG_TIME = 0x40 ;
private const DATETIME_FLAG_MILLI = 0x4000 ;
2020-12-24 03:25:10 +00:00
2020-12-26 04:12:26 +00:00
private const DATETIME_YEAR_SIGN = 0x40000000 ;
2020-12-24 03:25:10 +00:00
private const DATETIME_YEAR_MASK = 0x3FFF ;
2020-12-26 04:12:26 +00:00
private const DATETIME_YEAR_SHIFT = 16 ; // <<
2020-12-24 03:25:10 +00:00
private const DATETIME_MONTH_MASK = 0x0F ;
2020-12-26 04:12:26 +00:00
private const DATETIME_MONTH_SHIFT = 12 ; // <<
2020-12-24 03:25:10 +00:00
private const DATETIME_DAY_MASK = 0x1F ;
2020-12-26 04:12:26 +00:00
private const DATETIME_DAY_SHIFT = 7 ; // <<
2020-12-24 03:25:10 +00:00
private const DATETIME_HOUR_MASK = 0x1F ;
private const DATETIME_MINS_MASK = 0x3F ;
2020-12-26 04:12:26 +00:00
private const DATETIME_MINS_SHIFT = 8 ; // <<
2020-12-24 03:25:10 +00:00
private const DATETIME_SECS_MASK = 0x3F ;
2020-12-26 04:12:26 +00:00
private const DATETIME_SECS_SHIFT = 2 ; // <<
private const DATETIME_MILLI_HI_MASK = 0x300 ;
private const DATETIME_MILLI_HI_SHIFT = 8 ; // >>
private const DATETIME_MILLI_LO_MASK = 0x0FF ;
2020-12-27 04:10:44 +00:00
/* +--------+--------+ Y - Signed 15 - bit year W - w enable flag
* |. YYYYYYY | YYYYYYYY | M - Unsigned 4 - bit month m - unsigned 6 - bit minutes
2020-12-27 21:35:22 +00:00
* | MMMMDDDD | DT . HHHHH | D - Unsigned 5 - bit Day S - unsigned 6 - bit seconds
2020-12-27 04:10:44 +00:00
* |. Wmmmmmm | SSSSSSww | T - WmSw enable flag w - unsigned 10 - bit millisecs
2020-12-27 21:35:22 +00:00
* | wwwwwwww | | H - Unsigned 5 - bit hours
* +--------+--------+
2020-12-24 03:25:10 +00:00
*/
private static function encodeDateTime ( DateTimeInterface $dt , int $flags ) : string {
static $utc = null ;
if ( $utc === null )
$utc = new DateTimeZone ( 'utc' );
if ( $dt -> getTimezone () -> getOffset ( $dt ) !== 0 )
$dt = DateTime :: createFromInterface ( $dt ) -> setTimezone ( $utc );
2020-12-27 21:35:22 +00:00
$year = ( int ) $dt -> format ( 'Y' );
$month = ( int ) $dt -> format ( 'n' );
$day = ( int ) $dt -> format ( 'j' );
$hours = ( int ) $dt -> format ( 'G' );
$mins = ( int ) $dt -> format ( 'i' );
$secs = ( int ) $dt -> format ( 's' );
$millis = ( $flags & self :: DISCARD_MILLISECONDS ) ? 0 : ( int ) $dt -> format ( 'v' );
2020-12-24 03:25:10 +00:00
$subYear = $year < 0 ;
if ( $subYear )
$year = ~ $year ;
2020-12-26 04:41:52 +00:00
$ymdh = $subYear ? self :: DATETIME_YEAR_SIGN : 0 ;
$ymdh |= ( $year & self :: DATETIME_YEAR_MASK ) << self :: DATETIME_YEAR_SHIFT ;
$ymdh |= ( $month & self :: DATETIME_MONTH_MASK ) << self :: DATETIME_MONTH_SHIFT ;
$ymdh |= ( $day & self :: DATETIME_DAY_MASK ) << self :: DATETIME_DAY_SHIFT ;
$ymdh |= ( $hours & self :: DATETIME_HOUR_MASK );
2020-12-24 03:25:10 +00:00
2020-12-26 04:12:26 +00:00
if ( $mins > 0 || $secs > 0 || $millis > 0 ) {
2020-12-26 04:41:52 +00:00
$ymdh |= self :: DATETIME_FLAG_TIME ;
$msw = 0 ;
$msw |= ( $mins & self :: DATETIME_MINS_MASK ) << self :: DATETIME_MINS_SHIFT ;
$msw |= ( $secs & self :: DATETIME_SECS_MASK ) << self :: DATETIME_SECS_SHIFT ;
2020-12-24 03:25:10 +00:00
if ( $millis > 0 ) {
2020-12-26 04:38:51 +00:00
$msw |= self :: DATETIME_FLAG_MILLI ;
$msw |= ( $millis & self :: DATETIME_MILLI_HI_MASK ) >> self :: DATETIME_MILLI_HI_SHIFT ;
$w = $millis & self :: DATETIME_MILLI_LO_MASK ;
2020-12-24 03:25:10 +00:00
}
}
2020-12-26 04:41:52 +00:00
$packed = pack ( 'N' , $ymdh );
if ( $ymdh & self :: DATETIME_FLAG_TIME ) {
2020-12-26 04:38:51 +00:00
$packed .= pack ( 'n' , $msw );
if ( $msw & self :: DATETIME_FLAG_MILLI )
$packed .= chr ( $w );
2020-12-26 04:12:26 +00:00
}
2020-12-24 03:25:10 +00:00
2020-12-26 04:12:26 +00:00
return $packed ;
2020-12-24 03:25:10 +00:00
}
2020-12-26 04:12:26 +00:00
private static function decodeDateTime ( $data , int $flags ) : DateTimeInterface {
2020-12-27 21:35:22 +00:00
$ymdh = unpack ( 'N' , fread ( $data , 4 ))[ 1 ];
$years = ( $ymdh >> self :: DATETIME_YEAR_SHIFT ) & self :: DATETIME_YEAR_MASK ;
$months = ( $ymdh >> self :: DATETIME_MONTH_SHIFT ) & self :: DATETIME_MONTH_MASK ;
$days = ( $ymdh >> self :: DATETIME_DAY_SHIFT ) & self :: DATETIME_DAY_MASK ;
$hours = $ymdh & self :: DATETIME_HOUR_MASK ;
2020-12-26 04:12:26 +00:00
2020-12-26 04:41:52 +00:00
if ( $ymdh & self :: DATETIME_YEAR_SIGN )
2020-12-27 21:35:22 +00:00
$years = ~ $years ;
$dt = sprintf ( '%04d-%02d-%02dT%02d:' , $years , $months , $days , $hours );
2020-12-26 04:12:26 +00:00
2020-12-27 21:35:22 +00:00
if ( $ymdh & self :: DATETIME_FLAG_TIME ) {
$msw = unpack ( 'n' , fread ( $data , 2 ))[ 1 ];
2020-12-26 04:38:51 +00:00
$mins = ( $msw >> self :: DATETIME_MINS_SHIFT ) & self :: DATETIME_MINS_MASK ;
$secs = ( $msw >> self :: DATETIME_SECS_SHIFT ) & self :: DATETIME_SECS_MASK ;
2020-12-27 21:35:22 +00:00
$dt .= sprintf ( '%02d:%02d' , $mins , $secs );
if ( $msw & self :: DATETIME_FLAG_MILLI ) {
2020-12-26 04:38:51 +00:00
$millis = ( $msw << self :: DATETIME_MILLI_HI_SHIFT ) & self :: DATETIME_MILLI_HI_MASK ;
2020-12-27 21:35:22 +00:00
$millis |= ord ( fgetc ( $data ));
2020-12-26 04:12:26 +00:00
$dt .= sprintf ( '.%03d' , $millis );
}
} else $dt .= '00:00' ;
2020-12-27 21:35:22 +00:00
return new DateTimeImmutable ( $dt . 'UTC' );
2020-12-24 03:25:10 +00:00
}
2020-12-27 04:10:44 +00:00
2020-12-27 21:35:22 +00:00
private const TIMESPAN_FLAG_YEAR = 0x10000000 ;
private const TIMESPAN_FLAG_DMY = 0x20000000 ;
private const TIMESPAN_FLAG_NEGA = 0x40000000 ;
private const TIMESPAN_MILLI_SHIFT = 16 ; // <<
private const TIMESPAN_MILLI_MASK = 0x3FF ;
private const TIMESPAN_SECS_SHIFT = 11 ; // <<
private const TIMESPAN_MINS_SHIFT = 5 ; // <<
private const TIMESPAN_DAYS_SHIFT = 10 ; // <<
private const TIMESPAN_MONTH_SHIFT = 6 ; // <<
private const TIMESPAN_YEAR_HI_MASK = 0x3F00 ;
private const TIMESPAN_YEAR_HI_SHIFT = 8 ; // >>
private const TIMESPAN_YEAR_LO_MASK = 0x00FF ;
/* +--------+--------+ w - unsigned 10 - bit millisecs y - Y enable flag
* |. ndy . www | wwwwwwwS | S - unsigned 6 - bit seconds D - unsigned 5 - bit day
* | SSSSSmmm | mmmHHHHH | m - unsigned 6 - bit minutes M - unsigned 4 - bit month
* |. DDDDDMM | MMYYYYYY | H - unsigned 5 - bit hours Y - unsigned 14 - bit year
* | YYYYYYYY | | n - Negative flag
* +--------+--------+ d - DMY enable flag
*/
2020-12-27 04:10:44 +00:00
private static function encodeTimeSpan ( DateInterval $di , int $flags ) : string {
2020-12-27 21:35:22 +00:00
$wsmh = $di -> invert ? self :: TIMESPAN_FLAG_NEGA : 0 ;
$wsmh |= (( int )( $di -> f * 1000 ) & self :: TIMESPAN_MILLI_MASK ) << self :: TIMESPAN_MILLI_SHIFT ;
$wsmh |= ( $di -> s & self :: DATETIME_SECS_MASK ) << self :: TIMESPAN_SECS_SHIFT ;
$wsmh |= ( $di -> i & self :: DATETIME_MINS_MASK ) << self :: TIMESPAN_MINS_SHIFT ;
$wsmh |= ( $di -> h & self :: DATETIME_HOUR_MASK );
if ( $di -> d > 0 || $di -> m > 0 || $di -> y > 0 ) {
$wsmh |= self :: TIMESPAN_FLAG_DMY ;
$dmy = ( $di -> d & self :: DATETIME_DAY_MASK ) << self :: TIMESPAN_DAYS_SHIFT ;
$dmy |= ( $di -> m & self :: DATETIME_MONTH_MASK ) << self :: TIMESPAN_MONTH_SHIFT ;
if ( $di -> y > 0 ) {
$wsmh |= self :: TIMESPAN_FLAG_YEAR ;
$dmy |= ( $di -> y & self :: TIMESPAN_YEAR_HI_MASK ) >> self :: TIMESPAN_YEAR_HI_SHIFT ;
$y = ( $di -> y & self :: TIMESPAN_YEAR_LO_MASK );
}
}
$packed = pack ( 'N' , $wsmh );
if ( $wsmh & self :: TIMESPAN_FLAG_DMY ) {
$packed .= pack ( 'n' , $dmy );
if ( $wsmh & self :: TIMESPAN_FLAG_YEAR )
$packed .= chr ( $y );
}
return $packed ;
2020-12-27 04:10:44 +00:00
}
private static function decodeTimeSpan ( $data , int $flags ) : DateInterval {
2020-12-27 21:35:22 +00:00
$di = new DateInterval ( 'P0Y' );
$wsmh = unpack ( 'N' , fread ( $data , 4 ))[ 1 ];
$di -> invert = ( $wsmh & self :: TIMESPAN_FLAG_NEGA ) ? 1 : 0 ;
$di -> f = (( $wsmh >> self :: TIMESPAN_MILLI_SHIFT ) & self :: TIMESPAN_MILLI_MASK ) / 1000.0 ;
$di -> s = ( $wsmh >> self :: TIMESPAN_SECS_SHIFT ) & self :: DATETIME_SECS_MASK ;
$di -> i = ( $wsmh >> self :: TIMESPAN_MINS_SHIFT ) & self :: DATETIME_MINS_MASK ;
$di -> h = $wsmh & self :: DATETIME_HOUR_MASK ;
if ( $wsmh & self :: TIMESPAN_FLAG_DMY ) {
$dmy = unpack ( 'n' , fread ( $data , 2 ))[ 1 ];
$di -> d = ( $dmy >> self :: TIMESPAN_DAYS_SHIFT ) & self :: DATETIME_DAY_MASK ;
2020-12-28 01:09:41 +00:00
// OOPS! THESE WILL BE HORRIBLY INACCURATE!
// Perhaps go back to the drawing board and tack months and years onto days?
2020-12-27 21:35:22 +00:00
$di -> m = ( $dmy >> self :: TIMESPAN_MONTH_SHIFT ) & self :: DATETIME_MONTH_MASK ;
if ( $wsmh & self :: TIMESPAN_FLAG_YEAR )
$di -> y = ord ( fgetc ( $data ))
| (( $dmy << self :: TIMESPAN_YEAR_HI_SHIFT ) & self :: TIMESPAN_YEAR_HI_MASK );
}
return $di ;
2020-12-27 04:10:44 +00:00
}
2020-12-23 01:44:45 +00:00
}