Skip to content

Commit 25f4ccf

Browse files
committed
big update for 1.2.0
1 parent 3c8e278 commit 25f4ccf

File tree

9 files changed

+528
-163
lines changed

9 files changed

+528
-163
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ import humanise
1313
import humanise/time
1414
1515
pub fn main() {
16-
humanise.bytes_int(10_000) |> io.println // 10.0KB
16+
humanise.bytes_int(10_000) |> io.println // 10KB
1717
18-
time.Seconds(0.5) |> time.humanise |> time.to_string |> io.println // 500.0ms
18+
time.Seconds(0.5) |> time.humanise |> time.to_string |> io.println // 500ms
1919
20-
time.Hours(1.0) |> time.to_minutes |> io.debug // 60.0
20+
time.Hours(1.0) |> time.to_minutes |> echo // 60.0
21+
22+
time.Months(1.075) |> time.split(time.to_string_full) |> io.println // 1 month, 2.3 days
2123
}
2224
```
2325

gleam.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name = "humanise"
2-
version = "1.1.0"
2+
version = "1.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.

manifest.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# You typically do not need to edit this file
33

44
packages = [
5-
{ name = "gleam_stdlib", version = "0.62.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DC8872BC0B8550F6E22F0F698CFE7F1E4BDA7312FDEB40D6C3F44C5B706C8310" },
5+
{ name = "gleam_stdlib", version = "0.62.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "0080706D3A5A9A36C40C68481D1D231D243AF602E6D2A2BE67BA8F8F4DFF45EC" },
66
{ name = "gleam_time", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "DCDDC040CE97DA3D2A925CDBBA08D8A78681139745754A83998641C8A3F6587E" },
77
{ name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" },
88
]

src/humanise.gleam

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
//// This module contains a bunch of shortcuts to the `time`, `bytes` and 'bytes1024' modules for directly "humanising" and formatting a number (`Float` or `Int`) to a `String`.
1+
//// This module contains a bunch of shortcuts to the `time`, `bytes` and 'bytes1024' modules for directly "humanising"
2+
//// and formatting a number (`Float` or `Int`) to a `String`.
23
////
3-
//// For more control (e.g. work with `time.Time` or `bytes.Bytes` directly, use the given unit instead of the most optimal one), look at the `time`, `bytes` or `bytes1024` modules.
4+
//// For more control (e.g. work with `time.Time` or `bytes.Bytes` directly, use the given unit instead of the most
5+
//// optimal one), look at the `time`, `bytes` or `bytes1024` modules.
46

57
import gleam/float
68
import gleam/int
@@ -11,41 +13,46 @@ import gleam/time/timestamp.{type Timestamp}
1113
import humanise/bytes
1214
import humanise/bytes1024
1315
import humanise/time
16+
import humanise/util
1417

1518
/// Format a `Timestamp` relative to the provided current `Timestamp`.
1619
///
17-
/// This function finds the difference between the current time and the given time, and returns a string describing the difference. (e.g. "in 2.0s", "3.5d ago")
20+
/// This function finds the difference between the current time and the given time, and returns a string describing the
21+
/// difference using the provided formatter. (e.g. `"in 2s"`, `"3 days, 12 hours ago"`)
1822
///
19-
/// > If you're looking for prettier messages without decimal precision, I recommend the `timeago` package!
20-
pub fn date_relative(from date: Timestamp, now current: Timestamp) -> String {
23+
/// Examples:
24+
///
25+
/// ```
26+
/// let now = timestamp.system_time()
27+
///
28+
/// let from = now |> timestamp.add(duration.hours(36))
29+
/// humanise.date_relative(from:, now:, with: time.to_string) // "in 1.5d"
30+
///
31+
/// let from = now |> timestamp.add(duration.hours(-36))
32+
/// humanise.date_relative(from:, now:, with: time.split(_, time.to_string_full)) // "1 day, 12 hours ago"
33+
/// ```
34+
pub fn date_relative(
35+
from date: Timestamp,
36+
now current: Timestamp,
37+
with format: fn(time.Time) -> String,
38+
) -> String {
2139
let relative = current |> timestamp.difference(date) |> time.from_duration
2240

23-
let decompose = fn(a) {
24-
case a {
25-
time.Nanoseconds(n) -> #(time.Nanoseconds, n)
26-
time.Days(n) -> #(time.Days, n)
27-
time.Hours(n) -> #(time.Hours, n)
28-
time.Microseconds(n) -> #(time.Microseconds, n)
29-
time.Milliseconds(n) -> #(time.Milliseconds, n)
30-
time.Minutes(n) -> #(time.Minutes, n)
31-
time.Seconds(n) -> #(time.Seconds, n)
32-
time.Weeks(n) -> #(time.Weeks, n)
33-
}
34-
}
41+
let #(value, constructor) = time.decompose(relative)
3542

36-
let #(constructor, n) = decompose(relative)
37-
38-
case n >=. 0.0 {
39-
True -> "in " <> time.to_string(relative)
40-
False -> time.to_string(constructor(float.absolute_value(n))) <> " ago"
43+
case value >=. 0.0 {
44+
True -> "in " <> format(relative)
45+
False -> float.absolute_value(value) |> constructor |> format <> " ago"
4146
}
4247
}
4348

44-
/// Format a `Date`, `TimeOfDay` pair, automatically omitting redundant information (omit year if it matches the current year, omit month and day if it also matches the current day)
45-
///
49+
/// Format a `Date`, `TimeOfDay` pair, automatically omitting redundant information (omit year if it matches the current
50+
/// year, omit month and day if it also matches the current day)
51+
///
4652
/// The given date will be compared against the provided "current" date to determine what information to omit.
4753
///
48-
/// This function does not currently support internationalization, and simply returns a string in the following largest-to-smallest format:
54+
/// This function does not currently support internationalization, and simply returns a string in the following
55+
/// largest-to-smallest format:
4956
/// ```
5057
/// <maybe year> <maybe <month> <day>> <hours>:<minutes>:<seconds>
5158
/// ```
@@ -186,6 +193,26 @@ pub fn weeks_int(from n: Int) -> String {
186193
time.Weeks(int.to_float(n)) |> time.humanise |> time.to_string
187194
}
188195

196+
/// Format *n* months as a `Float`, converting to a more optimal unit if possible.
197+
pub fn months_float(from n: Float) -> String {
198+
time.Months(n) |> time.humanise |> time.to_string
199+
}
200+
201+
/// Format *n* months as an `Int`, converting to a more optimal unit if possible.
202+
pub fn months_int(from n: Int) -> String {
203+
time.Months(int.to_float(n)) |> time.humanise |> time.to_string
204+
}
205+
206+
/// Format *n* years as a `Float`, converting to a more optimal unit if possible.
207+
pub fn years_float(from n: Float) -> String {
208+
time.Years(n) |> time.humanise |> time.to_string
209+
}
210+
211+
/// Format *n* years as an `Int`, converting to a more optimal unit if possible.
212+
pub fn years_int(from n: Int) -> String {
213+
time.Years(int.to_float(n)) |> time.humanise |> time.to_string
214+
}
215+
189216
/// Format *n* bytes as a `Float`, converting to a more optimal unit if possible.
190217
pub fn bytes_float(from n: Float) -> String {
191218
bytes.Bytes(n) |> bytes.humanise |> bytes.to_string
@@ -291,3 +318,33 @@ pub fn tebibytes_int(from n: Int) -> String {
291318
|> bytes1024.humanise
292319
|> bytes1024.to_string
293320
}
321+
322+
/// Convert from 1000-multiple `bytes.Bytes` to 1024-multiple `bytes1024.Bytes`.
323+
pub fn bytes_to_bytes1024(from data: bytes.Bytes) -> bytes1024.Bytes {
324+
case data {
325+
bytes.Bytes(a) -> bytes1024.Bytes(a)
326+
bytes.Kilobytes(a) ->
327+
bytes1024.Kibibytes(a *. util.kilobyte /. util.kibibyte)
328+
bytes.Megabytes(a) ->
329+
bytes1024.Mebibytes(a *. util.megabyte /. util.mebibyte)
330+
bytes.Gigabytes(a) ->
331+
bytes1024.Gibibytes(a *. util.gigabyte /. util.gibibyte)
332+
bytes.Terabytes(a) ->
333+
bytes1024.Tebibytes(a *. util.terabyte /. util.tebibyte)
334+
}
335+
}
336+
337+
/// Convert from 1000-multiple `bytes.Bytes` to 1024-multiple `bytes1024.Bytes`.
338+
pub fn bytes1024_to_bytes(from data: bytes1024.Bytes) -> bytes.Bytes {
339+
case data {
340+
bytes1024.Bytes(a) -> bytes.Bytes(a)
341+
bytes1024.Kibibytes(a) ->
342+
bytes.Kilobytes(a *. util.kibibyte /. util.kilobyte)
343+
bytes1024.Mebibytes(a) ->
344+
bytes.Megabytes(a *. util.mebibyte /. util.megabyte)
345+
bytes1024.Gibibytes(a) ->
346+
bytes.Gigabytes(a *. util.gibibyte /. util.gigabyte)
347+
bytes1024.Tebibytes(a) ->
348+
bytes.Terabytes(a *. util.tebibyte /. util.terabyte)
349+
}
350+
}

src/humanise/bytes.gleam

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,20 @@
1-
//// This module contains functions for formatting amounts of data to `String`s (e.g. `"100.0B"`, `"1.5GB"`).
1+
//// This module contains functions for formatting amounts of data to `String`s (e.g. `"100B"`, `"1.5GB"`).
22
////
33
//// Usage generally looks like this:
44
//// ```
5-
//// bytes.Kilobytes(2000.0) |> bytes.humanise |> bytes.to_string // "2.0MB"
5+
//// bytes.Kilobytes(2000.0) |> bytes.humanise |> bytes.to_string // "2MB"
66
////
77
//// // or, if you don't want to change the unit
8-
//// bytes.Kilobytes(2000.0) |> bytes.to_string // "2000.0KB"
8+
//// bytes.Kilobytes(2000.0) |> bytes.to_string // "2000KB"
99
//// ```
1010
////
1111
//// *Note: This module is for 1000-multiple units! (kilobyte, megabyte, etc.)*
1212
//// *If you're looking for 1024-multiple units (kibibyte, mebibyte, etc.), look at the `bytes1024` module instead.*
1313

14-
import gleam/bool
1514
import gleam/float
1615

1716
import humanise/util
1817

19-
const kilobyte = 1000.0
20-
21-
const megabyte = 1_000_000.0
22-
23-
const gigabyte = 1_000_000_000.0
24-
25-
const terabyte = 1_000_000_000_000.0
26-
2718
/// The main type for holding data amount information.
2819
///
2920
/// Use its constructors directly to specify a unit for the value you want to format.
@@ -44,10 +35,10 @@ pub type Bytes {
4435
pub fn as_bytes(this bytes: Bytes) -> Float {
4536
case bytes {
4637
Bytes(n) -> n
47-
Kilobytes(n) -> n *. kilobyte
48-
Megabytes(n) -> n *. megabyte
49-
Gigabytes(n) -> n *. gigabyte
50-
Terabytes(n) -> n *. terabyte
38+
Kilobytes(n) -> n *. util.kilobyte
39+
Megabytes(n) -> n *. util.megabyte
40+
Gigabytes(n) -> n *. util.gigabyte
41+
Terabytes(n) -> n *. util.terabyte
5142
}
5243
}
5344

@@ -58,7 +49,7 @@ pub fn as_bytes(this bytes: Bytes) -> Float {
5849
/// bytes.Bytes(1000.0) |> bytes.as_kilobytes // 1.0
5950
/// ```
6051
pub fn as_kilobytes(this bytes: Bytes) -> Float {
61-
as_bytes(bytes) /. kilobyte
52+
as_bytes(bytes) /. util.kilobyte
6253
}
6354

6455
/// Convert a value to megabytes.
@@ -68,7 +59,7 @@ pub fn as_kilobytes(this bytes: Bytes) -> Float {
6859
/// bytes.Kilobytes(1000.0) |> bytes.as_megabytes // 1.0
6960
/// ```
7061
pub fn as_megabytes(this bytes: Bytes) -> Float {
71-
as_bytes(bytes) /. megabyte
62+
as_bytes(bytes) /. util.megabyte
7263
}
7364

7465
/// Convert a value to gigabytes.
@@ -78,7 +69,7 @@ pub fn as_megabytes(this bytes: Bytes) -> Float {
7869
/// bytes.Megabytes(1000.0) |> bytes.as_gigabytes // 1.0
7970
/// ```
8071
pub fn as_gigabytes(this bytes: Bytes) -> Float {
81-
as_bytes(bytes) /. gigabyte
72+
as_bytes(bytes) /. util.gigabyte
8273
}
8374

8475
/// Convert a value to terabytes.
@@ -88,7 +79,7 @@ pub fn as_gigabytes(this bytes: Bytes) -> Float {
8879
/// bytes.Gigabytes(1000.0) |> bytes.as_terabytes // 1.0
8980
/// ```
9081
pub fn as_terabytes(this bytes: Bytes) -> Float {
91-
as_bytes(bytes) /. terabyte
82+
as_bytes(bytes) /. util.terabyte
9283
}
9384

9485
/// Convert a value to a more optimal unit, if possible.
@@ -98,22 +89,22 @@ pub fn as_terabytes(this bytes: Bytes) -> Float {
9889
/// bytes.Megabytes(0.5) |> bytes.humanise // bytes.Kilobytes(500.0)
9990
/// ```
10091
pub fn humanise(this bytes: Bytes) -> Bytes {
101-
let abs = float.absolute_value
10292
let b = as_bytes(bytes)
10393

104-
use <- bool.guard(when: abs(b) <. kilobyte, return: Bytes(b))
105-
use <- bool.guard(when: abs(b) <. megabyte, return: Kilobytes(b /. kilobyte))
106-
use <- bool.guard(when: abs(b) <. gigabyte, return: Megabytes(b /. megabyte))
107-
use <- bool.guard(when: abs(b) <. terabyte, return: Gigabytes(b /. gigabyte))
108-
109-
Terabytes(b /. terabyte)
94+
case float.absolute_value(b) {
95+
abs_b if abs_b <. util.kilobyte -> Bytes(b)
96+
abs_b if abs_b <. util.megabyte -> Kilobytes(b /. util.kilobyte)
97+
abs_b if abs_b <. util.gigabyte -> Megabytes(b /. util.megabyte)
98+
abs_b if abs_b <. util.terabyte -> Gigabytes(b /. util.gigabyte)
99+
_ -> Terabytes(b /. util.terabyte)
100+
}
110101
}
111102

112-
/// Format a value as a `String`, rounded to at most 2 decimal places, followed by a unit suffix.
103+
/// Format a value as a `String`, rounded to at most 1 decimal place, followed by an abbreviated unit suffix.
113104
///
114105
/// Example:
115106
/// ```
116-
/// bytes.Gigabytes(30.125) |> bytes.to_string // "30.13GB"
107+
/// bytes.Gigabytes(30.125) |> bytes.to_string // "30.1GB"
117108
/// ```
118109
pub fn to_string(this bytes: Bytes) -> String {
119110
let #(n, suffix) = case bytes {
@@ -126,3 +117,47 @@ pub fn to_string(this bytes: Bytes) -> String {
126117

127118
util.format(n, suffix)
128119
}
120+
121+
/// Format a value as a `String`, rounded to at most 1 decimal place, followed by a long unit suffix.
122+
///
123+
/// Example:
124+
/// ```
125+
/// bytes.Gigabytes(30.125) |> bytes.to_string_full // "30.1 gigabytes"
126+
/// bytes.Megabytes(1.0) |> bytes.to_string_full // "1 megabyte"
127+
/// ```
128+
pub fn to_string_full(this bytes: Bytes) -> String {
129+
let #(n, suffix) = case bytes {
130+
Bytes(n) -> #(n, " byte")
131+
Kilobytes(n) -> #(n, " kilobyte")
132+
Megabytes(n) -> #(n, " megabyte")
133+
Gigabytes(n) -> #(n, " gigabyte")
134+
Terabytes(n) -> #(n, " terabyte")
135+
}
136+
137+
let plural = case n {
138+
1.0 -> ""
139+
_ -> "s"
140+
}
141+
142+
util.format(n, suffix) <> plural
143+
}
144+
145+
/// Decompose a value into its inner value and constructor.
146+
///
147+
/// This is useful if you need to operate directly on the value and then reconstruct it (i.e. for custom rounding precision).
148+
///
149+
/// Example:
150+
/// ```
151+
/// let #(value, constructor) = bytes.decompose(bytes.Megabytes(2.0)) // #(2.0, bytes.Megabytes)
152+
///
153+
/// constructor(value *. 2.0) // bytes.Megabytes(4.0)
154+
/// ```
155+
pub fn decompose(time: Bytes) -> #(Float, fn(Float) -> Bytes) {
156+
case time {
157+
Bytes(a) -> #(a, Bytes)
158+
Gigabytes(a) -> #(a, Gigabytes)
159+
Kilobytes(a) -> #(a, Kilobytes)
160+
Megabytes(a) -> #(a, Megabytes)
161+
Terabytes(a) -> #(a, Terabytes)
162+
}
163+
}

0 commit comments

Comments
 (0)