Compare commits

...

42 Commits

Author SHA1 Message Date
Max Kellermann
5680a3a4b7 release v0.21.17 2019-12-16 23:32:44 +01:00
Max Kellermann
15ce8eb487 time/ISO8601: support omitting field separators
Closes https://github.com/MusicPlayerDaemon/MPD/issues/685
2019-12-16 23:31:29 +01:00
Max Kellermann
b7744be208 song/Filter: try ParseISO8601() first
Prepare for allowing ISO8601 time stamps without delimiters, such as
20191216, and prevent them from being interpreted as numeric UNIX time
stamps.
2019-12-16 23:31:29 +01:00
Max Kellermann
63c5d66016 time/ISO8601: support omitting minutes 2019-12-16 23:24:43 +01:00
Max Kellermann
d09bd9178f time/ISO8601: support omitting seconds 2019-12-16 23:24:43 +01:00
Max Kellermann
7d8b1860c3 time/ISO8601: support time zone offset 2019-12-16 23:24:43 +01:00
Max Kellermann
b06825829b time/ISO8601: allow omitting the "Z" suffix
And allow "Z" suffix after date.
2019-12-16 23:24:43 +01:00
Max Kellermann
ba4cd47fd8 time/ISO8601: allow omitting the time of day 2019-12-16 23:24:43 +01:00
Max Kellermann
bbe403f141 test/TestISO8601: unit test for time/ISO8601 2019-12-16 23:24:43 +01:00
Max Kellermann
5df2707d98 time/ISO8601: ParseISO8601() returns precision 2019-12-16 23:24:43 +01:00
Max Kellermann
4859ea468f time/ISO8601: implement with strptime(), without ParseTimePoint()
Prepare for adding more flexible parsing.
2019-12-16 23:24:43 +01:00
Max Kellermann
2a8830db70 time/Convert: fallback TimeGm() implementation
Move code from Parser.cxx.
2019-12-16 23:24:38 +01:00
Max Kellermann
fed9b6fd74 time/Parser: use TimeGm() 2019-12-16 23:03:31 +01:00
Max Kellermann
b02890eb8a time/Parser: explicitly initialize struct tm before strptime()
This is recommended by the strptime() manpage, because strptime() does
not initialize/set attributes which were not specified in the format
string.
2019-12-16 23:03:25 +01:00
Max Kellermann
da882a6eb6 time/Convert: include sys/time.h for struct timeval
Closes https://github.com/MusicPlayerDaemon/MPD/issues/562
2019-12-16 23:03:21 +01:00
Max Kellermann
aeb89aa9d6 time/ISO8601: forward-declare StringBuffer 2019-12-16 23:02:59 +01:00
Max Kellermann
f885807ecc time/Convert: update copyright 2019-12-16 23:02:58 +01:00
Max Kellermann
b826fd71f0 time/Convert: mention exceptions 2019-12-16 23:02:57 +01:00
Max Kellermann
ae35df1126 zeroconf/AvahiPoll: move TimevalToChrono() to time/Convert.cxx 2019-12-16 23:02:45 +01:00
Max Kellermann
80e55f6bfc time/Convert: add noexcept 2019-12-16 23:02:40 +01:00
Max Kellermann
e7411c0c4b time/Convert: add pure attributes 2019-12-16 23:02:38 +01:00
Max Kellermann
e9af692973 util/Time*: move to time/ 2019-12-16 23:02:14 +01:00
Max Kellermann
0cf90ee8b6 decoder/mad: work around bogus -Wuninitialized in GCC 10 2019-12-16 23:01:31 +01:00
Max Kellermann
dc3c0c8866 pcm/Convert, ...: add missing include for std::runtime_error 2019-12-16 22:52:50 +01:00
Max Kellermann
1c46bb1ba6 lib/gcrypt/MD5: add missing include for uint8_t 2019-12-16 22:52:22 +01:00
Max Kellermann
2e8f42c6ad util/StringBuffer: use std::size_t instead of size_t 2019-12-16 22:51:23 +01:00
Jacob Vosmaer
2b301ffd2c lib/xiph: add missing meson dependency 2019-12-16 17:11:14 +01:00
Jacob Vosmaer
ef0765ca10 input: add missing boost meson dependency 2019-12-16 17:11:14 +01:00
Naglis Jonaitis
9766ac6db3 Fix typo in documentation 2019-12-16 17:09:45 +01:00
Max Kellermann
32799ff682 archive/zzip: improve error reporting
Most importantly, this commit translates ZZIP_ENOENT to
std::system_error(ENOENT) so IsFileNotFound() returns true and
find_stream_art() can suppress the log line.
2019-12-04 12:33:42 +01:00
Max Kellermann
ce093be12c system/Error: add FormatFileNotFound() 2019-12-04 12:33:38 +01:00
Max Kellermann
2c276770f0 util/PrintException, ...: update copyright 2019-12-04 12:33:36 +01:00
Max Kellermann
75a592f629 system/Error: move code to IsErrno() 2019-12-04 12:33:33 +01:00
Max Kellermann
13ce07d181 output/shout: declare metadata as UTF-8
Apparently, Icecast defaults to ISO-8859-1 for MP3:

 http://icecast.org/docs/icecast-2.4.0/config-file.html#mountsettings

This change forces Icecast to UTF-8 without having to configure it in
Icecast's configuration file.
2019-11-06 16:05:22 +01:00
Max Kellermann
d659c7df19 python/build/libs: update CURL to 7.66.0 2019-11-04 14:27:00 +01:00
Max Kellermann
f8403a1d29 python/build/libs: update FFmpeg to 4.2.1 2019-11-04 14:26:05 +01:00
Max Kellermann
ebb952c4ad neighbor/meson.build: disable if -Ddatabase=false
Fixes yet another build failure (which however only affects the 0.22
branch).

Closes https://github.com/MusicPlayerDaemon/MPD/issues/666
2019-11-04 14:18:48 +01:00
Max Kellermann
bea3b954a5 meson.build, SongUpdate: disable db-specific archive code if -Ddatabase=false
Fixes another build failure.
2019-11-04 14:15:48 +01:00
Max Kellermann
129d8e89b9 lib/sqlite: disable if -Ddatabase=false
Fixes build failure in StickerCommands.cxx.
2019-11-04 14:10:03 +01:00
Niklas Haas
65778a3774 output/Jack: mark ports as terminal
This is the correct thing to do for ports from which a signal ultimately
"originates", such as is the case with mpd.
2019-10-29 10:07:44 +01:00
kaliko
d9841668ff doc/user.rst: update build dependencies on Debian Buster 2019-10-28 15:20:05 +01:00
Max Kellermann
85d27cbcb9 increment version number to 0.21.17 2019-10-16 14:20:28 +02:00
70 changed files with 530 additions and 201 deletions

11
NEWS

@@ -1,3 +1,14 @@
ver 0.21.17 (2019/12/16)
* protocol
- relax the ISO 8601 parser: allow omitting field separators, the
time of day and the "Z" suffix
* archive
- zzip: improve error reporting
* outputs
- jack: mark ports as terminal
- shout: declare metadata as UTF-8
* fix build failure with -Ddatabase=false
ver 0.21.16 (2019/10/16)
* queue
- fix relative destination offset when moving a range

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="39"
android:versionName="0.21.16">
android:versionCode="40"
android:versionName="0.21.17">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>

@@ -38,7 +38,7 @@ author = 'Max Kellermann'
# built documents.
#
# The short X.Y version.
version = '0.21.16'
version = '0.21.17'
# The full version, including alpha/beta/rc tags.
release = version

@@ -42,7 +42,7 @@ Provides access to the database of another :program:`MPD` instance using libmpdc
* - **password**
- The password used to log in to the "master" :program:`MPD` instance.
* - **keepalive yes|no**
- Send TCP keepalive packets to the "master" :program:`MPD` instance? This option can help avoid certain firewalls dropping inactive connections, at the expensive of a very small amount of additional network traffic. Disabled by default.
- Send TCP keepalive packets to the "master" :program:`MPD` instance? This option can help avoid certain firewalls dropping inactive connections, at the expense of a very small amount of additional network traffic. Disabled by default.
upnp
----
@@ -1069,7 +1069,7 @@ Filter plugins
normalize
---------
Normalize the volume during playback (at the expensve of quality).
Normalize the volume during playback (at the expense of quality).
null

@@ -62,16 +62,16 @@ In any case, you need:
Each plugin usually needs a codec library, which you also need to
install. Check the :doc:`plugins` for details about required libraries
For example, the following installs a fairly complete list of build dependencies on Debian Jessie:
For example, the following installs a fairly complete list of build dependencies on Debian Buster:
.. code-block:: none
apt install g++ \
apt install meson g++ \
libpcre3-dev \
libmad0-dev libmpg123-dev libid3tag0-dev \
libflac-dev libvorbis-dev libopus-dev \
libflac-dev libvorbis-dev libopus-dev libogg-dev \
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
libfluidsynth-dev libgme-dev libmikmod2-dev libmodplug-dev \
libfluidsynth-dev libgme-dev libmikmod-dev libmodplug-dev \
libmpcdec-dev libwavpack-dev libwildmidi-dev \
libsidplay2-dev libsidutils-dev libresid-builder-dev \
libavcodec-dev libavformat-dev \
@@ -91,7 +91,9 @@ For example, the following installs a fairly complete list of build dependencies
libsystemd-dev \
libgtest-dev \
libboost-dev \
libicu-dev
libicu-dev \
libchromaprint-dev \
libgcrypt20-dev
Now configure the source tree:

@@ -1,7 +1,7 @@
project(
'mpd',
['c', 'cpp'],
version: '0.21.16',
version: '0.21.17',
meson_version: '>= 0.49.0',
default_options: [
'c_std=c99',
@@ -304,6 +304,7 @@ if enable_database
endif
subdir('src/util')
subdir('src/time')
subdir('src/system')
subdir('src/thread')
subdir('src/event')
@@ -385,8 +386,11 @@ endif
if archive_glue_dep.found()
sources += [
'src/TagArchive.cxx',
'src/db/update/Archive.cxx',
]
if enable_database
sources += ['src/db/update/Archive.cxx']
endif
endif
if is_windows

@@ -112,8 +112,8 @@ liblame = AutotoolsProject(
)
ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-4.2.tar.xz',
'023f10831a97ad93d798f53a3640e55cd564abfeba807ecbe8524dac4fedecd5',
'http://ffmpeg.org/releases/ffmpeg-4.2.1.tar.xz',
'cec7c87e9b60d174509e263ac4011b522385fd0775292e1670ecc1180c9bb6d4',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
)
curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.65.3.tar.xz',
'f2d98854813948d157f6a91236ae34ca4a1b4cb302617cebad263d79b0235fea',
'http://curl.haxx.se/download/curl-7.66.0.tar.xz',
'dbb48088193016d079b97c5c3efde8efa56ada2ebf336e8a97d04eb8e2ed98c1',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',

@@ -29,6 +29,8 @@
#include "storage/StorageInterface.hxx"
#endif
#include <stdexcept>
static LocatedUri
LocateFileUri(const char *uri, const Client *client
#ifdef ENABLE_DATABASE

@@ -21,8 +21,8 @@
#include "db/PlaylistVector.hxx"
#include "fs/io/TextFile.hxx"
#include "fs/io/BufferedOutputStream.hxx"
#include "time/ChronoUtil.hxx"
#include "util/StringStrip.hxx"
#include "util/ChronoUtil.hxx"
#include "util/RuntimeError.hxx"
#include <string.h>

@@ -27,7 +27,7 @@
#include "TagPrint.hxx"
#include "client/Response.hxx"
#include "fs/Traits.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx"
#define SONG_FILE "file: "

@@ -27,7 +27,7 @@
#include "tag/ParseName.hxx"
#include "tag/Tag.hxx"
#include "tag/Builder.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include "util/StringAPI.hxx"
#include "util/StringBuffer.hxx"
#include "util/StringStrip.hxx"

@@ -98,8 +98,6 @@ Song::UpdateFile(Storage &storage) noexcept
return true;
}
#endif
#ifdef ENABLE_ARCHIVE
Song *
@@ -145,6 +143,8 @@ Song::UpdateFileInArchive(ArchiveFile &archive) noexcept
#endif
#endif /* ENABLE_DATABASE */
bool
DetachedSong::LoadFile(Path path) noexcept
{

@@ -28,7 +28,7 @@
#include "db/Stats.hxx"
#include "system/Clock.hxx"
#include "Log.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include <chrono>
#include <cmath>

@@ -19,7 +19,7 @@
#include "TimePrint.hxx"
#include "client/Response.hxx"
#include "util/TimeISO8601.hxx"
#include "time/ISO8601.hxx"
void
time_print(Response &r, const char *name,

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,6 +27,7 @@
#include "../ArchiveVisitor.hxx"
#include "input/InputStream.hxx"
#include "fs/Path.hxx"
#include "system/Error.hxx"
#include "util/RuntimeError.hxx"
#include <zzip/zzip.h>
@@ -120,9 +121,19 @@ ZzipArchiveFile::OpenStream(const char *pathname,
Mutex &mutex)
{
ZZIP_FILE *_file = zzip_file_open(dir->dir, pathname, 0);
if (_file == nullptr)
throw FormatRuntimeError("not found in the ZIP file: %s",
pathname);
if (_file == nullptr) {
const auto error = (zzip_error_t)zzip_error(dir->dir);
switch (error) {
case ZZIP_ENOENT:
throw FormatFileNotFound("Failed to open '%s' in ZIP file",
pathname);
default:
throw FormatRuntimeError("Failed to open '%s' in ZIP file: %s",
pathname,
zzip_strerror(error));
}
}
return std::make_unique<ZzipInputStream>(dir, pathname,
mutex,

@@ -35,7 +35,7 @@
#include "decoder/DecoderPrint.hxx"
#include "ls.hxx"
#include "mixer/Volume.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx"
#include "util/StringAPI.hxx"
#include "fs/AllocatedPath.hxx"

@@ -37,9 +37,9 @@
#include "client/Response.hxx"
#include "Mapper.hxx"
#include "fs/AllocatedPath.hxx"
#include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx"
#include "util/ConstBuffer.hxx"
#include "util/ChronoUtil.hxx"
#include "LocateUri.hxx"
bool

@@ -23,8 +23,8 @@
#include "StorageCommands.hxx"
#include "Request.hxx"
#include "CommandError.hxx"
#include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx"
#include "util/ChronoUtil.hxx"
#include "util/ConstBuffer.hxx"
#include "fs/Traits.hxx"
#include "client/Client.hxx"

@@ -34,7 +34,7 @@
#include "PlaylistInfo.hxx"
#include "Interface.hxx"
#include "fs/Traits.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include "util/RecursiveMap.hxx"
#include <functional>

@@ -25,7 +25,7 @@
#include "PlaylistDatabase.hxx"
#include "fs/io/TextFile.hxx"
#include "fs/io/BufferedOutputStream.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include "util/StringCompare.hxx"
#include "util/NumberParser.hxx"
#include "util/RuntimeError.hxx"

@@ -719,6 +719,11 @@ MadDecoder::DecodeFirstFrame(Tag *tag) noexcept
{
struct xing xing;
#if GCC_CHECK_VERSION(10,0)
/* work around bogus -Wuninitialized in GCC 10 */
xing.frames = 0;
#endif
while (true) {
MadDecoderAction ret;
do {

@@ -35,6 +35,9 @@ input_glue = static_library(
'BufferedInputStream.cxx',
'MaybeBufferedInputStream.cxx',
include_directories: inc,
dependencies: [
boost_dep,
],
)
input_glue_dep = declare_dependency(

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2016 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2016 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2016-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -33,6 +33,7 @@
#include <curl/curl.h>
#include <algorithm>
#include <stdexcept>
/**
* OO wrapper for "struct curl_slist *".

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2017-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2017-2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -35,6 +35,8 @@
#include <array>
#include <stdint.h>
template<typename T> struct ConstBuffer;
gcc_pure

@@ -25,6 +25,7 @@
#include "util/ASCII.hxx"
#include <utility>
#include <stdexcept>
#include <assert.h>
#include <string.h>

@@ -1,4 +1,9 @@
sqlite_dep = dependency('sqlite3', version: '>= 3.7.3', required: get_option('sqlite'))
if enable_database
sqlite_dep = dependency('sqlite3', version: '>= 3.7.3', required: get_option('sqlite'))
else
sqlite_dep = dependency('', required: false)
endif
conf.set('ENABLE_SQLITE', sqlite_dep.found())
if not sqlite_dep.found()
subdir_done()

@@ -49,6 +49,9 @@ xiph = static_library(
'VorbisComments.cxx',
'XiphTags.cxx',
include_directories: inc,
dependencies: [
libvorbis_dep,
],
)
xiph_dep = declare_dependency(

@@ -1,4 +1,4 @@
if not get_option('neighbor')
if not get_option('neighbor') or not enable_database
conf.set('ENABLE_NEIGHBOR_PLUGINS', false)
neighbor_glue_dep = dependency('', required: false)
subdir_done()

@@ -31,6 +31,7 @@
#define STATIC_SOCKET_ADDRESS_HXX
#include "SocketAddress.hxx"
#include "Features.hxx"
#include "util/Compiler.h"
#include <assert.h>

@@ -403,10 +403,11 @@ JackOutput::Connect()
jack_on_shutdown(client, mpd_jack_shutdown, this);
for (unsigned i = 0; i < num_source_ports; ++i) {
unsigned long portflags = JackPortIsOutput | JackPortIsTerminal;
ports[i] = jack_port_register(client,
source_ports[i].c_str(),
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
portflags, 0);
if (ports[i] == nullptr) {
Disconnect();
throw FormatRuntimeError("Cannot register output port \"%s\"",

@@ -383,6 +383,7 @@ ShoutOutput::SendTag(const Tag &tag)
shout_tag_to_metadata(tag, song, sizeof(song));
shout_metadata_add(meta, "song", song);
shout_metadata_add(meta, "charset", "UTF-8");
if (SHOUTERR_SUCCESS != shout_set_metadata(shout_conn, meta)) {
LogWarning(shout_output_domain,
"error setting shout metadata");

@@ -21,6 +21,8 @@
#include "ConfiguredResampler.hxx"
#include "util/ConstBuffer.hxx"
#include <stdexcept>
#include <assert.h>
void

@@ -29,8 +29,9 @@
#include "AudioParser.hxx"
#include "tag/ParseName.hxx"
#include "tag/Tag.hxx"
#include "time/ChronoUtil.hxx"
#include "time/ISO8601.hxx"
#include "util/CharUtil.hxx"
#include "util/ChronoUtil.hxx"
#include "util/ConstBuffer.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringAPI.hxx"
@@ -38,7 +39,6 @@
#include "util/StringStrip.hxx"
#include "util/StringView.hxx"
#include "util/ASCII.hxx"
#include "util/TimeISO8601.hxx"
#include "util/UriUtil.hxx"
#include "lib/icu/CaseFold.hxx"
@@ -113,14 +113,19 @@ ParseTimeStamp(const char *s)
{
assert(s != nullptr);
char *endptr;
unsigned long long value = strtoull(s, &endptr, 10);
if (*endptr == 0 && endptr > s)
/* it's an integral UNIX time stamp */
return std::chrono::system_clock::from_time_t((time_t)value);
try {
/* try ISO 8601 */
return ParseISO8601(s).first;
} catch (...) {
char *endptr;
unsigned long long value = strtoull(s, &endptr, 10);
if (*endptr == 0 && endptr > s)
/* it's an integral UNIX time stamp */
return std::chrono::system_clock::from_time_t((time_t)value);
/* try ISO 8601 */
return ParseISO8601(s);
/* rethrow the ParseISO8601() error */
throw;
}
}
static constexpr bool

@@ -19,7 +19,8 @@
#include "ModifiedSinceSongFilter.hxx"
#include "LightSong.hxx"
#include "util/TimeISO8601.hxx"
#include "time/ISO8601.hxx"
#include "util/StringBuffer.hxx"
std::string
ModifiedSinceSongFilter::ToExpression() const noexcept

@@ -24,6 +24,7 @@ song_dep = declare_dependency(
icu_dep,
pcre_dep,
tag_dep,
time_dep,
util_dep,
],
)

@@ -35,12 +35,13 @@
#include "event/DeferEvent.hxx"
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
#include "time/ChronoUtil.hxx"
#include "time/Parser.hxx"
#include "util/ASCII.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx"
#include "util/StringFormat.hxx"
#include "util/TimeParser.hxx"
#include "util/UriUtil.hxx"
#include <algorithm>

@@ -57,5 +57,6 @@ storage_plugins_dep = declare_dependency(
dependencies: [
storage_api_dep,
fs_dep,
time_dep,
],
)

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2013-2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013-2015 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2013-2015 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -147,6 +147,26 @@ FormatErrno(const char *fmt, Args&&... args) noexcept
return FormatErrno(errno, fmt, std::forward<Args>(args)...);
}
template<typename... Args>
static inline std::system_error
FormatFileNotFound(const char *fmt, Args&&... args) noexcept
{
#ifdef _WIN32
return FormatLastError(ERROR_FILE_NOT_FOUND, fmt,
std::forward<Args>(args)...);
#else
return FormatErrno(ENOENT, fmt, std::forward<Args>(args)...);
#endif
}
gcc_pure
inline bool
IsErrno(const std::system_error &e, int code) noexcept
{
return e.code().category() == ErrnoCategory() &&
e.code().value() == code;
}
gcc_pure
static inline bool
IsFileNotFound(const std::system_error &e) noexcept
@@ -155,8 +175,7 @@ IsFileNotFound(const std::system_error &e) noexcept
return e.code().category() == std::system_category() &&
e.code().value() == ERROR_FILE_NOT_FOUND;
#else
return e.code().category() == ErrnoCategory() &&
e.code().value() == ENOENT;
return IsErrno(e, ENOENT);
#endif
}
@@ -168,8 +187,7 @@ IsPathNotFound(const std::system_error &e) noexcept
return e.code().category() == std::system_category() &&
e.code().value() == ERROR_PATH_NOT_FOUND;
#else
return e.code().category() == ErrnoCategory() &&
e.code().value() == ENOTDIR;
return IsErrno(e, ENOTDIR);
#endif
}
@@ -181,8 +199,7 @@ IsAccessDenied(const std::system_error &e) noexcept
return e.code().category() == std::system_category() &&
e.code().value() == ERROR_ACCESS_DENIED;
#else
return e.code().category() == ErrnoCategory() &&
e.code().value() == EACCES;
return IsErrno(e, EACCES);
#endif
}

@@ -20,9 +20,9 @@
#include "Format.hxx"
#include "Tag.hxx"
#include "ParseName.hxx"
#include "time/Convert.hxx"
#include "util/format.h"
#include "util/TruncateString.hxx"
#include "util/TimeConvert.hxx"
#include <algorithm>

@@ -48,6 +48,7 @@ tag = static_library(
tag_dep = declare_dependency(
link_with: tag,
dependencies: [
time_dep,
util_dep,
],
)

@@ -1,5 +1,5 @@
/*
* Copyright 2007-2017 Content Management AG
* Copyright 2007-2019 Content Management AG
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.com>
@@ -30,11 +30,12 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "TimeConvert.hxx"
#include "Convert.hxx"
#include <stdexcept>
#include <time.h>
#include <sys/time.h> /* for struct timeval */
struct tm
GmTime(std::chrono::system_clock::time_point tp)
@@ -66,18 +67,52 @@ LocalTime(std::chrono::system_clock::time_point tp)
return *tm;
}
#ifdef __GLIBC__
#ifndef __GLIBC__
std::chrono::system_clock::time_point
TimeGm(struct tm &tm)
/**
* Determine the time zone offset in a portable way.
*/
gcc_const
static time_t
GetTimeZoneOffset() noexcept
{
return std::chrono::system_clock::from_time_t(timegm(&tm));
time_t t = 1234567890;
struct tm tm;
tm.tm_isdst = 0;
#ifdef _WIN32
struct tm *p = gmtime(&t);
#else
struct tm *p = &tm;
gmtime_r(&t, p);
#endif
return t - mktime(&tm);
}
#endif
#endif /* !__GLIBC__ */
std::chrono::system_clock::time_point
MakeTime(struct tm &tm)
TimeGm(struct tm &tm) noexcept
{
#ifdef __GLIBC__
/* timegm() is a GNU extension */
const auto t = timegm(&tm);
#else
tm.tm_isdst = 0;
const auto t = mktime(&tm) + GetTimeZoneOffset();
#endif /* !__GLIBC__ */
return std::chrono::system_clock::from_time_t(t);
}
std::chrono::system_clock::time_point
MakeTime(struct tm &tm) noexcept
{
return std::chrono::system_clock::from_time_t(mktime(&tm));
}
std::chrono::steady_clock::duration
ToSteadyClockDuration(const struct timeval &tv) noexcept
{
return std::chrono::steady_clock::duration(std::chrono::seconds(tv.tv_sec)) +
std::chrono::steady_clock::duration(std::chrono::microseconds(tv.tv_usec));
}

@@ -1,5 +1,5 @@
/*
* Copyright 2007-2017 Content Management AG
* Copyright 2007-2019 Content Management AG
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.com>
@@ -33,34 +33,42 @@
#ifndef TIME_CONVERT_HXX
#define TIME_CONVERT_HXX
#include "util/Compiler.h"
#include <chrono>
/**
* Convert a UTC-based time point to a UTC-based "struct tm".
*
* Throws on error.
*/
struct tm
GmTime(std::chrono::system_clock::time_point tp);
/**
* Convert a UTC-based time point to a local "struct tm".
*
* Throws on error.
*/
struct tm
LocalTime(std::chrono::system_clock::time_point tp);
#ifdef __GLIBC__
/**
* Convert a UTC-based "struct tm" to a UTC-based time point.
*/
gcc_pure
std::chrono::system_clock::time_point
TimeGm(struct tm &tm);
#endif
TimeGm(struct tm &tm) noexcept;
/**
* Convert a local "struct tm" to a UTC-based time point.
*/
gcc_pure
std::chrono::system_clock::time_point
MakeTime(struct tm &tm);
MakeTime(struct tm &tm) noexcept;
gcc_pure
std::chrono::steady_clock::duration
ToSteadyClockDuration(const struct timeval &tv) noexcept;
#endif

172
src/time/ISO8601.cxx Normal file

@@ -0,0 +1,172 @@
/*
* Copyright 2007-2019 Content Management AG
* 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.
*/
#include "ISO8601.hxx"
#include "Convert.hxx"
#include "util/StringBuffer.hxx"
#include <stdexcept>
#include <assert.h>
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));
}
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;
}
std::pair<std::chrono::system_clock::time_point,
std::chrono::system_clock::duration>
ParseISO8601(const char *s)
{
assert(s != nullptr);
#ifdef _WIN32
/* TODO: emulate strptime()? */
(void)s;
throw std::runtime_error("Time parsing not implemented on Windows");
#else
struct tm tm{};
/* parse the date */
const char *end = strptime(s, "%F", &tm);
if (end == nullptr) {
/* try without field separators */
end = strptime(s, "%Y%m%d", &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;
if ((end = strptime(s, "%T", &tm)) != nullptr)
precision = std::chrono::seconds(1);
else if ((end = strptime(s, "%H%M%S", &tm)) != nullptr)
/* no field separators */
precision = std::chrono::seconds(1);
else if ((end = strptime(s, "%H%M", &tm)) != nullptr)
/* no field separators */
precision = std::chrono::minutes(1);
else if ((end = strptime(s, "%H:%M", &tm)) != nullptr)
precision = std::chrono::minutes(1);
else if ((end = strptime(s, "%H", &tm)) != nullptr)
precision = std::chrono::hours(1);
else
throw std::runtime_error("Failed to parse time of day");
s = end;
}
auto tp = TimeGm(tm);
/* time zone */
if (*s == 'Z')
++s;
else if (*s == '+' || *s == '-')
tp -= ParseTimeZoneOffset(s);
if (*s != 0)
throw std::runtime_error("Garbage at end of time stamp");
return std::make_pair(tp, precision);
#endif /* !_WIN32 */
}

@@ -1,5 +1,5 @@
/*
* Copyright 2007-2017 Content Management AG
* Copyright 2007-2019 Content Management AG
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.com>
@@ -33,13 +33,15 @@
#ifndef TIME_ISO8601_HXX
#define TIME_ISO8601_HXX
#include "StringBuffer.hxx"
#include "Compiler.h"
#include "util/Compiler.h"
#include <string>
#include <chrono>
#include <utility>
#include <stddef.h>
struct tm;
template<size_t CAPACITY> class StringBuffer;
gcc_pure
StringBuffer<64>
@@ -49,7 +51,16 @@ gcc_pure
StringBuffer<64>
FormatISO8601(std::chrono::system_clock::time_point tp);
std::chrono::system_clock::time_point
/**
* Parse a time stamp in ISO8601 format.
*
* Throws on error.
*
* @return a pair consisting of the time point and the specified
* precision; e.g. for a date, the second value is "one day"
*/
std::pair<std::chrono::system_clock::time_point,
std::chrono::system_clock::duration>
ParseISO8601(const char *s);
#endif

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2014-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2014-2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -27,32 +27,14 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "TimeParser.hxx"
#include "Compiler.h"
#include "Parser.hxx"
#include "Convert.hxx"
#include <stdexcept>
#include <assert.h>
#include <time.h>
#if !defined(__GLIBC__) && !defined(_WIN32)
/**
* Determine the time zone offset in a portable way.
*/
gcc_const
static time_t
GetTimeZoneOffset() noexcept
{
time_t t = 1234567890;
struct tm tm;
tm.tm_isdst = 0;
gmtime_r(&t, &tm);
return t - mktime(&tm);
}
#endif
std::chrono::system_clock::time_point
ParseTimePoint(const char *s, const char *format)
{
@@ -65,20 +47,11 @@ ParseTimePoint(const char *s, const char *format)
(void)format;
throw std::runtime_error("Time parsing not implemented on Windows");
#else
struct tm tm;
struct tm tm{};
const char *end = strptime(s, format, &tm);
if (end == nullptr || *end != 0)
throw std::runtime_error("Failed to parse time stamp");
#ifdef __GLIBC__
/* timegm() is a GNU extension */
const auto t = timegm(&tm);
#else
tm.tm_isdst = 0;
const auto t = mktime(&tm) + GetTimeZoneOffset();
#endif /* !__GLIBC__ */
return std::chrono::system_clock::from_time_t(t);
return TimeGm(tm);
#endif /* !_WIN32 */
}

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2014-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2014-2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions

11
src/time/meson.build Normal file

@@ -0,0 +1,11 @@
time = static_library(
'time',
'Parser.cxx',
'Convert.cxx',
'ISO8601.cxx',
include_directories: inc,
)
time_dep = declare_dependency(
link_with: time,
)

@@ -1,5 +1,5 @@
/*
* Copyright 2007-2017 Content Management AG
* Copyright 2007-2019 Content Management AG
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.com>

@@ -1,5 +1,5 @@
/*
* Copyright 2007-2017 Content Management AG
* Copyright 2007-2019 Content Management AG
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.com>

@@ -35,14 +35,14 @@
/**
* A statically allocated string buffer.
*/
template<typename T, size_t CAPACITY>
template<typename T, std::size_t CAPACITY>
class BasicStringBuffer {
public:
typedef T value_type;
typedef T &reference;
typedef T *pointer;
typedef const T *const_pointer;
typedef size_t size_type;
typedef std::size_t size_type;
static constexpr value_type SENTINEL = '\0';
@@ -104,7 +104,7 @@ public:
}
};
template<size_t CAPACITY>
template<std::size_t CAPACITY>
class StringBuffer : public BasicStringBuffer<char, CAPACITY> {};
#endif

@@ -36,13 +36,13 @@
template<typename... Args>
static inline void
StringFormat(char *buffer, size_t size,
StringFormat(char *buffer, std::size_t size,
const char *fmt, Args&&... args) noexcept
{
snprintf(buffer, size, fmt, args...);
}
template<size_t CAPACITY, typename... Args>
template<std::size_t CAPACITY, typename... Args>
static inline void
StringFormat(StringBuffer<CAPACITY> &buffer,
const char *fmt, Args&&... args) noexcept
@@ -50,7 +50,7 @@ StringFormat(StringBuffer<CAPACITY> &buffer,
StringFormat(buffer.data(), buffer.capacity(), fmt, args...);
}
template<size_t CAPACITY, typename... Args>
template<std::size_t CAPACITY, typename... Args>
static inline StringBuffer<CAPACITY>
StringFormat(const char *fmt, Args&&... args) noexcept
{

@@ -1,61 +0,0 @@
/*
* Copyright 2007-2017 Content Management AG
* 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.
*/
#include "TimeISO8601.hxx"
#include "TimeConvert.hxx"
#include "TimeParser.hxx"
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));
}
std::chrono::system_clock::time_point
ParseISO8601(const char *s)
{
return ParseTimePoint(s, "%FT%TZ");
}

@@ -16,9 +16,6 @@ util = static_library(
'SplitString.cxx',
'FormatString.cxx',
'Tokenizer.cxx',
'TimeParser.cxx',
'TimeConvert.cxx',
'TimeISO8601.cxx',
'UriUtil.cxx',
'LazyRandomEngine.cxx',
'HugeAllocator.cxx',

@@ -20,6 +20,7 @@
#include "AvahiPoll.hxx"
#include "event/SocketMonitor.hxx"
#include "event/TimerEvent.hxx"
#include "time/Convert.hxx"
static unsigned
FromAvahiWatchEvent(AvahiWatchEvent e)
@@ -78,12 +79,6 @@ private:
}
};
static constexpr std::chrono::steady_clock::duration
TimevalToChrono(const timeval &tv)
{
return std::chrono::seconds(tv.tv_sec) + std::chrono::microseconds(tv.tv_usec);
}
struct AvahiTimeout final {
TimerEvent timer;
@@ -97,12 +92,12 @@ public:
:timer(_loop, BIND_THIS_METHOD(OnTimeout)),
callback(_callback), userdata(_userdata) {
if (tv != nullptr)
timer.Schedule(TimevalToChrono(*tv));
timer.Schedule(ToSteadyClockDuration(*tv));
}
static void TimeoutUpdate(AvahiTimeout *t, const struct timeval *tv) {
if (tv != nullptr)
t->timer.Schedule(TimevalToChrono(*tv));
t->timer.Schedule(ToSteadyClockDuration(*tv));
else
t->timer.Cancel();
}

@@ -67,6 +67,7 @@ else
dependencies: [
libavahi_client_dep,
dbus_dep,
time_dep,
],
)

92
test/TestISO8601.cxx Normal file

@@ -0,0 +1,92 @@
/*
* Copyright 2019 Content Management AG
* 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.
*/
#include "time/ISO8601.hxx"
#include <gtest/gtest.h>
static constexpr struct {
const char *s;
time_t t;
std::chrono::system_clock::duration d;
} parse_tests[] = {
/* full ISO8601 */
{ "1970-01-01T00:00:00Z", 0, std::chrono::seconds(1) },
{ "1970-01-01T00:00:01Z", 1, std::chrono::seconds(1) },
{ "2019-02-04T16:46:41Z", 1549298801, std::chrono::seconds(1) },
{ "2018-12-31T23:59:59Z", 1546300799, std::chrono::seconds(1) },
{ "2019-01-01T00:00:00Z", 1546300800, std::chrono::seconds(1) },
/* only date */
{ "1970-01-01", 0, std::chrono::hours(24) },
{ "2019-02-04", 1549238400, std::chrono::hours(24) },
{ "2018-12-31", 1546214400, std::chrono::hours(24) },
{ "2019-01-01", 1546300800, std::chrono::hours(24) },
/* date with time zone */
{ "2019-02-04Z", 1549238400, std::chrono::hours(24) },
/* without time zone */
{ "2019-02-04T16:46:41", 1549298801, std::chrono::seconds(1) },
/* without seconds */
{ "2019-02-04T16:46", 1549298760, std::chrono::minutes(1) },
{ "2019-02-04T16:46Z", 1549298760, std::chrono::minutes(1) },
/* without minutes */
{ "2019-02-04T16", 1549296000, std::chrono::hours(1) },
{ "2019-02-04T16Z", 1549296000, std::chrono::hours(1) },
/* with time zone */
{ "2019-02-04T16:46:41+02", 1549291601, std::chrono::seconds(1) },
{ "2019-02-04T16:46:41+0200", 1549291601, std::chrono::seconds(1) },
{ "2019-02-04T16:46:41+02:00", 1549291601, std::chrono::seconds(1) },
{ "2019-02-04T16:46:41-0200", 1549306001, std::chrono::seconds(1) },
/* without field separators */
{ "19700101T000000Z", 0, std::chrono::seconds(1) },
{ "19700101T000001Z", 1, std::chrono::seconds(1) },
{ "20190204T164641Z", 1549298801, std::chrono::seconds(1) },
{ "19700101", 0, std::chrono::hours(24) },
{ "20190204", 1549238400, std::chrono::hours(24) },
{ "20190204T1646", 1549298760, std::chrono::minutes(1) },
{ "20190204T16", 1549296000, std::chrono::hours(1) },
};
TEST(ISO8601, Parse)
{
for (const auto &i : parse_tests) {
const auto result = ParseISO8601(i.s);
EXPECT_EQ(std::chrono::system_clock::to_time_t(result.first), i.t);
EXPECT_EQ(result.second, i.d);
}
}

@@ -46,6 +46,19 @@ test('TestUtil', executable(
],
))
test(
'TestTime',
executable(
'TestTime',
'TestISO8601.cxx',
include_directories: inc,
dependencies: [
time_dep,
gtest_dep,
],
),
)
test('TestRewindInputStream', executable(
'TestRewindInputStream',
'TestRewindInputStream.cxx',

@@ -22,7 +22,7 @@
#include "storage/StorageInterface.hxx"
#include "storage/FileInfo.hxx"
#include "net/Init.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include "util/PrintException.hxx"
#include <memory>

@@ -17,7 +17,7 @@
#include "storage/StorageInterface.hxx"
#include "storage/plugins/LocalStorage.hxx"
#include "Mapper.hxx"
#include "util/ChronoUtil.hxx"
#include "time/ChronoUtil.hxx"
#include <gtest/gtest.h>