diff --git a/CHANGELOG.md b/CHANGELOG.md index da44dca..aa50487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## v2.0.0 - 2026-08-?? +- Move the timezone file loader to a separate package to make this usable + on web browsers. +- Update documentation to reflect the changes. + ## v1.1.3 - 2026-08-08 - Add references to the zones gleam package with portable timezone data. - Update documentation. diff --git a/README.md b/README.md index f60ecb2..4b2f9b7 100644 --- a/README.md +++ b/README.md @@ -3,36 +3,49 @@ [![Package Version](https://img.shields.io/hexpm/v/tzif)](https://hex.pm/packages/tzif) [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/tzif/) -Time zone support for Gleam time using the IANA Time Zone Database. -This package loads the time zone database from the standard location -(`/usr/share/zoneinfo`) on MacOS and Linux computers. It includes a parser for -the Time Zone Information Format (TZif) or `tzfile` format, as well as utility -functions to convert a timestamp from the -[gleam_time](https://hexdocs.pm/gleam_time/) library into a date and time -of day in the given time zone. +Time zone support for Gleam time using the IANA Time Zone Database format. +This package includes a parser for the Time Zone Information Format (TZif) or +`tzfile` format, as well as utility functions to convert a timestamp from the +[gleam_time](https://hexdocs.pm/gleam_time/) library into a date and time of day +in the given time zone. + +There are two ways to obtain the timezone data: +- The [zones](https://zones.hexdocs.pm/) package maintains up to date + time zone data in a native gleam package format. This is the recommended + method for code running in the browser, docker containers, and the Windows + operating system. + + Add to your project with the command: + + ``` + gleam add tzif@2 zones + ``` + +- The [tzif_loader](https://tzif-loader.hexdocs.pm/) package will load the + operating system default time zone files from their standard location in + Linux and MacOS operating systems. + + Add to your project with the command: + + ``` + gleam add tzif@2 tzif_loader + ``` > We could really do with a timezone database package with a > fn(Timestamp, Zone) -> #(Date, TimeOfDay) function > > --- Louis Pilfold -Add to your project with the command: -``` -gleam add tzif@1 -``` - # Using the Package There are three modules in the `tzif` package: -- `tzif/database` has utilities for loading the IANA Time Zone database. +- `tzif/database` has utilities for managing the IANA Time Zone database. - `tzif/tzcalendar` has utilities for converting a [gleam_time](https://gleam-time.hexdocs.pm/) timestamp into date and time of day in a time zone. - `tzif/parser` has functions and records for parsing TZif formatted data. -The most straightforward use would be to load the database from the default -location on the operating system, and then obtain a timestamp using the -[gleam_time](https://gleam-time.hexdocs.pm/) package, and convert that timestamp -into a time of day in a time zone using the IANA time zone name. An example -of that is shown in the code below. +Below is an example of code which loads the native time zone data from the +operating system using [tzif_loader](https://tzif-loader.hexdocs.pm/) and +converts the system time to a time of day in the America/New_York time zone. ```gleam import gleam/int @@ -40,13 +53,14 @@ import gleam/io import gleam/string import gleam/time/timestamp import tzif/database +import tzif/loader import tzif/tzcalendar pub fn main() { let now = timestamp.system_time() // Load the database from the operating system - case database.load_from_os() { + case loader.load_from_os() { Ok(db) -> { case tzcalendar.to_time_and_zone(now, "America/New_York", db) { Ok(time_and_zone) -> { @@ -72,88 +86,3 @@ pub fn main() { Error(Nil) -> io.println("No parsable TZif files found.") } ``` -If you are on windows and have installed the IANA Time Zone Database, or want -to use a custom version you can use the `database.load_from_path` function -instead of the `database.load_from_os` function to specify a path to your -database files. - -# Installing the zoneinfo data files -Time zone information is frequently updated, therefore it makes sense to use the -package manager for your operating system to keep the time zone database up to -date. All common unix variants have time zone database packages and install the -time zone database files into the `/usr/share/zoneinfo` directory by default. - -If, however, your system does not have time zone data installed, you can use -the [zones](https://zones.hexdocs.pm/) package to install a database of -timezone data as a gleam library and dependency. To use the zones timezone -data get the database using the `zones.database` function rather than -`database.load_from_os` or `database.load_from_path`. - -## MacOS -The files should be included in your operating system by default. Check the -`/usr/share/zoneinfo` directory and see if they are present. - -## Ubuntu/Debian Linux Systems -The APT package manager can be used to install the compiled TZif files with the -following command: - -``` -sudo apt install tzdata -``` - -### Debian based docker containers -Installing and configuring the time zone database on a Debian or Ubuntu based -docker container can be done by adding the following to your `Dockerfile`: - -``` -# Use an ARG for the timezone, with a default of UTC -ARG TIMEZONE=Etc/UTC - -# Set the TZ environment variable and install tzdata -RUN apt-get update && \ - export DEBIAN_FRONTEND=noninteractive && \ - ln -fs /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && \ - apt-get install -y tzdata && \ - dpkg-reconfigure --frontend noninteractive tzdata -``` - -## Alpine Linux Systems -The Alpine Package Keeper can install the time zone database using the command: - -``` -sudo apk add tzdata -``` - -### Alpine based docker containers -Installing and configuring the time zone database on an Alpine based docker -container can be done by adding the following to your `Dockerfile`: - -``` -# Use an ARG for the timezone, with a default of UTC -ARG TIMEZONE=Etc/UTC - -# 1. Install the tzdata package -# 2. Copy the correct timezone file to /etc/localtime -# 3. Set the TZ environment variable to be used by applications -RUN apk add --no-cache tzdata && \ - cp /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && \ - echo "${TIMEZONE}" > /etc/timezone -``` - -## Red Hat/Rocky/Alma Linux Systems -You can use the YUM package manager or DNF to install the time zone database -on Red Hat variants. To use YUM run the command: - -``` -sudo yum install tzdata -``` - -Similarly, using DNF: - -``` -sudo dnf install tzdata -``` -## Windows -Microsoft Windows has a different mechanism for handling time zones, so we -recommend using the [zones](https://zones.hexdocs.pm/) portable gleam timezone -data library. diff --git a/examples/simple/gleam.toml b/examples/simple/gleam.toml index 7eba79e..47ba835 100644 --- a/examples/simple/gleam.toml +++ b/examples/simple/gleam.toml @@ -14,7 +14,14 @@ version = "1.0.0" [dependencies] gleam_stdlib = ">= 0.44.0 and < 2.0.0" -tzif = { path = "../../" } +tzif = { + git = "https://github.com/devries/timezone", + ref = "dfe650b9f761719b999511efbee7b902ed22c04f" +} +tzif_loader = { + git = "https://github.com/devries/tzif_loader", + ref = "2d64d20a4f6b1da6c48b3eaa5c49887f92885f0c" +} gleam_time = ">= 1.4.0 and < 2.0.0" [dev-dependencies] diff --git a/examples/simple/manifest.toml b/examples/simple/manifest.toml index 8acd960..08492a0 100644 --- a/examples/simple/manifest.toml +++ b/examples/simple/manifest.toml @@ -12,11 +12,13 @@ packages = [ { name = "gleam_time", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "533D8723774D61AD4998324F5DD1DABDCDBFABAFB9E87CB5D03C6955448FC97D" }, { name = "gleeunit", version = "1.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "EC31ABA74256AEA531EDF8169931D775BBB384FED0A8A1BDC4DD9354E3E21826" }, { name = "simplifile", version = "2.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "A2727627B063E87351934C7F7F008F2D1FDB16F6DE0B8C79F9E46459CFC9C164" }, - { name = "tzif", version = "1.1.3", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib", "gleam_time", "simplifile"], source = "local", path = "../.." }, + { name = "tzif", version = "1.1.3", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib", "gleam_time", "simplifile"], source = "git", repo = "https://github.com/devries/timezone", commit = "dfe650b9f761719b999511efbee7b902ed22c04f" }, + { name = "tzif_loader", version = "1.0.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib", "gleam_time", "simplifile", "tzif"], source = "git", repo = "https://github.com/devries/tzif_loader", commit = "2d64d20a4f6b1da6c48b3eaa5c49887f92885f0c" }, ] [requirements] gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } gleam_time = { version = ">= 1.4.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } -tzif = { path = "../../" } +tzif = { git = "https://github.com/devries/timezone", ref = "dfe650b9f761719b999511efbee7b902ed22c04f" } +tzif_loader = { git = "https://github.com/devries/tzif_loader", ref = "2d64d20a4f6b1da6c48b3eaa5c49887f92885f0c" } diff --git a/examples/time_now/gleam.toml b/examples/time_now/gleam.toml index eeeda27..37f4b82 100644 --- a/examples/time_now/gleam.toml +++ b/examples/time_now/gleam.toml @@ -14,7 +14,14 @@ version = "1.0.0" [dependencies] gleam_stdlib = ">= 0.44.0 and < 2.0.0" -tzif = { path = "../../" } +tzif = { + git = "https://github.com/devries/timezone", + ref = "dfe650b9f761719b999511efbee7b902ed22c04f" +} +tzif_loader = { + git = "https://github.com/devries/tzif_loader", + ref = "2d64d20a4f6b1da6c48b3eaa5c49887f92885f0c" +} gleam_time = ">= 1.4.0 and < 2.0.0" [dev-dependencies] diff --git a/examples/time_now/manifest.toml b/examples/time_now/manifest.toml index 8acd960..08492a0 100644 --- a/examples/time_now/manifest.toml +++ b/examples/time_now/manifest.toml @@ -12,11 +12,13 @@ packages = [ { name = "gleam_time", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "533D8723774D61AD4998324F5DD1DABDCDBFABAFB9E87CB5D03C6955448FC97D" }, { name = "gleeunit", version = "1.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "EC31ABA74256AEA531EDF8169931D775BBB384FED0A8A1BDC4DD9354E3E21826" }, { name = "simplifile", version = "2.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "A2727627B063E87351934C7F7F008F2D1FDB16F6DE0B8C79F9E46459CFC9C164" }, - { name = "tzif", version = "1.1.3", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib", "gleam_time", "simplifile"], source = "local", path = "../.." }, + { name = "tzif", version = "1.1.3", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib", "gleam_time", "simplifile"], source = "git", repo = "https://github.com/devries/timezone", commit = "dfe650b9f761719b999511efbee7b902ed22c04f" }, + { name = "tzif_loader", version = "1.0.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib", "gleam_time", "simplifile", "tzif"], source = "git", repo = "https://github.com/devries/tzif_loader", commit = "2d64d20a4f6b1da6c48b3eaa5c49887f92885f0c" }, ] [requirements] gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } gleam_time = { version = ">= 1.4.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } -tzif = { path = "../../" } +tzif = { git = "https://github.com/devries/timezone", ref = "dfe650b9f761719b999511efbee7b902ed22c04f" } +tzif_loader = { git = "https://github.com/devries/tzif_loader", ref = "2d64d20a4f6b1da6c48b3eaa5c49887f92885f0c" } diff --git a/gleam.toml b/gleam.toml index 94623e3..73ccb62 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,5 +1,5 @@ name = "tzif" -version = "1.1.3" +version = "2.0.0" # Fill out these fields if you intend to generate HTML documentation or publish # your project to the Hex package manager. @@ -17,8 +17,6 @@ pages = [ [dependencies] gleam_stdlib = ">= 0.44.0 and < 2.0.0" gleam_time = ">= 1.4.0 and < 2.0.0" -simplifile = ">= 2.3.0 and < 3.0.0" -filepath = ">= 1.1.2 and < 2.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml index 37f3989..5cfa6a7 100644 --- a/manifest.toml +++ b/manifest.toml @@ -7,16 +7,12 @@ # You should check this file into your source control repository. packages = [ - { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, { name = "gleam_stdlib", version = "1.0.5", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "CEE5B6C076A85B45F60C585F4316C63EC8B7127C119D5738C3958A9C4D50404E" }, - { name = "gleam_time", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "533D8723774D61AD4998324F5DD1DABDCDBFABAFB9E87CB5D03C6955448FC97D" }, + { name = "gleam_time", version = "1.10.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "56539216E4C4B1748714652AB38F0BD16B9101F61DB62769FDC7CD42A8E5E833" }, { name = "gleeunit", version = "1.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "EC31ABA74256AEA531EDF8169931D775BBB384FED0A8A1BDC4DD9354E3E21826" }, - { name = "simplifile", version = "2.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "A2727627B063E87351934C7F7F008F2D1FDB16F6DE0B8C79F9E46459CFC9C164" }, ] [requirements] -filepath = { version = ">= 1.1.2 and < 2.0.0" } gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } gleam_time = { version = ">= 1.4.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } -simplifile = { version = ">= 2.3.0 and < 3.0.0" } diff --git a/src/tzif/database.gleam b/src/tzif/database.gleam index 3126f24..848b9b4 100644 --- a/src/tzif/database.gleam +++ b/src/tzif/database.gleam @@ -2,14 +2,12 @@ //// from TZif formatted data you provide, or timezone data loaded from the //// operating system. -import filepath import gleam/dict import gleam/list import gleam/result import gleam/string import gleam/time/duration.{type Duration} import gleam/time/timestamp -import simplifile import tzif/parser /// Time Zone Database record. This is typically created by @@ -33,42 +31,6 @@ pub type TzDatabaseError { InfoNotFound } -/// Load time zone database from default operating system location -/// which is typically "/usr/share/zoneinfo". If no parsable TZif -/// files were found, returns `Error(Nil)`. -pub fn load_from_os() -> Result(TzDatabase, Nil) { - load_from_path("/usr/share/zoneinfo") -} - -/// Load time zone database from provided directory. This is -/// useful if you have compiled your own version of the [IANA -/// time zone database](https://data.iana.org/time-zones/tz-link.html) -/// or they are not stored in the standard location. If no -/// parsable TZif files were found, returns `Error(Nil)`. -pub fn load_from_path(path: String) -> Result(TzDatabase, Nil) { - let parts = filepath.split(path) - let drop_number = list.length(parts) - use filenames <- result.try( - simplifile.get_files(path) |> result.replace_error(Nil), - ) - - let data = - filenames - |> list.map(process_tzfile(_, drop_number)) - |> result.values - - // If no parsable zone files were found return an Error rather than - // fail silently. - case list.length(data) { - 0 -> Error(Nil) - _ -> - Ok(TzDatabase( - list.map(data, fn(v) { v.0 }) |> list.sort(string.compare), - dict.from_list(data), - )) - } -} - /// Create new empty TzDatabase. This can be useful if you /// will be loading TZif files using a different method, for /// example over the internet, and wish to load them up into @@ -93,23 +55,6 @@ pub fn add_tzfile( TzDatabase(namelist, dict.insert(db.zone_data, zone_name, tzfile)) } -fn process_tzfile( - filename: String, - components_to_drop: Int, -) -> Result(#(String, parser.TzFile), Nil) { - let zone_name = - filepath.split(filename) - |> list.drop(components_to_drop) - |> list.fold("", filepath.join) - - use tzdata <- result.try( - simplifile.read_bits(filename) |> result.replace_error(Nil), - ) - use timeinfo <- result.try(parser.parse(tzdata) |> result.replace_error(Nil)) - - Ok(#(zone_name, timeinfo)) -} - /// Get all list of all time zone names within the /// time zone database. pub fn get_available_timezones(db: TzDatabase) -> List(String) { diff --git a/src/tzif/tzcalendar.gleam b/src/tzif/tzcalendar.gleam index 73f970f..a657c77 100644 --- a/src/tzif/tzcalendar.gleam +++ b/src/tzif/tzcalendar.gleam @@ -3,12 +3,13 @@ //// time zone. //// //// This library makes use of the [IANA tz database](https://www.iana.org/time-zones) -//// which is generally already installed on computers. +//// formatted data. //// This library will search for timezone data in the TZif or [tzfile](https://www.man7.org/linux/man-pages/man5/tzfile.5.html) -//// file format. These are generally located in the `/usr/share/zoneinfo` +//// binary format. These are generally located in the `/usr/share/zoneinfo` //// directory on posix systems, however if they are installed elsewhere the -//// then they can be loaded ysung the full path of the directory -//// containing the tz database files. +//// then they can be loaded using the full path of the directory +//// containing the tz database files. They can also be installed by adding the +//// [zones](https://zones.hexdocs.pm) gleam package to your project. //// //// Time zone identifiers are generally of the form "Continent/City" for example //// `America/New_York`, `Europe/Amsterdam`, or `Asia/Tokyo`. A list of time zone @@ -46,10 +47,10 @@ pub type TimeAndZone { /// /// ```gleam /// import gleam/time/timestamp -/// import tzif/database +/// import tzif/loader /// /// let ts = timestamp.from_unix_seconds(1_758_223_300) -/// let assert Ok(db) = database.load_from_os() +/// let assert Ok(db) = loader.load_from_os() /// /// to_time_and_zone(ts, "America/New_York", db) /// // Ok(TimeAndZone( @@ -90,10 +91,10 @@ pub fn to_time_and_zone( /// /// ```gleam /// import gleam/time/timestamp -/// import tzif/database +/// import zones /// /// let ts = timestamp.from_unix_seconds(1_758_223_300) -/// let assert Ok(db) = database.load_from_os() +/// let db = zones.database() /// /// to_calendar(ts, "America/New_York", db) /// // Ok(#( @@ -124,9 +125,9 @@ pub fn to_calendar( /// /// ```gleam /// import gleam/time/calendar -/// import tzif/database +/// import tzif/loader /// -/// let assert Ok(db) = database.load_from_os() +/// let assert Ok(db) = loader.load_from_os() /// /// from_calendar( /// calendar.Date(2025, calendar.November, 2),