cqlsh: Add local timezone support to cqlsh patch by Stefan Podkowinski; reviewed by Paulo Motta for CASSANDRA-10397
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/128d144c Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/128d144c Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/128d144c Branch: refs/heads/cassandra-3.0 Commit: 128d144c0d22238a9045cc697daf880452be974b Parents: 15b0bd6 Author: Stefan Podkowinski <stefan.podkowin...@1und1.de> Authored: Thu Feb 11 15:00:24 2016 +0100 Committer: Joshua McKenzie <jmcken...@apache.org> Committed: Wed Feb 17 19:22:17 2016 -0500 ---------------------------------------------------------------------- CHANGES.txt | 1 + bin/cqlsh.py | 38 ++++++++++++++++++++++++++- conf/cqlshrc.sample | 3 +++ pylib/cqlshlib/formatting.py | 14 ++++++---- pylib/cqlshlib/test/test_cqlsh_output.py | 18 +++++++++++++ 5 files changed, 68 insertions(+), 6 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/128d144c/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 968c8b1..904a913 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -19,6 +19,7 @@ Merged from 2.1: * Avoid major compaction mixing repaired and unrepaired sstables in DTCS (CASSANDRA-11113) * Make it clear what DTCS timestamp_resolution is used for (CASSANDRA-11041) * test_bulk_round_trip_blogposts is failing occasionally (CASSANDRA-10938) + * (cqlsh) Support timezone conversion using pytz (CASSANDRA-10397) 2.2.5 http://git-wip-us.apache.org/repos/asf/cassandra/blob/128d144c/bin/cqlsh.py ---------------------------------------------------------------------- diff --git a/bin/cqlsh.py b/bin/cqlsh.py index 08cc6f4..6a464b0 100644 --- a/bin/cqlsh.py +++ b/bin/cqlsh.py @@ -666,6 +666,7 @@ class Shell(cmd.Cmd): display_timestamp_format=DEFAULT_TIMESTAMP_FORMAT, display_date_format=DEFAULT_DATE_FORMAT, display_float_precision=DEFAULT_FLOAT_PRECISION, + display_timezone=None, max_trace_wait=DEFAULT_MAX_TRACE_WAIT, ssl=False, single_statement=None, @@ -715,6 +716,8 @@ class Shell(cmd.Cmd): self.display_float_precision = display_float_precision + self.display_timezone = display_timezone + # If there is no schema metadata present (due to a schema mismatch), force schema refresh if not self.conn.metadata.keyspaces: self.refresh_schema_metadata_best_effort() @@ -801,7 +804,8 @@ class Shell(cmd.Cmd): self.decoding_errors.append(val) try: dtformats = DateTimeFormat(timestamp_format=self.display_timestamp_format, - date_format=self.display_date_format, nanotime_format=self.display_nanotime_format) + date_format=self.display_date_format, nanotime_format=self.display_nanotime_format, + timezone=self.display_timezone) return format_value(val, self.output_codec.name, addcolor=self.color, date_time_format=dtformats, float_precision=self.display_float_precision, **kwargs) @@ -2349,6 +2353,7 @@ def read_options(cmdlineargs, environment): optvalues.field_size_limit = option_with_default(configs.getint, 'csv', 'field_size_limit', csv.field_size_limit()) optvalues.max_trace_wait = option_with_default(configs.getfloat, 'tracing', 'max_trace_wait', DEFAULT_MAX_TRACE_WAIT) + optvalues.timezone = option_with_default(configs.get, 'ui', 'timezone', None) optvalues.debug = False optvalues.file = None @@ -2467,6 +2472,36 @@ def main(options, hostname, port): sys.stderr.write("Using CQL driver: %s\n" % (cassandra,)) sys.stderr.write("Using connect timeout: %s seconds\n" % (options.connect_timeout,)) + # create timezone based on settings, environment or auto-detection + timezone = None + if options.timezone or 'TZ' in os.environ: + try: + import pytz + if options.timezone: + try: + timezone = pytz.timezone(options.timezone) + except: + sys.stderr.write("Warning: could not recognize timezone '%s' specified in cqlshrc\n\n" % (options.timezone)) + if 'TZ' in os.environ: + try: + timezone = pytz.timezone(os.environ['TZ']) + except: + sys.stderr.write("Warning: could not recognize timezone '%s' from environment value TZ\n\n" % (os.environ['TZ'])) + except ImportError: + sys.stderr.write("Warning: Timezone defined and 'pytz' module for timezone conversion not installed. Timestamps will be displayed in UTC timezone.\n\n") + + # try auto-detect timezone if tzlocal is installed + if not timezone: + try: + from tzlocal import get_localzone + timezone = get_localzone() + except ImportError: + # we silently ignore and fallback to UTC unless a custom timestamp format (which likely + # does contain a TZ part) was specified + if options.time_format != DEFAULT_TIMESTAMP_FORMAT: + sys.stderr.write("Warning: custom timestamp format specified in cqlshrc, but local timezone could not be detected.\n" + + "Either install Python 'tzlocal' module for auto-detection or specify client timezone in your cqlshrc.\n\n") + try: shell = Shell(hostname, port, @@ -2483,6 +2518,7 @@ def main(options, hostname, port): display_nanotime_format=options.nanotime_format, display_date_format=options.date_format, display_float_precision=options.float_precision, + display_timezone=timezone, max_trace_wait=options.max_trace_wait, ssl=options.ssl, single_statement=options.execute, http://git-wip-us.apache.org/repos/asf/cassandra/blob/128d144c/conf/cqlshrc.sample ---------------------------------------------------------------------- diff --git a/conf/cqlshrc.sample b/conf/cqlshrc.sample index 0bcce6a..a0012a3 100644 --- a/conf/cqlshrc.sample +++ b/conf/cqlshrc.sample @@ -32,6 +32,9 @@ ;; Used for displaying timestamps (and reading them with COPY) ; datetimeformat = %Y-%m-%d %H:%M:%S%z +;; Display timezone +;timezone = Etc/UTC + ;; The number of digits displayed after the decimal point ;; (note that increasing this to large numbers can result in unusual values) ; float_precision = 5 http://git-wip-us.apache.org/repos/asf/cassandra/blob/128d144c/pylib/cqlshlib/formatting.py ---------------------------------------------------------------------- diff --git a/pylib/cqlshlib/formatting.py b/pylib/cqlshlib/formatting.py index 2e219c8..dcd08da 100644 --- a/pylib/cqlshlib/formatting.py +++ b/pylib/cqlshlib/formatting.py @@ -107,10 +107,12 @@ if platform.system() == 'Windows': class DateTimeFormat(): - def __init__(self, timestamp_format=DEFAULT_TIMESTAMP_FORMAT, date_format=DEFAULT_DATE_FORMAT, nanotime_format=DEFAULT_NANOTIME_FORMAT): + def __init__(self, timestamp_format=DEFAULT_TIMESTAMP_FORMAT, date_format=DEFAULT_DATE_FORMAT, + nanotime_format=DEFAULT_NANOTIME_FORMAT, timezone=None): self.timestamp_format = timestamp_format self.date_format = date_format self.nanotime_format = nanotime_format + self.timezone = timezone def format_value_default(val, colormap, **_): @@ -234,15 +236,17 @@ formatter_for('int')(format_integer_type) @formatter_for('datetime') def format_value_timestamp(val, colormap, date_time_format, quote=False, **_): - bval = strftime(date_time_format.timestamp_format, calendar.timegm(val.utctimetuple())) + bval = strftime(date_time_format.timestamp_format, calendar.timegm(val.utctimetuple()), timezone=date_time_format.timezone) if quote: bval = "'%s'" % bval return colorme(bval, colormap, 'timestamp') -def strftime(time_format, seconds): - tzless_dt = datetime_from_timestamp(seconds) - return tzless_dt.replace(tzinfo=UTC()).strftime(time_format) +def strftime(time_format, seconds, timezone=None): + ret_dt = datetime_from_timestamp(seconds).replace(tzinfo=UTC()) + if timezone: + ret_dt = ret_dt.astimezone(timezone) + return ret_dt.strftime(time_format) @formatter_for('Date') http://git-wip-us.apache.org/repos/asf/cassandra/blob/128d144c/pylib/cqlshlib/test/test_cqlsh_output.py ---------------------------------------------------------------------- diff --git a/pylib/cqlshlib/test/test_cqlsh_output.py b/pylib/cqlshlib/test/test_cqlsh_output.py index c62ed69..fdc562f 100644 --- a/pylib/cqlshlib/test/test_cqlsh_output.py +++ b/pylib/cqlshlib/test/test_cqlsh_output.py @@ -370,6 +370,24 @@ class TestCqlshOutput(BaseTestCase): nnnnnnnn """), ), env={'TZ': 'Etc/UTC'}) + try: + import pytz # test only if pytz is available on PYTHONPATH + self.assertQueriesGiveColoredOutput(( + ('''select timestampcol from has_all_types where num = 0;''', """ + timestampcol + MMMMMMMMMMMM + -------------------------- + + 2012-05-14 09:53:20-0300 + GGGGGGGGGGGGGGGGGGGGGGGG + + + (1 rows) + nnnnnnnn + """), + ), env={'TZ': 'America/Sao_Paulo'}) + except ImportError: + pass def test_boolean_output(self): self.assertCqlverQueriesGiveColoredOutput((