Upgrade origin-src to google transit feed 1.2.6
[bus.git] / origin-src / transitfeed-1.2.6 / transitfeed / problems.py
blob:a/origin-src/transitfeed-1.2.6/transitfeed/problems.py -> blob:b/origin-src/transitfeed-1.2.6/transitfeed/problems.py
--- a/origin-src/transitfeed-1.2.6/transitfeed/problems.py
+++ b/origin-src/transitfeed-1.2.6/transitfeed/problems.py
@@ -1,1 +1,706 @@
-
+#!/usr/bin/python2.5
+
+# Copyright (C) 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import time
+
+import util
+
+# These are used to distinguish between errors (not allowed by the spec)
+# and warnings (not recommended) when reporting issues.
+TYPE_ERROR = 0
+TYPE_WARNING = 1
+
+MAX_DISTANCE_FROM_STOP_TO_SHAPE = 1000
+MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_WARNING = 100.0
+MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_ERROR = 1000.0
+
+
+class ProblemReporter(object):
+  """Base class for problem reporters. Tracks the current context and creates
+     an exception object for each problem. Exception objects are sent to a
+     Problem Accumulator, which is responsible for handling them."""
+
+  def __init__(self, accumulator=None):
+    self.ClearContext()
+    if accumulator is None:
+      self.accumulator = SimpleProblemAccumulator()
+    else:
+      self.accumulator = accumulator
+
+  def SetAccumulator(self, accumulator):
+    self.accumulator = accumulator
+
+  def GetAccumulator(self):
+    return self.accumulator
+
+  def ClearContext(self):
+    """Clear any previous context."""
+    self._context = None
+
+  def SetFileContext(self, file_name, row_num, row, headers):
+    """Save the current context to be output with any errors.
+
+    Args:
+      file_name: string
+      row_num: int
+      row: list of strings
+      headers: list of column headers, its order corresponding to row's
+    """
+    self._context = (file_name, row_num, row, headers)
+
+  def AddToAccumulator(self,e):
+    """Report an exception to the Problem Accumulator"""
+    self.accumulator._Report(e)
+
+  def FeedNotFound(self, feed_name, context=None):
+    e = FeedNotFound(feed_name=feed_name, context=context,
+                     context2=self._context)
+    self.AddToAccumulator(e)
+
+  def UnknownFormat(self, feed_name, context=None):
+    e = UnknownFormat(feed_name=feed_name, context=context,
+                      context2=self._context)
+    self.AddToAccumulator(e)
+
+  def FileFormat(self, problem, context=None):
+    e = FileFormat(problem=problem, context=context,
+                   context2=self._context)
+    self.AddToAccumulator(e)
+
+  def MissingFile(self, file_name, context=None):
+    e = MissingFile(file_name=file_name, context=context,
+                    context2=self._context)
+    self.AddToAccumulator(e)
+
+  def UnknownFile(self, file_name, context=None):
+    e = UnknownFile(file_name=file_name, context=context,
+                  context2=self._context, type=TYPE_WARNING)
+    self.AddToAccumulator(e)
+
+  def EmptyFile(self, file_name, context=None):
+    e = EmptyFile(file_name=file_name, context=context,
+                  context2=self._context)
+    self.AddToAccumulator(e)
+
+  def MissingColumn(self, file_name, column_name, context=None):
+    e = MissingColumn(file_name=file_name, column_name=column_name,
+                      context=context, context2=self._context)
+    self.AddToAccumulator(e)
+
+  def UnrecognizedColumn(self, file_name, column_name, context=None):
+    e = UnrecognizedColumn(file_name=file_name, column_name=column_name,
+                           context=context, context2=self._context,
+                           type=TYPE_WARNING)
+    self.AddToAccumulator(e)
+
+  def CsvSyntax(self, description=None, context=None, type=TYPE_ERROR):
+    e = CsvSyntax(description=description, context=context,
+                  context2=self._context, type=type)
+    self.AddToAccumulator(e)
+
+  def DuplicateColumn(self, file_name, header, count, type=TYPE_ERROR, 
+                      context=None):
+    e = DuplicateColumn(file_name=file_name,
+                        header=header,
+                        count=count,
+                        type=type,
+                        context=context,
+                        context2=self._context)
+    self.AddToAccumulator(e)
+
+  def MissingValue(self, column_name, reason=None, context=None):
+    e = MissingValue(column_name=column_name, reason=reason, context=context,
+                     context2=self._context)
+    self.AddToAccumulator(e)
+
+  def InvalidValue(self, column_name, value, reason=None, context=None,
+                   type=TYPE_ERROR):
+    e = InvalidValue(column_name=column_name, value=value, reason=reason,
+                     context=context, context2=self._context, type=type)
+    self.AddToAccumulator(e)
+
+  def InvalidFloatValue(self, value, reason=None, context=None,
+                        type=TYPE_WARNING):
+    e = InvalidFloatValue(value=value, reason=reason, context=context,
+                          context2=self._context, type=type)
+    self.AddToAccumulator(e)
+
+  def InvalidNonNegativeIntegerValue(self, value, reason=None, context=None,
+                                     type=TYPE_WARNING):
+    e = InvalidNonNegativeIntegerValue(value=value, reason=reason,
+                                       context=context, context2=self._context,
+                                       type=type)
+    self.AddToAccumulator(e)
+
+  def DuplicateID(self, column_names, values, context=None, type=TYPE_ERROR):
+    if isinstance(column_names, (tuple, list)):
+      column_names = '(' + ', '.join(column_names) + ')'
+    if isinstance(values, tuple):
+      values = '(' + ', '.join(values) + ')'
+    e = DuplicateID(column_name=column_names, value=values,
+                    context=context, context2=self._context, type=type)
+    self.AddToAccumulator(e)
+
+  def UnusedStop(self, stop_id, stop_name, context=None):
+    e = UnusedStop(stop_id=stop_id, stop_name=stop_name,
+                   context=context, context2=self._context, type=TYPE_WARNING)
+    self.AddToAccumulator(e)
+
+  def UsedStation(self, stop_id, stop_name, context=None):
+    e = UsedStation(stop_id=stop_id, stop_name=stop_name,
+                    context=context, context2=self._context, type=TYPE_ERROR)
+    self.AddToAccumulator(e)
+
+  def StopTooFarFromParentStation(self, stop_id, stop_name, parent_stop_id,
+                                  parent_stop_name, distance,
+                                  type=TYPE_WARNING, context=None):
+    e = StopTooFarFromParentStation(
+        stop_id=stop_id, stop_name=stop_name,
+        parent_stop_id=parent_stop_id,
+        parent_stop_name=parent_stop_name, distance=distance,
+        context=context, context2=self._context, type=type)
+    self.AddToAccumulator(e)
+
+  def StopsTooClose(self, stop_name_a, stop_id_a, stop_name_b, stop_id_b,
+                    distance, type=TYPE_WARNING, context=None):
+    e = StopsTooClose(
+        stop_name_a=stop_name_a, stop_id_a=stop_id_a, stop_name_b=stop_name_b,
+        stop_id_b=stop_id_b, distance=distance, context=context,
+        context2=self._context, type=type)
+    self.AddToAccumulator(e)
+
+  def StationsTooClose(self, stop_name_a, stop_id_a, stop_name_b, stop_id_b,
+                       distance, type=TYPE_WARNING, context=None):
+    e = StationsTooClose(
+        stop_name_a=stop_name_a, stop_id_a=stop_id_a, stop_name_b=stop_name_b,
+        stop_id_b=stop_id_b, distance=distance, context=context,
+        context2=self._context, type=type)
+    self.AddToAccumulator(e)
+
+  def DifferentStationTooClose(self, stop_name, stop_id,
+                               station_stop_name, station_stop_id,
+                               distance, type=TYPE_WARNING, context=None):
+    e = DifferentStationTooClose(
+        stop_name=stop_name, stop_id=stop_id,
+        station_stop_name=station_stop_name, station_stop_id=station_stop_id,
+        distance=distance, context=context, context2=self._context, type=type)
+    self.AddToAccumulator(e)
+
+  def StopTooFarFromShapeWithDistTraveled(self, trip_id, stop_name, stop_id,
+                                          shape_dist_traveled, shape_id,
+                                          distance, max_distance,
+                                          type=TYPE_WARNING):
+    e = StopTooFarFromShapeWithDistTraveled(
+        trip_id=trip_id, stop_name=stop_name, stop_id=stop_id,
+        shape_dist_traveled=shape_dist_traveled, shape_id=shape_id,
+        distance=distance, max_distance=max_distance, type=type)
+    self.AddToAccumulator(e)
+
+  def ExpirationDate(self, expiration, context=None):
+    e = ExpirationDate(expiration=expiration, context=context,
+                       context2=self._context, type=TYPE_WARNING)
+    self.AddToAccumulator(e)
+
+  def FutureService(self, start_date, context=None):
+    e = FutureService(start_date=start_date, context=context,
+                      context2=self._context, type=TYPE_WARNING)
+    self.AddToAccumulator(e)
+
+  def NoServiceExceptions(self, start, end, type=TYPE_WARNING, context=None):
+    e = NoServiceExceptions(start=start, end=end, context=context,
+                            context2=self._context, type=type);
+    self.AddToAccumulator(e)
+
+  def InvalidLineEnd(self, bad_line_end, context=None):
+    """bad_line_end is a human readable string."""
+    e = InvalidLineEnd(bad_line_end=bad_line_end, context=context,
+                       context2=self._context, type=TYPE_WARNING)
+    self.AddToAccumulator(e)
+
+  def TooFastTravel(self, trip_id, prev_stop, next_stop, dist, time, speed,
+                    type=TYPE_ERROR):
+    e = TooFastTravel(trip_id=trip_id, prev_stop=prev_stop,
+                      next_stop=next_stop, time=time, dist=dist, speed=speed,
+                      context=None, context2=self._context, type=type)
+    self.AddToAccumulator(e)
+
+  def StopWithMultipleRouteTypes(self, stop_name, stop_id, route_id1, route_id2,
+                                 context=None):
+    e = StopWithMultipleRouteTypes(stop_name=stop_name, stop_id=stop_id,
+                                   route_id1=route_id1, route_id2=route_id2,
+                                   context=context, context2=self._context,
+                                   type=TYPE_WARNING)
+    self.AddToAccumulator(e)
+
+  def DuplicateTrip(self, trip_id1, route_id1, trip_id2, route_id2,
+                    context=None):
+    e = DuplicateTrip(trip_id1=trip_id1, route_id1=route_id1, trip_id2=trip_id2,
+                      route_id2=route_id2, context=context,
+                      context2=self._context, type=TYPE_WARNING)
+    self.AddToAccumulator(e)
+
+  def OverlappingTripsInSameBlock(self,trip_id1,trip_id2,block_id,context=None):
+    e = OverlappingTripsInSameBlock(trip_id1=trip_id1, trip_id2=trip_id2,
+                                    block_id=block_id, context=context,
+                                    context2=self._context,type=TYPE_WARNING)
+    self.AddToAccumulator(e)
+
+  def TransferDistanceTooBig(self, from_stop_id, to_stop_id, distance,
+                             context=None, type=TYPE_ERROR):
+    e = TransferDistanceTooBig(from_stop_id=from_stop_id, to_stop_id=to_stop_id,
+                               distance=distance, context=context,
+                               context2=self._context, type=type)
+    self.AddToAccumulator(e)
+
+  def TransferWalkingSpeedTooFast(self, from_stop_id, to_stop_id, distance,
+                                  transfer_time, context=None,
+                                  type=TYPE_WARNING):
+    e = TransferWalkingSpeedTooFast(from_stop_id=from_stop_id,
+                                    transfer_time=transfer_time,
+                                    distance=distance,
+                                    to_stop_id=to_stop_id, context=context,
+                                    context2=self._context, type=type)
+    self.AddToAccumulator(e)
+
+  def OtherProblem(self, description, context=None, type=TYPE_ERROR):
+    e = OtherProblem(description=description,
+                    context=context, context2=self._context, type=type)
+    self.AddToAccumulator(e)
+
+  def TooManyDaysWithoutService(self,
+                                first_day_without_service,
+                                last_day_without_service,
+                                consecutive_days_without_service,
+                                context=None, 
+                                type=TYPE_WARNING):
+    e = TooManyDaysWithoutService(
+        first_day_without_service=first_day_without_service,
+        last_day_without_service=last_day_without_service,
+        consecutive_days_without_service=consecutive_days_without_service,
+        context=context,
+        context2=self._context,
+        type=type)
+    self.AddToAccumulator(e)
+
+  def MinimumTransferTimeSetWithInvalidTransferType(self, 
+                                                    transfer_type=None,
+                                                    context=None,
+                                                    type=TYPE_ERROR):
+    e = MinimumTransferTimeSetWithInvalidTransferType(context=context,
+        context2=self._context, transfer_type=transfer_type, type=type)
+    self.AddToAccumulator(e)
+
+
+class ProblemAccumulatorInterface(object):
+  """The base class for Problem Accumulators, which defines their interface."""
+
+  def _Report(self, e):
+    raise NotImplementedError("Please use a concrete Problem Accumulator that "
+                              "implements error and warning handling.")
+
+
+class SimpleProblemAccumulator(ProblemAccumulatorInterface):
+  """This is a basic problem accumulator that just prints to console."""
+  def _Report(self, e):
+    context = e.FormatContext()
+    if context:
+      print context
+    print util.EncodeUnicode(self._LineWrap(e.FormatProblem(), 78))
+
+  @staticmethod
+  def _LineWrap(text, width):
+    """
+    A word-wrap function that preserves existing line breaks
+    and most spaces in the text. Expects that existing line
+    breaks are posix newlines (\n).
+
+    Taken from:
+    http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
+    """
+    return reduce(lambda line, word, width=width: '%s%s%s' %
+                  (line,
+                   ' \n'[(len(line) - line.rfind('\n') - 1 +
+                         len(word.split('\n', 1)[0]) >= width)],
+                   word),
+                  text.split(' ')
+                 )
+
+
+class ExceptionWithContext(Exception):
+  def __init__(self, context=None, context2=None, **kwargs):
+    """Initialize an exception object, saving all keyword arguments in self.
+    context and context2, if present, must be a tuple of (file_name, row_num,
+    row, headers). context2 comes from ProblemReporter.SetFileContext. context
+    was passed in with the keyword arguments. context2 is ignored if context
+    is present."""
+    Exception.__init__(self)
+
+    if context:
+      self.__dict__.update(self.ContextTupleToDict(context))
+    elif context2:
+      self.__dict__.update(self.ContextTupleToDict(context2))
+    self.__dict__.update(kwargs)
+
+    if ('type' in kwargs) and (kwargs['type'] == TYPE_WARNING):
+      self._type = TYPE_WARNING
+    else:
+      self._type = TYPE_ERROR
+
+  def GetType(self):
+    return self._type
+
+  def IsError(self):
+    return self._type == TYPE_ERROR
+
+  def IsWarning(self):
+    return self._type == TYPE_WARNING
+
+  CONTEXT_PARTS = ['file_name', 'row_num', 'row', 'headers']
+  @staticmethod
+  def ContextTupleToDict(context):
+    """Convert a tuple representing a context into a dict of (key, value) pairs"""
+    d = {}
+    if not context:
+      return d
+    for k, v in zip(ExceptionWithContext.CONTEXT_PARTS, context):
+      if v != '' and v != None:  # Don't ignore int(0), a valid row_num
+        d[k] = v
+    return d
+
+  def __str__(self):
+    return self.FormatProblem()
+
+  def GetDictToFormat(self):
+    """Return a copy of self as a dict, suitable for passing to FormatProblem"""
+    d = {}
+    for k, v in self.__dict__.items():
+      # TODO: Better handling of unicode/utf-8 within Schedule objects.
+      # Concatinating a unicode and utf-8 str object causes an exception such
+      # as "UnicodeDecodeError: 'ascii' codec can't decode byte ..." as python
+      # tries to convert the str to a unicode. To avoid that happening within
+      # the problem reporter convert all unicode attributes to utf-8.
+      # Currently valid utf-8 fields are converted to unicode in _ReadCsvDict.
+      # Perhaps all fields should be left as utf-8.
+      d[k] = util.EncodeUnicode(v)
+    return d
+
+  def FormatProblem(self, d=None):
+    """Return a text string describing the problem.
+
+    Args:
+      d: map returned by GetDictToFormat with  with formatting added
+    """
+    if not d:
+      d = self.GetDictToFormat()
+
+    output_error_text = self.__class__.ERROR_TEXT % d
+    if ('reason' in d) and d['reason']:
+      return '%s\n%s' % (output_error_text, d['reason'])
+    else:
+      return output_error_text
+
+  def FormatContext(self):
+    """Return a text string describing the context"""
+    text = ''
+    if hasattr(self, 'feed_name'):
+      text += "In feed '%s': " % self.feed_name
+    if hasattr(self, 'file_name'):
+      text += self.file_name
+    if hasattr(self, 'row_num'):
+      text += ":%i" % self.row_num
+    if hasattr(self, 'column_name'):
+      text += " column %s" % self.column_name
+    return text
+
+  def __cmp__(self, y):
+    """Return an int <0/0/>0 when self is more/same/less significant than y.
+
+    Subclasses should define this if exceptions should be listed in something
+    other than the order they are reported.
+
+    Args:
+      y: object to compare to self
+
+    Returns:
+      An int which is negative if self is more significant than y, 0 if they
+      are similar significance and positive if self is less significant than
+      y. Returning a float won't work.
+
+    Raises:
+      TypeError by default, meaning objects of the type can not be compared.
+    """
+    raise TypeError("__cmp__ not defined")
+
+
+class MissingFile(ExceptionWithContext):
+  ERROR_TEXT = "File %(file_name)s is not found"
+
+class EmptyFile(ExceptionWithContext):
+  ERROR_TEXT = "File %(file_name)s is empty"
+
+class UnknownFile(ExceptionWithContext):
+  ERROR_TEXT = 'The file named %(file_name)s was not expected.\n' \
+               'This may be a misspelled file name or the file may be ' \
+               'included in a subdirectory. Please check spellings and ' \
+               'make sure that there are no subdirectories within the feed'
+
+class FeedNotFound(ExceptionWithContext):
+  ERROR_TEXT = 'Couldn\'t find a feed named %(feed_name)s'
+
+class UnknownFormat(ExceptionWithContext):
+  ERROR_TEXT = 'The feed named %(feed_name)s had an unknown format:\n' \
+               'feeds should be either .zip files or directories.'
+
+class FileFormat(ExceptionWithContext):
+  ERROR_TEXT = 'Files must be encoded in utf-8 and may not contain ' \
+               'any null bytes (0x00). %(file_name)s %(problem)s.'
+
+class MissingColumn(ExceptionWithContext):
+  ERROR_TEXT = 'Missing column %(column_name)s in file %(file_name)s'
+
+class UnrecognizedColumn(ExceptionWithContext):
+  ERROR_TEXT = 'Unrecognized column %(column_name)s in file %(file_name)s. ' \
+               'This might be a misspelled column name (capitalization ' \
+               'matters!). Or it could be extra information (such as a ' \
+               'proposed feed extension) that the validator doesn\'t know ' \
+               'about yet. Extra information is fine; this warning is here ' \
+               'to catch misspelled optional column names.'
+
+class CsvSyntax(ExceptionWithContext):
+  ERROR_TEXT = '%(description)s'
+
+class DuplicateColumn(ExceptionWithContext):
+  ERROR_TEXT = 'Column %(header)s appears %(count)i times in file %(file_name)s'
+
+class MissingValue(ExceptionWithContext):
+  ERROR_TEXT = 'Missing value for column %(column_name)s'
+
+class InvalidValue(ExceptionWithContext):
+  ERROR_TEXT = 'Invalid value %(value)s in field %(column_name)s'
+
+class InvalidFloatValue(ExceptionWithContext):
+  ERROR_TEXT = (
+      "Invalid numeric value %(value)s. "
+      "Please ensure that the number includes an explicit whole "
+      "number portion (ie. use 0.5 instead of .5), that you do not use the "
+      "exponential notation (ie. use 0.001 instead of 1E-3), and "
+      "that it is a properly formated decimal value.")
+
+class InvalidNonNegativeIntegerValue(ExceptionWithContext):
+  ERROR_TEXT = (
+      "Invalid numeric value %(value)s. "
+      "Please ensure that the number does not have a leading zero (ie. use "
+      "3 instead of 03), and that it is a properly formated integer value.")
+
+class DuplicateID(ExceptionWithContext):
+  ERROR_TEXT = 'Duplicate ID %(value)s in column %(column_name)s'
+
+class UnusedStop(ExceptionWithContext):
+  ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) isn't used in any trips"
+
+class UsedStation(ExceptionWithContext):
+  ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) has location_type=1 " \
+               "(station) so it should not appear in stop_times"
+
+class StopTooFarFromParentStation(ExceptionWithContext):
+  ERROR_TEXT = (
+      "%(stop_name)s (ID %(stop_id)s) is too far from its parent station "
+      "%(parent_stop_name)s (ID %(parent_stop_id)s) : %(distance).2f meters.")
+  def __cmp__(self, y):
+    # Sort in decreasing order because more distance is more significant.
+    return cmp(y.distance, self.distance)
+
+
+class StopsTooClose(ExceptionWithContext):
+  ERROR_TEXT = (
+      "The stops \"%(stop_name_a)s\" (ID %(stop_id_a)s) and \"%(stop_name_b)s\""
+      " (ID %(stop_id_b)s) are %(distance)0.2fm apart and probably represent "
+      "the same location.")
+  def __cmp__(self, y):
+    # Sort in increasing order because less distance is more significant.
+    return cmp(self.distance, y.distance)
+
+class StationsTooClose(ExceptionWithContext):
+  ERROR_TEXT = (
+      "The stations \"%(stop_name_a)s\" (ID %(stop_id_a)s) and "
+      "\"%(stop_name_b)s\" (ID %(stop_id_b)s) are %(distance)0.2fm apart and "
+      "probably represent the same location.")
+  def __cmp__(self, y):
+    # Sort in increasing order because less distance is more significant.
+    return cmp(self.distance, y.distance)
+
+class DifferentStationTooClose(ExceptionWithContext):
+  ERROR_TEXT = (
+      "The parent_station of stop \"%(stop_name)s\" (ID %(stop_id)s) is not "
+      "station \"%(station_stop_name)s\" (ID %(station_stop_id)s) but they are "
+      "only %(distance)0.2fm apart.")
+  def __cmp__(self, y):
+    # Sort in increasing order because less distance is more significant.
+    return cmp(self.distance, y.distance)
+
+class StopTooFarFromShapeWithDistTraveled(ExceptionWithContext):
+  ERROR_TEXT = (
+      "For trip %(trip_id)s the stop \"%(stop_name)s\" (ID %(stop_id)s) is "
+      "%(distance).0f meters away from the corresponding point "
+      "(shape_dist_traveled: %(shape_dist_traveled)f) on shape %(shape_id)s. "
+      "It should be closer than %(max_distance).0f meters.")
+  def __cmp__(self, y):
+    # Sort in decreasing order because more distance is more significant.
+    return cmp(y.distance, self.distance)
+
+
+class TooManyDaysWithoutService(ExceptionWithContext):
+  ERROR_TEXT = "There are %(consecutive_days_without_service)i consecutive"\
+               " days, from %(first_day_without_service)s to" \
+               " %(last_day_without_service)s, without any scheduled service." \
+               " Please ensure this is intentional."
+
+class MinimumTransferTimeSetWithInvalidTransferType(ExceptionWithContext):
+  ERROR_TEXT = "The field min_transfer_time should only be set when " \
+               "transfer_type is set to 2, but it is set to %(transfer_type)s."
+
+
+class ExpirationDate(ExceptionWithContext):
+  def FormatProblem(self, d=None):
+    if not d:
+      d = self.GetDictToFormat()
+    expiration = d['expiration']
+    formatted_date = time.strftime("%B %d, %Y",
+                                   time.localtime(expiration))
+    if (expiration < time.mktime(time.localtime())):
+      return "This feed expired on %s" % formatted_date
+    else:
+      return "This feed will soon expire, on %s" % formatted_date
+
+class FutureService(ExceptionWithContext):
+  def FormatProblem(self, d=None):
+    if not d:
+      d = self.GetDictToFormat()
+    formatted_date = time.strftime("%B %d, %Y", time.localtime(d['start_date']))
+    return ("The earliest service date in this feed is in the future, on %s. "
+            "Published feeds must always include the current date." %
+            formatted_date)
+
+class NoServiceExceptions(ExceptionWithContext):
+  ERROR_TEXT = "All services are defined on a weekly basis from %(start)s " \
+               "to %(end)s with no single day variations. If there are " \
+               "exceptions such as holiday service dates please ensure they " \
+               "are listed in calendar_dates.txt"
+
+class InvalidLineEnd(ExceptionWithContext):
+  ERROR_TEXT = "Each line must end with CR LF or LF except for the last line " \
+               "of the file. This line ends with \"%(bad_line_end)s\"."
+
+class StopWithMultipleRouteTypes(ExceptionWithContext):
+  ERROR_TEXT = "Stop %(stop_name)s (ID=%(stop_id)s) belongs to both " \
+               "subway (ID=%(route_id1)s) and bus line (ID=%(route_id2)s)."
+
+class TooFastTravel(ExceptionWithContext):
+  def FormatProblem(self, d=None):
+    if not d:
+      d = self.GetDictToFormat()
+    if not d['speed']:
+      return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \
+                " to %(next_stop)s. %(dist).0f meters in %(time)d seconds." % d
+    else:
+      return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \
+             " to %(next_stop)s. %(dist).0f meters in %(time)d seconds." \
+             " (%(speed).0f km/h)." % d
+  def __cmp__(self, y):
+    # Sort in decreasing order because more distance is more significant. We
+    # can't sort by speed because not all TooFastTravel objects have a speed.
+    return cmp(y.dist, self.dist)
+
+class DuplicateTrip(ExceptionWithContext):
+  ERROR_TEXT = "Trip %(trip_id1)s of route %(route_id1)s might be duplicated " \
+               "with trip %(trip_id2)s of route %(route_id2)s. They go " \
+               "through the same stops with same service."
+
+class OverlappingTripsInSameBlock(ExceptionWithContext):
+  ERROR_TEXT = "Trip %(trip_id1)s and trip %(trip_id2)s both are in the " \
+               "same block %(block_id)s and have overlapping arrival times."
+
+class TransferDistanceTooBig(ExceptionWithContext):
+  ERROR_TEXT = "Transfer from stop %(from_stop_id)s to stop " \
+               "%(to_stop_id)s has a distance of %(distance)s meters."
+
+class TransferWalkingSpeedTooFast(ExceptionWithContext):
+  ERROR_TEXT = "Riders transfering from stop %(from_stop_id)s to stop " \
+               "%(to_stop_id)s would need to walk %(distance)s meters in " \
+               "%(transfer_time)s seconds."
+
+class OtherProblem(ExceptionWithContext):
+  ERROR_TEXT = '%(description)s'
+
+
+class ExceptionProblemAccumulator(ProblemAccumulatorInterface):
+  """A problem accumulator that handles errors and optionally warnings by
+     raising exceptions."""
+  def __init__(self, raise_warnings=False):
+    """Initialise.
+
+    Args:
+      raise_warnings: If this is True then warnings are also raised as
+                      exceptions.
+                      If it is false, warnings are printed to the console using
+                      SimpleProblemAccumulator.
+    """
+    self.raise_warnings = raise_warnings
+    self.accumulator = SimpleProblemAccumulator()
+
+  def _Report(self, e):
+    if self.raise_warnings or e.IsError():
+      raise e
+    else:
+      self.accumulator._Report(e)
+
+
+default_accumulator = ExceptionProblemAccumulator()
+default_problem_reporter = ProblemReporter(default_accumulator)
+
+# Add a default handler to send log messages to console
+console = logging.StreamHandler()
+console.setLevel(logging.WARNING)
+log = logging.getLogger("schedule_builder")
+log.addHandler(console)
+
+
+class Error(Exception):
+  pass
+
+# Below are the exceptions related to loading and setting up Feed Validator
+# extensions
+
+class ExtensionException(Exception):
+  pass
+
+class InvalidMapping(ExtensionException):
+  def __init__(self, missing_field):
+    self.missing_field = missing_field
+
+class NonexistentMapping(ExtensionException):
+  def __init__(self, name):
+    self.name = name
+
+class DuplicateMapping(ExtensionException):
+  def __init__(self, name):
+    self.name = name
+
+class NonStandardMapping(ExtensionException):
+  def __init__(self, name):
+    self.name = name
+