text: """
      jsonspec.validators.util
      ~~~~~~~~~~~~~~~~~~~~~~~~
  
  """
  
  from __future__ import absolute_import
  
  import ipaddress
  import logging
  import re
  import time
  from copy import deepcopy
  from decimal import Decimal
  from datetime import tzinfo, timedelta, datetime, date
  from urllib.parse import urlparse
  from .exceptions import ValidationError
  
  __all__ = []
  
  number_types = (int, float, Decimal)
  
  logger = logging.getLogger(__name__)
  
  HOSTNAME_TOKENS = re.compile('(?!-)[a-z\d-]{1,63}(?<!-)$', re.IGNORECASE)
  HOSTNAME_LAST_TOKEN = re.compile('[a-z]+$', re.IGNORECASE)
  EMAIL = re.compile('[^@]+@[^@]+\.[^@]+')
  
  CSS_COLORS = set([
      'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige',
      'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown',
      'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral',
      'cornflowerblue', 'cornsilk', 'crimson', 'darkblue', 'darkcyan',
      'darkgoldenrod', 'darkgray', 'darkgreen', 'darkkhaki', 'darkmagenta',
      'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon',
      'darkseagreen', 'darkslateblue', 'darkslategray', 'darkturquoise',
      'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dodgerblue',
      'feldspar', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia',
      'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green',
      'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory',
      'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon',
      'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow',
      'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen',
      'lightskyblue', 'lightslateblue', 'lightslategray', 'lightsteelblue',
      'lightyellow', 'lime', 'limegreen', 'linen', 'maroon', 'mediumaquamarine',
      'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen',
      'mediumslateblue', 'mediumspringgreen', 'mediumturquoise',
      'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin',
      'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange',
      'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise',
      'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum',
      'powderblue', 'purple', 'red', 'rosybrown', 'royalblue', 'saddlebrown',
      'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver',
      'skyblue', 'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue',
      'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violetred', 'wheat',
      'white', 'whitesmoke', 'yellow', 'yellowgreen'])
  
  
  def uncamel(name):
      """converts camelcase to underscore
      >>> uncamel('fooBar')
      'foo_bar'
      >>> uncamel('FooBar')
      'foo_bar'
      >>> uncamel('_fooBar')
      '_foo_bar'
      >>> uncamel('_FooBar')
      '__foo_bar'
      """
      response, name = name[0].lower(), name[1:]
      for n in name:
          if n.isupper():
              response += '_' + n.lower()
          else:
              response += n
      return response
  
  
  class offset(tzinfo):
      def __init__(self, value):
          self.value = value
  
      def utcoffset(self, dt):
          hours, minutes = self.value.split(':', 1)
          return timedelta(hours=int(hours), minutes=int(minutes))
  
      def tzname(self, dt):
          return '{}'.format(self.value)
  
  
  def rfc3339_to_datetime(data):
      """convert a rfc3339 date representation into a Python datetime"""
      try:
          ts = time.strptime(data, '%Y-%m-%d')
          return date(*ts[:3])
      except ValueError:
          pass
  
      try:
          dt, _, tz = data.partition('Z')
          if tz:
              tz = offset(tz)
          else:
              tz = offset('00:00')
          if '.' in dt and dt.rsplit('.', 1)[-1].isdigit():
              ts = time.strptime(dt, '%Y-%m-%dT%H:%M:%S.%f')
          else:
              ts = time.strptime(dt, '%Y-%m-%dT%H:%M:%S')
          return datetime(*ts[:6], tzinfo=tz)
      except ValueError:
          raise ValueError('date-time {!r} is not a valid rfc3339 date representation'.format(data))  # noqa
  
  
  def validate_css_color(obj):
      color = obj.lower()
      if len(color) == 7 and re.match('^#[0-9a-f]{6}$', color):
          return obj
      elif len(color) == 4 and re.match('^#[0-9a-f]{3}$', color):
          return obj
      elif color not in CSS_COLORS:
          raise ValidationError('Not a css color {!r}'.format(obj))
      return obj
  
  
  def validate_rfc3339_datetime(obj):
      try:
          rfc3339_to_datetime(obj)
      except ValueError:
          raise ValidationError('{!r} is not a valid datetime', obj)
      return obj
  
  
  def validate_utc_datetime(obj):
      if not obj.endswith('Z'):
          raise ValidationError('{!r} is not a valid datetime', obj)
      obj = obj[:-1]
      if '.' in obj:
          obj, milli = obj.split('.', 1)
          if not milli.isdigit():
              raise ValidationError('{!r} is not a valid datetime', obj)
  
      try:
          time.strptime(obj, '%Y-%m-%dT%H:%M:%S')
      except ValueError:
          raise ValidationError('{!r} is not a valid datetime', obj)
      return obj
  
  
  def validate_utc_date(obj):
      try:
          time.strptime(obj, '%Y-%m-%d')
      except (TypeError, ValueError):
          raise ValidationError('{!r} is not a valid date', obj)
      return obj
  
  
  def validate_utc_time(obj):
      try:
          time.strptime(obj, '%H:%M:%S')
      except (TypeError, ValueError):
          raise ValidationError('{!r} is not a valid time', obj)
      return obj
  
  
  def validate_utc_millisec(obj):
      try:
          if not isinstance(obj, number_types):
              raise TypeError
          datetime.utcfromtimestamp(obj / 1000)
      except (TypeError, ValueError):
          raise ValidationError('{!r} is not a valid utc millis', obj)
      return obj
  
  
  def validate_email(obj):
      if not EMAIL.match(obj):
          raise ValidationError('{!r} is not defined')
      return obj
  
  
  def validate_hostname(obj):
      try:
          host = deepcopy(obj)
          if len(host) > 255:
              raise ValueError
          if host[-1] == '.':
              host = host[:-1]
          tokens = host.split('.')
          if not all(HOSTNAME_TOKENS.match(x) for x in tokens):
              raise ValueError
          if not HOSTNAME_LAST_TOKEN.search(tokens[-1]):
              raise ValueError
      except ValueError:
          raise ValidationError('{!r} is not a valid hostname'.format(obj))
      return obj
  
  
  def validate_ipv4(obj):
      try:
          obj = str(obj)
          ipaddress.IPv4Address(obj)
      except (ipaddress.AddressValueError, ipaddress.NetmaskValueError):
          raise ValidationError('{!r} does not appear to '
                                'be an IPv4 address'.format(obj))
      return obj
  
  
  def validate_ipv6(obj):
      try:
          obj = str(obj)
          ipaddress.IPv6Address(obj)
      except (ipaddress.AddressValueError, ipaddress.NetmaskValueError):
          raise ValidationError('{!r} does not appear to '
                                'be an IPv6 address'.format(obj))
      return obj
  
  
  def validate_regex(obj):
      # TODO implement ECMA 262 regex
      import re
      try:
          re.compile(obj)
      except:
          raise ValidationError('Not a regex', obj)
      return obj
  
  
  def validate_uri(obj):
      try:
          if ':' not in obj:
              raise ValueError('missing scheme')
          urlparse(obj)
      except Exception as error:
          logger.exception(error)
          raise ValidationError('{!r} is not an uri'.format(obj))
      return obj
  