/** @file
 * @brief Parse and format date/time strings
 */
/* Copyright (c) 2013,2014,2015,2016,2019 Olly Betts
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include <config.h>

#include "datetime.h"

#include "timegm.h"

#include <algorithm>
#include <cstdlib>

using namespace std;

time_t
parse_datetime(const string & s)
{
    struct tm t;
    const char * p = s.c_str();
    char * q;
    if (s.find('T') != string::npos || s.find('-') != string::npos) {
	// E.g. "2013-01-17T09:10:55Z"
	t.tm_year = strtoul(p, &q, 10) - 1900;
	p = q;
	if (*p == '-') {
	    t.tm_mon = strtoul(p + 1, &q, 10) - 1;
	    p = q;
	} else {
	    t.tm_mon = 0;
	}
	if (*p == '-') {
	    t.tm_mday = strtoul(p + 1, &q, 10);
	    p = q;
	} else {
	    t.tm_mday = 1;
	}
	if (*p == 'T') {
	    t.tm_hour = strtoul(p + 1, &q, 10);
	    p = q;
	    if (*p == ':') {
		t.tm_min = strtoul(p + 1, &q, 10);
		p = q;
	    } else {
		t.tm_min = 0;
	    }
	    if (*p == ':') {
		t.tm_sec = strtoul(p + 1, &q, 10);
		p = q;
	    } else {
		t.tm_sec = 0;
	    }
	} else {
	    t.tm_hour = t.tm_min = t.tm_sec = 0;
	}
	if (*p == 'Z') {
	    // FIXME: always assume UTC for now...
	}
    } else {
	// As produced by LibreOffice HTML export.
	// E.g.
	// "20130117;09105500" == 2013-01-17T09:10:55
	// "20070903;200000000000" == 2007-09-03T00:02:00
	// "20070831;5100000000000" == 2007-08-31T00:51:00
	unsigned long v = strtoul(p, &q, 10);
	if (v == 0) {
	    // LibreOffice sometimes exports "0;0".  A date of "0" is
	    // clearly invalid.
	    return time_t(-1);
	}
	p = q;
	t.tm_mday = v % 100;
	v /= 100;
	t.tm_mon = v % 100 - 1;
	t.tm_year = v / 100 - 1900;
	if (*p == ';') {
	    ++p;
	    v = strtoul(p, &q, 10);
	    v /= (q - p > 10) ? 1000000000 : 100;
	    t.tm_sec = v % 100;
	    v /= 100;
	    t.tm_min = v % 100;
	    t.tm_hour = v / 100;
	} else {
	    t.tm_hour = t.tm_min = t.tm_sec = 0;
	}
    }
    t.tm_isdst = -1;

    return timegm(&t);
}

// Write exactly w chars to buffer p representing integer v.
//
// The result is left padded with zeros if v < pow(10, w - 1).
//
// If v >= pow(10, w), then the output will show v % pow(10, w) (i.e. the
// most significant digits are lost).
static void
format_int_fixed_width(char* p, int v, int w)
{
    while (--w >= 0) {
	p[w] = '0' + (v % 10);
	v /= 10;
    }
}

string
date_to_string(int year, int month, int day)
{
    year = std::clamp(year, 0, 9999);
    month = std::clamp(month, 1, 12);
    day = std::clamp(day, 1, 31);
    char buf[8];
    format_int_fixed_width(buf, year, 4);
    format_int_fixed_width(buf + 4, month, 2);
    format_int_fixed_width(buf + 6, day, 2);
    return string(buf, 8);
}
