A modern C++17 header-only library for parsing and manipulating human-readable time durations.
- Header-only: Easy integration into any C++ project
- Modern C++17: Uses standard library chrono types
- Flexible parsing: Supports multiple time formats and units
- Type-safe: Built on
std::chrono::seconds
for accuracy - Zero dependencies: No external libraries required for the core functionality
- Cross-platform: Works on Windows, Linux, and macOS
- SQL integration: Generate SQL-compatible interval strings
#include <timeduration/timeduration.hpp>
#include <iostream>
int main() {
using namespace timeduration;
// Parse a time duration string
CTimePeriod duration("2h 30m 15s");
// Access individual components
std::cout << "Hours: " << duration.hours() << std::endl; // 2
std::cout << "Minutes: " << duration.minutes() << std::endl; // 30
std::cout << "Seconds: " << duration.seconds() << std::endl; // 15
// Get total duration in seconds
std::cout << "Total: " << duration.duration().count() << " seconds" << std::endl; // 9015
// Format back to string
std::cout << "Formatted: " << duration.toString() << std::endl; // "2h 30m 15s"
return 0;
}
-
Clone the repository:
git clone https://github.com/anime-pdf/timeduration-cpp.git cd timeduration-cpp
-
Build and install:
mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release cmake --build . sudo cmake --install .
-
Use in your CMake project:
find_package(timeduration REQUIRED) target_link_libraries(your_target PRIVATE timeduration::timeduration)
Simply copy include/timeduration/timeduration.hpp
to your project and include it:
#include "timeduration.hpp"
The library supports both short and long forms of time units:
Unit | Short Form | Long Form | Seconds |
---|---|---|---|
Seconds | s |
seconds |
1 |
Minutes | m |
minutes |
60 |
Hours | h |
hours |
3,600 |
Days | d |
days |
86,400 |
Months | mo |
months |
2,419,200 (28 days) |
Years | y |
years |
31,536,000 (365 days) |
using namespace timeduration;
// Different formats
CTimePeriod p1("5s"); // 5 seconds
CTimePeriod p2("10m"); // 10 minutes
CTimePeriod p3("2h"); // 2 hours
CTimePeriod p4("1d"); // 1 day
// Combined formats
CTimePeriod p5("1h 30m"); // 1 hour 30 minutes
CTimePeriod p6("2d 5h 30m 15s"); // 2 days 5 hours 30 minutes 15 seconds
// Long form
CTimePeriod p7("1hours 30minutes 45seconds");
// Large units
CTimePeriod p8("1y 6mo 15d"); // 1 year 6 months 15 days
// From string
CTimePeriod duration1("2h 30m");
// From components (seconds, minutes, hours, days)
CTimePeriod duration2(15, 30, 2, 1); // 1d 2h 30m 15s
// From std::chrono::seconds
CTimePeriod duration3(std::chrono::seconds(3661)); // 1h 1m 1s
// Using factory method
auto duration4 = CTimePeriod::ParseFactory("5h 15m");
// Default constructor (zero duration)
CTimePeriod duration5; // 0s
CTimePeriod duration("1d 5h 30m 45s");
std::cout << "Days: " << duration.days() << std::endl; // 1
std::cout << "Hours: " << duration.hours() << std::endl; // 5
std::cout << "Minutes: " << duration.minutes() << std::endl; // 30
std::cout << "Seconds: " << duration.seconds() << std::endl; // 45
// Total duration as std::chrono::seconds
auto total = duration.duration();
std::cout << "Total seconds: " << total.count() << std::endl; // 106245
// Check if zero
if (duration.isZero()) {
std::cout << "Duration is zero" << std::endl;
}
CTimePeriod duration("2h 30m 15s");
// Human-readable string
std::cout << duration.toString() << std::endl; // "2h 30m 15s"
// SQL interval format
std::cout << duration.asSqlInterval() << std::endl; // "interval 9015 second"
CTimePeriod short_duration("30m");
CTimePeriod long_duration("1h");
// Equality
if (short_duration == long_duration) { /* ... */ }
if (short_duration != long_duration) { /* ... */ }
// Relational
if (short_duration < long_duration) { /* true */ }
if (short_duration > long_duration) { /* false */ }
if (short_duration <= long_duration) { /* true */ }
if (short_duration >= long_duration) { /* false */ }
// Handle normalization automatically
CTimePeriod duration(75); // 75 seconds becomes 1m 15s
std::cout << duration.toString() << std::endl; // "1m 15s"
// Accumulating durations
CTimePeriod total;
std::vector<std::string> durations = {"1h", "30m", "45s"};
for (const auto& d : durations) {
CTimePeriod current(d);
total = CTimePeriod(total.duration() + current.duration());
}
std::cout << total.toString() << std::endl; // "1h 30m 45s"
// Parse large numbers
CTimePeriod huge("999h 123456s");
std::cout << huge.toString() << std::endl; // Normalized output
The CScanner
class handles the low-level tokenization of input strings:
- Token Recognition: Identifies numbers and unit suffixes
- Flexible Parsing: Handles both "5m" and "5 minutes" formats
- Accumulation: Combines multiple instances of the same unit (e.g., "5m 10m" = "15m")
- Default Units: Numbers without units default to minutes
// Internal scanner usage (normally automatic)
CTimePeriod::TokenHolder tokens = {
{"s", 1L}, {"seconds", 1L},
{"m", 60L}, {"minutes", 60L},
{"h", 3600L}, {"hours", 3600L},
// ... more units
};
CTimePeriod::CScanner scanner("2h 30m 15s", tokens);
auto result = scanner.ScanTokens();
// result contains: {3600: 2, 60: 30, 1: 15}
The parser converts scanner results into std::chrono::seconds
:
- Multiplier Application: Applies time unit multipliers to values
- Accumulation: Sums all components into total seconds
- Normalization: Breaks down total seconds into days, hours, minutes, seconds
- Input:
"2h 30m 15s"
- Tokenization:
{3600: 2, 60: 30, 1: 15}
- Calculation:
2×3600 + 30×60 + 15×1 = 9015 seconds
- Normalization:
9015s → 0d 2h 30m 15s
- C++17 compatible compiler
- CMake 3.14 or later
- Git
# Configure with options
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DTIMEDURATION_BUILD_TESTS=ON \
-DTIMEDURATION_BUILD_EXAMPLES=ON
# Build
cmake --build build --config Release
# Run tests
cd build && ctest --output-on-failure
# Install
cmake --install build --prefix /usr/local
Option | Default | Description |
---|---|---|
TIMEDURATION_BUILD_TESTS |
OFF |
Build unit tests |
TIMEDURATION_BUILD_EXAMPLES |
OFF |
Build example programs |
TIMEDURATION_DOWNLOAD_GTEST |
ON |
Auto-download Google Test if not found |
The library includes comprehensive unit tests covering:
- Scanner functionality: Tokenization and parsing logic
- Parser accuracy: Duration calculation and normalization
- Edge cases: Zero durations, large numbers, malformed input
- API completeness: All public methods and operators
- Integration: Round-trip conversions and complex scenarios
Run tests with:
# Build with tests enabled
cmake -B build -DTIMEDURATION_BUILD_TESTS=ON
cmake --build build
# Run all tests
cd build && ctest --output-on-failure
# Run specific test patterns
cd build && ctest -R "Scanner" --verbose
- Header-only: No runtime linking overhead
- Parse caching: Consider caching parsed durations for repeated use
- Memory efficient: Uses standard integers and chrono types
- Stack allocated: No dynamic memory allocation during normal operation
The library follows a simple error handling strategy:
- Malformed input: Returns zero duration
- Unknown units: Ignored during parsing
- Overflow: Undefined behavior for extremely large values (same as standard integers)
// These all result in zero or partial parsing
CTimePeriod invalid1(""); // Empty string → 0s
CTimePeriod invalid2("invalid"); // No numbers → 0s
CTimePeriod partial("5h invalid"); // Partial parse → 5h 0m 0s
#include <chrono>
#include <thread>
CTimePeriod delay("5s");
std::this_thread::sleep_for(delay.duration());
CTimePeriod retention("30d");
std::string query = "DELETE FROM logs WHERE created_at < NOW() - " +
retention.asSqlInterval();
#include <fstream>
#include <sstream>
std::ifstream config("config.txt");
std::string line;
while (std::getline(config, line)) {
if (line.starts_with("timeout=")) {
std::string duration_str = line.substr(8);
CTimePeriod timeout(duration_str);
// Use timeout.duration() for network operations
}
}
- Fork the repository
- Create a feature branch:
git checkout -b feature-name
- Make your changes and add tests
- Ensure all tests pass:
cmake --build build && cd build && ctest
- Submit a pull request
- Follow the existing code style
- Use meaningful variable names
- Add unit tests for new functionality
- Update documentation for API changes
MIT License - see LICENSE file for details.
Q: Why use this instead of std::chrono parsing? A: std::chrono doesn't provide built-in parsing for human-readable formats like "2h 30m". This library bridges that gap.
Q: Are months and years exact? A: For simplicity, months are 28 days and years are 365 days. For precise calendar arithmetic, use a dedicated date/time library.
Q: Can I extend the supported units? A: Currently, units are hardcoded. Future versions may support custom unit definitions.
Q: What about negative durations? A: Negative durations are not currently supported. All parsed values are treated as positive.
Q: Is it thread-safe? A: Yes, the library is thread-safe for read operations. Multiple threads can safely parse durations simultaneously.