14
14
namespace Laudis \Neo4j \Formatter \Specialised ;
15
15
16
16
use Closure ;
17
+ use const DATE_ATOM ;
17
18
use DateTimeImmutable ;
18
19
use function is_array ;
19
20
use Laudis \Neo4j \Contracts \ConnectionInterface ;
25
26
use Laudis \Neo4j \Types \CypherList ;
26
27
use Laudis \Neo4j \Types \CypherMap ;
27
28
use Laudis \Neo4j \Types \Date ;
29
+ use Laudis \Neo4j \Types \DateTime ;
30
+ use Laudis \Neo4j \Types \LocalDateTime ;
28
31
use Laudis \Neo4j \Types \LocalTime ;
29
32
use Laudis \Neo4j \Types \Node ;
30
33
use Laudis \Neo4j \Types \Path ;
36
39
use function preg_match ;
37
40
use Psr \Http \Message \RequestInterface ;
38
41
use Psr \Http \Message \ResponseInterface ;
42
+ use RuntimeException ;
39
43
use stdClass ;
40
44
use function str_pad ;
41
45
use const STR_PAD_RIGHT ;
@@ -303,6 +307,10 @@ private function translateBinary(): Closure
303
307
throw new UnexpectedValueException ('Binary data has not been implemented ' );
304
308
}
305
309
310
+ private const TIME_REGEX = '(?<hours>\d{2}):(?<minutes>\d{2}):(?<seconds>\d{2})((\.)(?<nanoseconds>\d+))? ' ;
311
+ private const DATE_REGEX = '(?<date>[\-−]?\d+-\d{2}-\d{2}) ' ;
312
+ private const ZONE_REGEX = '(?<zone>.+) ' ;
313
+
306
314
/**
307
315
* @return OGMTypes
308
316
*
@@ -311,34 +319,56 @@ private function translateBinary(): Closure
311
319
*/
312
320
private function translateDateTime (string $ datetime )
313
321
{
314
- if (preg_match ('/^[\-−]?\d+-\d{2}-\d{2} $/u ' , $ datetime )) {
315
- $ date = DateTimeImmutable:: createFromFormat ( ' Y-m-d ' , $ datetime );
322
+ if (preg_match ('/^ ' . self :: DATE_REGEX . ' $/u ' , $ datetime, $ matches )) {
323
+ $ days = $ this -> daysFromMatches ( $ matches );
316
324
317
- return new Date (( int ) $ date -> diff ( new DateTimeImmutable ( ' @0 ' ))-> format ( ' %a ' ) );
325
+ return new Date ($ days );
318
326
}
319
327
320
- if (preg_match ('/^(\d{2}):(\d{2}):(\d{2})((\.)(\d+))?$/ ' , $ datetime , $ matches )) {
328
+ if (preg_match ('/^ ' . self :: TIME_REGEX . ' $/u ' , $ datetime , $ matches )) {
321
329
$ nanoseconds = $ this ->nanosecondsFromMatches ($ matches );
322
330
323
331
return new LocalTime ($ nanoseconds );
324
332
}
325
333
326
- if (preg_match ('/^(\d{2}):(\d{2}):(\d{2})((\.)(\d+))?(?<zone>.+)$/ ' , $ datetime , $ matches )) {
334
+ if (preg_match ('/^ ' . self :: TIME_REGEX . self :: ZONE_REGEX . ' $/u ' , $ datetime , $ matches )) {
327
335
$ nanoseconds = $ this ->nanosecondsFromMatches ($ matches );
328
336
329
337
$ offset = $ this ->offsetFromMatches ($ matches );
330
338
331
339
return new Time ($ nanoseconds , $ offset );
332
340
}
333
341
334
- throw new UnexpectedValueException ('Date/time values have not been implemented yet ' );
342
+ if (preg_match ('/^ ' .self ::DATE_REGEX .'T ' .self ::TIME_REGEX .'$/u ' , $ datetime , $ matches )) {
343
+ $ nanoseconds = $ this ->nanosecondsFromMatches ($ matches );
344
+ $ seconds = $ this ->secondsInDaysFromMatches ($ matches );
345
+
346
+ [$ seconds , $ nanoseconds ] = $ this ->addNanoSecondsToSeconds ($ nanoseconds , $ seconds );
347
+
348
+ return new LocalDateTime ($ seconds , $ nanoseconds );
349
+ }
350
+
351
+ if (preg_match ('/^ ' .self ::DATE_REGEX .'T ' .self ::TIME_REGEX .self ::ZONE_REGEX .'$/u ' , $ datetime , $ matches )) {
352
+ $ nanoseconds = $ this ->nanosecondsFromMatches ($ matches );
353
+ $ seconds = $ this ->secondsInDaysFromMatches ($ matches );
354
+
355
+ [$ seconds , $ nanoseconds ] = $ this ->addNanoSecondsToSeconds ($ nanoseconds , $ seconds );
356
+
357
+ $ offset = $ this ->offsetFromMatches ($ matches );
358
+
359
+ return new DateTime ($ seconds , $ nanoseconds , $ offset );
360
+ }
361
+
362
+ throw new UnexpectedValueException (sprintf ('Could not handle date/time "%s" ' , $ datetime ));
335
363
}
336
364
337
365
private function nanosecondsFromMatches (array $ matches ): int
338
366
{
339
- /** @var array{0: string, 1: string, 2: string, 3: string, 4?: array{0: string, 1: string}} $matches */
340
- $ seconds = ((int ) $ matches [1 ]) * 60 * 60 + ((int ) $ matches [2 ]) * 60 + ((int ) $ matches [3 ]);
341
- $ nanoseconds = $ matches [4 ][1 ] ?? '0 ' ;
367
+ /** @var array{0: string, hours: string, minutes: string, seconds: string, nanoseconds?: string} $matches */
368
+ ['hours ' => $ hours , 'minutes ' => $ minutes , 'seconds ' => $ seconds ] = $ matches ;
369
+ $ seconds = (((int ) $ hours ) * 60 * 60 ) + (((int ) $ minutes ) * 60 ) + ((int ) $ seconds );
370
+
371
+ $ nanoseconds = $ matches ['nanoseconds ' ] ?? '0 ' ;
342
372
$ nanoseconds = str_pad ($ nanoseconds , 9 , '0 ' , STR_PAD_RIGHT );
343
373
344
374
return $ seconds * 1000 * 1000 * 1000 + (int ) $ nanoseconds ;
@@ -351,9 +381,43 @@ private function offsetFromMatches(array $matches): int
351
381
352
382
if (preg_match ('/(\d{2}):(\d{2})/ ' , $ zone , $ matches )) {
353
383
/** @var array{0: string, 1: string, 2: string} $matches */
354
- return ((int ) $ matches [1 ]) * 60 + (int ) $ matches [2 ];
384
+ return ((int ) $ matches [1 ]) * 60 * 60 + (int ) $ matches [2 ] * 60 ;
355
385
}
356
386
357
387
return 0 ;
358
388
}
389
+
390
+ private function daysFromMatches (array $ matches ): int
391
+ {
392
+ /** @var array{date: string} $matches */
393
+ $ date = DateTimeImmutable::createFromFormat ('Y-m-d ' , $ matches ['date ' ]);
394
+ if ($ date === false ) {
395
+ throw new RuntimeException (sprintf ('Cannot create DateTime from "%s" in format "Y-m-d" ' , $ matches ['date ' ]));
396
+ }
397
+
398
+ /** @psalm-suppress ImpureMethodCall */
399
+ return (int ) $ date ->diff (new DateTimeImmutable ('@0 ' ))->format ('%a ' );
400
+ }
401
+
402
+ private function secondsInDaysFromMatches (array $ matches ): int
403
+ {
404
+ /** @var array{date: string} $matches */
405
+ $ date = DateTimeImmutable::createFromFormat (DATE_ATOM , $ matches ['date ' ].'T00:00:00+00:00 ' );
406
+ if ($ date === false ) {
407
+ throw new RuntimeException (sprintf ('Cannot create DateTime from "%s" in format "Y-m-d" ' , $ matches ['date ' ]));
408
+ }
409
+
410
+ return $ date ->getTimestamp ();
411
+ }
412
+
413
+ /**
414
+ * @return array{0: int, 1: int}
415
+ */
416
+ private function addNanoSecondsToSeconds (int $ nanoseconds , int $ seconds ): array
417
+ {
418
+ $ seconds += (int ) ($ nanoseconds / 1000 / 1000 / 1000 );
419
+ $ nanoseconds %= 1000000000 ;
420
+
421
+ return [$ seconds , $ nanoseconds ];
422
+ }
359
423
}
0 commit comments