daypack-dev / timere

OCaml date time handling and reasoning suite
MIT License
68 stars 7 forks source link

Confused by the Timere documentation #25

Closed gasche closed 3 years ago

gasche commented 3 years ago

I'm reading the documentation at

https://daypack-dev.github.io/timere/timere/Timere/index.html

(the current Timere head commit is 7bf138)

I am trying to represent a time interval in a calendar (for example: "May 10th 2021, 10:00 to 11:30 in the Europe/Paris timezone")

Browsing the Timere documentation, it is not clear to me how this should be represented.

gasche commented 3 years ago

More feedback:

darrenldl commented 3 years ago

Thanks very much for the detailed feedback! I've been looking for a detailed review from potential user, so this is very nice.

I am not sure what a Timere.t is. The documentation says it is "used to encode computation over time", what does that mean?

I've updated the doc to read as follows

" This is the core type of Timere used to encode computation over time, also called a "Timere object".

A Timere object is a lazily evaluated representation of computation built using constructors, where resolve forces the result of the computation (lazy sequence of intervals). "

Hope this is better(?)

Citing the documentation: "inter [] is equivalent to empty". Shouldn't this be always instead, the neutral element of inter?

I think you're right (don't recall what the issue was that caused me to define it as empty). I've added the change and see if changes anything in the the testing pipeline.

Plenty of functions are defined in terms of pattern before we have seen pattern in the list. Weird.

Moved them to after pattern.

After browsing plenty of stuff I don't care about (pattern, weekdays, whatever), I find modules for Span, Duration, Timezone, "Datetime and timestamps". Looks more like what I'm looking for.

I've moved the "calendar" stuff to the top, and reasoning/scheduling to the bottom.

After looking at the sub-modules, Span/Duration represents a vector in time, and are equivalent (Duration is more user-friendly). They could be grouped together in the toplevel documentation.

Currently moved them to "Time vectors" section at top.

I'm probably looking for a pair of a point in time (Datetime.t ?) and a Duration.t.

That is correct, though generally advised to go with a pair of Date_time.t or even better, timestamp.

let tz = Timere.Time_zone.make_exn "Europe/Paris" in
let x = Timere.Date_time.(
  make_exn ~year:2021 ~month:05 ~day:10 ~hour:10 ~minute:0 ~second:0 ()
  |> to_timestamp_single) in
let x = Timere.Date_time.(
  make_exn ~year:2021 ~month:05 ~day:11 ~hour:11 ~minute:30 ~second:0 ()
  |> to_timestamp_single)  in
(x, y)

Reason being distance between two time of day may not be constant due to DST depending on choice of hour etc.

There are better methods of constructions for this type of scheduling - I'll add them in the next comment.

It is not obvious to me what a "Date_time" is; a word of explanation in https://daypack-dev.github.io/timere/timere/Timere/Date_time/index.html would be helpful.

I guess that Date_time represents a point in time specified by hours/minutes/seconds/etc. within a given day/date.

I've amended the description of Date_time.t to read as follows

" This is the main date time type, and represents a point in the local time line with respect to the residing time zone. Conceptually a pair of "date" and "time of day".

A t always maps to at least one point on the UTC time line, and make fails if this is not the case. t may also map to two points on the UTC time line in the case of DST and without an unambiguous offset, however.

In the ambiguous case, functions which return _ localresult will yield an `Ambiguous value, and `Single _ otherwise.

ns may be >= 10^9 to represent leap second, but always remains < 2 * 10^9.

s is always >= 0 and < 60, even when second 60 is used during construction. In other words, second 60 is represented via ns field. "

The Timestamp documentation looks pretty dry. I don't know what rfc3339 or iso8601 are, but seeing example usages may help me understand which one I should use for the particular system I am interoperating with (Caldav).

I've added a small example, but I'm somewhat lost on what to add short of elaborating on the two individual standards.

For use in Caldav, I think you'll need to roll your own format string, e.g. format = {year}{mon:0X}{day:0X} for Caldav DATE, or = "{year}{mon:0X}{day:0X}T{hour:0X}{min:0X}{sec:0X}" for DATE-TIME, in calls to Date_time.pp ~format () or Date_time.to_string ~format.

tz_info is defined as a pair of Timezone and a Duration.t option. What is the meaning of the Duration.t option? No idea, no documentation.

I've repackaged it into a proper record type

type tz_info = {
  tz : Time_zone.t;
  offset : Duration.t option;
}

and description now reads as follows

" Time zone information of date time

tz is the time zone tied.

offset, if specified, provides an unambiguous specification of which timestamp/point the date time maps to on the UTC time line "

Data_time.t looks like a representation of a point in time. (But then it maybe has some duration information?)

Sorry I don't follow. Why would it have duration information?

"Note that this may yield a ambiguous date time if the time zone has varying offsets, e.g. DST." I don't understand what this means.

I've updated the text to read

" Note that this may yield a ambiguous date time if the time zone has varying offsets, causing a local date time to appear twice, e.g. countries with DST. "

I wanted to use Duration but I need to use addition of time vectors, so I have to go through Span as it is the only module giving algebraic operations on span/durations. I wonder if splitting in two modules is the right approach, or if we should rather have a single module with two "views" of the same type, for creation and also observation of the span/duration values.

Good point - I did feel point of friction at places. Could you elaborate what the approach with "views" would look like?

I was annoyed at the lack of way to convert 03 into `Mar automatically. Later I found by chance that this function is available in the Utils value, but maybe the documentation of the month type should mention that Utils has helpers for them.

I've removed type month altogether - ~month is now just tied to numbers.

It seems that the type month was only for avoiding ambiguity when this library was using Unix.tm many moons ago. But now that is no longer the case, this type is kinda moot.

darrenldl commented 3 years ago

For the scheduling problem, the following should work, but doesn't when time zone is specified (still investigating why...)

let t = CCResult.get_exn @@ Timere_parse.timere "May 10th 2021 10:00 to 11am Europe/Paris" in
Fmt.pr "%a" Timere.pp_sexp t

yields

(with_tz Europe/Paris
 (bounded_intervals whole (31622400 0)
  (points (pick ymdhms 2021 May 10 10 0 0)) (points (pick hms 11 0 0))))

but resolve yields an empty sequence. Though this works fine with UTC time zone it seems (more debugging...).

let t = CCResult.get_exn @@ Timere_parse.timere "May 10th 2021 10:00 to 11am" in
Fmt.pr "%a\n" (Timere.pp_intervals ()) (CCResult.get_exn @@ Timere.resolve t)

yields

[2021 May 10 10:00:00 +00:00:00, 2021 May 10 11:00:00 +00:00:00)

So in both cases, it seems that the correct TImere object/AST is constructed, but computation with time zone outside of UTC fails.

gasche commented 3 years ago

Thanks very much for the detailed feedback! I've been looking for a detailed review from potential user, so this is very nice.

You are welcome. Sorry for this being a fairly unorganized dump, that is correspongly not so convenient to discuss, but oh well.

I'm impressed with how reactive you have been in turning this ill-structured feedback into concrete changes.

I am not sure what a Timere.t is. The documentation says it is "used to encode computation over time", what does that mean?

I've updated the doc to read as follows

" This is the core type of Timere used to encode computation over time, also called a "Timere object".

A Timere object is a lazily evaluated representation of computation built using constructors, where resolve forces the result of the computation (lazy sequence of intervals). "

Hope this is better(?)

This is an improvement, thanks!. But I have the impression that you are putting implementation aspects (the fact that things are lazily evaluated) before defining what it represents (unions of intervals of points in time). I would maybe suggest the following:

" This is the core type of Timere that represents sets of points in time, more precisely, unions of time intervals. (For example: all Mondays of year 2000 at the UTC timezone.)

We call Timere.t values "timere object"; internally they are rich expressions representing the time computations (union, intersection, etc.), lazily forced into more low-level descriptions (lazy sequences of intervals). "

As a prospective user of Timere, I am less interested (than you and other implementors are) in the description of how it is implemented. Having a simple mental model in mind can still be useful to understand potential performance implicitations, and in particular "what can be done without blowing up completely".

I'm probably looking for a pair of a point in time (Datetime.t ?) and a Duration.t.

That is correct, though generally advised to go with a pair of Date_time.t or even better, timestamp.

let tz = Timere.Time_zone.make_exn "Europe/Paris" in
let x = Timere.Date_time.(
  make_exn ~year:2021 ~month:05 ~day:10 ~hour:10 ~minute:0 ~second:0 ()
  |> to_timestamp_single) in
let x = Timere.Date_time.(
  make_exn ~year:2021 ~month:05 ~day:11 ~hour:11 ~minute:30 ~second:0 ()
  |> to_timestamp_single)  in
(x, y)

Reason being distance between two time of day may not be constant due to DST depending on choice of hour etc.

Actually in my specific case I am implementing a calendar for a seminar (talks on a semi-regular basis); the talks start a given point in time, and should last for 50 minutes plus 20 minutes question and discussions. So storing the origin+vector makes perfect sense and storing the second endpoint requires a computation. But of course both representations are fine.

I've amended the description of Date_time.t to read as follows

" This is the main date time type, and represents a point in the local time line with respect to the residing time zone. Conceptually a pair of "date" and "time of day".

A t always maps to at least one point on the UTC time line, and make fails if this is not the case. t may also map to two points on the UTC time line in the case of DST and without an unambiguous offset, however.

In the ambiguous case, functions which return _ localresult will yield an `Ambiguous value, and `Single _ otherwise.

ns may be >= 10^9 to represent leap second, but always remains < 2 * 10^9.

s is always >= 0 and < 60, even when second 60 is used during construction. In other words, second 60 is represented via ns field. "

This is nice, thanks! As a non-expert in calendar matters, I am still a bit confused by this bit:

t may also map to two points on the UTC time line in the case of DST and without an unambiguous offset, however.

Actually I'm not sure what you are talking about. I would naively assume that if I give you a date, a time-of-day and a timezone, this is always unambiguous: even if the timezone is affected by Daylight Saving Time (its offset relative to UTC changes over the year), we can tell what the specific UTC offset is at this date. (Well okay, DST rules can evolve over time so this becomes wrong in the future, but let's say "within the current knowledge of timezone stuff".)

Or maybe you are talking about specifically what happens on the nights during which daylight-saving-time starts and stops, there is some ambiguity here about whether we are using pre-change or post-change dates?

I would like to figure out if this DST ambiguity business is something, as a user, I should understand well and be worried about because it's going to bite me if I work with dates in a country using DST, or if it's rather a pretty obscure corner case like leap seconds stuff that I can ignore for my simple application, because the dates I am working with are always during normal workdays and enver on midnight between a Saturday and a Sunday.

tz_info is defined as a pair of Timezone and a Duration.t option. What is the meaning of the Duration.t option? No idea, no documentation.

I've repackaged it into a proper record type

type tz_info = {
  tz : Time_zone.t;
  offset : Duration.t option;
}

and description now reads as follows

" Time zone information of date time

tz is the time zone tied.

offset, if specified, provides an unambiguous specification of which timestamp/point the date time maps to on the UTC time line "

It might be useful to point out in which direction the offset goes. If I'm working with ours in UTC+1, should I pass +3600 or -3600?

Data_time.t looks like a representation of a point in time. (But then it maybe has some duration information?)

Sorry I don't follow. Why would it have duration information?

I was confused by the Duration.t in the tz_info field, but now it's clear what this is and not an issue anymore. Thanks!

"Note that this may yield a ambiguous date time if the time zone has varying offsets, e.g. DST." I don't understand what this means.

I've updated the text to read

" Note that this may yield a ambiguous date time if the time zone has varying offsets, causing a local date time to appear twice, e.g. countries with DST. "

As mentioned above, having slightly more explanation for people who are not already calendar hackers could help. Looking at the documentation of Timere (and Ptime before that) is the way I get to learn about "how to deal with dates and timezones and stuff", I don't have pre-existing knowledge of the intricacies here.

I wanted to use Duration but I need to use addition of time vectors, so I have to go through Span as it is the only module giving algebraic operations on span/durations. I wonder if splitting in two modules is the right approach, or if we should rather have a single module with two "views" of the same type, for creation and also observation of the span/duration values.

Good point - I did feel point of friction at places. Could you elaborate what the approach with "views" would look like?

I'll try to come up with a proposal later.

I was annoyed at the lack of way to convert 03 into `Mar automatically. Later I found by chance that this function is available in the Utils value, but maybe the documentation of the month type should mention that Utils has helpers for them.

I've removed type month altogether - ~month is now just tied to numbers.

It seems that the type month was only for avoiding ambiguity when this library was using Unix.tm many moons ago. But now that is no longer the case, this type is kinda moot.

For the record, I decided to just update my codebase to use the polymorphic variant throughout (as I had no easy way to map numbers to the variant in a helper function; well I was too lazy to define it myself), and actually it's nice: I don't have to wonder whether I'm using 0-11 or 1-12 encoding at the use sites anymore.

You know better, I would be mildly in favor of keeping this type around, with conversion functions.

darrenldl commented 3 years ago

This is an improvement, thanks!. But I have the impression that you are putting implementation aspects (the fact that things are lazily evaluated) before defining what it represents (unions of intervals of points in time). I would maybe suggest the following:

...

As a prospective user of Timere, I am less interested (than you and other implementors are) in the description of how it is implemented. Having a simple mental model in mind can still be useful to understand potential performance implicitations, and in particular "what can be done without blowing up completely".

Done. Largely copying your text.

Actually in my specific case I am implementing a calendar for a seminar (talks on a semi-regular basis); the talks start a given point in time, and should last for 50 minutes plus 20 minutes question and discussions. So storing the origin+vector makes perfect sense and storing the second endpoint requires a computation. But of course both representations are fine.

Aha okay, then yep.

Actually I'm not sure what you are talking about. I would naively assume that if I give you a date, a time-of-day and a timezone, this is always unambiguous: even if the timezone is affected by Daylight Saving Time (its offset relative to UTC changes over the year), we can tell what the specific UTC offset is at this date. (Well okay, DST rules can evolve over time so this becomes wrong in the future, but let's say "within the current knowledge of timezone stuff".)

Or maybe you are talking about specifically what happens on the nights during which daylight-saving-time starts and stops, there is some ambiguity here about whether we are using pre-change or post-change dates?

I would like to figure out if this DST ambiguity business is something, as a user, I should understand well and be worried about because it's going to bite me if I work with dates in a country using DST, or if it's rather a pretty obscure corner case like leap seconds stuff that I can ignore for my simple application, because the dates I am working with are always during normal workdays and enver on midnight between a Saturday and a Sunday.

Allow me to elaborate on this in the next comment.

It might be useful to point out in which direction the offset goes. If I'm working with ours in UTC+1, should I pass +3600 or -3600?

Good point. I've amended the documentation of Date_time.make_unambiguous to read as follows

" Constructs a date time providing time zone offset in seconds, and optionally a time zone.

Nanosecond used is the addition of [ns] and [frac * 10^9].

If a time zone is provided, then [tz_offset] is checked against the time zone record, and returns [Error `Invalid_tz_info] if [tz_offset] is not a possible offset for the particular date time in said time zone.

As an example, for "UTC+1", you would give a duration of positive 1 hour for tz_offset.

Otherwise same leap second handling and error handling as [make]. "

As mentioned above, having slightly more explanation for people who are not already calendar hackers could help. Looking at the documentation of Timere (and Ptime before that) is the way I get to learn about "how to deal with dates and timezones and stuff", I don't have pre-existing knowledge of the intricacies here.

I'll note that Ptime's way of dealing with time zone is to ask user to provide a fixed time zone offset - this is not an accurate representation of what we normally mean by a "time zone", even with "Europe/Paris", sometimes the offset is UTC+2, sometimes it's UTC+1. Subsequently there is less confusion, at the cost of much difficulty moving between time zones or even between timestamps. Timere/Daypack-lib was originally using that, but it was then immensely difficult to state a scheduling constraint then from a user perspective, hence the current first-class(?) support of time zone.

EDIT: Sorry I misread your original paragraph. I completely agree with what you said, though I'll have to look around as to what authors of this type of libraries usually do in terms of explanation...

For the record, I decided to just update my codebase to use the polymorphic variant throughout (as I had no easy way to map numbers to the variant in a helper function; well I was too lazy to define it myself), and actually it's nice: I don't have to wonder whether I'm using 0-11 or 1-12 encoding at the use sites anymore.

You know better, I would be mildly in favor of keeping this type around, with conversion functions.

Oh oop, hm...should I add it back? Or just offer the previous bundle of variant type month utilities in Utils instead?

darrenldl commented 3 years ago

Actually I'm not sure what you are talking about. I would naively assume that if I give you a date, a time-of-day and a timezone, this is always unambiguous: even if the timezone is affected by Daylight Saving Time (its offset relative to UTC changes over the year), we can tell what the specific UTC offset is at this date. (Well okay, DST rules can evolve over time so this becomes wrong in the future, but let's say "within the current knowledge of timezone stuff".)

This is unfortunately incorrect. Take Europe/Paris as our example: DST ends at 3am 13th Oct 2021, at which point clocks switch back 1 hour, to 2am 13th Oct 2021. As a consequence the entirety of "2am to 3am (exclusive)" will appear twice.

Pick any intermediate point, say "2:30am 13th Oct 2021" and with only "Europe/Paris" as the sole time zone information, you necessarily have two possible interpretations. If you attach a fixed offset of either +1 or +2, then sure, you can have an unambiguous interpretation, but there is no general good rule on which interpretation is suitable for the user, hence we do not make up an answer here.

Some libraries (Crystal lang's stdlib time module, Pendulum from Python, Golang's stdlib) do implicit coercion when a timestamp interpretation is demanded (which interpretation is chosen is often undefined behaviour, however). They also do similar coercion with date times that cannot exist. DST started in Europe/Paris 28th March 2021 2am, at which point clocks jump forward to 3am, i.e. the entirety of "2am to 3am (exclusive) does not exist in the local time line. Timere (which borrows a fair bit of decisions from Rust's chrono package) refuses construction of such date time. Lbraries mentioned will coerce a non-existent "2:30am" into "3:30am" etc.

IMO this is highly problematic in terms of accuracy, and while might be tolerable for most date time handling, absolutely not fit for scheduling purposes.

If user wishes to mimic the coercion behaviour, they may use either min_of_local_result ormin_of_local_result (this incidentally reminded me I need to fix their signature...).

As an attempt at viewing this mathematically, we can consider the following illustration of their mapping, namely we can observe the lack of continuity of Europe/Paris time line (UTC time line is always continuous) when DST ends in Oct.

UTC          ----------------|----------------
                            1am

Europe/Paris ----------------|----------------
                          3am 2am
                         (+2) (+1)

Similarly, we can observe a jump when DST started in March

UTC          ----------------|----------------
                            1am

Europe/Paris ----------------|----------------
                          2am 3am
                         (+1) (+2)

Or maybe you are talking about specifically what happens on the nights during which daylight-saving-time starts and stops, there is some ambiguity here about whether we are using pre-change or post-change dates?

I would like to figure out if this DST ambiguity business is something, as a user, I should understand well and be worried about because it's going to bite me if I work with dates in a country using DST, or if it's rather a pretty obscure corner case like leap seconds stuff that I can ignore for my simple application, because the dates I am working with are always during normal workdays and enver on midnight between a Saturday and a Sunday.

If you are coordinating anything that has an international audience, then it is likely to bite you. For local only things, say if you're using Ptime, and you forget to use the right offset when converting to timestamp, you have just scheduled an appointment off by an hour. Which may or may not be critical.

As a user, Timere allows you to gloss over a lot of details (i.e. you don't have to figure out how the scheduler works with multiple time zones in a single timere object).

So you should be aware of the possibility (which Timere will explicitly bring up), but you don't need to understand it well (Timere offers exactly the possible interpretations when relevant).

Incidentally above is why there are no arithmetic operations in Date_time - you must pick an exact interpretation (yielding a timestamp/Span.t before doing any arithmetic over it).

darrenldl commented 3 years ago

On an unrelated note, I've fixed the issue : D

utop # let t = CCResult.get_exn @@ Timere_parse.timere "May 10th 2021 10:00 to 11:30am Europe/Paris" in
Fmt.pr "%a\n" (Timere.pp_intervals ~display_using_tz:(Timere.Time_zone.make_exn "Europe/Paris") ()) (CCResult.get_exn @@ Timere.resolve t);;
[2021 May 10 10:00:00 +02:00:00, 2021 May 10 11:30:00 +02:00:00)
gasche commented 3 years ago

The reason why I'm trying to switch from Ptime to TImere is precisely that I don't want to deal with DST shifts in my code by hardcoding specific offsets. So for me it's important to be able to understand what ambiguity cases arise (where I would need a specific offset, and there won't be one, and my program will behave slightly weirdly) and in which case they don't. The previous documentation didn't let me understand that the issue was during an hour around each dayling-saving-time change, which is perfectly fine for my needs.

The explanation in https://github.com/daypack-dev/timere/issues/25#issuecomment-829188579 is excellent, thanks. If there is a document that already explains that, mentioning it in the documentation could be nice. Otherwise rephrasing it and including it in the doc, or just pointing to it from the doc, could help. But maybe having a more concise summary first would also help, I'll see if I can suggest something.

darrenldl commented 3 years ago

The reason why I'm trying to switch from Ptime to TImere is precisely that I don't want to deal with DST shifts in my code by hardcoding specific offsets. So for me it's important to be able to understand what ambiguity cases arise (where I would need a specific offset, and there won't be one, and my program will behave slightly weirdly) and in which case they don't. The previous documentation didn't let me understand that the issue was during an hour around each dayling-saving-time change, which is perfectly fine for my needs.

Out of curiosity, where did you hear about Timere?

The explanation in #25 (comment) is excellent, thanks. If there is a document that already explains that, mentioning it in the documentation could be nice. Otherwise rephrasing it and including it in the doc, or just pointing to it from the doc, could help. But maybe having a more concise summary first would also help, I'll see if I can suggest something.

Awesome! I'll add it to the documentation in some way, and yep do let me know if you come up with something.

Incidentally we have a scheduling example in case it's useful: https://github.com/daypack-dev/timere/tree/main/examples#querying-across-dst-boundary

gasche commented 3 years ago

Out of curiosity, where did you hear about Timere?

I learned about it (from an obscure compiler issue about compilation blowup and) from your 0.1.3 announce on Discuss.

Incidentally we have a scheduling example in case it's useful: https://github.com/daypack-dev/timere/tree/main/examples#querying-across-dst-boundary

Thanks! My use-case is to programmatically create single events, so not directly useful for me, but it is a nice example. Could it maybe also be included in the generated documentation?

let me know if you come up with something.

Maybe something like this, as the introduction of the module?

The type Date_time.t is the main date-time type, it represents a point in a local time line with respect to the residing time zone. Conceptually a pair of "date" and "time of day".

Note: This module uses Ptime (TODO link to Ptime) underneath, and carries similar limitation of expressible range of date times (basically, years from 0 to 9999 only). The actual range is slightly more limited in Timere due to time zones.

Ambiguous date-times: for timezones that use Daylight Saving Time (DST), a date-time may be ambiguous if it falls within one hour around the DST change. For example, in 2012 DST ended in most of Europe at 3am 13th Oct 2021, switching clocks back to 2am on the same day. As a consequence the interval from 2am to 3am (exclusive) appears twice in the time line.

Such a datatime can be disambiguated by providing an explicit time zone offset. For example, if a particular timezone is at UTC+1 or UTC+2 depending on whether DST is in effect, specifying a disambiguation offset of 1 hour in the {!tzinfo} data will use UTC+1 in the ambiguous cases.

This disambiguation offset is optional, to Timere can represent both ambiguous and unambiguous datetimes. Ambiguity will show up when converting to an absolute timestamp, as the `Ambiguous constructor in the {!local_result} type.

Note: it is still not completely clear to me if the offset field of tzinfo is only used in the case of ambiguous local date-times (failing if it not one of the two possible offsets giving the two possible absolute times), or if it used for all local date-times, with a failure for date-times where it is not the current offset (or, in ambiguous cases, one of the current offsets). I think it is the former (the latter sounds rigid and inconvenient), but the current doc is not clear about this.

darrenldl commented 3 years ago

Thanks! My use-case is to programmatically create single events, so not directly useful for me, but it is a nice example. Could it maybe also be included in the generated documentation?

Good point.

Note: it is still not completely clear to me if the offset field of tzinfo is only used in the case of ambiguous local date-times (failing if it not one of the two possible offsets giving the two possible absolute times), or if it used for all local date-times, with a failure for date-times where it is not the current offset (or, in ambiguous cases, one of the current offsets). I think it is the former (the latter sounds rigid and inconvenient), but the current doc is not clear about this.

Okay yep, I should make when would yield which combination of tz_info more explicit.

Right now

darrenldl commented 3 years ago

Re month type:

Since reintroducing it back into the original places means a lot of friction that was removed has to be reintroduced as well, I opted to moving the type and corresponding helper functions to Timere.Utils for now.

gasche commented 3 years ago

Since reintroducing it back into the original places means a lot of friction that was removed has to be reintroduced as well, I opted to moving the type and corresponding helper functions to Timere.Utils for now.

Yes, I think this is fine! (And also not having it is fine.) Thanks again for the prompt changes.

gasche commented 3 years ago

I believe that the issues here have been addressed, so closing. Thanks again for being very reactive.

darrenldl commented 3 years ago

Thanks very much again for the feedback! Timedesc 0.1.0 will be pushed to opam shortly.