elixir-cldr / cldr_calendars

Calendar functions for CLDR
Other
15 stars 6 forks source link

Negative duration and partial duration construction #8

Closed kipcole9 closed 3 years ago

kipcole9 commented 3 years ago

From conversion with @jarrodmoldrich

A couple of small feature request would be to allow out of order durations, and also to permit a duration from a single scalar duration, like a Time.from_seconds_after_midnight. For now I'm making a duration from the absolute value of time, and building the struct myself if I have a negative duration. It's not elegant but perfectly practical for now:

  defp duration(seconds, locale) do
    {:ok, duration} = Cldr.Calendar.Duration.new(~T[00:00:00], Time.from_seconds_after_midnight(abs(seconds)))
    duration = if seconds < 0 do
      # new for negative durations are not properly supported yet
      %Cldr.Calendar.Duration{
        day: 0, hour: -duration.hour, microsecond: 0, minute: 0, month: 0, second: -duration.second, year: 0
      }
    else
      duration
    end
    {:ok, duration} = Cldr.Calendar.Duration.to_string duration, style: :narrow, locale: locale
    duration
  end
kipcole9 commented 3 years ago

As of this commit, its possible to create negative time-based durations. For example:

iex> {:ok, duration} = Cldr.Calendar.Duration.new ~T[10:00:00.0], ~T[09:00:00.0]
{:ok,
 %Cldr.Calendar.Duration{
   day: 0,
   hour: -1,
   microsecond: 0,
   minute: 0,
   month: 0,
   second: 0,
   year: 0
 }}

Would you consider giving this a try to see if it meets your needs? Its a bit of a special case for now since the Calendar operators (ie plus/3, minus/3) only work with dates (not times). But for formatting purposes I think it should fulfil your original request.

jarrodmoldrich commented 3 years ago

This works perfect, @kipcole9 . I think maybe having a constructor that takes a scalar and a time unit term like Cldr.Calendar.Duration.new -3600, :seconds would be ideal, but it's easy enough to work around this. I'm seeing that ultimately I'll need to do something custom anyway for some of my use cases, so the functionality as it is is more than fine. Thanks again :)

kipcole9 commented 3 years ago

@jarrodmoldrich, the functions in Cldr.Calendar are calendar-centric. That is, they keep integrity with how years, months, days are composed for each calendar. As you know, these vary a lot and converting seconds to a calendar date/datetime isn't possible without further information. So while creating time-based durations works because time is (generally, but not always - hebrew time is not the same as UT) typically composed of standard units.

There is another option that might work for you and that is Cldr.Unit.decompose/2. This allows you to take a unit and decompose it into other units. Using your example:

Cldr.Unit.decompose Cldr.Unit.new!(-10000, :second), [:hour, :minute, :second]
[#Cldr.Unit<:hour, -1>]

iex> d = Cldr.Unit.decompose Cldr.Unit.new!(-10000, :second), [:hour, :minute, :second]
[#Cldr.Unit<:hour, -2>, #Cldr.Unit<:minute, -46>, #Cldr.Unit<:second, -40>]

iex> Cldr.Unit.to_string d
{:ok, "-2 hours, -46 minutes, and -40 seconds"}

Perhaps the opens up some more possibilities?

jarrodmoldrich commented 3 years ago

I see the impedance mismatch between what I'm trying to achieve and what the library is meant for. Decompose is probably a much better way of going about it.

kipcole9 commented 3 years ago

I think that in general it does fit what I understand of your use case. I will close this issue but don't hesitate to open another one as required.