Completed
Push — master ( 2472c5...a814e8 )
by mw
35:03
created

includes/dataitems/SMW_DI_Time.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
use SMW\DataItemException;
4
use SMW\Libs\Time\JulianDay;
5
use SMW\Libs\Time\CalendarModel;
6
7
/**
8
 * This class implements time data items.
9
 * Such data items represent a unique point in time, given in either Julian or
10
 * Gregorian notation (possibly proleptic), and a precision setting that states
11
 * which of the components year, month, day, time were specified expicitly.
12
 * Even when not specified, the data item always assumes default values for the
13
 * missing parts, so the item really captures one point in time, no intervals.
14
 * Times are always assumed to be in UTC.
15
 *
16
 * "Y0K issue": Neither the Gregorian nor the Julian calendar assume a year 0,
17
 * i.e. the year 1 BC(E) was followed by 1 AD/CE. See
18
 * http://en.wikipedia.org/wiki/Year_zero
19
 * This implementation adheres to this convention and disallows year 0. The
20
 * stored year numbers use positive numbers for CE and negative numbers for
21
 * BCE. This is not just relevant for the question of how many years have
22
 * (exactly) passed since a given date, but also for the location of leap
23
 * years.
24
 *
25
 * @since 1.6
26
 *
27
 * @author Markus Krötzsch
28
 * @ingroup SMWDataItems
29
 */
30
class SMWDITime extends SMWDataItem implements CalendarModel {
31
32
	const PREC_Y    = SMW_PREC_Y;
33
	const PREC_YM   = SMW_PREC_YM;
34
	const PREC_YMD  = SMW_PREC_YMD;
35
	const PREC_YMDT = SMW_PREC_YMDT;
36
37
	/**
38
	 * The year before which we do not accept anything but year numbers and
39
	 * largely discourage calendar models.
40
	 */
41
	const PREHISTORY = -10000;
42
43
	/**
44
	 * Maximal number of days in a given month.
45
	 * @var array
46
	 */
47
	protected static $m_daysofmonths = array( 1 => 31, 2 => 29, 3 => 31, 4 => 30, 5 => 31, 6 => 30, 7 => 31, 8 => 31, 9 => 30, 10 => 31, 11 => 30, 12 => 31 );
48
49
	/**
50
	 * Precision SMWDITime::PREC_Y, SMWDITime::PREC_YM,
51
	 * SMWDITime::PREC_YMD, or SMWDITime::PREC_YMDT.
52
	 * @var integer
53
	 */
54
	protected $m_precision;
55
	/**
56
	 * Calendar model: SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN.
57
	 * @var integer
58
	 */
59
	protected $m_model;
60
	/**
61
	 * Number of year, possibly negative.
62
	 * @var integer
63
	 */
64
	protected $m_year;
65
	/**
66
	 * Number of month.
67
	 * @var integer
68
	 */
69
	protected $m_month;
70
	/**
71
	 * Number of day.
72
	 * @var integer
73
	 */
74
	protected $m_day;
75
	/**
76
	 * Hours of the day.
77
	 * @var integer
78
	 */
79
	protected $m_hours;
80
	/**
81
	 * Minutes of the hour.
82
	 * @var integer
83
	 */
84
	protected $m_minutes;
85
	/**
86
	 * Seconds of the minute.
87
	 * @var integer
88
	 */
89
	protected $m_seconds;
90
91
	/**
92
	 * @var integer
93
	 */
94
	protected $timezone;
95
96
	/**
97
	 * @var integer|null
98
	 */
99
	protected $era = null;
100
101
	/**
102
	 * @var integer
103
	 */
104
	protected $julianDay = null;
105
106
	/**
107
	 * Create a time data item. All time components other than the year can
108
	 * be false to indicate that they are not specified. This will affect
109
	 * the internal precision setting. The missing values are initialised
110
	 * to minimal values (0 or 1) for internal calculations.
111
	 *
112
	 * @param $calendarmodel integer one of SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
113
	 * @param $year integer number of the year (possibly negative)
114
	 * @param $month mixed integer number or false
115
	 * @param $day mixed integer number or false
116
	 * @param $hour mixed integer number or false
117
	 * @param $minute mixed integer number or false
118
	 * @param $second mixed integer number or false
119
	 * @param integer|false $timezone
120
	 *
121
	 * @todo Implement more validation here.
122
	 */
123 221
	public function __construct( $calendarmodel, $year, $month = false, $day = false,
124
	                             $hour = false, $minute = false, $second = false, $timezone = false ) {
125
126 221
		if ( ( $calendarmodel != self::CM_GREGORIAN ) && ( $calendarmodel != self::CM_JULIAN ) ) {
127
			throw new DataItemException( "Unsupported calendar model constant \"$calendarmodel\"." );
128
		}
129
130 221
		if ( $year == 0 ) {
131
			throw new DataItemException( "There is no year 0 in Gregorian and Julian calendars." );
132
		}
133
134 221
		$this->m_model   = $calendarmodel;
135 221
		$this->m_year    = intval( $year );
136 221
		$this->m_month   = $month != false ? intval( $month ) : 1;
137 221
		$this->m_day     = $day != false ? intval( $day ) : 1;
138 221
		$this->m_hours   = $hour !== false ? intval( $hour ) : 0;
139 221
		$this->m_minutes = $minute !== false ? intval( $minute ) : 0;
140 221
		$this->m_seconds = $second !== false ? floatval( $second ) : 0;
141
142 221
		$this->timezone = $timezone !== false ? $timezone : 0;
143 221
		$year = strval( $year );
144 221
		$this->era      = $year{0} === '+' ? 1 : ( $year{0} === '-' ? -1 : 0 );
145
146 221
		if ( $this->isOutOfBoundsBySome() ) {
147
			throw new DataItemException( "Part of the date is out of bounds." );
148
		}
149
150 221
		if ( $this->isOutOfBoundsByDayNumberOfMonth() ) {
151
			throw new DataItemException( "Month {$this->m_month} in year {$this->m_year} did not have {$this->m_day} days in this calendar model." );
152
		}
153
154 221
		$this->setPrecisionLevelBy( $month, $day, $hour );
155 221
	}
156
157
	/**
158
	 * @since 1.6
159
	 *
160
	 * @return integer
161
	 */
162 213
	public function getDIType() {
163 213
		return SMWDataItem::TYPE_TIME;
164
	}
165
166
	/**
167
	 * @since 1.6
168
	 *
169
	 * @return integer
170
	 */
171 212
	public function getCalendarModel() {
172 212
		return $this->m_model;
173
	}
174
175
	/**
176
	 * @since 2.5
177
	 *
178
	 * @return integer
179
	 */
180 1
	public function getTimezone() {
181 1
		return $this->timezone;
182
	}
183
184
	/**
185
	 * @since 1.6
186
	 *
187
	 * @return integer
188
	 */
189 80
	public function getPrecision() {
190 80
		return $this->m_precision;
191
	}
192
193
	/**
194
	 * Indicates whether a user explicitly used an era marker even for a positive
195
	 * year.
196
	 *
197
	 * - [-1] indicates BC(E)
198
	 * - [0]/null indicates no era marker
199
	 * - [1] indicates AD/CE was used
200
	 *
201
	 * @since 2.4
202
	 *
203
	 * @return integer
204
	 */
205 27
	public function getEra() {
206 27
		return $this->era;
207
	}
208
209
	/**
210
	 * @since 1.6
211
	 *
212
	 * @return integer
213
	 */
214 212
	public function getYear() {
215 212
		return $this->m_year;
216
	}
217
218
	/**
219
	 * @since 1.6
220
	 *
221
	 * @return integer
222
	 */
223 212
	public function getMonth() {
224 212
		return $this->m_month;
225
	}
226
227
	/**
228
	 * @since 1.6
229
	 *
230
	 * @return integer
231
	 */
232 212
	public function getDay() {
233 212
		return $this->m_day;
234
	}
235
236
	/**
237
	 * @since 1.6
238
	 *
239
	 * @return integer
240
	 */
241 212
	public function getHour() {
242 212
		return $this->m_hours;
243
	}
244
245
	/**
246
	 * @since 1.6
247
	 *
248
	 * @return integer
249
	 */
250 212
	public function getMinute() {
251 212
		return $this->m_minutes;
252
	}
253
254
	/**
255
	 * @since 1.6
256
	 *
257
	 * @return integer
258
	 */
259 213
	public function getSecond() {
260 213
		return $this->m_seconds;
261
	}
262
263
	/**
264
	 * @since 2.4
265
	 *
266
	 * @return string
267
	 */
268 6
	public function getCalendarModelLiteral() {
269
270
		$literal = array(
271 6
			self::CM_GREGORIAN => '',
272 6
			self::CM_JULIAN    => 'JL'
273
		);
274
275 6
		return $literal[$this->m_model];
276
	}
277
278
	/**
279
	 * @since 2.4
280
	 *
281
	 * @param DateTime $dateTime
282
	 *
283
	 * @return SMWDITime|false
284
	 */
285 2
	public static function newFromDateTime( DateTime $dateTime ) {
286
287 2
		$calendarModel = self::CM_JULIAN;
288
289 2
		$year = $dateTime->format( 'Y' );
290 2
		$month = $dateTime->format( 'm' );
291 2
		$day = $dateTime->format( 'd' );
292
293 2
		if ( ( $year > 1582 ) ||
294 1
			( ( $year == 1582 ) && ( $month > 10 ) ) ||
295 2
			( ( $year == 1582 ) && ( $month == 10 ) && ( $day > 4 ) ) ) {
296 2
			$calendarModel = self::CM_GREGORIAN;
297
		}
298
299 2
		return self::doUnserialize( $calendarModel . '/' . $dateTime->format( 'Y/m/d/H/i/s.u' ) );
300
	}
301
302
	/**
303
	 * @since 2.4
304
	 *
305
	 * @return DateTime
306
	 */
307 14
	public function asDateTime() {
308
309 14
		$year = str_pad( $this->m_year, 4, '0', STR_PAD_LEFT );
310
311
		// Avoid "Failed to parse time string (-900-02-02 00:00:00) at
312
		// position 7 (-): Double timezone specification"
313 14
		if ( $this->m_year < 0 ) {
314 2
			$year = '-' . str_pad( $this->m_year * -1, 4, '0', STR_PAD_LEFT );
315
		}
316
317
		// Avoid "Failed to parse time string (1300-11-02 12:03:25.888499949) at
318
		// at position 11 (1): The timezone could not ..."
319 14
		$seconds = number_format( str_pad( $this->m_seconds, 2, '0', STR_PAD_LEFT ), 7, '.', '' );
320
321 14
		$time = $year . '-' .
322 14
			str_pad( $this->m_month, 2, '0', STR_PAD_LEFT )     . '-' .
323 14
			str_pad( $this->m_day, 2, '0', STR_PAD_LEFT )       . ' ' .
324 14
			str_pad( $this->m_hours, 2, '0', STR_PAD_LEFT )     . ':' .
325 14
			str_pad( $this->m_minutes, 2, '0', STR_PAD_LEFT )   . ':' .
326 14
			$seconds;
327
328 14
		return new DateTime( $time );
329
	}
330
331
	/**
332
	 * Creates and returns a new instance of SMWDITime from a MW timestamp.
333
	 *
334
	 * @since 1.8
335
	 *
336
	 * @param string $timestamp must be in format
337
	 *
338
	 * @return SMWDITime|false
339
	 */
340 204
	public static function newFromTimestamp( $timestamp ) {
341 204
		$timestamp = wfTimestamp( TS_MW, (string)$timestamp );
342
343 204
		if ( $timestamp === false ) {
344
			return false;
345
		}
346
347 204
		return new self(
348 204
			self::CM_GREGORIAN,
349 204
			substr( $timestamp, 0, 4 ),
350 204
			substr( $timestamp, 4, 2 ),
351 204
			substr( $timestamp, 6, 2 ),
352 204
			substr( $timestamp, 8, 2 ),
353 204
			substr( $timestamp, 10, 2 ),
354 204
			substr( $timestamp, 12, 2 )
355
		);
356
	}
357
358
	/**
359
	 * Returns a MW timestamp representation of the value.
360
	 *
361
	 * @since 1.6.2
362
	 *
363
	 * @param $outputtype
364
	 *
365
	 * @return mixed
366
	 */
367 202
	public function getMwTimestamp( $outputtype = TS_UNIX ) {
368 202
		return wfTimestamp(
369
			$outputtype,
370 202
			implode( '', array(
371 202
				str_pad( $this->m_year, 4, '0', STR_PAD_LEFT ),
372 202
				str_pad( $this->m_month, 2, '0', STR_PAD_LEFT ),
373 202
				str_pad( $this->m_day, 2, '0', STR_PAD_LEFT ),
374 202
				str_pad( $this->m_hours, 2, '0', STR_PAD_LEFT ),
375 202
				str_pad( $this->m_minutes, 2, '0', STR_PAD_LEFT ),
376 202
				str_pad( $this->m_seconds, 2, '0', STR_PAD_LEFT ),
377
			) )
378
		);
379
	}
380
381
	/**
382
	 * Get the data in the specified calendar model. This might require
383
	 * conversion.
384
	 * @note Conversion can be unreliable for very large absolute year
385
	 * numbers when the internal calculations hit floating point accuracy.
386
	 * Callers might want to avoid this (calendar models make little sense
387
	 * in such cases anyway).
388
	 * @param $calendarmodel integer one of SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
389
	 * @return SMWDITime
390
	 */
391 76
	public function getForCalendarModel( $calendarmodel ) {
392 76
		if ( $calendarmodel == $this->m_model ) {
393 76
			return $this;
394
		} else {
395 4
			return self::newFromJD( $this->getJD(), $calendarmodel, $this->m_precision );
396
		}
397
	}
398
399
	/**
400
	 * Return a number that helps comparing time data items. For
401
	 * dates in the Julian Day era (roughly from 4713 BCE onwards), we use
402
	 * the Julian Day number. For earlier dates, the (negative) year number
403
	 * with a fraction for the date is used (times are ignored). This
404
	 * avoids calculation errors that would occur for very ancient dates
405
	 * if the JD number was used there.
406
	 * @return double sortkey
407
	 */
408 209
	public function getSortKey() {
409 209
		$jd = ( $this->m_year >= -4713 ) ? $jd = $this->getJD() : -1;
410 209
		if ( $jd > 0 ) {
411 209
			return $jd;
412
		} else {
413 3
			return $this->m_year - 1 + ( $this->m_month - 1 ) / 12 + ( $this->m_day - 1 ) / 12 / 31;
414
		}
415
	}
416
417
	/**
418
	 * @since 1.6
419
	 *
420
	 * @return double
421
	 */
422 211
	public function getJD() {
423
424 211
		if ( $this->julianDay !== null ) {
425 42
			return $this->julianDay;
426
		}
427
428 211
		$this->julianDay = JulianDay::getJD(
429 211
			$this->getCalendarModel(),
430 211
			$this->getYear(),
431 211
			$this->getMonth(),
432 211
			$this->getDay(),
433 211
			$this->getHour(),
434 211
			$this->getMinute(),
435 211
			$this->getSecond()
436
		);
437
438 211
		return $this->julianDay;
439
	}
440
441
	/**
442
	 * @since 1.6
443
	 *
444
	 * @return string
445
	 */
446 212
	public function getSerialization() {
447 212
		$result = strval( $this->m_model ) . '/' . ( $this->era > 0 ? '+' : '' ) . strval( $this->m_year );
448
449 212
		if ( $this->m_precision >= self::PREC_YM ) {
450 207
			$result .= '/' . strval( $this->m_month );
451
		}
452
453 212
		if ( $this->m_precision >= self::PREC_YMD ) {
454 207
			$result .= '/' . strval( $this->m_day );
455
		}
456
457 212
		if ( $this->m_precision >= self::PREC_YMDT ) {
458 206
			$result .= '/' . strval( $this->m_hours ) . '/' . strval( $this->m_minutes ) . '/' . strval( $this->m_seconds ) . '/' . strval( $this->timezone );
459
		}
460
461 212
		return $result;
462
	}
463
464
	/**
465
	 * Create a data item from the provided serialization string.
466
	 *
467
	 * @return SMWDITime
468
	 */
469 64
	public static function doUnserialize( $serialization ) {
470 64
		$parts = explode( '/', $serialization, 8 );
471 64
		$values = array();
472
473 64
		if ( count( $parts ) <= 1 ) {
474 1
			throw new DataItemException( "Unserialization failed: the string \"$serialization\" is no valid URI." );
475
		}
476
477 63
		for ( $i = 0; $i < 8; $i += 1 ) {
478
479 63
			$values[$i] = false;
480
481
			// Can contain something like '1/1970/1/12/11/43/0/Asia/Tokyo'
482 63
			if ( $i == 7 && isset( $parts[$i] ) ) {
483 54
				$values[$i] = strval( $parts[$i] );
484 54
				continue;
485
			}
486
487 63
			if ( $i < count( $parts ) ) {
488
489 63
				if ( $parts[$i] !== '' && !is_numeric( $parts[$i] ) ) {
490 1
					throw new DataItemException( "Unserialization failed: the string \"$serialization\" is no valid datetime specification." );
491
				}
492
493
				// 6 == seconds, we want to keep microseconds
494 63
				$values[$i] = $i == 6 ? floatval( $parts[$i] ) : intval( $parts[$i] );
495
496
				// Find out whether the input contained an explicit AD/CE era marker
497 63
				if ( $i == 1 ) {
498 63
					$values[$i] = ( $parts[1]{0} === '+' ? '+' : '' ) . $values[$i];
499
				}
500
			}
501
		}
502
503 62
		return new self( $values[0], $values[1], $values[2], $values[3], $values[4], $values[5], $values[6], $values[7] );
504
	}
505
506
	/**
507
	 * Create a new time dataItem from a specified Julian Day number,
508
	 * calendar model, presicion.
509
	 *
510
	 * @param double $jdValue
511
	 * @param integer|null $calendarmodel
512
	 * @param integer|null $precision
513
	 *
514
	 * @return DITime object
515
	 */
516 11
	public static function newFromJD( $jdValue, $calendarModel = null, $precision = null, $timezone = false ) {
517
518 11
		$hour = $minute = $second = false;
519 11
		$year = $month = $day = false;
520
521 11
		if ( $precision === null ) {
522 3
			$precision = strpos( strval( $jdValue ), '.5' ) !== false ? self::PREC_YMD : self::PREC_YMDT;
523
		}
524
525 11
		list( $calendarModel, $year, $month, $day ) = JulianDay::JD2Date( $jdValue, $calendarModel );
526
527 11
		if ( $precision <= self::PREC_YM ) {
528
			$day = false;
529
			if ( $precision === self::PREC_Y ) {
530
				$month = false;
531
			}
532
		}
533
534 11
		if ( $precision === self::PREC_YMDT ) {
535 9
			list( $hour, $minute, $second ) = JulianDay::JD2Time( $jdValue );
536
		}
537
538 11
		return new self( $calendarModel, $year, $month, $day, $hour, $minute, $second, $timezone );
539
	}
540
541
	/**
542
	 * Find out whether the given year number is a leap year.
543
	 * This calculation assumes that neither calendar has a year 0.
544
	 * @param $year integer year number
545
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
546
	 * @return boolean
547
	 */
548 6
	static public function isLeapYear( $year, $calendarmodel ) {
549 6
		$astroyear = ( $year < 1 ) ? ( $year + 1 ) : $year;
550 6
		if ( $calendarmodel == self::CM_JULIAN ) {
551 4
			return ( $astroyear % 4 ) == 0;
552
		} else {
553 5
			return ( ( $astroyear % 400 ) == 0 ) ||
554 5
			       ( ( ( $astroyear % 4 ) == 0 ) && ( ( $astroyear % 100 ) != 0 ) );
555
		}
556
	}
557
558
	/**
559
	 * Find out how many days the given month had in the given year
560
	 * based on the specified calendar model.
561
	 * This calculation assumes that neither calendar has a year 0.
562
	 * @param $month integer month number
563
	 * @param $year integer year number
564
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
565
	 * @return boolean
566
	 */
567 221
	static public function getDayNumberForMonth( $month, $year, $calendarmodel ) {
568 221
		if ( $month !== 2 ) {
569 220
			return self::$m_daysofmonths[$month];
570 6
		} elseif ( self::isLeapYear( $year, $calendarmodel ) ) {
571 3
			return 29;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 29; (integer) is incompatible with the return type documented by SMWDITime::getDayNumberForMonth of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
572
		} else {
573 4
			return 28;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 28; (integer) is incompatible with the return type documented by SMWDITime::getDayNumberForMonth of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
574
		}
575
	}
576
577
	public function equals( SMWDataItem $di ) {
578
		if ( $di->getDIType() !== SMWDataItem::TYPE_TIME ) {
579
			return false;
580
		}
581
582
		return $di->getSortKey() === $this->getSortKey();
583
	}
584
585 221
	private function isOutOfBoundsBySome() {
586 221
		return ( $this->m_hours < 0 ) || ( $this->m_hours > 23 ) ||
587 221
		( $this->m_minutes < 0 ) || ( $this->m_minutes > 59 ) ||
588 221
		( $this->m_seconds < 0 ) || ( $this->m_seconds > 59 ) ||
589 221
		( $this->m_month < 1 ) || ( $this->m_month > 12 );
590
	}
591
592 221
	private function isOutOfBoundsByDayNumberOfMonth() {
593 221
		return $this->m_day > self::getDayNumberForMonth( $this->m_month, $this->m_year, $this->m_model );
594
	}
595
596 221
	private function setPrecisionLevelBy( $month, $day, $hour ) {
597 221
		if ( $month === false ) {
598 22
			$this->m_precision = self::PREC_Y;
599 215
		} elseif ( $day === false ) {
600 4
			$this->m_precision = self::PREC_YM;
601 215
		} elseif ( $hour === false ) {
602 30
			$this->m_precision = self::PREC_YMD;
603
		} else {
604 212
			$this->m_precision = self::PREC_YMDT;
605
		}
606 221
	}
607
608
}
609