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.
- DateTime-Struktur
http://msdn.microsoft.com/de-de/library/System.DateTime(v=vs.110).aspx
Formatierungszeichen
Das Format einer Zeit unterscheidet sich nach Land, Sprache und auch die generelle Aufmachung kann beeinflusst werden. Systemintern wird die Zeit natürlich universelle gespeichert aber über folgende Formatierungszeichen können Sie bei der Ausgabe auf einen Bildschirm oder sonstige lesbare Form Einfluss nehmen
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.
Dateinamen mit Zeit
Eine Häufige Anwendung bei mir ist die Anlage von Log-Dateien mit einem Zeitstempel. Sie kennen das eventuell von den Exchange Message Tracking Logs aber ganz sicher von den Logdateien ihres Webservers (IISLogs, Apache etc.). Hier hat man es sich angewöhnt z.B. pro Tag oder Stunde eine neue Datei anzulegen. das geht dann recht einfach mit
# Format für Täglich $logfile = ".\Applog-$(get-date -Format yyyyMMdd).log" # Format für stündlich $logfile = ".\Applog-$(get-date -Format yyyyMMddHH).log" #Format für Sekunde $logfile = ".\Applog-$(get-date -Format yyyyMMdd-HHmmss).log"
Über den Weg lassen sich auch Dateien recht einfach nach dem Alter sortieren und löschen.
Knifflig wird es nur, wenn sie in einem Skript mehrere Programme mit dem gleichen Logfile aufrufen und dieses eine Logdatei überschreiben
In dem Fall definiere ich eher ein Prefix mit dem Skriptname und Zeit und hänge pro Programm dann ein Suffix an, z.B.
$logfileprefix = "Skriptname-$(get-date -Format yyyyMMdd-HHmmss)" adamsync.exe /import /log "$($logfileprefox)-import.log" adamsync.exe /sync /log "$($logfileprefox)-sync.log"
Sortierbarer Timestamp
Immer wieder benötige ich die Funktion, einen Zeitstempel in eine Datei oder ein Paket zu schreiben. Jedes Mal nutze ich natürlich "Get-Datei" dazu und genauso oft überlege ich mir wie ich einen "lesbaren" Zeitstempel hinbekomme. Soll ich die Zeit als lokale Zeit speichern oder doch lieber UTC, um bei weltweiten Analysen einen Abgleich zu ermöglichen?. Auf der anderen erschwert das natürlich ein lokales Debugging, wenn man immer die Stunden addieren oder abziehen muss. Dann gibt es ja noch unterschiedliche Schreibweisen je Land, z.B. "Tag Monat Jahr" in Deutschland aber eben "Monat Tag Jahr" in den USA. Abhängig davon kann man die Logs dann auch mehr oder wenige gut sortieren und zusammenführen. Irgendwann bin ich dann über den Formatter "-o" gestolpert.
PS C:\> get-date Montag, 17. Dezember 2018 15:11:30 PS C:\> get-date -Format o 2018-12-17T15:11:34.1552790+01:00 PS C:\> get-date -Format u 2018-12-17 15:11:44Z # Und nun noch mal mit UTC-Zeitzone # Achtung: Angeblich liefert "ToUniversalTime() in der Stunde der Zeitumschaltung falche Zeiten ((get-date).ToUniversalTime().tostring("u")) 2018-12-17 15:11:44Z # Besser ist daher folgender Aufruf ([System.DateTime]::UtcNow).tostring("u")
Achtung: Die Ausgabe kann aufgrund des ":" nicht als Dateiname verwendet werden
- Issues with the DateTime.Now() Method
https://docs.microsoft.com/de-de/previous-versions/dotnet/articles/ms973825(v=msdn.10)#issues-with-the-datetimenow-method
Das mit "format u" generierte Datum erfüllt all meine Wünsche, wenn ich die Zeit vorher auf UTC konvertiere:
- Lesbar
Das Format ist durch Menschen einfach lesbar - Sortierbar
Auch die Sortierung nach Jahr, Monat Tag Stunde(24 Schreibweise) Minute Sekunde funktioniert auch eine einfache Sortierung als Textdatei - Zeitzonentauglich
Die angehängte Korrektur anhand der Zeitzone zeigt einfach auf, wie weit die Zeit von UTC abweicht - Rückverwandelbar
Und eine Konvertierung von dem String in ein DateTime-Objekt ist mit Get-Time fehlerfrei ohne Verluste möglich
Es spricht für mich also nichts dagegen, wenn ich einfach diese Funktion nutze. Es gibt neben der Konvertierung bei "Get-Date" auch direkt die ToString-Funktion, die auch die gleichen Daten liefert
PS C:\> $timestamp=get-date PS C:\> $timestamp Montag, 17. Dezember 2018 15:45:12 PS C:\> $timestamp.ToString("o") 2018-12-17T15:45:12.2723844+01:00
Und in Gegenrichtung reicht einfach der Aufruf über get-date.
PS C:\> get-date "2018-12-17T15:45:12.2723844+01:00" Montag, 17. Dezember 2018 15:45:12
- The Universal Sortable ("u") Format
Specific
https://docs.microsoft.com/de-de/dotnet/standard/base-types/standard-date-and-time-format-strings#UniversalSortable - Converting times between time zones
https://docs.microsoft.com/en-US/dotnet/standard/datetime/converting-between-time-zones - Issues with the DateTime.Now() Method
https://docs.microsoft.com/de-de/previous-versions/dotnet/articles/ms973825(v=msdn.10)#issues-with-the-datetimenow-method
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 "MM.dd.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.ParseExact-Methode
(String, String, IFormatProvider)
http://msdn.microsoft.com/de-de/library/w2sa9yss(v=vs.110).aspx
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")
Wer z.B. einen Zeitstempel analog zu den IISLogs erzeugen will, kann folgende Zeile verwenden:
([datetime]::UtcNow).tostring("yyyy-MM-dd HH:mm:ss")
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
- DateTime.ToString-Methode
http://msdn.microsoft.com/de-de/library/system.datetime.tostring(v=vs.110).aspx
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.
Kalenderwoche
Bei der Ermittlung der Woche im Jahr orientiert sich PowerShell natürlich an den regionalen Einstellungen des Betriebssystems. Das passt also nicht immer. Sie können die Woche ganz einfach mit folgendem Befehle ermitteln
# nicht immer korrekt Get-Date -UFormat %V
Besser ist folgender Aufruf, damit das Gebietsschema zuverlässig beachtet wird.
# Richtig für Deutschland [System.Globalization.DateTimeFormatInfo]::CurrentInfo.Calendar.GetWeekOfYear((get-date),2,1)
Der Fehler passiert natürlich nicht immer sondern es hängt von dem Betriebssystem und der dort hinterlegten Einstellung. Wer es nachprüfen will, kann folgendes Skript mal ausführen.
for ($count=0; $count -le 365; $count++) { $dayofyear = (get-date 01.01.2020).adddays($count) $weeka = get-date $dayofyear -UFormat %V $weekb = [System.Globalization.DateTimeFormatInfo]::CurrentInfo.Calendar.GetWeekOfYear($dayofyear,2,1) write-host "Day $($count) Date:$($dayofyear) WeekA:$($weeka) WeekB:$($weekb) " -nonewline if ([int]$weeka -eq [int]$weekb) { write-host " OK" -foregroundcolor green } else { write-host " FAIL" -foregroundcolor red } }
Bei mir erwischt es genau einen Tag im Jahr:
Wenn Sie also z.B. die Kalenderwoche zur Bildung von Dateinamen hernehmen, dann kann ihnen dieser Bug von "Get-Date -uFormat %V" ihre schöne Ausgabe vernageln.
- PowerTip: Get Week of Year with
PowerShell
https://devblogs.microsoft.com/scripting/powertip-get-week-of-year-with-powershell/ - Calendar.GetWeekOfYear(DateTime,
CalendarWeekRule, DayOfWeek) Methode
https://docs.microsoft.com/de-de/dotnet/api/system.globalization.calendar.getweekofyear - Kalenderwoche in Powershell - wechselt
Donnerstags - dieses Jahr
https://administrator.de/forum/kalenderwoche-powershell-wechselt-donnerstags-jahr-307020.html
UNIX Timestamp
In der *nix-Welt hat sich als Zeitstempel oft die Anzahl der Sekunden seit 1.1.1970 bewährt. Ich war damit z.B. beim Parsen der Ladehistorie eines BMW 225xe (BMW 225xe Erfahrungsbericht) konfrontiert.
$unixTimeStamp = "1619257274" # Neu seit PowerShell 7.1 get-date -UnixTimeSeconds 1707140585 # vorher Variante 1 Get-Date 01/01/1970)+([System.TimeSpan]::fromseconds($unixTimeStamp)) # vorher Variante 2 (Get-Date 01/01/1970).AddSeconds($unixTimeStamp) # Beispiel (Get-Date 01/01/1970)+([System.TimeSpan]::fromseconds(1619257274)) Samstag, 24. April 2021 09:41:14
Auch in der Gegenrichtung können Sie natürlich einfach die Sekunden seit dem 1.1.1970 errechnen. am 5. Feb 2024 gegen 13:42 waren das
# Neu seit PowerShell 7.1 [int64](get-date -uformat %s) #Vorher [int64]((get-date) - (get-date 01.01.1970)).totalseconds 1707140585
-
Get-Date
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-date?view=powershell-7.4 - BMW 225xe Erfahrungsbericht
-
Converting Unix time to date time in
powershell
https://learn.microsoft.com/en-us/answers/questions/767021/converting-unix-time-to-date-time-in-powershell
Weitere Links
- PowerShell: Parsing Date and
Time
http://dusan.kuzmanovic.net/2012/05/07/PowerShell-parsing-date-and-time/ - Benutzerdefinierte
Formatzeichenfolgen für Datum und
Uhrzeit
http://msdn.microsoft.com/de-de/library/vstudio/8kb3ddd4.aspx - System.DateTime ParseExact
http://winPowerShell.blogspot.de/2006/09/systemdatetime-parseexact.html#!/2006/09/systemdatetime-parseexact.html - Get a Midnight DateTime
Value in Powershell
http://www.webofwood.com/2010/08/13/get-a-midnight-datetime-value-in-powershell/