diff --git a/.gitignore b/.gitignore index f971af7..5761abc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1 @@ -cpp_connector -cpp_connector_threaded -extractor *.o diff --git a/Makefile b/Makefile index e8df541..4b500ed 100644 --- a/Makefile +++ b/Makefile @@ -6,15 +6,19 @@ jsoncpp_cflags := $(shell pkg-config --cflags jsoncpp) jsoncpp_libs := $(shell pkg-config --libs jsoncpp) CFLAGS = -pthread -O2 -Wall -Werror -pedantic -Wno-long-long \ - -Wno-variadic-macros -Isrc $(jsoncpp_cflags) + -Wno-variadic-macros -Isrc $(jsoncpp_cflags) upl_libs = -pthread $(jsoncpp_libs) -lcurl -lssl ext_libs = $(jsoncpp_libs) +rfc_libs = $(jsoncpp_libs) test_py_files = tests/test_uploader.py tests/test_extractor.py -headers = src/CouchDB.h src/EZ.h src/Uploader.h src/UploaderThread.h \ +headers = src/CouchDB.h src/EZ.h src/RFC3339.h \ + src/Uploader.h src/UploaderThread.h \ src/Extractor.h src/UKHASExtractor.h \ - tests/test_extractor_mocks.h -upl_cxxfiles = src/CouchDB.cxx src/EZ.cxx src/Uploader.cxx + tests/test_extractor_mocks.h +rfc_cxxfiles = src/RFC3339.cxx tests/test_rfc3339_main.cxx +rfc_binary = tests/rfc3339 +upl_cxxfiles = src/CouchDB.cxx src/EZ.cxx src/RFC3339.cxx src/Uploader.cxx upl_thr_cflags = -DTHREADED upl_nrm_binary = tests/cpp_connector upl_nrm_objects = tests/test_uploader_main.o @@ -28,6 +32,7 @@ ext_mock_cflags = -include tests/test_extractor_mocks.h CXXFLAGS = $(CFLAGS) upl_objects = $(patsubst %.cxx,%.o,$(upl_cxxfiles)) ext_objects = $(patsubst %.cxx,%.ext_mock.o,$(ext_cxxfiles)) +rfc_objects = $(patsubst %.cxx,%.o,$(rfc_cxxfiles)) %.o : %.cxx $(headers) g++ -c $(CXXFLAGS) -o $@ $< @@ -47,7 +52,11 @@ $(upl_thr_binary) : $(upl_objects) $(upl_thr_objects) $(ext_binary) : $(ext_objects) g++ $(CXXFLAGS) -o $@ $(ext_objects) $(ext_libs) -test : $(upl_nrm_binary) $(upl_thr_binary) $(ext_binary) $(test_py_files) +$(rfc_binary) : $(rfc_objects) + g++ $(CXXFLAGS) -o $@ $(rfc_objects) $(rfc_libs) + +test : $(upl_nrm_binary) $(upl_thr_binary) $(ext_binary) $(rfc_binary) \ + $(test_py_files) nosetests clean : diff --git a/src/RFC3339.cxx b/src/RFC3339.cxx new file mode 100644 index 0000000..7274b16 --- /dev/null +++ b/src/RFC3339.cxx @@ -0,0 +1,324 @@ +/* Copyright 2012 (C) Daniel Richman. License: GNU GPL 3; see LICENSE. */ + +#include "RFC3339.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace RFC3339 { + +bool validate_rfc3339(const string &rfc3339) +{ + try + { + rfc3339_to_timestamp(rfc3339); + } + catch (InvalidFormat e) + { + return false; + } + + return true; +} + +/* Class to be used when extracting from an istream that consumes a single + * delimiter character */ +class Delim +{ + char expect; + +public: + Delim(char _e) : expect(_e) {}; + ~Delim() {}; + void extract(istream &in); +}; + +/* Delim can't be a reference here because in its intended use it is + * constructed on the spot like this ... >> Delim('-') >> ... and therefore + * it does not have an address */ +istream & operator>>(istream &in, Delim delim) +{ + delim.extract(in); + return in; +} + +void Delim::extract(istream &in) +{ + if (!in.good() || in.get() != expect) + in.setstate(ios_base::badbit); +} + +/* Extracts an integer of particular length, containing the digits 0-9 only */ +class StrictInt +{ + size_t length; + int ⌖ + bool check_range; + int min, max; + +public: + StrictInt(size_t _l, int &_t) + : length(_l), target(_t), check_range(false), min(0), max(0) {}; + StrictInt(size_t _l, int &_t, int _min, int _max) + : length(_l), target(_t), check_range(true), min(_min), max(_max) {}; + ~StrictInt() {}; + void extract(istream &in); +}; + +istream & operator>>(istream &in, StrictInt tgt) +{ + tgt.extract(in); + return in; +} + +void StrictInt::extract(istream &in) +{ + if (!in.good()) + { + in.setstate(ios_base::badbit); + return; + } + + char *temp = new char[length + 1]; + in.read(temp, length); + temp[length] = 0; + + if (in.fail() || strlen(temp) != length) + return; + + istringstream temp_ss(temp); + temp_ss >> target; + + if (temp_ss.fail() || temp_ss.peek() != EOF) + in.setstate(ios_base::badbit); + + if (check_range && (target < min || target > max)) + in.setstate(ios_base::badbit); +} + +static int mdays[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static int mydays[] = {0, 0, 31, 59, 90, 120, 151, 181, 212, + 243, 273, 304, 334}; + +/* Returns the number of multiples of n in [a,b] */ +static int multiples_between(int n, int a, int b) +{ + if (a % n != 0) + a += n - (a % n); + b -= (b % n); + return ((b - a) / n) + 1; +} + +/* Not provided on all platforms :-(. */ +static long long int my_timegm(int year, int month, int mday, + int hour, int min, int sec) +{ + /* I don't know the best way to get everything promoted to 64bit + * integers in the final line, this might do it */ + long long int epoch_days = 0; + bool leap_year = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); + + if (year > 1970) + { + int leap_years = multiples_between(4, 1970, year - 1) + - multiples_between(100, 1970, year - 1) + + multiples_between(400, 1970, year - 1); + epoch_days = ((year - 1970) * 365) + leap_years; + } + else if (year < 1970) + { + int leap_years = multiples_between(4, year, 1970 - 1) + - multiples_between(100, year, 1970 - 1) + + multiples_between(400, year, 1970 - 1); + epoch_days = -(((1970 - year) * 365) + leap_years); + } + + epoch_days += mydays[month]; + if (month > 2 && leap_year) + epoch_days++; + epoch_days += mday - 1; + + return (((((epoch_days * 24) + hour) * 60) + min) * 60) + sec; +} + +static long long int my_timegm(struct tm tm) +{ + return my_timegm(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); +} + +/* Thin wrappers adding exceptions and range checks */ +static struct tm my_localtime(long long int timestamp) +{ + time_t timestamp_s = timestamp; + if (timestamp_s != timestamp) + throw out_of_range("timestamp too large for time_t"); + + struct tm tm; + if (localtime_r(×tamp_s, &tm) == NULL) + throw runtime_error("localtime_r failed"); + + return tm; +} + +static struct tm my_gmtime(long long int timestamp) +{ + time_t timestamp_s = timestamp; + if (timestamp_s != timestamp) + throw out_of_range("timestamp too large for time_t"); + + struct tm tm; + if (gmtime_r(×tamp_s, &tm) == NULL) + throw runtime_error("gmtime_r failed"); + + return tm; +} + +long long int rfc3339_to_timestamp(const string &rfc3339) +{ + istringstream temp(rfc3339); + + int year, month, mday, hour, min, sec; + + temp >> StrictInt(4, year) >> Delim('-') + >> StrictInt(2, month, 1, 12) >> Delim('-') + >> StrictInt(2, mday, 1, 31) >> Delim('T') + >> StrictInt(2, hour, 0, 23) >> Delim(':') + >> StrictInt(2, min, 0, 59) >> Delim(':') + >> StrictInt(2, sec, 0, 59); + + if (temp.fail() || temp.eof() || temp.tellg() != 19) + throw InvalidFormat(); + + bool leap_year = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); + int this_mdays = mdays[month]; + + if (leap_year && month == 2) + this_mdays++; + + if (mday > this_mdays) + throw InvalidFormat(); + + if (temp.peek() == '.') + { + /* discard seconds part */ + do + temp.get(); + while (temp.good() && temp.peek() >= '0' && temp.peek() <= '9'); + } + + int offset = 0; + int offset_first_char = temp.get(); + + if (offset_first_char == 'Z') + { + /* UTC offset, 0 */ + } + else if (offset_first_char == '+' || offset_first_char == '-') + { + int offset_hours, offset_minutes; + temp >> StrictInt(2, offset_hours, 0, 23) >> Delim(':') + >> StrictInt(2, offset_minutes, 0, 59); + + if (temp.fail()) + throw InvalidFormat(); + + offset = offset_hours * 3600 + offset_minutes * 60; + + if (offset_first_char == '-') + offset = -offset; + } + else + { + throw InvalidFormat(); + } + + if (temp.peek() != EOF) + throw InvalidFormat(); + + return my_timegm(year, month, mday, hour, min, sec) - offset; +} + +static string make_datestring_start(int year, int month, int mday, + int hour, int min, int sec) +{ + ostringstream temp; + temp.fill('0'); + + temp << setw(4) << year << '-' + << setw(2) << month << '-' + << setw(2) << mday << 'T' + << setw(2) << hour << ':' + << setw(2) << min << ':' + << setw(2) << sec; + + return temp.str(); +} + +static string make_datestring_start(struct tm tm) +{ + return make_datestring_start(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); +} + +string timestamp_to_rfc3339_utcoffset(long long int timestamp) +{ + struct tm tm = my_gmtime(timestamp); + string ret = make_datestring_start(tm) + "Z"; +#ifndef NDEBUG + if (rfc3339_to_timestamp(ret) != timestamp) + throw runtime_error("reparse sanity check failed"); +#endif + return ret; +} + +string timestamp_to_rfc3339_localoffset(long long int timestamp) +{ + struct tm tm = my_localtime(timestamp); + struct tm gm_tm = my_gmtime(timestamp); + + int offset = my_timegm(tm) - my_timegm(gm_tm); + + if (abs(offset) % 60 != 0) + throw runtime_error("Your local offset is not a whole minute"); + + int offset_minutes = abs(offset) / 60; + int offset_hours = offset_minutes / 60; + offset_minutes %= 60; + + ostringstream temp; + temp << make_datestring_start(tm); + temp.fill('0'); + + temp << (offset < 0 ? '-' : '+') + << setw(2) << offset_hours << ':' + << setw(2) << offset_minutes; + + string ret = temp.str(); + +#ifndef NDEBUG + if (rfc3339_to_timestamp(ret) != timestamp) + throw runtime_error("reparse sanity check failed"); +#endif + return ret; +} + +string now_to_rfc3339_utcoffset() +{ + return timestamp_to_rfc3339_utcoffset(time(NULL)); +} + +string now_to_rfc3339_localoffset() +{ + return timestamp_to_rfc3339_localoffset(time(NULL)); +} + +} /* namespace RFC3339 */ diff --git a/src/RFC3339.h b/src/RFC3339.h new file mode 100644 index 0000000..5a8daa1 --- /dev/null +++ b/src/RFC3339.h @@ -0,0 +1,38 @@ +/* Copyright 2012 (C) Daniel Richman. License: GNU GPL 3; see LICENSE. */ + +#ifndef HABITAT_RFC3339_H + +#include +#include +#include + +using namespace std; + +namespace RFC3339 { + +class InvalidFormat : public invalid_argument +{ +public: + InvalidFormat() : invalid_argument("RFC3339::InvalidFormat") {}; + InvalidFormat(const string &what) : invalid_argument(what) {}; +}; + +/* + * This is basically a clone of habitat.utils.rfc3339, except all timestamps + * are time_t (may have restrictions on 32 bit, and they cannot be floats + * like they can in python) + * + * You should call tzset() before using either _localoffset function, since it + * calls localtime_r(). + */ + +bool validate_rfc3339(const string &rfc3339); +long long int rfc3339_to_timestamp(const string &rfc3339); +string timestamp_to_rfc3339_utcoffset(long long int timestamp); +string timestamp_to_rfc3339_localoffset(long long int timestamp); +string now_to_rfc3339_utcoffset(); +string now_to_rfc3339_localoffset(); + +} /* namespace RFC3339 */ + +#endif /* HABITAT_RFC3339_H */ diff --git a/tests/.gitignore b/tests/.gitignore index 7203990..142c883 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,2 +1,7 @@ test_extractor.pyc test_uploader.pyc +test_rfc3339.pyc +extractor +cpp_connector +cpp_connector_threaded +rfc3339 diff --git a/tests/test_rfc3339.py b/tests/test_rfc3339.py new file mode 100644 index 0000000..fd87c70 --- /dev/null +++ b/tests/test_rfc3339.py @@ -0,0 +1,276 @@ +# Copyright 2012 (C) Adam Greig, Daniel Richman +# +# This file is part of habitat. +# +# habitat 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. +# +# habitat 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 habitat. If not, see . + +import os +import time +import subprocess +import json +from nose import SkipTest + +# Identical to habitat's rfc3339 tests, except with a class that proxies +# calls to an executable and without the float tests. + +class ProxyFunction(object): + def __init__(self, process, name): + self.p = process + self.name = name + + def __call__(self, *args): + args = [self.name] + list(args) + self.p.stdin.write(json.dumps(args)) + self.p.stdin.write("\n") + return json.loads(self.p.stdout.readline()) + + +class ProxyFunctionModule(object): + # i.e., a module that contains functions only + def __init__(self, command, environ=None): + self.closed = False + self.p = subprocess.Popen(command, env=environ, + stdin=subprocess.PIPE, stdout=subprocess.PIPE) + + for n in ["validate_rfc3339", "rfc3339_to_timestamp", + "timestamp_to_rfc3339_utcoffset", + "timestamp_to_rfc3339_localoffset", + "now_to_rfc3339_utcoffset", + "now_to_rfc3339_localoffset"]: + setattr(self, n, ProxyFunction(self.p, n)) + + def close(self, quiet=False): + self.p.stdin.close() + ret = self.p.wait() + + if not quiet: + assert ret == 0 + + def __del__(self): + if not self.closed: + self.close(True) + + +class TestValidateRFC3339(object): + def setup(self): + self.mod = ProxyFunctionModule("tests/rfc3339") + self.validate = self.mod.validate_rfc3339 + + def teardown(self): + self.mod.close() + + def test_rejects_bad_format(self): + assert self.validate("asdf") == False + assert self.validate("24822") == False + assert self.validate("123-345-124T123:453:213") == False + assert self.validate("99-09-12T12:42:21Z") == False + assert self.validate("99-09-12T12:42:21+00:") == False + assert self.validate("99-09-12T12:42:21+00:00") == False + assert self.validate("2012-09-12T21:-1:21") == False + + def test_rejects_no_offset(self): + assert self.validate("2012-09-12T12:42:21") == False + + def test_rejects_out_of_range(self): + assert self.validate("2012-00-12T12:42:21Z") == False + assert self.validate("2012-13-12T12:42:21Z") == False + assert self.validate("2012-09-00T12:42:21Z") == False + assert self.validate("2012-09-31T12:42:21Z") == False # Sep + assert self.validate("2012-08-31T12:42:21Z") == True # Aug + assert self.validate("2012-08-32T12:42:21Z") == False + assert self.validate("2012-09-12T24:00:00Z") == False + assert self.validate("2012-09-12T12:60:21Z") == False + assert self.validate("2012-09-12T12:42:99Z") == False + assert self.validate("2012-09-12T12:42:21+24:00") == False + assert self.validate("2012-09-12T12:42:21-24:00") == False + assert self.validate("2012-09-12T12:42:21+02:60") == False + + def test_rejects_leap_seconds(self): + # with regret :-( + assert self.validate("2012-06-30T23:59:60Z") == False + assert self.validate("2012-03-21T09:21:60Z") == False + + def test_handles_leapyear(self): + assert self.validate("2012-02-29T12:42:21Z") == True + assert self.validate("2012-02-30T12:42:21Z") == False + assert self.validate("2000-02-29T12:42:21Z") == True + assert self.validate("2000-02-30T12:42:21Z") == False + assert self.validate("2100-02-28T12:42:21Z") == True + assert self.validate("2100-02-29T12:42:21Z") == False + assert self.validate("2011-02-28T12:42:21Z") == True + assert self.validate("2011-02-29T12:42:21Z") == False + + def test_accepts_good(self): + assert self.validate("1994-03-14T17:00:00Z") == True + assert self.validate("2011-06-23T17:12:00+05:21") == True + assert self.validate("1992-03-14T17:04:00-01:42") == True + + +class TestRFC3339toTimestamp(object): + def setup(self): + self.mod = ProxyFunctionModule("tests/rfc3339") + self.func = self.mod.rfc3339_to_timestamp + + def teardown(self): + self.mod.close() + + def test_simple_cases(self): + assert self.func("1996-12-19T16:39:57-08:00") == 851042397 + assert self.func("2012-08-08T21:30:36+01:00") == 1344457836 + assert self.func("1994-03-14T17:00:00Z") == 763664400 + assert self.func("1970-01-01T00:00:00Z") == 0 + assert self.func("1970-01-01T01:20:34Z") == 4834 + assert self.func("1969-12-31T23:59:59Z") == -1 + assert self.func("1969-12-31T22:51:12Z") == -4128 + + def test_y2038(self): + assert self.func("2100-01-01T00:00:00Z") == 4102444800 + assert self.func("1900-01-01T00:00:00Z") == -2208988800 + + def test_leap_year(self): + assert self.func("2012-02-29T12:42:21Z") == 1330519341 + assert self.func("2000-02-29T23:59:59Z") == 951868799 + assert self.func("2011-02-28T04:02:12Z") == 1298865732 + assert self.func("2100-02-28T00:00:00Z") == 4107456000 + assert self.func("1900-02-28T00:00:00Z") == -2203977600 + + def test_dst_transition(self): + assert self.func("2012-03-25T00:59:59+00:00") == 1332637199 + assert self.func("2012-03-25T02:00:00+01:00") == 1332637200 + assert self.func("2012-10-28T01:00:00+01:00") == 1351382400 + assert self.func("2012-10-28T01:00:00+00:00") == 1351386000 + + def test_wacky_offset(self): + assert self.func("1996-12-19T16:39:57-02:52") == 851042397 - 18480 + assert self.func("2012-08-08T21:30:36+23:11") == 1344457836 - 79860 + + def test_float(self): + assert self.func("1996-12-19T16:39:57.1234-08:00") == 851042397 + assert self.func("1996-12-20T00:39:57.004Z") == 851042397 + + +class TestTimestampToRFC3339UTCOffset(object): + def setup(self): + self.mod = ProxyFunctionModule("tests/rfc3339") + self.func = self.mod.timestamp_to_rfc3339_utcoffset + + def teardown(self): + self.mod.close() + def test_simple_cases(self): + assert self.func(851042397) == "1996-12-20T00:39:57Z" + assert self.func(1344457836) == "2012-08-08T20:30:36Z" + assert self.func(763664400) == "1994-03-14T17:00:00Z" + assert self.func(0) == "1970-01-01T00:00:00Z" + assert self.func(4834) == "1970-01-01T01:20:34Z" + assert self.func(-1) == "1969-12-31T23:59:59Z" + assert self.func(-4128) == "1969-12-31T22:51:12Z" + + def test_y2038(self): + try: + assert self.func(4102444800) == "2100-01-01T00:00:00Z" + assert self.func(-2208988800) == "1900-01-01T00:00:00Z" + except ValueError as e: + if str(e) == "timestamp out of range for platform time_t": + raise SkipTest("Can't run this test on 32 bit") + else: + raise + + def test_leap_year(self): + assert self.func(1330519341) == "2012-02-29T12:42:21Z" + assert self.func(951868799) == "2000-02-29T23:59:59Z" + assert self.func(1298865732) == "2011-02-28T04:02:12Z" + + try: + assert self.func(4107456000) == "2100-02-28T00:00:00Z" + assert self.func(-2203977600) == "1900-02-28T00:00:00Z" + except ValueError as e: + if str(e) == "timestamp out of range for platform time_t": + raise SkipTest("Can't run this test on 32 bit") + else: + raise + + def test_now(self): + s = self.mod.now_to_rfc3339_utcoffset() + assert s[-1] == "Z" + assert len(s) == 20 + d = int(time.time()) - self.mod.rfc3339_to_timestamp(s) + assert d == 0 or d == 1 + + +class TestTimestampToRFC3339LocalOffsetLondon(object): + def setup(self): + environ = os.environ.copy() + environ["TZ"] = "Europe/London" + + self.mod = ProxyFunctionModule("tests/rfc3339", environ=environ) + self.func = self.mod.timestamp_to_rfc3339_localoffset + + def teardown(self): + self.mod.close() + + def test_simple_cases(self): + assert self.func(851042397) == "1996-12-20T00:39:57+00:00" + assert self.func(1344457836) == "2012-08-08T21:30:36+01:00" + assert self.func(763664400) == "1994-03-14T17:00:00+00:00" + assert self.func(1340280000) == "2012-06-21T13:00:00+01:00" + assert self.func(1234) == "1970-01-01T01:20:34+01:00" + assert self.func(-7728) == "1969-12-31T22:51:12+01:00" + + def test_dst_transition(self): + assert self.func(1332637199) == "2012-03-25T00:59:59+00:00" + assert self.func(1332637200) == "2012-03-25T02:00:00+01:00" + assert self.func(1351382400) == "2012-10-28T01:00:00+01:00" + assert self.func(1351386000) == "2012-10-28T01:00:00+00:00" + + def test_now(self): + s = self.mod.now_to_rfc3339_localoffset() + w = self.mod.rfc3339_to_timestamp(s) + assert s[-6:] == ["+00:00", "+01:00"][time.localtime(w).tm_isdst] + + d = int(time.time()) - w + assert d == 0 or d == 1 + + +class TestTimestampToRFC3339LocalOffsetNewYork(object): + def setup(self): + environ = os.environ.copy() + environ["TZ"] = "America/New_York" + + self.mod = ProxyFunctionModule("tests/rfc3339", environ=environ) + self.func = self.mod.timestamp_to_rfc3339_localoffset + + def teardown(self): + self.mod.close() + + def test_simple_cases(self): + assert self.func(851042397) == "1996-12-19T19:39:57-05:00" + assert self.func(1344457836) == "2012-08-08T16:30:36-04:00" + assert self.func(761245200) == "1994-02-14T12:00:00-05:00" + assert self.func(1340280000) == "2012-06-21T08:00:00-04:00" + assert self.func(19234) == "1970-01-01T00:20:34-05:00" + assert self.func(-4128) == "1969-12-31T17:51:12-05:00" + + def test_dst_transition(self): + assert self.func(1331449199) == "2012-03-11T01:59:59-05:00" + assert self.func(1331449200) == "2012-03-11T03:00:00-04:00" + assert self.func(1352005200) == "2012-11-04T01:00:00-04:00" + assert self.func(1352008800) == "2012-11-04T01:00:00-05:00" + + def test_now(self): + s = self.mod.now_to_rfc3339_localoffset() + w = self.mod.rfc3339_to_timestamp(s) + assert s[-6:] == ["-05:00", "-04:00"][time.localtime(w).tm_isdst] + + d = int(time.time()) - w + assert d == 0 or d == 1 diff --git a/tests/test_rfc3339_main.cxx b/tests/test_rfc3339_main.cxx new file mode 100644 index 0000000..23ec96c --- /dev/null +++ b/tests/test_rfc3339_main.cxx @@ -0,0 +1,75 @@ +/* Copyright 2012 (C) Daniel Richman. License: GNU GPL 3; see LICENSE. */ + +#include +#include +#include +#include + +#include "RFC3339.h" + +using namespace std; + +void handle_command(const Json::Value &command); + +int main(int argc, char **argv) +{ + tzset(); + + for (;;) + { + char line[1024]; + cin.getline(line, 1024); + + if (line[0] == '\0') + break; + + Json::Reader reader; + Json::Value command; + + if (!reader.parse(line, command, false)) + throw runtime_error("JSON parsing failed"); + + if (!command.isArray() || !command[0u].isString()) + throw runtime_error("Invalid JSON input"); + + handle_command(command); + } +} + +void reply(const Json::Value &what) +{ + Json::FastWriter writer; + cout << writer.write(what); +} + +void handle_command(const Json::Value &command) +{ + string command_name = command[0u].asString(); + string string_arg; + long long int int_arg = 0; + + if (command_name == "validate_rfc3339" || + command_name == "rfc3339_to_timestamp") + { + string_arg = command[1u].asString(); + } + else + { + int_arg = command[1u].asLargestInt(); + } + + if (command_name == "validate_rfc3339") + reply(RFC3339::validate_rfc3339(string_arg)); + else if (command_name == "rfc3339_to_timestamp") + reply(RFC3339::rfc3339_to_timestamp(string_arg)); + else if (command_name == "timestamp_to_rfc3339_utcoffset") + reply(RFC3339::timestamp_to_rfc3339_utcoffset(int_arg)); + else if (command_name == "timestamp_to_rfc3339_localoffset") + reply(RFC3339::timestamp_to_rfc3339_localoffset(int_arg)); + else if (command_name == "now_to_rfc3339_utcoffset") + reply(RFC3339::now_to_rfc3339_utcoffset()); + else if (command_name == "now_to_rfc3339_localoffset") + reply(RFC3339::now_to_rfc3339_localoffset()); + else + throw runtime_error("Command not found"); +}