@@ -11,11 +11,21 @@ import timezone/tzparser
1111pub opaque type TzDatabase {
1212 TzDatabase (
1313 zone_names : List ( String ) ,
14- zone_data : dict . Dict ( String , tzparser . TZFile ) ,
14+ zone_data : dict . Dict ( String , tzparser . TzFile ) ,
1515 )
1616}
1717
18+ /// TzDatabase error types
19+ pub type TzDatabaseError {
20+ /// There is no information available for this zone.
21+ ZoneNotFound
22+ /// Unable to provide zone information due to missing or
23+ /// incomplete data.
24+ ProcessingError
25+ }
26+
1827/// Load timezone database from default operating system location
28+ /// which is typically "/usr/share/zoneinfo".
1929pub fn load_from_os ( ) {
2030 load_from_path ( "/usr/share/zoneinfo" )
2131}
@@ -37,10 +47,28 @@ pub fn load_from_path(path: String) {
3747 )
3848}
3949
50+ /// Create new empty TzDatabase.
51+ pub fn new ( ) -> TzDatabase {
52+ TzDatabase ( [ ] , dict . new ( ) )
53+ }
54+
55+ /// Add new timezone definition to TzDatabase.
56+ pub fn add_tzfile (
57+ db : TzDatabase ,
58+ zone_name : String ,
59+ tzfile : tzparser . TzFile ,
60+ ) -> TzDatabase {
61+ let namelist = case dict . has_key ( db . zone_data , zone_name ) {
62+ True -> db . zone_names
63+ False -> [ zone_name , .. db . zone_names ] |> list . sort ( string . compare )
64+ }
65+ TzDatabase ( namelist , dict . insert ( db . zone_data , zone_name , tzfile ) )
66+ }
67+
4068fn process_tzfile (
4169 filename : String ,
4270 components_to_drop : Int ,
43- ) -> Result ( # ( String , tzparser . TZFile ) , Nil ) {
71+ ) -> Result ( # ( String , tzparser . TzFile ) , Nil ) {
4472 let zone_name =
4573 filepath . split ( filename )
4674 |> list . drop ( components_to_drop )
@@ -49,7 +77,6 @@ fn process_tzfile(
4977 use tzdata <- result . try (
5078 simplifile . read_bits ( filename ) |> result . replace_error ( Nil ) ,
5179 )
52-
5380 use timeinfo <- result . try (
5481 tzparser . parse ( tzdata ) |> result . replace_error ( Nil ) ,
5582 )
@@ -70,30 +97,58 @@ pub type ZoneParameters {
7097 ZoneParameters ( offset : Duration , is_dst : Bool , designation : String )
7198}
7299
100+ /// Retrieve the time zone parameters for a zone at a particular time.
101+ /// The time is given as a `gleam/time/timestamp.Timestamp`, the zone name
102+ /// and a time zone database. Because of
103+ /// daylight savings time as well as other historical shifts in how time
104+ /// has been measured, time zone information for a location shifts over time,
105+ /// therefore the offset or designation you get from the database is time
106+ /// dependant. Do not assume that the time zone parameters will be the same
107+ /// at any other time.
108+ ///
109+ /// # Example
110+ ///
111+ /// ```gleam
112+ /// import gleam/time/timestamp
113+ ///
114+ /// let ts = timestamp.from_unix_seconds(1_758_223_300)
115+ /// let db = database.load_from_os()
116+ ///
117+ /// get_zone_parameters(ts, "America/New_York", db)
118+ /// // Ok(ZoneParameters(
119+ /// // Duration(-144_40, 0),
120+ /// // True,
121+ /// // "EDT",
122+ /// // ))
123+ /// ```
73124pub fn get_zone_parameters (
74125 ts : timestamp . Timestamp ,
75126 zone_name : String ,
76127 db : TzDatabase ,
77- ) -> Result ( ZoneParameters , Nil ) {
78- use tzdata <- result . try ( dict . get ( db . zone_data , zone_name ) )
128+ ) -> Result ( ZoneParameters , TzDatabaseError ) {
129+ use tzdata <- result . try (
130+ dict . get ( db . zone_data , zone_name ) |> result . replace_error ( ZoneNotFound ) ,
131+ )
79132 let default = default_slice ( tzdata . fields )
80133
81134 let slices = create_slices ( tzdata . fields )
82135
83- use slice <- result . try ( get_slice ( ts , slices , default ) )
136+ use slice <- result . try (
137+ get_slice ( ts , slices , default ) |> result . replace_error ( ProcessingError ) ,
138+ )
84139
85140 Ok ( ZoneParameters ( slice . utoff , slice . isdst , slice . designation ) )
86141}
87142
88143// Below are some things I previously had elsewhere
89144
90145/// Timezone definition slice
91- type TTSlice {
92- TTSlice ( start_time : Int , utoff : Duration , isdst : Bool , designation : String )
146+ type TtSlice {
147+ TtSlice ( start_time : Int , utoff : Duration , isdst : Bool , designation : String )
93148}
94149
95150// Turn time zone fields into a list of timezone information slices
96- fn create_slices ( fields : tzparser . TZFileFields ) -> List ( TTSlice ) {
151+ fn create_slices ( fields : tzparser . TzFileFields ) -> List ( TtSlice ) {
97152 let infos =
98153 list . zip ( fields . ttinfos , fields . designations )
99154 |> list . index_map ( fn ( tup , idx ) { # ( idx , tup ) } )
@@ -108,30 +163,30 @@ fn create_slices(fields: tzparser.TZFileFields) -> List(TTSlice) {
108163 0 -> False
109164 _ -> True
110165 }
111- Ok ( TTSlice ( tup . 0 , duration . seconds ( ttinfo . utoff ) , isdst , designation ) )
166+ Ok ( TtSlice ( tup . 0 , duration . seconds ( ttinfo . utoff ) , isdst , designation ) )
112167 }
113168 _ -> Error ( Nil )
114169 }
115170 } )
116171 |> result . values
117172}
118173
119- fn default_slice ( fields : tzparser . TZFileFields ) -> Result ( TTSlice , Nil ) {
174+ fn default_slice ( fields : tzparser . TzFileFields ) -> Result ( TtSlice , Nil ) {
120175 use ttinfo <- result . try ( list . first ( fields . ttinfos ) )
121176 use designation <- result . try ( list . first ( fields . designations ) )
122177 let isdst = case ttinfo . isdst {
123178 0 -> False
124179 _ -> True
125180 }
126181
127- Ok ( TTSlice ( 0 , duration . seconds ( ttinfo . utoff ) , isdst , designation ) )
182+ Ok ( TtSlice ( 0 , duration . seconds ( ttinfo . utoff ) , isdst , designation ) )
128183}
129184
130185fn get_slice (
131186 ts : timestamp . Timestamp ,
132- slices : List ( TTSlice ) ,
133- default : Result ( TTSlice , Nil ) ,
134- ) -> Result ( TTSlice , Nil ) {
187+ slices : List ( TtSlice ) ,
188+ default : Result ( TtSlice , Nil ) ,
189+ ) -> Result ( TtSlice , Nil ) {
135190 let # ( seconds , _ ) = timestamp . to_unix_seconds_and_nanoseconds ( ts )
136191
137192 slices
0 commit comments