From c3af2204e4afd5437d95485b29661c0e413cebe7 Mon Sep 17 00:00:00 2001 From: gr-im Date: Wed, 8 Apr 2026 04:21:26 +0200 Subject: [PATCH 1/3] fix #1 Without implementation, the module `Lunar` is not _includable_ inside an executable. --- CHANGES.md | 4 ++++ lib/dune | 2 +- lib/lunar.ml | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 lib/lunar.ml diff --git a/CHANGES.md b/CHANGES.md index bb0e2ae..f88f571 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +### unreleased + +- 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)). diff --git a/lib/dune b/lib/dune index 91e8cad..48a7988 100644 --- a/lib/dune +++ b/lib/dune @@ -1,7 +1,7 @@ (library (name lunar) (public_name lunar) - (modules_without_implementation sigs lunar) + (modules_without_implementation sigs) (private_modules util)) (mdx diff --git a/lib/lunar.ml b/lib/lunar.ml new file mode 100644 index 0000000..ef0468e --- /dev/null +++ b/lib/lunar.ml @@ -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 From 9256690697dc3830b0c63624cf4970951248f6dc Mon Sep 17 00:00:00 2001 From: gr-im Date: Wed, 8 Apr 2026 04:39:22 +0200 Subject: [PATCH 2/3] fix #2 (Add more duration conversion) --- CHANGES.md | 1 + lib/duration.ml | 29 +++--- lib/duration.mli | 16 +++- test/dune | 1 + test/duration_conv_test.ml | 180 +++++++++++++++++++++++++++++++++++++ 5 files changed, 211 insertions(+), 16 deletions(-) create mode 100644 test/duration_conv_test.ml diff --git a/CHANGES.md b/CHANGES.md index f88f571..f9fbc80 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ ### 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 diff --git a/lib/duration.ml b/lib/duration.ml index 8350c07..08bb5e4 100644 --- a/lib/duration.ml +++ b/lib/duration.ml @@ -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 @@ -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 @@ -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 ;; @@ -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 diff --git a/lib/duration.mli b/lib/duration.mli index 8e083be..0845e5b 100644 --- a/lib/duration.mli +++ b/lib/duration.mli @@ -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 @@ -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 diff --git a/test/dune b/test/dune index 3061153..59c6150 100644 --- a/test/dune +++ b/test/dune @@ -5,6 +5,7 @@ (libraries lunar) (private_modules duration_test + duration_conv_test month_test duration_dhms_test duration_weekday_test diff --git a/test/duration_conv_test.ml b/test/duration_conv_test.ml new file mode 100644 index 0000000..ccc12be --- /dev/null +++ b/test/duration_conv_test.ml @@ -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 |}] +;; From f4f1c9c23ca8ff975a3d049e2f1f7874d63e604f Mon Sep 17 00:00:00 2001 From: gr-im Date: Wed, 8 Apr 2026 04:46:47 +0200 Subject: [PATCH 3/3] Add missing `CI` --- .github/workflows/ci.yml | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..342b52e --- /dev/null +++ b/.github/workflows/ci.yml @@ -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