diff --git a/applications/test/vtkSeriesWriter/Make/files b/applications/test/vtkSeriesWriter/Make/files
new file mode 100644
index 0000000000..367611d7ec
--- /dev/null
+++ b/applications/test/vtkSeriesWriter/Make/files
@@ -0,0 +1,3 @@
+Test-vtkSeriesWriter.C
+
+EXE = $(FOAM_APPBIN)/Test-vtkSeriesWriter
diff --git a/applications/test/vtkSeriesWriter/Make/options b/applications/test/vtkSeriesWriter/Make/options
new file mode 100644
index 0000000000..7ce182425d
--- /dev/null
+++ b/applications/test/vtkSeriesWriter/Make/options
@@ -0,0 +1,5 @@
+EXE_INC = \
+ -I$(LIB_SRC)/fileFormats/lnInclude
+
+EXE_LIBS = \
+ -lfileFormats
diff --git a/applications/test/vtkSeriesWriter/Test-vtkSeriesWriter.C b/applications/test/vtkSeriesWriter/Test-vtkSeriesWriter.C
new file mode 100644
index 0000000000..043e42e034
--- /dev/null
+++ b/applications/test/vtkSeriesWriter/Test-vtkSeriesWriter.C
@@ -0,0 +1,118 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | Copyright (C) 2018 OpenCFD Ltd.
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+Application
+ Test-vtkSeriesWriter
+
+Description
+ Basic functionality tests for vtk::seriesWriter
+
+\*---------------------------------------------------------------------------*/
+
+#include "foamVtkSeriesWriter.H"
+#include "argList.H"
+
+using namespace Foam;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+int main(int argc, char *argv[])
+{
+ argList::addBoolOption("sort", "Sort value / name");
+ argList::addBoolOption("check", "Check for existence of files");
+ argList::addOption("time", "value", "Filter based on given time");
+ argList::addOption("scan", "series", "Scan directory to create series");
+
+ argList args(argc, argv, false, true);
+
+ const scalar currTime = args.lookupOrDefault("time", GREAT);
+
+ Info<< "Using currTime = " << currTime << nl
+ << "when loading " << (args.size()-1) << " files" << nl << nl;
+
+ for (label argi=1; argi < args.size(); ++argi)
+ {
+ const auto& input = args[argi];
+
+ Info << "load from " << input << nl;
+
+ vtk::seriesWriter writer;
+ writer.load(input);
+
+ writer.print(Info);
+ Info<< nl << nl;
+
+ if (writer.removeNewer(currTime))
+ {
+ Info<< "removed entries with time >= " << currTime << nl;
+ writer.print(Info);
+ Info<< nl << nl;
+ }
+
+ if (args.found("sort"))
+ {
+ writer.sort();
+
+ Info<< "sorted" << nl;
+ writer.print(Info);
+ Info<< nl << nl;
+ }
+
+ if (args.found("check"))
+ {
+ writer.load(input, true);
+
+ Info<< "reload, checking the existance of files" << nl;
+ writer.print(Info);
+ Info<< nl << nl;
+ }
+
+ if (writer.empty())
+ {
+ Info<< "No entries" << nl;
+ }
+ else
+ {
+ Info<< writer.size() << " entries" << nl;
+ }
+ }
+
+ if (args.found("scan"))
+ {
+ vtk::seriesWriter writer;
+
+ writer.scan(args.opt("scan"));
+
+ Info<< "scanned for files" << nl;
+ writer.print(Info);
+ Info<< nl << nl;
+ }
+
+
+ Info<< "\nEnd\n" << nl;
+
+ return 0;
+}
+
+
+// ************************************************************************* //
diff --git a/applications/test/vtkSeriesWriter/file.vtm b/applications/test/vtkSeriesWriter/file.vtm
new file mode 100644
index 0000000000..28338dea7a
--- /dev/null
+++ b/applications/test/vtkSeriesWriter/file.vtm
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/applications/test/vtkSeriesWriter/file_00000679.vtm b/applications/test/vtkSeriesWriter/file_00000679.vtm
new file mode 100644
index 0000000000..4b00bcbe8f
--- /dev/null
+++ b/applications/test/vtkSeriesWriter/file_00000679.vtm
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/applications/test/vtkSeriesWriter/file_00001025.vtm b/applications/test/vtkSeriesWriter/file_00001025.vtm
new file mode 100644
index 0000000000..3d4cc1c6df
--- /dev/null
+++ b/applications/test/vtkSeriesWriter/file_00001025.vtm
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/applications/test/vtkSeriesWriter/file_00001234.vtm b/applications/test/vtkSeriesWriter/file_00001234.vtm
new file mode 100644
index 0000000000..b49c580b1a
--- /dev/null
+++ b/applications/test/vtkSeriesWriter/file_00001234.vtm
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/applications/test/vtkSeriesWriter/file_00005680.vtm b/applications/test/vtkSeriesWriter/file_00005680.vtm
new file mode 100644
index 0000000000..feab3b1cff
--- /dev/null
+++ b/applications/test/vtkSeriesWriter/file_00005680.vtm
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/applications/test/vtkSeriesWriter/file_23.vtm b/applications/test/vtkSeriesWriter/file_23.vtm
new file mode 100644
index 0000000000..43ff669bd2
--- /dev/null
+++ b/applications/test/vtkSeriesWriter/file_23.vtm
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/applications/test/vtkSeriesWriter/test1.vtm.series b/applications/test/vtkSeriesWriter/test1.vtm.series
new file mode 100644
index 0000000000..a5931a31ac
--- /dev/null
+++ b/applications/test/vtkSeriesWriter/test1.vtm.series
@@ -0,0 +1,20 @@
+{
+ "file-series-version" : "1.0",
+ "files": [
+ { "name" : "file_00001742.vtm", "time" : 50 },
+ { "name" : "file_00001025.vtm", "time" : 30 },
+ {
+ "time" : 40,
+ "name" : "file_00001380.vtm", ,,,,
+ },
+ { "name" : "file_00000679.vtm", "time" : 20 },
+ { "name" : "file_00002110.vtm", "time" : 60 },
+ { "name" : "file_00001234.vtm", "time" : 60 },
+ { "name" : "file_00001742.vtm", "time" : 150 },
+ { "name" : "file_00001742.vtm", "time" : 5 },
+
+ { "name" : "file_1000.vtm", "badTime" : 60 },
+ { "" : "", "" : 60, "" : false },
+ { "name" : "", "time" : 10 },
+ ]
+}
diff --git a/src/fileFormats/Make/files b/src/fileFormats/Make/files
index 640d98882b..2ed0cc373a 100644
--- a/src/fileFormats/Make/files
+++ b/src/fileFormats/Make/files
@@ -17,6 +17,7 @@ stl/STLAsciiParseManual.C
stl/STLAsciiParseRagel.C
vtk/file/foamVtkFileWriter.C
+vtk/file/foamVtkSeriesWriter.C
vtk/file/foamVtmWriter.C
vtk/core/foamVtkCore.C
vtk/core/foamVtkPTraits.C
diff --git a/src/fileFormats/vtk/file/foamVtkSeriesWriter.C b/src/fileFormats/vtk/file/foamVtkSeriesWriter.C
new file mode 100644
index 0000000000..c7d243f578
--- /dev/null
+++ b/src/fileFormats/vtk/file/foamVtkSeriesWriter.C
@@ -0,0 +1,753 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | Copyright (C) 2018 OpenCFD Ltd.
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+\*---------------------------------------------------------------------------*/
+
+#include "foamVtkSeriesWriter.H"
+#include "Fstream.H"
+#include "ListOps.H"
+#include "stringOpsSort.H"
+#include "OSspecific.H"
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+ // Get any single token.
+ static inline bool getToken(ISstream& is, token& tok)
+ {
+ return (!is.read(tok).bad() && tok.good());
+ }
+
+ // Get two tokens.
+ // The first one must be a ':' token, the second one is any value
+ //
+ // This corrsponds to the JSON "key" : value syntax,
+ // we trigger after reading the "key".
+ static inline bool getValueToken(ISstream& is, token& tok)
+ {
+ return
+ (
+ // Token 1 = ':' separator
+ (
+ getToken(is, tok)
+ && tok.isPunctuation() && tok.pToken() == token::COLON
+ )
+
+ // Token 2 is the value
+ && getToken(is, tok)
+ );
+ }
+
+
+ // Sorting for fileNameInstant
+ // 1. sort by value (time)
+ // 2. natural sort (name)
+ struct seriesLess
+ {
+ bool operator()(const fileNameInstant a, const fileNameInstant b) const
+ {
+ scalar val = compareOp()(a.value(), b.value());
+ if (val == 0)
+ {
+ return
+ stringOps::natural_sort::compare(a.name(), b.name()) < 0;
+ }
+ return val < 0;
+ }
+ };
+
+
+ // Check if value is less than upper, with some tolerance.
+ static inline bool lessThan(const scalar& val, const scalar& upper)
+ {
+ return (val < upper && Foam::mag(val - upper) > ROOTVSMALL);
+ }
+}
+
+
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+
+Foam::fileName Foam::vtk::seriesWriter::base
+(
+ const fileName& outputName,
+ char sep
+)
+{
+ const auto dash = outputName.rfind(sep);
+
+ // No separator? Or separator in path() instead of name()?
+ if
+ (
+ std::string::npos == dash
+ || std::string::npos != outputName.find('/', dash)
+ )
+ {
+ // Warn?
+ return outputName;
+ }
+
+ const auto dot = outputName.find('.', dash);
+
+ if (std::string::npos == dot)
+ {
+ return outputName.substr(0, dash);
+ }
+
+ return outputName.substr(0, dash) + outputName.substr(dot);
+}
+
+
+Foam::word Foam::vtk::seriesWriter::suffix
+(
+ const fileName& file,
+ char sep
+)
+{
+ const auto dash = file.rfind(sep);
+
+ // No separator? Or separator in path() instead of name()?
+ if
+ (
+ std::string::npos == dash
+ || std::string::npos != file.find('/', dash)
+ )
+ {
+ // Warn?
+ return "";
+ }
+
+ const auto dot = file.find('.', dash);
+
+ if (std::string::npos == dot)
+ {
+ return file.substr(dash);
+ }
+
+ return file.substr(dash, (dot-dash));
+}
+
+
+Foam::Ostream& Foam::vtk::seriesWriter::print
+(
+ Ostream& os,
+ const fileName& base,
+ const UList& series,
+ const char sep
+)
+{
+ // Split the base into (stem, ext) components
+ //
+ // base = "path/file.vtm"
+ //
+ // stem = "file"
+ // ext = ".vtm"
+
+ const word stem = base.nameLessExt();
+ const word ext = "." + base.ext();
+
+ // Begin file-series (JSON)
+ os << "{\n \"file-series-version\" : \"1.0\",\n \"files\" : [\n";
+
+ // Track how many entries are remaining
+ // - trailing commas on all but the final entry (JSON requirement)
+ label nremain = series.size();
+
+ // Each entry
+ // { "name" : "name", "time" : value }
+
+ for (const instant& inst : series)
+ {
+ os << " { \"name\" : \""
+ << stem << sep << inst.name() << ext
+ << "\", \"time\" : " << inst.value() << " }";
+
+ if (--nremain)
+ {
+ os << ',';
+ }
+ os << nl;
+ }
+
+ os << " ]\n}\n";
+
+ return os;
+}
+
+
+Foam::Ostream& Foam::vtk::seriesWriter::print
+(
+ Ostream& os,
+ const UList& series
+)
+{
+ // Begin file-series (JSON)
+ os << "{\n \"file-series-version\" : \"1.0\",\n \"files\" : [\n";
+
+ // Track how many entries are remaining
+ // - trailing commas on all but the final entry (JSON requirement)
+ label nremain = series.size();
+
+ // Each entry
+ // { "name" : "", "time" : }
+
+ for (const fileNameInstant& inst : series)
+ {
+ os << " { \"name\" : \""
+ << inst.name().name()
+ << "\", \"time\" : " << inst.value() << " }";
+
+ if (--nremain)
+ {
+ os << ',';
+ }
+ os << nl;
+ }
+
+ os << " ]\n}\n";
+
+ return os;
+}
+
+
+void Foam::vtk::seriesWriter::write
+(
+ const fileName& seriesName,
+ const UList& series,
+ const char sep
+)
+{
+ mkDir(seriesName.path());
+
+ autoPtr osPtr =
+ (
+ seriesName.hasExt("series")
+ ? autoPtr::New(seriesName)
+ : autoPtr::New(seriesName + ".series")
+ );
+
+ print(*osPtr, seriesName, series, sep);
+}
+
+
+
+void Foam::vtk::seriesWriter::write
+(
+ const fileName& seriesName,
+ const UList& series
+)
+{
+ mkDir(seriesName.path());
+
+ autoPtr osPtr =
+ (
+ seriesName.hasExt("series")
+ ? autoPtr::New(seriesName)
+ : autoPtr::New(seriesName + ".series")
+ );
+
+ print(*osPtr, series);
+}
+
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+bool Foam::vtk::seriesWriter::appendCheck(fileNameInstant inst)
+{
+ if (inst.name().empty())
+ {
+ return false;
+ }
+
+ const auto iter = existing_.find(inst.name());
+
+ if (iter.found())
+ {
+ for (fileNameInstant& dst : entries_)
+ {
+ if (dst.name() == inst.name())
+ {
+ // Replace value
+ dst.value() = inst.value();
+ return true;
+ }
+ }
+ }
+
+ entries_.append(inst);
+ existing_.insert(inst.name());
+
+ return true;
+}
+
+
+bool Foam::vtk::seriesWriter::removeDuplicates()
+{
+ const label nElem = entries_.size();
+
+ HashTable