···11+open B0_kit.V000
22+open Result.Syntax
33+44+(* OCaml library names *)
55+66+let b0_std = B0_ocaml.libname "b0.std"
77+let compiler_libs_toplevel = B0_ocaml.libname "compiler-libs.toplevel"
88+let unix = B0_ocaml.libname "unix"
99+1010+let ptime = B0_ocaml.libname "ptime"
1111+let ptime_clock = B0_ocaml.libname "ptime.clock"
1212+let ptime_clock_os = B0_ocaml.libname "ptime.clock.os"
1313+let ptime_top = B0_ocaml.libname "ptime.top"
1414+1515+(* Libraries *)
1616+1717+let ptime_lib =
1818+ let srcs = [`Dir ~/"src"; `X ~/"src/ptime_top_init.ml" ] in
1919+ B0_ocaml.lib ptime ~srcs
2020+2121+let ptime_clock_lib =
2222+ let srcs = [`Dir ~/"src/clock"] in
2323+ B0_ocaml.lib ptime_clock ~srcs ~requires:[ptime] ~exports:[ptime]
2424+2525+let ptime_clock_os_lib =
2626+ B0_ocaml.deprecated_lib ~exports:[ptime_clock] ptime_clock_os
2727+2828+let ptime_top_lib =
2929+ let srcs = [`Dir ~/"src/top"] in
3030+ B0_ocaml.lib ptime_top ~srcs ~requires:[ptime; compiler_libs_toplevel]
3131+3232+(* Tests *)
3333+3434+let test ?(requires = []) =
3535+ B0_ocaml.test ~requires:(ptime :: b0_std :: requires)
3636+3737+let testing_ptime = `File ~/"test/testing_ptime.ml"
3838+3939+let test_ptime =
4040+ let srcs =
4141+ [ testing_ptime;
4242+ `File ~/"test/test_span.ml"; `File ~/"test/test_base.ml";
4343+ `File ~/"test/test_date.ml"; `File ~/"test/test_date_time.ml";
4444+ `File ~/"test/test_rfc3339.ml"; `File ~/"test/test_ptime.ml" ]
4545+ in
4646+ test ~/"test/test_ptime.ml" ~srcs ~requires:[unix]
4747+4848+let test_gmtime =
4949+ let doc = "Test stamps against Unix.gmtime" in
5050+ let srcs = [testing_ptime] in
5151+ test ~/"test/test_gmtime.ml" ~srcs ~requires:[unix] ~doc
5252+5353+let min_clock =
5454+ let doc = "Minimal clock example" in
5555+ test ~/"test/min_clock.ml" ~run:false ~doc ~requires:[ptime_clock]
5656+5757+(* FIXME b0 this makes the whole build bytecode. *)
5858+(* let min_clock_jsoo =
5959+ let doc = "Minimal clock example in JavaScript" in
6060+ let srcs = [`File ~/"test/min_clock.ml"] in
6161+ let meta = B0_meta.(empty |> tag test) in
6262+ let requires = [ptime; ptime_clock] in
6363+ B0_jsoo.html_page "min-clock-jsoo" ~doc ~srcs ~meta ~requires *)
6464+6565+let examples =
6666+ test ~/"test/examples.ml" ~run:false ~doc:"Examples from the API docs"
6767+6868+(* Packs *)
6969+7070+let default =
7171+ let meta =
7272+ B0_meta.empty
7373+ |> ~~ B0_meta.authors ["The ptime programmers"]
7474+ |> ~~ B0_meta.maintainers ["Daniel Bünzli <daniel.buenzl i@erratique.ch>"]
7575+ |> ~~ B0_meta.homepage "https://erratique.ch/software/ptime"
7676+ |> ~~ B0_meta.online_doc "https://erratique.ch/software/ptime/doc/"
7777+ |> ~~ B0_meta.licenses ["ISC"]
7878+ |> ~~ B0_meta.repo "git+https://erratique.ch/repos/ptime.git"
7979+ |> ~~ B0_meta.issues "https://github.com/dbuenzli/ptime/issues"
8080+ |> ~~ B0_meta.description_tags ["time"; "posix"; "system"; "org:erratique"]
8181+ |> ~~ B0_opam.depends
8282+ [ "ocaml", {|>= "4.08.0"|};
8383+ "ocamlfind", {|build|};
8484+ "ocamlbuild", {|build & != "0.9.0"|};
8585+ "topkg", {|build & >= "1.1.0"|};
8686+ ]
8787+ |> ~~ B0_opam.build
8888+ {|[["ocaml" "pkg/pkg.ml" "build" "--dev-pkg" "%{dev}%"]]|}
8989+ |> B0_meta.tag B0_opam.tag
9090+ |> B0_meta.tag B0_release.tag
9191+ in
9292+ B0_pack.make "default" ~doc:"ptime package" ~meta ~locked:true @@
9393+ B0_unit.list ()
+1
vendor/opam/ptime/BRZO
···11+(srcs-x myocamlbuild.ml pkg test src/ptime_top_init.ml)
+102
vendor/opam/ptime/CHANGES.md
···11+v1.2.0 2024-09-10 Zagreb
22+------------------------
33+44+- Fix fractional renderings of `Ptime.Span.pp` with leading zeros. For
55+ example 1.036s would render as 1.36s. This is a *rendering* bug in a
66+ function for human display, not a bug in the computations or
77+ conversion functions of `Ptime`.
88+- Add `Ptime.weekday` type for naming the result of the `Ptime.weekday`
99+ function.
1010+- Regularize naming structure. The `ptime.clock.os` library is deprecated.
1111+ Use `ptime.clock` instead.
1212+- Make the library `ptime.clock` export `ptime`.
1313+1414+v1.1.0 2022-12-02 Zagreb
1515+------------------------
1616+1717+- `Ptime.of_rfc3339` timezone offset parsing. Be even more lenient
1818+ in non-strict parsing mode: allow `hhmm` and `hh` timezone offsets.
1919+ (strict is `hh:mm`). Allows to parse an even larger subset of
2020+ ISO 8601 than RFC 3339 (#31).
2121+- Add `Ptime.{to,of}_year`. Less costly than extracting the first
2222+ component of `Ptime.to_date_time`. Useful for example to find
2323+ out which DST rules a timestamp is subjected to for rendering.
2424+- Add `?tz_offset_s` optional argument to `Ptime.{of,to}_date` (#32).
2525+- Add `Ptime.weekday_num`. An integer is often more convenient
2626+ than the enum value of `Ptime.weekday` (#30).
2727+- Add `Ptime.rfc3339_string_error` convenience function.
2828+- Use the new `js_of_ocaml` META `ocamlfind` standard to link
2929+ JavaScript stubs (#28).
3030+- No longer install interfaces in the `ptime.clock` package,
3131+ this package is now empty.
3232+3333+v1.0.0 2022-02-16 La Forclaz
3434+----------------------------
3535+3636+* Change the `js_of_ocaml` strategy for `Ptime_clock`'s JavaScript
3737+ implementation. Primitives of `ptime.clock.os` are now implemented
3838+ in pure JavaScript and linked by `js_of_ocaml`. This means that the
3939+ `ptime.clock.jsoo` library no longer exists, simply link against
4040+ `ptime.clock.os` instead. Thanks to Hugo Heuzard for suggesting and
4141+ implementing this.
4242+4343+* Require OCaml >= 4.08
4444+* Correct a potential overflow in Ptime.Span.of_float_s (#26).
4545+4646+v0.8.6 2021-11-28 Zagreb
4747+------------------------
4848+4949+* Require OCaml >= 4.03
5050+* Drop dependency on `result` compatibility package.
5151+* Alter install structure. `ptime/{os,jsoo}` are now installed in
5252+ `ptime/clock/{os,jsoo}`. Also a `ptime_clock.cm[t]i` is now
5353+ installed in `ptime/clock/`. The `ocamlfind` packages are unchanged
5454+ except for `ptime.clock.os.top` which no longer exists.
5555+* Handle `Pervasives` deprecation.
5656+* Fix `Ptime.truncate` to always truncate down. Thanks to David
5757+ Kaloper Meršinjak for the report & fix.
5858+* Allow compiling with MSVC compiler. Thanks to Jonah Beckford for the
5959+ patch.
6060+6161+v0.8.5 2019-05-02 La Forclaz (VS)
6262+---------------------------------
6363+6464+* Make the package compatible with `js_of_ocaml` 3.3.0's
6565+ namespacing
6666+6767+v0.8.4 2018-07-26 Zagreb
6868+------------------------
6969+7070+* `Ptime_clock`: Windows support. Thanks to IndiscriminateCoding
7171+ and David Allsopp for the contribution.
7272+* Fix `Ptime.frac_s` on pre-epoch time stamps. The function computed a
7373+ span of `1s - f` instead of `f` on these. This function is not used
7474+ internally so this only affects users of this function that apply it
7575+ on pre-epoch time stamps (#12). Thanks to David Kaloper Meršinjak
7676+ for the report.
7777+7878+v0.8.3 2017-02-05 La Forclaz (VS)
7979+---------------------------------
8080+8181+* Fix package for -custom linking.
8282+8383+v0.8.2 2016-07-22 Zagreb
8484+------------------------
8585+8686+* Add `?tz_offset_s` optional argument to `Ptime.weekday`. Thanks
8787+ to Maxence Guesdon for suggesting.
8888+8989+v0.8.1 2015-07-14 Cambridge (UK)
9090+--------------------------------
9191+9292+* Add `Ptime.v` and `Ptime.Span.v` to safely deal with trusted
9393+ inputs. Thanks to Matt Gray for suggesting.
9494+* Add `Ptime.weekday`, to help conversions to denormalized
9595+ timestamp formats. Thanks to Romain Calascibetta for suggesting.
9696+* Build depend on topkg.
9797+* Relicense from BSD3 to ISC.
9898+9999+v0.8.0 2015-12-24 Cambridge (UK)
100100+--------------------------------
101101+102102+First release. Thanks to Raphaël Proust for lodging support.
+13
vendor/opam/ptime/LICENSE.md
···11+Copyright (c) 2014 The ptime programmers
22+33+Permission to use, copy, modify, and/or distribute this software for any
44+purpose with or without fee is hereby granted, provided that the above
55+copyright notice and this permission notice appear in all copies.
66+77+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
88+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
99+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1010+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1111+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1212+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1313+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+47
vendor/opam/ptime/README.md
···11+Ptime — POSIX time for OCaml
22+============================
33+44+Ptime has platform independent POSIX time support in pure OCaml. It
55+provides a type to represent a well-defined range of POSIX timestamps
66+with picosecond precision, conversion with date-time values,
77+conversion with [RFC 3339 timestamps][rfc3339] and pretty printing to
88+a human-readable, locale-independent representation.
99+1010+The additional Ptime_clock library provides access to a system POSIX
1111+clock and to the system's current time zone offset.
1212+1313+Ptime is not a calendar library.
1414+1515+Ptime has no dependency. Ptime_clock depends on your system library or
1616+JavaScript runtime system. Ptime and its libraries are distributed
1717+under the ISC license.
1818+1919+[rfc3339]: http://tools.ietf.org/html/rfc3339
2020+2121+Home page: <http://erratique.ch/software/ptime>
2222+2323+# Installation
2424+2525+Ptime can be installed with `opam`:
2626+2727+ opam install ptime
2828+2929+If you don't use `opam` consult the [`opam`](opam) file for build
3030+instructions.
3131+3232+# Documentation
3333+3434+The documentation can be consulted [online] or via `odig doc mtime`.
3535+3636+Questions are welcome but better asked on the [OCaml forum] than on
3737+the issue tracker.
3838+3939+[online]: http://erratique.ch/software/ptime/doc/
4040+[OCaml forum]: https://discuss.ocaml.org/
4141+4242+# Sample programs
4343+4444+See [test/min_clock.ml](test/min_clock.ml).
4545+4646+If you installed ptime with `opam` sample programs are located in
4747+the directory `opam var ptime:doc`.
+8
vendor/opam/ptime/_tags
···11+true : bin_annot, safe_string
22+<_b0> : -traverse
33+<src> : include
44+<src/top> : include
55+<src/top/ptime_top*> : package(compiler-libs.toplevel)
66+<src/clock> : include
77+<src/clock/ptime_clock.{cma,cmxa}> : record_ptime_clock_stubs
88+<src/clock/ptime_clock.cmxs> : link_ptime_clock_stubs
+26
vendor/opam/ptime/doc/index.mld
···11+{0 Ptime {%html: <span class="version">%%VERSION%%</span>%}}
22+33+{!Ptime} has platform independent support for POSIX time.
44+55+It provides a {{!Ptime.t}type} to represent a well-defined range of
66+POSIX timestamps with picosecond precision, conversion with
77+{{!Ptime.date_time}date-time values}, conversion with
88+{{!Ptime.rfc3339}RFC 3339 timestamps} and {{!Ptime.print}pretty
99+printing} to a human-readable, locale-independent representation.
1010+1111+{!Ptime_clock} provides access to a
1212+{{!Ptime_clock.platform_support}system POSIX clock} and the system's
1313+current time zone offset.
1414+1515+Ptime is not a calendar library.
1616+1717+{1:ptime Library [ptime]}
1818+1919+{!modules: Ptime}
2020+2121+{1:ptime_clock Library [ptime.clock]}
2222+2323+{!modules: Ptime_clock}
2424+2525+This library also works with JavaScript,
2626+see the {{!Ptime_clock.platform_support}platform support}.
···11+open Ocamlbuild_plugin
22+open Command
33+44+let os = try Sys.getenv "PTIME_OS" with
55+| Not_found -> Ocamlbuild_pack.My_unix.run_and_read "uname -s"
66+77+let system_support_lib = match os with
88+| "Linux\n" -> [A "-cclib"; A "-lrt"]
99+| _ -> []
1010+1111+let lib s =
1212+ match !Ocamlbuild_plugin.Options.ext_lib with
1313+ | "" -> s ^ ".a"
1414+ | x -> s ^ "." ^ x
1515+1616+let () =
1717+ dispatch begin function
1818+ | After_rules ->
1919+2020+ (* ptime *)
2121+2222+ ocaml_lib ~tag_name:"use_ptime" ~dir:"src" "src/ptime";
2323+2424+ (* ptime_clock *)
2525+2626+ flag_and_dep ["link"; "ocaml"; "link_ptime_clock_stubs"]
2727+ (A (lib "src/clock/libptime_clock_stubs"));
2828+2929+ dep ["record_ptime_clock_stubs"]
3030+ [lib "src/clock/libptime_clock_stubs"];
3131+3232+ flag ["library"; "ocaml"; "byte"; "record_ptime_clock_stubs"]
3333+ (S ([A "-dllib"; A "-lptime_clock_stubs"] @ system_support_lib));
3434+ flag ["library"; "ocaml"; "record_ptime_clock_stubs"] (* byt + nat *)
3535+ (S ([A "-cclib"; A "-lptime_clock_stubs"] @ system_support_lib));
3636+3737+ ocaml_lib ~tag_name:"use_ptime_clock" ~dir:"src-clock"
3838+ "src/clock/ptime_clock";
3939+4040+ flag ["link"; "ocaml"; "use_ptime_clock"]
4141+ (S [A "-ccopt"; A "-Lsrc-clock"]);
4242+ | _ -> ()
4343+ end
+37
vendor/opam/ptime/opam
···11+opam-version: "2.0"
22+name: "ptime"
33+synopsis: "POSIX time for OCaml"
44+description: """\
55+Ptime has platform independent POSIX time support in pure OCaml. It
66+provides a type to represent a well-defined range of POSIX timestamps
77+with picosecond precision, conversion with date-time values,
88+conversion with [RFC 3339 timestamps][rfc3339] and pretty printing to
99+a human-readable, locale-independent representation.
1010+1111+The additional Ptime_clock library provides access to a system POSIX
1212+clock and to the system's current time zone offset.
1313+1414+Ptime is not a calendar library.
1515+1616+Ptime has no dependency. Ptime_clock depends on your system library or
1717+JavaScript runtime system. Ptime and its libraries are distributed
1818+under the ISC license.
1919+2020+[rfc3339]: http://tools.ietf.org/html/rfc3339
2121+2222+Home page: <http://erratique.ch/software/ptime>"""
2323+maintainer: "Daniel Bünzli <daniel.buenzl i@erratique.ch>"
2424+authors: "The ptime programmers"
2525+license: "ISC"
2626+tags: ["time" "posix" "system" "org:erratique"]
2727+homepage: "https://erratique.ch/software/ptime"
2828+doc: "https://erratique.ch/software/ptime/doc/"
2929+bug-reports: "https://github.com/dbuenzli/ptime/issues"
3030+depends: [
3131+ "ocaml" {>= "4.08.0"}
3232+ "ocamlfind" {build}
3333+ "ocamlbuild" {build & != "0.9.0"}
3434+ "topkg" {build & >= "1.1.0"}
3535+]
3636+build: ["ocaml" "pkg/pkg.ml" "build" "--dev-pkg" "%{dev}%"]
3737+dev-repo: "git+https://erratique.ch/repos/ptime.git"
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2015 The ptime programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(* Stubs *)
77+88+external ptime_clock_now_d_ps : unit -> int * int64 =
99+ "ocaml_ptime_clock_now_d_ps"
1010+1111+external ptime_clock_period_d_ps : unit -> (int * int64) option =
1212+ "ocaml_ptime_clock_period_d_ps"
1313+1414+external ptime_clock_current_tz_offset_s : unit -> int option =
1515+ "ocaml_ptime_clock_current_tz_offset_s"
1616+1717+(* POSIX clock *)
1818+1919+let now () = Ptime.unsafe_of_d_ps (ptime_clock_now_d_ps ())
2020+let period () = Ptime.Span.unsafe_of_d_ps_option (ptime_clock_period_d_ps ())
2121+2222+(* System time zone offset *)
2323+2424+let current_tz_offset_s = ptime_clock_current_tz_offset_s
2525+2626+(* Raw interface *)
2727+2828+let now_d_ps = ptime_clock_now_d_ps
2929+let period_d_ps = ptime_clock_period_d_ps
+80
vendor/opam/ptime/src/clock/ptime_clock.mli
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2015 The ptime programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** POSIX time clock.
77+88+ [Ptime_clock] provides access to a system POSIX time clock and to
99+ the system's current time zone offset.
1010+1111+ This time does not increase monotically and is subject to system
1212+ calendar time adjustments. Use {!Mtime} if you need monotonic
1313+ wall-clock time to measure time spans.
1414+1515+ Consult important information about {{!err}error handling}
1616+ and {{!platform_support}platform support}. *)
1717+1818+(** {1:clock POSIX clock} *)
1919+2020+val now : unit -> Ptime.t
2121+(** [now ()] is the current POSIX time, by definition always on the
2222+ UTC timeline.
2323+2424+ Raises {!Sys_error}, see {{!err}error handling}. *)
2525+2626+val period : unit -> Ptime.span option
2727+(** [period ()] is a positive POSIX time span representing
2828+ the clock's period (if available). *)
2929+3030+(** {1:tz System time zone offset} *)
3131+3232+val current_tz_offset_s : unit -> Ptime.tz_offset_s option
3333+(** [current_tz_offset_s ()] is the system's current local time zone
3434+ offset to UTC in seconds, if known. This is the duration local
3535+ time - UTC time in seconds. *)
3636+3737+(** {1:raw POSIX clock raw interface} *)
3838+3939+val now_d_ps : unit -> int * int64
4040+(** [now_d_ps ()] is [(d, ps)] representing POSIX time occuring at
4141+ [d] * 86'400e12 + [ps] POSIX picoseconds from the epoch
4242+ 1970-01-01 00:00:00 UTC. [ps] is in the range
4343+ \[[0];[86_399_999_999_999_999L]\].
4444+4545+ Raises {!Sys_error}, see {{!err}error handling} *)
4646+4747+val period_d_ps : unit -> (int * int64) option
4848+(** [period_d_ps ()] is if available [Some (d, ps)] representing the
4949+ clock's picosecond period [d] * 86'400e12 + [ps]. [ps] is in the
5050+ range \[[0];[86_399_999_999_999_999L]\]. *)
5151+5252+(** {1:err Error handling}
5353+5454+ The functions {!now} and {!now_d_ps} raise [Sys_error] whenever
5555+ they can't determine the current time or that it doesn't fit in
5656+ [Ptime]'s well-defined {{!Ptime.t}range}. This exception should
5757+ only be catched at the toplevel of your program to log it and
5858+ abort the program. It indicates a serious error condition in the
5959+ system.
6060+6161+ All the other functions, whose functionality is less essential,
6262+ simply silently return [None] if they can't determine the
6363+ information either because it is unavailable or because an error
6464+ occured.
6565+6666+ {1:platform_support Platform support}
6767+6868+ {ul
6969+ {- Platforms with a POSIX clock (includes Linux) use
7070+ {{:http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_gettime.html}[clock_gettime]} with [CLOCK_REALTIME].}
7171+ {- On Darwin {{:http://pubs.opengroup.org/onlinepubs/9699919799/}
7272+ [gettimeofday]} is used.}
7373+ {- On Windows
7474+ {{:https://msdn.microsoft.com/en-us/library/windows/desktop/ms724390(v=vs.85).aspx}[GetSystemTime]}
7575+ and
7676+ {{:https://msdn.microsoft.com/en-us/library/windows/desktop/ms724421(v=vs.85).aspx}[GetTimeZoneInformation]}
7777+ are used.}
7878+ {- On JavaScript
7979+ {{:http://www.ecma-international.org/ecma-262/6.0/index.html#sec-date.now}[Date.now ()]} and
8080+ {{:http://www.ecma-international.org/ecma-262/6.0/index.html#sec-date.prototype.gettimezoneoffset}[Date.prototype.getTimezoneOffset]} are used.}} *)
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2015 The ptime programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(* Julian day and proleptic Gregorian calendar date conversion.
77+88+ Formulae are from the calendar FAQ:
99+ http://www.tondering.dk/claus/cal/julperiod.php#formula
1010+1111+ These formulae work for positive Julian days. They represent
1212+ Gegorian calendar BCE year `y` by `-(y-1)`, e.g. 2 BCE is -1, this
1313+ follows the convention of ISO 8601.
1414+1515+ All timestamps in Ptime's [min;max] range are represented by
1616+ positive Julian days and the formulae do not overflow on 32-bit
1717+ platforms in this restricted range. *)
1818+1919+let jd_to_date jd =
2020+ let a = jd + 32044 in
2121+ let b = (4 * a + 3) / 146097 in
2222+ let c = a - ((146097 * b) / 4) in
2323+ let d = (4 * c + 3) / 1461 in
2424+ let e = c - ((1461 * d) / 4) in
2525+ let m = (5 * e + 2) / 153 in
2626+ let day = e - ((153 * m + 2) / 5) + 1 in
2727+ let month = m + 3 - (12 * (m / 10)) in
2828+ let year = 100 * b + d - 4800 + (m / 10) in
2929+ (year, month, day)
3030+3131+let jd_to_year jd = (* Same as above but only for the year *)
3232+ let a = jd + 32044 in
3333+ let b = (4 * a + 3) / 146097 in
3434+ let c = a - ((146097 * b) / 4) in
3535+ let d = (4 * c + 3) / 1461 in
3636+ let e = c - ((1461 * d) / 4) in
3737+ let m = (5 * e + 2) / 153 in
3838+ 100 * b + d - 4800 + (m / 10)
3939+4040+let jd_of_date (year, month, day) =
4141+ let a = (14 - month) / 12 in
4242+ let y = year + 4800 - a in
4343+ let m = month + 12 * a - 3 in
4444+ day + ((153 * m) + 2)/ 5 + 365 * y +
4545+ (y / 4) - (y / 100) + (y / 400) - 32045
4646+4747+let jd_posix_epoch = 2_440_588 (* the Julian day of the POSIX epoch *)
4848+let jd_ptime_min = 1_721_060 (* the Julian day of Ptime.min *)
4949+let jd_ptime_max = 5_373_484 (* the Julian day of Ptime.max *)
5050+5151+(* Picosecond precision POSIX timestamps and time span representation.
5252+5353+ POSIX timestamps and spans are represented by int * int64 pairs
5454+ with the int64 in the range [0L;86_399_999_999_999_999L]. A pair
5555+ [(d, ps)] denotes the POSIX picosecond duration [d] * 86_400e12 +
5656+ [ps].
5757+5858+ For a timestamp this can be seen as a POSIX day count from the
5959+ epoch paired with a picosecond precision POSIX time point in that
6060+ day starting from 00:00:00.
6161+6262+ By definition with a negative [d] the [ps] duration brings us
6363+ towards zero, *not* towards infinity:
6464+6565+6666+ (d * 86_400e12) (d * 86_400e12 + ps) 0
6767+ ... -----+-----------------+-------------------+--------- ...
6868+ [---------------->|
6969+ ps
7070+7171+ [d] is largely sufficent to represent all the days in Ptime's
7272+ [min;max] range on both 32-bit and 64-bit platforms. *)
7373+7474+type t = int * int64
7575+7676+let ps_count_in_ps = 1L
7777+let ps_count_in_ns = 1_000L
7878+let ps_count_in_100ns = 100_000L
7979+let ps_count_in_us = 1_000_000L
8080+let ps_count_in_100us = 100_000_000L
8181+let ps_count_in_ms = 1_000_000_000L
8282+let ps_count_in_100ms = 100_000_000_000L
8383+let ps_count_in_s = 1_000_000_000_000L
8484+let ps_count_in_min = 60_000_000_000_000L
8585+let ps_count_in_hour = 3600_000_000_000_000L
8686+let ps_count_in_day = 86_400_000_000_000_000L
8787+let ps_day_max = 86_399_999_999_999_999L
8888+8989+let day_min = jd_ptime_min - jd_posix_epoch
9090+let day_max = jd_ptime_max - jd_posix_epoch
9191+9292+let epoch = (0, 0L) (* 1970-01-01 00:00:00 UTC *)
9393+let min = (day_min, 0L) (* 0000-01-01 00:00:00 UTC *)
9494+let max = (day_max, ps_day_max) (* 9999-12-31 23:59:59 UTC *)
9595+9696+(* POSIX time spans *)
9797+9898+type span = t
9999+100100+module Span = struct
101101+102102+ let stdlib_abs = abs
103103+104104+ (* Arithmetic *)
105105+106106+ let neg = function
107107+ | (d, 0L) -> (-d, 0L)
108108+ | (d, ps) -> (-(d + 1), Int64.sub ps_count_in_day ps)
109109+110110+ let add (d0, ps0) (d1, ps1) =
111111+ let d = d0 + d1 in
112112+ let ps = Int64.add ps0 ps1 in
113113+ let ps_clamp = Int64.rem ps ps_count_in_day in
114114+ let d = d + Int64.compare ps ps_clamp in
115115+ d, ps_clamp
116116+117117+ let sub s0 s1 = add s0 (neg s1)
118118+ let abs (d, _ as s) = if d < 0 then neg s else s
119119+120120+ (* POSIX time spans *)
121121+122122+ type t = span
123123+124124+ let zero = (0, 0L)
125125+ let v (d, ps as s) =
126126+ if ps < 0L || ps > ps_day_max
127127+ then invalid_arg (Format.sprintf "illegal ptime time span: (%d,%Ld)" d ps)
128128+ else s
129129+130130+ let of_d_ps (d, ps as s) = if ps < 0L || ps > ps_day_max then None else Some s
131131+ let unsafe_of_d_ps s = s
132132+ let unsafe_of_d_ps_option s = s
133133+ let to_d_ps s = s
134134+135135+ let of_int_s secs =
136136+ let d = stdlib_abs secs in
137137+ let s = (d / 86_400, Int64.(mul (of_int (d mod 86_400)) ps_count_in_s)) in
138138+ if secs < 0 then neg s else s
139139+140140+ let day_int_min = min_int / 86_400
141141+ let day_int_max = max_int / 86_400
142142+ let to_int_s (d, ps) =
143143+ if d < day_int_min || d > day_int_max then None else
144144+ let days_s = d * 86_400 in
145145+ let day_s = Int64.(to_int (div ps ps_count_in_s)) (* always positive *) in
146146+ let secs = days_s + day_s in
147147+ if secs < days_s (* positive overflow *) then None else Some secs
148148+149149+ let min_int_float = float min_int
150150+ let max_int_float = float max_int
151151+ let of_float_s secs =
152152+ if secs <> secs (* nan *) then None else
153153+ let days = floor (secs /. 86_400.) in
154154+ if days < min_int_float || days > max_int_float then None else
155155+ let rem_s = mod_float secs 86_400. in
156156+ let rem_s = if rem_s < 0. then 86_400. +. rem_s else rem_s in
157157+ if rem_s >= 86_400. then
158158+ (* Guard against a potential overflow in the computation of [rem_s] *)
159159+ let days = days +. 1. in
160160+ if days > max_int_float then None else
161161+ Some (int_of_float days, 0L)
162162+ else
163163+ let frac_s, rem_s = modf rem_s in
164164+ let rem_ps = Int64.(mul (of_float rem_s) ps_count_in_s) in
165165+ let frac_ps = Int64.(of_float (frac_s *. 1e12)) in
166166+ Some (int_of_float days, (Int64.add rem_ps frac_ps))
167167+168168+ let to_float_s (d, ps) =
169169+ let days_s = (float d) *. 86_400. in
170170+ let day_s = Int64.(to_float (div ps ps_count_in_s)) in
171171+ let day_rem_ps = Int64.(to_float (rem ps ps_count_in_s)) in
172172+ days_s +. day_s +. (day_rem_ps *. 1e-12)
173173+174174+ (* Predicates *)
175175+176176+ let equal (d0, ps0) (d1, ps1) =
177177+ (compare : int -> int -> int) d0 d1 = 0 &&
178178+ Int64.compare ps0 ps1 = 0
179179+180180+ let compare (d0, ps0) (d1, ps1) =
181181+ let c = (compare : int -> int -> int) d0 d1 in
182182+ if c <> 0 then c else (compare : int64 -> int64 -> int) ps0 ps1
183183+184184+ (* Rounding *)
185185+186186+ let round_div a b = (* a >= 0 and b > 0 *)
187187+ if a = 0L then 0L else
188188+ Int64.(div (add a (div b 2L)) b)
189189+190190+ let frac_div = [| 1_000_000_000_000L;
191191+ 100_000_000_000L;
192192+ 10_000_000_000L;
193193+ 1_000_000_000L;
194194+ 100_000_000L;
195195+ 10_000_000L;
196196+ 1_000_000L;
197197+ 100_000L;
198198+ 10_000L;
199199+ 1_000L;
200200+ 100L;
201201+ 10L;
202202+ 1L; |]
203203+204204+ let round ~frac_s:frac (sign, _ as t) =
205205+ let frac = if frac < 0 then 0 else (if frac > 12 then 12 else frac) in
206206+ let (d, ps) = if sign < 0 then neg t else t in
207207+ let rps = Int64.mul (round_div ps frac_div.(frac)) frac_div.(frac) in
208208+ let t = if rps > ps_day_max then (d + 1, 0L) else (d, rps) in
209209+ if sign < 0 then neg t else t
210210+211211+ let truncate ~frac_s:frac (sign, _ as t) =
212212+ let frac = if frac < 0 then 0 else (if frac > 12 then 12 else frac) in
213213+ let (d, ps) = if sign < 0 then neg t else t in
214214+ let tps = Int64.(sub ps (rem ps frac_div.(frac))) in
215215+ if sign < 0 then neg (d, tps) else (d, tps)
216216+217217+ let truncate_down ~frac_s:frac (d, ps) =
218218+ (d, Int64.(sub ps (rem ps frac_div.(frac ))))
219219+220220+ (* Pretty printing *)
221221+222222+ let dump ppf (d, ps) = Format.fprintf ppf "@[<1>(%d,@,%Ld)@]" d ps
223223+224224+ (* Warning laborious code follows. Is there a better way ? *)
225225+226226+ let divide_ps ~carry ps hi lo =
227227+ let hi_d = Int64.(to_int (div ps hi)) in
228228+ let rem_ps = Int64.rem ps hi in
229229+ let lo_d = Int64.to_int (round_div rem_ps lo) in
230230+ if lo_d = carry then hi_d + 1, 0 else hi_d, lo_d
231231+232232+ let pp_y_d ppf ~neg d ps = (* assert d >= 0 *)
233233+ let y, rem_d =
234234+ let max_d = max_int / 4 in
235235+ if d > max_d then (* d * 4 overflows *) d / 365, d mod 365 else
236236+ let y = (d * 4) / 1461 (* / 365.25 *) in
237237+ y, d - (y * 1461) / 4
238238+ in
239239+ let days = rem_d + Int64.to_int (round_div ps ps_count_in_day) in
240240+ let y, days = if days = 366 then y + 1, 1 else y, days in
241241+ let y = if neg then -y else y in
242242+ Format.fprintf ppf "%dy" y;
243243+ if days <> 0 then Format.fprintf ppf "%dd" days;
244244+ ()
245245+246246+ let pp_d_h ppf ~neg d ps =
247247+ let h, _ = divide_ps ~carry:1 ps ps_count_in_hour ps_count_in_hour in
248248+ let d, h = if h = 24 then d + 1, 0 else d, h in
249249+ if d = 366 then Format.fprintf ppf "%dy1d" (if neg then -1 else 1) else
250250+ if d = 365 && h >= 6
251251+ then Format.fprintf ppf "%dy" (if neg then -1 else 1) else
252252+ let d = if neg then -d else d in
253253+ Format.fprintf ppf "%dd" d;
254254+ if h <> 0 then Format.fprintf ppf "%dh" h;
255255+ ()
256256+257257+ let pp_h_m ppf ~neg ps =
258258+ let h, m = divide_ps ~carry:60 ps ps_count_in_hour ps_count_in_min in
259259+ if h = 24 then Format.fprintf ppf "%dd" (if neg then -1 else 1) else
260260+ let h = if neg then -h else h in
261261+ Format.fprintf ppf "%dh" h;
262262+ if m <> 0 then Format.fprintf ppf "%dmin" m;
263263+ ()
264264+265265+ let pp_m_s ppf ~neg ps =
266266+ let m, s = divide_ps ~carry:60 ps ps_count_in_min ps_count_in_s in
267267+ if m = 60 then Format.fprintf ppf "%dh" (if neg then -1 else 1) else
268268+ let m = if neg then -m else m in
269269+ Format.fprintf ppf "%dmin" m;
270270+ if s <> 0 then Format.fprintf ppf "%ds" s;
271271+ ()
272272+273273+ let pp_s ppf ~neg ps =
274274+ let s, ms = divide_ps ~carry:1000 ps ps_count_in_s ps_count_in_ms in
275275+ if s = 60 then Format.fprintf ppf "%dmin" (if neg then -1 else 1) else
276276+ let s = if neg then -s else s in
277277+ if ms <> 0 then Format.fprintf ppf "%d.%03ds" s ms else
278278+ Format.fprintf ppf "%ds" s
279279+280280+ let pp_unit higher_str hi hi_str frac_limit lo ppf ~neg ps =
281281+ let pp_unit_integral ppf ~neg h =
282282+ if h = 1000
283283+ then Format.fprintf ppf "%d%s" (if neg then -1 else 1) higher_str
284284+ else Format.fprintf ppf "%d%s" (if neg then -h else h) hi_str
285285+ in
286286+ if ps < frac_limit then begin
287287+ let h, l = divide_ps ~carry:1000 ps hi lo in
288288+ if h >= 100 || l = 0 then pp_unit_integral ppf ~neg h else
289289+ let h = if neg then -h else h in
290290+ Format.fprintf ppf "%d.%03d%s" h l hi_str
291291+ end else begin
292292+ let ms, _ = divide_ps ~carry:1 ps hi hi in
293293+ pp_unit_integral ppf ~neg ms
294294+ end
295295+296296+ let pp_ms =
297297+ pp_unit "s" ps_count_in_ms "ms" ps_count_in_100ms ps_count_in_us
298298+299299+ let pp_us =
300300+ pp_unit "ms" ps_count_in_us "us" ps_count_in_100us ps_count_in_ns
301301+302302+ let pp_ns =
303303+ pp_unit "us" ps_count_in_ns "ns" ps_count_in_100ns ps_count_in_ps
304304+305305+ let pp_ps ppf ~neg ps =
306306+ let ps = Int64.to_int ps in
307307+ Format.fprintf ppf "%dps" (if neg then -ps else ps)
308308+309309+ let pp ppf (sign, _ as s) =
310310+ let neg = sign < 0 in
311311+ match (abs s) with
312312+ | (0, ps) ->
313313+ if ps >= ps_count_in_hour then pp_h_m ppf ~neg ps else
314314+ if ps >= ps_count_in_min then pp_m_s ppf ~neg ps else
315315+ if ps >= ps_count_in_s then pp_s ppf ~neg ps else
316316+ if ps >= ps_count_in_ms then pp_ms ppf ~neg ps else
317317+ if ps >= ps_count_in_us then pp_us ppf ~neg ps else
318318+ if ps >= ps_count_in_ns then pp_ns ppf ~neg ps else
319319+ pp_ps ppf ~neg ps
320320+ | (d, ps) ->
321321+ if d > 365 then pp_y_d ppf ~neg d ps else
322322+ pp_d_h ppf ~neg d ps
323323+end
324324+325325+(* POSIX timestamps *)
326326+327327+let v (d, ps as s) =
328328+ if (ps < 0L || ps > ps_day_max || d < day_min || d > day_max)
329329+ then invalid_arg (Format.sprintf "illegal ptime timestamp: (%d,%Ld)" d ps)
330330+ else s
331331+332332+let unsafe_of_d_ps s = s
333333+334334+let of_span (d, _ as span) =
335335+ if d < day_min || d > day_max then None else Some span
336336+337337+let to_span t = t
338338+339339+let of_float_s secs = match Span.of_float_s secs with
340340+| None -> None
341341+| Some d -> of_span d
342342+343343+let to_float_s = Span.to_float_s
344344+345345+let truncate = Span.truncate_down
346346+347347+let frac_s (_, ps) = (0, Int64.(rem ps ps_count_in_s))
348348+349349+(* Predicates *)
350350+351351+let equal = Span.equal
352352+let compare = Span.compare
353353+let is_earlier t ~than = compare t than = -1
354354+let is_later t ~than = compare t than = 1
355355+356356+(* POSIX arithmetic *)
357357+358358+let add_span t d = of_span (Span.add t d)
359359+let sub_span t d = of_span (Span.sub t d)
360360+let diff t1 t0 = Span.sub t1 t0
361361+362362+(* Time zone offsets between local and UTC timelines *)
363363+364364+type tz_offset_s = int
365365+366366+(* Date-time conversion
367367+368368+ POSIX time counts seconds since 1970-01-01 00:00:00 UTC without
369369+ counting leap seconds -- when a leap second occurs a POSIX second
370370+ can be two SI seconds or zero SI second. Hence 86400 POSIX seconds
371371+ always represent an UTC day and the translations below are accurate
372372+ without having to refer to a leap seconds table. *)
373373+374374+type date = (int * int * int)
375375+type time = (int * int * int) * tz_offset_s
376376+377377+let max_month_day = (* max day number in a given year's month. *)
378378+ let is_leap_year y = (y mod 4 = 0) && (y mod 100 <> 0 || y mod 400 = 0) in
379379+ let mlen = [|31; 28 (* or not *); 31; 30; 31; 30; 31; 31; 30; 31; 30; 31|] in
380380+ fun y m -> if (m = 2 && is_leap_year y) then 29 else mlen.(m - 1)
381381+382382+let is_date_valid (y, m, d) =
383383+ 0 <= y && y <= 9999 &&
384384+ 1 <= m && m <= 12 &&
385385+ 1 <= d && d <= max_month_day y m
386386+387387+let is_time_valid ((hh, mm, ss), _) =
388388+ 0 <= hh && hh <= 23 &&
389389+ 0 <= mm && mm <= 59 &&
390390+ 0 <= ss && ss <= 60
391391+392392+let of_date_time (date, ((hh, mm, ss), tz_offset_s as t)) =
393393+ (* We first verify that the given date and time are Ptime-valid.
394394+ Once this has been established we find find the number of Julian
395395+ days since the epoch for the given proleptic Georgian calendar
396396+ date. This gives us the POSIX day component of the timestamp. The
397397+ remaining time fields are used to derive the picosecond precision
398398+ time in that day compensated by the time zone offset. The final
399399+ result is checked to be in Ptime's [min;max] range.
400400+401401+ By definition POSIX timestamps cannot represent leap seconds.
402402+ With the code below any date-time with a seconds value of 60
403403+ (leap second addition) is mapped to the POSIX timestamp that
404404+ happens 1 second later which is what POSIX mktime would to. Any
405405+ formally non-existing UTC date-time with a seconds value of 59
406406+ (leap second subtraction) is mapped on the POSIX timestamp that
407407+ represents this non existing instant. *)
408408+ if not (is_date_valid date && is_time_valid t) then None else
409409+ let d = jd_of_date date - jd_posix_epoch in
410410+ let hh_ps = Int64.(mul (of_int hh) ps_count_in_hour) in
411411+ let mm_ps = Int64.(mul (of_int mm) ps_count_in_min) in
412412+ let ss_ps = Int64.(mul (of_int ss) ps_count_in_s) in
413413+ let ps = Int64.(add hh_ps (add mm_ps ss_ps)) in
414414+ sub_span (d, ps) (Span.of_int_s tz_offset_s)
415415+416416+let to_date_time ?(tz_offset_s = 0) t =
417417+ (* To render the timestamp in the given time zone offset we first
418418+ express the timestamp in local time and then compute the date
419419+ fields on that stamp as if it were UTC. If the local timestamp is
420420+ not in [min;max] then its date fields cannot be valid according
421421+ to the constraints guaranteed by Ptime and we fallback to UTC,
422422+ i.e. a time zone offset of 0.
423423+424424+ We then apply the following algorithm whose description makes
425425+ sense on a POSIX timestamp (i.e. UTC) but works equally well to
426426+ render the date-time fields of a local timestamp.
427427+428428+ We first take take the POSIX day count [d] (equivalent by
429429+ definition to an UTC day count) from the epoch, convert it to a
430430+ Julian day and use this to get the proleptic Gregorian calendar
431431+ date. The POSIX picoseconds [ps] in the day are are converted to
432432+ a daytime according to to its various units.
433433+434434+ By definition no POSIX timestamp can represent a date-time with a
435435+ seconds value of 60 (leap second addition) and thus the function
436436+ will never return a date-time with such a value. On the other
437437+ hand it will return an inexisting UTC date-time with a seconds
438438+ value of 59 whenever a leap second is subtracted since there is a
439439+ POSIX timestamp that represents this instant. *)
440440+ let (d, ps), tz_offset_s = match add_span t (Span.of_int_s tz_offset_s) with
441441+ | None -> t, 0 (* fallback to UTC *)
442442+ | Some local -> local, tz_offset_s
443443+ in
444444+ let jd = d + jd_posix_epoch in
445445+ let date = jd_to_date jd in
446446+ let hh = Int64.(to_int (div ps ps_count_in_hour)) in
447447+ let hh_rem = Int64.rem ps ps_count_in_hour in
448448+ let mm = Int64.(to_int (div hh_rem ps_count_in_min)) in
449449+ let mm_rem = Int64.rem hh_rem ps_count_in_min in
450450+ let ss = Int64.(to_int (div mm_rem ps_count_in_s)) in
451451+ date, ((hh, mm, ss), tz_offset_s)
452452+453453+let of_date ?tz_offset_s:(tz = 0) date = of_date_time (date, ((00, 00, 00), tz))
454454+let to_date ?tz_offset_s t = fst (to_date_time ?tz_offset_s t)
455455+let of_year ?tz_offset_s y = of_date ?tz_offset_s (y, 01, 01)
456456+let to_year ?(tz_offset_s = 0) t =
457457+ let d = match add_span t (Span.of_int_s tz_offset_s) with
458458+ | None -> fst t (* fallback to UTC *) | Some (local_d, _) -> local_d
459459+ in
460460+ jd_to_year (d + jd_posix_epoch)
461461+462462+463463+type weekday = [ `Sun | `Mon | `Tue | `Wed | `Thu | `Fri | `Sat ]
464464+465465+let weekday_num ?(tz_offset_s = 0) t =
466466+ let (d, _) = Span.add t (Span.of_int_s tz_offset_s) in
467467+ (* N.B. in contrast to [to_date_time] we don't care if we fall outside
468468+ [min;max]. Even if it happens the result of the computation is still
469469+ correct *)
470470+ let i = (d + 4 (* Epoch, d = 0, was a thu, we want 4 for that day *)) mod 7 in
471471+ if i < 0 then 7 + i else i
472472+473473+let weekday =
474474+ let wday = [| `Sun; `Mon; `Tue; `Wed; `Thu; `Fri; `Sat; |] in
475475+ fun ?tz_offset_s t -> wday.(weekday_num ?tz_offset_s t)
476476+477477+(* RFC 3339 timestamp conversions *)
478478+479479+(* RFC 3339 timestamp parser *)
480480+481481+type error_range = int * int
482482+type rfc3339_error =
483483+ [ `Invalid_stamp | `Eoi | `Exp_chars of char list | `Trailing_input ]
484484+485485+let pp_rfc3339_error ppf = function
486486+| `Invalid_stamp -> Format.fprintf ppf "@[invalid@ time@ stamp@]"
487487+| `Eoi -> Format.fprintf ppf "@[unexpected@ end@ of@ input@]"
488488+| `Trailing_input -> Format.fprintf ppf "@[trailing@ input@]"
489489+| `Exp_chars cs ->
490490+ let rec pp_chars ppf = function
491491+ | c :: cs -> Format.fprintf ppf "@ %C" c; pp_chars ppf cs
492492+ | [] -> ()
493493+ in
494494+ Format.fprintf ppf "@[expected@ a@ character@ in:%a@]" pp_chars cs
495495+496496+let pp_range ppf (s, e) =
497497+ if s = e then Format.pp_print_int ppf s else Format.fprintf ppf "%d-%d" s e
498498+499499+let _rfc3339_error_to_string (r, err) =
500500+ Format.asprintf "@[<h>%a: %a@]" pp_range r pp_rfc3339_error err
501501+502502+let rfc3339_string_error = function
503503+| Ok _ as v -> v | Error (`RFC3339 e) -> Error (_rfc3339_error_to_string e)
504504+505505+let rfc3339_error_to_msg = function
506506+| Ok _ as v -> v | Error (`RFC3339 e) ->
507507+ Error (`Msg (_rfc3339_error_to_string e))
508508+509509+exception RFC3339 of (int * int) * rfc3339_error (* Internal *)
510510+511511+let error r e = raise (RFC3339 (r, e))
512512+let error_pos p e = error (p, p) e
513513+let error_exp_digit p =
514514+ error_pos p (`Exp_chars ['0'; '1'; '2'; '3'; '4'; '5'; '6'; '7'; '8'; '9'])
515515+516516+let is_digit = function '0' .. '9' -> true | _ -> false
517517+518518+let parse_digits ~count pos max s =
519519+ let stop = pos + count - 1 in
520520+ if stop > max then error_pos max `Eoi else
521521+ let rec loop k acc =
522522+ if k > stop then acc else
523523+ if is_digit s.[k] then loop (k+1) (acc * 10 + Char.code s.[k] - 0x30) else
524524+ error_exp_digit k
525525+ in
526526+ loop pos 0
527527+528528+let parse_char c pos max s =
529529+ if pos > max then error_pos max `Eoi else
530530+ if s.[pos] = c then () else error_pos pos (`Exp_chars [c])
531531+532532+let parse_dt_sep ~strict pos max s =
533533+ let is_dt_sep = function
534534+ | 'T' -> true
535535+ | 't' | ' ' when not strict -> true
536536+ | _ -> false
537537+ in
538538+ if pos > max then error_pos max `Eoi else
539539+ if is_dt_sep s.[pos] then () else
540540+ error_pos pos (`Exp_chars (['T'] @ if strict then [] else ['t'; ' ']))
541541+542542+let decide_frac_or_tz ~strict pos max s =
543543+ if pos > max then error_pos max `Eoi else
544544+ match s.[pos] with
545545+ | '.' -> `Frac
546546+ | '+' | '-' | 'Z' -> `Tz
547547+ | 'z' when not strict -> `Tz
548548+ | c ->
549549+ let chars = ['.'; '+'; '-'; 'Z'] @ if strict then [] else ['z'] in
550550+ error_pos pos (`Exp_chars chars)
551551+552552+let parse_frac_ps pos max s =
553553+ if pos > max then error_pos max `Eoi else
554554+ if not (is_digit s.[pos]) then error_exp_digit pos else
555555+ let rec loop k acc pow =
556556+ if k > max then error_pos max `Eoi else
557557+ if not (is_digit s.[k]) then (Some acc), k else
558558+ let count = k - pos + 1 in
559559+ if count > 12 then (* truncate *) loop (k + 1) acc pow else
560560+ let pow = Int64.div pow 10L in
561561+ let acc = Int64.(add acc (mul (of_int (Char.code s.[k] - 0x30)) pow)) in
562562+ loop (k + 1) acc pow
563563+ in
564564+ loop pos 0L ps_count_in_s
565565+566566+let parse_tz_s ~strict pos max s =
567567+ let parse_tz_mag sign pos =
568568+ let hh_pos = pos in
569569+ let hh = parse_digits ~count:2 hh_pos max s in
570570+ let mm, mm_pos = match strict with
571571+ | true ->
572572+ let mm_pos = hh_pos + 3 in
573573+ parse_char ':' (mm_pos - 1) max s;
574574+ parse_digits ~count:2 mm_pos max s, mm_pos
575575+ | false ->
576576+ let next = hh_pos + 2 in
577577+ if next > max || not (s.[next] = ':' || is_digit s.[next])
578578+ then (0, hh_pos (* end pos of parse - 1, one is added at the end *))
579579+ else
580580+ let mm_pos = if s.[next] = ':' then hh_pos + 3 else hh_pos + 2 in
581581+ parse_digits ~count:2 mm_pos max s, mm_pos
582582+ in
583583+ if hh > 23 then error (hh_pos, hh_pos + 1) `Invalid_stamp else
584584+ if mm > 59 then error (mm_pos, mm_pos + 1) `Invalid_stamp else
585585+ let secs = hh * 3600 + mm * 60 in
586586+ let tz_s = match secs = 0 && sign = -1 with
587587+ | true -> None (* -00:00 convention *)
588588+ | false -> Some (sign * secs)
589589+ in
590590+ tz_s, mm_pos + 1
591591+ in
592592+ if pos > max then error_pos max `Eoi else
593593+ match s.[pos] with
594594+ | 'Z' -> Some 0, pos
595595+ | 'z' when not strict -> Some 0, pos
596596+ | '+' -> parse_tz_mag ( 1) (pos + 1)
597597+ | '-' -> parse_tz_mag (-1) (pos + 1)
598598+ | c ->
599599+ let chars = ['+'; '-'; 'Z'] @ if strict then [] else ['z'] in
600600+ error_pos pos (`Exp_chars chars)
601601+602602+let of_rfc3339 ?(strict = false) ?(sub = false) ?(start = 0) s =
603603+ try
604604+ let s_len = String.length s in
605605+ let max = s_len - 1 in
606606+ if s_len = 0 || start < 0 || start > max then error_pos start `Eoi else
607607+ let y_pos = start in
608608+ let m_pos = y_pos + 5 in
609609+ let d_pos = m_pos + 3 in
610610+ let hh_pos = d_pos + 3 in
611611+ let mm_pos = hh_pos + 3 in
612612+ let ss_pos = mm_pos + 3 in
613613+ let decide_pos = ss_pos + 2 in
614614+ let y = parse_digits ~count:4 y_pos max s in
615615+ parse_char '-' (m_pos - 1) max s;
616616+ let m = parse_digits ~count:2 m_pos max s in
617617+ parse_char '-' (d_pos - 1) max s;
618618+ let d = parse_digits ~count:2 d_pos max s in
619619+ parse_dt_sep ~strict (hh_pos - 1) max s;
620620+ let hh = parse_digits ~count:2 hh_pos max s in
621621+ parse_char ':' (mm_pos - 1) max s;
622622+ let mm = parse_digits ~count:2 mm_pos max s in
623623+ parse_char ':' (ss_pos - 1) max s;
624624+ let ss = parse_digits ~count:2 ss_pos max s in
625625+ let frac, tz_pos = match decide_frac_or_tz ~strict decide_pos max s with
626626+ | `Frac -> parse_frac_ps (decide_pos + 1) max s
627627+ | `Tz -> None, decide_pos
628628+ in
629629+ let tz_s_opt, last_pos = parse_tz_s ~strict tz_pos max s in
630630+ let tz_s = match tz_s_opt with None -> 0 | Some s -> s in
631631+ match of_date_time ((y, m, d), ((hh, mm, ss), tz_s)) with
632632+ | None -> error (start, last_pos) `Invalid_stamp
633633+ | Some t ->
634634+ let t, tz_s = match frac with
635635+ | None | Some 0L -> t, tz_s
636636+ | Some frac ->
637637+ match add_span t (0, frac) with
638638+ | None -> error (start, last_pos) `Invalid_stamp
639639+ | Some t -> t, tz_s
640640+ in
641641+ if not sub && last_pos <> max
642642+ then error_pos (last_pos + 1) `Trailing_input
643643+ else Ok (t, tz_s_opt, last_pos - start + 1)
644644+ with RFC3339 (r, e) -> Error (`RFC3339 (r, e))
645645+646646+(* RFC 3339 timestamp formatter *)
647647+648648+let rfc3339_adjust_tz_offset tz_offset_s =
649649+ (* The RFC 3339 time zone offset field is limited in expression to
650650+ the bounds below with minute precision. If the requested time
651651+ zone offset exceeds these bounds or is not an *integral* number
652652+ of minutes we simply use UTC. An alternative would be to
653653+ compensate the offset *and* the timestamp but it's more
654654+ complicated to explain and maybe more surprising to the user. *)
655655+ let min = -86340 (* -23h59 in secs *) in
656656+ let max = +86340 (* +23h59 in secs *) in
657657+ if min <= tz_offset_s && tz_offset_s <= max && tz_offset_s mod 60 = 0
658658+ then tz_offset_s, false
659659+ else 0 (* UTC *), true
660660+661661+let s_frac_of_ps frac ps =
662662+ Int64.(div (rem ps ps_count_in_s) Span.frac_div.(frac))
663663+664664+let to_rfc3339 ?(space = false) ?frac_s:(frac = 0) ?tz_offset_s (_, ps as t) =
665665+ let buf = Buffer.create 255 in
666666+ let tz_offset_s, tz_unknown = match tz_offset_s with
667667+ | Some tz -> rfc3339_adjust_tz_offset tz
668668+ | None -> 0, true
669669+ in
670670+ let (y, m, d), ((hh, ss, mm), tz_offset_s) = to_date_time ~tz_offset_s t in
671671+ let dt_sep = if space then ' ' else 'T' in
672672+ Printf.bprintf buf "%04d-%02d-%02d%c%02d:%02d:%02d" y m d dt_sep hh ss mm;
673673+ let frac = if frac < 0 then 0 else (if frac > 12 then 12 else frac) in
674674+ if frac <> 0 then Printf.bprintf buf ".%0*Ld" frac (s_frac_of_ps frac ps);
675675+ if tz_offset_s = 0 && not tz_unknown then Printf.bprintf buf "Z" else
676676+ begin
677677+ let tz_sign = if tz_offset_s < 0 || tz_unknown then '-' else '+' in
678678+ let tz_min = abs (tz_offset_s / 60) in
679679+ let tz_hh = tz_min / 60 in
680680+ let tz_mm = tz_min mod 60 in
681681+ Printf.bprintf buf "%c%02d:%02d" tz_sign tz_hh tz_mm;
682682+ end;
683683+ Buffer.contents buf
684684+685685+let pp_rfc3339 ?space ?frac_s ?tz_offset_s () ppf t =
686686+ Format.fprintf ppf "%s" (to_rfc3339 ?space ?frac_s ?tz_offset_s t)
687687+688688+(* Pretty printing *)
689689+690690+let pp_human ?frac_s:(frac = 0) ?tz_offset_s () ppf (_, ps as t) =
691691+ let tz_offset_s, tz_unknown = match tz_offset_s with
692692+ | Some tz -> rfc3339_adjust_tz_offset tz
693693+ | None -> 0, true
694694+ in
695695+ let (y, m, d), ((hh, ss, mm), tz_offset_s) = to_date_time ~tz_offset_s t in
696696+ Format.fprintf ppf "%04d-%02d-%02d %02d:%02d:%02d" y m d hh ss mm;
697697+ let frac = if frac < 0 then 0 else (if frac > 12 then 12 else frac) in
698698+ if frac <> 0 then Format.fprintf ppf ".%0*Ld" frac (s_frac_of_ps frac ps);
699699+ let tz_sign = if tz_offset_s < 0 || tz_unknown then '-' else '+' in
700700+ let tz_min = abs (tz_offset_s / 60) in
701701+ let tz_hh = tz_min / 60 in
702702+ let tz_mm = tz_min mod 60 in
703703+ Format.fprintf ppf " %c%02d:%02d" tz_sign tz_hh tz_mm;
704704+ ()
705705+706706+let pp = pp_human ~tz_offset_s:0 ()
707707+let dump = Span.dump
+612
vendor/opam/ptime/src/ptime.mli
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2015 The ptime programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** POSIX time values.
77+88+ Consult the {{!basics}basics} and a few {{!notes}notes
99+ and limitations}.
1010+1111+ {b References}
1212+ {ul
1313+ {- The Open Group. {{:http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_15}The Open Group Base Specifications Issue 7, section 4.15 Seconds Since the Epoch}. 2013}
1414+ {- G. Klyne et al.
1515+ {{:http://tools.ietf.org/html/rfc3339}
1616+ {e Date and Time on the Internet: Timestamps}}. RFC 3339, 2002.}} *)
1717+1818+(** {1:timespans POSIX time spans} *)
1919+2020+type span
2121+(** The type for signed picosecond precision POSIX time spans. A value
2222+ of this type represent the POSIX duration between two POSIX
2323+ timestamps. *)
2424+2525+(** POSIX time spans.
2626+2727+ {b WARNING.} A POSIX time span is not equal to an SI second based time
2828+ span see the {{!basics}basics}. *)
2929+module Span : sig
3030+3131+ (** {1:spans POSIX time spans} *)
3232+3333+ type t = span
3434+ (** The type for signed, picosecond precision, POSIX time spans. *)
3535+3636+ val v : int * int64 -> span
3737+ (** [v s] is like {!of_d_ps}[ s] but raises [Invalid_argument] if
3838+ [s] is not in the right range. Use {!of_d_ps} to deal with
3939+ untrusted input. *)
4040+4141+ val zero : span
4242+ (** [zero] is the neutral element of {!add}. *)
4343+4444+ val of_d_ps : int * int64 -> span option
4545+ (** [of_d_ps (d, ps)] is a span for the signed POSIX picosecond
4646+ span [d] * 86_400e12 + [ps]. [d] is a signed number of POSIX
4747+ days and [ps] a number of picoseconds in the range
4848+ \[[0];[86_399_999_999_999_999L]\]. [None] is returned if
4949+ [ps] is not in the right range. *)
5050+5151+ (**/**)
5252+ val unsafe_of_d_ps : int * int64 -> span
5353+ val unsafe_of_d_ps_option : (int * int64) option -> span option
5454+ (**/**)
5555+5656+ val to_d_ps : span -> int * int64
5757+ (** [to_d_ps d] is the span [d] as a pair [(d, ps)] expressing the
5858+ POSIX picosecond span [d] * 86_400e12 + [ps] with
5959+ [ps] in the range \[[0];[86_399_999_999_999_999L]\] *)
6060+6161+ val of_int_s : int -> span
6262+ (** [of_int_s secs] is a span from the signed integer POSIX second
6363+ span [secs]. *)
6464+6565+ val to_int_s : span -> int option
6666+ (** [to_int_s d] is the span [d] as a signed integer POSIX second
6767+ span, if [int]'s range can represent it (note that this
6868+ depends on {!Sys.word_size}). Subsecond precision numbers are
6969+ truncated. *)
7070+7171+ val of_float_s : float -> span option
7272+ (** [of_float_s secs] is a span from the signed floating point POSIX
7373+ second span [d]. Subpicosecond precision numbers are truncated.
7474+7575+ [None] is returned if [secs] cannot be represented as a span.
7676+ This occurs on {!Stdlib.nan} or if the duration in POSIX
7777+ days cannot fit on an [int] (on 32-bit platforms this means the
7878+ absolute magnitude of the duration is greater than ~2'941'758
7979+ years). *)
8080+8181+ val to_float_s : span -> float
8282+ (** [to_float_s s] is the span [d] as floating point POSIX seconds.
8383+8484+ {b Warning.} The magnitude of [s] may not be represented exactly
8585+ by the floating point value. *)
8686+8787+ (** {1:predicates Predicates} *)
8888+8989+ val equal : span -> span -> bool
9090+ (** [equal d d'] is [true] iff [d] and [d'] are the same time span. *)
9191+9292+ val compare : span -> span -> int
9393+ (** [compare d d'] is a total order on durations that is compatible
9494+ with signed time span order. *)
9595+9696+ (** {1:arith Arithmetic}
9797+9898+ {b Note.} The following functions rollover on overflows. *)
9999+100100+ val neg : span -> span
101101+ (** [neg d] is the span [d] negated. *)
102102+103103+ val add : span -> span -> span
104104+ (** [add d d'] is [d] + [d']. *)
105105+106106+ val sub : span -> span -> span
107107+ (** [sub d d'] is [d] - [d']. *)
108108+109109+ val abs : span -> span
110110+ (** [abs d] is the absolute value of span [d]. *)
111111+112112+ (** {1:rounding Rounding} *)
113113+114114+ val round : frac_s:int -> span -> span
115115+ (** [round ~frac_s t] is [t] rounded to the [frac_s] decimal
116116+ fractional second. Ties are rounded away from zero. [frac_s] is
117117+ clipped to the range \[[0];[12]\]. *)
118118+119119+ val truncate : frac_s:int -> span -> span
120120+ (** [truncate ~frac_s t] is [t] truncated to the [frac_s] decimal
121121+ fractional second. [frac_s] is clipped to the range
122122+ \[[0];[12]\]. *)
123123+124124+ (** {1:print Pretty printing} *)
125125+126126+ val pp : Format.formatter -> span -> unit
127127+ (** [pp ppf d] prints an unspecified, approximative, representation of [d]
128128+ on [ppf].
129129+130130+ The representation is not fixed-width, depends on the magnitude of [d]
131131+ and uses locale independent
132132+ {{:http://www.bipm.org/en/publications/si-brochure/chapter3.html}SI
133133+ prefixes} on seconds and
134134+ {{:http://www.bipm.org/en/publications/si-brochure/table6.html}accepted
135135+ non-SI units}. Years are counted in Julian years (365.25
136136+ SI-accepted days) as
137137+ {{:http://www.iau.org/publications/proceedings_rules/units/}defined}
138138+ by the International Astronomical Union (IUA).
139139+140140+ The representation is approximative. In particular beyond 60
141141+ seconds it only keeps the two most significant time units and
142142+ rounds towards the infinity. The latter means that case arising,
143143+ it always {e over} approximates durations.
144144+145145+ {b Warning} Becomes unprecise (but does not overflow) if the
146146+ absolute number of POSIX days in the time span is greater than [max_int /
147147+ 4] (on 32-bit platforms this is ~735'439 years) *)
148148+149149+ val dump : Format.formatter -> span -> unit
150150+ (** [dump ppf s] prints an unspecified raw representation of [d]
151151+ on [ppf]. *)
152152+end
153153+154154+(** {1:timestamps POSIX timestamps} *)
155155+156156+type t
157157+(** The type for picosecond precision POSIX timestamps in the range
158158+ \[{!min};{!max}\]. Note that POSIX timestamps, and hence values of
159159+ this type, are by definition always on the UTC timeline. *)
160160+161161+val v : int * int64 -> t
162162+(** [v s] is [of_span (Span.v s)] but raise [Invalid_argument] if [s]
163163+ is not in the right range. Use {!Span.of_d_ps} and {!of_span}
164164+ to deal with untrusted input. *)
165165+166166+val epoch : t
167167+(** [epoch] is 1970-01-01 00:00:00 UTC. *)
168168+169169+val min : t
170170+(** [min] is 0000-01-01 00:00:00 UTC, the earliest timestamp
171171+ representable by {!Ptime}. *)
172172+173173+val max : t
174174+(** [max] is 9999-12-31 23:59:59.999999999999 UTC, the latest timestamp
175175+ representable by {!Ptime}. *)
176176+177177+val of_span : span -> t option
178178+(** [of_span d] is the POSIX time stamp that:
179179+ {ul
180180+ {- Happens at the POSIX span [d] {e after} {!epoch}
181181+ if [d] is positive.}
182182+ {- Happens at the POSIX span [d] {e before} {!epoch}
183183+ if [d] is negative.}}
184184+ [None] is returned if the timestamp is not in the range
185185+ \[{!min};{!max}\]. *)
186186+187187+val to_span : t -> span
188188+(** [to_span t] is the signed POSIX span that happen between [t]
189189+ and {!epoch}:
190190+ {ul
191191+ {- If the number is positive [t] happens {e after} {!epoch}.}
192192+ {- If the number is negative [t] happens {e before} {!epoch}.}} *)
193193+194194+(**/**)
195195+val unsafe_of_d_ps : int * int64 -> t
196196+(**/**)
197197+198198+val of_float_s : float -> t option
199199+(** [of_float_s d] is like {!of_span} but with [d] as a floating point
200200+ second POSIX span [d]. This function is compatible with the result
201201+ of {!Unix.gettimeofday}. Decimal fractional seconds beyond [1e-12]
202202+ are truncated. *)
203203+204204+val to_float_s : t -> float
205205+(** [to_float_s t] is like {!to_span} but returns a floating point second
206206+ POSIX span.
207207+208208+ {b Warning.} Due to floating point inaccuracies do not expect the
209209+ function to round trip with {!of_float_s}; especially near
210210+ {!Ptime.min} and {!Ptime.max}. *)
211211+212212+val truncate : frac_s:int -> t -> t
213213+(** [truncate ~frac_s t] is [t] truncated to the [frac_s] decimal
214214+ fractional second. Effectively this reduces precision without
215215+ rounding, the timestamp remains in the second it is in. [frac_s]
216216+ is clipped to the range \[[0];[12]\]. *)
217217+218218+val frac_s : t -> span
219219+(** [frac_s t] is the (positive) fractional second duration in [t]. *)
220220+221221+(** {1:predicates Predicates} *)
222222+223223+val equal : t -> t -> bool
224224+(** [equal t t'] is [true] iff [t] and [t'] are the same timestamps. *)
225225+226226+val compare : t -> t -> int
227227+(** [compare t t'] is a total order on timestamps that is compatible
228228+ with timeline order. *)
229229+230230+val is_earlier : t -> than:t -> bool
231231+(** [is_earlier t ~than] is [true] iff [compare t than = -1]. *)
232232+233233+val is_later : t -> than:t -> bool
234234+(** [is_later t than] is [true] iff [compare t than = 1]. *)
235235+236236+(** {1:posix_arithmetic POSIX arithmetic}
237237+238238+ {b WARNING.} A POSIX time span is not equal to an SI second based
239239+ time span, see the {{!basics}basics}. Do not use these functions
240240+ to perform calendar arithmetic or measure wall-clock durations,
241241+ you will fail. *)
242242+243243+val add_span : t -> span -> t option
244244+(** [add_span t d] is timestamp [t + d], that is [t] with the signed
245245+ POSIX span [d] added. [None] is returned if the result is not
246246+ in the range \[{!min};{!max}\]. *)
247247+248248+val sub_span : t -> span -> t option
249249+(** [sub_span t d] is the timestamp [t - d], that is [t] with the
250250+ signed POSIX span [d] subtracted. [None] is returned if the result
251251+ is not in the range \[{!min};{!max}\]. *)
252252+253253+val diff : t -> t -> span
254254+(** [diff t t'] is the signed POSIX span [t - t'] that happens between
255255+ the timestamps [t] and [t']. *)
256256+257257+(** {1:tz_offset Time zone offsets between local and UTC timelines} *)
258258+259259+type tz_offset_s = int
260260+(** The type for time zone offsets between local and UTC timelines
261261+ in seconds. This is the signed difference in seconds between the local
262262+ timeline and the UTC timeline:
263263+{[
264264+ tz_offset_s = local - UTC
265265+]}
266266+ {ul
267267+ {- A value of [-3600] means that the local timeline is sixty minutes
268268+ {e behind} the UTC timeline.}
269269+ {- A value of [3600] means that the local timeline is sixty
270270+ minutes {e ahead} the UTC timeline.}} *)
271271+272272+(** {1:date_time Date-time value conversions}
273273+274274+ A {e date-time} represents a point on the UTC timeline by pairing
275275+ a date in the proleptic Gregorian calendar and a second precision
276276+ daytime in a local timeline with stated relationship to the UTC
277277+ timeline. *)
278278+279279+type date = int * int * int
280280+(** The type for big-endian proleptic Gregorian dates. A triple
281281+ [(y, m, d)] with:
282282+ {ul
283283+ {- [y] the year from [0] to [9999]. [0] denotes -1 BCE
284284+ (this follows the
285285+ {{:http://www.iso.org/iso/home/standards/iso8601.htm}ISO 8601}
286286+ convention).}
287287+ {- [m] is the month from [1] to [12]}
288288+ {- [d] is the day from [1] to [28], [29], [30] or [31]
289289+ depending on [m] and [y]}}
290290+291291+ A date is said to be {e valid} iff the values [(y, m, d)] are
292292+ in the range mentioned above and represent an existing date in the
293293+ proleptic Gregorian calendar. *)
294294+295295+type time = (int * int * int) * tz_offset_s
296296+(** The type for daytimes on a local timeline. Pairs a triple [(hh,
297297+ mm, ss)] denoting the time on the local timeline and a [tz_offset]
298298+ stating the {{!tz_offset_s}relationship} of the local timeline to
299299+ the UTC timeline.
300300+301301+ The [(hh, mm, ss)] components are understood and constrainted as
302302+ follows:
303303+ {ul
304304+ {- [hh] is the hour from [0] to [23].}
305305+ {- [mm] is the minute from [0] to [59].}
306306+ {- [ss] is the seconds from [0] to [60]. [60] may happen whenever
307307+ a leap second is added.}}
308308+ A [time] value is said to be {e valid} iff the values [(hh, mm, ss)]
309309+ are in the ranges mentioned above. *)
310310+311311+(** {2:datetimes Date and time} *)
312312+313313+val of_date_time : date * time -> t option
314314+(** [of_date_time dt] is the POSIX timestamp corresponding to
315315+ date-time [dt] or [None] if [dt] has an {{!date}invalid date},
316316+ {{!time}invalid time} or the date-time is not in the range
317317+ \[{!min};{!max}\].
318318+319319+ {b Leap seconds.} Any date-time with a seconds value of [60], hence
320320+ representing a leap second addition, is mapped to the date-time
321321+ that happens 1 second later. Any date-time with a seconds value of
322322+ [59] is mapped to the POSIX timestamp that represents this
323323+ instant, if a leap second was subtracted at that point, this is
324324+ the POSIX timestamp that represents this inexisting instant. See
325325+ the {{!basics}basics}. *)
326326+327327+val to_date_time : ?tz_offset_s:tz_offset_s -> t -> date * time
328328+(** [to_date_time ~tz_offset_s t] is the date-time of the timestamp [t].
329329+330330+ [tz_offset_s] hints the time zone offset used for the resulting
331331+ daytime component (defaults to [0], i.e. UTC). The offset is not
332332+ honoured and fallbacks to [0] in case the resulting date-time
333333+ rendering of the timestamp would yield an {{!date}invalid
334334+ date}. This means that you should always interpret the resulting
335335+ time component with the time zone offset it is paired with in the
336336+ result and not assume it will be the one you gave to the
337337+ function. Note that for real-world time zone offsets the fallback
338338+ to [0] will only happen around {!Ptime.min} and {!Ptime.max}.
339339+ Formally the fallback occurs whenever [add_span t (Span.of_int_s
340340+ tz_offset_s)] is [None].
341341+342342+ {b Leap seconds.} No POSIX timestamp can represent a date-time
343343+ with a leap second added, hence this function will never return a
344344+ date-time with a [60] seconds value. This function does return
345345+ inexisting UTC date-times with [59] seconds whenever a leap second is
346346+ subtracted since POSIX timestamps do represent them. See the
347347+ {{!basics}basics}.
348348+349349+ {b Subsecond precision.} POSIX timestamps with subsecond precision
350350+ are floored, i.e. the date-time always has the second mentioned in
351351+ the timestamp. *)
352352+353353+(** {2:dates Date} *)
354354+355355+val of_date : ?tz_offset_s:tz_offset_s -> date -> t option
356356+(** [of_date d] is
357357+ [of_date_time (d, ((00, 00, 00), tz_offset_s))]. [tz_offset_s]
358358+ defaults to 0, i.e. UTC. *)
359359+360360+val to_date : ?tz_offset_s:tz_offset_s -> t -> date
361361+(** [to_date t] is [fst (to_date_time ?tz_offset_s t)]. *)
362362+363363+(** {2:years Year} *)
364364+365365+val of_year : ?tz_offset_s:tz_offset_s -> int -> t option
366366+(** [of_year y] is [of_date ?tz_offset_s (y, 01, 01)]. *)
367367+368368+val to_year : ?tz_offset_s:tz_offset_s -> t -> int
369369+(** [to_year t] is the first component of [(to_date ?tz_offset_s t))] but
370370+ more efficient. *)
371371+372372+(** {2:weekdays Week days} *)
373373+374374+type weekday = [ `Sun | `Mon | `Tue | `Wed | `Thu | `Fri | `Sat ]
375375+(** The type for the days of the 7-day week. *)
376376+377377+val weekday : ?tz_offset_s:tz_offset_s -> t -> weekday
378378+(** [weekday ~tz_offset_s t] is the day in the 7-day week of timestamp [t]
379379+ expressed in the time zone offset [ts_offset_s] (defaults to [0]). *)
380380+381381+val weekday_num : ?tz_offset_s:tz_offset_s -> t -> int
382382+(** [weekday_num] is like {!weekday} but returns a weekday number, 0
383383+ is sunday, 1 is monday, …, 6 is saturday etc. *)
384384+385385+(** {1:rfc3339 RFC 3339 timestamp conversions} *)
386386+387387+type error_range = int * int
388388+(** The type for error ranges, starting and ending position. *)
389389+390390+type rfc3339_error =
391391+ [ `Invalid_stamp
392392+ | `Eoi
393393+ | `Exp_chars of char list
394394+ | `Trailing_input ]
395395+(** The type for RFC 3339 timestamp parsing errors. [`Invalid_stamp]
396396+ means that either the time stamp is not in the range
397397+ \[{!min};{!max}\], or the date is invalid, or one of the fields is
398398+ not in the right range. *)
399399+400400+val pp_rfc3339_error : Format.formatter -> rfc3339_error -> unit
401401+(** [pp_rfc3339_error ppf e] prints an unspecified representation of
402402+ [e] on [ppf]. *)
403403+404404+val rfc3339_error_to_msg : ('a, [`RFC3339 of error_range * rfc3339_error])
405405+ result -> ('a, [> `Msg of string]) result
406406+(** [rfc3339_error_to_msg r] converts RFC 3339 parse errors to error
407407+ messages. *)
408408+409409+val rfc3339_string_error :
410410+ ('a, [`RFC3339 of error_range * rfc3339_error]) result -> ('a, string) result
411411+(** [rfc3339_string_error r] converts RFC 3339 parse errors errors to
412412+ string errors. *)
413413+414414+val of_rfc3339 : ?strict:bool -> ?sub:bool -> ?start:int -> string ->
415415+ ((t * tz_offset_s option * int),
416416+ [> `RFC3339 of error_range * rfc3339_error]) result
417417+(** [of_rfc3339 ~strict ~sub ~start s] parses an RFC 3339
418418+ {{:https://tools.ietf.org/html/rfc3339#section-5.6}[date-time]}
419419+ starting at [start] (defaults to [0]) in [s] to a triple [(t, tz, count)]
420420+ with:
421421+ {ul
422422+ {- [t] the POSIX timestamp (hence on the UTC timeline).}
423423+ {- [tz], the optional {{!tz_offset_s}time zone offset} found in the
424424+ timestamp. [None] is returned iff the date-time satisfies the
425425+ {{:https://tools.ietf.org/html/rfc3339#section-4.3}unknown local
426426+ offset convention}.}
427427+ {- [count] the number of bytes read starting at [start] to parse the
428428+ timestamp. If [sub] is [false] (default) this is always
429429+ [String.length s - start] and [Error `Trailing_input] is returned
430430+ if there are still bytes in [s] after the date-time was parsed. Use
431431+ [~sub:true] for allowing trailing input to exist.}
432432+ {- [strict] if [false] (default) the pasring function does
433433+ not error on timestamp with lowercase ['T'] or ['Z'] characters, or
434434+ space separated date and times, and `hhmm` and `hh` timezone
435435+ offsets (strict mandates [hh:mm]). This allows to parse a slightly
436436+ larger subset of ISO 8601 than what RFC 3339 allows}}
437437+438438+ {b Notes and limitations.}
439439+ {ul
440440+ {- If [start] is not an index of [s], [Error ((start, start), `Eoi)] is
441441+ returned.}
442442+ {- RFC 3339 allows a few degenerate (I say) timestamps with
443443+ non-zero time zone offsets to be parsed at the boundaries that
444444+ correspond to timestamps that cannot be expressed in UTC in RFC
445445+ 3339 itself (e.g. [0000-01-01T00:00:00+00:01]). The function
446446+ errors on these timestamps with [`Invalid_stamp] as they cannot
447447+ be represented in the range \[{!min};{!max}\].}
448448+ {- Leap seconds are allowed on any date-time and handled as in
449449+ {!of_date_time}}
450450+ {- Fractional parts beyond the picosecond ([1e-12]) are truncated.}} *)
451451+452452+val to_rfc3339 : ?space:bool -> ?frac_s:int -> ?tz_offset_s:tz_offset_s ->
453453+ t -> string
454454+(** [to_rfc3339_tz ~space ~frac_s ~tz_offset_s t] formats the timestamp
455455+ [t] according to a RFC 3339
456456+ {{:https://tools.ietf.org/html/rfc3339#section-5.6}[date-time]}
457457+ production with:
458458+ {ul
459459+ {- [tz_offset_s] hints the time zone offset to use, use [0] for UTC.
460460+ The hint is ignored in the following cases: if [tz_offset_s] is not an
461461+ integral number of minutes and its magnitude not in the range permitted
462462+ by the standard, if [add_span t (Span.of_int_s tz_offset_s)] is [None]
463463+ (the resulting timestamp rendering would not be RFC 3339 compliant).
464464+ If either the hint is ignored or [tz_offset_s] is unspecified then
465465+ the
466466+ {{:https://tools.ietf.org/html/rfc3339#section-4.3}unknown local offset
467467+ convention} is used to render the time zone component.}
468468+ {- [frac_s], clipped to the range \[[0];[12]\] specifies that exactly
469469+ [frac_s] decimal digits of the fractional second of [t] are
470470+ rendered (defaults to [0]).}
471471+ {- [space] if [true] the date and time separator is a space
472472+ rather than a ['T'] (not recommended but may be allowed by the
473473+ protocol you are dealing with, defaults to [false]).}} *)
474474+475475+val pp_rfc3339 : ?space:bool -> ?frac_s:int -> ?tz_offset_s:tz_offset_s ->
476476+ unit -> Format.formatter -> t -> unit
477477+(** [pp_rfc3339 ?space ?frac_s ?tz_offset_s () ppf t] is
478478+ [Format.fprintf ppf "%s" (to_rfc3339 ?space ?frac_s ?tz_offset_s t)]. *)
479479+480480+(** {1:print Pretty printing} *)
481481+482482+val pp_human : ?frac_s:int -> ?tz_offset_s:tz_offset_s -> unit ->
483483+ Format.formatter -> t -> unit
484484+(** [pp_human ~frac_s ~tz_offset_s () ppf t] prints an unspecified, human
485485+ readable, locale-independent, representation of [t] with:
486486+ {ul
487487+ {- [tz_offset_s] hints the time zone offset to use. The hint is ignored
488488+ in the following cases: if [tz_offset_s] is not an integral number of
489489+ minutes and its magnitude not in the range permitted by the standard,
490490+ if [add_span t (Span.of_int_s tz_offset_s)] is [None].
491491+ If either the hint is ignored or [tz_offset_s] is unspecified then
492492+ RFC 3339's
493493+ {{:https://tools.ietf.org/html/rfc3339#section-4.3}unknown local offset
494494+ convention} is used to render the time zone component.}
495495+ {- [frac_s] clipped to the range \[[0];[12]\] specifies that exactly
496496+ [frac_s] decimal digits of the fractional second of [t] are
497497+ rendered (defaults to [0]).}}
498498+499499+ {b Note.} The output of this function is similar to but {b not}
500500+ compliant with RFC 3339, it should only be used for presentation,
501501+ not as a serialization format. *)
502502+503503+val pp : Format.formatter -> t -> unit
504504+(** [pp] is [pp_human ~tz_offset_s:0]. *)
505505+506506+val dump : Format.formatter -> t -> unit
507507+(** [dump ppf t] prints an unspecified raw representation of [t]
508508+ on [ppf]. *)
509509+510510+(** {1:basics Basics}
511511+512512+ POSIX time counts POSIX seconds since the epoch 1970-01-01
513513+ 00:00:00 UTC. As such a POSIX timestamp is {b always} on the UTC
514514+ timeline.
515515+516516+ POSIX time doesn't count leap seconds, so by definition it cannot
517517+ represent them. One way of viewing this is that whenever a leap
518518+ second is added a POSIX second lasts two SI seconds and whenever a
519519+ leap second is subtracted a POSIX second lasts zero SI second.
520520+521521+ {!Ptime} does not provide any mean to convert the duration between
522522+ two POSIX timestamps to SI seconds. The reason is that in order to
523523+ accurately find this number, a
524524+ {{:http://www.ietf.org/timezones/data/leap-seconds.list}leap
525525+ second table} is needed. However since this table may change every
526526+ six months, {!Ptime} decides not to include it so as not to
527527+ potentially become incorrect every six months.
528528+529529+ This decision has the following implications. First it should be
530530+ realised that the durations mentioned by the {!add_span},
531531+ {!sub_span} and {!diff} functions are expressed in {e
532532+ POSIX seconds} which may represent zero, one, or two SI
533533+ seconds. For example if we add 1 second with
534534+ {!add_span} to the POSIX timestamp for 1998-12-31 23:59:59 UTC,
535535+ what we get is the timestamp for 1999-01-01 00:00:00 UTC:
536536+{[
537537+let get = function None -> assert false | Some v -> v
538538+let utc d t = get @@ Ptime.of_date_time (d, (t, 0))
539539+let t0 = utc (1998, 12, 31) (23, 59, 59)
540540+let t1 = utc (1999, 01, 01) (00, 00, 00)
541541+let one_s = Ptime.Span.of_int_s 1
542542+let () = assert (Ptime.equal (get @@ Ptime.add_span t0 one_s) t1)
543543+]}
544544+ However since the leap second 1998-12-31 23:59:60 UTC exists,
545545+ {e two} actual SI seconds elapsed between [t0] and [t1]. Now if we use
546546+ {!diff} to find the POSIX duration that elapsed between
547547+ [t0] and [t1] we get one POSIX second:
548548+{[
549549+let () = assert (Ptime.Span.equal (Ptime.diff t1 t0) one_s)
550550+]}
551551+ But still, two SI seconds elapsed between these two points in
552552+ time. Note also that no value of type {!t} can represent the UTC
553553+ timetamp 1998-12-31 23:59:60 and hence {!Ptime.to_date_time}
554554+ will never return a date-time with a seconds value of [60]. In
555555+ fact both 1998-12-31 23:59:60 UTC and 1999-01-01 00:00:00 UTC are
556556+ represented by the same timestamp:
557557+{[
558558+let t2 = utc (1998, 12, 31) (23, 59, 60)
559559+let () = assert (Ptime.equal t1 t2)
560560+]}
561561+ This is true of any added leap second, we map it on the first second
562562+ of the next minute, thus matching the behaviour
563563+ of POSIX's
564564+ {{:http://pubs.opengroup.org/onlinepubs/9699919799/functions/mktime.html}
565565+ mktime} function.
566566+567567+ If a leap second is subtracted on a day the following occurs –
568568+ 2015, as of writing this never happened. Let YYYY-06-30 23:59:58
569569+ be the instant a leap second is subtracted, this means that the
570570+ next UTC date-time, one SI second later, is YYYY-07-01
571571+ 00:00:00. However if we diff the two instants:
572572+{[
573573+let y = 9999 (* hypothetical year were this happens *)
574574+let t0 = utc (y, 06, 30) (23, 59, 58)
575575+let t1 = utc (y, 07, 01) (00, 00, 00)
576576+let two_s = Ptime.Span.of_int_s 2
577577+let () = assert (Ptime.Span.equal (Ptime.diff t1 t0) two_s)
578578+]}
579579+ We get two POSIX seconds, but only one SI second
580580+ elapsed between these two points in time. It should also
581581+ be noted that POSIX time will represent a point that never
582582+ existed in time namely YYYY-06-30 23:59:59, the POSIX second
583583+ with 0 SI second duration and that {!Ptime.to_date_time}
584584+ will return a date-time value for this timestamp even though
585585+ it never existed:
586586+{[
587587+let t2 = utc (y, 06, 30) (23, 59, 59)
588588+let () = assert (Ptime.equal (get @@ Ptime.add_span t0 one_s) t2)
589589+]}
590590+591591+ {1:notes Notes and limitations}
592592+593593+ The following points should be taken into account
594594+ {ul
595595+ {- {!Ptime} is not a calendar library and will never be.}
596596+ {- {!Ptime} can only represent picosecond precision timestamps in
597597+ the range \[{!Ptime.min};{!Ptime.max}\]. It is however able to
598598+ convert {e any} of these timestamps to a valid date-time or RFC
599599+ 3339 timestamp.}
600600+ {- POSIX time in general is ill-suited to measure wall-clock
601601+ time spans for the following reasons.
602602+ {ul
603603+ {- POSIX time counts time in POSIX seconds. POSIX
604604+ seconds can represent 2, 1 or 0 SI seconds. [Ptime]
605605+ offers no mechanism to determine the SI duration between
606606+ two timestamps, see the {{!basics}basics}.}
607607+ {- The POSIX timestamps returned by your platform are not
608608+ monotonic: they are subject to operating system time
609609+ adjustements and can even go back in time. If you need to
610610+ measure time spans in a single program run use a monotonic
611611+ time source (e.g. {!Mtime}).}}}}
612612+*)
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2015 The ptime programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(* Ptime test unit comonalities *)
77+88+open B0_std
99+open B0_testing
1010+1111+module T = struct
1212+1313+ (* Time spans *)
1414+1515+ let eq_raw_span =
1616+ let raw_span ppf (d, ps) = Fmt.pf ppf "@[<1>(%d,@ %Ld)@]" d ps in
1717+ Test.T.make ~pp:raw_span ()
1818+1919+ let raw_span ?__POS__ = Test.eq ?__POS__ eq_raw_span
2020+2121+ let eq_span = Test.T.make ~equal:Ptime.Span.equal ~pp:Ptime.Span.dump ()
2222+ let span ?__POS__ = Test.eq ?__POS__ eq_span
2323+ let span_option ?__POS__ = Test.option ?__POS__ eq_span
2424+2525+ (* Timestamps *)
2626+2727+ let eq_stamp = Test.T.make ~equal:Ptime.equal ~pp:Ptime.dump ()
2828+ let stamp ?__POS__ = Test.eq ?__POS__ eq_stamp
2929+ let stamp_option ?__POS__ = Test.option ?__POS__ eq_stamp
3030+3131+ (* Dates *)
3232+3333+ module Date = struct
3434+ type t = Ptime.date
3535+ let equal = ( = )
3636+ let pp ppf (y,m,d) = Fmt.pf ppf "(%d, %d, %d)" y m d
3737+ end
3838+3939+ let date ?__POS__ = Test.eq ?__POS__ (module Date)
4040+4141+ (* Date time *)
4242+4343+ module Date_time = struct
4444+ type t = Ptime.date * Ptime.time
4545+ let equal = ( = )
4646+ let pp ppf ((y, m, d), ((hh, mm, ss), tz)) =
4747+ Fmt.pf ppf "(%d, %d, %d), ((%d, %d, %d), %d)" y m d hh mm ss tz
4848+ end
4949+5050+ let date_time ?__POS__ = Test.eq ?__POS__ (module Date_time)
5151+5252+ let gmtime_to_date_time t =
5353+ let t = Ptime.to_float_s t in
5454+ let t = floor t (* see https://github.com/ocaml/ocaml/issues/6921 *) in
5555+ let tm = Unix.gmtime t in
5656+ let d = (tm.Unix.tm_year + 1900), (tm.Unix.tm_mon + 1), (tm.Unix.tm_mday) in
5757+ let t = tm.Unix.tm_hour, tm.Unix.tm_min, tm.Unix.tm_sec in
5858+ (d, (t, 0)), tm.Unix.tm_wday
5959+6060+ let date_time_gmtime_witness ?__POS__:pos t =
6161+ let fail ?__POS__ n ~assertions:_ =
6262+ Test.log_fail ?__POS__ "On stamp %g" (Ptime.to_float_s t)
6363+ in
6464+ Test.block ?__POS__:pos ~fail @@ fun () ->
6565+ let dt, wday = gmtime_to_date_time t in
6666+ let ut = Ptime.to_date_time t in
6767+ Test.eq ~__POS__ (module Date_time) dt ut;
6868+ Test.int ~__POS__ (Ptime.weekday_num t) wday
6969+end
7070+7171+module Rand = struct
7272+7373+ (* Random loop length *)
7474+7575+ let loop_len = ref 100_000
7676+ let loop_len () = !loop_len
7777+7878+ (* Random Ptime-valid stamps from floats *)
7979+8080+ let float_stamp_range min max =
8181+ let bound = max -. min in
8282+ fun () ->
8383+ let r = Random.State.float (Test.Rand.state ()) bound (* inclusive *) in
8484+ let stamp = min +. r in
8585+ match Ptime.(of_float_s stamp) with
8686+ | None -> Fmt.failwith "cannot convert valid random stamp %f" stamp
8787+ | Some t -> t
8888+8989+ let float_stamp_32bits =
9090+ let min_stamp = Int32.(to_float min_int) in
9191+ let max_stamp = Int32.(to_float max_int) in
9292+ float_stamp_range min_stamp max_stamp
9393+9494+ let float_stamp : unit -> Ptime.t =
9595+ let min_stamp = Ptime.(to_float_s min) in
9696+ let max_stamp = Ptime.(to_float_s max) in
9797+ float_stamp_range min_stamp max_stamp
9898+9999+ let stamp =
100100+ if Sys.word_size = 32 then float_stamp_32bits else float_stamp
101101+102102+ (* Random Ptime-valid dates *)
103103+104104+ let date : unit -> (int * int * int) =
105105+ let month_len = [|31; 28; 31; 30; 31; 30; 31; 31; 30; 31; 30; 31 |] in
106106+ let is_leap y = (y mod 4 = 0) && (y mod 100 <> 0 || y mod 400 = 0) in
107107+ fun () ->
108108+ let rstate = Test.Rand.state () in
109109+ let rint bound = Random.State.int rstate bound in
110110+ let y = rint 10_000 in
111111+ let m = 1 + rint 11 in
112112+ let m_len = if (m = 2 && is_leap y) then 29 else month_len.(m - 1) in
113113+ let d = 1 + rint m_len in
114114+ (y, m, d)
115115+116116+ (* Random times *)
117117+118118+ let tz_interval_s = (1 lsl 30 - 1) (* max of Random.int *)
119119+ let tz_offset_s : unit -> int =
120120+ fun () ->
121121+ let rstate = Test.Rand.state () in
122122+ (* N.B. We don't cover the whole spectrum *)
123123+ (Random.State.int rstate tz_interval_s) - (tz_interval_s / 2)
124124+125125+ let min_tz_interval_s = 2000
126126+ let min_tz_offset_s : unit -> int =
127127+ fun () ->
128128+ let rstate = Test.Rand.state () in
129129+ ((Random.State.int rstate min_tz_interval_s) - (min_tz_interval_s / 2)) * 60
130130+131131+ let time : unit -> (int * int * int) =
132132+ fun () ->
133133+ let rstate = Test.Rand.state () in
134134+ let rint bound = Random.State.int rstate bound in
135135+ let hh = rint 24 in
136136+ let mm = rint 60 in
137137+ let ss = rint 61 in
138138+ (hh, mm, ss)
139139+end