[How-To] Parse DateTime strings (PowerShell hybrid)
Posted: 21 Feb 2021 07:02
Preface: Aacini provided the excelent 3rd party tools StdTime.exe and StdDate.exe which have similar functionality. If they meet your requirements and if performance matters, you will likely prefer using them:
viewtopic.php?f=3&t=3428
Another possibility is dbenham's jTimestamp.bat which is a JScript hybrid and supports custom output formatting and time zone offsets.
viewtopic.php?f=3&t=7523
The code below contains the ParseDateTime macro which wraps .NET functions in a little PowerShell code. The date and/or time strings to be converted are redirected to the standard input. The current date and time is assumed for an empty string or in case nothing is redirected. The output of the macro is a space-separated string with 11 tokens.
Define the 'culture' environment variable to change the locale settings used to parse the redirected string.
You are able to customize the code by adding specific format patterns.
For more information refer to the description in the code.
Note: Even if all examples in the code below are successfully converted for me, don't assume they can be converted for you as well. They contain localized German strings which are expected to fail on machines where German isn't the default culture. Replace them with the localized strings for your default language settings.
Output on my machine:
Some information about the magic numbers in the code for those who are wondering ...
The 15 passed to the 4th parameter of ParseExact and the 143 passed to the 3rd parameter of Parse are DateTimeStyles AllowWhiteSpaces + NoCurrentDateDefault (+ RoundtripKind).
The 2 passed to the 2nd parameter of GetWeekOfYear is the CalendarWeekRule FirstFourDayWeek, the 1 passed to the 3rd parameter is the DayOfWeek Monday.
Steffen
viewtopic.php?f=3&t=3428
Another possibility is dbenham's jTimestamp.bat which is a JScript hybrid and supports custom output formatting and time zone offsets.
viewtopic.php?f=3&t=7523
The code below contains the ParseDateTime macro which wraps .NET functions in a little PowerShell code. The date and/or time strings to be converted are redirected to the standard input. The current date and time is assumed for an empty string or in case nothing is redirected. The output of the macro is a space-separated string with 11 tokens.
Define the 'culture' environment variable to change the locale settings used to parse the redirected string.
You are able to customize the code by adding specific format patterns.
For more information refer to the description in the code.
Note: Even if all examples in the code below are successfully converted for me, don't assume they can be converted for you as well. They contain localized German strings which are expected to fail on machines where German isn't the default culture. Replace them with the localized strings for your default language settings.
Code: Select all
@echo off &setlocal
:: https://www.dostips.com/forum/viewtopic.php?f=3&t=9971
:: The ParseDateTime macro reads a string from StdIn and tries to parse it as
:: DateTime value.
:: If an empty string is redirected or if the macro is executed without a
:: redirected string, the current date and time will be assumed.
:: If the macro succeeds to parse the string, it outputs 11 space-separated
:: values with fixed lengths:
:: yyyy MM dd HH mm ss fff n iii ww YYYY
:: yyyy year
:: MM month
:: dd day
:: HH hours (00 .. 24)
:: mm minutes
:: ss seconds
:: fff fraction (milliseconds)
:: n day of week (0 for Sunday .. 6 for Saturday)
:: iii day of year (001 .. 365/366)
:: ww ISO 8601 week of year
:: YYYY year of the ISO 8601 calendar week
:: Strings containing a time zone offset are converted to local time.
:: Date values which are not specified in the input string are set to 1.
:: Time values are set to 0, respectively.
::
:: DMTF (CIM_DATETIME), ISO 8601, and RFC1123 formatted datetime strings are
:: supported. Long and short date and time strings are converted as long as
:: they meet the invariant or the local format.
:: Define variable 'culture' before calling the macro to use the format of a
:: different culture to parse the redirected string. E.g.:
:: set "culture=sv-SE"
:: This makes the macro use invariant or Swedish format.
:: For a list of possibly supported culture names refer to column 'Language
:: tag' in the related table:
:: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c
:: Extra spaces in the converted string are automatically ignored.
:: For more information about standard formats refer to:
:: https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings
::
:: The macro can be customized by specifying additional format strings. Right
:: now the list of custom formats in $cfo contains only one string for input
:: from the asctime C function (as used in ROBOCOPY). However, more format
:: strings can be appended, separated by a comma each. E.g.:
:: $cfo=[string[]]('ddd MMM d HH:mm:ss yyyy','HH:mm \h');^
:: For a list of custom format specifiers and syntax refer to:
:: https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings
set ParseDateTime=powershell -nop -ep Bypass -c ^"$s=[string]$input;if(-Not $s){$dt=[DateTime]::Now}else{^
$cfo=[string[]]('ddd MMM d HH:mm:ss yyyy');^
$ic=[Globalization.CultureInfo]::InvariantCulture;^
$lc=if($env:culture){try{[Globalization.CultureInfo]$env:culture}catch{exit 1}}else{$null};^
try{$dt=[DateTime]::ParseExact($s,$cfo,$ic,15)}catch{^
try{$dt=[DateTime]::ParseExact($s,$cfo,$lc,15)}catch{^
try{$dt=[Management.ManagementDateTimeConverter]::ToDateTime($s)}catch{^
$s=$($s -replace '(\d),(\d)','$1.$2') -replace 'Z\b','+0:00';^
try{$dt=[DateTime]::Parse($s,$ic,143)}catch{^
try{$dt=[DateTime]::Parse($s,$lc,143)}catch{^
exit 1}}}}}}$dow=[int]$dt.DayOfWeek;$cw=$(Get-Culture).Calendar.GetWeekOfYear($(if($dow -match '[1-3]'){$dt.AddDays(3)}else{$dt}),2,1);^
$yow=if($dt.Month -eq 1 -and $cw -gt 51){$dt.Year-1}else{$dt.Year};^
($dt.toString('yyyy MM dd HH mm ss fff ')+$dow+$dt.DayOfYear.toString(' 000')+$cw.toString(' 00')+$yow.toString(' 0000'))^"
:: EXAMPLES:
echo *** no redirected string, leading zeros removed using SET /A ***
for /f "tokens=1-11" %%a in ('%ParseDateTime%') do set /a "year=1%%a-10000,mon=1%%b-100,day=1%%c-100,hh=1%%d-100,mm=1%%e-100,ss=1%%f-100,ms=1%%g-1000,dow=%%h,doy=1%%i-1000,woy=1%%j-100,yow=1%%k-10000"
echo year %year%
echo month %mon%
echo day %day%
echo hours %hh%
echo minutes %mm%
echo seconds %ss%
echo milliseconds %ms%
echo day of week %dow%
echo day of year %doy%
echo week of year %woy%
echo year of week %yow%
echo(
echo *** several tests, beginning with an empty string ***
for %%S in (
"" %= an empty string results in the output of the current date and time =%
"19720126204745.800000+060" %= DMTF formatted (as received from WMI) =%
"26.01.1972 20:47:45,80" %= date and time variables, localized German output (comma as decimal separator) =%
" 26. 01. 1972 20: 47: 45,80 " %= date and time, localized German, extra spaces =%
"26/01/1972 20:47:45.80" %= different date and decimal separators =%
"26.01.1972" %= date only, localized German output =%
"26.01.72" %= date only, localized German, year with 2 digits =%
"1972-01-26" %= different order and separator, ISO 8601 =%
"Januar 1972" %= German month + year =%
"January 1972" %= English month + year =%
"20:47:45,80" %= time only, localized German =%
"08:47:45.80 PM" %= time only, English =%
"20:47:45" %= time without fraction =%
"20:47" %= time without seconds =%
"1972-01-26T20:47:45.8Z" %= RFC3339, ISO 8601 UTC notation =%
"1972-01-26T20:47:45.8+1:00" %= RFC3339, ISO 8601 with offset =%
"Wed Jan 26 20:47:45 1972" %= C asctime()-like (as received from ROBOCOPY) =%
"Mi Jan 26 20:47:45 1972" %= same but localized German =%
"Wed 1972-Jan-26 20:47:45" %= as received from MAKECAB =%
"Wed, 26 Jan 1972 20:47:45 GMT" %= RFC1123 =%
"Mittwoch, 26. Januar 1972 20:47:45" %= long localized German =%
"January, 26 1972 20:47:45" %= long English =%
"Wednesday, January 26, 1972 8:47:45.8 PM" %= another long English =%
) do (
REM Note that the surrounding quotes are removed for the redirection.
echo(%%~S
for /f "tokens=1-11" %%a in ('echo(%%~S^|%ParseDateTime%') do echo -^> y:%%a M:%%b d:%%c H:%%d m:%%e s:%%f ms:%%g DoW:%%h DoY:%%i WoY:%%j YoW:%%k
echo(
)
echo(
echo *** use Swedish culture settings for a localized string ***
set "culture=sv-SE"
set "S=den 26 januari 1972 20:47:45"
echo(%S%
for /f "tokens=1-11" %%a in ('echo(%S%^|%ParseDateTime%') do echo -^> y:%%a M:%%b d:%%c H:%%d m:%%e s:%%f ms:%%g DoW:%%h DoY:%%i WoY:%%j YoW:%%k
echo(
pause
Output on my machine:
Code: Select all
*** no redirected string, leading zeros removed using SET /A ***
year 2021
month 2
day 26
hours 18
minutes 3
seconds 2
milliseconds 978
day of week 5
day of year 57
week of year 8
year of week 2021
*** several tests, beginning with an empty string ***
-> y:2021 M:02 d:26 H:18 m:03 s:03 ms:260 DoW:5 DoY:057 WoY:08 YoW:2021
19720126204745.800000+060
-> y:1972 M:01 d:26 H:20 m:47 s:45 ms:800 DoW:3 DoY:026 WoY:04 YoW:1972
26.01.1972 20:47:45,80
-> y:1972 M:01 d:26 H:20 m:47 s:45 ms:800 DoW:3 DoY:026 WoY:04 YoW:1972
26. 01. 1972 20: 47: 45,80
-> y:1972 M:01 d:26 H:20 m:47 s:45 ms:800 DoW:3 DoY:026 WoY:04 YoW:1972
26/01/1972 20:47:45.80
-> y:1972 M:01 d:26 H:20 m:47 s:45 ms:800 DoW:3 DoY:026 WoY:04 YoW:1972
26.01.1972
-> y:1972 M:01 d:26 H:00 m:00 s:00 ms:000 DoW:3 DoY:026 WoY:04 YoW:1972
26.01.72
-> y:1972 M:01 d:26 H:00 m:00 s:00 ms:000 DoW:3 DoY:026 WoY:04 YoW:1972
1972-01-26
-> y:1972 M:01 d:26 H:00 m:00 s:00 ms:000 DoW:3 DoY:026 WoY:04 YoW:1972
Januar 1972
-> y:1972 M:01 d:01 H:00 m:00 s:00 ms:000 DoW:6 DoY:001 WoY:52 YoW:1971
January 1972
-> y:1972 M:01 d:01 H:00 m:00 s:00 ms:000 DoW:6 DoY:001 WoY:52 YoW:1971
20:47:45,80
-> y:0001 M:01 d:01 H:20 m:47 s:45 ms:800 DoW:1 DoY:001 WoY:01 YoW:0001
08:47:45.80 PM
-> y:0001 M:01 d:01 H:20 m:47 s:45 ms:800 DoW:1 DoY:001 WoY:01 YoW:0001
20:47:45
-> y:0001 M:01 d:01 H:20 m:47 s:45 ms:000 DoW:1 DoY:001 WoY:01 YoW:0001
20:47
-> y:0001 M:01 d:01 H:20 m:47 s:00 ms:000 DoW:1 DoY:001 WoY:01 YoW:0001
1972-01-26T20:47:45.8Z
-> y:1972 M:01 d:26 H:21 m:47 s:45 ms:800 DoW:3 DoY:026 WoY:04 YoW:1972
1972-01-26T20:47:45.8+1:00
-> y:1972 M:01 d:26 H:20 m:47 s:45 ms:800 DoW:3 DoY:026 WoY:04 YoW:1972
Wed Jan 26 20:47:45 1972
-> y:1972 M:01 d:26 H:20 m:47 s:45 ms:000 DoW:3 DoY:026 WoY:04 YoW:1972
Mi Jan 26 20:47:45 1972
-> y:1972 M:01 d:26 H:20 m:47 s:45 ms:000 DoW:3 DoY:026 WoY:04 YoW:1972
Wed 1972-Jan-26 20:47:45
-> y:1972 M:01 d:26 H:20 m:47 s:45 ms:000 DoW:3 DoY:026 WoY:04 YoW:1972
Wed, 26 Jan 1972 20:47:45 GMT
-> y:1972 M:01 d:26 H:20 m:47 s:45 ms:000 DoW:3 DoY:026 WoY:04 YoW:1972
Mittwoch, 26. Januar 1972 20:47:45
-> y:1972 M:01 d:26 H:20 m:47 s:45 ms:000 DoW:3 DoY:026 WoY:04 YoW:1972
January, 26 1972 20:47:45
-> y:1972 M:01 d:26 H:20 m:47 s:45 ms:000 DoW:3 DoY:026 WoY:04 YoW:1972
Wednesday, January 26, 1972 8:47:45.8 PM
-> y:1972 M:01 d:26 H:20 m:47 s:45 ms:800 DoW:3 DoY:026 WoY:04 YoW:1972
*** use Swedish culture settings for a localized string ***
den 26 januari 1972 20:47:45
-> y:1972 M:01 d:26 H:20 m:47 s:45 ms:000 DoW:3 DoY:026 WoY:04 YoW:1972
Drücken Sie eine beliebige Taste . . .
The 15 passed to the 4th parameter of ParseExact and the 143 passed to the 3rd parameter of Parse are DateTimeStyles AllowWhiteSpaces + NoCurrentDateDefault (+ RoundtripKind).
The 2 passed to the 2nd parameter of GetWeekOfYear is the CalendarWeekRule FirstFourDayWeek, the 1 passed to the 3rd parameter is the DayOfWeek Monday.
Steffen