1 /*
2  MDAL - Mesh Data Abstraction Library (MIT License)
3  Copyright (C) 2019 Vincent Cloarec (vcloarec at gmail dot com)
4 */
5 #include "mdal_datetime.hpp"
6 #include "mdal_utils.hpp"
7 
8 
9 constexpr double MILLISECONDS_IN_SECOND = 1000;
10 constexpr double MILLISECONDS_IN_MINUTE = 1000 * 60;
11 constexpr double MILLISECONDS_IN_HOUR = 1000 * 60 * 60;
12 constexpr double MILLISECONDS_IN_DAY = 1000 * 60 * 60 * 24;
13 constexpr double MILLISECONDS_IN_WEEK = 1000 * 60 * 60 * 24 * 7;
14 
15 //https://www.unidata.ucar.edu/software/netcdf-java/current/CDM/CalendarDateTime.html
16 constexpr double MILLISECONDS_IN_EXACT_YEAR = 3.15569259747e10; //CF Compliant
17 constexpr double MILLISECONDS_IN_MONTH_CF = MILLISECONDS_IN_EXACT_YEAR / 12.0; //CF Compliant
18 
19 MDAL::DateTime::DateTime() = default;
20 
DateTime(int year,int month,int day,int hours,int minutes,double seconds,MDAL::DateTime::Calendar calendar)21 MDAL::DateTime::DateTime( int year, int month, int day, int hours, int minutes, double seconds, MDAL::DateTime::Calendar calendar )
22 {
23   DateTimeValues value{year, month, day, hours, minutes, seconds};
24 
25   switch ( calendar )
26   {
27     case MDAL::DateTime::Gregorian:
28       setWithGregorianJulianCalendarDate( value );
29       break;
30     case MDAL::DateTime::ProlepticGregorian:
31       setWithGregorianCalendarDate( value );
32       break;
33     case MDAL::DateTime::Julian:
34       setWithJulianCalendarDate( value );
35       break;
36   }
37 }
38 
DateTime(double value,Epoch epoch)39 MDAL::DateTime::DateTime( double value, Epoch epoch ):  mValid( true )
40 {
41   switch ( epoch )
42   {
43     case MDAL::DateTime::Unix:
44       mJulianTime = ( DateTime( 1970, 01, 01, 0, 0, 0, Gregorian ) + RelativeTimestamp( value, RelativeTimestamp::seconds ) ).mJulianTime;
45       break;
46     case MDAL::DateTime::JulianDay:
47       mJulianTime = int64_t( value * MILLISECONDS_IN_DAY + 0.5 );
48       break;
49   }
50 }
51 
DateTime(const std::string & fromISO8601)52 MDAL::DateTime::DateTime( const std::string &fromISO8601 )
53 {
54   std::vector<std::string> splitedDateTime = split( fromISO8601, 'T' );
55 
56   if ( splitedDateTime.size() != 2 )
57     return;
58   //parse date
59   std::vector<std::string> splitedDate = split( splitedDateTime.at( 0 ), '-' );
60   if ( splitedDate.size() != 3 )
61     return;
62 
63   //parse time
64   splitedDateTime[1] = replace( splitedDateTime.at( 1 ), "Z", "", ContainsBehaviour::CaseInsensitive );
65   std::vector<std::string> splitedTime = split( splitedDateTime.at( 1 ), ':' );
66   if ( splitedTime.size() < 2 || splitedTime.size() > 3 )
67     return;
68 
69   DateTimeValues dateTimeValues;
70   dateTimeValues.year = toInt( splitedDate[0] );
71   dateTimeValues.month = toInt( splitedDate[1] );
72   dateTimeValues.day = toInt( splitedDate[2] );
73   dateTimeValues.hours = toInt( splitedTime[0] );
74   dateTimeValues.minutes = toInt( splitedTime[1] );
75   if ( splitedTime.size() == 3 )
76     dateTimeValues.seconds = toDouble( splitedTime[2] );
77   else
78     dateTimeValues.seconds = 0.0;
79 
80   setWithGregorianCalendarDate( dateTimeValues );
81 }
82 
toStandardCalendarISO8601() const83 std::string MDAL::DateTime::toStandardCalendarISO8601() const
84 {
85   if ( mValid )
86   {
87     DateTimeValues value = dateTimeGregorianProleptic();
88     if ( value.year > 0 )
89       return toString( value );
90   }
91 
92   return "";
93 }
94 
toJulianDay() const95 double MDAL::DateTime::toJulianDay() const
96 {
97   return mJulianTime / MILLISECONDS_IN_DAY;
98 }
99 
toJulianDayString() const100 std::string MDAL::DateTime::toJulianDayString() const
101 {
102   return std::to_string( toJulianDay() );
103 }
104 
expandToCalendarArray() const105 std::vector<int> MDAL::DateTime::expandToCalendarArray() const
106 {
107   std::vector<int> dateTimeArray( 6, 0 );
108   if ( mValid )
109   {
110     DateTimeValues value = dateTimeGregorianProleptic();
111     dateTimeArray[0] = value.year;
112     dateTimeArray[1] = value.month;
113     dateTimeArray[2] = value.day;
114     dateTimeArray[3] = value.hours;
115     dateTimeArray[4] = value.minutes;
116     dateTimeArray[5] = int( value.seconds + 0.5 );
117   }
118 
119   return dateTimeArray;
120 }
121 
122 
operator +(const MDAL::RelativeTimestamp & duration) const123 MDAL::DateTime MDAL::DateTime::operator+( const MDAL::RelativeTimestamp &duration ) const
124 {
125   if ( !mValid )
126     return DateTime();
127   return DateTime( mJulianTime + duration.mDuration );
128 }
129 
130 
operator -(const MDAL::RelativeTimestamp & duration) const131 MDAL::DateTime MDAL::DateTime::operator-( const MDAL::RelativeTimestamp &duration ) const
132 {
133   if ( !mValid )
134     return DateTime();
135   return DateTime( mJulianTime - duration.mDuration );
136 }
137 
operator ==(const MDAL::DateTime & other) const138 bool MDAL::DateTime::operator==( const MDAL::DateTime &other ) const
139 {
140   if ( !mValid && !other.mValid )
141     return true;
142 
143   return ( mValid && other.mValid ) && ( mJulianTime == other.mJulianTime );
144 }
145 
operator <(const MDAL::DateTime & other) const146 bool MDAL::DateTime::operator<( const MDAL::DateTime &other ) const
147 {
148   if ( !mValid && !other.mValid )
149     return false;
150   return ( mValid && other.mValid ) && ( mJulianTime < other.mJulianTime );
151 }
152 
isValid() const153 bool MDAL::DateTime::isValid() const { return mValid; }
154 
DateTime(int64_t julianTime)155 MDAL::DateTime::DateTime( int64_t julianTime ): mJulianTime( julianTime ), mValid( true )
156 {}
157 
158 /*
159 MDAL::DateTime::DateTimeValues MDAL::DateTime::dateTimeGregorianJulianCalendar() const
160 {
161   // https://fr.wikipedia.org/wiki/Jour_julien
162   DateTimeValues values;
163   int Z = int( mJulianTime / MILLISECONDS_IN_DAY + 0.5 ); // integer part of julian days count
164   double F = ( mJulianTime - MILLISECONDS_IN_DAY * ( Z - 0.5 ) ) / MILLISECONDS_IN_DAY; // fractional part of julian days count;
165   int S;
166 
167   if ( Z < 2299161 )
168     S = Z;
169   else
170   {
171     int alpha = int( ( Z - 1867216.25 ) / 36524.25 );
172     S = Z + 1 + alpha - int( alpha / 4 );
173   }
174 
175   int B = S + 1524;
176   int C = int( ( B - 122.1 ) / 365.25 );
177   int D = int( 365.25 * C );
178   int E = int( ( B - D ) / 30.6001 );
179 
180   values.day = B - D - int( 30.6001 * E );
181   if ( E < 14 )
182     values.month = E - 1;
183   else
184     values.month = E - 13;
185 
186   if ( values.month > 2 )
187     values.year = C - 4716;
188   else
189     values.year = C - 4715;
190 
191   values.hours = int( F / MILLISECONDS_IN_HOUR );
192   F = int( F - values.hours * MILLISECONDS_IN_HOUR );
193   values.minutes = int( F / MILLISECONDS_IN_MINUTE );
194   F = int( F  - values.minutes * MILLISECONDS_IN_MINUTE );
195   values.seconds = int( F / MILLISECONDS_IN_SECOND );
196 
197   return values;
198 }
199 */
200 
dateTimeGregorianProleptic() const201 MDAL::DateTime::DateTimeValues MDAL::DateTime::dateTimeGregorianProleptic() const
202 {
203   // https://fr.wikipedia.org/wiki/Jour_julien
204   DateTimeValues values;
205   int Z = int( mJulianTime / MILLISECONDS_IN_DAY + 0.5 ); // integer part of julian days count
206   int F = int( mJulianTime - MILLISECONDS_IN_DAY * ( Z - 0.5 ) ) ; // fractional part of julian days count in ms;
207 
208   int alpha = int( ( Z - 1867216.25 ) / 36524.25 );
209   int S = Z + 1 + alpha - int( alpha / 4 );
210 
211   int B = S + 1524;
212   int C = int( ( B - 122.1 ) / 365.25 );
213   int D = int( 365.25 * C );
214   int E = int( ( B - D ) / 30.6001 );
215 
216   values.day = B - D - int( 30.6001 * E );
217   if ( E < 14 )
218     values.month = E - 1;
219   else
220     values.month = E - 13;
221 
222   if ( values.month > 2 )
223     values.year = C - 4716;
224   else
225     values.year = C - 4715;
226 
227   values.hours = int( F / MILLISECONDS_IN_HOUR );
228   F = int( F - values.hours * MILLISECONDS_IN_HOUR );
229   values.minutes = int( F / MILLISECONDS_IN_MINUTE );
230   F = int( F  - values.minutes * MILLISECONDS_IN_MINUTE );
231   values.seconds = int( F / MILLISECONDS_IN_SECOND );
232 
233   return values;
234 }
235 
236 
setWithGregorianCalendarDate(MDAL::DateTime::DateTimeValues values)237 void MDAL::DateTime::setWithGregorianCalendarDate( MDAL::DateTime::DateTimeValues values )
238 {
239   // https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html
240   if ( values.month <= 2 )
241   {
242     values.year--;
243     values.month += 12;
244   }
245 
246   int A = values.year / 100;
247   int B = A / 4;
248   int C = 2 - A + B;
249   int E = int( 365.25 * ( values.year + 4716 ) );
250   int F = int( 30.6001 * ( values.month + 1 ) );
251   double julianDay = C + values.day + E + F - 1524.5;
252 
253   mValid = true;
254   mJulianTime = int64_t( julianDay * MILLISECONDS_IN_DAY +
255                          ( values.hours ) * MILLISECONDS_IN_HOUR +
256                          values.minutes * MILLISECONDS_IN_MINUTE +
257                          values.seconds * MILLISECONDS_IN_SECOND );
258 }
259 
setWithJulianCalendarDate(MDAL::DateTime::DateTimeValues values)260 void MDAL::DateTime::setWithJulianCalendarDate( MDAL::DateTime::DateTimeValues values )
261 {
262   // https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html
263   if ( values.month <= 2 )
264   {
265     values.year--;
266     values.month += 12;
267   }
268 
269   int E = int( 365.25 * ( values.year + 4716 ) );
270   int F = int( 30.6001 * ( values.month + 1 ) );
271   double julianDay = values.day + E + F - 1524.5;
272 
273   mValid = true;
274   mJulianTime = int64_t( julianDay * MILLISECONDS_IN_DAY +
275                          ( values.hours ) * MILLISECONDS_IN_HOUR +
276                          values.minutes * MILLISECONDS_IN_MINUTE +
277                          values.seconds * MILLISECONDS_IN_SECOND );
278 }
279 
setWithGregorianJulianCalendarDate(MDAL::DateTime::DateTimeValues values)280 void MDAL::DateTime::setWithGregorianJulianCalendarDate( MDAL::DateTime::DateTimeValues values )
281 {
282   // https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html
283 
284   mValid = true;
285 
286   if ( values.year > 1582 ||
287        ( values.year == 1582 && ( values.month > 10 || ( values.month == 10 && values.day >= 15 ) ) ) ) // gregorian calendar
288   {
289     setWithGregorianCalendarDate( values );
290   }
291   else
292     setWithJulianCalendarDate( values );
293 }
294 
toString(MDAL::DateTime::DateTimeValues values) const295 std::string MDAL::DateTime::toString( MDAL::DateTime::DateTimeValues values ) const
296 {
297   int milliseconds = int( ( values.seconds - int( values.seconds ) ) * 1000 + 0.5 );
298   std::string msStr;
299   if ( milliseconds > 0 )
300   {
301     if ( milliseconds < 10 )
302       msStr = prependZero( std::to_string( milliseconds ), 3 );
303     else if ( milliseconds < 100 )
304       msStr = prependZero( std::to_string( milliseconds ), 2 );
305     else if ( milliseconds < 1000 )
306       msStr = std::to_string( milliseconds );
307 
308     msStr = std::string( "," ).append( msStr );
309   }
310 
311   std::string strDateTime = prependZero( std::to_string( values.year ), 4 ) + "-" +
312                             prependZero( std::to_string( values.month ), 2 ) + "-" +
313                             prependZero( std::to_string( values.day ), 2 ) + "T" +
314                             prependZero( std::to_string( values.hours ), 2 ) + ":" +
315                             prependZero( std::to_string( values.minutes ), 2 ) + ":" +
316                             prependZero( std::to_string( int( values.seconds ) ), 2 ) +
317                             msStr;
318 
319   return strDateTime;
320 }
321 
operator -(const MDAL::DateTime & other) const322 MDAL::RelativeTimestamp MDAL::DateTime::operator-( const MDAL::DateTime &other ) const
323 {
324   if ( !mValid || !other.mValid )
325     return RelativeTimestamp();
326   return RelativeTimestamp( mJulianTime - other.mJulianTime );
327 }
328 
329 
330 MDAL::RelativeTimestamp::RelativeTimestamp() = default;
331 
RelativeTimestamp(double duration,MDAL::RelativeTimestamp::Unit unit)332 MDAL::RelativeTimestamp::RelativeTimestamp( double duration, MDAL::RelativeTimestamp::Unit unit )
333 {
334   switch ( unit )
335   {
336     case MDAL::RelativeTimestamp::milliseconds:
337       mDuration = int64_t( duration );
338       break;
339     case MDAL::RelativeTimestamp::seconds:
340       mDuration = int64_t( duration * MILLISECONDS_IN_SECOND + 0.5 );
341       break;
342     case MDAL::RelativeTimestamp::minutes:
343       mDuration = int64_t( duration * MILLISECONDS_IN_MINUTE + 0.5 );
344       break;
345     case MDAL::RelativeTimestamp::hours:
346       mDuration = int64_t( duration * MILLISECONDS_IN_HOUR + 0.5 );
347       break;
348     case MDAL::RelativeTimestamp::days:
349       mDuration = int64_t( duration * MILLISECONDS_IN_DAY + 0.5 );
350       break;
351     case MDAL::RelativeTimestamp::weeks:
352       mDuration = int64_t( duration * MILLISECONDS_IN_WEEK + 0.5 );
353       break;
354     case MDAL::RelativeTimestamp::months_CF:
355       mDuration = int64_t( duration * MILLISECONDS_IN_MONTH_CF + 0.5 );
356       break;
357     case MDAL::RelativeTimestamp::exact_years:
358       mDuration = int64_t( duration * MILLISECONDS_IN_EXACT_YEAR + 0.5 );
359       break;
360   }
361 }
362 
value(MDAL::RelativeTimestamp::Unit unit) const363 double MDAL::RelativeTimestamp::value( MDAL::RelativeTimestamp::Unit unit ) const
364 {
365   switch ( unit )
366   {
367     case MDAL::RelativeTimestamp::milliseconds:
368       return double( mDuration );
369     case MDAL::RelativeTimestamp::seconds:
370       return mDuration / MILLISECONDS_IN_SECOND;
371     case MDAL::RelativeTimestamp::minutes:
372       return mDuration  / MILLISECONDS_IN_MINUTE;
373     case MDAL::RelativeTimestamp::hours:
374       return mDuration / MILLISECONDS_IN_HOUR;
375     case MDAL::RelativeTimestamp::days:
376       return double( mDuration ) / MILLISECONDS_IN_DAY;
377     case MDAL::RelativeTimestamp::weeks:
378       return double( mDuration )  / MILLISECONDS_IN_WEEK;
379     case MDAL::RelativeTimestamp::months_CF:
380       return double( mDuration ) / MILLISECONDS_IN_MONTH_CF;
381     case MDAL::RelativeTimestamp::exact_years:
382       return double( mDuration )  / MILLISECONDS_IN_EXACT_YEAR;
383   }
384 
385   return 0;
386 }
387 
operator ==(const MDAL::RelativeTimestamp & other) const388 bool MDAL::RelativeTimestamp::operator==( const MDAL::RelativeTimestamp &other ) const
389 {
390   return mDuration == other.mDuration;
391 }
392 
operator <(const MDAL::RelativeTimestamp & other) const393 bool MDAL::RelativeTimestamp::operator<( const MDAL::RelativeTimestamp &other ) const
394 {
395   return mDuration < other.mDuration;
396 }
397 
RelativeTimestamp(int64_t ms)398 MDAL::RelativeTimestamp::RelativeTimestamp( int64_t ms ): mDuration( ms )
399 {}
400