setFlags(SplFileObject::READ_CSV | SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE); $file = new LimitIterator($budgetFile, 1); $fmt = new NumberFormatter($locale, NumberFormatter::CURRENCY); foreach ($file as $row) { $txn = new BudgetTransaction; $txn->date = DateTimeImmutable::createFromFormat('d F Y', '1 ' . $row[0])->setTime(0, 0, 0); $txn->category = explode(':', $row[1], 2); $txn->in = $fmt->parseCurrency($row[4], $curr); $txn->out = $fmt->parseCurrency($row[5], $curr); $txn->balance = $row[6]; if (!in_array($row[2], $txn->category, true)) { array_unshift($txn->category, $row[2]); } yield $txn; } } function readRegister (SplFileObject $registerFile, $locale) { $registerFile->setFlags(SplFileObject::READ_CSV | SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE); $iterator = new LimitIterator($registerFile, 1); $iterator->rewind(); $convertDate = convertDate($iterator->current()[9]); $fmt = new NumberFormatter($locale, NumberFormatter::CURRENCY); $txns = []; foreach ($iterator as $i => $row) { // "Account","Flag","Check Number","Date","Payee","Category","Master Category","Sub Category","Memo","Outflow","Inflow","Cleared","Running Balance" $txn = new RegisterTransaction(); $txn->account = $row[0]; $txn->date = $convertDate($row[3])->setTime(0, 0, 0); $txn->payee = $row[4]; $txn->category = explode(':', $row[5]); if (!in_array($row[6], $txn->category, true)) { array_unshift($txn->category, $row[6]); } $txn->memo = $row[8]; $txn->out = $fmt->parseCurrency($row[9], $curr); $txn->in = $fmt->parseCurrency($row[10], $curr); $txn->cleared = ($row[11] !== 'U'); $txn->line = $i; $txns[] = $txn; } // Sort by date, then promote income and fall back on line in file usort($txns, function ($a, $b) use ($convertDate) { return strcmp($a->date->format('U'), $b->date->format('U')) ?: ($b->in < 0.01 ? 0 : $a->line - $b->line); } ); foreach ($txns as $txn) yield $txn; } function multiRead (Generator $budgetFile, Generator $registerFile) { $budgetFile->rewind(); foreach ($registerFile as $rTxn) { if ($rTxn->payee !== 'Starting Balance' && $rTxn->category[1] !== 'Available this month') { if ($budgetFile->valid()) { $bTxn = $budgetFile->current(); $bDate = $bTxn->date; if ($bTxn->date < $rTxn->date) { sleep(2); do { yield $bTxn; $budgetFile->next(); $bTxn = $budgetFile->current(); } while ($bTxn->date == $bDate); } } } yield $rTxn; } } class ConvertCommand extends Command { public function brief () { return 'Convert budget & register exports'; } public function arguments ($args) { $args->add('budget') ->desc('Budget export file') ->isa('file') ->glob('*-Budget.csv'); $args->add('register') ->desc('Register export file') ->isa('file') ->glob('*-Register.csv'); } public function execute ($budgetFile, $registerFile) { $locale = 'en_GB'; $budget = readBudget(new SplFileObject($budgetFile)); $register = readRegister(new SplFileObject($registerFile), $locale); foreach (multiRead($budget, $register) as $txn) { printf("%-8s %-10s\t%-30s %s\n", $txn instanceof RegisterTransaction ? 'Register' : 'Budget', $txn->date->format('Y-m-d'), implode(':', $txn->category), isset($txn->payee) ? $txn->payee : '' ); } } }