Skip to content

Instantly share code, notes, and snippets.

@commonquail
Last active February 8, 2025 10:43
Show Gist options
  • Save commonquail/3c1f9503034358fdbf0d679b8b8a2c08 to your computer and use it in GitHub Desktop.
Save commonquail/3c1f9503034358fdbf0d679b8b8a2c08 to your computer and use it in GitHub Desktop.
var months = {
"jan.": "01",
"feb.": "02",
"mar.": "03",
"apr.": "04",
"maj": "05",
"juni": "06",
"juli": "07",
"aug.": "08",
"sept.": "09",
"okt.": "10",
"nov.": "11",
"dec.": "12"
};
var save = (transactions) => {
const filename = "ynab.csv";
const blob = new Blob([transactions], {type: 'text/csv'});
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, filename);
} else {
const elem = document.createElement('a');
elem.href = window.URL.createObjectURL(blob);
elem.download = filename;
document.body.appendChild(elem);
elem.click();
document.body.removeChild(elem);
window.URL.revokeObjectURL(elem);
}
}
var stot = (e, selector) => e.querySelector(selector).innerText;
var perItem = (prev, cur) => {
// YNAB wants date format dd/mm/yyyy.
// Date field format is "dd. mmm. yyyy" ...
var date = stot(cur, '.transaction-field--date');
// ... unless it is "I dag" or "I går", then construct the value dynamically.
if (date.startsWith("I ")) {
const d = new Date(Date.now());
if (date === "I går") {
d.setDate(d.getDate() - 1);
}
const opts = { year: "numeric", month: "numeric", day: "numeric" };
// dd/mm/yyyy
date = d.toLocaleDateString('en-DK', opts);
} else {
for (const k in months) {
date = date.replace(`. ${k} `, `-${months[k]}-`)
}
date = date.replace(/-/g, '/');
}
const statement = stot(cur, '.transaction-field--statementText');
const memoRegex = /\s+((?:Nota (?:nr. )?[A-Z0-9]+)|(?:Aftalenr. \d+))/;
var components = statement.split(memoRegex)
const payee = '"' + components[0] + '"';
const memo = components.length > 1 ? '"' + components[1] + '"' : '';
// Naively US-ify transaction amount.
const value = stot(cur, '.transaction-field--amount').replace('.','').replace(',', '.');
const isOutflow = value.startsWith('-');
const inflow = isOutflow ? '' : value;
const outflow = isOutflow ? value.substr(1) : '';
return prev + date + ',' + payee + ',,' + memo + ',' + outflow + ',' + inflow + '\n';
}
var csv = Array.prototype.reduce.call(document.querySelectorAll('.transaction-item.transaction-item--hasClick'), perItem, 'Date,Payee,Category,Memo,Outflow,Inflow\n');
save(csv);
#!/usr/bin/env python3
import csv
import codecs
import sys
import re
regex = re.compile(
r"""
\s+(
(?:Nota (?:nr. )?[a-zA-Z0-9]+)
|
(?:Aftalenr. \d+)
|
(?:\)*\s*\d+)
)
""",
re.X)
def payee_and_memo_of(statement):
comps = regex.split(statement)
memo = ""
payee = comps[0]
if len(comps) > 1:
memo = comps[1]
memo = memo.replace("))))", "")
payee = payee.replace("Nettoløn", "Danske Bank")
payee = payee.replace("EDWIN RAHRS VEJ", "Danske Bank PER")
payee = payee.replace("MobilePay:", "")
payee = payee.replace("MobilePay køb", "")
payee = payee.replace("MobilePay", "")
payee = payee.replace("kontaktløs Dankort", "")
payee = payee.replace("Visa/Dankort", "")
payee = payee.replace("Dankort-køb", "")
payee = payee.replace("Dankort", "")
payee = payee.replace("Betalingsservice", "")
if "gebyr" in payee.lower():
memo = f"{memo} {payee}"
payee = "Danske Bank"
payee = payee.strip()
memo = memo.strip()
return (payee, memo)
if len(sys.argv) < 2:
print("usage: %s <csv>" % __file__, file=sys.stderr)
exit(1)
ynab_header = ["Date", "Payee", "Category", "Memo", "Outflow", "Inflow"]
def read(f):
return codecs.open(f, encoding="latin1")
with read(sys.argv[1]) as f:
reader = csv.reader(f, delimiter=";")
writer = csv.writer(sys.stdout, delimiter=",")
writer.writerow(ynab_header)
# payee startswith "MobilePay:"
for row in reader:
date = payee = category = memo = inflow = outflow = ""
date = row[0]
# Skip header if present
if date == "Dato":
continue
date = date.replace(".", "/")
flow = row[2]
flow = flow.replace(".", "")
flow = flow.replace(",", ".")
payee, memo = payee_and_memo_of(row[1])
if flow.startswith("-"):
outflow = flow[1:]
else:
inflow = flow
writer.writerow([date, payee, category, memo, outflow, inflow])
We can make this file beautiful and searchable if this error is corrected: Any value after quoted field isn't allowed in line 1.
"Dato";"Tekst";"Belřb";"Saldo";"Status";"Afstemt"
"08.06.2023";"VISA/DANKORT gebyr";"-269,00";"29.873,31";"Udfřrt";"Nej"
"19.06.2023";"Fřtex City Vest 28255";"-374,20";"29.499,11";"Udfřrt";"Nej"
"20.06.2023";"Fřtex City Vest )))) 02573";"-220,85";"29.278,26";"Udfřrt";"Nej"
"23.06.2023";"Fřtex City Vest )))) 80928";"-121,10";"29.157,16";"Udfřrt";"Nej"
"23.06.2023";"Fřtex City Vest )))) 04871";"-153,80";"29.003,36";"Udfřrt";"Nej"
"26.06.2023";"Fřtex City Vest )))) 93578";"-131,15";"28.872,21";"Udfřrt";"Nej"
"27.06.2023";"Nettolřn";"35.246,35";"64.118,56";"Udfřrt";"Nej"
"28.06.2023";"EDWIN RAHRS VEJ";"-20,00";"64.098,56";"Udfřrt";"Nej"
"28.06.2023";"Report ID 157297";"153,80";"64.252,36";"Udfřrt";"Nej"
"29.06.2023";"Fřtex City Vest )))) 50350";"-333,50";"63.918,86";"Udfřrt";"Nej"
"30.06.2023";"Mikkel budget";"-7.000,00";"56.918,86";"Udfřrt";"Nej"
"30.06.2023";"Mĺnedsopsparing";"-3.000,00";"53.918,86";"Udfřrt";"Nej"
"30.06.2023";"Gebyrer i alt";"-5,00";"53.913,86";"Udfřrt";"Nej"
Date Payee Category Memo Outflow Inflow
04/01/2022 out 19 590.00
04/01/2022 out 18 138.90
03/01/2022 out 17 1494.00
03/01/2022 out 16 1420.00
03/01/2022 out 15 494.23
03/01/2022 out 14 250.00
03/01/2022 out 13 Nota c6d1df766c2 228.00
03/01/2022 out 12 110.95
03/01/2022 out 10 79.00
03/01/2022 out 9 330.75
03/01/2022 out 8 6.26
03/01/2022 out 7 166.00
31/12/2021 out 6 653.24
30/12/2021 out 5 510.00
30/12/2021 out 4 Nota nr. 64393 6000.00
30/12/2021 in 1 26418.35
23/12/2021 out 3/ Nota Z24000617998 703.00
23/12/2021 out 2 Aftalenr. 901711476 132.00
22/12/2021 out 1 æøåÆØÅ 113.20
#!/usr/bin/env python3
import csv
import codecs
import sys
import re
regex = re.compile("\\s+((?:Nota (?:nr. )?[a-zA-Z0-9]+)|(?:Aftalenr. \\d+))")
def payee_and_memo_of(statement):
comps = regex.split(statement)
memo = ""
payee = comps[0]
if len(comps) > 1:
memo = comps[1]
payee = payee.replace("MobilePay:", "")
payee = payee.replace("MobilePay køb", "")
payee = payee.replace("MobilePay", "")
payee = payee.replace("kontaktløs Dankort", "")
payee = payee.replace("Visa/Dankort", "")
payee = payee.replace("Dankort-køb", "")
payee = payee.replace("Dankort", "")
payee = payee.replace("Betalingsservice", "")
payee = payee.strip()
return (payee, memo)
if len(sys.argv) < 2:
print("usage: %s <csv>" % __file__, file=sys.stderr)
exit(1)
ynab_header = ["Date", "Payee", "Category", "Memo", "Outflow", "Inflow"]
def read(f):
return codecs.open(f, encoding="utf-8-sig")
with read(sys.argv[1]) as f:
reader = csv.reader(f, delimiter=";")
writer = csv.writer(sys.stdout, delimiter=",")
writer.writerow(ynab_header)
# payee startswith "MobilePay:"
for row in reader:
date = payee = category = memo = inflow = outflow = ""
date = row[0]
date = date.replace("-", "/")
flow = row[2]
flow = flow.replace(".", "")
flow = flow.replace(",", ".")
payee, memo = payee_and_memo_of(row[1])
if flow.startswith("-"):
outflow = flow[1:]
else:
inflow = flow
writer.writerow([date, payee, category, memo, outflow, inflow])
04-01-2022;out 19;-590 00;477.986 44;DKK
04-01-2022;out 18;-138 90;478.576 44;DKK
03-01-2022;out 17;-1.494 00;478.715 34;DKK
03-01-2022;out 16;-1.420 00;480.209 34;DKK
03-01-2022;out 15;-494 23;481.629 34;DKK
03-01-2022;out 14;-250 00;482.123 57;DKK
03-01-2022;MobilePay out 13 Nota c6d1df766c2;-228 00;482.373 57;DKK
03-01-2022;Betalingsservice out 12;-110 95;482.601 57;DKK
03-01-2022;Dankort out 10;-79 00;482.712 52;DKK
03-01-2022;Dankort-køb out 9;-330 75;482.791 52;DKK
03-01-2022;Visa/Dankort out 8;-6 26;483.122 27;DKK
03-01-2022;kontaktløs Dankort out 7;-166 00;483.128 53;DKK
31-12-2021;MobilePay køb out 6;-653 24;483.294 53;DKK
30-12-2021;MobilePay: out 5;-510 00;483.947 77;DKK
30-12-2021;out 4 Nota nr. 64393;-6.000 00;484.457 77;DKK
30-12-2021;in 1;26.418 35;490.457 77;DKK
23-12-2021;out 3/ Nota Z24000617998;-703 00;464.039 42;DKK
23-12-2021;out 2 Aftalenr. 901711476;-132 00;464.742 42;DKK
22-12-2021;out 1 æøåÆØÅ;-113 20;464.874 42;DKK
Date Payee Category Memo Outflow Inflow
25/07/2010 Sample Payee Sample Category Sample Memo for an outflow 100.00
26/07/2010 Sample Payee 2 Sample Category Sample memo for an inflow 500.00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment