{
    "file_diffs": [
        {
            "old_file_content": "import contextlib\nimport csv\nimport locale\nimport pickle\nimport re\nimport subprocess\nimport sys\nimport warnings\n\nfrom ast import literal_eval\nfrom collections import OrderedDict, Counter, defaultdict\nfrom functools import lru_cache\nfrom importlib import import_module\nfrom itertools import chain, repeat\nfrom math import isnan\n\nfrom os import path, remove\nfrom tempfile import NamedTemporaryFile\nfrom urllib.parse import urlparse, urlsplit, urlunsplit, unquote as urlunquote\nfrom urllib.request import urlopen, Request\nfrom fnmatch import fnmatch\nfrom glob import glob\n\nimport numpy as np\nfrom chardet.universaldetector import UniversalDetector\n\nimport xlrd\nimport xlsxwriter\n\nfrom Orange.data import (\n    _io, is_discrete_values, MISSING_VALUES, Table, Domain, Variable,\n    DiscreteVariable, StringVariable, ContinuousVariable, TimeVariable,\n)\nfrom Orange.util import Registry, flatten, namegen\n\n\n# Support values longer than 128K (i.e. text contents features)\ncsv.field_size_limit(100*1024*1024)\n\n__all__ = [\"Flags\", \"FileFormat\"]\n\n\n_IDENTITY = lambda i: i\n\n\nclass Compression:\n    \"\"\"Supported compression extensions\"\"\"\n    GZIP = '.gz'\n    BZIP2 = '.bz2'\n    XZ = '.xz'\n    all = (GZIP, BZIP2, XZ)\n\n\ndef open_compressed(filename, *args, _open=open, **kwargs):\n    \"\"\"Return seamlessly decompressed open file handle for `filename`\"\"\"\n    if isinstance(filename, str):\n        if filename.endswith(Compression.GZIP):\n            from gzip import open as _open\n        elif filename.endswith(Compression.BZIP2):\n            from bz2 import open as _open\n        elif filename.endswith(Compression.XZ):\n            from lzma import open as _open\n        return _open(filename, *args, **kwargs)\n    # Else already a file, just pass it through\n    return filename\n\n\ndef detect_encoding(filename):\n    \"\"\"\n    Detect encoding of `filename`, which can be a ``str`` filename, a\n    ``file``-like object, or ``bytes``.\n    \"\"\"\n    # Try with Unix file utility first because it's faster (~10ms vs 100ms)\n    if isinstance(filename, str) and not filename.endswith(Compression.all):\n        try:\n            with subprocess.Popen(('file', '--brief', '--mime-encoding', filename),\n                                  stdout=subprocess.PIPE) as process:\n                process.wait()\n                if process.returncode == 0:\n                    encoding = process.stdout.read().strip()\n                    # file only supports these encodings; for others it says\n                    # unknown-8bit or binary. So we give chardet a chance to do\n                    # better\n                    if encoding in (b'utf-8', b'us-ascii', b'iso-8859-1',\n                                    b'utf-7', b'utf-16le', b'utf-16be', b'ebcdic'):\n                        return encoding.decode('us-ascii')\n        except OSError:\n            pass  # windoze\n\n    # file not available or unable to guess the encoding, have chardet do it\n    detector = UniversalDetector()\n    # We examine only first N 4kB blocks of file because chardet is really slow\n    MAX_BYTES = 4*1024*12\n\n    def _from_file(f):\n        detector.feed(f.read(MAX_BYTES))\n        detector.close()\n        return (detector.result.get('encoding')\n                if detector.result.get('confidence', 0) >= .85 else\n                'utf-8')\n\n    if isinstance(filename, str):\n        with open_compressed(filename, 'rb') as f:\n            return _from_file(f)\n    elif isinstance(filename, bytes):\n        detector.feed(filename[:MAX_BYTES])\n        detector.close()\n        return detector.result.get('encoding')\n    elif hasattr(filename, 'encoding'):\n        return filename.encoding\n    else:  # assume file-like object that you can iter through\n        return _from_file(filename)\n\n\n__isnastr = np.frompyfunc(\n    {v for v in MISSING_VALUES if isinstance(v, str)}.__contains__, 1, 1)\n\n# wrapper for __isnastr with proper default out dtype\ndef isnastr(arr, out=None):\n    \"\"\"\n    Given an (object) array of string values, return a boolean mask array\n    that is True where the `arr` contains one of the string constants\n    considered as N/A.\n\n    Parameters\n    ----------\n    arr : np.ndarray\n        Input array of strings.\n    out : Optional[np.ndarray]\n        Optional output array of the same shape as arr\n\n    Returns\n    -------\n    mask : np.ndarray\n    \"\"\"\n    arr = np.asarray(arr)\n    if out is None and arr.shape != ():\n        out = np.empty_like(arr, dtype=bool)\n    return __isnastr(arr, out=out)\n\n\ndef guess_data_type(orig_values, namask=None):\n    \"\"\"\n    Use heuristics to guess data type.\n    \"\"\"\n    valuemap, values = None, orig_values\n    is_discrete = is_discrete_values(orig_values)\n    if is_discrete:\n        valuemap = sorted(is_discrete)\n        coltype = DiscreteVariable\n    else:\n        # try to parse as float\n        orig_values = np.asarray(orig_values)\n        if namask is None:\n            namask = isnastr(orig_values)\n        values = np.empty_like(orig_values, dtype=float)\n        values[namask] = np.nan\n        try:\n            np.copyto(values, orig_values, where=~namask, casting=\"unsafe\")\n        except ValueError:\n            tvar = TimeVariable('_')\n            try:\n                values[~namask] = [tvar.parse(i) for i in orig_values[~namask]]\n            except ValueError:\n                coltype = StringVariable\n                # return original_values\n                values = orig_values\n            else:\n                coltype = TimeVariable\n        else:\n            coltype = ContinuousVariable\n    return valuemap, values, coltype\n\n\ndef sanitize_variable(valuemap, values, orig_values, coltype, coltype_kwargs,\n                      name=None):\n    assert issubclass(coltype, Variable)\n\n    def get_number_of_decimals(values):\n        len_ = len\n        ndecimals = max((len_(value) - value.find(\".\")\n                         for value in values if \".\" in value),\n                        default=1)\n        return ndecimals - 1\n\n    if issubclass(coltype, DiscreteVariable) and valuemap is not None:\n        coltype_kwargs.update(values=valuemap)\n\n    var = coltype.make(name, **coltype_kwargs)\n\n    if isinstance(var, DiscreteVariable):\n        # Map discrete data to 'ints' (or at least what passes as int around\n        # here)\n        mapping = defaultdict(\n            lambda: np.nan,\n            {val: i for i, val in enumerate(var.values)},\n        )\n        mapping[\"\"] = np.nan\n        mapvalues_ = np.frompyfunc(mapping.__getitem__, 1, 1)\n\n        def mapvalues(arr):\n            arr = np.asarray(arr, dtype=object)\n            return mapvalues_(arr, out=np.empty_like(arr, dtype=float))\n        values = mapvalues(orig_values)\n\n    if coltype is StringVariable:\n        values = orig_values\n\n    # ContinuousVariable.number_of_decimals is supposed to be handled by\n    # ContinuousVariable.to_val. In the interest of speed, the reader bypasses\n    # it, so we set the number of decimals here.\n    # The number of decimals is increased if not set manually (in which case\n    # var.adjust_decimals would be 0).\n    if isinstance(var, ContinuousVariable) and var.adjust_decimals:\n        ndecimals = get_number_of_decimals(orig_values)\n        if var.adjust_decimals == 2 or ndecimals > var.number_of_decimals:\n            var.number_of_decimals = ndecimals\n            var.adjust_decimals = 1\n\n    if isinstance(var, TimeVariable) or coltype is TimeVariable:\n        # Re-parse the values because only now after coltype.make call\n        # above, variable var is the correct one\n        _var = var if isinstance(var, TimeVariable) else TimeVariable('_')\n        values = [_var.parse(i) for i in orig_values]\n\n    return values, var\n\n\nclass Flags:\n    \"\"\"Parser for column flags (i.e. third header row)\"\"\"\n    DELIMITER = ' '\n    _RE_SPLIT = re.compile(r'(?<!\\\\)' + DELIMITER).split\n    _RE_ATTR_UNQUOTED_STR = re.compile(r'^[a-zA-Z_]').match\n    ALL = OrderedDict((\n        ('class', 'c'),\n        ('ignore', 'i'),\n        ('meta', 'm'),\n        ('weight', 'w'),\n        ('.+?=.*?', ''),  # general key=value attributes\n    ))\n    _RE_ALL = re.compile(r'^({})$'.format('|'.join(filter(None, flatten(ALL.items())))))\n\n    def __init__(self, flags):\n        for v in filter(None, self.ALL.values()):\n            setattr(self, v, False)\n        self.attributes = {}\n        for flag in flags or []:\n            flag = flag.strip()\n            if self._RE_ALL.match(flag):\n                if '=' in flag:\n                    k, v = flag.split('=', 1)\n                    if not Flags._RE_ATTR_UNQUOTED_STR(v):\n                        try:\n                            v = literal_eval(v)\n                        except SyntaxError:\n                            # If parsing failed, treat value as string\n                            pass\n                    self.attributes[k] = v\n                else:\n                    setattr(self, flag, True)\n                    setattr(self, self.ALL.get(flag, ''), True)\n            elif flag:\n                warnings.warn('Invalid attribute flag \\'{}\\''.format(flag))\n\n    @staticmethod\n    def join(iterable, *args):\n        return Flags.DELIMITER.join(i.strip().replace(Flags.DELIMITER, '\\\\' + Flags.DELIMITER)\n                                    for i in chain(iterable, args)).lstrip()\n\n    @staticmethod\n    def split(s):\n        return [i.replace('\\\\' + Flags.DELIMITER, Flags.DELIMITER)\n                for i in Flags._RE_SPLIT(s)]\n\n\n# Matches discrete specification where all the values are listed, space-separated\n_RE_DISCRETE_LIST = re.compile(r'^\\s*[^\\s]+(\\s[^\\s]+)+\\s*$')\n_RE_TYPES = re.compile(r'^\\s*({}|{}|)\\s*$'.format(\n    _RE_DISCRETE_LIST.pattern,\n    '|'.join(flatten(getattr(vartype, 'TYPE_HEADERS') for vartype in Variable.registry.values()))\n    ))\n_RE_FLAGS = re.compile(r'^\\s*( |{}|)*\\s*$'.format(\n    '|'.join(flatten(filter(None, i) for i in Flags.ALL.items()))\n    ))\n\n\nclass FileFormatMeta(Registry):\n\n    def __new__(cls, name, bases, attrs):\n        newcls = super().__new__(cls, name, bases, attrs)\n\n        # Optionally add compressed versions of extensions as supported\n        if getattr(newcls, 'SUPPORT_COMPRESSED', False):\n            new_extensions = list(getattr(newcls, 'EXTENSIONS', ()))\n            for compression in Compression.all:\n                for ext in newcls.EXTENSIONS:\n                    new_extensions.append(ext + compression)\n                if sys.platform in ('darwin', 'win32'):\n                    # OSX file dialog doesn't support filtering on double\n                    # extensions (e.g. .csv.gz)\n                    # https://bugreports.qt.io/browse/QTBUG-38303\n                    # This is just here for OWFile that gets QFileDialog\n                    # filters from FileFormat.readers.keys()\n                    # EDIT: Windows exhibit similar problems:\n                    # while .tab.gz works, .tab.xz and .tab.bz2 do not!\n                    new_extensions.append(compression)\n            newcls.EXTENSIONS = tuple(new_extensions)\n\n        return newcls\n\n    @property\n    def formats(cls):\n        return cls.registry.values()\n\n    @lru_cache(5)\n    def _ext_to_attr_if_attr2(cls, attr, attr2):\n        \"\"\"\n        Return ``{ext: `attr`, ...}`` dict if ``cls`` has `attr2`.\n        If `attr` is '', return ``{ext: cls, ...}`` instead.\n\n        If there are multiple formats for an extension, return a format\n        with the lowest priority.\n        \"\"\"\n        formats = OrderedDict()\n        for format in sorted(cls.registry.values(), key=lambda x: x.PRIORITY):\n            if not hasattr(format, attr2):\n                continue\n            for ext in getattr(format, 'EXTENSIONS', []):\n                # Only adds if not yet registered\n                formats.setdefault(ext, getattr(format, attr, format))\n        return formats\n\n    @property\n    def names(cls):\n        return cls._ext_to_attr_if_attr2('DESCRIPTION', '__class__')\n\n    @property\n    def writers(cls):\n        return cls._ext_to_attr_if_attr2('', 'write_file')\n\n    @property\n    def readers(cls):\n        return cls._ext_to_attr_if_attr2('', 'read')\n\n    @property\n    def img_writers(cls):\n        warnings.warn(\n            f\"'{__name__}.FileFormat.img_writers' is no longer used and \"\n            \"will be removed. Please use \"\n            \"'Orange.widgets.io.FileFormat.img_writers' instead.\",\n            DeprecationWarning, stacklevel=2\n        )\n        return cls._ext_to_attr_if_attr2('', 'write_image')\n\n    @property\n    def graph_writers(cls):\n        return cls._ext_to_attr_if_attr2('', 'write_graph')\n\n\nclass FileFormat(metaclass=FileFormatMeta):\n    \"\"\"\n    Subclasses set the following attributes and override the following methods:\n\n        EXTENSIONS = ('.ext1', '.ext2', ...)\n        DESCRIPTION = 'human-readable file format description'\n        SUPPORT_COMPRESSED = False\n        SUPPORT_SPARSE_DATA = False\n\n        def read(self):\n            ...  # load headers, data, ...\n            return self.data_table(data, headers)\n\n        @classmethod\n        def write_file(cls, filename, data):\n            ...\n            self.write_headers(writer.write, data)\n            writer.writerows(data)\n\n    Wrapper FileFormat.data_table() returns Orange.data.Table from `data`\n    iterable (list (rows) of lists of values (cols)).\n    \"\"\"\n\n    # Priority when multiple formats support the same extension. Also\n    # the sort order in file open/save combo boxes. Lower is better.\n    PRIORITY = 10000\n    OPTIONAL_TYPE_ANNOTATIONS = False\n\n    def __init__(self, filename):\n        \"\"\"\n        Parameters\n        ----------\n        filename : str\n            name of the file to open\n        \"\"\"\n        self.filename = filename\n        self.sheet = None\n\n    @property\n    def sheets(self):\n        \"\"\"FileFormats with a notion of sheets should override this property\n        to return a list of sheet names in the file.\n\n        Returns\n        -------\n        a list of sheet names\n        \"\"\"\n        return ()\n\n    def select_sheet(self, sheet):\n        \"\"\"Select sheet to be read\n\n        Parameters\n        ----------\n        sheet : str\n            sheet name\n        \"\"\"\n        self.sheet = sheet\n\n    @classmethod\n    def get_reader(cls, filename):\n        \"\"\"Return reader instance that can be used to read the file\n\n        Parameters\n        ----------\n        filename : str\n\n        Returns\n        -------\n        FileFormat\n        \"\"\"\n        for ext, reader in cls.readers.items():\n            # Skip ambiguous, invalid compression-only extensions added on OSX\n            if ext in Compression.all:\n                continue\n            if fnmatch(path.basename(filename), '*' + ext):\n                return reader(filename)\n\n        raise IOError('No readers for file \"{}\"'.format(filename))\n\n    @classmethod\n    def write(cls, filename, data, with_annotations=True):\n        if cls.OPTIONAL_TYPE_ANNOTATIONS:\n            return cls.write_file(filename, data, with_annotations)\n        else:\n            return cls.write_file(filename, data)\n\n    @classmethod\n    def write_table_metadata(cls, filename, data):\n\n        def write_file(fn):\n            if all(isinstance(key, str) and isinstance(value, str)\n                   for key, value in data.attributes.items()):\n                with open(fn, 'w', encoding='utf-8') as f:\n                    f.write(\"\\n\".join(\"{}: {}\".format(*kv)\n                                      for kv in data.attributes.items()))\n            else:\n                with open(fn, 'wb') as f:\n                    pickle.dump(data.attributes, f, pickle.HIGHEST_PROTOCOL)\n\n        if isinstance(filename, str):\n            metafile = filename + '.metadata'\n            if getattr(data, 'attributes', None):\n                write_file(metafile)\n            elif path.exists(metafile):\n                remove(metafile)\n\n    @classmethod\n    def set_table_metadata(cls, filename, table):\n        # pylint: disable=bare-except\n        if isinstance(filename, str) and path.exists(filename + '.metadata'):\n            try:\n                with open(filename + '.metadata', 'rb') as f:\n                    table.attributes = pickle.load(f)\n            # Unpickling throws different exceptions, not just UnpickleError\n            except:\n                with open(filename + '.metadata', encoding='utf-8') as f:\n                    table.attributes = OrderedDict(\n                        (k.strip(), v.strip())\n                        for k, v in (line.split(\":\", 1)\n                                     for line in f.readlines()))\n\n    @classmethod\n    def locate(cls, filename, search_dirs=('.',)):\n        \"\"\"Locate a file with given filename that can be opened by one\n        of the available readers.\n\n        Parameters\n        ----------\n        filename : str\n        search_dirs : Iterable[str]\n\n        Returns\n        -------\n        str\n            Absolute path to the file\n        \"\"\"\n        if path.exists(filename):\n            return filename\n\n        for directory in search_dirs:\n            absolute_filename = path.join(directory, filename)\n            if path.exists(absolute_filename):\n                break\n            for ext in cls.readers:\n                if fnmatch(path.basename(filename), '*' + ext):\n                    break\n                # glob uses fnmatch internally\n                matching_files = glob(absolute_filename + ext)\n                if matching_files:\n                    absolute_filename = matching_files[0]\n                    break\n            if path.exists(absolute_filename):\n                break\n        else:\n            absolute_filename = \"\"\n\n        if not path.exists(absolute_filename):\n            raise IOError('File \"{}\" was not found.'.format(filename))\n\n        return absolute_filename\n\n    @staticmethod\n    def open(filename, *args, **kwargs):\n        \"\"\"\n        Format handlers can use this method instead of the builtin ``open()``\n        to transparently (de)compress files if requested (according to\n        `filename` extension). Set ``SUPPORT_COMPRESSED=True`` if you use this.\n        \"\"\"\n        return open_compressed(filename, *args, **kwargs)\n\n    @staticmethod\n    def parse_headers(data):\n        \"\"\"Return (header rows, rest of data) as discerned from `data`\"\"\"\n\n        def is_number(item):\n            try:\n                float(item)\n            except ValueError:\n                return False\n            return True\n        # Second row items are type identifiers\n        def header_test2(items):\n            return all(map(_RE_TYPES.match, items))\n        # Third row items are flags and column attributes (attr=value)\n        def header_test3(items):\n            return all(map(_RE_FLAGS.match, items))\n\n        data = iter(data)\n        header_rows = []\n\n        # Try to parse a three-line header\n        lines = []\n        try:\n            lines.append(list(next(data)))\n            lines.append(list(next(data)))\n            lines.append(list(next(data)))\n        except StopIteration:\n            lines, data = [], chain(lines, data)\n        if lines:\n            l1, l2, l3 = lines\n            # Three-line header if line 2 & 3 match (1st line can be anything)\n            if header_test2(l2) and header_test3(l3):\n                header_rows = [l1, l2, l3]\n            else:\n                lines, data = [], chain((l1, l2, l3), data)\n\n        # Try to parse a single-line header\n        if not header_rows:\n            try:\n                lines.append(list(next(data)))\n            except StopIteration:\n                pass\n            if lines:\n                # Header if none of the values in line 1 parses as a number\n                if not all(is_number(i) for i in lines[0]):\n                    header_rows = [lines[0]]\n                else:\n                    data = chain(lines, data)\n\n        return header_rows, data\n\n    @classmethod\n    def data_table(cls, data, headers=None):\n        \"\"\"\n        Return Orange.data.Table given rows of `headers` (iterable of iterable)\n        and rows of `data` (iterable of iterable).\n\n        Basically, the idea of subclasses is to produce those two iterables,\n        however they might.\n\n        If `headers` is not provided, the header rows are extracted from `data`,\n        assuming they precede it.\n        \"\"\"\n        if not headers:\n            headers, data = cls.parse_headers(data)\n\n        # Consider various header types (single-row, two-row, three-row, none)\n        if len(headers) == 3:\n            names, types, flags = map(list, headers)\n        else:\n            if len(headers) == 1:\n                HEADER1_FLAG_SEP = '#'\n                # First row format either:\n                #   1) delimited column names\n                #   2) -||- with type and flags prepended, separated by #,\n                #      e.g. d#sex,c#age,cC#IQ\n                _flags, names = zip(*[i.split(HEADER1_FLAG_SEP, 1)\n                                      if HEADER1_FLAG_SEP in i else ('', i)\n                                      for i in headers[0]]\n                                   )\n                names = list(names)\n            elif len(headers) == 2:\n                names, _flags = map(list, headers)\n            else:\n                # Use heuristics for everything\n                names, _flags = [], []\n            types = [''.join(filter(str.isupper, flag)).lower() for flag in _flags]\n            flags = [Flags.join(filter(str.islower, flag)) for flag in _flags]\n\n        # Determine maximum row length\n        rowlen = max(map(len, (names, types, flags)))\n\n        strip = False\n\n        def _equal_length(lst):\n            nonlocal strip\n            if len(lst) > rowlen > 0:\n                lst = lst[:rowlen]\n                strip = True\n            elif len(lst) < rowlen:\n                lst.extend(['']*(rowlen - len(lst)))\n            return lst\n\n        # Ensure all data is of equal width in a column-contiguous array\n        data = [_equal_length([s.strip() for s in row])\n                for row in data if any(row)]\n        data = np.array(data, dtype=object, order='F')\n\n        if strip:\n            warnings.warn(\"Columns with no headers were removed.\")\n\n        # Data may actually be longer than headers were\n        try:\n            rowlen = data.shape[1]\n        except IndexError:\n            pass\n        else:\n            for lst in (names, types, flags):\n                _equal_length(lst)\n\n        NAMEGEN = namegen('Feature ', 1)\n        Xcols, attrs = [], []\n        Mcols, metas = [], []\n        Ycols, clses = [], []\n        Wcols = []\n\n        # Rename variables if necessary\n        # Reusing across files still works if both files have same duplicates\n        name_counts = Counter(names)\n        del name_counts[\"\"]\n        if len(name_counts) != len(names) and name_counts:\n            uses = {name: 0 for name, count in name_counts.items() if count > 1}\n            for i, name in enumerate(names):\n                if name in uses:\n                    uses[name] += 1\n                    names[i] = \"{}_{}\".format(name, uses[name])\n\n        namask = np.empty(data.shape[0], dtype=bool)\n        # Iterate through the columns\n        for col in range(rowlen):\n            flag = Flags(Flags.split(flags[col]))\n            if flag.i:\n                continue\n\n            type_flag = types and types[col].strip()\n            try:\n                orig_values = data[:, col]\n            except IndexError:\n                orig_values = np.array([], dtype=object)\n\n            namask = isnastr(orig_values, out=namask)\n\n            coltype_kwargs = {}\n            valuemap = None\n            values = orig_values\n\n            if type_flag in StringVariable.TYPE_HEADERS:\n                coltype = StringVariable\n                values = orig_values\n            elif type_flag in ContinuousVariable.TYPE_HEADERS:\n                coltype = ContinuousVariable\n                values = np.empty(data.shape[0], dtype=float)\n                try:\n                    np.copyto(values, orig_values, casting=\"unsafe\",\n                              where=~namask)\n                    values[namask] = np.nan\n                except ValueError:\n                    for row, num in enumerate(orig_values):\n                        if not isnastr(num):\n                            try:\n                                float(num)\n                            except ValueError:\n                                break\n                    raise ValueError('Non-continuous value in (1-based) '\n                                     'line {}, column {}'.format(row + len(headers) + 1,\n                                                                 col + 1))\n\n            elif type_flag in TimeVariable.TYPE_HEADERS:\n                coltype = TimeVariable\n                values = np.where(namask, \"\", orig_values)\n            elif (type_flag in DiscreteVariable.TYPE_HEADERS or\n                  _RE_DISCRETE_LIST.match(type_flag)):\n                coltype = DiscreteVariable\n                orig_values = values = np.where(namask, \"\", orig_values)\n                if _RE_DISCRETE_LIST.match(type_flag):\n                    valuemap = Flags.split(type_flag)\n                    coltype_kwargs.update(ordered=True)\n                else:\n                    valuemap = sorted(set(orig_values) - {\"\"})\n            else:\n                # No known type specified, use heuristics\n                valuemap, values, coltype = guess_data_type(orig_values, namask)\n\n            if flag.m or coltype is StringVariable:\n                append_to = (Mcols, metas)\n            elif flag.w:\n                append_to = (Wcols, None)\n            elif flag.c:\n                append_to = (Ycols, clses)\n            else:\n                append_to = (Xcols, attrs)\n\n            cols, domain_vars = append_to\n\n            if domain_vars is not None:\n                var_name = names and names[col]\n                if not var_name:\n                    var_name = next(NAMEGEN)\n\n                values, var = sanitize_variable(\n                    valuemap, values, orig_values, coltype, coltype_kwargs,\n                    name=var_name)\n            else:\n                var = None\n            if domain_vars is not None:\n                var.attributes.update(flag.attributes)\n                domain_vars.append(var)\n\n            if isinstance(values, np.ndarray) and not values.flags.owndata:\n                values = values.copy()  # might view `data` (string columns)\n            cols.append(values)\n\n            try:\n                # allow gc to reclaim memory used by string values\n                data[:, col] = None\n            except IndexError:\n                pass\n\n        domain = Domain(attrs, clses, metas)\n\n        if not data.size:\n            return Table.from_domain(domain, 0)\n\n        X = Y = M = W = None\n        if Xcols:\n            X = np.c_[tuple(Xcols)]\n            assert X.dtype == np.float_\n        else:\n            X = np.empty((data.shape[0], 0), dtype=np.float_)\n        if Ycols:\n            Y = np.c_[tuple(Ycols)]\n            assert Y.dtype == np.float_\n        if Mcols:\n            M = np.c_[tuple(Mcols)].astype(object)\n        if Wcols:\n            W = np.c_[tuple(Wcols)].astype(float)\n\n        table = Table.from_numpy(domain, X, Y, M, W)\n        return table\n\n    @staticmethod\n    def header_names(data):\n        return ['weights'] * data.has_weights() + \\\n               [v.name for v in chain(data.domain.attributes,\n                                      data.domain.class_vars,\n                                      data.domain.metas)]\n\n    @staticmethod\n    def header_types(data):\n        def _vartype(var):\n            if var.is_continuous or var.is_string:\n                return var.TYPE_HEADERS[0]\n            elif var.is_discrete:\n                return Flags.join(var.values) if var.ordered else var.TYPE_HEADERS[0]\n            raise NotImplementedError\n        return ['continuous'] * data.has_weights() + \\\n               [_vartype(v) for v in chain(data.domain.attributes,\n                                           data.domain.class_vars,\n                                           data.domain.metas)]\n\n    @staticmethod\n    def header_flags(data):\n        return list(chain(['weight'] * data.has_weights(),\n                          (Flags.join([flag], *('{}={}'.format(*a)\n                                                for a in sorted(var.attributes.items())))\n                           for flag, var in chain(zip(repeat(''), data.domain.attributes),\n                                                  zip(repeat('class'), data.domain.class_vars),\n                                                  zip(repeat('meta'), data.domain.metas)))))\n\n    @classmethod\n    def write_headers(cls, write, data, with_annotations=True):\n        \"\"\"`write` is a callback that accepts an iterable\"\"\"\n        write(cls.header_names(data))\n        if with_annotations:\n            write(cls.header_types(data))\n            write(cls.header_flags(data))\n\n    @classmethod\n    def formatter(cls, var):\n        # type: (Variable) -> Callable[[Variable], Any]\n        # Return a column 'formatter' function. The function must return\n        # something that `write` knows how to write\n        if var.is_time:\n            return var.repr_val\n        elif var.is_continuous:\n            return lambda value: \"\" if isnan(value) else value\n        elif var.is_discrete:\n            return lambda value: \"\" if isnan(value) else var.values[int(value)]\n        elif var.is_string:\n            return lambda value: value\n        else:\n            return var.repr_val\n\n    @classmethod\n    def write_data(cls, write, data):\n        \"\"\"`write` is a callback that accepts an iterable\"\"\"\n        vars = list(chain((ContinuousVariable('_w'),) if data.has_weights() else (),\n                          data.domain.attributes,\n                          data.domain.class_vars,\n                          data.domain.metas))\n\n        formatters = [cls.formatter(v) for v in vars]\n        for row in zip(data.W if data.W.ndim > 1 else data.W[:, np.newaxis],\n                       data.X,\n                       data.Y if data.Y.ndim > 1 else data.Y[:, np.newaxis],\n                       data.metas):\n            write([fmt(v) for fmt, v in zip(formatters, flatten(row))])\n\n    @classmethod\n    def qualified_name(cls):\n        return cls.__module__ + '.' + cls.__name__\n\n\ndef class_from_qualified_name(format_name):\n    \"\"\" File format class from qualified name. \"\"\"\n    elements = format_name.split(\".\")\n    m = import_module(\".\".join(elements[:-1]))\n    return getattr(m, elements[-1])\n\n\nclass CSVReader(FileFormat):\n    \"\"\"Reader for comma separated files\"\"\"\n\n    EXTENSIONS = ('.csv',)\n    DESCRIPTION = 'Comma-separated values'\n    DELIMITERS = ',;:\\t$ '\n    SUPPORT_COMPRESSED = True\n    SUPPORT_SPARSE_DATA = False\n    PRIORITY = 20\n    OPTIONAL_TYPE_ANNOTATIONS = True\n\n    def read(self):\n        for encoding in (lambda: ('us-ascii', None),                 # fast\n                         lambda: (detect_encoding(self.filename), None),  # precise\n                         lambda: (locale.getpreferredencoding(False), None),\n                         lambda: (sys.getdefaultencoding(), None),   # desperate\n                         lambda: ('utf-8', None),                    # ...\n                         lambda: ('utf-8', 'ignore')):               # fallback\n            encoding, errors = encoding()\n            # Clear the error flag for all except the last check, because\n            # the error of second-to-last check is stored and shown as warning in owfile\n            if errors != 'ignore':\n                error = ''\n            with self.open(self.filename, mode='rt', newline='',\n                           encoding=encoding, errors=errors) as file:\n                # Sniff the CSV dialect (delimiter, quotes, ...)\n                try:\n                    dialect = csv.Sniffer().sniff(\n                        # Take first couple of *complete* lines as sample\n                        ''.join(file.readline() for _ in range(10)),\n                        self.DELIMITERS)\n                    delimiter = dialect.delimiter\n                    quotechar = dialect.quotechar\n                except UnicodeDecodeError as e:\n                    error = e\n                    continue\n                except csv.Error:\n                    delimiter = self.DELIMITERS[0]\n                    quotechar = csv.excel.quotechar\n\n                file.seek(0)\n                try:\n                    reader = csv.reader(\n                        file, delimiter=delimiter, quotechar=quotechar,\n                        skipinitialspace=True,\n                    )\n                    data = self.data_table(reader)\n\n                    # TODO: Name can be set unconditionally when/if\n                    # self.filename will always be a string with the file name.\n                    # Currently, some tests pass StringIO instead of\n                    # the file name to a reader.\n                    if isinstance(self.filename, str):\n                        data.name = path.splitext(\n                            path.split(self.filename)[-1])[0]\n                    if error and isinstance(error, UnicodeDecodeError):\n                        pos, endpos = error.args[2], error.args[3]\n                        warning = ('Skipped invalid byte(s) in position '\n                                   '{}{}').format(pos,\n                                                  ('-' + str(endpos)) if (endpos - pos) > 1 else '')\n                        warnings.warn(warning)\n                    self.set_table_metadata(self.filename, data)\n                    return data\n                except Exception as e:\n                    error = e\n                    continue\n        raise ValueError('Cannot parse dataset {}: {}'.format(self.filename, error)) from error\n\n    @classmethod\n    def write_file(cls, filename, data, with_annotations=True):\n        with cls.open(filename, mode='wt', newline='', encoding='utf-8') as file:\n            writer = csv.writer(file, delimiter=cls.DELIMITERS[0])\n            cls.write_headers(writer.writerow, data, with_annotations)\n            cls.write_data(writer.writerow, data)\n            cls.write_table_metadata(filename, data)\n\n\nclass TabReader(CSVReader):\n    \"\"\"Reader for tab separated files\"\"\"\n    EXTENSIONS = ('.tab', '.tsv')\n    DESCRIPTION = 'Tab-separated values'\n    DELIMITERS = '\\t'\n    PRIORITY = 10\n\n\nclass PickleReader(FileFormat):\n    \"\"\"Reader for pickled Table objects\"\"\"\n    EXTENSIONS = ('.pkl', '.pickle')\n    DESCRIPTION = 'Pickled Orange data'\n    SUPPORT_COMPRESSED = True\n    SUPPORT_SPARSE_DATA = True\n\n    def read(self):\n        with self.open(self.filename, 'rb') as f:\n            table = pickle.load(f)\n            if not isinstance(table, Table):\n                raise TypeError(\"file does not contain a data table\")\n            else:\n                return table\n\n    @classmethod\n    def write_file(cls, filename, data):\n        with cls.open(filename, 'wb') as f:\n            pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)\n\n\nclass BasketReader(FileFormat):\n    \"\"\"Reader for basket (sparse) files\"\"\"\n    EXTENSIONS = ('.basket', '.bsk')\n    DESCRIPTION = 'Basket file'\n    SUPPORT_SPARSE_DATA = True\n\n    def read(self):\n        def constr_vars(inds):\n            if inds:\n                return [ContinuousVariable(x.decode(\"utf-8\")) for _, x in\n                        sorted((ind, name) for name, ind in inds.items())]\n\n        X, Y, metas, attr_indices, class_indices, meta_indices = \\\n            _io.sparse_read_float(self.filename.encode(sys.getdefaultencoding()))\n\n        attrs = constr_vars(attr_indices)\n        classes = constr_vars(class_indices)\n        meta_attrs = constr_vars(meta_indices)\n        domain = Domain(attrs, classes, meta_attrs)\n        table = Table.from_numpy(\n            domain, attrs and X, classes and Y, metas and meta_attrs)\n        table.name = path.splitext(path.split(self.filename)[-1])[0]\n        return table\n\n\nclass ExcelReader(FileFormat):\n    \"\"\"Reader for excel files\"\"\"\n    EXTENSIONS = ('.xlsx',)\n    DESCRIPTION = 'Microsoft Excel spreadsheet'\n    SUPPORT_COMPRESSED = False\n    SUPPORT_SPARSE_DATA = False\n\n    def __init__(self, filename):\n        super().__init__(filename=filename)\n        self._workbook = None\n\n    @property\n    def workbook(self):\n        if not self._workbook:\n            self._workbook = xlrd.open_workbook(self.filename, on_demand=True)\n        return self._workbook\n\n    @property\n    @lru_cache(1)\n    def sheets(self):\n        if self.workbook:\n            return self.workbook.sheet_names()\n        else:\n            return ()\n\n    def read(self):\n        if self.sheet:\n            ss = self.workbook.sheet_by_name(self.sheet)\n        else:\n            ss = self.workbook.sheet_by_index(0)\n        try:\n            first_row = next(i for i in range(ss.nrows) if any(ss.row_values(i)))\n            first_col = next(i for i in range(ss.ncols) if ss.cell_value(first_row, i))\n            row_len = ss.row_len(first_row)\n            cells = filter(any,\n                           [[str(ss.cell_value(row, col)) if col < ss.row_len(row) else ''\n                             for col in range(first_col, row_len)]\n                            for row in range(first_row, ss.nrows)])\n            table = self.data_table(cells)\n            table.name = path.splitext(path.split(self.filename)[-1])[0]\n            if self.sheet:\n                table.name = '-'.join((table.name, self.sheet))\n        except Exception:\n            raise IOError(\"Couldn't load spreadsheet from \" + self.filename)\n        return table\n\n    @classmethod\n    def write_file(cls, filename, data):\n        vars = list(chain((ContinuousVariable('_w'),) if data.has_weights() else (),\n                          data.domain.attributes,\n                          data.domain.class_vars,\n                          data.domain.metas))\n        formatters = [cls.formatter(v) for v in vars]\n        zipped_list_data = zip(data.W if data.W.ndim > 1 else data.W[:, np.newaxis],\n                               data.X,\n                               data.Y if data.Y.ndim > 1 else data.Y[:, np.newaxis],\n                               data.metas)\n        headers = cls.header_names(data)\n        workbook = xlsxwriter.Workbook(filename)\n        sheet = workbook.add_worksheet()\n        for c, header in enumerate(headers):\n            sheet.write(0, c, header)\n        for i, row in enumerate(zipped_list_data, 1):\n            for j, (fmt, v) in enumerate(zip(formatters, flatten(row))):\n                sheet.write(i, j, fmt(v))\n        workbook.close()\n\n\nclass DotReader(FileFormat):\n    \"\"\"Writer for dot (graph) files\"\"\"\n    EXTENSIONS = ('.dot', '.gv')\n    DESCRIPTION = 'Dot graph description'\n    SUPPORT_COMPRESSED = True\n    SUPPORT_SPARSE_DATA = False\n\n    @classmethod\n    def write_graph(cls, filename, graph):\n        from sklearn import tree\n        tree.export_graphviz(graph, out_file=cls.open(filename, 'wt'))\n\n    @classmethod\n    def write(cls, filename, tree):\n        if type(tree) == dict:\n            tree = tree['tree']\n        cls.write_graph(filename, tree)\n\n\nclass UrlReader(FileFormat):\n    def __init__(self, filename):\n        filename = filename.strip()\n        if not urlparse(filename).scheme:\n            filename = 'http://' + filename\n        super().__init__(filename)\n\n    @staticmethod\n    def urlopen(url):\n        req = Request(\n            url,\n            # Avoid 403 error with servers that dislike scrapers\n            headers={'User-Agent': 'Mozilla/5.0 (X11; Linux) Gecko/20100101 Firefox/'})\n        return urlopen(req, timeout=10)\n\n    def read(self):\n        self.filename = self._trim(self._resolve_redirects(self.filename))\n        with contextlib.closing(self.urlopen(self.filename)) as response:\n            name = self._suggest_filename(response.headers['content-disposition'])\n            with NamedTemporaryFile(suffix=name, delete=False) as f:\n                f.write(response.read())\n                # delete=False is a workaround for https://bugs.python.org/issue14243\n\n            reader = self.get_reader(f.name)\n            data = reader.read()\n            remove(f.name)\n        # Override name set in from_file() to avoid holding the temp prefix\n        data.name = path.splitext(name)[0]\n        data.origin = self.filename\n        return data\n\n    def _resolve_redirects(self, url):\n        # Resolve (potential) redirects to a final URL\n        with contextlib.closing(self.urlopen(url)) as response:\n            return response.url\n\n    @classmethod\n    def _trim(cls, url):\n        URL_TRIMMERS = (\n            cls._trim_googlesheet,\n            cls._trim_dropbox,\n        )\n        for trim in URL_TRIMMERS:\n            try:\n                url = trim(url)\n            except ValueError:\n                continue\n            else:\n                break\n        return url\n\n    @staticmethod\n    def _trim_googlesheet(url):\n        match = re.match(r'(?:https?://)?(?:www\\.)?'\n                         r'docs\\.google\\.com/spreadsheets/d/'\n                         r'(?P<workbook_id>[-\\w_]+)'\n                         r'(?:/.*?gid=(?P<sheet_id>\\d+).*|.*)?',\n                         url, re.IGNORECASE)\n        try:\n            workbook, sheet = match.group('workbook_id'), match.group('sheet_id')\n            if not workbook:\n                raise ValueError\n        except (AttributeError, ValueError):\n            raise ValueError\n        url = 'https://docs.google.com/spreadsheets/d/{}/export?format=tsv'.format(workbook)\n        if sheet:\n            url += '&gid=' + sheet\n        return url\n\n    @staticmethod\n    def _trim_dropbox(url):\n        parts = urlsplit(url)\n        if not parts.netloc.endswith('dropbox.com'):\n            raise ValueError\n        return urlunsplit(parts._replace(query='dl=1'))\n\n    def _suggest_filename(self, content_disposition):\n        default_name = re.sub(r'[\\\\:/]', '_', urlparse(self.filename).path)\n\n        # See https://tools.ietf.org/html/rfc6266#section-4.1\n        matches = re.findall(r\"filename\\*?=(?:\\\"|.{0,10}?'[^']*')([^\\\"]+)\",\n                             content_disposition or '')\n        return urlunquote(matches[-1]) if matches else default_name\n",
            "new_file_content": "import contextlib\nimport csv\nimport locale\nimport pickle\nimport re\nimport subprocess\nimport sys\nimport warnings\n\nfrom ast import literal_eval\nfrom collections import OrderedDict, Counter, defaultdict\nfrom functools import lru_cache\nfrom importlib import import_module\nfrom itertools import chain, repeat\nfrom math import isnan\n\nfrom os import path, remove\nfrom tempfile import NamedTemporaryFile\nfrom urllib.parse import urlparse, urlsplit, urlunsplit, unquote as urlunquote\nfrom urllib.request import urlopen, Request\nfrom fnmatch import fnmatch\nfrom glob import glob\n\nimport numpy as np\nfrom chardet.universaldetector import UniversalDetector\n\nimport xlrd\nimport xlsxwriter\n\nfrom Orange.data import (\n    _io, is_discrete_values, MISSING_VALUES, Table, Domain, Variable,\n    DiscreteVariable, StringVariable, ContinuousVariable, TimeVariable,\n)\nfrom Orange.util import Registry, flatten, namegen\n\n\n# Support values longer than 128K (i.e. text contents features)\ncsv.field_size_limit(100*1024*1024)\n\n__all__ = [\"Flags\", \"FileFormat\"]\n\n\n_IDENTITY = lambda i: i\n\n\nclass Compression:\n    \"\"\"Supported compression extensions\"\"\"\n    GZIP = '.gz'\n    BZIP2 = '.bz2'\n    XZ = '.xz'\n    all = (GZIP, BZIP2, XZ)\n\n\ndef open_compressed(filename, *args, _open=open, **kwargs):\n    \"\"\"Return seamlessly decompressed open file handle for `filename`\"\"\"\n    if isinstance(filename, str):\n        if filename.endswith(Compression.GZIP):\n            from gzip import open as _open\n        elif filename.endswith(Compression.BZIP2):\n            from bz2 import open as _open\n        elif filename.endswith(Compression.XZ):\n            from lzma import open as _open\n        return _open(filename, *args, **kwargs)\n    # Else already a file, just pass it through\n    return filename\n\n\ndef detect_encoding(filename):\n    \"\"\"\n    Detect encoding of `filename`, which can be a ``str`` filename, a\n    ``file``-like object, or ``bytes``.\n    \"\"\"\n    # Try with Unix file utility first because it's faster (~10ms vs 100ms)\n    if isinstance(filename, str) and not filename.endswith(Compression.all):\n        try:\n            with subprocess.Popen(('file', '--brief', '--mime-encoding', filename),\n                                  stdout=subprocess.PIPE) as process:\n                process.wait()\n                if process.returncode == 0:\n                    encoding = process.stdout.read().strip()\n                    # file only supports these encodings; for others it says\n                    # unknown-8bit or binary. So we give chardet a chance to do\n                    # better\n                    if encoding in (b'utf-8', b'us-ascii', b'iso-8859-1',\n                                    b'utf-7', b'utf-16le', b'utf-16be', b'ebcdic'):\n                        return encoding.decode('us-ascii')\n        except OSError:\n            pass  # windoze\n\n    # file not available or unable to guess the encoding, have chardet do it\n    detector = UniversalDetector()\n    # We examine only first N 4kB blocks of file because chardet is really slow\n    MAX_BYTES = 4*1024*12\n\n    def _from_file(f):\n        detector.feed(f.read(MAX_BYTES))\n        detector.close()\n        return (detector.result.get('encoding')\n                if detector.result.get('confidence', 0) >= .85 else\n                'utf-8')\n\n    if isinstance(filename, str):\n        with open_compressed(filename, 'rb') as f:\n            return _from_file(f)\n    elif isinstance(filename, bytes):\n        detector.feed(filename[:MAX_BYTES])\n        detector.close()\n        return detector.result.get('encoding')\n    elif hasattr(filename, 'encoding'):\n        return filename.encoding\n    else:  # assume file-like object that you can iter through\n        return _from_file(filename)\n\n\n__isnastr = np.frompyfunc(\n    {v for v in MISSING_VALUES if isinstance(v, str)}.__contains__, 1, 1)\n\n# wrapper for __isnastr with proper default out dtype\ndef isnastr(arr, out=None):\n    \"\"\"\n    Given an (object) array of string values, return a boolean mask array\n    that is True where the `arr` contains one of the string constants\n    considered as N/A.\n\n    Parameters\n    ----------\n    arr : np.ndarray\n        Input array of strings.\n    out : Optional[np.ndarray]\n        Optional output array of the same shape as arr\n\n    Returns\n    -------\n    mask : np.ndarray\n    \"\"\"\n    arr = np.asarray(arr)\n    if out is None and arr.shape != ():\n        out = np.empty_like(arr, dtype=bool)\n    return __isnastr(arr, out=out)\n\n\ndef guess_data_type(orig_values, namask=None):\n    \"\"\"\n    Use heuristics to guess data type.\n    \"\"\"\n    valuemap, values = None, orig_values\n    is_discrete = is_discrete_values(orig_values)\n    orig_values = np.asarray(orig_values, dtype=str)\n    if namask is None:\n        namask = isnastr(orig_values)\n    if is_discrete:\n        valuemap = sorted(is_discrete)\n        coltype = DiscreteVariable\n    else:\n        # try to parse as float\n        values = np.empty_like(orig_values, dtype=float)\n        values[namask] = np.nan\n        try:\n            np.copyto(values, orig_values, where=~namask, casting=\"unsafe\")\n        except ValueError:\n            values = orig_values\n            coltype = StringVariable\n        else:\n            coltype = ContinuousVariable\n\n    if coltype is not ContinuousVariable:\n        # when not continuous variable it can still be time variable even it\n        # was before recognized as a discrete\n        tvar = TimeVariable('_')\n        # introducing new variable prevent overwriting orig_values and values\n        temp_values = np.empty_like(orig_values, dtype=float)\n        try:\n            temp_values[~namask] = [\n                tvar.parse_exact_iso(i) for i in orig_values[~namask]]\n        except ValueError:\n            pass\n        else:\n            valuemap = None\n            coltype = TimeVariable\n            values = temp_values\n    return valuemap, values, coltype\n\n\ndef sanitize_variable(valuemap, values, orig_values, coltype, coltype_kwargs,\n                      name=None):\n    assert issubclass(coltype, Variable)\n\n    def get_number_of_decimals(values):\n        len_ = len\n        ndecimals = max((len_(value) - value.find(\".\")\n                         for value in values if \".\" in value),\n                        default=1)\n        return ndecimals - 1\n\n    if issubclass(coltype, DiscreteVariable) and valuemap is not None:\n        coltype_kwargs.update(values=valuemap)\n\n    var = coltype.make(name, **coltype_kwargs)\n\n    if isinstance(var, DiscreteVariable):\n        # Map discrete data to 'ints' (or at least what passes as int around\n        # here)\n        mapping = defaultdict(\n            lambda: np.nan,\n            {val: i for i, val in enumerate(var.values)},\n        )\n        mapping[\"\"] = np.nan\n        mapvalues_ = np.frompyfunc(mapping.__getitem__, 1, 1)\n\n        def mapvalues(arr):\n            arr = np.asarray(arr, dtype=object)\n            return mapvalues_(arr, out=np.empty_like(arr, dtype=float))\n        values = mapvalues(orig_values)\n\n    if coltype is StringVariable:\n        values = orig_values\n\n    # ContinuousVariable.number_of_decimals is supposed to be handled by\n    # ContinuousVariable.to_val. In the interest of speed, the reader bypasses\n    # it, so we set the number of decimals here.\n    # The number of decimals is increased if not set manually (in which case\n    # var.adjust_decimals would be 0).\n    if isinstance(var, ContinuousVariable) and var.adjust_decimals:\n        ndecimals = get_number_of_decimals(orig_values)\n        if var.adjust_decimals == 2 or ndecimals > var.number_of_decimals:\n            var.number_of_decimals = ndecimals\n            var.adjust_decimals = 1\n\n    if isinstance(var, TimeVariable) or coltype is TimeVariable:\n        # Re-parse the values because only now after coltype.make call\n        # above, variable var is the correct one\n        _var = var if isinstance(var, TimeVariable) else TimeVariable('_')\n        values = [_var.parse(i) for i in orig_values]\n\n    return values, var\n\n\nclass Flags:\n    \"\"\"Parser for column flags (i.e. third header row)\"\"\"\n    DELIMITER = ' '\n    _RE_SPLIT = re.compile(r'(?<!\\\\)' + DELIMITER).split\n    _RE_ATTR_UNQUOTED_STR = re.compile(r'^[a-zA-Z_]').match\n    ALL = OrderedDict((\n        ('class', 'c'),\n        ('ignore', 'i'),\n        ('meta', 'm'),\n        ('weight', 'w'),\n        ('.+?=.*?', ''),  # general key=value attributes\n    ))\n    _RE_ALL = re.compile(r'^({})$'.format('|'.join(filter(None, flatten(ALL.items())))))\n\n    def __init__(self, flags):\n        for v in filter(None, self.ALL.values()):\n            setattr(self, v, False)\n        self.attributes = {}\n        for flag in flags or []:\n            flag = flag.strip()\n            if self._RE_ALL.match(flag):\n                if '=' in flag:\n                    k, v = flag.split('=', 1)\n                    if not Flags._RE_ATTR_UNQUOTED_STR(v):\n                        try:\n                            v = literal_eval(v)\n                        except SyntaxError:\n                            # If parsing failed, treat value as string\n                            pass\n                    self.attributes[k] = v\n                else:\n                    setattr(self, flag, True)\n                    setattr(self, self.ALL.get(flag, ''), True)\n            elif flag:\n                warnings.warn('Invalid attribute flag \\'{}\\''.format(flag))\n\n    @staticmethod\n    def join(iterable, *args):\n        return Flags.DELIMITER.join(i.strip().replace(Flags.DELIMITER, '\\\\' + Flags.DELIMITER)\n                                    for i in chain(iterable, args)).lstrip()\n\n    @staticmethod\n    def split(s):\n        return [i.replace('\\\\' + Flags.DELIMITER, Flags.DELIMITER)\n                for i in Flags._RE_SPLIT(s)]\n\n\n# Matches discrete specification where all the values are listed, space-separated\n_RE_DISCRETE_LIST = re.compile(r'^\\s*[^\\s]+(\\s[^\\s]+)+\\s*$')\n_RE_TYPES = re.compile(r'^\\s*({}|{}|)\\s*$'.format(\n    _RE_DISCRETE_LIST.pattern,\n    '|'.join(flatten(getattr(vartype, 'TYPE_HEADERS') for vartype in Variable.registry.values()))\n    ))\n_RE_FLAGS = re.compile(r'^\\s*( |{}|)*\\s*$'.format(\n    '|'.join(flatten(filter(None, i) for i in Flags.ALL.items()))\n    ))\n\n\nclass FileFormatMeta(Registry):\n\n    def __new__(cls, name, bases, attrs):\n        newcls = super().__new__(cls, name, bases, attrs)\n\n        # Optionally add compressed versions of extensions as supported\n        if getattr(newcls, 'SUPPORT_COMPRESSED', False):\n            new_extensions = list(getattr(newcls, 'EXTENSIONS', ()))\n            for compression in Compression.all:\n                for ext in newcls.EXTENSIONS:\n                    new_extensions.append(ext + compression)\n                if sys.platform in ('darwin', 'win32'):\n                    # OSX file dialog doesn't support filtering on double\n                    # extensions (e.g. .csv.gz)\n                    # https://bugreports.qt.io/browse/QTBUG-38303\n                    # This is just here for OWFile that gets QFileDialog\n                    # filters from FileFormat.readers.keys()\n                    # EDIT: Windows exhibit similar problems:\n                    # while .tab.gz works, .tab.xz and .tab.bz2 do not!\n                    new_extensions.append(compression)\n            newcls.EXTENSIONS = tuple(new_extensions)\n\n        return newcls\n\n    @property\n    def formats(cls):\n        return cls.registry.values()\n\n    @lru_cache(5)\n    def _ext_to_attr_if_attr2(cls, attr, attr2):\n        \"\"\"\n        Return ``{ext: `attr`, ...}`` dict if ``cls`` has `attr2`.\n        If `attr` is '', return ``{ext: cls, ...}`` instead.\n\n        If there are multiple formats for an extension, return a format\n        with the lowest priority.\n        \"\"\"\n        formats = OrderedDict()\n        for format in sorted(cls.registry.values(), key=lambda x: x.PRIORITY):\n            if not hasattr(format, attr2):\n                continue\n            for ext in getattr(format, 'EXTENSIONS', []):\n                # Only adds if not yet registered\n                formats.setdefault(ext, getattr(format, attr, format))\n        return formats\n\n    @property\n    def names(cls):\n        return cls._ext_to_attr_if_attr2('DESCRIPTION', '__class__')\n\n    @property\n    def writers(cls):\n        return cls._ext_to_attr_if_attr2('', 'write_file')\n\n    @property\n    def readers(cls):\n        return cls._ext_to_attr_if_attr2('', 'read')\n\n    @property\n    def img_writers(cls):\n        warnings.warn(\n            f\"'{__name__}.FileFormat.img_writers' is no longer used and \"\n            \"will be removed. Please use \"\n            \"'Orange.widgets.io.FileFormat.img_writers' instead.\",\n            DeprecationWarning, stacklevel=2\n        )\n        return cls._ext_to_attr_if_attr2('', 'write_image')\n\n    @property\n    def graph_writers(cls):\n        return cls._ext_to_attr_if_attr2('', 'write_graph')\n\n\nclass FileFormat(metaclass=FileFormatMeta):\n    \"\"\"\n    Subclasses set the following attributes and override the following methods:\n\n        EXTENSIONS = ('.ext1', '.ext2', ...)\n        DESCRIPTION = 'human-readable file format description'\n        SUPPORT_COMPRESSED = False\n        SUPPORT_SPARSE_DATA = False\n\n        def read(self):\n            ...  # load headers, data, ...\n            return self.data_table(data, headers)\n\n        @classmethod\n        def write_file(cls, filename, data):\n            ...\n            self.write_headers(writer.write, data)\n            writer.writerows(data)\n\n    Wrapper FileFormat.data_table() returns Orange.data.Table from `data`\n    iterable (list (rows) of lists of values (cols)).\n    \"\"\"\n\n    # Priority when multiple formats support the same extension. Also\n    # the sort order in file open/save combo boxes. Lower is better.\n    PRIORITY = 10000\n    OPTIONAL_TYPE_ANNOTATIONS = False\n\n    def __init__(self, filename):\n        \"\"\"\n        Parameters\n        ----------\n        filename : str\n            name of the file to open\n        \"\"\"\n        self.filename = filename\n        self.sheet = None\n\n    @property\n    def sheets(self):\n        \"\"\"FileFormats with a notion of sheets should override this property\n        to return a list of sheet names in the file.\n\n        Returns\n        -------\n        a list of sheet names\n        \"\"\"\n        return ()\n\n    def select_sheet(self, sheet):\n        \"\"\"Select sheet to be read\n\n        Parameters\n        ----------\n        sheet : str\n            sheet name\n        \"\"\"\n        self.sheet = sheet\n\n    @classmethod\n    def get_reader(cls, filename):\n        \"\"\"Return reader instance that can be used to read the file\n\n        Parameters\n        ----------\n        filename : str\n\n        Returns\n        -------\n        FileFormat\n        \"\"\"\n        for ext, reader in cls.readers.items():\n            # Skip ambiguous, invalid compression-only extensions added on OSX\n            if ext in Compression.all:\n                continue\n            if fnmatch(path.basename(filename), '*' + ext):\n                return reader(filename)\n\n        raise IOError('No readers for file \"{}\"'.format(filename))\n\n    @classmethod\n    def write(cls, filename, data, with_annotations=True):\n        if cls.OPTIONAL_TYPE_ANNOTATIONS:\n            return cls.write_file(filename, data, with_annotations)\n        else:\n            return cls.write_file(filename, data)\n\n    @classmethod\n    def write_table_metadata(cls, filename, data):\n\n        def write_file(fn):\n            if all(isinstance(key, str) and isinstance(value, str)\n                   for key, value in data.attributes.items()):\n                with open(fn, 'w', encoding='utf-8') as f:\n                    f.write(\"\\n\".join(\"{}: {}\".format(*kv)\n                                      for kv in data.attributes.items()))\n            else:\n                with open(fn, 'wb') as f:\n                    pickle.dump(data.attributes, f, pickle.HIGHEST_PROTOCOL)\n\n        if isinstance(filename, str):\n            metafile = filename + '.metadata'\n            if getattr(data, 'attributes', None):\n                write_file(metafile)\n            elif path.exists(metafile):\n                remove(metafile)\n\n    @classmethod\n    def set_table_metadata(cls, filename, table):\n        # pylint: disable=bare-except\n        if isinstance(filename, str) and path.exists(filename + '.metadata'):\n            try:\n                with open(filename + '.metadata', 'rb') as f:\n                    table.attributes = pickle.load(f)\n            # Unpickling throws different exceptions, not just UnpickleError\n            except:\n                with open(filename + '.metadata', encoding='utf-8') as f:\n                    table.attributes = OrderedDict(\n                        (k.strip(), v.strip())\n                        for k, v in (line.split(\":\", 1)\n                                     for line in f.readlines()))\n\n    @classmethod\n    def locate(cls, filename, search_dirs=('.',)):\n        \"\"\"Locate a file with given filename that can be opened by one\n        of the available readers.\n\n        Parameters\n        ----------\n        filename : str\n        search_dirs : Iterable[str]\n\n        Returns\n        -------\n        str\n            Absolute path to the file\n        \"\"\"\n        if path.exists(filename):\n            return filename\n\n        for directory in search_dirs:\n            absolute_filename = path.join(directory, filename)\n            if path.exists(absolute_filename):\n                break\n            for ext in cls.readers:\n                if fnmatch(path.basename(filename), '*' + ext):\n                    break\n                # glob uses fnmatch internally\n                matching_files = glob(absolute_filename + ext)\n                if matching_files:\n                    absolute_filename = matching_files[0]\n                    break\n            if path.exists(absolute_filename):\n                break\n        else:\n            absolute_filename = \"\"\n\n        if not path.exists(absolute_filename):\n            raise IOError('File \"{}\" was not found.'.format(filename))\n\n        return absolute_filename\n\n    @staticmethod\n    def open(filename, *args, **kwargs):\n        \"\"\"\n        Format handlers can use this method instead of the builtin ``open()``\n        to transparently (de)compress files if requested (according to\n        `filename` extension). Set ``SUPPORT_COMPRESSED=True`` if you use this.\n        \"\"\"\n        return open_compressed(filename, *args, **kwargs)\n\n    @staticmethod\n    def parse_headers(data):\n        \"\"\"Return (header rows, rest of data) as discerned from `data`\"\"\"\n\n        def is_number(item):\n            try:\n                float(item)\n            except ValueError:\n                return False\n            return True\n        # Second row items are type identifiers\n        def header_test2(items):\n            return all(map(_RE_TYPES.match, items))\n        # Third row items are flags and column attributes (attr=value)\n        def header_test3(items):\n            return all(map(_RE_FLAGS.match, items))\n\n        data = iter(data)\n        header_rows = []\n\n        # Try to parse a three-line header\n        lines = []\n        try:\n            lines.append(list(next(data)))\n            lines.append(list(next(data)))\n            lines.append(list(next(data)))\n        except StopIteration:\n            lines, data = [], chain(lines, data)\n        if lines:\n            l1, l2, l3 = lines\n            # Three-line header if line 2 & 3 match (1st line can be anything)\n            if header_test2(l2) and header_test3(l3):\n                header_rows = [l1, l2, l3]\n            else:\n                lines, data = [], chain((l1, l2, l3), data)\n\n        # Try to parse a single-line header\n        if not header_rows:\n            try:\n                lines.append(list(next(data)))\n            except StopIteration:\n                pass\n            if lines:\n                # Header if none of the values in line 1 parses as a number\n                if not all(is_number(i) for i in lines[0]):\n                    header_rows = [lines[0]]\n                else:\n                    data = chain(lines, data)\n\n        return header_rows, data\n\n    @classmethod\n    def data_table(cls, data, headers=None):\n        \"\"\"\n        Return Orange.data.Table given rows of `headers` (iterable of iterable)\n        and rows of `data` (iterable of iterable).\n\n        Basically, the idea of subclasses is to produce those two iterables,\n        however they might.\n\n        If `headers` is not provided, the header rows are extracted from `data`,\n        assuming they precede it.\n        \"\"\"\n        if not headers:\n            headers, data = cls.parse_headers(data)\n\n        # Consider various header types (single-row, two-row, three-row, none)\n        if len(headers) == 3:\n            names, types, flags = map(list, headers)\n        else:\n            if len(headers) == 1:\n                HEADER1_FLAG_SEP = '#'\n                # First row format either:\n                #   1) delimited column names\n                #   2) -||- with type and flags prepended, separated by #,\n                #      e.g. d#sex,c#age,cC#IQ\n                _flags, names = zip(*[i.split(HEADER1_FLAG_SEP, 1)\n                                      if HEADER1_FLAG_SEP in i else ('', i)\n                                      for i in headers[0]]\n                                   )\n                names = list(names)\n            elif len(headers) == 2:\n                names, _flags = map(list, headers)\n            else:\n                # Use heuristics for everything\n                names, _flags = [], []\n            types = [''.join(filter(str.isupper, flag)).lower() for flag in _flags]\n            flags = [Flags.join(filter(str.islower, flag)) for flag in _flags]\n\n        # Determine maximum row length\n        rowlen = max(map(len, (names, types, flags)))\n\n        strip = False\n\n        def _equal_length(lst):\n            nonlocal strip\n            if len(lst) > rowlen > 0:\n                lst = lst[:rowlen]\n                strip = True\n            elif len(lst) < rowlen:\n                lst.extend(['']*(rowlen - len(lst)))\n            return lst\n\n        # Ensure all data is of equal width in a column-contiguous array\n        data = [_equal_length([s.strip() for s in row])\n                for row in data if any(row)]\n        data = np.array(data, dtype=object, order='F')\n\n        if strip:\n            warnings.warn(\"Columns with no headers were removed.\")\n\n        # Data may actually be longer than headers were\n        try:\n            rowlen = data.shape[1]\n        except IndexError:\n            pass\n        else:\n            for lst in (names, types, flags):\n                _equal_length(lst)\n\n        NAMEGEN = namegen('Feature ', 1)\n        Xcols, attrs = [], []\n        Mcols, metas = [], []\n        Ycols, clses = [], []\n        Wcols = []\n\n        # Rename variables if necessary\n        # Reusing across files still works if both files have same duplicates\n        name_counts = Counter(names)\n        del name_counts[\"\"]\n        if len(name_counts) != len(names) and name_counts:\n            uses = {name: 0 for name, count in name_counts.items() if count > 1}\n            for i, name in enumerate(names):\n                if name in uses:\n                    uses[name] += 1\n                    names[i] = \"{}_{}\".format(name, uses[name])\n\n        namask = np.empty(data.shape[0], dtype=bool)\n        # Iterate through the columns\n        for col in range(rowlen):\n            flag = Flags(Flags.split(flags[col]))\n            if flag.i:\n                continue\n\n            type_flag = types and types[col].strip()\n            try:\n                orig_values = data[:, col]\n            except IndexError:\n                orig_values = np.array([], dtype=object)\n\n            namask = isnastr(orig_values, out=namask)\n\n            coltype_kwargs = {}\n            valuemap = None\n            values = orig_values\n\n            if type_flag in StringVariable.TYPE_HEADERS:\n                coltype = StringVariable\n                values = orig_values\n            elif type_flag in ContinuousVariable.TYPE_HEADERS:\n                coltype = ContinuousVariable\n                values = np.empty(data.shape[0], dtype=float)\n                try:\n                    np.copyto(values, orig_values, casting=\"unsafe\",\n                              where=~namask)\n                    values[namask] = np.nan\n                except ValueError:\n                    for row, num in enumerate(orig_values):\n                        if not isnastr(num):\n                            try:\n                                float(num)\n                            except ValueError:\n                                break\n                    raise ValueError('Non-continuous value in (1-based) '\n                                     'line {}, column {}'.format(row + len(headers) + 1,\n                                                                 col + 1))\n\n            elif type_flag in TimeVariable.TYPE_HEADERS:\n                coltype = TimeVariable\n                values = np.where(namask, \"\", orig_values)\n            elif (type_flag in DiscreteVariable.TYPE_HEADERS or\n                  _RE_DISCRETE_LIST.match(type_flag)):\n                coltype = DiscreteVariable\n                orig_values = values = np.where(namask, \"\", orig_values)\n                if _RE_DISCRETE_LIST.match(type_flag):\n                    valuemap = Flags.split(type_flag)\n                    coltype_kwargs.update(ordered=True)\n                else:\n                    valuemap = sorted(set(orig_values) - {\"\"})\n            else:\n                # No known type specified, use heuristics\n                valuemap, values, coltype = guess_data_type(orig_values, namask)\n\n            if flag.m or coltype is StringVariable:\n                append_to = (Mcols, metas)\n            elif flag.w:\n                append_to = (Wcols, None)\n            elif flag.c:\n                append_to = (Ycols, clses)\n            else:\n                append_to = (Xcols, attrs)\n\n            cols, domain_vars = append_to\n\n            if domain_vars is not None:\n                var_name = names and names[col]\n                if not var_name:\n                    var_name = next(NAMEGEN)\n\n                values, var = sanitize_variable(\n                    valuemap, values, orig_values, coltype, coltype_kwargs,\n                    name=var_name)\n            else:\n                var = None\n            if domain_vars is not None:\n                var.attributes.update(flag.attributes)\n                domain_vars.append(var)\n\n            if isinstance(values, np.ndarray) and not values.flags.owndata:\n                values = values.copy()  # might view `data` (string columns)\n            cols.append(values)\n\n            try:\n                # allow gc to reclaim memory used by string values\n                data[:, col] = None\n            except IndexError:\n                pass\n\n        domain = Domain(attrs, clses, metas)\n\n        if not data.size:\n            return Table.from_domain(domain, 0)\n\n        X = Y = M = W = None\n        if Xcols:\n            X = np.c_[tuple(Xcols)]\n            assert X.dtype == np.float_\n        else:\n            X = np.empty((data.shape[0], 0), dtype=np.float_)\n        if Ycols:\n            Y = np.c_[tuple(Ycols)]\n            assert Y.dtype == np.float_\n        if Mcols:\n            M = np.c_[tuple(Mcols)].astype(object)\n        if Wcols:\n            W = np.c_[tuple(Wcols)].astype(float)\n\n        table = Table.from_numpy(domain, X, Y, M, W)\n        return table\n\n    @staticmethod\n    def header_names(data):\n        return ['weights'] * data.has_weights() + \\\n               [v.name for v in chain(data.domain.attributes,\n                                      data.domain.class_vars,\n                                      data.domain.metas)]\n\n    @staticmethod\n    def header_types(data):\n        def _vartype(var):\n            if var.is_continuous or var.is_string:\n                return var.TYPE_HEADERS[0]\n            elif var.is_discrete:\n                return Flags.join(var.values) if var.ordered else var.TYPE_HEADERS[0]\n            raise NotImplementedError\n        return ['continuous'] * data.has_weights() + \\\n               [_vartype(v) for v in chain(data.domain.attributes,\n                                           data.domain.class_vars,\n                                           data.domain.metas)]\n\n    @staticmethod\n    def header_flags(data):\n        return list(chain(['weight'] * data.has_weights(),\n                          (Flags.join([flag], *('{}={}'.format(*a)\n                                                for a in sorted(var.attributes.items())))\n                           for flag, var in chain(zip(repeat(''), data.domain.attributes),\n                                                  zip(repeat('class'), data.domain.class_vars),\n                                                  zip(repeat('meta'), data.domain.metas)))))\n\n    @classmethod\n    def write_headers(cls, write, data, with_annotations=True):\n        \"\"\"`write` is a callback that accepts an iterable\"\"\"\n        write(cls.header_names(data))\n        if with_annotations:\n            write(cls.header_types(data))\n            write(cls.header_flags(data))\n\n    @classmethod\n    def formatter(cls, var):\n        # type: (Variable) -> Callable[[Variable], Any]\n        # Return a column 'formatter' function. The function must return\n        # something that `write` knows how to write\n        if var.is_time:\n            return var.repr_val\n        elif var.is_continuous:\n            return lambda value: \"\" if isnan(value) else value\n        elif var.is_discrete:\n            return lambda value: \"\" if isnan(value) else var.values[int(value)]\n        elif var.is_string:\n            return lambda value: value\n        else:\n            return var.repr_val\n\n    @classmethod\n    def write_data(cls, write, data):\n        \"\"\"`write` is a callback that accepts an iterable\"\"\"\n        vars = list(chain((ContinuousVariable('_w'),) if data.has_weights() else (),\n                          data.domain.attributes,\n                          data.domain.class_vars,\n                          data.domain.metas))\n\n        formatters = [cls.formatter(v) for v in vars]\n        for row in zip(data.W if data.W.ndim > 1 else data.W[:, np.newaxis],\n                       data.X,\n                       data.Y if data.Y.ndim > 1 else data.Y[:, np.newaxis],\n                       data.metas):\n            write([fmt(v) for fmt, v in zip(formatters, flatten(row))])\n\n    @classmethod\n    def qualified_name(cls):\n        return cls.__module__ + '.' + cls.__name__\n\n\ndef class_from_qualified_name(format_name):\n    \"\"\" File format class from qualified name. \"\"\"\n    elements = format_name.split(\".\")\n    m = import_module(\".\".join(elements[:-1]))\n    return getattr(m, elements[-1])\n\n\nclass CSVReader(FileFormat):\n    \"\"\"Reader for comma separated files\"\"\"\n\n    EXTENSIONS = ('.csv',)\n    DESCRIPTION = 'Comma-separated values'\n    DELIMITERS = ',;:\\t$ '\n    SUPPORT_COMPRESSED = True\n    SUPPORT_SPARSE_DATA = False\n    PRIORITY = 20\n    OPTIONAL_TYPE_ANNOTATIONS = True\n\n    def read(self):\n        for encoding in (lambda: ('us-ascii', None),                 # fast\n                         lambda: (detect_encoding(self.filename), None),  # precise\n                         lambda: (locale.getpreferredencoding(False), None),\n                         lambda: (sys.getdefaultencoding(), None),   # desperate\n                         lambda: ('utf-8', None),                    # ...\n                         lambda: ('utf-8', 'ignore')):               # fallback\n            encoding, errors = encoding()\n            # Clear the error flag for all except the last check, because\n            # the error of second-to-last check is stored and shown as warning in owfile\n            if errors != 'ignore':\n                error = ''\n            with self.open(self.filename, mode='rt', newline='',\n                           encoding=encoding, errors=errors) as file:\n                # Sniff the CSV dialect (delimiter, quotes, ...)\n                try:\n                    dialect = csv.Sniffer().sniff(\n                        # Take first couple of *complete* lines as sample\n                        ''.join(file.readline() for _ in range(10)),\n                        self.DELIMITERS)\n                    delimiter = dialect.delimiter\n                    quotechar = dialect.quotechar\n                except UnicodeDecodeError as e:\n                    error = e\n                    continue\n                except csv.Error:\n                    delimiter = self.DELIMITERS[0]\n                    quotechar = csv.excel.quotechar\n\n                file.seek(0)\n                try:\n                    reader = csv.reader(\n                        file, delimiter=delimiter, quotechar=quotechar,\n                        skipinitialspace=True,\n                    )\n                    data = self.data_table(reader)\n\n                    # TODO: Name can be set unconditionally when/if\n                    # self.filename will always be a string with the file name.\n                    # Currently, some tests pass StringIO instead of\n                    # the file name to a reader.\n                    if isinstance(self.filename, str):\n                        data.name = path.splitext(\n                            path.split(self.filename)[-1])[0]\n                    if error and isinstance(error, UnicodeDecodeError):\n                        pos, endpos = error.args[2], error.args[3]\n                        warning = ('Skipped invalid byte(s) in position '\n                                   '{}{}').format(pos,\n                                                  ('-' + str(endpos)) if (endpos - pos) > 1 else '')\n                        warnings.warn(warning)\n                    self.set_table_metadata(self.filename, data)\n                    return data\n                except Exception as e:\n                    error = e\n                    continue\n        raise ValueError('Cannot parse dataset {}: {}'.format(self.filename, error)) from error\n\n    @classmethod\n    def write_file(cls, filename, data, with_annotations=True):\n        with cls.open(filename, mode='wt', newline='', encoding='utf-8') as file:\n            writer = csv.writer(file, delimiter=cls.DELIMITERS[0])\n            cls.write_headers(writer.writerow, data, with_annotations)\n            cls.write_data(writer.writerow, data)\n            cls.write_table_metadata(filename, data)\n\n\nclass TabReader(CSVReader):\n    \"\"\"Reader for tab separated files\"\"\"\n    EXTENSIONS = ('.tab', '.tsv')\n    DESCRIPTION = 'Tab-separated values'\n    DELIMITERS = '\\t'\n    PRIORITY = 10\n\n\nclass PickleReader(FileFormat):\n    \"\"\"Reader for pickled Table objects\"\"\"\n    EXTENSIONS = ('.pkl', '.pickle')\n    DESCRIPTION = 'Pickled Orange data'\n    SUPPORT_COMPRESSED = True\n    SUPPORT_SPARSE_DATA = True\n\n    def read(self):\n        with self.open(self.filename, 'rb') as f:\n            table = pickle.load(f)\n            if not isinstance(table, Table):\n                raise TypeError(\"file does not contain a data table\")\n            else:\n                return table\n\n    @classmethod\n    def write_file(cls, filename, data):\n        with cls.open(filename, 'wb') as f:\n            pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)\n\n\nclass BasketReader(FileFormat):\n    \"\"\"Reader for basket (sparse) files\"\"\"\n    EXTENSIONS = ('.basket', '.bsk')\n    DESCRIPTION = 'Basket file'\n    SUPPORT_SPARSE_DATA = True\n\n    def read(self):\n        def constr_vars(inds):\n            if inds:\n                return [ContinuousVariable(x.decode(\"utf-8\")) for _, x in\n                        sorted((ind, name) for name, ind in inds.items())]\n\n        X, Y, metas, attr_indices, class_indices, meta_indices = \\\n            _io.sparse_read_float(self.filename.encode(sys.getdefaultencoding()))\n\n        attrs = constr_vars(attr_indices)\n        classes = constr_vars(class_indices)\n        meta_attrs = constr_vars(meta_indices)\n        domain = Domain(attrs, classes, meta_attrs)\n        table = Table.from_numpy(\n            domain, attrs and X, classes and Y, metas and meta_attrs)\n        table.name = path.splitext(path.split(self.filename)[-1])[0]\n        return table\n\n\nclass ExcelReader(FileFormat):\n    \"\"\"Reader for excel files\"\"\"\n    EXTENSIONS = ('.xlsx',)\n    DESCRIPTION = 'Microsoft Excel spreadsheet'\n    SUPPORT_COMPRESSED = False\n    SUPPORT_SPARSE_DATA = False\n\n    def __init__(self, filename):\n        super().__init__(filename=filename)\n        self._workbook = None\n\n    @property\n    def workbook(self):\n        if not self._workbook:\n            self._workbook = xlrd.open_workbook(self.filename, on_demand=True)\n        return self._workbook\n\n    @property\n    @lru_cache(1)\n    def sheets(self):\n        if self.workbook:\n            return self.workbook.sheet_names()\n        else:\n            return ()\n\n    def read(self):\n        if self.sheet:\n            ss = self.workbook.sheet_by_name(self.sheet)\n        else:\n            ss = self.workbook.sheet_by_index(0)\n        try:\n            first_row = next(i for i in range(ss.nrows) if any(ss.row_values(i)))\n            first_col = next(i for i in range(ss.ncols) if ss.cell_value(first_row, i))\n            row_len = ss.row_len(first_row)\n            cells = filter(any,\n                           [[str(ss.cell_value(row, col)) if col < ss.row_len(row) else ''\n                             for col in range(first_col, row_len)]\n                            for row in range(first_row, ss.nrows)])\n            table = self.data_table(cells)\n            table.name = path.splitext(path.split(self.filename)[-1])[0]\n            if self.sheet:\n                table.name = '-'.join((table.name, self.sheet))\n        except Exception:\n            raise IOError(\"Couldn't load spreadsheet from \" + self.filename)\n        return table\n\n    @classmethod\n    def write_file(cls, filename, data):\n        vars = list(chain((ContinuousVariable('_w'),) if data.has_weights() else (),\n                          data.domain.attributes,\n                          data.domain.class_vars,\n                          data.domain.metas))\n        formatters = [cls.formatter(v) for v in vars]\n        zipped_list_data = zip(data.W if data.W.ndim > 1 else data.W[:, np.newaxis],\n                               data.X,\n                               data.Y if data.Y.ndim > 1 else data.Y[:, np.newaxis],\n                               data.metas)\n        headers = cls.header_names(data)\n        workbook = xlsxwriter.Workbook(filename)\n        sheet = workbook.add_worksheet()\n        for c, header in enumerate(headers):\n            sheet.write(0, c, header)\n        for i, row in enumerate(zipped_list_data, 1):\n            for j, (fmt, v) in enumerate(zip(formatters, flatten(row))):\n                sheet.write(i, j, fmt(v))\n        workbook.close()\n\n\nclass DotReader(FileFormat):\n    \"\"\"Writer for dot (graph) files\"\"\"\n    EXTENSIONS = ('.dot', '.gv')\n    DESCRIPTION = 'Dot graph description'\n    SUPPORT_COMPRESSED = True\n    SUPPORT_SPARSE_DATA = False\n\n    @classmethod\n    def write_graph(cls, filename, graph):\n        from sklearn import tree\n        tree.export_graphviz(graph, out_file=cls.open(filename, 'wt'))\n\n    @classmethod\n    def write(cls, filename, tree):\n        if type(tree) == dict:\n            tree = tree['tree']\n        cls.write_graph(filename, tree)\n\n\nclass UrlReader(FileFormat):\n    def __init__(self, filename):\n        filename = filename.strip()\n        if not urlparse(filename).scheme:\n            filename = 'http://' + filename\n        super().__init__(filename)\n\n    @staticmethod\n    def urlopen(url):\n        req = Request(\n            url,\n            # Avoid 403 error with servers that dislike scrapers\n            headers={'User-Agent': 'Mozilla/5.0 (X11; Linux) Gecko/20100101 Firefox/'})\n        return urlopen(req, timeout=10)\n\n    def read(self):\n        self.filename = self._trim(self._resolve_redirects(self.filename))\n        with contextlib.closing(self.urlopen(self.filename)) as response:\n            name = self._suggest_filename(response.headers['content-disposition'])\n            with NamedTemporaryFile(suffix=name, delete=False) as f:\n                f.write(response.read())\n                # delete=False is a workaround for https://bugs.python.org/issue14243\n\n            reader = self.get_reader(f.name)\n            data = reader.read()\n            remove(f.name)\n        # Override name set in from_file() to avoid holding the temp prefix\n        data.name = path.splitext(name)[0]\n        data.origin = self.filename\n        return data\n\n    def _resolve_redirects(self, url):\n        # Resolve (potential) redirects to a final URL\n        with contextlib.closing(self.urlopen(url)) as response:\n            return response.url\n\n    @classmethod\n    def _trim(cls, url):\n        URL_TRIMMERS = (\n            cls._trim_googlesheet,\n            cls._trim_dropbox,\n        )\n        for trim in URL_TRIMMERS:\n            try:\n                url = trim(url)\n            except ValueError:\n                continue\n            else:\n                break\n        return url\n\n    @staticmethod\n    def _trim_googlesheet(url):\n        match = re.match(r'(?:https?://)?(?:www\\.)?'\n                         r'docs\\.google\\.com/spreadsheets/d/'\n                         r'(?P<workbook_id>[-\\w_]+)'\n                         r'(?:/.*?gid=(?P<sheet_id>\\d+).*|.*)?',\n                         url, re.IGNORECASE)\n        try:\n            workbook, sheet = match.group('workbook_id'), match.group('sheet_id')\n            if not workbook:\n                raise ValueError\n        except (AttributeError, ValueError):\n            raise ValueError\n        url = 'https://docs.google.com/spreadsheets/d/{}/export?format=tsv'.format(workbook)\n        if sheet:\n            url += '&gid=' + sheet\n        return url\n\n    @staticmethod\n    def _trim_dropbox(url):\n        parts = urlsplit(url)\n        if not parts.netloc.endswith('dropbox.com'):\n            raise ValueError\n        return urlunsplit(parts._replace(query='dl=1'))\n\n    def _suggest_filename(self, content_disposition):\n        default_name = re.sub(r'[\\\\:/]', '_', urlparse(self.filename).path)\n\n        # See https://tools.ietf.org/html/rfc6266#section-4.1\n        matches = re.findall(r\"filename\\*?=(?:\\\"|.{0,10}?'[^']*')([^\\\"]+)\",\n                             content_disposition or '')\n        return urlunquote(matches[-1]) if matches else default_name\n",
            "header": {
                "file": {
                    "path": "Orange/data/io.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "15d0ca0aa",
                "new_commit_hash": "dc1606379",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/data/io.py"
            },
            "plus_file": {
                "path": "b/Orange/data/io.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 145,
                            "length": 30
                        },
                        "new_range": {
                            "start": 145,
                            "length": 39
                        },
                        "section": "def guess_data_type(orig_values, namask=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    \"\"\"",
                                "type": "context"
                            },
                            {
                                "content": "    valuemap, values = None, orig_values",
                                "type": "context"
                            },
                            {
                                "content": "    is_discrete = is_discrete_values(orig_values)",
                                "type": "context"
                            },
                            {
                                "content": "    orig_values = np.asarray(orig_values, dtype=str)",
                                "type": "added"
                            },
                            {
                                "content": "    if namask is None:",
                                "type": "added"
                            },
                            {
                                "content": "        namask = isnastr(orig_values)",
                                "type": "added"
                            },
                            {
                                "content": "    if is_discrete:",
                                "type": "context"
                            },
                            {
                                "content": "        valuemap = sorted(is_discrete)",
                                "type": "context"
                            },
                            {
                                "content": "        coltype = DiscreteVariable",
                                "type": "context"
                            },
                            {
                                "content": "    else:",
                                "type": "context"
                            },
                            {
                                "content": "        # try to parse as float",
                                "type": "context"
                            },
                            {
                                "content": "        orig_values = np.asarray(orig_values)",
                                "type": "deleted"
                            },
                            {
                                "content": "        if namask is None:",
                                "type": "deleted"
                            },
                            {
                                "content": "            namask = isnastr(orig_values)",
                                "type": "deleted"
                            },
                            {
                                "content": "        values = np.empty_like(orig_values, dtype=float)",
                                "type": "context"
                            },
                            {
                                "content": "        values[namask] = np.nan",
                                "type": "context"
                            },
                            {
                                "content": "        try:",
                                "type": "context"
                            },
                            {
                                "content": "            np.copyto(values, orig_values, where=~namask, casting=\"unsafe\")",
                                "type": "context"
                            },
                            {
                                "content": "        except ValueError:",
                                "type": "context"
                            },
                            {
                                "content": "            tvar = TimeVariable('_')",
                                "type": "deleted"
                            },
                            {
                                "content": "            try:",
                                "type": "deleted"
                            },
                            {
                                "content": "                values[~namask] = [tvar.parse(i) for i in orig_values[~namask]]",
                                "type": "deleted"
                            },
                            {
                                "content": "            except ValueError:",
                                "type": "deleted"
                            },
                            {
                                "content": "                coltype = StringVariable",
                                "type": "deleted"
                            },
                            {
                                "content": "                # return original_values",
                                "type": "deleted"
                            },
                            {
                                "content": "                values = orig_values",
                                "type": "deleted"
                            },
                            {
                                "content": "            else:",
                                "type": "deleted"
                            },
                            {
                                "content": "                coltype = TimeVariable",
                                "type": "deleted"
                            },
                            {
                                "content": "            values = orig_values",
                                "type": "added"
                            },
                            {
                                "content": "            coltype = StringVariable",
                                "type": "added"
                            },
                            {
                                "content": "        else:",
                                "type": "context"
                            },
                            {
                                "content": "            coltype = ContinuousVariable",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    if coltype is not ContinuousVariable:",
                                "type": "added"
                            },
                            {
                                "content": "        # when not continuous variable it can still be time variable even it",
                                "type": "added"
                            },
                            {
                                "content": "        # was before recognized as a discrete",
                                "type": "added"
                            },
                            {
                                "content": "        tvar = TimeVariable('_')",
                                "type": "added"
                            },
                            {
                                "content": "        # introducing new variable prevent overwriting orig_values and values",
                                "type": "added"
                            },
                            {
                                "content": "        temp_values = np.empty_like(orig_values, dtype=float)",
                                "type": "added"
                            },
                            {
                                "content": "        try:",
                                "type": "added"
                            },
                            {
                                "content": "            temp_values[~namask] = [",
                                "type": "added"
                            },
                            {
                                "content": "                tvar.parse_exact_iso(i) for i in orig_values[~namask]]",
                                "type": "added"
                            },
                            {
                                "content": "        except ValueError:",
                                "type": "added"
                            },
                            {
                                "content": "            pass",
                                "type": "added"
                            },
                            {
                                "content": "        else:",
                                "type": "added"
                            },
                            {
                                "content": "            valuemap = None",
                                "type": "added"
                            },
                            {
                                "content": "            coltype = TimeVariable",
                                "type": "added"
                            },
                            {
                                "content": "            values = temp_values",
                                "type": "added"
                            },
                            {
                                "content": "    return valuemap, values, coltype",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/data/io.py",
                            "type": "function",
                            "name": "guess_data_type",
                            "content": "def guess_data_type(orig_values, namask=None):\n    \"\"\"\n    Use heuristics to guess data type.\n    \"\"\"\n    valuemap, values = None, orig_values\n    is_discrete = is_discrete_values(orig_values)\n    if is_discrete:\n        valuemap = sorted(is_discrete)\n        coltype = DiscreteVariable\n    else:\n        # try to parse as float\n        orig_values = np.asarray(orig_values)\n        if namask is None:\n            namask = isnastr(orig_values)\n        values = np.empty_like(orig_values, dtype=float)\n        values[namask] = np.nan\n        try:\n            np.copyto(values, orig_values, where=~namask, casting=\"unsafe\")\n        except ValueError:\n            tvar = TimeVariable('_')\n            try:\n                values[~namask] = [tvar.parse(i) for i in orig_values[~namask]]\n            except ValueError:\n                coltype = StringVariable\n                # return original_values\n                values = orig_values\n            else:\n                coltype = TimeVariable\n        else:\n            coltype = ContinuousVariable\n    return valuemap, values, coltype",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 142,
                            "end_lineno": 172,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "import unittest\nimport numpy as np\n\nfrom Orange.data import ContinuousVariable, DiscreteVariable, StringVariable\nfrom Orange.data.io import guess_data_type\n\n\nclass TestTableFilters(unittest.TestCase):\n\n    def test_guess_data_type_continuous(self):\n        # should be ContinuousVariable\n        valuemap, values, coltype = guess_data_type(list(range(1, 100)))\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(np.array(list(range(1, 100))), values)\n\n        valuemap, values, coltype = guess_data_type([1, 2, 3, 1, 2, 3])\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal([1, 2, 3, 1, 2, 3], values)\n\n        valuemap, values, coltype = guess_data_type(\n            [\"1\", \"2\", \"3\", \"1\", \"2\", \"3\"])\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal([1, 2, 3, 1, 2, 3], values)\n\n    def test_guess_data_type_discrete(self):\n        # should be DiscreteVariable\n        valuemap, values, coltype = guess_data_type([1, 2, 1, 2])\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual([1, 2], valuemap)\n        np.testing.assert_array_equal([1, 2, 1, 2], values)\n\n        valuemap, values, coltype = guess_data_type([\"1\", \"2\", \"1\", \"2\", \"a\"])\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual([\"1\", \"2\", \"a\"], valuemap)\n        np.testing.assert_array_equal(['1', '2', '1', '2', 'a'], values)\n\n        # just below the threshold for string variable\n        in_values = list(map(lambda x: str(x) + \"a\", range(24))) + [\"a\"] * 76\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual(sorted(set(in_values)), valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n    def test_guess_data_type_string(self):\n        # should be StringVariable\n        # too many different values for discrete\n        in_values = list(map(lambda x: str(x) + \"a\", range(90)))\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n        # more than len(values)**0.7\n        in_values = list(map(lambda x: str(x) + \"a\", range(25))) + [\"a\"] * 75\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n        # more than 100 different values - exactly 101\n        # this is the case when len(values)**0.7 rule would vote for the\n        # DiscreteVariable\n        in_values = list(map(lambda x: str(x) + \"a\", range(100))) + [\"a\"] * 999\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n",
            "new_file_content": "import unittest\nimport numpy as np\n\nfrom Orange.data import ContinuousVariable, DiscreteVariable, StringVariable, \\\n    TimeVariable\nfrom Orange.data.io import guess_data_type\n\n\nclass TestTableFilters(unittest.TestCase):\n\n    def test_guess_data_type_continuous(self):\n        # should be ContinuousVariable\n        valuemap, values, coltype = guess_data_type(list(range(1, 100)))\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(np.array(list(range(1, 100))), values)\n\n        valuemap, values, coltype = guess_data_type([1, 2, 3, 1, 2, 3])\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal([1, 2, 3, 1, 2, 3], values)\n\n        valuemap, values, coltype = guess_data_type(\n            [\"1\", \"2\", \"3\", \"1\", \"2\", \"3\"])\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal([1, 2, 3, 1, 2, 3], values)\n\n    def test_guess_data_type_discrete(self):\n        # should be DiscreteVariable\n        valuemap, values, coltype = guess_data_type([1, 2, 1, 2])\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual([1, 2], valuemap)\n        np.testing.assert_array_equal([1, 2, 1, 2], values)\n\n        valuemap, values, coltype = guess_data_type([\"1\", \"2\", \"1\", \"2\", \"a\"])\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual([\"1\", \"2\", \"a\"], valuemap)\n        np.testing.assert_array_equal(['1', '2', '1', '2', 'a'], values)\n\n        # just below the threshold for string variable\n        in_values = list(map(lambda x: str(x) + \"a\", range(24))) + [\"a\"] * 76\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual(sorted(set(in_values)), valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n    def test_guess_data_type_string(self):\n        # should be StringVariable\n        # too many different values for discrete\n        in_values = list(map(lambda x: str(x) + \"a\", range(90)))\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n        # more than len(values)**0.7\n        in_values = list(map(lambda x: str(x) + \"a\", range(25))) + [\"a\"] * 75\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n        # more than 100 different values - exactly 101\n        # this is the case when len(values)**0.7 rule would vote for the\n        # DiscreteVariable\n        in_values = list(map(lambda x: str(x) + \"a\", range(100))) + [\"a\"] * 999\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n    def test_guess_data_type_time(self):\n        in_values = [\"2019-10-10\", \"2019-10-10\", \"2019-10-10\", \"2019-10-01\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10T12:08:51\", \"2019-10-10T12:08:51\",\n                     \"2019-10-10T12:08:51\", \"2019-10-01T12:08:51\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10 12:08:51\", \"2019-10-10 12:08:51\",\n                     \"2019-10-10 12:08:51\", \"2019-10-01 12:08:51\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10 12:08\", \"2019-10-10 12:08\",\n                     \"2019-10-10 12:08\", \"2019-10-01 12:08\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n",
            "header": {
                "file": {
                    "path": "Orange/data/tests/test_io.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "ad0c34281",
                "new_commit_hash": "acac8e87d",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/data/tests/test_io.py"
            },
            "plus_file": {
                "path": "b/Orange/data/tests/test_io.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1,
                            "length": 7
                        },
                        "new_range": {
                            "start": 1,
                            "length": 8
                        },
                        "section": ""
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "import unittest",
                                "type": "context"
                            },
                            {
                                "content": "import numpy as np",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "from Orange.data import ContinuousVariable, DiscreteVariable, StringVariable",
                                "type": "deleted"
                            },
                            {
                                "content": "from Orange.data import ContinuousVariable, DiscreteVariable, StringVariable, \\",
                                "type": "added"
                            },
                            {
                                "content": "    TimeVariable",
                                "type": "added"
                            },
                            {
                                "content": "from Orange.data.io import guess_data_type",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Orange/data/tests/test_io.py",
                            "type": "statement",
                            "name": "",
                            "content": "from Orange.data import ContinuousVariable, DiscreteVariable, StringVariable, \\\n    TimeVariable",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 4,
                            "end_lineno": 5,
                            "parent": null
                        }
                    ],
                    "deleted_entities": [
                        {
                            "file_name": "Orange/data/tests/test_io.py",
                            "type": "statement",
                            "name": "",
                            "content": "from Orange.data import ContinuousVariable, DiscreteVariable, StringVariable",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 4,
                            "end_lineno": 4,
                            "parent": null
                        }
                    ]
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 68,
                            "length": 3
                        },
                        "new_range": {
                            "start": 69,
                            "length": 27
                        },
                        "section": "class TestTableFilters(unittest.TestCase):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.assertEqual(StringVariable, coltype)",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertIsNone(valuemap)",
                                "type": "context"
                            },
                            {
                                "content": "        np.testing.assert_array_equal(in_values, values)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def test_guess_data_type_time(self):",
                                "type": "added"
                            },
                            {
                                "content": "        in_values = [\"2019-10-10\", \"2019-10-10\", \"2019-10-10\", \"2019-10-01\"]",
                                "type": "added"
                            },
                            {
                                "content": "        valuemap, _, coltype = guess_data_type(in_values)",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(TimeVariable, coltype)",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertIsNone(valuemap)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        in_values = [\"2019-10-10T12:08:51\", \"2019-10-10T12:08:51\",",
                                "type": "added"
                            },
                            {
                                "content": "                     \"2019-10-10T12:08:51\", \"2019-10-01T12:08:51\"]",
                                "type": "added"
                            },
                            {
                                "content": "        valuemap, _, coltype = guess_data_type(in_values)",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(TimeVariable, coltype)",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertIsNone(valuemap)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        in_values = [\"2019-10-10 12:08:51\", \"2019-10-10 12:08:51\",",
                                "type": "added"
                            },
                            {
                                "content": "                     \"2019-10-10 12:08:51\", \"2019-10-01 12:08:51\"]",
                                "type": "added"
                            },
                            {
                                "content": "        valuemap, _, coltype = guess_data_type(in_values)",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(TimeVariable, coltype)",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertIsNone(valuemap)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        in_values = [\"2019-10-10 12:08\", \"2019-10-10 12:08\",",
                                "type": "added"
                            },
                            {
                                "content": "                     \"2019-10-10 12:08\", \"2019-10-01 12:08\"]",
                                "type": "added"
                            },
                            {
                                "content": "        valuemap, _, coltype = guess_data_type(in_values)",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(TimeVariable, coltype)",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertIsNone(valuemap)",
                                "type": "added"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/data/tests/test_io.py",
                            "type": "class",
                            "name": "TestTableFilters",
                            "content": "class TestTableFilters(unittest.TestCase):\n\n    def test_guess_data_type_continuous(self):\n        # should be ContinuousVariable\n        valuemap, values, coltype = guess_data_type(list(range(1, 100)))\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(np.array(list(range(1, 100))), values)\n\n        valuemap, values, coltype = guess_data_type([1, 2, 3, 1, 2, 3])\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal([1, 2, 3, 1, 2, 3], values)\n\n        valuemap, values, coltype = guess_data_type(\n            [\"1\", \"2\", \"3\", \"1\", \"2\", \"3\"])\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal([1, 2, 3, 1, 2, 3], values)\n\n    def test_guess_data_type_discrete(self):\n        # should be DiscreteVariable\n        valuemap, values, coltype = guess_data_type([1, 2, 1, 2])\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual([1, 2], valuemap)\n        np.testing.assert_array_equal([1, 2, 1, 2], values)\n\n        valuemap, values, coltype = guess_data_type([\"1\", \"2\", \"1\", \"2\", \"a\"])\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual([\"1\", \"2\", \"a\"], valuemap)\n        np.testing.assert_array_equal(['1', '2', '1', '2', 'a'], values)\n\n        # just below the threshold for string variable\n        in_values = list(map(lambda x: str(x) + \"a\", range(24))) + [\"a\"] * 76\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual(sorted(set(in_values)), valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n    def test_guess_data_type_string(self):\n        # should be StringVariable\n        # too many different values for discrete\n        in_values = list(map(lambda x: str(x) + \"a\", range(90)))\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n        # more than len(values)**0.7\n        in_values = list(map(lambda x: str(x) + \"a\", range(25))) + [\"a\"] * 75\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n        # more than 100 different values - exactly 101\n        # this is the case when len(values)**0.7 rule would vote for the\n        # DiscreteVariable\n        in_values = list(map(lambda x: str(x) + \"a\", range(100))) + [\"a\"] * 999\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n    def test_guess_data_type_time(self):\n        in_values = [\"2019-10-10\", \"2019-10-10\", \"2019-10-10\", \"2019-10-01\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10T12:08:51\", \"2019-10-10T12:08:51\",\n                     \"2019-10-10T12:08:51\", \"2019-10-01T12:08:51\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10 12:08:51\", \"2019-10-10 12:08:51\",\n                     \"2019-10-10 12:08:51\", \"2019-10-01 12:08:51\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10 12:08\", \"2019-10-10 12:08\",\n                     \"2019-10-10 12:08\", \"2019-10-01 12:08\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 9,
                            "end_lineno": 95,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "Orange/data/tests/test_io.py",
                            "type": "method",
                            "name": "TestTableFilters.test_guess_data_type_time",
                            "content": "class TestTableFilters(unittest.TestCase):\n\n    def test_guess_data_type_continuous(self):\n        # should be ContinuousVariable\n        valuemap, values, coltype = guess_data_type(list(range(1, 100)))\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(np.array(list(range(1, 100))), values)\n\n        valuemap, values, coltype = guess_data_type([1, 2, 3, 1, 2, 3])\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal([1, 2, 3, 1, 2, 3], values)\n\n        valuemap, values, coltype = guess_data_type(\n            [\"1\", \"2\", \"3\", \"1\", \"2\", \"3\"])\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal([1, 2, 3, 1, 2, 3], values)\n\n    def test_guess_data_type_discrete(self):\n        # should be DiscreteVariable\n        valuemap, values, coltype = guess_data_type([1, 2, 1, 2])\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual([1, 2], valuemap)\n        np.testing.assert_array_equal([1, 2, 1, 2], values)\n\n        valuemap, values, coltype = guess_data_type([\"1\", \"2\", \"1\", \"2\", \"a\"])\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual([\"1\", \"2\", \"a\"], valuemap)\n        np.testing.assert_array_equal(['1', '2', '1', '2', 'a'], values)\n\n        # just below the threshold for string variable\n        in_values = list(map(lambda x: str(x) + \"a\", range(24))) + [\"a\"] * 76\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual(sorted(set(in_values)), valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n    def test_guess_data_type_string(self):\n        # should be StringVariable\n        # too many different values for discrete\n        in_values = list(map(lambda x: str(x) + \"a\", range(90)))\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n        # more than len(values)**0.7\n        in_values = list(map(lambda x: str(x) + \"a\", range(25))) + [\"a\"] * 75\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n        # more than 100 different values - exactly 101\n        # this is the case when len(values)**0.7 rule would vote for the\n        # DiscreteVariable\n        in_values = list(map(lambda x: str(x) + \"a\", range(100))) + [\"a\"] * 999\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n    def test_guess_data_type_time(self):\n        in_values = [\"2019-10-10\", \"2019-10-10\", \"2019-10-10\", \"2019-10-01\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10T12:08:51\", \"2019-10-10T12:08:51\",\n                     \"2019-10-10T12:08:51\", \"2019-10-01T12:08:51\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10 12:08:51\", \"2019-10-10 12:08:51\",\n                     \"2019-10-10 12:08:51\", \"2019-10-01 12:08:51\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10 12:08\", \"2019-10-10 12:08\",\n                     \"2019-10-10 12:08\", \"2019-10-01 12:08\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 73,
                            "end_lineno": 95,
                            "parent": {
                                "file_name": "Orange/data/tests/test_io.py",
                                "type": "class",
                                "name": "TestTableFilters",
                                "content": "class TestTableFilters(unittest.TestCase):\n\n    def test_guess_data_type_continuous(self):\n        # should be ContinuousVariable\n        valuemap, values, coltype = guess_data_type(list(range(1, 100)))\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(np.array(list(range(1, 100))), values)\n\n        valuemap, values, coltype = guess_data_type([1, 2, 3, 1, 2, 3])\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal([1, 2, 3, 1, 2, 3], values)\n\n        valuemap, values, coltype = guess_data_type(\n            [\"1\", \"2\", \"3\", \"1\", \"2\", \"3\"])\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal([1, 2, 3, 1, 2, 3], values)\n\n    def test_guess_data_type_discrete(self):\n        # should be DiscreteVariable\n        valuemap, values, coltype = guess_data_type([1, 2, 1, 2])\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual([1, 2], valuemap)\n        np.testing.assert_array_equal([1, 2, 1, 2], values)\n\n        valuemap, values, coltype = guess_data_type([\"1\", \"2\", \"1\", \"2\", \"a\"])\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual([\"1\", \"2\", \"a\"], valuemap)\n        np.testing.assert_array_equal(['1', '2', '1', '2', 'a'], values)\n\n        # just below the threshold for string variable\n        in_values = list(map(lambda x: str(x) + \"a\", range(24))) + [\"a\"] * 76\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual(sorted(set(in_values)), valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n    def test_guess_data_type_string(self):\n        # should be StringVariable\n        # too many different values for discrete\n        in_values = list(map(lambda x: str(x) + \"a\", range(90)))\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n        # more than len(values)**0.7\n        in_values = list(map(lambda x: str(x) + \"a\", range(25))) + [\"a\"] * 75\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n        # more than 100 different values - exactly 101\n        # this is the case when len(values)**0.7 rule would vote for the\n        # DiscreteVariable\n        in_values = list(map(lambda x: str(x) + \"a\", range(100))) + [\"a\"] * 999\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n    def test_guess_data_type_time(self):\n        in_values = [\"2019-10-10\", \"2019-10-10\", \"2019-10-10\", \"2019-10-01\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10T12:08:51\", \"2019-10-10T12:08:51\",\n                     \"2019-10-10T12:08:51\", \"2019-10-01T12:08:51\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10 12:08:51\", \"2019-10-10 12:08:51\",\n                     \"2019-10-10 12:08:51\", \"2019-10-01 12:08:51\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10 12:08\", \"2019-10-10 12:08\",\n                     \"2019-10-10 12:08\", \"2019-10-01 12:08\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 9,
                                "end_lineno": 95,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "import re\nimport warnings\nfrom collections import Iterable\n\nfrom datetime import datetime, timedelta, timezone\nfrom numbers import Number, Real, Integral\nfrom math import isnan, floor\nfrom pickle import PickleError\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom Orange.data import _variable\nfrom Orange.util import Registry, hex_to_color, Reprable,\\\n    OrangeDeprecationWarning\n\n__all__ = [\"Unknown\", \"MISSING_VALUES\", \"make_variable\", \"is_discrete_values\",\n           \"Value\", \"Variable\", \"ContinuousVariable\", \"DiscreteVariable\",\n           \"StringVariable\", \"TimeVariable\"]\n\n\n# For storing unknowns\nUnknown = ValueUnknown = float(\"nan\")\n# For checking for unknowns\nMISSING_VALUES = {np.nan, \"?\", \"nan\", \".\", \"\", \"NA\", \"~\", None}\n\nDISCRETE_MAX_VALUES = 3  # == 2 + nan\nMAX_NUM_OF_DECIMALS = 5\n# the variable with more than 100 different values should not be StringVariable\nDISCRETE_MAX_ALLOWED_VALUES = 100\n\n\ndef make_variable(cls, compute_value, *args):\n    if compute_value is not None:\n        return cls(*args, compute_value=compute_value)\n    else:\n        # For compatibility with old pickles\n        return cls(*args)\n\n\ndef is_discrete_values(values):\n    \"\"\"\n    Return set of uniques if `values` is an iterable of discrete values\n    else False if non-discrete, or None if indeterminate.\n\n    Note\n    ----\n    Assumes consistent type of items of `values`.\n    \"\"\"\n    if not len(values):\n        return None\n    # If the first few values are, or can be converted to, floats,\n    # the type is numeric\n    try:\n        isinstance(next(iter(values)), Number) or \\\n        [float(v) for _, v in zip(range(min(3, len(values))), values)]\n    except ValueError:\n        is_numeric = False\n        max_values = int(round(len(values)**.7))\n    else:\n        is_numeric = True\n        max_values = DISCRETE_MAX_VALUES\n\n    # If more than max values => not discrete\n    unique = set()\n    for i in values:\n        unique.add(i)\n        if (len(unique) > max_values or\n                len(unique) > DISCRETE_MAX_ALLOWED_VALUES):\n            return False\n\n    # Strip NaN from unique\n    unique = {i for i in unique\n              if (not i in MISSING_VALUES and\n                  not (isinstance(i, Number) and np.isnan(i)))}\n\n    # All NaNs => indeterminate\n    if not unique:\n        return None\n\n    # Strings with |values| < max_unique\n    if not is_numeric:\n        return unique\n\n    # Handle numbers\n    try:\n        unique_float = set(map(float, unique))\n    except ValueError:\n        # Converting all the values to floats resulted in an error.\n        # Since the values have enough unique values, they are probably\n        # string values and discrete.\n        return unique\n\n    # If only values are {0, 1} or {1, 2} (or a subset of those sets) => discrete\n    return (not (unique_float - {0, 1}) or\n            not (unique_float - {1, 2})) and unique\n\n\nclass Value(float):\n    \"\"\"\n    The class representing a value. The class is not used to store values but\n    only to return them in contexts in which we want the value to be accompanied\n    with the descriptor, for instance to print the symbolic value of discrete\n    variables.\n\n    The class is derived from `float`, with an additional attribute `variable`\n    which holds the descriptor of type :obj:`Orange.data.Variable`. If the\n    value continuous or discrete, it is stored as a float. Other types of\n    values, like strings, are stored in the attribute `value`.\n\n    The class overloads the methods for printing out the value:\n    `variable.repr_val` and `variable.str_val` are used to get a suitable\n    representation of the value.\n\n    Equivalence operator is overloaded as follows:\n\n    - unknown values are equal; if one value is unknown and the other is not,\n      they are different;\n\n    - if the value is compared with the string, the value is converted to a\n      string using `variable.str_val` and the two strings are compared\n\n    - if the value is stored in attribute `value`, it is compared with the\n      given other value\n\n    - otherwise, the inherited comparison operator for `float` is called.\n\n    Finally, value defines a hash, so values can be put in sets and appear as\n    keys in dictionaries.\n\n    .. attribute:: variable (:obj:`Orange.data.Variable`)\n\n        Descriptor; used for printing out and for comparing with strings\n\n    .. attribute:: value\n\n        Value; the value can be of arbitrary type and is used only for variables\n        that are neither discrete nor continuous. If `value` is `None`, the\n        derived `float` value is used.\n    \"\"\"\n    __slots__ = \"variable\", \"_value\"\n\n    def __new__(cls, variable, value=Unknown):\n        \"\"\"\n        Construct a new instance of Value with the given descriptor and value.\n        If the argument `value` can be converted to float, it is stored as\n        `float` and the attribute `value` is set to `None`. Otherwise, the\n        inherited float is set to `Unknown` and the value is held by the\n        attribute `value`.\n\n        :param variable: descriptor\n        :type variable: Orange.data.Variable\n        :param value: value\n        \"\"\"\n        if variable.is_primitive():\n            self = super().__new__(cls, value)\n            self.variable = variable\n            self._value = None\n        else:\n            isunknown = value == variable.Unknown\n            self = super().__new__(\n                cls, np.nan if isunknown else np.finfo(float).min)\n            self.variable = variable\n            self._value = value\n        return self\n\n    def __init__(self, _, __=Unknown):\n        pass\n\n    def __repr__(self):\n        return \"Value('%s', %s)\" % (self.variable.name,\n                                    self.variable.repr_val(self))\n\n    def __str__(self):\n        return self.variable.str_val(self)\n\n    def __eq__(self, other):\n        if isinstance(self, Real) and isnan(self):\n            return (isinstance(other, Real) and isnan(other)\n                    or other in self.variable.unknown_str)\n        if isinstance(other, str):\n            return self.variable.str_val(self) == other\n        if isinstance(other, Value):\n            return self.value == other.value\n        return super().__eq__(other)\n\n    def __ne__(self, other):\n        return not self.__eq__(other)\n\n    def __lt__(self, other):\n        if self.variable.is_primitive():\n            if isinstance(other, str):\n                return super().__lt__(self.variable.to_val(other))\n            else:\n                return super().__lt__(other)\n        else:\n            if isinstance(other, str):\n                return self.value < other\n            else:\n                return self.value < other.value\n\n    def __le__(self, other):\n        return self.__lt__(other) or self.__eq__(other)\n\n    def __gt__(self, other):\n        return not self.__le__(other)\n\n    def __ge__(self, other):\n        return not self.__lt__(other)\n\n    def __contains__(self, other):\n        if (self._value is not None\n                and isinstance(self._value, str)\n                and isinstance(other, str)):\n            return other in self._value\n        raise TypeError(\"invalid operation on Value()\")\n\n    def __hash__(self):\n        if self.variable.is_discrete:\n            # It is not possible to hash the id and the domain value to the\n            # same number as required by __eq__.\n            # hash(1) == hash(Value(DiscreteVariable(\"var\", [\"red\", \"green\", \"blue\"]), 1)) == hash(\"green\")\n            # User should hash directly ids or domain values instead.\n            raise TypeError(\"unhashable type - cannot hash values of discrete variables!\")\n        if self._value is None:\n            return super().__hash__()\n        else:\n            return hash(self._value)\n\n    @property\n    def value(self):\n        if self.variable.is_discrete:\n            return Unknown if isnan(self) else self.variable.values[int(self)]\n        if self.variable.is_string:\n            return self._value\n        return float(self)\n\n    def __getnewargs__(self):\n        return self.variable, float(self)\n\n    def __getstate__(self):\n        return dict(value=getattr(self, '_value', None))\n\n    def __setstate__(self, state):\n        self._value = state.get('value', None)\n\n\nclass VariableMeta(Registry):\n    pass\n\n\nclass _predicatedescriptor(property):\n    \"\"\"\n    A property that behaves as a class method if accessed via a class\n    >>> class A:\n    ...     foo = False\n    ...     @_predicatedescriptor\n    ...     def is_foo(self):\n    ...         return self.foo\n    ...\n    >>> a = A()\n    >>> a.is_foo\n    False\n    >>> A.is_foo(a)\n    False\n    \"\"\"\n    def __get__(self, instance, objtype=None):\n        if instance is None:\n            return self.fget\n        else:\n            return super().__get__(instance, objtype)\n\n\nclass Variable(Reprable, metaclass=VariableMeta):\n    \"\"\"\n    The base class for variable descriptors contains the variable's\n    name and some basic properties.\n\n    .. attribute:: name\n\n        The name of the variable.\n\n    .. attribute:: unknown_str\n\n        A set of values that represent unknowns in conversion from textual\n        formats. Default is `{\"?\", \".\", \"\", \"NA\", \"~\", None}`.\n\n    .. attribute:: compute_value\n\n        A function for computing the variable's value when converting from\n        another domain which does not contain this variable. The function will\n        be called with a data set (`Orange.data.Table`) and has to return\n        an array of computed values for all its instances. The base class\n        defines a static method `compute_value`, which returns `Unknown`.\n        Non-primitive variables must redefine it to return `None`.\n\n    .. attribute:: sparse\n\n        A flag about sparsity of the variable. When set, the variable suggests\n        it should be stored in a sparse matrix.\n\n    .. attribute:: source_variable\n\n        An optional descriptor of the source variable - if any - from which\n        this variable is derived and computed via :obj:`compute_value`.\n\n    .. attribute:: attributes\n\n        A dictionary with user-defined attributes of the variable\n    \"\"\"\n    Unknown = ValueUnknown\n\n    def __init__(self, name=\"\", compute_value=None, *, sparse=False):\n        \"\"\"\n        Construct a variable descriptor.\n        \"\"\"\n        if not name:\n            warnings.warn(\"Variable must have a name\", OrangeDeprecationWarning,\n                          stacklevel=3)\n        self._name = name\n        self._compute_value = compute_value\n        self.unknown_str = MISSING_VALUES\n        self.source_variable = None\n        self.sparse = sparse\n        self.attributes = {}\n        self._colors = None\n\n    @property\n    def name(self):\n        return self._name\n\n    @property\n    def colors(self):  # unreachable; pragma: no cover\n        return self._colors\n\n    @colors.setter\n    def colors(self, value):\n        self._colors = value\n\n    def make_proxy(self):\n        \"\"\"\n        Copy the variable and set the master to `self.master` or to `self`.\n\n        :return: copy of self\n        :rtype: Variable\n        \"\"\"\n        var = self.__class__(self.name)\n        var.__dict__.update(self.__dict__)\n        var.attributes = dict(self.attributes)\n        return var\n\n    def __eq__(self, other):\n        return type(self) is type(other) \\\n               and self.name == other.name \\\n               and self._compute_value == other._compute_value\n\n    def __hash__(self):\n        return hash((self.name, type(self), self._compute_value))\n\n    @classmethod\n    def make(cls, name, *args, **kwargs):\n        \"\"\"\n        Return an existing continuous variable with the given name, or\n        construct and return a new one.\n        \"\"\"\n        return cls(name, *args, **kwargs)\n\n    @classmethod\n    def _clear_cache(cls):\n        warnings.warn(\n            \"_clear_cache is no longer needed and thus deprecated\")\n\n    @staticmethod\n    def _clear_all_caches():\n        warnings.warn(\n            \"_clear_all_caches is no longer needed and thus deprecated\")\n\n    @classmethod\n    def is_primitive(cls, var=None):\n        \"\"\"\n        `True` if the variable's values are stored as floats.\n        Non-primitive variables can appear in the data only as meta attributes.\n        \"\"\"\n        to_check = cls if var is None else type(var)\n        return issubclass(to_check, (DiscreteVariable, ContinuousVariable))\n\n    @_predicatedescriptor\n    def is_discrete(self):\n        return isinstance(self, DiscreteVariable)\n\n    @_predicatedescriptor\n    def is_continuous(self):\n        return isinstance(self, ContinuousVariable)\n\n    @_predicatedescriptor\n    def is_string(self):\n        return isinstance(self, StringVariable)\n\n    @_predicatedescriptor\n    def is_time(self):\n        return isinstance(self, TimeVariable)\n\n    def repr_val(self, val):\n        \"\"\"\n        Return a textual representation of variable's value `val`. Argument\n        `val` must be a float (for primitive variables) or an arbitrary\n        Python object (for non-primitives).\n\n        Derived classes must overload the function.\n        \"\"\"\n        raise RuntimeError(\"variable descriptors must overload repr_val()\")\n\n    str_val = repr_val\n\n    def to_val(self, s):\n        \"\"\"\n        Convert the given argument to a value of the variable. The\n        argument can be a string, a number or `None`. For primitive variables,\n        the base class provides a method that returns\n        :obj:`~Orange.data.Unknown` if `s` is found in\n        :obj:`~Orange.data.Variable.unknown_str`, and raises an exception\n        otherwise. For non-primitive variables it returns the argument itself.\n\n        Derived classes of primitive variables must overload the function.\n\n        :param s: value, represented as a number, string or `None`\n        :type s: str, float or None\n        :rtype: float or object\n        \"\"\"\n        if not self.is_primitive():\n            return s\n        if s in self.unknown_str:\n            return Unknown\n        raise RuntimeError(\n            \"primitive variable descriptors must overload to_val()\")\n\n    def val_from_str_add(self, s):\n        \"\"\"\n        Convert the given string to a value of the variable. The method\n        is similar to :obj:`to_val` except that it only accepts strings and\n        that it adds new values to the variable's domain where applicable.\n\n        The base class method calls `to_val`.\n\n        :param s: symbolic representation of the value\n        :type s: str\n        :rtype: float or object\n        \"\"\"\n        return self.to_val(s)\n\n    def __str__(self):\n        return self.name\n\n    @property\n    def compute_value(self):\n        return self._compute_value\n\n    def __reduce__(self):\n        if not self.name:\n            raise PickleError(\"Variables without names cannot be pickled\")\n\n        # Use make to unpickle variables.\n        return make_variable, (self.__class__, self._compute_value, self.name), self.__dict__\n\n    def copy(self, compute_value=None, *, name=None, **kwargs):\n        var = type(self)(name=name or self.name,\n                         compute_value=compute_value or self.compute_value,\n                         sparse=self.sparse, **kwargs)\n        var.attributes = dict(self.attributes)\n        return var\n\n\ndel _predicatedescriptor\n\n\nclass ContinuousVariable(Variable):\n    \"\"\"\n    Descriptor for continuous variables.\n\n    .. attribute:: number_of_decimals\n\n        The number of decimals when the value is printed out (default: 3).\n\n    .. attribute:: adjust_decimals\n\n        A flag regulating whether the `number_of_decimals` is being adjusted\n        by :obj:`to_val`.\n\n    The value of `number_of_decimals` is set to 3 and `adjust_decimals`\n    is set to 2. When :obj:`val_from_str_add` is called for the first\n    time with a string as an argument, `number_of_decimals` is set to the\n    number of decimals in the string and `adjust_decimals` is set to 1.\n    In the subsequent calls of `to_val`, the nubmer of decimals is\n    increased if the string argument has a larger number of decimals.\n\n    If the `number_of_decimals` is set manually, `adjust_decimals` is\n    set to 0 to prevent changes by `to_val`.\n    \"\"\"\n\n    TYPE_HEADERS = ('continuous', 'c', 'numeric', 'n')\n\n    def __init__(self, name=\"\", number_of_decimals=None, compute_value=None, *, sparse=False):\n        \"\"\"\n        Construct a new continuous variable. The number of decimals is set to\n        three, but adjusted at the first call of :obj:`to_val`.\n        \"\"\"\n        super().__init__(name, compute_value, sparse=sparse)\n        if number_of_decimals is None:\n            self._number_of_decimals = 3\n            self.adjust_decimals = 2\n            self._format_str = \"%g\"\n        else:\n            self.number_of_decimals = number_of_decimals\n\n    @property\n    def number_of_decimals(self):\n        return self._number_of_decimals\n\n    @property\n    def format_str(self):\n        return self._format_str\n\n    @format_str.setter\n    def format_str(self, value):\n        self._format_str = value\n\n    @Variable.colors.getter\n    def colors(self):\n        if self._colors is not None:\n            return self._colors\n        try:\n            col1, col2, black = self.attributes[\"colors\"]\n            return (hex_to_color(col1), hex_to_color(col2), black)\n        except (KeyError, ValueError):\n            # User-provided colors were not available or invalid\n            return ((0, 0, 255), (255, 255, 0), False)\n\n    # noinspection PyAttributeOutsideInit\n    @number_of_decimals.setter\n    def number_of_decimals(self, x):\n        self._number_of_decimals = x\n        self.adjust_decimals = 0\n        if self._number_of_decimals <= MAX_NUM_OF_DECIMALS:\n            self._format_str = \"%.{}f\".format(self.number_of_decimals)\n        else:\n            self._format_str = \"%g\"\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if s in self.unknown_str:\n            return Unknown\n        return float(s)\n\n    def val_from_str_add(self, s):\n        \"\"\"\n        Convert a value from a string and adjust the number of decimals if\n        `adjust_decimals` is non-zero.\n        \"\"\"\n        return _variable.val_from_str_add_cont(self, s)\n\n    def repr_val(self, val):\n        \"\"\"\n        Return the value as a string with the prescribed number of decimals.\n        \"\"\"\n        if isnan(val):\n            return \"?\"\n        return self._format_str % val\n\n    str_val = repr_val\n\n    def copy(self, compute_value=None, *, name=None, **kwargs):\n        var = super().copy(compute_value=compute_value, name=name,\n                           number_of_decimals=self.number_of_decimals,\n                           **kwargs)\n        var.adjust_decimals = self.adjust_decimals\n        var.format_str = self._format_str\n        return var\n\n\nclass DiscreteVariable(Variable):\n    \"\"\"\n    Descriptor for symbolic, discrete variables. Values of discrete variables\n    are stored as floats; the numbers corresponds to indices in the list of\n    values.\n\n    .. attribute:: values\n\n        A list of variable's values.\n\n    .. attribute:: ordered\n\n        Some algorithms (and, in particular, visualizations) may\n        sometime reorder the values of the variable, e.g. alphabetically.\n        This flag hints that the given order of values is \"natural\"\n        (e.g. \"small\", \"middle\", \"large\") and should not be changed.\n    \"\"\"\n\n    TYPE_HEADERS = ('discrete', 'd', 'categorical')\n\n    presorted_values = []\n\n    def __init__(self, name=\"\", values=(), ordered=False, compute_value=None,\n                 *, sparse=False):\n        \"\"\" Construct a discrete variable descriptor with the given values. \"\"\"\n        self.values = list(values)\n        if not all(isinstance(value, str) for value in self.values):\n            raise TypeError(\"values of DiscreteVariables must be strings\")\n        super().__init__(name, compute_value, sparse=sparse)\n        self.ordered = ordered\n\n    def get_mapping_from(self, other):\n        return np.array(\n            [self.values.index(value) if value in self.values else np.nan\n             for value in other.values], dtype=float)\n\n    def get_mapper_from(self, other):\n        mapping = self.get_mapping_from(other)\n        if not mapping.size:\n            # Nans in data are temporarily replaced with 0, mapped and changed\n            # back to nans. This would fail is mapping[0] is out of range.\n            mapping = np.array([np.nan])\n\n        def mapper(value, col_idx=None):\n\n            # In-place mapping\n            if col_idx is not None:\n                if sp.issparse(value) and mapping[0] != 0:\n                    raise ValueError(\n                        \"In-place mapping of sparse matrices must map 0 to 0\")\n\n                # CSR requires mapping of non-contiguous area\n                if sp.isspmatrix_csr(value):\n                    col = value.indices == col_idx\n                    nans = np.isnan(value.data) * col\n                    value.data[nans] = 0\n                    value.data[col] = mapping[value.data[col].astype(int)]\n                    value.data[nans] = np.nan\n                    return\n\n                # Dense and CSC map a contiguous area\n                if isinstance(value, np.ndarray) and value.ndim == 2:\n                    col = value[:, col_idx]\n                elif sp.isspmatrix_csc(value):\n                    col = value.data[value.indptr[col_idx]\n                                     :value.indptr[col_idx + 1]]\n                else:\n                    raise ValueError(\n                        \"In-place column mapping requires a 2d array or\"\n                        \"a csc or csr matrix.\")\n\n                nans = np.isnan(col)\n                col[nans] = 0\n                col[:] = mapping[col.astype(int)]\n                col[nans] = np.nan\n                return\n\n            # Mapping into a copy\n            if isinstance(value, (int, float)):\n                return mapping[int(value)] if value == value else value\n            if isinstance(value, str):\n                return mapping[other.values.index(value)]\n            if isinstance(value, np.ndarray):\n                if not (value.ndim == 1\n                        or value.ndim != 2 and min(value.shape) != 1):\n                    raise ValueError(\n                        f\"Column mapping can't map {value.ndim}-d objects\")\n\n                if value.dtype == object:\n                    value = value.astype(float)  # this happens with metas\n                try:\n                    nans = np.isnan(value)\n                except TypeError:  # suppose it's already an integer type\n                    return mapping[value]\n                value = value.astype(int)\n                value[nans] = 0\n                value = mapping[value]\n                value[nans] = np.nan\n                return value\n            if sp.issparse(value):\n                if min(value.shape) != 1:\n                    raise ValueError(\"Column mapping can't map \"\n                                     f\"{value.ndim}-dimensional objects\")\n                if mapping[0] != 0 and not np.isnan(mapping[0]):\n                    return mapper(np.array(value.todense()).flatten())\n                value = value.copy()\n                value.data = mapper(value.data)\n                return value\n            if isinstance(value, Iterable):\n                return type(value)(mapping[int(val)] if val == val else val\n                                   for val in value)\n            raise ValueError(\n                f\"invalid type for value(s): {type(value).__name__}\")\n\n        return mapper\n\n    @Variable.colors.getter\n    def colors(self):\n        if self._colors is not None:\n            colors = np.array(self._colors)\n        elif not self.values:\n            colors = np.zeros((0, 3))  # to match additional colors in vstacks\n        else:\n            from Orange.widgets.utils.colorpalette import ColorPaletteGenerator\n            default = tuple(ColorPaletteGenerator.palette(self))\n            colors = self.attributes.get('colors', ())\n            colors = tuple(hex_to_color(color) for color in colors) \\\n                    + default[len(colors):]\n            colors = np.array(colors)\n        colors.flags.writeable = False\n        return colors\n\n    def to_val(self, s):\n        \"\"\"\n        Convert the given argument to a value of the variable (`float`).\n        If the argument is numeric, its value is returned without checking\n        whether it is integer and within bounds. `Unknown` is returned if the\n        argument is one of the representations for unknown values. Otherwise,\n        the argument must be a string and the method returns its index in\n        :obj:`values`.\n\n        :param s: values, represented as a number, string or `None`\n        :rtype: float\n        \"\"\"\n        if s is None:\n            return ValueUnknown\n\n        if isinstance(s, Integral):\n            return s\n        if isinstance(s, Real):\n            return s if isnan(s) else floor(s + 0.25)\n        if s in self.unknown_str:\n            return ValueUnknown\n        if not isinstance(s, str):\n            raise TypeError('Cannot convert {} to value of \"{}\"'.format(\n                type(s).__name__, self.name))\n        return self.values.index(s)\n\n    def add_value(self, s):\n        \"\"\" Add a value `s` to the list of values.\n        \"\"\"\n        if not isinstance(s, str):\n            raise TypeError(\"values of DiscreteVariables must be strings\")\n        self.values.append(s)\n        self._colors = None\n\n    def val_from_str_add(self, s):\n        \"\"\"\n        Similar to :obj:`to_val`, except that it accepts only strings and that\n        it adds the value to the list if it does not exist yet.\n\n        :param s: symbolic representation of the value\n        :type s: str\n        :rtype: float\n        \"\"\"\n        s = str(s) if s is not None else s\n        try:\n            return ValueUnknown if s in self.unknown_str \\\n                else self.values.index(s)\n        except ValueError:\n            self.add_value(s)\n            return len(self.values) - 1\n\n    def repr_val(self, val):\n        \"\"\"\n        Return a textual representation of the value (`self.values[int(val)]`)\n        or \"?\" if the value is unknown.\n\n        :param val: value\n        :type val: float (should be whole number)\n        :rtype: str\n        \"\"\"\n        if isnan(val):\n            return \"?\"\n        return '{}'.format(self.values[int(val)])\n\n    str_val = repr_val\n\n    def __reduce__(self):\n        if not self.name:\n            raise PickleError(\"Variables without names cannot be pickled\")\n        __dict__ = dict(self.__dict__)\n        __dict__.pop(\"values\")\n        return make_variable, (self.__class__, self._compute_value, self.name,\n                               self.values, self.ordered), \\\n            __dict__\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            values=self.values, ordered=self.ordered)\n\n\nclass StringVariable(Variable):\n    \"\"\"\n    Descriptor for string variables. String variables can only appear as\n    meta attributes.\n    \"\"\"\n    Unknown = \"\"\n    TYPE_HEADERS = ('string', 's', 'text')\n\n    def to_val(self, s):\n        \"\"\"\n        Return the value as a string. If it is already a string, the same\n        object is returned.\n        \"\"\"\n        if s is None:\n            return \"\"\n        if isinstance(s, str):\n            return s\n        return str(s)\n\n    val_from_str_add = to_val\n\n    @staticmethod\n    def str_val(val):\n        \"\"\"Return a string representation of the value.\"\"\"\n        if val is \"\":\n            return \"?\"\n        if isinstance(val, Value):\n            if val.value is \"\":\n                return \"?\"\n            val = val.value\n        return str(val)\n\n    def repr_val(self, val):\n        \"\"\"Return a string representation of the value.\"\"\"\n        return '\"{}\"'.format(self.str_val(val))\n\n\nclass TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        ERROR = ValueError(\"Invalid datetime format '{}'. \"\n                           \"Only ISO 8601 supported.\".format(datestr))\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise ERROR\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise ERROR\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)\n",
            "new_file_content": "import re\nimport warnings\nfrom collections import Iterable\n\nfrom datetime import datetime, timedelta, timezone\nfrom numbers import Number, Real, Integral\nfrom math import isnan, floor\nfrom pickle import PickleError\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom Orange.data import _variable\nfrom Orange.util import Registry, hex_to_color, Reprable,\\\n    OrangeDeprecationWarning\n\n__all__ = [\"Unknown\", \"MISSING_VALUES\", \"make_variable\", \"is_discrete_values\",\n           \"Value\", \"Variable\", \"ContinuousVariable\", \"DiscreteVariable\",\n           \"StringVariable\", \"TimeVariable\"]\n\n\n# For storing unknowns\nUnknown = ValueUnknown = float(\"nan\")\n# For checking for unknowns\nMISSING_VALUES = {np.nan, \"?\", \"nan\", \".\", \"\", \"NA\", \"~\", None}\n\nDISCRETE_MAX_VALUES = 3  # == 2 + nan\nMAX_NUM_OF_DECIMALS = 5\n# the variable with more than 100 different values should not be StringVariable\nDISCRETE_MAX_ALLOWED_VALUES = 100\n\n\ndef make_variable(cls, compute_value, *args):\n    if compute_value is not None:\n        return cls(*args, compute_value=compute_value)\n    else:\n        # For compatibility with old pickles\n        return cls(*args)\n\n\ndef is_discrete_values(values):\n    \"\"\"\n    Return set of uniques if `values` is an iterable of discrete values\n    else False if non-discrete, or None if indeterminate.\n\n    Note\n    ----\n    Assumes consistent type of items of `values`.\n    \"\"\"\n    if not len(values):\n        return None\n    # If the first few values are, or can be converted to, floats,\n    # the type is numeric\n    try:\n        isinstance(next(iter(values)), Number) or \\\n        [float(v) for _, v in zip(range(min(3, len(values))), values)]\n    except ValueError:\n        is_numeric = False\n        max_values = int(round(len(values)**.7))\n    else:\n        is_numeric = True\n        max_values = DISCRETE_MAX_VALUES\n\n    # If more than max values => not discrete\n    unique = set()\n    for i in values:\n        unique.add(i)\n        if (len(unique) > max_values or\n                len(unique) > DISCRETE_MAX_ALLOWED_VALUES):\n            return False\n\n    # Strip NaN from unique\n    unique = {i for i in unique\n              if (not i in MISSING_VALUES and\n                  not (isinstance(i, Number) and np.isnan(i)))}\n\n    # All NaNs => indeterminate\n    if not unique:\n        return None\n\n    # Strings with |values| < max_unique\n    if not is_numeric:\n        return unique\n\n    # Handle numbers\n    try:\n        unique_float = set(map(float, unique))\n    except ValueError:\n        # Converting all the values to floats resulted in an error.\n        # Since the values have enough unique values, they are probably\n        # string values and discrete.\n        return unique\n\n    # If only values are {0, 1} or {1, 2} (or a subset of those sets) => discrete\n    return (not (unique_float - {0, 1}) or\n            not (unique_float - {1, 2})) and unique\n\n\nclass Value(float):\n    \"\"\"\n    The class representing a value. The class is not used to store values but\n    only to return them in contexts in which we want the value to be accompanied\n    with the descriptor, for instance to print the symbolic value of discrete\n    variables.\n\n    The class is derived from `float`, with an additional attribute `variable`\n    which holds the descriptor of type :obj:`Orange.data.Variable`. If the\n    value continuous or discrete, it is stored as a float. Other types of\n    values, like strings, are stored in the attribute `value`.\n\n    The class overloads the methods for printing out the value:\n    `variable.repr_val` and `variable.str_val` are used to get a suitable\n    representation of the value.\n\n    Equivalence operator is overloaded as follows:\n\n    - unknown values are equal; if one value is unknown and the other is not,\n      they are different;\n\n    - if the value is compared with the string, the value is converted to a\n      string using `variable.str_val` and the two strings are compared\n\n    - if the value is stored in attribute `value`, it is compared with the\n      given other value\n\n    - otherwise, the inherited comparison operator for `float` is called.\n\n    Finally, value defines a hash, so values can be put in sets and appear as\n    keys in dictionaries.\n\n    .. attribute:: variable (:obj:`Orange.data.Variable`)\n\n        Descriptor; used for printing out and for comparing with strings\n\n    .. attribute:: value\n\n        Value; the value can be of arbitrary type and is used only for variables\n        that are neither discrete nor continuous. If `value` is `None`, the\n        derived `float` value is used.\n    \"\"\"\n    __slots__ = \"variable\", \"_value\"\n\n    def __new__(cls, variable, value=Unknown):\n        \"\"\"\n        Construct a new instance of Value with the given descriptor and value.\n        If the argument `value` can be converted to float, it is stored as\n        `float` and the attribute `value` is set to `None`. Otherwise, the\n        inherited float is set to `Unknown` and the value is held by the\n        attribute `value`.\n\n        :param variable: descriptor\n        :type variable: Orange.data.Variable\n        :param value: value\n        \"\"\"\n        if variable.is_primitive():\n            self = super().__new__(cls, value)\n            self.variable = variable\n            self._value = None\n        else:\n            isunknown = value == variable.Unknown\n            self = super().__new__(\n                cls, np.nan if isunknown else np.finfo(float).min)\n            self.variable = variable\n            self._value = value\n        return self\n\n    def __init__(self, _, __=Unknown):\n        pass\n\n    def __repr__(self):\n        return \"Value('%s', %s)\" % (self.variable.name,\n                                    self.variable.repr_val(self))\n\n    def __str__(self):\n        return self.variable.str_val(self)\n\n    def __eq__(self, other):\n        if isinstance(self, Real) and isnan(self):\n            return (isinstance(other, Real) and isnan(other)\n                    or other in self.variable.unknown_str)\n        if isinstance(other, str):\n            return self.variable.str_val(self) == other\n        if isinstance(other, Value):\n            return self.value == other.value\n        return super().__eq__(other)\n\n    def __ne__(self, other):\n        return not self.__eq__(other)\n\n    def __lt__(self, other):\n        if self.variable.is_primitive():\n            if isinstance(other, str):\n                return super().__lt__(self.variable.to_val(other))\n            else:\n                return super().__lt__(other)\n        else:\n            if isinstance(other, str):\n                return self.value < other\n            else:\n                return self.value < other.value\n\n    def __le__(self, other):\n        return self.__lt__(other) or self.__eq__(other)\n\n    def __gt__(self, other):\n        return not self.__le__(other)\n\n    def __ge__(self, other):\n        return not self.__lt__(other)\n\n    def __contains__(self, other):\n        if (self._value is not None\n                and isinstance(self._value, str)\n                and isinstance(other, str)):\n            return other in self._value\n        raise TypeError(\"invalid operation on Value()\")\n\n    def __hash__(self):\n        if self.variable.is_discrete:\n            # It is not possible to hash the id and the domain value to the\n            # same number as required by __eq__.\n            # hash(1) == hash(Value(DiscreteVariable(\"var\", [\"red\", \"green\", \"blue\"]), 1)) == hash(\"green\")\n            # User should hash directly ids or domain values instead.\n            raise TypeError(\"unhashable type - cannot hash values of discrete variables!\")\n        if self._value is None:\n            return super().__hash__()\n        else:\n            return hash(self._value)\n\n    @property\n    def value(self):\n        if self.variable.is_discrete:\n            return Unknown if isnan(self) else self.variable.values[int(self)]\n        if self.variable.is_string:\n            return self._value\n        return float(self)\n\n    def __getnewargs__(self):\n        return self.variable, float(self)\n\n    def __getstate__(self):\n        return dict(value=getattr(self, '_value', None))\n\n    def __setstate__(self, state):\n        self._value = state.get('value', None)\n\n\nclass VariableMeta(Registry):\n    pass\n\n\nclass _predicatedescriptor(property):\n    \"\"\"\n    A property that behaves as a class method if accessed via a class\n    >>> class A:\n    ...     foo = False\n    ...     @_predicatedescriptor\n    ...     def is_foo(self):\n    ...         return self.foo\n    ...\n    >>> a = A()\n    >>> a.is_foo\n    False\n    >>> A.is_foo(a)\n    False\n    \"\"\"\n    def __get__(self, instance, objtype=None):\n        if instance is None:\n            return self.fget\n        else:\n            return super().__get__(instance, objtype)\n\n\nclass Variable(Reprable, metaclass=VariableMeta):\n    \"\"\"\n    The base class for variable descriptors contains the variable's\n    name and some basic properties.\n\n    .. attribute:: name\n\n        The name of the variable.\n\n    .. attribute:: unknown_str\n\n        A set of values that represent unknowns in conversion from textual\n        formats. Default is `{\"?\", \".\", \"\", \"NA\", \"~\", None}`.\n\n    .. attribute:: compute_value\n\n        A function for computing the variable's value when converting from\n        another domain which does not contain this variable. The function will\n        be called with a data set (`Orange.data.Table`) and has to return\n        an array of computed values for all its instances. The base class\n        defines a static method `compute_value`, which returns `Unknown`.\n        Non-primitive variables must redefine it to return `None`.\n\n    .. attribute:: sparse\n\n        A flag about sparsity of the variable. When set, the variable suggests\n        it should be stored in a sparse matrix.\n\n    .. attribute:: source_variable\n\n        An optional descriptor of the source variable - if any - from which\n        this variable is derived and computed via :obj:`compute_value`.\n\n    .. attribute:: attributes\n\n        A dictionary with user-defined attributes of the variable\n    \"\"\"\n    Unknown = ValueUnknown\n\n    def __init__(self, name=\"\", compute_value=None, *, sparse=False):\n        \"\"\"\n        Construct a variable descriptor.\n        \"\"\"\n        if not name:\n            warnings.warn(\"Variable must have a name\", OrangeDeprecationWarning,\n                          stacklevel=3)\n        self._name = name\n        self._compute_value = compute_value\n        self.unknown_str = MISSING_VALUES\n        self.source_variable = None\n        self.sparse = sparse\n        self.attributes = {}\n        self._colors = None\n\n    @property\n    def name(self):\n        return self._name\n\n    @property\n    def colors(self):  # unreachable; pragma: no cover\n        return self._colors\n\n    @colors.setter\n    def colors(self, value):\n        self._colors = value\n\n    def make_proxy(self):\n        \"\"\"\n        Copy the variable and set the master to `self.master` or to `self`.\n\n        :return: copy of self\n        :rtype: Variable\n        \"\"\"\n        var = self.__class__(self.name)\n        var.__dict__.update(self.__dict__)\n        var.attributes = dict(self.attributes)\n        return var\n\n    def __eq__(self, other):\n        return type(self) is type(other) \\\n               and self.name == other.name \\\n               and self._compute_value == other._compute_value\n\n    def __hash__(self):\n        return hash((self.name, type(self), self._compute_value))\n\n    @classmethod\n    def make(cls, name, *args, **kwargs):\n        \"\"\"\n        Return an existing continuous variable with the given name, or\n        construct and return a new one.\n        \"\"\"\n        return cls(name, *args, **kwargs)\n\n    @classmethod\n    def _clear_cache(cls):\n        warnings.warn(\n            \"_clear_cache is no longer needed and thus deprecated\")\n\n    @staticmethod\n    def _clear_all_caches():\n        warnings.warn(\n            \"_clear_all_caches is no longer needed and thus deprecated\")\n\n    @classmethod\n    def is_primitive(cls, var=None):\n        \"\"\"\n        `True` if the variable's values are stored as floats.\n        Non-primitive variables can appear in the data only as meta attributes.\n        \"\"\"\n        to_check = cls if var is None else type(var)\n        return issubclass(to_check, (DiscreteVariable, ContinuousVariable))\n\n    @_predicatedescriptor\n    def is_discrete(self):\n        return isinstance(self, DiscreteVariable)\n\n    @_predicatedescriptor\n    def is_continuous(self):\n        return isinstance(self, ContinuousVariable)\n\n    @_predicatedescriptor\n    def is_string(self):\n        return isinstance(self, StringVariable)\n\n    @_predicatedescriptor\n    def is_time(self):\n        return isinstance(self, TimeVariable)\n\n    def repr_val(self, val):\n        \"\"\"\n        Return a textual representation of variable's value `val`. Argument\n        `val` must be a float (for primitive variables) or an arbitrary\n        Python object (for non-primitives).\n\n        Derived classes must overload the function.\n        \"\"\"\n        raise RuntimeError(\"variable descriptors must overload repr_val()\")\n\n    str_val = repr_val\n\n    def to_val(self, s):\n        \"\"\"\n        Convert the given argument to a value of the variable. The\n        argument can be a string, a number or `None`. For primitive variables,\n        the base class provides a method that returns\n        :obj:`~Orange.data.Unknown` if `s` is found in\n        :obj:`~Orange.data.Variable.unknown_str`, and raises an exception\n        otherwise. For non-primitive variables it returns the argument itself.\n\n        Derived classes of primitive variables must overload the function.\n\n        :param s: value, represented as a number, string or `None`\n        :type s: str, float or None\n        :rtype: float or object\n        \"\"\"\n        if not self.is_primitive():\n            return s\n        if s in self.unknown_str:\n            return Unknown\n        raise RuntimeError(\n            \"primitive variable descriptors must overload to_val()\")\n\n    def val_from_str_add(self, s):\n        \"\"\"\n        Convert the given string to a value of the variable. The method\n        is similar to :obj:`to_val` except that it only accepts strings and\n        that it adds new values to the variable's domain where applicable.\n\n        The base class method calls `to_val`.\n\n        :param s: symbolic representation of the value\n        :type s: str\n        :rtype: float or object\n        \"\"\"\n        return self.to_val(s)\n\n    def __str__(self):\n        return self.name\n\n    @property\n    def compute_value(self):\n        return self._compute_value\n\n    def __reduce__(self):\n        if not self.name:\n            raise PickleError(\"Variables without names cannot be pickled\")\n\n        # Use make to unpickle variables.\n        return make_variable, (self.__class__, self._compute_value, self.name), self.__dict__\n\n    def copy(self, compute_value=None, *, name=None, **kwargs):\n        var = type(self)(name=name or self.name,\n                         compute_value=compute_value or self.compute_value,\n                         sparse=self.sparse, **kwargs)\n        var.attributes = dict(self.attributes)\n        return var\n\n\ndel _predicatedescriptor\n\n\nclass ContinuousVariable(Variable):\n    \"\"\"\n    Descriptor for continuous variables.\n\n    .. attribute:: number_of_decimals\n\n        The number of decimals when the value is printed out (default: 3).\n\n    .. attribute:: adjust_decimals\n\n        A flag regulating whether the `number_of_decimals` is being adjusted\n        by :obj:`to_val`.\n\n    The value of `number_of_decimals` is set to 3 and `adjust_decimals`\n    is set to 2. When :obj:`val_from_str_add` is called for the first\n    time with a string as an argument, `number_of_decimals` is set to the\n    number of decimals in the string and `adjust_decimals` is set to 1.\n    In the subsequent calls of `to_val`, the nubmer of decimals is\n    increased if the string argument has a larger number of decimals.\n\n    If the `number_of_decimals` is set manually, `adjust_decimals` is\n    set to 0 to prevent changes by `to_val`.\n    \"\"\"\n\n    TYPE_HEADERS = ('continuous', 'c', 'numeric', 'n')\n\n    def __init__(self, name=\"\", number_of_decimals=None, compute_value=None, *, sparse=False):\n        \"\"\"\n        Construct a new continuous variable. The number of decimals is set to\n        three, but adjusted at the first call of :obj:`to_val`.\n        \"\"\"\n        super().__init__(name, compute_value, sparse=sparse)\n        if number_of_decimals is None:\n            self._number_of_decimals = 3\n            self.adjust_decimals = 2\n            self._format_str = \"%g\"\n        else:\n            self.number_of_decimals = number_of_decimals\n\n    @property\n    def number_of_decimals(self):\n        return self._number_of_decimals\n\n    @property\n    def format_str(self):\n        return self._format_str\n\n    @format_str.setter\n    def format_str(self, value):\n        self._format_str = value\n\n    @Variable.colors.getter\n    def colors(self):\n        if self._colors is not None:\n            return self._colors\n        try:\n            col1, col2, black = self.attributes[\"colors\"]\n            return (hex_to_color(col1), hex_to_color(col2), black)\n        except (KeyError, ValueError):\n            # User-provided colors were not available or invalid\n            return ((0, 0, 255), (255, 255, 0), False)\n\n    # noinspection PyAttributeOutsideInit\n    @number_of_decimals.setter\n    def number_of_decimals(self, x):\n        self._number_of_decimals = x\n        self.adjust_decimals = 0\n        if self._number_of_decimals <= MAX_NUM_OF_DECIMALS:\n            self._format_str = \"%.{}f\".format(self.number_of_decimals)\n        else:\n            self._format_str = \"%g\"\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if s in self.unknown_str:\n            return Unknown\n        return float(s)\n\n    def val_from_str_add(self, s):\n        \"\"\"\n        Convert a value from a string and adjust the number of decimals if\n        `adjust_decimals` is non-zero.\n        \"\"\"\n        return _variable.val_from_str_add_cont(self, s)\n\n    def repr_val(self, val):\n        \"\"\"\n        Return the value as a string with the prescribed number of decimals.\n        \"\"\"\n        if isnan(val):\n            return \"?\"\n        return self._format_str % val\n\n    str_val = repr_val\n\n    def copy(self, compute_value=None, *, name=None, **kwargs):\n        var = super().copy(compute_value=compute_value, name=name,\n                           number_of_decimals=self.number_of_decimals,\n                           **kwargs)\n        var.adjust_decimals = self.adjust_decimals\n        var.format_str = self._format_str\n        return var\n\n\nclass DiscreteVariable(Variable):\n    \"\"\"\n    Descriptor for symbolic, discrete variables. Values of discrete variables\n    are stored as floats; the numbers corresponds to indices in the list of\n    values.\n\n    .. attribute:: values\n\n        A list of variable's values.\n\n    .. attribute:: ordered\n\n        Some algorithms (and, in particular, visualizations) may\n        sometime reorder the values of the variable, e.g. alphabetically.\n        This flag hints that the given order of values is \"natural\"\n        (e.g. \"small\", \"middle\", \"large\") and should not be changed.\n    \"\"\"\n\n    TYPE_HEADERS = ('discrete', 'd', 'categorical')\n\n    presorted_values = []\n\n    def __init__(self, name=\"\", values=(), ordered=False, compute_value=None,\n                 *, sparse=False):\n        \"\"\" Construct a discrete variable descriptor with the given values. \"\"\"\n        self.values = list(values)\n        if not all(isinstance(value, str) for value in self.values):\n            raise TypeError(\"values of DiscreteVariables must be strings\")\n        super().__init__(name, compute_value, sparse=sparse)\n        self.ordered = ordered\n\n    def get_mapping_from(self, other):\n        return np.array(\n            [self.values.index(value) if value in self.values else np.nan\n             for value in other.values], dtype=float)\n\n    def get_mapper_from(self, other):\n        mapping = self.get_mapping_from(other)\n        if not mapping.size:\n            # Nans in data are temporarily replaced with 0, mapped and changed\n            # back to nans. This would fail is mapping[0] is out of range.\n            mapping = np.array([np.nan])\n\n        def mapper(value, col_idx=None):\n\n            # In-place mapping\n            if col_idx is not None:\n                if sp.issparse(value) and mapping[0] != 0:\n                    raise ValueError(\n                        \"In-place mapping of sparse matrices must map 0 to 0\")\n\n                # CSR requires mapping of non-contiguous area\n                if sp.isspmatrix_csr(value):\n                    col = value.indices == col_idx\n                    nans = np.isnan(value.data) * col\n                    value.data[nans] = 0\n                    value.data[col] = mapping[value.data[col].astype(int)]\n                    value.data[nans] = np.nan\n                    return\n\n                # Dense and CSC map a contiguous area\n                if isinstance(value, np.ndarray) and value.ndim == 2:\n                    col = value[:, col_idx]\n                elif sp.isspmatrix_csc(value):\n                    col = value.data[value.indptr[col_idx]\n                                     :value.indptr[col_idx + 1]]\n                else:\n                    raise ValueError(\n                        \"In-place column mapping requires a 2d array or\"\n                        \"a csc or csr matrix.\")\n\n                nans = np.isnan(col)\n                col[nans] = 0\n                col[:] = mapping[col.astype(int)]\n                col[nans] = np.nan\n                return\n\n            # Mapping into a copy\n            if isinstance(value, (int, float)):\n                return mapping[int(value)] if value == value else value\n            if isinstance(value, str):\n                return mapping[other.values.index(value)]\n            if isinstance(value, np.ndarray):\n                if not (value.ndim == 1\n                        or value.ndim != 2 and min(value.shape) != 1):\n                    raise ValueError(\n                        f\"Column mapping can't map {value.ndim}-d objects\")\n\n                if value.dtype == object:\n                    value = value.astype(float)  # this happens with metas\n                try:\n                    nans = np.isnan(value)\n                except TypeError:  # suppose it's already an integer type\n                    return mapping[value]\n                value = value.astype(int)\n                value[nans] = 0\n                value = mapping[value]\n                value[nans] = np.nan\n                return value\n            if sp.issparse(value):\n                if min(value.shape) != 1:\n                    raise ValueError(\"Column mapping can't map \"\n                                     f\"{value.ndim}-dimensional objects\")\n                if mapping[0] != 0 and not np.isnan(mapping[0]):\n                    return mapper(np.array(value.todense()).flatten())\n                value = value.copy()\n                value.data = mapper(value.data)\n                return value\n            if isinstance(value, Iterable):\n                return type(value)(mapping[int(val)] if val == val else val\n                                   for val in value)\n            raise ValueError(\n                f\"invalid type for value(s): {type(value).__name__}\")\n\n        return mapper\n\n    @Variable.colors.getter\n    def colors(self):\n        if self._colors is not None:\n            colors = np.array(self._colors)\n        elif not self.values:\n            colors = np.zeros((0, 3))  # to match additional colors in vstacks\n        else:\n            from Orange.widgets.utils.colorpalette import ColorPaletteGenerator\n            default = tuple(ColorPaletteGenerator.palette(self))\n            colors = self.attributes.get('colors', ())\n            colors = tuple(hex_to_color(color) for color in colors) \\\n                    + default[len(colors):]\n            colors = np.array(colors)\n        colors.flags.writeable = False\n        return colors\n\n    def to_val(self, s):\n        \"\"\"\n        Convert the given argument to a value of the variable (`float`).\n        If the argument is numeric, its value is returned without checking\n        whether it is integer and within bounds. `Unknown` is returned if the\n        argument is one of the representations for unknown values. Otherwise,\n        the argument must be a string and the method returns its index in\n        :obj:`values`.\n\n        :param s: values, represented as a number, string or `None`\n        :rtype: float\n        \"\"\"\n        if s is None:\n            return ValueUnknown\n\n        if isinstance(s, Integral):\n            return s\n        if isinstance(s, Real):\n            return s if isnan(s) else floor(s + 0.25)\n        if s in self.unknown_str:\n            return ValueUnknown\n        if not isinstance(s, str):\n            raise TypeError('Cannot convert {} to value of \"{}\"'.format(\n                type(s).__name__, self.name))\n        return self.values.index(s)\n\n    def add_value(self, s):\n        \"\"\" Add a value `s` to the list of values.\n        \"\"\"\n        if not isinstance(s, str):\n            raise TypeError(\"values of DiscreteVariables must be strings\")\n        self.values.append(s)\n        self._colors = None\n\n    def val_from_str_add(self, s):\n        \"\"\"\n        Similar to :obj:`to_val`, except that it accepts only strings and that\n        it adds the value to the list if it does not exist yet.\n\n        :param s: symbolic representation of the value\n        :type s: str\n        :rtype: float\n        \"\"\"\n        s = str(s) if s is not None else s\n        try:\n            return ValueUnknown if s in self.unknown_str \\\n                else self.values.index(s)\n        except ValueError:\n            self.add_value(s)\n            return len(self.values) - 1\n\n    def repr_val(self, val):\n        \"\"\"\n        Return a textual representation of the value (`self.values[int(val)]`)\n        or \"?\" if the value is unknown.\n\n        :param val: value\n        :type val: float (should be whole number)\n        :rtype: str\n        \"\"\"\n        if isnan(val):\n            return \"?\"\n        return '{}'.format(self.values[int(val)])\n\n    str_val = repr_val\n\n    def __reduce__(self):\n        if not self.name:\n            raise PickleError(\"Variables without names cannot be pickled\")\n        __dict__ = dict(self.__dict__)\n        __dict__.pop(\"values\")\n        return make_variable, (self.__class__, self._compute_value, self.name,\n                               self.values, self.ordered), \\\n            __dict__\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            values=self.values, ordered=self.ordered)\n\n\nclass StringVariable(Variable):\n    \"\"\"\n    Descriptor for string variables. String variables can only appear as\n    meta attributes.\n    \"\"\"\n    Unknown = \"\"\n    TYPE_HEADERS = ('string', 's', 'text')\n\n    def to_val(self, s):\n        \"\"\"\n        Return the value as a string. If it is already a string, the same\n        object is returned.\n        \"\"\"\n        if s is None:\n            return \"\"\n        if isinstance(s, str):\n            return s\n        return str(s)\n\n    val_from_str_add = to_val\n\n    @staticmethod\n    def str_val(val):\n        \"\"\"Return a string representation of the value.\"\"\"\n        if val is \"\":\n            return \"?\"\n        if isinstance(val, Value):\n            if val.value is \"\":\n                return \"?\"\n            val = val.value\n        return str(val)\n\n    def repr_val(self, val):\n        \"\"\"Return a string representation of the value.\"\"\"\n        return '\"{}\"'.format(self.str_val(val))\n\n\nclass TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n\n    class InvalidDateTimeFormatError(ValueError):\n        def __init__(self, date_string):\n            super().__init__(\n                \"Invalid datetime format '{}'. \"\n                \"Only ISO 8601 supported.\".format(date_string))\n\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise self.InvalidDateTimeFormatError(datestr)\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise self.InvalidDateTimeFormatError(datestr)\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def parse_exact_iso(self, datestr):\n        \"\"\"\n        This function is a meta function to `parse` function. It checks\n        whether the date is of the iso format - it does not accept float-like\n        date.\n        \"\"\"\n        if not self._matches_iso_format(datestr):\n            raise self.InvalidDateTimeFormatError(datestr)\n        return self.parse(datestr)\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)\n",
            "header": {
                "file": {
                    "path": "Orange/data/variable.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "e333118b7",
                "new_commit_hash": "211bb6a44",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/data/variable.py"
            },
            "plus_file": {
                "path": "b/Orange/data/variable.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 889,
                            "length": 6
                        },
                        "new_range": {
                            "start": 889,
                            "length": 13
                        },
                        "section": "class TimeVariable(ContinuousVariable):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'",
                                "type": "context"
                            },
                            {
                                "content": "             r'\\d{1,4}(-?\\d{2,3})?'",
                                "type": "context"
                            },
                            {
                                "content": "             r')$')",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    class InvalidDateTimeFormatError(ValueError):",
                                "type": "added"
                            },
                            {
                                "content": "        def __init__(self, date_string):",
                                "type": "added"
                            },
                            {
                                "content": "            super().__init__(",
                                "type": "added"
                            },
                            {
                                "content": "                \"Invalid datetime format '{}'. \"",
                                "type": "added"
                            },
                            {
                                "content": "                \"Only ISO 8601 supported.\".format(date_string))",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    _matches_iso_format = re.compile(REGEX).match",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    # UTC offset and associated timezone. If parsed datetime values provide an",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/data/variable.py",
                            "type": "class",
                            "name": "TimeVariable",
                            "content": "class TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n\n    class InvalidDateTimeFormatError(ValueError):\n        def __init__(self, date_string):\n            super().__init__(\n                \"Invalid datetime format '{}'. \"\n                \"Only ISO 8601 supported.\".format(date_string))\n\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise self.InvalidDateTimeFormatError(datestr)\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise self.InvalidDateTimeFormatError(datestr)\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def parse_exact_iso(self, datestr):\n        \"\"\"\n        This function is a meta function to `parse` function. It checks\n        whether the date is of the iso format - it does not accept float-like\n        date.\n        \"\"\"\n        if not self._matches_iso_format(datestr):\n            raise self.InvalidDateTimeFormatError(datestr)\n        return self.parse(datestr)\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 831,
                            "end_lineno": 1035,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 954,
                            "length": 8
                        },
                        "new_range": {
                            "start": 961,
                            "length": 6
                        },
                        "section": "class TimeVariable(ContinuousVariable):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            return Unknown",
                                "type": "context"
                            },
                            {
                                "content": "        datestr = datestr.strip().rstrip('Z')",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        ERROR = ValueError(\"Invalid datetime format '{}'. \"",
                                "type": "deleted"
                            },
                            {
                                "content": "                           \"Only ISO 8601 supported.\".format(datestr))",
                                "type": "deleted"
                            },
                            {
                                "content": "        if not self._matches_iso_format(datestr):",
                                "type": "context"
                            },
                            {
                                "content": "            try:",
                                "type": "context"
                            },
                            {
                                "content": "                # If it is a number, assume it is a unix timestamp",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/data/variable.py",
                            "type": "method",
                            "name": "TimeVariable.parse",
                            "content": "class TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        ERROR = ValueError(\"Invalid datetime format '{}'. \"\n                           \"Only ISO 8601 supported.\".format(datestr))\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise ERROR\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise ERROR\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 941,
                            "end_lineno": 1011,
                            "parent": {
                                "file_name": "Orange/data/variable.py",
                                "type": "class",
                                "name": "TimeVariable",
                                "content": "class TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        ERROR = ValueError(\"Invalid datetime format '{}'. \"\n                           \"Only ISO 8601 supported.\".format(datestr))\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise ERROR\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise ERROR\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 831,
                                "end_lineno": 1020,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/data/variable.py",
                            "type": "class",
                            "name": "TimeVariable",
                            "content": "class TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        ERROR = ValueError(\"Invalid datetime format '{}'. \"\n                           \"Only ISO 8601 supported.\".format(datestr))\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise ERROR\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise ERROR\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 831,
                            "end_lineno": 1020,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 963,
                            "length": 7
                        },
                        "new_range": {
                            "start": 968,
                            "length": 7
                        },
                        "section": "class TimeVariable(ContinuousVariable):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "                self.have_date = self.have_time = 1",
                                "type": "context"
                            },
                            {
                                "content": "                return value",
                                "type": "context"
                            },
                            {
                                "content": "            except ValueError:",
                                "type": "context"
                            },
                            {
                                "content": "                raise ERROR",
                                "type": "deleted"
                            },
                            {
                                "content": "                raise self.InvalidDateTimeFormatError(datestr)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):",
                                "type": "context"
                            },
                            {
                                "content": "            try:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/data/variable.py",
                            "type": "method",
                            "name": "TimeVariable.parse",
                            "content": "class TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        ERROR = ValueError(\"Invalid datetime format '{}'. \"\n                           \"Only ISO 8601 supported.\".format(datestr))\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise ERROR\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise ERROR\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 941,
                            "end_lineno": 1011,
                            "parent": {
                                "file_name": "Orange/data/variable.py",
                                "type": "class",
                                "name": "TimeVariable",
                                "content": "class TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        ERROR = ValueError(\"Invalid datetime format '{}'. \"\n                           \"Only ISO 8601 supported.\".format(datestr))\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise ERROR\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise ERROR\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 831,
                                "end_lineno": 1020,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/data/variable.py",
                            "type": "class",
                            "name": "TimeVariable",
                            "content": "class TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        ERROR = ValueError(\"Invalid datetime format '{}'. \"\n                           \"Only ISO 8601 supported.\".format(datestr))\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise ERROR\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise ERROR\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 831,
                            "end_lineno": 1020,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 984,
                            "length": 7
                        },
                        "new_range": {
                            "start": 989,
                            "length": 7
                        },
                        "section": "class TimeVariable(ContinuousVariable):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "                                    self.UNIX_EPOCH.day)",
                                "type": "context"
                            },
                            {
                                "content": "                break",
                                "type": "context"
                            },
                            {
                                "content": "        else:",
                                "type": "context"
                            },
                            {
                                "content": "            raise ERROR",
                                "type": "deleted"
                            },
                            {
                                "content": "            raise self.InvalidDateTimeFormatError(datestr)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        # Remember UTC offset. If not all parsed values share the same offset,",
                                "type": "context"
                            },
                            {
                                "content": "        # remember none of it.",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/data/variable.py",
                            "type": "method",
                            "name": "TimeVariable.parse",
                            "content": "class TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        ERROR = ValueError(\"Invalid datetime format '{}'. \"\n                           \"Only ISO 8601 supported.\".format(datestr))\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise ERROR\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise ERROR\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 941,
                            "end_lineno": 1011,
                            "parent": {
                                "file_name": "Orange/data/variable.py",
                                "type": "class",
                                "name": "TimeVariable",
                                "content": "class TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        ERROR = ValueError(\"Invalid datetime format '{}'. \"\n                           \"Only ISO 8601 supported.\".format(datestr))\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise ERROR\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise ERROR\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 831,
                                "end_lineno": 1020,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/data/variable.py",
                            "type": "class",
                            "name": "TimeVariable",
                            "content": "class TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        ERROR = ValueError(\"Invalid datetime format '{}'. \"\n                           \"Only ISO 8601 supported.\".format(datestr))\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise ERROR\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise ERROR\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 831,
                            "end_lineno": 1020,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1010,
                            "length": 6
                        },
                        "new_range": {
                            "start": 1015,
                            "length": 16
                        },
                        "section": "class TimeVariable(ContinuousVariable):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        except OverflowError:",
                                "type": "context"
                            },
                            {
                                "content": "            return -(self.UNIX_EPOCH - dt).total_seconds()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def parse_exact_iso(self, datestr):",
                                "type": "added"
                            },
                            {
                                "content": "        \"\"\"",
                                "type": "added"
                            },
                            {
                                "content": "        This function is a meta function to `parse` function. It checks",
                                "type": "added"
                            },
                            {
                                "content": "        whether the date is of the iso format - it does not accept float-like",
                                "type": "added"
                            },
                            {
                                "content": "        date.",
                                "type": "added"
                            },
                            {
                                "content": "        \"\"\"",
                                "type": "added"
                            },
                            {
                                "content": "        if not self._matches_iso_format(datestr):",
                                "type": "added"
                            },
                            {
                                "content": "            raise self.InvalidDateTimeFormatError(datestr)",
                                "type": "added"
                            },
                            {
                                "content": "        return self.parse(datestr)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def to_val(self, s):",
                                "type": "context"
                            },
                            {
                                "content": "        \"\"\"",
                                "type": "context"
                            },
                            {
                                "content": "        Convert a value, given as an instance of an arbitrary type, to a float.",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/data/variable.py",
                            "type": "class",
                            "name": "TimeVariable",
                            "content": "class TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n\n    class InvalidDateTimeFormatError(ValueError):\n        def __init__(self, date_string):\n            super().__init__(\n                \"Invalid datetime format '{}'. \"\n                \"Only ISO 8601 supported.\".format(date_string))\n\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise self.InvalidDateTimeFormatError(datestr)\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise self.InvalidDateTimeFormatError(datestr)\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def parse_exact_iso(self, datestr):\n        \"\"\"\n        This function is a meta function to `parse` function. It checks\n        whether the date is of the iso format - it does not accept float-like\n        date.\n        \"\"\"\n        if not self._matches_iso_format(datestr):\n            raise self.InvalidDateTimeFormatError(datestr)\n        return self.parse(datestr)\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 831,
                            "end_lineno": 1035,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "Orange/data/variable.py",
                            "type": "method",
                            "name": "TimeVariable.parse_exact_iso",
                            "content": "class TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n\n    class InvalidDateTimeFormatError(ValueError):\n        def __init__(self, date_string):\n            super().__init__(\n                \"Invalid datetime format '{}'. \"\n                \"Only ISO 8601 supported.\".format(date_string))\n\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise self.InvalidDateTimeFormatError(datestr)\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise self.InvalidDateTimeFormatError(datestr)\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def parse_exact_iso(self, datestr):\n        \"\"\"\n        This function is a meta function to `parse` function. It checks\n        whether the date is of the iso format - it does not accept float-like\n        date.\n        \"\"\"\n        if not self._matches_iso_format(datestr):\n            raise self.InvalidDateTimeFormatError(datestr)\n        return self.parse(datestr)\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 1018,
                            "end_lineno": 1026,
                            "parent": {
                                "file_name": "Orange/data/variable.py",
                                "type": "class",
                                "name": "TimeVariable",
                                "content": "class TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n\n    class InvalidDateTimeFormatError(ValueError):\n        def __init__(self, date_string):\n            super().__init__(\n                \"Invalid datetime format '{}'. \"\n                \"Only ISO 8601 supported.\".format(date_string))\n\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise self.InvalidDateTimeFormatError(datestr)\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise self.InvalidDateTimeFormatError(datestr)\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def parse_exact_iso(self, datestr):\n        \"\"\"\n        This function is a meta function to `parse` function. It checks\n        whether the date is of the iso format - it does not accept float-like\n        date.\n        \"\"\"\n        if not self._matches_iso_format(datestr):\n            raise self.InvalidDateTimeFormatError(datestr)\n        return self.parse(datestr)\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 831,
                                "end_lineno": 1035,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "b08fc7e897d38f85d19823221e420a8d6d7beddb^",
    "new_commit_hash": "b08fc7e897d38f85d19823221e420a8d6d7beddb",
    "commit_message": "Merge pull request #4226 from PrimozGodec/fix-time\n\n[FIX] Fix guessing strategy for date and time variables",
    "commit_date": "2019-11-29T12:23:01Z",
    "metadata": {}
}
{
    "repo_name": "orange3",
    "new_commit_hash": "b08fc7e897d38f85d19823221e420a8d6d7beddb",
    "test_file_codes": [
        "import unittest\nimport numpy as np\n\nfrom Orange.data import ContinuousVariable, DiscreteVariable, StringVariable, \\\n    TimeVariable\nfrom Orange.data.io import guess_data_type\n\n\nclass TestTableFilters(unittest.TestCase):\n\n    def test_guess_data_type_continuous(self):\n        # should be ContinuousVariable\n        valuemap, values, coltype = guess_data_type(list(range(1, 100)))\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(np.array(list(range(1, 100))), values)\n\n        valuemap, values, coltype = guess_data_type([1, 2, 3, 1, 2, 3])\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal([1, 2, 3, 1, 2, 3], values)\n\n        valuemap, values, coltype = guess_data_type(\n            [\"1\", \"2\", \"3\", \"1\", \"2\", \"3\"])\n        self.assertEqual(ContinuousVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal([1, 2, 3, 1, 2, 3], values)\n\n    def test_guess_data_type_discrete(self):\n        # should be DiscreteVariable\n        valuemap, values, coltype = guess_data_type([1, 2, 1, 2])\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual([1, 2], valuemap)\n        np.testing.assert_array_equal([1, 2, 1, 2], values)\n\n        valuemap, values, coltype = guess_data_type([\"1\", \"2\", \"1\", \"2\", \"a\"])\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual([\"1\", \"2\", \"a\"], valuemap)\n        np.testing.assert_array_equal(['1', '2', '1', '2', 'a'], values)\n\n        # just below the threshold for string variable\n        in_values = list(map(lambda x: str(x) + \"a\", range(24))) + [\"a\"] * 76\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(DiscreteVariable, coltype)\n        self.assertEqual(sorted(set(in_values)), valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n    def test_guess_data_type_string(self):\n        # should be StringVariable\n        # too many different values for discrete\n        in_values = list(map(lambda x: str(x) + \"a\", range(90)))\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n        # more than len(values)**0.7\n        in_values = list(map(lambda x: str(x) + \"a\", range(25))) + [\"a\"] * 75\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n        # more than 100 different values - exactly 101\n        # this is the case when len(values)**0.7 rule would vote for the\n        # DiscreteVariable\n        in_values = list(map(lambda x: str(x) + \"a\", range(100))) + [\"a\"] * 999\n        valuemap, values, coltype = guess_data_type(in_values)\n        self.assertEqual(StringVariable, coltype)\n        self.assertIsNone(valuemap)\n        np.testing.assert_array_equal(in_values, values)\n\n    def test_guess_data_type_time(self):\n        in_values = [\"2019-10-10\", \"2019-10-10\", \"2019-10-10\", \"2019-10-01\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10T12:08:51\", \"2019-10-10T12:08:51\",\n                     \"2019-10-10T12:08:51\", \"2019-10-01T12:08:51\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10 12:08:51\", \"2019-10-10 12:08:51\",\n                     \"2019-10-10 12:08:51\", \"2019-10-01 12:08:51\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n\n        in_values = [\"2019-10-10 12:08\", \"2019-10-10 12:08\",\n                     \"2019-10-10 12:08\", \"2019-10-01 12:08\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n        self.assertEqual(TimeVariable, coltype)\n        self.assertIsNone(valuemap)\n"
    ],
    "test_file_names": [
        "test_1.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Orange installation attempts...\nAttempting installation with Python 3.7...\nrunning build_ext\nrunning build_src\nbuild_src\nbuilding extension \"Orange.classification._simple_tree\" sources\nbuilding extension \"Orange.classification._tree_scorers\" sources\nbuilding extension \"Orange.data._valuecount\" sources\nbuilding extension \"Orange.data._contingency\" sources\nbuilding extension \"Orange.data._io\" sources\nbuilding extension \"Orange.data._variable\" sources\nbuilding extension \"Orange.distance._distance\" sources\nbuilding extension \"Orange.preprocess._discretize\" sources\nbuilding extension \"Orange.preprocess._relieff\" sources\nbuilding extension \"Orange.projection._som\" sources\nbuilding extension \"Orange.widgets.utils._grid_density\" sources\nbuild_src: building npy-pkg config files\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmpc48c4bz5/tmp\ncreating /tmp/tmpc48c4bz5/tmp/tmpc48c4bz5\ncompile options: '-MMD -MF /tmp/tmpc48c4bz5/file.c.d -c'\ncc: /tmp/tmpc48c4bz5/file.c\ncustomize UnixCCompiler using build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmpvirt0ase/tmp\ncreating /tmp/tmpvirt0ase/tmp/tmpvirt0ase\ncompile options: '-MMD -MF /tmp/tmpvirt0ase/file.c.d -c'\ncc: /tmp/tmpvirt0ase/file.c\ncustomize UnixCCompiler using build_ext\nbuilding 'Orange.classification._simple_tree' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange\ncreating build/temp.linux-x86_64-3.7/Orange/classification\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/classification/_simple_tree.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_simple_tree.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.classification._tree_scorers' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/classification/_tree_scorers.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_tree_scorers.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._valuecount' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/data\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_valuecount.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_valuecount.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._contingency' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_contingency.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_contingency.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._io' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_io.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_io.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_io.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._variable' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_variable.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_variable.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.distance._distance' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/distance\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/distance/_distance.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/distance/_distance.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._discretize' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/preprocess\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/preprocess/_discretize.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_discretize.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._relieff' extension\ncompiling C++ sources\nC compiler: c++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\nc++: Orange/preprocess/_relieff.cpp\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_relieff.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.projection._som' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/projection\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/projection/_som.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/projection/_som.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.widgets.utils._grid_density' extension\ncompiling C++ sources\nC compiler: c++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/widgets\ncreating build/temp.linux-x86_64-3.7/Orange/widgets/utils\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\nc++: Orange/widgets/utils/_grid_density.cpp\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so\nrunning develop\nrunning build_scripts\nrunning egg_info\nrunning build_src\nbuild_src\nbuilding extension \"Orange.classification._simple_tree\" sources\nbuilding extension \"Orange.classification._tree_scorers\" sources\nbuilding extension \"Orange.data._valuecount\" sources\nbuilding extension \"Orange.data._contingency\" sources\nbuilding extension \"Orange.data._io\" sources\nbuilding extension \"Orange.data._variable\" sources\nbuilding extension \"Orange.distance._distance\" sources\nbuilding extension \"Orange.preprocess._discretize\" sources\nbuilding extension \"Orange.preprocess._relieff\" sources\nbuilding extension \"Orange.projection._som\" sources\nbuilding extension \"Orange.widgets.utils._grid_density\" sources\nbuild_src: building npy-pkg config files\ncreating Orange3.egg-info\nwriting Orange3.egg-info/PKG-INFO\nwriting dependency_links to Orange3.egg-info/dependency_links.txt\nwriting entry points to Orange3.egg-info/entry_points.txt\nwriting requirements to Orange3.egg-info/requires.txt\nwriting top-level names to Orange3.egg-info/top_level.txt\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest template 'MANIFEST.in'\nwarning: no files found matching 'README*' under directory 'Orange'\nwarning: no files found matching 'LICENSE' under directory 'Orange'\nwarning: no files found matching '*.basket' under directory 'Orange/datasets'\nwarning: no files found matching '*.js' under directory 'Orange/widgets'\nwarning: no files found matching '*.css' under directory 'Orange/widgets'\nwarning: no files found matching '*.html' under directory 'Orange/widgets'\nwarning: no files found matching 'COPYING'\nadding license file 'LICENSE'\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nrunning build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmpch2c7tpr/tmp\ncreating /tmp/tmpch2c7tpr/tmp/tmpch2c7tpr\ncompile options: '-MMD -MF /tmp/tmpch2c7tpr/file.c.d -c'\ncc: /tmp/tmpch2c7tpr/file.c\ncustomize UnixCCompiler using build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmpvkoldokx/tmp\ncreating /tmp/tmpvkoldokx/tmp/tmpvkoldokx\ncompile options: '-MMD -MF /tmp/tmpvkoldokx/file.c.d -c'\ncc: /tmp/tmpvkoldokx/file.c\ncustomize UnixCCompiler using build_ext\nCreating /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/Orange3.egg-link (link to .)\nAdding Orange3 3.24.0.dev0+b08fc7e to easy-install.pth file\nInstalling orange-canvas script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb\nProcessing dependencies for Orange3==3.24.0.dev0+b08fc7e\nSearching for XlsxWriter==3.2.0\nBest match: XlsxWriter 3.2.0\nAdding XlsxWriter 3.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for xlrd==2.0.1\nBest match: xlrd 2.0.1\nAdding xlrd 2.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for setuptools==59.8.0\nBest match: setuptools 59.8.0\nAdding setuptools 59.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for serverfiles==0.3.1\nBest match: serverfiles 0.3.1\nAdding serverfiles 0.3.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for scipy==1.7.3\nBest match: scipy 1.7.3\nAdding scipy 1.7.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for scikit-learn==1.0.2\nBest match: scikit-learn 1.0.2\nAdding scikit-learn 1.0.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for requests==2.31.0\nBest match: requests 2.31.0\nAdding requests 2.31.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for PyYAML==6.0.1\nBest match: PyYAML 6.0.1\nAdding PyYAML 6.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for python-louvain==0.16\nBest match: python-louvain 0.16\nAdding python-louvain 0.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for pyqtgraph==0.12.4\nBest match: pyqtgraph 0.12.4\nAdding pyqtgraph 0.12.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for pip==24.0\nBest match: pip 24.0\nAdding pip 24.0 to easy-install.pth file\nInstalling pip script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\nInstalling pip3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\nInstalling pip3.10 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for pandas==1.3.5\nBest match: pandas 1.3.5\nAdding pandas 1.3.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for orange-widget-base==4.22.0\nBest match: orange-widget-base 4.22.0\nAdding orange-widget-base 4.22.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for orange-canvas-core==0.1.35\nBest match: orange-canvas-core 0.1.35\nAdding orange-canvas-core 0.1.35 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for openTSNE==1.0.1\nBest match: openTSNE 1.0.1\nAdding openTSNE 1.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for numpy==1.17.5\nBest match: numpy 1.17.5\nAdding numpy 1.17.5 to easy-install.pth file\nInstalling f2py script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\nInstalling f2py3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\nInstalling f2py3.7 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for networkx==2.6.3\nBest match: networkx 2.6.3\nAdding networkx 2.6.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for matplotlib==3.5.3\nBest match: matplotlib 3.5.3\nAdding matplotlib 3.5.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for keyrings.alt==4.2.0\nBest match: keyrings.alt 4.2.0\nAdding keyrings.alt 4.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for keyring==24.1.1\nBest match: keyring 24.1.1\nAdding keyring 24.1.1 to easy-install.pth file\nInstalling keyring script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for joblib==1.3.2\nBest match: joblib 1.3.2\nAdding joblib 1.3.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for docutils==0.20.1\nBest match: docutils 0.20.1\nAdding docutils 0.20.1 to easy-install.pth file\nInstalling docutils script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for chardet==5.2.0\nBest match: chardet 5.2.0\nAdding chardet 5.2.0 to easy-install.pth file\nInstalling chardetect script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for Bottleneck==1.4.0\nBest match: Bottleneck 1.4.0\nAdding Bottleneck 1.4.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for AnyQt==0.2.0\nBest match: AnyQt 0.2.0\nAdding AnyQt 0.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for threadpoolctl==3.1.0\nBest match: threadpoolctl 3.1.0\nAdding threadpoolctl 3.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for charset-normalizer==3.4.1\nBest match: charset-normalizer 3.4.1\nAdding charset-normalizer 3.4.1 to easy-install.pth file\nInstalling normalizer script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for certifi==2024.12.14\nBest match: certifi 2024.12.14\nAdding certifi 2024.12.14 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for urllib3==2.0.7\nBest match: urllib3 2.0.7\nAdding urllib3 2.0.7 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for idna==3.10\nBest match: idna 3.10\nAdding idna 3.10 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for python-dateutil==2.9.0.post0\nBest match: python-dateutil 2.9.0.post0\nAdding python-dateutil 2.9.0.post0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for pytz==2024.2\nBest match: pytz 2024.2\nAdding pytz 2024.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for typing-extensions==4.7.1\nBest match: typing-extensions 4.7.1\nAdding typing-extensions 4.7.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for requests-cache==1.1.1\nBest match: requests-cache 1.1.1\nAdding requests-cache 1.1.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for commonmark==0.9.1\nBest match: commonmark 0.9.1\nAdding commonmark 0.9.1 to easy-install.pth file\nInstalling cmark script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for qasync==0.25.0\nBest match: qasync 0.25.0\nAdding qasync 0.25.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for importlib-metadata==6.7.0\nBest match: importlib-metadata 6.7.0\nAdding importlib-metadata 6.7.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for dictdiffer==0.9.0\nBest match: dictdiffer 0.9.0\nAdding dictdiffer 0.9.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for packaging==24.0\nBest match: packaging 24.0\nAdding packaging 24.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for fonttools==4.38.0\nBest match: fonttools 4.38.0\nAdding fonttools 4.38.0 to easy-install.pth file\nInstalling fonttools script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\nInstalling pyftmerge script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\nInstalling pyftsubset script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\nInstalling ttx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for cycler==0.11.0\nBest match: cycler 0.11.0\nAdding cycler 0.11.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for kiwisolver==1.4.5\nBest match: kiwisolver 1.4.5\nAdding kiwisolver 1.4.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for Pillow==9.5.0\nBest match: Pillow 9.5.0\nAdding Pillow 9.5.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for pyparsing==3.1.4\nBest match: pyparsing 3.1.4\nAdding pyparsing 3.1.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for jaraco.classes==3.2.3\nBest match: jaraco.classes 3.2.3\nAdding jaraco.classes 3.2.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for jeepney==0.8.0\nBest match: jeepney 0.8.0\nAdding jeepney 0.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for importlib-resources==5.12.0\nBest match: importlib-resources 5.12.0\nAdding importlib-resources 5.12.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for SecretStorage==3.3.3\nBest match: SecretStorage 3.3.3\nAdding SecretStorage 3.3.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for six==1.17.0\nBest match: six 1.17.0\nAdding six 1.17.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for attrs==24.2.0\nBest match: attrs 24.2.0\nAdding attrs 24.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for url-normalize==1.4.3\nBest match: url-normalize 1.4.3\nAdding url-normalize 1.4.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for platformdirs==4.0.0\nBest match: platformdirs 4.0.0\nAdding platformdirs 4.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for cattrs==23.1.2\nBest match: cattrs 23.1.2\nAdding cattrs 23.1.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for zipp==3.15.0\nBest match: zipp 3.15.0\nAdding zipp 3.15.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for more-itertools==9.1.0\nBest match: more-itertools 9.1.0\nAdding more-itertools 9.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for cryptography==44.0.0\nBest match: cryptography 44.0.0\nAdding cryptography 44.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for exceptiongroup==1.2.2\nBest match: exceptiongroup 1.2.2\nAdding exceptiongroup 1.2.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for cffi==1.15.1\nBest match: cffi 1.15.1\nAdding cffi 1.15.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nSearching for pycparser==2.21\nBest match: pycparser 2.21\nAdding pycparser 2.21 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages\nFinished processing dependencies for Orange3==3.24.0.dev0+b08fc7e\nVerifying Orange installation...\n✅ Orange installation successful!\nSuccessfully installed orange using Python 3.7\n",
    "setup_res_stderr": "Using CPython 3.7.9\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 18 packages in 153ms\nPrepared 18 packages in 6ms\nInstalled 18 packages in 523ms\n + cython==0.29.37\n + exceptiongroup==1.2.2\n + importlib-metadata==6.7.0\n + iniconfig==2.0.0\n + numpy==1.17.5\n + packaging==24.0\n + pluggy==1.2.0\n + pyqt5==5.15.10\n + pyqt5-qt5==5.15.16\n + pyqt5-sip==12.13.0\n + pyqtwebengine==5.15.6\n + pyqtwebengine-qt5==5.15.16\n + pytest==7.4.4\n + setuptools==59.8.0\n + tomli==2.0.1\n + typing-extensions==4.7.1\n + wheel==0.42.0\n + zipp==3.15.0\nResolved 38 packages in 206ms\nInstalled 33 packages in 506ms\n + bottleneck==1.4.0\n + certifi==2024.12.14\n + cffi==1.15.1\n + chardet==5.2.0\n + charset-normalizer==3.4.1\n + cryptography==44.0.0\n + idna==3.10\n + importlib-resources==5.12.0\n + jaraco-classes==3.2.3\n + jeepney==0.8.0\n + joblib==1.3.2\n + keyring==24.1.1\n + keyrings-alt==4.2.0\n + more-itertools==9.1.0\n + networkx==2.6.3\n + opentsne==1.0.1\n + pandas==1.3.5\n + pip==24.0\n + pycparser==2.21\n + python-dateutil==2.9.0.post0\n + python-louvain==0.16\n + pytz==2024.2\n + pyyaml==6.0.1\n + requests==2.31.0\n + scikit-learn==1.0.2\n + scipy==1.7.3\n + secretstorage==3.3.3\n + serverfiles==0.3.1\n + six==1.17.0\n + threadpoolctl==3.1.0\n + urllib3==2.0.7\n + xlrd==2.0.1\n + xlsxwriter==3.2.0\nResolved 34 packages in 175ms\nInstalled 19 packages in 60ms\n + anyqt==0.2.0\n + attrs==24.2.0\n + cattrs==23.1.2\n + commonmark==0.9.1\n + cycler==0.11.0\n + dictdiffer==0.9.0\n + docutils==0.20.1\n + fonttools==4.38.0\n + kiwisolver==1.4.5\n + matplotlib==3.5.3\n + orange-canvas-core==0.1.35\n + orange-widget-base==4.22.0\n + pillow==9.5.0\n + platformdirs==4.0.0\n + pyparsing==3.1.4\n + pyqtgraph==0.12.4\n + qasync==0.25.0\n + requests-cache==1.1.1\n + url-normalize==1.4.3\nResolved 2 packages in 3ms\nInstalled 2 packages in 7ms\n + psycopg2==2.9.9\n + pymssql==2.3.2\nAudited 1 package in 1ms\nAudited 2 packages in 3ms\nOrange/classification/_simple_tree.c: In function ‘gain_ratio_c’:\nOrange/classification/_simple_tree.c:63:9: warning: implicit declaration of function ‘qsort_r’; did you mean ‘qsort’? [-Wimplicit-function-declaration]\n   63 |         qsort_r(base, nel, size, compar, thunk)\n      |         ^~~~~~~\nOrange/classification/_simple_tree.c:147:9: note: in expansion of macro ‘QSORT_R_FUNC’\n  147 |         QSORT_R_FUNC(examples, size, sizeof(struct Example), (void*) &compar_attr, compar_examples);\n      |         ^~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/classification/_tree_scorers.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_2find_threshold_entropy’:\nOrange/classification/_tree_scorers.c:3115:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 3115 |   __pyx_t_7 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_6find_threshold_MSE’:\nOrange/classification/_tree_scorers.c:4619:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 4619 |   __pyx_t_1 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_8find_binarization_MSE’:\nOrange/classification/_tree_scorers.c:5396:43: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5396 |           for (__pyx_t_22 = 0; __pyx_t_22 < __pyx_t_21; __pyx_t_22+=1) {\n      |                                           ^\nOrange/classification/_tree_scorers.c:5517:39: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5517 |           __pyx_t_27 = ((__pyx_v_left >= __pyx_v_min_leaf) != 0);\n      |                                       ^~\nOrange/classification/_tree_scorers.c:5523:53: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5523 |           __pyx_t_27 = (((__pyx_v_N - __pyx_v_left) >= __pyx_v_min_leaf) != 0);\n      |                                                     ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_14compute_predictions_csr’:\nOrange/classification/_tree_scorers.c:7051:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7051 |         for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_16compute_predictions_csc’:\nOrange/classification/_tree_scorers.c:7741:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7741 |         for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c:7801:143: warning: comparison of integer expressions of different signedness: ‘__pyx_t_5numpy_int32_t’ {aka ‘int’} and ‘unsigned int’ [-Wsign-compare]\n 7801 |               __pyx_t_17 = (((*((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ (__pyx_v_indices.data + __pyx_t_18 * __pyx_v_indices.strides[0]) ))) != __pyx_v_i) != 0);\n      |                                                                                                                                               ^~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_valuecount.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_contingency.c:598:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/data/_io.c: In function ‘__pyx_pf_6Orange_4data_3_io_sparse_read_float.constprop’:\nOrange/data/_io.c:3881:24: warning: ‘__pyx_v_decs’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3881 |           __pyx_v_decs = (__pyx_v_decs / 10.0);\n      |           ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:4070:24: warning: ‘__pyx_v_value’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4070 |           __pyx_t_10 = PyFloat_FromDouble(__pyx_v_value); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 221, __pyx_L5_error)\n      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:480:40: warning: ‘__pyx_v_attr_index’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n  480 |   #define PyInt_FromLong               PyLong_FromLong\n      |                                        ^~~~~~~~~~~~~~~\nOrange/data/_io.c:1301:7: note: ‘__pyx_v_attr_index’ was declared here\n 1301 |   int __pyx_v_attr_index;\n      |       ^~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:2935:25: warning: ‘__pyx_v_atomp’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2935 |           __pyx_v_atomp = (__pyx_v_atomp + 1);\n      |           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/distance/_distance.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_discretize.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_relieff.cpp:581:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/projection/_som.c:598:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_5update_hex’:\nOrange/projection/_som.c:3655:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3655 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3654:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3654 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_7get_winners_sparse’:\nOrange/projection/_som.c:4547:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4547 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_38, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_39, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4114:7: note: ‘__pyx_v_win_y’ was declared here\n 4114 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4536:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4536 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_36, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_37, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4113:7: note: ‘__pyx_v_win_x’ was declared here\n 4113 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_8update_sparse.constprop’:\nOrange/projection/_som.c:4746:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4746 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4745:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4745 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_3update’:\nOrange/projection/_som.c:3141:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3141 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_1get_winners’:\nOrange/projection/_som.c:2964:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2964 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_28, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2665:7: note: ‘__pyx_v_win_y’ was declared here\n 2665 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:2953:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2953 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_25, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2664:7: note: ‘__pyx_v_win_x’ was declared here\n 2664 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_10update_sparse_hex.constprop’:\nOrange/projection/_som.c:5515:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5515 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:5514:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5514 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/setuptools/command/install.py:37: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n  setuptools.SetuptoolsDeprecationWarning,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/setuptools/command/easy_install.py:159: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools.\n  EasyInstallDeprecationWarning,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/setuptools/command/install.py:37: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n  setuptools.SetuptoolsDeprecationWarning,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb/.venv/lib/python3.7/site-packages/setuptools/command/egg_info.py:628: SetuptoolsDeprecationWarning: Custom 'build_py' does not implement 'get_data_files_without_manifest'.\nPlease extend command classes from setuptools instead of distutils.\n  SetuptoolsDeprecationWarning\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb\ncollected 4 items\n\nr2e_tests/test_1.py ....                                                 [100%]\n\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestTableFilters::test_guess_data_type_continuous\nPASSED r2e_tests/test_1.py::TestTableFilters::test_guess_data_type_discrete\nPASSED r2e_tests/test_1.py::TestTableFilters::test_guess_data_type_string\nPASSED r2e_tests/test_1.py::TestTableFilters::test_guess_data_type_time\n============================== 4 passed in 1.03s ===============================\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_b08fc7e897d38f85d19823221e420a8d6d7beddb\ncollected 4 items\n\nr2e_tests/test_1.py ...F                                                 [100%]\n\n=================================== FAILURES ===================================\n__________________ TestTableFilters.test_guess_data_type_time __________________\n\nself = <r2e_tests.test_1.TestTableFilters testMethod=test_guess_data_type_time>\n\n    def test_guess_data_type_time(self):\n        in_values = [\"2019-10-10\", \"2019-10-10\", \"2019-10-10\", \"2019-10-01\"]\n        valuemap, _, coltype = guess_data_type(in_values)\n>       self.assertEqual(TimeVariable, coltype)\nE       AssertionError: <class 'Orange.data.variable.TimeVariable'> != <class 'Orange.data.variable.DiscreteVariable'>\n\nr2e_tests/test_1.py:76: AssertionError\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestTableFilters::test_guess_data_type_continuous\nPASSED r2e_tests/test_1.py::TestTableFilters::test_guess_data_type_discrete\nPASSED r2e_tests/test_1.py::TestTableFilters::test_guess_data_type_string\nFAILED r2e_tests/test_1.py::TestTableFilters::test_guess_data_type_time - Ass...\n========================= 1 failed, 3 passed in 1.11s ==========================\n",
    "old_commit_res_stderr": ""
}
Orange/data/io.py Orange/data/tests/test_io.py Orange/data/variable.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Incorrect Data Type Inference for Date and Time Variables

**Description:**
When passing date strings to the data type guessing function, the system incorrectly identifies them as discrete variables instead of time variables. This misclassification leads to improper handling of temporal data.

**Example Buggy Code:**
```python
in_values = ["2019-10-10", "2019-10-10", "2019-10-10", "2019-10-01"]
valuemap, _, coltype = guess_data_type(in_values)
```
In this example, the `coltype` is expected to be `TimeVariable`, but it is incorrectly set to `DiscreteVariable`.

**Expected Behavior:**
Date and time strings should be recognized and classified as `TimeVariable`, allowing the system to handle temporal data accurately.

**Actual Behavior:**
Date and time strings are misclassified as `DiscreteVariable`, resulting in incorrect data type handling and potential issues in data processing workflows.
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### Orange/data/io.py
import contextlib
import csv
import locale
import pickle
import re
import subprocess
import sys
import warnings

from ast import literal_eval
from collections import OrderedDict, Counter, defaultdict
from functools import lru_cache
from importlib import import_module
from itertools import chain, repeat
from math import isnan

from os import path, remove
from tempfile import NamedTemporaryFile
from urllib.parse import urlparse, urlsplit, urlunsplit, unquote as urlunquote
from urllib.request import urlopen, Request
from fnmatch import fnmatch
from glob import glob

import numpy as np
from chardet.universaldetector import UniversalDetector

import xlrd
import xlsxwriter

from Orange.data import (
    _io, is_discrete_values, MISSING_VALUES, Table, Domain, Variable,
    DiscreteVariable, StringVariable, ContinuousVariable, TimeVariable,
)
from Orange.util import Registry, flatten, namegen


# Support values longer than 128K (i.e. text contents features)
csv.field_size_limit(100*1024*1024)

__all__ = ["Flags", "FileFormat"]


_IDENTITY = lambda i: i


class Compression:
    """Supported compression extensions"""
    GZIP = '.gz'
    BZIP2 = '.bz2'
    XZ = '.xz'
    all = (GZIP, BZIP2, XZ)


def open_compressed(filename, *args, _open=open, **kwargs):
    """Return seamlessly decompressed open file handle for `filename`"""
    if isinstance(filename, str):
        if filename.endswith(Compression.GZIP):
            from gzip import open as _open
        elif filename.endswith(Compression.BZIP2):
            from bz2 import open as _open
        elif filename.endswith(Compression.XZ):
            from lzma import open as _open
        return _open(filename, *args, **kwargs)
    # Else already a file, just pass it through
    return filename


def detect_encoding(filename):
    """
    Detect encoding of `filename`, which can be a ``str`` filename, a
    ``file``-like object, or ``bytes``.
    """
    # Try with Unix file utility first because it's faster (~10ms vs 100ms)
    if isinstance(filename, str) and not filename.endswith(Compression.all):
        try:
            with subprocess.Popen(('file', '--brief', '--mime-encoding', filename),
                                  stdout=subprocess.PIPE) as process:
                process.wait()
                if process.returncode == 0:
                    encoding = process.stdout.read().strip()
                    # file only supports these encodings; for others it says
                    # unknown-8bit or binary. So we give chardet a chance to do
                    # better
                    if encoding in (b'utf-8', b'us-ascii', b'iso-8859-1',
                                    b'utf-7', b'utf-16le', b'utf-16be', b'ebcdic'):
                        return encoding.decode('us-ascii')
        except OSError:
            pass  # windoze

    # file not available or unable to guess the encoding, have chardet do it
    detector = UniversalDetector()
    # We examine only first N 4kB blocks of file because chardet is really slow
    MAX_BYTES = 4*1024*12

    def _from_file(f):
        detector.feed(f.read(MAX_BYTES))
        detector.close()
        return (detector.result.get('encoding')
                if detector.result.get('confidence', 0) >= .85 else
                'utf-8')

    if isinstance(filename, str):
        with open_compressed(filename, 'rb') as f:
            return _from_file(f)
    elif isinstance(filename, bytes):
        detector.feed(filename[:MAX_BYTES])
        detector.close()
        return detector.result.get('encoding')
    elif hasattr(filename, 'encoding'):
        return filename.encoding
    else:  # assume file-like object that you can iter through
        return _from_file(filename)


__isnastr = np.frompyfunc(
    {v for v in MISSING_VALUES if isinstance(v, str)}.__contains__, 1, 1)

# wrapper for __isnastr with proper default out dtype
def isnastr(arr, out=None):
    """
    Given an (object) array of string values, return a boolean mask array
    that is True where the `arr` contains one of the string constants
    considered as N/A.

    Parameters
    ----------
    arr : np.ndarray
        Input array of strings.
    out : Optional[np.ndarray]
        Optional output array of the same shape as arr

    Returns
    -------
    mask : np.ndarray
    """
    arr = np.asarray(arr)
    if out is None and arr.shape != ():
        out = np.empty_like(arr, dtype=bool)
    return __isnastr(arr, out=out)


def guess_data_type(orig_values, namask=None):
    """
    Use heuristics to guess data type.
    """
    valuemap, values = None, orig_values
    is_discrete = is_discrete_values(orig_values)
    if is_discrete:
        valuemap = sorted(is_discrete)
        coltype = DiscreteVariable
    else:
        # try to parse as float
        orig_values = np.asarray(orig_values)
        if namask is None:
            namask = isnastr(orig_values)
        values = np.empty_like(orig_values, dtype=float)
        values[namask] = np.nan
        try:
            np.copyto(values, orig_values, where=~namask, casting="unsafe")
        except ValueError:
            tvar = TimeVariable('_')
            try:
                values[~namask] = [tvar.parse(i) for i in orig_values[~namask]]
            except ValueError:
                coltype = StringVariable
                # return original_values
                values = orig_values
            else:
                coltype = TimeVariable
        else:
            coltype = ContinuousVariable
    return valuemap, values, coltype


def sanitize_variable(valuemap, values, orig_values, coltype, coltype_kwargs,
                      name=None):
    assert issubclass(coltype, Variable)

    def get_number_of_decimals(values):
        len_ = len
        ndecimals = max((len_(value) - value.find(".")
                         for value in values if "." in value),
                        default=1)
        return ndecimals - 1

    if issubclass(coltype, DiscreteVariable) and valuemap is not None:
        coltype_kwargs.update(values=valuemap)

    var = coltype.make(name, **coltype_kwargs)

    if isinstance(var, DiscreteVariable):
        # Map discrete data to 'ints' (or at least what passes as int around
        # here)
        mapping = defaultdict(
            lambda: np.nan,
            {val: i for i, val in enumerate(var.values)},
        )
        mapping[""] = np.nan
        mapvalues_ = np.frompyfunc(mapping.__getitem__, 1, 1)

        def mapvalues(arr):
            arr = np.asarray(arr, dtype=object)
            return mapvalues_(arr, out=np.empty_like(arr, dtype=float))
        values = mapvalues(orig_values)

    if coltype is StringVariable:
        values = orig_values

    # ContinuousVariable.number_of_decimals is supposed to be handled by
    # ContinuousVariable.to_val. In the interest of speed, the reader bypasses
    # it, so we set the number of decimals here.
    # The number of decimals is increased if not set manually (in which case
    # var.adjust_decimals would be 0).
    if isinstance(var, ContinuousVariable) and var.adjust_decimals:
        ndecimals = get_number_of_decimals(orig_values)
        if var.adjust_decimals == 2 or ndecimals > var.number_of_decimals:
            var.number_of_decimals = ndecimals
            var.adjust_decimals = 1

    if isinstance(var, TimeVariable) or coltype is TimeVariable:
        # Re-parse the values because only now after coltype.make call
        # above, variable var is the correct one
        _var = var if isinstance(var, TimeVariable) else TimeVariable('_')
        values = [_var.parse(i) for i in orig_values]

    return values, var


class Flags:
    """Parser for column flags (i.e. third header row)"""
    DELIMITER = ' '
    _RE_SPLIT = re.compile(r'(?<!\\)' + DELIMITER).split
    _RE_ATTR_UNQUOTED_STR = re.compile(r'^[a-zA-Z_]').match
    ALL = OrderedDict((
        ('class', 'c'),
        ('ignore', 'i'),
        ('meta', 'm'),
        ('weight', 'w'),
        ('.+?=.*?', ''),  # general key=value attributes
    ))
    _RE_ALL = re.compile(r'^({})$'.format('|'.join(filter(None, flatten(ALL.items())))))

    def __init__(self, flags):
        for v in filter(None, self.ALL.values()):
            setattr(self, v, False)
        self.attributes = {}
        for flag in flags or []:
            flag = flag.strip()
            if self._RE_ALL.match(flag):
                if '=' in flag:
                    k, v = flag.split('=', 1)
                    if not Flags._RE_ATTR_UNQUOTED_STR(v):
                        try:
                            v = literal_eval(v)
                        except SyntaxError:
                            # If parsing failed, treat value as string
                            pass
                    self.attributes[k] = v
                else:
                    setattr(self, flag, True)
                    setattr(self, self.ALL.get(flag, ''), True)
            elif flag:
                warnings.warn('Invalid attribute flag \'{}\''.format(flag))

    @staticmethod
    def join(iterable, *args):
        return Flags.DELIMITER.join(i.strip().replace(Flags.DELIMITER, '\\' + Flags.DELIMITER)
                                    for i in chain(iterable, args)).lstrip()

    @staticmethod
    def split(s):
        return [i.replace('\\' + Flags.DELIMITER, Flags.DELIMITER)
                for i in Flags._RE_SPLIT(s)]


# Matches discrete specification where all the values are listed, space-separated
_RE_DISCRETE_LIST = re.compile(r'^\s*[^\s]+(\s[^\s]+)+\s*$')
_RE_TYPES = re.compile(r'^\s*({}|{}|)\s*$'.format(
    _RE_DISCRETE_LIST.pattern,
    '|'.join(flatten(getattr(vartype, 'TYPE_HEADERS') for vartype in Variable.registry.values()))
    ))
_RE_FLAGS = re.compile(r'^\s*( |{}|)*\s*$'.format(
    '|'.join(flatten(filter(None, i) for i in Flags.ALL.items()))
    ))


class FileFormatMeta(Registry):

    def __new__(cls, name, bases, attrs):
        newcls = super().__new__(cls, name, bases, attrs)

        # Optionally add compressed versions of extensions as supported
        if getattr(newcls, 'SUPPORT_COMPRESSED', False):
            new_extensions = list(getattr(newcls, 'EXTENSIONS', ()))
            for compression in Compression.all:
                for ext in newcls.EXTENSIONS:
                    new_extensions.append(ext + compression)
                if sys.platform in ('darwin', 'win32'):
                    # OSX file dialog doesn't support filtering on double
                    # extensions (e.g. .csv.gz)
                    # https://bugreports.qt.io/browse/QTBUG-38303
                    # This is just here for OWFile that gets QFileDialog
                    # filters from FileFormat.readers.keys()
                    # EDIT: Windows exhibit similar problems:
                    # while .tab.gz works, .tab.xz and .tab.bz2 do not!
                    new_extensions.append(compression)
            newcls.EXTENSIONS = tuple(new_extensions)

        return newcls

    @property
    def formats(cls):
        return cls.registry.values()

    @lru_cache(5)
    def _ext_to_attr_if_attr2(cls, attr, attr2):
        """
        Return ``{ext: `attr`, ...}`` dict if ``cls`` has `attr2`.
        If `attr` is '', return ``{ext: cls, ...}`` instead.

        If there are multiple formats for an extension, return a format
        with the lowest priority.
        """
        formats = OrderedDict()
        for format in sorted(cls.registry.values(), key=lambda x: x.PRIORITY):
            if not hasattr(format, attr2):
                continue
            for ext in getattr(format, 'EXTENSIONS', []):
                # Only adds if not yet registered
                formats.setdefault(ext, getattr(format, attr, format))
        return formats

    @property
    def names(cls):
        return cls._ext_to_attr_if_attr2('DESCRIPTION', '__class__')

    @property
    def writers(cls):
        return cls._ext_to_attr_if_attr2('', 'write_file')

    @property
    def readers(cls):
        return cls._ext_to_attr_if_attr2('', 'read')

    @property
    def img_writers(cls):
        warnings.warn(
            f"'{__name__}.FileFormat.img_writers' is no longer used and "
            "will be removed. Please use "
            "'Orange.widgets.io.FileFormat.img_writers' instead.",
            DeprecationWarning, stacklevel=2
        )
        return cls._ext_to_attr_if_attr2('', 'write_image')

    @property
    def graph_writers(cls):
        return cls._ext_to_attr_if_attr2('', 'write_graph')


class FileFormat(metaclass=FileFormatMeta):
    """
    Subclasses set the following attributes and override the following methods:

        EXTENSIONS = ('.ext1', '.ext2', ...)
        DESCRIPTION = 'human-readable file format description'
        SUPPORT_COMPRESSED = False
        SUPPORT_SPARSE_DATA = False

        def read(self):
            ...  # load headers, data, ...
            return self.data_table(data, headers)

        @classmethod
        def write_file(cls, filename, data):
            ...
            self.write_headers(writer.write, data)
            writer.writerows(data)

    Wrapper FileFormat.data_table() returns Orange.data.Table from `data`
    iterable (list (rows) of lists of values (cols)).
    """

    # Priority when multiple formats support the same extension. Also
    # the sort order in file open/save combo boxes. Lower is better.
    PRIORITY = 10000
    OPTIONAL_TYPE_ANNOTATIONS = False

    def __init__(self, filename):
        """
        Parameters
        ----------
        filename : str
            name of the file to open
        """
        self.filename = filename
        self.sheet = None

    @property
    def sheets(self):
        """FileFormats with a notion of sheets should override this property
        to return a list of sheet names in the file.

        Returns
        -------
        a list of sheet names
        """
        return ()

    def select_sheet(self, sheet):
        """Select sheet to be read

        Parameters
        ----------
        sheet : str
            sheet name
        """
        self.sheet = sheet

    @classmethod
    def get_reader(cls, filename):
        """Return reader instance that can be used to read the file

        Parameters
        ----------
        filename : str

        Returns
        -------
        FileFormat
        """
        for ext, reader in cls.readers.items():
            # Skip ambiguous, invalid compression-only extensions added on OSX
            if ext in Compression.all:
                continue
            if fnmatch(path.basename(filename), '*' + ext):
                return reader(filename)

        raise IOError('No readers for file "{}"'.format(filename))

    @classmethod
    def write(cls, filename, data, with_annotations=True):
        if cls.OPTIONAL_TYPE_ANNOTATIONS:
            return cls.write_file(filename, data, with_annotations)
        else:
            return cls.write_file(filename, data)

    @classmethod
    def write_table_metadata(cls, filename, data):

        def write_file(fn):
            if all(isinstance(key, str) and isinstance(value, str)
                   for key, value in data.attributes.items()):
                with open(fn, 'w', encoding='utf-8') as f:
                    f.write("\n".join("{}: {}".format(*kv)
                                      for kv in data.attributes.items()))
            else:
                with open(fn, 'wb') as f:
                    pickle.dump(data.attributes, f, pickle.HIGHEST_PROTOCOL)

        if isinstance(filename, str):
            metafile = filename + '.metadata'
            if getattr(data, 'attributes', None):
                write_file(metafile)
            elif path.exists(metafile):
                remove(metafile)

    @classmethod
    def set_table_metadata(cls, filename, table):
        # pylint: disable=bare-except
        if isinstance(filename, str) and path.exists(filename + '.metadata'):
            try:
                with open(filename + '.metadata', 'rb') as f:
                    table.attributes = pickle.load(f)
            # Unpickling throws different exceptions, not just UnpickleError
            except:
                with open(filename + '.metadata', encoding='utf-8') as f:
                    table.attributes = OrderedDict(
                        (k.strip(), v.strip())
                        for k, v in (line.split(":", 1)
                                     for line in f.readlines()))

    @classmethod
    def locate(cls, filename, search_dirs=('.',)):
        """Locate a file with given filename that can be opened by one
        of the available readers.

        Parameters
        ----------
        filename : str
        search_dirs : Iterable[str]

        Returns
        -------
        str
            Absolute path to the file
        """
        if path.exists(filename):
            return filename

        for directory in search_dirs:
            absolute_filename = path.join(directory, filename)
            if path.exists(absolute_filename):
                break
            for ext in cls.readers:
                if fnmatch(path.basename(filename), '*' + ext):
                    break
                # glob uses fnmatch internally
                matching_files = glob(absolute_filename + ext)
                if matching_files:
                    absolute_filename = matching_files[0]
                    break
            if path.exists(absolute_filename):
                break
        else:
            absolute_filename = ""

        if not path.exists(absolute_filename):
            raise IOError('File "{}" was not found.'.format(filename))

        return absolute_filename

    @staticmethod
    def open(filename, *args, **kwargs):
        """
        Format handlers can use this method instead of the builtin ``open()``
        to transparently (de)compress files if requested (according to
        `filename` extension). Set ``SUPPORT_COMPRESSED=True`` if you use this.
        """
        return open_compressed(filename, *args, **kwargs)

    @staticmethod
    def parse_headers(data):
        """Return (header rows, rest of data) as discerned from `data`"""

        def is_number(item):
            try:
                float(item)
            except ValueError:
                return False
            return True
        # Second row items are type identifiers
        def header_test2(items):
            return all(map(_RE_TYPES.match, items))
        # Third row items are flags and column attributes (attr=value)
        def header_test3(items):
            return all(map(_RE_FLAGS.match, items))

        data = iter(data)
        header_rows = []

        # Try to parse a three-line header
        lines = []
        try:
            lines.append(list(next(data)))
            lines.append(list(next(data)))
            lines.append(list(next(data)))
        except StopIteration:
            lines, data = [], chain(lines, data)
        if lines:
            l1, l2, l3 = lines
            # Three-line header if line 2 & 3 match (1st line can be anything)
            if header_test2(l2) and header_test3(l3):
                header_rows = [l1, l2, l3]
            else:
                lines, data = [], chain((l1, l2, l3), data)

        # Try to parse a single-line header
        if not header_rows:
            try:
                lines.append(list(next(data)))
            except StopIteration:
                pass
            if lines:
                # Header if none of the values in line 1 parses as a number
                if not all(is_number(i) for i in lines[0]):
                    header_rows = [lines[0]]
                else:
                    data = chain(lines, data)

        return header_rows, data

    @classmethod
    def data_table(cls, data, headers=None):
        """
        Return Orange.data.Table given rows of `headers` (iterable of iterable)
        and rows of `data` (iterable of iterable).

        Basically, the idea of subclasses is to produce those two iterables,
        however they might.

        If `headers` is not provided, the header rows are extracted from `data`,
        assuming they precede it.
        """
        if not headers:
            headers, data = cls.parse_headers(data)

        # Consider various header types (single-row, two-row, three-row, none)
        if len(headers) == 3:
            names, types, flags = map(list, headers)
        else:
            if len(headers) == 1:
                HEADER1_FLAG_SEP = '#'
                # First row format either:
                #   1) delimited column names
                #   2) -||- with type and flags prepended, separated by #,
                #      e.g. d#sex,c#age,cC#IQ
                _flags, names = zip(*[i.split(HEADER1_FLAG_SEP, 1)
                                      if HEADER1_FLAG_SEP in i else ('', i)
                                      for i in headers[0]]
                                   )
                names = list(names)
            elif len(headers) == 2:
                names, _flags = map(list, headers)
            else:
                # Use heuristics for everything
                names, _flags = [], []
            types = [''.join(filter(str.isupper, flag)).lower() for flag in _flags]
            flags = [Flags.join(filter(str.islower, flag)) for flag in _flags]

        # Determine maximum row length
        rowlen = max(map(len, (names, types, flags)))

        strip = False

        def _equal_length(lst):
            nonlocal strip
            if len(lst) > rowlen > 0:
                lst = lst[:rowlen]
                strip = True
            elif len(lst) < rowlen:
                lst.extend(['']*(rowlen - len(lst)))
            return lst

        # Ensure all data is of equal width in a column-contiguous array
        data = [_equal_length([s.strip() for s in row])
                for row in data if any(row)]
        data = np.array(data, dtype=object, order='F')

        if strip:
            warnings.warn("Columns with no headers were removed.")

        # Data may actually be longer than headers were
        try:
            rowlen = data.shape[1]
        except IndexError:
            pass
        else:
            for lst in (names, types, flags):
                _equal_length(lst)

        NAMEGEN = namegen('Feature ', 1)
        Xcols, attrs = [], []
        Mcols, metas = [], []
        Ycols, clses = [], []
        Wcols = []

        # Rename variables if necessary
        # Reusing across files still works if both files have same duplicates
        name_counts = Counter(names)
        del name_counts[""]
        if len(name_counts) != len(names) and name_counts:
            uses = {name: 0 for name, count in name_counts.items() if count > 1}
            for i, name in enumerate(names):
                if name in uses:
                    uses[name] += 1
                    names[i] = "{}_{}".format(name, uses[name])

        namask = np.empty(data.shape[0], dtype=bool)
        # Iterate through the columns
        for col in range(rowlen):
            flag = Flags(Flags.split(flags[col]))
            if flag.i:
                continue

            type_flag = types and types[col].strip()
            try:
                orig_values = data[:, col]
            except IndexError:
                orig_values = np.array([], dtype=object)

            namask = isnastr(orig_values, out=namask)

            coltype_kwargs = {}
            valuemap = None
            values = orig_values

            if type_flag in StringVariable.TYPE_HEADERS:
                coltype = StringVariable
                values = orig_values
            elif type_flag in ContinuousVariable.TYPE_HEADERS:
                coltype = ContinuousVariable
                values = np.empty(data.shape[0], dtype=float)
                try:
                    np.copyto(values, orig_values, casting="unsafe",
                              where=~namask)
                    values[namask] = np.nan
                except ValueError:
                    for row, num in enumerate(orig_values):
                        if not isnastr(num):
                            try:
                                float(num)
                            except ValueError:
                                break
                    raise ValueError('Non-continuous value in (1-based) '
                                     'line {}, column {}'.format(row + len(headers) + 1,
                                                                 col + 1))

            elif type_flag in TimeVariable.TYPE_HEADERS:
                coltype = TimeVariable
                values = np.where(namask, "", orig_values)
            elif (type_flag in DiscreteVariable.TYPE_HEADERS or
                  _RE_DISCRETE_LIST.match(type_flag)):
                coltype = DiscreteVariable
                orig_values = values = np.where(namask, "", orig_values)
                if _RE_DISCRETE_LIST.match(type_flag):
                    valuemap = Flags.split(type_flag)
                    coltype_kwargs.update(ordered=True)
                else:
                    valuemap = sorted(set(orig_values) - {""})
            else:
                # No known type specified, use heuristics
                valuemap, values, coltype = guess_data_type(orig_values, namask)

            if flag.m or coltype is StringVariable:
                append_to = (Mcols, metas)
            elif flag.w:
                append_to = (Wcols, None)
            elif flag.c:
                append_to = (Ycols, clses)
            else:
                append_to = (Xcols, attrs)

            cols, domain_vars = append_to

            if domain_vars is not None:
                var_name = names and names[col]
                if not var_name:
                    var_name = next(NAMEGEN)

                values, var = sanitize_variable(
                    valuemap, values, orig_values, coltype, coltype_kwargs,
                    name=var_name)
            else:
                var = None
            if domain_vars is not None:
                var.attributes.update(flag.attributes)
                domain_vars.append(var)

            if isinstance(values, np.ndarray) and not values.flags.owndata:
                values = values.copy()  # might view `data` (string columns)
            cols.append(values)

            try:
                # allow gc to reclaim memory used by string values
                data[:, col] = None
            except IndexError:
                pass

        domain = Domain(attrs, clses, metas)

        if not data.size:
            return Table.from_domain(domain, 0)

        X = Y = M = W = None
        if Xcols:
            X = np.c_[tuple(Xcols)]
            assert X.dtype == np.float_
        else:
            X = np.empty((data.shape[0], 0), dtype=np.float_)
        if Ycols:
            Y = np.c_[tuple(Ycols)]
            assert Y.dtype == np.float_
        if Mcols:
            M = np.c_[tuple(Mcols)].astype(object)
        if Wcols:
            W = np.c_[tuple(Wcols)].astype(float)

        table = Table.from_numpy(domain, X, Y, M, W)
        return table

    @staticmethod
    def header_names(data):
        return ['weights'] * data.has_weights() + \
               [v.name for v in chain(data.domain.attributes,
                                      data.domain.class_vars,
                                      data.domain.metas)]

    @staticmethod
    def header_types(data):
        def _vartype(var):
            if var.is_continuous or var.is_string:
                return var.TYPE_HEADERS[0]
            elif var.is_discrete:
                return Flags.join(var.values) if var.ordered else var.TYPE_HEADERS[0]
            raise NotImplementedError
        return ['continuous'] * data.has_weights() + \
               [_vartype(v) for v in chain(data.domain.attributes,
                                           data.domain.class_vars,
                                           data.domain.metas)]

    @staticmethod
    def header_flags(data):
        return list(chain(['weight'] * data.has_weights(),
                          (Flags.join([flag], *('{}={}'.format(*a)
                                                for a in sorted(var.attributes.items())))
                           for flag, var in chain(zip(repeat(''), data.domain.attributes),
                                                  zip(repeat('class'), data.domain.class_vars),
                                                  zip(repeat('meta'), data.domain.metas)))))

    @classmethod
    def write_headers(cls, write, data, with_annotations=True):
        """`write` is a callback that accepts an iterable"""
        write(cls.header_names(data))
        if with_annotations:
            write(cls.header_types(data))
            write(cls.header_flags(data))

    @classmethod
    def formatter(cls, var):
        # type: (Variable) -> Callable[[Variable], Any]
        # Return a column 'formatter' function. The function must return
        # something that `write` knows how to write
        if var.is_time:
            return var.repr_val
        elif var.is_continuous:
            return lambda value: "" if isnan(value) else value
        elif var.is_discrete:
            return lambda value: "" if isnan(value) else var.values[int(value)]
        elif var.is_string:
            return lambda value: value
        else:
            return var.repr_val

    @classmethod
    def write_data(cls, write, data):
        """`write` is a callback that accepts an iterable"""
        vars = list(chain((ContinuousVariable('_w'),) if data.has_weights() else (),
                          data.domain.attributes,
                          data.domain.class_vars,
                          data.domain.metas))

        formatters = [cls.formatter(v) for v in vars]
        for row in zip(data.W if data.W.ndim > 1 else data.W[:, np.newaxis],
                       data.X,
                       data.Y if data.Y.ndim > 1 else data.Y[:, np.newaxis],
                       data.metas):
            write([fmt(v) for fmt, v in zip(formatters, flatten(row))])

    @classmethod
    def qualified_name(cls):
        return cls.__module__ + '.' + cls.__name__


def class_from_qualified_name(format_name):
    """ File format class from qualified name. """
    elements = format_name.split(".")
    m = import_module(".".join(elements[:-1]))
    return getattr(m, elements[-1])


class CSVReader(FileFormat):
    """Reader for comma separated files"""

    EXTENSIONS = ('.csv',)
    DESCRIPTION = 'Comma-separated values'
    DELIMITERS = ',;:\t$ '
    SUPPORT_COMPRESSED = True
    SUPPORT_SPARSE_DATA = False
    PRIORITY = 20
    OPTIONAL_TYPE_ANNOTATIONS = True

    def read(self):
        for encoding in (lambda: ('us-ascii', None),                 # fast
                         lambda: (detect_encoding(self.filename), None),  # precise
                         lambda: (locale.getpreferredencoding(False), None),
                         lambda: (sys.getdefaultencoding(), None),   # desperate
                         lambda: ('utf-8', None),                    # ...
                         lambda: ('utf-8', 'ignore')):               # fallback
            encoding, errors = encoding()
            # Clear the error flag for all except the last check, because
            # the error of second-to-last check is stored and shown as warning in owfile
            if errors != 'ignore':
                error = ''
            with self.open(self.filename, mode='rt', newline='',
                           encoding=encoding, errors=errors) as file:
                # Sniff the CSV dialect (delimiter, quotes, ...)
                try:
                    dialect = csv.Sniffer().sniff(
                        # Take first couple of *complete* lines as sample
                        ''.join(file.readline() for _ in range(10)),
                        self.DELIMITERS)
                    delimiter = dialect.delimiter
                    quotechar = dialect.quotechar
                except UnicodeDecodeError as e:
                    error = e
                    continue
                except csv.Error:
                    delimiter = self.DELIMITERS[0]
                    quotechar = csv.excel.quotechar

                file.seek(0)
                try:
                    reader = csv.reader(
                        file, delimiter=delimiter, quotechar=quotechar,
                        skipinitialspace=True,
                    )
                    data = self.data_table(reader)

                    # TODO: Name can be set unconditionally when/if
                    # self.filename will always be a string with the file name.
                    # Currently, some tests pass StringIO instead of
                    # the file name to a reader.
                    if isinstance(self.filename, str):
                        data.name = path.splitext(
                            path.split(self.filename)[-1])[0]
                    if error and isinstance(error, UnicodeDecodeError):
                        pos, endpos = error.args[2], error.args[3]
                        warning = ('Skipped invalid byte(s) in position '
                                   '{}{}').format(pos,
                                                  ('-' + str(endpos)) if (endpos - pos) > 1 else '')
                        warnings.warn(warning)
                    self.set_table_metadata(self.filename, data)
                    return data
                except Exception as e:
                    error = e
                    continue
        raise ValueError('Cannot parse dataset {}: {}'.format(self.filename, error)) from error

    @classmethod
    def write_file(cls, filename, data, with_annotations=True):
        with cls.open(filename, mode='wt', newline='', encoding='utf-8') as file:
            writer = csv.writer(file, delimiter=cls.DELIMITERS[0])
            cls.write_headers(writer.writerow, data, with_annotations)
            cls.write_data(writer.writerow, data)
            cls.write_table_metadata(filename, data)


class TabReader(CSVReader):
    """Reader for tab separated files"""
    EXTENSIONS = ('.tab', '.tsv')
    DESCRIPTION = 'Tab-separated values'
    DELIMITERS = '\t'
    PRIORITY = 10


class PickleReader(FileFormat):
    """Reader for pickled Table objects"""
    EXTENSIONS = ('.pkl', '.pickle')
    DESCRIPTION = 'Pickled Orange data'
    SUPPORT_COMPRESSED = True
    SUPPORT_SPARSE_DATA = True

    def read(self):
        with self.open(self.filename, 'rb') as f:
            table = pickle.load(f)
            if not isinstance(table, Table):
                raise TypeError("file does not contain a data table")
            else:
                return table

    @classmethod
    def write_file(cls, filename, data):
        with cls.open(filename, 'wb') as f:
            pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)


class BasketReader(FileFormat):
    """Reader for basket (sparse) files"""
    EXTENSIONS = ('.basket', '.bsk')
    DESCRIPTION = 'Basket file'
    SUPPORT_SPARSE_DATA = True

    def read(self):
        def constr_vars(inds):
            if inds:
                return [ContinuousVariable(x.decode("utf-8")) for _, x in
                        sorted((ind, name) for name, ind in inds.items())]

        X, Y, metas, attr_indices, class_indices, meta_indices = \
            _io.sparse_read_float(self.filename.encode(sys.getdefaultencoding()))

        attrs = constr_vars(attr_indices)
        classes = constr_vars(class_indices)
        meta_attrs = constr_vars(meta_indices)
        domain = Domain(attrs, classes, meta_attrs)
        table = Table.from_numpy(
            domain, attrs and X, classes and Y, metas and meta_attrs)
        table.name = path.splitext(path.split(self.filename)[-1])[0]
        return table


class ExcelReader(FileFormat):
    """Reader for excel files"""
    EXTENSIONS = ('.xlsx',)
    DESCRIPTION = 'Microsoft Excel spreadsheet'
    SUPPORT_COMPRESSED = False
    SUPPORT_SPARSE_DATA = False

    def __init__(self, filename):
        super().__init__(filename=filename)
        self._workbook = None

    @property
    def workbook(self):
        if not self._workbook:
            self._workbook = xlrd.open_workbook(self.filename, on_demand=True)
        return self._workbook

    @property
    @lru_cache(1)
    def sheets(self):
        if self.workbook:
            return self.workbook.sheet_names()
        else:
            return ()

    def read(self):
        if self.sheet:
            ss = self.workbook.sheet_by_name(self.sheet)
        else:
            ss = self.workbook.sheet_by_index(0)
        try:
            first_row = next(i for i in range(ss.nrows) if any(ss.row_values(i)))
            first_col = next(i for i in range(ss.ncols) if ss.cell_value(first_row, i))
            row_len = ss.row_len(first_row)
            cells = filter(any,
                           [[str(ss.cell_value(row, col)) if col < ss.row_len(row) else ''
                             for col in range(first_col, row_len)]
                            for row in range(first_row, ss.nrows)])
            table = self.data_table(cells)
            table.name = path.splitext(path.split(self.filename)[-1])[0]
            if self.sheet:
                table.name = '-'.join((table.name, self.sheet))
        except Exception:
            raise IOError("Couldn't load spreadsheet from " + self.filename)
        return table

    @classmethod
    def write_file(cls, filename, data):
        vars = list(chain((ContinuousVariable('_w'),) if data.has_weights() else (),
                          data.domain.attributes,
                          data.domain.class_vars,
                          data.domain.metas))
        formatters = [cls.formatter(v) for v in vars]
        zipped_list_data = zip(data.W if data.W.ndim > 1 else data.W[:, np.newaxis],
                               data.X,
                               data.Y if data.Y.ndim > 1 else data.Y[:, np.newaxis],
                               data.metas)
        headers = cls.header_names(data)
        workbook = xlsxwriter.Workbook(filename)
        sheet = workbook.add_worksheet()
        for c, header in enumerate(headers):
            sheet.write(0, c, header)
        for i, row in enumerate(zipped_list_data, 1):
            for j, (fmt, v) in enumerate(zip(formatters, flatten(row))):
                sheet.write(i, j, fmt(v))
        workbook.close()


class DotReader(FileFormat):
    """Writer for dot (graph) files"""
    EXTENSIONS = ('.dot', '.gv')
    DESCRIPTION = 'Dot graph description'
    SUPPORT_COMPRESSED = True
    SUPPORT_SPARSE_DATA = False

    @classmethod
    def write_graph(cls, filename, graph):
        from sklearn import tree
        tree.export_graphviz(graph, out_file=cls.open(filename, 'wt'))

    @classmethod
    def write(cls, filename, tree):
        if type(tree) == dict:
            tree = tree['tree']
        cls.write_graph(filename, tree)


class UrlReader(FileFormat):
    def __init__(self, filename):
        filename = filename.strip()
        if not urlparse(filename).scheme:
            filename = 'http://' + filename
        super().__init__(filename)

    @staticmethod
    def urlopen(url):
        req = Request(
            url,
            # Avoid 403 error with servers that dislike scrapers
            headers={'User-Agent': 'Mozilla/5.0 (X11; Linux) Gecko/20100101 Firefox/'})
        return urlopen(req, timeout=10)

    def read(self):
        self.filename = self._trim(self._resolve_redirects(self.filename))
        with contextlib.closing(self.urlopen(self.filename)) as response:
            name = self._suggest_filename(response.headers['content-disposition'])
            with NamedTemporaryFile(suffix=name, delete=False) as f:
                f.write(response.read())
                # delete=False is a workaround for https://bugs.python.org/issue14243

            reader = self.get_reader(f.name)
            data = reader.read()
            remove(f.name)
        # Override name set in from_file() to avoid holding the temp prefix
        data.name = path.splitext(name)[0]
        data.origin = self.filename
        return data

    def _resolve_redirects(self, url):
        # Resolve (potential) redirects to a final URL
        with contextlib.closing(self.urlopen(url)) as response:
            return response.url

    @classmethod
    def _trim(cls, url):
        URL_TRIMMERS = (
            cls._trim_googlesheet,
            cls._trim_dropbox,
        )
        for trim in URL_TRIMMERS:
            try:
                url = trim(url)
            except ValueError:
                continue
            else:
                break
        return url

    @staticmethod
    def _trim_googlesheet(url):
        match = re.match(r'(?:https?://)?(?:www\.)?'
                         r'docs\.google\.com/spreadsheets/d/'
                         r'(?P<workbook_id>[-\w_]+)'
                         r'(?:/.*?gid=(?P<sheet_id>\d+).*|.*)?',
                         url, re.IGNORECASE)
        try:
            workbook, sheet = match.group('workbook_id'), match.group('sheet_id')
            if not workbook:
                raise ValueError
        except (AttributeError, ValueError):
            raise ValueError
        url = 'https://docs.google.com/spreadsheets/d/{}/export?format=tsv'.format(workbook)
        if sheet:
            url += '&gid=' + sheet
        return url

    @staticmethod
    def _trim_dropbox(url):
        parts = urlsplit(url)
        if not parts.netloc.endswith('dropbox.com'):
            raise ValueError
        return urlunsplit(parts._replace(query='dl=1'))

    def _suggest_filename(self, content_disposition):
        default_name = re.sub(r'[\\:/]', '_', urlparse(self.filename).path)

        # See https://tools.ietf.org/html/rfc6266#section-4.1
        matches = re.findall(r"filename\*?=(?:\"|.{0,10}?'[^']*')([^\"]+)",
                             content_disposition or '')
        return urlunquote(matches[-1]) if matches else default_name


### Orange/data/variable.py
import re
import warnings
from collections import Iterable

from datetime import datetime, timedelta, timezone
from numbers import Number, Real, Integral
from math import isnan, floor
from pickle import PickleError

import numpy as np
import scipy.sparse as sp

from Orange.data import _variable
from Orange.util import Registry, hex_to_color, Reprable,\
    OrangeDeprecationWarning

__all__ = ["Unknown", "MISSING_VALUES", "make_variable", "is_discrete_values",
           "Value", "Variable", "ContinuousVariable", "DiscreteVariable",
           "StringVariable", "TimeVariable"]


# For storing unknowns
Unknown = ValueUnknown = float("nan")
# For checking for unknowns
MISSING_VALUES = {np.nan, "?", "nan", ".", "", "NA", "~", None}

DISCRETE_MAX_VALUES = 3  # == 2 + nan
MAX_NUM_OF_DECIMALS = 5
# the variable with more than 100 different values should not be StringVariable
DISCRETE_MAX_ALLOWED_VALUES = 100


def make_variable(cls, compute_value, *args):
    if compute_value is not None:
        return cls(*args, compute_value=compute_value)
    else:
        # For compatibility with old pickles
        return cls(*args)


def is_discrete_values(values):
    """
    Return set of uniques if `values` is an iterable of discrete values
    else False if non-discrete, or None if indeterminate.

    Note
    ----
    Assumes consistent type of items of `values`.
    """
    if not len(values):
        return None
    # If the first few values are, or can be converted to, floats,
    # the type is numeric
    try:
        isinstance(next(iter(values)), Number) or \
        [float(v) for _, v in zip(range(min(3, len(values))), values)]
    except ValueError:
        is_numeric = False
        max_values = int(round(len(values)**.7))
    else:
        is_numeric = True
        max_values = DISCRETE_MAX_VALUES

    # If more than max values => not discrete
    unique = set()
    for i in values:
        unique.add(i)
        if (len(unique) > max_values or
                len(unique) > DISCRETE_MAX_ALLOWED_VALUES):
            return False

    # Strip NaN from unique
    unique = {i for i in unique
              if (not i in MISSING_VALUES and
                  not (isinstance(i, Number) and np.isnan(i)))}

    # All NaNs => indeterminate
    if not unique:
        return None

    # Strings with |values| < max_unique
    if not is_numeric:
        return unique

    # Handle numbers
    try:
        unique_float = set(map(float, unique))
    except ValueError:
        # Converting all the values to floats resulted in an error.
        # Since the values have enough unique values, they are probably
        # string values and discrete.
        return unique

    # If only values are {0, 1} or {1, 2} (or a subset of those sets) => discrete
    return (not (unique_float - {0, 1}) or
            not (unique_float - {1, 2})) and unique


class Value(float):
    """
    The class representing a value. The class is not used to store values but
    only to return them in contexts in which we want the value to be accompanied
    with the descriptor, for instance to print the symbolic value of discrete
    variables.

    The class is derived from `float`, with an additional attribute `variable`
    which holds the descriptor of type :obj:`Orange.data.Variable`. If the
    value continuous or discrete, it is stored as a float. Other types of
    values, like strings, are stored in the attribute `value`.

    The class overloads the methods for printing out the value:
    `variable.repr_val` and `variable.str_val` are used to get a suitable
    representation of the value.

    Equivalence operator is overloaded as follows:

    - unknown values are equal; if one value is unknown and the other is not,
      they are different;

    - if the value is compared with the string, the value is converted to a
      string using `variable.str_val` and the two strings are compared

    - if the value is stored in attribute `value`, it is compared with the
      given other value

    - otherwise, the inherited comparison operator for `float` is called.

    Finally, value defines a hash, so values can be put in sets and appear as
    keys in dictionaries.

    .. attribute:: variable (:obj:`Orange.data.Variable`)

        Descriptor; used for printing out and for comparing with strings

    .. attribute:: value

        Value; the value can be of arbitrary type and is used only for variables
        that are neither discrete nor continuous. If `value` is `None`, the
        derived `float` value is used.
    """
    __slots__ = "variable", "_value"

    def __new__(cls, variable, value=Unknown):
        """
        Construct a new instance of Value with the given descriptor and value.
        If the argument `value` can be converted to float, it is stored as
        `float` and the attribute `value` is set to `None`. Otherwise, the
        inherited float is set to `Unknown` and the value is held by the
        attribute `value`.

        :param variable: descriptor
        :type variable: Orange.data.Variable
        :param value: value
        """
        if variable.is_primitive():
            self = super().__new__(cls, value)
            self.variable = variable
            self._value = None
        else:
            isunknown = value == variable.Unknown
            self = super().__new__(
                cls, np.nan if isunknown else np.finfo(float).min)
            self.variable = variable
            self._value = value
        return self

    def __init__(self, _, __=Unknown):
        pass

    def __repr__(self):
        return "Value('%s', %s)" % (self.variable.name,
                                    self.variable.repr_val(self))

    def __str__(self):
        return self.variable.str_val(self)

    def __eq__(self, other):
        if isinstance(self, Real) and isnan(self):
            return (isinstance(other, Real) and isnan(other)
                    or other in self.variable.unknown_str)
        if isinstance(other, str):
            return self.variable.str_val(self) == other
        if isinstance(other, Value):
            return self.value == other.value
        return super().__eq__(other)

    def __ne__(self, other):
        return not self.__eq__(other)

    def __lt__(self, other):
        if self.variable.is_primitive():
            if isinstance(other, str):
                return super().__lt__(self.variable.to_val(other))
            else:
                return super().__lt__(other)
        else:
            if isinstance(other, str):
                return self.value < other
            else:
                return self.value < other.value

    def __le__(self, other):
        return self.__lt__(other) or self.__eq__(other)

    def __gt__(self, other):
        return not self.__le__(other)

    def __ge__(self, other):
        return not self.__lt__(other)

    def __contains__(self, other):
        if (self._value is not None
                and isinstance(self._value, str)
                and isinstance(other, str)):
            return other in self._value
        raise TypeError("invalid operation on Value()")

    def __hash__(self):
        if self.variable.is_discrete:
            # It is not possible to hash the id and the domain value to the
            # same number as required by __eq__.
            # hash(1) == hash(Value(DiscreteVariable("var", ["red", "green", "blue"]), 1)) == hash("green")
            # User should hash directly ids or domain values instead.
            raise TypeError("unhashable type - cannot hash values of discrete variables!")
        if self._value is None:
            return super().__hash__()
        else:
            return hash(self._value)

    @property
    def value(self):
        if self.variable.is_discrete:
            return Unknown if isnan(self) else self.variable.values[int(self)]
        if self.variable.is_string:
            return self._value
        return float(self)

    def __getnewargs__(self):
        return self.variable, float(self)

    def __getstate__(self):
        return dict(value=getattr(self, '_value', None))

    def __setstate__(self, state):
        self._value = state.get('value', None)


class VariableMeta(Registry):
    pass


class _predicatedescriptor(property):
    """
    A property that behaves as a class method if accessed via a class
    >>> class A:
    ...     foo = False
    ...     @_predicatedescriptor
    ...     def is_foo(self):
    ...         return self.foo
    ...
    >>> a = A()
    >>> a.is_foo
    False
    >>> A.is_foo(a)
    False
    """
    def __get__(self, instance, objtype=None):
        if instance is None:
            return self.fget
        else:
            return super().__get__(instance, objtype)


class Variable(Reprable, metaclass=VariableMeta):
    """
    The base class for variable descriptors contains the variable's
    name and some basic properties.

    .. attribute:: name

        The name of the variable.

    .. attribute:: unknown_str

        A set of values that represent unknowns in conversion from textual
        formats. Default is `{"?", ".", "", "NA", "~", None}`.

    .. attribute:: compute_value

        A function for computing the variable's value when converting from
        another domain which does not contain this variable. The function will
        be called with a data set (`Orange.data.Table`) and has to return
        an array of computed values for all its instances. The base class
        defines a static method `compute_value`, which returns `Unknown`.
        Non-primitive variables must redefine it to return `None`.

    .. attribute:: sparse

        A flag about sparsity of the variable. When set, the variable suggests
        it should be stored in a sparse matrix.

    .. attribute:: source_variable

        An optional descriptor of the source variable - if any - from which
        this variable is derived and computed via :obj:`compute_value`.

    .. attribute:: attributes

        A dictionary with user-defined attributes of the variable
    """
    Unknown = ValueUnknown

    def __init__(self, name="", compute_value=None, *, sparse=False):
        """
        Construct a variable descriptor.
        """
        if not name:
            warnings.warn("Variable must have a name", OrangeDeprecationWarning,
                          stacklevel=3)
        self._name = name
        self._compute_value = compute_value
        self.unknown_str = MISSING_VALUES
        self.source_variable = None
        self.sparse = sparse
        self.attributes = {}
        self._colors = None

    @property
    def name(self):
        return self._name

    @property
    def colors(self):  # unreachable; pragma: no cover
        return self._colors

    @colors.setter
    def colors(self, value):
        self._colors = value

    def make_proxy(self):
        """
        Copy the variable and set the master to `self.master` or to `self`.

        :return: copy of self
        :rtype: Variable
        """
        var = self.__class__(self.name)
        var.__dict__.update(self.__dict__)
        var.attributes = dict(self.attributes)
        return var

    def __eq__(self, other):
        return type(self) is type(other) \
               and self.name == other.name \
               and self._compute_value == other._compute_value

    def __hash__(self):
        return hash((self.name, type(self), self._compute_value))

    @classmethod
    def make(cls, name, *args, **kwargs):
        """
        Return an existing continuous variable with the given name, or
        construct and return a new one.
        """
        return cls(name, *args, **kwargs)

    @classmethod
    def _clear_cache(cls):
        warnings.warn(
            "_clear_cache is no longer needed and thus deprecated")

    @staticmethod
    def _clear_all_caches():
        warnings.warn(
            "_clear_all_caches is no longer needed and thus deprecated")

    @classmethod
    def is_primitive(cls, var=None):
        """
        `True` if the variable's values are stored as floats.
        Non-primitive variables can appear in the data only as meta attributes.
        """
        to_check = cls if var is None else type(var)
        return issubclass(to_check, (DiscreteVariable, ContinuousVariable))

    @_predicatedescriptor
    def is_discrete(self):
        return isinstance(self, DiscreteVariable)

    @_predicatedescriptor
    def is_continuous(self):
        return isinstance(self, ContinuousVariable)

    @_predicatedescriptor
    def is_string(self):
        return isinstance(self, StringVariable)

    @_predicatedescriptor
    def is_time(self):
        return isinstance(self, TimeVariable)

    def repr_val(self, val):
        """
        Return a textual representation of variable's value `val`. Argument
        `val` must be a float (for primitive variables) or an arbitrary
        Python object (for non-primitives).

        Derived classes must overload the function.
        """
        raise RuntimeError("variable descriptors must overload repr_val()")

    str_val = repr_val

    def to_val(self, s):
        """
        Convert the given argument to a value of the variable. The
        argument can be a string, a number or `None`. For primitive variables,
        the base class provides a method that returns
        :obj:`~Orange.data.Unknown` if `s` is found in
        :obj:`~Orange.data.Variable.unknown_str`, and raises an exception
        otherwise. For non-primitive variables it returns the argument itself.

        Derived classes of primitive variables must overload the function.

        :param s: value, represented as a number, string or `None`
        :type s: str, float or None
        :rtype: float or object
        """
        if not self.is_primitive():
            return s
        if s in self.unknown_str:
            return Unknown
        raise RuntimeError(
            "primitive variable descriptors must overload to_val()")

    def val_from_str_add(self, s):
        """
        Convert the given string to a value of the variable. The method
        is similar to :obj:`to_val` except that it only accepts strings and
        that it adds new values to the variable's domain where applicable.

        The base class method calls `to_val`.

        :param s: symbolic representation of the value
        :type s: str
        :rtype: float or object
        """
        return self.to_val(s)

    def __str__(self):
        return self.name

    @property
    def compute_value(self):
        return self._compute_value

    def __reduce__(self):
        if not self.name:
            raise PickleError("Variables without names cannot be pickled")

        # Use make to unpickle variables.
        return make_variable, (self.__class__, self._compute_value, self.name), self.__dict__

    def copy(self, compute_value=None, *, name=None, **kwargs):
        var = type(self)(name=name or self.name,
                         compute_value=compute_value or self.compute_value,
                         sparse=self.sparse, **kwargs)
        var.attributes = dict(self.attributes)
        return var


del _predicatedescriptor


class ContinuousVariable(Variable):
    """
    Descriptor for continuous variables.

    .. attribute:: number_of_decimals

        The number of decimals when the value is printed out (default: 3).

    .. attribute:: adjust_decimals

        A flag regulating whether the `number_of_decimals` is being adjusted
        by :obj:`to_val`.

    The value of `number_of_decimals` is set to 3 and `adjust_decimals`
    is set to 2. When :obj:`val_from_str_add` is called for the first
    time with a string as an argument, `number_of_decimals` is set to the
    number of decimals in the string and `adjust_decimals` is set to 1.
    In the subsequent calls of `to_val`, the nubmer of decimals is
    increased if the string argument has a larger number of decimals.

    If the `number_of_decimals` is set manually, `adjust_decimals` is
    set to 0 to prevent changes by `to_val`.
    """

    TYPE_HEADERS = ('continuous', 'c', 'numeric', 'n')

    def __init__(self, name="", number_of_decimals=None, compute_value=None, *, sparse=False):
        """
        Construct a new continuous variable. The number of decimals is set to
        three, but adjusted at the first call of :obj:`to_val`.
        """
        super().__init__(name, compute_value, sparse=sparse)
        if number_of_decimals is None:
            self._number_of_decimals = 3
            self.adjust_decimals = 2
            self._format_str = "%g"
        else:
            self.number_of_decimals = number_of_decimals

    @property
    def number_of_decimals(self):
        return self._number_of_decimals

    @property
    def format_str(self):
        return self._format_str

    @format_str.setter
    def format_str(self, value):
        self._format_str = value

    @Variable.colors.getter
    def colors(self):
        if self._colors is not None:
            return self._colors
        try:
            col1, col2, black = self.attributes["colors"]
            return (hex_to_color(col1), hex_to_color(col2), black)
        except (KeyError, ValueError):
            # User-provided colors were not available or invalid
            return ((0, 0, 255), (255, 255, 0), False)

    # noinspection PyAttributeOutsideInit
    @number_of_decimals.setter
    def number_of_decimals(self, x):
        self._number_of_decimals = x
        self.adjust_decimals = 0
        if self._number_of_decimals <= MAX_NUM_OF_DECIMALS:
            self._format_str = "%.{}f".format(self.number_of_decimals)
        else:
            self._format_str = "%g"

    def to_val(self, s):
        """
        Convert a value, given as an instance of an arbitrary type, to a float.
        """
        if s in self.unknown_str:
            return Unknown
        return float(s)

    def val_from_str_add(self, s):
        """
        Convert a value from a string and adjust the number of decimals if
        `adjust_decimals` is non-zero.
        """
        return _variable.val_from_str_add_cont(self, s)

    def repr_val(self, val):
        """
        Return the value as a string with the prescribed number of decimals.
        """
        if isnan(val):
            return "?"
        return self._format_str % val

    str_val = repr_val

    def copy(self, compute_value=None, *, name=None, **kwargs):
        var = super().copy(compute_value=compute_value, name=name,
                           number_of_decimals=self.number_of_decimals,
                           **kwargs)
        var.adjust_decimals = self.adjust_decimals
        var.format_str = self._format_str
        return var


class DiscreteVariable(Variable):
    """
    Descriptor for symbolic, discrete variables. Values of discrete variables
    are stored as floats; the numbers corresponds to indices in the list of
    values.

    .. attribute:: values

        A list of variable's values.

    .. attribute:: ordered

        Some algorithms (and, in particular, visualizations) may
        sometime reorder the values of the variable, e.g. alphabetically.
        This flag hints that the given order of values is "natural"
        (e.g. "small", "middle", "large") and should not be changed.
    """

    TYPE_HEADERS = ('discrete', 'd', 'categorical')

    presorted_values = []

    def __init__(self, name="", values=(), ordered=False, compute_value=None,
                 *, sparse=False):
        """ Construct a discrete variable descriptor with the given values. """
        self.values = list(values)
        if not all(isinstance(value, str) for value in self.values):
            raise TypeError("values of DiscreteVariables must be strings")
        super().__init__(name, compute_value, sparse=sparse)
        self.ordered = ordered

    def get_mapping_from(self, other):
        return np.array(
            [self.values.index(value) if value in self.values else np.nan
             for value in other.values], dtype=float)

    def get_mapper_from(self, other):
        mapping = self.get_mapping_from(other)
        if not mapping.size:
            # Nans in data are temporarily replaced with 0, mapped and changed
            # back to nans. This would fail is mapping[0] is out of range.
            mapping = np.array([np.nan])

        def mapper(value, col_idx=None):

            # In-place mapping
            if col_idx is not None:
                if sp.issparse(value) and mapping[0] != 0:
                    raise ValueError(
                        "In-place mapping of sparse matrices must map 0 to 0")

                # CSR requires mapping of non-contiguous area
                if sp.isspmatrix_csr(value):
                    col = value.indices == col_idx
                    nans = np.isnan(value.data) * col
                    value.data[nans] = 0
                    value.data[col] = mapping[value.data[col].astype(int)]
                    value.data[nans] = np.nan
                    return

                # Dense and CSC map a contiguous area
                if isinstance(value, np.ndarray) and value.ndim == 2:
                    col = value[:, col_idx]
                elif sp.isspmatrix_csc(value):
                    col = value.data[value.indptr[col_idx]
                                     :value.indptr[col_idx + 1]]
                else:
                    raise ValueError(
                        "In-place column mapping requires a 2d array or"
                        "a csc or csr matrix.")

                nans = np.isnan(col)
                col[nans] = 0
                col[:] = mapping[col.astype(int)]
                col[nans] = np.nan
                return

            # Mapping into a copy
            if isinstance(value, (int, float)):
                return mapping[int(value)] if value == value else value
            if isinstance(value, str):
                return mapping[other.values.index(value)]
            if isinstance(value, np.ndarray):
                if not (value.ndim == 1
                        or value.ndim != 2 and min(value.shape) != 1):
                    raise ValueError(
                        f"Column mapping can't map {value.ndim}-d objects")

                if value.dtype == object:
                    value = value.astype(float)  # this happens with metas
                try:
                    nans = np.isnan(value)
                except TypeError:  # suppose it's already an integer type
                    return mapping[value]
                value = value.astype(int)
                value[nans] = 0
                value = mapping[value]
                value[nans] = np.nan
                return value
            if sp.issparse(value):
                if min(value.shape) != 1:
                    raise ValueError("Column mapping can't map "
                                     f"{value.ndim}-dimensional objects")
                if mapping[0] != 0 and not np.isnan(mapping[0]):
                    return mapper(np.array(value.todense()).flatten())
                value = value.copy()
                value.data = mapper(value.data)
                return value
            if isinstance(value, Iterable):
                return type(value)(mapping[int(val)] if val == val else val
                                   for val in value)
            raise ValueError(
                f"invalid type for value(s): {type(value).__name__}")

        return mapper

    @Variable.colors.getter
    def colors(self):
        if self._colors is not None:
            colors = np.array(self._colors)
        elif not self.values:
            colors = np.zeros((0, 3))  # to match additional colors in vstacks
        else:
            from Orange.widgets.utils.colorpalette import ColorPaletteGenerator
            default = tuple(ColorPaletteGenerator.palette(self))
            colors = self.attributes.get('colors', ())
            colors = tuple(hex_to_color(color) for color in colors) \
                    + default[len(colors):]
            colors = np.array(colors)
        colors.flags.writeable = False
        return colors

    def to_val(self, s):
        """
        Convert the given argument to a value of the variable (`float`).
        If the argument is numeric, its value is returned without checking
        whether it is integer and within bounds. `Unknown` is returned if the
        argument is one of the representations for unknown values. Otherwise,
        the argument must be a string and the method returns its index in
        :obj:`values`.

        :param s: values, represented as a number, string or `None`
        :rtype: float
        """
        if s is None:
            return ValueUnknown

        if isinstance(s, Integral):
            return s
        if isinstance(s, Real):
            return s if isnan(s) else floor(s + 0.25)
        if s in self.unknown_str:
            return ValueUnknown
        if not isinstance(s, str):
            raise TypeError('Cannot convert {} to value of "{}"'.format(
                type(s).__name__, self.name))
        return self.values.index(s)

    def add_value(self, s):
        """ Add a value `s` to the list of values.
        """
        if not isinstance(s, str):
            raise TypeError("values of DiscreteVariables must be strings")
        self.values.append(s)
        self._colors = None

    def val_from_str_add(self, s):
        """
        Similar to :obj:`to_val`, except that it accepts only strings and that
        it adds the value to the list if it does not exist yet.

        :param s: symbolic representation of the value
        :type s: str
        :rtype: float
        """
        s = str(s) if s is not None else s
        try:
            return ValueUnknown if s in self.unknown_str \
                else self.values.index(s)
        except ValueError:
            self.add_value(s)
            return len(self.values) - 1

    def repr_val(self, val):
        """
        Return a textual representation of the value (`self.values[int(val)]`)
        or "?" if the value is unknown.

        :param val: value
        :type val: float (should be whole number)
        :rtype: str
        """
        if isnan(val):
            return "?"
        return '{}'.format(self.values[int(val)])

    str_val = repr_val

    def __reduce__(self):
        if not self.name:
            raise PickleError("Variables without names cannot be pickled")
        __dict__ = dict(self.__dict__)
        __dict__.pop("values")
        return make_variable, (self.__class__, self._compute_value, self.name,
                               self.values, self.ordered), \
            __dict__

    def copy(self, compute_value=None, *, name=None, **_):
        return super().copy(compute_value=compute_value, name=name,
                            values=self.values, ordered=self.ordered)


class StringVariable(Variable):
    """
    Descriptor for string variables. String variables can only appear as
    meta attributes.
    """
    Unknown = ""
    TYPE_HEADERS = ('string', 's', 'text')

    def to_val(self, s):
        """
        Return the value as a string. If it is already a string, the same
        object is returned.
        """
        if s is None:
            return ""
        if isinstance(s, str):
            return s
        return str(s)

    val_from_str_add = to_val

    @staticmethod
    def str_val(val):
        """Return a string representation of the value."""
        if val is "":
            return "?"
        if isinstance(val, Value):
            if val.value is "":
                return "?"
            val = val.value
        return str(val)

    def repr_val(self, val):
        """Return a string representation of the value."""
        return '"{}"'.format(self.str_val(val))


class TimeVariable(ContinuousVariable):
    """
    TimeVariable is a continuous variable with Unix epoch
    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive
    real numbers (equivalent to Unix timestamp, with microseconds in the
    fraction part), and the dates before it map to the negative real numbers.

    Unfortunately due to limitation of Python datetime, only dates
    with year >= 1 (A.D.) are supported.

    If time is specified without a date, Unix epoch is assumed.

    If time is specified wihout an UTC offset, localtime is assumed.
    """
    _all_vars = {}
    TYPE_HEADERS = ('time', 't')
    UNIX_EPOCH = datetime(1970, 1, 1)
    _ISO_FORMATS = [
        # have_date, have_time, format_str
        # in order of decreased probability
        (1, 1, '%Y-%m-%d %H:%M:%S%z'),
        (1, 1, '%Y-%m-%d %H:%M:%S'),
        (1, 1, '%Y-%m-%d %H:%M'),
        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),
        (1, 1, '%Y-%m-%dT%H:%M:%S'),

        (1, 0, '%Y-%m-%d'),

        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),
        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),
        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),
        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),

        (1, 1, '%Y%m%dT%H%M%S%z'),
        (1, 1, '%Y%m%d%H%M%S%z'),

        (0, 1, '%H:%M:%S.%f'),
        (0, 1, '%H:%M:%S'),
        (0, 1, '%H:%M'),

        # These parse as continuous features (plain numbers)
        (1, 1, '%Y%m%dT%H%M%S'),
        (1, 1, '%Y%m%d%H%M%S'),
        (1, 0, '%Y%m%d'),
        (1, 0, '%Y%j'),
        (1, 0, '%Y'),
        (0, 1, '%H%M%S.%f'),

        # BUG: In Python as in C, %j doesn't necessitate 0-padding,
        # so these two lines must be in this order
        (1, 0, '%Y-%m'),
        (1, 0, '%Y-%j'),
    ]
    # The regex that matches all above formats
    REGEX = (r'^('
             r'\d{1,4}-\d{2}-\d{2}([ T]\d{2}:\d{2}(:\d{2}(\.\d+)?([+-]\d{4})?)?)?|'
             r'\d{1,4}\d{2}\d{2}(T?\d{2}\d{2}\d{2}([+-]\d{4})?)?|'
             r'\d{2}:\d{2}(:\d{2}(\.\d+)?)?|'
             r'\d{2}\d{2}\d{2}\.\d+|'
             r'\d{1,4}(-?\d{2,3})?'
             r')$')
    _matches_iso_format = re.compile(REGEX).match

    # UTC offset and associated timezone. If parsed datetime values provide an
    # offset, it is used for display. If not all values have the same offset,
    # +0000 (=UTC) timezone is used and utc_offset is set to False.
    utc_offset = None
    timezone = timezone.utc

    def __init__(self, *args, have_date=0, have_time=0, **kwargs):
        super().__init__(*args, **kwargs)
        self.have_date = have_date
        self.have_time = have_time

    def copy(self, compute_value=None, *, name=None, **_):
        return super().copy(compute_value=compute_value, name=name,
                            have_date=self.have_date, have_time=self.have_time)

    @staticmethod
    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\d\d):(\d\d)$').sub):
        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000
        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\1\2\3', s)

    def repr_val(self, val):
        if isnan(val):
            return '?'
        if not self.have_date and not self.have_time:
            # The time is relative, unitless. The value is absolute.
            return str(val.value) if isinstance(val, Value) else str(val)

        # If you know how to simplify this, be my guest
        seconds = int(val)
        microseconds = int(round((val - seconds) * 1e6))
        if val < 0:
            if microseconds:
                seconds, microseconds = seconds - 1, int(1e6) + microseconds
            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)
        else:
            date = datetime.fromtimestamp(seconds, tz=self.timezone)
        date = str(date.replace(microsecond=microseconds))

        if self.have_date and not self.have_time:
            date = date.split()[0]
        elif not self.have_date and self.have_time:
            date = date.split()[1]
        date = self._tzre_sub(date)
        return date

    str_val = repr_val

    def parse(self, datestr):
        """
        Return `datestr`, a datetime provided in one of ISO 8601 formats,
        parsed as a real number. Value 0 marks the Unix epoch, positive values
        are the dates after it, negative before.

        If date is unspecified, epoch date is assumed.

        If time is unspecified, 00:00:00.0 is assumed.

        If timezone is unspecified, local time is assumed.
        """
        if datestr in MISSING_VALUES:
            return Unknown
        datestr = datestr.strip().rstrip('Z')

        ERROR = ValueError("Invalid datetime format '{}'. "
                           "Only ISO 8601 supported.".format(datestr))
        if not self._matches_iso_format(datestr):
            try:
                # If it is a number, assume it is a unix timestamp
                value = float(datestr)
                self.have_date = self.have_time = 1
                return value
            except ValueError:
                raise ERROR

        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):
            try:
                dt = datetime.strptime(datestr, fmt)
            except ValueError:
                continue
            else:
                # Pop this most-recently-used format to front
                if 0 < i < len(self._ISO_FORMATS) - 2:
                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \
                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]

                self.have_date |= have_date
                self.have_time |= have_time
                if not have_date:
                    dt = dt.replace(self.UNIX_EPOCH.year,
                                    self.UNIX_EPOCH.month,
                                    self.UNIX_EPOCH.day)
                break
        else:
            raise ERROR

        # Remember UTC offset. If not all parsed values share the same offset,
        # remember none of it.
        offset = dt.utcoffset()
        if self.utc_offset is not False:
            if offset and self.utc_offset is None:
                self.utc_offset = offset
                self.timezone = timezone(offset)
            elif self.utc_offset != offset:
                self.utc_offset = False
                self.timezone = timezone.utc

        # Convert time to UTC timezone. In dates without timezone,
        # localtime is assumed. See also:
        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp
        if dt.tzinfo:
            dt -= dt.utcoffset()
        dt = dt.replace(tzinfo=timezone.utc)

        # Unix epoch is the origin, older dates are negative
        try:
            return dt.timestamp()
        except OverflowError:
            return -(self.UNIX_EPOCH - dt).total_seconds()

    def to_val(self, s):
        """
        Convert a value, given as an instance of an arbitrary type, to a float.
        """
        if isinstance(s, str):
            return self.parse(s)
        else:
            return super().to_val(s)

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** Incorrect Data Type Inference for Date and Time Variables

**Description:**
When passing date strings to the data type guessing function, the system incorrectly identifies them as discrete variables instead of time variables. This misclassification leads to improper handling of temporal data.

**Example Buggy Code:**
```python
in_values = ["2019-10-10", "2019-10-10", "2019-10-10", "2019-10-01"]
valuemap, _, coltype = guess_data_type(in_values)
```
In this example, the `coltype` is expected to be `TimeVariable`, but it is incorrectly set to `DiscreteVariable`.

**Expected Behavior:**
Date and time strings should be recognized and classified as `TimeVariable`, allowing the system to handle temporal data accurately.

**Actual Behavior:**
Date and time strings are misclassified as `DiscreteVariable`, resulting in incorrect data type handling and potential issues in data processing workflows.
[/ISSUE]
{
    "TestTableFilters.test_guess_data_type_continuous": "PASSED",
    "TestTableFilters.test_guess_data_type_discrete": "PASSED",
    "TestTableFilters.test_guess_data_type_string": "PASSED",
    "TestTableFilters.test_guess_data_type_time": "PASSED"
}
diff --git a/Orange/data/io.py b/Orange/data/io.py
index 15d0ca0aa..dc1606379 100644
--- a/Orange/data/io.py
+++ b/Orange/data/io.py
@@ -145,30 +145,39 @@ def guess_data_type(orig_values, namask=None):
     """
     valuemap, values = None, orig_values
     is_discrete = is_discrete_values(orig_values)
+    orig_values = np.asarray(orig_values, dtype=str)
+    if namask is None:
+        namask = isnastr(orig_values)
     if is_discrete:
         valuemap = sorted(is_discrete)
         coltype = DiscreteVariable
     else:
         # try to parse as float
-        orig_values = np.asarray(orig_values)
-        if namask is None:
-            namask = isnastr(orig_values)
         values = np.empty_like(orig_values, dtype=float)
         values[namask] = np.nan
         try:
             np.copyto(values, orig_values, where=~namask, casting="unsafe")
         except ValueError:
-            tvar = TimeVariable('_')
-            try:
-                values[~namask] = [tvar.parse(i) for i in orig_values[~namask]]
-            except ValueError:
-                coltype = StringVariable
-                # return original_values
-                values = orig_values
-            else:
-                coltype = TimeVariable
+            values = orig_values
+            coltype = StringVariable
         else:
             coltype = ContinuousVariable
+
+    if coltype is not ContinuousVariable:
+        # when not continuous variable it can still be time variable even it
+        # was before recognized as a discrete
+        tvar = TimeVariable('_')
+        # introducing new variable prevent overwriting orig_values and values
+        temp_values = np.empty_like(orig_values, dtype=float)
+        try:
+            temp_values[~namask] = [
+                tvar.parse_exact_iso(i) for i in orig_values[~namask]]
+        except ValueError:
+            pass
+        else:
+            valuemap = None
+            coltype = TimeVariable
+            values = temp_values
     return valuemap, values, coltype
 
 
diff --git a/Orange/data/variable.py b/Orange/data/variable.py
index e333118b7..211bb6a44 100644
--- a/Orange/data/variable.py
+++ b/Orange/data/variable.py
@@ -889,6 +889,13 @@ class TimeVariable(ContinuousVariable):
              r'\d{2}\d{2}\d{2}\.\d+|'
              r'\d{1,4}(-?\d{2,3})?'
              r')$')
+
+    class InvalidDateTimeFormatError(ValueError):
+        def __init__(self, date_string):
+            super().__init__(
+                "Invalid datetime format '{}'. "
+                "Only ISO 8601 supported.".format(date_string))
+
     _matches_iso_format = re.compile(REGEX).match
 
     # UTC offset and associated timezone. If parsed datetime values provide an
@@ -954,8 +961,6 @@ class TimeVariable(ContinuousVariable):
             return Unknown
         datestr = datestr.strip().rstrip('Z')
 
-        ERROR = ValueError("Invalid datetime format '{}'. "
-                           "Only ISO 8601 supported.".format(datestr))
         if not self._matches_iso_format(datestr):
             try:
                 # If it is a number, assume it is a unix timestamp
@@ -963,7 +968,7 @@ class TimeVariable(ContinuousVariable):
                 self.have_date = self.have_time = 1
                 return value
             except ValueError:
-                raise ERROR
+                raise self.InvalidDateTimeFormatError(datestr)
 
         for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):
             try:
@@ -984,7 +989,7 @@ class TimeVariable(ContinuousVariable):
                                     self.UNIX_EPOCH.day)
                 break
         else:
-            raise ERROR
+            raise self.InvalidDateTimeFormatError(datestr)
 
         # Remember UTC offset. If not all parsed values share the same offset,
         # remember none of it.
@@ -1010,6 +1015,16 @@ class TimeVariable(ContinuousVariable):
         except OverflowError:
             return -(self.UNIX_EPOCH - dt).total_seconds()
 
+    def parse_exact_iso(self, datestr):
+        """
+        This function is a meta function to `parse` function. It checks
+        whether the date is of the iso format - it does not accept float-like
+        date.
+        """
+        if not self._matches_iso_format(datestr):
+            raise self.InvalidDateTimeFormatError(datestr)
+        return self.parse(datestr)
+
     def to_val(self, s):
         """
         Convert a value, given as an instance of an arbitrary type, to a float.
 

{"Orange/data/io.py": "import contextlib\nimport csv\nimport locale\nimport pickle\nimport re\nimport subprocess\nimport sys\nimport warnings\n\nfrom ast import literal_eval\nfrom collections import OrderedDict, Counter, defaultdict\nfrom functools import lru_cache\nfrom importlib import import_module\nfrom itertools import chain, repeat\nfrom math import isnan\n\nfrom os import path, remove\nfrom tempfile import NamedTemporaryFile\nfrom urllib.parse import urlparse, urlsplit, urlunsplit, unquote as urlunquote\nfrom urllib.request import urlopen, Request\nfrom fnmatch import fnmatch\nfrom glob import glob\n\nimport numpy as np\nfrom chardet.universaldetector import UniversalDetector\n\nimport xlrd\nimport xlsxwriter\n\nfrom Orange.data import (\n    _io, is_discrete_values, MISSING_VALUES, Table, Domain, Variable,\n    DiscreteVariable, StringVariable, ContinuousVariable, TimeVariable,\n)\nfrom Orange.util import Registry, flatten, namegen\n\n\n# Support values longer than 128K (i.e. text contents features)\ncsv.field_size_limit(100*1024*1024)\n\n__all__ = [\"Flags\", \"FileFormat\"]\n\n\n_IDENTITY = lambda i: i\n\n\nclass Compression:\n    \"\"\"Supported compression extensions\"\"\"\n    GZIP = '.gz'\n    BZIP2 = '.bz2'\n    XZ = '.xz'\n    all = (GZIP, BZIP2, XZ)\n\n\ndef open_compressed(filename, *args, _open=open, **kwargs):\n    \"\"\"Return seamlessly decompressed open file handle for `filename`\"\"\"\n    if isinstance(filename, str):\n        if filename.endswith(Compression.GZIP):\n            from gzip import open as _open\n        elif filename.endswith(Compression.BZIP2):\n            from bz2 import open as _open\n        elif filename.endswith(Compression.XZ):\n            from lzma import open as _open\n        return _open(filename, *args, **kwargs)\n    # Else already a file, just pass it through\n    return filename\n\n\ndef detect_encoding(filename):\n    \"\"\"\n    Detect encoding of `filename`, which can be a ``str`` filename, a\n    ``file``-like object, or ``bytes``.\n    \"\"\"\n    # Try with Unix file utility first because it's faster (~10ms vs 100ms)\n    if isinstance(filename, str) and not filename.endswith(Compression.all):\n        try:\n            with subprocess.Popen(('file', '--brief', '--mime-encoding', filename),\n                                  stdout=subprocess.PIPE) as process:\n                process.wait()\n                if process.returncode == 0:\n                    encoding = process.stdout.read().strip()\n                    # file only supports these encodings; for others it says\n                    # unknown-8bit or binary. So we give chardet a chance to do\n                    # better\n                    if encoding in (b'utf-8', b'us-ascii', b'iso-8859-1',\n                                    b'utf-7', b'utf-16le', b'utf-16be', b'ebcdic'):\n                        return encoding.decode('us-ascii')\n        except OSError:\n            pass  # windoze\n\n    # file not available or unable to guess the encoding, have chardet do it\n    detector = UniversalDetector()\n    # We examine only first N 4kB blocks of file because chardet is really slow\n    MAX_BYTES = 4*1024*12\n\n    def _from_file(f):\n        detector.feed(f.read(MAX_BYTES))\n        detector.close()\n        return (detector.result.get('encoding')\n                if detector.result.get('confidence', 0) >= .85 else\n                'utf-8')\n\n    if isinstance(filename, str):\n        with open_compressed(filename, 'rb') as f:\n            return _from_file(f)\n    elif isinstance(filename, bytes):\n        detector.feed(filename[:MAX_BYTES])\n        detector.close()\n        return detector.result.get('encoding')\n    elif hasattr(filename, 'encoding'):\n        return filename.encoding\n    else:  # assume file-like object that you can iter through\n        return _from_file(filename)\n\n\n__isnastr = np.frompyfunc(\n    {v for v in MISSING_VALUES if isinstance(v, str)}.__contains__, 1, 1)\n\n# wrapper for __isnastr with proper default out dtype\ndef isnastr(arr, out=None):\n    \"\"\"\n    Given an (object) array of string values, return a boolean mask array\n    that is True where the `arr` contains one of the string constants\n    considered as N/A.\n\n    Parameters\n    ----------\n    arr : np.ndarray\n        Input array of strings.\n    out : Optional[np.ndarray]\n        Optional output array of the same shape as arr\n\n    Returns\n    -------\n    mask : np.ndarray\n    \"\"\"\n    arr = np.asarray(arr)\n    if out is None and arr.shape != ():\n        out = np.empty_like(arr, dtype=bool)\n    return __isnastr(arr, out=out)\n\n\ndef guess_data_type(orig_values, namask=None):\n    \"\"\"\n    Use heuristics to guess data type.\n    \"\"\"\n    valuemap, values = None, orig_values\n    is_discrete = is_discrete_values(orig_values)\n    if is_discrete:\n        valuemap = sorted(is_discrete)\n        coltype = DiscreteVariable\n    else:\n        # try to parse as float\n        orig_values = np.asarray(orig_values)\n        if namask is None:\n            namask = isnastr(orig_values)\n        values = np.empty_like(orig_values, dtype=float)\n        values[namask] = np.nan\n        try:\n            np.copyto(values, orig_values, where=~namask, casting=\"unsafe\")\n        except ValueError:\n            tvar = TimeVariable('_')\n            try:\n                values[~namask] = [tvar.parse(i) for i in orig_values[~namask]]\n            except ValueError:\n                coltype = StringVariable\n                # return original_values\n                values = orig_values\n            else:\n                coltype = TimeVariable\n        else:\n            coltype = ContinuousVariable\n    return valuemap, values, coltype\n\n\ndef sanitize_variable(valuemap, values, orig_values, coltype, coltype_kwargs,\n                      name=None):\n    assert issubclass(coltype, Variable)\n\n    def get_number_of_decimals(values):\n        len_ = len\n        ndecimals = max((len_(value) - value.find(\".\")\n                         for value in values if \".\" in value),\n                        default=1)\n        return ndecimals - 1\n\n    if issubclass(coltype, DiscreteVariable) and valuemap is not None:\n        coltype_kwargs.update(values=valuemap)\n\n    var = coltype.make(name, **coltype_kwargs)\n\n    if isinstance(var, DiscreteVariable):\n        # Map discrete data to 'ints' (or at least what passes as int around\n        # here)\n        mapping = defaultdict(\n            lambda: np.nan,\n            {val: i for i, val in enumerate(var.values)},\n        )\n        mapping[\"\"] = np.nan\n        mapvalues_ = np.frompyfunc(mapping.__getitem__, 1, 1)\n\n        def mapvalues(arr):\n            arr = np.asarray(arr, dtype=object)\n            return mapvalues_(arr, out=np.empty_like(arr, dtype=float))\n        values = mapvalues(orig_values)\n\n    if coltype is StringVariable:\n        values = orig_values\n\n    # ContinuousVariable.number_of_decimals is supposed to be handled by\n    # ContinuousVariable.to_val. In the interest of speed, the reader bypasses\n    # it, so we set the number of decimals here.\n    # The number of decimals is increased if not set manually (in which case\n    # var.adjust_decimals would be 0).\n    if isinstance(var, ContinuousVariable) and var.adjust_decimals:\n        ndecimals = get_number_of_decimals(orig_values)\n        if var.adjust_decimals == 2 or ndecimals > var.number_of_decimals:\n            var.number_of_decimals = ndecimals\n            var.adjust_decimals = 1\n\n    if isinstance(var, TimeVariable) or coltype is TimeVariable:\n        # Re-parse the values because only now after coltype.make call\n        # above, variable var is the correct one\n        _var = var if isinstance(var, TimeVariable) else TimeVariable('_')\n        values = [_var.parse(i) for i in orig_values]\n\n    return values, var\n\n\nclass Flags:\n    \"\"\"Parser for column flags (i.e. third header row)\"\"\"\n    DELIMITER = ' '\n    _RE_SPLIT = re.compile(r'(?<!\\\\)' + DELIMITER).split\n    _RE_ATTR_UNQUOTED_STR = re.compile(r'^[a-zA-Z_]').match\n    ALL = OrderedDict((\n        ('class', 'c'),\n        ('ignore', 'i'),\n        ('meta', 'm'),\n        ('weight', 'w'),\n        ('.+?=.*?', ''),  # general key=value attributes\n    ))\n    _RE_ALL = re.compile(r'^({})$'.format('|'.join(filter(None, flatten(ALL.items())))))\n\n    def __init__(self, flags):\n        for v in filter(None, self.ALL.values()):\n            setattr(self, v, False)\n        self.attributes = {}\n        for flag in flags or []:\n            flag = flag.strip()\n            if self._RE_ALL.match(flag):\n                if '=' in flag:\n                    k, v = flag.split('=', 1)\n                    if not Flags._RE_ATTR_UNQUOTED_STR(v):\n                        try:\n                            v = literal_eval(v)\n                        except SyntaxError:\n                            # If parsing failed, treat value as string\n                            pass\n                    self.attributes[k] = v\n                else:\n                    setattr(self, flag, True)\n                    setattr(self, self.ALL.get(flag, ''), True)\n            elif flag:\n                warnings.warn('Invalid attribute flag \\'{}\\''.format(flag))\n\n    @staticmethod\n    def join(iterable, *args):\n        return Flags.DELIMITER.join(i.strip().replace(Flags.DELIMITER, '\\\\' + Flags.DELIMITER)\n                                    for i in chain(iterable, args)).lstrip()\n\n    @staticmethod\n    def split(s):\n        return [i.replace('\\\\' + Flags.DELIMITER, Flags.DELIMITER)\n                for i in Flags._RE_SPLIT(s)]\n\n\n# Matches discrete specification where all the values are listed, space-separated\n_RE_DISCRETE_LIST = re.compile(r'^\\s*[^\\s]+(\\s[^\\s]+)+\\s*$')\n_RE_TYPES = re.compile(r'^\\s*({}|{}|)\\s*$'.format(\n    _RE_DISCRETE_LIST.pattern,\n    '|'.join(flatten(getattr(vartype, 'TYPE_HEADERS') for vartype in Variable.registry.values()))\n    ))\n_RE_FLAGS = re.compile(r'^\\s*( |{}|)*\\s*$'.format(\n    '|'.join(flatten(filter(None, i) for i in Flags.ALL.items()))\n    ))\n\n\nclass FileFormatMeta(Registry):\n\n    def __new__(cls, name, bases, attrs):\n        newcls = super().__new__(cls, name, bases, attrs)\n\n        # Optionally add compressed versions of extensions as supported\n        if getattr(newcls, 'SUPPORT_COMPRESSED', False):\n            new_extensions = list(getattr(newcls, 'EXTENSIONS', ()))\n            for compression in Compression.all:\n                for ext in newcls.EXTENSIONS:\n                    new_extensions.append(ext + compression)\n                if sys.platform in ('darwin', 'win32'):\n                    # OSX file dialog doesn't support filtering on double\n                    # extensions (e.g. .csv.gz)\n                    # https://bugreports.qt.io/browse/QTBUG-38303\n                    # This is just here for OWFile that gets QFileDialog\n                    # filters from FileFormat.readers.keys()\n                    # EDIT: Windows exhibit similar problems:\n                    # while .tab.gz works, .tab.xz and .tab.bz2 do not!\n                    new_extensions.append(compression)\n            newcls.EXTENSIONS = tuple(new_extensions)\n\n        return newcls\n\n    @property\n    def formats(cls):\n        return cls.registry.values()\n\n    @lru_cache(5)\n    def _ext_to_attr_if_attr2(cls, attr, attr2):\n        \"\"\"\n        Return ``{ext: `attr`, ...}`` dict if ``cls`` has `attr2`.\n        If `attr` is '', return ``{ext: cls, ...}`` instead.\n\n        If there are multiple formats for an extension, return a format\n        with the lowest priority.\n        \"\"\"\n        formats = OrderedDict()\n        for format in sorted(cls.registry.values(), key=lambda x: x.PRIORITY):\n            if not hasattr(format, attr2):\n                continue\n            for ext in getattr(format, 'EXTENSIONS', []):\n                # Only adds if not yet registered\n                formats.setdefault(ext, getattr(format, attr, format))\n        return formats\n\n    @property\n    def names(cls):\n        return cls._ext_to_attr_if_attr2('DESCRIPTION', '__class__')\n\n    @property\n    def writers(cls):\n        return cls._ext_to_attr_if_attr2('', 'write_file')\n\n    @property\n    def readers(cls):\n        return cls._ext_to_attr_if_attr2('', 'read')\n\n    @property\n    def img_writers(cls):\n        warnings.warn(\n            f\"'{__name__}.FileFormat.img_writers' is no longer used and \"\n            \"will be removed. Please use \"\n            \"'Orange.widgets.io.FileFormat.img_writers' instead.\",\n            DeprecationWarning, stacklevel=2\n        )\n        return cls._ext_to_attr_if_attr2('', 'write_image')\n\n    @property\n    def graph_writers(cls):\n        return cls._ext_to_attr_if_attr2('', 'write_graph')\n\n\nclass FileFormat(metaclass=FileFormatMeta):\n    \"\"\"\n    Subclasses set the following attributes and override the following methods:\n\n        EXTENSIONS = ('.ext1', '.ext2', ...)\n        DESCRIPTION = 'human-readable file format description'\n        SUPPORT_COMPRESSED = False\n        SUPPORT_SPARSE_DATA = False\n\n        def read(self):\n            ...  # load headers, data, ...\n            return self.data_table(data, headers)\n\n        @classmethod\n        def write_file(cls, filename, data):\n            ...\n            self.write_headers(writer.write, data)\n            writer.writerows(data)\n\n    Wrapper FileFormat.data_table() returns Orange.data.Table from `data`\n    iterable (list (rows) of lists of values (cols)).\n    \"\"\"\n\n    # Priority when multiple formats support the same extension. Also\n    # the sort order in file open/save combo boxes. Lower is better.\n    PRIORITY = 10000\n    OPTIONAL_TYPE_ANNOTATIONS = False\n\n    def __init__(self, filename):\n        \"\"\"\n        Parameters\n        ----------\n        filename : str\n            name of the file to open\n        \"\"\"\n        self.filename = filename\n        self.sheet = None\n\n    @property\n    def sheets(self):\n        \"\"\"FileFormats with a notion of sheets should override this property\n        to return a list of sheet names in the file.\n\n        Returns\n        -------\n        a list of sheet names\n        \"\"\"\n        return ()\n\n    def select_sheet(self, sheet):\n        \"\"\"Select sheet to be read\n\n        Parameters\n        ----------\n        sheet : str\n            sheet name\n        \"\"\"\n        self.sheet = sheet\n\n    @classmethod\n    def get_reader(cls, filename):\n        \"\"\"Return reader instance that can be used to read the file\n\n        Parameters\n        ----------\n        filename : str\n\n        Returns\n        -------\n        FileFormat\n        \"\"\"\n        for ext, reader in cls.readers.items():\n            # Skip ambiguous, invalid compression-only extensions added on OSX\n            if ext in Compression.all:\n                continue\n            if fnmatch(path.basename(filename), '*' + ext):\n                return reader(filename)\n\n        raise IOError('No readers for file \"{}\"'.format(filename))\n\n    @classmethod\n    def write(cls, filename, data, with_annotations=True):\n        if cls.OPTIONAL_TYPE_ANNOTATIONS:\n            return cls.write_file(filename, data, with_annotations)\n        else:\n            return cls.write_file(filename, data)\n\n    @classmethod\n    def write_table_metadata(cls, filename, data):\n\n        def write_file(fn):\n            if all(isinstance(key, str) and isinstance(value, str)\n                   for key, value in data.attributes.items()):\n                with open(fn, 'w', encoding='utf-8') as f:\n                    f.write(\"\\n\".join(\"{}: {}\".format(*kv)\n                                      for kv in data.attributes.items()))\n            else:\n                with open(fn, 'wb') as f:\n                    pickle.dump(data.attributes, f, pickle.HIGHEST_PROTOCOL)\n\n        if isinstance(filename, str):\n            metafile = filename + '.metadata'\n            if getattr(data, 'attributes', None):\n                write_file(metafile)\n            elif path.exists(metafile):\n                remove(metafile)\n\n    @classmethod\n    def set_table_metadata(cls, filename, table):\n        # pylint: disable=bare-except\n        if isinstance(filename, str) and path.exists(filename + '.metadata'):\n            try:\n                with open(filename + '.metadata', 'rb') as f:\n                    table.attributes = pickle.load(f)\n            # Unpickling throws different exceptions, not just UnpickleError\n            except:\n                with open(filename + '.metadata', encoding='utf-8') as f:\n                    table.attributes = OrderedDict(\n                        (k.strip(), v.strip())\n                        for k, v in (line.split(\":\", 1)\n                                     for line in f.readlines()))\n\n    @classmethod\n    def locate(cls, filename, search_dirs=('.',)):\n        \"\"\"Locate a file with given filename that can be opened by one\n        of the available readers.\n\n        Parameters\n        ----------\n        filename : str\n        search_dirs : Iterable[str]\n\n        Returns\n        -------\n        str\n            Absolute path to the file\n        \"\"\"\n        if path.exists(filename):\n            return filename\n\n        for directory in search_dirs:\n            absolute_filename = path.join(directory, filename)\n            if path.exists(absolute_filename):\n                break\n            for ext in cls.readers:\n                if fnmatch(path.basename(filename), '*' + ext):\n                    break\n                # glob uses fnmatch internally\n                matching_files = glob(absolute_filename + ext)\n                if matching_files:\n                    absolute_filename = matching_files[0]\n                    break\n            if path.exists(absolute_filename):\n                break\n        else:\n            absolute_filename = \"\"\n\n        if not path.exists(absolute_filename):\n            raise IOError('File \"{}\" was not found.'.format(filename))\n\n        return absolute_filename\n\n    @staticmethod\n    def open(filename, *args, **kwargs):\n        \"\"\"\n        Format handlers can use this method instead of the builtin ``open()``\n        to transparently (de)compress files if requested (according to\n        `filename` extension). Set ``SUPPORT_COMPRESSED=True`` if you use this.\n        \"\"\"\n        return open_compressed(filename, *args, **kwargs)\n\n    @staticmethod\n    def parse_headers(data):\n        \"\"\"Return (header rows, rest of data) as discerned from `data`\"\"\"\n\n        def is_number(item):\n            try:\n                float(item)\n            except ValueError:\n                return False\n            return True\n        # Second row items are type identifiers\n        def header_test2(items):\n            return all(map(_RE_TYPES.match, items))\n        # Third row items are flags and column attributes (attr=value)\n        def header_test3(items):\n            return all(map(_RE_FLAGS.match, items))\n\n        data = iter(data)\n        header_rows = []\n\n        # Try to parse a three-line header\n        lines = []\n        try:\n            lines.append(list(next(data)))\n            lines.append(list(next(data)))\n            lines.append(list(next(data)))\n        except StopIteration:\n            lines, data = [], chain(lines, data)\n        if lines:\n            l1, l2, l3 = lines\n            # Three-line header if line 2 & 3 match (1st line can be anything)\n            if header_test2(l2) and header_test3(l3):\n                header_rows = [l1, l2, l3]\n            else:\n                lines, data = [], chain((l1, l2, l3), data)\n\n        # Try to parse a single-line header\n        if not header_rows:\n            try:\n                lines.append(list(next(data)))\n            except StopIteration:\n                pass\n            if lines:\n                # Header if none of the values in line 1 parses as a number\n                if not all(is_number(i) for i in lines[0]):\n                    header_rows = [lines[0]]\n                else:\n                    data = chain(lines, data)\n\n        return header_rows, data\n\n    @classmethod\n    def data_table(cls, data, headers=None):\n        \"\"\"\n        Return Orange.data.Table given rows of `headers` (iterable of iterable)\n        and rows of `data` (iterable of iterable).\n\n        Basically, the idea of subclasses is to produce those two iterables,\n        however they might.\n\n        If `headers` is not provided, the header rows are extracted from `data`,\n        assuming they precede it.\n        \"\"\"\n        if not headers:\n            headers, data = cls.parse_headers(data)\n\n        # Consider various header types (single-row, two-row, three-row, none)\n        if len(headers) == 3:\n            names, types, flags = map(list, headers)\n        else:\n            if len(headers) == 1:\n                HEADER1_FLAG_SEP = '#'\n                # First row format either:\n                #   1) delimited column names\n                #   2) -||- with type and flags prepended, separated by #,\n                #      e.g. d#sex,c#age,cC#IQ\n                _flags, names = zip(*[i.split(HEADER1_FLAG_SEP, 1)\n                                      if HEADER1_FLAG_SEP in i else ('', i)\n                                      for i in headers[0]]\n                                   )\n                names = list(names)\n            elif len(headers) == 2:\n                names, _flags = map(list, headers)\n            else:\n                # Use heuristics for everything\n                names, _flags = [], []\n            types = [''.join(filter(str.isupper, flag)).lower() for flag in _flags]\n            flags = [Flags.join(filter(str.islower, flag)) for flag in _flags]\n\n        # Determine maximum row length\n        rowlen = max(map(len, (names, types, flags)))\n\n        strip = False\n\n        def _equal_length(lst):\n            nonlocal strip\n            if len(lst) > rowlen > 0:\n                lst = lst[:rowlen]\n                strip = True\n            elif len(lst) < rowlen:\n                lst.extend(['']*(rowlen - len(lst)))\n            return lst\n\n        # Ensure all data is of equal width in a column-contiguous array\n        data = [_equal_length([s.strip() for s in row])\n                for row in data if any(row)]\n        data = np.array(data, dtype=object, order='F')\n\n        if strip:\n            warnings.warn(\"Columns with no headers were removed.\")\n\n        # Data may actually be longer than headers were\n        try:\n            rowlen = data.shape[1]\n        except IndexError:\n            pass\n        else:\n            for lst in (names, types, flags):\n                _equal_length(lst)\n\n        NAMEGEN = namegen('Feature ', 1)\n        Xcols, attrs = [], []\n        Mcols, metas = [], []\n        Ycols, clses = [], []\n        Wcols = []\n\n        # Rename variables if necessary\n        # Reusing across files still works if both files have same duplicates\n        name_counts = Counter(names)\n        del name_counts[\"\"]\n        if len(name_counts) != len(names) and name_counts:\n            uses = {name: 0 for name, count in name_counts.items() if count > 1}\n            for i, name in enumerate(names):\n                if name in uses:\n                    uses[name] += 1\n                    names[i] = \"{}_{}\".format(name, uses[name])\n\n        namask = np.empty(data.shape[0], dtype=bool)\n        # Iterate through the columns\n        for col in range(rowlen):\n            flag = Flags(Flags.split(flags[col]))\n            if flag.i:\n                continue\n\n            type_flag = types and types[col].strip()\n            try:\n                orig_values = data[:, col]\n            except IndexError:\n                orig_values = np.array([], dtype=object)\n\n            namask = isnastr(orig_values, out=namask)\n\n            coltype_kwargs = {}\n            valuemap = None\n            values = orig_values\n\n            if type_flag in StringVariable.TYPE_HEADERS:\n                coltype = StringVariable\n                values = orig_values\n            elif type_flag in ContinuousVariable.TYPE_HEADERS:\n                coltype = ContinuousVariable\n                values = np.empty(data.shape[0], dtype=float)\n                try:\n                    np.copyto(values, orig_values, casting=\"unsafe\",\n                              where=~namask)\n                    values[namask] = np.nan\n                except ValueError:\n                    for row, num in enumerate(orig_values):\n                        if not isnastr(num):\n                            try:\n                                float(num)\n                            except ValueError:\n                                break\n                    raise ValueError('Non-continuous value in (1-based) '\n                                     'line {}, column {}'.format(row + len(headers) + 1,\n                                                                 col + 1))\n\n            elif type_flag in TimeVariable.TYPE_HEADERS:\n                coltype = TimeVariable\n                values = np.where(namask, \"\", orig_values)\n            elif (type_flag in DiscreteVariable.TYPE_HEADERS or\n                  _RE_DISCRETE_LIST.match(type_flag)):\n                coltype = DiscreteVariable\n                orig_values = values = np.where(namask, \"\", orig_values)\n                if _RE_DISCRETE_LIST.match(type_flag):\n                    valuemap = Flags.split(type_flag)\n                    coltype_kwargs.update(ordered=True)\n                else:\n                    valuemap = sorted(set(orig_values) - {\"\"})\n            else:\n                # No known type specified, use heuristics\n                valuemap, values, coltype = guess_data_type(orig_values, namask)\n\n            if flag.m or coltype is StringVariable:\n                append_to = (Mcols, metas)\n            elif flag.w:\n                append_to = (Wcols, None)\n            elif flag.c:\n                append_to = (Ycols, clses)\n            else:\n                append_to = (Xcols, attrs)\n\n            cols, domain_vars = append_to\n\n            if domain_vars is not None:\n                var_name = names and names[col]\n                if not var_name:\n                    var_name = next(NAMEGEN)\n\n                values, var = sanitize_variable(\n                    valuemap, values, orig_values, coltype, coltype_kwargs,\n                    name=var_name)\n            else:\n                var = None\n            if domain_vars is not None:\n                var.attributes.update(flag.attributes)\n                domain_vars.append(var)\n\n            if isinstance(values, np.ndarray) and not values.flags.owndata:\n                values = values.copy()  # might view `data` (string columns)\n            cols.append(values)\n\n            try:\n                # allow gc to reclaim memory used by string values\n                data[:, col] = None\n            except IndexError:\n                pass\n\n        domain = Domain(attrs, clses, metas)\n\n        if not data.size:\n            return Table.from_domain(domain, 0)\n\n        X = Y = M = W = None\n        if Xcols:\n            X = np.c_[tuple(Xcols)]\n            assert X.dtype == np.float_\n        else:\n            X = np.empty((data.shape[0], 0), dtype=np.float_)\n        if Ycols:\n            Y = np.c_[tuple(Ycols)]\n            assert Y.dtype == np.float_\n        if Mcols:\n            M = np.c_[tuple(Mcols)].astype(object)\n        if Wcols:\n            W = np.c_[tuple(Wcols)].astype(float)\n\n        table = Table.from_numpy(domain, X, Y, M, W)\n        return table\n\n    @staticmethod\n    def header_names(data):\n        return ['weights'] * data.has_weights() + \\\n               [v.name for v in chain(data.domain.attributes,\n                                      data.domain.class_vars,\n                                      data.domain.metas)]\n\n    @staticmethod\n    def header_types(data):\n        def _vartype(var):\n            if var.is_continuous or var.is_string:\n                return var.TYPE_HEADERS[0]\n            elif var.is_discrete:\n                return Flags.join(var.values) if var.ordered else var.TYPE_HEADERS[0]\n            raise NotImplementedError\n        return ['continuous'] * data.has_weights() + \\\n               [_vartype(v) for v in chain(data.domain.attributes,\n                                           data.domain.class_vars,\n                                           data.domain.metas)]\n\n    @staticmethod\n    def header_flags(data):\n        return list(chain(['weight'] * data.has_weights(),\n                          (Flags.join([flag], *('{}={}'.format(*a)\n                                                for a in sorted(var.attributes.items())))\n                           for flag, var in chain(zip(repeat(''), data.domain.attributes),\n                                                  zip(repeat('class'), data.domain.class_vars),\n                                                  zip(repeat('meta'), data.domain.metas)))))\n\n    @classmethod\n    def write_headers(cls, write, data, with_annotations=True):\n        \"\"\"`write` is a callback that accepts an iterable\"\"\"\n        write(cls.header_names(data))\n        if with_annotations:\n            write(cls.header_types(data))\n            write(cls.header_flags(data))\n\n    @classmethod\n    def formatter(cls, var):\n        # type: (Variable) -> Callable[[Variable], Any]\n        # Return a column 'formatter' function. The function must return\n        # something that `write` knows how to write\n        if var.is_time:\n            return var.repr_val\n        elif var.is_continuous:\n            return lambda value: \"\" if isnan(value) else value\n        elif var.is_discrete:\n            return lambda value: \"\" if isnan(value) else var.values[int(value)]\n        elif var.is_string:\n            return lambda value: value\n        else:\n            return var.repr_val\n\n    @classmethod\n    def write_data(cls, write, data):\n        \"\"\"`write` is a callback that accepts an iterable\"\"\"\n        vars = list(chain((ContinuousVariable('_w'),) if data.has_weights() else (),\n                          data.domain.attributes,\n                          data.domain.class_vars,\n                          data.domain.metas))\n\n        formatters = [cls.formatter(v) for v in vars]\n        for row in zip(data.W if data.W.ndim > 1 else data.W[:, np.newaxis],\n                       data.X,\n                       data.Y if data.Y.ndim > 1 else data.Y[:, np.newaxis],\n                       data.metas):\n            write([fmt(v) for fmt, v in zip(formatters, flatten(row))])\n\n    @classmethod\n    def qualified_name(cls):\n        return cls.__module__ + '.' + cls.__name__\n\n\ndef class_from_qualified_name(format_name):\n    \"\"\" File format class from qualified name. \"\"\"\n    elements = format_name.split(\".\")\n    m = import_module(\".\".join(elements[:-1]))\n    return getattr(m, elements[-1])\n\n\nclass CSVReader(FileFormat):\n    \"\"\"Reader for comma separated files\"\"\"\n\n    EXTENSIONS = ('.csv',)\n    DESCRIPTION = 'Comma-separated values'\n    DELIMITERS = ',;:\\t$ '\n    SUPPORT_COMPRESSED = True\n    SUPPORT_SPARSE_DATA = False\n    PRIORITY = 20\n    OPTIONAL_TYPE_ANNOTATIONS = True\n\n    def read(self):\n        for encoding in (lambda: ('us-ascii', None),                 # fast\n                         lambda: (detect_encoding(self.filename), None),  # precise\n                         lambda: (locale.getpreferredencoding(False), None),\n                         lambda: (sys.getdefaultencoding(), None),   # desperate\n                         lambda: ('utf-8', None),                    # ...\n                         lambda: ('utf-8', 'ignore')):               # fallback\n            encoding, errors = encoding()\n            # Clear the error flag for all except the last check, because\n            # the error of second-to-last check is stored and shown as warning in owfile\n            if errors != 'ignore':\n                error = ''\n            with self.open(self.filename, mode='rt', newline='',\n                           encoding=encoding, errors=errors) as file:\n                # Sniff the CSV dialect (delimiter, quotes, ...)\n                try:\n                    dialect = csv.Sniffer().sniff(\n                        # Take first couple of *complete* lines as sample\n                        ''.join(file.readline() for _ in range(10)),\n                        self.DELIMITERS)\n                    delimiter = dialect.delimiter\n                    quotechar = dialect.quotechar\n                except UnicodeDecodeError as e:\n                    error = e\n                    continue\n                except csv.Error:\n                    delimiter = self.DELIMITERS[0]\n                    quotechar = csv.excel.quotechar\n\n                file.seek(0)\n                try:\n                    reader = csv.reader(\n                        file, delimiter=delimiter, quotechar=quotechar,\n                        skipinitialspace=True,\n                    )\n                    data = self.data_table(reader)\n\n                    # TODO: Name can be set unconditionally when/if\n                    # self.filename will always be a string with the file name.\n                    # Currently, some tests pass StringIO instead of\n                    # the file name to a reader.\n                    if isinstance(self.filename, str):\n                        data.name = path.splitext(\n                            path.split(self.filename)[-1])[0]\n                    if error and isinstance(error, UnicodeDecodeError):\n                        pos, endpos = error.args[2], error.args[3]\n                        warning = ('Skipped invalid byte(s) in position '\n                                   '{}{}').format(pos,\n                                                  ('-' + str(endpos)) if (endpos - pos) > 1 else '')\n                        warnings.warn(warning)\n                    self.set_table_metadata(self.filename, data)\n                    return data\n                except Exception as e:\n                    error = e\n                    continue\n        raise ValueError('Cannot parse dataset {}: {}'.format(self.filename, error)) from error\n\n    @classmethod\n    def write_file(cls, filename, data, with_annotations=True):\n        with cls.open(filename, mode='wt', newline='', encoding='utf-8') as file:\n            writer = csv.writer(file, delimiter=cls.DELIMITERS[0])\n            cls.write_headers(writer.writerow, data, with_annotations)\n            cls.write_data(writer.writerow, data)\n            cls.write_table_metadata(filename, data)\n\n\nclass TabReader(CSVReader):\n    \"\"\"Reader for tab separated files\"\"\"\n    EXTENSIONS = ('.tab', '.tsv')\n    DESCRIPTION = 'Tab-separated values'\n    DELIMITERS = '\\t'\n    PRIORITY = 10\n\n\nclass PickleReader(FileFormat):\n    \"\"\"Reader for pickled Table objects\"\"\"\n    EXTENSIONS = ('.pkl', '.pickle')\n    DESCRIPTION = 'Pickled Orange data'\n    SUPPORT_COMPRESSED = True\n    SUPPORT_SPARSE_DATA = True\n\n    def read(self):\n        with self.open(self.filename, 'rb') as f:\n            table = pickle.load(f)\n            if not isinstance(table, Table):\n                raise TypeError(\"file does not contain a data table\")\n            else:\n                return table\n\n    @classmethod\n    def write_file(cls, filename, data):\n        with cls.open(filename, 'wb') as f:\n            pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)\n\n\nclass BasketReader(FileFormat):\n    \"\"\"Reader for basket (sparse) files\"\"\"\n    EXTENSIONS = ('.basket', '.bsk')\n    DESCRIPTION = 'Basket file'\n    SUPPORT_SPARSE_DATA = True\n\n    def read(self):\n        def constr_vars(inds):\n            if inds:\n                return [ContinuousVariable(x.decode(\"utf-8\")) for _, x in\n                        sorted((ind, name) for name, ind in inds.items())]\n\n        X, Y, metas, attr_indices, class_indices, meta_indices = \\\n            _io.sparse_read_float(self.filename.encode(sys.getdefaultencoding()))\n\n        attrs = constr_vars(attr_indices)\n        classes = constr_vars(class_indices)\n        meta_attrs = constr_vars(meta_indices)\n        domain = Domain(attrs, classes, meta_attrs)\n        table = Table.from_numpy(\n            domain, attrs and X, classes and Y, metas and meta_attrs)\n        table.name = path.splitext(path.split(self.filename)[-1])[0]\n        return table\n\n\nclass ExcelReader(FileFormat):\n    \"\"\"Reader for excel files\"\"\"\n    EXTENSIONS = ('.xlsx',)\n    DESCRIPTION = 'Microsoft Excel spreadsheet'\n    SUPPORT_COMPRESSED = False\n    SUPPORT_SPARSE_DATA = False\n\n    def __init__(self, filename):\n        super().__init__(filename=filename)\n        self._workbook = None\n\n    @property\n    def workbook(self):\n        if not self._workbook:\n            self._workbook = xlrd.open_workbook(self.filename, on_demand=True)\n        return self._workbook\n\n    @property\n    @lru_cache(1)\n    def sheets(self):\n        if self.workbook:\n            return self.workbook.sheet_names()\n        else:\n            return ()\n\n    def read(self):\n        if self.sheet:\n            ss = self.workbook.sheet_by_name(self.sheet)\n        else:\n            ss = self.workbook.sheet_by_index(0)\n        try:\n            first_row = next(i for i in range(ss.nrows) if any(ss.row_values(i)))\n            first_col = next(i for i in range(ss.ncols) if ss.cell_value(first_row, i))\n            row_len = ss.row_len(first_row)\n            cells = filter(any,\n                           [[str(ss.cell_value(row, col)) if col < ss.row_len(row) else ''\n                             for col in range(first_col, row_len)]\n                            for row in range(first_row, ss.nrows)])\n            table = self.data_table(cells)\n            table.name = path.splitext(path.split(self.filename)[-1])[0]\n            if self.sheet:\n                table.name = '-'.join((table.name, self.sheet))\n        except Exception:\n            raise IOError(\"Couldn't load spreadsheet from \" + self.filename)\n        return table\n\n    @classmethod\n    def write_file(cls, filename, data):\n        vars = list(chain((ContinuousVariable('_w'),) if data.has_weights() else (),\n                          data.domain.attributes,\n                          data.domain.class_vars,\n                          data.domain.metas))\n        formatters = [cls.formatter(v) for v in vars]\n        zipped_list_data = zip(data.W if data.W.ndim > 1 else data.W[:, np.newaxis],\n                               data.X,\n                               data.Y if data.Y.ndim > 1 else data.Y[:, np.newaxis],\n                               data.metas)\n        headers = cls.header_names(data)\n        workbook = xlsxwriter.Workbook(filename)\n        sheet = workbook.add_worksheet()\n        for c, header in enumerate(headers):\n            sheet.write(0, c, header)\n        for i, row in enumerate(zipped_list_data, 1):\n            for j, (fmt, v) in enumerate(zip(formatters, flatten(row))):\n                sheet.write(i, j, fmt(v))\n        workbook.close()\n\n\nclass DotReader(FileFormat):\n    \"\"\"Writer for dot (graph) files\"\"\"\n    EXTENSIONS = ('.dot', '.gv')\n    DESCRIPTION = 'Dot graph description'\n    SUPPORT_COMPRESSED = True\n    SUPPORT_SPARSE_DATA = False\n\n    @classmethod\n    def write_graph(cls, filename, graph):\n        from sklearn import tree\n        tree.export_graphviz(graph, out_file=cls.open(filename, 'wt'))\n\n    @classmethod\n    def write(cls, filename, tree):\n        if type(tree) == dict:\n            tree = tree['tree']\n        cls.write_graph(filename, tree)\n\n\nclass UrlReader(FileFormat):\n    def __init__(self, filename):\n        filename = filename.strip()\n        if not urlparse(filename).scheme:\n            filename = 'http://' + filename\n        super().__init__(filename)\n\n    @staticmethod\n    def urlopen(url):\n        req = Request(\n            url,\n            # Avoid 403 error with servers that dislike scrapers\n            headers={'User-Agent': 'Mozilla/5.0 (X11; Linux) Gecko/20100101 Firefox/'})\n        return urlopen(req, timeout=10)\n\n    def read(self):\n        self.filename = self._trim(self._resolve_redirects(self.filename))\n        with contextlib.closing(self.urlopen(self.filename)) as response:\n            name = self._suggest_filename(response.headers['content-disposition'])\n            with NamedTemporaryFile(suffix=name, delete=False) as f:\n                f.write(response.read())\n                # delete=False is a workaround for https://bugs.python.org/issue14243\n\n            reader = self.get_reader(f.name)\n            data = reader.read()\n            remove(f.name)\n        # Override name set in from_file() to avoid holding the temp prefix\n        data.name = path.splitext(name)[0]\n        data.origin = self.filename\n        return data\n\n    def _resolve_redirects(self, url):\n        # Resolve (potential) redirects to a final URL\n        with contextlib.closing(self.urlopen(url)) as response:\n            return response.url\n\n    @classmethod\n    def _trim(cls, url):\n        URL_TRIMMERS = (\n            cls._trim_googlesheet,\n            cls._trim_dropbox,\n        )\n        for trim in URL_TRIMMERS:\n            try:\n                url = trim(url)\n            except ValueError:\n                continue\n            else:\n                break\n        return url\n\n    @staticmethod\n    def _trim_googlesheet(url):\n        match = re.match(r'(?:https?://)?(?:www\\.)?'\n                         r'docs\\.google\\.com/spreadsheets/d/'\n                         r'(?P<workbook_id>[-\\w_]+)'\n                         r'(?:/.*?gid=(?P<sheet_id>\\d+).*|.*)?',\n                         url, re.IGNORECASE)\n        try:\n            workbook, sheet = match.group('workbook_id'), match.group('sheet_id')\n            if not workbook:\n                raise ValueError\n        except (AttributeError, ValueError):\n            raise ValueError\n        url = 'https://docs.google.com/spreadsheets/d/{}/export?format=tsv'.format(workbook)\n        if sheet:\n            url += '&gid=' + sheet\n        return url\n\n    @staticmethod\n    def _trim_dropbox(url):\n        parts = urlsplit(url)\n        if not parts.netloc.endswith('dropbox.com'):\n            raise ValueError\n        return urlunsplit(parts._replace(query='dl=1'))\n\n    def _suggest_filename(self, content_disposition):\n        default_name = re.sub(r'[\\\\:/]', '_', urlparse(self.filename).path)\n\n        # See https://tools.ietf.org/html/rfc6266#section-4.1\n        matches = re.findall(r\"filename\\*?=(?:\\\"|.{0,10}?'[^']*')([^\\\"]+)\",\n                             content_disposition or '')\n        return urlunquote(matches[-1]) if matches else default_name\n", "Orange/data/variable.py": "import re\nimport warnings\nfrom collections import Iterable\n\nfrom datetime import datetime, timedelta, timezone\nfrom numbers import Number, Real, Integral\nfrom math import isnan, floor\nfrom pickle import PickleError\n\nimport numpy as np\nimport scipy.sparse as sp\n\nfrom Orange.data import _variable\nfrom Orange.util import Registry, hex_to_color, Reprable,\\\n    OrangeDeprecationWarning\n\n__all__ = [\"Unknown\", \"MISSING_VALUES\", \"make_variable\", \"is_discrete_values\",\n           \"Value\", \"Variable\", \"ContinuousVariable\", \"DiscreteVariable\",\n           \"StringVariable\", \"TimeVariable\"]\n\n\n# For storing unknowns\nUnknown = ValueUnknown = float(\"nan\")\n# For checking for unknowns\nMISSING_VALUES = {np.nan, \"?\", \"nan\", \".\", \"\", \"NA\", \"~\", None}\n\nDISCRETE_MAX_VALUES = 3  # == 2 + nan\nMAX_NUM_OF_DECIMALS = 5\n# the variable with more than 100 different values should not be StringVariable\nDISCRETE_MAX_ALLOWED_VALUES = 100\n\n\ndef make_variable(cls, compute_value, *args):\n    if compute_value is not None:\n        return cls(*args, compute_value=compute_value)\n    else:\n        # For compatibility with old pickles\n        return cls(*args)\n\n\ndef is_discrete_values(values):\n    \"\"\"\n    Return set of uniques if `values` is an iterable of discrete values\n    else False if non-discrete, or None if indeterminate.\n\n    Note\n    ----\n    Assumes consistent type of items of `values`.\n    \"\"\"\n    if not len(values):\n        return None\n    # If the first few values are, or can be converted to, floats,\n    # the type is numeric\n    try:\n        isinstance(next(iter(values)), Number) or \\\n        [float(v) for _, v in zip(range(min(3, len(values))), values)]\n    except ValueError:\n        is_numeric = False\n        max_values = int(round(len(values)**.7))\n    else:\n        is_numeric = True\n        max_values = DISCRETE_MAX_VALUES\n\n    # If more than max values => not discrete\n    unique = set()\n    for i in values:\n        unique.add(i)\n        if (len(unique) > max_values or\n                len(unique) > DISCRETE_MAX_ALLOWED_VALUES):\n            return False\n\n    # Strip NaN from unique\n    unique = {i for i in unique\n              if (not i in MISSING_VALUES and\n                  not (isinstance(i, Number) and np.isnan(i)))}\n\n    # All NaNs => indeterminate\n    if not unique:\n        return None\n\n    # Strings with |values| < max_unique\n    if not is_numeric:\n        return unique\n\n    # Handle numbers\n    try:\n        unique_float = set(map(float, unique))\n    except ValueError:\n        # Converting all the values to floats resulted in an error.\n        # Since the values have enough unique values, they are probably\n        # string values and discrete.\n        return unique\n\n    # If only values are {0, 1} or {1, 2} (or a subset of those sets) => discrete\n    return (not (unique_float - {0, 1}) or\n            not (unique_float - {1, 2})) and unique\n\n\nclass Value(float):\n    \"\"\"\n    The class representing a value. The class is not used to store values but\n    only to return them in contexts in which we want the value to be accompanied\n    with the descriptor, for instance to print the symbolic value of discrete\n    variables.\n\n    The class is derived from `float`, with an additional attribute `variable`\n    which holds the descriptor of type :obj:`Orange.data.Variable`. If the\n    value continuous or discrete, it is stored as a float. Other types of\n    values, like strings, are stored in the attribute `value`.\n\n    The class overloads the methods for printing out the value:\n    `variable.repr_val` and `variable.str_val` are used to get a suitable\n    representation of the value.\n\n    Equivalence operator is overloaded as follows:\n\n    - unknown values are equal; if one value is unknown and the other is not,\n      they are different;\n\n    - if the value is compared with the string, the value is converted to a\n      string using `variable.str_val` and the two strings are compared\n\n    - if the value is stored in attribute `value`, it is compared with the\n      given other value\n\n    - otherwise, the inherited comparison operator for `float` is called.\n\n    Finally, value defines a hash, so values can be put in sets and appear as\n    keys in dictionaries.\n\n    .. attribute:: variable (:obj:`Orange.data.Variable`)\n\n        Descriptor; used for printing out and for comparing with strings\n\n    .. attribute:: value\n\n        Value; the value can be of arbitrary type and is used only for variables\n        that are neither discrete nor continuous. If `value` is `None`, the\n        derived `float` value is used.\n    \"\"\"\n    __slots__ = \"variable\", \"_value\"\n\n    def __new__(cls, variable, value=Unknown):\n        \"\"\"\n        Construct a new instance of Value with the given descriptor and value.\n        If the argument `value` can be converted to float, it is stored as\n        `float` and the attribute `value` is set to `None`. Otherwise, the\n        inherited float is set to `Unknown` and the value is held by the\n        attribute `value`.\n\n        :param variable: descriptor\n        :type variable: Orange.data.Variable\n        :param value: value\n        \"\"\"\n        if variable.is_primitive():\n            self = super().__new__(cls, value)\n            self.variable = variable\n            self._value = None\n        else:\n            isunknown = value == variable.Unknown\n            self = super().__new__(\n                cls, np.nan if isunknown else np.finfo(float).min)\n            self.variable = variable\n            self._value = value\n        return self\n\n    def __init__(self, _, __=Unknown):\n        pass\n\n    def __repr__(self):\n        return \"Value('%s', %s)\" % (self.variable.name,\n                                    self.variable.repr_val(self))\n\n    def __str__(self):\n        return self.variable.str_val(self)\n\n    def __eq__(self, other):\n        if isinstance(self, Real) and isnan(self):\n            return (isinstance(other, Real) and isnan(other)\n                    or other in self.variable.unknown_str)\n        if isinstance(other, str):\n            return self.variable.str_val(self) == other\n        if isinstance(other, Value):\n            return self.value == other.value\n        return super().__eq__(other)\n\n    def __ne__(self, other):\n        return not self.__eq__(other)\n\n    def __lt__(self, other):\n        if self.variable.is_primitive():\n            if isinstance(other, str):\n                return super().__lt__(self.variable.to_val(other))\n            else:\n                return super().__lt__(other)\n        else:\n            if isinstance(other, str):\n                return self.value < other\n            else:\n                return self.value < other.value\n\n    def __le__(self, other):\n        return self.__lt__(other) or self.__eq__(other)\n\n    def __gt__(self, other):\n        return not self.__le__(other)\n\n    def __ge__(self, other):\n        return not self.__lt__(other)\n\n    def __contains__(self, other):\n        if (self._value is not None\n                and isinstance(self._value, str)\n                and isinstance(other, str)):\n            return other in self._value\n        raise TypeError(\"invalid operation on Value()\")\n\n    def __hash__(self):\n        if self.variable.is_discrete:\n            # It is not possible to hash the id and the domain value to the\n            # same number as required by __eq__.\n            # hash(1) == hash(Value(DiscreteVariable(\"var\", [\"red\", \"green\", \"blue\"]), 1)) == hash(\"green\")\n            # User should hash directly ids or domain values instead.\n            raise TypeError(\"unhashable type - cannot hash values of discrete variables!\")\n        if self._value is None:\n            return super().__hash__()\n        else:\n            return hash(self._value)\n\n    @property\n    def value(self):\n        if self.variable.is_discrete:\n            return Unknown if isnan(self) else self.variable.values[int(self)]\n        if self.variable.is_string:\n            return self._value\n        return float(self)\n\n    def __getnewargs__(self):\n        return self.variable, float(self)\n\n    def __getstate__(self):\n        return dict(value=getattr(self, '_value', None))\n\n    def __setstate__(self, state):\n        self._value = state.get('value', None)\n\n\nclass VariableMeta(Registry):\n    pass\n\n\nclass _predicatedescriptor(property):\n    \"\"\"\n    A property that behaves as a class method if accessed via a class\n    >>> class A:\n    ...     foo = False\n    ...     @_predicatedescriptor\n    ...     def is_foo(self):\n    ...         return self.foo\n    ...\n    >>> a = A()\n    >>> a.is_foo\n    False\n    >>> A.is_foo(a)\n    False\n    \"\"\"\n    def __get__(self, instance, objtype=None):\n        if instance is None:\n            return self.fget\n        else:\n            return super().__get__(instance, objtype)\n\n\nclass Variable(Reprable, metaclass=VariableMeta):\n    \"\"\"\n    The base class for variable descriptors contains the variable's\n    name and some basic properties.\n\n    .. attribute:: name\n\n        The name of the variable.\n\n    .. attribute:: unknown_str\n\n        A set of values that represent unknowns in conversion from textual\n        formats. Default is `{\"?\", \".\", \"\", \"NA\", \"~\", None}`.\n\n    .. attribute:: compute_value\n\n        A function for computing the variable's value when converting from\n        another domain which does not contain this variable. The function will\n        be called with a data set (`Orange.data.Table`) and has to return\n        an array of computed values for all its instances. The base class\n        defines a static method `compute_value`, which returns `Unknown`.\n        Non-primitive variables must redefine it to return `None`.\n\n    .. attribute:: sparse\n\n        A flag about sparsity of the variable. When set, the variable suggests\n        it should be stored in a sparse matrix.\n\n    .. attribute:: source_variable\n\n        An optional descriptor of the source variable - if any - from which\n        this variable is derived and computed via :obj:`compute_value`.\n\n    .. attribute:: attributes\n\n        A dictionary with user-defined attributes of the variable\n    \"\"\"\n    Unknown = ValueUnknown\n\n    def __init__(self, name=\"\", compute_value=None, *, sparse=False):\n        \"\"\"\n        Construct a variable descriptor.\n        \"\"\"\n        if not name:\n            warnings.warn(\"Variable must have a name\", OrangeDeprecationWarning,\n                          stacklevel=3)\n        self._name = name\n        self._compute_value = compute_value\n        self.unknown_str = MISSING_VALUES\n        self.source_variable = None\n        self.sparse = sparse\n        self.attributes = {}\n        self._colors = None\n\n    @property\n    def name(self):\n        return self._name\n\n    @property\n    def colors(self):  # unreachable; pragma: no cover\n        return self._colors\n\n    @colors.setter\n    def colors(self, value):\n        self._colors = value\n\n    def make_proxy(self):\n        \"\"\"\n        Copy the variable and set the master to `self.master` or to `self`.\n\n        :return: copy of self\n        :rtype: Variable\n        \"\"\"\n        var = self.__class__(self.name)\n        var.__dict__.update(self.__dict__)\n        var.attributes = dict(self.attributes)\n        return var\n\n    def __eq__(self, other):\n        return type(self) is type(other) \\\n               and self.name == other.name \\\n               and self._compute_value == other._compute_value\n\n    def __hash__(self):\n        return hash((self.name, type(self), self._compute_value))\n\n    @classmethod\n    def make(cls, name, *args, **kwargs):\n        \"\"\"\n        Return an existing continuous variable with the given name, or\n        construct and return a new one.\n        \"\"\"\n        return cls(name, *args, **kwargs)\n\n    @classmethod\n    def _clear_cache(cls):\n        warnings.warn(\n            \"_clear_cache is no longer needed and thus deprecated\")\n\n    @staticmethod\n    def _clear_all_caches():\n        warnings.warn(\n            \"_clear_all_caches is no longer needed and thus deprecated\")\n\n    @classmethod\n    def is_primitive(cls, var=None):\n        \"\"\"\n        `True` if the variable's values are stored as floats.\n        Non-primitive variables can appear in the data only as meta attributes.\n        \"\"\"\n        to_check = cls if var is None else type(var)\n        return issubclass(to_check, (DiscreteVariable, ContinuousVariable))\n\n    @_predicatedescriptor\n    def is_discrete(self):\n        return isinstance(self, DiscreteVariable)\n\n    @_predicatedescriptor\n    def is_continuous(self):\n        return isinstance(self, ContinuousVariable)\n\n    @_predicatedescriptor\n    def is_string(self):\n        return isinstance(self, StringVariable)\n\n    @_predicatedescriptor\n    def is_time(self):\n        return isinstance(self, TimeVariable)\n\n    def repr_val(self, val):\n        \"\"\"\n        Return a textual representation of variable's value `val`. Argument\n        `val` must be a float (for primitive variables) or an arbitrary\n        Python object (for non-primitives).\n\n        Derived classes must overload the function.\n        \"\"\"\n        raise RuntimeError(\"variable descriptors must overload repr_val()\")\n\n    str_val = repr_val\n\n    def to_val(self, s):\n        \"\"\"\n        Convert the given argument to a value of the variable. The\n        argument can be a string, a number or `None`. For primitive variables,\n        the base class provides a method that returns\n        :obj:`~Orange.data.Unknown` if `s` is found in\n        :obj:`~Orange.data.Variable.unknown_str`, and raises an exception\n        otherwise. For non-primitive variables it returns the argument itself.\n\n        Derived classes of primitive variables must overload the function.\n\n        :param s: value, represented as a number, string or `None`\n        :type s: str, float or None\n        :rtype: float or object\n        \"\"\"\n        if not self.is_primitive():\n            return s\n        if s in self.unknown_str:\n            return Unknown\n        raise RuntimeError(\n            \"primitive variable descriptors must overload to_val()\")\n\n    def val_from_str_add(self, s):\n        \"\"\"\n        Convert the given string to a value of the variable. The method\n        is similar to :obj:`to_val` except that it only accepts strings and\n        that it adds new values to the variable's domain where applicable.\n\n        The base class method calls `to_val`.\n\n        :param s: symbolic representation of the value\n        :type s: str\n        :rtype: float or object\n        \"\"\"\n        return self.to_val(s)\n\n    def __str__(self):\n        return self.name\n\n    @property\n    def compute_value(self):\n        return self._compute_value\n\n    def __reduce__(self):\n        if not self.name:\n            raise PickleError(\"Variables without names cannot be pickled\")\n\n        # Use make to unpickle variables.\n        return make_variable, (self.__class__, self._compute_value, self.name), self.__dict__\n\n    def copy(self, compute_value=None, *, name=None, **kwargs):\n        var = type(self)(name=name or self.name,\n                         compute_value=compute_value or self.compute_value,\n                         sparse=self.sparse, **kwargs)\n        var.attributes = dict(self.attributes)\n        return var\n\n\ndel _predicatedescriptor\n\n\nclass ContinuousVariable(Variable):\n    \"\"\"\n    Descriptor for continuous variables.\n\n    .. attribute:: number_of_decimals\n\n        The number of decimals when the value is printed out (default: 3).\n\n    .. attribute:: adjust_decimals\n\n        A flag regulating whether the `number_of_decimals` is being adjusted\n        by :obj:`to_val`.\n\n    The value of `number_of_decimals` is set to 3 and `adjust_decimals`\n    is set to 2. When :obj:`val_from_str_add` is called for the first\n    time with a string as an argument, `number_of_decimals` is set to the\n    number of decimals in the string and `adjust_decimals` is set to 1.\n    In the subsequent calls of `to_val`, the nubmer of decimals is\n    increased if the string argument has a larger number of decimals.\n\n    If the `number_of_decimals` is set manually, `adjust_decimals` is\n    set to 0 to prevent changes by `to_val`.\n    \"\"\"\n\n    TYPE_HEADERS = ('continuous', 'c', 'numeric', 'n')\n\n    def __init__(self, name=\"\", number_of_decimals=None, compute_value=None, *, sparse=False):\n        \"\"\"\n        Construct a new continuous variable. The number of decimals is set to\n        three, but adjusted at the first call of :obj:`to_val`.\n        \"\"\"\n        super().__init__(name, compute_value, sparse=sparse)\n        if number_of_decimals is None:\n            self._number_of_decimals = 3\n            self.adjust_decimals = 2\n            self._format_str = \"%g\"\n        else:\n            self.number_of_decimals = number_of_decimals\n\n    @property\n    def number_of_decimals(self):\n        return self._number_of_decimals\n\n    @property\n    def format_str(self):\n        return self._format_str\n\n    @format_str.setter\n    def format_str(self, value):\n        self._format_str = value\n\n    @Variable.colors.getter\n    def colors(self):\n        if self._colors is not None:\n            return self._colors\n        try:\n            col1, col2, black = self.attributes[\"colors\"]\n            return (hex_to_color(col1), hex_to_color(col2), black)\n        except (KeyError, ValueError):\n            # User-provided colors were not available or invalid\n            return ((0, 0, 255), (255, 255, 0), False)\n\n    # noinspection PyAttributeOutsideInit\n    @number_of_decimals.setter\n    def number_of_decimals(self, x):\n        self._number_of_decimals = x\n        self.adjust_decimals = 0\n        if self._number_of_decimals <= MAX_NUM_OF_DECIMALS:\n            self._format_str = \"%.{}f\".format(self.number_of_decimals)\n        else:\n            self._format_str = \"%g\"\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if s in self.unknown_str:\n            return Unknown\n        return float(s)\n\n    def val_from_str_add(self, s):\n        \"\"\"\n        Convert a value from a string and adjust the number of decimals if\n        `adjust_decimals` is non-zero.\n        \"\"\"\n        return _variable.val_from_str_add_cont(self, s)\n\n    def repr_val(self, val):\n        \"\"\"\n        Return the value as a string with the prescribed number of decimals.\n        \"\"\"\n        if isnan(val):\n            return \"?\"\n        return self._format_str % val\n\n    str_val = repr_val\n\n    def copy(self, compute_value=None, *, name=None, **kwargs):\n        var = super().copy(compute_value=compute_value, name=name,\n                           number_of_decimals=self.number_of_decimals,\n                           **kwargs)\n        var.adjust_decimals = self.adjust_decimals\n        var.format_str = self._format_str\n        return var\n\n\nclass DiscreteVariable(Variable):\n    \"\"\"\n    Descriptor for symbolic, discrete variables. Values of discrete variables\n    are stored as floats; the numbers corresponds to indices in the list of\n    values.\n\n    .. attribute:: values\n\n        A list of variable's values.\n\n    .. attribute:: ordered\n\n        Some algorithms (and, in particular, visualizations) may\n        sometime reorder the values of the variable, e.g. alphabetically.\n        This flag hints that the given order of values is \"natural\"\n        (e.g. \"small\", \"middle\", \"large\") and should not be changed.\n    \"\"\"\n\n    TYPE_HEADERS = ('discrete', 'd', 'categorical')\n\n    presorted_values = []\n\n    def __init__(self, name=\"\", values=(), ordered=False, compute_value=None,\n                 *, sparse=False):\n        \"\"\" Construct a discrete variable descriptor with the given values. \"\"\"\n        self.values = list(values)\n        if not all(isinstance(value, str) for value in self.values):\n            raise TypeError(\"values of DiscreteVariables must be strings\")\n        super().__init__(name, compute_value, sparse=sparse)\n        self.ordered = ordered\n\n    def get_mapping_from(self, other):\n        return np.array(\n            [self.values.index(value) if value in self.values else np.nan\n             for value in other.values], dtype=float)\n\n    def get_mapper_from(self, other):\n        mapping = self.get_mapping_from(other)\n        if not mapping.size:\n            # Nans in data are temporarily replaced with 0, mapped and changed\n            # back to nans. This would fail is mapping[0] is out of range.\n            mapping = np.array([np.nan])\n\n        def mapper(value, col_idx=None):\n\n            # In-place mapping\n            if col_idx is not None:\n                if sp.issparse(value) and mapping[0] != 0:\n                    raise ValueError(\n                        \"In-place mapping of sparse matrices must map 0 to 0\")\n\n                # CSR requires mapping of non-contiguous area\n                if sp.isspmatrix_csr(value):\n                    col = value.indices == col_idx\n                    nans = np.isnan(value.data) * col\n                    value.data[nans] = 0\n                    value.data[col] = mapping[value.data[col].astype(int)]\n                    value.data[nans] = np.nan\n                    return\n\n                # Dense and CSC map a contiguous area\n                if isinstance(value, np.ndarray) and value.ndim == 2:\n                    col = value[:, col_idx]\n                elif sp.isspmatrix_csc(value):\n                    col = value.data[value.indptr[col_idx]\n                                     :value.indptr[col_idx + 1]]\n                else:\n                    raise ValueError(\n                        \"In-place column mapping requires a 2d array or\"\n                        \"a csc or csr matrix.\")\n\n                nans = np.isnan(col)\n                col[nans] = 0\n                col[:] = mapping[col.astype(int)]\n                col[nans] = np.nan\n                return\n\n            # Mapping into a copy\n            if isinstance(value, (int, float)):\n                return mapping[int(value)] if value == value else value\n            if isinstance(value, str):\n                return mapping[other.values.index(value)]\n            if isinstance(value, np.ndarray):\n                if not (value.ndim == 1\n                        or value.ndim != 2 and min(value.shape) != 1):\n                    raise ValueError(\n                        f\"Column mapping can't map {value.ndim}-d objects\")\n\n                if value.dtype == object:\n                    value = value.astype(float)  # this happens with metas\n                try:\n                    nans = np.isnan(value)\n                except TypeError:  # suppose it's already an integer type\n                    return mapping[value]\n                value = value.astype(int)\n                value[nans] = 0\n                value = mapping[value]\n                value[nans] = np.nan\n                return value\n            if sp.issparse(value):\n                if min(value.shape) != 1:\n                    raise ValueError(\"Column mapping can't map \"\n                                     f\"{value.ndim}-dimensional objects\")\n                if mapping[0] != 0 and not np.isnan(mapping[0]):\n                    return mapper(np.array(value.todense()).flatten())\n                value = value.copy()\n                value.data = mapper(value.data)\n                return value\n            if isinstance(value, Iterable):\n                return type(value)(mapping[int(val)] if val == val else val\n                                   for val in value)\n            raise ValueError(\n                f\"invalid type for value(s): {type(value).__name__}\")\n\n        return mapper\n\n    @Variable.colors.getter\n    def colors(self):\n        if self._colors is not None:\n            colors = np.array(self._colors)\n        elif not self.values:\n            colors = np.zeros((0, 3))  # to match additional colors in vstacks\n        else:\n            from Orange.widgets.utils.colorpalette import ColorPaletteGenerator\n            default = tuple(ColorPaletteGenerator.palette(self))\n            colors = self.attributes.get('colors', ())\n            colors = tuple(hex_to_color(color) for color in colors) \\\n                    + default[len(colors):]\n            colors = np.array(colors)\n        colors.flags.writeable = False\n        return colors\n\n    def to_val(self, s):\n        \"\"\"\n        Convert the given argument to a value of the variable (`float`).\n        If the argument is numeric, its value is returned without checking\n        whether it is integer and within bounds. `Unknown` is returned if the\n        argument is one of the representations for unknown values. Otherwise,\n        the argument must be a string and the method returns its index in\n        :obj:`values`.\n\n        :param s: values, represented as a number, string or `None`\n        :rtype: float\n        \"\"\"\n        if s is None:\n            return ValueUnknown\n\n        if isinstance(s, Integral):\n            return s\n        if isinstance(s, Real):\n            return s if isnan(s) else floor(s + 0.25)\n        if s in self.unknown_str:\n            return ValueUnknown\n        if not isinstance(s, str):\n            raise TypeError('Cannot convert {} to value of \"{}\"'.format(\n                type(s).__name__, self.name))\n        return self.values.index(s)\n\n    def add_value(self, s):\n        \"\"\" Add a value `s` to the list of values.\n        \"\"\"\n        if not isinstance(s, str):\n            raise TypeError(\"values of DiscreteVariables must be strings\")\n        self.values.append(s)\n        self._colors = None\n\n    def val_from_str_add(self, s):\n        \"\"\"\n        Similar to :obj:`to_val`, except that it accepts only strings and that\n        it adds the value to the list if it does not exist yet.\n\n        :param s: symbolic representation of the value\n        :type s: str\n        :rtype: float\n        \"\"\"\n        s = str(s) if s is not None else s\n        try:\n            return ValueUnknown if s in self.unknown_str \\\n                else self.values.index(s)\n        except ValueError:\n            self.add_value(s)\n            return len(self.values) - 1\n\n    def repr_val(self, val):\n        \"\"\"\n        Return a textual representation of the value (`self.values[int(val)]`)\n        or \"?\" if the value is unknown.\n\n        :param val: value\n        :type val: float (should be whole number)\n        :rtype: str\n        \"\"\"\n        if isnan(val):\n            return \"?\"\n        return '{}'.format(self.values[int(val)])\n\n    str_val = repr_val\n\n    def __reduce__(self):\n        if not self.name:\n            raise PickleError(\"Variables without names cannot be pickled\")\n        __dict__ = dict(self.__dict__)\n        __dict__.pop(\"values\")\n        return make_variable, (self.__class__, self._compute_value, self.name,\n                               self.values, self.ordered), \\\n            __dict__\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            values=self.values, ordered=self.ordered)\n\n\nclass StringVariable(Variable):\n    \"\"\"\n    Descriptor for string variables. String variables can only appear as\n    meta attributes.\n    \"\"\"\n    Unknown = \"\"\n    TYPE_HEADERS = ('string', 's', 'text')\n\n    def to_val(self, s):\n        \"\"\"\n        Return the value as a string. If it is already a string, the same\n        object is returned.\n        \"\"\"\n        if s is None:\n            return \"\"\n        if isinstance(s, str):\n            return s\n        return str(s)\n\n    val_from_str_add = to_val\n\n    @staticmethod\n    def str_val(val):\n        \"\"\"Return a string representation of the value.\"\"\"\n        if val is \"\":\n            return \"?\"\n        if isinstance(val, Value):\n            if val.value is \"\":\n                return \"?\"\n            val = val.value\n        return str(val)\n\n    def repr_val(self, val):\n        \"\"\"Return a string representation of the value.\"\"\"\n        return '\"{}\"'.format(self.str_val(val))\n\n\nclass TimeVariable(ContinuousVariable):\n    \"\"\"\n    TimeVariable is a continuous variable with Unix epoch\n    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive\n    real numbers (equivalent to Unix timestamp, with microseconds in the\n    fraction part), and the dates before it map to the negative real numbers.\n\n    Unfortunately due to limitation of Python datetime, only dates\n    with year >= 1 (A.D.) are supported.\n\n    If time is specified without a date, Unix epoch is assumed.\n\n    If time is specified wihout an UTC offset, localtime is assumed.\n    \"\"\"\n    _all_vars = {}\n    TYPE_HEADERS = ('time', 't')\n    UNIX_EPOCH = datetime(1970, 1, 1)\n    _ISO_FORMATS = [\n        # have_date, have_time, format_str\n        # in order of decreased probability\n        (1, 1, '%Y-%m-%d %H:%M:%S%z'),\n        (1, 1, '%Y-%m-%d %H:%M:%S'),\n        (1, 1, '%Y-%m-%d %H:%M'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S'),\n\n        (1, 0, '%Y-%m-%d'),\n\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),\n        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),\n        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),\n\n        (1, 1, '%Y%m%dT%H%M%S%z'),\n        (1, 1, '%Y%m%d%H%M%S%z'),\n\n        (0, 1, '%H:%M:%S.%f'),\n        (0, 1, '%H:%M:%S'),\n        (0, 1, '%H:%M'),\n\n        # These parse as continuous features (plain numbers)\n        (1, 1, '%Y%m%dT%H%M%S'),\n        (1, 1, '%Y%m%d%H%M%S'),\n        (1, 0, '%Y%m%d'),\n        (1, 0, '%Y%j'),\n        (1, 0, '%Y'),\n        (0, 1, '%H%M%S.%f'),\n\n        # BUG: In Python as in C, %j doesn't necessitate 0-padding,\n        # so these two lines must be in this order\n        (1, 0, '%Y-%m'),\n        (1, 0, '%Y-%j'),\n    ]\n    # The regex that matches all above formats\n    REGEX = (r'^('\n             r'\\d{1,4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?([+-]\\d{4})?)?)?|'\n             r'\\d{1,4}\\d{2}\\d{2}(T?\\d{2}\\d{2}\\d{2}([+-]\\d{4})?)?|'\n             r'\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?|'\n             r'\\d{2}\\d{2}\\d{2}\\.\\d+|'\n             r'\\d{1,4}(-?\\d{2,3})?'\n             r')$')\n    _matches_iso_format = re.compile(REGEX).match\n\n    # UTC offset and associated timezone. If parsed datetime values provide an\n    # offset, it is used for display. If not all values have the same offset,\n    # +0000 (=UTC) timezone is used and utc_offset is set to False.\n    utc_offset = None\n    timezone = timezone.utc\n\n    def __init__(self, *args, have_date=0, have_time=0, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.have_date = have_date\n        self.have_time = have_time\n\n    def copy(self, compute_value=None, *, name=None, **_):\n        return super().copy(compute_value=compute_value, name=name,\n                            have_date=self.have_date, have_time=self.have_time)\n\n    @staticmethod\n    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\\d\\d):(\\d\\d)$').sub):\n        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000\n        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\\1\\2\\3', s)\n\n    def repr_val(self, val):\n        if isnan(val):\n            return '?'\n        if not self.have_date and not self.have_time:\n            # The time is relative, unitless. The value is absolute.\n            return str(val.value) if isinstance(val, Value) else str(val)\n\n        # If you know how to simplify this, be my guest\n        seconds = int(val)\n        microseconds = int(round((val - seconds) * 1e6))\n        if val < 0:\n            if microseconds:\n                seconds, microseconds = seconds - 1, int(1e6) + microseconds\n            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)\n        else:\n            date = datetime.fromtimestamp(seconds, tz=self.timezone)\n        date = str(date.replace(microsecond=microseconds))\n\n        if self.have_date and not self.have_time:\n            date = date.split()[0]\n        elif not self.have_date and self.have_time:\n            date = date.split()[1]\n        date = self._tzre_sub(date)\n        return date\n\n    str_val = repr_val\n\n    def parse(self, datestr):\n        \"\"\"\n        Return `datestr`, a datetime provided in one of ISO 8601 formats,\n        parsed as a real number. Value 0 marks the Unix epoch, positive values\n        are the dates after it, negative before.\n\n        If date is unspecified, epoch date is assumed.\n\n        If time is unspecified, 00:00:00.0 is assumed.\n\n        If timezone is unspecified, local time is assumed.\n        \"\"\"\n        if datestr in MISSING_VALUES:\n            return Unknown\n        datestr = datestr.strip().rstrip('Z')\n\n        ERROR = ValueError(\"Invalid datetime format '{}'. \"\n                           \"Only ISO 8601 supported.\".format(datestr))\n        if not self._matches_iso_format(datestr):\n            try:\n                # If it is a number, assume it is a unix timestamp\n                value = float(datestr)\n                self.have_date = self.have_time = 1\n                return value\n            except ValueError:\n                raise ERROR\n\n        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):\n            try:\n                dt = datetime.strptime(datestr, fmt)\n            except ValueError:\n                continue\n            else:\n                # Pop this most-recently-used format to front\n                if 0 < i < len(self._ISO_FORMATS) - 2:\n                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \\\n                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]\n\n                self.have_date |= have_date\n                self.have_time |= have_time\n                if not have_date:\n                    dt = dt.replace(self.UNIX_EPOCH.year,\n                                    self.UNIX_EPOCH.month,\n                                    self.UNIX_EPOCH.day)\n                break\n        else:\n            raise ERROR\n\n        # Remember UTC offset. If not all parsed values share the same offset,\n        # remember none of it.\n        offset = dt.utcoffset()\n        if self.utc_offset is not False:\n            if offset and self.utc_offset is None:\n                self.utc_offset = offset\n                self.timezone = timezone(offset)\n            elif self.utc_offset != offset:\n                self.utc_offset = False\n                self.timezone = timezone.utc\n\n        # Convert time to UTC timezone. In dates without timezone,\n        # localtime is assumed. See also:\n        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp\n        if dt.tzinfo:\n            dt -= dt.utcoffset()\n        dt = dt.replace(tzinfo=timezone.utc)\n\n        # Unix epoch is the origin, older dates are negative\n        try:\n            return dt.timestamp()\n        except OverflowError:\n            return -(self.UNIX_EPOCH - dt).total_seconds()\n\n    def to_val(self, s):\n        \"\"\"\n        Convert a value, given as an instance of an arbitrary type, to a float.\n        \"\"\"\n        if isinstance(s, str):\n            return self.parse(s)\n        else:\n            return super().to_val(s)\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Incorrect Data Type Inference for Date and Time Variables

**Description:**
When passing date strings to the data type guessing function, the system incorrectly identifies them as discrete variables instead of time variables. This misclassification leads to improper handling of temporal data.

**Example Buggy Code:**
```python
in_values = ["2019-10-10", "2019-10-10", "2019-10-10", "2019-10-01"]
valuemap, _, coltype = guess_data_type(in_values)
```
In this example, the `coltype` is expected to be `TimeVariable`, but it is incorrectly set to `DiscreteVariable`.

**Expected Behavior:**
Date and time strings should be recognized and classified as `TimeVariable`, allowing the system to handle temporal data accurately.

**Actual Behavior:**
Date and time strings are misclassified as `DiscreteVariable`, resulting in incorrect data type handling and potential issues in data processing workflows.
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### Orange/data/io.py
import contextlib
import csv
import locale
import pickle
import re
import subprocess
import sys
import warnings

from ast import literal_eval
from collections import OrderedDict, Counter, defaultdict
from functools import lru_cache
from importlib import import_module
from itertools import chain, repeat
from math import isnan

from os import path, remove
from tempfile import NamedTemporaryFile
from urllib.parse import urlparse, urlsplit, urlunsplit, unquote as urlunquote
from urllib.request import urlopen, Request
from fnmatch import fnmatch
from glob import glob

import numpy as np
from chardet.universaldetector import UniversalDetector

import xlrd
import xlsxwriter

from Orange.data import (
    _io, is_discrete_values, MISSING_VALUES, Table, Domain, Variable,
    DiscreteVariable, StringVariable, ContinuousVariable, TimeVariable,
)
from Orange.util import Registry, flatten, namegen


# Support values longer than 128K (i.e. text contents features)
csv.field_size_limit(100*1024*1024)

__all__ = ["Flags", "FileFormat"]


_IDENTITY = lambda i: i


class Compression:
    """Supported compression extensions"""
    GZIP = '.gz'
    BZIP2 = '.bz2'
    XZ = '.xz'
    all = (GZIP, BZIP2, XZ)


def open_compressed(filename, *args, _open=open, **kwargs):
    """Return seamlessly decompressed open file handle for `filename`"""
    if isinstance(filename, str):
        if filename.endswith(Compression.GZIP):
            from gzip import open as _open
        elif filename.endswith(Compression.BZIP2):
            from bz2 import open as _open
        elif filename.endswith(Compression.XZ):
            from lzma import open as _open
        return _open(filename, *args, **kwargs)
    # Else already a file, just pass it through
    return filename


def detect_encoding(filename):
    """
    Detect encoding of `filename`, which can be a ``str`` filename, a
    ``file``-like object, or ``bytes``.
    """
    # Try with Unix file utility first because it's faster (~10ms vs 100ms)
    if isinstance(filename, str) and not filename.endswith(Compression.all):
        try:
            with subprocess.Popen(('file', '--brief', '--mime-encoding', filename),
                                  stdout=subprocess.PIPE) as process:
                process.wait()
                if process.returncode == 0:
                    encoding = process.stdout.read().strip()
                    # file only supports these encodings; for others it says
                    # unknown-8bit or binary. So we give chardet a chance to do
                    # better
                    if encoding in (b'utf-8', b'us-ascii', b'iso-8859-1',
                                    b'utf-7', b'utf-16le', b'utf-16be', b'ebcdic'):
                        return encoding.decode('us-ascii')
        except OSError:
            pass  # windoze

    # file not available or unable to guess the encoding, have chardet do it
    detector = UniversalDetector()
    # We examine only first N 4kB blocks of file because chardet is really slow
    MAX_BYTES = 4*1024*12

    def _from_file(f):
        detector.feed(f.read(MAX_BYTES))
        detector.close()
        return (detector.result.get('encoding')
                if detector.result.get('confidence', 0) >= .85 else
                'utf-8')

    if isinstance(filename, str):
        with open_compressed(filename, 'rb') as f:
            return _from_file(f)
    elif isinstance(filename, bytes):
        detector.feed(filename[:MAX_BYTES])
        detector.close()
        return detector.result.get('encoding')
    elif hasattr(filename, 'encoding'):
        return filename.encoding
    else:  # assume file-like object that you can iter through
        return _from_file(filename)


__isnastr = np.frompyfunc(
    {v for v in MISSING_VALUES if isinstance(v, str)}.__contains__, 1, 1)

# wrapper for __isnastr with proper default out dtype
def isnastr(arr, out=None):
    """
    Given an (object) array of string values, return a boolean mask array
    that is True where the `arr` contains one of the string constants
    considered as N/A.

    Parameters
    ----------
    arr : np.ndarray
        Input array of strings.
    out : Optional[np.ndarray]
        Optional output array of the same shape as arr

    Returns
    -------
    mask : np.ndarray
    """
    arr = np.asarray(arr)
    if out is None and arr.shape != ():
        out = np.empty_like(arr, dtype=bool)
    return __isnastr(arr, out=out)


def guess_data_type(orig_values, namask=None):
    """
    Use heuristics to guess data type.
    """
    valuemap, values = None, orig_values
    is_discrete = is_discrete_values(orig_values)
    if is_discrete:
        valuemap = sorted(is_discrete)
        coltype = DiscreteVariable
    else:
        # try to parse as float
        orig_values = np.asarray(orig_values)
        if namask is None:
            namask = isnastr(orig_values)
        values = np.empty_like(orig_values, dtype=float)
        values[namask] = np.nan
        try:
            np.copyto(values, orig_values, where=~namask, casting="unsafe")
        except ValueError:
            tvar = TimeVariable('_')
            try:
                values[~namask] = [tvar.parse(i) for i in orig_values[~namask]]
            except ValueError:
                coltype = StringVariable
                # return original_values
                values = orig_values
            else:
                coltype = TimeVariable
        else:
            coltype = ContinuousVariable
    return valuemap, values, coltype


def sanitize_variable(valuemap, values, orig_values, coltype, coltype_kwargs,
                      name=None):
    assert issubclass(coltype, Variable)

    def get_number_of_decimals(values):
        len_ = len
        ndecimals = max((len_(value) - value.find(".")
                         for value in values if "." in value),
                        default=1)
        return ndecimals - 1

    if issubclass(coltype, DiscreteVariable) and valuemap is not None:
        coltype_kwargs.update(values=valuemap)

    var = coltype.make(name, **coltype_kwargs)

    if isinstance(var, DiscreteVariable):
        # Map discrete data to 'ints' (or at least what passes as int around
        # here)
        mapping = defaultdict(
            lambda: np.nan,
            {val: i for i, val in enumerate(var.values)},
        )
        mapping[""] = np.nan
        mapvalues_ = np.frompyfunc(mapping.__getitem__, 1, 1)

        def mapvalues(arr):
            arr = np.asarray(arr, dtype=object)
            return mapvalues_(arr, out=np.empty_like(arr, dtype=float))
        values = mapvalues(orig_values)

    if coltype is StringVariable:
        values = orig_values

    # ContinuousVariable.number_of_decimals is supposed to be handled by
    # ContinuousVariable.to_val. In the interest of speed, the reader bypasses
    # it, so we set the number of decimals here.
    # The number of decimals is increased if not set manually (in which case
    # var.adjust_decimals would be 0).
    if isinstance(var, ContinuousVariable) and var.adjust_decimals:
        ndecimals = get_number_of_decimals(orig_values)
        if var.adjust_decimals == 2 or ndecimals > var.number_of_decimals:
            var.number_of_decimals = ndecimals
            var.adjust_decimals = 1

    if isinstance(var, TimeVariable) or coltype is TimeVariable:
        # Re-parse the values because only now after coltype.make call
        # above, variable var is the correct one
        _var = var if isinstance(var, TimeVariable) else TimeVariable('_')
        values = [_var.parse(i) for i in orig_values]

    return values, var


class Flags:
    """Parser for column flags (i.e. third header row)"""
    DELIMITER = ' '
    _RE_SPLIT = re.compile(r'(?<!\\)' + DELIMITER).split
    _RE_ATTR_UNQUOTED_STR = re.compile(r'^[a-zA-Z_]').match
    ALL = OrderedDict((
        ('class', 'c'),
        ('ignore', 'i'),
        ('meta', 'm'),
        ('weight', 'w'),
        ('.+?=.*?', ''),  # general key=value attributes
    ))
    _RE_ALL = re.compile(r'^({})$'.format('|'.join(filter(None, flatten(ALL.items())))))

    def __init__(self, flags):
        for v in filter(None, self.ALL.values()):
            setattr(self, v, False)
        self.attributes = {}
        for flag in flags or []:
            flag = flag.strip()
            if self._RE_ALL.match(flag):
                if '=' in flag:
                    k, v = flag.split('=', 1)
                    if not Flags._RE_ATTR_UNQUOTED_STR(v):
                        try:
                            v = literal_eval(v)
                        except SyntaxError:
                            # If parsing failed, treat value as string
                            pass
                    self.attributes[k] = v
                else:
                    setattr(self, flag, True)
                    setattr(self, self.ALL.get(flag, ''), True)
            elif flag:
                warnings.warn('Invalid attribute flag \'{}\''.format(flag))

    @staticmethod
    def join(iterable, *args):
        return Flags.DELIMITER.join(i.strip().replace(Flags.DELIMITER, '\\' + Flags.DELIMITER)
                                    for i in chain(iterable, args)).lstrip()

    @staticmethod
    def split(s):
        return [i.replace('\\' + Flags.DELIMITER, Flags.DELIMITER)
                for i in Flags._RE_SPLIT(s)]


# Matches discrete specification where all the values are listed, space-separated
_RE_DISCRETE_LIST = re.compile(r'^\s*[^\s]+(\s[^\s]+)+\s*$')
_RE_TYPES = re.compile(r'^\s*({}|{}|)\s*$'.format(
    _RE_DISCRETE_LIST.pattern,
    '|'.join(flatten(getattr(vartype, 'TYPE_HEADERS') for vartype in Variable.registry.values()))
    ))
_RE_FLAGS = re.compile(r'^\s*( |{}|)*\s*$'.format(
    '|'.join(flatten(filter(None, i) for i in Flags.ALL.items()))
    ))


class FileFormatMeta(Registry):

    def __new__(cls, name, bases, attrs):
        newcls = super().__new__(cls, name, bases, attrs)

        # Optionally add compressed versions of extensions as supported
        if getattr(newcls, 'SUPPORT_COMPRESSED', False):
            new_extensions = list(getattr(newcls, 'EXTENSIONS', ()))
            for compression in Compression.all:
                for ext in newcls.EXTENSIONS:
                    new_extensions.append(ext + compression)
                if sys.platform in ('darwin', 'win32'):
                    # OSX file dialog doesn't support filtering on double
                    # extensions (e.g. .csv.gz)
                    # https://bugreports.qt.io/browse/QTBUG-38303
                    # This is just here for OWFile that gets QFileDialog
                    # filters from FileFormat.readers.keys()
                    # EDIT: Windows exhibit similar problems:
                    # while .tab.gz works, .tab.xz and .tab.bz2 do not!
                    new_extensions.append(compression)
            newcls.EXTENSIONS = tuple(new_extensions)

        return newcls

    @property
    def formats(cls):
        return cls.registry.values()

    @lru_cache(5)
    def _ext_to_attr_if_attr2(cls, attr, attr2):
        """
        Return ``{ext: `attr`, ...}`` dict if ``cls`` has `attr2`.
        If `attr` is '', return ``{ext: cls, ...}`` instead.

        If there are multiple formats for an extension, return a format
        with the lowest priority.
        """
        formats = OrderedDict()
        for format in sorted(cls.registry.values(), key=lambda x: x.PRIORITY):
            if not hasattr(format, attr2):
                continue
            for ext in getattr(format, 'EXTENSIONS', []):
                # Only adds if not yet registered
                formats.setdefault(ext, getattr(format, attr, format))
        return formats

    @property
    def names(cls):
        return cls._ext_to_attr_if_attr2('DESCRIPTION', '__class__')

    @property
    def writers(cls):
        return cls._ext_to_attr_if_attr2('', 'write_file')

    @property
    def readers(cls):
        return cls._ext_to_attr_if_attr2('', 'read')

    @property
    def img_writers(cls):
        warnings.warn(
            f"'{__name__}.FileFormat.img_writers' is no longer used and "
            "will be removed. Please use "
            "'Orange.widgets.io.FileFormat.img_writers' instead.",
            DeprecationWarning, stacklevel=2
        )
        return cls._ext_to_attr_if_attr2('', 'write_image')

    @property
    def graph_writers(cls):
        return cls._ext_to_attr_if_attr2('', 'write_graph')


class FileFormat(metaclass=FileFormatMeta):
    """
    Subclasses set the following attributes and override the following methods:

        EXTENSIONS = ('.ext1', '.ext2', ...)
        DESCRIPTION = 'human-readable file format description'
        SUPPORT_COMPRESSED = False
        SUPPORT_SPARSE_DATA = False

        def read(self):
            ...  # load headers, data, ...
            return self.data_table(data, headers)

        @classmethod
        def write_file(cls, filename, data):
            ...
            self.write_headers(writer.write, data)
            writer.writerows(data)

    Wrapper FileFormat.data_table() returns Orange.data.Table from `data`
    iterable (list (rows) of lists of values (cols)).
    """

    # Priority when multiple formats support the same extension. Also
    # the sort order in file open/save combo boxes. Lower is better.
    PRIORITY = 10000
    OPTIONAL_TYPE_ANNOTATIONS = False

    def __init__(self, filename):
        """
        Parameters
        ----------
        filename : str
            name of the file to open
        """
        self.filename = filename
        self.sheet = None

    @property
    def sheets(self):
        """FileFormats with a notion of sheets should override this property
        to return a list of sheet names in the file.

        Returns
        -------
        a list of sheet names
        """
        return ()

    def select_sheet(self, sheet):
        """Select sheet to be read

        Parameters
        ----------
        sheet : str
            sheet name
        """
        self.sheet = sheet

    @classmethod
    def get_reader(cls, filename):
        """Return reader instance that can be used to read the file

        Parameters
        ----------
        filename : str

        Returns
        -------
        FileFormat
        """
        for ext, reader in cls.readers.items():
            # Skip ambiguous, invalid compression-only extensions added on OSX
            if ext in Compression.all:
                continue
            if fnmatch(path.basename(filename), '*' + ext):
                return reader(filename)

        raise IOError('No readers for file "{}"'.format(filename))

    @classmethod
    def write(cls, filename, data, with_annotations=True):
        if cls.OPTIONAL_TYPE_ANNOTATIONS:
            return cls.write_file(filename, data, with_annotations)
        else:
            return cls.write_file(filename, data)

    @classmethod
    def write_table_metadata(cls, filename, data):

        def write_file(fn):
            if all(isinstance(key, str) and isinstance(value, str)
                   for key, value in data.attributes.items()):
                with open(fn, 'w', encoding='utf-8') as f:
                    f.write("\n".join("{}: {}".format(*kv)
                                      for kv in data.attributes.items()))
            else:
                with open(fn, 'wb') as f:
                    pickle.dump(data.attributes, f, pickle.HIGHEST_PROTOCOL)

        if isinstance(filename, str):
            metafile = filename + '.metadata'
            if getattr(data, 'attributes', None):
                write_file(metafile)
            elif path.exists(metafile):
                remove(metafile)

    @classmethod
    def set_table_metadata(cls, filename, table):
        # pylint: disable=bare-except
        if isinstance(filename, str) and path.exists(filename + '.metadata'):
            try:
                with open(filename + '.metadata', 'rb') as f:
                    table.attributes = pickle.load(f)
            # Unpickling throws different exceptions, not just UnpickleError
            except:
                with open(filename + '.metadata', encoding='utf-8') as f:
                    table.attributes = OrderedDict(
                        (k.strip(), v.strip())
                        for k, v in (line.split(":", 1)
                                     for line in f.readlines()))

    @classmethod
    def locate(cls, filename, search_dirs=('.',)):
        """Locate a file with given filename that can be opened by one
        of the available readers.

        Parameters
        ----------
        filename : str
        search_dirs : Iterable[str]

        Returns
        -------
        str
            Absolute path to the file
        """
        if path.exists(filename):
            return filename

        for directory in search_dirs:
            absolute_filename = path.join(directory, filename)
            if path.exists(absolute_filename):
                break
            for ext in cls.readers:
                if fnmatch(path.basename(filename), '*' + ext):
                    break
                # glob uses fnmatch internally
                matching_files = glob(absolute_filename + ext)
                if matching_files:
                    absolute_filename = matching_files[0]
                    break
            if path.exists(absolute_filename):
                break
        else:
            absolute_filename = ""

        if not path.exists(absolute_filename):
            raise IOError('File "{}" was not found.'.format(filename))

        return absolute_filename

    @staticmethod
    def open(filename, *args, **kwargs):
        """
        Format handlers can use this method instead of the builtin ``open()``
        to transparently (de)compress files if requested (according to
        `filename` extension). Set ``SUPPORT_COMPRESSED=True`` if you use this.
        """
        return open_compressed(filename, *args, **kwargs)

    @staticmethod
    def parse_headers(data):
        """Return (header rows, rest of data) as discerned from `data`"""

        def is_number(item):
            try:
                float(item)
            except ValueError:
                return False
            return True
        # Second row items are type identifiers
        def header_test2(items):
            return all(map(_RE_TYPES.match, items))
        # Third row items are flags and column attributes (attr=value)
        def header_test3(items):
            return all(map(_RE_FLAGS.match, items))

        data = iter(data)
        header_rows = []

        # Try to parse a three-line header
        lines = []
        try:
            lines.append(list(next(data)))
            lines.append(list(next(data)))
            lines.append(list(next(data)))
        except StopIteration:
            lines, data = [], chain(lines, data)
        if lines:
            l1, l2, l3 = lines
            # Three-line header if line 2 & 3 match (1st line can be anything)
            if header_test2(l2) and header_test3(l3):
                header_rows = [l1, l2, l3]
            else:
                lines, data = [], chain((l1, l2, l3), data)

        # Try to parse a single-line header
        if not header_rows:
            try:
                lines.append(list(next(data)))
            except StopIteration:
                pass
            if lines:
                # Header if none of the values in line 1 parses as a number
                if not all(is_number(i) for i in lines[0]):
                    header_rows = [lines[0]]
                else:
                    data = chain(lines, data)

        return header_rows, data

    @classmethod
    def data_table(cls, data, headers=None):
        """
        Return Orange.data.Table given rows of `headers` (iterable of iterable)
        and rows of `data` (iterable of iterable).

        Basically, the idea of subclasses is to produce those two iterables,
        however they might.

        If `headers` is not provided, the header rows are extracted from `data`,
        assuming they precede it.
        """
        if not headers:
            headers, data = cls.parse_headers(data)

        # Consider various header types (single-row, two-row, three-row, none)
        if len(headers) == 3:
            names, types, flags = map(list, headers)
        else:
            if len(headers) == 1:
                HEADER1_FLAG_SEP = '#'
                # First row format either:
                #   1) delimited column names
                #   2) -||- with type and flags prepended, separated by #,
                #      e.g. d#sex,c#age,cC#IQ
                _flags, names = zip(*[i.split(HEADER1_FLAG_SEP, 1)
                                      if HEADER1_FLAG_SEP in i else ('', i)
                                      for i in headers[0]]
                                   )
                names = list(names)
            elif len(headers) == 2:
                names, _flags = map(list, headers)
            else:
                # Use heuristics for everything
                names, _flags = [], []
            types = [''.join(filter(str.isupper, flag)).lower() for flag in _flags]
            flags = [Flags.join(filter(str.islower, flag)) for flag in _flags]

        # Determine maximum row length
        rowlen = max(map(len, (names, types, flags)))

        strip = False

        def _equal_length(lst):
            nonlocal strip
            if len(lst) > rowlen > 0:
                lst = lst[:rowlen]
                strip = True
            elif len(lst) < rowlen:
                lst.extend(['']*(rowlen - len(lst)))
            return lst

        # Ensure all data is of equal width in a column-contiguous array
        data = [_equal_length([s.strip() for s in row])
                for row in data if any(row)]
        data = np.array(data, dtype=object, order='F')

        if strip:
            warnings.warn("Columns with no headers were removed.")

        # Data may actually be longer than headers were
        try:
            rowlen = data.shape[1]
        except IndexError:
            pass
        else:
            for lst in (names, types, flags):
                _equal_length(lst)

        NAMEGEN = namegen('Feature ', 1)
        Xcols, attrs = [], []
        Mcols, metas = [], []
        Ycols, clses = [], []
        Wcols = []

        # Rename variables if necessary
        # Reusing across files still works if both files have same duplicates
        name_counts = Counter(names)
        del name_counts[""]
        if len(name_counts) != len(names) and name_counts:
            uses = {name: 0 for name, count in name_counts.items() if count > 1}
            for i, name in enumerate(names):
                if name in uses:
                    uses[name] += 1
                    names[i] = "{}_{}".format(name, uses[name])

        namask = np.empty(data.shape[0], dtype=bool)
        # Iterate through the columns
        for col in range(rowlen):
            flag = Flags(Flags.split(flags[col]))
            if flag.i:
                continue

            type_flag = types and types[col].strip()
            try:
                orig_values = data[:, col]
            except IndexError:
                orig_values = np.array([], dtype=object)

            namask = isnastr(orig_values, out=namask)

            coltype_kwargs = {}
            valuemap = None
            values = orig_values

            if type_flag in StringVariable.TYPE_HEADERS:
                coltype = StringVariable
                values = orig_values
            elif type_flag in ContinuousVariable.TYPE_HEADERS:
                coltype = ContinuousVariable
                values = np.empty(data.shape[0], dtype=float)
                try:
                    np.copyto(values, orig_values, casting="unsafe",
                              where=~namask)
                    values[namask] = np.nan
                except ValueError:
                    for row, num in enumerate(orig_values):
                        if not isnastr(num):
                            try:
                                float(num)
                            except ValueError:
                                break
                    raise ValueError('Non-continuous value in (1-based) '
                                     'line {}, column {}'.format(row + len(headers) + 1,
                                                                 col + 1))

            elif type_flag in TimeVariable.TYPE_HEADERS:
                coltype = TimeVariable
                values = np.where(namask, "", orig_values)
            elif (type_flag in DiscreteVariable.TYPE_HEADERS or
                  _RE_DISCRETE_LIST.match(type_flag)):
                coltype = DiscreteVariable
                orig_values = values = np.where(namask, "", orig_values)
                if _RE_DISCRETE_LIST.match(type_flag):
                    valuemap = Flags.split(type_flag)
                    coltype_kwargs.update(ordered=True)
                else:
                    valuemap = sorted(set(orig_values) - {""})
            else:
                # No known type specified, use heuristics
                valuemap, values, coltype = guess_data_type(orig_values, namask)

            if flag.m or coltype is StringVariable:
                append_to = (Mcols, metas)
            elif flag.w:
                append_to = (Wcols, None)
            elif flag.c:
                append_to = (Ycols, clses)
            else:
                append_to = (Xcols, attrs)

            cols, domain_vars = append_to

            if domain_vars is not None:
                var_name = names and names[col]
                if not var_name:
                    var_name = next(NAMEGEN)

                values, var = sanitize_variable(
                    valuemap, values, orig_values, coltype, coltype_kwargs,
                    name=var_name)
            else:
                var = None
            if domain_vars is not None:
                var.attributes.update(flag.attributes)
                domain_vars.append(var)

            if isinstance(values, np.ndarray) and not values.flags.owndata:
                values = values.copy()  # might view `data` (string columns)
            cols.append(values)

            try:
                # allow gc to reclaim memory used by string values
                data[:, col] = None
            except IndexError:
                pass

        domain = Domain(attrs, clses, metas)

        if not data.size:
            return Table.from_domain(domain, 0)

        X = Y = M = W = None
        if Xcols:
            X = np.c_[tuple(Xcols)]
            assert X.dtype == np.float_
        else:
            X = np.empty((data.shape[0], 0), dtype=np.float_)
        if Ycols:
            Y = np.c_[tuple(Ycols)]
            assert Y.dtype == np.float_
        if Mcols:
            M = np.c_[tuple(Mcols)].astype(object)
        if Wcols:
            W = np.c_[tuple(Wcols)].astype(float)

        table = Table.from_numpy(domain, X, Y, M, W)
        return table

    @staticmethod
    def header_names(data):
        return ['weights'] * data.has_weights() + \
               [v.name for v in chain(data.domain.attributes,
                                      data.domain.class_vars,
                                      data.domain.metas)]

    @staticmethod
    def header_types(data):
        def _vartype(var):
            if var.is_continuous or var.is_string:
                return var.TYPE_HEADERS[0]
            elif var.is_discrete:
                return Flags.join(var.values) if var.ordered else var.TYPE_HEADERS[0]
            raise NotImplementedError
        return ['continuous'] * data.has_weights() + \
               [_vartype(v) for v in chain(data.domain.attributes,
                                           data.domain.class_vars,
                                           data.domain.metas)]

    @staticmethod
    def header_flags(data):
        return list(chain(['weight'] * data.has_weights(),
                          (Flags.join([flag], *('{}={}'.format(*a)
                                                for a in sorted(var.attributes.items())))
                           for flag, var in chain(zip(repeat(''), data.domain.attributes),
                                                  zip(repeat('class'), data.domain.class_vars),
                                                  zip(repeat('meta'), data.domain.metas)))))

    @classmethod
    def write_headers(cls, write, data, with_annotations=True):
        """`write` is a callback that accepts an iterable"""
        write(cls.header_names(data))
        if with_annotations:
            write(cls.header_types(data))
            write(cls.header_flags(data))

    @classmethod
    def formatter(cls, var):
        # type: (Variable) -> Callable[[Variable], Any]
        # Return a column 'formatter' function. The function must return
        # something that `write` knows how to write
        if var.is_time:
            return var.repr_val
        elif var.is_continuous:
            return lambda value: "" if isnan(value) else value
        elif var.is_discrete:
            return lambda value: "" if isnan(value) else var.values[int(value)]
        elif var.is_string:
            return lambda value: value
        else:
            return var.repr_val

    @classmethod
    def write_data(cls, write, data):
        """`write` is a callback that accepts an iterable"""
        vars = list(chain((ContinuousVariable('_w'),) if data.has_weights() else (),
                          data.domain.attributes,
                          data.domain.class_vars,
                          data.domain.metas))

        formatters = [cls.formatter(v) for v in vars]
        for row in zip(data.W if data.W.ndim > 1 else data.W[:, np.newaxis],
                       data.X,
                       data.Y if data.Y.ndim > 1 else data.Y[:, np.newaxis],
                       data.metas):
            write([fmt(v) for fmt, v in zip(formatters, flatten(row))])

    @classmethod
    def qualified_name(cls):
        return cls.__module__ + '.' + cls.__name__


def class_from_qualified_name(format_name):
    """ File format class from qualified name. """
    elements = format_name.split(".")
    m = import_module(".".join(elements[:-1]))
    return getattr(m, elements[-1])


class CSVReader(FileFormat):
    """Reader for comma separated files"""

    EXTENSIONS = ('.csv',)
    DESCRIPTION = 'Comma-separated values'
    DELIMITERS = ',;:\t$ '
    SUPPORT_COMPRESSED = True
    SUPPORT_SPARSE_DATA = False
    PRIORITY = 20
    OPTIONAL_TYPE_ANNOTATIONS = True

    def read(self):
        for encoding in (lambda: ('us-ascii', None),                 # fast
                         lambda: (detect_encoding(self.filename), None),  # precise
                         lambda: (locale.getpreferredencoding(False), None),
                         lambda: (sys.getdefaultencoding(), None),   # desperate
                         lambda: ('utf-8', None),                    # ...
                         lambda: ('utf-8', 'ignore')):               # fallback
            encoding, errors = encoding()
            # Clear the error flag for all except the last check, because
            # the error of second-to-last check is stored and shown as warning in owfile
            if errors != 'ignore':
                error = ''
            with self.open(self.filename, mode='rt', newline='',
                           encoding=encoding, errors=errors) as file:
                # Sniff the CSV dialect (delimiter, quotes, ...)
                try:
                    dialect = csv.Sniffer().sniff(
                        # Take first couple of *complete* lines as sample
                        ''.join(file.readline() for _ in range(10)),
                        self.DELIMITERS)
                    delimiter = dialect.delimiter
                    quotechar = dialect.quotechar
                except UnicodeDecodeError as e:
                    error = e
                    continue
                except csv.Error:
                    delimiter = self.DELIMITERS[0]
                    quotechar = csv.excel.quotechar

                file.seek(0)
                try:
                    reader = csv.reader(
                        file, delimiter=delimiter, quotechar=quotechar,
                        skipinitialspace=True,
                    )
                    data = self.data_table(reader)

                    # TODO: Name can be set unconditionally when/if
                    # self.filename will always be a string with the file name.
                    # Currently, some tests pass StringIO instead of
                    # the file name to a reader.
                    if isinstance(self.filename, str):
                        data.name = path.splitext(
                            path.split(self.filename)[-1])[0]
                    if error and isinstance(error, UnicodeDecodeError):
                        pos, endpos = error.args[2], error.args[3]
                        warning = ('Skipped invalid byte(s) in position '
                                   '{}{}').format(pos,
                                                  ('-' + str(endpos)) if (endpos - pos) > 1 else '')
                        warnings.warn(warning)
                    self.set_table_metadata(self.filename, data)
                    return data
                except Exception as e:
                    error = e
                    continue
        raise ValueError('Cannot parse dataset {}: {}'.format(self.filename, error)) from error

    @classmethod
    def write_file(cls, filename, data, with_annotations=True):
        with cls.open(filename, mode='wt', newline='', encoding='utf-8') as file:
            writer = csv.writer(file, delimiter=cls.DELIMITERS[0])
            cls.write_headers(writer.writerow, data, with_annotations)
            cls.write_data(writer.writerow, data)
            cls.write_table_metadata(filename, data)


class TabReader(CSVReader):
    """Reader for tab separated files"""
    EXTENSIONS = ('.tab', '.tsv')
    DESCRIPTION = 'Tab-separated values'
    DELIMITERS = '\t'
    PRIORITY = 10


class PickleReader(FileFormat):
    """Reader for pickled Table objects"""
    EXTENSIONS = ('.pkl', '.pickle')
    DESCRIPTION = 'Pickled Orange data'
    SUPPORT_COMPRESSED = True
    SUPPORT_SPARSE_DATA = True

    def read(self):
        with self.open(self.filename, 'rb') as f:
            table = pickle.load(f)
            if not isinstance(table, Table):
                raise TypeError("file does not contain a data table")
            else:
                return table

    @classmethod
    def write_file(cls, filename, data):
        with cls.open(filename, 'wb') as f:
            pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)


class BasketReader(FileFormat):
    """Reader for basket (sparse) files"""
    EXTENSIONS = ('.basket', '.bsk')
    DESCRIPTION = 'Basket file'
    SUPPORT_SPARSE_DATA = True

    def read(self):
        def constr_vars(inds):
            if inds:
                return [ContinuousVariable(x.decode("utf-8")) for _, x in
                        sorted((ind, name) for name, ind in inds.items())]

        X, Y, metas, attr_indices, class_indices, meta_indices = \
            _io.sparse_read_float(self.filename.encode(sys.getdefaultencoding()))

        attrs = constr_vars(attr_indices)
        classes = constr_vars(class_indices)
        meta_attrs = constr_vars(meta_indices)
        domain = Domain(attrs, classes, meta_attrs)
        table = Table.from_numpy(
            domain, attrs and X, classes and Y, metas and meta_attrs)
        table.name = path.splitext(path.split(self.filename)[-1])[0]
        return table


class ExcelReader(FileFormat):
    """Reader for excel files"""
    EXTENSIONS = ('.xlsx',)
    DESCRIPTION = 'Microsoft Excel spreadsheet'
    SUPPORT_COMPRESSED = False
    SUPPORT_SPARSE_DATA = False

    def __init__(self, filename):
        super().__init__(filename=filename)
        self._workbook = None

    @property
    def workbook(self):
        if not self._workbook:
            self._workbook = xlrd.open_workbook(self.filename, on_demand=True)
        return self._workbook

    @property
    @lru_cache(1)
    def sheets(self):
        if self.workbook:
            return self.workbook.sheet_names()
        else:
            return ()

    def read(self):
        if self.sheet:
            ss = self.workbook.sheet_by_name(self.sheet)
        else:
            ss = self.workbook.sheet_by_index(0)
        try:
            first_row = next(i for i in range(ss.nrows) if any(ss.row_values(i)))
            first_col = next(i for i in range(ss.ncols) if ss.cell_value(first_row, i))
            row_len = ss.row_len(first_row)
            cells = filter(any,
                           [[str(ss.cell_value(row, col)) if col < ss.row_len(row) else ''
                             for col in range(first_col, row_len)]
                            for row in range(first_row, ss.nrows)])
            table = self.data_table(cells)
            table.name = path.splitext(path.split(self.filename)[-1])[0]
            if self.sheet:
                table.name = '-'.join((table.name, self.sheet))
        except Exception:
            raise IOError("Couldn't load spreadsheet from " + self.filename)
        return table

    @classmethod
    def write_file(cls, filename, data):
        vars = list(chain((ContinuousVariable('_w'),) if data.has_weights() else (),
                          data.domain.attributes,
                          data.domain.class_vars,
                          data.domain.metas))
        formatters = [cls.formatter(v) for v in vars]
        zipped_list_data = zip(data.W if data.W.ndim > 1 else data.W[:, np.newaxis],
                               data.X,
                               data.Y if data.Y.ndim > 1 else data.Y[:, np.newaxis],
                               data.metas)
        headers = cls.header_names(data)
        workbook = xlsxwriter.Workbook(filename)
        sheet = workbook.add_worksheet()
        for c, header in enumerate(headers):
            sheet.write(0, c, header)
        for i, row in enumerate(zipped_list_data, 1):
            for j, (fmt, v) in enumerate(zip(formatters, flatten(row))):
                sheet.write(i, j, fmt(v))
        workbook.close()


class DotReader(FileFormat):
    """Writer for dot (graph) files"""
    EXTENSIONS = ('.dot', '.gv')
    DESCRIPTION = 'Dot graph description'
    SUPPORT_COMPRESSED = True
    SUPPORT_SPARSE_DATA = False

    @classmethod
    def write_graph(cls, filename, graph):
        from sklearn import tree
        tree.export_graphviz(graph, out_file=cls.open(filename, 'wt'))

    @classmethod
    def write(cls, filename, tree):
        if type(tree) == dict:
            tree = tree['tree']
        cls.write_graph(filename, tree)


class UrlReader(FileFormat):
    def __init__(self, filename):
        filename = filename.strip()
        if not urlparse(filename).scheme:
            filename = 'http://' + filename
        super().__init__(filename)

    @staticmethod
    def urlopen(url):
        req = Request(
            url,
            # Avoid 403 error with servers that dislike scrapers
            headers={'User-Agent': 'Mozilla/5.0 (X11; Linux) Gecko/20100101 Firefox/'})
        return urlopen(req, timeout=10)

    def read(self):
        self.filename = self._trim(self._resolve_redirects(self.filename))
        with contextlib.closing(self.urlopen(self.filename)) as response:
            name = self._suggest_filename(response.headers['content-disposition'])
            with NamedTemporaryFile(suffix=name, delete=False) as f:
                f.write(response.read())
                # delete=False is a workaround for https://bugs.python.org/issue14243

            reader = self.get_reader(f.name)
            data = reader.read()
            remove(f.name)
        # Override name set in from_file() to avoid holding the temp prefix
        data.name = path.splitext(name)[0]
        data.origin = self.filename
        return data

    def _resolve_redirects(self, url):
        # Resolve (potential) redirects to a final URL
        with contextlib.closing(self.urlopen(url)) as response:
            return response.url

    @classmethod
    def _trim(cls, url):
        URL_TRIMMERS = (
            cls._trim_googlesheet,
            cls._trim_dropbox,
        )
        for trim in URL_TRIMMERS:
            try:
                url = trim(url)
            except ValueError:
                continue
            else:
                break
        return url

    @staticmethod
    def _trim_googlesheet(url):
        match = re.match(r'(?:https?://)?(?:www\.)?'
                         r'docs\.google\.com/spreadsheets/d/'
                         r'(?P<workbook_id>[-\w_]+)'
                         r'(?:/.*?gid=(?P<sheet_id>\d+).*|.*)?',
                         url, re.IGNORECASE)
        try:
            workbook, sheet = match.group('workbook_id'), match.group('sheet_id')
            if not workbook:
                raise ValueError
        except (AttributeError, ValueError):
            raise ValueError
        url = 'https://docs.google.com/spreadsheets/d/{}/export?format=tsv'.format(workbook)
        if sheet:
            url += '&gid=' + sheet
        return url

    @staticmethod
    def _trim_dropbox(url):
        parts = urlsplit(url)
        if not parts.netloc.endswith('dropbox.com'):
            raise ValueError
        return urlunsplit(parts._replace(query='dl=1'))

    def _suggest_filename(self, content_disposition):
        default_name = re.sub(r'[\\:/]', '_', urlparse(self.filename).path)

        # See https://tools.ietf.org/html/rfc6266#section-4.1
        matches = re.findall(r"filename\*?=(?:\"|.{0,10}?'[^']*')([^\"]+)",
                             content_disposition or '')
        return urlunquote(matches[-1]) if matches else default_name


### Orange/data/variable.py
import re
import warnings
from collections import Iterable

from datetime import datetime, timedelta, timezone
from numbers import Number, Real, Integral
from math import isnan, floor
from pickle import PickleError

import numpy as np
import scipy.sparse as sp

from Orange.data import _variable
from Orange.util import Registry, hex_to_color, Reprable,\
    OrangeDeprecationWarning

__all__ = ["Unknown", "MISSING_VALUES", "make_variable", "is_discrete_values",
           "Value", "Variable", "ContinuousVariable", "DiscreteVariable",
           "StringVariable", "TimeVariable"]


# For storing unknowns
Unknown = ValueUnknown = float("nan")
# For checking for unknowns
MISSING_VALUES = {np.nan, "?", "nan", ".", "", "NA", "~", None}

DISCRETE_MAX_VALUES = 3  # == 2 + nan
MAX_NUM_OF_DECIMALS = 5
# the variable with more than 100 different values should not be StringVariable
DISCRETE_MAX_ALLOWED_VALUES = 100


def make_variable(cls, compute_value, *args):
    if compute_value is not None:
        return cls(*args, compute_value=compute_value)
    else:
        # For compatibility with old pickles
        return cls(*args)


def is_discrete_values(values):
    """
    Return set of uniques if `values` is an iterable of discrete values
    else False if non-discrete, or None if indeterminate.

    Note
    ----
    Assumes consistent type of items of `values`.
    """
    if not len(values):
        return None
    # If the first few values are, or can be converted to, floats,
    # the type is numeric
    try:
        isinstance(next(iter(values)), Number) or \
        [float(v) for _, v in zip(range(min(3, len(values))), values)]
    except ValueError:
        is_numeric = False
        max_values = int(round(len(values)**.7))
    else:
        is_numeric = True
        max_values = DISCRETE_MAX_VALUES

    # If more than max values => not discrete
    unique = set()
    for i in values:
        unique.add(i)
        if (len(unique) > max_values or
                len(unique) > DISCRETE_MAX_ALLOWED_VALUES):
            return False

    # Strip NaN from unique
    unique = {i for i in unique
              if (not i in MISSING_VALUES and
                  not (isinstance(i, Number) and np.isnan(i)))}

    # All NaNs => indeterminate
    if not unique:
        return None

    # Strings with |values| < max_unique
    if not is_numeric:
        return unique

    # Handle numbers
    try:
        unique_float = set(map(float, unique))
    except ValueError:
        # Converting all the values to floats resulted in an error.
        # Since the values have enough unique values, they are probably
        # string values and discrete.
        return unique

    # If only values are {0, 1} or {1, 2} (or a subset of those sets) => discrete
    return (not (unique_float - {0, 1}) or
            not (unique_float - {1, 2})) and unique


class Value(float):
    """
    The class representing a value. The class is not used to store values but
    only to return them in contexts in which we want the value to be accompanied
    with the descriptor, for instance to print the symbolic value of discrete
    variables.

    The class is derived from `float`, with an additional attribute `variable`
    which holds the descriptor of type :obj:`Orange.data.Variable`. If the
    value continuous or discrete, it is stored as a float. Other types of
    values, like strings, are stored in the attribute `value`.

    The class overloads the methods for printing out the value:
    `variable.repr_val` and `variable.str_val` are used to get a suitable
    representation of the value.

    Equivalence operator is overloaded as follows:

    - unknown values are equal; if one value is unknown and the other is not,
      they are different;

    - if the value is compared with the string, the value is converted to a
      string using `variable.str_val` and the two strings are compared

    - if the value is stored in attribute `value`, it is compared with the
      given other value

    - otherwise, the inherited comparison operator for `float` is called.

    Finally, value defines a hash, so values can be put in sets and appear as
    keys in dictionaries.

    .. attribute:: variable (:obj:`Orange.data.Variable`)

        Descriptor; used for printing out and for comparing with strings

    .. attribute:: value

        Value; the value can be of arbitrary type and is used only for variables
        that are neither discrete nor continuous. If `value` is `None`, the
        derived `float` value is used.
    """
    __slots__ = "variable", "_value"

    def __new__(cls, variable, value=Unknown):
        """
        Construct a new instance of Value with the given descriptor and value.
        If the argument `value` can be converted to float, it is stored as
        `float` and the attribute `value` is set to `None`. Otherwise, the
        inherited float is set to `Unknown` and the value is held by the
        attribute `value`.

        :param variable: descriptor
        :type variable: Orange.data.Variable
        :param value: value
        """
        if variable.is_primitive():
            self = super().__new__(cls, value)
            self.variable = variable
            self._value = None
        else:
            isunknown = value == variable.Unknown
            self = super().__new__(
                cls, np.nan if isunknown else np.finfo(float).min)
            self.variable = variable
            self._value = value
        return self

    def __init__(self, _, __=Unknown):
        pass

    def __repr__(self):
        return "Value('%s', %s)" % (self.variable.name,
                                    self.variable.repr_val(self))

    def __str__(self):
        return self.variable.str_val(self)

    def __eq__(self, other):
        if isinstance(self, Real) and isnan(self):
            return (isinstance(other, Real) and isnan(other)
                    or other in self.variable.unknown_str)
        if isinstance(other, str):
            return self.variable.str_val(self) == other
        if isinstance(other, Value):
            return self.value == other.value
        return super().__eq__(other)

    def __ne__(self, other):
        return not self.__eq__(other)

    def __lt__(self, other):
        if self.variable.is_primitive():
            if isinstance(other, str):
                return super().__lt__(self.variable.to_val(other))
            else:
                return super().__lt__(other)
        else:
            if isinstance(other, str):
                return self.value < other
            else:
                return self.value < other.value

    def __le__(self, other):
        return self.__lt__(other) or self.__eq__(other)

    def __gt__(self, other):
        return not self.__le__(other)

    def __ge__(self, other):
        return not self.__lt__(other)

    def __contains__(self, other):
        if (self._value is not None
                and isinstance(self._value, str)
                and isinstance(other, str)):
            return other in self._value
        raise TypeError("invalid operation on Value()")

    def __hash__(self):
        if self.variable.is_discrete:
            # It is not possible to hash the id and the domain value to the
            # same number as required by __eq__.
            # hash(1) == hash(Value(DiscreteVariable("var", ["red", "green", "blue"]), 1)) == hash("green")
            # User should hash directly ids or domain values instead.
            raise TypeError("unhashable type - cannot hash values of discrete variables!")
        if self._value is None:
            return super().__hash__()
        else:
            return hash(self._value)

    @property
    def value(self):
        if self.variable.is_discrete:
            return Unknown if isnan(self) else self.variable.values[int(self)]
        if self.variable.is_string:
            return self._value
        return float(self)

    def __getnewargs__(self):
        return self.variable, float(self)

    def __getstate__(self):
        return dict(value=getattr(self, '_value', None))

    def __setstate__(self, state):
        self._value = state.get('value', None)


class VariableMeta(Registry):
    pass


class _predicatedescriptor(property):
    """
    A property that behaves as a class method if accessed via a class
    >>> class A:
    ...     foo = False
    ...     @_predicatedescriptor
    ...     def is_foo(self):
    ...         return self.foo
    ...
    >>> a = A()
    >>> a.is_foo
    False
    >>> A.is_foo(a)
    False
    """
    def __get__(self, instance, objtype=None):
        if instance is None:
            return self.fget
        else:
            return super().__get__(instance, objtype)


class Variable(Reprable, metaclass=VariableMeta):
    """
    The base class for variable descriptors contains the variable's
    name and some basic properties.

    .. attribute:: name

        The name of the variable.

    .. attribute:: unknown_str

        A set of values that represent unknowns in conversion from textual
        formats. Default is `{"?", ".", "", "NA", "~", None}`.

    .. attribute:: compute_value

        A function for computing the variable's value when converting from
        another domain which does not contain this variable. The function will
        be called with a data set (`Orange.data.Table`) and has to return
        an array of computed values for all its instances. The base class
        defines a static method `compute_value`, which returns `Unknown`.
        Non-primitive variables must redefine it to return `None`.

    .. attribute:: sparse

        A flag about sparsity of the variable. When set, the variable suggests
        it should be stored in a sparse matrix.

    .. attribute:: source_variable

        An optional descriptor of the source variable - if any - from which
        this variable is derived and computed via :obj:`compute_value`.

    .. attribute:: attributes

        A dictionary with user-defined attributes of the variable
    """
    Unknown = ValueUnknown

    def __init__(self, name="", compute_value=None, *, sparse=False):
        """
        Construct a variable descriptor.
        """
        if not name:
            warnings.warn("Variable must have a name", OrangeDeprecationWarning,
                          stacklevel=3)
        self._name = name
        self._compute_value = compute_value
        self.unknown_str = MISSING_VALUES
        self.source_variable = None
        self.sparse = sparse
        self.attributes = {}
        self._colors = None

    @property
    def name(self):
        return self._name

    @property
    def colors(self):  # unreachable; pragma: no cover
        return self._colors

    @colors.setter
    def colors(self, value):
        self._colors = value

    def make_proxy(self):
        """
        Copy the variable and set the master to `self.master` or to `self`.

        :return: copy of self
        :rtype: Variable
        """
        var = self.__class__(self.name)
        var.__dict__.update(self.__dict__)
        var.attributes = dict(self.attributes)
        return var

    def __eq__(self, other):
        return type(self) is type(other) \
               and self.name == other.name \
               and self._compute_value == other._compute_value

    def __hash__(self):
        return hash((self.name, type(self), self._compute_value))

    @classmethod
    def make(cls, name, *args, **kwargs):
        """
        Return an existing continuous variable with the given name, or
        construct and return a new one.
        """
        return cls(name, *args, **kwargs)

    @classmethod
    def _clear_cache(cls):
        warnings.warn(
            "_clear_cache is no longer needed and thus deprecated")

    @staticmethod
    def _clear_all_caches():
        warnings.warn(
            "_clear_all_caches is no longer needed and thus deprecated")

    @classmethod
    def is_primitive(cls, var=None):
        """
        `True` if the variable's values are stored as floats.
        Non-primitive variables can appear in the data only as meta attributes.
        """
        to_check = cls if var is None else type(var)
        return issubclass(to_check, (DiscreteVariable, ContinuousVariable))

    @_predicatedescriptor
    def is_discrete(self):
        return isinstance(self, DiscreteVariable)

    @_predicatedescriptor
    def is_continuous(self):
        return isinstance(self, ContinuousVariable)

    @_predicatedescriptor
    def is_string(self):
        return isinstance(self, StringVariable)

    @_predicatedescriptor
    def is_time(self):
        return isinstance(self, TimeVariable)

    def repr_val(self, val):
        """
        Return a textual representation of variable's value `val`. Argument
        `val` must be a float (for primitive variables) or an arbitrary
        Python object (for non-primitives).

        Derived classes must overload the function.
        """
        raise RuntimeError("variable descriptors must overload repr_val()")

    str_val = repr_val

    def to_val(self, s):
        """
        Convert the given argument to a value of the variable. The
        argument can be a string, a number or `None`. For primitive variables,
        the base class provides a method that returns
        :obj:`~Orange.data.Unknown` if `s` is found in
        :obj:`~Orange.data.Variable.unknown_str`, and raises an exception
        otherwise. For non-primitive variables it returns the argument itself.

        Derived classes of primitive variables must overload the function.

        :param s: value, represented as a number, string or `None`
        :type s: str, float or None
        :rtype: float or object
        """
        if not self.is_primitive():
            return s
        if s in self.unknown_str:
            return Unknown
        raise RuntimeError(
            "primitive variable descriptors must overload to_val()")

    def val_from_str_add(self, s):
        """
        Convert the given string to a value of the variable. The method
        is similar to :obj:`to_val` except that it only accepts strings and
        that it adds new values to the variable's domain where applicable.

        The base class method calls `to_val`.

        :param s: symbolic representation of the value
        :type s: str
        :rtype: float or object
        """
        return self.to_val(s)

    def __str__(self):
        return self.name

    @property
    def compute_value(self):
        return self._compute_value

    def __reduce__(self):
        if not self.name:
            raise PickleError("Variables without names cannot be pickled")

        # Use make to unpickle variables.
        return make_variable, (self.__class__, self._compute_value, self.name), self.__dict__

    def copy(self, compute_value=None, *, name=None, **kwargs):
        var = type(self)(name=name or self.name,
                         compute_value=compute_value or self.compute_value,
                         sparse=self.sparse, **kwargs)
        var.attributes = dict(self.attributes)
        return var


del _predicatedescriptor


class ContinuousVariable(Variable):
    """
    Descriptor for continuous variables.

    .. attribute:: number_of_decimals

        The number of decimals when the value is printed out (default: 3).

    .. attribute:: adjust_decimals

        A flag regulating whether the `number_of_decimals` is being adjusted
        by :obj:`to_val`.

    The value of `number_of_decimals` is set to 3 and `adjust_decimals`
    is set to 2. When :obj:`val_from_str_add` is called for the first
    time with a string as an argument, `number_of_decimals` is set to the
    number of decimals in the string and `adjust_decimals` is set to 1.
    In the subsequent calls of `to_val`, the nubmer of decimals is
    increased if the string argument has a larger number of decimals.

    If the `number_of_decimals` is set manually, `adjust_decimals` is
    set to 0 to prevent changes by `to_val`.
    """

    TYPE_HEADERS = ('continuous', 'c', 'numeric', 'n')

    def __init__(self, name="", number_of_decimals=None, compute_value=None, *, sparse=False):
        """
        Construct a new continuous variable. The number of decimals is set to
        three, but adjusted at the first call of :obj:`to_val`.
        """
        super().__init__(name, compute_value, sparse=sparse)
        if number_of_decimals is None:
            self._number_of_decimals = 3
            self.adjust_decimals = 2
            self._format_str = "%g"
        else:
            self.number_of_decimals = number_of_decimals

    @property
    def number_of_decimals(self):
        return self._number_of_decimals

    @property
    def format_str(self):
        return self._format_str

    @format_str.setter
    def format_str(self, value):
        self._format_str = value

    @Variable.colors.getter
    def colors(self):
        if self._colors is not None:
            return self._colors
        try:
            col1, col2, black = self.attributes["colors"]
            return (hex_to_color(col1), hex_to_color(col2), black)
        except (KeyError, ValueError):
            # User-provided colors were not available or invalid
            return ((0, 0, 255), (255, 255, 0), False)

    # noinspection PyAttributeOutsideInit
    @number_of_decimals.setter
    def number_of_decimals(self, x):
        self._number_of_decimals = x
        self.adjust_decimals = 0
        if self._number_of_decimals <= MAX_NUM_OF_DECIMALS:
            self._format_str = "%.{}f".format(self.number_of_decimals)
        else:
            self._format_str = "%g"

    def to_val(self, s):
        """
        Convert a value, given as an instance of an arbitrary type, to a float.
        """
        if s in self.unknown_str:
            return Unknown
        return float(s)

    def val_from_str_add(self, s):
        """
        Convert a value from a string and adjust the number of decimals if
        `adjust_decimals` is non-zero.
        """
        return _variable.val_from_str_add_cont(self, s)

    def repr_val(self, val):
        """
        Return the value as a string with the prescribed number of decimals.
        """
        if isnan(val):
            return "?"
        return self._format_str % val

    str_val = repr_val

    def copy(self, compute_value=None, *, name=None, **kwargs):
        var = super().copy(compute_value=compute_value, name=name,
                           number_of_decimals=self.number_of_decimals,
                           **kwargs)
        var.adjust_decimals = self.adjust_decimals
        var.format_str = self._format_str
        return var


class DiscreteVariable(Variable):
    """
    Descriptor for symbolic, discrete variables. Values of discrete variables
    are stored as floats; the numbers corresponds to indices in the list of
    values.

    .. attribute:: values

        A list of variable's values.

    .. attribute:: ordered

        Some algorithms (and, in particular, visualizations) may
        sometime reorder the values of the variable, e.g. alphabetically.
        This flag hints that the given order of values is "natural"
        (e.g. "small", "middle", "large") and should not be changed.
    """

    TYPE_HEADERS = ('discrete', 'd', 'categorical')

    presorted_values = []

    def __init__(self, name="", values=(), ordered=False, compute_value=None,
                 *, sparse=False):
        """ Construct a discrete variable descriptor with the given values. """
        self.values = list(values)
        if not all(isinstance(value, str) for value in self.values):
            raise TypeError("values of DiscreteVariables must be strings")
        super().__init__(name, compute_value, sparse=sparse)
        self.ordered = ordered

    def get_mapping_from(self, other):
        return np.array(
            [self.values.index(value) if value in self.values else np.nan
             for value in other.values], dtype=float)

    def get_mapper_from(self, other):
        mapping = self.get_mapping_from(other)
        if not mapping.size:
            # Nans in data are temporarily replaced with 0, mapped and changed
            # back to nans. This would fail is mapping[0] is out of range.
            mapping = np.array([np.nan])

        def mapper(value, col_idx=None):

            # In-place mapping
            if col_idx is not None:
                if sp.issparse(value) and mapping[0] != 0:
                    raise ValueError(
                        "In-place mapping of sparse matrices must map 0 to 0")

                # CSR requires mapping of non-contiguous area
                if sp.isspmatrix_csr(value):
                    col = value.indices == col_idx
                    nans = np.isnan(value.data) * col
                    value.data[nans] = 0
                    value.data[col] = mapping[value.data[col].astype(int)]
                    value.data[nans] = np.nan
                    return

                # Dense and CSC map a contiguous area
                if isinstance(value, np.ndarray) and value.ndim == 2:
                    col = value[:, col_idx]
                elif sp.isspmatrix_csc(value):
                    col = value.data[value.indptr[col_idx]
                                     :value.indptr[col_idx + 1]]
                else:
                    raise ValueError(
                        "In-place column mapping requires a 2d array or"
                        "a csc or csr matrix.")

                nans = np.isnan(col)
                col[nans] = 0
                col[:] = mapping[col.astype(int)]
                col[nans] = np.nan
                return

            # Mapping into a copy
            if isinstance(value, (int, float)):
                return mapping[int(value)] if value == value else value
            if isinstance(value, str):
                return mapping[other.values.index(value)]
            if isinstance(value, np.ndarray):
                if not (value.ndim == 1
                        or value.ndim != 2 and min(value.shape) != 1):
                    raise ValueError(
                        f"Column mapping can't map {value.ndim}-d objects")

                if value.dtype == object:
                    value = value.astype(float)  # this happens with metas
                try:
                    nans = np.isnan(value)
                except TypeError:  # suppose it's already an integer type
                    return mapping[value]
                value = value.astype(int)
                value[nans] = 0
                value = mapping[value]
                value[nans] = np.nan
                return value
            if sp.issparse(value):
                if min(value.shape) != 1:
                    raise ValueError("Column mapping can't map "
                                     f"{value.ndim}-dimensional objects")
                if mapping[0] != 0 and not np.isnan(mapping[0]):
                    return mapper(np.array(value.todense()).flatten())
                value = value.copy()
                value.data = mapper(value.data)
                return value
            if isinstance(value, Iterable):
                return type(value)(mapping[int(val)] if val == val else val
                                   for val in value)
            raise ValueError(
                f"invalid type for value(s): {type(value).__name__}")

        return mapper

    @Variable.colors.getter
    def colors(self):
        if self._colors is not None:
            colors = np.array(self._colors)
        elif not self.values:
            colors = np.zeros((0, 3))  # to match additional colors in vstacks
        else:
            from Orange.widgets.utils.colorpalette import ColorPaletteGenerator
            default = tuple(ColorPaletteGenerator.palette(self))
            colors = self.attributes.get('colors', ())
            colors = tuple(hex_to_color(color) for color in colors) \
                    + default[len(colors):]
            colors = np.array(colors)
        colors.flags.writeable = False
        return colors

    def to_val(self, s):
        """
        Convert the given argument to a value of the variable (`float`).
        If the argument is numeric, its value is returned without checking
        whether it is integer and within bounds. `Unknown` is returned if the
        argument is one of the representations for unknown values. Otherwise,
        the argument must be a string and the method returns its index in
        :obj:`values`.

        :param s: values, represented as a number, string or `None`
        :rtype: float
        """
        if s is None:
            return ValueUnknown

        if isinstance(s, Integral):
            return s
        if isinstance(s, Real):
            return s if isnan(s) else floor(s + 0.25)
        if s in self.unknown_str:
            return ValueUnknown
        if not isinstance(s, str):
            raise TypeError('Cannot convert {} to value of "{}"'.format(
                type(s).__name__, self.name))
        return self.values.index(s)

    def add_value(self, s):
        """ Add a value `s` to the list of values.
        """
        if not isinstance(s, str):
            raise TypeError("values of DiscreteVariables must be strings")
        self.values.append(s)
        self._colors = None

    def val_from_str_add(self, s):
        """
        Similar to :obj:`to_val`, except that it accepts only strings and that
        it adds the value to the list if it does not exist yet.

        :param s: symbolic representation of the value
        :type s: str
        :rtype: float
        """
        s = str(s) if s is not None else s
        try:
            return ValueUnknown if s in self.unknown_str \
                else self.values.index(s)
        except ValueError:
            self.add_value(s)
            return len(self.values) - 1

    def repr_val(self, val):
        """
        Return a textual representation of the value (`self.values[int(val)]`)
        or "?" if the value is unknown.

        :param val: value
        :type val: float (should be whole number)
        :rtype: str
        """
        if isnan(val):
            return "?"
        return '{}'.format(self.values[int(val)])

    str_val = repr_val

    def __reduce__(self):
        if not self.name:
            raise PickleError("Variables without names cannot be pickled")
        __dict__ = dict(self.__dict__)
        __dict__.pop("values")
        return make_variable, (self.__class__, self._compute_value, self.name,
                               self.values, self.ordered), \
            __dict__

    def copy(self, compute_value=None, *, name=None, **_):
        return super().copy(compute_value=compute_value, name=name,
                            values=self.values, ordered=self.ordered)


class StringVariable(Variable):
    """
    Descriptor for string variables. String variables can only appear as
    meta attributes.
    """
    Unknown = ""
    TYPE_HEADERS = ('string', 's', 'text')

    def to_val(self, s):
        """
        Return the value as a string. If it is already a string, the same
        object is returned.
        """
        if s is None:
            return ""
        if isinstance(s, str):
            return s
        return str(s)

    val_from_str_add = to_val

    @staticmethod
    def str_val(val):
        """Return a string representation of the value."""
        if val is "":
            return "?"
        if isinstance(val, Value):
            if val.value is "":
                return "?"
            val = val.value
        return str(val)

    def repr_val(self, val):
        """Return a string representation of the value."""
        return '"{}"'.format(self.str_val(val))


class TimeVariable(ContinuousVariable):
    """
    TimeVariable is a continuous variable with Unix epoch
    (1970-01-01 00:00:00+0000) as the origin (0.0). Later dates are positive
    real numbers (equivalent to Unix timestamp, with microseconds in the
    fraction part), and the dates before it map to the negative real numbers.

    Unfortunately due to limitation of Python datetime, only dates
    with year >= 1 (A.D.) are supported.

    If time is specified without a date, Unix epoch is assumed.

    If time is specified wihout an UTC offset, localtime is assumed.
    """
    _all_vars = {}
    TYPE_HEADERS = ('time', 't')
    UNIX_EPOCH = datetime(1970, 1, 1)
    _ISO_FORMATS = [
        # have_date, have_time, format_str
        # in order of decreased probability
        (1, 1, '%Y-%m-%d %H:%M:%S%z'),
        (1, 1, '%Y-%m-%d %H:%M:%S'),
        (1, 1, '%Y-%m-%d %H:%M'),
        (1, 1, '%Y-%m-%dT%H:%M:%S%z'),
        (1, 1, '%Y-%m-%dT%H:%M:%S'),

        (1, 0, '%Y-%m-%d'),

        (1, 1, '%Y-%m-%d %H:%M:%S.%f'),
        (1, 1, '%Y-%m-%dT%H:%M:%S.%f'),
        (1, 1, '%Y-%m-%d %H:%M:%S.%f%z'),
        (1, 1, '%Y-%m-%dT%H:%M:%S.%f%z'),

        (1, 1, '%Y%m%dT%H%M%S%z'),
        (1, 1, '%Y%m%d%H%M%S%z'),

        (0, 1, '%H:%M:%S.%f'),
        (0, 1, '%H:%M:%S'),
        (0, 1, '%H:%M'),

        # These parse as continuous features (plain numbers)
        (1, 1, '%Y%m%dT%H%M%S'),
        (1, 1, '%Y%m%d%H%M%S'),
        (1, 0, '%Y%m%d'),
        (1, 0, '%Y%j'),
        (1, 0, '%Y'),
        (0, 1, '%H%M%S.%f'),

        # BUG: In Python as in C, %j doesn't necessitate 0-padding,
        # so these two lines must be in this order
        (1, 0, '%Y-%m'),
        (1, 0, '%Y-%j'),
    ]
    # The regex that matches all above formats
    REGEX = (r'^('
             r'\d{1,4}-\d{2}-\d{2}([ T]\d{2}:\d{2}(:\d{2}(\.\d+)?([+-]\d{4})?)?)?|'
             r'\d{1,4}\d{2}\d{2}(T?\d{2}\d{2}\d{2}([+-]\d{4})?)?|'
             r'\d{2}:\d{2}(:\d{2}(\.\d+)?)?|'
             r'\d{2}\d{2}\d{2}\.\d+|'
             r'\d{1,4}(-?\d{2,3})?'
             r')$')
    _matches_iso_format = re.compile(REGEX).match

    # UTC offset and associated timezone. If parsed datetime values provide an
    # offset, it is used for display. If not all values have the same offset,
    # +0000 (=UTC) timezone is used and utc_offset is set to False.
    utc_offset = None
    timezone = timezone.utc

    def __init__(self, *args, have_date=0, have_time=0, **kwargs):
        super().__init__(*args, **kwargs)
        self.have_date = have_date
        self.have_time = have_time

    def copy(self, compute_value=None, *, name=None, **_):
        return super().copy(compute_value=compute_value, name=name,
                            have_date=self.have_date, have_time=self.have_time)

    @staticmethod
    def _tzre_sub(s, _subtz=re.compile(r'([+-])(\d\d):(\d\d)$').sub):
        # Replace +ZZ:ZZ with ISO-compatible +ZZZZ, or strip +0000
        return s[:-6] if s.endswith(('+00:00', '-00:00')) else _subtz(r'\1\2\3', s)

    def repr_val(self, val):
        if isnan(val):
            return '?'
        if not self.have_date and not self.have_time:
            # The time is relative, unitless. The value is absolute.
            return str(val.value) if isinstance(val, Value) else str(val)

        # If you know how to simplify this, be my guest
        seconds = int(val)
        microseconds = int(round((val - seconds) * 1e6))
        if val < 0:
            if microseconds:
                seconds, microseconds = seconds - 1, int(1e6) + microseconds
            date = datetime.fromtimestamp(0, tz=self.timezone) + timedelta(seconds=seconds)
        else:
            date = datetime.fromtimestamp(seconds, tz=self.timezone)
        date = str(date.replace(microsecond=microseconds))

        if self.have_date and not self.have_time:
            date = date.split()[0]
        elif not self.have_date and self.have_time:
            date = date.split()[1]
        date = self._tzre_sub(date)
        return date

    str_val = repr_val

    def parse(self, datestr):
        """
        Return `datestr`, a datetime provided in one of ISO 8601 formats,
        parsed as a real number. Value 0 marks the Unix epoch, positive values
        are the dates after it, negative before.

        If date is unspecified, epoch date is assumed.

        If time is unspecified, 00:00:00.0 is assumed.

        If timezone is unspecified, local time is assumed.
        """
        if datestr in MISSING_VALUES:
            return Unknown
        datestr = datestr.strip().rstrip('Z')

        ERROR = ValueError("Invalid datetime format '{}'. "
                           "Only ISO 8601 supported.".format(datestr))
        if not self._matches_iso_format(datestr):
            try:
                # If it is a number, assume it is a unix timestamp
                value = float(datestr)
                self.have_date = self.have_time = 1
                return value
            except ValueError:
                raise ERROR

        for i, (have_date, have_time, fmt) in enumerate(self._ISO_FORMATS):
            try:
                dt = datetime.strptime(datestr, fmt)
            except ValueError:
                continue
            else:
                # Pop this most-recently-used format to front
                if 0 < i < len(self._ISO_FORMATS) - 2:
                    self._ISO_FORMATS[i], self._ISO_FORMATS[0] = \
                        self._ISO_FORMATS[0], self._ISO_FORMATS[i]

                self.have_date |= have_date
                self.have_time |= have_time
                if not have_date:
                    dt = dt.replace(self.UNIX_EPOCH.year,
                                    self.UNIX_EPOCH.month,
                                    self.UNIX_EPOCH.day)
                break
        else:
            raise ERROR

        # Remember UTC offset. If not all parsed values share the same offset,
        # remember none of it.
        offset = dt.utcoffset()
        if self.utc_offset is not False:
            if offset and self.utc_offset is None:
                self.utc_offset = offset
                self.timezone = timezone(offset)
            elif self.utc_offset != offset:
                self.utc_offset = False
                self.timezone = timezone.utc

        # Convert time to UTC timezone. In dates without timezone,
        # localtime is assumed. See also:
        # https://docs.python.org/3.4/library/datetime.html#datetime.datetime.timestamp
        if dt.tzinfo:
            dt -= dt.utcoffset()
        dt = dt.replace(tzinfo=timezone.utc)

        # Unix epoch is the origin, older dates are negative
        try:
            return dt.timestamp()
        except OverflowError:
            return -(self.UNIX_EPOCH - dt).total_seconds()

    def to_val(self, s):
        """
        Convert a value, given as an instance of an arbitrary type, to a float.
        """
        if isinstance(s, str):
            return self.parse(s)
        else:
            return super().to_val(s)

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user