2021-12-22 16:33:38 -05:00
/*
2022-01-31 13:07:22 -05:00
* Copyright ( c ) 2022 , Tim Flynn < trflynn89 @ serenityos . org >
2021-12-22 16:33:38 -05:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2021-12-28 12:53:53 -05:00
# include "../LibUnicode/GeneratorUtil.h" // FIXME: Move this somewhere common.
2022-03-16 19:08:53 -06:00
# include <AK/DateConstants.h>
2022-01-10 16:56:09 -05:00
# include <AK/Format.h>
2021-12-28 12:53:53 -05:00
# include <AK/HashMap.h>
2021-12-22 16:33:38 -05:00
# include <AK/SourceGenerator.h>
# include <AK/String.h>
# include <AK/StringBuilder.h>
# include <AK/Vector.h>
# include <LibCore/ArgsParser.h>
2022-02-06 18:40:55 +00:00
# include <LibCore/Stream.h>
2022-02-02 14:31:05 -05:00
# include <LibTimeZone/TimeZone.h>
2021-12-22 16:33:38 -05:00
2022-01-10 15:20:48 +01:00
namespace {
2022-07-05 15:56:23 -04:00
using StringIndexType = u16 ;
constexpr auto s_string_index_type = " u16 " sv ;
2022-01-24 10:25:47 -05:00
2021-12-28 12:53:53 -05:00
struct DateTime {
u16 year { 0 } ;
Optional < u8 > month ;
2022-01-10 16:56:09 -05:00
Optional < u8 > day ;
2021-12-28 12:53:53 -05:00
Optional < u8 > last_weekday ;
Optional < u8 > after_weekday ;
2022-01-18 11:20:43 -05:00
Optional < u8 > before_weekday ;
2022-01-10 16:56:09 -05:00
Optional < u8 > hour ;
Optional < u8 > minute ;
Optional < u8 > second ;
2021-12-28 12:53:53 -05:00
} ;
2022-01-10 16:56:09 -05:00
struct TimeZoneOffset {
i64 offset { 0 } ;
2021-12-28 12:53:53 -05:00
Optional < DateTime > until ;
2022-01-18 12:20:15 -05:00
Optional < String > dst_rule ;
Optional < i32 > dst_rule_index ;
i64 dst_offset { 0 } ;
2022-01-24 10:25:47 -05:00
StringIndexType standard_format { 0 } ;
StringIndexType daylight_format { 0 } ;
2021-12-28 12:53:53 -05:00
} ;
2022-01-18 11:20:43 -05:00
struct DaylightSavingsOffset {
i64 offset { 0 } ;
u16 year_from { 0 } ;
u16 year_to { 0 } ;
DateTime in_effect ;
2022-01-24 10:25:47 -05:00
StringIndexType format { 0 } ;
2022-01-18 11:20:43 -05:00
} ;
2021-12-28 12:53:53 -05:00
struct TimeZoneData {
2022-01-24 10:25:47 -05:00
UniqueStringStorage < StringIndexType > unique_strings ;
2022-01-10 16:56:09 -05:00
HashMap < String , Vector < TimeZoneOffset > > time_zones ;
2022-01-08 08:26:04 -05:00
Vector < String > time_zone_names ;
Vector < Alias > time_zone_aliases ;
2022-01-18 11:20:43 -05:00
HashMap < String , Vector < DaylightSavingsOffset > > dst_offsets ;
Vector < String > dst_offset_names ;
2022-02-02 14:31:05 -05:00
HashMap < String , TimeZone : : Location > time_zone_coordinates ;
2022-07-05 15:56:23 -04:00
HashMap < String , Vector < StringIndexType > > time_zone_regions ;
Vector < String > time_zone_region_names ;
2021-12-28 12:53:53 -05:00
} ;
2022-01-10 16:56:09 -05:00
}
2021-12-28 12:53:53 -05:00
2022-01-10 16:56:09 -05:00
template < >
struct AK : : Formatter < DateTime > : Formatter < FormatString > {
ErrorOr < void > format ( FormatBuilder & builder , DateTime const & date_time )
{
return Formatter < FormatString > : : format ( builder ,
2022-07-11 17:32:29 +00:00
" {{ {}, {}, {}, {}, {}, {}, {}, {}, {} }} " sv ,
2022-01-10 16:56:09 -05:00
date_time . year ,
date_time . month . value_or ( 1 ) ,
date_time . day . value_or ( 1 ) ,
date_time . last_weekday . value_or ( 0 ) ,
date_time . after_weekday . value_or ( 0 ) ,
2022-01-18 11:20:43 -05:00
date_time . before_weekday . value_or ( 0 ) ,
2022-01-10 16:56:09 -05:00
date_time . hour . value_or ( 0 ) ,
date_time . minute . value_or ( 0 ) ,
date_time . second . value_or ( 0 ) ) ;
}
} ;
2021-12-28 12:53:53 -05:00
2022-01-10 16:56:09 -05:00
template < >
struct AK : : Formatter < TimeZoneOffset > : Formatter < FormatString > {
ErrorOr < void > format ( FormatBuilder & builder , TimeZoneOffset const & time_zone_offset )
{
return Formatter < FormatString > : : format ( builder ,
2022-07-11 17:32:29 +00:00
" {{ {}, {}, {}, {}, {}, {}, {} }} " sv ,
2022-01-10 16:56:09 -05:00
time_zone_offset . offset ,
time_zone_offset . until . value_or ( { } ) ,
2022-01-18 12:20:15 -05:00
time_zone_offset . until . has_value ( ) ,
time_zone_offset . dst_rule_index . value_or ( - 1 ) ,
2022-01-24 10:25:47 -05:00
time_zone_offset . dst_offset ,
time_zone_offset . standard_format ,
time_zone_offset . daylight_format ) ;
2022-01-10 16:56:09 -05:00
}
} ;
2021-12-28 12:53:53 -05:00
2022-01-18 11:20:43 -05:00
template < >
struct AK : : Formatter < DaylightSavingsOffset > : Formatter < FormatString > {
ErrorOr < void > format ( FormatBuilder & builder , DaylightSavingsOffset const & dst_offset )
{
return Formatter < FormatString > : : format ( builder ,
2022-07-11 17:32:29 +00:00
" {{ {}, {}, {}, {}, {} }} " sv ,
2022-01-18 11:20:43 -05:00
dst_offset . offset ,
dst_offset . year_from ,
dst_offset . year_to ,
2022-01-24 10:25:47 -05:00
dst_offset . in_effect ,
dst_offset . format ) ;
2022-01-18 11:20:43 -05:00
}
} ;
2022-02-02 14:31:05 -05:00
template < >
struct AK : : Formatter < TimeZone : : Coordinate > : Formatter < FormatString > {
ErrorOr < void > format ( FormatBuilder & builder , TimeZone : : Coordinate const & coordinate )
{
return Formatter < FormatString > : : format ( builder ,
2022-07-11 17:32:29 +00:00
" {{ {}, {}, {} }} " sv ,
2022-02-02 14:31:05 -05:00
coordinate . degrees ,
coordinate . minutes ,
coordinate . seconds ) ;
}
} ;
template < >
struct AK : : Formatter < TimeZone : : Location > : Formatter < FormatString > {
ErrorOr < void > format ( FormatBuilder & builder , TimeZone : : Location const & location )
{
return Formatter < FormatString > : : format ( builder ,
2022-07-11 17:32:29 +00:00
" {{ {}, {} }} " sv ,
2022-02-02 14:31:05 -05:00
location . latitude ,
location . longitude ) ;
}
} ;
2021-12-28 12:53:53 -05:00
static Optional < DateTime > parse_date_time ( Span < StringView const > segments )
{
auto comment_index = find_index ( segments . begin ( ) , segments . end ( ) , " # " sv ) ;
if ( comment_index ! = segments . size ( ) )
segments = segments . slice ( 0 , comment_index ) ;
if ( segments . is_empty ( ) )
return { } ;
DateTime date_time { } ;
date_time . year = segments [ 0 ] . to_uint ( ) . value ( ) ;
if ( segments . size ( ) > 1 )
2022-03-16 19:08:53 -06:00
date_time . month = find_index ( short_month_names . begin ( ) , short_month_names . end ( ) , segments [ 1 ] ) + 1 ;
2021-12-28 12:53:53 -05:00
if ( segments . size ( ) > 2 ) {
if ( segments [ 2 ] . starts_with ( " last " sv ) ) {
auto weekday = segments [ 2 ] . substring_view ( " last " sv . length ( ) ) ;
2022-03-16 19:08:53 -06:00
date_time . last_weekday = find_index ( short_day_names . begin ( ) , short_day_names . end ( ) , weekday ) ;
2021-12-28 12:53:53 -05:00
} else if ( auto index = segments [ 2 ] . find ( " >= " sv ) ; index . has_value ( ) ) {
auto weekday = segments [ 2 ] . substring_view ( 0 , * index ) ;
2022-03-16 19:08:53 -06:00
date_time . after_weekday = find_index ( short_day_names . begin ( ) , short_day_names . end ( ) , weekday ) ;
2021-12-28 12:53:53 -05:00
auto day = segments [ 2 ] . substring_view ( * index + " >= " sv . length ( ) ) ;
date_time . day = day . to_uint ( ) . value ( ) ;
2022-01-18 11:20:43 -05:00
} else if ( auto index = segments [ 2 ] . find ( " <= " sv ) ; index . has_value ( ) ) {
auto weekday = segments [ 2 ] . substring_view ( 0 , * index ) ;
2022-03-16 19:08:53 -06:00
date_time . before_weekday = find_index ( short_day_names . begin ( ) , short_day_names . end ( ) , weekday ) ;
2022-01-18 11:20:43 -05:00
auto day = segments [ 2 ] . substring_view ( * index + " <= " sv . length ( ) ) ;
date_time . day = day . to_uint ( ) . value ( ) ;
2021-12-28 12:53:53 -05:00
} else {
date_time . day = segments [ 2 ] . to_uint ( ) . value ( ) ;
}
}
2022-01-10 16:56:09 -05:00
if ( segments . size ( ) > 3 ) {
// FIXME: Some times end with a letter, e.g. "2:00u" and "2:00s". Figure out what this means and handle it.
auto time_segments = segments [ 3 ] . split_view ( ' : ' ) ;
date_time . hour = time_segments [ 0 ] . to_int ( ) . value ( ) ;
date_time . minute = time_segments . size ( ) > 1 ? time_segments [ 1 ] . substring_view ( 0 , 2 ) . to_uint ( ) . value ( ) : 0 ;
date_time . second = time_segments . size ( ) > 2 ? time_segments [ 2 ] . substring_view ( 0 , 2 ) . to_uint ( ) . value ( ) : 0 ;
}
2021-12-28 12:53:53 -05:00
return date_time ;
}
2022-01-10 16:56:09 -05:00
static i64 parse_time_offset ( StringView segment )
{
auto segments = segment . split_view ( ' : ' ) ;
i64 hours = segments [ 0 ] . to_int ( ) . value ( ) ;
i64 minutes = segments . size ( ) > 1 ? segments [ 1 ] . to_uint ( ) . value ( ) : 0 ;
i64 seconds = segments . size ( ) > 2 ? segments [ 2 ] . to_uint ( ) . value ( ) : 0 ;
i64 sign = ( ( hours < 0 ) | | ( segments [ 0 ] = = " -0 " sv ) ) ? - 1 : 1 ;
return ( hours * 3600 ) + sign * ( ( minutes * 60 ) + seconds ) ;
}
2022-01-18 12:20:15 -05:00
static void parse_dst_rule ( StringView segment , TimeZoneOffset & time_zone )
{
if ( segment . contains ( ' : ' ) )
time_zone . dst_offset = parse_time_offset ( segment ) ;
else if ( segment ! = " - " sv )
time_zone . dst_rule = segment ;
}
2022-01-24 10:25:47 -05:00
static void parse_format ( StringView format , TimeZoneData & time_zone_data , TimeZoneOffset & time_zone )
{
2022-07-05 22:33:15 +02:00
auto formats = format . replace ( " %s " sv , " {} " sv , ReplaceMode : : FirstOnly ) . split ( ' / ' ) ;
2022-01-24 10:25:47 -05:00
VERIFY ( formats . size ( ) < = 2 ) ;
time_zone . standard_format = time_zone_data . unique_strings . ensure ( formats [ 0 ] ) ;
if ( formats . size ( ) = = 2 )
time_zone . daylight_format = time_zone_data . unique_strings . ensure ( formats [ 1 ] ) ;
else
time_zone . daylight_format = time_zone . standard_format ;
}
2022-01-10 16:56:09 -05:00
static Vector < TimeZoneOffset > & parse_zone ( StringView zone_line , TimeZoneData & time_zone_data )
2021-12-28 12:53:53 -05:00
{
auto segments = zone_line . split_view_if ( [ ] ( char ch ) { return ( ch = = ' \t ' ) | | ( ch = = ' ' ) ; } ) ;
// "Zone" NAME STDOFF RULES FORMAT [UNTIL]
VERIFY ( segments [ 0 ] = = " Zone " sv ) ;
auto name = segments [ 1 ] ;
2022-01-10 16:56:09 -05:00
TimeZoneOffset time_zone { } ;
time_zone . offset = parse_time_offset ( segments [ 2 ] ) ;
2022-01-18 12:20:15 -05:00
parse_dst_rule ( segments [ 3 ] , time_zone ) ;
2022-01-24 10:25:47 -05:00
parse_format ( segments [ 4 ] , time_zone_data , time_zone ) ;
2021-12-28 12:53:53 -05:00
if ( segments . size ( ) > 5 )
time_zone . until = parse_date_time ( segments . span ( ) . slice ( 5 ) ) ;
auto & time_zones = time_zone_data . time_zones . ensure ( name ) ;
time_zones . append ( move ( time_zone ) ) ;
2022-01-08 08:26:04 -05:00
if ( ! time_zone_data . time_zone_names . contains_slow ( name ) )
time_zone_data . time_zone_names . append ( name ) ;
2021-12-28 12:53:53 -05:00
return time_zones ;
}
2022-01-24 10:25:47 -05:00
static void parse_zone_continuation ( StringView zone_line , TimeZoneData & time_zone_data , Vector < TimeZoneOffset > & time_zones )
2021-12-28 12:53:53 -05:00
{
auto segments = zone_line . split_view_if ( [ ] ( char ch ) { return ( ch = = ' \t ' ) | | ( ch = = ' ' ) ; } ) ;
// STDOFF RULES FORMAT [UNTIL]
2022-01-10 16:56:09 -05:00
TimeZoneOffset time_zone { } ;
time_zone . offset = parse_time_offset ( segments [ 0 ] ) ;
2022-01-18 12:20:15 -05:00
parse_dst_rule ( segments [ 1 ] , time_zone ) ;
2022-01-24 10:25:47 -05:00
parse_format ( segments [ 2 ] , time_zone_data , time_zone ) ;
2021-12-28 12:53:53 -05:00
if ( segments . size ( ) > 3 )
time_zone . until = parse_date_time ( segments . span ( ) . slice ( 3 ) ) ;
time_zones . append ( move ( time_zone ) ) ;
}
2022-01-07 08:59:52 -05:00
static void parse_link ( StringView link_line , TimeZoneData & time_zone_data )
{
auto segments = link_line . split_view_if ( [ ] ( char ch ) { return ( ch = = ' \t ' ) | | ( ch = = ' ' ) ; } ) ;
// Link TARGET LINK-NAME
VERIFY ( segments [ 0 ] = = " Link " sv ) ;
auto target = segments [ 1 ] ;
auto alias = segments [ 2 ] ;
2022-01-08 08:26:04 -05:00
time_zone_data . time_zone_aliases . append ( { target , alias } ) ;
2022-01-07 08:59:52 -05:00
}
2022-01-18 11:20:43 -05:00
static void parse_rule ( StringView rule_line , TimeZoneData & time_zone_data )
{
auto segments = rule_line . split_view_if ( [ ] ( char ch ) { return ( ch = = ' \t ' ) | | ( ch = = ' ' ) ; } ) ;
// Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
VERIFY ( segments [ 0 ] = = " Rule " sv ) ;
auto name = segments [ 1 ] ;
DaylightSavingsOffset dst_offset { } ;
dst_offset . offset = parse_time_offset ( segments [ 8 ] ) ;
dst_offset . year_from = segments [ 2 ] . to_uint ( ) . value ( ) ;
if ( segments [ 3 ] = = " only " )
dst_offset . year_to = dst_offset . year_from ;
else if ( segments [ 3 ] = = " max " sv )
dst_offset . year_to = NumericLimits < u16 > : : max ( ) ;
else
dst_offset . year_to = segments [ 3 ] . to_uint ( ) . value ( ) ;
auto in_effect = Array { " 0 " sv , segments [ 5 ] , segments [ 6 ] , segments [ 7 ] } ;
dst_offset . in_effect = parse_date_time ( in_effect ) . release_value ( ) ;
2022-01-24 10:25:47 -05:00
if ( segments [ 9 ] ! = " - " sv )
dst_offset . format = time_zone_data . unique_strings . ensure ( segments [ 9 ] ) ;
2022-01-18 11:20:43 -05:00
auto & dst_offsets = time_zone_data . dst_offsets . ensure ( name ) ;
dst_offsets . append ( move ( dst_offset ) ) ;
if ( ! time_zone_data . dst_offset_names . contains_slow ( name ) )
time_zone_data . dst_offset_names . append ( name ) ;
}
2021-12-28 12:53:53 -05:00
static ErrorOr < void > parse_time_zones ( StringView time_zone_path , TimeZoneData & time_zone_data )
{
// For reference, the man page for `zic` has the best documentation of the TZDB file format.
2022-02-06 18:40:55 +00:00
auto file = TRY ( open_file ( time_zone_path , Core : : Stream : : OpenMode : : Read ) ) ;
Array < u8 , 1024 > buffer { } ;
2022-01-10 16:56:09 -05:00
Vector < TimeZoneOffset > * last_parsed_zone = nullptr ;
2021-12-28 12:53:53 -05:00
2022-02-06 18:40:55 +00:00
while ( TRY ( file - > can_read_line ( ) ) ) {
2022-04-15 14:52:33 +01:00
auto line = TRY ( file - > read_line ( buffer ) ) ;
2022-02-06 18:40:55 +00:00
2021-12-28 12:53:53 -05:00
if ( line . is_empty ( ) | | line . trim_whitespace ( TrimMode : : Left ) . starts_with ( ' # ' ) )
continue ;
if ( line . starts_with ( " Zone " sv ) ) {
last_parsed_zone = & parse_zone ( line , time_zone_data ) ;
} else if ( line . starts_with ( ' \t ' ) ) {
VERIFY ( last_parsed_zone ! = nullptr ) ;
2022-01-24 10:25:47 -05:00
parse_zone_continuation ( line , time_zone_data , * last_parsed_zone ) ;
2021-12-28 12:53:53 -05:00
} else {
last_parsed_zone = nullptr ;
2022-01-07 08:59:52 -05:00
if ( line . starts_with ( " Link " sv ) )
parse_link ( line , time_zone_data ) ;
2022-01-18 11:20:43 -05:00
else if ( line . starts_with ( " Rule " sv ) )
parse_rule ( line , time_zone_data ) ;
2021-12-28 12:53:53 -05:00
}
}
return { } ;
}
2022-02-06 18:40:55 +00:00
static ErrorOr < void > parse_time_zone_coordinates ( Core : : Stream : : BufferedFile & file , TimeZoneData & time_zone_data )
2022-02-02 14:31:05 -05:00
{
auto parse_coordinate = [ ] ( auto coordinate ) {
VERIFY ( coordinate . substring_view ( 0 , 1 ) . is_one_of ( " + " sv , " - " sv ) ) ;
TimeZone : : Coordinate parsed { } ;
if ( coordinate . length ( ) = = 5 ) {
// ±DDMM
parsed . degrees = coordinate . substring_view ( 0 , 3 ) . to_int ( ) . value ( ) ;
parsed . minutes = coordinate . substring_view ( 3 ) . to_int ( ) . value ( ) ;
} else if ( coordinate . length ( ) = = 6 ) {
// ±DDDMM
parsed . degrees = coordinate . substring_view ( 0 , 4 ) . to_int ( ) . value ( ) ;
parsed . minutes = coordinate . substring_view ( 4 ) . to_int ( ) . value ( ) ;
} else if ( coordinate . length ( ) = = 7 ) {
// ±DDMMSS
parsed . degrees = coordinate . substring_view ( 0 , 3 ) . to_int ( ) . value ( ) ;
parsed . minutes = coordinate . substring_view ( 3 , 2 ) . to_int ( ) . value ( ) ;
parsed . seconds = coordinate . substring_view ( 5 ) . to_int ( ) . value ( ) ;
} else if ( coordinate . length ( ) = = 8 ) {
// ±DDDDMMSS
parsed . degrees = coordinate . substring_view ( 0 , 4 ) . to_int ( ) . value ( ) ;
parsed . minutes = coordinate . substring_view ( 4 , 2 ) . to_int ( ) . value ( ) ;
parsed . seconds = coordinate . substring_view ( 6 ) . to_int ( ) . value ( ) ;
} else {
VERIFY_NOT_REACHED ( ) ;
}
return parsed ;
} ;
2022-02-06 18:40:55 +00:00
Array < u8 , 1024 > buffer { } ;
while ( TRY ( file . can_read_line ( ) ) ) {
2022-04-15 14:52:33 +01:00
auto line = TRY ( file . read_line ( buffer ) ) ;
2022-02-06 18:40:55 +00:00
2022-02-02 14:31:05 -05:00
if ( line . is_empty ( ) | | line . trim_whitespace ( TrimMode : : Left ) . starts_with ( ' # ' ) )
continue ;
auto segments = line . split_view ( ' \t ' ) ;
2022-07-05 15:56:23 -04:00
auto regions = segments [ 0 ] ;
2022-02-02 14:31:05 -05:00
auto coordinates = segments [ 1 ] ;
auto zone = segments [ 2 ] ;
VERIFY ( time_zone_data . time_zones . contains ( zone ) ) ;
auto index = coordinates . find_any_of ( " +- " sv , StringView : : SearchDirection : : Backward ) . value ( ) ;
auto latitude = parse_coordinate ( coordinates . substring_view ( 0 , index ) ) ;
auto longitude = parse_coordinate ( coordinates . substring_view ( index ) ) ;
time_zone_data . time_zone_coordinates . set ( zone , { latitude , longitude } ) ;
2022-07-05 15:56:23 -04:00
regions . for_each_split_view ( ' , ' , false , [ & ] ( auto region ) {
auto index = time_zone_data . unique_strings . ensure ( zone ) ;
time_zone_data . time_zone_regions . ensure ( region ) . append ( index ) ;
if ( ! time_zone_data . time_zone_region_names . contains_slow ( region ) )
time_zone_data . time_zone_region_names . append ( region ) ;
} ) ;
2022-02-02 14:31:05 -05:00
}
2022-02-06 18:40:55 +00:00
return { } ;
2022-02-02 14:31:05 -05:00
}
2022-01-18 12:20:15 -05:00
static void set_dst_rule_indices ( TimeZoneData & time_zone_data )
{
for ( auto & time_zone : time_zone_data . time_zones ) {
for ( auto & time_zone_offset : time_zone . value ) {
if ( ! time_zone_offset . dst_rule . has_value ( ) )
continue ;
auto dst_rule_index = time_zone_data . dst_offset_names . find_first_index ( * time_zone_offset . dst_rule ) ;
time_zone_offset . dst_rule_index = static_cast < i32 > ( dst_rule_index . value ( ) ) ;
}
}
}
2021-12-28 12:53:53 -05:00
static String format_identifier ( StringView owner , String identifier )
{
2022-01-07 09:05:35 -05:00
constexpr auto gmt_time_zones = Array { " Etc/GMT " sv , " GMT " sv } ;
2021-12-28 12:53:53 -05:00
2022-01-07 09:05:35 -05:00
for ( auto gmt_time_zone : gmt_time_zones ) {
if ( identifier . starts_with ( gmt_time_zone ) ) {
auto offset = identifier . substring_view ( gmt_time_zone . length ( ) ) ;
2021-12-28 12:53:53 -05:00
2022-01-07 09:05:35 -05:00
if ( offset . starts_with ( ' + ' ) )
2022-01-10 16:41:08 -05:00
identifier = String : : formatted ( " {}_Ahead_{} " , gmt_time_zone , offset . substring_view ( 1 ) ) ;
2022-01-07 09:05:35 -05:00
else if ( offset . starts_with ( ' - ' ) )
2022-01-10 16:41:08 -05:00
identifier = String : : formatted ( " {}_Behind_{} " , gmt_time_zone , offset . substring_view ( 1 ) ) ;
2022-01-07 09:05:35 -05:00
}
2021-12-28 12:53:53 -05:00
}
2022-07-05 22:33:15 +02:00
identifier = identifier . replace ( " - " sv , " _ " sv , ReplaceMode : : All ) ;
identifier = identifier . replace ( " / " sv , " _ " sv , ReplaceMode : : All ) ;
2021-12-28 12:53:53 -05:00
if ( all_of ( identifier , is_ascii_digit ) )
return String : : formatted ( " {}_{} " , owner [ 0 ] , identifier ) ;
if ( is_ascii_lower_alpha ( identifier [ 0 ] ) )
return String : : formatted ( " {:c}{} " , to_ascii_uppercase ( identifier [ 0 ] ) , identifier . substring_view ( 1 ) ) ;
return identifier ;
}
2022-02-06 18:40:55 +00:00
static ErrorOr < void > generate_time_zone_data_header ( Core : : Stream : : BufferedFile & file , TimeZoneData & time_zone_data )
2021-12-22 16:33:38 -05:00
{
StringBuilder builder ;
SourceGenerator generator { builder } ;
generator . append ( R " ~~~(
# pragma once
2021-12-28 12:53:53 -05:00
# include <AK/Types.h>
namespace TimeZone {
) ~ ~ ~ " );
2022-01-08 08:26:04 -05:00
generate_enum ( generator , format_identifier , " TimeZone " sv , { } , time_zone_data . time_zone_names , time_zone_data . time_zone_aliases ) ;
2022-01-18 11:20:43 -05:00
generate_enum ( generator , format_identifier , " DaylightSavingsRule " sv , { } , time_zone_data . dst_offset_names ) ;
2022-07-05 15:56:23 -04:00
generate_enum ( generator , format_identifier , " Region " sv , { } , time_zone_data . time_zone_region_names ) ;
2021-12-28 12:53:53 -05:00
generator . append ( R " ~~~(
}
2021-12-22 16:33:38 -05:00
) ~ ~ ~ " );
2022-02-06 18:40:55 +00:00
TRY ( file . write ( generator . as_string_view ( ) . bytes ( ) ) ) ;
return { } ;
2021-12-22 16:33:38 -05:00
}
2022-02-06 18:40:55 +00:00
static ErrorOr < void > generate_time_zone_data_implementation ( Core : : Stream : : BufferedFile & file , TimeZoneData & time_zone_data )
2021-12-22 16:33:38 -05:00
{
StringBuilder builder ;
SourceGenerator generator { builder } ;
2022-01-24 10:25:47 -05:00
generator . set ( " string_index_type " sv , s_string_index_type ) ;
2021-12-22 16:33:38 -05:00
2022-01-18 12:20:15 -05:00
set_dst_rule_indices ( time_zone_data ) ;
2021-12-22 16:33:38 -05:00
generator . append ( R " ~~~(
2021-12-28 12:53:53 -05:00
# include <AK/Array.h>
# include <AK/BinarySearch.h>
# include <AK/Optional.h>
2022-01-10 16:56:09 -05:00
# include <AK/Span.h>
2021-12-28 12:53:53 -05:00
# include <AK/StringView.h>
2022-01-10 16:56:09 -05:00
# include <AK/Time.h>
2021-12-28 12:53:53 -05:00
# include <LibTimeZone/TimeZone.h>
2021-12-22 16:33:38 -05:00
# include <LibTimeZone/TimeZoneData.h>
2021-12-28 12:53:53 -05:00
namespace TimeZone {
2022-01-10 16:56:09 -05:00
struct DateTime {
AK : : Time time_since_epoch ( ) const
{
2022-01-18 11:20:43 -05:00
// FIXME: This implementation does not take last_weekday, after_weekday, or before_weekday into account.
2022-01-10 22:41:40 -05:00
return AK : : Time : : from_timestamp ( year , month , day , hour , minute , second , 0 ) ;
2022-01-10 16:56:09 -05:00
}
u16 year { 0 } ;
u8 month { 1 } ;
u8 day { 1 } ;
u8 last_weekday { 0 } ;
u8 after_weekday { 0 } ;
2022-01-18 11:20:43 -05:00
u8 before_weekday { 0 } ;
2022-01-10 16:56:09 -05:00
u8 hour { 0 } ;
u8 minute { 0 } ;
u8 second { 0 } ;
} ;
struct TimeZoneOffset {
i64 offset { 0 } ;
DateTime until { } ;
bool has_until { false } ;
2022-01-18 12:20:15 -05:00
i32 dst_rule { - 1 } ;
i64 dst_offset { 0 } ;
2022-01-24 10:25:47 -05:00
@ string_index_type @ standard_format { 0 } ;
@ string_index_type @ daylight_format { 0 } ;
2022-01-10 16:56:09 -05:00
} ;
2022-01-18 11:20:43 -05:00
struct DaylightSavingsOffset {
2022-01-24 11:19:35 -05:00
AK : : Time time_in_effect ( AK : : Time time ) const
{
auto in_effect = this - > in_effect ;
in_effect . year = seconds_since_epoch_to_year ( time . to_seconds ( ) ) ;
return in_effect . time_since_epoch ( ) ;
}
2022-01-18 11:20:43 -05:00
i64 offset { 0 } ;
u16 year_from { 0 } ;
u16 year_to { 0 } ;
DateTime in_effect { } ;
2022-01-24 10:25:47 -05:00
@ string_index_type @ format { 0 } ;
2022-01-18 11:20:43 -05:00
} ;
2022-01-10 16:56:09 -05:00
) ~ ~ ~ " );
2022-01-24 10:25:47 -05:00
time_zone_data . unique_strings . generate ( generator ) ;
2022-01-18 11:20:43 -05:00
auto append_offsets = [ & ] ( auto const & name , auto type , auto const & offsets ) {
2022-01-10 16:56:09 -05:00
generator . set ( " name " , name ) ;
2022-01-18 11:20:43 -05:00
generator . set ( " type " , type ) ;
generator . set ( " size " , String : : number ( offsets . size ( ) ) ) ;
2022-01-10 16:56:09 -05:00
generator . append ( R " ~~~(
2022-01-18 11:20:43 -05:00
static constexpr Array < @ type @ , @ size @ > @ name @ { {
2021-12-28 12:53:53 -05:00
) ~ ~ ~ " );
2022-01-18 11:20:43 -05:00
for ( auto const & offset : offsets )
generator . append ( String : : formatted ( " {}, \n " , offset ) ) ;
2022-01-10 16:56:09 -05:00
generator . append ( " } }; \n " ) ;
} ;
2022-07-11 17:32:29 +00:00
generate_mapping ( generator , time_zone_data . time_zone_names , " TimeZoneOffset " sv , " s_time_zone_offsets " sv , " s_time_zone_offsets_{} " sv , format_identifier ,
2022-01-10 16:56:09 -05:00
[ & ] ( auto const & name , auto const & value ) {
auto const & time_zone_offsets = time_zone_data . time_zones . find ( value ) - > value ;
2022-01-18 11:20:43 -05:00
append_offsets ( name , " TimeZoneOffset " sv , time_zone_offsets ) ;
} ) ;
2022-07-11 17:32:29 +00:00
generate_mapping ( generator , time_zone_data . dst_offset_names , " DaylightSavingsOffset " sv , " s_dst_offsets " sv , " s_dst_offsets_{} " sv , format_identifier ,
2022-01-18 11:20:43 -05:00
[ & ] ( auto const & name , auto const & value ) {
auto const & dst_offsets = time_zone_data . dst_offsets . find ( value ) - > value ;
append_offsets ( name , " DaylightSavingsOffset " sv , dst_offsets ) ;
2022-01-10 16:56:09 -05:00
} ) ;
2022-07-11 17:32:29 +00:00
generate_mapping ( generator , time_zone_data . time_zone_region_names , s_string_index_type , " s_regional_time_zones " sv , " s_regional_time_zones_{} " sv , format_identifier ,
2022-07-05 15:56:23 -04:00
[ & ] ( auto const & name , auto const & value ) {
auto const & time_zones = time_zone_data . time_zone_regions . find ( value ) - > value ;
generator . set ( " name " , name ) ;
generator . set ( " size " , String : : number ( time_zones . size ( ) ) ) ;
generator . append ( R " ~~~(
static constexpr Array < @ string_index_type @ , @ size @ > @ name @ { { ) ~ ~ ~ " );
bool first = true ;
for ( auto const & time_zone : time_zones ) {
2022-07-11 17:32:29 +00:00
generator . append ( first ? " " sv : " , " sv ) ;
2022-07-05 15:56:23 -04:00
generator . append ( String : : number ( time_zone ) ) ;
first = false ;
}
generator . append ( " } }; " ) ;
} ) ;
2022-02-02 14:31:05 -05:00
generator . set ( " size " , String : : number ( time_zone_data . time_zone_names . size ( ) ) ) ;
generator . append ( R " ~~~(
static constexpr Array < Location , @ size @ > s_time_zone_locations { {
) ~ ~ ~ " );
for ( auto const & time_zone : time_zone_data . time_zone_names ) {
auto location = time_zone_data . time_zone_coordinates . get ( time_zone ) . value_or ( { } ) ;
generator . append ( String : : formatted ( " {}, \n " , location ) ) ;
}
generator . append ( " } }; \n " ) ;
2022-01-18 11:20:43 -05:00
auto append_string_conversions = [ & ] ( StringView enum_title , StringView enum_snake , auto const & values , Vector < Alias > const & aliases = { } ) {
2021-12-28 12:53:53 -05:00
HashValueMap < String > hashes ;
hashes . ensure_capacity ( values . size ( ) ) ;
2022-01-10 12:23:22 -05:00
auto hash = [ ] ( auto const & value ) {
return CaseInsensitiveStringViewTraits : : hash ( value ) ;
} ;
2021-12-28 12:53:53 -05:00
for ( auto const & value : values )
2022-01-10 12:23:22 -05:00
hashes . set ( hash ( value ) , format_identifier ( enum_title , value ) ) ;
2022-01-07 08:59:52 -05:00
for ( auto const & alias : aliases )
2022-01-10 12:23:22 -05:00
hashes . set ( hash ( alias . alias ) , format_identifier ( enum_title , alias . alias ) ) ;
ValueFromStringOptions options { } ;
options . sensitivity = CaseSensitivity : : CaseInsensitive ;
2021-12-28 12:53:53 -05:00
2022-01-10 12:23:22 -05:00
generate_value_from_string ( generator , " {}_from_string " sv , enum_title , enum_snake , move ( hashes ) , options ) ;
2022-01-10 12:45:16 -05:00
generate_value_to_string ( generator , " {}_to_string " sv , enum_title , enum_snake , format_identifier , values ) ;
2021-12-28 12:53:53 -05:00
} ;
2022-01-10 12:45:16 -05:00
append_string_conversions ( " TimeZone " sv , " time_zone " sv , time_zone_data . time_zone_names , time_zone_data . time_zone_aliases ) ;
2022-01-18 11:20:43 -05:00
append_string_conversions ( " DaylightSavingsRule " sv , " daylight_savings_rule " sv , time_zone_data . dst_offset_names ) ;
2022-07-05 15:56:23 -04:00
append_string_conversions ( " Region " sv , " region " sv , time_zone_data . time_zone_region_names ) ;
2021-12-28 12:53:53 -05:00
generator . append ( R " ~~~(
2022-01-24 11:19:35 -05:00
static Array < DaylightSavingsOffset const * , 2 > find_dst_offsets ( TimeZoneOffset const & time_zone_offset , AK : : Time time )
2022-01-19 11:17:54 -05:00
{
auto const & dst_rules = s_dst_offsets [ time_zone_offset . dst_rule ] ;
DaylightSavingsOffset const * standard_offset = nullptr ;
DaylightSavingsOffset const * daylight_offset = nullptr ;
auto preferred_rule = [ & ] ( auto * current_offset , auto & new_offset ) {
if ( ! current_offset )
return & new_offset ;
2022-01-24 11:19:35 -05:00
auto new_time_in_effect = new_offset . time_in_effect ( time ) ;
2022-01-19 11:17:54 -05:00
return ( time > = new_time_in_effect ) ? & new_offset : current_offset ;
} ;
for ( size_t index = 0 ; ( index < dst_rules . size ( ) ) & & ( ! standard_offset | | ! daylight_offset ) ; + + index ) {
auto const & dst_rule = dst_rules [ index ] ;
auto year_from = AK : : Time : : from_timestamp ( dst_rule . year_from , 1 , 1 , 0 , 0 , 0 , 0 ) ;
auto year_to = AK : : Time : : from_timestamp ( dst_rule . year_to + 1 , 1 , 1 , 0 , 0 , 0 , 0 ) ;
if ( ( time < year_from ) | | ( time > = year_to ) )
continue ;
if ( dst_rule . offset = = 0 )
standard_offset = preferred_rule ( standard_offset , dst_rule ) ;
else
daylight_offset = preferred_rule ( daylight_offset , dst_rule ) ;
}
2022-01-24 11:19:35 -05:00
// In modern times, there will always be a standard rule in the TZDB, but that isn't true in
// all time zones in or before the early 1900s. For example, the "US" rules begin in 1918.
if ( ! standard_offset ) {
static DaylightSavingsOffset const empty_offset { } ;
return { & empty_offset , & empty_offset } ;
}
return { standard_offset , daylight_offset ? daylight_offset : standard_offset } ;
}
static Offset get_active_dst_offset ( TimeZoneOffset const & time_zone_offset , AK : : Time time )
{
auto offsets = find_dst_offsets ( time_zone_offset , time ) ;
if ( offsets [ 0 ] = = offsets [ 1 ] )
return { offsets [ 0 ] - > offset , InDST : : No } ;
2022-01-19 11:17:54 -05:00
2022-01-24 11:19:35 -05:00
auto standard_time_in_effect = offsets [ 0 ] - > time_in_effect ( time ) ;
auto daylight_time_in_effect = offsets [ 1 ] - > time_in_effect ( time ) ;
2022-01-19 11:17:54 -05:00
2022-01-24 13:33:19 -05:00
if ( daylight_time_in_effect < standard_time_in_effect ) {
if ( ( time < daylight_time_in_effect ) | | ( time > = standard_time_in_effect ) )
return { offsets [ 0 ] - > offset , InDST : : No } ;
} else {
if ( ( time > = standard_time_in_effect ) & & ( time < daylight_time_in_effect ) )
return { offsets [ 0 ] - > offset , InDST : : No } ;
}
2022-01-24 11:19:35 -05:00
return { offsets [ 1 ] - > offset , InDST : : Yes } ;
2022-01-19 11:17:54 -05:00
}
2022-01-24 11:19:35 -05:00
static TimeZoneOffset const & find_time_zone_offset ( TimeZone time_zone , AK : : Time time )
2022-01-10 16:56:09 -05:00
{
auto const & time_zone_offsets = s_time_zone_offsets [ to_underlying ( time_zone ) ] ;
size_t index = 0 ;
for ( ; index < time_zone_offsets . size ( ) ; + + index ) {
auto const & time_zone_offset = time_zone_offsets [ index ] ;
if ( ! time_zone_offset . has_until | | ( time_zone_offset . until . time_since_epoch ( ) > time ) )
break ;
}
VERIFY ( index < time_zone_offsets . size ( ) ) ;
2022-01-24 11:19:35 -05:00
return time_zone_offsets [ index ] ;
}
Optional < Offset > get_time_zone_offset ( TimeZone time_zone , AK : : Time time )
{
auto const & time_zone_offset = find_time_zone_offset ( time_zone , time ) ;
2022-01-19 11:17:54 -05:00
2022-01-19 14:18:02 -05:00
Offset dst_offset { } ;
if ( time_zone_offset . dst_rule ! = - 1 ) {
2022-01-24 11:19:35 -05:00
dst_offset = get_active_dst_offset ( time_zone_offset , time ) ;
2022-01-19 14:18:02 -05:00
} else {
auto in_dst = time_zone_offset . dst_offset = = 0 ? InDST : : No : InDST : : Yes ;
dst_offset = { time_zone_offset . dst_offset , in_dst } ;
}
2022-01-19 11:17:54 -05:00
2022-01-19 14:18:02 -05:00
dst_offset . seconds + = time_zone_offset . offset ;
return dst_offset ;
2021-12-28 12:53:53 -05:00
}
2021-12-22 16:33:38 -05:00
2022-01-24 12:36:17 -05:00
Optional < Array < NamedOffset , 2 > > get_named_time_zone_offsets ( TimeZone time_zone , AK : : Time time )
{
auto const & time_zone_offset = find_time_zone_offset ( time_zone , time ) ;
Array < NamedOffset , 2 > named_offsets ;
auto format_name = [ ] ( auto format , auto offset ) - > String {
if ( offset = = 0 )
2022-08-15 13:01:42 -04:00
return decode_string ( format ) . replace ( " { } " sv, " " sv, ReplaceMode::FirstOnly);
return String : : formatted ( decode_string ( format ) , decode_string ( offset ) ) ;
2022-01-24 12:36:17 -05:00
} ;
auto set_named_offset = [ & ] ( auto & named_offset , auto dst_offset , auto in_dst , auto format , auto offset ) {
named_offset . seconds = time_zone_offset . offset + dst_offset ;
named_offset . in_dst = in_dst ;
named_offset . name = format_name ( format , offset ) ;
} ;
if ( time_zone_offset . dst_rule ! = - 1 ) {
auto offsets = find_dst_offsets ( time_zone_offset , time ) ;
auto in_dst = offsets [ 1 ] - > offset = = 0 ? InDST : : No : InDST : : Yes ;
set_named_offset ( named_offsets [ 0 ] , offsets [ 0 ] - > offset , InDST : : No , time_zone_offset . standard_format , offsets [ 0 ] - > format ) ;
set_named_offset ( named_offsets [ 1 ] , offsets [ 1 ] - > offset , in_dst , time_zone_offset . daylight_format , offsets [ 1 ] - > format ) ;
} else {
auto in_dst = time_zone_offset . dst_offset = = 0 ? InDST : : No : InDST : : Yes ;
set_named_offset ( named_offsets [ 0 ] , time_zone_offset . dst_offset , in_dst , time_zone_offset . standard_format , 0 ) ;
set_named_offset ( named_offsets [ 1 ] , time_zone_offset . dst_offset , in_dst , time_zone_offset . daylight_format , 0 ) ;
}
return named_offsets ;
}
2022-02-02 14:31:05 -05:00
Optional < Location > get_time_zone_location ( TimeZone time_zone )
{
auto is_valid_coordinate = [ ] ( auto const & coordinate ) {
return ( coordinate . degrees ! = 0 ) | | ( coordinate . minutes ! = 0 ) | | ( coordinate . seconds ! = 0 ) ;
} ;
auto const & location = s_time_zone_locations [ to_underlying ( time_zone ) ] ;
if ( is_valid_coordinate ( location . latitude ) & & is_valid_coordinate ( location . longitude ) )
return location ;
return { } ;
}
2022-07-05 15:56:23 -04:00
Vector < StringView > time_zones_in_region ( StringView region )
{
auto region_value = region_from_string ( region ) ;
if ( ! region_value . has_value ( ) )
return { } ;
auto region_index = to_underlying ( * region_value ) ;
auto const & regional_time_zones = s_regional_time_zones [ region_index ] ;
Vector < StringView > time_zones ;
time_zones . ensure_capacity ( regional_time_zones . size ( ) ) ;
for ( auto time_zone : regional_time_zones )
2022-08-15 13:01:42 -04:00
time_zones . unchecked_append ( decode_string ( time_zone ) ) ;
2022-07-05 15:56:23 -04:00
return time_zones ;
}
2022-01-30 17:35:24 -05:00
) ~ ~ ~ " );
2022-01-24 12:36:17 -05:00
2022-01-30 17:35:24 -05:00
generate_available_values ( generator , " all_time_zones " sv , time_zone_data . time_zone_names ) ;
2022-01-19 18:05:31 -05:00
generator . append ( R " ~~~(
2021-12-22 16:33:38 -05:00
}
2022-01-10 16:56:09 -05:00
) ~ ~ ~ " );
2021-12-22 16:33:38 -05:00
2022-02-06 18:40:55 +00:00
TRY ( file . write ( generator . as_string_view ( ) . bytes ( ) ) ) ;
return { } ;
2022-01-10 15:20:48 +01:00
}
2021-12-22 16:33:38 -05:00
ErrorOr < int > serenity_main ( Main : : Arguments arguments )
{
StringView generated_header_path ;
StringView generated_implementation_path ;
2022-02-02 14:31:05 -05:00
StringView time_zone_coordinates_path ;
2021-12-22 16:33:38 -05:00
Vector < StringView > time_zone_paths ;
Core : : ArgsParser args_parser ;
args_parser . add_option ( generated_header_path , " Path to the time zone data header file to generate " , " generated-header-path " , ' h ' , " generated-header-path " ) ;
args_parser . add_option ( generated_implementation_path , " Path to the time zone data implementation file to generate " , " generated-implementation-path " , ' c ' , " generated-implementation-path " ) ;
2022-02-02 14:31:05 -05:00
args_parser . add_option ( time_zone_coordinates_path , " Path to the time zone data coordinates file " , " time-zone-coordinates-path " , ' z ' , " time-zone-coordinates-path " ) ;
2021-12-22 16:33:38 -05:00
args_parser . add_positional_argument ( time_zone_paths , " Paths to the time zone database files " , " time-zone-paths " ) ;
args_parser . parse ( arguments ) ;
2022-02-06 18:40:55 +00:00
auto generated_header_file = TRY ( open_file ( generated_header_path , Core : : Stream : : OpenMode : : Write ) ) ;
auto generated_implementation_file = TRY ( open_file ( generated_implementation_path , Core : : Stream : : OpenMode : : Write ) ) ;
auto time_zone_coordinates_file = TRY ( open_file ( time_zone_coordinates_path , Core : : Stream : : OpenMode : : Read ) ) ;
2021-12-22 16:33:38 -05:00
2021-12-28 12:53:53 -05:00
TimeZoneData time_zone_data { } ;
for ( auto time_zone_path : time_zone_paths )
TRY ( parse_time_zones ( time_zone_path , time_zone_data ) ) ;
2022-02-06 18:40:55 +00:00
TRY ( parse_time_zone_coordinates ( * time_zone_coordinates_file , time_zone_data ) ) ;
2022-02-02 14:31:05 -05:00
2022-02-06 18:40:55 +00:00
TRY ( generate_time_zone_data_header ( * generated_header_file , time_zone_data ) ) ;
TRY ( generate_time_zone_data_implementation ( * generated_implementation_file , time_zone_data ) ) ;
2021-12-22 16:33:38 -05:00
return 0 ;
}