Tip: DateTime Durations in Python

Addressing a question where a REST API is used to schedule an automation plan, where a literal start time is not accepted. Per the schema, only TimeOffsets are allowed.

<xsd:complexType name="PlanSchedule" mixed="true">
            <xsd:sequence>
                <xsd:element name="HasStartTime"        type="xsd:boolean"/>
                <xsd:element name="StartDateTimeOffset" type="TimeInterval" minOccurs="0" maxOccurs="1" />
                <xsd:element name="HasEndTime"	        type="xsd:boolean"/>
                <xsd:element name="EndDateTimeOffset"   type="TimeInterval" minOccurs="0" maxOccurs="1" />
                <xsd:element name="UseUTCTime"          type="xsd:boolean"/>
            </xsd:sequence>
        </xsd:complexType>

where TimeOffset is an ISO8601 Duration – actually a subset of the ISO8601 Duration, as we don’t accept Years or Months, only Days,Hours,Minutes,Seconds

<xsd:simpleType name="TimeInterval">
            <xsd:restriction	base="xsd:duration">
                <!--
                    Duration Data Type:
    
                    The duration data type is used to specify a time interval.
                    The time interval is specified in the following form "PnYnMnDTnHnMnS" where:
    
                    * P indicates the period (required)
                    * nY indicates the number of years (not used here)
                    * nM indicates the number of months (not used here)
                    * nD indicates the number of days
                    * T indicates the start of a time section (required if you are going to specify hours, minutes, or seconds)
                    * nH indicates the number of hours
                    * nM indicates the number of minutes
                    * nS indicates the number of seconds			 
                 -->
                <xsd:pattern value="\-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+(\.[0-9]{1,6})?S)?)?" />
            </xsd:restriction>
        </xsd:simpleType>

This Python snippet, given Date/Time String, should calculate the time duration between the given time and the current time, and output an ISO8601-formatted string that we can accept:

import datetime

# The time we want the action to start, and the format in which we supply the time string
#   see https://docs.python.org/3/library/datetime.html
schedule_time = datetime.datetime.strptime(
    "2022-09-30 23:30:00 -0600", "%Y-%m-%d %H:%M:%S %z"
)

# The current time now, with local machine time zone
time_now = datetime.datetime.astimezone(datetime.datetime.now())

# the python datetime duration between the schedule and now
time_delta = schedule_time - time_now
# the total number of seconds in that interval, cast as Integer to ignore milliseconds and microseconds
seconds = int(time_delta.total_seconds())

  # split minutes and seconds, then hours and minutes, then days and hours

minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
days, hours = divmod(hours, 24)
iso_duration = f"P{days}DT{hours}H{minutes}M{seconds}S"
print(iso_duration)

In my case, given that I executed this shortly after 2022-09-27 at 4pm CST, the result printed is

P3DT8H22M36S

representing a Period of 3 Days, with Time of 8 Hours, 22 Minutes, and 36 Seconds

3 Likes

Since I’m normally not working with Python, but Powershell, I converted your script.

$Schedule_Time = “2022-09-30 23:30:00 -0600”
$Time_Now = Get-Date
$Time_Delta = (Get-Date $Schedule_Time) - (Get-Date $Time_Now)
“P$($Time_Delta.Days)DT$($Time_Delta.Hours)H$($Time_Delta.Minutes)M$($Time_Delta.Seconds)S”

Result for me

P2DT22H48M11S

When it was run Wednesday 28. September 2022 08:41:48 my timezone (CEST)

4 Likes

Incase anyone was wondering why you would not want to directly leverage the built-in .NET library support for doing the ISO8601 duration string conversion (which handles all the edge cases) with:

$Schedule_Time = "2022-09-30 23:30:00 -0600"
$Time_Delta = (Get-Date $Schedule_Time) - (Get-Date)
[System.Xml.XmlConvert]::ToString($Time_Delta)

This will produce a result that [almost] fully complies with the format except it will produce up to 7 digits of fractional second precision (and the API allows at-most 6). To compensate for this and continue to get the other benefits provided by the library routine, you can just remove the milliseconds from the duration (which for this use case is way overboard to even include in the first place)

$Schedule_Time = "2022-09-30 23:30:00 -0600"
$Time_Delta = (Get-Date $Schedule_Time) - (Get-Date)
$Time_Delta -= ($Time_Delta.Ticks % [TimeSpan]::TicksPerSecond)
[System.Xml.XmlConvert]::ToString($Time_Delta)
3 Likes

Hopefully one day this will be more user-friendly and doesn’t require to use a lot of code to define start_time and end_time for an action.

Which xml schema is this? Where do I find it?

You actually don’t need to calculate an offset for a regular bigfix action, see the BES.xsd schema here where you can specify a specific start time:

<xs:element name="HasStartTime" type="xs:boolean" minOccurs="0"/>
<!-- 
	Use StartDateTimeOffset to specify the start time using GMT as the reference time.
	Use StartDateTimeLocalOffset to specify the start time using local time as the reference time.
				
	These are set according to the ISO 8601 specification.
				
	For example, suppose it's January 1, 12:00 local time and your time zone is GMT-8.
				
	"StartDateTimeOffset = P1DT2H" sets the start time to January 2, 22:00.
	"StartDateTimeLocalOffset = P1DT2H" sets the start time to January 2, 14:00.
				
	You probably want to use StartDateTimeLocalOffset, which was added in 8.1 patch #2.

	Use StartDateTime to specify the exact start time using GMT as the reference time.
	Use StartDateTimeLocal to specify the exact start time using the local time as the reference time.

	These are set using the format 'yyyy-mm-dd hh:mm:ss'
			 -->
<xs:choice>
<xs:element name="StartDateTimeOffset" type="TimeInterval" minOccurs="0"/>
<xs:element name="StartDateTimeLocalOffset" type="TimeInterval" minOccurs="0"/>
<xs:element name="StartDateTime" type="DateTime" minOccurs="0"/>
<xs:element name="StartDateTimeLocal" type="DateTime" minOccurs="0"/>
</xs:choice>

I believe what @JasonWalker said above only applies to server automation plans.