Powershell format date after .AddDays() method - powershell

I can't find a way to format a date after using .AddDays()
CODE
[datetime] $searchDate = '2020-01-10'
$searchDate = '{0:yyyy-MM-dd}' -f $searchDate.AddDays(1)
returns "Saturday, January 11, 2020 12:00:00 AM" while I'm looking for 2020-01-11

tl;dr
# NOTE: [datetime] must be on the RHS if you want to assign a different type later.
$searchDate = [datetime] '2020-01-10'
$searchDate = '{0:yyyy-MM-dd}' -f $searchDate.AddDays(1)
Of course, you can combine that into a single assignment:
$searchDate = '{0:yyyy-MM-dd}' -f ([datetime] '2020-01-10').AddDays(1)
Or, via Get-Date:
$searchDate = Get-Date ([datetime] '2020-01-10').AddDays(1) -Format yyyy-MM-dd
Your own solution simply bypasses the conceptual problem with your code, which Jeroen Mostert describes well in a comment on the question.
[datetime] $searchDate = '2020-01-10'
By placing the cast ([datetime]) to the left of the variable ($searchDate) in your assignment, you type-constrain it.
This means that any values assigned later are invariably and implicitly coerced (converted) to the specified type ([datetime], in this case).
Therefore, you mustn't use the same variable to assign your string representation of a date, obtained with the -f operator, as that string representation is automatically reconverted to [datetime].
That is, after executing
$searchDate = '{0:yyyy-MM-dd}' -f $searchDate.AddDays(1), $searchData again contains a [datetime] instance, not the string of interest.
Another solution is to simply assign to a different variable, one that either isn't type-constrained or is constrained to [string].

Solved with Get-Date $searchDate -Format yyyy-MM-dd

Related

Powershell - Find the latest Friday

How can the following code be modified to identify the latest Friday within the past week (instead of the next one), but with formatting?
$Date = #(#(0..7) | % {$(Get-Date).AddDays($_)} | ? {$_.DayOfWeek -ieq "Friday"})[0]
Source: https://stackoverflow.com/a/23939203/5651418
The post you linked to offers a more elegant solution, which you can adapt as follows:
# Get the most recent Friday relative to the given date,
# which may be that date itself.
$mostRecentFriday =
($date = Get-Date).AddDays((-7 - $date.DayOfWeek + [DayOfWeek]::Friday) % 7)
If you want to create a formatted string representation of the resulting [datetime] instance (all examples below yield something like '07 01 2022':
To use Unix-style format specifiers, use Get-Date's -UFormat parameter:
Get-Date $mostRecentFriday -UFormat '%d %m %Y'
To use .NET's format specifiers, use Get-Data's -Format parameter:
Get-Date $mostRecentFriday -Format 'dd MM yyyy'
Alternatively, pass the format string to the [datetime]
instance's .ToString() method:
$mostRecentFriday.ToString('dd MM yyyy')
If I understood correctly, your expected output would be 1 7 2022, I would personally use a do loop that stops as soon as the DayOfWeek Property of the DateTime instance is Friday:
$date = [datetime]::Now
do {
$date = $date.AddDays(-1)
} until($date.DayOfWeek -eq [DayOfWeek]::Friday)
$date.ToString("d M yyyy")
I noticed that some Get-Date -UFormat specifiers didn't seem to work when attempting to incorporate them into an output string.
Should anyone need to incorporate some rarely needed ones (like 'Week of Year' (%G), 'Day of Year (%j), etc) you could preset needed variables and add them to the output string:
$DayOfYear = (Get-Date -UFormat %j)
$WeekOfYear = (Get-Date -UFormat %V)
$Date = #(#(0..7) | % {$(Get-Date).AddDays(-$_)} | ? {$_.DayOfWeek -ieq "Wednesday"})[0].ToString("MM-dd-yyyy|Week $WeekOfYear|'Day' $DayOfYear")
I imagine someone could incorporate all the code into one Powershell command.
Additional Get-Date -UFormat specifiers: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-date?view=powershell-7.2#notes

Split and regex match with Powershell

Say I have a filename string, something like:
test_ABC_19000101_010101.987.txt,
Where "test" could be any combination of white space, characters, numbers, etc. I wish to extract the 19000101_010101 part (date and time) with Powershell. Currently I am assigning -split "_ABC_" to a variable and taking the second element of the array. I am then splitting this string subsequent times. Is there a way to accomplish this in one go?
PS
"_ABC_" is constant, occurring unchanged in all instances of filename(s).
A more concise - albeit perhaps more obscure - alternative to Santiago Squarzon's helpful answer:
# Construct a regex that consumes the entire file name while
# using capture groups for the parts of interest.
$re = '.+_ABC_(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})\.(\d{3})\..+'
[datetime] (
# In the replacement string, use $1, $2, ... to refer to what the
# first, second, ... capture group captured.
'test_ABC_19000101_010101.987.txt' -replace $re, '$1-$2-$3T$4:$5:$6.$7'
)
Output:
Monday, January 1, 1900 1:01:01 AM
The -replace operation results in string '1900-01-01T01:01:01.987', which is a (culture-invariant) format that you can use as-is with a [datetime] cast.
Note that with a Get-ChildItem call as input you could slightly simplify the regex by providing $_.BaseName rather than $_.Name as the -replace LHS, which obviates the need to also match the extension (.\.+) in the regex.
An aside re the [datetime] cast: [datetime] '...' results in a [datetime] instance that is an unspecified timestamp (its .Kind property value is Unspecified), i.e. it is undefined whether it represents as Local or a Utc timestamp.
To get a Local timestamp, use
[datetime]::Parse('...', [cultureinfo]::InvariantCulture, 'AssumeLocal')
(use 'AssumeLocal, AdjustToUniversal' to get a Utc timestamp).
Alternatively, you can cast to [datetimeoffset] - a type that is generally preferable to [datetime] - which interprets a string cast to it as local by default. (You can then access its .LocalDateTime / .UtcDateTime properties to get Local / Utc [datetime] instances).
This regex seems an overkill but I think it should work, as long as _ABC_ is constant and there is a _ to separate the date from the time and a . to separate time from milliseconds:
$re = [regex]'(?<=_ABC_)(?<date>\d*)_(?<time>\d*)\.(?<millisec>\d*)(?=\.)'
#'
test_ABC_19000101_010101.987.txt
t' az# 0est_ABC_20000101_090101.123.txt
tes8as712t_ABC_21000101_080101.456.txt
te098d $st_ABC_22000101_070101.789.txt
[test]_ABC_23000101_060101.101.txt
t?\est_ABC_24000101_050101.112.txt
'# -split '\r?\n' | ForEach-Object {
$groups = $re.Match($_).Groups
$date = $groups['date']
$time = $groups['time']
$msec = $groups['millisec']
[datetime]::ParseExact(
"$date $time $msec",
"yyyyMMdd HHmmss fff",
[cultureinfo]::InvariantCulture
)
}
See https://regex101.com/r/8oSpqf/1 for details.
If there will never be multiple sequences in the filename that appear as the timestamp (8 digits, _, 6 digits, then you could match on that pattern of digits.
PS C:\> 'test_ABC_19000101_010101.987.txt' -match '^.*ABC_(\d{8}_\d{6})\..*'
True
PS C:\> $Matches
Name Value
---- -----
1 19000101_010101
0 test_ABC_19000101_010101.987.txt
PS C:\> $Matches[1]
19000101_010101
You would use the filename instead of the explicit string.
If you want to get a [System.DateTime] from it:
PS C:\> [datetime]::ParseExact($Matches[1], 'yyyyMMdd_HHmmss', $null)
Monday, January 1, 1900 01:01:01

Powershell - compare date with CSV date

I am trying to compare a date entered with a date from a CSV and display a message if the date entered is before the date in the CSV.
Am having difficulty, unsure if I need to convert the date in the CSV from string for it to be compared correctly.
Have got this far with the code:
$CSV=Import-Csv C:\Users\person\Desktop\date.csv
$InputDate = Get-Date -Format "MM/dd/yyyy" (Read-Host -Prompt 'Enter the date')
$CreatedDate = Get-Date -Format "MM/dd/yyyy HH:mm" $CSV.updated
if($InputDate -gt $CreatedDate) {
write-host "Input Newer"
}
else {
write-host "Input Older"
}
The comparison doesn't seem to work correctly.
CSV formatted as below (contains a single row, so $CSV.updated can be assumed to contain a single date string):
updated
11/07/2016 16:14
You need to parse the date in string to datetime type to do the date comparisons.
$CSV = Import-Csv C:\Users\person\Desktop\date.csv
$InputDate = [datetime]::Parse( $(Read-Host -Prompt 'Enter the date (MM/dd/yyyy)') )
$CreatedDate = [datetime]::Parse( $CSV.updated )
if($InputDate -gt $CreatedDate)
{write-host "Input Newer"}
else
{write-host "Input Older"}
Kiran Patnayakuni's answer provides an effective solution; let me make it clearer what the problem was:
You seemingly expected -Format "MM/dd/yyyy" to specify an input parsing format; in fact, it formats the output, which then becomes a [string].
For meaningful date comparison you need a [datetime] instance, however.
Your arguments to the (implied) -Date parameter ((Read-Host ...), $csv.updated) were implicitly parsed, based on the standard formats recognized by the current culture (System.Globalization.CultureInfo.CurrentCulture).
Therefore:
If your arguments are already properly recognized by the implicit parsing (as your acceptance of Kiran's answer suggests), the solution is to simply remove the -Format arguments from your code:
# Parse each input string implicitly, based on the current culture,
# and output a [datetime] instance, which will compare as expected
# with `-gt`
$InputDate = Get-Date (Read-Host -Prompt 'Enter the date')
$CreatedDate = Get-Date $CSV.updated
If, by contrast, you do need to parse by the "MM/dd/yyyy" format string explicitly in order for your arguments to be recognized, call .NET method [datetime]::ParseExact(<string>, <format>[, <culture>]) (System.DateTime.ParseExact); e.g.:
# Note: The input string must match the format *exactly*
$InputDate = [datetime]::ParseExact(
(Read-Host -Prompt 'Enter the date'), # input string
"MM/dd/yyyy", # format string
$null # default to the current culture's rules
)
Read on for background information.
Get-Date input and output formats an data types:
-Format "MM/dd/yyyy" doesn't specify an input parsing format, it specifies the output string-formatting format, which has two implications:
String input passed to input parameter -Date (which the first positional argument is bound to) is implicitly parsed as [datetime], based on the standard string formats recognized by the current culture[1]; in effect, the following is called behind the scenes:
[datetime]::Parse((Read-Host -Prompt 'Enter the date'), [cultureinfo]::CurrentCulture)
(Omitting [cultureinfo]::CurrentCulture in the call above has the same effect.)
Caveat: When you cast a string to [datetime], it is the invariant culture ([cultureinfo]::InvariantCulture, based on US-English) that is applied, which PowerShell often does for stability of code across cultures; that a cmdlet such as Get-Date is not culture-invariant is a historical accident that won't be corrected for fear of breaking old code; see this GitHub issue for background. For instance, with culture fr-FR (French) in effect, [datetime] '12/1] yields December 1st (month first), whereas Get-Date -Date '12/1' yields January 12 (day first).
The Get-Date command will return a (formatted) string rather than a [datetime] instance; that is, the [datetime] instance implicitly parsed on input is formatted based on the format string passed to -Format, based on the rules of the current culture; if $dt contains a [datetime] instance, -Format "MM/dd/yyyy" is the equivalent of (note how a direct call to System.DateTime.ToString() gives you the option to specify a different culture, which -Format doesn't):
$dt.ToString("MM/dd/yyyy", [cultureinfo]::CurrentCulture)
(As of PowerShell v7.0), the Get-Date cmdlet has no support for specifying a format string for input parsing.
As stated, passing a string to -Date causes it to be implicitly parsed, based on the standard date/time formats recognized by the current culture.
You'll have to call .NET method [datetime]::ParseExact(<string>, <format>[, <culture>]) (System.DateTime.ParseExact) directly, as demonstrated in the top section.
Once you have a [datetime] instance, you may pass it (via -Date) to Get-Date -Format to get a formatted string representation for the current culture; alternatively, you can call .ToString(<format>[, <culture>]) (System.DateTime.ToString) directly on the instance, which also gives you the option to format for a different culture.
As for Get-Date input and output data types:
Fundamentally, how the input is passed (specific arguments) determines the how the resulting [datetime] instance is interpreted, based on how the cmdlet chooses the instance's
.Kind property value (Utc, Local, or Unspecified).
The .Kind property value determines what specific global point in time, if any, a [datetime] instance represents. The .Kind value can also affect an instance's string representation, depending on what specific formatting is applied (notably, inclusion of time-zone information).
The resulting [datetime] instance is:
either: output directly (by default)
or: if a -Format argument was passed, is the basis for deriving the requested string representation ([string]).
Get-Date determines the .Kind property value as follows:
With no -Date argument, you get a Local instance (representing the current point in time)
(Unless a string is also passed to -Date), using the various offset parameters, such as -Year and -Day, produces a Local instance as well.
With a -Date argument that already is of type [datetime], that instance is used as-is, which means the existing .Kind property value is retained.
With a string -Date argument (which gets implicitly parsed), the .Kind value will be Unspecified (representing an abstract point in time, without reference to a specific time zone), including if combined with parameters such as -Year.
A numeric -Date argument (e.g, 637165787436900010) is interpreted as a System.DateTime.Ticks value, which also produces an Unspecified instance.
Note: PowerShell [Core] 7.1 will introduce an -AsUTC switch that makes the output / to-be-string-formatted instance be of kind Utc; -AsLocal and -AsUnspecified switches (and/or an -AsKind <kind> parameter) are also being discussed - see this GitHub issue.
[1] If the input already is a [datetime] instance, it is used as-is.

Culture based formatting of time variable

In this example it seems to me that the first two outputs should match, giving me formatting based on my defined culture. The last should be different because French formatting is different. Instead, the last two are the same, and are both getting some kind of default formatting. So, how do I do Culture based formatting when the time is a variable rather than formatting directly with Get-Date? It seems like it should be the same, but it's not.
get-date -format ((Get-Culture).DateTimeFormat.FullDateTimePattern)
$time = Get-Date
$pattern = 'FullDateTimePattern'
$formattedTime = $time -f (Get-Culture).DateTimeFormat.$pattern
Write-Host "$formattedTime"
$culture = New-Object system.globalization.cultureinfo('fr-FR')
$formattedTime = $time -f ($culture).DateTimeFormat.$pattern
Write-Host "$formattedTime"
The output I get is
July 9, 2019 11:22:01 AM
07/09/2019 11:22:01
07/09/2019 11:22:01
What I want to get is
July 9, 2019 11:26:46 AM
July 9, 2019 11:26:46 AM
Tuesday 9 July 2019 11:26:46
EDIT: So, based on I.T Delinquent's response, I tried this
$pattern = 'longDateTimePattern'
$date = Get-Date
$format = (Get-Culture).DateTimeFormat.$pattern
$string = ($date).ToString($format)
Write-Host $string
$culture = New-Object system.globalization.cultureinfo('de-DE')
$format = $culture.DateTimeFormat.$pattern
$string = ($date).ToString($format)
Write-Host $string
And it gave me identical results. Because it's not 'longDateTimePattern', its 'longDatePattern'. Given that the pattern could become a user supplied string, I better validate them.
Your attempt at using the -f operator is flawed (see bottom section).
To get the desired output, use the [datetime] type's appropriate .ToString() overload:
$time.ToString($culture.DateTimeFormat.$pattern, $culture)
Passing $culture as the 2nd argument ensures that the formatting is applied in the context of that culture.
If your intent is truly to use a format from another culture and apply it in the context of the current culture, simply omit the 2nd argument (as an alternative to the Get-Date -Format approach in your question):
$time.ToString($culture.DateTimeFormat.$pattern)
If there's no need to involve a different culture, the task becomes much simpler, by way of the standard date-time format strings, where single-character strings such as "D" refer to standard formats, such as LongDatePattern:
$time.ToString("D")
You can also pass these strings to Get-Date -Format
Get-Date -Format D
As for what you tried:
In order for the -f operator to work correctly, your LHS must be a string template with placeholders ({0} for the first one, {1} for the second, ...), to be replaced with the RHS operands.
Using a simple example:
Format the RHS, an [int], as a number with 2 decimal places.
PS> '{0:N2}' -f 1
1.00
Therefore, $time -f (Get-Culture).DateTimeFormat.$pattern doesn't perform (explicit) formatting at all, because the LHS - $time - contains no placeholders.
That is, the RHS is ignored, and the LHS is returned as a string: It is effectively the same as calling $time.ToString() in the context of the invariant culture (because the result of applying the -f operator is always a string and PowerShell uses the invariant culture in many string-related contexts).
While you can incorporate a specific date-time format string into a template-string placeholder - by following the placeholder index with : and a format string, as shown above ({0:N2}) - you cannot also provide a culture context for it.
You'd have to (temporarily) switch to the desired culture first:
# Save the currently effective culture and switch to the French culture
$prev = [cultureinfo]::CurrentCulture
[cultureinfo]::CurrentCulture = 'fr-FR'
# Format with the desired format string.
"{0:$($culture.DateTimeFormat.$pattern)}" -f $time
[cultureinfo]::CurrentCulture = $prev
I think this has something to do with how the Get-Date is passed using the variable, it seems to lose the format capability. In fact, if you try using Write-Host ($date -Format $format) gives an error:
Unexpected token '$format' in expression or statement
Here are my setup variables:
$pattern = 'FullDateTimePattern'
$format = (Get-Culture).DateTimeFormat.$pattern
$date = Get-Date
As stated above, using Write-Host ($date -f $format) and incorrectly outputs 07/09/2019 12:24:38. However, using any of the below options does work and correctly outputs 09 July 2019 12:24:38:
Write-Host (Get-Date -Format $format)
Write-Host (Get-Date).ToString($format)
Write-Host ($date).ToString($format)
Hope this helps :)

Convert existing DateTime to a different format

I have a DateTime object from a Get-ADUser which is stored into $logonDt. This is actually the returned value from an attribute in AD, namely LastLogonDate.
A gettype() confirms it is of type DateTime but it is in an American layout.
How can I take this existing DateTime object and re-format as UK, and dd/mm/yyyy
You can use Get-Date $logondt -f "dd/MM/yyyy" and change the format string as you like.
See https://technet.microsoft.com/library/hh849887.aspx for the details.
You cannot change the formatting after it is stored in the variable but you could access the single values, like $logondt.day etc.
//PowerShell's string formatting (–f) operator:
PS >$date = [DateTime] "05/09/1998 1:23 PM"
PS >"{0:dd-MM-yyyy # hh:mm:ss}" -f $date
09-05-1998 # 01:23:00
//CMDLET
PS >Get-Date -Date "05/09/1998 1:23 PM" -Format "dd-MM-yyyy # hh:mm:ss"
09-05-1998 # 01:23:00
Get-Date -format 'MM/dd/YYY
The format can be whatever you want and there are characters to represent almost every kinda of date-time unit (month, day, century, etc.)
You can find more info here.
https://technet.microsoft.com/en-us/library/hh849887.aspx