Convert snmp octet string to human readable date format - python

Using the pysnmp framework i get some values doing a snmp walk. Unfortunately for the oid
1.3.6.1.21.69.1.5.8.1.2 (DOCS-CABLE-DEVICE-MIB)
i get a weird result which i cant correctly print here since it contains ascii chars like BEL ACK
When doing a repr i get:
OctetString('\x07\xd8\t\x17\x03\x184\x00')
But the output should look like:
2008-9-23,3:24:52.0
the format is called "DateAndTime". How can i translate the OctetString output to a "human readable" date/time ?

You can find the format specification here.
A date-time specification.
field octets contents range
----- ------ -------- -----
1 1-2 year* 0..65536
2 3 month 1..12
3 4 day 1..31
4 5 hour 0..23
5 6 minutes 0..59
6 7 seconds 0..60
(use 60 for leap-second)
7 8 deci-seconds 0..9
8 9 direction from UTC '+' / '-'
9 10 hours from UTC* 0..13
10 11 minutes from UTC 0..59
* Notes:
- the value of year is in network-byte order
- daylight saving time in New Zealand is +13 For example,
Tuesday May 26, 1992 at 1:30:15 PM EDT would be displayed as:
1992-5-26,13:30:15.0,-4:0
Note that if only local time is known, then timezone
information (fields 8-10) is not present.
In order to decode your sample data you can use this quick-and-dirty one-liner:
>>> import struct, datetime
>>> s = '\x07\xd8\t\x17\x03\x184\x00'
>>> datetime.datetime(*struct.unpack('>HBBBBBB', s))
datetime.datetime(2008, 9, 23, 3, 24, 52)
The example above is far from perfect, it does not account for size (this object has variable size) and is missing timezone information. Also note that the field 7 is deci-seconds (0..9) while timetuple[6] is microseconds (0 <= x < 1000000); the correct implementations is left as an exercise for the reader.
[update]
8 years later, lets try to fix this answer (am I lazy or what?):
import struct, pytz, datetime
def decode_snmp_date(octetstr: bytes) -> datetime.datetime:
size = len(octetstr)
if size == 8:
(year, month, day, hour, minutes,
seconds, deci_seconds,
) = struct.unpack('>HBBBBBB', octetstr)
return datetime.datetime(
year, month, day, hour, minutes, seconds,
deci_seconds * 100_000, tzinfo=pytz.utc)
elif size == 11:
(year, month, day, hour, minutes,
seconds, deci_seconds, direction,
hours_from_utc, minutes_from_utc,
) = struct.unpack('>HBBBBBBcBB', octetstr)
offset = datetime.timedelta(
hours=hours_from_utc, minutes=minutes_from_utc)
if direction == b'-':
offset = -offset
return datetime.datetime(
year, month, day, hour, minutes, seconds,
deci_seconds * 100_000, tzinfo=pytz.utc) + offset
raise ValueError("The provided OCTETSTR is not a valid SNMP date")
I'm not sure I got the timezone offset right but I don't have sample data to test, feel free to amend the answer or ping me in the comments.

#Paulo Scardine: This was the best answer I found online when working to resolve a very similar problem. It still took me a little while to resolve my issue even with this answer, so I wanted to post a follow up answer that may add more clarity. (specifically the issue with the date having different length options).
The following piece of code connects to a server and grabs the system time and then outputs it as a string to illustrate the method.
import netsnmp
import struct
oid = netsnmp.Varbind('hrSystemDate.0')
resp = netsnmp.snmpget(oid, Version=1, DestHost='<ip>', Community='public')
oct = str(resp[0])
# hrSystemDate can be either 8 or 11 units in length.
oct_len = len(oct)
fmt_mapping = dict({8:'>HBBBBBB', 11:'>HBBBBBBcBB'})
if oct_len == 8 or oct_len == 11:
t = struct.unpack(fmt_mapping[oct_len], oct)
print 'date tuple: %s' % (repr(t))
else:
print 'invalid date format'
I hope this helps other people who are having similar issues trying to work with this type of data.

Shameless plug here: The Pycopia SNMP and SMI modules correctly handle this object, and others as well.
Pycopia is installed from source, and dont forget the mibs file if you try it.

Related

Python convert string to datetime but formatting is not very predictable

I'm extract the execution time of a Linux process using Subprocess and ps. I'd like to put it in a datetime object, to perform datetime arithmetic. However, I'm a little concerned about the output ps returns for the execution time:
1-01:12:23 // 1 day, 1 hour, 12 minutes, 23 seconds
05:39:03 // 5 hours, 39 minutes, 3 seconds
15:06 // 15 minutes, 6 seconds
Notice there is no zero padding before the day. And it doesn't include months/years, whereas technically something could run for that long.
Consequently i'm unsure what format string to convert it to a timedelta because I don't want it to break if a process has ran for months, or another has only ran for hours.
UPDATE
Mozway has given a very smart answer. However, I'm taking a step back and wondering if I can get the execution time another way. I'm currently using ps to get the time, but it means I also have the pid. Is there something else I can do with the pid, to get the execution time in a simpler format?
(Can only use official Python libraries)
UPDATE2
It's actually colons between the hours, mins and seconds.
You should use a timedelta
Here is a suggestion on how to convert from your string:
import datetime
s = '1-01-12-23'
out = datetime.timedelta(**dict(zip(['days', 'hours', 'minutes', 'seconds'],
map(int, s.split('-')))))
Output:
datetime.timedelta(days=1, seconds=4343)
If you can have more or less units, and assuming the smallest units are present you take advantage of the fact that zip stops with the smallest iterable, just reverse the inputs:
s = '12-23'
units = ['days', 'hours', 'minutes', 'seconds']
out = datetime.timedelta(**dict(zip(reversed(units),
map(int, reversed(s.split('-'))))))
Output:
datetime.timedelta(seconds=743)
As a function
Using re.split to handle the 1-01:23:45 format
import re
def to_timedelta(s):
units = ['days', 'hours', 'minutes', 'seconds']
return datetime.timedelta(**dict(zip(reversed(units),
map(int, reversed(re.split('[-:]', s))))))
to_timedelta('1-01:12:23')
# datetime.timedelta(days=1, seconds=4343)
to_timedelta('05:39:03')
# datetime.timedelta(seconds=20343)
to_timedelta('15:06')
# datetime.timedelta(seconds=906)

How could I implement “HH:MM:SS” format

I am asked to do this:
Write a program that adds one second to a clock time, given its hours, minutes and seconds.
Input consists of three natural numbers h, m and s that represent a clock time, that is, such that h<24, m<60 and s<60.
This is the code I came up with:
from easyinput import read
h = read(int)
m = read(int)
s = read(int)
seconds = (s+1)%60
minutes = (m + (s+1)//60)%60
hours = h + (m + (s+1)//60))//60
print(hours, minutes, seconds)
It does its function well, if I have
13 59 59
it returns
14 0 0
I am sure it could be bettered, but that's not the problem right now.
The problem is that I need the format to be like this:
11:33:16
It should be “HH:MM:SS”, and I don't know how to do it.
Anyone could help me?? Thanksss :)))
Use an f-string with format modifiers. 02d says "an int with field width 2 padded with 0."
print(f"{hours:02d}:{minutes:02d}:{seconds:02d}")
>>> hours = 13
>>> minutes = 3
>>> seconds = 5
>>> print(f"{hours:02d}:{minutes:02d}:{seconds:02d}")
13:03:05
>>>
Note that the d in the format specifiers is unnecessary. You could write:
print(f"{hours:02}:{minutes:02}:{seconds:02}")
Documentation on f-strings.
Usually, you don't want to deal with calculating date and time yourself, so a better approach is to use the native library that works with date and time out of the box:
from datetime import datetime, timedelta
from easyinput import read
h, m, s = read(int), read(int), read(int)
time = datetime.now().replace(hour=h, minute=m, second=s)
time += timedelta(seconds=1)
print(time.strftime("%H:%M:%S"))
print(f'{hours:>02}:{minutes:>02}:{seconds:>02}')

Python Datetime: Converting seconds to hours/minutes/seconds - but converting the day option into hours also?

Pretty basic question. I'm using a function that parses seconds from JSON and uses datetime in Python to output to hours/minutes/seconds, like so:
str(datetime.timedelta(seconds=seconds here))
This outputs something like so:
Timestamp: 23:54:02.513000
Timestamp: 1 day, 0:01:07.827000
It works perfectly, but I don't want datetime to print "1 day", I want hours only. So for example the second above should be something like 24:01:07.827000
I tried using my own custom function to convert the seconds, but I feel there must be an easier way.
According to Python Docs, https://docs.python.org/3/library/datetime.html#timedelta-objects
Only days, seconds and microseconds are stored internally.
So you have to compute the hours and minutes yourself using days and seconds. Below code uses f-strings.
import datetime
t = datetime.timedelta(seconds=60*60*24 + 11569) # A random number for testing
print(t) # 1 day, 3:12:49
print(f'{t.days * 24 + t.seconds // 3600:02}:{(t.seconds % 3600) // 60:02}:{t.seconds % 60:02}') # 27:12:49

Keeping a strange time format, and adding values to it in python

So i have been trying to add a time format to my REST calls in python, but there seems to always be some type of issue, first of all here is the time format requirement, and it has to be exact, or it wont work unfortunately.
Use the following ISO-8601 compliant date/time format in request parameters.
yyyy-MM-dd'T'HH:mm:ss.SSSXXX
For example, May 26 2014 at 21:49:46 PM could have a format like one of the following:
l In PDT: 2014-05-26T21:49:46.000-07:00
l In UTC: 2014-05-26T21:49:46.000Z
Code Description
yyyy Four digit year
MM Two-digit month (01=January, etc.)
dd Two-digit day of month (01 through 31)
T Separator for date/time
HH Two digits of hour (00 through 23) (am/pm NOT allowed)
mm Two digits of minute (00 through 59)
ss Two digits of second (00 through 59)
SSS Three digit milliseconds of the second
XXX ISO 8601 time zone (Z or +hh:mm or -hh:mm)
So, what i have tried before is:
def format_time(self, isnow):
currentdt = datetime.datetime.utcnow()
if not isnow:
currentdt += datetime.timedelta(0,3)
(dt, micro) = currentdt.strftime('%Y-%m-%dT%H:%M:%S.%f').split('.')
dt = "%s.%03dZ" % (dt, int(micro) / 1000)
return dt
Now, this might return it in the kinda right format, but there is still the problem with timezones.
The end result i am trying to accomplish, is when i execute this, it finds the current time, (Amsterdam timezone/GMT/UTC+1), and creates it in this format.
And the else statement, to get the same time, but append X seconds.
Would anyone be so kind to help me out here?
Ok, so you got the microseconds formatted as milliseconds, well done there.
Now your challenge is to handle the timezone offset; it can't only be Z.
And to make things more difficult, strftime's %z format gives + (or -) HHMM, instead of HH:MM.
So you'll need to deal with that. Here's one way to do it:
Python 3:
def format_time(self, isnow):
currentdt = datetime.datetime.now(datetime.timezone.utc)
if not isnow:
currentdt += datetime.timedelta(0,3)
(dt, micro) = currentdt.strftime('%Y-%m-%dT%H:%M:%S.%f').split('.')
tz_offset = currentdt.astimezone().strftime('%z')
tz_offset = "Z" if tz_offset == "" else tz_offset[:3] + ":" + tz_offset[3:]
dt = "%s.%03d%s" % (dt, int(micro) / 1000, tz_offset)
return dt
Python 2:
import pytz
from dateutil.tz import *
def format_time(self, isnow):
currentdt = datetime.datetime.now(pytz.utc)
if not isnow:
currentdt += datetime.timedelta(0,3)
(dt, micro) = currentdt.strftime('%Y-%m-%dT%H:%M:%S.%f').split('.')
tz_offset = currentdt.astimezone(tzlocal()).strftime('%z')
tz_offset = "Z" if tz_offset == "" else tz_offset[:3] + ":" + tz_offset[3:]
dt = "%s.%03d%s" % (dt, int(micro) / 1000, tz_offset)
return dt
Response to comment:
I needed to make a few changes. It's remarkably non-trivial to find the current timezone. The easiest way I could find was from https://stackoverflow.com/a/25887393/1404311 and I've integrated those concepts into the code that is now above.
Basically, instead of utcnow(), you should use now(datetime.timezone.utc). The former gives a naive datetime, while the latter gives a datetime set to UTC, but aware that it is. Then use astimezone() to make it aware of your local timezone, then use strftime('%z') to get the time offzone from there. THEN go through the string manipulation.

Convert systemtime to filetime (Python) [duplicate]

Any links for me to convert datetime to filetime using python?
Example: 13 Apr 2011 07:21:01.0874 (UTC) FILETIME=[57D8C920:01CBF9AB]
Got the above from an email header.
My answer in duplicated question got deleted, so I'll post here:
Surfing around i found this link: http://cboard.cprogramming.com/windows-programming/85330-hex-time-filetime.html
After that, everything become simple:
>>> ft = "57D8C920:01CBF9AB"
... # switch parts
... h2, h1 = [int(h, base=16) for h in ft.split(':')]
... # rebuild
... ft_dec = struct.unpack('>Q', struct.pack('>LL', h1, h2))[0]
... ft_dec
... 129471528618740000L
... # use function from iceaway's comment
... print filetime_to_dt(ft_dec)
2011-04-13 07:21:01
Tuning it up is up for you.
Well here is the solution I end up with
parm3=0x57D8C920; parm3=0x01CBF9AB
#Int32x32To64
ft_dec = struct.unpack('>Q', struct.pack('>LL', parm4, parm3))[0]
from datetime import datetime
EPOCH_AS_FILETIME = 116444736000000000; HUNDREDS_OF_NANOSECONDS = 10000000
dt = datetime.fromtimestamp((ft_dec - EPOCH_AS_FILETIME) / HUNDREDS_OF_NANOSECONDS)
print dt
Output will be:
2011-04-13 09:21:01 (GMT +1)
13 Apr 2011 07:21:01.0874 (UTC)
base on David Buxton 'filetimes.py'
^-Note that theres a difference in the hours
Well I changes two things:
fromtimestamp() fits somehow better than *UTC*fromtimestamp() since I'm dealing with file times here.
FAT time resolution is 2 seconds so I don't care about the 100ns rest that might fall apart.
(Well actually since resolution is 2 seconds normally there be no rest when dividing HUNDREDS_OF_NANOSECONDS )
... and beside the order of parameter passing pay attention that struct.pack('>LL' is for unsigned 32bit Int's!
If you've signed int's simply change it to struct.pack('>ll' for signed 32bit Int's!
(or click the struct.pack link above for more info)

Categories