1: <?php
2: /*
3: * Copyright (c) 2016 Geraldo B. Landre
4: *
5: * See the file LICENSE for copying permission.
6: */
7: namespace pgn\tags;
8:
9: use utils\Parser;
10:
11: /**
12: * Description of Date:
13: * The Date tag value gives the starting date for the game. (Note: this is not
14: * necessarily the same as the starting date for the event.) The date is given
15: * with respect to the local time of the site given in the Event tag. The Date
16: * tag value field always uses a standard ten character format: "YYYY.MM.DD". The
17: * first four characters are digits that give the year, the next character is a
18: * period, the next two characters are digits that give the month, the next
19: * character is a period, and the final two characters are digits that give the
20: * day of the month. If the any of the digit fields are not known, then question
21: * marks are used in place of the digits.
22: * @see pgn_standard.txt
23: * @author Geraldo
24: */
25: class Date extends Tag {
26: static private $ERROR_MESSAGE = "Date should be formatted [Y.m.d] Example: 1992.08.31";
27:
28: /**
29: *
30: * @assert ("????.??.??") === true
31: * Convention: year zero is valid:
32: * @assert ("0000.??.??") === true
33: * @assert ("????.??.4?") === false
34: * @assert ("9999.?9.31") === false
35: * @assert ("9999.?9.30") === true
36: * @assert ("????.2?.??") === false
37: * @assert ("????.1?.??") === true
38: * @assert ("????.0?.??") === true
39: * @assert ("????.03.??") === true
40: * @assert ("????.02.??") === true
41: * @assert ("0001.02.?1") === true
42: * @assert ("????.02.3?") === false
43: * @assert ("????.02.1?") === true
44: * @assert ("????.02.2?") === true
45: * @assert ("????.02.29") === true
46: * @assert ("1992.08.31") === true
47: * @assert ("2015.11.2?") === true
48: * @assert ("2015.11.??") === true
49: * @assert ("2015.1?.??") === true
50: * @assert ("2015.??.??") === true
51: * @assert ("201?.??.??") === true
52: * @assert ("20??.??.??") === true
53: * @assert ("2???.??.??") === true
54: * @assert (new \DateTime) === true
55: * @assert ("1992.0?.??") === true
56: * @assert ("1992.02.3?") === false
57: * @assert ("1992.4?.??") === false
58: * @assert ("1992.1?.??") === true
59: * @assert ("1992.31.08") === false
60: * @assert ("08.31.1992") === false
61: * @assert ("08/31/1992") === false
62: * @assert ("1992/08/31") === false
63: * @assert (null) === false
64: * @assert (new \stdClass) === false
65: * @assert (array (1988,11,02)) === false
66: * @assert ("aabb.vv.aa") === false
67: * Testes relacionados a anos bissextos
68: * @assert ("??11.02.29") === false
69: * @assert ("??12.02.29") === true
70: * @assert ("??14.02.29") === false
71: * @assert ("??16.02.29") === true
72: * @assert ("??18.02.29") === false
73: * @assert ("??20.02.29") === true
74: * @assert ("??22.02.29") === false
75: * @assert ("??24.02.29") === true
76: * @assert ("??26.02.29") === false
77: * @assert ("??28.02.29") === true
78: * @assert ("2015.02.29") === false
79: *
80: * @todo to assert errorMsg contents after each test
81: *
82: * @param \DateTime $date
83: * @return boolean returns true if data is a valid Date
84: */
85: public function validate($date) {
86:
87: if ($date instanceof \DateTime) {
88: return true;
89: }
90:
91: if (!$this->checkFormat($date)) {
92: return false;
93: }
94:
95: list($yearPartial, $monthPartial, $dayPartial) = explode(".", strval($date));
96:
97: // 1 is better for default values in day and month
98: $day = str_replace('?', '1', $dayPartial);
99: $month = $this->monthReplaceDefaults($monthPartial);
100: $year = $this->yearReplaceDefaults($yearPartial);
101:
102: // special case
103: if ($month == 2 && $day == 29) {
104: return $this->isBissextile($year);
105: }
106:
107: $d = \DateTime::createFromFormat('Y.m.d', "$year.$month.$day");
108: $errors = \DateTime::getLastErrors();
109:
110: $isValid = ($d !== false && empty($errors['warnings']));
111:
112: $this->errorMsg = ($isValid) ? self::$ERROR_MESSAGE : '';
113: return $isValid;
114: }
115:
116: /**
117: * @assert () == "????.??.??"
118: *
119: * @return string Default Date Value
120: */
121: public function getDefaultValue() {
122: return '????.??.??';
123: }
124:
125: /**
126: * @assert () == "Date"
127: *
128: * @return string the name of the tag
129: */
130: public function getName() {
131: $parsed = Parser::parseClassName(get_class());
132: return $parsed['className'];
133: }
134:
135: /**
136: * @assert (null) === '????.??.??'
137: * @assert ("1988.02.11") === '1988.02.11'
138: * @assert (\DateTime::createFromFormat('d/m/Y', '02/11/1988')) === '1988.11.02'
139: *
140: * @param \DateTime|string $data
141: * @return string
142: */
143: protected function formatted($data) {
144: if (isset($data)) {
145: if ($data instanceof \DateTime) {
146: return $data->format('Y.m.d');
147: } else {
148: return strval($data);
149: }
150: }
151: return $this->getDefaultValue();
152: }
153:
154: /**
155: * WARNING: this method takes on $year having only numbers or question marks
156: *
157: * @assert ("????") === '1124'
158: * @assert ("19??") === '1924'
159: * @assert ("1???") === '1124'
160: * @assert ("2???") === '2124'
161: * @assert ("?0??") === '1024'
162: * @assert ("20?6") === '2016'
163: * @assert ("20?4") === '2024'
164: * @assert ("198?") === '1984'
165: * @assert ("??11") === '1111'
166: * @assert ("??12") === '1112'
167: * @assert ("??14") === '1114'
168: * @assert ("??16") === '1116'
169: * @assert ("??18") === '1118'
170: * @assert ("??20") === '1120'
171: * @assert ("??22") === '1122'
172: * @assert ("??24") === '1124'
173: * @assert ("??26") === '1126'
174: * @assert ("??28") === '1128'
175: * @assert ("0000") === '0000'
176: *
177: * @param string $year
178: * @return string the year with question marks replaced by default values
179: */
180: protected function yearReplaceDefaults($year) {
181: // first two digits are useless on verification
182: // that's why they are replaced by 1
183: $century = str_replace('?', 1, substr($year, 0, 2));
184:
185: // the third digit can be even or odd
186: // if it's even (or zero) and with the 4th digit being (0, 4 or 8) than
187: // it may be bissextile
188: // if it's odd and with the 4th digit being (2 or 6) than
189: // it also may be bissextile
190: // default value is 1, because odd is easier
191: $decade = $year[2];
192: if ($year[2] == '?') {
193: if ($year[3] != '?') {
194: $decade = ($year[3] == 2 || $year[3] == 6) ? 1 : 2;
195: } else {
196: $decade = 2;
197: }
198: }
199:
200: // assumes that third digit is even...
201: $defaultUnity = 4;
202: // if third digit is odd
203: if ($decade % 2 != 0) {
204: $defaultUnity = 2;
205: }
206:
207: $unity = ($year[3] != '?') ? $year[3] : $defaultUnity;
208:
209: return $century . $decade . $unity;
210: }
211:
212: /**
213: * @assert ('??') === '11'
214: * @assert ('?0') === '10'
215: * @assert ('?2') === '12'
216: * @assert ('?3') === '03'
217: * @assert ('?9') === '09'
218: *
219: * @param string $monthPartial with two characters
220: * @return string
221: */
222: protected function monthReplaceDefaults($monthPartial) {
223: $tens = $monthPartial[0];
224: $unity = $monthPartial[1] != '?' ? $monthPartial[1] : '1';
225:
226: if ($tens == '?') {
227: if ($unity <= 2) {
228: $tens = '1';
229: } else {
230: $tens = '0';
231: }
232: }
233:
234: $newMonth = $tens . $unity;
235: return $newMonth;
236: }
237:
238: /**
239: * WARNING: this method takes on $year having only numbers or question marks
240: *
241: * @assert ("2015") === false
242: * @assert ("2014") === false
243: * @assert ("0004") === true
244: * @assert ("0008") === true
245: * @assert ("1000") === false
246: * @assert ("2000") === true
247: * @assert ("2016") === true
248: * @assert ("2012") === true
249: * @param string $year
250: * @return boolean true if a given year is bissextile
251: */
252: protected function isBissextile($year) {
253:
254: // return ( ( ano % 4 == 0 && ano % 100 != 0 ) || ano % 400 == 0 )
255: if ($year % 400 == 0) {
256: return true;
257: }
258:
259: if ($year % 4 == 0) {
260: if ($year % 100 != 0) {
261: return true;
262: }
263: }
264:
265: return false;
266: }
267:
268: private function checkFormat($date) {
269: if (!is_string($date) || !preg_match_all("/^" . self::validPattern() . "$/", $date)) {
270: $this->errorMsg = self::$ERROR_MESSAGE;
271: return false;
272: }
273: return true;
274: }
275:
276: /**
277: *
278: * @return string Valid Regular Expression Pattern for PGN Dates
279: */
280: static public function validPattern() {
281: return "(\d|\?){4}\.(\d|\?){2}\.(\d|\?){2}";
282: }
283: }
284: