Batch-Job: In einen Ordner mit Tages-Nummer kopieren

7. November 2011 17:47

Musste ich heute machen, und weil man so was immer wieder mal braucht, hier ein kleiner Tipp, wie man in einem Batch-Job an die Nummer des aktuellen Tages im aktuellen Monat (also 1 bis 31) kommt, und diese Nummer dann als Zielordner beim kopieren verwendet.

Einfacher ausgedrückt: Man will jeden Tag ein Backup von irgendwas anlegen, und das Backup soll in einem nummerierten Ordner landen. Die Nummer des Ordners ist dabei die Nummer des Tages.

Geht so (das interessante ist die Zweite Zeile. Da werden 2 Zeichen aus dem Date-String entnommen, und zwar ab dem ‘0’ten Zeichen.):

set DATE=%date%
set DAY=%DATE:~0,2%
copy quelle\*.* ziel\%DAY%

Ein Witz (eher für Programmierer)

28. November 2010 14:29

pdc10

27. Oktober 2010 17:02

Morgen und Übermorgen: Mit Live Streams

FileNotFoundException beim XmlSerializer

17. September 2010 16:56

Folgendes passiert: Man versucht Instanzen mit dem XmlSerializer zu serialisieren, bekommt aber im Konstruktor der Serializers eine FileNotFoundException. Was ist zu tun?

Dazu muss man zunächst mal wissen, das es im Wesentlichen zwei Methoden gibt, die Serialisierungs-Assemblies zu erzeugen:

  1. Zur Kompilierzeit, also beim bauen des Projektes
  2. Zur Laufzeit der Anwendung

Der erste Fall ist manchmal ganz gut dafür geeignet, die Ladezeit der Anwendung zu verkürzen: In diesem Fall ist die DLL aber garantiert da, denn wenn das nicht der Fall wäre, wäre bereits beim bauen der Anwendung ein Fehler aufgetreten.

Es geht also um den zweiten Fall. Da passiert folgendes: Der Konstruktor des XmlSerializers versucht zunächst eine passende Assembly zu finden (die hat den Namen der DLL, in der der zu serialisierende Typ steckt, plus die Erweiterung “Xmlserializer.dll”). Findet er die nicht, wird zur Laufzeit Code zum serialisieren erzeugt und übersetzt – und die daraus resultierende Assembly wird dann geladen und zum serialisieren eingesetzt.

Die FileNotFound Exception kommt dann, wenn das erzeugen der Serialisierungs-Assembly nicht geklappt hat. Dafür kann es jede Menge Gründe geben – nicht zuletzt Fehler in der Struktur der zu serialisierenden Klassen oder deren Auszeichnung.

Wie findet man nun das Problem? So:

  1. Einen (Visual Studio) Command-Prompt öffnen
  2. In das Verzeichnis wechseln, in der sich die Anwendung befindet
  3. “sgen NameDerAusgabgsDll.Dll /v” eingben

Sgen gibt dann eine im allgemeinen einfach verständliche Fehlermeldung aus: Korrigiert man den Fehler, ist man auch die FileNotFoundException los.

Wir wir die Performance verbessern

14. September 2010 15:00

Ich hatte in den letzten Tagen mehrfach darauf hingewiesen, das wir die Performance der Baustatik spürbar verbessert haben. Das ist natürlich schön – aber wie macht man das eigentlich ?

image

Es gibt verschiedene Möglichkeiten, aber im Wesentlichen läuft es darauf hinaus, das man die Zeit misst, die die einzelnen Funktionen des Programms brauchen. Dann sucht man die Funktion die am langsamsten ist und überlegt sich, ob die benötigte Zeit “angemessen” ist, oder nicht. Wenn nicht, hat man einen Kandidaten für die Optimierung. Die kann daraus bestehen, das man einfach nur den Code ein bisschen umstellt (hauptsächlich in Fällen, in denen Funktionen extrem oft aufgerufen werden), oder auch daraus, das man tatsächlich einen völlig neuen Algorithmus verwendet.

Das wichtige ist aber immer das Messen – vor allem auch das erneute Messen nach der Änderung: Die Dinge sollen ja nicht langsamer werden…

Damit man sowas messen kann, wird das Programm nicht direkt, sondern unter der Kontrolle eines anderen Programmes gestartet. Das kontrollierende Programm, das auch die Messungen durchführt, nennt man Profiler. Davon gibt es eine ganz Reihe – in unserem konkreten Fall haben wir das Programm GlowCode verwendet. (Das übrigens wirklich erstaunlich schnell ist, wenn ich dessen Performance mit anderen Profilern vergleiche, die ich ausprobiert habe.)

Letzten Endes ist es also ganz einfach: Messen, ändern, wieder messen: Wenn es dann schneller ist – prima, sonst zurück zum Anfang.

Patterns for Parallel Programming

11. August 2010 17:21

Sauer auf Microsoft

14. Juli 2010 17:54

Im Normalfall bin ich Microsoft gegenüber eher positiv eingestellt: Letzthin haben die sich aber etwas geleistet, was ich alles andere als lustig finde. Das Visual Studio hat seit Version 2003 eine Projektart namens “Setup and Deployment Project”.

Wir installieren die Baustatik damit seit der ersten Version, und haben das Projekt auch brav nach VS 2005, nach VS 2008 und nun nach VS 2010 portiert. Und zwar nicht mit wenig Arbeit: Zur Zeit installieren wir damit circa 5000 Files. Wenn ich raten müssten würde ich sagen, das im Laufe der Zeit mindestens 4 Monate Arbeit in dieses Projekt gesteckt wurden.

Und mit VS 2010 geht nicht mehr. Das Projekt erzeugt zwar noch ein MSI – aber das kann man eigentlich nicht mehr richtig nutzen: Installieren kann man nur noch auf einem Rechner, auf dem es keine Vorgängerversion gibt – und der von Microsoft gelieferte Workaround führt zwar zu installierten Dateien, aber auch zu nicht mehr funktionierenden Links im Startmenü.

Das ist auch bei Connect alles prima dokumentiert (und der Fehler ist auch schon bestätigt) – nur interessiert es irgendwie niemand. Die letzten 7 Tage habe ich auf irgendein Feedback irgend einer Art gewartet – jetzt geht’s nicht mehr länger: Ich werde in den sauren Apfel beißen und mich nach einem anderen Werkzeug zur Herstellung des Setups umsehen – und dann den ganzen Spaß von vorne aufbauen.

Bin regelrecht stinkig.

Setup and Deployment Project: Probleme mit VS 2010

7. Juli 2010 16:40

Nachdem mich das nun fast 3 Tage gekostet hat, hilft es vielleicht jemand anderem in einer ähnlichen Situation. Ich hatte eine Setup and Deployment Project, das für die Installation unserer Software zuständig war, und das ursprünglich mit VS 2005 erstellt worden war. Das gleiche Projekt funktionierte auch prima mit VS 2008.

In der Version mit VS 2010 klappt es nicht mehr: Wenn man die Software auf einem “leeren” System installiert, geht alles gut. Befindet sich aber bereits eine Vorgängerversion der gleichen Software auf dem Zielsystem, dann werden im Laufe des Installationsvorganges (fast) alle Dateien der Software entfernt: Der Installer sagt das alle in Ordnung wäre – schaut man dann aber auf die Festplatte, so ist keine einzige Datei der Installation vorhanden.

Grund ist ein bekannter Fehler in VS 2010. (Connect-Eintrag dazu.). Im Wesentlichen läuft es darauf hinaus: Wenn man die Option “RemovePreviousVersions” auf “true” stellt, dann werden die Dateien der Vorgängerversion im Zuge der Installation entfernt. Der Fehler macht sich dadurch bemerkbar, das der Schritt mit dem entfernen der alten Version nicht vor der Installation der neuen stattfindet, sondern danach: Alle Dateien die sowohl in der alten als auch in der neuen Version drin sind, landen dann eben nicht auf dem Zielsystem. :-(

Angesichts der Tatsache das sich bei den Setup-Projekten in den letzten 6 Jahren praktisch nichts getan hat, finde ich es schon ein bisschen schwach, das die Softies es trotzdem geschafft haben, eine wichtige Funktion hops gehen zu lassen.

Ein (teilweise brauchbarer) Workaround ist im Connect-Eintrag beschrieben. (Im Wesentlichen baut man einen Post-Build Step, der die Reihenfolge der Arbeitsschritte korrigiert.)

Ein einfaches Web Stress Tool

17. Juni 2010 12:23

Ich wollte kürzlich die Performance-Unterschiede vom IIS mit und ohne Output-File-Caching untersuchen: Mit dem Caching sollte der IIS dramatisch mehr Seiten ausliefern können, als ohne. Was mir nicht klar war, war, wie man dieses Caching nun genau einschaltet: Von Haus aus steht im Konfigurations-Manager des IIS, das das Caching “eingeschaltet” ist: Auf den Beispielvideos auf IIS.net werden aber immer noch zusätzliche Maßnahmen getroffen.

Mir war nicht klar, ob das tatsächlich notwendig ist, oder nicht. Die Idee das zu überprüfen war aber ganz einfach: Man nimmt das Web Stress Tool von Microsoft und schaut einfach nach, wie viele Seiten pro Sekunde geliefert werden können.

Leider ist das nur in der Theorie so, denn das an vielen Stellen verlinkte Web Stress Tool gibt es einfach nicht mehr: Man muss entweder WCAT verwenden, oder die Visual Studio-Ausgabe für Tester. Was blöd ist, denn die Tester-Ausgabe habe ich nicht, und WCAT ist mir eindeutig zu umfangreich für meine Zwecke.

Also habe ich selbst ein kleines Web-Stress Tool gebaut.

web_stress_tool

Es handelt sich um eine einfache Windows-Forms Anwendung. Oben gibt man die URL ein, die man testen will. Darunter kann man die Anzahl an Threads einstellen, die parallel die angegebene URL aufrufen sollen. Drückt man dann auf “Start”, fängt das Programm eben mit der eingestellten Anzahl an Threads an, Seiten abzurufen.

Darunter gibt es eine Statistik, die die Anzahl Seiten pro Sekunde und die gesamte Anzahl an Aufrufen auflistet. Das reicht für einen Test vom Caching völlig aus. (Die “optimale” Anzahl an Threads muss man ausprobieren.)

Wer mag kann sich die Sache hier herunterladen: Ausführbare Datei (.Net 4), Quellcode (VS 2010).

Ein "Trick" für Javascript

9. April 2010 09:49

Ich musste kürzlich ein bisschen was an unsere Online-Dokumentation ändern. Dabei bin ich über folgendes Problem gestossen: Im Event-Modell von Browsern gibt es innerhalb einer Fensters das "Onload" Ereignis: Das wird aufgerufen, wenn die Seite komplett geladen ist. Zu diesem Zeitpunkt kann man prima im DOM rumändern - zum Beispiel nachträglich irgendwelche Dinge einfügen, die während des normalen Ladens des Dokument die Anzeige verzögern würden.

Das Problem dabei ist, das man sich an verschiedenen Stellen an "document.onload" dranhängen will - das das aber eigentlich nicht möglich ist: Anders als in C# kann man nicht einfach "document.onload += function()" schreiben, denn nur die Zuweisung "=" ist zulässig. Die überschreibt aber vorher gesetzte Handler. Was es also braucht ist ein Mechanismus, mit dem man "onload" Handler verketten kann. Ein Lösung dafür sieht so aus:

  function X() { alert ("X"); }
  function Y() { alert ("Y"); }

  var _allOnload = new Array();
  function AddOnload(f)
  {
    if (window.onload)
    {
      if (window.onload != Onload)
      {
        _allOnload[0] = window.onload;
        window.onload = Onload;
      }
      _allOnload[_allOnload.length] = f;
    }
    else
    {
      window.onload = f;
    }
  }
 
  function Onload()
  {
    for (var i=0;i<_allOnload.length;i++)
    {
      _allOnload[i]();
    }
  } 
 
   AddOnload( X );
   AddOnload( Y );

Mix10 Keynote: Jetzt zu haben

16. März 2010 16:39

Mix 10 Keynote 1 on demand. (Der "echte" Anfang scheint zu fehlen, los gehts bei Minute 30...)

Custom Action eines Setup-Projektes wird nicht ausgeführt

4. Februar 2010 11:50

Das hat mich nun etwa 2 Tage gekostet - vielleicht hilft es ja mal jemand anderem... Ich hatte folgendes Problem: In meinem VS (2008) Setup-Projekt gab es eine Custom Action (als C# Projekt) Assembly - und die wurd beim testen des Setups auf einer Maschine einfach nicht ausgeführt. (Das Ding schreibt einen Event-Log Eintrag, mit dem das einfach nachzuvollziehen war.)

Grund: Der MSI scheint bei Custom Actions Assemblies ein etwas anderes Verhalten an den Tag zu legen, als bei "normalen" Assemblies. Im Normalfall reicht es bei einer Managed Assembly aus, das die neue Version der Assembly eine höhere AssemblyVersion (per AssemblyVersionAttribute [1.0.0.*] einfach zu handhaben) hat, als eine auf dem Zielsystem bereits vorliegende Assembly. Hat die "neue" eine höhere Version, wird die "ältere" ersetzt. (Bei nativen Images muss die FileVersion höher sein, damit die ersetzt werden.)

Bei einer Assembly die eine Custom Action enthält scheint es nicht auszureichen die AssemblyVersion Notation [1.0.0.*] zu verwenden - auch wenn die Version hochgezählt wird, ersetzt MSI eine bereits vorliegende DLL mit gleichem Namen nicht. Offenbar muss man bei Custom Actions auf jeden Fall auch eine höhere FileVersion (per AssemblyFileVersionAttribute zu vergeben) haben. Ist das der Fall, wird die "ältere" DLL dann auch durch die neuere ersetzt - und im Zuge der Ausführung der Custom Actions dann auch tatsächlich aufgerufen.

Wäre schön gewesen, wenn das irgendwo in der Dokumentation zu finden gewesen wäre. :-(

Microsoft PDC 09

16. November 2009 16:30

Wer nicht hinkonnte (so wie ich :-(), kann sich die Keynotes morgen und übermorgen (Dienstag und Mittwoch) live ab 17.30 unserer Zeit ansehen. Zu sehen ist das ganze unter http://microsoftpdc.com/. Alle anderen Veranstaltungen aus dem großen Saal werden ebenfalls live übertragen, und zwar bei Channel 9. Ich habs noch nicht verifiziert, gehe aber davon aus, das die Sessions wie auch früher schon aufgenommen und kurz nach Ende der Session als normaler Stream zur Verfügung gestellt werden.

Diese Woche: TechEd in Berlin

11. November 2009 18:20

Wer nicht hin kann: TechEd Europe Online

Gestern: Windows 7 - TechTalk für Entwickler

13. Oktober 2009 09:47

Gestern in München: Der MSDN TechTalk: Windows 7 - ein Überblick für Entwickler mit Oliver Scheer und Peter Kirchner. Das ganze hatte 2 Teile und ging von 18.00 bis 22.00. Kurz gesagt: Ich fands ein bisschen langweilig.

Im ersten Block ging es hauptsächlich um Anwendungskompatibilität - und der wichtigste Punkt scheint (noch immer, fast 15 Jahre nach Windows 95) zu sein, das viele Anwendungen die Versionsnummer von Windows nicht richtig nachsehen. Verstehe ja, das das für Microsoft wichtig ist - aber ich habe den zugehörigen Vortrag nun gefühlte 30mal gehört. (Mal sehen: WfW 3.1, W95, NT 3.1, NT 3.51, 2000, W98, XP, 2003, Vista, Server 2008 und nun W7 - also mindestens 10mal...).

Außerdem ging es darum das es nun (eigentlich schon seit Vista) Manifeste gibt (die man braucht, wenn man diverse Warnungen loswerden will) und das es (auch schon länger) auch Shims und das sdb Tool (und angelagertes gibt.).

Wer das schonmal für Vista gesehen und gehört hat: Viel Neues gibts in dem Umfeld offenbar nicht.

Der zweite Teil litt ein wenig unter der immer stärker schwindenden Stimme des Vortragenden (ein Problem das ich gut nachvollziehen kann: Ist mir letzte Woche am 3. Tag der Roadshow dann auch so ergangen... :-). Dabei ging es inhaltlich um die Nutzung der neuen Windows 7 Features im eigenen Programm: Ich hatte der Ankündigung entnommen, das es sich hier eher um einen Code-lastigen Vortrag halten würde: Statt dessen gab es auch hier eher Slides, viele Vorführungen der Windows 7 Features - aber doch eher dürftige konkrete Programmierbeispiele: Auf die wurde zwar immer wieder verwiesen, aber dafür hätte man sich die Zeit sparen können, wenn man die folgenden beiden Links kennt:

Windows 7 SDK Download, Windows API Code Pack for the .NET Framework

Insgesamt betrachtet war es interessant genug um nicht zu gehen, aber eigentlich nicht so interessant das ich eine Teilnahme an den anderen Vortragsorten empfehlen würde. Statt dessen schaut man besser in die Samples aus den beiden Links.

Inhalt von Message-Boxen kopieren

9. Oktober 2009 18:41

Manchmal ganz praktisch: Wenn eine Message-Box angezeigt wird...

... kann man den text darin mit Ctrl+C (Strg+C) einfach in Slipboard kopieren. Das sieht dann etwa so aus:

---------------------------
Microsoft Visual C++ Debug Library
---------------------------
Debug Assertion Failed!

Program: c:\statik\runtime\xfalt\debugx64\baustatik.exe
File: f:\dd\vctools\crt_bld\self_64_amd64\crt\prebuild\tran\amd64\ieee.c
Line: 109

Expression: (mask&~(_MCW_DN|_MCW_EM|_MCW_RC))==0

For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.

(Press Retry to debug the application)
---------------------------
Abort   Retry   Ignore  
---------------------------

So horcht man mit C# auf den Bildschirmschoner

12. August 2009 18:59

Wie man in C# auf den Screensaver reagiert

Manchmal möchte man in einem Programm darauf reagieren, das der Benutzer das Programm nicht länger verwendet, obwohl es noch läuft. Solche Situationen treten zum Beispiel dann ein, wenn die Workstation gelockt, oder aber, wenn der Bildschirmschoner aktiviert wird.
Das geht mit Win32 schon seit geraumer Zeit relativ einfach, und zwar mit Hilfe der SensEvents - in diesem Fall mit dem ISensLogon Event Interface.

Für die Nutzung aus .Net heraus gibt es aber leider keine wirklich brauchbare Beschreibung dafür - zumindest konnte ich keine finden. Daher hier: Eine Kurzbeschreibung fürs horchen per ISensLogon Implementierung.

Die Sache ist danbarerweise viel einfacher, als die COM-Dokumentation vermuten läßt. Zunächst braucht man im Projekt zwei neue Referenzen: Eine auf die "COM+ Admin Type Library", und eine auf die "SENS Events Type Library".

Mit der "Admin" referenz kommt man an die COMAdminCatalgoClass, die man zum registrieren (und abmelden) von Event Listenern benötigt, die "SENS" Referenz liefert einem die benötigten Definitionen um mit dem ISensLogon Interface arbeiten zu können.

Im Beispiel erzeuge ich einfach eine Instance der COMAdminCatalogClass als Member einer Form. Mit Hilfe dieses Members kommt man dann an die Sammlung aus Subscriptions, und mit deren Hilfe kann man dann eine neue Subscription erzeugen: In diesem Fall eine, für die "SENS Logon Events". Das geht mit Hilfe der EventCLSID dieser Events (die man notfalls durch Suche in der Registry findet.).

Das anlegen eines neuen Abos verläuft dann wie folgt - wichtig ist dabei nur, das man sich die ID des Abos merkt - ansonsten kann man es später nicht mehr kündigen:

private
string Subscribe()
{
   var subscription = (ICatalogObject)_subscriptions.Add();
   subscription.set_Value("EventCLSID", "{D5978630-5B9F-11D1-8DD2-00AA004ABD5E}"); // (findet man per Suche nach SENS Logon Events in der registry)
   subscription.set_Value("Name", "Subscription to ISensLogon");
   subscription.set_Value("SubscriberInterface", this);
   subscription.set_Value("Enabled", true);
   subscription.set_Value("PerUser", true);

   _subscriptions.SaveChanges();

   return subscription.get_Value("ID").ToString();
}

Damit man die Notifizierungen auch bekommt  muss irgendwer das ISensLogon Interface implentieren: Im Beispiel tut das einfach die Form. Das Interface hat 7 Methoden, und im Beispiel wird einfach in einer Liste aus Strings mitgeführt, welches Ereignis eingetreten ist. (Diese Liste kann man sich dann später per Klick auf den Button der Form anzeigen lassen. Das ist darum so, weil man keine Desktop-Interaktionen durchführen kann, wenn kein Benutzer angemeldet ist: Würden die Methoden aus der Interface-Implementierung direkt in die Listbox schreiben, gäbe es nur eine Exception aus Windows.Forms.)

Zum abbestellen des Abos muss man einfach über die Liste der Abos laufen, und dann dasjenige entfernen, das die "eigene" ID hat, die beim bestellen gemerkt wurde.

private void Unsubscribe()
{
   for (int i = 0; i < _subscriptions.Count; i++ )
   {
      var subscription = (ICatalogObject)_subscriptions.get_Item(i);

      if (subscription.get_Value("ID").ToString() == _subscriptionId)
      {
         _subscriptions.Remove(i);
         _subscriptions.SaveChanges();
         return;
      }
   }
}

Das wars eigentlich schon: Wer mag kann sich das komplette Projekt (VS 2088, C# 3.0) runterladen und ausprobieren...

SensTest.zip (10.23 KB)

Das kann man doch bestimmt kurz einbauen...

26. Juni 2009 18:49

Den Satz höre ich häufiger: Das kann man doch bestimmt kurz einbauen...

Gemein ist dabei eigentlich immer ein "ganz einfaches" Feature für die Baustatik. Nun geben wir uns ja redlich Mühe, Kundenwünsche zur erfüllen, nur: "Ganz einfach" ist eigentlich nie irgendwas. Um genau zu sein: Den Umfang, den schon die "einfachsten" Features in Form von Programmieraufwand annehmen, kann man sich kaum vorstellen. Hier ein kleines Beispiel:

Winkelstützmauer

In der Graphik für die Winkelstützmauer (ja ich weiss, die Zahlen die hier angezeigt werden machen keinen Sinn...) sollte folgendes erreicht werden:

  • Die Beschriftungen für die Bodenschichten sollten tabellarisch in die Graphik formatiert werden.
  • Die Tabellen sollten nicht in die Graphik reinragen
  • Die Bezeichnungen in den Tabellen sollten linksbündig sein
  • Die Werte sollten rechtsbündig sein
  • Rechts- bzw. Linksbündigkeit sollte auch über mehrere Bodenschichten hinweg vorliegen

Eigentlich "ganz einfach". Arbeitsaufwand: Etwa 220 Zeilen Programmcode und ca. 5 Stunden Arbeitsaufwand.

Nur, um ein paar Texte "hübscher" zu formatieren. Aber es macht ja Spaß... :-)

Paralle Programmierung

10. Juni 2009 09:31

Gestern in München: TechTalk Parallele Programmierung mit Visual Studio 2010 und Intel Parallel Studio mit Dariusz Parys und Mario Deilmann.

Kurzfassung: War interessant.

Längere Fassung: Die Veranstaltung war grob in vier Teile unterteilt, davon ging es (leider) in drei Teilen hauptsächlich um nativen Code und diverse Intel Tools. Die sind sicherlich für viele von Interesse, mein Hauptinteresse wäre aber Managed Code und Visual Studio gewesen: Dieser Teil wurde von Dariusz bestritten der wohl ein wenig krank war - und darum eher kurz und (ein wenig zu) schnell abgehandelt wurde.

Insgesamt gesehen gab es zwar wenige Codebeispiele, dafür aber viel Hintergrundwissen rund ums entwickeln für multi-threaded Anwendungen: Auch wenn ich die Intel Tools eher nicht einsetzen werde sind viele Probleme wohl recht identisch - egal ob man managed oder nativen Code produziert. Was die Möglichkeiten in VS 2010 (und .Net 4.0) angeht wird man sich noch überraschen lassen müssen: Offenbar ist da noch vieles in Bewegung und unterscheidet sich sogar zwischen Beta 1 und Beta 2 noch stark. Im Kern ist aber eines klar: Man wird sich selbst nicht mehr um die Nutzung von Threads (oder dem ThreadPool) kümmern müssen - und das ist sicher mehr als wünschenswert.

Wer noch einen Platz bei den kommenden Terminen für den Talk bekommen kann: Hingehen.

Hintergrund Links: Parallel Computing bei MSDN, Parallel Programming for Managed Developers

Heute in München...

11. Mai 2009 10:52

Kalender

<<  Februar 2012  >>
MoDiMiDoFrSaSo
303112345
6789101112
13141516171819
20212223242526
2728291234
567891011

View posts in large calendar