PowerShell und DateTime

An allen Ecken und Enden werden Zeitstempel verwendet. Sei es im Eventlog, in Mails, in Logfiles, Snooper u.a. Allerdings gibt es gleich viele verschiedene Formate, wie Zeiten angezeigt werden. Je nach Land unterscheidet sich die Schreibweise doch stark, sei es in der Reihenfolge der Werte, die Bezeichnung von Monaten etc.

Der Typ DateTime

.NET und damit auch PowerShell kennt den Variablentyp "[datetime]", der generisch die Speicherung von Datum und Zeitwerten zulässt. Zudem gibt es mit dem Commandlet "Get-Date" eine Möglichkeit , z.B. das aktuelle Datum zu ermitteln. Ein einfacher Aufruf zeigt alle Properties.

PS C:\> get-date | fl *


DisplayHint : DateTime
DateTime    : Dienstag, 29. Oktober 2013 00:16:08
Date        : 29.10.2013 00:00:00
Day         : 29
DayOfWeek   : Tuesday
DayOfYear   : 302
Hour        : 0
Kind        : Local
Millisecond : 229
Minute      : 16
Month       : 10
Second      : 8
Ticks       : 635186025682291672
TimeOfDay   : 00:16:08.2291672
Year        : 2013

Das Commandlet hat auch noch einige Methoden, die Sie mit folgenden Befehl anzeigen können (Nur ein Auszug)

PS C:\> get-date | gm *

   TypeName: System.DateTime

Name                 MemberType     Definition
----                 ----------     ----------
Add                  Method         datetime Add(timespan value)
AddDays              Method         datetime AddDays(double value)
AddHours             Method         datetime AddHours(double value)
AddMilliseconds      Method         datetime AddMilliseconds(double value)
AddMinutes           Method         datetime AddMinutes(double value)
AddMonths            Method         datetime AddMonths(int months)
AddSeconds           Method         datetime AddSeconds(double value)
AddTicks             Method         datetime AddTicks(long value)
AddYears             Method         datetime AddYears(int value)
CompareTo            Method         int CompareTo(System.Object value), int CompareTo(datetime value), int IComparable.CompareTo..
Equals               Method         bool Equals(System.Object value), bool Equals(datetime value), bool IEquatable[datetime].Equ..
IsDaylightSavingTime Method         bool IsDaylightSavingTime()
Subtract             Method         timespan Subtract(datetime value), datetime Subtract(timespan value)
ToFileTime           Method         long ToFileTime()
ToFileTimeUtc        Method         long ToFileTimeUtc()
ToLocalTime          Method         datetime ToLocalTime()
ToLongDateString     Method         string ToLongDateString()
ToLongTimeString     Method         string ToLongTimeString()
ToOADate             Method         double ToOADate()
ToSByte              Method         sbyte IConvertible.ToSByte(System.IFormatProvider provider)
ToShortDateString    Method         string ToShortDateString()
ToShortTimeString    Method         string ToShortTimeString()
ToString             Method         string ToString(), string ToString(string format), string ToString(System.IFormatProvider pr..
ToUniversalTime      Method         datetime ToUniversalTime()
DisplayHint          NoteProperty   Microsoft.PowerShell.Commands.DisplayHintType DisplayHint=DateTime
Date                 Property       datetime Date {get;}
Day                  Property       int Day {get;}
DayOfWeek            Property       System.DayOfWeek DayOfWeek {get;}
DayOfYear            Property       int DayOfYear {get;}
Hour                 Property       int Hour {get;}
Kind                 Property       System.DateTimeKind Kind {get;}
Millisecond          Property       int Millisecond {get;}
Minute               Property       int Minute {get;}
Month                Property       int Month {get;}
Second               Property       int Second {get;}
Ticks                Property       long Ticks {get;}
TimeOfDay            Property       timespan TimeOfDay {get;}
Year                 Property       int Year {get;}
DateTime             ScriptProperty System.Object DateTime {get=if ((& { Set-StrictMode -Version 1; $this.DisplayHint }) -ieq  "..

Interessante Funktionen sind z.B. alle, die mit "Add" beginnen, weil damit recht einfach eine Zeitspanne addiert aber auch subtrahiert werden kann.

Formatierungszeichen

Ehe ich auf die verschiedenen Varianten zur Überführung von Werten kommen, sollten Sie ein paar "besondere" Zeichen können

­ 
d     Day of month 1-31
dd    Day of month 01-31
ddd   Day of month as abbreviated weekday name
dddd  Weekday name
h     Hour from 1-12
H     Hour from 1-24
hh    Hour from 01-12
HH    Hour from 01-24
m     Minute from 0-59
mm    Minute from 00-59
M     Month from 1-12
MM    Month from 01-12
MMM   Abbreviated Month Name
MMMM  Month name
s     Seconds from 1-60
ss    Seconds from 01-60
t     A or P (for AM or PM)
tt    AM or PM
yy    Year as 2-digit
yyyy  Year as 4-digit
z     Timezone as one digit
zz    Timezone as 2-digit
zzz   Timezone
fff   Hundertstel

Diese Zeichen kommen in der Folge noch zum Einsatz.

String zu DateTime

Wie ich anfangs beschrieben habe, gibt es unterschiedlichste Schreibweise und Zeitinformationen darzustellen. Als Entwickler ist man natürlich daran informiert, solche "Strings" in strukturierte Datenformate zu überführen. Mit einer "Textversion" eines Zeitstempel lässt sich nicht rechnen oder vergleichen und Fehlinterpretationen sind je nach Sprach- und Landeseinstellung wahrscheinlich. Ein einfacher Weg ist die Konvertierung über die "ParseExact"-Methode bei der Ein String mit einer Parametrisierung zu einem DateTime übertragen werden kann

$timestamp = [datetime]::parseexact($matches.timestamp,"yyyy-MM-dd HH:mm:ss",$null)

Natürlich können Sie auch einfach einen String direkt übernehmen. Solange der String das passende Format" hat und z. B. die lokale Einstellungen übereinstimmen. Ansonsten sehen Sie einen Fehler wie den folgenden.

PS C:\> [datetime]$timesstamp = "20.12.2013"
Der Wert "20.12.2013" kann nicht in den Typ "System.DateTime" konvertiert werden. Fehler: "String was not recognized as a valid
DateTime."
In Zeile:1 Zeichen:1
+ [datetime]$timesstamp = "20.12.2013"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException
    + FullyQualifiedErrorId : RuntimeException

Wenn Sie solch Tests machen, dann sollten sie natürlich Zahlen verwenden, die auch einen Fehler produzieren. Selbst auf einem deutschen Windows 7 erwartet die Konvertierung in der Form "dd.MM.yyyy"

PS C:\> [datetime]$timestamp = "12.20.2013"
PS C:\> $timestamp

Freitag, 20. Dezember 2013 00:00:00

Natürlich können auch Zeiten mit gesetzt werden, die sogar bis auf Millisekunden und Ticks auflösen.

DateTime to String

Aber auch umgekehrt ist es natürlich sehr interessant, aus einer DateTime-Struktur eine Textversion zu generieren, die in Dateien, Logfiles oder Bildschirmausgaben verwendet werden kann. Mühsam ist es natürlich, aus den einzelnen Properties sich einen String zusammen zu kopieren:

[datetime]$timestamp = "12.20.2013 14:56"
write-host "Zeitstempel:" +$timestamp.day + "." +$timestamp.month +"." +$timestamp.year

Viel einfacher geht das mit der "ToString"-Methode, der man ein kurzes Datumsformat oder sogar einen komplett eigenen Formatierungsstring übergeben kann. Hier nutze ich die Funktion "now", um die aktuelle Zeit anders zu formatieren

([datetime]::now).tostring("dd.MM.yyyy HH:mm:ss.fff")

Eine Besonderheit sind natürlich Sonderzeichen. So benötigt der Lync Snooper das Datum durch "/" getrennt und die Zeit mit einem "|" abgetrennt. Das sieht dann wie folgt aus:

$timestamp.tostring("MM\/dd\/yyyy|HH:mm:ss.fff") +" 0000:0000 TRACE :: "+ $logline

Der "/" muss also durch das Escape-Zeichen "\" gesondert behandelt werden, da ansonsten ein "." ausgegeben wird. Mit dieser einfachen Funktion ist es natürlich sehr einfach, ein passendes Datum/Zeit-Format auszugeben

Addieren, Subtrahieren und Vergleichen

Mit Strings kann man nicht rechnen, aber mit DateTime schon. Intern wird der Zeitstempel "serialisiert" so dass Sie problemlos zwei Zeitstempel vergleichen und miteinander verrechnen können. Bei Letzterem ist das Ergebnis wieder ein DateTime

Diese Funktion ist besonders hilfreich, wenn Sie z.B. beim Exchange Message tracking die Mails der letzten 10 Minuten erhalten wollen.

get-messagetrackinglog -start (get-date).addminutes(10)

Auch beim Vergleich von Datei-Zeitstempeln um sehr alte Dateien zu entfernen, ist dies hilfreich.

foreach ($file in (get-childitem ".\" | where {$_.lastwritetime -lt ( (get-date).adddays(-30))} )) {
	write-host ("Purging old log::" + $file)
	remove-item -path $file
}

Solche schnellen Lösungen waren als Batch fast gar nicht möglich und in VBScript war durchaus mehr Code zu schreiben.

Sortieren mit DateTime und Ticks

Das Commandlet "Sort-Object" erlaubt das Sortieren von Listen und kann nicht nur numerische Werte, sondern auch Zeitwerte sortieren. Ein einfacher Test geht mit einem Verzeichnis. Hier ein Auszug meines TEMP-Verzeichnis:

PS C:\> Get-ChildItem c:\temp\t*

    Verzeichnis: C:\temp

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----        15.04.2014     12:25            temp
-a---        13.02.2013     00:11        334 tes2.csv
-a---        13.02.2013     00:15       5226 tes2.xml
-a---        18.10.2013     10:08      44924 test.cfg.ofg
-a---        23.11.2013     13:53        122 test.csv
-a---        18.03.2013     17:48       1758 test.htm
-a---        21.03.2013     18:30      15323 topo.tbxml

Sie sehen gut, dass das LastWriteTime-Feld unsortiert ist. Und nun das ganze "sortiert"

PS C:\> Get-ChildItem c:\temp\t*  | Sort-Object -Property LastWriteTime

    Verzeichnis: C:\temp

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        13.02.2013     00:11        334 tes2.csv
-a---        13.02.2013     00:15       5226 tes2.xml
-a---        18.03.2013     17:48       1758 test.htm
-a---        21.03.2013     18:30      15323 topo.tbxml
-a---        18.10.2013     10:08      44924 test.cfg.ofg
-a---        23.11.2013     13:53        122 test.csv
d----        15.04.2014     12:25            temp

Measure-Object mit DateTime

Wenn Sie in einer Liste mehrere Elemente mit einem DateTime-Wert haben, dann kann die Anforderung bestehen, auch darüber z.B. das älteste oder jüngste Datum oder den Durchschnitt zu ermitteln. Measure-Objekt kann leider nicht mit DateTime umgehen, aber natürlich mit [long]-Werten. Hier hilft es dann mit der "TICKS"-Funktion den Datumwert in Ticks seit 1.1.0001 umzurechnen. Hier am Beispiel eine Exchange 2007 Überwachung, bei der in alles Mails in den Queues die älteste Mail gesucht wird.

get-date ([long]((get-transportserver `
   | Get-Message ` 
   | %{$_.datereceived.ticks} 
   | Measure-Object -Maximum).maximum))

Das ist deutlich einfacher, übersichtlicher und schneller als selbst durch die Elemente zu laufen und jeden Eintrag mit dem aktuellen Maximum zu vergleichen.

Ein ähnlicher Anwendungsfall nutze ich, wenn ich Exchange Transaktionsprotokolle prüfe, um das älteste und neueste Log zu ermitteln, und dann über die Anzahl die durchschnittliche Aktivität errechne.

Weitere Links