diff --git a/CHANGELOG.md b/CHANGELOG.md index fcd8fc6..1ddad6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ folgen [Semantic Versioning](https://semver.org/lang/de/). Die Version steht in ## [Unveröffentlicht] +### Hinzugefügt +- Steuertabellen 2026 für `tax`: `demo/taxDE2026`, `taxAT2026`, `taxCH2026` (nur Bundessteuer), `taxLI2026` (nur Landessteuer), erzeugt mit `tools/gen_tax_tables.rb`, mit Tests. + ### Geändert - README und `man/row.man` erklären, warum `select` in bash/zsh/ksh einen Syntaxfehler gibt und wie man es trotzdem aufruft. diff --git a/README.md b/README.md index d720940..7561e2a 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,30 @@ pick -m -k 'down space down enter' < inventory Tasten aus einem String (Skrip Pfeile, Bild auf/ab, Pos1/Ende scrollen; `/` filtert (Enter behält den Filter, Esc löscht ihn); Enter wählt; Esc bricht ab (Status 1, keine Ausgabe). Ohne Markierung gilt die Zeile unter dem Cursor. +## Steuertabellen 2026 für `tax` + +`demo/` enthält vier Tarife für das Steuerjahr 2026 im Format von `tax` (income, tax, percent), jeweils für +Alleinstehende (Grundtarif). Erzeugt mit `ruby tools/gen_tax_tables.rb`. + +| Datei | Land | Quelle | Genauigkeit | +|---|---|---|---| +| `taxDE2026` | Deutschland, Einkommensteuer | § 32a EStG (Grundfreibetrag 12 348 €) | Kurvenstücke durch Geraden genähert, Abweichung unter 0,15 € (die Formel rundet ab, `tax` auf) | +| `taxAT2026` | Österreich, Einkommensteuer | § 33 EStG (0 % bis 13 539 €, dann 20/30/40/48/50/55 %) | exakt, vor Absetzbeträgen | +| `taxCH2026` | Schweiz, **nur direkte Bundessteuer** | Art. 36 DBG, ESTV-Rundschreiben 2-215-D-2025 | Stufen exakt, das Gesetz rechnet in 100-Franken-Schritten | +| `taxLI2026` | Liechtenstein, **nur Landessteuer** | Art. 19 SteG (LR 640.0, Fassung 01.07.2026) | exakt | + +``` +tax 60000 < demo/taxDE2026 14233 +tax 60000 < demo/taxAT2026 15447 +tax 60000 < demo/taxCH2026 671 +tax 60000 < demo/taxLI2026 1396 (mal 1 + Gemeindezuschlag, 150 % bis 250 %) +``` + +Der Betrag ist die Steuer auf das **zu versteuernde** Einkommen. Abzüge, Sozialversicherung, Absetzbeträge, +Kirchensteuer und Soli sind nicht enthalten. Für die Schweiz kommen Kanton und Gemeinde dazu (sie sind meist +höher als der Bund), für Liechtenstein der Gemeindezuschlag: gesamt = Landessteuer × (1 + Zuschlag/100). +Ehepaar- und Alleinerziehendentarife fehlen. Das ist keine Steuerberatung. + ## Befehle | Gruppe | Befehle | diff --git a/demo/taxAT2026 b/demo/taxAT2026 new file mode 100644 index 0000000..13db509 --- /dev/null +++ b/demo/taxAT2026 @@ -0,0 +1,9 @@ +income tax percent +------ --- ------- +0 0.00 0 +13539 0.00 20 +21992 1690.60 30 +36458 6030.40 40 +70365 19593.20 48 +104859 36150.32 50 +1000000 483720.82 55 diff --git a/demo/taxCH2026 b/demo/taxCH2026 new file mode 100644 index 0000000..feca7ca --- /dev/null +++ b/demo/taxCH2026 @@ -0,0 +1,14 @@ +income tax percent +------ --- ------- +0 0.00 0 +15200 0.00 0.77 +33200 138.60 0.88 +43500 229.20 2.64 +58000 612.00 2.97 +76200 1152.50 5.94 +82100 1502.95 6.6 +108900 3271.75 8.8 +141500 6140.55 11 +185100 10936.55 13.2 +793900 91298.15 11.85 +794000 91310.00 11.5 diff --git a/demo/taxDE2026 b/demo/taxDE2026 new file mode 100644 index 0000000..bf065f5 --- /dev/null +++ b/demo/taxDE2026 @@ -0,0 +1,235 @@ +income tax percent +------ --- ------- +0 0.00 0 +12348 0.00 14.228628 +12598 35.57 14.685882 +12848 72.29 15.143138 +13098 110.14 15.600392 +13348 149.15 16.057648 +13598 189.29 16.514902 +13848 230.58 16.972158 +14098 273.01 17.429413 +14348 316.58 17.886668 +14598 361.30 18.343922 +14848 407.16 18.801178 +15098 454.16 19.258432 +15348 502.31 19.715688 +15598 551.60 20.172942 +15848 602.03 20.630198 +16098 653.60 21.087453 +16348 706.32 21.544707 +16598 760.18 22.001963 +16848 815.19 22.459217 +17098 871.34 22.916473 +17348 928.63 23.373727 +17598 987.06 23.786172 +17799 1034.87 24.012466 +18049 1094.90 24.099825 +18299 1155.15 24.186375 +18549 1215.62 24.272925 +18799 1276.30 24.359475 +19049 1337.20 24.446025 +19299 1398.31 24.532575 +19549 1459.65 24.619125 +19799 1521.19 24.705675 +20049 1582.96 24.792225 +20299 1644.94 24.878775 +20549 1707.14 24.965325 +20799 1769.55 25.051875 +21049 1832.18 25.138425 +21299 1895.02 25.224975 +21549 1958.09 25.311525 +21799 2021.37 25.398075 +22049 2084.86 25.484625 +22299 2148.57 25.571175 +22549 2212.50 25.657725 +22799 2276.64 25.744275 +23049 2341.01 25.830825 +23299 2405.58 25.917375 +23549 2470.38 26.003925 +23799 2535.39 26.090475 +24049 2600.61 26.177025 +24299 2666.05 26.263575 +24549 2731.71 26.350125 +24799 2797.59 26.436675 +25049 2863.68 26.523225 +25299 2929.99 26.609775 +25549 2996.51 26.696325 +25799 3063.25 26.782875 +26049 3130.21 26.869425 +26299 3197.38 26.955975 +26549 3264.77 27.042525 +26799 3332.38 27.129075 +27049 3400.20 27.215625 +27299 3468.24 27.302175 +27549 3536.50 27.388725 +27799 3604.97 27.475275 +28049 3673.66 27.561825 +28299 3742.56 27.648375 +28549 3811.68 27.734925 +28799 3881.02 27.821475 +29049 3950.57 27.908025 +29299 4020.34 27.994575 +29549 4090.33 28.081125 +29799 4160.53 28.167675 +30049 4230.95 28.254225 +30299 4301.59 28.340775 +30549 4372.44 28.427325 +30799 4443.51 28.513875 +31049 4514.79 28.600425 +31299 4586.29 28.686975 +31549 4658.01 28.773525 +31799 4729.95 28.860075 +32049 4802.10 28.946625 +32299 4874.46 29.033175 +32549 4947.05 29.119725 +32799 5019.84 29.206275 +33049 5092.86 29.292825 +33299 5166.09 29.379375 +33549 5239.54 29.465925 +33799 5313.21 29.552475 +34049 5387.09 29.639025 +34299 5461.18 29.725575 +34549 5535.50 29.812125 +34799 5610.03 29.898675 +35049 5684.78 29.985225 +35299 5759.74 30.071775 +35549 5834.92 30.158325 +35799 5910.31 30.244875 +36049 5985.93 30.331425 +36299 6061.75 30.417975 +36549 6137.80 30.504525 +36799 6214.06 30.591075 +37049 6290.54 30.677625 +37299 6367.23 30.764175 +37549 6444.14 30.850725 +37799 6521.27 30.937275 +38049 6598.61 31.023825 +38299 6676.17 31.110375 +38549 6753.95 31.196925 +38799 6831.94 31.283475 +39049 6910.15 31.370025 +39299 6988.57 31.456575 +39549 7067.22 31.543125 +39799 7146.07 31.629675 +40049 7225.15 31.716225 +40299 7304.44 31.802775 +40549 7383.95 31.889325 +40799 7463.67 31.975875 +41049 7543.61 32.062425 +41299 7623.76 32.148975 +41549 7704.14 32.235525 +41799 7784.73 32.322075 +42049 7865.53 32.408625 +42299 7946.55 32.495175 +42549 8027.79 32.581725 +42799 8109.24 32.668275 +43049 8190.92 32.754825 +43299 8272.80 32.841375 +43549 8354.91 32.927925 +43799 8437.23 33.014475 +44049 8519.76 33.101025 +44299 8602.51 33.187575 +44549 8685.48 33.274125 +44799 8768.67 33.360675 +45049 8852.07 33.447225 +45299 8935.69 33.533775 +45549 9019.52 33.620325 +45799 9103.57 33.706875 +46049 9187.84 33.793425 +46299 9272.32 33.879975 +46549 9357.02 33.966525 +46799 9441.94 34.053075 +47049 9527.07 34.139625 +47299 9612.42 34.226175 +47549 9697.99 34.312725 +47799 9783.77 34.399275 +48049 9869.77 34.485825 +48299 9955.98 34.572375 +48549 10042.41 34.658925 +48799 10129.06 34.745475 +49049 10215.92 34.832025 +49299 10303.00 34.918575 +49549 10390.30 35.005125 +49799 10477.81 35.091675 +50049 10565.54 35.178225 +50299 10653.49 35.264775 +50549 10741.65 35.351325 +50799 10830.03 35.437875 +51049 10918.62 35.524425 +51299 11007.43 35.610975 +51549 11096.46 35.697525 +51799 11185.71 35.784075 +52049 11275.17 35.870625 +52299 11364.84 35.957175 +52549 11454.74 36.043725 +52799 11544.84 36.130275 +53049 11635.17 36.216825 +53299 11725.71 36.303375 +53549 11816.47 36.389925 +53799 11907.45 36.476475 +54049 11998.64 36.563025 +54299 12090.04 36.649575 +54549 12181.67 36.736125 +54799 12273.51 36.822675 +55049 12365.57 36.909225 +55299 12457.84 36.995775 +55549 12550.33 37.082325 +55799 12643.03 37.168875 +56049 12735.96 37.255425 +56299 12829.09 37.341975 +56549 12922.45 37.428525 +56799 13016.02 37.515075 +57049 13109.81 37.601625 +57299 13203.81 37.688175 +57549 13298.03 37.774725 +57799 13392.47 37.861275 +58049 13487.12 37.947825 +58299 13581.99 38.034375 +58549 13677.08 38.120925 +58799 13772.38 38.207475 +59049 13867.90 38.294025 +59299 13963.63 38.380575 +59549 14059.59 38.467125 +59799 14155.75 38.553675 +60049 14252.14 38.640225 +60299 14348.74 38.726775 +60549 14445.56 38.813325 +60799 14542.59 38.899875 +61049 14639.84 38.986425 +61299 14737.30 39.072975 +61549 14834.99 39.159525 +61799 14932.89 39.246075 +62049 15031.00 39.332625 +62299 15129.33 39.419175 +62549 15227.88 39.505725 +62799 15326.64 39.592275 +63049 15425.63 39.678825 +63299 15524.82 39.765375 +63549 15624.24 39.851925 +63799 15723.87 39.938475 +64049 15823.71 40.025025 +64299 15923.77 40.111575 +64549 16024.05 40.198125 +64799 16124.55 40.284675 +65049 16225.26 40.371225 +65299 16326.19 40.457775 +65549 16427.33 40.544325 +65799 16528.69 40.630875 +66049 16630.27 40.717425 +66299 16732.06 40.803975 +66549 16834.07 40.890525 +66799 16936.30 40.977075 +67049 17038.74 41.063625 +67299 17141.40 41.150175 +67549 17244.28 41.236725 +67799 17347.37 41.323275 +68049 17450.68 41.409825 +68299 17554.20 41.496375 +68549 17657.94 41.582925 +68799 17761.90 41.669475 +69049 17866.07 41.756025 +69299 17970.46 41.842575 +69549 18075.07 41.963277 +69879 18213.55 42.000014 +277826 105551.32 45 diff --git a/demo/taxLI2026 b/demo/taxLI2026 new file mode 100644 index 0000000..7a5f387 --- /dev/null +++ b/demo/taxLI2026 @@ -0,0 +1,11 @@ +income tax percent +------ --- ------- +0 0.00 0 +15900 0.00 1 +21100 52.00 3 +42300 688.00 4 +74000 1956.00 5 +105700 3541.00 6 +137400 5443.00 6.5 +169200 7510.00 7 +211400 10464.00 8 diff --git a/demo/testall.cmds b/demo/testall.cmds index bf8b5bc..5ea6e65 100644 --- a/demo/testall.cmds +++ b/demo/testall.cmds @@ -223,6 +223,12 @@ act cashflow < cash ## tax: look up the tax for an income in a tax table tax 20000 < X83 +## tax also works with the real 2026 tables of DE, AT, CH (federal tax only) and LI (Landessteuer, +## before the municipal surcharge of 150 % to 250 %); see README. Tax on an income of 60000: +tax 60000 < taxDE2026 +tax 60000 < taxAT2026 +tax 60000 < taxCH2026 +tax 60000 < taxLI2026 ## explode: parts list -> all subparts with counts (part, amount) explode 10001 2 parttable ## chartdup: look for duplicate account numbers and short names in chart diff --git a/demo/testall.old b/demo/testall.old index d1f7060..bf9a3ac 100644 --- a/demo/testall.old +++ b/demo/testall.old @@ -935,6 +935,16 @@ Date Amount Balance Description ## tax: look up the tax for an income in a tax table $ tax 20000 < X83 3369 +## tax also works with the real 2026 tables of DE, AT, CH (federal tax only) and LI (Landessteuer, +## before the municipal surcharge of 150 % to 250 %); see README. Tax on an income of 60000: +$ tax 60000 < taxDE2026 + 14233 +$ tax 60000 < taxAT2026 + 15447 +$ tax 60000 < taxCH2026 + 671 +$ tax 60000 < taxLI2026 + 1396 ## explode: parts list -> all subparts with counts (part, amount) $ explode 10001 2 parttable Part Amount diff --git a/test/rdb_test.rb b/test/rdb_test.rb index 64e5c7a..d3b4c24 100644 --- a/test/rdb_test.rb +++ b/test/rdb_test.rb @@ -723,3 +723,40 @@ class PickTest < RdbCase assert_equal '', table end end + +class TaxTablesTest < RdbCase + def tax(income, table) = rdb('tax', income.to_s, file: table).to_i + + def test_at_2026 + assert_equal 0, tax(13_539, 'taxAT2026') + assert_equal 11_447, tax(50_000, 'taxAT2026') # 6 030.40 + 13 542 * 40 % + assert_equal 33_818, tax(100_000, 'taxAT2026') + end + + def test_ch_federal_2026 + assert_equal 0, tax(15_200, 'taxCH2026') + assert_equal 401, tax(50_000, 'taxCH2026') + assert_equal 2684, tax(100_000, 'taxCH2026') + assert_equal 91_310, tax(794_000, 'taxCH2026') + end + + def test_li_landessteuer_2026 + assert_equal 996, tax(50_000, 'taxLI2026') # 0,04 * x - 1 004 + assert_equal 3256, tax(100_000, 'taxLI2026') # 0,05 * x - 1 744 + assert_equal 10_464, tax(211_400, 'taxLI2026') + assert_equal 0, tax(15_855, 'taxLI2026') + end + + # § 32a EStG: the table is a straight-line approximation of the curved zones + def test_de_2026_follows_the_formula + [12_348, 14_000, 17_799, 20_000, 33_333, 50_000, 69_878, 100_000, 277_825, 300_000].each do |x| + exact = if x <= 12_348 then 0 + elsif x <= 17_799 then y = (x - 12_348) / 10_000.0; ((914.51 * y) + 1400) * y + elsif x <= 69_878 then z = (x - 17_799) / 10_000.0; (((173.10 * z) + 2397) * z) + 1034.87 + elsif x <= 277_825 then (0.42 * x) - 11_135.63 + else (0.45 * x) - 19_470.38 + end + assert_in_delta exact.floor, tax(x, 'taxDE2026'), 1, "income #{x}" + end + end +end diff --git a/tools/gen_tax_tables.rb b/tools/gen_tax_tables.rb new file mode 100644 index 0000000..2645d47 --- /dev/null +++ b/tools/gen_tax_tables.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +# Writes the 2026 income tax tables in the format of `tax` (income, tax, percent) +# into demo/. Sources: § 32a EStG (DE), § 33 EStG (AT), Tarif 2026 Art. 36 DBG (CH, +# ESTV Rundschreiben 2-215-D-2025), Art. 19 SteG LR 640.0, Fassung 01.07.2026 (LI). +# ruby tools/gen_tax_tables.rb + +DEMO = File.expand_path('../demo', __dir__) + +def write_table(name, points) + rows = points.map { |i, t, p| [i.to_s, format('%.2f', t), format('%.6f', p).sub(/0+\z/, '').sub(/\.\z/, '')] } + File.write(File.join(DEMO, name), "income\ttax\tpercent\n------\t---\t-------\n#{rows.map { |r| r.join("\t") }.join("\n")}\n") +end + +# --- Germany: single assessment, § 32a Abs. 1 (the zones 2 and 3 are curves, so +# the table approximates them with short straight pieces; error below one euro) +def de_tax(x) + if x <= 12_348 then 0.0 + elsif x <= 17_799 then y = (x - 12_348) / 10_000.0; ((914.51 * y) + 1400) * y + elsif x <= 69_878 then z = (x - 17_799) / 10_000.0; (((173.10 * z) + 2397) * z) + 1034.87 + elsif x <= 277_825 then (0.42 * x) - 11_135.63 + else (0.45 * x) - 19_470.38 + end +end + +de = [12_348] + (12_598..17_798).step(250).to_a + [17_799] + (18_049..69_549).step(250).to_a + [69_879, 277_826] +pts = de.each_with_index.map do |i, n| + nxt = de[n + 1] + slope = nxt ? (de_tax(nxt) - de_tax(i)) / (nxt - i) * 100 : 45.0 + [i, de_tax(i), slope] +end +write_table('taxDE2026', [[0, 0.0, 0.0]] + pts) + +# --- Austria: § 33 EStG, the tariff before the Absetzbeträge +at = [[13_539, 20], [21_992, 30], [36_458, 40], [70_365, 48], [104_859, 50], [1_000_000, 55]] +acc = 0.0 +prev = [0, 0] +pts = [[0, 0.0, 0.0]] +at.each do |limit, rate| + acc += (limit - prev[0]) * prev[1] / 100.0 + pts << [limit, acc, rate] + prev = [limit, rate] +end +write_table('taxAT2026', pts) + +# --- Switzerland: direct federal tax, Art. 36 DBG, single persons (without canton and municipality) +ch = [[0, 0, 0], [15_200, 0, 0.77], [33_200, 138.60, 0.88], [43_500, 229.20, 2.64], [58_000, 612.00, 2.97], + [76_200, 1152.50, 5.94], [82_100, 1502.95, 6.60], [108_900, 3271.75, 8.80], [141_500, 6140.55, 11.00], + [185_100, 10_936.55, 13.20], [793_900, 91_298.15, 11.85], [794_000, 91_310.00, 11.50]] +write_table('taxCH2026', ch) + +# --- Liechtenstein: Landessteuer, Art. 19 Abs. 1 Bst. a SteG; the municipal surcharge +# (150 % to 250 % of it) comes on top: total = Landessteuer * (1 + surcharge / 100) +li_rates = [[15_900, 1], [21_100, 3], [42_300, 4], [74_000, 5], [105_700, 6], [137_400, 6.5], [169_200, 7], [211_400, 8]] +acc = 0.0 +prev = [15_855, 0] +pts = [[0, 0.0, 0.0]] +li_rates.each do |limit, rate| + acc += (limit - prev[0]) * prev[1] / 100.0 if prev[1].positive? + pts << [limit, acc, rate] + prev = [limit, rate] +end +write_table('taxLI2026', pts)