date.h Source File

CPP API: date.h Source File
date.h
Go to the documentation of this file.
1 /*
2 * Copyright (C) 2020-2026 MEmilio
3 *
4 * Authors: Daniel Abele, Martin J. Kuehn
5 *
6 * Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20 #ifndef EPI_UTILS_DATE_H
21 #define EPI_UTILS_DATE_H
22 
23 #include "memilio/io/io.h"
24 #include "memilio/utils/logging.h"
25 #include <string>
26 #include <iostream>
27 #include <tuple>
28 #include <array>
29 #include <numeric>
30 #include <algorithm>
31 #include <cassert>
32 
33 namespace mio
34 {
35 
41 inline bool is_leap_year(int year)
42 {
43  return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
44 }
45 
50 struct Date {
54  Date() = default;
55 
62  Date(int y, int m, int d)
63 
64  : year(y)
65  , month(m)
66  , day(d)
67  {
68  assert(month > 0 && month < 13);
69  assert(day > 0 &&
71  }
72 
78  bool operator==(const Date& other) const
79  {
80  return year == other.year && month == other.month && day == other.day;
81  }
82  bool operator!=(const Date& other) const
83  {
84  return !(*this == other);
85  }
86  bool operator<(const Date& other) const
87  {
88  if (std::tie(year, month, day) < std::tie(other.year, other.month, other.day)) {
89  return true;
90  }
91  else {
92  return false;
93  }
94  }
95  bool operator<=(const Date& other) const
96  {
97  return !(other < *this);
98  }
99  bool operator>(const Date& other) const
100  {
101  return other < *this;
102  }
103  bool operator>=(const Date& other) const
104  {
105  return !(*this < other);
106  }
108 
113  std::string to_iso_string() const
114  {
115  // the format after ":" reads as
116  // 1) '0' -> fill with zeros
117  // 2) '>' -> align text right
118  // 3) '4' or '2' -> specify the width (4 for year, 2 for month and day)
119  return fmt::format("{:0>4}-{:0>2}-{:0>2}", year, month, day);
120  }
121 
128  friend std::ostream& operator<<(std::ostream& os, const Date& date)
129  {
130  return os << date.to_iso_string();
131  }
132 
137  template <class IOContext>
138  void serialize(IOContext& io) const
139  {
140  auto obj = io.create_object("Date");
141  obj.add_element("Year", year);
142  obj.add_element("Month", month);
143  obj.add_element("Day", day);
144  }
145 
150  template <class IOContext>
151  static IOResult<Date> deserialize(IOContext& io)
152  {
153  auto obj = io.expect_object("Date");
154  auto y = obj.expect_element("Year", Tag<int>{});
155  auto m = obj.expect_element("Month", Tag<int>{});
156  auto d = obj.expect_element("Day", Tag<int>{});
157  return apply(
158  io,
159  [](auto&& y_, auto&& m_, auto&& d_) -> IOResult<Date> {
160  if (m_ <= 0 || m_ > 12)
161  return failure(StatusCode::OutOfRange, "Month must be between 1 and 12 (inclusive).");
162  if (d_ <= 0 || d_ > ((is_leap_year(y_)) ? month_lengths_leap_year[m_ - 1] : month_lengths[m_ - 1]))
163  return failure(StatusCode::OutOfRange, "Day is not valid for the given month.");
164  return success(Date{y_, m_, d_});
165  },
166  y, m, d);
167  }
168 
169  int year;
170  int month;
171  int day;
172  static constexpr std::array<int, 12> month_lengths = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
173  static constexpr std::array<int, 12> month_lengths_leap_year = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
174 };
175 
180 inline std::string format_as(const mio::Date& d)
181 {
182  return d.to_iso_string();
183 }
184 
190 inline int get_month_length(Date date)
191 {
192  return ((is_leap_year(date.year)) ? date.month_lengths_leap_year[date.month - 1]
193  : date.month_lengths[date.month - 1]);
194 }
195 
201 inline std::array<int, 12> calculate_partial_sum_of_months(const Date& date)
202 {
203  std::array<int, 12> part_sum;
204  if (is_leap_year(date.year)) {
205  std::partial_sum(date.month_lengths_leap_year.begin(), date.month_lengths_leap_year.end(), part_sum.begin());
206  }
207  else {
208  std::partial_sum(date.month_lengths.begin(), date.month_lengths.end(), part_sum.begin());
209  }
210  return part_sum;
211 }
212 
219 inline IOResult<Date> parse_date(const std::string& date_str)
220 {
221  try {
222  Date date;
223  date.year = std::stoi(date_str.substr(0, 4));
224  date.month = std::stoi(date_str.substr(5, 2));
225  date.day = std::stoi(date_str.substr(8, 2));
226  if (date.month < 1 || date.month > 12 || date.day < 1 || date.day > get_month_length(date)) {
227  return failure(StatusCode::OutOfRange, "Argument is not a valid date.");
228  }
229  return success(date);
230  }
231  catch (const std::invalid_argument&) {
232  return failure(StatusCode::InvalidValue, "Argument ist not a valid date string.");
233  }
234 }
235 
242 inline Date offset_date_by_days(Date date, int offset_days)
243 {
244  auto year = date.year;
245  auto month = date.month;
246  auto day = date.day;
247  assert(month > 0 && month < 13 && day > 0 && day <= get_month_length(date));
248 
249  if (day + offset_days > 0 && day + offset_days <= get_month_length(date)) {
250  return {year, month, day + offset_days};
251  }
252  else {
253  auto part_sum = calculate_partial_sum_of_months(date);
254 
255  int day_in_year = day + offset_days;
256  if (month > 1) {
257  // take month-2 since end of last month has to be found and due to start at 0 of C++ (against January=1 in date format)
258  day_in_year += part_sum[month - 2];
259  }
260 
261  if (day_in_year > 0 && day_in_year <= part_sum[11]) {
262  auto iter = std::find_if(part_sum.begin(), part_sum.end(), [day_in_year](auto s) {
263  return day_in_year <= s;
264  });
265  int i = static_cast<int>(iter - part_sum.begin());
266  return {year, i + 1, day_in_year - (i > 0 ? part_sum[static_cast<size_t>(i - 1)] : 0)};
267  }
268  else {
269  if (day_in_year > 0) {
270  return offset_date_by_days({year + 1, 1, 1}, day_in_year - part_sum[11] - 1);
271  }
272  else {
273  return offset_date_by_days({year - 1, 12, 31}, day_in_year);
274  }
275  }
276  }
277 }
278 
284 inline int get_day_in_year(Date date)
285 {
286  auto month = date.month;
287  auto day = date.day;
288  assert(month > 0 && month < 13 && day > 0 && day <= get_month_length(date));
289 
290  if (month > 1) {
291 
292  auto part_sum = calculate_partial_sum_of_months(date);
293 
294  // take month-2 since end of last month has to be found and due to start at 0 of C++ (against January=1 in date format)
295  int day_in_year = part_sum[month - 2] + day;
296 
297  return day_in_year;
298  }
299  else {
300  return day;
301  }
302 }
303 
310 inline int get_offset_in_days(Date date1, Date date2)
311 {
312  auto year1 = date1.year;
313  auto month1 = date1.month;
314  auto day1 = date1.day;
315 
316  auto year2 = date2.year;
317  auto month2 = date2.month;
318  auto day2 = date2.day;
319 
320  if (year1 == year2 && month1 == month2) {
321  return day1 - day2;
322  }
323  else {
324  int day_in_year1 = get_day_in_year(date1);
325  int day_in_year2 = get_day_in_year(date2);
326 
327  if (year1 < year2) {
328  int sum_days = 0;
329  for (int i = year1; i < year2; i++) {
330  sum_days += 365;
331  if (is_leap_year(i)) {
332  sum_days += 1;
333  }
334  }
335  return -(sum_days - day_in_year1) - day_in_year2;
336  }
337  else if (year1 > year2) {
338  int sum_days = 0;
339  for (int i = year2; i < year1; i++) {
340  sum_days += 365;
341  if (is_leap_year(i)) {
342  sum_days += 1;
343  }
344  }
345  return day_in_year1 + sum_days - day_in_year2;
346  }
347  else {
348 
349  return day_in_year1 - day_in_year2;
350  }
351  }
352 }
353 
354 } // end namespace mio
355 
356 #endif // EPI_UTILS_DATE_H
A collection of classes to simplify handling of matrix shapes in meta programming.
Definition: models/abm/analyze_result.h:30
int get_month_length(Date date)
Computes the length of a month for a given date.
Definition: date.h:190
auto failure(const IOStatus &s)
Create an object that is implicitly convertible to an error IOResult<T>.
Definition: io.h:380
boost::outcome_v2::in_place_type_t< T > Tag
Type that is used for overload resolution.
Definition: io.h:407
auto i
Definition: io.h:809
details::ApplyResultT< F, T... > apply(IOContext &io, F f, const IOResult< T > &... rs)
Evaluate a function with zero or more unpacked IOResults as arguments.
Definition: io.h:481
std::string format_as(const mio::Date &d)
Format date objects using the ISO notation for logging with spdlog.
Definition: date.h:180
auto success()
Create an object that is implicitly convertible to a succesful IOResult<void>.
Definition: io.h:359
int get_offset_in_days(Date date1, Date date2)
Computes the offset in days given two dates: first date minus second date.
Definition: date.h:310
IOResult< Date > parse_date(const std::string &date_str)
parses a date from a string.
Definition: date.h:219
bool is_leap_year(int year)
Computes if a given year is a leap year.
Definition: date.h:41
std::array< int, 12 > calculate_partial_sum_of_months(const Date &date)
Computes the cumulative number of days at the end of each month for a given year.
Definition: date.h:201
Date offset_date_by_days(Date date, int offset_days)
Computes the new date corresponding to a given date and a offset in days.
Definition: date.h:242
boost::outcome_v2::unchecked< T, IOStatus > IOResult
Value-or-error type for operations that return a value but can fail.
Definition: io.h:353
int get_day_in_year(Date date)
Computes the day in year based on a given date.
Definition: date.h:284
Simple date representation as year, month, and day.
Definition: date.h:50
Date(int y, int m, int d)
initializing constructor.
Definition: date.h:62
bool operator<=(const Date &other) const
Definition: date.h:95
friend std::ostream & operator<<(std::ostream &os, const Date &date)
Overload for stream operator to use the ISO 8601 format.
Definition: date.h:128
static constexpr std::array< int, 12 > month_lengths
Definition: date.h:172
bool operator<(const Date &other) const
Definition: date.h:86
bool operator>=(const Date &other) const
Definition: date.h:103
bool operator!=(const Date &other) const
Definition: date.h:82
int month
Definition: date.h:170
bool operator==(const Date &other) const
equality comparison operators.
Definition: date.h:78
void serialize(IOContext &io) const
serialize this.
Definition: date.h:138
int year
Definition: date.h:169
bool operator>(const Date &other) const
Definition: date.h:99
int day
Definition: date.h:171
Date()=default
default constructor.
static constexpr std::array< int, 12 > month_lengths_leap_year
Definition: date.h:173
std::string to_iso_string() const
Formats the date into a string in ISO 8601 format (YYYY-MM-DD).
Definition: date.h:113
static IOResult< Date > deserialize(IOContext &io)
deserialize an object of this class.
Definition: date.h:151