From 083d98e4315574a4d556d3fc8184c71ae1234ef9 Mon Sep 17 00:00:00 2001
From: mesozoic-drones <mesozoic.drones@gmail.com>
Date: Sat, 28 Mar 2020 12:05:20 +0300
Subject: [PATCH] Update README.md

---
 .clang-format                              |   31 +
 .github/workflows/ccpp.yml                 |   31 +
 .gitignore                                 |    9 +
 .gitmodules                                |    3 +
 CMakeLists.txt                             |   15 +
 LICENSE => LICENSE.MIT                     |    0
 README.md                                  |  112 +-
 benchmarks/CMakeLists.txt                  |    0
 docs/CPP_STYLE.md                          |    6 +
 docs/logo.jpeg                             |  Bin 0 -> 46868 bytes
 doctest                                    |    1 +
 include/just_gtfs/just_gtfs.h              | 1923 ++++++++++++++++++++
 tests/CMakeLists.txt                       |   10 +
 tests/data/sample_feed/agency.txt          |    2 +
 tests/data/sample_feed/calendar.txt        |    3 +
 tests/data/sample_feed/calendar_dates.txt  |    2 +
 tests/data/sample_feed/fare_attributes.txt |    3 +
 tests/data/sample_feed/fare_rules.txt      |    5 +
 tests/data/sample_feed/frequencies.txt     |   12 +
 tests/data/sample_feed/routes.txt          |    6 +
 tests/data/sample_feed/shapes.txt          |    9 +
 tests/data/sample_feed/stop_times.txt      |   29 +
 tests/data/sample_feed/stops.txt           |   10 +
 tests/data/sample_feed/trips.txt           |   12 +
 tests/unit_tests.cpp                       |  345 ++++
 25 files changed, 2577 insertions(+), 2 deletions(-)
 create mode 100644 .clang-format
 create mode 100644 .github/workflows/ccpp.yml
 create mode 100644 .gitmodules
 create mode 100644 CMakeLists.txt
 rename LICENSE => LICENSE.MIT (100%)
 create mode 100644 benchmarks/CMakeLists.txt
 create mode 100644 docs/CPP_STYLE.md
 create mode 100644 docs/logo.jpeg
 create mode 160000 doctest
 create mode 100644 include/just_gtfs/just_gtfs.h
 create mode 100644 tests/CMakeLists.txt
 create mode 100644 tests/data/sample_feed/agency.txt
 create mode 100644 tests/data/sample_feed/calendar.txt
 create mode 100644 tests/data/sample_feed/calendar_dates.txt
 create mode 100644 tests/data/sample_feed/fare_attributes.txt
 create mode 100644 tests/data/sample_feed/fare_rules.txt
 create mode 100644 tests/data/sample_feed/frequencies.txt
 create mode 100644 tests/data/sample_feed/routes.txt
 create mode 100644 tests/data/sample_feed/shapes.txt
 create mode 100644 tests/data/sample_feed/stop_times.txt
 create mode 100644 tests/data/sample_feed/stops.txt
 create mode 100644 tests/data/sample_feed/trips.txt
 create mode 100644 tests/unit_tests.cpp

diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..a82ec7f
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,31 @@
+# Configuration file for clang-format, based on docs/CPP_STYLE.md.
+
+BasedOnStyle: Google
+IndentWidth: 2
+BreakBeforeBraces: Allman
+ColumnLimit: 100
+
+Language: Cpp
+AccessModifierOffset: -2
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: true
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+BreakConstructorInitializersBeforeComma: true
+ConstructorInitializerIndentWidth: 4
+DerivePointerAlignment: false
+IndentCaseLabels: false
+NamespaceIndentation: None
+PointerAlignment: Middle
+SortIncludes: true
+Standard: Cpp11
+
+IncludeBlocks:   Preserve
+IncludeCategories:
+  - Regex:           '^<.*\.h>'
+    Priority:        1
+  - Regex:           '^<.*'
+    Priority:        2
+  - Regex:           '.*'
+    Priority:        3
diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml
new file mode 100644
index 0000000..75af05f
--- /dev/null
+++ b/.github/workflows/ccpp.yml
@@ -0,0 +1,31 @@
+name: C/C++ CI
+
+on:
+  push:
+    branches: [ master, add-* ]
+  pull_request:
+    branches: [ master ]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: git_actions
+      run: git submodule update --init --recursive
+    - name: cmake
+      run: |
+        sudo apt update
+        sudo apt install mm-common g++-9
+        export CXX=g++-9
+        cmake .
+    - name: make
+      run: |
+       export CXX=g++-9
+       make
+    - name: run_tests
+      run: |
+        pwd
+        ctest --output-on-failure
diff --git a/.gitignore b/.gitignore
index 259148f..36b4117 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,12 @@
 *.exe
 *.out
 *.app
+
+# Other
+.DS_Store
+.idea/
+cmake-build-debug/
+CMakeFiles/
+Makefile
+*.cmake
+CMakeCache.txt
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..3906885
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "doctest"]
+	path = doctest
+	url = https://github.com/onqtam/doctest
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..b9558e6
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.6)
+
+project(just_gtfs LANGUAGES CXX VERSION 0.1)
+
+include_directories(include)
+include_directories(doctest/doctest)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED on)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror")
+
+enable_testing()
+
+add_subdirectory(tests)
+add_subdirectory(benchmarks)
diff --git a/LICENSE b/LICENSE.MIT
similarity index 100%
rename from LICENSE
rename to LICENSE.MIT
diff --git a/README.md b/README.md
index cf960f2..be2e8af 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,110 @@
-# just_gtfs
-C++17 header-only GTFS parsing library
+# just_gtfs - header-only modern C++ GTFS parsing library
+
+[![GTFS parser for C++](https://github.com/mapsme/just_gtfs/blob/add-the-most-important-readers/docs/logo.jpeg)](https://github.com/mapsme/just_gtfs)
+
+[![C++](https://img.shields.io/badge/c%2B%2B-17-informational.svg)](https://shields.io/)
+[![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](https://lbesson.mit-license.org/)
+![](https://github.com/mapsme/just_gtfs/workflows/C%2FC%2B%2B%20CI/badge.svg)
+[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/mapsme/just_gtfs/issues)
+
+ - Header-only
+ - C++17
+ - Tested on GCC and Clang
+ - STL-compatible containers
+ - Fast reading and parsing of GTFS feeds
+
+## Table of Contents
+- [Working with GTFS feeds](#working-with-gtfs-feeds)
+- [How to use just_library](#how-to-use-it)
+- [Used third-party tools](#used-third-party-tools)
+
+## Working with GTFS feeds
+The library implements reading static transit data in GTFS - [General Transit Feed Specification](https://developers.google.com/transit/gtfs/reference).
+It provides class for working with GTFS feeds: `gtfs::Feed`.
+GTFS csv files are mapped to the corresponding C++ classes. Every GTFS entity can be accessed through `gtfs::Feed`.
+
+:pushpin: Example of providing `gtfs::Feed` the feed path, reading it and working with GTFS entities such as stops and routes:
+```c++
+Feed feed("~/data/SFMTA/");
+if (feed.read_feed() == ResultCode::OK)
+{
+	Stops stops = feed.get_stops();
+	std::cout << stops.size() << std::endl;
+
+	Route route = feed.get_route("route_id_1009");
+	if (route)
+	{
+		std::cout << route->route_long_name << std::endl;
+	}
+}
+```
+
+GTFS feed can be wholly read from directory as in the example above or you can read GTFS files separately. E.g., if you need only shapes data, you can avoid parsing all other files and just work with the shapes.
+
+:pushpin: Example of reading only `shapes.txt` from the feed and working with shapes:
+```c++
+Feed feed("~/data/SFMTA/");
+if (feed.read_shapes() == ResultCode::OK)
+{
+	Shapes all_shapes = feed.get_shapes();
+	Shape shape = feed.get_shape("9367");
+}
+```
+
+
+## Methods for reading and writing GTFS entities
+Methods of the `Feed` class for working with agencies:
+
+Read agencies from the corresponding csv file.
+```c++
+Result read_agencies()
+```
+
+Get reference to `Agencies` - `std::vector` of `Agency` objects.
+```c++
+const Agencies & get_agencies()
+```
+
+Find agency by its id. This method returns `std::optional` so you should check if the result is `std::nullopt`.
+```c++
+std::optional<Agency> get_agency(const Id & agency_id)
+``` 
+
+Add agency to the feed.
+```c++
+void add_agency(const Agency & agency)
+```
+
+Add agency to the feed by filling agency object fields with parsed csv values. `row` is `std::map` with csv column titles as keys ans csv row items as values. 
+```c++
+Result add_agency(ParsedCsvRow const & row)
+```
+
+
+:pushpin: **There are similar methods for all other GTFS entities** for getting the list of entities, finding and adding them.
+For some of them additional methods are provided. 
+For example, you can find all the stop times for current stop by its id:
+```c++
+StopTimes get_stop_times_for_stop(const Id & stop_id)
+```
+
+Or you can find stop times for the particular trip:
+```c++
+StopTimes get_stop_times_for_trip(const Id & trip_id, bool sort_by_sequence = true)
+```
+
+## How to use library
+- For including the library in your own project: just_gtfs is completely contained inside a single header and therefore it is sufficient to copy include/just_gtfs/just_gtfs.h to your include pathes. The library does not have to be explicitly build.
+- For running library tests:
+Clone just_gtfs with `git clone --recursive` or run `git submodule update --init --recursive` after cloning.
+In the just_gtfs project directory build the project and run unit tests: 
+```
+cmake .
+make
+ctest --output-on-failure
+```
+The library makes use of the C++17 features and therefore you have to use appropriate compiler version.
+- For including as a submodule: use branch "for-usage-as-submodule" which consists of a single header.
+
+## Used third-party tools
+- [**doctest**](https://github.com/onqtam/doctest) for unit testing.
diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt
new file mode 100644
index 0000000..e69de29
diff --git a/docs/CPP_STYLE.md b/docs/CPP_STYLE.md
new file mode 100644
index 0000000..1b51fd3
--- /dev/null
+++ b/docs/CPP_STYLE.md
@@ -0,0 +1,6 @@
+## C++ Style Guide
+
+We use C++ code style similar to the [MAPS.ME project](https://github.com/mapsme/omim/blob/master/docs/CPP_STYLE.md) with some differences:
+- Use **CamelCase** for class names and **snake_case** for other entities like methods, variables, etc.
+- Use left-to-right order for variables/params: `const std::string & s` (reference to the const string).
+- Do not use prefixes like `m_` for member variables.
diff --git a/docs/logo.jpeg b/docs/logo.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..22ad621e3b8c2a493e0dc7e970855fedd6d41716
GIT binary patch
literal 46868
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

literal 0
HcmV?d00001

diff --git a/doctest b/doctest
new file mode 160000
index 0000000..932a2ca
--- /dev/null
+++ b/doctest
@@ -0,0 +1 @@
+Subproject commit 932a2ca50666138256dae56fbb16db3b1cae133a
diff --git a/include/just_gtfs/just_gtfs.h b/include/just_gtfs/just_gtfs.h
new file mode 100644
index 0000000..3817c5e
--- /dev/null
+++ b/include/just_gtfs/just_gtfs.h
@@ -0,0 +1,1923 @@
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <exception>
+#include <filesystem>
+#include <fstream>
+#include <functional>
+#include <istream>
+#include <map>
+#include <optional>
+#include <stdexcept>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace gtfs
+{
+// Helper classes ----------------------------------------------------------------------------------
+struct InvalidFieldFormat : public std::exception
+{
+public:
+  explicit InvalidFieldFormat(const std::string & msg) : message(prefix + msg) {}
+
+  const char * what() const noexcept
+  {
+    return message.c_str();
+  }
+
+private:
+  const std::string prefix = "Invalid GTFS field format. ";
+  std::string message;
+};
+
+enum ResultCode
+{
+  OK,
+  END_OF_FILE,
+  ERROR_INVALID_GTFS_PATH,
+  ERROR_FILE_ABSENT,
+  ERROR_REQUIRED_FIELD_ABSENT,
+  ERROR_INVALID_FIELD_FORMAT
+};
+
+using Message = std::string;
+
+struct Result
+{
+  ResultCode code = OK;
+  Message message;
+
+  bool operator==(ResultCode result_code) const { return code == result_code; }
+  bool operator!=(ResultCode result_code) const { return !(code == result_code); }
+};
+
+// Csv parser  -------------------------------------------------------------------------------------
+class CsvParser
+{
+public:
+  CsvParser() = default;
+  inline explicit CsvParser(const std::string & gtfs_directory);
+
+  inline Result read_header(const std::string & csv_filename);
+  inline Result read_row(std::map<std::string, std::string> & obj);
+
+  inline static std::vector<std::string> split_record(const std::string & record,
+                                                      bool is_header = false);
+
+private:
+  std::vector<std::string> field_sequence;
+  std::filesystem::path gtfs_path;
+  std::ifstream csv_stream;
+  static const char delimiter = ',';
+};
+
+inline CsvParser::CsvParser(const std::string & gtfs_directory) : gtfs_path(gtfs_directory) {}
+
+inline void trim_spaces(std::string & token)
+{
+  while (!token.empty() && token.back() == ' ')
+    token.pop_back();
+}
+
+inline std::vector<std::string> CsvParser::split_record(const std::string & record, bool is_header)
+{
+  std::string const delims = "\r\t";
+  size_t start_index = 0;
+  if (is_header)
+  {
+    // ignore UTF-8 BOM prefix:
+    if (record.size() > 2 && record[0] == '\xef' && record[1] == '\xbb' && record[2] == '\xbf')
+      start_index = 3;
+  }
+  std::vector<std::string> fields;
+  fields.reserve(20);
+
+  std::string token;
+  token.reserve(record.size());
+
+  size_t token_start_index = start_index;
+  bool is_inside_quotes = false;
+
+  for (size_t i = start_index; i < record.size(); ++i)
+  {
+    if (record[i] == '"')
+    {
+      is_inside_quotes = !is_inside_quotes;
+      continue;
+    }
+
+    if (record[i] == ' ')
+    {
+      if (token_start_index == i)
+        token_start_index = i + 1;
+      else
+        token += record[i];
+      continue;
+    }
+
+    if (record[i] == delimiter)
+    {
+      if (is_inside_quotes)
+      {
+        token += record[i];
+        continue;
+      }
+      token_start_index = i + 1;
+      trim_spaces(token);
+      fields.push_back(token);
+      token.erase();
+      continue;
+    }
+
+    if (delims.find(record[i]) == std::string::npos)
+      token += record[i];
+  }
+  trim_spaces(token);
+  fields.push_back(token);
+  return fields;
+}
+
+inline Result CsvParser::read_header(const std::string & csv_filename)
+{
+  if (csv_stream.is_open())
+    csv_stream.close();
+
+  csv_stream.open(gtfs_path / csv_filename);
+  if (!csv_stream.is_open())
+    return {ResultCode::ERROR_FILE_ABSENT, {}};
+
+  std::string header;
+  if (!getline(csv_stream, header) || header.empty())
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, {}};
+
+  field_sequence = split_record(header, true);
+  return {ResultCode::OK, {}};
+}
+
+inline Result CsvParser::read_row(std::map<std::string, std::string> & obj)
+{
+  std::string row;
+  if (!getline(csv_stream, row))
+  {
+    obj = {};
+    return {ResultCode::END_OF_FILE, {}};
+  }
+
+  if (row == "\r")
+  {
+    obj = {};
+    return {ResultCode::OK, {}};
+  }
+
+  std::vector<std::string> fields_values = split_record(row);
+
+  // Different count of fields in row and in the header of csv.
+  // Typical approach to skip not required fields.
+  if (fields_values.size() != field_sequence.size())
+    obj = {};
+
+  for (size_t i = 0; i < field_sequence.size(); ++i)
+    obj[field_sequence[i]] = fields_values[i];
+
+  return {ResultCode::OK, {}};
+}
+
+// Custom types for GTFS fields --------------------------------------------------------------------
+// Id of GTFS entity, a sequence of any UTF-8 characters. Used as type for ID GTFS fields.
+using Id = std::string;
+// A string of UTF-8 characters. Used as type for Text GTFS fields.
+using Text = std::string;
+
+// Time in GTFS is in the HH:MM:SS format (H:MM:SS is also accepted)
+// Time within a service day can be above 24:00:00, e.g. 28:41:30
+class Time
+{
+public:
+  inline Time() = default;
+  inline explicit Time(const std::string & raw_time_str);
+  inline Time(uint16_t hours, uint16_t minutes, uint16_t seconds);
+  inline bool is_provided() const;
+  inline size_t get_total_seconds() const;
+  inline std::tuple<uint16_t, uint16_t, uint16_t> get_hh_mm_ss() const;
+  inline std::string get_raw_time() const;
+  inline bool limit_hours_to_24max();
+
+private:
+  inline void set_total_seconds();
+  inline void set_raw_time();
+  bool time_is_provided = false;
+  std::string raw_time;
+  size_t total_seconds = 0;
+  uint16_t hh = 0;
+  uint16_t mm = 0;
+  uint16_t ss = 0;
+};
+
+inline bool operator==(const Time & lhs, const Time & rhs)
+{
+  return lhs.get_hh_mm_ss() == rhs.get_hh_mm_ss() && lhs.is_provided() == rhs.is_provided();
+}
+
+inline bool Time::limit_hours_to_24max()
+{
+  if (hh < 24)
+    return false;
+
+  hh = hh % 24;
+  set_total_seconds();
+  set_raw_time();
+  return true;
+}
+
+inline void Time::set_total_seconds()
+{
+  total_seconds = hh * 60 * 60 + mm * 60 + ss;
+}
+
+inline std::string append_leading_zero(const std::string & s, bool check = true)
+{
+  if (check && s.size() > 2)
+    throw InvalidFieldFormat("The string for appending zero is too long: " + s);
+
+  if (s.size() == 2)
+    return s;
+  return "0" + s;
+}
+
+inline void Time::set_raw_time()
+{
+  const std::string hh_str = append_leading_zero(std::to_string(hh), false);
+  const std::string mm_str = append_leading_zero(std::to_string(mm));
+  const std::string ss_str = append_leading_zero(std::to_string(ss));
+
+  raw_time = hh_str + ":" + mm_str + ":" + ss_str;
+}
+
+// Time in the HH:MM:SS format (H:MM:SS is also accepted). Used as type for Time GTFS fields.
+inline Time::Time(const std::string & raw_time_str) : raw_time(raw_time_str)
+{
+  if (raw_time_str.empty())
+    return;
+
+  const size_t len = raw_time.size();
+  if (!(len == 7 || len == 8) || (raw_time[len - 3] != ':' && raw_time[len - 6] != ':'))
+    throw InvalidFieldFormat("Time is not in [H]H:MM:SS format: " + raw_time_str);
+
+  hh = static_cast<uint16_t>(std::stoi(raw_time.substr(0, len - 6)));
+  mm = static_cast<uint16_t>(std::stoi(raw_time.substr(len - 5, 2)));
+  ss = static_cast<uint16_t>(std::stoi(raw_time.substr(len - 2)));
+
+  if (mm > 60 || ss > 60)
+    throw InvalidFieldFormat("Time minutes/seconds wrong value: " + std::to_string(mm) +
+                             " minutes, " + std::to_string(ss) + " seconds");
+
+  set_total_seconds();
+  time_is_provided = true;
+}
+
+inline Time::Time(uint16_t hours, uint16_t minutes, uint16_t seconds)
+    : hh(hours), mm(minutes), ss(seconds)
+{
+  if (mm > 60 || ss > 60)
+    throw InvalidFieldFormat("Time is out of range: " + std::to_string(mm) + "minutes " +
+                             std::to_string(ss) + "seconds");
+
+  set_total_seconds();
+  set_raw_time();
+  time_is_provided = true;
+}
+
+inline bool Time::is_provided() const { return time_is_provided; }
+
+inline size_t Time::get_total_seconds() const { return total_seconds; }
+
+inline std::tuple<uint16_t, uint16_t, uint16_t> Time::get_hh_mm_ss() const { return {hh, mm, ss}; }
+
+inline std::string Time::get_raw_time() const { return raw_time; }
+
+// Service day in the YYYYMMDD format.
+class Date
+{
+public:
+  inline Date() = default;
+  inline Date(uint16_t year, uint16_t month, uint16_t day);
+  inline explicit Date(const std::string & raw_date_str);
+  inline bool is_provided() const;
+  inline std::tuple<uint16_t, uint16_t, uint16_t> get_yyyy_mm_dd() const;
+  inline std::string get_raw_date() const;
+
+private:
+  inline void check_valid() const;
+
+  std::string raw_date;
+  uint16_t yyyy = 0;
+  uint16_t mm = 0;
+  uint16_t dd = 0;
+  bool date_is_provided = false;
+};
+
+inline bool operator==(const Date & lhs, const Date & rhs)
+{
+  return lhs.get_yyyy_mm_dd() == rhs.get_yyyy_mm_dd() && lhs.is_provided() == rhs.is_provided();
+}
+
+inline void Date::check_valid() const
+{
+  if (yyyy < 1000 || yyyy > 9999 || mm < 1 || mm > 12 || dd < 1 || dd > 31)
+    throw InvalidFieldFormat("Date check failed: out of range. " + std::to_string(yyyy) +
+                             " year, " + std::to_string(mm) + " month, " + std::to_string(dd) +
+                             " day");
+
+  if (mm == 2 && dd > 28)
+  {
+    // The year is not leap. Days count should be 28.
+    if (yyyy % 4 != 0 || (yyyy % 100 == 0 && yyyy % 400 != 0))
+      throw InvalidFieldFormat("Invalid days count in February of non-leap year: " +
+                               std::to_string(dd) + " year" + std::to_string(yyyy));
+
+    // The year is leap. Days count should be 29.
+    if (dd > 29)
+      throw InvalidFieldFormat("Invalid days count in February of leap year: " +
+                               std::to_string(dd) + " year" + std::to_string(yyyy));
+  }
+
+  if (dd > 30 && (mm == 4 || mm == 6 || mm == 9 || mm == 11))
+    throw InvalidFieldFormat("Invalid days count in month: " + std::to_string(dd) + " days in " +
+                             std::to_string(mm));
+}
+
+inline Date::Date(uint16_t year, uint16_t month, uint16_t day) : yyyy(year), mm(month), dd(day)
+{
+  check_valid();
+  const std::string mm_str = append_leading_zero(std::to_string(mm));
+  const std::string dd_str = append_leading_zero(std::to_string(dd));
+
+  raw_date = std::to_string(yyyy) + mm_str + dd_str;
+  date_is_provided = true;
+}
+
+inline Date::Date(const std::string & raw_date_str) : raw_date(raw_date_str)
+{
+  if (raw_date.empty())
+    return;
+
+  if (raw_date.size() != 8)
+    throw InvalidFieldFormat("Date is not in YYYY:MM::DD format: " + raw_date_str);
+
+  yyyy = static_cast<uint16_t>(std::stoi(raw_date.substr(0, 4)));
+  mm = static_cast<uint16_t>(std::stoi(raw_date.substr(4, 2)));
+  dd = static_cast<uint16_t>(std::stoi(raw_date.substr(6, 2)));
+
+  check_valid();
+
+  date_is_provided = true;
+}
+
+inline bool Date::is_provided() const { return date_is_provided; }
+
+inline std::tuple<uint16_t, uint16_t, uint16_t> Date::get_yyyy_mm_dd() const
+{
+  return {yyyy, mm, dd};
+}
+
+inline std::string Date::get_raw_date() const { return raw_date; }
+
+// An ISO 4217 alphabetical currency code. Used as type for Currency Code GTFS fields.
+using CurrencyCode = std::string;
+// An IETF BCP 47 language code. Used as type for Language Code GTFS fields.
+using LanguageCode = std::string;
+
+// Helper enums for some GTFS fields ---------------------------------------------------------------
+enum class StopLocationType
+{
+  StopOrPlatform = 0,
+  Station = 1,
+  EntranceExit = 2,
+  GenericNode = 3,
+  BoardingArea = 4
+};
+
+// The type of transportation used on a route.
+enum class RouteType
+{
+  // GTFS route types
+  Tram = 0,         // Tram, Streetcar, Light rail
+  Subway = 1,       // Any underground rail system within a metropolitan area
+  Rail = 2,         // Intercity or long-distance travel
+  Bus = 3,          // Short- and long-distance bus routes
+  Ferry = 4,        // Boat service
+  CableTram = 5,    // Street-level rail cars where the cable runs beneath the vehicle
+  AerialLift = 6,   // Aerial lift, suspended cable car (gondola lift, aerial tramway)
+  Funicular = 7,    // Any rail system designed for steep inclines
+  Trolleybus = 11,  // Electric buses that draw power from overhead wires using poles
+  Monorail = 12,    // Railway in which the track consists of a single rail or a beam
+
+  // Extended route types
+  // https://developers.google.com/transit/gtfs/reference/extended-route-types
+  RailwayService = 100,
+  HighSpeedRailService = 101,
+  LongDistanceTrains = 102,
+  InterRegionalRailService = 103,
+  CarTransportRailService = 104,
+  SleeperRailService = 105,
+  RegionalRailService = 106,
+  TouristRailwayService = 107,
+  RailShuttleWithinComplex = 108,
+  SuburbanRailway = 109,
+  ReplacementRailService = 110,
+  SpecialRailService = 111,
+  LorryTransportRailService = 112,
+  AllRailServices = 113,
+  CrossCountryRailService = 114,
+  VehicleTransportRailService = 115,
+  RackAndPinionRailway = 116,
+  AdditionalRailService = 117,
+
+  CoachService = 200,
+  InternationalCoachService = 201,
+  NationalCoachService = 202,
+  ShuttleCoachService = 203,
+  RegionalCoachService = 204,
+  SpecialCoachService = 205,
+  SightseeingCoachService = 206,
+  TouristCoachService = 207,
+  CommuterCoachService = 208,
+  AllCoachServices = 209,
+
+  UrbanRailwayService400 = 400,
+  MetroService = 401,
+  UndergroundService = 402,
+  UrbanRailwayService403 = 403,
+  AllUrbanRailwayServices = 404,
+  Monorail405 = 405,
+
+  BusService = 700,
+  RegionalBusService = 701,
+  ExpressBusService = 702,
+  StoppingBusService = 703,
+  LocalBusService = 704,
+  NightBusService = 705,
+  PostBusService = 706,
+  SpecialNeedsBus = 707,
+  MobilityBusService = 708,
+  MobilityBusForRegisteredDisabled = 709,
+  SightseeingBus = 710,
+  ShuttleBus = 711,
+  SchoolBus = 712,
+  SchoolAndPublicServiceBus = 713,
+  RailReplacementBusService = 714,
+  DemandAndResponseBusService = 715,
+  AllBusServices = 716,
+
+  TrolleybusService = 800,
+
+  TramService = 900,
+  CityTramService = 901,
+  LocalTramService = 902,
+  RegionalTramService = 903,
+  SightseeingTramService = 904,
+  ShuttleTramService = 905,
+  AllTramServices = 906,
+
+  WaterTransportService = 1000,
+  AirService = 1100,
+  FerryService = 1200,
+  AerialLiftService = 1300,
+  FunicularService = 1400,
+  TaxiService = 1500,
+  CommunalTaxiService = 1501,
+  WaterTaxiService = 1502,
+  RailTaxiService = 1503,
+  BikeTaxiService = 1504,
+  LicensedTaxiService = 1505,
+  PrivateHireServiceVehicle = 1506,
+  AllTaxiServices = 1507,
+  MiscellaneousService = 1700,
+  HorseDrawnCarriage = 1702
+};
+
+enum class TripDirectionId
+{
+  DefaultDirection = 0,  // e.g. outbound
+  OppositeDirection = 1  // e.g. inbound
+};
+
+enum class TripAccess
+{
+  NoInfo = 0,
+  Yes = 1,
+  No = 2
+};
+
+enum class StopTimeBoarding
+{
+  RegularlyScheduled = 0,
+  No = 1,                   // Not available
+  Phone = 2,                // Must phone agency to arrange
+  CoordinateWithDriver = 3  // Must coordinate with driver to arrange
+};
+
+enum class StopTimePoint
+{
+  Approximate = 0,
+  Exact = 1
+};
+
+enum class CalendarAvailability
+{
+  NotAvailable = 0,
+  Available = 1
+};
+
+enum class CalendarDateException
+{
+  Added = 1,  // Service has been added for the specified date
+  Removed = 2
+};
+
+enum class FarePayment
+{
+  OnBoard = 0,
+  BeforeBoarding = 1  // Fare must be paid before boarding
+};
+
+enum class FareTransfers
+{
+  No = 0,  // No transfers permitted on this fare
+  Once = 1,
+  Twice = 2,
+  Unlimited = 3
+};
+
+enum class FrequencyTripService
+{
+  FrequencyBased = 0,  // Frequency-based trips
+  ScheduleBased = 1    // Schedule-based trips with the exact same headway throughout the day
+};
+
+enum class TransferType
+{
+  Recommended = 0,
+  Timed = 1,
+  MinimumTime = 2,
+  NotPossible = 3
+};
+
+enum class PathwayMode
+{
+  Walkway = 1,
+  Stairs = 2,
+  MovingSidewalk = 3,  // Moving sidewalk/travelator
+  Escalator = 4,
+  Elevator = 5,
+  FareGate = 6,  // Payment gate
+  ExitGate = 7
+};
+
+enum class PathwayDirection
+{
+  Unidirectional = 0,
+  Bidirectional = 1
+};
+
+enum class TranslationTable
+{
+  Agency = 0,
+  Stops,
+  Routes,
+  Trips,
+  StopTimes,
+  FeedInfo
+};
+
+enum class AttributionRole
+{
+  No = 0,  // Organization doesn’t have this role
+  Yes = 1  // Organization does have this role
+};
+
+// Structures representing GTFS entities -----------------------------------------------------------
+// Required dataset file
+struct Agency
+{
+  // Conditionally optional:
+  Id agency_id;
+
+  // Required:
+  Text agency_name;
+  Text agency_url;
+  Text agency_timezone;
+
+  // Optional:
+  Text agency_lang;
+  Text agency_phone;
+  Text agency_fare_url;
+  Text agency_email;
+};
+
+// Required dataset file
+struct Stop
+{
+  // Required:
+  Id stop_id;
+
+  // Conditionally required:
+  Text stop_name;
+
+  bool coordinates_present = true;
+  double stop_lat = 0.0;
+  double stop_lon = 0.0;
+  Id zone_id;
+  Id parent_station;
+
+  // Optional:
+  Text stop_code;
+  Text stop_desc;
+  Text stop_url;
+  StopLocationType location_type = StopLocationType::GenericNode;
+  Text stop_timezone;
+  Text wheelchair_boarding;
+  Id level_id;
+  Text platform_code;
+};
+
+// Required dataset file
+struct Route
+{
+  // Required:
+  Id route_id;
+  RouteType route_type = RouteType::Tram;
+
+  // Conditionally required:
+  Id agency_id;
+  Text route_short_name;
+  Text route_long_name;
+
+  // Optional
+  Text route_desc;
+  Text route_url;
+  Text route_color;
+  Text route_text_color;
+  size_t route_sort_order = 0;  // Routes with smaller value values should be displayed first
+};
+
+// Required dataset file
+struct Trip
+{
+  // Required:
+  Id route_id;
+  Id service_id;
+  Id trip_id;
+
+  // Optional:
+  Text trip_headsign;
+  Text trip_short_name;
+  TripDirectionId direction_id = TripDirectionId::DefaultDirection;
+  Id block_id;
+  Id shape_id;
+  TripAccess wheelchair_accessible = TripAccess::NoInfo;
+  TripAccess bikes_allowed = TripAccess::NoInfo;
+};
+
+// Required dataset file
+struct StopTime
+{
+  // Required:
+  Id trip_id;
+  Id stop_id;
+  size_t stop_sequence = 0;
+
+  // Conditionally required:
+  Time arrival_time;
+
+  Time departure_time;
+
+  // Optional:
+  Text stop_headsign;
+  StopTimeBoarding pickup_type = StopTimeBoarding::RegularlyScheduled;
+  StopTimeBoarding drop_off_type = StopTimeBoarding::RegularlyScheduled;
+
+  double shape_dist_traveled = 0.0;
+  StopTimePoint timepoint = StopTimePoint::Exact;
+};
+
+// Conditionally required dataset file:
+struct CalendarItem
+{
+  // Required:
+  Id service_id;
+
+  CalendarAvailability monday = CalendarAvailability::NotAvailable;
+  CalendarAvailability tuesday = CalendarAvailability::NotAvailable;
+  CalendarAvailability wednesday = CalendarAvailability::NotAvailable;
+  CalendarAvailability thursday = CalendarAvailability::NotAvailable;
+  CalendarAvailability friday = CalendarAvailability::NotAvailable;
+  CalendarAvailability saturday = CalendarAvailability::NotAvailable;
+  CalendarAvailability sunday = CalendarAvailability::NotAvailable;
+
+  Date start_date;
+  Date end_date;
+};
+
+// Conditionally required dataset file
+struct CalendarDate
+{
+  // Required:
+  Id service_id;
+  Date date;
+  CalendarDateException exception_type = CalendarDateException::Added;
+};
+
+// Optional dataset file
+struct FareAttribute
+{
+  // Required:
+  Id fare_id;
+  double price = 0.0;
+  CurrencyCode currency_code;
+  FarePayment payment_method = FarePayment::BeforeBoarding;
+  FareTransfers transfers = FareTransfers::Unlimited;
+
+  // Conditionally required:
+  Id agency_id;
+
+  // Optional:
+  size_t transfer_duration = 0;  // Length of time in seconds before a transfer expires
+};
+
+// Optional dataset file
+struct FareRule
+{
+  // Required:
+  Id fare_id;
+
+  // Optional:
+  Id route_id;
+  Id origin_id;
+  Id destination_id;
+  Id contains_id;
+};
+
+// Optional dataset file
+struct ShapePoint
+{
+  // Required:
+  Id shape_id;
+  double shape_pt_lat = 0.0;
+  double shape_pt_lon = 0.0;
+  size_t shape_pt_sequence = 0;
+
+  // Optional:
+  double shape_dist_traveled = 0;
+};
+
+// Optional dataset file
+struct Frequency
+{
+  // Required:
+  Id trip_id;
+  Time start_time;
+  Time end_time;
+  size_t headway_secs = 0;
+
+  // Optional:
+  FrequencyTripService exact_times = FrequencyTripService::FrequencyBased;
+};
+
+// Optional dataset file
+struct Transfer
+{
+  // Required:
+  Id from_stop_id;
+  Id to_stop_id;
+  TransferType transfer_type = TransferType::Recommended;
+
+  // Optional:
+  size_t min_transfer_time = 0;
+};
+
+// Optional dataset file for the GTFS-Pathways extension
+struct Pathway
+{
+  // Required:
+  Id pathway_d;
+  Id from_stop_id;
+  Id to_stop_id;
+  PathwayMode pathway_mode = PathwayMode::Walkway;
+  PathwayDirection is_bidirectional = PathwayDirection::Unidirectional;
+
+  // Optional fields:
+  // Horizontal length in meters of the pathway from the origin location
+  double length = 0.0;
+  // Average time in seconds needed to walk through the pathway from the origin location
+  size_t traversal_time = 0;
+  // Number of stairs of the pathway
+  size_t stair_count = 0;
+  // Maximum slope ratio of the pathway
+  double max_slope = 0.0;
+  // Minimum width of the pathway in meters
+  double min_width = 0.0;
+  // Text from physical signage visible to transit riders
+  Text signposted_as;
+  // Same as signposted_as, but when the pathways is used backward
+  Text reversed_signposted_as;
+};
+
+// Optional dataset file
+struct Level
+{
+  // Required:
+  Id level_id;
+
+  // Numeric index of the level that indicates relative position of this level in relation to other
+  // levels (levels with higher indices are assumed to be located above levels with lower indices).
+  // Ground level should have index 0, with levels above ground indicated by positive indices and
+  // levels below ground by negative indices
+  double level_index = 0.0;
+
+  // Optional:
+  Text level_name;
+};
+
+// Optional dataset file
+struct FeedInfo
+{
+  // Required:
+  Text feed_publisher_name;
+  Text feed_publisher_url;
+  LanguageCode feed_lang;
+
+  // Optional:
+  Date feed_start_date;
+  Date feed_end_date;
+  Text feed_version;
+  Text feed_contact_email;
+  Text feed_contact_url;
+};
+
+// Optional dataset file
+struct Translation
+{
+  // Required:
+  TranslationTable table_name = TranslationTable::Agency;
+  Text field_name;
+  LanguageCode language;
+  Text translation;
+
+  // Conditionally required:
+  Id record_id;
+  Id record_sub_id;
+  Text field_value;
+};
+
+// Optional dataset file
+struct Attribution
+{
+  // Required:
+  Text organization_name;
+
+  // Optional:
+  Id attribution_id;  // Useful for translations
+  Id agency_id;
+  Id route_id;
+  Id trip_id;
+
+  AttributionRole is_producer = AttributionRole::No;
+  AttributionRole is_operator = AttributionRole::No;
+  AttributionRole is_authority = AttributionRole::No;
+
+  Text attribution_url;
+  Text attribution_email;
+  Text attribution_phone;
+};
+
+// Main classes for working with GTFS feeds
+using Agencies = std::vector<Agency>;
+using Stops = std::vector<Stop>;
+using Routes = std::vector<Route>;
+using Trips = std::vector<Trip>;
+using StopTimes = std::vector<StopTime>;
+using Calendar = std::vector<CalendarItem>;
+using CalendarDates = std::vector<CalendarDate>;
+
+using FareRules = std::vector<FareRule>;
+using Shapes = std::vector<ShapePoint>;
+using Shape = std::vector<ShapePoint>;
+using Frequencies = std::vector<Frequency>;
+using Transfers = std::vector<Transfer>;
+using Pathways = std::vector<Pathway>;
+using Levels = std::vector<Level>;
+// FeedInfo is a unique object and doesn't need a container.
+using Translations = std::vector<Translation>;
+using Attributions = std::vector<Attribution>;
+
+using ParsedCsvRow = std::map<std::string, std::string>;
+
+class Feed
+{
+public:
+  inline Feed() = default;
+  inline explicit Feed(const std::string & gtfs_path);
+
+  inline Result read_feed();
+
+  inline Result write_feed(const std::string & gtfs_path = {}) const;
+
+  inline Result read_agencies();
+  inline const Agencies & get_agencies() const;
+  inline std::optional<Agency> get_agency(const Id & agency_id) const;
+  inline void add_agency(const Agency & agency);
+
+  inline Result read_stops();
+  inline const Stops & get_stops() const;
+  inline std::optional<Stop> get_stop(const Id & stop_id) const;
+  inline void add_stop(const Stop & stop);
+
+  inline Result read_routes();
+  inline const Routes & get_routes() const;
+  inline std::optional<Route> get_route(const Id & route_id) const;
+  inline void add_route(const Route & route);
+
+  inline Result read_trips();
+  inline const Trips & get_trips() const;
+  inline std::optional<Trip> get_trip(const Id & trip_id) const;
+  inline void add_trip(const Trip & trip);
+
+  inline Result read_stop_times();
+  inline const StopTimes & get_stop_times() const;
+  inline StopTimes get_stop_times_for_stop(const Id & stop_id) const;
+  inline StopTimes get_stop_times_for_trip(const Id & trip_id, bool sort_by_sequence = true) const;
+  inline void add_stop_time(const StopTime & stop_time);
+
+  inline Result read_calendar();
+  inline const Calendar & get_calendar() const;
+  inline std::optional<CalendarItem> get_calendar(const Id & service_id) const;
+  inline void add_calendar_item(const CalendarItem & calendar_item);
+
+  inline Result read_calendar_dates();
+  inline const CalendarDates & get_calendar_dates() const;
+  inline CalendarDates get_calendar_dates(const Id & service_id, bool sort_by_date = true) const;
+  inline void add_calendar_date(const CalendarDate & calendar_date);
+
+  inline Result read_fare_rules();
+  inline const FareRules & get_fare_rules() const;
+  inline std::optional<FareRule> get_fare_rule(const Id & fare_id) const;
+  inline void add_fare_rule(const FareRule & fare_rule);
+
+  inline Result read_shapes();
+  inline const Shapes & get_shapes() const;
+  inline Shape get_shape(const Id & shape_id, bool sort_by_sequence = true) const;
+  inline void add_shape(const ShapePoint & shape);
+
+  inline Result read_frequencies();
+  inline const Frequencies & get_frequencies() const;
+  inline std::optional<Frequency> get_frequency(const Id & trip_id) const;
+  inline void add_frequency(const Frequency & frequency);
+
+  inline Result read_transfers();
+  inline const Transfers & get_transfers() const;
+  inline std::optional<Transfer> get_transfer(const Id & from_stop_id, const Id & to_stop_id) const;
+  inline void add_transfer(const Transfer & transfer);
+
+  inline Result read_pathways();
+  inline const Pathways & get_pathways() const;
+  inline std::optional<Pathway> get_pathway(const Id & pathway_id) const;
+  inline std::optional<Pathway> get_pathway(const Id & from_stop_id, const Id & to_stop_id) const;
+  inline void add_pathway(const Pathway & pathway);
+
+  inline Result read_levels();
+  inline const Levels & get_levels() const;
+  inline std::optional<Level> get_level(const Id & level_id) const;
+  inline void add_level(const Level & level);
+
+  inline Result read_feed_info();
+  inline FeedInfo get_feed_info() const;
+  inline void set_feed_info(const FeedInfo & feed_info);
+
+  inline Result read_translations();
+  inline const Translations & get_translations() const;
+  inline std::optional<Translation> get_translation(const TranslationTable & table_name) const;
+  inline void add_translation(const Translation & translation);
+
+  inline Result read_attributions();
+  inline const Attributions & get_attributions() const;
+  inline void add_attribution(const Attribution & attribution);
+
+private:
+  inline Result parse_csv(const std::string & filename,
+                          const std::function<Result(const ParsedCsvRow & record)> & add_entity);
+
+  inline Result add_agency(ParsedCsvRow const & row);
+  inline Result add_route(ParsedCsvRow const & row);
+  inline Result add_shape(ParsedCsvRow const & row);
+  inline Result add_trip(ParsedCsvRow const & row);
+  inline Result add_stop(ParsedCsvRow const & row);
+  inline Result add_stop_time(ParsedCsvRow const & row);
+  inline Result add_calendar_item(ParsedCsvRow const & row);
+  inline Result add_calendar_date(ParsedCsvRow const & row);
+  inline Result add_transfer(ParsedCsvRow const & row);
+  inline Result add_frequency(ParsedCsvRow const & row);
+
+  std::string gtfs_directory;
+
+  Agencies agencies;
+  Stops stops;
+  Routes routes;
+  Trips trips;
+  StopTimes stop_times;
+
+  Calendar calendar;
+  CalendarDates calendar_dates;
+  FareRules fare_rules;
+  Shape shapes;
+  Frequencies frequencies;
+  Transfers transfers;
+  Pathways pathways;
+  Levels levels;
+  Translations translations;
+  Attributions attributions;
+  FeedInfo feed_info;
+};
+
+inline Feed::Feed(const std::string & gtfs_path) : gtfs_directory(gtfs_path) {}
+
+inline Result Feed::read_feed()
+{
+  if (!std::filesystem::exists(gtfs_directory))
+    return {ResultCode::ERROR_INVALID_GTFS_PATH, "Invalid path " + gtfs_directory};
+
+  // Read required files
+  if (auto const res = read_agencies(); res.code != ResultCode::OK)
+    return res;
+
+  if (auto const res = read_stops(); res.code != ResultCode::OK)
+    return res;
+
+  if (auto const res = read_routes(); res.code != ResultCode::OK)
+    return res;
+
+  if (auto const res = read_trips(); res.code != ResultCode::OK)
+    return res;
+
+  if (auto const res = read_stop_times(); res.code != ResultCode::OK)
+    return res;
+
+  // Conditionally required:
+  if (auto const res = read_calendar(); res.code != ResultCode::OK)
+  {
+    if (res != ResultCode::ERROR_FILE_ABSENT)
+      return res;
+  }
+
+  if (auto const res = read_calendar_dates(); res.code != ResultCode::OK)
+  {
+    if (res != ResultCode::ERROR_FILE_ABSENT)
+      return res;
+  }
+
+  // Optional files:
+  if (auto const res = read_shapes(); res.code != ResultCode::OK)
+  {
+    if (res != ResultCode::ERROR_FILE_ABSENT)
+      return res;
+  }
+
+  if (auto const res = read_transfers(); res.code != ResultCode::OK)
+  {
+    if (res != ResultCode::ERROR_FILE_ABSENT)
+      return res;
+  }
+
+  if (auto const res = read_frequencies(); res.code != ResultCode::OK)
+  {
+    if (res != ResultCode::ERROR_FILE_ABSENT)
+      return res;
+  }
+
+  // TODO Read other conditionally optional and optional files
+
+  return {ResultCode::OK, {}};
+}
+
+inline Result Feed::write_feed(const std::string & gtfs_path) const
+{
+  if (gtfs_path.empty())
+    return {ResultCode::ERROR_INVALID_GTFS_PATH, "Empty output path for writing feed"};
+  // TODO Write feed to csv files
+  return {};
+}
+
+inline std::string get_value_or_default(ParsedCsvRow const & container, const std::string & key,
+                                        const std::string & default_value = "")
+{
+  const auto it = container.find(key);
+  if (it == container.end())
+    return default_value;
+
+  return it->second;
+}
+
+template <class T>
+inline void set_field(T & field, ParsedCsvRow const & container, const std::string & key,
+                      bool is_optional = true)
+{
+  const std::string key_str = get_value_or_default(container, key);
+  if (!key_str.empty() || !is_optional)
+    field = static_cast<T>(std::stoi(key_str));
+}
+
+inline bool set_fractional(double & field, ParsedCsvRow const & container, const std::string & key,
+                           bool is_optional = true)
+{
+  const std::string key_str = get_value_or_default(container, key);
+  if (!key_str.empty() || !is_optional)
+  {
+    field = std::stod(key_str);
+    return true;
+  }
+  return false;
+}
+
+// Throw if not valid WGS84 decimal degrees.
+inline void check_coordinates(double latitude, double longitude)
+{
+  if (latitude < -90.0 || latitude > 90.0)
+    throw std::out_of_range("Latitude");
+
+  if (longitude < -180.0 || longitude > 180.0)
+    throw std::out_of_range("Longitude");
+}
+
+inline Result Feed::add_agency(ParsedCsvRow const & row)
+{
+  Agency agency;
+
+  // Conditionally required id:
+  agency.agency_id = get_value_or_default(row, "agency_id");
+
+  // Required fields:
+  try
+  {
+    agency.agency_name = row.at("agency_name");
+    agency.agency_url = row.at("agency_url");
+    agency.agency_timezone = row.at("agency_timezone");
+  }
+  catch (const std::out_of_range & ex)
+  {
+    return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+  }
+
+  // Optional fields:
+  agency.agency_lang = get_value_or_default(row, "agency_lang");
+  agency.agency_phone = get_value_or_default(row, "agency_phone");
+  agency.agency_fare_url = get_value_or_default(row, "agency_fare_url");
+  agency.agency_email = get_value_or_default(row, "agency_email");
+
+  agencies.push_back(agency);
+  return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_route(ParsedCsvRow const & row)
+{
+  Route route;
+
+  try
+  {
+    // Required fields:
+    route.route_id = row.at("route_id");
+    set_field(route.route_type, row, "route_type", false);
+
+    // Optional:
+    set_field(route.route_sort_order, row, "route_sort_order");
+  }
+  catch (const std::out_of_range & ex)
+  {
+    return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+  }
+  catch (const std::invalid_argument & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+
+  // Conditionally required:
+  route.agency_id = get_value_or_default(row, "agency_id");
+
+  route.route_short_name = get_value_or_default(row, "route_short_name");
+  route.route_long_name = get_value_or_default(row, "route_long_name");
+
+  if (route.route_short_name.empty() && route.route_long_name.empty())
+  {
+    return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT,
+            "'route_short_name' or 'route_long_name' must be specified"};
+  }
+
+  route.route_color = get_value_or_default(row, "route_color");
+  route.route_text_color = get_value_or_default(row, "route_text_color");
+  route.route_desc = get_value_or_default(row, "route_desc");
+  route.route_url = get_value_or_default(row, "route_url");
+
+  routes.push_back(route);
+
+  return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_shape(ParsedCsvRow const & row)
+{
+  ShapePoint point;
+  try
+  {
+    // Required:
+    point.shape_id = row.at("shape_id");
+    point.shape_pt_sequence = std::stoi(row.at("shape_pt_sequence"));
+
+    point.shape_pt_lon = std::stod(row.at("shape_pt_lon"));
+    point.shape_pt_lat = std::stod(row.at("shape_pt_lat"));
+    check_coordinates(point.shape_pt_lat, point.shape_pt_lon);
+
+    // Optional:
+    set_fractional(point.shape_dist_traveled, row, "shape_dist_traveled");
+    if (point.shape_dist_traveled < 0.0)
+      throw std::invalid_argument("Invalid shape_dist_traveled");
+  }
+  catch (const std::out_of_range & ex)
+  {
+    return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+  }
+  catch (const std::invalid_argument & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+
+  shapes.push_back(point);
+  return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_trip(ParsedCsvRow const & row)
+{
+  Trip trip;
+  try
+  {
+    // Required:
+    trip.route_id = row.at("route_id");
+    trip.service_id = row.at("service_id");
+    trip.trip_id = row.at("trip_id");
+
+    // Optional:
+    set_field(trip.direction_id, row, "direction_id");
+    set_field(trip.wheelchair_accessible, row, "wheelchair_accessible");
+    set_field(trip.bikes_allowed, row, "bikes_allowed");
+  }
+  catch (const std::out_of_range & ex)
+  {
+    return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+  }
+  catch (const std::invalid_argument & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+
+  // Optional:
+  trip.shape_id = get_value_or_default(row, "shape_id");
+  trip.trip_headsign = get_value_or_default(row, "trip_headsign");
+  trip.trip_short_name = get_value_or_default(row, "trip_short_name");
+  trip.block_id = get_value_or_default(row, "block_id");
+
+  trips.push_back(trip);
+  return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_stop(ParsedCsvRow const & row)
+{
+  Stop stop;
+
+  try
+  {
+    stop.stop_id = row.at("stop_id");
+
+    // Optional:
+    bool const set_lon = set_fractional(stop.stop_lon, row, "stop_lon");
+    bool const set_lat = set_fractional(stop.stop_lat, row, "stop_lat");
+
+    if (!set_lon || !set_lat)
+      stop.coordinates_present = false;
+  }
+  catch (const std::out_of_range & ex)
+  {
+    return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+  }
+  catch (const std::invalid_argument & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+
+  // Conditionally required:
+  stop.stop_name = get_value_or_default(row, "stop_name");
+  stop.parent_station = get_value_or_default(row, "parent_station");
+  stop.zone_id = get_value_or_default(row, "zone_id");
+
+  // Optional:
+  stop.stop_code = get_value_or_default(row, "stop_code");
+  stop.stop_desc = get_value_or_default(row, "stop_desc");
+  stop.stop_url = get_value_or_default(row, "stop_url");
+  set_field(stop.location_type, row, "location_type");
+  stop.stop_timezone = get_value_or_default(row, "stop_timezone");
+  stop.wheelchair_boarding = get_value_or_default(row, "wheelchair_boarding");
+  stop.level_id = get_value_or_default(row, "level_id");
+  stop.platform_code = get_value_or_default(row, "platform_code");
+
+  stops.push_back(stop);
+
+  return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_stop_time(ParsedCsvRow const & row)
+{
+  StopTime stop_time;
+
+  try
+  {
+    // Required:
+    stop_time.trip_id = row.at("trip_id");
+    stop_time.stop_id = row.at("stop_id");
+    stop_time.stop_sequence = std::stoi(row.at("stop_sequence"));
+
+    // Conditionally required:
+    stop_time.departure_time = Time(row.at("departure_time"));
+    stop_time.arrival_time = Time(row.at("arrival_time"));
+
+    // Optional:
+    set_field(stop_time.pickup_type, row, "pickup_type");
+    set_field(stop_time.drop_off_type, row, "drop_off_type");
+
+    set_fractional(stop_time.shape_dist_traveled, row, "shape_dist_traveled");
+    if (stop_time.shape_dist_traveled < 0.0)
+      throw std::invalid_argument("Invalid shape_dist_traveled");
+
+    set_field(stop_time.timepoint, row, "timepoint");
+  }
+  catch (const std::out_of_range & ex)
+  {
+    return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+  }
+  catch (const std::invalid_argument & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+  catch (const InvalidFieldFormat & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+
+  // Optional:
+  stop_time.stop_headsign = get_value_or_default(row, "stop_headsign");
+
+  stop_times.push_back(stop_time);
+  return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_calendar_item(ParsedCsvRow const & row)
+{
+  CalendarItem calendar_item;
+  try
+  {
+    // Required fields:
+    calendar_item.service_id = row.at("service_id");
+
+    set_field(calendar_item.monday, row, "monday", false);
+    set_field(calendar_item.tuesday, row, "tuesday", false);
+    set_field(calendar_item.wednesday, row, "wednesday", false);
+    set_field(calendar_item.thursday, row, "thursday", false);
+    set_field(calendar_item.friday, row, "friday", false);
+    set_field(calendar_item.saturday, row, "saturday", false);
+    set_field(calendar_item.sunday, row, "sunday", false);
+
+    calendar_item.start_date = Date(row.at("start_date"));
+    calendar_item.end_date = Date(row.at("end_date"));
+  }
+  catch (const std::out_of_range & ex)
+  {
+    return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+  }
+  catch (const std::invalid_argument & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+  catch (const InvalidFieldFormat & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+
+  calendar.push_back(calendar_item);
+  return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_calendar_date(ParsedCsvRow const & row)
+{
+  CalendarDate calendar_date;
+  try
+  {
+    // Required fields:
+    calendar_date.service_id = row.at("service_id");
+
+    set_field(calendar_date.exception_type, row, "exception_type", false);
+    calendar_date.date = Date(row.at("date"));
+  }
+  catch (const std::out_of_range & ex)
+  {
+    return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+  }
+  catch (const std::invalid_argument & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+  catch (const InvalidFieldFormat & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+
+  calendar_dates.push_back(calendar_date);
+  return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_transfer(ParsedCsvRow const & row)
+{
+  Transfer transfer;
+  try
+  {
+    // Required fields:
+    transfer.from_stop_id = row.at("from_stop_id");
+    transfer.to_stop_id = row.at("to_stop_id");
+    set_field(transfer.transfer_type, row, "transfer_type", false);
+
+    // Optional:
+    set_field(transfer.min_transfer_time, row, "min_transfer_time");
+  }
+  catch (const std::out_of_range & ex)
+  {
+    return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+  }
+  catch (const std::invalid_argument & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+  catch (const InvalidFieldFormat & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+
+  transfers.push_back(transfer);
+  return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_frequency(ParsedCsvRow const & row)
+{
+  Frequency frequency;
+  try
+  {
+    // Required fields:
+    frequency.trip_id = row.at("trip_id");
+    frequency.start_time = Time(row.at("start_time"));
+    frequency.end_time = Time(row.at("end_time"));
+    set_field(frequency.headway_secs, row, "headway_secs", false);
+
+    // Optional:
+    set_field(frequency.exact_times, row, "exact_times");
+  }
+  catch (const std::out_of_range & ex)
+  {
+    return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+  }
+  catch (const std::invalid_argument & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+  catch (const InvalidFieldFormat & ex)
+  {
+    return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+  }
+
+  frequencies.push_back(frequency);
+  return {ResultCode::OK, {}};
+}
+
+inline Result Feed::parse_csv(const std::string & filename,
+                              const std::function<Result(const ParsedCsvRow & record)> & add_entity)
+{
+  CsvParser parser(gtfs_directory);
+  auto res_header = parser.read_header(filename);
+  if (res_header.code != ResultCode::OK)
+    return res_header;
+
+  ParsedCsvRow record;
+  Result res_row;
+  while ((res_row = parser.read_row(record)) != ResultCode::END_OF_FILE)
+  {
+    if (res_row != ResultCode::OK)
+      return res_row;
+
+    if (record.empty())
+      continue;
+
+    Result res = add_entity(record);
+    if (res != ResultCode::OK)
+      return res;
+  }
+
+  return {ResultCode::OK, {"Parsed " + filename}};
+}
+
+inline Result Feed::read_agencies()
+{
+  auto handler = [this](const ParsedCsvRow & record) { return this->add_agency(record); };
+  return parse_csv("agency.txt", handler);
+}
+
+inline const Agencies & Feed::get_agencies() const { return agencies; }
+
+inline std::optional<Agency> Feed::get_agency(const Id & agency_id) const
+{
+  // agency id is required when the dataset contains data for multiple agencies,
+  // otherwise it is optional:
+  if (agency_id.empty() && agencies.size() == 1)
+    return agencies[0];
+
+  const auto it =
+      std::find_if(agencies.begin(), agencies.end(),
+                   [&agency_id](const Agency & agency) { return agency.agency_id == agency_id; });
+
+  if (it == agencies.end())
+    return std::nullopt;
+
+  return *it;
+}
+
+inline void Feed::add_agency(const Agency & agency) { agencies.push_back(agency); }
+
+inline Result Feed::read_stops()
+{
+  auto handler = [this](const ParsedCsvRow & record) { return this->add_stop(record); };
+  return parse_csv("stops.txt", handler);
+}
+
+inline const Stops & Feed::get_stops() const { return stops; }
+
+inline std::optional<Stop> Feed::get_stop(const Id & stop_id) const
+{
+  const auto it = std::find_if(stops.begin(), stops.end(),
+                               [&stop_id](const Stop & stop) { return stop.stop_id == stop_id; });
+
+  if (it == stops.end())
+    return std::nullopt;
+
+  return *it;
+}
+
+inline void Feed::add_stop(const Stop & stop) { stops.push_back(stop); }
+
+inline Result Feed::read_routes()
+{
+  auto handler = [this](const ParsedCsvRow & record) { return this->add_route(record); };
+  return parse_csv("routes.txt", handler);
+}
+
+inline const Routes & Feed::get_routes() const { return routes; }
+
+inline std::optional<Route> Feed::get_route(const Id & route_id) const
+{
+  const auto it = std::find_if(routes.begin(), routes.end(), [&route_id](const Route & route) {
+    return route.route_id == route_id;
+  });
+
+  if (it == routes.end())
+    return std::nullopt;
+
+  return *it;
+}
+
+inline void Feed::add_route(const Route & route) { routes.push_back(route); }
+
+inline Result Feed::read_trips()
+{
+  auto handler = [this](const ParsedCsvRow & record) { return this->add_trip(record); };
+  return parse_csv("trips.txt", handler);
+}
+
+inline const Trips & Feed::get_trips() const { return trips; }
+
+inline std::optional<Trip> Feed::get_trip(const Id & trip_id) const
+{
+  const auto it = std::find_if(trips.begin(), trips.end(),
+                               [&trip_id](const Trip & trip) { return trip.trip_id == trip_id; });
+
+  if (it == trips.end())
+    return std::nullopt;
+
+  return *it;
+}
+
+inline void Feed::add_trip(const Trip & trip) { trips.push_back(trip); }
+
+inline Result Feed::read_stop_times()
+{
+  auto handler = [this](const ParsedCsvRow & record) { return this->add_stop_time(record); };
+  return parse_csv("stop_times.txt", handler);
+}
+
+inline const StopTimes & Feed::get_stop_times() const { return stop_times; }
+
+inline StopTimes Feed::get_stop_times_for_stop(const Id & stop_id) const
+{
+  StopTimes res;
+  for (const auto & stop_time : stop_times)
+  {
+    if (stop_time.stop_id == stop_id)
+      res.push_back(stop_time);
+  }
+  return res;
+}
+
+inline StopTimes Feed::get_stop_times_for_trip(const Id & trip_id, bool sort_by_sequence) const
+{
+  StopTimes res;
+  for (const auto & stop_time : stop_times)
+  {
+    if (stop_time.trip_id == trip_id)
+      res.push_back(stop_time);
+  }
+  if (sort_by_sequence)
+  {
+    std::sort(res.begin(), res.end(), [](const StopTime & t1, const StopTime & t2) {
+      return t1.stop_sequence < t2.stop_sequence;
+    });
+  }
+  return res;
+}
+
+inline void Feed::add_stop_time(const StopTime & stop_time) { stop_times.push_back(stop_time); }
+
+inline Result Feed::read_calendar()
+{
+  auto handler = [this](const ParsedCsvRow & record) { return this->add_calendar_item(record); };
+  return parse_csv("calendar.txt", handler);
+}
+
+inline const Calendar & Feed::get_calendar() const { return calendar; }
+
+inline std::optional<CalendarItem> Feed::get_calendar(const Id & service_id) const
+{
+  const auto it = std::find_if(calendar.begin(), calendar.end(),
+                               [&service_id](const CalendarItem & calendar_item) {
+                                 return calendar_item.service_id == service_id;
+                               });
+
+  if (it == calendar.end())
+    return std::nullopt;
+
+  return *it;
+}
+
+inline void Feed::add_calendar_item(const CalendarItem & calendar_item)
+{
+  calendar.push_back(calendar_item);
+}
+
+inline Result Feed::read_calendar_dates()
+{
+  auto handler = [this](const ParsedCsvRow & record) { return this->add_calendar_date(record); };
+  return parse_csv("calendar_dates.txt", handler);
+}
+
+inline const CalendarDates & Feed::get_calendar_dates() const { return calendar_dates; }
+
+inline CalendarDates Feed::get_calendar_dates(const Id & service_id, bool sort_by_date) const
+{
+  std::vector<CalendarDate> res;
+  for (const auto & calendar_date : calendar_dates)
+  {
+    if (calendar_date.service_id == service_id)
+      res.push_back(calendar_date);
+  }
+
+  if (sort_by_date)
+  {
+    std::sort(res.begin(), res.end(), [](const CalendarDate & d1, const CalendarDate & d2) {
+      return d1.date.get_raw_date() < d2.date.get_raw_date();
+    });
+  }
+
+  return res;
+}
+
+inline void Feed::add_calendar_date(const CalendarDate & calendar_date)
+{
+  calendar_dates.push_back(calendar_date);
+}
+
+inline Result Feed::read_fare_rules()
+{
+  // TODO Read csv
+  return {};
+}
+
+inline const FareRules & Feed::get_fare_rules() const { return fare_rules; }
+
+inline std::optional<FareRule> Feed::get_fare_rule(const Id & fare_id) const
+{
+  const auto it =
+      std::find_if(fare_rules.begin(), fare_rules.end(),
+                   [&fare_id](const FareRule & fare_rule) { return fare_rule.fare_id == fare_id; });
+
+  if (it == fare_rules.end())
+    return std::nullopt;
+
+  return *it;
+}
+
+inline void Feed::add_fare_rule(const FareRule & fare_rule) { fare_rules.push_back(fare_rule); }
+
+inline Result Feed::read_shapes()
+{
+  auto handler = [this](const ParsedCsvRow & record) { return this->add_shape(record); };
+  return parse_csv("shapes.txt", handler);
+}
+
+inline const Shapes & Feed::get_shapes() const { return shapes; }
+
+inline Shape Feed::get_shape(const Id & shape_id, bool sort_by_sequence) const
+{
+  Shape res;
+  for (const auto & shape : shapes)
+  {
+    if (shape.shape_id == shape_id)
+      res.push_back(shape);
+  }
+  if (sort_by_sequence)
+  {
+    std::sort(res.begin(), res.end(), [](const ShapePoint & s1, const ShapePoint & s2) {
+      return s1.shape_pt_sequence < s2.shape_pt_sequence;
+    });
+  }
+  return res;
+}
+
+inline void Feed::add_shape(const ShapePoint & shape) { shapes.push_back(shape); }
+
+inline Result Feed::read_frequencies()
+{
+  auto handler = [this](const ParsedCsvRow & record) { return this->add_frequency(record); };
+  return parse_csv("frequencies.txt", handler);
+}
+
+inline const Frequencies & Feed::get_frequencies() const { return frequencies; }
+
+inline std::optional<Frequency> Feed::get_frequency(const Id & trip_id) const
+{
+  const auto it = std::find_if(
+      frequencies.begin(), frequencies.end(),
+      [&trip_id](const Frequency & frequency) { return frequency.trip_id == trip_id; });
+
+  if (it == frequencies.end())
+    return std::nullopt;
+
+  return *it;
+}
+
+inline void Feed::add_frequency(const Frequency & frequency) { frequencies.push_back(frequency); }
+
+inline Result Feed::read_transfers()
+{
+  auto handler = [this](const ParsedCsvRow & record) { return this->add_transfer(record); };
+  return parse_csv("transfers.txt", handler);
+}
+
+inline const Transfers & Feed::get_transfers() const { return transfers; }
+
+inline std::optional<Transfer> Feed::get_transfer(const Id & from_stop_id,
+                                                  const Id & to_stop_id) const
+{
+  const auto it = std::find_if(
+      transfers.begin(), transfers.end(), [&from_stop_id, &to_stop_id](const Transfer & transfer) {
+        return transfer.from_stop_id == from_stop_id && transfer.to_stop_id == to_stop_id;
+      });
+
+  if (it == transfers.end())
+    return std::nullopt;
+
+  return *it;
+}
+
+inline void Feed::add_transfer(const Transfer & transfer) { transfers.push_back(transfer); }
+
+inline Result Feed::read_pathways()
+{
+  // TODO Read csv
+  return {};
+}
+
+inline const Pathways & Feed::get_pathways() const { return pathways; }
+
+inline std::optional<Pathway> Feed::get_pathway(const Id & pathway_id) const
+{
+  const auto it = std::find_if(
+      pathways.begin(), pathways.end(),
+      [&pathway_id](const Pathway & pathway) { return pathway.pathway_d == pathway_id; });
+
+  if (it == pathways.end())
+    return std::nullopt;
+
+  return *it;
+}
+
+inline std::optional<Pathway> Feed::get_pathway(const Id & from_stop_id,
+                                                const Id & to_stop_id) const
+{
+  const auto it = std::find_if(
+      pathways.begin(), pathways.end(), [&from_stop_id, &to_stop_id](const Pathway & pathway) {
+        return pathway.from_stop_id == from_stop_id && pathway.to_stop_id == to_stop_id;
+      });
+
+  if (it == pathways.end())
+    return std::nullopt;
+
+  return *it;
+}
+
+inline void Feed::add_pathway(const Pathway & pathway) { pathways.push_back(pathway); }
+
+inline Result Feed::read_levels()
+{
+  // TODO Read csv
+  return {};
+}
+
+inline const Levels & Feed::get_levels() const { return levels; }
+
+inline std::optional<Level> Feed::get_level(const Id & level_id) const
+{
+  const auto it = std::find_if(levels.begin(), levels.end(), [&level_id](const Level & level) {
+    return level.level_id == level_id;
+  });
+
+  if (it == levels.end())
+    return std::nullopt;
+
+  return *it;
+}
+
+inline void Feed::add_level(const Level & level) { levels.push_back(level); }
+
+inline Result Feed::read_feed_info()
+{
+  // TODO Read csv
+  return {};
+}
+
+inline FeedInfo Feed::get_feed_info() const { return feed_info; }
+
+inline void Feed::set_feed_info(const FeedInfo & info) { feed_info = info; }
+
+inline Result Feed::read_translations()
+{
+  // TODO Read csv
+  return {};
+}
+
+inline const Translations & Feed::get_translations() const { return translations; }
+
+inline std::optional<Translation> Feed::get_translation(const TranslationTable & table_name) const
+{
+  const auto it = std::find_if(translations.begin(), translations.end(),
+                               [&table_name](const Translation & translation) {
+                                 return translation.table_name == table_name;
+                               });
+
+  if (it == translations.end())
+    return std::nullopt;
+
+  return *it;
+}
+
+inline void Feed::add_translation(const Translation & translation)
+{
+  translations.push_back(translation);
+}
+
+inline Result Feed::read_attributions()
+{
+  // TODO Read csv
+  return {};
+}
+
+inline const Attributions & Feed::get_attributions() const { return attributions; }
+
+inline void Feed::add_attribution(const Attribution & attribution)
+{
+  attributions.push_back(attribution);
+}
+}  // namespace gtfs
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..aadc7f2
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,10 @@
+file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp)
+
+message(STATUS "CMAKE_CURRENT_BINARY_DIR=" ${CMAKE_CURRENT_BINARY_DIR})
+
+foreach(TEST_SOURCE ${TESTS})
+    string(REPLACE ".cpp" "" TEST_TARGET "${TEST_SOURCE}")
+    add_executable(${TEST_TARGET} ${TEST_SOURCE})
+    target_compile_features(${TEST_TARGET} PRIVATE cxx_std_17)
+    add_test("${TEST_TARGET}" "${TEST_TARGET}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} --verbose)
+endforeach()
diff --git a/tests/data/sample_feed/agency.txt b/tests/data/sample_feed/agency.txt
new file mode 100644
index 0000000..eb24555
--- /dev/null
+++ b/tests/data/sample_feed/agency.txt
@@ -0,0 +1,2 @@
+agency_id,agency_name,agency_url,agency_timezone
+DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
\ No newline at end of file
diff --git a/tests/data/sample_feed/calendar.txt b/tests/data/sample_feed/calendar.txt
new file mode 100644
index 0000000..7a2abb5
--- /dev/null
+++ b/tests/data/sample_feed/calendar.txt
@@ -0,0 +1,3 @@
+service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
+FULLW,1,1,1,1,1,1,1,20070101,20101231
+WE,0,0,0,0,0,1,1,20070101,20101231
\ No newline at end of file
diff --git a/tests/data/sample_feed/calendar_dates.txt b/tests/data/sample_feed/calendar_dates.txt
new file mode 100644
index 0000000..94a557b
--- /dev/null
+++ b/tests/data/sample_feed/calendar_dates.txt
@@ -0,0 +1,2 @@
+service_id,date,exception_type
+FULLW,20070604,2
\ No newline at end of file
diff --git a/tests/data/sample_feed/fare_attributes.txt b/tests/data/sample_feed/fare_attributes.txt
new file mode 100644
index 0000000..3ee7a99
--- /dev/null
+++ b/tests/data/sample_feed/fare_attributes.txt
@@ -0,0 +1,3 @@
+fare_id,price,currency_type,payment_method,transfers,transfer_duration
+p,1.25,USD,0,0,
+a,5.25,USD,0,0,
\ No newline at end of file
diff --git a/tests/data/sample_feed/fare_rules.txt b/tests/data/sample_feed/fare_rules.txt
new file mode 100644
index 0000000..ee776c9
--- /dev/null
+++ b/tests/data/sample_feed/fare_rules.txt
@@ -0,0 +1,5 @@
+fare_id,route_id,origin_id,destination_id,contains_id
+p,AB,,,
+p,STBA,,,
+p,BFC,,,
+a,AAMV,,,
\ No newline at end of file
diff --git a/tests/data/sample_feed/frequencies.txt b/tests/data/sample_feed/frequencies.txt
new file mode 100644
index 0000000..47941ef
--- /dev/null
+++ b/tests/data/sample_feed/frequencies.txt
@@ -0,0 +1,12 @@
+trip_id,start_time,end_time,headway_secs
+STBA,6:00:00,22:00:00,1800
+CITY1,6:00:00,7:59:59,1800
+CITY2,6:00:00,7:59:59,1800
+CITY1,8:00:00,9:59:59,600
+CITY2,8:00:00,9:59:59,600
+CITY1,10:00:00,15:59:59,1800
+CITY2,10:00:00,15:59:59,1800
+CITY1,16:00:00,18:59:59,600
+CITY2,16:00:00,18:59:59,600
+CITY1,19:00:00,22:00:00,1800
+CITY2,19:00:00,22:00:00,1800
\ No newline at end of file
diff --git a/tests/data/sample_feed/routes.txt b/tests/data/sample_feed/routes.txt
new file mode 100644
index 0000000..0b1eb61
--- /dev/null
+++ b/tests/data/sample_feed/routes.txt
@@ -0,0 +1,6 @@
+route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
+AB,DTA,10,Airport - Bullfrog,,3,,,
+BFC,DTA,20,Bullfrog - Furnace Creek Resort,,3,,,
+STBA,DTA,30,Stagecoach - Airport Shuttle,,3,,,
+CITY,DTA,40,City,,3,,,
+AAMV,DTA,50,Airport - Amargosa Valley,,3,,,
\ No newline at end of file
diff --git a/tests/data/sample_feed/shapes.txt b/tests/data/sample_feed/shapes.txt
new file mode 100644
index 0000000..70956bf
--- /dev/null
+++ b/tests/data/sample_feed/shapes.txt
@@ -0,0 +1,9 @@
+shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled
+10237,  43.5176524709, -79.6906570431,50017,12669
+10237,  43.5176982107, -79.6906412064,50018,12669
+10237,  43.5177439788, -79.6906278437,50019,12669
+10237,  43.5177457792, -79.6906278048,50020,12669
+10243,  43.6448714082, -79.5249161004,10001,0
+10243,  43.6448078510, -79.5252239093,10002,0
+10243,  43.6446766156, -79.5251713255,10003,0
+10243,  43.6445544452, -79.5251234796,10004,0
\ No newline at end of file
diff --git a/tests/data/sample_feed/stop_times.txt b/tests/data/sample_feed/stop_times.txt
new file mode 100644
index 0000000..89cf487
--- /dev/null
+++ b/tests/data/sample_feed/stop_times.txt
@@ -0,0 +1,29 @@
+trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_time,shape_dist_traveled
+STBA,6:00:00,6:00:00,STAGECOACH,1,,,,
+STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,
+CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
+CITY1,6:05:00,6:07:00,NANAA,2,,,,
+CITY1,6:12:00,6:14:00,NADAV,3,,,,
+CITY1,6:19:00,6:21:00,DADAN,4,,,,
+CITY1,6:26:00,6:28:00,EMSI,5,,,,
+CITY2,6:28:00,6:30:00,EMSI,1,,,,
+CITY2,6:35:00,6:37:00,DADAN,2,,,,
+CITY2,6:42:00,6:44:00,NADAV,3,,,,
+CITY2,6:49:00,6:51:00,NANAA,4,,,,
+CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
+AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
+AB1,8:10:00,8:15:00,BULLFROG,2,,,,
+AB2,12:05:00,12:05:00,BULLFROG,1,,,,
+AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2
+BFC1,8:20:00,8:20:00,BULLFROG,1
+BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2
+BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1
+BFC2,12:00:00,12:00:00,BULLFROG,2
+AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1
+AAMV1,9:00:00,9:00:00,AMV,2
+AAMV2,10:00:00,10:00:00,AMV,1
+AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2
+AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1
+AAMV3,14:00:00,14:00:00,AMV,2
+AAMV4,15:00:00,15:00:00,AMV,1
+AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2
\ No newline at end of file
diff --git a/tests/data/sample_feed/stops.txt b/tests/data/sample_feed/stops.txt
new file mode 100644
index 0000000..4ee756a
--- /dev/null
+++ b/tests/data/sample_feed/stops.txt
@@ -0,0 +1,10 @@
+stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
+FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
+BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
+BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
+STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
+NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
+NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
+DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
+EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
+AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
\ No newline at end of file
diff --git a/tests/data/sample_feed/trips.txt b/tests/data/sample_feed/trips.txt
new file mode 100644
index 0000000..41aad6e
--- /dev/null
+++ b/tests/data/sample_feed/trips.txt
@@ -0,0 +1,12 @@
+route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
+AB,FULLW,AB1,to Bullfrog,0,1,
+AB,FULLW,AB2,to Airport,1,2,
+STBA,FULLW,STBA,Shuttle,,,
+CITY,FULLW,CITY1,,0,,
+CITY,FULLW,CITY2,,1,,
+BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
+BFC,FULLW,BFC2,to Bullfrog,1,2,
+AAMV,WE,AAMV1,to Amargosa Valley,0,,
+AAMV,WE,AAMV2,to Airport,1,,
+AAMV,WE,AAMV3,to Amargosa Valley,0,,
+AAMV,WE,AAMV4,to Airport,1,,
\ No newline at end of file
diff --git a/tests/unit_tests.cpp b/tests/unit_tests.cpp
new file mode 100644
index 0000000..629974a
--- /dev/null
+++ b/tests/unit_tests.cpp
@@ -0,0 +1,345 @@
+#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+#include "doctest.h"
+
+#include "just_gtfs/just_gtfs.h"
+
+using namespace gtfs;
+
+TEST_SUITE_BEGIN("Handling time GTFS fields");
+TEST_CASE("Time in H:MM:SS format")
+{
+  Time stop_time("0:19:00");
+  CHECK(stop_time.is_provided());
+  CHECK_EQ(stop_time.get_hh_mm_ss(), std::make_tuple(0, 19, 0));
+  CHECK_EQ(stop_time.get_raw_time(), "0:19:00");
+  CHECK_EQ(stop_time.get_total_seconds(), 19 * 60);
+}
+
+TEST_CASE("Time in HH:MM:SS format")
+{
+  Time stop_time("39:45:30");
+  CHECK_EQ(stop_time.get_hh_mm_ss(), std::make_tuple(39, 45, 30));
+  CHECK_EQ(stop_time.get_raw_time(), "39:45:30");
+  CHECK_EQ(stop_time.get_total_seconds(), 39 * 60 * 60 + 45 * 60 + 30);
+}
+
+TEST_CASE("Time from integers 1")
+{
+  Time stop_time(14, 30, 0);
+  CHECK_EQ(stop_time.get_hh_mm_ss(), std::make_tuple(14, 30, 0));
+  CHECK_EQ(stop_time.get_raw_time(), "14:30:00");
+  CHECK_EQ(stop_time.get_total_seconds(), 14 * 60 * 60 + 30 * 60);
+}
+
+TEST_CASE("Time from integers 2")
+{
+  Time stop_time(3, 0, 0);
+  CHECK_EQ(stop_time.get_hh_mm_ss(), std::make_tuple(3, 0, 0));
+  CHECK_EQ(stop_time.get_raw_time(), "03:00:00");
+  CHECK_EQ(stop_time.get_total_seconds(), 3 * 60 * 60);
+}
+
+TEST_CASE("Invalid time format")
+{
+  CHECK_THROWS_AS(Time("12/10/00"), const InvalidFieldFormat &);
+  CHECK_THROWS_AS(Time("12:100:00"), const InvalidFieldFormat &);
+  CHECK_THROWS_AS(Time("12:10:100"), const InvalidFieldFormat &);
+}
+
+TEST_CASE("Time not provided")
+{
+  Time stop_time("");
+  CHECK(!stop_time.is_provided());
+}
+
+TEST_CASE("Convert to Time with 24 hours max")
+{
+  Time stop_time_near_midnight("24:05:00");
+  CHECK(stop_time_near_midnight.limit_hours_to_24max());
+  CHECK_EQ(stop_time_near_midnight.get_raw_time(), "00:05:00");
+
+  Time stop_time_morning("27:05:00");
+  stop_time_morning.limit_hours_to_24max();
+  CHECK_EQ(stop_time_morning.get_raw_time(), "03:05:00");
+}
+
+TEST_SUITE_END();
+
+TEST_SUITE_BEGIN("Handling date GTFS fields");
+TEST_CASE("Date not provided")
+{
+  Date date("");
+  CHECK(!date.is_provided());
+}
+
+TEST_CASE("Invalid date format")
+{
+  // Violation of the format YYYYMMDD:
+  CHECK_THROWS_AS(Date("1999314"), const InvalidFieldFormat &);
+  CHECK_THROWS_AS(Date("20081414"), const InvalidFieldFormat &);
+  CHECK_THROWS_AS(Date("20170432"), const InvalidFieldFormat &);
+
+  // Count of days in february (leap year):
+  CHECK_THROWS_AS(Date("20200230"), const InvalidFieldFormat &);
+  // Count of days in february (not leap year):
+  CHECK_THROWS_AS(Date("20210229"), const InvalidFieldFormat &);
+
+  // Count of days in months with 30 days:
+  CHECK_THROWS_AS(Date("19980431"), const InvalidFieldFormat &);
+  CHECK_THROWS_AS(Date("19980631"), const InvalidFieldFormat &);
+  CHECK_THROWS_AS(Date("19980931"), const InvalidFieldFormat &);
+  CHECK_THROWS_AS(Date("19981131"), const InvalidFieldFormat &);
+}
+
+TEST_CASE("Date from string 1")
+{
+  Date date("20230903");
+  CHECK_EQ(date.get_yyyy_mm_dd(), std::make_tuple(2023, 9, 3));
+  CHECK_EQ(date.get_raw_date(), "20230903");
+  CHECK(date.is_provided());
+}
+
+TEST_CASE("Date from string 2")
+{
+  Date date("20161231");
+  CHECK_EQ(date.get_yyyy_mm_dd(), std::make_tuple(2016, 12, 31));
+  CHECK_EQ(date.get_raw_date(), "20161231");
+  CHECK(date.is_provided());
+}
+
+TEST_CASE("Date from string 3")
+{
+  Date date("20200229");
+  CHECK_EQ(date.get_yyyy_mm_dd(), std::make_tuple(2020, 2, 29));
+  CHECK_EQ(date.get_raw_date(), "20200229");
+  CHECK(date.is_provided());
+}
+
+TEST_CASE("Date from integers")
+{
+  Date date(2022, 8, 16);
+  CHECK_EQ(date.get_yyyy_mm_dd(), std::make_tuple(2022, 8, 16));
+
+  CHECK_EQ(date.get_raw_date(), "20220816");
+  CHECK(date.is_provided());
+}
+
+TEST_SUITE_END();
+
+TEST_SUITE_BEGIN("Csv parsing");
+TEST_CASE("Record with empty values")
+{
+  const auto res = CsvParser::split_record(",, ,");
+  CHECK_EQ(res.size(), 4);
+  for (const auto & token : res)
+    CHECK(token.empty());
+}
+
+TEST_CASE("Header with UTF BOM")
+{
+  const auto res = CsvParser::split_record("\xef\xbb\xbfroute_id, agency_id", true);
+  CHECK_EQ(res.size(), 2);
+  CHECK_EQ(res[0], "route_id");
+  CHECK_EQ(res[1], "agency_id");
+}
+
+TEST_CASE("Quotation marks")
+{
+  const auto res = CsvParser::split_record(R"(27681 ,,"Sisters, OR",,"44.29124",1)");
+  CHECK_EQ(res.size(), 6);
+  CHECK_EQ(res[2], "Sisters, OR");
+  CHECK_EQ(res[4], "44.29124");
+  CHECK_EQ(res[5], "1");
+}
+TEST_SUITE_END();
+
+TEST_SUITE_BEGIN("Read");
+// Credits:
+// https://www.sfmta.com/reports/gtfs-transit-data
+TEST_CASE("Empty container before parsing")
+{
+  Feed feed("data/San Francisco Municipal Transportation Agency");
+  CHECK(feed.get_agencies().empty());
+  auto agency = feed.get_agency("10");
+  CHECK(!agency);
+}
+
+TEST_CASE("Transfers")
+{
+  Feed feed("data/sample_feed");
+  auto res = feed.read_transfers();
+  CHECK_EQ(res.code, ResultCode::ERROR_FILE_ABSENT);
+  CHECK_EQ(feed.get_transfers().size(), 0);
+}
+
+TEST_CASE("Calendar")
+{
+  Feed feed("data/sample_feed");
+  auto res = feed.read_calendar();
+  CHECK_EQ(res.code, ResultCode::OK);
+  const auto & calendar = feed.get_calendar();
+  CHECK_EQ(calendar.size(), 2);
+
+  const auto calendar_record = feed.get_calendar("WE");
+  CHECK(calendar_record);
+
+  CHECK_EQ(calendar_record->start_date, Date(2007, 01, 01));
+  CHECK_EQ(calendar_record->end_date, Date(2010, 12, 31));
+
+  CHECK_EQ(calendar_record->monday, CalendarAvailability::NotAvailable);
+  CHECK_EQ(calendar_record->tuesday, CalendarAvailability::NotAvailable);
+  CHECK_EQ(calendar_record->wednesday, CalendarAvailability::NotAvailable);
+  CHECK_EQ(calendar_record->thursday, CalendarAvailability::NotAvailable);
+  CHECK_EQ(calendar_record->friday, CalendarAvailability::NotAvailable);
+  CHECK_EQ(calendar_record->saturday, CalendarAvailability::Available);
+  CHECK_EQ(calendar_record->sunday, CalendarAvailability::Available);
+}
+
+TEST_CASE("Calendar dates")
+{
+  Feed feed("data/sample_feed");
+  auto res = feed.read_calendar_dates();
+  CHECK_EQ(res.code, ResultCode::OK);
+  const auto & calendar_dates = feed.get_calendar_dates();
+  CHECK_EQ(calendar_dates.size(), 1);
+
+  const auto calendar_record = feed.get_calendar_dates("FULLW");
+  CHECK(!calendar_record.empty());
+
+  CHECK_EQ(calendar_record[0].date, Date(2007, 06, 04));
+  CHECK_EQ(calendar_record[0].exception_type, CalendarDateException::Removed);
+}
+
+TEST_CASE("Read GTFS feed")
+{
+  Feed feed("data/sample_feed");
+  auto res = feed.read_feed();
+  CHECK_EQ(res.code, ResultCode::OK);
+  CHECK_EQ(feed.get_agencies().size(), 1);
+  CHECK_EQ(feed.get_routes().size(), 5);
+  CHECK_EQ(feed.get_trips().size(), 11);
+  CHECK_EQ(feed.get_shapes().size(), 8);
+  CHECK_EQ(feed.get_stops().size(), 9);
+  CHECK_EQ(feed.get_stop_times().size(), 28);
+}
+
+TEST_CASE("Agencies")
+{
+  Feed feed("data/sample_feed");
+  auto res = feed.read_agencies();
+  CHECK_EQ(res.code, ResultCode::OK);
+  const auto & agencies = feed.get_agencies();
+  CHECK_EQ(agencies.size(), 1);
+  CHECK_EQ(agencies[0].agency_id, "DTA");
+  CHECK_EQ(agencies[0].agency_name, "Demo Transit Authority");
+  CHECK_EQ(agencies[0].agency_url, "http://google.com");
+  CHECK(agencies[0].agency_lang.empty());
+  CHECK_EQ(agencies[0].agency_timezone, "America/Los_Angeles");
+
+  const auto agency = feed.get_agency("DTA");
+  CHECK(agency);
+}
+
+TEST_CASE("Routes")
+{
+  Feed feed("data/sample_feed");
+  auto res = feed.read_routes();
+  CHECK_EQ(res.code, ResultCode::OK);
+  const auto & routes = feed.get_routes();
+  CHECK_EQ(routes.size(), 5);
+  CHECK_EQ(routes[0].route_id, "AB");
+  CHECK_EQ(routes[0].agency_id, "DTA");
+  CHECK_EQ(routes[0].route_short_name, "10");
+  CHECK_EQ(routes[0].route_long_name, "Airport - Bullfrog");
+  CHECK_EQ(routes[0].route_type, RouteType::Bus);
+  CHECK(routes[0].route_text_color.empty());
+  CHECK(routes[0].route_color.empty());
+  CHECK(routes[0].route_desc.empty());
+
+  auto const route = feed.get_route("AB");
+  CHECK(route);
+}
+
+TEST_CASE("Trips")
+{
+  Feed feed("data/sample_feed");
+  auto res = feed.read_trips();
+  CHECK_EQ(res.code, ResultCode::OK);
+  const auto & trips = feed.get_trips();
+  CHECK_EQ(trips.size(), 11);
+
+  CHECK_EQ(trips[0].block_id, "1");
+  CHECK_EQ(trips[0].route_id, "AB");
+  CHECK_EQ(trips[0].direction_id, TripDirectionId::DefaultDirection);
+  CHECK_EQ(trips[0].trip_headsign, "to Bullfrog");
+  CHECK(trips[0].shape_id.empty());
+  CHECK_EQ(trips[0].service_id, "FULLW");
+  CHECK_EQ(trips[0].trip_id, "AB1");
+
+  auto const trip = feed.get_trip("AB1");
+  CHECK(trip);
+  CHECK(trip.value().trip_short_name.empty());
+}
+
+TEST_CASE("Stops")
+{
+  Feed feed("data/sample_feed");
+  auto res = feed.read_stops();
+  CHECK_EQ(res.code, ResultCode::OK);
+
+  const auto & stops = feed.get_stops();
+  CHECK_EQ(stops.size(), 9);
+  CHECK_EQ(stops[0].stop_lat, 36.425288);
+  CHECK_EQ(stops[0].stop_lon, -117.133162);
+  CHECK(stops[0].stop_code.empty());
+  CHECK(stops[0].stop_url.empty());
+  CHECK_EQ(stops[0].stop_id, "FUR_CREEK_RES");
+  CHECK(stops[0].stop_desc.empty());
+  CHECK_EQ(stops[0].stop_name, "Furnace Creek Resort (Demo)");
+  CHECK_EQ(stops[0].location_type, StopLocationType::GenericNode);
+  CHECK(stops[0].zone_id.empty());
+
+  auto const stop = feed.get_stop("FUR_CREEK_RES");
+  CHECK(stop);
+}
+
+TEST_CASE("StopTimes")
+{
+  Feed feed("data/sample_feed");
+  auto res = feed.read_stop_times();
+  CHECK_EQ(res.code, ResultCode::OK);
+
+  const auto & stop_times = feed.get_stop_times();
+  CHECK_EQ(stop_times.size(), 28);
+
+  CHECK_EQ(stop_times[0].trip_id, "STBA");
+  CHECK_EQ(stop_times[0].arrival_time, Time(06, 00, 00));
+  CHECK_EQ(stop_times[0].departure_time, Time(06, 00, 00));
+  CHECK_EQ(stop_times[0].stop_id, "STAGECOACH");
+  CHECK_EQ(stop_times[0].stop_sequence, 1);
+  CHECK(stop_times[0].stop_headsign.empty());
+  CHECK_EQ(stop_times[0].pickup_type, StopTimeBoarding::RegularlyScheduled);
+  CHECK_EQ(stop_times[0].drop_off_type, StopTimeBoarding::RegularlyScheduled);
+
+  CHECK_EQ(feed.get_stop_times_for_stop("STAGECOACH").size(), 3);
+  CHECK_EQ(feed.get_stop_times_for_trip("STBA").size(), 2);
+}
+
+TEST_CASE("Shapes")
+{
+  Feed feed("data/sample_feed");
+  auto res = feed.read_shapes();
+  CHECK_EQ(res.code, ResultCode::OK);
+
+  const auto & shapes = feed.get_shapes();
+  CHECK_EQ(shapes.size(), 8);
+  CHECK_EQ(shapes[0].shape_id, "10237");
+  CHECK_EQ(shapes[0].shape_pt_lat, 43.5176524709);
+  CHECK_EQ(shapes[0].shape_pt_lon, -79.6906570431);
+  CHECK_EQ(shapes[0].shape_pt_sequence, 50017);
+  CHECK_EQ(shapes[0].shape_dist_traveled, 12669);
+
+  auto const shape = feed.get_shape("10237");
+  CHECK_EQ(shape.size(), 4);
+}
+TEST_SUITE_END();