Skip to content

Instantly share code, notes, and snippets.

@commonquail
Last active April 12, 2026 07:37
Show Gist options
  • Select an option

  • Save commonquail/3c1f9503034358fdbf0d679b8b8a2c08 to your computer and use it in GitHub Desktop.

Select an option

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).map(s => s.replace(/[\n\r]/, ' '));
const payee = '"' + components[0] + '"';
const memo = components.length > 1 ? '"' + components[1] + '"' : '';
var category = '';
if (/gjensidige/i.test(payee)) {
if (/013778004/.test(memo)) {
category = '"Rainy Day Funds: Insurance, ulykke"';
}
if (/013778005/.test(memo)) {
category = '"Rainy Day Funds: Insurance, hus"';
}
if (/013778008/.test(memo)) {
category = '"Rainy Day Funds: Insurance, rejse"';
}
}
if (/kredsl.b/i.test(payee)) {
if (/024540837/.test(memo)) {
category = '"Monthly Bills: Heating"';
}
if (/021739653/.test(memo)) {
category = '"Monthly Bills: Affald"';
}
}
// 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 + ',' + category + ',' + 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*
(?:
(?:[)]{4})
|
(?:[ ]{3})
|
(?:\\\s\\)
)\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", "")
payee = payee.replace("PAYPAL *", "")
if "gebyr" in payee.lower():
memo = f"{memo} {payee}"
payee = "Danske Bank"
if "udl. kortbetaling" 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"
"01.09.2025";"REMA 1000 Stavtrup )))) 06875";"-186,90";"50.175,61";"Udført";"Nej"
"16.09.2025";"Udl. kortbetaling, 1.50% af...";"-1,56";"45.739,36";"Udført";"Nej"
"26.09.2025";"PAYPAL *STEAM GAMES\ \35314...";"-45,92";"82.722,25";"Udført";"Nej"
"16.09.2025";"PAYPAL *WIKIPEDIA\ \4029357...";"-104,00";"45.740,92";"Udført";"Nej"
"05.09.2025";"NETFLIX.COM\ \80251252\";"-169,00";"47.583,01";"Udført";"Nej"
"09.09.2025";"MobilePay R 919 - DB Aarhus";"-32,00";"46.892,77";"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])
We can make this file beautiful and searchable if this error is corrected: It looks like row 20 should actually have 3 columns, instead of 4 in line 19.
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
23-12-2021;KREDSLøB A/S Aftalenr. 024540837;-3892,00;464.742,42;DKK
23-12-2021;GJENSIDIGE FORSIKRING, DANSK FI Aftalenr. 013778004;-4954,87;464.742,42;DKK
23-12-2021;GJENSIDIGE FORSIKRING, DANSK FI Aftalenr. 013778005;-3554,92;464.742,42;DKK
23-12-2021;GJENSIDIGE FORSIKRING, DANSK FI Aftalenr. 013778008;-1093,75;464.742,42;DKK
23-12-2021;KREDSLøB GENBRUG A/S Aftalenr. 021739653;-1931,49;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