Skip to content

stop using date(1) to parse date/time stamps. #4333

@igalic

Description

@igalic

Bug report

cloud-init uses GNU date(1) for parsing timestamps.
GNU date(1) is not available on all platforms cloud-init runs on.

Steps to reproduce the problem

run unit tests on FreeBSD and see:

                                                                                                                                       
self = <test_dump.TestParseTimestamp object at 0x14f2505885b0>                                                                         
                                                                                                                                       
    @skipIf(not which("date"), "'date' command not available.")                                                                        
    @pytest.mark.allow_subp_for("date")                                                                                                
    def test_parse_unexpected_timestamp_format_with_date_command(self):                                                                
        """Dump sends unexpected timestamp formats to date for processing."""                                                          
        new_fmt = "%H:%M %m/%d %Y"                                                                                                     
        new_stamp = "17:15 08/08"                                                                                                      
        # convert stamp ourselves by adding the missing year value                                                                     
        year = datetime.now().year                                                                                                     
        dt = datetime.strptime(new_stamp + " " + str(year), new_fmt)                                                                   
     
>       assert float(dt.strftime("%s.%f")) == parse_timestamp(new_stamp)

dt         = datetime.datetime(2023, 8, 8, 17, 15)
new_fmt    = '%H:%M %m/%d %Y'
new_stamp  = '17:15 08/08'
self       = <test_dump.TestParseTimestamp object at 0x14f2505885b0>
year       = 2023

tests/unittests/analyze/test_dump.py:56: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
cloudinit/analyze/dump.py:48: in parse_timestamp
    timestamp = parse_timestamp_from_date(timestampstr)
        months     = ['Jan',
 'Feb',
 'Mar',
 'Apr',
 'May',
 'Jun',
 'Jul',
 'Aug',
 'Sep',
 'Oct',
 'Nov',
 'Dec']
        timestampstr = '17:15 08/08'
cloudinit/analyze/dump.py:54: in parse_timestamp_from_date
    out, _ = subp.subp(["date", "+%s.%3N", "-d", timestampstr])
        timestampstr = '17:15 08/08'
<string>:3: in subp
    ???
        args       = (['date', '+%s.%3N', '-d', '17:15 08/08'],)
        kwargs     = {}
/usr/local/lib/python3.9/unittest/mock.py:1092: in __call__
    return self._mock_call(*args, **kwargs)
        args       = (['date', '+%s.%3N', '-d', '17:15 08/08'],)
        kwargs     = {}
        self       = <MagicMock name='subp' spec='function' id='23031021882336'>
/usr/local/lib/python3.9/unittest/mock.py:1096: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
        args       = (['date', '+%s.%3N', '-d', '17:15 08/08'],)
        kwargs     = {}
        self       = <MagicMock name='subp' spec='function' id='23031021882336'>
/usr/local/lib/python3.9/unittest/mock.py:1157: in _execute_mock_call
    result = effect(*args, **kwargs)
        args       = (['date', '+%s.%3N', '-d', '17:15 08/08'],)
        effect     = <function disable_subp_usage.<locals>.side_effect at 0x14f255a52e50>
        kwargs     = {}
        self       = <MagicMock name='subp' spec='function' id='23031021882336'>
conftest.py:168: in side_effect                                                                                              [998/1841]
    return real_subp(args, *other_args, **kwargs)
        allow_subp_for = ('date',)
        args       = ['date', '+%s.%3N', '-d', '17:15 08/08']
        cmd        = 'date'
        kwargs     = {}
        other_args = ()
        real_subp  = <function subp at 0x14f24df1c160>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

args = ['date', '+%s.%3N', '-d', '17:15 08/08'], data = None, rcs = [0], env = None, capture = True, combine_capture = False
shell = False, logstring = False, decode = 'replace', target = None, update_env = None, status_cb = None, cwd = None

        if rc not in rcs:                                                                                                    [812/1841]
            if status_cb:
                status_cb("ERROR: End run command: exit({code})\n".format(code=rc))
>           raise ProcessExecutionError(
                stdout=out, stderr=err, exit_code=rc, cmd=args
            )
E           cloudinit.subp.ProcessExecutionError: Unexpected error while running command.
E           Command: ['date', '+%s.%3N', '-d', '17:15 08/08']
E           Exit code: 1
E           Reason: -
E           Stdout: 
E           Stderr: date: illegal time format
E                   usage: date [-jnRu] [-I[date|hours|minutes|seconds]] [-f input_fmt]
E                               [ -z output_zone ] [-r filename|seconds] [-v[+|-]val[y|m|w|d|H|M|S]]
E                               [[[[[[cc]yy]mm]dd]HH]MM[.SS] | new_date] [+output_fmt]

args       = ['date', '+%s.%3N', '-d', '17:15 08/08']
bytes_args = [b'date', b'+%s.%3N', b'-d', b'17:15 08/08']
capture    = True
combine_capture = False
cwd        = None
data       = None
decode     = 'replace'
devnull_fp = <_io.TextIOWrapper name='/dev/null' mode='r' encoding='UTF-8'>
env        = None
err        = ('date: illegal time format\n'
 'usage: date [-jnRu] [-I[date|hours|minutes|seconds]] [-f input_fmt]\n'
 '            [ -z output_zone ] [-r filename|seconds] '
 '[-v[+|-]val[y|m|w|d|H|M|S]]\n'
 '            [[[[[[cc]yy]mm]dd]HH]MM[.SS] | new_date] [+output_fmt]\n')
ldecode    = <function subp.<locals>.ldecode at 0x14f255a571f0>
logstring  = False
out        = ''
rc         = 1
rcs        = [0]
shell      = False
sp         = <Popen: returncode: 1 args: [b'date', b'+%s.%3N', b'-d', b'17:15 08/08']>
status_cb  = None
stderr     = -1
stdin      = <_io.TextIOWrapper name='/dev/null' mode='r' encoding='UTF-8'>
stdout     = -1
target     = None
update_env = None

cloudinit/subp.py:335: ProcessExecutionError
-------------------------------------------------------- Captured stdout call ---------------------------------------------------------
2023-08-10 20:45:34,288 - subp.py[DEBUG]: Running command ['date', '+%s.%3N', '-d', '17:15 08/08'] with allowed return codes [0] (shell
=False, capture=True)
---------------------------------------------------------- Captured log call ----------------------------------------------------------
2023-08-10 20:45:34 DEBUG     cloudinit.subp:subp.py:245 Running command ['date', '+%s.%3N', '-d', '17:15 08/08'] with allowed return c
odes [0] (shell=False, capture=True)

Environment details

  • Cloud-init version: tip of main
  • Operating System Distribution: FreeBSD 14.0-CURRENt

how to fix it

use python for parsing of dates.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working correctly

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions