ISO8601.cxx 4.11 KB
Newer Older
1
/*
2
 * Copyright 2007-2019 Content Management AG
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
 * All rights reserved.
 *
 * author: Max Kellermann <mk@cm4all.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

33 34
#include "ISO8601.hxx"
#include "Convert.hxx"
35
#include "util/StringBuffer.hxx"
36

37 38 39 40
#include <stdexcept>

#include <assert.h>

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
StringBuffer<64>
FormatISO8601(const struct tm &tm) noexcept
{
	StringBuffer<64> buffer;
	strftime(buffer.data(), buffer.capacity(),
#ifdef _WIN32
		 "%Y-%m-%dT%H:%M:%SZ",
#else
		 "%FT%TZ",
#endif
		 &tm);
	return buffer;
}

StringBuffer<64>
FormatISO8601(std::chrono::system_clock::time_point tp)
{
	return FormatISO8601(GmTime(tp));
}

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
static std::pair<unsigned, unsigned>
ParseTimeZoneOffsetRaw(const char *&s)
{
	char *endptr;
	unsigned long value = strtoul(s, &endptr, 10);
	if (endptr == s + 4) {
		s = endptr;
		return std::make_pair(value / 100, value % 100);
	} else if (endptr == s + 2) {
		s = endptr;

		unsigned hours = value, minutes = 0;
		if (*s == ':') {
			++s;
			minutes = strtoul(s, &endptr, 10);
			if (endptr != s + 2)
				throw std::runtime_error("Failed to parse time zone offset");

			s = endptr;
		}

		return std::make_pair(hours, minutes);
	} else
		throw std::runtime_error("Failed to parse time zone offset");
}

static std::chrono::system_clock::duration
ParseTimeZoneOffset(const char *&s)
{
	assert(*s == '+' || *s == '-');

	bool negative = *s == '-';
	++s;

	auto raw = ParseTimeZoneOffsetRaw(s);
	if (raw.first > 13)
		throw std::runtime_error("Time offset hours out of range");

	if (raw.second >= 60)
		throw std::runtime_error("Time offset minutes out of range");

	std::chrono::system_clock::duration d = std::chrono::hours(raw.first);
	d += std::chrono::minutes(raw.second);

	if (negative)
		d = -d;

	return d;
}

111 112
std::pair<std::chrono::system_clock::time_point,
	  std::chrono::system_clock::duration>
113 114
ParseISO8601(const char *s)
{
115 116 117 118 119 120 121 122 123
	assert(s != nullptr);

#ifdef _WIN32
	/* TODO: emulate strptime()? */
	(void)s;
	throw std::runtime_error("Time parsing not implemented on Windows");
#else
	struct tm tm{};

124 125 126 127 128 129 130 131 132 133 134 135
	/* parse the date */
	const char *end = strptime(s, "%F", &tm);
	if (end == nullptr)
		throw std::runtime_error("Failed to parse date");

	s = end;

	std::chrono::system_clock::duration precision = std::chrono::hours(24);

	/* parse the time of day */
	if (*s == 'T') {
		++s;
136 137 138 139 140

		if ((end = strptime(s, "%T", &tm)) != nullptr)
			precision = std::chrono::seconds(1);
		else if ((end = strptime(s, "%H:%M", &tm)) != nullptr)
			precision = std::chrono::minutes(1);
141 142
		else if ((end = strptime(s, "%H", &tm)) != nullptr)
			precision = std::chrono::hours(1);
143
		else
144 145 146 147
			throw std::runtime_error("Failed to parse time of day");

		s = end;
	}
148 149 150

	auto tp = TimeGm(tm);

151
	/* time zone */
152 153
	if (*s == 'Z')
		++s;
154 155
	else if (*s == '+' || *s == '-')
		tp -= ParseTimeZoneOffset(s);
156

157 158 159
	if (*s != 0)
		throw std::runtime_error("Garbage at end of time stamp");

160
	return std::make_pair(tp, precision);
161
#endif /* !_WIN32 */
162
}