HipByte / Flow

Cross-platform libraries for RubyMotion
BSD 2-Clause "Simplified" License
141 stars 29 forks source link

Idea : Date library #1

Open bmichotte opened 8 years ago

bmichotte commented 8 years ago

It's always a mess to play with dates. Especially with NSDate on iOS and Calendar stuff on Android.

Based on the api from Sugarcube, we could add

Fixnum

Time.now + 3.days
Time.now - 1.year

Time

Time.from_hash(hour: 5, day: 8, month: 10)
jjaffeux commented 8 years ago

Hi @bmichotte,

I fully agree that we need some cross platform common time/date/datetime API.

I'm stil not sure about the strategy however :

We would need some popular dates usage and see what we would need to write on android and ios, and see how we could write it cross platform, on top of my mind:

jjaffeux commented 8 years ago

To clarify my point of view, I don't like first solution that much, we provide the same thing people expect from ruby or we provide a full new API, but I don't like that mix of ruby and other things. it's not good for discoverability and people don't expect it. I would never be sure how Time would respond.

bmichotte commented 8 years ago

I like the idea of something like moment.js more than Time, Date, and Datetime classes.

And I think we can mix it with helpers for fixnum. And helper for String also can be cool... like '2015-12-14'.to_date

lrz commented 8 years ago

A date library would be useful, I'm not sure if monkey-patching Fixnum is a good idea for this API, at the same time we already add Object#to_json...

lrz commented 8 years ago

Maybe a first approach would be to look at date.rb in CRuby's standard library and see what would make sense to be implemented?

bmichotte commented 8 years ago

@lrz The same thing could be achieved using something like my_time.add(days: 5) or something like that, but I think my_time + 5.days is really visual and clear

jjaffeux commented 8 years ago

I think the safest best is working on an independent API, we can always pollute std lib methods with call to this independent API after, but creating a cross platform independent API should be our first move IMO.

If it's not clear, what I would do :

module Flow
  class Moment
    def self.parse(string)
      # xxx
    end

    def to_date
      # xxx
    end
  end
end

class String
  def to_date
    Flow::Moment.parse(self).to_date
  end
end
jjaffeux commented 8 years ago

Or we could just get rid of all the Time/Date/DateTime/Calendar/Whatever... and just introduce the concept of 'moment', and pollute string and other classes with ".to_moment"

That would be very nice, easy to use and would still allow other libs/code from people to work without issues.

jjaffeux commented 8 years ago

What I propose:

bmichotte commented 8 years ago

:+1: (very constructive answer, but I'm totally for this idea, so don't have much to say)

jjaffeux commented 8 years ago

@bmichotte If @lrz agrees with this idea, I can work on 1 and 2, and then we can split on 3

jjaffeux commented 8 years ago

Basic top level API proposal

Mostly inspired by momentjs http://momentjs.com/docs. We might add more sugar in the future, but I would like the base API to be most easy and straightforward possible, and I think mommentjs totally achieve this goal. My only grief being Timezone management.


# Default works like Time.now (we could also add Moment.now later)
Moment.new

# Can parse a string
Moment.new("1995-12-25")

# Can parse a string with format
Moment.new("1995-12-25", "YYYY-MM-DD")

# Can parse an array
Moment.new([2010, 1, 14, 15, 25, 50, 125])

# Can parse a hash
Moment.new({ hour:15, minute:10 })

# Can format any Moment
Moment.new.format("MM-DD")

# Can substract and add time
Moment.new.subtract(1, 'day')
Moment.new.add(1, 'day')
Moment.new.subtract(2, 'hours')
Moment.new.add(4, 'hours')

# Support for start_of and end_of
Moment.new.start_of('year')
Moment.new.end_of('month')

# Most of Moment methods return an instance of Moment, chainable
Moment.new.minutes(0).seconds(0).milliseconds(0)

# Comparison
Moment.new <= Moment.new
Moment.new >= Moment.new
Moment.new == Moment.new

# Difference
a = Moment.new
b = Moment.new
a.diff(b, 'seconds') # 0

# Timezones support
Moment.zone = "Europe/Paris"
Moment.new.utc_offset # 60
a = Moment.new
a.zone = "America/Los_Angeles"
a.utc_offset #-480

b = Moment.new
b.zone = "America/Los_Angeles"
b.zone # "America/Los_Angeles"
bmichotte commented 8 years ago

What about

# properties
a = Moment.new
a.year | a.year=
a.month | a.month=
# and so on

# Difference
(a - b).days # .day ?
=> 8

# Compatibility
t = Moment.new.to_date
t.class
=> Time

Moment.new(Time.now)
bmichotte commented 8 years ago

a = Moment.new
a.zone = :america_los_angeles
lrz commented 8 years ago

Why not using Date instead of Moment?

bmichotte commented 8 years ago

Why not both

(sorry, too tempting)

jjaffeux commented 8 years ago

@bmichotte @lrz

jjaffeux commented 8 years ago

One thing I forgot to add in my proposal, I would like the library to support similar method than https://github.com/travisjeffery/timecop to facilitate testing, we would need it to test the lib itself anyways :)

Moment.new("1995-12-25").freeze do |moment|
  Moment.new.to_s.should == "xxxxxxx"
end
jjaffeux commented 8 years ago

See this for example, is very confusing, I would like to avoid this :

t = Moment.new.to_date
t.class
=> Time

I'm working with a Moment, sending to_date, and I get a Time. WAT?

lrz commented 8 years ago

But Date doesn't exist in RubyMotion, neither in CRuby core (it's defined in date.rb).

lrz commented 8 years ago

Here is a session from CRuby that isn't confusing?

lrz-mba:motion-game lrz$ irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> o = Date.parse('2016-02-19')
=> #<Date: 2016-02-19 ((2457438j,0s,0n),+0s,2299161j)>
irb(main):003:0> o.to_time
=> 2016-02-19 00:00:00 +0100
irb(main):004:0> o.to_time.class
=> Time
bmichotte commented 8 years ago

@jjaffeux

bmichotte commented 8 years ago

or to_date => java.util.Date | NSDate, to_time.class => Time

jjaffeux commented 8 years ago

Yes Date is not in core CRuby however, people are used to use it, and if we provide it, people will think they are working with the std library CRuby Date.rb So they will be writing ruby, using a Dateobject that is different than the Date object they are used to. That's why I proposed a totally different name, to avoid any confusion.

jjaffeux commented 8 years ago

Moreover, ruby has 3 different concepts : Time, DateTime and Date, which are IMO very confusing to new comers, that's why I don't think we could accurately express those 3 concepts in a Date object, which would loose sense and be too different from regular Date.rb. Moment is a more generic name avoiding those issues.

jjaffeux commented 8 years ago

Using https://github.com/Watson1978/motion-date ./cc @Watson1978 @lrz should be taking into consideration.

Pros

Cons

Other questions

jjaffeux commented 8 years ago

@lrz @bmichotte any comment on this ?

bmichotte commented 8 years ago

IMHO, I prefer either Date with all CRuby methods, either Moment with a "different" api

andrewhavens commented 8 years ago

A few thoughts...I agree that date parsing is not as simple as it should be (in Ruby, RubyMotion, Rails, iOS, etc). Switching between Date, Time, and DateTime objects is confusing. I'm currently using two libraries to do something simple:

For the first task, I'm using sugarcube-nsdate to add nsdate method to the string class. This was the only way that I could find to parse my iso8601 date correctly. For the second task, I use the DateTools library for adding the timeAgoSinceNow method. This results in the following code:

date_string_or_nil.to_s.nsdate.timeAgoSinceNow

Not very pretty.

I would rather not have to rely on a handful of large libraries that I only use a small part of. I find that many support libraries end up trying to support every possible use case. However, it seems like a small library (or componentized so you could require just the features you want) which achieve 90% (or even 10%) of the most common use cases would be more helpful than trying to replicate all of Sugarcube, ActiveSupport, etc.

I would be happy with something like this:

Moment.parse(date_string_or_nil).format(:time_ago)
# or even...
Motion::Flow::DateTime.parse_iso8601(date_string_or_nil).to_s_in_time_ago_format

Ultimately, all I want is a relatively intuitive API (no need for monkey patching) from a small library with well documented examples.