1 #ifndef READR_DATE_TIME_H_
2 #define READR_DATE_TIME_H_
3 
4 #include "cpp11/R.hpp"
5 
6 #include <stdlib.h>
7 #include <string>
8 #include <tzdb/tzdb.h>
9 
10 // Much of this code is adapted from R's src/main/datetime.c.
11 // Author: The R Core Team.
12 // License: GPL >= 2
13 
14 static const int month_length[12] = {
15     31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
16 static const int month_start[12] = {
17     0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
18 
19 // Leap days occur in a 400 year cycle: this records the cumulative number
20 // of leap days in per cycle. Generated with:
21 // is_leap <- function(y) (y %% 4) == 0 & ((y %% 100) != 0 | (y %% 400) == 0)
22 // cumsum(is_leap(0:399))
23 static const int leap_days[400] = {
24     0,  1,  1,  1,  1,  2,  2,  2,  2,  3,  3,  3,  3,  4,  4,  4,  4,  5,  5,
25     5,  5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  8,  9,  9,  9,  9,  10,
26     10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14,
27     15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19,
28     19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24,
29     24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28,
30     28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32,
31     33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, 36, 37, 37, 37,
32     37, 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42,
33     42, 42, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47,
34     47, 47, 47, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49, 49, 49, 50, 50, 50, 50,
35     51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55,
36     55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60,
37     60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63, 64, 64, 64, 64, 65,
38     65, 65, 65, 66, 66, 66, 66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69,
39     70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, 72, 73, 73, 73, 73, 73, 73, 73,
40     73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 78, 78,
41     78, 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82, 83,
42     83, 83, 83, 84, 84, 84, 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87, 87,
43     88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, 90, 91, 91, 91, 91, 92, 92, 92,
44     92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, 96, 97, 97,
45     97};
46 
47 static const int cycle_days = 400 * 365 + 97;
48 
is_leap(unsigned y)49 inline int is_leap(unsigned y) {
50   return (y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0);
51 }
52 
53 class DateTime {
54   int year_, mon_, day_, hour_, min_, sec_, offset_;
55   double psec_;
56   std::string tz_;
57 
58 public:
59   DateTime(
60       int year,
61       int mon,
62       int day,
63       int hour = 0,
64       int min = 0,
65       int sec = 0,
66       double psec = 0,
67       const std::string& tz = "UTC")
year_(year)68       : year_(year),
69         mon_(mon),
70         day_(day),
71         hour_(hour),
72         min_(min),
73         sec_(sec),
74         offset_(0),
75         psec_(psec),
76         tz_(tz) {}
77 
78   // Used to add time zone offsets which can only be easily applied once
79   // we've converted into seconds since epoch.
setOffset(int offset)80   void setOffset(int offset) { offset_ = offset; }
81 
82   // Is this a valid date time?
validDateTime()83   bool validDateTime() const { return validDate() && validTime(); }
84 
validDate()85   bool validDate() const {
86     if (year_ < 0)
87       return false;
88 
89     return (date::year{year_} / mon_ / day_).ok();
90   }
91 
validTime()92   bool validTime() const {
93     if (sec_ < 0 || sec_ > 60)
94       return false;
95     if (min_ < 0 || min_ > 59)
96       return false;
97     if (hour_ < 0 || hour_ > 23)
98       return false;
99 
100     return true;
101   }
102 
validDuration()103   bool validDuration() const {
104     if (sec_ < -59 || sec_ > 59)
105       return false;
106     if (min_ < -59 || min_ > 59)
107       return false;
108 
109     return true;
110   }
111 
datetime()112   double datetime() const { return (tz_ == "UTC") ? utctime() : localtime(); }
113 
date()114   int date() const { return utcdate(); }
115 
time()116   double time() const {
117     return psec_ + sec_ + (min_ * 60.0) + (hour_ * 3600.0);
118   }
119 
120 private:
121   // Number of number of seconds since 1970-01-01T00:00:00Z.
122   // Compared to usual implementations this returns a double, and supports
123   // a wider range of dates. Invalid dates have undefined behaviour.
utctime()124   double utctime() const { return utcdate() * 86400.0 + time() + offset_; }
125 
126   // Find number of days since 1970-01-01.
127   // Invalid dates have undefined behaviour.
utcdate()128   int utcdate() const {
129     if (!validDate())
130       return NA_REAL;
131 
132     const date::year_month_day ymd{date::year(year_) / mon_ / day_};
133     const date::sys_days st{ymd};
134     return st.time_since_epoch().count();
135   }
136 
localtime()137   double localtime() const {
138     if (!validDateTime())
139       return NA_REAL;
140 
141     const date::time_zone* p_time_zone;
142 
143     if (!tzdb::locate_zone(tz_, p_time_zone)) {
144       throw std::runtime_error(
145           "'" + tz_ + "' not found in the time zone database.");
146     }
147 
148     const date::local_seconds lt =
149         std::chrono::seconds{sec_} + std::chrono::minutes{min_} +
150         std::chrono::hours{hour_} +
151         date::local_days{date::year{year_} / mon_ / day_};
152 
153     date::local_info info;
154 
155     if (!tzdb::get_local_info(lt, p_time_zone, info)) {
156       throw std::runtime_error(
157           "Can't lookup local time info for the supplied time zone.");
158     }
159 
160     switch (info.result) {
161     case date::local_info::unique:
162       return (lt.time_since_epoch() - info.first.offset).count() + psec_ +
163              offset_;
164     case date::local_info::ambiguous:
165       // Choose `earliest` of the two ambiguous times
166       return (lt.time_since_epoch() - info.first.offset).count() + psec_ +
167              offset_;
168     case date::local_info::nonexistent:
169       return NA_REAL;
170     }
171 
172     throw std::runtime_error("should never happen");
173   }
174 };
175 
176 #endif
177