Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:

build-and-test:
strategy:
fail-fast: true
matrix:
os:
- ubuntu-latest
- macos-latest
ocaml-compiler:
- 5
runs-on: ${{ matrix.os }}

steps:
- name: Checkout Tree
uses: actions/checkout@v4

- name: Set-up OCaml
uses: ocaml/setup-ocaml@v3
with:
ocaml-compiler: ${{ matrix.ocaml-compiler }}

- run: opam install . --deps-only --with-test
- run: opam exec -- dune build
- run: opam exec -- dune runtest

lint-doc:
strategy:
fail-fast: true
matrix:
os: [ ubuntu-latest ]
ocaml-compiler: [ ocaml-base-compiler.5.4.0 ]
runs-on: ${{ matrix.os }}

steps:
- name: Checkout Tree
uses: actions/checkout@v4

- name: Set-up OCaml
uses: ocaml/setup-ocaml@v3
with:
ocaml-compiler: ${{ matrix.ocaml-compiler }}
- uses: ocaml/setup-ocaml/lint-doc@v3

lint-fmt:
strategy:
fail-fast: true
matrix:
os: [ ubuntu-latest ]
ocaml-compiler: [ ocaml-base-compiler.5.4.0 ]
runs-on: ${{ matrix.os }}

steps:
- name: Checkout Tree
uses: actions/checkout@v4

- name: Set-up OCaml
uses: ocaml/setup-ocaml@v3
with:
ocaml-compiler: ${{ matrix.ocaml-compiler }}
- uses: ocaml/setup-ocaml/lint-fmt@v3
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
### unreleased

- Add `Duration.to_(seconds|minutes|hours)` ([gr-im](https://github.com/gr-im))
- Add an implementation for `Lunar` ([gr-im](https://github.com/gr-im))

### v1.0.0

- First release of `lunar` exposing _among other things_ `Time`, `Date`, `Datetime`, `Timezone`, `Zoned_datetime` and `Range` (by [xvw](https://github.com/xvw), [gr-im](https://github.com/gr-im)).
2 changes: 1 addition & 1 deletion lib/dune
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(library
(name lunar)
(public_name lunar)
(modules_without_implementation sigs lunar)
(modules_without_implementation sigs)
(private_modules util))

(mdx
Expand Down
29 changes: 15 additions & 14 deletions lib/duration.ml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,11 @@ let from_minutes x = x |> from_seconds |> Int64.mul 60L
let from_hours x = x |> from_seconds |> Int64.mul 3600L
let from_days x = x |> from_seconds |> Int64.mul 86400L
let one_day = from_days 1
let one_minute = from_minutes 1
let one_hour = from_hours 1
let abs = Int64.abs
let neg x = Int64.(sub zero x)

let to_days t =
if t >= 0L
then Int64.(to_int (div t one_day))
else Int64.(to_int (div (sub t (sub one_day 1L)) one_day))
;;

let div_floor a b =
let q = a / b
and r = a mod b in
Expand Down Expand Up @@ -102,9 +98,9 @@ let pred = Int64.pred

let wdhms x =
let weeks, rem = Util.i64_div_mod_floor x (from_days 7) in
let days, rem = Util.i64_div_mod_floor rem (from_days 1) in
let hour, rem = Util.i64_div_mod_floor rem (from_hours 1) in
let min, sec = Util.i64_div_mod_floor rem (from_minutes 1) in
let days, rem = Util.i64_div_mod_floor rem one_day in
let hour, rem = Util.i64_div_mod_floor rem one_hour in
let min, sec = Util.i64_div_mod_floor rem one_minute in
(* MAYBE: an opportunity for labelled-tuple. *)
( Int64.to_int weeks
, Int64.to_int days
Expand All @@ -114,16 +110,16 @@ let wdhms x =
;;

let dhms x =
let days, rem = Util.i64_div_mod_floor x (from_days 1) in
let hour, rem = Util.i64_div_mod_floor rem (from_hours 1) in
let min, sec = Util.i64_div_mod_floor rem (from_minutes 1) in
let days, rem = Util.i64_div_mod_floor x one_day in
let hour, rem = Util.i64_div_mod_floor rem one_hour in
let min, sec = Util.i64_div_mod_floor rem one_minute in
(* MAYBE: an opportunity for labelled-tuple. *)
Int64.to_int days, Int64.to_int hour, Int64.to_int min, Int64.to_int sec
;;

let hms x =
let hours, rem = Util.i64_div_mod_floor x (from_hours 1) in
let min, sec = Util.i64_div_mod_floor rem (from_minutes 1) in
let hours, rem = Util.i64_div_mod_floor x one_hour in
let min, sec = Util.i64_div_mod_floor rem one_minute in
(* MAYBE: an opportunity for labelled-tuple. *)
Int64.to_int hours, Int64.to_int min, Int64.to_int sec
;;
Expand All @@ -135,6 +131,11 @@ let weekday x =
weekdays.((id + 7) mod 7)
;;

let to_seconds t = t |> to_int64 |> Int64.to_int
let to_minutes t = Int64.(to_int (div t one_minute))
let to_hours t = Int64.(to_int (div t one_hour))
let to_days t = Int64.(to_int (div t one_day))

module CE = struct
type nonrec t = t

Expand Down
16 changes: 14 additions & 2 deletions lib/duration.mli
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ val from_datetime
[year * month * day_of_month * hour * minute * second]. *)
val to_datetime : t -> int * int * int * int * int * int

(** [to_int64 d] returns the int64 representation of a duration.*)
(** [to_int64 d] returns the int64 representation of a duration in
seconds. *)
val to_int64 : t -> int64

(** [wdhms duration] returns the number of weeks, days, hours, minutes, and
Expand All @@ -81,7 +82,18 @@ val dhms : t -> int * int * int * int
seconds that describe the duration.*)
val hms : t -> int * int * int

(** [to_days duration] get a day approx for a duration. *)
(** [to_seconds duration] get a seconds approx for a duration. *)
val to_seconds : t -> int

(** [to_minutes duration] get a minute approx for a duration (drop seconds). *)
val to_minutes : t -> int

(** [to_hours duration] get a hour approx for a duration (drop seconds,
minutes). *)
val to_hours : t -> int

(** [to_days duration] get a day approx for a duration (drop seconds,
minutes and hours). *)
val to_days : t -> int

(** [weekday d] returns the {!type:Weekday.t} for the given duration since
Expand Down
17 changes: 17 additions & 0 deletions lib/lunar.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
(* Copyright (c) 2026, Cargocut and the Lunar developers.
All rights reserved.

SPDX-License-Identifier: BSD-3-Clause *)

module Era = Era
module Month = Month
module Weekday = Weekday
module Time = Time
module Date = Date
module Datetime = Datetime
module Timezone = Timezone
module Zoned_datetime = Zoned_datetime
module Resolution = Resolution
module Duration = Duration
module Range = Range
module Sigs = Sigs
1 change: 1 addition & 0 deletions test/dune
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
(libraries lunar)
(private_modules
duration_test
duration_conv_test
month_test
duration_dhms_test
duration_weekday_test
Expand Down
180 changes: 180 additions & 0 deletions test/duration_conv_test.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
(* Copyright (c) 2026, Cargocut and the Lunar developers.
All rights reserved.

SPDX-License-Identifier: BSD-3-Clause *)

let%expect_test "to_seconds" =
124 |> Duration.from_seconds |> Duration.to_seconds |> print_int;
[%expect {| 124 |}]
;;

let%expect_test "to_seconds" =
-124 |> Duration.from_seconds |> Duration.to_seconds |> print_int;
[%expect {| -124 |}]
;;

let%expect_test "to_minutes" =
124 |> Duration.from_minutes |> Duration.to_minutes |> print_int;
[%expect {| 124 |}]
;;

let%expect_test "to_minutes" =
-124 |> Duration.from_minutes |> Duration.to_minutes |> print_int;
[%expect {| -124 |}]
;;

let%expect_test "to_hours" =
124 |> Duration.from_hours |> Duration.to_hours |> print_int;
[%expect {| 124 |}]
;;

let%expect_test "to_hours" =
-124 |> Duration.from_hours |> Duration.to_hours |> print_int;
[%expect {| -124 |}]
;;

let%expect_test "to_days" =
124 |> Duration.from_days |> Duration.to_days |> print_int;
[%expect {| 124 |}]
;;

let%expect_test "to_days" =
-124 |> Duration.from_days |> Duration.to_days |> print_int;
[%expect {| -124 |}]
;;

let%expect_test "seconds_to_minutes_to_seconds" =
360
|> Duration.from_seconds
|> Duration.to_minutes
|> Duration.from_minutes
|> Duration.to_seconds
|> print_int;
[%expect {| 360 |}]
;;

let%expect_test "negative_seconds_to_minutes_to_seconds" =
-360
|> Duration.from_seconds
|> Duration.to_minutes
|> Duration.from_minutes
|> Duration.to_seconds
|> print_int;
[%expect {| -360 |}]
;;

let%expect_test "minutes_to_hours_to_minutes" =
180
|> Duration.from_minutes
|> Duration.to_hours
|> Duration.from_hours
|> Duration.to_minutes
|> print_int;
[%expect {| 180 |}]
;;

let%expect_test "negative_minutes_to_hours_to_minutes" =
-180
|> Duration.from_minutes
|> Duration.to_hours
|> Duration.from_hours
|> Duration.to_minutes
|> print_int;
[%expect {| -180 |}]
;;

let%expect_test "hours_to_days_to_hours" =
48
|> Duration.from_hours
|> Duration.to_days
|> Duration.from_days
|> Duration.to_hours
|> print_int;
[%expect {| 48 |}]
;;

let%expect_test "negative_hours_to_days_to_hours" =
-48
|> Duration.from_hours
|> Duration.to_days
|> Duration.from_days
|> Duration.to_hours
|> print_int;
[%expect {| -48 |}]
;;

let%expect_test "seconds_to_minutes_to_hours_to_seconds" =
7200
|> Duration.from_seconds
|> Duration.to_minutes
|> Duration.from_minutes
|> Duration.to_hours
|> Duration.from_hours
|> Duration.to_seconds
|> print_int;
[%expect {| 7200 |}]
;;

let%expect_test "negative_seconds_to_minutes_to_hours_to_seconds" =
-7200
|> Duration.from_seconds
|> Duration.to_minutes
|> Duration.from_minutes
|> Duration.to_hours
|> Duration.from_hours
|> Duration.to_seconds
|> print_int;
[%expect {| -7200 |}]
;;

let%expect_test "days_to_hours_to_minutes_to_seconds_to_days" =
3
|> Duration.from_days
|> Duration.to_hours
|> Duration.from_hours
|> Duration.to_minutes
|> Duration.from_minutes
|> Duration.to_seconds
|> Duration.from_seconds
|> Duration.to_days
|> print_int;
[%expect {| 3 |}]
;;

let%expect_test "negative_days_to_hours_to_minutes_to_seconds_to_days" =
-3
|> Duration.from_days
|> Duration.to_hours
|> Duration.from_hours
|> Duration.to_minutes
|> Duration.from_minutes
|> Duration.to_seconds
|> Duration.from_seconds
|> Duration.to_days
|> print_int;
[%expect {| -3 |}]
;;

let%expect_test "non_round_seconds_to_minutes_to_seconds" =
90
|> Duration.from_seconds
|> Duration.to_minutes
|> Duration.from_minutes
|> Duration.to_seconds
|> print_int;
[%expect {| 60 |}]
;;

let%expect_test "large_seconds_conversion" =
1_000_000
|> Duration.from_seconds
|> Duration.to_days
|> Duration.from_days
|> Duration.to_hours
|> Duration.from_hours
|> Duration.to_minutes
|> Duration.from_minutes
|> Duration.to_seconds
|> print_int;
[%expect {| 950400 |}]
;;
Loading