How does PowerShell handle ((Get-Date).Month)-1 in January? - powershell

I wrote a Powershell/Robocopy backup script a few months back. It creates a folder for the backups with the year and month that the backups were taken (ex: 2019 11). The month always has to be one less because the script runs on the first of each new month. Everything has been smooth sailing but I just realized that I'm not really sure how the script will behave come January 1st. Does anyone have any incite as to what the output will be on January 1st, and is there a way for me to test this to confirm?
$month = (Get-Date -UFormat "%Y") + ' ' + ((((Get-Date).Month) - 1)).ToString()
# When run on November 1st, it creates a folder for the October backups called "2019 10".
# When run on December 1st, it creates a folder for the November backups called "2019 11".
When run on January 1st, what will it name the folder for the December backups? Will it be called "2019 12"? "2019 00"? Is there a way for me to easily test the behavior that relies on time, without manually adjusting my PC's calendar?

Get-Date optionally accepts a date to operate on (via -Date or positionally), which defaults to the current point in time.
Additionally, you can use -Day to modify the day-of-the-month part of the target date (as well as -Month and -Year, analogously); passing -Day 1 returns the date of the first of the month that the target date falls into.
Calling .AddDays(-1) on the resulting date is then guaranteed to fall in the previous month (it returns the previous month's last day).
The .ToString() method of System.DateTime allows you to perform custom string formatting of a date, using custom date and time format strings.
To put it all together:
# PRODUCTION USE:
# The reference date - now.
$now = Get-Date
# OVERRIDE FOR TESTING:
# Set $now to an arbitrary date, 1 January 2020 in this case.
# Note: With this syntax, *month comes first*, irrespective or the current culture.
$now = [datetime] '1/1/2020'
# Get the first day of the month of the date in $now,
# subtract 1 day to get the last day of the previous month,
# then use .ToString() to produce the desired format.
(Get-Date $now -Day 1).AddDays(-1).ToString('yyyy MM')
The above yields:
2019 12
Note: PowerShell's casts such as [datetime] '1/1/2020' generally use the invariant culture for stability of behavior across different cultures; this virtual culture is associated with the US-English culture and supports its month-first date format (e.g., 12/1/2020 refers to 1 December 2020, not 12 January 2020).
Surprisingly, by contrast, when you pass arguments to cmdlets, data conversions are culture-sensitive; that is, in the French culture (fr-FR), for instance, calling Get-Date 12/1/2020 would result in 12 January 2020, not 1 December 2020, which is what it returns in the US-English culture (en-US)).
This problematic discrepancy in behavior is discussed in this GitHub issue - the behavior is unlikely to change, however, in the interest of preserving backward compatibility.

You are able to create an arbitrary date like so
$testdate = get-date -year 2020 -month 1 -day 1
Your code however will then generate "2020 0" as an output.
You'd be better off with something like this. You would not be bound to running on the first day of the next month either:
$month = $(get-date (get-date $testdate).AddMonths(-1) -format "yyyy MM")

If you can guarantee that you will always run this on the first of the month, then you can use $folderName = ((Get-Date) - (New-TimeSpan -Days 1)).ToString("yyyy MM"). See Microsoft Docs on New-TimeSpan and this StackOverflow question on formatting dates
EDIT: hcm's answer leads to a better response here; instead of using the New-Timespan method above, modify my suggested code above to
$foldername = (Get-Date).AddMonths(-1).ToString("yyyy MM")
This eliminates the requirement that the supporting code be run on the first of the month.

Related

Why do my DateTime objects behave differently?

I have a script that checks for certain logs between two times $startDate and $endDate using Search-UnifiedAuditLog where $startDate is found by checking a line in a .txt file and $endDate is the current time. I run this as shown below.
$startDate = Get-Content $logPath -Last 1
$startDate = [datetime]$startDate
$startDate = $startDate.AddHours(-1)
$endDate = (Get-Date)
This particular script is run every hour, so the time between $startDate and $endDate is two hours (due to the AddHours). If I check the value of these variables, they are indeed two hours apart. However, when I run the script, it goes through the previous 6 hours of logs. This makes me think it is assuming that $startDate is in UTC, and it is converting it to my time zone. Is this what it is doing, and if so, how can I get my script to only check for logs one hour before the time listed in my .txt document?
Turning my comment into an answer
You can check the .Kind property of the $startDate variable.
This property can be either Local, Utc or Unspecified. See DateTimeKind Enum.
In case of Unspecified, since .NET 2.0, "This instance of DateTime is assumed to be a UTC time, and the conversion is performed as if Kind were Utc." as stated in the docs

How can I get the start of the calendar day from a DateTime object?

I'm trying to filter files older than a certain day, based on the day of the newest file in the directory.
I can get the LastWriteTime, which seems to be an exact time of, say 7/24/2016 10:48 AM for some file.
How can I strip the 10:48 AM part out of the object so I can compare against 7/24/2016 12:00 AM instead?
Use the Date property to retrieve a new DateTime value of midnight on the same date:
> $file.LastWriteTime
Sunday, July 24, 2016 10:48:00 AM
> $file.LastWriteTime.Date
Sunday, July 24, 2016 12:00:00 AM
The LastWriteTime property of the System.IO.DirectoryInfo and System.IO.FileInfo instances returned by Get-ChildItem and Get-Item returns an instance of type System.DateTime.
(Verify with: (Get-Item \).LastWriteTime.GetType().FullName)
System.DateTime represents a point in time (date + time), but has a .Date property, which returns another System.DateTime instance with the time-of-day portion set to midnight at the start of the input date, effectively representing a date only.
Compare
(Get-ChildItem \).LastWriteTime
to
(Get-ChildItem \).LastWriteTime.Date
$thing.LastWriteTime.Day # e.g. 10, (10th day of the month)
$thing.LastWriteTime.DayOfWeek # e.g. Wednesday

Converting other zone date time format to local date-time format in PowerShell

I am trying to perform date-time comparison.
Datetime in log file- Tue Aug 4 17:05:41 2015
Local system date time from get-date command -
6. elokuuta 2015 10:18:47
It's 6 Aug 2015 10:18:47 after I translated it on Google Translate.
How do I convert this date/time format, so I can perform comparison?
I tried multiple things and options, but no luck.
My script so far:
# To get today's date
$Today=[datetime]::Today
# Output is 6. elokuuta 2015 0:00:00
$log_date = "Tue Aug 4 17:05:41 2015"
I read somewhere that by using new-object system.globalization.datetimeformatinfo
it can be achieved, but I don't know how to change the current date-time with this. https://technet.microsoft.com/en-us/library/ff730960.aspx
[DateTime] objects do not have a format. It just a number of ticks (0.0000001 second) passed since some point in time (01/01/0001 00:00:00.0000000). The format is only needed to save a [DateTime] object as a string or to display it to the user, since 635744160000000000 ticks is not really understandable to a user.
All you need is to parse the string to get a [DateTime] object:
$log_date = [datetime]::ParseExact('Tue Aug 4 17:05:41 2015','ddd MMM d HH:mm:ss yyyy',[cultureinfo]::InvariantCulture,'AllowInnerWhite')
Now as you have two [DateTime] objects you can compare them:
$Today -eq $log_date
$Today -lt $log_date
$Today -gt $log_date
Or find a difference between them:
$Today - $log_date

How can I use get-date to change the month while controlling the format in Powershell

I just started working with Powershell and I am trying to manipulate a date such that I add two months to the date and set it as a var. Powershell handles this nicely and even handles year rollover. However I am stuck trying to figure out how to also control the format of the date before, during or after adding two months to the date. Both of the statements below give me what I want but i have not been able to figure out how to combine them.
$ndate = (Get-Date).AddMonths(2)
$exp = date -format MM/dd/yyyy
Thank you,
Get-Date (Get-Date).AddMonths(2) -f MM/dd/yyyy
When the -Format operator is not avaiable you can use the ToString method:
(Get-Date).AddMonths(2).ToString('MM/dd/yyyy')

Powershell's Get-date: How to get Yesterday at 22:00 in a variable?

For a check i need yesterday's date at 10:00pm in a variable.
I get yesterdays Date and current Time with
$a = (get-date).AddDays(-1)
But how do i manipulate the time to 22.00 and leave the variable still as Date-Object?
Use DateTime.Today as opposed to DateTime.Now (which is what Get-Date returns) because Today is just the date with 00:00 as the time, and now is the moment in time down to the millisecond. (from masenkablast)
> [DateTime]::Today.AddDays(-1).AddHours(22)
Thursday, March 11, 2010 10:00:00 PM
I see this topic, but in my case I was looking for a way to improve the format. Using UFormat and adding -1 day
(get-date (get-date).addDays(-1) -UFormat "%Y%m%d-%H%M")
(Get-Date (Get-Date -Format d)).AddHours(-2)
I saw in at least one other place that people don't realize Date-Time takes in times as well, so I figured I'd share it here since it's really short to do so:
Get-Date # Following the OP's example, let's say it's Friday, March 12, 2010 9:00:00 AM
(Get-Date '22:00').AddDays(-1) # Thursday, March 11, 2010 10:00:00 PM
It's also the shortest way to strip time information and still use other parameters of Get-Date. For instance you can get seconds since 1970 this way (Unix timestamp):
Get-Date '0:00' -u '%s' # 1268352000
Or you can get an ISO 8601 timestamp:
Get-Date '0:00' -f 's' # 2010-03-12T00:00:00
Then again if you reverse the operands, it gives you a little more freedom with formatting with any date object:
'The sortable timestamp: {0:s}Z{1}Vs measly human format: {0:D}' -f (Get-Date '0:00'), "`r`n"
# The sortable timestamp: 2010-03-12T00:00:00Z
# Vs measly human format: Friday, March 12, 2010
However if you wanted to both format a Unix timestamp (via -u aka -UFormat), you'll need to do it separately. Here's an example of that:
'ISO 8601: {0:s}Z{1}Unix: {2}' -f (Get-Date '0:00'), "`r`n", (Get-Date '0:00' -u '%s')
# ISO 8601: 2010-03-12T00:00:00Z
# Unix: 1268352000
Hope this helps!
Format in other syntax is possible in this way
[DateTime]::Today.AddDays(-1).ToString("yyyyMMdd")
When I was to get yesterday with just the date in the format Year/Month/Day I use:
$Variable = Get-Date((get-date ).AddDays(-1)) -Format "yyyy-MM-dd"
Yet another way to do this:
(Get-Date).AddDays(-1).Date.AddHours(22)
Another possible method but Unix timestamp
([int64](Get-Date -UFormat %s) - [int64](New-TimeSpan -Hours 1).TotalSeconds)