diff --git a/lib/FWIF/FWIF.php b/lib/FWIF/FWIF.php index b9f449e..490ed3a 100644 --- a/lib/FWIF/FWIF.php +++ b/lib/FWIF/FWIF.php @@ -8,8 +8,6 @@ use DateTimeImmutable; use DateTimeZone; use InvalidArgumentException; -// TODO: IMPLEMENT TYPE_TIMESPAN - class FWIF { public const CONTENT_TYPE = 'text/plain; charset=us-ascii'; // TODO: come up with a mime type @@ -304,7 +302,6 @@ class FWIF { return fread($data, self::decodeInteger($data, $flags)); } - private const DATETIME_FLAG_NEGA = 0x20; private const DATETIME_FLAG_TIME = 0x40; private const DATETIME_FLAG_MILLI = 0x4000; @@ -332,10 +329,10 @@ class FWIF { /* +--------+--------+ Y - Signed 15-bit year W - w enable flag * |.YYYYYYY|YYYYYYYY| M - Unsigned 4-bit month m - unsigned 6-bit minutes - * |MMMMDDDD|DTNHHHHH| D - Unsigned 5-bit Day S - unsigned 6-bit seconds + * |MMMMDDDD|DT.HHHHH| D - Unsigned 5-bit Day S - unsigned 6-bit seconds * |.Wmmmmmm|SSSSSSww| T - WmSw enable flag w - unsigned 10-bit millisecs - * |wwwwwwww| | N - Negative flag (for TYPE_PERIOD) - * +--------+--------+ H - Unsigned 5-bit hours + * |wwwwwwww| | H - Unsigned 5-bit hours + * +--------+--------+ */ private static function encodeDateTime(DateTimeInterface $dt, int $flags): string { @@ -346,10 +343,13 @@ class FWIF { if($dt->getTimezone()->getOffset($dt) !== 0) $dt = DateTime::createFromInterface($dt)->setTimezone($utc); - $year = (int)$dt->format('Y'); - $month = (int)$dt->format('n'); - $day = (int)$dt->format('j'); - $hours = (int)$dt->format('G'); + $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'); $subYear = $year < 0; if($subYear) @@ -361,10 +361,6 @@ class FWIF { $ymdh |= ($day & self::DATETIME_DAY_MASK) << self::DATETIME_DAY_SHIFT; $ymdh |= ($hours & self::DATETIME_HOUR_MASK); - $mins = (int)$dt->format('i'); - $secs = (int)$dt->format('s'); - $millis = ($flags & self::DISCARD_MILLISECONDS) ? 0 : (int)$dt->format('v'); - if($mins > 0 || $secs > 0 || $millis > 0) { $ymdh |= self::DATETIME_FLAG_TIME; $msw = 0; @@ -388,39 +384,103 @@ class FWIF { return $packed; } private static function decodeDateTime($data, int $flags): DateTimeInterface { - $ymdh = unpack('N', fread($data, 4))[1]; - $hasMsw = $ymdh & self::DATETIME_FLAG_TIME; - $msw = $hasMsw ? unpack('n', fread($data, 2))[1] : 0; - $hasW = $hasMsw && ($msw & self::DATETIME_FLAG_MILLI); - $w = $hasW ? ord(fgetc($data)) : 0; - - $year = ($ymdh >> self::DATETIME_YEAR_SHIFT) & self::DATETIME_YEAR_MASK; - $month = ($ymdh >> self::DATETIME_MONTH_SHIFT) & self::DATETIME_MONTH_MASK; - $day = ($ymdh >> self::DATETIME_DAY_SHIFT) & self::DATETIME_DAY_MASK; - $hour = $ymdh & self::DATETIME_HOUR_MASK; + $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; if($ymdh & self::DATETIME_YEAR_SIGN) - $year = ~$year; - $dt = sprintf('%04d-%02d-%02dT%02d:', $year, $month, $day, $hour); + $years = ~$years; + $dt = sprintf('%04d-%02d-%02dT%02d:', $years, $months, $days, $hours); - if($hasMsw) { + if($ymdh & self::DATETIME_FLAG_TIME) { + $msw = unpack('n', fread($data, 2))[1]; $mins = ($msw >> self::DATETIME_MINS_SHIFT) & self::DATETIME_MINS_MASK; $secs = ($msw >> self::DATETIME_SECS_SHIFT) & self::DATETIME_SECS_MASK; - $dt .= sprintf('%02d:%02d', $mins, $secs); - if($hasW) { + $dt .= sprintf('%02d:%02d', $mins, $secs); + if($msw & self::DATETIME_FLAG_MILLI) { $millis = ($msw << self::DATETIME_MILLI_HI_SHIFT) & self::DATETIME_MILLI_HI_MASK; - $millis |= $w; + $millis |= ord(fgetc($data)); $dt .= sprintf('.%03d', $millis); } } else $dt .= '00:00'; - return new DateTimeImmutable($dt); + return new DateTimeImmutable($dt . 'UTC'); } + 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 + */ + private static function encodeTimeSpan(DateInterval $di, int $flags): string { - return ''; + $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; } private static function decodeTimeSpan($data, int $flags): DateInterval { - return new \DateInterval('P1Y'); + $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; + $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; } }