1616//// article on Wikipedia. The time zone identifiers are passed to many of the
1717//// functions of this library as the `zone_name` parameter.
1818
19- import envoy
20- import filepath
21- import gleam/dict
22- import gleam/list
2319import gleam/result
2420import gleam/time/calendar . { type Date , type TimeOfDay }
2521import gleam/time/duration . { type Duration }
2622import gleam/time/timestamp . { type Timestamp }
27- import simplifile
2823import timezone/database
29- import timezone/internal
3024
3125/// Time Zone error type
26+ /// This needs review and change as we split things up
3227pub type TimeZoneError {
28+ /// Error parsing timezone data
3329 ParseError
30+
31+ /// Error constructing time zone transition table.
3432 TimeSliceError
35- ZoneFileError
36- }
3733
38- /// Timezone definition slice
39- type TTSlice {
40- TTSlice ( start_time : Int , utoff : Duration , isdst : Bool , designation : String )
34+ /// Other error in the Zone file
35+ ZoneFileError
4136}
4237
4338/// Representation of time in a time zone
4439pub type TimeInZone {
4540 TimeInZone (
4641 date : Date ,
4742 time_of_day : TimeOfDay ,
48- offset : duration . Duration ,
43+ offset : Duration ,
4944 designation : String ,
5045 is_dst : Bool ,
5146 )
5247}
5348
54- /// Get the full path to the zoneinfo file for that
55- /// time zone. Use IANA time zone identifiers such as
56- /// "America/New_York".
57- pub fn tzfile_path ( name : String ) -> String {
58- let root = case envoy . get ( "ZONEINFO" ) {
59- Ok ( dir ) -> dir
60- _ -> "/usr/share/zoneinfo"
61- }
62-
63- filepath . join ( root , name )
64- }
65-
66- /// Given a timestamp and a zone name, get the time in that time zone.
67- /// The zone_name should be an IANA identifier like "America/New_York".
68- pub fn get_time_in_zone (
69- ts : Timestamp ,
70- zone_name : String ,
71- ) -> Result ( TimeInZone , TimeZoneError ) {
72- // Parse the datafile
73- use tzdata <- result . try (
74- tzfile_path ( zone_name )
75- |> simplifile . read_bits
76- |> result . replace_error ( ZoneFileError ) ,
77- )
78- get_time_with_tzdata ( ts , tzdata )
79- }
80-
81- /// Given a timestamp and the binary contents of a tzfile, get the time in
82- /// that time zone.
83- pub fn get_time_with_tzdata (
84- ts : Timestamp ,
85- tzdata : BitArray ,
86- ) -> Result ( TimeInZone , TimeZoneError ) {
87- use tz <- result . try (
88- internal . parse ( tzdata ) |> result . replace_error ( ParseError ) ,
89- )
90-
91- // Pull out the TTSlice representing the timezone at that timestamp
92- use slice_of_interest <- result . map (
93- get_slice ( ts , create_slices ( tz . fields ) , default_slice ( tz . fields ) )
94- |> result . replace_error ( TimeSliceError ) ,
95- )
96- let # ( dt , tm ) = timestamp . to_calendar ( ts , slice_of_interest . utoff )
97-
98- TimeInZone (
99- dt ,
100- tm ,
101- slice_of_interest . utoff ,
102- slice_of_interest . designation ,
103- slice_of_interest . isdst ,
104- )
105- }
106-
10749/// Potentially new API for time_in_zone
10850pub fn get_time_in_zone_tzdb (
10951 ts : Timestamp ,
@@ -125,85 +67,3 @@ pub fn get_time_in_zone_tzdb(
12567 zone_parameters . is_dst ,
12668 )
12769}
128-
129- /// Convert a timestamp to a calendar date and time of day for a
130- /// given time zone.
131- pub fn to_calendar (
132- ts : Timestamp ,
133- zone_name : String ,
134- ) -> Result ( # ( calendar . Date , calendar . TimeOfDay ) , TimeZoneError ) {
135- use time_in_zone <- result . map ( get_time_in_zone ( ts , zone_name ) )
136-
137- # ( time_in_zone . date , time_in_zone . time_of_day )
138- }
139-
140- /// Get the offset for a time zone at a particular moment in time.
141- pub fn zone_offset (
142- ts : Timestamp ,
143- zone_name : String ,
144- ) -> Result ( duration . Duration , TimeZoneError ) {
145- use time_in_zone <- result . map ( get_time_in_zone ( ts , zone_name ) )
146-
147- time_in_zone . offset
148- }
149-
150- /// Get zone designation (e.g. "EST", "CEST", "JST").
151- pub fn zone_designation (
152- ts : Timestamp ,
153- zone_name : String ,
154- ) -> Result ( String , TimeZoneError ) {
155- use time_in_zone <- result . map ( get_time_in_zone ( ts , zone_name ) )
156-
157- time_in_zone . designation
158- }
159-
160- // Turn time zone fields into a list of timezone information slices
161- fn create_slices ( fields : internal . TimeZoneFields ) -> List ( TTSlice ) {
162- let infos =
163- list . zip ( fields . ttinfos , fields . designations )
164- |> list . index_map ( fn ( tup , idx ) { # ( idx , tup ) } )
165- |> dict . from_list
166-
167- list . zip ( fields . transition_times , fields . time_types )
168- |> list . map ( fn ( tup ) {
169- let info_tuple = dict . get ( infos , tup . 1 )
170- case info_tuple {
171- Ok ( # ( ttinfo , designation ) ) -> {
172- let isdst = case ttinfo . isdst {
173- 0 -> False
174- _ -> True
175- }
176- Ok ( TTSlice ( tup . 0 , duration . seconds ( ttinfo . utoff ) , isdst , designation ) )
177- }
178- _ -> Error ( Nil )
179- }
180- } )
181- |> result . values
182- }
183-
184- fn default_slice ( fields : internal . TimeZoneFields ) -> Result ( TTSlice , Nil ) {
185- use ttinfo <- result . try ( list . first ( fields . ttinfos ) )
186- use designation <- result . try ( list . first ( fields . designations ) )
187- let isdst = case ttinfo . isdst {
188- 0 -> False
189- _ -> True
190- }
191-
192- Ok ( TTSlice ( 0 , duration . seconds ( ttinfo . utoff ) , isdst , designation ) )
193- }
194-
195- fn get_slice (
196- ts : timestamp . Timestamp ,
197- slices : List ( TTSlice ) ,
198- default : Result ( TTSlice , Nil ) ,
199- ) -> Result ( TTSlice , Nil ) {
200- let # ( seconds , _ ) = timestamp . to_unix_seconds_and_nanoseconds ( ts )
201-
202- slices
203- |> list . fold_until ( default , fn ( acc , slice ) {
204- case slice . start_time < seconds {
205- True -> list . Continue ( Ok ( slice ) )
206- False -> list . Stop ( acc )
207- }
208- } )
209- }
0 commit comments