Modify interface from utc boolean to utc_offset

The system set/get date/time functions now have
a utc_offset instead of a utc boolean so the time
can be set based off of any timezone.
This commit is contained in:
Collin Richards 2016-06-21 14:24:49 -05:00
parent 7b7e7d7ab4
commit 72d47a67c0
2 changed files with 212 additions and 120 deletions

View File

@ -6,7 +6,7 @@ from __future__ import absolute_import
# Import python libs
from datetime import datetime, timedelta, tzinfo
import os
import re
# Import salt libs
import salt.utils
@ -118,15 +118,17 @@ def _date_bin_set_datetime(new_date):
'''
set the system date/time using the date command
Note using a posix date binary we can only set the date up to the minute
Note using a strictly posix-compliant date binary we can only set the date
up to the minute.
'''
cmd = 'date'
cmd = ['date']
# if there is a timezone in the datetime object use that offset
if new_date.tzname() is not None:
new_date += new_date.utcoffset()
cmd += ' -u'
# This will modify the new_date to be the equivalent time in UTC
if new_date.utcoffset() is not None:
new_date = new_date - new_date.utcoffset()
new_date = new_date.replace(tzinfo=FixedOffset(0))
cmd.append('-u')
# the date can be set in the following format:
# Note that setting the time with a resolution of seconds
@ -134,24 +136,23 @@ def _date_bin_set_datetime(new_date):
# fails we will try again only using posix features
# date MMDDhhmm[[CC]YY[.ss]]
non_posix = " {1:02}{2:02}{3:02}{4:02}{0:04}.{5:02}".format(*new_date.timetuple())
non_posix_cmd = cmd + non_posix
non_posix = ("{1:02}{2:02}{3:02}{4:02}{0:04}.{5:02}"
.format(*new_date.timetuple()))
non_posix_cmd = cmd + [non_posix]
ret_non_posix = __salt__['cmd.run_all'](non_posix_cmd, python_shell=False)
if ret_non_posix['retcode'] != 0:
# We will now try the command again following posix
# date MMDDhhmm[[CC]YY]
posix = " {1:02}{2:02}{3:02}{4:02}{0:04}".format(*new_date.timetuple())
posix_cmd = cmd + posix
posix_cmd = cmd + [posix]
ret_posix = __salt__['cmd.run_all'](posix_cmd, python_shell=False)
if ret_posix['retcode'] != 0:
# if both fail it's likely an invalid date string
# so we will give back the error from the first attemp
# so we will give back the error from the first attempt
msg = 'date failed: {0}'.format(ret_non_posix['stderr'])
raise CommandExecutionError(msg)
else:
return True
return True
@ -175,11 +176,48 @@ def _try_parse_datetime(time_str, fmts):
return result
def get_system_time(utc=None):
def _offset_to_min(utc_offset):
'''
Helper function that converts the utc offset string into number of minutes
offset. Input is in form "[+-]?HHMM". Example valid inputs are "+0500"
"-0300" and "0800". These would return -300, 180, 480 respectively.
'''
match = re.match(r"^([+-])?(\d\d)(\d\d)$", utc_offset)
if not match:
raise SaltInvocationError("Invalid UTC offset")
sign = -1 if match.group(1) == '-' else 1
hours_offset = int(match.group(2))
minutes_offset = int(match.group(3))
total_offset = sign * (hours_offset * 60 + minutes_offset)
return total_offset
def _get_offset_time(utc_offset):
'''
Will return the current time adjusted using the input timezone offset.
:rtype datetime:
'''
if utc_offset is not None:
minutes = _offset_to_min(utc_offset)
offset = timedelta(minutes=minutes)
offset_time = datetime.utcnow() + offset
offset_time = offset_time.replace(tzinfo=FixedOffset(minutes))
else:
offset_time = datetime.now()
return offset_time
def get_system_time(utc_offset=None):
'''
Get the system time.
:param bool utc: A Boolean that indicates if the output timezone is UTC.
:param str utc_offset: The utc offset in 4 digit (+0600) format with an
optional sign (+/-). Will default to None which will use the local
timezone. To set the time based off of UTC use "'+0000'". Note: if being
passed through the command line will need to be quoted twice to allow
negative offsets.
:return: Returns the system time in HH:MM AM/PM format.
:rtype: str
@ -189,14 +227,11 @@ def get_system_time(utc=None):
salt '*' system.get_system_time
'''
if utc is True:
t = datetime.utcnow()
else:
t = datetime.now()
return datetime.strftime(t, "%I:%M %p")
offset_time = _get_offset_time(utc_offset)
return datetime.strftime(offset_time, "%I:%M %p")
def set_system_time(newtime, utc=None):
def set_system_time(newtime, utc_offset=None):
'''
Set the system time.
@ -212,6 +247,11 @@ def set_system_time(newtime, utc=None):
Therefore the argument must be passed in as a string.
Meaning you may have to quote the text twice from the command line.
:param str utc_offset: The utc offset in 4 digit (+0600) format with an
optional sign (+/-). Will default to None which will use the local
timezone. To set the time based off of UTC use "'+0000'". Note: if being
passed through the command line will need to be quoted twice to allow
negative offsets.
:return: Returns True if successful. Otherwise False.
:rtype: bool
@ -221,21 +261,24 @@ def set_system_time(newtime, utc=None):
salt '*' system.set_system_time "'11:20'"
'''
fmts = ['%I:%M:%S %p', '%I:%M %p', '%H:%M:%S', '%H:%M']
dt_obj = _try_parse_datetime(newtime, fmts)
if dt_obj is None:
return False
return set_system_date_time(hours=dt_obj.hour, minutes=dt_obj.minute,
seconds=dt_obj.second, utc=utc)
seconds=dt_obj.second, utc_offset=utc_offset)
def get_system_date_time(utc=None):
def get_system_date_time(utc_offset=None):
'''
Get the system date/time.
:param bool utc: A Boolean that indicates if the output timezone is UTC.
:param str utc_offset: The utc offset in 4 digit (+0600) format with an
optional sign (+/-). Will default to None which will use the local
timezone. To set the time based off of UTC use "'+0000'". Note: if being
passed through the command line will need to be quoted twice to allow
negative offsets.
:return: Returns the system time in YYYY-MM-DD hh:mm:ss format.
:rtype: str
@ -243,14 +286,10 @@ def get_system_date_time(utc=None):
.. code-block:: bash
salt '*' system.get_system_date_time utc=True
salt '*' system.get_system_date_time "'-0500'"
'''
if utc is True:
t = datetime.utcnow()
else:
t = datetime.now()
return datetime.strftime(t, "%Y-%m-%d %H:%M:%S")
offset_time = _get_offset_time(utc_offset)
return datetime.strftime(offset_time, "%Y-%m-%d %H:%M:%S")
def set_system_date_time(years=None,
@ -259,7 +298,7 @@ def set_system_date_time(years=None,
hours=None,
minutes=None,
seconds=None,
utc=None):
utc_offset=None):
'''
Set the system date and time. Each argument is an element of the date, but
not required. If an element is not passed, the current system value for
@ -273,8 +312,11 @@ def set_system_date_time(years=None,
:param int hours: Hours digit: 0 - 23
:param int minutes: Minutes digit: 0 - 59
:param int seconds: Seconds digit: 0 - 59
:param bool utc: A Boolean to specify input time is UTC.
:param str utc_offset: The utc offset in 4 digit (+0600) format with an
optional sign (+/-). Will default to None which will use the local
timezone. To set the time based off of UTC use "'+0000'". Note: if being
passed through the command line will need to be quoted twice to allow
negative offsets.
:return: True if successful. Otherwise False.
:rtype: bool
@ -282,15 +324,10 @@ def set_system_date_time(years=None,
.. code-block:: bash
salt '*' system.set_system_date_time 2015 5 12 11 37 53 True
salt '*' system.set_system_date_time 2015 5 12 11 37 53 "'-0500'"
'''
# Get the current date/time
if utc is True:
date_time = datetime.utcnow()
timezone = _UTC()
else:
date_time = datetime.now()
timezone = None
date_time = _get_offset_time(utc_offset)
# Check for passed values. If not passed, use current values
if years is None:
@ -307,18 +344,23 @@ def set_system_date_time(years=None,
seconds = date_time.second
try:
dt = datetime(years, months, days, hours, minutes, seconds, 0, timezone)
except ValueError, e:
raise SaltInvocationError(e.message)
new_datetime = datetime(years, months, days, hours, minutes, seconds, 0,
date_time.tzinfo)
except ValueError, err:
raise SaltInvocationError(err.message)
return _date_bin_set_datetime(dt)
return _date_bin_set_datetime(new_datetime)
def get_system_date(utc=None):
def get_system_date(utc_offset=None):
'''
Get the system date
:param bool utc: A Boolean that indicates if the output timezone is UTC.
:param str utc_offset: The utc offset in 4 digit (+0600) format with an
optional sign (+/-). Will default to None which will use the local
timezone. To set the time based off of UTC use "'+0000'". Note: if being
passed through the command line will need to be quoted twice to allow
negative offsets.
:return: Returns the system date.
:rtype: str
@ -328,15 +370,11 @@ def get_system_date(utc=None):
salt '*' system.get_system_date
'''
if utc is True:
t = datetime.utcnow()
else:
t = datetime.now()
return datetime.strftime(t, "%a %m/%d/%Y")
offset_time = _get_offset_time(utc_offset)
return datetime.strftime(offset_time, "%a %m/%d/%Y")
def set_system_date(newdate, utc=None):
def set_system_date(newdate, utc_offset=None):
'''
Set the Windows system date. Use <mm-dd-yy> format for the date.
@ -355,29 +393,38 @@ def set_system_date(newdate, utc=None):
salt '*' system.set_system_date '03-28-13'
'''
fmts = ['%Y-%m-%d', '%m-%d-%Y', '%m-%d-%y',
'%m/%d/%Y', '%m/%d/%y', '%Y/%m/%d']
# Get date/time object from newdate
# dt_obj = salt.utils.date_cast(newdate)
dt_obj = _try_parse_datetime(newdate, fmts)
if dt_obj is None:
return False
raise SaltInvocationError("Invalid date format")
# Set time using set_system_date_time()
return set_system_date_time(years=dt_obj.year, months=dt_obj.month,
days=dt_obj.day, utc=utc)
days=dt_obj.day, utc_offset=utc_offset)
class _UTC(tzinfo):
"""UTC"""
# Class from: <https://docs.python.org/2.7/library/datetime.html>
def utcoffset(self, dt):
return timedelta(0)
def tzname(self, dt):
return "UTC"
def dst(self, dt):
# A class building tzinfo objects for fixed-offset time zones.
# Note that FixedOffset(0) is a way to build a UTC tzinfo object.
class FixedOffset(tzinfo):
"""
Fixed offset in minutes east from UTC.
"""
def __init__(self, offset):
super(self.__class__, self).__init__()
self.__offset = timedelta(minutes=offset)
def utcoffset(self, dt): # pylint: disable=W0613
return self.__offset
def tzname(self, dt): # pylint: disable=W0613
return None
def dst(self, dt): # pylint: disable=W0613
return timedelta(0)

View File

@ -16,31 +16,51 @@ ensure_in_syspath('../../')
# Import salt libs
import integration
import salt.utils
@skipIf(not salt.utils.is_linux(), 'These tests can only be run on linux')
class SystemModuleTest(integration.ModuleCase):
'''
Validate the date/time functions in the system module
'''
fmt_str = "%Y-%m-%d %H:%M:%S"
def __init__(self, arg):
super(self.__class__, self).__init__(arg)
self._orig_time = None
def setUp(self):
super(SystemModuleTest, self).setUp()
os_grain = self.run_function('grains.item', ['kernel'])
if os_grain['kernel'] not in ('Linux'):
self.skipTest(
'Test not applicable to \'{kernel}\' kernel'.format(
**os_grain
)
)
def tearDown(self):
if self._orig_time is not None:
self._restore_time()
self._orig_time = None
def _save_time(self):
self._orig_time = datetime.datetime.now()
self._orig_time = datetime.datetime.utcnow()
def _set_time(self, new_time, utc=None):
def _set_time(self, new_time, offset=None):
t = new_time.timetuple()[:6]
return self.run_function('system.set_system_date_time', t, utc=utc)
t += (offset,)
return self.run_function('system.set_system_date_time', t)
def _restore_time(self, utc=None):
if utc is True:
t = datetime.datetime.utcnow()
else:
t = datetime.datetime.now()
test_timediff = t - self._fake_time
now = test_timediff + self._orig_time
self._set_time(now)
def _restore_time(self):
result = self._set_time(self._orig_time, "+0000")
self.assertTrue(result, msg="Unable to restore time properly")
def _same_times(self, t1, t2, seconds_diff=1):
def _same_times(self, t1, t2, seconds_diff=2):
'''
Helper function to check if two datetime objects
are close enough to the same time.
'''
return abs(t1 - t2) < datetime.timedelta(seconds=seconds_diff)
def test_get_system_date_time(self):
@ -59,7 +79,8 @@ class SystemModuleTest(integration.ModuleCase):
Test we are able to get the correct time with utc
'''
t1 = datetime.datetime.utcnow()
res = self.run_function('system.get_system_date_time', utc=True)
res = self.run_function('system.get_system_date_time',
utc_offset="+0000")
t2 = datetime.datetime.strptime(res, self.fmt_str)
msg = ("Difference in times is too large. Now: {0} Fake: {1}"
.format(t1, t2))
@ -72,18 +93,17 @@ class SystemModuleTest(integration.ModuleCase):
Test changing the system clock. We are only able to set it up to a
resolution of a second so this test may appear to run in negative time.
'''
self._fake_time = datetime.datetime.strptime("1981-02-03 04:05:06",
self.fmt_str)
cmp_time = datetime.datetime.strptime("1981-02-03 04:05:06",
self.fmt_str)
self._save_time()
self._set_time(self._fake_time)
result = self._set_time(cmp_time)
time_now = datetime.datetime.now()
msg = ("Difference in times is too large. Now: {0} Fake: {1}"
.format(time_now, self._fake_time))
self.assertTrue(self._same_times(time_now, self._fake_time), msg=msg)
self._restore_time()
.format(time_now, cmp_time))
self.assertTrue(result and self._same_times(time_now, cmp_time),
msg=msg)
@destructiveTest
@skipIf(os.geteuid() != 0, 'you must be root to run this test')
@ -92,19 +112,61 @@ class SystemModuleTest(integration.ModuleCase):
Test changing the system clock. We are only able to set it up to a
resolution of a second so this test may appear to run in negative time.
'''
self._fake_time = datetime.datetime.strptime("1981-02-03 04:05:06",
self.fmt_str)
cmp_time = datetime.datetime.strptime("1981-02-03 04:05:06", self.fmt_str)
self._save_time()
result = self._set_time(self._fake_time, utc=True)
result = self._set_time(cmp_time, offset="+0000")
time_now = datetime.datetime.utcnow()
msg = ("Difference in times is too large. Now: {0} Fake: {1}"
.format(time_now, self._fake_time))
self.assertTrue(result and self._same_times(time_now, self._fake_time),
msg=msg)
.format(time_now, cmp_time))
self.assertTrue(result)
self.assertTrue(self._same_times(time_now, cmp_time), msg=msg)
self._restore_time(utc=True)
@destructiveTest
@skipIf(os.geteuid() != 0, 'you must be root to run this test')
def test_set_system_date_time_utcoffset_east(self):
'''
Test changing the system clock. We are only able to set it up to a
resolution of a second so this test may appear to run in negative time.
'''
cmp_time = datetime.datetime.strptime("1981-02-03 11:05:06",
self.fmt_str)
offset_str = "-0700"
time_to_set = datetime.datetime.strptime("1981-02-03 04:05:06",
self.fmt_str)
self._save_time()
result = self._set_time(time_to_set, offset=offset_str)
time_now = datetime.datetime.utcnow()
msg = ("Difference in times is too large. Now: {0} Fake: {1}"
.format(time_now, cmp_time))
self.assertTrue(result)
self.assertTrue(self._same_times(time_now, cmp_time), msg=msg)
@destructiveTest
@skipIf(os.geteuid() != 0, 'you must be root to run this test')
def test_set_system_date_time_utcoffset_west(self):
'''
Test changing the system clock. We are only able to set it up to a
resolution of a second so this test may appear to run in negative time.
'''
cmp_time = datetime.datetime.strptime("1981-02-03 02:05:06",
self.fmt_str)
offset_str = "+0200"
time_to_set = datetime.datetime.strptime("1981-02-03 04:05:06",
self.fmt_str)
self._save_time()
result = self._set_time(time_to_set, offset=offset_str)
time_now = datetime.datetime.utcnow()
msg = ("Difference in times is too large. Now: {0} Fake: {1}"
.format(time_now, cmp_time))
self.assertTrue(result)
self.assertTrue(self._same_times(time_now, cmp_time), msg=msg)
@destructiveTest
@skipIf(os.geteuid() != 0, 'you must be root to run this test')
@ -112,23 +174,17 @@ class SystemModuleTest(integration.ModuleCase):
'''
Test setting the system time without adjusting the date.
'''
self._fake_time = datetime.datetime.combine(datetime.date.today(),
datetime.time(4, 5, 0))
cmp_time = datetime.datetime.now().replace(hour=10, minute=5, second=0)
self._save_time()
result = self.run_function('system.set_system_time', ["04:05:00"])
result = self.run_function('system.set_system_time', ["10:05:00"])
time_now = datetime.datetime.now()
msg = ("Difference in times is too large. Now: {0} Fake: {1}"
.format(time_now, self._fake_time))
.format(time_now, cmp_time))
self.assertTrue(result)
self.assertTrue(time_now.hour == 4 and
time_now.minute == 5 and
(time_now.second < 10), msg=msg)
self._restore_time()
self.assertTrue(self._same_times(time_now, cmp_time), msg=msg)
@destructiveTest
@skipIf(os.geteuid() != 0, 'you must be root to run this test')
@ -136,28 +192,17 @@ class SystemModuleTest(integration.ModuleCase):
'''
Test setting the system date without adjusting the time.
'''
self._fake_time = datetime.datetime.combine(
datetime.datetime(2000, 12, 25),
datetime.datetime.now().time()
)
cmp_time = datetime.datetime.now().replace(year=2000, month=12, day=25)
self._save_time()
result = self.run_function('system.set_system_date', ["2000-12-25"])
time_now = datetime.datetime.now()
msg = ("Difference in times is too large. Now: {0} Fake: {1}"
.format(time_now, self._fake_time))
.format(time_now, cmp_time))
self.assertTrue(result)
self.assertTrue(time_now.year == 2000 and
time_now.day == 25 and
time_now.month == 12 and
time_now.hour == self._orig_time.hour and
time_now.minute == self._orig_time.minute,
msg=msg)
self._restore_time()
self.assertTrue(self._same_times(time_now, cmp_time), msg=msg)
if __name__ == '__main__':
from integration import run_tests