Tag Standards

  • Saner Timestamps With DIT: In Pelican and Beyond

    The other day Randall Munroe posted XKCD 2867:

    This lament about time calculus struck me as something of a weird (pun alarm) synchronicity, as one evening or two before that I had written a few lines of flamboyant time-related code.

    Admittedly, I was neither concerned with “sin to ask” nor with „impossible to know“: Both are a consequence of the theory of relativity, which literally states that (against Newton) there is no absolute time and hence when two clocks are in two different places, even synchronising them once is deep science.

    Sold on Decimal Internet Time

    No, my coding was exclusively about the entirely unnecessary trouble of having to account for time zones, daylight savings time, factors of 60, 24, sometimes 30, 31, 29, or 28, and quite a few other entirely avoidable warts in our time notation. Civil time on Earth is not complicated because of physics. On human scales of time, space, velocity, gravitation, and precision, it is not particularly hard to define an absolute time even though it physically does not exist.

    Rather, civil time calculations are difficult because of the (pun alarm) Byzantine legacy from Babylon – base-60 and base-12, seven-day weeks, moon calendar – exacerbated by misguided attempts of patching that legacy up for the railway age (as in: starting in 1840, by and large done about 1920). On top of that, these patches don't work particularly well even for rail travel. I speak from recent experience in this particular matter.

    Against this backdrop I was almost instantly sold on DIT, the Decimal Internet Time apparently derived from a plan a person named Anarkat (the Mastodon link on the spec page is gone now) proposed: Basically, you divide the common day in what currently is the time zone UTC-12 into 10 parts and write the result in decimal. Call the integer part “Dek” and the first two digits after the dot “Sim”. That's a globally valid timestamp precise to about a (Babylonian) minute. For example, in central Europe what's now 14:30 (or 15:30 during daylight savings time; sigh!) would be 0.62 in DIT, and so would Babylonian 13:30 in the UK or 8:30 in Boston, Mass. This may look like a trivial simplification, but makes a universe of a difference in how much less painful time calculations become.

    I admit I'd much rather have based time keeping on the second (the SI unit of time), but I have to give Anarkat that the day is much more important in most people's lives than the second. Thus, their plan obviously is a lot saner for human use than any I would have come up with (“let's call the kilosecond kes and use that instead of an hour…”)[1].

    If you use pelican…

    Since I think that this would be a noticeably better world if we adopted DIT (clearly, in a grassrootsy step-by-step process), I'd like to do a bit of propaganda for it. Well, a tiny bit perhaps, but I am now giving the timestamps of the posts on this blog in StarDIT, which is an extension of DIT where you count the days in a (Gregorian, UTC-12) year and number the years from the “Holocene epoch”, which technically means “prepend a one to the Gregorian year number“ (in other words, add 10'000 to “AD”).

    Like DIT itself, with sufficient adoption StarDIT would make some people's lives significantly simpler, in this case in particular historians (no year 0 problem any more!). I would like that a lot, too, as all that talk about “Domini” doesn't quite cater to my enlightened tastes.

    How do I do produce the starDITs? Well, I first wrote a rather trivial extension for my blog engine, pelican, which adds an attribute starDIT to posts. You will find it as ditdate.py in my pelican plugins repo on codeberg. Activate it by copying the file into your blog's plugins directory and adding "ditdate" to the PLUGINS list in your pelicanconf.py. You can then use the new attribute in your templates. In mine, there is something like:

    <a href="http://blog.tfiu.de/mach-mit-bei-dit.html">DIT</a>
    <abbr class="StarDIT">{{ article.starDIT[:-4] }}</abbr>
    (<abbr class="date">{{ article.date.strftime("%Y-%m-%d") }}</abbr>)
    

    If you don't use pelican…

    I have also written a Python module to convert between datetimes and DITs which shows a Tkinter UI when called as a program:

    A small grey window on top of some bright background; sans-serif letters say 12023:351 (small) 1.08.5 (large).

    I have that on my desktop now. And since alarmingly many people these days use a web browser as their primary execution platform, I have also written some HTML/Javascript to have the DIT on a web page and its title (also hosted here).

    Both of these things are in my dit-py repo on codeberg, available under CC0: Do with them whatever you want. (Almost) anything furthering the the cause of DIT is – or so I think I have argued above – very likely progress overall.

    [1]If you speak German or trust automatic translation, I have a longer elaboration of DIT aspects I don't like in a previous blogpost.
  • Mach mit bei DIT

    [In case you're coming here from an English-language article, see here]

    A small grey window on top of some bright background; sans-serif letters say 12023:351 (small) 1.08.5 (large).

    Hier zeigt meine DIT-Uhr die Zeit (und das Datum) in meinem sawfish-Dock. Nein, das ist kein Startrek-Unfug. Ich hoffe stattdessen, dass etwas in dieser Art im Laufe der Zeit zum In-Accessoire werden wird: Wer keins hat, darf nicht mehr Digitalisierung sagen [nun: glücklicherweise hat niemand, der_die sowas wollen könnte, Mittel, mit denen so ein Verbot durchzusetzen wäre].

    Heraus aus der babylonischen Verwirrung!

    Es gibt nach 3000 Jahren nicht mehr allzu viele Gründe, sauer auf die großen KriegsherrInnen aus Babylon und ihre mesopotamischen KollegInnen zu sein. Mit dem babylonischen Klerus sieht das anders aus: Nicht nur sexagesimale Koordinaten etwa in der Astronomie geht auf ihn zurück, sondern auch all der krumme Kram mit Faktoren von 60 oder 24 oder 7, mit dem wir uns völlig ohne Not[1] immer noch in der Zeitrechnung herumschlagen.

    Keine Schuld haben die mesopotamischen PriesterInnen am Ärgernis Zeitzonen und dem damit zusammenhängenden Sommerzeit-Elend, aber ich wollte auch die schon ewig loswerden, nicht nur wie neulich aus Betroffenheit.

    So hat die Decimal Internet Time (DIT) mein Herz (fast) im Sturm genommen, ein Vorschlag, die Zeit durch Zehnteln des Tages zu notieren. Dieser Stundenersatz heißt Dek (von Dekatag) und entspricht fast zweieinhalb (nämlich 24/10) babylonischen Stunden.

    Selbst für sehr grobe Zeitangaben werden Deks in der Regel nicht reichen, weshalb sie in hundert Sims (von Decimal Minute) aufgeteilt werden. So ein Sim entspricht 86 Sekunden, ist also ziemlich nahe an einer babylonischen Minute. Das wäre wohl so die Einheit für Verabredungen: „Mittagessen um neun komma fünfundsiebzig“ oder meinetwegen „fünfungzwanzig vor null“, denn um die 100 Sekunden warten sollten für niemand ein Problem sein, und viel genauer fährt die Bahn nicht mal in der Schweiz. Aber weils Dezimal ist, wärs auch kein Problem, einfach nach den Zehnern aufzuhören: „Ich breche dann um 7.8 auf“, eine Angabe, die etwa auf eine Viertelstunde genau ist – sehr menschengemäß in meinem Buch.

    Ich finde das total plausibel; wenn euch das demgegenüber komisch vorkommt, ist das, ich muss es euch sagen, sehr parallel zur Abneigung von in imperialen Einheiten aufgewachsenen Leuten, etwas wie „ein Meter Fünfundachtzig“ zu sagen, wo doch „six foot two inches“ soo viel intuitiver ist.

    Um ein Gefühl für die Dezimalzeit zu bekommen, hätte ich folgende Kurzreferenz für BRD-Gewohnheiten anzubieten:

    DIT MEZ in Worten
    0 Mittag (13:00)
    1.5 Nachmittag (~16:30)
    2 Früher Abend (~18:00)
    3 Abend (20:00)
    4.5 Mitternacht
    6 Unchristliche Zeit (3:30)
    7.5 Morgen (7:00)
    9 Vormittag (10:30)

    Deseks: Vielleicht nicht so nützlich

    Weniger begeistert bin ich von der kleinsten Zeiteinheit von DIT, der Dezimalsekunde, Desek oder kurz Sek; das ist ein Tag/100'000, gegenüber einem Tag/86'400 bei der SI-Sekunde.

    Als SI-Taliban hätte ich die ganze dezimale Zeitrechnung ja ohnehin lieber auf die Sekunde aufgebaut und die Kilosekunde (ungefähr eine Viertelstunde) als Stundenersatz etabliert. Zwar gebe ich zu, dass die DIT-Wahl des Bezugs auf den Tag für menschliche Nutzung ein besserer Plan ist als die Kilosekunde (von der es 86.4 in einem Tag gibt, was eingestandenermaßen blöd ist).

    Aber für rein menschliche Nutzung (Verabredungen, Tagesplan, Fahrpläne…) spielen Zeiten im Sekundenbereich in der Regel keine Rolle, und so hätte ich die Deseks einfach rausgelassen und gesagt: Wers genauer braucht, soll zu den PhysikerInnen gehen und von denen die Sekunde nehmen. Dass ein Sim ziemlich genau aus 86.4 von diesen SI-Sekunden besteht, ist eher eine putzige Kuriosität als eine praktische Schwierigkeit, und jedenfalls nicht nennenswert lästiger als die 60 Sekunden, die eine babylonische Minute hat.

    Und nein, die physikalische Sekunde als Tag/100,000 umzudefinieren lohnt den Aufwand nicht; dafür ist die Erdrotation längst zu ungenau, und dann wir wollen ohnehin den Schaltsekunden-Unfug nicht mehr. Die Sekunde ist Physik, die braucht nichts mit menschlichen Zeiten zu tun zu haben. Insofern: Es wäre schöner, wenn es keine Desek gäbe, aber ich will auch nicht streiten.

    Good riddance, Zeitzonen

    Der neben der Nutzung des Dezimalsystems zweite große Fortschritt von DIT ist, dass sie auf der ganzen Welt einheitlich verläuft. Es gibt also in DIT keine Zeitzonen mehr.

    Mehr nebenbei ist das so gemacht, dass das babylonische 12 Uhr, die Mittagszeit bzw. 5 Deks in DIT, in der aktuellen UTC-12-Zeitzone (der „frühesten“, die es gibt), tatsächlich ungefähr mit der Kulmination der Sonne, also einer naiven Mittagsdefinition, zusammenfällt. Aber das spielt – im Gegensatz zum etwas antibritisch klingenden Sentiment in der DIT-Spec – eigentlich keine Rolle. Relevant ist nur, dass DIT-Uhren auf der ganzen Welt den gleichen Wert anzeigen. Ich darf meine Fantasie von neulich für DIT aktualisieren:

    Wäre es wirklich ein Problem, wenn Menschen, die in Kasachstan leben, 2 Deks für eine gute Zeit fürs Mittagessen halten würden und sich die Leute in New York eher so gegen siebeneinalb Deks über ihres hermachten? Ich wette, alle würden sich schnell dran gewöhnen. Es ist jedenfalls einfacher als das Sommerzeit-Mantra „spring forward, fall back“.

    Eingestanden: Wenn ich DIT entworfen hätte, hätte ich die auf die Referenz 12 babylonische Stunden entfernt von UTC verzichtet, denn alle anständigen Zeitstempel sind bereits jetzt in UTC. Wenn mensch für die DIT davon weggeht, verschränken sich Datum und Zeit bei der Umrechnung dieser anständigen Zeitstempel zu DIT – beim Übergang von babylonischer Zeit zu DIT kann sich also auch das Datum ändern.

    Das ist eine Komplikation, die keinen erkennbaren Nutzen hat; es ist eben kein Privileg, dass die Sonne um 5 Deks kulminiert, und so ist der Versuch albern, dabei möglichst wenige Menschen „zu bevorzugen“. Aber seis drum.

    Das Datum zur Zeit: StarDIT

    Insbesondere spielt das keine Rolle mehr, wenn mensch auch das Datum in DIT schreibt. Dazu gibt es eine Erweiterung von DIT zu größeren Zeiträumen hin, die im Vorschlag StarDIT genannt wird. Ob die Gesellschaft schon durchnerdet genug ist, um mit so einem Namen durchzukommen? Weiß nicht.

    An sich ist, wo wir schon bei Namen sind, ja auch das I, „Internet“, in DIT nicht so richtig seriös. Ich würde es vielleicht lieber als „International“ lesen – Internationalismus ist und bleibt einer der sympathischeren Ismen.

    Im StarDIT-Plan jedenfalls besteht das Datum aus (gregorianischem) Jahr zu einer leicht entchristlichten Epoche sowie der laufenden Tagesnummer innerhalb eines Jahres, mit einem Doppelpunkt getrennt, also für heute etwa 12023:350. Wer Wochen haben will, nimmt den Zehneranteil und schreibt ein x dahinter; aktuell haben wir also die Woche 35x.

    Zehntagewochen bergen ein wenig das Risiko, dass aus fünf Arbeitstagen acht werden; ein analoger Effekt hat schon dem Französischen Revolutionskalender (in meiner Geschichtserzählung) den Hals gebrochen. Aber wir müssen ja gerade sowieso über drastische Arbeitszeitverkürzung reden, um irgendwie die immer noch wachsende CO₂-Emission in den Griff zu kriegen. Da könnte der Übergang zu DIT durchaus mit einem Zwischenmodell mit weiterhin fünf Tagen Lohnarbeit, dafür dann auch fünf Tagen Selbstbestimmung („Wochenende“) zusammengehen – bevor die Lohnarbeit weiter abnimmt, natürlich.

    Putzig, wenn auch nicht allzu praktikabel für den Alltag, finde ich die DIT-Idee, die christliche Epoche (zu meinen eigenen Bedenken vgl. Fußnote 1 hier) durchs Holozän-Jahr zu ersetzen. Das ist wie das normale Gregorianische Jahr, nur dass die Zählung 9'999 vdCE anfängt (das heißt: Zählt einfach 10'000 zu ndCE-Jahren dazu).

    Es ist sicher prima, wenn die Leute nicht mehr durch Kennungen wie „v. Chr“ oder „n. Chr“ letztlich fromme Märchen verbreiten, und es ist auch großartig, wenn das Jahr-0-Problem (es gibt nämlich kein Jahr 0: die derzeitige Jahreszählung geht direkt von 1 v. zu 1 n., und drum ist auch die DIT-Referenzepoche etwas krumm) zumindest aus der post-mittelsteinzeitlichen Geschichtsschreibung komplett verschwindet. Ob jedoch das ein Deal ist, wenn mensch dafür mit einer Extraziffer in Jahreszahlen bezahlen muss? Fünf ist, haha, eben nicht zwingend Trümpf.

    Andererseits: Das StarDIT ist trivial aus der gewohnten Jahreszahl auszurechnen, und realistisch würden die Leute auch mit DIT im Alltag wohl weiterhin „Dreiundzwanzig“ oder „Zwanziger Jahre“ sagen und nicht „Zwölftausenddreiundzwanzig“ oder „zwölftausendzwanziger Jahre“. Insofern: Meinen Segen haben sie.

    Implementation: Python und Javascript

    Um nun mit gutem Beispiel voranzugehen, will ich selbst ein Gefühl für DIT bekommen. Dazu habe ich ein Python-Modul geschrieben, das Konversionen von Python-Datetimes von und nach DIT unterstützt. Das ist so wenig Code, dass ich lieber niemand verführen würde, das als Dependency zu importieren. Drum habe ich es auch nicht ins pyPI geschoben; guckt einfach in mein codeberg-Repo. Meine vorgeschlagene Vorgehensweise ist copy-paste (oder halt einfach das Modul in den eigenen Quellbaum packen).

    Das Modul funktioniert auch als Programm; legt es dazu einfach in euren Pfad und macht es ausführbar. Es zeigt dann eine DIT-Uhr in einem Tkinter-Fenster an. Ich habe das in meinen Sawfish-Dock aufgenommen – siehe das Eingangsbild.

    Ich habe außerdem noch ein Stück Javascript geschrieben, das DITs ausrechnen und anzeigen kann. Es ist eingebettet in der Datei dit.html im Repo oder unter https://blog.tfiu.de/media/2023/dit.html erreichbar. Menschen, die (ganz anders als ich) breit Tabs in ihren Browsern nutzen, können die Webseite öffenen und haben mit etwas Glück (wenn der Browser nämlich das Javascript auch …

  • Bruchsal zwischen Mandolinen und Soldaten

    Foto von ca. 20 Lederbändern mit aufgepressten Metallplättchen über Klaviersaiten mit einem Holzbügel mit der Aufschrift „Mandoline auf die Zapfen Z setzen”.

    Ein Detail eines automatischen Klaviers („Pianova“ von den Leipziger Musikwerken Paul Lochmann GmbH), ca. 1910. Was es mit diesen Lederstreifen auf sich hat, erzähle ich ziemlich gegen Ende dieses Posts.

    Unter den Einrichtungen, die beim Oberrhein-Musesumpass mitmachen, finden sich einige Burgen (z.B.) und Schlösser (nochmal z.B.). An Fronleichnam verschlug es mich in dieser Angelegenheit in die Residenz der Fürstbischöfe von Speyer in Bruchsal.

    Dafür, dass das Bistum Speyer eine recht überschaubare Herrschaft war, ist der Palast, den der Potentat Damian von Schönborn-Buchheim seinen Untertanen in den 1720er Jahren abgepresst hat, beeindruckend groß und großzügig. Noch überraschender angesichts des Duodezbauherren ist, dass beim Bau die Stars der damaligen Prunkbauten-Szene – etwa Balthasar Neuman oder Cosmas und Damian Asam – am Start waren.

    Der Fairness halber will ich einräumen, dass sich Schönborn-Buchheim ein Schloss ersetzen ließ, das Soldaten in einem Krieg kaputtgehauen hatten, mit dem er nichts zu tun hatte, und dass er selbst sich mit Kriegen und anderem Gemetzel vorbildlich zurückgehalten hat.

    Insofern darf mensch das völlig übertrieben große Treppenhaus und die Prunkhallen mit ihren nicht immer ganz geschmackssicheren und schon wegen ganzer Bände heidnischer Mythen entschieden unfrommen Deckenmalereien ohne viel schlechtes Gewissen goutieren, um so mehr, als die aktuellen Decken ohnehin aus der Nachkriegszeit stammen. Alliierte Bomber haben das Schloss nämlich bei Angriffen auf die in Bruchsal stationierten deutschen Zweite-Weltkrieger noch im März 1945 getroffen, woraufhin es ausgebrannt ist.

    Foto einer großen runden Mauer mit Durchblick auf ein hohes Deckenfresko

    Das Treppenhaus im Schloss von Bruchsal ist eigentlich klar überdimensioniert für das kleine Reich seiner Besitzer.

    Soldaten gehen und kommen

    Das mutmaßliche Ziel der Alliierten gehört zur bösen Geschichte Bruchsals: Seit den Zeiten des Fürstbischofs hatte das Städtchen eine Kaserne. 1922 befreiten die Demilitarisierungsregeln des Versailler Vertrags die BürgerInnen von den Soldaten, 1945 nochmal der Zusammenbruch der faschistischen Herrschaft. In beiden Fällen kamen die Soldaten leider bald wieder.

    Da die Bundeswehr Anfang der 1990er Jahre ein vorerst letztes Mal verschwunden ist („Friedensdividende“), ist Bruchsal zur Zeit wieder militärisch unbelastet. Die zurückbleibende Kaserne – ich kann der Abschweifung nicht widerstehen – nutzte die Landesregierung von Baden-Württemberg für ein besonders verdrehtes Experiment im Rahmen ihrer Privatisierungsstrategie für Hochschulen (vgl. neulich zu Bologna): Die International University in Germany, in die die öffentliche Hand mehrfach ein paar Millionen Euro versenkte, bevor der Laden erwartungsgemäß Pleite ging. Ich weiß nicht, was jetzt in der Kaserne ist; hoffentlich kommen die Soldaten nicht schon wieder zurück. Ganz sicher wäre selbst ein weiteres Experiment mit einer Privatuniversität weniger grässlich – und erheblich billiger sowieso.

    Die Guillotine von Plötzensee

    Aber zurück zum Schloss: Neben den Prunkräumen befindet sich dort heute das Stadtmuseum, das etwa die recht interessante Geschichte des Bruchsaler Knastes beleuchtet (bemerkenswerterweise ohne Erwähnung des dort bis vor 15 Jahren einsitzenden Promis Christian Klar). Insbesondere war mir ganz neu, dass der Scharfrichter des NS-Volksgerichtshofs im Strafgefängnis Plötzensee seinen Opfern mit der badischen Guillotine von Bruchsal die Köpfe abschlug – angesichts der eigentlich liberalen Tradition des Landes Baden hat diese Geschichte, finde ich, eine gewisse Ironie.

    Liberale Tradition? Ja, eine weitere Geschichte aus dem Stadtmuseum handelt vom Revolutionsjahr 1848, als Bruchsal schon einmal ohne langfristigen Erfolg die Soldaten abgeschüttelt hatte: Damals nämlich waren die Dragoner ausgerückt, um den Hecker-Aufstand niederzuschlagen. Als sie damit fertig und also zurück in Bruchsal waren, nahmen viele der für eine Weile freiheitlich gesinnten BürgerInnen gegen sie eine „drohende Haltung ein“. Der Museumstext weiter: „Schließlich musste das Militär nach Mannheim verlegt werden, um die Ruhe in Bruchsal nicht weiter zu gefährden.”

    Aber wie gesagt: Trotz dieses vorübergehend vorbildlichen BürgerInnensinns waren waren die Dragoner wenig später wieder da. In der Realität gewinnen halt doch meistens die Bösen.

    Musikautomaten

    Aber ich will die Realität nicht schelten, denn es gibt auch immer wieder bezaubernde Wunderdinge. Besonders viele davon sind im Musikautomaten-Museum versammelt, das ebenfalls mit dem Schloss-Ticket besichtigt werden kann (ihr solltet mindestens zwei Stunden dafür einplanen). Dort hatte ich die Einsicht, dass es neben der Analog-Schallplatte („Schellack“) rund um 1900 herum eine ernstzunehmende digitale Konkurrenz durch Lochplatten gab:

    Eine Metallplatte mit eingestanzen Löchern entlang von konzentrischen Spuren in einem Abspielmechanismus mit hölzernem Kasten.

    In diesen Platten sind Impulse kodiert, die Stimmzungen oder Pfeifen ansteuerten; jede Spur entsprach einem Ton, so dass der Zauber schon nach einer Umdrehung etwas repititiv wurde – aber sie drehten natürlich auch viel langsamer als die 45 Umdrehungen pro Minute zeitgenössischer Schallplatten, und ihre Klangqualität war um Längen besser. Das Bruchsaler Muserum hat zahlreiche Mechanismen, die Platten dieser Art mit verschiedenen Techniken abspielten.

    Mir ist beim Blick auf die Dinger durch den Kopf gegangen, dass ein wesentliches Problem der Technologie gegenüber der Schellackplatte neben der eher kurzen Spieldauer fehlende Standards gewesen sein dürften, also etwa: Wie groß soll die Platte sein? Wie schnell soll sie gedreht werden? Welche Zähnung hat die Antriebsspur? Welche Spur macht welchen Ton? Beim Schellack konnte recht bald jede Platte auf jedem Gerät gespielt werden, hier ziemlich sicher nicht.

    Putziges Implementationsdetail zur letzten Frage: Weil Basslinien in normalen Stücken deutlich langsamer sind als Läufe im Sopran, entsprechen innere (also kürzere) Spuren bei den Geräten fast immer tieferen Tönen.

    Was mich allerdings am meisten hingerissen hat: Der Hack vom Aufmacherfoto, nämlich auf Lederbänder gepresste Blechlein. Diese Teile haben einem mechanischen Klavier wirklich so eine Art Mandolinenklang (also: etwas schnarrend) beigebracht, und zwar indem der Holzbügel, der im oberen Teil des Fotos erkennbar ist, die Lederbänder auf die Saiten gedrückt hat, vielleicht ein wenig wie bei einem Dämpfer. Erstaunlicherweise haben dann die mitschwingenden Blechlein wirklich den Toncharakter erheblich verändert. Ob ich den neuen Klang ohne Vorsagen „Mandoline“ genannt hätte, lasse ich mal offen.

    Ein Rat noch: Nehmt euch Gehörschutz mit. Etliche der Maschinen waren für Jahrmärkte und laute Kneipen gedacht und von der Lautstärke her entsprechend ausgelegt.

  • Oracle, Mozilla, W3C: Broken Links, Broken Web, Slugs in a Bucket

    Type plate of a DEC PDP 12 minicomputer.  A large rotary switch is set to offline.

    This post is about why today I had intense fantasies about having an offline switch at least as large as the one on this DEC PDP-12 photographed at Vienna Observatory in 2018.

    I give you it has become somewhat trite to state the obvious: The Web (and quite possibly most of the Internet) is broken. Admittedly, a few relatively simple measures – a few well-placed dnsmasq statements, a default-no-javascript browser and a general avoidance of for-profit pages – can hide quite a bit of that brokenness. Today, however, it hit me hard; and two nonprofits were part of what makes me once more seriously consider a livelihood as a gardener.

    A long history of evil getting worse

    I must concede that it didn't take the pure evil of Oracle to fool around with redirects instead of 404s and to wantonly tear down static pages that could have been maintained forever at zero cost. Even the lesser evil that Sun was did that, as evinced by this 2006 capture from the Web Archive. That's already less than ten years after JDK 1.1 was released a quarter century ago (February 1997).

    Which brings me to another reason to want to retire: This link has been broken for more than 15 years now. I suspect I'm imagining the W3C to be a lot better staffed than it actually is, and yes, touching normative content is a tricky thing to do. But either through an Erratum or as an editorial change the W3C could and should have fixed the clickjacked link some time within those 15 years. That would have been particularly thoughtful given that my reaction probably isn't too untypical: Rather than go to the Internet Archive to fetch the original documentation right away, I looked for DecimalFormat in today's Java documentation. And yay!, it says:

    It also supports […] scientific notation […] Example: "0.###E0" formats the number 1234 as "1.234E3".

    Except it didn't for me. The numbers were stubbornly unformatted regardless of what I did as soon as I tried any sort of scientific notation. Which drove me entirely crazy until I ran the browser from a terminal and saw log output there:

    error
    xsltFormatNumberConversion : error in format string '0.###E0', using default
    

    It still took a while until I realised the problem was not actually with the format string – that I had taken literally from the modern documentation: Gnah! – but that JDK 1.1 just didn't have the feature. I actually had to go to an older capture of Sun's documentation to figure that out (do donate to the Internet Archive, please).

    That was not the end of my webaches of the day.

    Mozilla: Crashes in the supply chain

    Proclamation: I'm a huge fan of the MDN whenever I do “web development“. You may hence get an idea of my desperation when, minutes after I had somewhat worked out the Sun-Oracle-W3C debacle, I was looking up the border-spacing CSS property at MDN and just saw a brief flash of content and then this:

    Screenshot: an empty browser window

    What deviltry…? From folks who are at least reasonably concerned about standards compliance and accessability? Well, a quick glance into the developer tools made my me shiver. Here's what I found in the error console:

    [Error] The source list for Content Security Policy directive 'script-src' contains an invalid source: ''report-sample''. It will be ignored.
    [and a lot more CSP stuff]
    [Error] Failed to load resource: Unacceptable TLS certificate (analytics.js, line 0)
    "Error executing Glean task: NotFoundError: Failed to execute 'transaction' on 'IDBDatabase': One of the specified object stores was not found."
    [Error] (Glean.core.Dispatcher) – "Error initializing dispatcher, won't execute anything further." – "There might be more error logs above."
    [Error] TypeError: null is not an object (evaluating 'localStorage.getItem')
    [Error] TypeError: null is not an object (evaluating 'localStorage.getItem')
    

    So… the Mozilla folks list all kinds of new-fangled (i.e., bullseye Webkit doesn't know about them) Content Security Policies. I'll allow they do that out of concern for my privacy – though frankly, I'm not much worried about cross-site scripting from MDN, in particular because the site generally works fine without Javascript (I don't remember when or why I switched it on for them). Be that as it may, after all the CSP-ing, they turn around and try to rat me out to Google analytics (which fails here because google-analytics.com resolves to 127.0.0.1 on my boxes). Hu?

    Want to report a bug? Feed Microsoft!

    Anyway, I had Javascript on, and then I got the blank page because rendering crashes when the thing cannot do Javascript local storage. If you wonder why I keep that off by default, check out my rant against it; sorry: in German). That's why I get a blank page. It all goes belly-up starting with a message “Unable to read theme from localStorage“ from witin some React code. Call me a purist, but I don't even see how you'd need the ghastly React Javascript “framework“ <cough> for a documentation site. Why this would need to store a theme at all and why this should be crashing the whole doument[2] when it can't do that is entirely beyond me.

    Given that Mozilla are (to some extent) good guys, I wanted to file a bug. They also were home to the premier bugtracker of the 2000s, bugzilla, so… No. Even the contribution guide [warning: github link] – a static document! – sits in github, collecting behavioural surplus for Microsoft.

    Ah… sorry, Mozilla, if React and github are your ideas of an open, accessible, and standards-compliant Web, there is no point filing bugs against MDN. That's not a good use of the surveillance capital I have to give.

    But then: being a gardener isn't all that idyllic either:

    Lots of slugs in a bucket

    Given that, Oracle and Mozilla are not much worse. So, I guess I'll keep doing computers for a little while, at least as long as I can somehow still work around the postmodern Web's breakage most of the time.

    [1]Well, actually I was looking at a local mirror of that, but if turns out that looking at the online material right away wouldn't have made a difference.
    [2]I just notice that the existence of the phrase “crashing a document“ is a rather characteristic symptom of the state of the Web.
  • Newszone-Urteil: Keine Rauschmedien-Prävention, nirgends

    Foto eines Münchner Zeitungsständers mit Schlagzeile: „Schnösel-Mob greift Polizei-Wache an!“

    München, im Juli 2019: Die freie Presse™ kommt ihrem Informationsauftrag nach.

    Manchmal entscheiden Gerichte zwar nachvollziehbar, aber aus jedenfalls ethisch oder praktisch ganz falschen Gründen. Heute zum Beispiel hat das Landgericht Stuttgart dem SWR untersagt, Newszone zu betreiben (der SWR selbst dazu).

    Das Urteil kann ich vom Ergebnis her nachvollziehen, denn, soweit ich das nach einer schnellen Inspektion der Newszone-Webseite beurteilen kann, ist das eine öffentliche Subvention für die privaten Infrastrukturen von Apple und Google. Mit anderen Worten: öffentlich finanzierte Inhalte sind bei Newszone für die Öffentlichkeit nur zugänglich, wenn sie sich überwachungskapitalistischen Praktiken unterwirft. Obendrauf geschieht das weitgehend ohne Not, da die App sehr wahrscheinlich nichts kann, das nicht auch im Browser oder auf einem Desktop-Client funktionieren würde.

    Ein möglicher guter Grund

    Demgegenüber hätte ein wohlfunktionierendes Gemeinweisen ein einklagbares Recht auf Zugang zu öffentlichen Daten und Diensten über offene, gemeinschaftskontrollierte Standards. Dieses Recht gibt es leider nicht. Und so konnte, jaja, das Gericht diese Urteilsbegründung auch nicht nutzen.

    Ich hätte gerade noch Verständnis gehabt für ein Urteil, das sagt, ein Angebot, das (ausweislich der Teaser, die gerade auf der offen zugänglichen Webseite stehen) zur Hälfte besteht aus Meldungen wie:

    • Kevin Spacey: Freigesprochen vom Vorwurf der sexuellen Belästigung
    • Messerangriff: Mann verletzt 16-Jährige und ihre Mutter
    • Corona-Nachwirkungen: Darunter leidet Elevator-Boy Jacob
    • So geht es mit Silent Hill weiter
    • DAS darf Fat Comedy jetzt nicht mehr machen
    • Hat Sascha Koslowski eine neue Freundin?
    • Verfolgungsjagd und Unfälle: Trennung endet bei der Polizei!
    • Aus Kader geflogen: Das sagt Ronaldo jetzt!
    • Fall gelöst: Polizei weiß jetzt, wer diese Frau ist!

    vielleicht nicht in öffentlichem Interesse ist und mithin auch nicht aus öffentlichem Geld zu finanzieren ist. Allerdings hätte ich das für eine gefährliche Argumentation gehalten, so sehr es mich auch reut, wenn meine GEZ-Beiträge in Volksmusikshows und Bundesliga fließen statt in Deutschland- oder Zündfunk und Sendung mit der Maus. Solange mir aber die Bundesliga-Leute nicht mein Forschung aktuell nehmen wollen, ist ein gewisses Maß an Boulevard wahrscheinlich nicht zuletzt taktisch gut, denn werden diese Inhalte noch kommerzieller aufbereitet, sind sie sicher sozial wie individuell deutlich schädlicher.

    Gericht: Presse ist Clickbait

    Das war aber ohnehin auch nicht die Argumentation des Gerichts. Das Landgericht Stuttgart hat die App untersagt, weil sie zu presseähnlich sei. Zunächst würde ich, wäre ich der Kläger – ein Verband kommerzieller Medienunternehmen aus Baden-Württemberg – diese Urteilsbegründung als Beleidigung auffassen. Wenn nämlich die Sammlung von grenzdebilem Clickbait, die mir Newszone größtenteils zu sein scheint, presseähnlich sein sollte, müsste ich mich aufhängen, wenn ich die Sorte von Presse sein wollte, von der Thomas Jefferson mal gesagt hat:

    Unsere Freiheit kann nicht erhalten werden ohne die Pressefreiheit. Und diese kann nicht beschränkt werden, ohne dass ihr Verlust droht.

    Das Dramatische an dem Urteil ist jedoch, dass es die ständige Rechtsprechung vertieft, nach dem der Staat das Geschäftsmodell kommerzieller Medien irgendwie zu schützen und deshalb eine künstliche Verknappung redaktioneller Inhalte zu verordnen hat. Nicht nur als täglicher Konsument der DLF-Presseschau bestreite ich das. Klar: Für eine sinnvolle Meinungsbildung sind freie Medien, ganz wie Jefferson sagte, zweifellos unverzichtbar.

    Es gibt keine Freiheit im Unternehmen

    Doch sind kommerzielle Medien jedenfalls unter Bedingungen des faktischen Arbeitszwangs nicht frei, denn ihre AutorInnen schreiben immer unter der latenten oder (weit häufiger, da sie ja normalerweise keine ordentlichen Arbeitsverträge mehr haben) offenen Drohung, bei unbotmäßigen Äußerungen ihre Existenzgrundlage zu verlieren. Entsprechend sind sich zumindest die Kommentarspalten in aller Regel erschreckend einig, wenn es um Klasseninteressen[1] geht, seien sie nun Hartz IV oder die Privatisierungspolitik.

    Nun operieren auch öffentlich-rechtliche Medien unter Bedingungen des Arbeitszwangs, und inzwischen ist auch dort die breite Mehrheit der AutorInnen prekär (feste Freie) bis ultraprekär (Freie) beschäftigt, was ihre Freiheit gerade angesichts selbstherrlicher IntendantInnen logischerweise auch stark einschränkt. Aber ohne den Druck der privaten Medien ließe sich da wieder viel mehr Tarifbindung – und damit Freiheit – durchsetzen, und immerhin hat Selbstherrlichkeit weniger einseitige Wirkungen als Klasseninteresse.

    Nach dieser Überlegung besteht selbstverständlich ein großes gesellschaftliches Interesse an möglichst breiten Angeboten und breit zugänglichen von öffentlich-rechtlichen oder noch besser gänzlich ohne Bedrohung der Existenz der AutorInnen produzierten Medien. Mit Bildzeitung und Big Brother hat das aber nichts zu tun. Im Gegenteil liegt die Schädlichkeit dieser Sorte kommerzieller Medien auf der Hand.

    Rauschmediensteuer?

    Nun sollen mündige BürgerInnen natürlich Cannabis, Tabak oder Alkohol erwerben und konsumieren und mithin zumindest in Maßen auch produzieren dürfen. Ich argumentiere also ganz entschieden nicht für ein Verbot kommerzieller Medien, und seien sie auch so offensichtlich auf Rausch und Abhängigkeit optimiert wie Springerpresse, Bertelsmann-Fernsehen oder die Angebote von Meta. Wobei… die aktuellen Ereignisse im UK, die es gewiss ohne die Dauerhetze der Murdoch-Medien[2] so nicht gäbe, könnten durchaus Argumente liefern. Aber wie dem auch sei: Es gibt wegen ihrer mangelnden Bedeutung für Medien- und Informationsfreiheit gewiss kein öffentliches Interesse an der Erhaltung der Geschäftsmodelle privater Medien.

    Wenn es nun umgekehrt schon keine – aus meiner Sicht naheliegende – Rauschmediensteuer gibt in Analogie zur Branntweinsteuer (oder was immer inzwischen aus ihr geworden ist), so sollten Staat und Gerichte jedenfalls nicht ungiftigere Medien daran hindern, NutzerInnen von kommerziellen Medien tendenziell zu entwöhnen.

    Deshalb: Das LG Stuttgart hat aus den falschen Gründen nachvollziehbar entschieden. Es ist nun Job der Gesellschaft, diese falschen Gründe wegzukriegen.

    [1]Wer bei diesem Wort zuckt: Tja. Mit ordentlich freien Medien würde es allenfalls ein mildes Achselzucken auslösen. Klasseninteressen existieren so unbestreitbar wie der Klimawandel, und bis auf Weiteres bestimmen sie das politische Leben noch deutlich stärker. Immerhin das dürfte sich aber in den nächsten paar Jahrzehnten ändern…
    [2]So gesehen sollten wir Axel Springer dankbar für sein Erbe sein, das uns zumindest Murdochs Produkte vom Hals gehalten hat.
  • SPARQL 4: Be Extra Careful on your Birthday

    A Yak staring at the photographer

    Enough Yak Shaving already.

    I am now three parts into my yak-shaving investigation of Wikidata and SPARQL (one, two, three) with the goal of figuring out whether birthdays are more or less dangerous – measured by whether or not people survive them – than ordinary days.

    At the end of the last instalment I was stumped by timeouts on Wikidata, and so much of this post is about massaging my query so that I get some answers in the CPU minute that one gets on Wikidata's triplestore. While plain optimisation certainly is necessary, working on six million people within a minute is probably highly ambitious whatever I do[1]. I will hence have to work on a subset. Given what I have worked out in part 3,

    # This will time out and just waste CPU.
    SELECT (count(?person) AS ?n)
    WHERE {
      ?person rdfs:label ?personName.
      ?person wdt:P569 ?bdate.
      hint:Prior hint:rangeSafe true.
      ?person wdt:P570 ?ddate.
      hint:Prior hint:rangeSafe true.
    
      FILTER (MONTH(?bdate)>1 || DAY(?bdate)>1)
      FILTER (MONTH(?bdate) = MONTH(?ddate)
        && DAY(?bdate) = DAY(?ddate)
        && YEAR(?bdate) != YEAR(?ddate))
      FILTER (YEAR(?bdate)>1850)
    
      FILTER (lang(?personName)="en")
    }
    

    – how do I do a subset? Proper sampling takes almost as much time as working with the whole thing. But for now, I'd be content with just evaluating my query on whatever subset Wikidata likes to work on. Drawing such a (statiscally badly sampled) subset is what the LIMIT clause you have already seen in part one does. But where do I put it, since, if things work out, my query would only return a single row anyway?

    Subqueries in SPARQL, and 50 Labels per Entity

    The answer, as in SQL, is: A subquery. In SPARQL, you can have subqueries like this:

    SELECT (count(?person) as ?n)
    WHERE {
      { SELECT ?person ?personName ?bdate ?ddate
        WHERE {
          ?person rdfs:label ?personName;
            wdt:P569 ?bdate;
            wdt:P570 ?ddate.
        }
        LIMIT 50
      }
      FILTER (lang(?personName)="en")
    }
    

    – so, within a pair of curly braces, you write another SELECT clause, and its is then the input for another WHERE, FILTER, or other SPARQL construct. In this case, by the way, I'm getting 50 records with all kinds of labels in the subquery and then filter out everything that's not English. Amazingly, only one record out of these 50 remains: there are clearly at least 50 statements on labels for the first entity Wikidata has picked here.

    Raising the innner limit to 500, I get 10 records. For the particular sample that Wikidata chose for me, a person indeed has 50 labels on average. Wow. Raising the limit to 5000, which probably lowers the the pharaohs to non-pharaohs in the sample, gives 130 records, which translates into 38 labels per person.

    Clearly, adding the labels is an expensive operation, and since I do not need them for counting, I will drop them henceforth. Also, I am doing my filtering for off-January 1st birthdays in the subquery. In this way, I probably have a good chance that everything coming out of the subquery actually counts in the outer filter, which means I can compute the rate of people dying on their birthday by dividing my count by the limit.

    Let's see where this gets us:

    SELECT (count(?person) AS ?n)
    WHERE {
      { SELECT ?person ?bdate ?ddate
        WHERE {
          ?person wdt:P569 ?bdate.
          hint:Prior hint:rangeSafe true.
          ?person wdt:P570 ?ddate.
           FILTER (MONTH(?bdate)>1 || DAY(?bdate)>1)
          FILTER (YEAR(?bdate)>1850)
          hint:Prior hint:rangeSafe true.
        }
        LIMIT 500
      }
    
      FILTER (MONTH(?bdate) = MONTH(?ddate)
        && DAY(?bdate) = DAY(?ddate)
        && YEAR(?bdate) != YEAR(?ddate))
      hint:Prior hint:rangeSafe true.
    }
    

    Named Subqueries and Planner Barriers

    That's returning a two for me, which is not implausible, but for just 500 records it ran for about 20 seconds, which does not feel right. Neither pulling the 500 records nor filtering them should take that long.

    When a database engine takes a lot longer than one thinks it should, what one should do is take look at the query plan, in which the database engine states in which sequence it will compute the result, which indexes it intends to use, etc.

    Working out a good query plan is hard, because in general you need to know the various partial results to find one; in my example, for instance, the system could first filter out everyone born after 1850 and then find the death dates for them, or it could first match the death dates to the birthdays (discarding everything that does not have a death day in the process) and then filter for 1850. Ff there were may people with birthdays but no death days (for instance, because your database talks mostly about living people), the second approach might be a lot faster. But you, that is, the database engine, have to have good statistics to figure that out.

    Since that is such a hard problem, it is not uncommon that the query planner gets confused and re-orders operations such that things are a lot slower than they would be if it hadn't done the ordering, and that's when one should use some sort of explain feature (cf. Wikidata's optimisation hints). On Wikidata, you can add an explain=detail parameter to the query and then somehow display the bunch of HTML you get back.

    I did that and, as I usually do when I try this kind of thing, found that query plans are notoriously hard to understand, in particular when one is unfamiliar with the database engine. But in the process of figuring out the explain thing, I had discovered that SPARQL has the equivalent of SQL's common table expressions (CTEs), which gave me an excuse to tinker rather than think about plans. Who could resist that?

    In SPARQL, CTEs are called named subqueries and used like this:

    SELECT (count(?person) AS ?n)
    WITH { SELECT ?person ?bdate ?ddate
        WHERE {
          ?person wdt:P569 ?bdate;
          hint:Prior hint:rangeSafe true.
          ?person wdt:P570 ?ddate.
          hint:Prior hint:rangeSafe true.
           FILTER (MONTH(?bdate)>1 || DAY(?bdate)>1)
          FILTER (YEAR(?bdate)>1850)
        }
        LIMIT 30000
      } AS %selection
    WHERE {
      INCLUDE %selection
    
      FILTER (MONTH(?bdate) = MONTH(?ddate)
        && DAY(?bdate) = DAY(?ddate)
        && YEAR(?bdate) != YEAR(?ddate))
      hint:Prior hint:rangeSafe true.
    

    – you write your subquery in a WITH-block and give it a name that you then INCLUDE in your WHERE clause. In several SQL database systems, such a construct provides a planner barrier, that is, the planner will not rearrange expressions across a WITH.

    So does, according to the optimisation hints, Wikidata's current SPARQL engine. But disappointingly, things don't speed up. Hmpf. Even so, I consider named subexpresisons more readable than nested ones[2], so for this post, I will stay with them. In case you come up with a brilliant all-Wikidata query, you probably want to go back to inline subqueries, though, because then you probably do not want to constrain the planner too much.

    Finally: Numbers. But what do they Mean?

    With my barrier-protected subqueries, I have definitely given up on working with all 6 million persons with birthdays within Wikidata. Interestingly, I could go from a limit of 500 to one of 30'000 and stay within the time limit. I never went back to try and figure out what causes this odd scaling law, though I'd probably learn a lot if I did. I'd almost certainly learn even more if I tried to understand why with a limit of 50'000, the queries tended to time out. But then 30'000 persons are plenty for my purpose provided they are drawn reasonably randomly, and hence I skipped all the tempting opportunities to learn.

    And, ta-da: With the above query, I get 139 matches (i.e., people who died on their birthdays).

    What does that say on the danger of birthdays? Well, let's see: If birthdays were just like other days, one would expect 30'000/365 deaths on birthdays in this sample, which works out to a bit more than 80. Is the 140 I am finding different from that 80 taking into account statistical noise? A good rule of thumb (that in the end is related to the grand central limit theorem) is that when you count n samples, your random error is something like (n) if everything is benevolent. For 140, that square root is about 12, which we physicist-hackers like to write as σ = 12, and then we quickly divide the offset (i.e., 140 − 80 = 60) by that σ and triumphantly proclaim that “we've got a 5-σ effect”, at which point everyone is convinced that birthdays are life-threatening.

    This is related to the normal distribution (“Gauss curve”) that has about 2/3s of its area within “one σ” (which is its width as half maximum and would be the standard deviation of something you draw from such a distribution), 95% of …

  • SPAQRL 3: Who Died on their Birthdays?

    Many Yaks grazing on a mountain meadow

    A lot of Yak Shaving left to do here.

    Now that I have learned how to figure out dates of birth and death in Wikidata and have made myself sensible tools to submit queries, I can finally start to figure out how I can let Wikidata pick out people dying on the same day of the year they were born on, just like Mary Lea Heger.

    I first fetched people with their birthdays and dates of death:

    SELECT ?person ?bday ?dday
    WHERE {
      ?person wdt:P569 ?bday.
      ?person wdt:P570 ?dday.
    }
    LIMIT 2
    

    Consider that query for a while and appreciate that by giving two triple patterns using the same variable, ?person, I am performing what in SQL databases would be a join. That that's such a natural thing in SPARQL is, I'd say, a clear strong point for the language.

    Here is what I got back when I ran this this through my wpd shell function from part two:

    person=http://www.wikidata.org/entity/Q18722
    dday=-1871-06-29T00:00:00Z
    bday=-2000-01-01T00:00:00Z
    
    person=http://www.wikidata.org/entity/Q18734
    dday=-1884-01-01T00:00:00Z
    bday=-2000-01-01T00:00:00Z
    

    This seems to work, except the dates look a bit odd. Did these people really die more than a hundred years before they were born? Ah, wait: these are negative dates. Opening the person URIs as per part one in a browser, I one learns that Q18722 is pharaoh Senusret II, and at least his birthday clearly is… not very certain. If these kinds of estimates are common, I probably should exclude January 1st from my considerations.

    Getting Labels

    But the first thing I wanted at that point was to not have to click on the URIs to see names. I knew enough about RDF to simply try and get labels according to RDF schema:

    SELECT ?personName ?bday ?dday
    WHERE {
      ?person rdfs:label ?personName.
      ?person wdt:P569 ?bday.
      ?person wdt:P570 ?dday.
    }
    LIMIT 10
    

    That's another SQL join, by the way. Nice. Except what comes back is this:

    dday=-2596-01-01T00:00:00Z
    bday=-2710-01-01T00:00:00Z
    personName=Хуан-ди
    
    dday=-2596-01-01T00:00:00Z
    bday=-2710-01-01T00:00:00Z
    personName=Huang Di
    
    dday=-2596-01-01T00:00:00Z
    bday=-2710-01-01T00:00:00Z
    personName=ኋንግ ዲ
    
    dday=-2596-01-01T00:00:00Z
    bday=-2710-01-01T00:00:00Z
    personName=هوان جي دي
    
    dday=-2596-01-01T00:00:00Z
    bday=-2710-01-01T00:00:00Z
    personName=Emperador mariellu
    

    If you select the URI in ?person in addition to just the name, you'll see that we now got many records per person. Actually, one per label, as is to be expected in a proper join, and since there are so many languages and scripts out there, many persons in Wikidata have many labels.

    At this point I consulted Bob DuCharme's Learning SPARQL and figured a filter on the language of the label is what I wanted. This does not call for a further join (i.e., triple pattern), as the language is something like a property of the object literal (i.e., the string) itself. To retrieve it, there is a function determining the language, used with a FILTER clause like this:

    SELECT ?personName ?bday ?dday
    WHERE {
      ?person rdfs:label ?personName.
      ?person wdt:P569 ?bday.
      ?person wdt:P570 ?dday.
    
      FILTER (lang(?personName)="en")
    }
    LIMIT 10
    

    FILTER is a generic SPARQL thing that is more like a SQL WHERE clause than SPARQL's WHERE clause itself. We will be using it a lot more below.

    There is a nice alternative to this kind of joining and filtering I would have found in the wikidata user manual had I read it in time. You see, SPARQL also defines a service interface, and that then allows queriers to mix and match SPARQL-speaking services within a query. Wikidata has many uses for that, and one is a service that can automatically add labels with minimal effort. You just declare that you want that service to filter your query, and then you write ?varLabel to get labels instead of URIs for ?var, as in:

    # don't run this.  It'll put load on Wikidata and then time out
    SELECT ?personLabel ?bday ?dday
    WHERE {
      ?person wdt:P569 ?bday.
      ?person wdt:P570 ?dday.
    
      SERVICE wikibase:label {
        bd:serviceParam wikibase:language "en" .
      }
    }
    LIMIT 10
    

    The trouble with that: This would first pull out all result triples (a couple of million) out of wikidata and then hand over these triples to the wikibase:label service, which would then add the labels and hand back all the labelled records. Only then will the engine discard the all result rows but the first 10. That obviously is terribly inefficient, and Wikidata will kill this query after a minute.

    So: Be careful with SERVICE when intermediate result sets could be large. I later had to use subqueries anyway; I could have used them here, too, to work around the millions-of-triples problem, but at that point I hadn't progressed to these subqueries, let alone means to defeat the planner (that's part four material).

    Determining Day and Month

    Turtle (about my preference for which you could already read in part two) has a nifty abbreviation where you can put a semicolon after a triple and then leave out the subject in the next triple. SPARQL will put the previous subject into this next triple. That works in SPARQL, too, so I can write my query above with fewer keystrokes:

    SELECT ?personName ?bday ?dday
    WHERE {
      ?person rdfs:label ?personName;
        wdt:P569 ?bday;
        wdt:P570 ?dday.
    
      FILTER (lang(?personName)="en")
    }
    LIMIT 10
    

    Now I need to figure out the birthday, as in: date within a year. In Bob DuCharme's book I found a SUBSTR function, and BIND clauses that let you compute values and bind them to variables. Treating the dates as ISO strings (“YYYY-MM-DD“) should let me pull out month and date starting at index 6 (gna: SPARQL starts with index 1 rather than with 0 as all deities in known history wanted), and then five characters, no? Let's see:

    SELECT ?personName ?bdate ?ddate
     WHERE {
       ?person rdfs:label ?personName;
         wdt:P569 ?bday;
         wdt:P570 ?dday.
    
       BIND (SUBSTR(?bday, 6, 5) as ?bdate)
       BIND (SUBSTR(?dday, 6, 5) as ?ddate)
    
       FILTER (lang(?personName)="en")
     }
     LIMIT 3
    

    This gives:

    personName=Sobekhotep I
    bdate=-01-0
    ddate=-01-0
    
    personName=Amenemhat I
    ddate=-02-1
    
    personName=Senusret II
    bdate=-01-0
    ddate=-06-2
    

    Well, that is a failure. And that's because my assumptions on string indices are wrong in general, that is: for people born before the Christian era, and then again for people born after 9999-12-31. Which makes we wonder: Does Wikidata have people born in the future? Well, see below.

    I'll cheat a bit and spare you a few of the dead alleys I actually followed trying to fix this, because they are not very educational. Also, when I strugged with the timeouts I will discuss in a moment I learned about the right way to do this on Wikidata's optimisation page: When something is a date, you can apply the functions DAY, MONTH, and YEAR on it, and that will plausibly even use indexes and thus be a lot faster.

    Thinking about YEAR and having seen the fantasy dates for the ancient Egyptian pharaohs, I also decided to exclude everyone before 1850; that ought to suffice for letting me forget about Gregorian vs. Julian dates, and the likelihood that the dates are more or less right ought to be a lot higher in those days than in the 14th century, say.

    With that, I can write the “birth day equals death day“ in a filter without further qualms. The resulting query is starting to look imposing:

    SELECT ?person ?personName ?bdate ?ddate
    WHERE {
      ?person rdfs:label ?personName.
      ?person rdfs:label  wdt:P569 ?bdate.
      hint:Prior hint:rangeSafe true.
      ?person rdfs:label wdt:P570 ?ddate.
      hint:Prior hint:rangeSafe true.
    
      FILTER (MONTH(?bdate) = MONTH(?ddate)
        && DAY(?bdate) = DAY(?ddate))
      FILTER (YEAR(?bdate)>1850)
    
      FILTER (lang(?personName)="en")
    }
    LIMIT 10
    

    The odd triples with hint:Prior are hints for Wikidata's triple store that, or so I understand Wikidata's documentation, encourages it to use indexes on the properties mentioned in the previous lines; the query certainly works without those, and I frankly have not bothered to see if they actually do anything at all for the present queries. There. Accuse me of practising Cargo Cult if you want.

    Anyway, the result is looking as awful as I had expected given my first impressions with January 1st, and clearly, ensuring birthdays after 1850 is not enough to filter out junk:

    person=http://www.wikidata.org/entity/Q112689783
    ddate=0080-01-01T00:00:00Z
    bdate=1920-01-01T00:00:00Z
    personName=Enrico Mezzasalma
    
    person=http://www.wikidata.org/entity/Q19976926
    ddate=1342-01-01T00:00:00Z
    bdate=2000-01-01T00:00:00Z
    personName=Peter Jonsson
    
    person=http://www.wikidata.org/entity/Q19291026
    ddate=1400-01-01T00:00:00Z
    bdate=2000-01-01T00:00:00Z
    personName=Galceran Marquet
    ...
    

    It seems Wikidata even uses 2000-01-01 as some sort of NULL value. Dang. Let's filter out January 1st, then:

    SELECT ?person ?personName ?bdate ?ddate
    WHERE {
      ?person rdfs:label …
  • SPARQL 2: Improvising a client

    A Yak on a mountain path, watching the observer

    There is still a lot of hair on the Yak I am shaving in this little series of posts on SPARQL. All the Yaks shown in the series lived on the Valüla Mountain in Vorarlberg, Austria.

    This picks up my story on figuring out whether birthdays are dangerous using SPRAQL on Wikidata. You can probably skip this part if you're only interested in writing SPARQL queries to Wikidata and are happy with the browser form they give you. But you shouldn't. On both accounts.

    At the end of part one, I, for one, was unhappy about the Javascript-based UI at Wikidata and had decided I wanted a user interface that would let me edit my queries in a proper editor (in particular, locally on my machine, giving me the freedom to choose my tooling).

    My browser's web inspector quickly showed me that the non-Javascript web UI simply sent a query argument to https://query.wikidata.org/sparql. That's easy to do using curl, except I want to read the argument from a file (that is, the one I am editing in my vi). Helpfully, curl's man page informs on the --form option:

    This enables uploading of binary files etc. To force the 'content' part to be a file, prefix the file name with an @ sign. To just get the content part from a file, prefix the file name with the symbol <. The difference between @ and < is then that @ makes a file get attached in the post as a file upload, while the < makes a text field and just get the contents for that text field from a file.

    Uploads, Multipart, Urlencoded, Oh My!

    In this case, Wikidata probably does not expect actual uploads in the query argument (and the form does not submit it in this way), so < it ought to be.

    To try it, I put:

    SELECT ?p ?o
    WHERE {
      wd:Q937 ?p ?o.
    }
    LIMIT 5
    

    (the query for everything Wikidata says about Albert Einstein, plus a LIMIT clause so I only pull five triples, both to reduce load on Wikidata and to reduce clutter in my terminal while experimenting) into a file einstein.rq. And then I typed:

    curl --form query=<einstein.rq https://query.wikidata.org/sparql
    

    into my shell. Soberingly, this gives:

    Not writable.
    

    Huh? I was not trying to write anything, was I? Well, who knows: Curl, in its man page, says that using --form does a POST with a media type of multipart/form-data, which many web components (mistakenly, I would argue) take as a file upload. Perhaps the remote machinery shares this misconception?

    Going back to the source of https://query.wikidata.org/, it turns out the form there does a GET, and the query parameter hence does not get uploaded in a POST but rather appended to the URL. Appending to the URL isn't trivial with curl (I think), but curl's --data option at least POSTs the parameters in application/x-www-form-urlencoded, which is what browsers do when you don't have uploads. It can read from files, too, using @<filename>. Let's try that:

    curl --data query=@einstein.rq https://query.wikidata.org/sparql
    

    Oh bother. That returns a lenghty message with about a ton of Java traceback and an error message in its core:

    org.openrdf.query.MalformedQueryException: Encountered " <LANGTAG> "@einstein "" at line 1, column 1.
    Was expecting one of:
        "base" ...
        "prefix" ...
        "select" ...
        "construct" ...
        "describe" ...
        "ask" ...
    

    Hu? Apparently, my query was malformed? Helpfully, Wikidata says what query it saw: queryStr=@einstein.rq. So, curl did not make good on its promise of putting in the contents of einstein.rq. Reading the man page again, this time properly, I have to admit I should have expected that: “if you start the data with the letter @“, it says there (emphasis mine). But haven't I regularly put in query parameters in this way in the past?

    Sure I did, but I was using the --data-urlencode option, which is what actually simulates a browser and has a slightly different syntax again:

    curl --data-urlencode query@einstein.rq https://query.wikidata.org/sparql
    

    Ha! That does the trick. What comes back is a bunch of XML, starting with:

    <sparql xmlns='http://www.w3.org/2005/sparql-results#'>
      <head>
        <variable name='p'/>
        <variable name='o'/>
      </head>
      <results>
        <result>
          <binding name='p'>
            <uri>http://schema.org/version</uri>
          </binding>
          <binding name='o'>
            <literal datatype='http://www.w3.org/2001/XMLSchema#integer'>1692345626</literal>
          </binding>
        </result>
    

    Making the Output Friendlier: Turtle?

    Hm. That's not nice to read. I thought: Well, there's Turtle, a nice way to write RDF triples in plain text. In RDF land, people rather regularly support the HTTP accept header, a wildly underused and cool feature of HTTP that lets a client say what kind of data it would like to get (see Content negotiation in the Wikipedia). So, I thought, perhaps I can tell Wikidata to produce Turtle using accept?

    This plan looks like this when translated to curl:

    curl --header "accept: text/turtle" \
      --data-urlencode query@einstein.rq https://query.wikidata.org/sparql
    

    Only, the output does not change, Wikidata ignores my request.

    Thinking again, it is well advised to do so (except it could have produced a 406 Not Acceptable response, but that would probably be even less useful). The most important thing to remember from part one is that RDF talks about triples of subject, predicate, and object. In SPARQL, you have a SELECT clause, which means a result row in general will not consist of subject, predicate, and object. Hence, the service couldn't possibly return results in Turtle: What does not consist of RDF triples canot be serialised as RDF triples.

    Making the Output Friendlier: XSLT!

    But then what do I do instead to improve result readability? For quick and (relatively) easy XML manipulation on the command line, I almost always recommend xmlstarlet. While I give you its man page has ample room for improvement, and compared to writing XSL stylesheets, the command line options of xmlstarlet sel (use its -h option for explanations) are somewhat obscure, but it just works and is compact.

    If you inspect the response from Wikidata, you will notice that the results come in result elements, which for every variable in your SELECT clause have one binding element, which in turn has a name attribute and then some sort of value in its content; for now, I'll settle for fetching either uri or literal (again, part one has a bit more on what that might mean). What I need to tell xmlstarlet thus is: “Look for all result elements and produce one output record per such element. Within each, make a name/value pair from a binding's name attribute and any uri or literal element you find.” In code, I furthermore need to add an XML prefix definition (that's totally orthogonal to RDF prefixes). With the original curl and a pipe, this results in:

    curl --data-urlencode query@einstein.rq https://query.wikidata.org/sparql \
    | xmlstarlet sel -T -N s="http://www.w3.org/2005/sparql-results#" -t \
      -m //s:result --nl -m s:binding -v @name -o = -v s:uri -v s:literal --nl
    

    Phewy. I told you xmlstarlet sel had a bit of an obscure command line. I certainy don't want to type that every time I run a query. Saving keystrokes that are largely constant across multiple command invocations is what shell aliases are for, or, because this one would be a bit long and fiddly, shell functions. Hence, I put the following into my ~/.aliases (which is being read by the shell in most distributions, I think; in case of doubt, ~/.bashrc would work whenever you use bash):

    function wdq() {
      curl -s --data-urlencode "query@$1" https://query.wikidata.org/sparql
      | xmlstarlet sel -T -N s="http://www.w3.org/2005/sparql-results#" -t \
        -m //s:result --nl -m s:binding -v @name -o = -v s:uri -v s:literal --nl
    }
    

    (notice the $1 instead of the constant file name here). With an exec bash – my preferred way to get a shell to reflecting the current startup scripts –, I can now type:

    wdq einstein.rq | less
    

    and get a nicely paged output like:

    p=http://schema.org/version
    o=1692345626
    
    p=http://schema.org/dateModified
    o=2022-07-31T01:52:04Z
    
    p=http://schema.org/description
    o=ލިޔުންތެރިއެއް
    
    p=http://schema.org/description
    o=ಗಣಿತಜ್ಞ
    
    p=http://schema.org/description
    o=भौतिकशास्त्रातील नोबेल पारितोषिकविजेता शास्त्रज्ञ.
    

    We will look at how to filter out descriptions in languagues one can't read, let alone speak, in the next instalment.

    For now, I'm reasonably happy with this, except of course I'll get many queries wrong initially, and then Wikidata does not return XML at all. In that case, xmlstarlet produces nothing but an unhelpful error message of its own, because it …

  • SPARQL and Wikidata 1: Setting out

    Yaks todding along a mountain path

    If you continue, you will read about a first-rate example of Yak Shaving

    While listening to a short biography of the astrophysicist Mary Lea Heger (my story; sorry, in German), I learned that she died on her birthday. That made me wonder: How common is that? Are people prone to die on their birthdays, perhaps because the parties are so strenuous, perhaps because they consider them a landmark that they are so determined to reach that they hold on to dear life until they have reached it? Or are they perhaps less likely to die because all that attention strengthens their spirits?

    I figured that could be a nice question for Wikidata, a semantic database that feeds Wikipedia with all kinds of semi-linguistic or numeric information. Even if you are not Wikipedia, you can run fairly complex queries against it using a language called SPARQL. I've always wanted to play with that, in particular because SPARQL seems an interesting piece of tech. Answering the question of the letality of birthdays turned out to be a mildly exciting (in a somewhat nerdy sense) journey, and I thought my story of how I did my first steps with SPARQL might be suitably entertaining.

    Since it is a relatively long story, I will split it up into a few instalments. This first part relates a few preliminaries and then does the first few (and very simple) queries. The preliminaries are mainly introducing the design of (parts of) RDF with my take on why people built it like that.

    Basics: RDF in a few paragraphs

    For motivating the Resource Description Format RDF and why people bother with it, I couldn't possibly do better than Norman Gray in his witty piece on Jordan, Jordan and Jordan. For immediate applicability, Wikidata's User Manual is hard to beat.

    But if you're in a hurry, you can get by with remembering that within RDF, information is represented in triples of (subject, predicate, object). This is somewhat reminiscent of a natural-language sentence, although the “predicate“ typically would be a full verb phrase, possibly with a few prepositions sprinkled in for good measure. Things typically serving as predicates are called “property“ in RDF land, and the first example for those defined in wikidata, P10[1], would be something like has-a-video-about-it-at or so, as in:

    "Christer Fuglesang", P10, http://commons.wikimedia.org/wiki/Special:FilePath/Christer%20Fuglesang%20en.webm
    "Christer Fuglesang", P10, http://commons.wikimedia.org/wiki/Special:FilePath/Christer%20Fuglesang%20ru.webm
    

    If you know about first order logic: It's that kind of predicate. And please don't ask who Christer Fuglesang is, the triples just came up first in a query you will learn about a bit further down.

    This was a bit of a simplification, because RDF will not usually refer to a thing (an “entity“ in RDF jargon) with a string (“literal”), if only because there could be multiple Christer Fuglesangs and a computer would be at a loss as to which one I mean in the two triples above. RDF instead talks about “resources“, which is anything that has a URI and encompasses both entities and properties. So, a statement as above would actually combine three URIs:

    http://www.wikidata.org/entity/Q317382, http://www.wikidata.org/prop/direct/P10, http://commons.wikimedia.org/wiki/Special:FilePath/Christer%20Fuglesang%20en.webm

    CURIEs

    That is a lot of stuff to type, and thus almost everything in the RDF world supports URL abbreviation using prefixes. Basically, in some way you say that whenever there's wpt: in a token, some magic replaces it by http://www.wikidata.org/prop/direct/. Ff you know about XML namespaces (and if you're doing any sort of non-trivial XML, you should): same thing, except that the exact syntax for writing down the mapping from prefixes to URIs depends on how you represent the RDF triples.

    These “words with a colon that expand to long URIs by some find-and-replace rules“ were called CURIEs, compact URIs, for a while, but I think that term has become unpopular again. I consider this a bit of a pity, because it was nice to have a name for them, and such a physics-related one on top. But it seems nobody cared enough to push the W3C draft for that ahead.

    As with XML namespaces, each RDF document could use its own prefix mapping; if you want, you could instruct an RDF processor to let you write wikidata-direct-property: for http://www.wikidata.org/prop/direct/ rather than wpt: as usual. But that would be an unfriendly act. At least for the more popular collections of RDF resources, there are canonical prefixes: you don't have to use them, but everyone will hate you if you don't. In particular, don't bind well-known prefixes like foaf: (see below) to URIs other than the canonical ones except when seeing whether a piece of software does it right or setting traps for unsuspecting people you don't like.

    Then again, for what we are doing here, you do not need to bother about prefix mappings at all, because the wikidata engine has all prefixes we will use prefined and built in. So, as long as you are aware that you can replace the funny prefixes with URI parts and that there is some place where those URIs parts are defined, you are fine.

    Long URIs in RDF?

    You could certainly ask why we're bothering with the URIs in the first place if people in practice use the canonical prefixes almost exclusively. I think the main reason RDF was built on URIs was because its parents on the one hand wanted to let everyone “build” resources with minimal effort. On the other hand, they wanted to ensure as best they could that two people would not accidentally use the same resource identifier while meaning different things.

    To ensure the uniqueness of identifiers, piggybacking on the domain name system, which already makes sure that there are never two machines called, say, blog.tfiu.de in the world, is a rather obvious move. In HTTP URIs, domain names show up as the authority (the host part, the thing between the first colon and the double slash), and so with URIs of that sort you can start creating (if you will) your resources and would never conflict with anyone else as long as hold on to your domain.

    In addition, nobody can predict which of these namespace URIs will become popular enough to warrant a globally reserved prefix of their own; you see, family-safe prefixes with four (or fewer) letters are a rather scarce resource, and you don't want to run a registry of those. If you did, you would become really unpopular with all the people you had to tell things like “no, your stuff is too unimportant for a nice abbreviation, but you can have aegh7Eba-veeHeql1:“

    The admittedly unwieldy URIs in practice also have a big advantage, and one that would require some complex scheme like the Handle system if you wanted to replicate it with prefixes: most of the time, you can resolve them.

    Non-speaking URIs

    While RDF itself does not care, most URIs in this business actually resolve to something that is readable in a common web browser; you can always try it with the resources I will be mentioning later. This easy resolution is particularly important in the case of Wikidata's URIs, which are essentially just numbers. Except for a few cases (wd:Q42 is Douglas Adams, and wd:Q1 is the Universe), these numbers don't tell you anything.

    There is no fixed rule that RDF URIs must have a lexical form that does not suggest their meaning. As a counterexample, http://xmlns.com/foaf/0.1/birthday is a property linking a person with its, well, birthday in a popular collection of RDF resources[2] called foaf (as in friend of a friend – basically, you can write RDF-complicant address books with that).

    There are three arguments I have heard against URIs with such a speaking form:

    • Don't favour English (a goal that the very multilingual Wikipedia projects might plausibly have).
    • It's hard to automatically generate such URIs (which certainly is an important point when someone generates resources as quickly and with minimal supervision as Wikidata).
    • People endlessly quarrel about what should be in the URI when they should really be quarrelling about the label, i.e., what is actually shown to readers in the various natural languages, and still more about the actual concepts and definitions. Also, you can't repair the URI if you later find you got the lexical form slightly wrong, whereas it's easy to fix labels.

    I'm not sure which of these made Wikidata choose their schema of Q<number> (for entities) and P<number> (for properties) – but all of them would apply, so that's what we have: without looking …

Seite 1 / 1

Letzte Ergänzungen