Skip to content

Commit 6aae8a1

Browse files
committed
middle of moving things tests not updated
1 parent 439ca33 commit 6aae8a1

File tree

5 files changed

+65
-242
lines changed

5 files changed

+65
-242
lines changed

examples/time_now/src/time_now.gleam

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import gleam/int
22
import gleam/io
3-
import gleam/result
3+
import gleam/list
44
import gleam/string
55
import gleam/time/calendar
66
import gleam/time/timestamp
77
import timezone
8+
import timezone/database
89

910
pub fn main() {
1011
let now = timestamp.system_time()
@@ -17,44 +18,19 @@ pub fn main() {
1718
fn print_all_times(
1819
now: timestamp.Timestamp,
1920
) -> Result(Nil, timezone.TimeZoneError) {
20-
use new_york <- result.try(timezone.get_time_in_zone(now, "America/New_York"))
21-
use vevay <- result.try(timezone.get_time_in_zone(
22-
now,
23-
"America/Indiana/Vevay",
24-
))
25-
use utc <- result.try(timezone.get_time_in_zone(now, "UTC"))
26-
use phoenix <- result.try(timezone.get_time_in_zone(now, "America/Phoenix"))
27-
use amsterdam <- result.try(timezone.get_time_in_zone(now, "Europe/Amsterdam"))
28-
use london <- result.try(timezone.get_time_in_zone(now, "Europe/London"))
29-
use tokyo <- result.try(timezone.get_time_in_zone(now, "Asia/Tokyo"))
30-
use auckland <- result.try(timezone.get_time_in_zone(now, "Pacific/Auckland"))
31-
use cairo <- result.try(timezone.get_time_in_zone(now, "Africa/Cairo"))
32-
use calcutta <- result.try(timezone.get_time_in_zone(now, "Asia/Calcutta"))
33-
use posix <- result.try(timezone.get_time_in_zone(now, "posixrules"))
21+
let db = database.load_from_os()
3422

35-
io.println("UTC: " <> format_time(utc))
36-
io.println("New York: " <> format_time(new_york))
37-
io.println("Vevay: " <> format_time(vevay))
38-
io.println("Phoenix: " <> format_time(phoenix))
39-
io.println("Amsterdam: " <> format_time(amsterdam))
40-
io.println("London: " <> format_time(london))
41-
io.println("Tokyo: " <> format_time(tokyo))
42-
io.println("Auckland: " <> format_time(auckland))
43-
io.println("Cairo: " <> format_time(cairo))
44-
io.println("Calcutta: " <> format_time(calcutta))
45-
io.println("POSIX: " <> format_time(posix))
46-
io.println("")
47-
io.println("RFC3339:")
48-
io.println("UTC: " <> timestamp.to_rfc3339(now, utc.offset))
49-
io.println("New York: " <> timestamp.to_rfc3339(now, new_york.offset))
50-
io.println("Vevay: " <> timestamp.to_rfc3339(now, vevay.offset))
51-
io.println("Phoenix: " <> timestamp.to_rfc3339(now, phoenix.offset))
52-
io.println("Amsterdam: " <> timestamp.to_rfc3339(now, amsterdam.offset))
53-
io.println("London: " <> timestamp.to_rfc3339(now, london.offset))
54-
io.println("Tokyo: " <> timestamp.to_rfc3339(now, tokyo.offset))
55-
io.println("Auckland: " <> timestamp.to_rfc3339(now, auckland.offset))
56-
io.println("Cairo: " <> timestamp.to_rfc3339(now, cairo.offset))
57-
io.println("POSIX: " <> timestamp.to_rfc3339(now, posix.offset))
23+
database.get_available_timezones(db)
24+
|> list.map(fn(zone_name) {
25+
case timezone.get_time_in_zone_tzdb(now, zone_name, db) {
26+
Ok(tiz) ->
27+
io.println(
28+
string.pad_end(zone_name <> ":", 40, " ") <> format_time(tiz),
29+
)
30+
Error(_) ->
31+
io.println(string.pad_end(zone_name <> ":", 40, " ") <> "ERROR")
32+
}
33+
})
5834
Ok(Nil)
5935
}
6036

src/timezone.gleam

Lines changed: 7 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -16,94 +16,36 @@
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
2319
import gleam/result
2420
import gleam/time/calendar.{type Date, type TimeOfDay}
2521
import gleam/time/duration.{type Duration}
2622
import gleam/time/timestamp.{type Timestamp}
27-
import simplifile
2823
import timezone/database
29-
import timezone/internal
3024

3125
/// Time Zone error type
26+
/// This needs review and change as we split things up
3227
pub 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
4439
pub 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
10850
pub 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-
}

src/timezone/database.gleam

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,21 @@ import gleam/string
66
import gleam/time/duration.{type Duration}
77
import gleam/time/timestamp
88
import simplifile
9-
import timezone/internal
9+
import timezone/tzparser
1010

1111
pub opaque type TzDatabase {
1212
TzDatabase(
1313
zone_names: List(String),
14-
zone_data: dict.Dict(String, internal.TimeZoneData),
14+
zone_data: dict.Dict(String, tzparser.TZFile),
1515
)
1616
}
1717

18+
/// Load timezone database from default operating system location
1819
pub fn load_from_os() {
1920
load_from_path("/usr/share/zoneinfo")
2021
}
2122

23+
/// Load timezone database from provided directory
2224
pub fn load_from_path(path: String) {
2325
let parts = filepath.split(path)
2426
let drop_number = list.length(parts)
@@ -38,7 +40,7 @@ pub fn load_from_path(path: String) {
3840
fn process_tzfile(
3941
filename: String,
4042
components_to_drop: Int,
41-
) -> Result(#(String, internal.TimeZoneData), Nil) {
43+
) -> Result(#(String, tzparser.TZFile), Nil) {
4244
let zone_name =
4345
filepath.split(filename)
4446
|> list.drop(components_to_drop)
@@ -49,16 +51,21 @@ fn process_tzfile(
4951
)
5052

5153
use timeinfo <- result.try(
52-
internal.parse(tzdata) |> result.replace_error(Nil),
54+
tzparser.parse(tzdata) |> result.replace_error(Nil),
5355
)
5456

5557
Ok(#(zone_name, timeinfo))
5658
}
5759

60+
/// Get all timezones in the database
5861
pub fn get_available_timezones(db: TzDatabase) -> List(String) {
5962
db.zone_names
6063
}
6164

65+
/// Time zone parameters record.
66+
/// - `offset` is the offset from UTC.
67+
/// - `is_dst` indicates if it is daylight savings time
68+
/// - `designation` is the time zone designation
6269
pub type ZoneParameters {
6370
ZoneParameters(offset: Duration, is_dst: Bool, designation: String)
6471
}
@@ -86,7 +93,7 @@ type TTSlice {
8693
}
8794

8895
// Turn time zone fields into a list of timezone information slices
89-
fn create_slices(fields: internal.TimeZoneFields) -> List(TTSlice) {
96+
fn create_slices(fields: tzparser.TZFileFields) -> List(TTSlice) {
9097
let infos =
9198
list.zip(fields.ttinfos, fields.designations)
9299
|> list.index_map(fn(tup, idx) { #(idx, tup) })
@@ -109,7 +116,7 @@ fn create_slices(fields: internal.TimeZoneFields) -> List(TTSlice) {
109116
|> result.values
110117
}
111118

112-
fn default_slice(fields: internal.TimeZoneFields) -> Result(TTSlice, Nil) {
119+
fn default_slice(fields: tzparser.TZFileFields) -> Result(TTSlice, Nil) {
113120
use ttinfo <- result.try(list.first(fields.ttinfos))
114121
use designation <- result.try(list.first(fields.designations))
115122
let isdst = case ttinfo.isdst {

0 commit comments

Comments
 (0)