TimeValueCalculator   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 113
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 92.86%

Importance

Changes 0
Metric Value
wmc 19
c 0
b 0
f 0
lcom 1
cbo 1
dl 0
loc 113
ccs 39
cts 42
cp 0.9286
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getTimestamp() 0 3 1
A getSecondsSinceUnixEpoch() 0 29 5
A isLeapYear() 0 7 4
A getNumberOfLeapYears() 0 4 2
B getSecondsForPrecision() 0 23 7
1
<?php
2
3
namespace DataValues;
4
5
use InvalidArgumentException;
6
7
/**
8
 * Logical and mathematical helper functions for calculations with and conversions from TimeValue
9
 * objects.
10
 *
11
 * @since 0.6
12
 *
13
 * @license GPL-2.0+
14
 * @author Thiemo Kreuz
15
 */
16
class TimeValueCalculator {
17
18
	/**
19
	 * Average length of a year in the Gregorian calendar in seconds, calculated via
20
	 * 365 + 1 / 4 - 1 / 100 + 1 / 400 = 365.2425 days.
21
	 */
22
	const SECONDS_PER_GREGORIAN_YEAR = 31556952;
23
24
	/**
25
	 * This returns a Unix timestamp from a TimeValue similar to PHP's mk_time() (or strtotime()),
26
	 * but with no range limitations. Data type is float because PHP's 32 bit integer would
27
	 * clip in the year 2038.
28
	 *
29
	 * @param TimeValue $timeValue
30
	 *
31
	 * @return float seconds since 1970-01-01T00:00:00Z
32
	 */
33 34
	public function getTimestamp( TimeValue $timeValue ) {
34 34
		return $this->getSecondsSinceUnixEpoch( $timeValue->getTime(), $timeValue->getTimezone() );
35
	}
36
37
	/**
38
	 * @param string $time an ISO 8601 date and time
39
	 * @param int $timezone offset from UTC in minutes
40
	 *
41
	 * @throws InvalidArgumentException
42
	 * @return float seconds since 1970-01-01T00:00:00Z
43
	 */
44 34
	private function getSecondsSinceUnixEpoch( $time, $timezone = 0 ) {
45
		// Validation is done in TimeValue. As long if we found enough numbers we are fine.
46 34
		if ( !preg_match( '/([-+]?\d+)\D+(\d+)\D+(\d+)\D+(\d+)\D+(\d+)\D+(\d+)/', $time, $matches )
47
		) {
48
			throw new InvalidArgumentException( "Failed to parse time value $time." );
49
		}
50 34
		list( , $fullYear, $month, $day, $hour, $minute, $second ) = $matches;
51
52
		// We use mktime only for the month, day and time calculation. Set the year to the smallest
53
		// possible in the 1970-2038 range to be safe, even if it's 1901-2038 since PHP 5.1.0.
54 34
		$year = $this->isLeapYear( $fullYear ) ? 1972 : 1970;
55
56 34
		$defaultTimezone = date_default_timezone_get();
57 34
		date_default_timezone_set( 'UTC' );
58
		// With day/month set to 0 mktime would calculate the last day of the previous month/year.
59
		// In the context of this calculation we must assume 0 means "start of the month/year".
60 34
		$timestamp = mktime( $hour, $minute, $second, max( 1, $month ), max( 1, $day ), $year );
61 34
		date_default_timezone_set( $defaultTimezone );
62
63 34
		if ( $timestamp === false ) {
64
			throw new InvalidArgumentException( "Failed to get epoche from time value $time." );
65
		}
66
67 34
		$missingYears = ( $fullYear < 0 ? $fullYear + 1 : $fullYear ) - $year;
68 34
		$missingLeapDays = $this->getNumberOfLeapYears( $fullYear )
69 34
			- $this->getNumberOfLeapYears( $year );
70
71 34
		return $timestamp + ( $missingYears * 365 + $missingLeapDays ) * 86400 - $timezone * 60;
72
	}
73
74
	/**
75
	 * @param float $year
76
	 *
77
	 * @return bool if the year is a leap year in the Gregorian calendar
78
	 */
79 68
	public function isLeapYear( $year ) {
80 68
		$year = $year < 0 ? ceil( $year ) + 1 : floor( $year );
81 68
		$isMultipleOf4   = fmod( $year,   4 ) === 0.0;
82 68
		$isMultipleOf100 = fmod( $year, 100 ) === 0.0;
83 68
		$isMultipleOf400 = fmod( $year, 400 ) === 0.0;
84 68
		return $isMultipleOf4 && !$isMultipleOf100 || $isMultipleOf400;
85
	}
86
87
	/**
88
	 * @param float $year
89
	 *
90
	 * @return float The number of leap years since year 1. To be more precise: The number of
91
	 * leap days in the range between 31 December of year 1 and 31 December of the given year.
92
	 */
93 68
	public function getNumberOfLeapYears( $year ) {
94 68
		$year = $year < 0 ? ceil( $year ) + 1 : floor( $year );
95 68
		return floor( $year / 4 ) - floor( $year / 100 ) + floor( $year / 400 );
96
	}
97
98
	/**
99
	 * @param int $precision One of the TimeValue::PRECISION_... constants
100
	 *
101
	 * @throws InvalidArgumentException
102
	 * @return float number of seconds in one unit of the given precision
103
	 */
104 8
	public function getSecondsForPrecision( $precision ) {
105 8
		if ( $precision <= TimeValue::PRECISION_YEAR ) {
106 3
			return self::SECONDS_PER_GREGORIAN_YEAR * pow(
107 3
				10,
108 3
				TimeValue::PRECISION_YEAR - $precision
109
			);
110
		}
111
112
		switch ( $precision ) {
113 5
			case TimeValue::PRECISION_SECOND:
114 1
				return 1;
115 4
			case TimeValue::PRECISION_MINUTE:
116 1
				return 60;
117 3
			case TimeValue::PRECISION_HOUR:
118 1
				return 3600;
119 2
			case TimeValue::PRECISION_DAY:
120 1
				return 86400;
121 1
			case TimeValue::PRECISION_MONTH:
122 1
				return self::SECONDS_PER_GREGORIAN_YEAR / 12;
123
		}
124
125
		throw new InvalidArgumentException( "Unable to get seconds for precision $precision." );
126
	}
127
128
}
129