+1
-1
gleam.toml
+1
-1
gleam.toml
+163
-26
src/birl.gleam
+163
-26
src/birl.gleam
···
66
66
67
67
Time(
68
68
now,
69
-
offset_in_minutes
70
-
* 60_000_000,
69
+
offset_in_minutes * 60_000_000,
71
70
option.map(timezone, fn(tz) {
72
71
case
73
72
zones.list
···
82
81
)
83
82
}
84
83
85
-
/// Use this to get the current time in utc
84
+
/// use this to get the current time in utc
86
85
pub fn utc_now() -> Time {
87
86
let now = ffi_now()
88
87
let monotonic_now = ffi_monotonic_now()
89
88
Time(now, 0, option.Some("Etc/UTC"), option.Some(monotonic_now))
90
89
}
91
90
92
-
/// Use this to get the current time with a given offset.
91
+
/// use this to get the current time with a given offset.
93
92
///
94
-
/// Some examples of acceptable offsets:
93
+
/// some examples of acceptable offsets:
95
94
///
96
95
/// `"+330", "03:30", "-8:00","-7", "-0400", "03"`
97
96
pub fn now_with_offset(offset: String) -> Result(Time, Nil) {
···
112
111
let monotonic_now = ffi_monotonic_now()
113
112
Time(
114
113
now,
115
-
offset
116
-
* 1_000_000,
114
+
offset * 1_000_000,
117
115
option.Some(timezone),
118
116
option.Some(monotonic_now),
119
117
)
···
128
126
ffi_monotonic_now()
129
127
}
130
128
129
+
/// returns a string which is the date part of an ISO8601 string along with the offset
130
+
pub fn to_date_string(value: Time) -> String {
131
+
let #(#(year, month, day), _, offset) = to_parts(value)
132
+
133
+
int.to_string(year)
134
+
<> "-"
135
+
<> {
136
+
month
137
+
|> int.to_string
138
+
|> string.pad_left(2, "0")
139
+
}
140
+
<> "-"
141
+
<> {
142
+
day
143
+
|> int.to_string
144
+
|> string.pad_left(2, "0")
145
+
}
146
+
<> offset
147
+
}
148
+
149
+
/// like `to_date_string` except it does not contain the offset
150
+
pub fn to_naive_date_string(value: Time) -> String {
151
+
let #(#(year, month, day), _, _) = to_parts(value)
152
+
153
+
int.to_string(year)
154
+
<> "-"
155
+
<> {
156
+
month
157
+
|> int.to_string
158
+
|> string.pad_left(2, "0")
159
+
}
160
+
<> "-"
161
+
<> {
162
+
day
163
+
|> int.to_string
164
+
|> string.pad_left(2, "0")
165
+
}
166
+
}
167
+
168
+
/// returns a string which is the time part of an ISO8601 string along with the offset
169
+
pub fn to_time_string(value: Time) -> String {
170
+
let #(_, #(hour, minute, second, milli_second), offset) = to_parts(value)
171
+
172
+
{
173
+
hour
174
+
|> int.to_string
175
+
|> string.pad_left(2, "0")
176
+
}
177
+
<> ":"
178
+
<> {
179
+
minute
180
+
|> int.to_string
181
+
|> string.pad_left(2, "0")
182
+
}
183
+
<> ":"
184
+
<> {
185
+
second
186
+
|> int.to_string
187
+
|> string.pad_left(2, "0")
188
+
}
189
+
<> "."
190
+
<> {
191
+
milli_second
192
+
|> int.to_string
193
+
|> string.pad_left(3, "0")
194
+
}
195
+
<> offset
196
+
}
197
+
198
+
/// like `to_time_string` except it does not contain the offset
199
+
pub fn to_naive_time_string(value: Time) -> String {
200
+
let #(_, #(hour, minute, second, milli_second), _) = to_parts(value)
201
+
202
+
{
203
+
hour
204
+
|> int.to_string
205
+
|> string.pad_left(2, "0")
206
+
}
207
+
<> ":"
208
+
<> {
209
+
minute
210
+
|> int.to_string
211
+
|> string.pad_left(2, "0")
212
+
}
213
+
<> ":"
214
+
<> {
215
+
second
216
+
|> int.to_string
217
+
|> string.pad_left(2, "0")
218
+
}
219
+
<> "."
220
+
<> {
221
+
milli_second
222
+
|> int.to_string
223
+
|> string.pad_left(3, "0")
224
+
}
225
+
}
226
+
131
227
pub fn to_iso8601(value: Time) -> String {
132
228
let #(#(year, month, day), #(hour, minute, second, milli_second), offset) =
133
229
to_parts(value)
···
172
268
<> offset
173
269
}
174
270
175
-
/// If you need to parse an `ISO8601` string, this is probably what you're looking for.
271
+
/// if you need to parse an `ISO8601` string, this is probably what you're looking for.
176
272
///
177
-
/// Given the huge surface area that `ISO8601` covers, it does not make sense for `birl`
273
+
/// given the huge surface area that `ISO8601` covers, it does not make sense for `birl`
178
274
/// to support all of it in one function, so this function parses only strings for which both
179
275
/// day and time of day can be extracted or deduced. Some acceptable examples are given below:
180
276
///
···
274
370
}
275
371
}
276
372
277
-
/// This function parses `ISO8601` strings in which no date is specified, which
373
+
/// this function parses `ISO8601` strings in which no date is specified, which
278
374
/// means such inputs don't actually represent a particular moment in time. That's why
279
375
/// the result of this function is an instance of `TimeOfDay` along with the offset specificed
280
376
/// in the string. Some acceptable examples are given below:
···
295
391
pub fn parse_time_of_day(value: String) -> Result(#(TimeOfDay, String), Nil) {
296
392
let assert Ok(offset_pattern) = regex.from_string("(.*)([+|\\-].*)")
297
393
298
-
let value = case
394
+
let time_string = case
299
395
[string.starts_with(value, "T"), string.starts_with(value, "t")]
300
396
{
301
397
[True, _] | [_, True] -> string.drop_left(value, 1)
···
303
399
}
304
400
305
401
use #(time_string, offset_string) <- result.then(case
306
-
string.ends_with(value, "Z")
307
-
|| string.ends_with(value, "z")
402
+
string.ends_with(time_string, "Z")
403
+
|| string.ends_with(time_string, "z")
308
404
{
309
405
True -> Ok(#(string.drop_right(value, 1), "+00:00"))
310
406
False ->
···
351
447
}
352
448
}
353
449
450
+
/// accepts fromats similar to the ones listed for `parse_time_of_day` except that there shoundn't be any offset information
451
+
pub fn parse_naive_time_of_day(
452
+
value: String,
453
+
) -> Result(#(TimeOfDay, String), Nil) {
454
+
let time_string = case
455
+
[string.starts_with(value, "T"), string.starts_with(value, "t")]
456
+
{
457
+
[True, _] | [_, True] -> string.drop_left(value, 1)
458
+
_ -> value
459
+
}
460
+
461
+
let time_string = string.replace(time_string, ":", "")
462
+
463
+
use #(time_string, milli_seconds_result) <- result.then(case
464
+
[string.split(time_string, "."), string.split(time_string, ",")]
465
+
{
466
+
[[_], [_]] -> {
467
+
Ok(#(time_string, Ok(0)))
468
+
}
469
+
[[time_string, milli_seconds_string], [_]]
470
+
| [[_], [time_string, milli_seconds_string]] -> {
471
+
Ok(#(
472
+
time_string,
473
+
milli_seconds_string
474
+
|> string.slice(0, 3)
475
+
|> string.pad_right(3, "0")
476
+
|> int.parse,
477
+
))
478
+
}
479
+
480
+
_ -> Error(Nil)
481
+
})
482
+
483
+
case milli_seconds_result {
484
+
Ok(milli_seconds) -> {
485
+
use time_of_day <- result.then(parse_time_section(time_string))
486
+
let assert [hour, minute, second] = time_of_day
487
+
488
+
Ok(#(TimeOfDay(hour, minute, second, milli_seconds), "Z"))
489
+
}
490
+
Error(Nil) -> Error(Nil)
491
+
}
492
+
}
493
+
354
494
/// the naive format is the same as ISO8601 except that it does not contain the offset
355
495
pub fn to_naive(value: Time) -> String {
356
496
let #(#(year, month, day), #(hour, minute, second, milli_second), _) =
···
395
535
}
396
536
}
397
537
398
-
/// Accepts fromats similar to the ones listed for `parse` except that there shoundn't be any offset information
538
+
/// accepts fromats similar to the ones listed for `parse` except that there shoundn't be any offset information
399
539
pub fn from_naive(value: String) -> Result(Time, Nil) {
400
540
let value = string.trim(value)
401
541
···
813
953
case mt {
814
954
option.Some(mt) ->
815
955
Time(
816
-
wall_time: wt
817
-
+ duration,
956
+
wall_time: wt + duration,
818
957
offset: o,
819
958
timezone: timezone,
820
959
monotonic_time: option.Some(mt + duration),
821
960
)
822
961
option.None ->
823
962
Time(
824
-
wall_time: wt
825
-
+ duration,
963
+
wall_time: wt + duration,
826
964
offset: o,
827
965
timezone: timezone,
828
966
monotonic_time: option.None,
···
837
975
case mt {
838
976
option.Some(mt) ->
839
977
Time(
840
-
wall_time: wt
841
-
- duration,
978
+
wall_time: wt - duration,
842
979
offset: o,
843
980
timezone: timezone,
844
981
monotonic_time: option.Some(mt - duration),
845
982
)
846
983
option.None ->
847
984
Time(
848
-
wall_time: wt
849
-
- duration,
985
+
wall_time: wt - duration,
850
986
offset: o,
851
987
timezone: timezone,
852
988
monotonic_time: option.None,
···
980
1116
981
1117
/// use this to change the offset of a given time value.
982
1118
///
983
-
/// Some examples of acceptable offsets:
1119
+
/// some examples of acceptable offsets:
984
1120
///
985
1121
/// `"+330", "03:30", "-8:00","-7", "-0400", "03", "Z"`
986
1122
pub fn set_offset(value: Time, new_offset: String) -> Result(Time, Nil) {
···
1025
1161
1026
1162
@target(erlang)
1027
1163
/// calculates erlang datetime using the offset in the DateTime value
1028
-
pub fn to_erlang_datetime(value: Time) -> #(#(Int, Int, Int), #(Int, Int, Int)) {
1164
+
pub fn to_erlang_datetime(
1165
+
value: Time,
1166
+
) -> #(#(Int, Int, Int), #(Int, Int, Int)) {
1029
1167
let #(date, #(hour, minute, second, _), _) = to_parts(value)
1030
1168
#(date, #(hour, minute, second))
1031
1169
}
···
1057
1195
1058
1196
Time(
1059
1197
wall_time,
1060
-
offset_in_minutes
1061
-
* 60_000_000,
1198
+
offset_in_minutes * 60_000_000,
1062
1199
option.map(timezone, fn(tz) {
1063
1200
case
1064
1201
zones.list
+34
-13
src/birl/duration.gleam
+34
-13
src/birl/duration.gleam
···
28
28
Duration(a + b)
29
29
}
30
30
31
+
pub fn subtract(a: Duration, b: Duration) -> Duration {
32
+
let Duration(a) = a
33
+
let Duration(b) = b
34
+
Duration(a - b)
35
+
}
36
+
31
37
pub fn seconds(value: Int) -> Duration {
32
38
Duration(value * second)
33
39
}
···
56
62
Duration(value * year)
57
63
}
58
64
59
-
/// Use this if you need short durations where a year just means 365 days and a month just means 30 days
65
+
/// use this if you need short durations where a year just means 365 days and a month just means 30 days
60
66
pub fn new(values: List(#(Int, Unit))) -> Duration {
61
67
values
62
68
|> list.fold(0, fn(total, current) {
···
75
81
|> Duration
76
82
}
77
83
78
-
/// Use this if you need very long durations where small inaccuracies could lead to large errors
84
+
/// use this if you need very long durations where small inaccuracies could lead to large errors
79
85
pub fn accurate_new(values: List(#(Int, Unit))) -> Duration {
80
86
values
81
87
|> list.fold(0, fn(total, current) {
···
94
100
|> Duration
95
101
}
96
102
97
-
/// Use this if you need short durations where a year just means 365 days and a month just means 30 days
103
+
/// use this if you need short durations where a year just means 365 days and a month just means 30 days
98
104
pub fn decompose(duration: Duration) -> List(#(Int, Unit)) {
99
105
let Duration(value) = duration
100
106
let absolute_value = int.absolute_value(value)
···
127
133
})
128
134
}
129
135
130
-
/// Use this if you need very long durations where small inaccuracies could lead to large errors
136
+
/// use this if you need very long durations where small inaccuracies could lead to large errors
131
137
pub fn accurate_decompose(duration: Duration) -> List(#(Int, Unit)) {
132
138
let Duration(value) = duration
133
139
let absolute_value = int.absolute_value(value)
···
160
166
})
161
167
}
162
168
169
+
/// approximates the duration by only the given unit
170
+
///
171
+
/// if the duration is not an integer multiple of the unit,
172
+
/// the remainder will be disgarded if it's less than two thirds of the unit,
173
+
/// otherwise a single unit will be added to the multiplier
174
+
///
175
+
/// - `duration.blur_to(duration.days(16), duration.Month)` -> 0
176
+
/// - `duration.blur_to(duration.days(20), duration.Month)` -> 1
177
+
pub fn blur_to(duration: Duration, unit: Unit) -> Int {
178
+
let assert Ok(unit_value) = list.key_find(unit_values, unit)
179
+
let Duration(value) = duration
180
+
let #(unit_counts, remaining) = extract(value, unit_value)
181
+
case remaining >= unit_value * 2 / 3 {
182
+
True -> unit_counts + 1
183
+
False -> unit_counts
184
+
}
185
+
}
186
+
163
187
/// approximates the duration by a value in a single unit
164
188
pub fn blur(duration: Duration) -> #(Int, Unit) {
165
-
case
166
-
duration
167
-
|> decompose
168
-
{
189
+
case decompose(duration) {
169
190
[] -> #(0, MicroSecond)
170
191
decomposed ->
171
192
decomposed
···
256
277
#(MilliSecond, milli_second_units),
257
278
]
258
279
259
-
/// You can use this function to create a new duration using expressions like:
280
+
/// you can use this function to create a new duration using expressions like:
260
281
///
261
282
/// "accurate: 1 Year - 2days + 152M -1h + 25 years + 25secs"
262
283
///
···
278
299
///
279
300
/// MilliSecond: ms, Msec, mSecs, milliSecond, MilliSecond, ...
280
301
///
281
-
/// Numbers with no unit are considered as microseconds.
282
-
/// Specifying `accurate:` is equivalent to using `accurate_new`.
302
+
/// numbers with no unit are considered as microseconds.
303
+
/// specifying `accurate:` is equivalent to using `accurate_new`.
283
304
pub fn parse(expression: String) -> Result(Duration, Nil) {
284
305
let assert Ok(re) = regex.from_string("([+|\\-])?\\s*(\\d+)\\s*(\\w+)?")
285
306
···
338
359
}
339
360
}
340
361
341
-
fn extract(duration: Int, unit: Int) -> #(Int, Int) {
342
-
#(duration / unit, duration % unit)
362
+
fn extract(duration: Int, unit_value: Int) -> #(Int, Int) {
363
+
#(duration / unit_value, duration % unit_value)
343
364
}