How does Ruby's ActiveSupport "between?" function handle impossible times? - ruby

I'm using the ActiveSupport between? to determine if a time is between 8 AM and 4:45 PM, and I accidentally was putting a later time as the first argument and the earlier time as the second, like so:
# variable `time` gets set up here
unless time.between?(Time.local(time.year, time.month, time.day, 8), Time.local(time.year, time.month, time.day, 4, 45))
# short circuit
end
# do stuff
I know this is wrong since I should have it use 16, 45 instead of 4, 45 so I was able to fix the bug, but I couldn't find documentation on what happens in this case, since it wasn't throwing any errors which was strange to me. It seemed that it was always returning true, since the inside of this unless statement never executed during testing, which was bizarre, unexpected behavior to me. I would assume it would either throw some error or always return false, since technically no time can be between 8 AM and 4:45 AM.
So how does the between? function actually handle times that are "impossible", e.g. the later time comes before the earlier time?

It always returns false, not true. You can try yourself with every number for hours:
my_time = Time.local(2022, 05, 8, 6)
eight = Time.local(2022, 05, 8, 8)
four = Time.local(2022, 05, 8, 4)
puts my_time.between?(eight, four)
and will always return false

Related

Why isn't there a endWith operator in Rx?

Having such a convenient method like .startWith it would make sense to have his oposite, .endWith, which makes the observable yield a value whenever it gets completed.
I have come up with this solution, but is there anything better? This thing gets a bit hard to read for what it is.
source.concat(Rx.Observable.just(lastValue))
There is in RxJS6 (no clue when it was added to be honest)
Documentation:
https://rxjs-dev.firebaseapp.com/api/operators/endWith
Source: https://github.com/ReactiveX/rxjs/blob/df0ea7c78767c07a6ed839608af5a7bb4cefbde5/src/internal/operators/endWith.ts
Also, defaultIfEmpty() only emits a value if the observable CLOSES without emitting a value. It's a subtle, yet not so subtle distinction. It may have the same effect as endWith() in limited situations.
Example of endWith():
const source = of(1, 2, 3, 4, 5);
const example = source.pipe(
takeWhile(val => val != 4),
endWith(4));
Emits:
[1, 2, 3, 4]
Also I'm noticing that the https://learnrxjs.io website is increasingly out of date, and currently doesn't show this operator.
Why did I need it?
I was looking for the ability to emit false until a condition became true, but never go back to false. So slightly similar to debouncing, but not quite.

How to get precision (number of digits past decimal) from a Ruby BigDecimal object?

Given the following expression for a new BigDecimal object:
b = BigDecimal.new("3.3")
How can I get the precision that has been defined for it? I would like to know a method that will return 1, as there is 1 digit after the decimal. I'm asking this because b.precision or b.digits don't work.
Thanks to Stefan, a method name for dealing with such information is BigDecimal#precs. Given that a BigDecimal object comes from a database, I don't know the precision of that database object. I have tried the following, but it does not seem useful for my situation.
b = BigDecimal.new(3.14, 2)
b.precs
=> [18, 27]
How can I retrieve the 2 information/argument?
In Ruby 2.2.2 (and, I'm guessing, in prior versions), you can't get
back the precision that was given to BigDecimal::new. That's
because it is used in some computations; only the result of those
computations is stored. This doc comment is a clue:
The actual number of significant digits used in computation is
usually larger than the specified number.
Let's look at the source to see what's going on. BigDecimal_new
extracts the parameters, does some limit and type checking, and calls
VpAlloc. mf holds the digits argument to BigDecimal::new:
return VpAlloc(mf, RSTRING_PTR(iniValue));
In VpAlloc, mf gets
renamed to mx:
VpAlloc(size_t mx, const char *szVal)
The very first thing MxAlloc does is to round mx (the precision) up to
the nearest multiple of BASE_FIG:
mx = (mx + BASE_FIG - 1) / BASE_FIG; /* Determine allocation unit. */
if (mx == 0) ++mx;
BASE_FIG is equivalent to RMPD_COMPONENT_FIGURES, which has a platform
dependent value of either 38, 19, 9, 4, or 2.
There are further computations with mx before it is stored in the
BigDecimal being created, but we can already see that the original
argument passed to ::new is destroyed and not recoverable.

Comparison using Ruby's .days.ago seems to use reverse logic

Can anyone help me make sense of this, please?
I am getting a very weird behaviour (reverse logic), when I am trying to use the following code.
require 'active_support/all'
c = {
id: 5,
years_of_experience: 4,
github_points: 293,
languages: ['C', 'Ruby', 'Python', 'Clojure'],
date_applied: 5.days.ago.to_date,
age: 26
}
c["date_applied"] > 15.days.ago.to_date - #works
c["date_applied"] < 15.days.ago.to_date - #doesnt work
c["date_applied"] gives a date value stored in a hash.
The latter makes more logical sense, but the first returns the right answer.
The code's behavior is correct, but I think I understand the confusion.
You're reading
c["date_applied"] > 15.days.ago
as:
Is the date applied more than 15 days ago?
and
c["date_applied"] < 15.days.ago
as:
Is the date applied less than 15 days ago?
and it's giving you the reverse of the answer you expect, right?
If that's the case, you should take a moment to understand how time comparisons operate. When you type date1 > date2, you're actually saying,
If I plot date1 and date2 on a number line with time increasing from left to right,
is date1 to the right of date2?
This is the same as when you type 2 > 1. It means,
If I plot 1 and 2 on a number line with the numbers increasing from left to right,
is 2 to the right of 1?
Given that this is how time comparisons operate, let's reexamine your code.
require 'active_support/all'
c = { date_applied: 5.days.ago.to_date }
c[:date_applied] > 15.days.ago.to_date
Correctly interpreted, this says
Is the date 5 days ago further rightward on a left-to-right timeline than the date 15 days ago?
and the answer is yes, or true.
If, on the other hand, you were to incorrectly interpret this as
Is 5 days ago more than 15 days ago?
you would get (or expect to get) the mistaken answer of no, or false.
The correct way to think about the task in English is to reframe the question of
Is date d more than n days ago?
and instead think of it as
Is date d earlier than the date n days ago?
and the correct code becomes apparent:
d.to_date < n.days.ago.to_date
If I understood your question correctly, this should explain it.
irb ## ruby-1.9.3-p448
require 'active_support/time'
c = {
id: 5,
years_of_experience: 4,
github_points: 293,
languages: ['C', 'Ruby', 'Python', 'Clojure'],
date_applied: 5.days.ago.to_date,
age: 26
}
(c[:date_applied] > 15.days.ago.to_date) - #true
(c[:date_applied] < 15.days.ago.to_date) - #false
###or you can try it by adding your own private methods###
class Fixnum
def days
self * 60 * 60 * 24 # we store seconds in a day
end
def ago
Time.now - self
end
end

Find the closest date from string

I'm trying to convert strings like "Sep 11, Oct 31, Feb 28" into DateTime instances as part of a screen-scraper. Using DateTime.parse() works fine apart from when the data goes across years, and it naively (and probably correctly) returns a date in the current year.
For example the following test case.
test "dateA convert next year" do
TimeService.stubs(:now).returns(Time.new(2013, 12, 30, 9, 30))
assert_equal(Date.new(2014, 1, 2), Extraction.dateA("Jan 2"))
end
I updated my method to look at what would be date with year + 1, and return the closest to 'now' - this works fine. However it feels a bit ugly, and I'm looking for a more elegant solution.
def Extraction.dateA(content)
return_value = DateTime.parse(content)
next_year = return_value.change(:year => return_value.year + 1)
now = TimeService.now.to_i
if (next_year.to_i - now).abs < (return_value.to_i - now).abs then
return_value = next_year
end
return_value
end
TimeService.now is just a utility to return current time to help with stubbing.
Excuse my ruby, I'm new to it.
I think this works as intended, allowing for closest date in previous year as well:
module Extraction
def Extraction.date_a(content)
parsed_date = DateTime.parse(content)
now = DateTime.now
dates = [ parsed_date, parsed_date.next_year, parsed_date.prev_year ]
dates.min_by { |d| ( d - now ).abs }
end
end
A few points:
Changed method name to date_a, just a Ruby convention that differs from Java.
I made use of some built-in methods next_year and prev_year on DateTime
I used a time difference metric and selected date with the minimal value of it from three candidate dates (this is what min_by does). This is simpler code than the conditional switching, especially with three dates to consider.
I forgot about min_by originally, I don't use it often, but it's a very good fit for this problem.
Note there is a pathological case - "29 Feb". If it appears correctly in text, by its nature it will define which year is valid, and it won't parse if current year is e.g. 2015.

Get the count of elements in a ruby range made of Time objects

How would I be able to get the size or count of a range made up of Time objects?
Something that would achieve the same result as my pseudo Ruby code, which doesn't work:
((Time.now.end_of_day - 31.days)..(Time.now.end_of_day - 1.day)).size == 30
currently doing the above gives an error:
NoMethodError: undefined method `size' for 2012-05-18 23:59:59 -0400..2012-06-17 23:59:59 -0400:Range
and trying to turn it into array (range).to_a :
can't iterate from Time
update
Interesting, Just tried to do
((Date.today.end_of_day - 31.days)..(Date.today.end_of_day - 1.day)).count
Users/.../gems/ruby/1.9.1/gems/activesupport-3.0.15/lib/active_support/time_with_zone.rb:322: warning: Time#succ is obsolete; use time + 1
However
((Date.today - 31.days)..(Date.today - 1.day)).count == 31
I would be willing to settle for that?
Also ((Date.today - 31.days)..(Date.yesterday)).count == 31
update 2
On the other hand, taking Mu's hint we can do:
(((Time.now.end_of_day - 31.days)..(Time.now.end_of_day - 1.day)).first.to_date..((Time.now.end_of_day - 31.days)..(Time.now.end_of_day - 1.day)).last.to_date).count == 31
There's no such method as Range#size, try Range#count (as suggested by Andrew Marshall), though it still won't work for a range of Time objects.
If you want to perform number-of-days computations, you're better off using Date objects, either by instantiating them directly (Date.today - 31, for example), or by calling #to_date on your Time objects.
Date objects can be used for iteration too:
((Date.today - 2)..(Date.today)).to_a
=> [#<Date: 2012-06-17 ((2456096j,0s,0n),+0s,2299161j)>,
#<Date: 2012-06-18 ((2456097j,0s,0n),+0s,2299161j)>,
#<Date: 2012-06-19 ((2456098j,0s,0n),+0s,2299161j)>]
((Date.today - 2)..(Date.today)).map(&:to_s)
=> ["2012-06-17", "2012-06-18", "2012-06-19"]
It's because a size for a date range doesn't make senseā€”it doesn't know if you want to view it as days, minutes, seconds, months, or something else. The reason the error mentions iterating is that in order to determine the size of the range, it must know how to iterate over them so that it may count the number of elements.
Since what you want is the difference in days, just do that:
date_one = Time.now.end_of_day - 31.days
date_two = Time.now.end_of_day - 1.day
((date_one - date_two) / 1.day).abs
#=> 30.0
You must divide by 1.day since a difference of Times returns seconds.
To have any chance of your code working you should wrap everything before .size in parentheses.
Instead of using a range, maybe you can just subtract one time object from another?
I know you make ranges out of Date objects so you could convert to that.

Resources