1<?php
2
3namespace Sabre\VObject\Property\VCard;
4
5use
6    Sabre\VObject\DateTimeParser,
7    Sabre\VObject\Property\Text,
8    Sabre\VObject\Property,
9    DateTime;
10
11/**
12 * DateAndOrTime property
13 *
14 * This object encodes DATE-AND-OR-TIME values.
15 *
16 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
17 * @author Evert Pot (http://evertpot.com/)
18 * @license http://sabre.io/license/ Modified BSD License
19 */
20class DateAndOrTime extends Property {
21
22    /**
23     * Field separator
24     *
25     * @var null|string
26     */
27    public $delimiter = null;
28
29    /**
30     * Returns the type of value.
31     *
32     * This corresponds to the VALUE= parameter. Every property also has a
33     * 'default' valueType.
34     *
35     * @return string
36     */
37    public function getValueType() {
38
39        return "DATE-AND-OR-TIME";
40
41    }
42
43    /**
44     * Sets a multi-valued property.
45     *
46     * You may also specify DateTime objects here.
47     *
48     * @param array $parts
49     * @return void
50     */
51    public function setParts(array $parts) {
52
53        if (count($parts)>1) {
54            throw new \InvalidArgumentException('Only one value allowed');
55        }
56        if (isset($parts[0]) && $parts[0] instanceof \DateTime) {
57            $this->setDateTime($parts[0]);
58        } else {
59            parent::setParts($parts);
60        }
61
62    }
63
64    /**
65     * Updates the current value.
66     *
67     * This may be either a single, or multiple strings in an array.
68     *
69     * Instead of strings, you may also use DateTime here.
70     *
71     * @param string|array|\DateTime $value
72     * @return void
73     */
74    public function setValue($value) {
75
76        if ($value instanceof \DateTime) {
77            $this->setDateTime($value);
78        } else {
79            parent::setValue($value);
80        }
81
82    }
83
84    /**
85     * Sets the property as a DateTime object.
86     *
87     * @param \DateTime $dt
88     * @return void
89     */
90    public function setDateTime(\DateTime $dt) {
91
92        $values = array();
93
94        $tz = null;
95        $isUtc = false;
96
97        $tz = $dt->getTimeZone();
98        $isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z'));
99
100        if ($isUtc) {
101            $value = $dt->format('Ymd\\THis\\Z');
102        } else {
103            // Calculating the offset.
104            $value = $dt->format('Ymd\\THisO');
105        }
106
107        $this->value = $value;
108
109    }
110
111    /**
112     * Returns a date-time value.
113     *
114     * Note that if this property contained more than 1 date-time, only the
115     * first will be returned. To get an array with multiple values, call
116     * getDateTimes.
117     *
118     * If no time was specified, we will always use midnight (in the default
119     * timezone) as the time.
120     *
121     * If parts of the date were omitted, such as the year, we will grab the
122     * current values for those. So at the time of writing, if the year was
123     * omitted, we would have filled in 2014.
124     *
125     * @return \DateTime
126     */
127    public function getDateTime() {
128
129        $dts = array();
130        $now = new DateTime();
131
132        $tzFormat = $now->getTimezone()->getOffset($now)===0?'\\Z':'O';
133        $nowParts = DateTimeParser::parseVCardDateTime($now->format('Ymd\\This' . $tzFormat));
134
135        $value = $this->getValue();
136
137        $dateParts = DateTimeParser::parseVCardDateTime($this->getValue());
138
139        // This sets all the missing parts to the current date/time.
140        // So if the year was missing for a birthday, we're making it 'this
141        // year'.
142        foreach($dateParts as $k=>$v) {
143            if (is_null($v)) {
144                $dateParts[$k] = $nowParts[$k];
145            }
146        }
147        return new DateTime("$dateParts[year]-$dateParts[month]-$dateParts[date] $dateParts[hour]:$dateParts[minute]:$dateParts[second] $dateParts[timezone]");
148
149    }
150
151    /**
152     * Returns the value, in the format it should be encoded for json.
153     *
154     * This method must always return an array.
155     *
156     * @return array
157     */
158    public function getJsonValue() {
159
160        $parts = DateTimeParser::parseVCardDateTime($this->getValue());
161
162        $dateStr = '';
163
164        // Year
165        if (!is_null($parts['year'])) {
166            $dateStr.=$parts['year'];
167
168            if (!is_null($parts['month'])) {
169                // If a year and a month is set, we need to insert a separator
170                // dash.
171                $dateStr.='-';
172            }
173
174        } else {
175
176            if (!is_null($parts['month']) || !is_null($parts['date'])) {
177                // Inserting two dashes
178                $dateStr.='--';
179            }
180
181        }
182
183        // Month
184
185        if (!is_null($parts['month'])) {
186            $dateStr.=$parts['month'];
187
188            if (isset($parts['date'])) {
189                // If month and date are set, we need the separator dash.
190                $dateStr.='-';
191            }
192        } else {
193            if (isset($parts['date'])) {
194                // If the month is empty, and a date is set, we need a 'empty
195                // dash'
196                $dateStr.='-';
197            }
198        }
199
200        // Date
201        if (!is_null($parts['date'])) {
202            $dateStr.=$parts['date'];
203        }
204
205
206        // Early exit if we don't have a time string.
207        if (is_null($parts['hour']) && is_null($parts['minute']) && is_null($parts['second'])) {
208            return array($dateStr);
209        }
210
211        $dateStr.='T';
212
213        // Hour
214        if (!is_null($parts['hour'])) {
215            $dateStr.=$parts['hour'];
216
217            if (!is_null($parts['minute'])) {
218                $dateStr.=':';
219            }
220        } else {
221            // We know either minute or second _must_ be set, so we insert a
222            // dash for an empty value.
223            $dateStr.='-';
224        }
225
226        // Minute
227        if (!is_null($parts['minute'])) {
228            $dateStr.=$parts['minute'];
229
230            if (!is_null($parts['second'])) {
231                $dateStr.=':';
232            }
233        } else {
234            if (isset($parts['second'])) {
235                // Dash for empty minute
236                $dateStr.='-';
237            }
238        }
239
240        // Second
241        if (!is_null($parts['second'])) {
242            $dateStr.=$parts['second'];
243        }
244
245        // Timezone
246        if (!is_null($parts['timezone'])) {
247            $dateStr.=$parts['timezone'];
248        }
249
250        return array($dateStr);
251
252    }
253
254    /**
255     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
256     *
257     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
258     * not yet done, but parameters are not included.
259     *
260     * @param string $val
261     * @return void
262     */
263    public function setRawMimeDirValue($val) {
264
265        $this->setValue($val);
266
267    }
268
269    /**
270     * Returns a raw mime-dir representation of the value.
271     *
272     * @return string
273     */
274    public function getRawMimeDirValue() {
275
276        return implode($this->delimiter, $this->getParts());
277
278    }
279
280    /**
281     * Validates the node for correctness.
282     *
283     * The following options are supported:
284     *   Node::REPAIR - May attempt to automatically repair the problem.
285     *
286     * This method returns an array with detected problems.
287     * Every element has the following properties:
288     *
289     *  * level - problem level.
290     *  * message - A human-readable string describing the issue.
291     *  * node - A reference to the problematic node.
292     *
293     * The level means:
294     *   1 - The issue was repaired (only happens if REPAIR was turned on)
295     *   2 - An inconsequential issue
296     *   3 - A severe issue.
297     *
298     * @param int $options
299     * @return array
300     */
301    public function validate($options = 0) {
302
303        $messages = parent::validate($options);
304        $value = $this->getValue();
305        try {
306            DateTimeParser::parseVCardDateTime($value);
307        } catch (\InvalidArgumentException $e) {
308            $messages[] = array(
309                'level' => 3,
310                'message' => 'The supplied value (' . $value . ') is not a correct DATE-AND-OR-TIME property',
311                'node' => $this,
312            );
313        }
314        return $messages;
315
316    }
317}
318