2020-03-19 10:38:34 +00:00
# include "DateLUTImpl.h"
2018-01-23 00:09:09 +00:00
2020-03-19 10:38:34 +00:00
# include <cctz/civil_time.h>
2018-01-19 21:33:48 +00:00
# include <cctz/time_zone.h>
2020-04-22 03:17:29 +00:00
# include <cctz/zone_info_source.h>
2020-08-08 03:42:42 +00:00
# include <common/getResource.h>
2015-06-26 15:11:31 +00:00
# include <Poco/Exception.h>
2015-06-26 17:57:49 +00:00
2020-04-22 03:17:29 +00:00
# include <algorithm>
2020-03-19 10:38:34 +00:00
# include <cassert>
2017-01-21 02:32:02 +00:00
# include <chrono>
2015-06-26 17:57:49 +00:00
# include <cstring>
2016-12-27 06:36:53 +00:00
# include <iostream>
2020-03-19 10:38:34 +00:00
# include <memory>
2016-12-27 06:36:53 +00:00
namespace
{
2015-06-26 15:11:31 +00:00
2017-01-21 02:32:02 +00:00
UInt8 getDayOfWeek ( const cctz : : civil_day & date )
2015-06-26 15:11:31 +00:00
{
2017-04-01 07:20:54 +00:00
cctz : : weekday day_of_week = cctz : : get_weekday ( date ) ;
switch ( day_of_week )
{
case cctz : : weekday : : monday : return 1 ;
2017-06-22 19:51:38 +00:00
case cctz : : weekday : : tuesday : return 2 ;
case cctz : : weekday : : wednesday : return 3 ;
case cctz : : weekday : : thursday : return 4 ;
2017-04-01 07:20:54 +00:00
case cctz : : weekday : : friday : return 5 ;
2017-06-22 19:51:38 +00:00
case cctz : : weekday : : saturday : return 6 ;
2017-04-01 07:20:54 +00:00
case cctz : : weekday : : sunday : return 7 ;
}
2020-05-09 22:59:34 +00:00
__builtin_unreachable ( ) ;
2016-12-27 06:36:53 +00:00
}
}
2015-06-26 15:11:31 +00:00
2019-08-28 17:13:29 +00:00
__attribute__ ( ( __weak__ ) ) extern bool inside_main ;
2016-11-13 19:34:31 +00:00
DateLUTImpl : : DateLUTImpl ( const std : : string & time_zone_ )
2017-04-01 07:20:54 +00:00
: time_zone ( time_zone_ )
2015-06-26 15:11:31 +00:00
{
2019-08-28 17:13:29 +00:00
/// DateLUT should not be initialized in global constructors for the following reasons:
/// 1. It is too heavy.
if ( & inside_main )
assert ( inside_main ) ;
2017-04-01 07:20:54 +00:00
cctz : : time_zone cctz_time_zone ;
2020-03-08 21:04:10 +00:00
if ( ! cctz : : load_time_zone ( time_zone , & cctz_time_zone ) )
2017-04-01 07:20:54 +00:00
throw Poco : : Exception ( " Cannot load time zone " + time_zone_ ) ;
2020-04-17 13:26:44 +00:00
const cctz : : civil_day epoch { 1970 , 1 , 1 } ;
const cctz : : civil_day lut_start { DATE_LUT_MIN_YEAR , 1 , 1 } ;
2021-02-17 22:33:34 +00:00
time_t start_of_day ;
2020-04-17 13:26:44 +00:00
time_offset_epoch = cctz : : convert ( cctz : : civil_second ( lut_start ) , cctz_time_zone ) . time_since_epoch ( ) . count ( ) ;
// Note validated this against all timezones in the system.
assert ( ( epoch - lut_start ) = = daynum_offset_epoch ) ;
offset_at_start_of_epoch = cctz_time_zone . lookup ( cctz_time_zone . lookup ( epoch ) . pre ) . offset ;
offset_at_start_of_lut = cctz_time_zone . lookup ( cctz_time_zone . lookup ( lut_start ) . pre ) . offset ;
2018-12-23 21:45:28 +00:00
offset_is_whole_number_of_hours_everytime = true ;
2017-04-01 07:20:54 +00:00
2020-04-17 13:26:44 +00:00
cctz : : civil_day date = lut_start ;
2017-04-01 07:20:54 +00:00
2020-04-17 13:26:44 +00:00
UInt32 i = 0 ;
2017-04-01 07:20:54 +00:00
do
{
cctz : : time_zone : : civil_lookup lookup = cctz_time_zone . lookup ( date ) ;
2018-03-14 15:57:13 +00:00
start_of_day = std : : chrono : : system_clock : : to_time_t ( lookup . pre ) ; /// Ambiguity is possible.
2017-04-01 07:20:54 +00:00
Values & values = lut [ i ] ;
values . year = date . year ( ) ;
values . month = date . month ( ) ;
values . day_of_month = date . day ( ) ;
values . day_of_week = getDayOfWeek ( date ) ;
values . date = start_of_day ;
2020-04-17 13:26:44 +00:00
assert ( values . year > = DATE_LUT_MIN_YEAR & & values . year < = DATE_LUT_MAX_YEAR + 1 ) ;
2020-05-10 01:43:26 +00:00
assert ( values . month > = 1 & & values . month < = 12 ) ;
assert ( values . day_of_month > = 1 & & values . day_of_month < = 31 ) ;
assert ( values . day_of_week > = 1 & & values . day_of_week < = 7 ) ;
2017-10-29 04:18:48 +00:00
if ( values . day_of_month = = 1 )
{
cctz : : civil_month month ( date ) ;
values . days_in_month = cctz : : civil_day ( month + 1 ) - cctz : : civil_day ( month ) ;
}
else
values . days_in_month = i ! = 0 ? lut [ i - 1 ] . days_in_month : 31 ;
2020-04-17 13:26:44 +00:00
values . time_at_offset_change_value = 0 ;
values . amount_of_offset_change_value = 0 ;
2017-04-01 07:20:54 +00:00
2020-04-17 13:26:44 +00:00
// TODO: this partially ignores fractional pre-epoch offsets, which may cause incorrect toRelativeHourNum() results for some timezones, namelly Europe\Minsk
// when pre-May 2 1924 it had an offset of UTC+1:50, and after it was UTC+2h.
// https://www.timeanddate.com/time/zone/belarus/minsk?syear=1900
if ( start_of_day > 0 & & start_of_day % 3600 )
2018-12-23 21:45:28 +00:00
offset_is_whole_number_of_hours_everytime = false ;
2017-04-01 07:20:54 +00:00
/// If UTC offset was changed in previous day.
if ( i ! = 0 )
{
auto amount_of_offset_change_at_prev_day = 86400 - ( lut [ i ] . date - lut [ i - 1 ] . date ) ;
if ( amount_of_offset_change_at_prev_day )
{
2020-04-17 13:26:44 +00:00
lut [ i - 1 ] . amount_of_offset_change_value = amount_of_offset_change_at_prev_day / Values : : OffsetChangeFactor ;
2017-04-01 07:20:54 +00:00
const auto utc_offset_at_beginning_of_day = cctz_time_zone . lookup ( std : : chrono : : system_clock : : from_time_t ( lut [ i - 1 ] . date ) ) . offset ;
/// Find a time (timestamp offset from beginning of day),
/// when UTC offset was changed. Search is performed with 15-minute granularity, assuming it is enough.
time_t time_at_offset_change = 900 ;
2018-12-11 19:13:22 +00:00
while ( time_at_offset_change < 86400 )
2017-04-01 07:20:54 +00:00
{
auto utc_offset_at_current_time = cctz_time_zone . lookup ( std : : chrono : : system_clock : : from_time_t (
lut [ i - 1 ] . date + time_at_offset_change ) ) . offset ;
if ( utc_offset_at_current_time ! = utc_offset_at_beginning_of_day )
break ;
time_at_offset_change + = 900 ;
}
2020-04-17 13:26:44 +00:00
lut [ i - 1 ] . time_at_offset_change_value = time_at_offset_change / Values : : OffsetChangeFactor ;
2017-04-01 07:20:54 +00:00
2020-04-17 13:26:44 +00:00
/// We don't support cases when time change results in switching to previous day.
if ( static_cast < int > ( lut [ i - 1 ] . time_at_offset_change ( ) ) + static_cast < int > ( lut [ i - 1 ] . amount_of_offset_change ( ) ) < 0 )
lut [ i - 1 ] . time_at_offset_change_value = - lut [ i - 1 ] . amount_of_offset_change_value ;
2017-04-01 07:20:54 +00:00
}
}
/// Going to next day.
+ + date ;
+ + i ;
}
2020-04-17 13:26:44 +00:00
while ( i < DATE_LUT_SIZE & & lut [ i - 1 ] . year < = DATE_LUT_MAX_YEAR ) ;
// date_lut_max = start_of_day;
2017-04-01 07:20:54 +00:00
2017-10-29 00:51:40 +00:00
/// Fill excessive part of lookup table. This is needed only to simplify handling of overflow cases.
while ( i < DATE_LUT_SIZE )
{
2020-05-11 04:25:46 +00:00
lut [ i ] = lut [ i - 1 ] ;
2017-10-29 00:51:40 +00:00
+ + i ;
}
2017-11-15 22:58:50 +00:00
/// Fill lookup table for years and months.
2020-04-12 22:25:41 +00:00
size_t year_months_lut_index = 0 ;
size_t first_day_of_last_month = 0 ;
for ( size_t day = 0 ; day < DATE_LUT_SIZE ; + + day )
2017-04-01 07:20:54 +00:00
{
2017-11-15 22:58:50 +00:00
const Values & values = lut [ day ] ;
if ( values . day_of_month = = 1 )
{
if ( values . month = = 1 )
years_lut [ values . year - DATE_LUT_MIN_YEAR ] = day ;
2020-04-12 22:25:41 +00:00
year_months_lut_index = ( values . year - DATE_LUT_MIN_YEAR ) * 12 + values . month - 1 ;
years_months_lut [ year_months_lut_index ] = day ;
first_day_of_last_month = day ;
2017-11-15 22:58:50 +00:00
}
2017-04-01 07:20:54 +00:00
}
2020-04-12 22:25:41 +00:00
/// Fill the rest of lookup table with the same last month (2106-02-01).
for ( ; year_months_lut_index < DATE_LUT_YEARS * 12 ; + + year_months_lut_index )
{
years_months_lut [ year_months_lut_index ] = first_day_of_last_month ;
}
2015-06-26 15:11:31 +00:00
}
2020-04-22 03:17:29 +00:00
2020-04-22 17:11:36 +00:00
# if !defined(ARCADIA_BUILD) /// Arcadia's variant of CCTZ already has the same implementation.
2020-04-22 03:17:29 +00:00
/// Prefer to load timezones from blobs linked to the binary.
/// The blobs are provided by "tzdata" library.
/// This allows to avoid dependency on system tzdata.
namespace cctz_extension
{
namespace
{
class Source : public cctz : : ZoneInfoSource
{
public :
Source ( const char * data_ , size_t size_ ) : data ( data_ ) , size ( size_ ) { }
size_t Read ( void * buf , size_t bytes ) override
{
if ( bytes > size )
bytes = size ;
memcpy ( buf , data , bytes ) ;
data + = bytes ;
size - = bytes ;
return bytes ;
}
int Skip ( size_t offset ) override
{
if ( offset < = size )
{
data + = offset ;
size - = offset ;
return 0 ;
}
else
{
errno = EINVAL ;
return - 1 ;
}
}
private :
const char * data ;
size_t size ;
} ;
std : : unique_ptr < cctz : : ZoneInfoSource > custom_factory (
const std : : string & name ,
const std : : function < std : : unique_ptr < cctz : : ZoneInfoSource > ( const std : : string & name ) > & fallback )
{
2020-08-08 03:42:42 +00:00
std : : string_view resource = getResource ( name ) ;
if ( ! resource . empty ( ) )
return std : : make_unique < Source > ( resource . data ( ) , resource . size ( ) ) ;
2020-04-22 03:17:29 +00:00
return fallback ( name ) ;
}
}
ZoneInfoSourceFactory zone_info_source_factory = custom_factory ;
}
2020-04-22 17:11:36 +00:00
# endif