Skip to content

Commit 4137a46

Browse files
committed
lots of moving things around and need to do test stuff
1 parent 6aae8a1 commit 4137a46

File tree

8 files changed

+186
-162
lines changed

8 files changed

+186
-162
lines changed

examples/time_now/src/time_now.gleam

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ pub fn main() {
1717

1818
fn print_all_times(
1919
now: timestamp.Timestamp,
20-
) -> Result(Nil, timezone.TimeZoneError) {
20+
) -> Result(Nil, database.TzDatabaseError) {
2121
let db = database.load_from_os()
2222

2323
database.get_available_timezones(db)
2424
|> list.map(fn(zone_name) {
25-
case timezone.get_time_in_zone_tzdb(now, zone_name, db) {
25+
case timezone.get_time_in_zone(now, zone_name, db) {
2626
Ok(tiz) ->
2727
io.println(
2828
string.pad_end(zone_name <> ":", 40, " ") <> format_time(tiz),

gleam.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name = "timezone"
2-
version = "0.1.0"
2+
version = "0.2.0"
33

44
# Fill out these fields if you intend to generate HTML documentation or publish
55
# your project to the Hex package manager.
@@ -17,7 +17,6 @@ gleam_stdlib = ">= 0.44.0 and < 2.0.0"
1717
gleam_time = ">= 1.4.0 and < 2.0.0"
1818
simplifile = ">= 2.3.0 and < 3.0.0"
1919
filepath = ">= 1.1.2 and < 2.0.0"
20-
envoy = ">= 1.0.2 and < 2.0.0"
2120

2221
[dev-dependencies]
2322
gleeunit = ">= 1.0.0 and < 2.0.0"

manifest.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# You typically do not need to edit this file
33

44
packages = [
5-
{ name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" },
65
{ name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" },
76
{ name = "gleam_stdlib", version = "0.63.2", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "962B25C667DA07F4CAB32001F44D3C41C1A89E58E3BBA54F183B482CF6122150" },
87
{ name = "gleam_time", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "DCDDC040CE97DA3D2A925CDBBA08D8A78681139745754A83998641C8A3F6587E" },
@@ -11,7 +10,6 @@ packages = [
1110
]
1211

1312
[requirements]
14-
envoy = { version = ">= 1.0.2 and < 2.0.0" }
1513
filepath = { version = ">= 1.1.2 and < 2.0.0" }
1614
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
1715
gleam_time = { version = ">= 1.4.0 and < 2.0.0" }

src/timezone.gleam

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,7 @@ import gleam/result
2020
import gleam/time/calendar.{type Date, type TimeOfDay}
2121
import gleam/time/duration.{type Duration}
2222
import gleam/time/timestamp.{type Timestamp}
23-
import timezone/database
24-
25-
/// Time Zone error type
26-
/// This needs review and change as we split things up
27-
pub type TimeZoneError {
28-
/// Error parsing timezone data
29-
ParseError
30-
31-
/// Error constructing time zone transition table.
32-
TimeSliceError
33-
34-
/// Other error in the Zone file
35-
ZoneFileError
36-
}
23+
import timezone/database.{type TzDatabase, type TzDatabaseError}
3724

3825
/// Representation of time in a time zone
3926
pub type TimeInZone {
@@ -46,16 +33,39 @@ pub type TimeInZone {
4633
)
4734
}
4835

49-
/// Potentially new API for time_in_zone
50-
pub fn get_time_in_zone_tzdb(
36+
/// Given a timestamp, IANA time zone name, and a `TzDatabase` record,
37+
/// get the data and time of the timestamp along with information about
38+
/// the timezone, such as its offset from UTC, designation, and if it
39+
/// is daylight savings time in that zone.
40+
///
41+
/// # Example
42+
///
43+
/// ```gleam
44+
/// import gleam/time/timestamp
45+
/// import timezone/database
46+
///
47+
/// let ts = timestamp.from_unix_seconds(1_758_223_300)
48+
/// let db = database.load_from_os()
49+
///
50+
/// get_time_in_zone(ts, "America/New_York", db)
51+
/// // Ok(TimeInZone(
52+
/// // Date(2025, September, 18),
53+
/// // TimeOfDay(15, 21, 40, 0),
54+
/// // Duration(-14_400, 0),
55+
/// // "EDT",
56+
/// // True,
57+
/// // ))
58+
/// ```
59+
pub fn get_time_in_zone(
5160
ts: Timestamp,
5261
zone_name: String,
53-
db: database.TzDatabase,
54-
) -> Result(TimeInZone, TimeZoneError) {
55-
use zone_parameters <- result.map(
56-
database.get_zone_parameters(ts, zone_name, db)
57-
|> result.replace_error(ParseError),
58-
)
62+
db: TzDatabase,
63+
) -> Result(TimeInZone, TzDatabaseError) {
64+
use zone_parameters <- result.map(database.get_zone_parameters(
65+
ts,
66+
zone_name,
67+
db,
68+
))
5969

6070
let #(dt, tm) = timestamp.to_calendar(ts, zone_parameters.offset)
6171

src/timezone/database.gleam

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,21 @@ import timezone/tzparser
1111
pub 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".
1929
pub 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+
4068
fn 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+
/// ```
73124
pub 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

130185
fn 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

Comments
 (0)