--- /dev/null
+/data/bdrem.config.php
--- /dev/null
+#!/usr/bin/env php
+<?php
+namespace bdrem;
+
+if (file_exists(__DIR__ . '/../src/bdrem/Autoloader.php')) {
+ require_once __DIR__ . '/../src/bdrem/Autoloader.php';
+ Autoloader::register();
+}
+$cli = new Cli();
+$cli->run();
+?>
--- /dev/null
+<?php
+$source = array('Bdf', '/path/to/birthday.bdf');
+$daysBefore = 3;
+$daysAfter = 14;
+?>
--- /dev/null
+<?php
+namespace bdrem;
+
+class Autoloader
+{
+ public function load($class)
+ {
+ $file = strtr($class, '_\\', '//') . '.php';
+ if (stream_resolve_include_path($file)) {
+ require $file;
+ }
+ }
+
+ public static function register()
+ {
+ set_include_path(
+ get_include_path() . PATH_SEPARATOR . __DIR__ . '/../'
+ );
+ spl_autoload_register(array(new self(), 'load'));
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?php
+namespace bdrem;
+
+class Cli
+{
+ public function run()
+ {
+ $cfg = new Config();
+ $cfg->load();
+ $source = $cfg->loadSource();
+
+ $arEvents = $source->getEvents(
+ date('Y-m-d'), $cfg->daysBefore, $cfg->daysAfter
+ );
+ usort($arEvents, '\\bdrem\\Event::compare');
+
+ $r = new Renderer_Console();
+ echo $r->render($arEvents);
+ }
+}
+?>
--- /dev/null
+<?php
+namespace bdrem;
+
+class Config
+{
+ public $source;
+ public $daysBefore;
+ public $daysAfter;
+
+ public function load()
+ {
+ $f = __DIR__ . '/../../data/bdrem.config.php';
+ if (file_exists($f)) {
+ return $this->loadFile($f);
+ }
+
+ throw new \Exception('No config file found');
+ }
+
+ protected function loadFile($filename)
+ {
+ include $filename;
+ $this->source = $source;
+ $this->daysBefore = $daysBefore;
+ $this->daysAfter = $daysAfter;
+ }
+
+ public function loadSource()
+ {
+ if ($this->source === null) {
+ throw new \Exception('No source defined');
+ }
+
+ $settings = $this->source;
+ $class = '\\bdrem\\Source_' . array_shift($settings);
+
+ return new $class($settings[0]);
+ //$rm = new \ReflectionMethod($class, '__construct');
+ //return $rm->invokeArgs(null, $settings);
+ //return call_user_func_array($class . '::__construct', $settings);
+ }
+}
+?>
--- /dev/null
+<?php
+namespace bdrem;
+
+class Event
+{
+ /**
+ * Title of the event or name of the person that has the event
+ */
+ public $title;
+
+ /**
+ * Type of the event, e.g. "birthday"
+ */
+ public $type;
+
+ /**
+ * Date of the event.
+ * ???? as year is allowed
+ *
+ * @var string YYYY-MM-DD
+ */
+ public $date;
+
+ /**
+ * Which repetition this is
+ *
+ * @var integer
+ */
+ public $age;
+
+ /**
+ * Number of days until the event (positive) or since the event (negative)
+ *
+ * @var integer
+ */
+ public $days;
+
+
+
+ public function __construct($title = null, $type = null, $date = null)
+ {
+ $this->title = $title;
+ $this->type = $type;
+ $this->date = $date;
+ }
+
+ /**
+ * Checks if the event's date is within the given date.
+ * Also calculates the age and days since the event.
+ *
+ * @return boolean True if the event's date is within the given range
+ */
+ public function isWithin($strDate, $nDaysBefore, $nDaysAfter)
+ {
+ list($rYear, $rMonth, $rDay) = explode('-', $strDate);
+ list($eYear, $eMonth, $eDay) = explode('-', $this->date);
+
+ if ($rMonth == $eMonth && $rDay == $eDay) {
+ $this->days = 0;
+ if ($eYear == '????') {
+ $this->age = null;
+ } else {
+ $this->age = $rYear - $eYear;
+ }
+ return true;
+ }
+
+ $yearOffset = 0;
+ if ($eMonth < 3 && $rMonth > 10) {
+ $yearOffset = 1;
+ } else if ($eMonth > 10 && $rMonth < 3) {
+ $yearOffset = -1;
+ }
+
+ $rD = new \DateTime($strDate);
+ $eD = new \DateTime(($rYear + $yearOffset) . '-' . $eMonth . '-' . $eDay);
+
+ $nDiff = (int) $rD->diff($eD)->format('%r%a');
+
+ $this->days = $nDiff;
+ if ($eYear == '????') {
+ $this->age = null;
+ } else {
+ $this->age = $rYear - $eYear + $yearOffset;
+ }
+
+ if ($nDiff > 0) {
+ return $nDiff <= $nDaysAfter;
+ } else {
+ return -$nDiff <= $nDaysBefore;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return integer x < 0: e1 is less than e2
+ * x > 0: e1 is larger than e2
+ */
+ public static function compare(Event $e1, Event $e2)
+ {
+ list($e1Year, $e1Month, $e1Day) = explode('-', $e1->date);
+ list($e2Year, $e2Month, $e2Day) = explode('-', $e2->date);
+
+ if ($e1Month < 3 && $e2Month > 10) {
+ return 1;
+ } else if ($e1Month > 10 && $e2Month < 3) {
+ return -1;
+ } else if ($e1Month != $e2Month) {
+ return $e1Month - $e2Month;
+ } else if ($e1Day != $e2Day) {
+ return $e1Day - $e2Day;
+ }
+ return strcmp($e1->title, $e2->title);
+ }
+}
+?>
--- /dev/null
+<?php
+namespace bdrem;
+
+class Renderer_Console
+{
+ public function render($arEvents)
+ {
+ $s = "Days Age Name Event Date\n";
+ $s .= "---- --- ---------------------------------------- -------------------- ----------\n";
+ foreach ($arEvents as $event) {
+ $s .= sprintf(
+ "%3d %4s %s %s %s\n",
+ $event->days,
+ $event->age,
+ $this->str_pad($event->title, 40),
+ $this->str_pad($event->type, 20),
+ $event->date
+ );
+ }
+ return $s;
+ }
+
+ public function str_pad(
+ $input, $pad_length, $pad_string = ' ', $pad_type = STR_PAD_RIGHT
+ ) {
+ $l = mb_strlen($input, 'utf-8');
+ if ($l >= $pad_length) {
+ return $input;
+ }
+
+ $p = str_repeat($pad_string, $pad_length - $l);
+ if ($pad_type == STR_PAD_RIGHT) {
+ return $input . $p;
+ } else {
+ return $p . $input;
+ }
+ }
+}
+?>
--- /dev/null
+<?php
+namespace bdrem;
+
+/**
+ * Reads birthday reminder 2's birthday files (.bdf).
+ */
+class Source_Bdf
+{
+ protected $filename;
+
+ public function __construct($filename)
+ {
+ $this->filename = $filename;
+ if (!file_exists($this->filename)) {
+ throw new \Exception(
+ 'Birthday file does not exist: ' . $this->filename
+ );
+ }
+ }
+
+ /**
+ * @param string $strDate Date the events shall be found for, YYYY-MM-DD
+ */
+ public function getEvents($strDate, $nDaysBefore, $nDaysAfter)
+ {
+ $x = simplexml_load_file($this->filename);
+
+ $arEvents = array();
+ foreach ($x->content->person as $xPerson) {
+ $date = implode(
+ '-',
+ array_reverse(
+ explode('.', (string) $xPerson->date)
+ )
+ );
+ $event = new Event(
+ (string) $xPerson->name,
+ (string) $xPerson->event,
+ $date
+ );
+ if ($event->isWithin($strDate, $nDaysBefore, $nDaysAfter)) {
+ $arEvents[] = $event;
+ }
+ }
+ return $arEvents;
+ }
+}
+?>
--- /dev/null
+<?php
+namespace bdrem;
+
+class EventTest extends \PHPUnit_Framework_TestCase
+{
+ public function testIsWithinSameDaySameYear()
+ {
+ $event = new Event('Amy', 'birthday', '1995-02-24');
+ $this->assertTrue($event->isWithin('1995-02-24', 0, 0));
+ $this->assertTrue($event->isWithin('1995-02-24', 3, 7));
+ }
+
+ public function testIsWithinSameDayDifferentYear()
+ {
+ $event = new Event('Amy', 'birthday', '1995-02-24');
+ $this->assertTrue($event->isWithin('2019-02-24', 0, 0));
+ $this->assertTrue($event->isWithin('1996-02-24', 3, 7));
+ }
+
+ public function testIsWithinOneDayAfterSameYear()
+ {
+ $event = new Event('Amy', 'birthday', '1995-02-24');
+ $this->assertFalse($event->isWithin('1995-02-23', 0, 0));
+ $this->assertTrue($event->isWithin('1995-02-23', 3, 7));
+ }
+
+ public function testIsWithinOneDayBeforeSameYear()
+ {
+ $event = new Event('Amy', 'birthday', '1995-02-24');
+ $this->assertFalse($event->isWithin('1995-02-25', 0, 0));
+ $this->assertTrue($event->isWithin('1995-02-25', 3, 7));
+ }
+
+ public function testIsWithinOneDayBeforeDifferentYear()
+ {
+ $event = new Event('Amy', 'birthday', '1995-02-24');
+ $this->assertFalse($event->isWithin('1999-02-25', 0, 0));
+ $this->assertTrue($event->isWithin('1990-02-25', 3, 7));
+ }
+
+ public function testIsWithinThreeDaysBeforeSameYear()
+ {
+ $event = new Event('Amy', 'birthday', '1995-02-24');
+ $this->assertFalse($event->isWithin('1999-02-27', 0, 0));
+ $this->assertFalse($event->isWithin('1999-02-27', 1, 0));
+ $this->assertFalse($event->isWithin('1999-02-27', 2, 0));
+ $this->assertTrue( $event->isWithin('1990-02-27', 3, 0));
+
+ $this->assertFalse($event->isWithin('1999-02-27', 0, 3));
+ }
+
+ public function testIsWithinYearOverflowAfter()
+ {
+ $event = new Event('Amy', 'birthday', '1995-01-01');
+ $this->assertTrue($event->isWithin('2019-12-31', 0, 1));
+ $this->assertFalse($event->isWithin('1996-12-30', 0, 1));
+ }
+
+ public function testIsWithinYearOverflowBefore()
+ {
+ $event = new Event('Amy', 'birthday', '1995-12-30');
+ $this->assertTrue($event->isWithin('2019-01-02', 3, 0));
+ $this->assertFalse($event->isWithin('1996-01-02', 2, 0));
+ $this->assertTrue($event->isWithin('1996-01-01', 2, 0));
+ }
+
+ public function testCompareDifferentMonths()
+ {
+ $this->assertLessThan(
+ 0,
+ Event::compare(
+ new Event('Amy', 'birthday', '2013-05-10'),
+ new Event('Bob', 'birthday', '2013-06-10')
+ )
+ );
+ $this->assertGreaterThan(
+ 0,
+ Event::compare(
+ new Event('Amy', 'birthday', '2013-10-10'),
+ new Event('Bob', 'birthday', '2013-08-10')
+ )
+ );
+ }
+
+ public function testCompareDifferentMonthsYearOverflow()
+ {
+ $this->assertGreaterThan(
+ 0,
+ Event::compare(
+ new Event('Amy', 'birthday', '2013-01-10'),
+ new Event('Bob', 'birthday', '2013-12-10')
+ )
+ );
+ $this->assertLessThan(
+ 0,
+ Event::compare(
+ new Event('Amy', 'birthday', '2013-12-10'),
+ new Event('Bob', 'birthday', '2013-02-10')
+ )
+ );
+ }
+
+ public function testCompareDifferentDays()
+ {
+ $this->assertLessThan(
+ 0,
+ Event::compare(
+ new Event('Amy', 'birthday', '1950-05-10'),
+ new Event('Bob', 'birthday', '2013-05-11')
+ )
+ );
+ $this->assertGreaterThan(
+ 0,
+ Event::compare(
+ new Event('Amy', 'birthday', '2013-10-20'),
+ new Event('Bob', 'birthday', '1992-10-02')
+ )
+ );
+ }
+
+ public function testCompareSameDay()
+ {
+ $this->assertLessThan(
+ 0,
+ Event::compare(
+ new Event('Amy', 'birthday', '1950-05-10'),
+ new Event('Bob', 'birthday', '2013-05-10')
+ )
+ );
+ $this->assertGreaterThan(
+ 0,
+ Event::compare(
+ new Event('Bob', 'birthday', '1992-10-02'),
+ new Event('Amy', 'birthday', '2013-10-02')
+ )
+ );
+ }
+}
+?>
--- /dev/null
+<?php
+namespace bdrem;
+require_once __DIR__ . '/../src/bdrem/Autoloader.php';
+Autoloader::register();
+?>
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<phpunit strict="true" colors="true" bootstrap="bootstrap.php">
+ <filter>
+ <whitelist addUncoveredFilesFromWhitelist="false">
+ <directory suffix=".php">../src/</directory>
+ </whitelist>
+ </filter>
+</phpunit>