Ruby is great. Or maybe Rails is what's greatā¦ after 4 years, I still don't quite know where the line lies ?
One of my favorite features is this:
> "next sunday".to_date
=> Sun, 26 Mar 2017
```
```ruby
> 2.2.0 :003 > "last sunday".to_date
=> Sun, 26 Mar 2017
Wait a minuteā¦ next sunday
and last sunday
are the same date? O.o
By the way, it's Tuesday right now. Next Sunday is April 2nd, and last Sunday was March 26th.
Ok, that's weird. But what if we change Date.now
to Monday? Let's try that.
2.2.0 :008 > Timecop.freeze("April 3rd, 2017".to_datetime) do
2.2.0 :009 > Date.today
2.2.0 :010?> end
=> Sun, 02 Apr 2017
?
PS: Timecop is a Ruby library meant for testing. It lets you specify the value of "now"
April 2nd is not April 3rdā¦ timezones? Gotta be timezones.
2.2.0 :011 > Timecop.freeze("April 3rd, 2017".to_datetime) do
2.2.0 :012 > DateTime.now
2.2.0 :013?> end
=> Sun, 02 Apr 2017 17:00:00 -0700
Ah. to_datetime
without a time of day sets time to midnight in UTC. Then when you call DateTime.now
, you get your local timezone. Completely intuitive!
2.2.0 :014 > Timecop.freeze("April 3rd, 2017 1pm".to_datetime) do
2.2.0 :015 > DateTime.now
2.2.0 :016?> end
=> Mon, 03 Apr 2017 07:00:00 -0700
Okay, that's better. Does "next Sunday"
work now?
2.2.0 :020 > Timecop.freeze("Mar 27, 2017 1pm".to_datetime) do
2.2.0 :021 > "next sunday".to_datetime
2.2.0 :022?> end
=> Sun, 26 Mar 2017 00:00:00 +0000
Nope. Doesn't even react to current time being mocked. That's intuitive.
Turns out, if you want to find next Sunday, you have to use Date.today.sunday
. Becauseā¦ I don't know why.
2.2.0 :023 > Date.today.sunday
=> Sun, 02 Apr 2017
2.2.0 :024 > Timecop.freeze("April 3rd, 2017 1pm".to_datetime) do
2.2.0 :025 > Date.today.sunday
2.2.0 :026?> end
=> Sun, 09 Apr 2017
Excellent! That worked. Today, we get next Sunday. On Monday, we get next next Sunday. Just what you'd expect!
Now watch this.
2.2.0 :027 > Timecop.freeze("April 3rd, 2017".to_datetime) do
2.2.0 :028 > Date.today.sunday
2.2.0 :029?> end
=> Sun, 02 Apr 2017
Timezones? Timezones.
Even though time freezing happens in UTC, time getting happens in your local timezone. So when they misalign, Monday becomes Sunday, and obviously if today is Sunday and you want to find next Sunday, that's today. D'oh.
?
Oh, the fun I had figuring that one out! Almost pushed to production without realizing these quirks existed! ?
But why? Let's find out.
Here is the source for to_datetime
: link
# File activesupport/lib/active_support/core_ext/string/conversions.rb, line 53
def to_datetime
::DateTime.parse(self, false) unless blank?
end
Great, we know that to_datetime
is a Rails method, not a Ruby method. Itās part of the ActiveSupport
gem, which looks like a bag of all utility methods you might find useful in a Rails project.
Neat.
The to_datetime
method looks like magic. My understanding is that it's part of the string class, which makes it defined on every string. Once called, it passes itself ā self
ā into Datetime.parse
.
So what is DateTime
?
It looks like a part of core extensions. Its source is split into five files, so I have no idea what's what. Curiously, there is also DateAndTime
. They do not seem to do the same things. ?
And I'm having trouble finding the parse
method. It shows up in 107 rails source files and none of them is DateTime
. How strangeā¦ maybe it's in Ruby after all?
It is!
Deep inside Ruby's C source code, the DateTime
class is defined. You'd think Ruby used the bootstrapping compiler approach and was written in Ruby, but nope. It looks like it's done in C. Who knewā¦ ĀÆ*(ć)*/ĀÆ
To the best of my understanding, this is the code that becomes .parse
once Ruby is compiled and looks like Ruby.
static VALUE
datetime_s_parse(int argc, VALUE *argv, VALUE klass)
{
VALUE str, comp, sg;
rb_scan_args(argc, argv, "03", &str, &comp, &sg);
switch (argc) {
case 0:
str = rb_str_new2("-4712-01-01T00:00:00+00:00");
case 1:
comp = Qtrue;
case 2:
sg = INT2FIX(DEFAULT_SG);
}
{
VALUE argv2[2], hash;
argv2[0] = str;
argv2[1] = comp;
hash = date_s__parse(2, argv2, klass);
return dt_new_by_frags(klass, hash, sg);
}
}
I don't know what all this code does, but it seems to eventually use good old strptime
and guesses which format to use. That means it doesn't really care if we say this Sunday
or next Sunday
. It sees only Sunday
.
And when strptime
sees Sunday
, it returns the current's week's Sunday, which in Ruby land is the 0th day of the week. I don't know why Ruby thinks Sunday is the first day of the week, but it seems like large parts of the world think that way.
shrug
According to the Hebrew calendars and traditional Christian calendars, Sunday is the first day of the week. However, according to the International Organization for Standardization ISO 8601, Sunday is the seventh and last day of the week.
So?
That was fun. I don't think we learned anything useful. But it's good to know that even though Ruby and Rails can handle fancy relative time strings, they actually ignore the most informative part.
Oops. ??āāļø
Continue reading about Time is funny in Ruby
Semantically similar articles hand-picked by GPT-4
- A day is not 60*60*24 seconds long
- Saving time in UTC doesn't work and offsets aren't enough
- Javascript's lack of strftime
- I kicked myself in the balls
- More messing with time: Deduping messages between iOS and JavaScript
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below š
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails š on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. š"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help š swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers š ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ā¤ļø